Skip to content

feat(cli): os secret orphans also reports sys_secret handles more than one holder names, or a column other than their own holds - #27792

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-3kh5oz
Oct 30, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-3kh5oz

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

Fixes #27721

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:os secret orphans 的报告模式在原有孤儿报告之外,新增一节「被命名的 handle」:列出被多于一个持有记录命名的 sys_secret handle(shared),以及持有它的列不是其 (namespace, key) 所指列的 handle(misplaced),带计数与每个持有者的对象/字段/记录;谓词更新造成的同列共享单独列出、不作判定。
  • 为什么改:非系统写入现在只能保留本记录自己的引用,但此前已存的值没有被重新检查;原报告只回答「谁都不引用的行」,回答不了「一个 handle 被几处引用、是否在自己的列里」。
  • 风险与回滚:只读、只报告;不进入 --delete 的删除计划,不修复任何东西,不打印任何密文或明文。--json 多出一个顶层键 holders(增量)。回滚即整 PR revert。
  • 先看:packages/cli/src/utils/sys-secret-holder-report.ts(分类规则与谓词更新的区分)、packages/cli/src/utils/secret-reference-union.ts(每个引用新增的结构化持有坐标)、packages/cli/src/commands/secret/orphans.ts(只在报告模式输出)。
  • 两分钟验证:pnpm --filter @objectstack/cli exec vitest run --project integration src/utils/sys-secret-holder-report.test.ts,预期 14 个用例全绿,其中谓词更新那条由真实引擎写出共享 handle。
  • 需要你做的:无,席位落地。

What changes

  • secret-reference-union.ts — every SecretReference now carries column: the holding object, field and record, and the (namespace, key) its producer records on a handle it mints for that column (null for the datasource binder, which records the pair its caller passes). Collected at the same read; the settings read now also selects id to name the record. The printable holder string is unchanged, and the module still contains no classification.
  • sys-secret-holder-report.ts (new, pure) — a report-only pass over the sys_secret snapshots and the union:
    • shared: a handle more than one holder record names;
    • misplaced: a handle at least one holder of which is not the column its (namespace, key) names;
    • sharedInOwnColumn, listed apart: every holder is an object-field row of the handle's own minting column. That is the shape a predicate update writes; stored rows cannot tell it from a reference copied between rows of the same column, so it is listed, not judged. A share across settings rows never gets this exemption: settings seals one handle per row write.
    • Datasource holders are not judged for placement (counted, with a note). An incomplete union is reported on what was enumerated and names the missing family.
  • os secret orphans — report mode prints the section and --json carries it as a new top-level holders key beside plan. --delete is untouched; the section never feeds the deletion plan. No new flag.
  • Docs — the os secret orphans section of content/docs/deployment/cli.mdx describes the section. Changeset: @objectstack/cli patch.

On the additive --json key: no shipped consumer parses the os secret orphans --json document. In this repository it is read only by tests (orphans.driver-contract.test.ts, data-commands.absent-database.integration.test.ts, both via toMatchObject on mode and plan), and the docs call it "the same report, machine-readable" without stating a closed shape.

Why

The orphan report answers which sys_secret rows nothing names. It did not answer whether a handle something does name is named by more than one holder, or held by a column other than the one its pair names, which is the question the engine's release asks before it removes a handle. Values stored before writes were limited to their own record's reference were never re-examined. Whether a repair path follows is a later decision; this PR builds none.

Tests

  • sys-secret-holder-report.test.ts (new, 14 cases): fixtures minted by the real producers (real ObjectQL write path including a real predicate update, real LocalCryptoProvider, real datasource binder), read back by the real union collector. Pins: a handle two settings rows name is shared with both holders' coordinates; a handle copied onto another column is shared and misplaced, naming which holder is out of place; a handle whose only holder is out of place is misplaced; the share the engine's predicate update wrote is in sharedInOwnColumn and in neither other class; a settings share is not exempted; datasource placement is not judged; counts per class; no plaintext and no cipher material anywhere in the serialized report (with an anti-vacuity check), and the store is unchanged; an incomplete union names its missing family.
  • secret-reference-union.test.ts: each family's reference carries its column coordinates.
  • orphans.driver-contract.test.ts (real SQLite, the command's own boot): the --json report lists a handle two seeded settings rows name under holders.shared, and the command's whole stdout carries none of the seeded cipher material. The command is run once and memoized across the two end-to-end cases.
  • Ablations (each restored, blob equal to HEAD and git diff HEAD empty, via scripts/ablation-replace.mjs):
    • own-column predicate forced false → 3 red (the predicate-update pin, the settings-not-exempt pin, counts);
    • placement forced in_place → 7 red (both misplaced pins, the cross-column share, the control, counts, the incomplete-union pin, the settings-not-exempt pin);
    • single-holder skip forced for every handle → 6 red (both shared pins, the predicate-update pin, counts, the incomplete-union pin, the settings-not-exempt pin);
    • holders dropped from the command's --json → the command-level pin red.

Acceptance notes

  • sys-secret-orphan-sweep.test.ts changes one fixture literal only (the new required column member); no assertion moved.
  • A reference that names a handle with no sys_secret row is not listed: the card asks about sys_secret handles, and a dangling reference is a different question.

Verification

All readings on head 2569bd1ae2 (this branch with main at 6d8372a8cd merged in), after pnpm install --frozen-lockfile && pnpm build.

  • node scripts/pm/dispatch-gates.mjs --ran — 93 derived, 93 run, 0 NOT-MEASURED, 0 UNRUN (exit 0). Every derived family exited 0.
  • pnpm --filter @objectstack/cli typecheck — exit 0 (tsc --noEmit and check:test-typecheck: OK, debt unchanged at 3 files / 28 errors / 6 pinned signatures).
  • Touched suites, vitest run --project integration: sys-secret-holder-report, secret-reference-union, sys-secret-orphan-sweep, orphans.guards, orphans.driver-contract, orphans.journal-mode, data-commands.absent-database — Test Files 7 passed, Tests 136 passed.
  • The whole unit project of @objectstack/cli ran once before the merge (head 48a9b65dd2): 349 of 351 files passed, 5055 tests passed. The two files that failed (published-subpath-console.pin, published-subpath-hook-body.pin) refused because packages/cli itself was not built yet; after the full build they pass on the final head (2 files, 29 tests).
  • ESLint with the repo config and --no-inline-config over the seven touched TypeScript files — exit 0. The repo-wide pnpm lint is CI's Lint & Repo Gates.
  • Dogfood battery: n/a — no ! in the title and no (narrowing) arm.
Derived gate families and exit codes (93)
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-adr-links.mjs :: exit 0
node scripts/check-adr-links.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-adoption.mjs :: exit 0
node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-doc-route-spelling.mjs --advisory :: exit 0
node scripts/check-doc-route-spelling.mjs --self-test :: exit 0
node scripts/check-docs-section-name.mjs :: exit 0
node scripts/check-docs-section-name.mjs --self-test :: exit 0
node scripts/check-dts-emitted.mjs --self-test :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-issue-citations.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0
node scripts/check-section-landing-index.mjs :: exit 0
node scripts/check-section-landing-index.mjs --self-test :: exit 0
node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0
node scripts/docs-audit/check-affected-docs.mjs :: exit 0
node scripts/docs-audit/check-drift-comment.mjs :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0
pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0
pnpm --filter @objectstack/spec run check:docs :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm --filter @objectstack/spec run check:empty-state :: exit 0
pnpm --filter @objectstack/spec run check:liveness :: exit 0
pnpm --filter @objectstack/spec run check:skill-examples :: exit 0
pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0
pnpm --filter @objectstack/spec run check:variant-docs :: exit 0
pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:cli-examples-parity :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:corpus-claim-drift :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:doc-anchors :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:doc-frontmatter :: exit 0
pnpm check:docs-audit-scope :: exit 0
pnpm check:docs-redirects :: exit 0
pnpm check:docs-single-h1 :: exit 0
pnpm check:docs-spec-enumerations :: exit 0
pnpm check:docs-transcript-drift :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:dts-closure :: exit 0
pnpm check:dual-build-cjs-loads :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:error-status-conformance :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:i18n :: exit 0
pnpm check:i18n-coverage :: exit 0
pnpm check:i18n-walk-parity :: exit 0
pnpm check:issue-citations :: exit 0
pnpm check:keyed-text-bounds :: exit 0
pnpm check:lean-entry-closure :: exit 0
pnpm check:logger-receiver-detach :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:objectql-double-limit :: exit 0
pnpm check:org-identifier :: exit 0
pnpm check:page-declaration-shape :: exit 0
pnpm check:platform-object-tenancy-census :: exit 0
pnpm check:pm-changeset-deadline-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:published-readme-links :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:react-page-adapter-contract :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:role-word :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:slot-lookup :: exit 0
pnpm check:sourcemap-no-sources-content :: exit 0
pnpm check:system-context-census :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:vendor-version-stamps :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (packages/spec untouched.)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes. (ESLint over the touched files locally; the repo-wide run is CI's Lint & Repo Gates.)
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: its generated artifacts are regenerated and committed. (Not touched.)
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done.

🤖 Generated with Claude Code

https://claude.ai/code/session_016WV4cw9cC8mhpZX1HgeLg5


Generated by Claude Code

…es, or a column other than their own holds

The orphan report answered which sys_secret rows nothing names. Its report
mode now also lists, from the same reference union, each handle more than one
holder record names (shared), each handle a column other than the one its
(namespace, key) names holds (misplaced), and, apart from both, a share whose
every holder is a row of the handle's own minting column, the shape a
predicate update writes. Report-only: nothing feeds the deletion plan and
nothing is repaired. The union now carries each holder's coordinates as data.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cw9cC8mhpZX1HgeLg5
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 22 documentable anchor(s).

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

  • content/docs/api/error-catalog.mdx (via user_id (literal, a string literal in collectSettingsSecretReferences))
  • content/docs/data-modeling/drivers.mdx (via external.credentialsRef (literal, a string literal in collectDatasourceSecretReferences), user_id (literal, a string literal in collectSettingsSecretReferences))
  • content/docs/data-modeling/external-datasources.mdx (via external.credentialsRef (literal, a string literal in collectDatasourceSecretReferences))
  • content/docs/data-modeling/virtual-objects.mdx (via external.credentialsRef (literal, a string literal in collectDatasourceSecretReferences))
  • content/docs/deployment/cli.mdx (via user_id (literal, a string literal in collectSettingsSecretReferences), os secret orphans (command, read off packages/cli/src/commands/secret/orphans.ts))
  • content/docs/kernel/runtime-services/audit-service.mdx (via user_id (literal, a string literal in collectSettingsSecretReferences))
  • content/docs/permissions/authentication.mdx (via user_id (literal, a string literal in collectSettingsSecretReferences))
  • content/docs/permissions/delegated-administration.mdx (via user_id (literal, a string literal in collectSettingsSecretReferences))
  • content/docs/permissions/permission-sets.mdx (via user_id (literal, a string literal in collectSettingsSecretReferences))
  • content/docs/permissions/positions.mdx (via user_id (literal, a string literal in collectSettingsSecretReferences))
  • content/docs/permissions/record-view-auditing.mdx (via user_id (literal, a string literal in collectSettingsSecretReferences))
  • content/docs/permissions/system-context.mdx (via user_id (literal, a string literal in collectSettingsSecretReferences))
  • content/docs/protocol/kernel/config-resolution.mdx (via user_id (literal, a string literal in collectSettingsSecretReferences))

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

  • content/docs/releases/v17/17-0.mdx (via external.credentialsRef (literal, a string literal in collectDatasourceSecretReferences))
  • content/docs/releases/v17/17-1.mdx (via external.credentialsRef (literal, a string literal in collectDatasourceSecretReferences))
  • content/docs/releases/v17/17-5.mdx (via user_id (literal, a string literal in collectSettingsSecretReferences))
  • content/docs/releases/v17/17-7.mdx (via os secret orphans (command, read off packages/cli/src/commands/secret/orphans.ts))

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
  • 12 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 58 of 210 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 — 35 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 ca64f6f9ea9e23a1c444d01faa09ea648aa32b21 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from a34e48edc5ace98a01d31da4d35fe4d1cfb11ea1 — the merge of head 2569bd1ae2ebd1b0fecd368f6043e4ff4fdcc886 into base ca64f6f9ea9e23a1c444d01faa09ea648aa32b21, 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 a34e48edc5ace98a01d31da4d35fe4d1cfb11ea1 && git checkout a34e48edc5ace98a01d31da4d35fe4d1cfb11ea1
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ca64f6f9ea9e23a1c444d01faa09ea648aa32b21 2569bd1ae2ebd1b0fecd368f6043e4ff4fdcc886 && git checkout -B drift-repro ca64f6f9ea9e23a1c444d01faa09ea648aa32b21 && git merge --no-ff 2569bd1ae2ebd1b0fecd368f6043e4ff4fdcc886

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

⚠️ 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 ca64f6f9ea9e23a1c444d01faa09ea648aa32b21 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Oct 29, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia

Seat ACCEPT · PR #27792 · head 2569bd1ae2ebd1b0fecd368f6043e4ff4fdcc886 · 2026-10-29T23:37Z

维护者速读

os secret orphans (report mode) now also lists sys_secret handles that something names in a way no producer writes: a handle more than one holder names (shared), and a handle held by a column other than its own (namespace, key) (misplaced). Handles shared only within their own minting column are listed apart and not judged. Report only: --delete is unchanged and nothing is repaired. Output carries ids and holder locations, never secret or cipher material.

席位核对

  • Shape: draft → main, with Fixes #27721. 9 files, all under packages/cli/ plus content/docs/deployment/cli.mdx and one changeset. check-governed-merges --pr 27792: NOT governed, +851 / −18.
  • Diff read against the card's rule: the new report is built from the same cross-producer reference union the orphan sweep uses; it reads only, and the --delete plan is untouched (pinned in sys-secret-orphan-sweep.test.ts and the driver-contract test). The datasource holder is excluded from the placement judgment, for the reason the changeset states.
  • Disclosure: the PR text and changeset stay at rule level for a security card.
  • Gate check-runs on this head: 50 total, 38 success, 12 skipped by the path filter, 0 failed. mergeable_state: clean.
  • Changeset: @objectstack/cli patch, additive report output (a new holders key beside plan under --json).
  • Declarations: Clause-②: no, Metadata-protocol: none. Additive CLI output, no published schema or governed text moves, so no contract review is owed.
  • Landing: Tier S, under the maintainer's landing grant for this term (2026-10-29 22:05Z, seat thread). Ready + auto-merge through the relay.

ACCEPT

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/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

secrets: a report-only check for stored secret: references that more than one holder names, or that a column other than the one that minted it holds

1 participant