Repository navigation
feat(spec): draft provenance records MCP build-door writes and the OAuth client that made them - #27220
Conversation
…nance Claude-Session: https://claude.ai/code/session_01QUs3BthV9Hg8gmo8r35XcV Co-Authored-By: Claude <noreply@anthropic.com>
…provenance, and render them in Studio Claude-Session: https://claude.ai/code/session_01QUs3BthV9Hg8gmo8r35XcV Co-Authored-By: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 3 package(s): 12 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 5 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 152 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin aa497a2e3109443cfe0a44332bf2d34e659089c6 && git checkout aa497a2e3109443cfe0a44332bf2d34e659089c6
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin cdb949a288e1bd3350f09527c5f2169eb4752c23 532985f37f9aa4460526df86699de0a4f7cbde91 && git checkout -B drift-repro cdb949a288e1bd3350f09527c5f2169eb4752c23 && git merge --no-ff 532985f37f9aa4460526df86699de0a4f7cbde91
node scripts/docs-audit/affected-docs.mjs --json cdb949a288e1bd3350f09527c5f2169eb4752c23
|
Contract reviewServed-tier: ① Derived judgmentsCheck-runs on the head at read time (2026-10-28): 46 runs;
② Semver levelChangeset ③ Boundary flags
④ Design judgment
Holds:
Tried to break it:
Should-fix (not blocking): the spec and the writer disagree on an unknown member. No must-fix. Implemented-by: VERDICT: PASS |
…lient by it The writer's authoringClientOf refused an unknown member that the published schema stripped; the schema is now .strict() and the writer parses through it, so the contract and the enforcement give one verdict. Its describe no longer names itself. Claude-Session: https://claude.ai/code/session_01QUs3BthV9Hg8gmo8r35XcV Co-Authored-By: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Delta review. This head is ① Derived judgmentsCheck-runs on
② Semver levelUnchanged from ③ Boundary flags
④ Design judgment
No must-fix, no should-fix. Implemented-by: VERDICT: PASS |
|
Seat review (
Landing via queue once every check on this head is green, not just the required subset. The cloud half (the MCP door passes |
Claude-Session: https://claude.ai/code/session_01QUs3BthV9Hg8gmo8r35XcV Co-Authored-By: Claude <noreply@anthropic.com>
|
Seat 2 review: ACCEPT carried to head The only change since the ACCEPT at Landing via queue. |
Fixes #27176
What changes
A pending draft's per-key provenance (
draftChanges, served on the strict?state=draftread) 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)MetadataAuthoringSurfaceSchemagainsmcp. All the describes that list the set (draftChanges[].by, the save'ssurface, the docblocks) now name it.MetadataAuthoringClientSchema({ id, name? }): the OAuth client the person consented to.idis the verified token's client id (opaque).nameis its registeredclient_name, and it is never the id standing in for a name.draftChanges[]gains an optionalclient, present only besideby: 'mcp'.SaveMetaItemRequestSchemagains an optionalclient. It is stated only by the MCP build door, in-process, from the token it verified. No HTTP door reads it.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)surface: 'mcp'stampsby: 'mcp'and itsclienton every key path it changes (draftChangesClienton the repositoryput, besidedraftChangesBy).actorstays the consenting person.clientbeside any other surface (or with none), or of the wrong shape, is refused400 INVALID_REQUESTbefore anything is read or written.authoringClientOfis the one shape check, shared by the save and the stored read.storedDraftChangesreadsmcpentries with or without a client. A stored client beside a non-mcpsurface, or a malformed one, is malformed, like any other bad entry.HTTP doors (
@objectstack/rest, runtime dispatcher)?surface=mcpis refused400 VALIDATION_ERRORwith its own sentence, as?surface=copilotis. Both doors sharemetaSaveRequestOptions.Console
@object-ui/data-objectstack:MetadataClientSaveOptions.surfaceexcludesmcpas it excludescopilot. The type now has a@ts-expect-errorpin formcp.StudioDraftMark): the draft chip and the 来源 line markmcpwrites 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 carrybyandclient.{agent}hole, with three newengine.studio.draftMark.agent.*rows (en + zh, the designer table's two languages).DraftChangesPanel): an entry's drill-in already sends the strict draft read. It now reads that read'sdraftChangestoo, 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*)._draftsrow, as metadata-protocol / spec: a draft carries no per-field provenance (which surface changed which key, and when), so Studio cannot mark a copilot's draft change with the ruled 草稿 chip and 来源 line #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:writegrant drafts metadata through the same authoring tools the copilot uses. Its writes were recorded asapi, 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 passingsurface: '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-typecheckis OK after updatingmeta-item-response-shapes.test.ts(the surface set and the client type are pinned; the client shape is refused atdraftChanges[n].client).meta-item-response-shapes.test.tsandtype-alias-convention.pin.test.tspass (26).tsc --noEmitis clean.draft-changes.test.tsandprotocol.packageless-save-address.test.tspass (103). The new cases cover the client stamp and keep rule, the unnamed client, anmcpsave without a client, and refusal of a client beside another surface or of the wrong shape, with nothing written.rest-server-meta-door-closed-sets.test.tspasses (29), with?surface=mcprefused. runtime:meta-save-preconditions-parity.test.tspasses (23), with?surface=mcprefused on the dispatcher too.tsc --noEmitis clean forapp-shellanddata-objectstack.StudioDraftMark.test.ts, the newDraftChangesPanel.agentSource-27176.test.tsx, anddraftChanges-12725.test.tspass.DraftChangesPanel.*suite and every copilot-mark suite (52 files) passes.check:i18n-keys,check:i18n-drift,check:i18n-designer-parityandcheck:i18n-dead-keysshow nothing for the new keys.pnpm linton the touched files: 0 errors.Clause-②: yes (widening)
Metadata-protocol: added
Human Minimum
packages/specholds no runtime logic.pnpm lintpasses.main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code