Repository navigation
Commit 8d79bbd
feat(spec): draft provenance records MCP build-door writes and the OAuth client that made them (#27220)
Fixes #27176
## What changes
A pending draft's per-key provenance (`draftChanges`, served on the
strict `?state=draft` read) can now say that a key was written by an AI
agent outside the console, through a deployment's MCP build door, and
which OAuth client that agent was.
**Spec (`@objectstack/spec/api`)**
- `MetadataAuthoringSurfaceSchema` gains `mcp`. All the describes that
list the set (`draftChanges[].by`, the save's `surface`, the docblocks)
now name it.
- New `MetadataAuthoringClientSchema` (`{ id, name? }`): the OAuth
client the person consented to. `id` is the verified token's client id
(opaque). `name` is its registered `client_name`, and it is never the id
standing in for a name.
- `draftChanges[]` gains an optional `client`, present only beside `by:
'mcp'`.
- `SaveMetaItemRequestSchema` gains an optional `client`. It is stated
only by the MCP build door, in-process, from the token it verified. No
HTTP door reads it.
- Pinned as isomorphic in `type-alias-convention.pin.test.ts` (798 →
799). Generated artifacts are regenerated: api-surface, export-origins,
declaration-map, authorable-surface and json-schema manifest, reference
docs, and the strictness ledger.
**Writer (`@objectstack/metadata-protocol`)**
- A draft save with `surface: 'mcp'` stamps `by: 'mcp'` and its `client`
on every key path it changes (`draftChangesClient` on the repository
`put`, beside `draftChangesBy`). `actor` stays the consenting person.
- A `client` beside any other surface (or with none), or of the wrong
shape, is refused `400 INVALID_REQUEST` before anything is read or
written. `authoringClientOf` is the one shape check, shared by the save
and the stored read.
- `storedDraftChanges` reads `mcp` entries with or without a client. A
stored client beside a non-`mcp` surface, or a malformed one, is
malformed, like any other bad entry.
**HTTP doors (`@objectstack/rest`, runtime dispatcher)**
- `?surface=mcp` is refused `400 VALIDATION_ERROR` with its own
sentence, as `?surface=copilot` is. Both doors share
`metaSaveRequestOptions`.
**Console**
- `@object-ui/data-objectstack`: `MetadataClientSaveOptions.surface`
excludes `mcp` as it excludes `copilot`. The type now has a
`@ts-expect-error` pin for `mcp`.
- Studio properties (`StudioDraftMark`): the draft chip and the 来源 line
mark `mcp` writes as they mark the copilot's. The line names the client:
「由 MCP 客户端「Claude」在 2 分钟前作为草稿加入」 / "Added as a draft by the MCP client
"Claude" 2 min ago". A client with no registered name reads as an
unnamed MCP client, never by its id.
- `copilotDraftMarkOf` → `agentDraftMarkOf`. Its marks carry `by` and
`client`.
- The source keys take an `{agent}` hole, with three new
`engine.studio.draftMark.agent.*` rows (en + zh, the designer table's
two languages).
- Changes drawer (`DraftChangesPanel`): an entry's drill-in already
sends the strict draft read. It now reads that read's `draftChanges`
too, and lists which AI tools drafted part of the item: the copilot
once, and each MCP client once. Copy is in all ten locale packs
(`preview.changes.draftedBy*`).
- Provenance stays off the `_drafts` row, as #24298 ruled. The drawer
reads it from the per-item draft read it already sends.
## Why
cloud#3302 opened the MCP build door: an agent with a `metadata:write`
grant drafts metadata through the same authoring tools the copilot uses.
Its writes were recorded as `api`, so a person reviewing the draft could
not tell an agent's edit from any other caller's, or tell which agent it
was. The cloud half (the door passing `surface: 'mcp'` and the verified
client from its principal into the save) follows once the pin carries
this change.
## Verification
- `pnpm --filter @objectstack/spec build && pnpm --filter
@objectstack/spec check:generated --fix`: the five stale artifacts were
regenerated and re-checked green. `check:test-typecheck` is OK after
updating `meta-item-response-shapes.test.ts` (the surface set and the
client type are pinned; the client shape is refused at
`draftChanges[n].client`).
- spec: `meta-item-response-shapes.test.ts` and
`type-alias-convention.pin.test.ts` pass (26).
- metadata-protocol: `tsc --noEmit` is clean. `draft-changes.test.ts`
and `protocol.packageless-save-address.test.ts` pass (103). The new
cases cover the client stamp and keep rule, the unnamed client, an `mcp`
save without a client, and refusal of a client beside another surface or
of the wrong shape, with nothing written.
- rest: `rest-server-meta-door-closed-sets.test.ts` passes (29), with
`?surface=mcp` refused. runtime:
`meta-save-preconditions-parity.test.ts` passes (23), with
`?surface=mcp` refused on the dispatcher too.
- ui:
- `tsc --noEmit` is clean for `app-shell` and `data-objectstack`.
- `StudioDraftMark.test.ts`, the new
`DraftChangesPanel.agentSource-27176.test.tsx`, and
`draftChanges-12725.test.ts` pass.
- Every `DraftChangesPanel.*` suite and every copilot-mark suite (52
files) passes.
- `check:i18n-keys`, `check:i18n-drift`, `check:i18n-designer-parity`
and `check:i18n-dead-keys` show nothing for the new keys.
- `pnpm lint` on the touched files: 0 errors.
Clause-②: yes (widening)
Metadata-protocol: added
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes.
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QUs3BthV9Hg8gmo8r35XcV)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 9dec054 commit 8d79bbd
43 files changed
Lines changed: 746 additions & 134 deletions
File tree
- .changeset
- content/docs/references
- api
- docs/audits/2026-07-unknown-key-strictness-ledger.counts
- packages
- metadata-protocol/src
- rest/src
- runtime/src/domains
- spec
- api-surface
- authorable-surface
- declaration-map
- export-origins
- json-schema.manifest
- src
- api
- ui/packages
- app-shell/src
- preview
- __tests__
- views/studio-design
- data-objectstack/src
- i18n/src
- __tests__
- locales
- namespaces/studio
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
24 | | - | |
| 24 | + | |
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
37 | | - | |
| 37 | + | |
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
| |||
63 | 63 | | |
64 | 64 | | |
65 | 65 | | |
66 | | - | |
| 66 | + | |
67 | 67 | | |
68 | 68 | | |
69 | 69 | | |
| |||
91 | 91 | | |
92 | 92 | | |
93 | 93 | | |
94 | | - | |
| 94 | + | |
95 | 95 | | |
96 | 96 | | |
97 | 97 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
22 | | - | |
| 22 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
13 | | - | |
| 13 | + | |
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| |||
105 | 105 | | |
106 | 106 | | |
107 | 107 | | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
108 | 138 | | |
109 | 139 | | |
110 | 140 | | |
| |||
115 | 145 | | |
116 | 146 | | |
117 | 147 | | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
118 | 154 | | |
119 | 155 | | |
120 | 156 | | |
| |||
124 | 160 | | |
125 | 161 | | |
126 | 162 | | |
| 163 | + | |
| 164 | + | |
127 | 165 | | |
128 | 166 | | |
129 | 167 | | |
| |||
0 commit comments