chore: replace em dashes with hyphens, add no-em-dash rule to CLAUDE.md
This commit is contained in:
@@ -54,7 +54,7 @@ agent/internal/updates/
|
||||
updates.go # PackageUpdate; CheckAvailable/ApplyAll declared once
|
||||
updates_linux.go # existing detectPM, checkApt/DnfYum/Pacman/Zypper/Apk, ApplyAll
|
||||
updates_windows.go # Windows Update COM, driven through PowerShell
|
||||
updates_other.go # //go:build !linux && !windows — no-ops
|
||||
updates_other.go # //go:build !linux && !windows - no-ops
|
||||
```
|
||||
|
||||
`updates_other.go` carries the build constraint for the same reason
|
||||
@@ -116,7 +116,7 @@ A new field `reboot_required` on `InventoryReport`, added to
|
||||
|
||||
It travels on the inventory report rather than the update report because it is a
|
||||
host property like the kernel version, and it is set on the **static** snapshot
|
||||
only — every 15 minutes rather than every 30 seconds. A host rebooted by hand
|
||||
only - every 15 minutes rather than every 30 seconds. A host rebooted by hand
|
||||
clears the flag in a quarter of an hour instead of showing it for up to a full
|
||||
one, and the detection costs a PowerShell process on Windows, which is not
|
||||
something to spawn twice a minute forever.
|
||||
@@ -142,7 +142,7 @@ Both platforms set it, since parity is free here:
|
||||
|
||||
```
|
||||
agent/internal/workloads/
|
||||
workloads.go # Result, Collect, Hash — Collect calls collectUnits
|
||||
workloads.go # Result, Collect, Hash - Collect calls collectUnits
|
||||
docker.go # unchanged, shared: shells to the docker binary
|
||||
systemd_linux.go # was systemd.go
|
||||
services_windows.go # new: Win32_Service collection
|
||||
@@ -167,7 +167,7 @@ not responding, and running nothing.
|
||||
|
||||
### Collecting Windows services
|
||||
|
||||
`Get-CimInstance Win32_Service` converted to JSON — not `Get-Service`, which
|
||||
`Get-CimInstance Win32_Service` converted to JSON - not `Get-Service`, which
|
||||
exposes neither `PathName` nor `StartMode`, and the filter needs both.
|
||||
|
||||
A service is reported when its executable does **not** resolve under
|
||||
@@ -193,7 +193,7 @@ Field mapping:
|
||||
`Kind: "unit"` and the existing `systemd_ok` / `systemd_error` fields are reused
|
||||
rather than a `service` kind and `services_ok` fields being added. That would
|
||||
cost a proto change, both pb copies, the server model, the service layer and the
|
||||
web client, and would teach every existing consumer a second kind — to describe
|
||||
web client, and would teach every existing consumer a second kind - to describe
|
||||
the same thing. The naming is corrected where it is read, in the UI, which knows
|
||||
the server's OS.
|
||||
|
||||
@@ -206,8 +206,8 @@ The protected set stays computed and enforced agent-side, as it is on Linux: the
|
||||
control plane may name a target, but the agent decides what it will do to
|
||||
itself.
|
||||
|
||||
On Windows the protected workload is the `VantageAgent` service — the NSSM
|
||||
service name written by `installer/setup.ps1` — matched case-insensitively,
|
||||
On Windows the protected workload is the `VantageAgent` service - the NSSM
|
||||
service name written by `installer/setup.ps1` - matched case-insensitively,
|
||||
because Windows service names are. `detectOwnContainer` and its
|
||||
`/proc/self/cgroup` read move to `control_linux.go`; the Windows build returns
|
||||
no own-container ID.
|
||||
@@ -257,13 +257,13 @@ rather than on `os_type`. `os_type` is stored and serialised but unread by
|
||||
the two come to disagree. `WorkloadList` takes the result as a prop, since it
|
||||
receives only a `serverId`:
|
||||
|
||||
1. `web/components/workloads/WorkloadList.tsx` — takes an `isWindows` prop from
|
||||
1. `web/components/workloads/WorkloadList.tsx` - takes an `isWindows` prop from
|
||||
the server detail page, and the systemd status lines become
|
||||
platform-worded. On Windows the error line reads "Windows services could not
|
||||
be read" and the "systemd is not in use on this server" line is not rendered
|
||||
at all. The empty-state line drops "on Linux only". The Docker lines are
|
||||
unchanged.
|
||||
2. Server detail — a `Reboot required` pill beside the update count when the
|
||||
2. Server detail - a `Reboot required` pill beside the update count when the
|
||||
flag is set, placed with the update panel because that is what caused it.
|
||||
3. The Updates panel's Windows copy describes a list of KB articles rather than
|
||||
package upgrades, since `current_version` is empty on that platform.
|
||||
@@ -272,7 +272,7 @@ receives only a `serverId`:
|
||||
|
||||
The Windows collectors are, in substance, parsers of PowerShell output. Parsing
|
||||
is separated from invocation and table-tested against captured real output. The
|
||||
`agent` module has no tests at all today, so these are the first — they live
|
||||
`agent` module has no tests at all today, so these are the first - they live
|
||||
beside the parsers as ordinary `_test.go` files, run with `go test ./...` from
|
||||
`agent/`, and need no new dependency:
|
||||
|
||||
@@ -292,6 +292,6 @@ service start/stop/restart, a protected refusal on `VantageAgent`, and logs on
|
||||
both a chatty service and a silent one.
|
||||
|
||||
`GOOS=windows go build ./...` and `GOOS=linux go build ./...` both belong in the
|
||||
implementation plan as explicit steps — a build-tag split is exactly the change
|
||||
implementation plan as explicit steps - a build-tag split is exactly the change
|
||||
that compiles on the machine you are sitting at and nowhere else. CI already
|
||||
cross-builds the agent on release, so no workflow change is needed.
|
||||
|
||||
@@ -36,7 +36,7 @@ instance:
|
||||
|
||||
Three things do not exist: any concept of a page, any operator-authored
|
||||
incident, and any unauthenticated read path. The third is the constraint that
|
||||
shapes the rest — every route under `/api` carries `auth.Middleware`,
|
||||
shapes the rest - every route under `/api` carries `auth.Middleware`,
|
||||
`RequireScopes`, `RateLimitTokens` and `RequireActiveLicense` by virtue of where
|
||||
it is mounted, and `AssertScopeMapComplete` fails boot on an `/api` route with
|
||||
no scope entry.
|
||||
@@ -77,7 +77,7 @@ pages; a random identifier would be unguessable and unmemorable in equal
|
||||
measure.
|
||||
|
||||
`published` exists so a page can be composed before anyone sees it. An
|
||||
unpublished page answers the same 404 as a page that does not exist — a
|
||||
unpublished page answers the same 404 as a page that does not exist - a
|
||||
distinct 403 would confirm it exists.
|
||||
|
||||
Sections are page-local and unrelated to `Monitor.Group`, which is a display
|
||||
@@ -128,7 +128,7 @@ end and duration.
|
||||
refused` lives.
|
||||
|
||||
Copying auto-incidents into `status_incidents` would be a second writer for the
|
||||
same fact, arriving by a different route with its own opportunity to disagree —
|
||||
same fact, arriving by a different route with its own opportunity to disagree -
|
||||
the same argument that keeps `RefreshWorkloadsCmd` from returning workloads
|
||||
inline.
|
||||
|
||||
@@ -155,7 +155,7 @@ is private by default rather than published by accident.
|
||||
|
||||
What the snapshot contains, per entry: display name, current status, uptime
|
||||
percentage over the last 90 days, and a 90-day history bar of one cell per day.
|
||||
A cell is up, down, under maintenance, or no-data — `no-data` for days before
|
||||
A cell is up, down, under maintenance, or no-data - `no-data` for days before
|
||||
the monitor existed, which is a distinct thing from a day it was down. No
|
||||
latency, no addresses, no failure text.
|
||||
|
||||
@@ -167,7 +167,7 @@ GET /public/status/:pageId
|
||||
|
||||
Mounted on the gin root, not under `apiGroup`. Putting it under `/api` would
|
||||
require exempting it from authentication, scope enforcement, token rate
|
||||
limiting and the licence gate — four holes, each one something a later change
|
||||
limiting and the licence gate - four holes, each one something a later change
|
||||
can widen. Outside `/api` it needs none of them.
|
||||
|
||||
The instance is resolved from the request host through `auth.InstanceFromHost`.
|
||||
@@ -208,7 +208,7 @@ cache separately and two visitors would see different states during an incident.
|
||||
### Rate limit
|
||||
|
||||
Per client address, one-minute fixed window, 120 requests, 429 with
|
||||
`Retry-After` — the same shape as `RateLimitTokens`, including its most
|
||||
`Retry-After` - the same shape as `RateLimitTokens`, including its most
|
||||
important property: **when Redis is unavailable, allow rather than deny.** A
|
||||
status page must survive the outage it exists to report.
|
||||
|
||||
@@ -240,7 +240,7 @@ are required, not optional: `AssertScopeMapComplete` fails boot on an `/api`
|
||||
route with no scope entry, which is exactly the safeguard working.
|
||||
|
||||
Handlers need `@…` annotations and `openapi.json` must be regenerated and
|
||||
committed — `server-deploy.yml` runs `git diff --exit-code` against the
|
||||
committed - `server-deploy.yml` runs `git diff --exit-code` against the
|
||||
committed copy, so a handler whose annotation drifted fails CI.
|
||||
|
||||
## Frontend
|
||||
@@ -252,7 +252,7 @@ against the Go endpoint, with a client refresh every 60 seconds.
|
||||
`web/next.config.ts` gains a `/public/:path*` rewrite so that client refresh
|
||||
reaches the server.
|
||||
|
||||
The page stays dark, like the rest of `web/`, and carries no hex values — the
|
||||
The page stays dark, like the rest of `web/`, and carries no hex values - the
|
||||
existing token palette covers every state it needs.
|
||||
|
||||
Authoring UI at `/status-pages` inside `(app)`, in the **Instance** sidebar
|
||||
@@ -277,7 +277,7 @@ boundary made executable:
|
||||
|
||||
## Migration and rollout
|
||||
|
||||
No migration is needed — both collections are new and absent means empty. Index
|
||||
No migration is needed - both collections are new and absent means empty. Index
|
||||
builders follow the `EnsureWorkflowIndexes` precedent and warn rather than being
|
||||
fatal: a missing index on a small collection degrades to a scan, which is no
|
||||
reason to refuse to serve the fleet.
|
||||
|
||||
@@ -6,8 +6,8 @@ Status: approved, ready for implementation planning
|
||||
## Problem
|
||||
|
||||
Vantage has no backup story. A self-hosted deployment holds its entire state in
|
||||
MongoDB and encrypts the sensitive half of it — SSH private keys, key
|
||||
passphrases, vault secrets, OIDC client secrets, RDP and VNC credentials — with
|
||||
MongoDB and encrypts the sensitive half of it - SSH private keys, key
|
||||
passphrases, vault secrets, OIDC client secrets, RDP and VNC credentials - with
|
||||
AES-256-GCM under a single 32-byte key supplied as the `KEY_ENCRYPTION_KEY`
|
||||
environment variable.
|
||||
|
||||
@@ -48,7 +48,7 @@ such assertion available, so a second hand-maintained registry would drift
|
||||
silently and the first symptom would be a restore missing a collection nobody
|
||||
noticed was added.
|
||||
|
||||
`--exclude` accepts collection names for the volume-heavy ones —
|
||||
`--exclude` accepts collection names for the volume-heavy ones -
|
||||
`workflow_log_lines`, `monitor_samples`, `audit_logs`. Whatever is excluded is
|
||||
recorded in the manifest, so an archive can never claim to be complete when it
|
||||
is not.
|
||||
@@ -72,7 +72,7 @@ into the server binary.
|
||||
cobra command tree and nothing else. A separate module rather than a package
|
||||
under `shared/` because adding cobra to `shared/go.mod` would put cobra and
|
||||
pflag into the module graph of `server`, `admin` and `sitesvc`, none of which
|
||||
use them. Binaries are unaffected — Go links only what is imported — but three
|
||||
use them. Binaries are unaffected - Go links only what is imported - but three
|
||||
`go.sum` files would grow and three CI builds would fetch a dependency they do
|
||||
not need. `agent/` is already a separate module for the same reason.
|
||||
|
||||
@@ -80,7 +80,7 @@ The tool imports nothing from `server/`. No `db.Col()`, no `services`, no config
|
||||
loader, and it never dials the REST or gRPC API. It needs only network reach to
|
||||
MongoDB, a database name, and `KEY_ENCRYPTION_KEY` in its own environment. This
|
||||
is what lets it run against a control plane that is down, half-migrated, or was
|
||||
deleted an hour ago — which is the only condition under which anyone runs a
|
||||
deleted an hour ago - which is the only condition under which anyone runs a
|
||||
restore.
|
||||
|
||||
### Dump implementation
|
||||
@@ -149,7 +149,7 @@ environment:
|
||||
- Fingerprints differ: refuse, printing both.
|
||||
- Archive has a fingerprint, environment has no key: refuse.
|
||||
- `--ignore-key-mismatch`: proceed, having first printed exactly which
|
||||
collections hold ciphertext that will be undecryptable — `keys`, `secrets`,
|
||||
collections hold ciphertext that will be undecryptable - `keys`, `secrets`,
|
||||
`auth_providers`, `console_sessions`, `settings`.
|
||||
|
||||
### Restore semantics
|
||||
@@ -169,7 +169,7 @@ The order is fixed:
|
||||
|
||||
Restore is not idempotent, and says so. A second run without `--force` is
|
||||
refused because step 4 now finds data. A restore interrupted during step 5
|
||||
leaves a partial database that the next run refuses to touch — correct, because
|
||||
leaves a partial database that the next run refuses to touch - correct, because
|
||||
the alternative is a silent merge. There are no merge or upsert semantics at
|
||||
all: merging two control planes reconciles nothing and produces a fleet that
|
||||
half works, and upserting by `_id` resurrects rows deleted since the backup,
|
||||
@@ -178,8 +178,8 @@ costume of a convenience.
|
||||
|
||||
Index replay is fatal per collection when a unique index fails to build, and a
|
||||
warning when a non-unique one does. A unique index that cannot be created means
|
||||
the restored data violates it, and the unique indexes here — `(instance_id,
|
||||
email)`, instance slug, settings instance, the ESO token hash — are
|
||||
the restored data violates it, and the unique indexes here - `(instance_id,
|
||||
email)`, instance slug, settings instance, the ESO token hash - are
|
||||
tenant-isolation properties rather than optimisations. The failure names the
|
||||
offending index.
|
||||
|
||||
@@ -187,7 +187,7 @@ offending index.
|
||||
|
||||
Restore under `--force` requires a typed confirmation when stdin is a TTY.
|
||||
|
||||
When stdin is not a TTY — a Kubernetes Job, a CI step, a cron entry — the
|
||||
When stdin is not a TTY - a Kubernetes Job, a CI step, a cron entry - the
|
||||
confirmation comes from `--confirm-db <name>`, whose value must equal the
|
||||
resolved target database name or restore refuses. Naming the database in the
|
||||
argument means a copy-pasted restore command carries its intended target with
|
||||
@@ -219,13 +219,13 @@ system; this tool reads no configuration file, and pulling it in to call
|
||||
`os.Getenv` would make the largest dependency in the binary the one doing the
|
||||
smallest job.
|
||||
|
||||
`inspect` prints the manifest — when the archive was made, by what version,
|
||||
`inspect` prints the manifest - when the archive was made, by what version,
|
||||
which collections it holds, how many documents, what was excluded, and the key
|
||||
fingerprint — and contacts no database. It is what an operator runs to find out
|
||||
fingerprint - and contacts no database. It is what an operator runs to find out
|
||||
whether an archive they have found is worth anything.
|
||||
|
||||
`verify` adds a live check: whether the archive's fingerprint matches the key in
|
||||
the current environment, and — when `--mongo-uri` is given — whether that key
|
||||
the current environment, and - when `--mongo-uri` is given - whether that key
|
||||
actually decrypts the target database. The second half is a probe: read one
|
||||
ciphertext field from `secrets`, `keys` or `auth_providers` and attempt to open
|
||||
it. A fingerprint comparison proves two archives agree; only a probe proves the
|
||||
@@ -235,8 +235,8 @@ the documentation recommends running it on a schedule.
|
||||
|
||||
The probe needs AES-256-GCM open, which today lives in
|
||||
`server/internal/services/crypto.go` and cannot be imported from another module.
|
||||
Rather than copy it — the exact hazard `CLAUDE.md` names around mirrored token
|
||||
blocks and `web/lib/targets.ts` — the primitives move to a new `shared/cryptobox`
|
||||
Rather than copy it - the exact hazard `CLAUDE.md` names around mirrored token
|
||||
blocks and `web/lib/targets.ts` - the primitives move to a new `shared/cryptobox`
|
||||
package, and `services/crypto.go` becomes a thin delegation that keeps its
|
||||
existing unexported function names and its `KEY_ENCRYPTION_KEY` lookup. One
|
||||
implementation of the cipher, two callers.
|
||||
@@ -255,11 +255,11 @@ Kubernetes, or neither.
|
||||
`linux/arm64`, `darwin/arm64` and `windows/amd64` with `CGO_ENABLED=0`, writes
|
||||
`checksums.txt`, and creates a Gitea release.
|
||||
|
||||
**Container image.** `vantagectl/Dockerfile` — the repo's convention is a
|
||||
**Container image.** `vantagectl/Dockerfile` - the repo's convention is a
|
||||
Dockerfile per module built from the repository root, because every Go module
|
||||
depends on `shared` through a replace directive — produces a `scratch`
|
||||
depends on `shared` through a replace directive - produces a `scratch`
|
||||
image holding the static binary and an explicitly copied `/tmp`, which the
|
||||
archive is staged in before compression — the same omission that silently
|
||||
archive is staged in before compression - the same omission that silently
|
||||
disabled `vulnsched` on a scratch image. Pushed by `server-deploy.yml` as an
|
||||
eighth image.
|
||||
|
||||
@@ -281,8 +281,8 @@ Restore in Kubernetes is the same image run as a one-shot `Job`. The chart ships
|
||||
no restore manifest: a restore is an operator decision with a confirmation
|
||||
attached to it, and must never be something a `helm upgrade` can trigger.
|
||||
|
||||
`server-deploy.yml`'s rebuild trigger table gains a `vantagectl` row —
|
||||
`vantagectl/`, `shared/`, `go.work` — which makes `shared/` fan out to four Go
|
||||
`server-deploy.yml`'s rebuild trigger table gains a `vantagectl` row -
|
||||
`vantagectl/`, `shared/`, `go.work` - which makes `shared/` fan out to four Go
|
||||
images rather than three. That table is already called out in `CLAUDE.md` as a
|
||||
place where a missed entry ships a stale image.
|
||||
|
||||
@@ -294,8 +294,8 @@ variable that skips when unset.
|
||||
|
||||
Required cases:
|
||||
|
||||
- Round trip: seed one document of every awkward BSON type — `ObjectId`,
|
||||
`Decimal128`, `DateTime`, binary, null, nested arrays — back up, restore into
|
||||
- Round trip: seed one document of every awkward BSON type - `ObjectId`,
|
||||
`Decimal128`, `DateTime`, binary, null, nested arrays - back up, restore into
|
||||
a second database, assert byte-equal BSON.
|
||||
- A single corrupted byte in a `.bson` member causes restore to refuse before
|
||||
writing anything.
|
||||
@@ -317,7 +317,7 @@ Fingerprint computation is a pure function and is tested without a database.
|
||||
- A restore drill: restore into a scratch database and run `verify`, because an
|
||||
untested backup is a hypothesis.
|
||||
- What is not covered: Redis sessions, the vulnerability database (re-pulled
|
||||
automatically), and agent state on managed servers — agents reconnect on their
|
||||
automatically), and agent state on managed servers - agents reconnect on their
|
||||
own and `servers.agent_token_hash` is in the backup, so no re-enrolment is
|
||||
needed.
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ Date: 2026-09-08
|
||||
## Goal
|
||||
|
||||
Expose Vantage to LLM agents as a first-class tool surface, so that an agent
|
||||
acting for a user can answer questions about the fleet and — when explicitly
|
||||
permitted — act on it, under the same identity, scopes, licence and audit trail
|
||||
acting for a user can answer questions about the fleet and - when explicitly
|
||||
permitted - act on it, under the same identity, scopes, licence and audit trail
|
||||
as every other API caller.
|
||||
|
||||
Concretely: a user mints a Vantage API token, points Claude (or any MCP client)
|
||||
@@ -67,8 +67,8 @@ unchanged.
|
||||
The design principle throughout: **MCP is a presentation layer over the service
|
||||
layer, and introduces no new authority.** It calls the same service functions
|
||||
the REST handlers call, and every decision about who may do what is made by
|
||||
machinery that already exists. Where MCP needs something new — tag-scoped
|
||||
tokens — that thing is built as a general capability of the API, not as an MCP
|
||||
machinery that already exists. Where MCP needs something new - tag-scoped
|
||||
tokens - that thing is built as a general capability of the API, not as an MCP
|
||||
feature.
|
||||
|
||||
Three independent gates gate every tool call, and all three must pass:
|
||||
@@ -84,16 +84,16 @@ Three independent gates gate every tool call, and all three must pass:
|
||||
them, `ScopeSatisfied` already implements write-implies-read, and the token
|
||||
creation UI advertises them without modification.
|
||||
|
||||
A bespoke `mcp:use` scope was rejected. The vocabulary is deliberately uniform —
|
||||
every resource has exactly `:read` and `:write` — and one special-cased action
|
||||
A bespoke `mcp:use` scope was rejected. The vocabulary is deliberately uniform -
|
||||
every resource has exactly `:read` and `:write` - and one special-cased action
|
||||
verb would be the first exception in a table whose value is having none.
|
||||
|
||||
The meanings:
|
||||
|
||||
- **`mcp:read`** — the token may reach `/api/mcp` at all. A token without it is
|
||||
- **`mcp:read`** - the token may reach `/api/mcp` at all. A token without it is
|
||||
not an agent token, whatever else it holds. Read tools are listed and callable
|
||||
subject to their own resource scopes.
|
||||
- **`mcp:write`** — write tools are listed and callable, again subject to their
|
||||
- **`mcp:write`** - write tools are listed and callable, again subject to their
|
||||
own resource scopes. Implied by the existing rule when a token holds
|
||||
`mcp:write`, so `mcp:read` need not be requested separately.
|
||||
|
||||
@@ -120,7 +120,7 @@ TagSelector map[string]string `bson:"tag_selector,omitempty" json:"tag_selector,
|
||||
|
||||
Validated on creation by the existing `services.ValidateTags`, so a token
|
||||
selector cannot express a tag a server could never carry. A caller may only
|
||||
create a token whose selector is at least as narrow as their own — the same
|
||||
create a token whose selector is at least as narrow as their own - the same
|
||||
rule `ScopeSatisfied` already enforces for scopes, applied to tags.
|
||||
|
||||
`auth.Session` carries `TagSelector`, populated in `sessionFromToken` and always
|
||||
@@ -166,13 +166,13 @@ console and OIDC being opt-in per customer.
|
||||
|
||||
Enforced in three places:
|
||||
|
||||
1. **Route** — `RequireFeature(license.FeatureMCP)` on the `/api/mcp` group,
|
||||
1. **Route** - `RequireFeature(license.FeatureMCP)` on the `/api/mcp` group,
|
||||
answering the standard `feature_unavailable` 403.
|
||||
2. **Token minting** — creating a token with `mcp:read` or `mcp:write` is
|
||||
2. **Token minting** - creating a token with `mcp:read` or `mcp:write` is
|
||||
refused without the feature. A licence downgrade should not leave live agent
|
||||
credentials that fail confusingly mid-conversation, and the same
|
||||
guard-at-source thinking is already in `services/packages.go`.
|
||||
3. **UI** — the token form's MCP scopes and the MCP connection panel are hidden
|
||||
3. **UI** - the token form's MCP scopes and the MCP connection panel are hidden
|
||||
when the licence does not grant it, as console is today.
|
||||
|
||||
Existing tokens are unaffected: absent the new scopes, no token can reach the
|
||||
@@ -185,7 +185,7 @@ returns either a JSON response or an SSE stream. The transport is stateless
|
||||
rather than session-resuming precisely so each request can stand alone and
|
||||
sit behind ordinary request middleware with no special-casing, and that
|
||||
stateless mode leaves no session for a server-to-client stream to resume
|
||||
against — so `GET /api/mcp` is registered but answers the protocol's 405
|
||||
against - so `GET /api/mcp` is registered but answers the protocol's 405
|
||||
rather than opening a stream. A client probing the endpoint therefore learns
|
||||
"POST-only here" rather than seeing a bare 404, which is what the MCP spec
|
||||
expects from a server that does not offer the GET/SSE leg.
|
||||
@@ -197,7 +197,7 @@ duplicate auth and double every request's cost for no benefit.
|
||||
|
||||
`routeScopes` gains `POST /api/mcp` and `GET /api/mcp`, both mapped to
|
||||
`mcp:read`, satisfying `AssertScopeMapComplete`. Per-tool scope enforcement
|
||||
happens inside the handler, because one route serves many operations — this is
|
||||
happens inside the handler, because one route serves many operations - this is
|
||||
the first route where the route-level scope is a floor rather than the whole
|
||||
answer, and the map entry's comment says so.
|
||||
|
||||
@@ -275,7 +275,7 @@ someone, so they carry extra rules on top of the ordinary write gates:
|
||||
tell at a glance what a model wrote. Workflows and monitors get the same
|
||||
treatment through their audit event rather than a new field.
|
||||
- **Script validation.** `create_step` runs the same parse and scan the existing
|
||||
step-create route runs (`services.CreateStep` already does this) — an agent
|
||||
step-create route runs (`services.CreateStep` already does this) - an agent
|
||||
gets no laxer a path than the UI.
|
||||
|
||||
## Audit
|
||||
@@ -292,14 +292,14 @@ instance".
|
||||
Event type `mcp.tool_call`; actor is the token name, as REST token actions
|
||||
already record; detail is the tool name, a compact argument summary, and the
|
||||
number of servers affected. Failures record `mcp.tool_denied` with the gate that
|
||||
refused — licence, MCP scope, resource scope, or tag selector — which is what
|
||||
refused - licence, MCP scope, resource scope, or tag selector - which is what
|
||||
turns "the agent said it couldn't" into a diagnosable event.
|
||||
|
||||
Arguments are summarised, never dumped verbatim: an argument could carry
|
||||
arbitrary text from a model, and the audit log is read by humans in a UI.
|
||||
|
||||
A chatty agent can produce many events. If that becomes a problem the throttle
|
||||
pattern already used for `token.expired_use` applies, but v1 records everything —
|
||||
pattern already used for `token.expired_use` applies, but v1 records everything -
|
||||
under-recording a new and sensitive surface is the worse failure.
|
||||
|
||||
## Errors
|
||||
@@ -310,7 +310,7 @@ workflows:write". The agent must be able to read the refusal and adapt or tell
|
||||
its user, and a transport-level failure is invisible to the model.
|
||||
|
||||
Out-of-scope hosts are not-found, matching the REST rule. Upstream service
|
||||
errors are summarised — a raw Mongo error is neither useful to a model nor safe
|
||||
errors are summarised - a raw Mongo error is neither useful to a model nor safe
|
||||
to expose.
|
||||
|
||||
## HQ, catalogue and Paddle
|
||||
@@ -321,7 +321,7 @@ to expose.
|
||||
more `KindFeature` row at `ScopeShared`, sold by every paid plan at one price.
|
||||
`SeedCatalogue` is `$setOnInsert` only, so the row appears empty on deploy and
|
||||
staff-entered price IDs are never blanked. The comment naming the row count
|
||||
("nine rows") is updated — the file explicitly asks the next person to keep that
|
||||
("nine rows") is updated - the file explicitly asks the next person to keep that
|
||||
number deliberate.
|
||||
|
||||
`catalogue.LineItems` needs no change: a `KindFeature` row the customer selected
|
||||
@@ -349,7 +349,7 @@ One product, two prices, created in the sandbox environment first:
|
||||
|
||||
| Field | Value |
|
||||
| --- | --- |
|
||||
| Product name | Vantage — Agent Access (MCP) |
|
||||
| Product name | Vantage - Agent Access (MCP) |
|
||||
| Description | AI agent access to a Vantage instance over the Model Context Protocol |
|
||||
| Tax category | `standard` |
|
||||
| Currency | GBP |
|
||||
@@ -362,7 +362,7 @@ rows use.
|
||||
Creation runs through the connected `paddle-sandbox` MCP server during
|
||||
implementation, with the exact payload confirmed before each call. The resulting
|
||||
price IDs are recorded in the catalogue row's `price_ids.sandbox` map through
|
||||
the existing staff pricing page — not by a migration, because that page is the
|
||||
the existing staff pricing page - not by a migration, because that page is the
|
||||
only place price IDs are meant to be entered and a migration writing them would
|
||||
be a second source of truth.
|
||||
|
||||
@@ -379,8 +379,8 @@ the licence grants the feature:
|
||||
- A short client configuration snippet, again copyable.
|
||||
- A link to the docs page.
|
||||
|
||||
The token creation form gains the two MCP scopes in its scope list — no special
|
||||
UI, they are ordinary scopes — and a **tag restriction** field, which is shown
|
||||
The token creation form gains the two MCP scopes in its scope list - no special
|
||||
UI, they are ordinary scopes - and a **tag restriction** field, which is shown
|
||||
for every token regardless of licence because tag scoping is not gated. The
|
||||
field offers the tag keys and values already in use on servers, as the workflow
|
||||
target selector does.
|
||||
@@ -418,7 +418,7 @@ is the thing that must stay correct as tools are added:
|
||||
- **Audit.** A successful call and a refused call each write exactly one event
|
||||
of the expected type.
|
||||
- **Response size.** `list_servers` over a seeded fleet stays under a stated
|
||||
byte budget — a regression here degrades every agent interaction and is
|
||||
byte budget - a regression here degrades every agent interaction and is
|
||||
otherwise invisible.
|
||||
|
||||
`services/statuspages_test.go` is the style model.
|
||||
|
||||
Reference in New Issue
Block a user