Skip to content

fix(runtime): the package doors record the signed-in caller in metadata history, never a body actor - #28502

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-a0v6t5
Nov 1, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-a0v6t5

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Sam · project thread
Fixes #28414

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:丢弃草稿、撤销提交、回滚、收编孤儿元数据、复制这五个包操作,以及发布草稿时重新保存应用的那一步,现在都把登录用户记进元数据历史和包提交,不再采用请求体里填的 actor。
  • 为什么改:之前持有 manage_metadata 的人可以在请求体里填别人的名字,历史里就显示成别人改的;管理员靠这份记录决定回滚什么、找谁问。
  • 风险与回滚:请求体 actor 只是被忽略,不会被拒绝,老调用照常工作;系统调用(没有用户)跟以前一样不记 actor。没有数据迁移,回滚即 revert 本 PR。
  • 先看:packages/runtime/src/domains/packages.ts(六处调用改用 resolvePackageWriteActor,以及该函数头注释)、packages/runtime/src/domains/packages-write-actor-session.test.ts。
  • 两分钟验证:pnpm --filter @objectstack/runtime exec vitest run src/domains/packages-write-actor-session.test.ts,预期 17 个用例全绿。
  • 需要你做的:无,席位落地。

What changes

Runtime (packages/runtime/src/domains/packages.ts). Six protocol calls that passed body.actor straight through now record the user id of the request's authorized execution context, through resolvePackageWriteActor:

  • POST /packages/:id/discard-drafts → discardPackageDrafts
  • POST /packages/:id/commits/:commitId/revert → revertCommit
  • POST /packages/:id/rollback → rollbackToPackageCommit
  • POST /packages/:id/adopt-orphans → reassignOrphanedMetadata
  • POST /packages/:id/duplicate → duplicatePackage
  • the ADR-0045 app re-save inside POST /packages/:id/publish-drafts → saveMetaItem. It reuses the actor that door already resolves for its own publish.

A body actor is ignored, not refused. A context with no user id (a system caller) sends no actor, so the protocol records the actor-less null (#4556), as it did before for a body without one.

Shared helper owner: #28406 (PR #28422). N-06 landed first, so it owns resolvePackageWriteActor. This PR consumes it and adds no second helper. Its docblock no longer says "only publish-drafts reads it today"; it now lists every door that reads it.

SDK (packages/client/src/index.ts). The actor member of the inline opts object types on packages.discardDrafts, packages.revertCommit, packages.rollback, packages.adoptOrphans and packages.duplicate is marked @deprecated (the server ignores it). This matches what #28422 did for packages.publishDrafts. No type changes shape and no export is added or removed, hence Clause-②: no.

Spec (Wanted 3): no diff. No packages/spec request schema declares actor for these doors. package-lifecycle.zod.ts holds only response transcriptions; the route ledger names no requestSchema for them; PublishPackageDraftsRequestSchema (#28447) already strips actor. With nothing to drop or mark, there is no spec change, no regenerated artifact, and Metadata-protocol: none. The claim's Clause-②: yes line assumed a spec hunk that does not exist.

Tests

  • packages/runtime/src/domains/packages-write-actor-session.test.ts (new, 17 cases), driving dispatch() over a real SchemaRegistry and a recording protocol:
    • For each of the five doors: a body naming another user records the session user; a body naming no one records the session user; a system context sends no actor, with or without a body actor.
    • The publish-drafts app re-save: a body naming another user re-saves the app as the session user; a system context sends no actor.
  • Two existing pins flipped to the fixed behaviour:

Acceptance notes

  • Wanted 2, on-behalf-of callers: none found. The console sends {} or no body on all five doors (PackagesPage.tsx, commitHistory.ts, packages-io.ts). The SDK sends opts only when given, and no in-repo caller passes actor. The CLI calls none of these routes. ADR-0008 and ADR-0067 describe recorded_by holding an AI principal such as ai:claude for an AI apply's commit. That commit is written server-side by the protocol and is untouched here; only fixtures sent ai:claude through these HTTP doors.
  • Premise correction: the card's :2074 → saveMetaItem is not a seventh door. It is the app re-save inside the publish-drafts handler. feat(runtime,metadata-protocol,client,console): Studio publish takes a change note; item history names the person #28422 left it reading body.actor (its own conformance pin said so), and this PR closes it.
  • The merge commit 881d0d31 was committed with the repo hook path overridden by mistake, so .githooks/pre-commit did not run on it. Its only check, node scripts/check-regen-pending.mjs, was run by hand right after: exit 0. No generated artifact is in this diff.

Verification

Readings at head 4f64ab7c unless stated. fea2382b differs from it only by a test-file comment.

Package tests, typecheck, lint
  • pnpm --filter @objectstack/runtime test at 881d0d31: Test Files 1 failed / 496 passed, Tests 1 failed / 8069 passed / 19 skipped. The one failure was the feat(spec): declare the publish-drafts request body, so the change note is part of the published API contract #28447 pin above, flipped in fea2382b. That file plus the new file and http-dispatcher.test.ts at fea2382b: 3 files, 276 tests passed.
  • New file at 4f64ab7c: 17 passed.
  • pnpm --filter @objectstack/client test at 881d0d31: Test Files 53 passed, Tests 689 passed (lock VERDICT command-exit 0).
  • pnpm --filter @objectstack/runtime typecheck at fea2382b: VERDICT command-exit 0. pnpm --filter @objectstack/client typecheck at 881d0d31: VERDICT command-exit 0.
  • pnpm lint (repo-wide) at fea2382b: exit 0. eslint --no-inline-config on the changed test file at 4f64ab7c: exit 0.
Ablation (scripts/ablation-replace.mjs; each leg restores the `body.actor` pass-through at ONE call site; restored blob equals HEAD, git diff HEAD empty)

Tests import src/ relatively, so no dist rebuild sits between mutation and run. At fea2382b:

  • publish-drafts app re-save → 2 failed / 15 passed (its two cases)
  • discard-drafts → 3 failed / 14 passed. The first attempt's anchor matched twice (the publish call has the same two lines) and was refused before any write. The rerun with a three-line anchor landed.
  • commit revert → 3 failed / 14 passed
  • rollback → 3 failed / 14 passed
  • adopt-orphans → 3 failed / 14 passed
  • duplicate → 3 failed / 14 passed

Each leg reds exactly its own door's cases.

Dispatch gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 61 families. Every command was run with exit captured to disk before any pipe, against 881d0d31 (same 61 as the 6-path derivation at fea2382b).

  • Three gates exited 3 (PREREQUISITE NOT MET) on that pass: check-plugin-teardown-shape --self-test (shallow clone), spec check:skill-examples and check:dual-build-cjs-loads (no full build). After git fetch --unshallow and a full pnpm build (VERDICT command-exit 0) at fea2382b, all three exit 0.
  • Re-run at fea2382b because the last commits touched test files: check:cross-package-test-inputs, check:test-source-alias, check:engine-double-contract, check:nul-bytes, check:issue-citations, check:doc-authoring, check:type-check-coverage, check:type-check-debt, check:tier-file-adoption, check:objectql-double-limit, check:rest-log-spy-declared: all exit 0.
  • --ran reconciliation: 61 derived, 61 run, 0 NOT-MEASURED (derived zero), 0 UNRUN.

Generated by Claude Code

Discard-drafts, commit revert, rollback, adopt-orphans, duplicate and the
publish-drafts app unhide save now record the request's authenticated user
through one helper (packageWriteActor), the rule resolveMetaWriteActor
states for the single-item door. A body actor is ignored, not refused.

Claude-Session: https://claude.ai/code/session_01QUs3BvyWc2j7Vh5KBnJtam
Co-authored-by: Claude <noreply@anthropic.com>
…-a0v6t5

# Conflicts:
#	packages/runtime/src/domains/packages.ts
@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/client, @objectstack/runtime, touching 4 documentable anchor(s).

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/metadata-api.mdx (via /:commitId/revert (route, a path literal in a comment on a changed line))
  • content/docs/permissions/system-context.mdx (via handlePackagesRequest (symbol, a top-level function), /:commitId/revert (route, a path literal in a comment on a changed line))

⛔ 1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v18/18-1.mdx (via revertCommit (sdk, the bare tail of client method packages.revertCommit, bound to POST /packages/:id/commits/:commitId/revert))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 212 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 39 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json ea74ebf93f8b1b955b2b77327c3a34c677d4d751 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from bc9a2306c9bc09131c52c18316ef4cbf194c5f33 — the merge of head 4f64ab7c64284373fb9c8a5310e8c8abf805e8a0 into base ea74ebf93f8b1b955b2b77327c3a34c677d4d751, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin bc9a2306c9bc09131c52c18316ef4cbf194c5f33 && git checkout bc9a2306c9bc09131c52c18316ef4cbf194c5f33
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ea74ebf93f8b1b955b2b77327c3a34c677d4d751 4f64ab7c64284373fb9c8a5310e8c8abf805e8a0 && git checkout -B drift-repro ea74ebf93f8b1b955b2b77327c3a34c677d4d751 && git merge --no-ff 4f64ab7c64284373fb9c8a5310e8c8abf805e8a0

node scripts/docs-audit/affected-docs.mjs --json ea74ebf93f8b1b955b2b77327c3a34c677d4d751

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs ea74ebf93f8b1b955b2b77327c3a34c677d4d751 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-zhuang · session_01QUs3BtyH9zgxKzYrJiH18J

ACCEPT · PR #28502 · Head-sha 4f64ab7c64284373fb9c8a5310e8c8abf805e8a0 · 2026-11-01T03:15Z

维护者速读

  • 改了什么: 包的六个写入口(丢弃草稿、回退提交、回滚、认领孤儿元数据、复制包,以及发布草稿时的应用重存)在元数据历史和包提交里记登录用户,不再记请求体里的 actor;SDK 这五个方法的 actor 选项标为 deprecated。
  • 为什么改: 原来能改元数据的调用方可以在请求体里随便写一个名字,让改动看起来是别人做的;单条元数据入口早就只认登录身份。
  • 风险与回滚: 请求体的 actor 被忽略而不是拒绝,老客户端照常工作;无用户的系统调用仍记 null。回滚即 revert 本 PR。
  • 先看: packages/runtime/src/domains/packages.ts 的 resolvePackageWriteActor 注释与六处调用;新测试 packages-write-actor-session.test.ts。
  • 两分钟验证: pnpm --filter @objectstack/runtime exec vitest run src/domains/packages-write-actor-session.test.ts(17/17)。
  • 需要你做的: 无,席位落地。

席位意见:六处都改用 N-06 已落地的同一个解析函数,没有第二份;每个入口单独消融验证;可合。

席位核对

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 1, 2026 03:16
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 1, 2026 03:16
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 1, 2026
Merged via the queue into main with commit 58cb05d Nov 1, 2026
49 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-a0v6t5 branch November 1, 2026 03:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

1 participant