Skip to content

feat(spec): a save of a connector's served form drops a leftover undeclared auth key and names it - #29456

Merged
objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/project-thread-5z4z6j
Nov 2, 2026
Merged

objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/project-thread-5z4z6j

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steve · project thread
Part of #28640

Clause-②: yes (widening)
Metadata-protocol: none
Ruling-ref: 6566912252
Skills-docs: none — no authorable key, accepted value or metadata type changes; the change is a read redactor's result type and the save answer's advisories, which no skill teaches

维护者速读

  • 改了什么:连接器旧数据里 auth 未声明键上的明文(如 auth.token),现在普通「读取 → 保存」一次就会从库里清掉,保存结果的 advisories 逐个点名被清掉的键。
  • 为什么改:裁决 ②B(6566912252)。这些键门口早已拒收、运行时也从不读,带回去只是让明文一直留在库里。
  • 风险与回滚:只影响连接器 auth 未声明键这一类;其他所有隐藏位置(请求头表、datasource 等)照旧带回。回滚即还原本 PR。
  • 先看:packages/metadata-protocol/src/metadata-redaction.ts(planCarryForward 跳过不带回的位置)、packages/spec/src/integration/connector-credential-redaction.ts(标记)、packages/spec/src/kernel/metadata-type-redaction.ts(新成员与契约说明)。
  • 两分钟验证:pnpm --filter @objectstack/metadata-protocol exec vitest run src/protocol.metadata-redaction.test.ts -t "28640",预期 6 条全绿。
  • 需要你做的:批准新的公开名 notCarriedForwardKeys 与提示规则名 withheld-key-dropped(见 Acceptance notes),其余席位落地。

What changes

  • MetadataRedactionResult gains notCarriedForwardKeys?: string[] (@objectstack/spec/kernel). It names the positions among redactedKeys that are withheld AND not carried forward. It is a subset of redactedKeys, not a disjoint list: redactedKeys still names every withheld position, so every existing reader that asks "was anything withheld?" through redactedKeys (redactStoredMetadataBody, redactMetadataItem, metadata-manager's served-document walk) withholds exactly what it did before. Optional, and absent means none, so the datasource built-in, the schema-derived redactors and every registered redactor (service-automation's flow redactor included) keep carrying every withheld position forward with no edit. The interface docblock says what a read exit and what a save's carry-forward do with each list.
  • The connector redactor marks its undeclared auth keys (redactConnectorItem, item 1 of its module note). Each such key is named in both lists. A cleared value there (null, '', an empty map) is withheld too now, the way items 7 and 8 already withhold cleared undeclared keys: every instance-auth kind is a strict shape that refuses the key whatever it holds, so serving auth.token: null back made a plain read then save fail with unrecognized_keys. Every other withheld connector position (the four maps, authentication, credential URLs, undeclared top-level, retryConfig and actions keys, a non-object auth) stays carried forward.
  • The carry-forward skips those positions and reports what it dropped (metadata-protocol). planCarryForward never grafts a not-carried-forward path; it records each one the stored row holds and the incoming body does not hold. A value the incoming body itself holds there is the author's word, judged at the door, and not a drop. carryForwardRedactedValues and redactedPathsCarriedForward keep their signatures; a new package-internal carryForwardRedactedValuesReportingDrops returns the body and the dropped paths. carryForwardRedactedCredentials (the private save-door body) now answers { item, dropped }.
  • The save answer names each drop on advisories, the existing channel a 2xx save carries its non-gating findings on (no second channel). One warning per dropped position: rule withheld-key-dropped, where = connector "NAME", path = where the value sat at rest in the advisory spelling (connectors[0].auth.token), never the value. The key is still omitted when there is nothing to report. A draft save drains and reports too. SaveMetaItemResponseSchema.advisories's description says so, and content/docs/references/api/protocol.mdx is regenerated from it.

Why

Ruling ②B (6566912252, the maintainer's 「同意」): "The redactor can mark a position "withheld and not carried forward"; the class covers undeclared keys under auth … A normal save of the served form therefore drops them instead of re-grafting them, and the save result names the dropped keys in its notices, so a drop at rest is loud, never silent."

Tests

New, all against rows written below the door (direct seed / stored body), as a pre-strict row would be:

  • metadata-protocol/src/protocol.metadata-redaction.test.ts › [#28640 ②B] a save of the served connector form drains an undeclared auth key (6): read serves without auth.token → save of the served form stores none and advisories names connectors[0].auth.token (value absent from the answer); CONTROL the header map beside it round-trips unchanged; the drain converges (second save: no advisories key); a draft save drains and reports; a cleared auth.token: null row now saves; CONTROL a datasource row's carry-forward is unchanged and its answer carries no advisories.
  • metadata-protocol/src/metadata-redaction.connector-undeclared-keys.test.ts › an undeclared auth key is not carried forward (5): drop + report while the undeclared root key and the header map are still carried; the runtime gate's redactedPathsCarriedForward no longer names it; the author's own value at that position (a switched auth kind that declares the key) is not a drop; removing auth drops and reports; CONTROL no drop on a row without one.
  • spec/src/integration/connector-credential-redaction.test.ts › withheld and not carried forward (8): both lists name every undeclared auth key; unknown type; the three cleared forms are withheld and the served form then passes DeclarativeConnectorEntrySchema (it fails before redaction); CONTROL every other withheld position leaves the member absent; CONTROL a non-object auth is still carried; only the auth key is marked beside the other positions.
  • spec/src/kernel/metadata-type-redaction.test.ts › notCarriedForwardKeys (4): the built-in names it; a row whose ONLY withheld position is not carried forward is still redacted by the stored-row seam (pins subset-not-disjoint); CONTROL the datasource built-in and the derived webhook redactor never set it; a registered redactor without it type-checks.

Forced edits to existing tests: none.

Acceptance notes

  • Contract review: per the ruling this is a MetadataRedactionResult contract change (spec + metadata-protocol) and takes a CONTRACT_REVIEW_TIER record; that record is the reviewing seat's, not this PR's.
  • Two new public names await the maintainer: the member notCarriedForwardKeys and the advisory rule id withheld-key-dropped. Alternatives considered for the member: droppedOnSaveKeys, drainedKeys.
  • Deviation from the card delta's expected surface: the delta said undeclared auth keys land "in the new list, not in redactedKeys". They land in both (subset). A disjoint list fails open: three existing readers decide "nothing withheld" from redactedKeys.length === 0 (redactStoredMetadataBody and redactMetadataItem, and packages/metadata/src/metadata-manager.ts redactServedDocument), so a connector row whose only withheld position was an undeclared auth key would have been served in cleartext by every one of them, and by any third-party reader. The subset keeps all of them correct with no edit; only the carry-forward reads the new member. The ruling's words ("the redactor can mark a position") fit either.
  • Cleared undeclared auth keys are now withheld too (they were served as written). Not in the delta; included because the save of their served form was a 422 before this PR and the ruling wants that save to drain them.
  • File surface beyond the claim's four files: packages/metadata-protocol/src/protocol.ts (the save door threads dropped into advisories: 3 hunks) and packages/spec/src/api/protocol.zod.ts (the advisories description).
  • Docs: no hand-written page describes the connector read redaction or the save's carry-forward (content/docs/automation/connectors.mdx already sends credentials to auth.credentialRef), so the regenerated protocol.mdx and the changeset are the documentation change.
  • Not in scope, unchanged: a non-object auth (item 4) and every authentication.KEY (item 2) are still carried forward; sys_metadata_history snapshots keep the old body (served redacted by the stored-row seam); ①A (connectors: os validate and the save door warn on a credential-shaped literal in a connector header or env map (#28640 ruling 1A) #29341) and the ③A export marker (packages: a package export marks each withheld connector header/env map, so an import knows it is incomplete (#28640 ruling 3A) #29342).

Verification

Readings on head 04ba73c2207187c08cb3566eab228eab9658976d, tree objectstack-ai/objectstack.

All on the merged head (main at 7f277a86 merged in), each heavy run under scripts/pm/os-verify-lock.sh (VERDICT command-exit 0 on every one).

Run Reading
turbo run build --filter='./packages/**' 78/78 tasks
@objectstack/spec focused (redaction + protocol tests) 3 files, 333/333
@objectstack/metadata-protocol focused (5 redaction / carry-forward files) 5 files, 113/113
@objectstack/spec test (full) 791 files, 23463 passed + 1 todo
@objectstack/spec typecheck (incl. scripts + test typecheck) exit 0
@objectstack/metadata-protocol test (full) 304 files passed + 4 skipped; 22166 passed + 26 skipped
@objectstack/metadata-protocol typecheck (src/**/*, tests included) exit 0
@objectstack/objectql save / publish / advisory answer conformance 3 files, 45/45
@objectstack/metadata redaction bookkeeping + service 2 files, 81/81; typecheck exit 0
@objectstack/service-automation flow / connector credential files 4 files, 72/72; typecheck exit 0
@objectstack/runtime flow credential files 2 files, 13/13; typecheck exit 0
ESLint --no-inline-config on every changed .ts exit 0
pnpm --filter @objectstack/spec check:generated clean after the post-merge regen (the one prerequisite red was a pre-build dist)
check-changeset-no-major.mjs --event on this body exit 0 (yes (widening) / none, patch)

Ablations (pre-merge head, restored by blob hash after each): disabling the carry-forward's not-carried-forward skip turns 6 tests red; deleting the connector redactor's notCarriedForwardKeys push turns 7 red.

Derived gates — dispatch-gates.mjs --ran: 106 derived, 106 run, 0 NOT-MEASURED, 0 UNRUN; every one exit 0
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-dev-prereqs.mjs --self-test :: 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:api-surface :: exit 0
pnpm --filter @objectstack/spec run check:authorable-surface :: exit 0
pnpm --filter @objectstack/spec run check:browser-reachable-entries :: exit 0
pnpm --filter @objectstack/spec run check:docs :: exit 0
pnpm --filter @objectstack/spec run check:dual-source-exports :: 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:entry-nameability :: exit 0
pnpm --filter @objectstack/spec run check:export-origins :: exit 0
pnpm --filter @objectstack/spec run check:exported-any :: exit 0
pnpm --filter @objectstack/spec run check:generated :: exit 0
pnpm --filter @objectstack/spec run check:liveness :: exit 0
pnpm --filter @objectstack/spec run check:llms-txt :: exit 0
pnpm --filter @objectstack/spec run check:skill-examples :: exit 0
pnpm --filter @objectstack/spec run check:skill-refs :: 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: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:durability-log-level :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:error-status-conformance :: exit 0
pnpm check:filter-alias-parity :: exit 0
pnpm check:gitlink-declared :: 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:merge-driver :: 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:pm-prior-rulings :: 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:spec-docblock-symbol-anchors :: exit 0
pnpm check:spec-parsed-alias :: 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

Each item is one rule from
CONTRIBUTING.md › Human Minimum,
which links the AGENTS.md text that states it.

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. The new member is a type on the existing redaction contract; the connector edit extends the pure redactor packages/spec already hosts, and the carry-forward that acts on the mark lives in metadata-protocol.
  • 2. Every name I added uses the casing for its kind (notCarriedForwardKeys camelCase member; withheld-key-dropped kebab-case rule id, like the gate's own rule ids).
  • 3. No formatter ran; ESLint (the root pnpm lint command, --no-inline-config) is clean on every changed .ts file.
  • 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 (content/docs/references/api/protocol.mdx, regenerated again on the merged tree).
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English (the ## 维护者速读 digest is the maintainer's requested Chinese summary).
  • The Before Submitting list in CONTRIBUTING.md is done.

Generated by Claude Code

…rry forward

MetadataRedactionResult gains an optional notCarriedForwardKeys, a subset
of redactedKeys naming the withheld positions a save of the served form
drops instead of re-grafting. The connector redactor marks every
undeclared auth key (cleared values included, which the strict
instance-auth shapes refuse whatever they hold). The metadata-protocol
carry-forward skips those positions and the save names each one it
dropped on the answer's advisories as a withheld-key-dropped warning.

Claude-Session: https://claude.ai/code/session_01ADHkmS7ggoatyXpxRfHq4w
Co-authored-by: Claude <noreply@anthropic.com>
The merge took the branch's side of the generated protocol reference, which
dropped main's retired form drawer keys; regenerating from the merged source
restores them beside this branch's advisories description.

Claude-Session: https://claude.ai/code/session_01ADHkmS7ggoatyXpxRfHq4w
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/metadata-protocol, @objectstack/spec, touching 13 documentable anchor(s).

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

  • content/docs/concepts/metadata-lifecycle.mdx (via ObjectStackProtocolImplementation (symbol, a top-level class))

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

  • content/docs/releases/v16.mdx (via ObjectStackProtocolImplementation (symbol, a top-level class))
  • content/docs/releases/v17/17-0.mdx (via ObjectStackProtocolImplementation (symbol, a top-level class))

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
  • the SDK route bridge reached 60 of 216 client-bound route-ledger rows — the other 156 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 156: 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; 101 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 — 169 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 0ab4a09e6525823c4b4b8dfa989f394b0a68b716 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 0ab4a09e6525823c4b4b8dfa989f394b0a68b716

⚠️ 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 0ab4a09e6525823c4b4b8dfa989f394b0a68b716 → 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:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut

Contract-review-claim: session_01ADHkmSduNGC2SZXszyrNut · head 04ba73c · 2026-11-02T15:48Z

The lane seat reviews this head itself at CONTRACT_REVIEW_TIER (triage ruling 6575141496 on #28741 returned contract review to the lane): card #28640 slice ②B, a new optional member notCarriedForwardKeys on MetadataRedactionResult, the connector redactor withholding undeclared auth keys, and a new save advisory rule withheld-key-dropped.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 04ba73c2207187c08cb3566eab228eab9658976d
Local-runs: none
Scope: packages/spec/src/api/protocol.zod.ts, packages/spec/src/integration/connector-credential-redaction.ts, packages/spec/src/kernel/metadata-type-redaction.ts + packages/metadata-protocol/src/metadata-redaction.ts, packages/metadata-protocol/src/protocol-credential-carry-forward.ts, packages/metadata-protocol/src/protocol.ts, packages/metadata-protocol/src/index.ts, packages/metadata/src/metadata-manager.ts, packages/runtime/src/flow-clone.ts, packages/lint/src/runtime-gate.ts, packages/spec/src/shared/connector-auth.zod.ts, packages/spec/src/kernel/metadata-type-schemas.ts, packages/spec/src/meta-spelling/manifest-collection-spelling.ts, ui/packages/data-objectstack/src/metadata-client.ts, ui/packages/app-shell/src/providers/saveAdvisoryToast.ts, content/docs/references/api/protocol.mdx, .changeset/28640-withheld-not-carried-forward.md

Inputs read: card #28640 (body and all six comments, ruling 6566912252 and os-dev-report 6581427705 included), PR #29456 (body, 12-file list, net diff against the merge-base with origin/main, commit 7f277a8 of this repository), the check-runs on the head, the seat's delta (/mnt/project-files/spec-seat2/28640-delta.md), and the cited code in the live tree at the head via git show. Card slice: ②B only (①A is #29341, ③A's export marker is #29342). The rest of the diff (the four test files, the changeset, the regenerated protocol.mdx, the three metadata-protocol source files) is the lane seat's ACCEPT at TIER_DEFAULT; the metadata-protocol files were READ here as the first consumer, not reviewed.

Check-runs on the head at 2026-11-02T16:02Z: 52 success, 8 skipped, 0 failure, 3 still in progress. Concluded green among the required set: Lint & Repo Gates, Build Core, Governed Surface Queue Guard, Type Check UI, Test UI (all 16 shards), Build & E2E UI, Live E2E UI, and Test Core shards 1, 2, 4, 6, 7 and 8 of 8; Lint UI skipped (no ui/ path in the diff); Check Changeset success. Still running when this record was written: Test Core shards 3 and 5, and Type Check · workspace. No gate is red; the landing waits for those three to conclude green, as the queue rule already requires — this record judges the tree, and nothing below depends on their outcome.

① Derived judgments

  1. MetadataRedactionResult.notCarriedForwardKeys?: string[] (@objectstack/spec/kernel, packages/spec/src/kernel/metadata-type-redaction.ts) — a new OPTIONAL member on an exported interface. Public-surface WIDENING. Right. Every existing implementer type-checks unchanged (the datasource built-in and the derivation in this package, redactFlowCredentials in packages/services/service-automation/src/flow-credential-projection.ts; the PR's legacyShape test pins the type), and absent means [] is honoured by the one reader (planCarryForward: served.notCarriedForwardKeys ?? []). packages/spec/api-surface/kernel.json lists the interface by name only, so no snapshot regeneration is owed (check:api-surface ran green on the head).
  2. Subset, not disjoint — every not-carried-forward path is also in redactedKeys. Right, and the dev's fail-open claim measured TRUE at FOUR sites, not three: (a) redactStoredMetadataBody (packages/spec/src/kernel/metadata-type-redaction.ts L323) returns the RAW body when result.redactedKeys.length === 0 — and redactStoredMetadataRow(s) above it, which the generic data door and the runtime reader contexts serve sys_metadata / sys_metadata_history through, inherit that; (b) redactMetadataItem (packages/metadata-protocol/src/metadata-redaction.ts L140) returns the input; (c) redactServedDocument (packages/metadata/src/metadata-manager.ts L364) returns the record; (d) planCarryForward itself (metadata-redaction.ts L615) returns none on an empty redactedKeys, so with a disjoint list a row whose ONLY withheld position was an undeclared auth key would never reach the drop branch and never be reported. packages/runtime/src/flow-clone.ts L305 reads redactedKeys too, flow-only, unaffected either way. No reader in ui/** (git grep redactedKeys over ui/** at the head: zero hits). A disjoint list would have served that row in cleartext at (a)–(c) and silenced the drop at (d); the subset keeps all four correct with no edit. Deviation (1) is the right call.
  3. The connector redactor marks undeclared auth keys in both lists (redactConnectorItem, item 1). For a non-cleared value the read exit's output is byte-identical to main's; only the result gains the member. Right.
  4. A CLEARED undeclared auth key (null, '', {}) is now withheld where main served it as written (merge-base credentialPathsOf pushed auth.KEY and the loop skipped isClearedForm). A read-side reduction of the served item. Right: the save door validates a connector against DeclarativeConnectorEntrySchema (packages/spec/src/kernel/metadata-type-schemas.ts L272), whose instance-auth kinds are strict shapes since feat(spec)!: the connector write door refuses an inline secret on every instance auth kind; the metadata read redacts connector credential positions #28596, so on main a plain read-then-save of such a row was a 422 unrecognized_keys and the row could never drain; the PR's test pins safeParse(item).success === false for all three cleared forms and true for the served form. No accept set changes: the door refused the key before and refuses it still. Clients that read the cleared key: none found — ui/** has no reader of auth.token / undeclared auth keys (grep over ui/packages/** non-test sources: zero hits); a package export of the served form drops a key no import accepted. Deviation (2) is right.
  5. SaveMetaItemResponseSchema.advisories can now carry rule withheld-key-dropped, on a DRAFT save too (packages/spec/src/api/protocol.zod.ts, description only; rule is an open z.string(), so no enum widens). Right. A client ignoring the key behaves as before; the key is still omitted when nothing is reported (the convergence test pins not.toHaveProperty('advisories') on the second save; the datasource CONTROL pins no key). New observable: a draft save, never gated (D1 early return), can now answer advisories — a client that assumed "draft saves carry no advisories" was reading an absence, not a contract (the schema declared the key optional on every save). ui's readSaveAdvisories requires all six keys as strings; the notice supplies rule, severity, where, path, message, hint — it passes the filter and formatFinding renders it.
  6. redactedPathsCarriedForward no longer names an undeclared auth path (the runtime gate's restoredCredentialPaths). Right — no rule reads an undeclared key; the test pins ['apiKey', 'providerConfig.headers'].
  7. metadata-protocol public surface: unchanged. carryForwardRedactedValuesReportingDrops, droppedWithheldKeyNotices and WITHHELD_KEY_DROPPED are module exports consumed inside the package; packages/metadata-protocol/src/index.ts (L178–222) still exports only carryForwardRedactedValues, hasMetadataRedactor, redactMetadataItem(s) and the stored-row family from metadata-redaction.js. carryForwardRedactedCredentials is a private method. The package's only published change is the content of a 2xx answer. Deviation (3) — the two files beyond the claimed four — is right: the save door (protocol.ts L17176–L17190, L17307) is where the answer is assembled and advisories is the ruled channel ("don't invent a second one", the delta's own words); protocol.zod.ts is the schema face and a hits file in any case.
  8. The notice never carries the value. droppedWithheldKeyNotices (protocol-credential-carry-forward.ts L76–L95) builds where, path, message and hint from the type, the item name and the dotted path alone — the stored value is never an input to it, so no save shape (draft included) can leak it. The active-save test also asserts allStrings(res) excludes the token.

② Semver level

.changeset/28640-withheld-not-carried-forward.md: @objectstack/spec patch, @objectstack/metadata-protocol patch, carrying both declaration lines; the PR body carries the same two.

Clause-②: yes (widening)
Metadata-protocol: none

Matches the diff. The level follows the metadata protocol alone (AGENTS.md Post-Task Checklist §3): nothing an author can write changes — no key, value or type added; auth.KEY was refused at the door before this PR and is refused after — so none → patch on both packages is the rule, and check-changeset-no-major --event (the Check Changeset run on the head) concluded success. yes (widening) is right for a new optional member on an exported interface plus a new advisory rule id on an open string field. The (narrowing) arm is not owed: the two sibling PRs on this same redactor that made the read serve LESS while the door's accept set stood (#28764, #28997) declared Clause-②: no / Metadata-protocol: none, and the one that narrowed the accept set (#28596) declared no (narrowing) / narrowed — this PR is of the first class on its read side and widens a type on top, so yes (widening) is the honest line. Skills-docs: none holds: no skill under skills/** teaches the connector read redaction or the save's carry-forward (skills/objectstack-automation/SKILL.md L96 speaks of flow http nodes only). Regenerated content/docs/references/api/protocol.mdx matches the new description byte for byte in the diff; check:generated ran green on the head.

③ Boundary flags

Dev flags (os-dev-report 6581427705), each answered or escalated:

  • open_questions 1 — member name notCarriedForwardKeys (alternatives droppedOnSaveKeys, drainedKeys). A NEW PUBLIC NAME on @objectstack/spec/kernel. Judged fit: it names the mechanism in the ruling's own words and pairs with carryForwardRedactedValues / redactedPathsCarriedForward; nothing in the tree contradicts it. Escalated to the maintainer by the seat — this record does not stand in for that approval.
  • open_questions 2 — rule id withheld-key-dropped (alternative carry-forward-dropped). A NEW PUBLIC NAME on the wire (advisories[].rule, the key a client groups or suppresses by, stable across releases). Judged fit: kebab-case like the gate's rule ids; names what happened to the row at rest. Escalated to the maintainer by the seat, as above.
  • open_questions 3 — subset vs disjoint. ANSWERED: subset. Four fail-open readers named in ① item 2; the delta's "in the new list, not in redactedKeys" would have served cleartext at three read exits. The ruling's words ("the redactor can mark a position") admit either; the tree admits only the subset without reader edits.
  • open_questions 4 — keep withholding cleared undeclared auth keys. ANSWERED: keep. On main the served auth.token: null met the strict shape at the door (422), so the ruled drain was unreachable for exactly those rows; no client reads the cleared key (① item 4). Recorded here as an accepted deviation from the delta.
  • open_questions 5 — the two files beyond the claimed four. ANSWERED: accept (① item 7). The seat's claim comment 6578360910 named four source files; the save-door hunks are the consumer half the ruling itself names ("spec + metadata-protocol"), and the description is on a hits path.
  • Deviation 4 (no MCP writes / assignee set) — process, outside the tree; the standing order was followed.
  • Deviation 5 — main moved 25 commits after the gate run; one touches protocol.ts. Read from the diff alone: git diff 7f277a86..origin/main -- packages/metadata-protocol/src/protocol.ts is three hunks at L8778, L9012 and L9123 (+36/−17); the PR's four hunks sit at L353 (import list), L3736–L3745, L17164–L17190 and L17293–L17310 — no overlap, the nearest pair more than 7,000 lines apart. Conflict risk: low; the import hunk adds one name to an existing block main did not touch. The queue leg re-judges on the merged base, as it must. No other file of the PR moved on main in the window.
  • Out-of-scope finding (dev, "noted, not filed") — sys_metadata_history snapshots keep the pre-drain body; a read of them is served through the stored-row seam and withholds it, so there is no cleartext EXIT, only cleartext AT REST in history. Correctly outside ②B (the ruling's class is the live row's save). The dev marked it maintainer-reach; the seat decides whether to file it as a follow-up card, this record does not.
  • No open_questions entry was left unanswered. Nothing in the report contradicts the tree.

④ Design judgment

packages/spec/src/kernel/metadata-type-redaction.ts — judged against ruling ②B (6566912252) and its first consumer, planCarryForward (packages/metadata-protocol/src/metadata-redaction.ts L603–L685). HOLDS. The docblock does what the delta asked: it says what a READ exit does with each list and what a SAVE's carry-forward does, and states the subset invariant and the "absent means []" reading, both of which the consumer honours (?? []; the drop branch keyed off membership while iterating redactedKeys). Probes: (a) a redactor that names a path in notCarriedForwardKeys but NOT in redactedKeys — the consumer ignores it: the position was served, so the resent value is the author's word and is judged at the door. Fail-safe direction; the invariant is stated, not enforced, and no built-in violates it. Not a finding. (b) The connector sets the member only when non-empty — the CONTROL tests pin the member ABSENT on the datasource built-in, a derived (webhook) redactor and a connector with every other withheld position, so a reader using toEqual on those results is unchanged. (c) The module note's write-path paragraph now names the one fact a redactor states about the carry-forward; consistent with the consumer's header (metadata-redaction.ts L48–L57). No must-fix, no should-fix.

packages/spec/src/integration/connector-credential-redaction.ts — judged against ruling ②B (the class is undeclared keys under auth), ③A (the four maps stay withheld WHOLE and carried forward — the CONTROL test pins notCarriedForwardKeys absent with all four maps plus apiKey, authentication.key, retryConfig.secret, actions.0.token and a credential URL withheld), and the strict instance-auth union (packages/spec/src/shared/connector-auth.zod.ts: every static kind a strict shape with a required credentialRef). HOLDS. Probes: (a) order — items 7/8 first, then item 1, then the credential paths: auth is a declared root key, so item 7 never removes it and no path is pushed twice; item 1 reads served after items 7/8, correct. (b) Unknown auth.type — only type is declared, so every other key (a credentialRef included) is withheld AND not carried forward; the served { type: 'digest' } then fails the union at the door (422), nothing is persisted and no notice is answered. Same outcome as main for an unsaveable row; the PR's test pins notCarriedForwardKeys equal to redactedKeys for it. Acceptable. (c) auth a non-object (item 4) — still carried forward, CONTROL pinned; the right boundary, since the whole value may be the secret and the ruling's class is keys UNDER auth. (d) A legacy row { type: 'bearer', token: X } with no credentialRef — served { type: 'bearer' }, save refused for the missing credentialRef; the author must supply the reference before the row drains. Correct: the kind asks for its reference again, and nothing is lost that the door would have accepted. (e) The "Cleared forms" paragraph of the module note is amended to name items 1, 7 and 8 as the exception — the note and the code agree. No must-fix, no should-fix.

packages/spec/src/api/protocol.zod.ts — judged against the ruling's "the save result names the dropped keys in its notices" and the consumer that emits them (droppedWithheldKeyNotices, protocol-credential-carry-forward.ts L76–L95; the save door protocol.ts L17176–L17190 and L17307) and the two ui/ readers. HOLDS. The description is the published contract text for a behaviour the consumer measurably has: one warning per dropped position, draft saves included, key omitted when empty. path spelling connectors[0].auth.token is the schema's own positional rule: connectors is not a name-keyed context collection (packages/lint/src/runtime-gate.ts L587–L592: objects, permissions, books, datasets only), so the written item IS [0]; an index segment in the dotted path is re-spelled [i] (actions.0.KEY → actions[0].KEY), matching the gate's nested spelling. where follows the gate's TYPE "name" form. hint is REQUIRED by RuntimeAuthoringIssueSchema and ui's readSaveAdvisories drops a finding without it — the notice supplies it. No ui/** mirror of MetadataRedactionResult exists, and the advisory shape is re-exported from @objectstack/spec (RuntimeAuthoringIssue) rather than restated (ui/packages/data-objectstack/src/metadata-client.ts L49–L71), so there is no objectui#7715-class mirror to drop the new member or rule. No must-fix.

Should-fix (one, low): the notice's message says "the stored row held a value at 'auth.token' … this save removed it from the row at rest". Two cases make that sentence imprecise: a cleared form (token: null is reported with the same words — the row held a cleared position, not "a value"), and the code-layer fallback (storedBodyForCarryForward compares against the CODE layer when no overlay row exists, so the first save creates an overlay WITHOUT the key and removes nothing from any row). Both are wording, not behaviour: the drop, the convergence and the loudness are right in each case. Remedy: say "held a position at …, which the read withholds and no save keeps; this save stored the row without it" — one string in droppedWithheldKeyNotices, and the two droppedNotice expectations use objectContaining without message, so no test moves. May land as a follow-up.

Observations, not findings: (i) the ui toast title (saveAdvisoryToast.ts L122) frames every advisory as "the authoring check raised N advisory finding(s)"; a drop notice is the save's own, not the gate's — a ui/ wording follow-up, and the channel is the ruled one. (ii) The draft-save test asserts the notice shape but not allStrings(res) against the token; the emitter is value-blind by construction (① item 8) and the active-save test asserts it, so the pin is adequate. (iii) The publish door (protocol.ts L18750) runs no carry-forward and promotes the draft body; a draft saved through the save door is already drained, and a pre-PR draft still holding the key drains on its next save — consistent with the ruling's "a normal save".

Implemented-by: claude/project-thread-5z4z6j
Reviewed-by: session_01ADHkmSduNGC2SZXszyrNut

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut

待裁:两个新公开名字 notCarriedForwardKeys 与 withheld-key-dropped(#28640 ②B)

  • 一句话问题:连接器的认证配置里如果残留一个未声明的密钥(例如旧版的 auth.token),读出时它被隐藏,再保存时却会被原样带回,永远清不掉。这个 PR 让保存时丢掉这类键并告诉作者,为此新增两个公开名字,需要您定。
  • 背景:总监裁定 ②B(6566912252):未声明的认证键读出隐藏、保存不带回、并给作者一条提示。席位的契约复审判 PASS(6581889597)。
  • 名字一:MetadataRedactionResult.notCarriedForwardKeys(@objectstack/spec/kernel 接口上的可选成员,是 redactedKeys 的子集:隐藏且保存时不带回的路径)。
    • A 保留:与现有的 carryForwardRedactedValues、redactedPathsCarriedForward 同一套"带回"用语,零额外工作。
    • B 改名(droppedOnSaveKeys 或 drainedKeys):更直说"保存时丢掉",但与"带回"那组名字不成对;改名约半小时(改代码、测试、重新生成参考页),复审补 delta。
  • 名字二:保存提示的规则 id withheld-key-dropped(出现在保存响应 advisories[].rule,客户端按它归类或屏蔽,发布后应保持稳定)。
    • A 保留:与现有规则 id 一样用短横线,直说"被隐藏的键被丢掉"。
    • B 改为 carry-forward-dropped:强调"带回机制丢掉了它",与名字一的用语更贴近;改名成本同上。
  • 推荐:两个都选 A。盲区:如果将来"保存不带回"还会用于认证以外的键,withheld-key-dropped 仍然成立;notCarriedForwardKeys 是双重否定,读起来稍绕,但说明文字已写清它是 redactedKeys 的子集、缺省即为空。
  • 自检:只看①选 A;②③④ 是否翻转:否。
  • 落地:在此 PR 标签取下前,席位不挂自动合并。

… cleared value and a first save

The notice said the stored row held a value that this save removed. A cleared
value (`null`) held nothing, and a first save over the code layer writes a new
row that never held the key. It now says this save did not carry the key
forward and the row it wrote holds nothing there: true in all three cases,
with no value in it.

Claude-Session: https://claude.ai/code/session_01ADHkmS7ggoatyXpxRfHq4w
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 9c87c39e8941f75ebf937cec05dc6f4a910f857a
Local-runs: none

This is a DELTA record for the push after the PASS record 6581889597 (head 04ba73c2). The delta 04ba73c2...9c87c39e is one fast-forward commit across 2 files, and it is the record's Should-fix done as asked. In packages/metadata-protocol/src/protocol-credential-carry-forward.ts the withheld-key-dropped message no longer claims "the stored row held a value … removed it from the row at rest"; it now reads "this save did not carry '' forward: the read withholds that position and no save keeps it …, so the row this save wrote holds nothing there", which is true of a stored value, a cleared one (null, '') and a first save over the code layer. It still names the key and never the value, and carries no tracker id. The docblock states the three cases. protocol.metadata-redaction.test.ts adds the draft-save allStrings assertion against the token (the record's observation) and, on the first-save case, asserts the message names 'auth.token' and no longer says "held a value". No spec file, export, rule id, changeset or regenerated reference moved, so every judgment in 6581889597 (①–④, patch ×2, Clause-②: yes (widening), Metadata-protocol: none) carries to this head unchanged. ci-failure.mjs --sha 9c87c39e…: GREEN, 65 check-runs, none failed. The two new names are with the maintainer (needs-user-decision, comment 6581907766).

Implemented-by: claude/project-thread-5z4z6j
Reviewed-by: session_01ADHkmSduNGC2SZXszyrNut

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut

Head-sha: 9c87c39e8941f75ebf937cec05dc6f4a910f857a · read 2026-11-02T16:56Z

维护者速读

  • 改了什么:连接器认证配置里残留的未声明密钥(例如旧版 auth.token)读出时本来就被隐藏;现在作者把读到的表单再保存一次,这个键会被丢掉而不是被悄悄带回,保存响应里多一条提示说明丢了哪个键(只写键名,从不写值)。
  • 为什么改:以前读出隐藏、保存又原样带回,这类明文残留永远清不掉。按总监裁定 ②B(6566912252)落实。
  • 风险与回滚:只影响带这类残留键的连接器行;已声明的凭据位置与四个 header/env 映射照旧带回(有对照测试);数据源行不变。回滚就是 revert 本 PR;丢掉的残留值本就不该存在,不需要恢复。
  • 先看:packages/spec/src/integration/connector-credential-redaction.ts 与 packages/metadata-protocol/src/protocol-credential-carry-forward.ts。
  • 两分钟验证:pnpm --filter @objectstack/metadata-protocol exec vitest run src/protocol.metadata-redaction.test.ts,预期全部通过。
  • 需要你做的:两个新公开名字 notCarriedForwardKeys、withheld-key-dropped 待总监裁定(needs-user-decision,分析见 6581907766)。裁定保留后席位落地。

席位意见:可落地,只等名字。契约复审在 04ba73c 判 PASS(6581889597),在现 head 的 delta 判 PASS(6582978233),CI 全绿。

席位核对 — ACCEPT · domain:spec#2 · head 9c87c39e

  1. Shape: draft, base main, Part of #28640 (the card stays open for connectors: os validate and the save door warn on a credential-shaped literal in a connector header or env map (#28640 ruling 1A) #29341 and packages: a package export marks each withheld connector header/env map, so an import knows it is incomplete (#28640 ruling 3A) #29342). Clause-②: yes (widening) and Metadata-protocol: none sit at line start in the body and the changeset (@objectstack/spec and @objectstack/metadata-protocol, each patch). 12 files, +584/−39, inside claim 6578360910 plus the two files the record accepted (protocol.ts, protocol.zod.ts). No governed path.
  2. Premise measured: on main a stored connector row with an undeclared auth.token is served without it and a save of the served form re-grafts it. On the head the same save stores no auth.token and answers one withheld-key-dropped advisory naming connectors[0].auth.token, on a draft save too, with no value in any string of the response. The dev's ablations turn 6 and 7 red.
  3. Gates: ci-failure.mjs --sha 9c87c39e… GREEN: 65 check-runs, none failed.
  4. Accepted deviations: notCarriedForwardKeys is a subset of redactedKeys, not the delta's disjoint list (a disjoint list fails open at four readers, named in the record); cleared undeclared auth keys are withheld too (the round trip of the served form was a 422 before); packages/metadata-protocol/src/protocol.ts and packages/spec/src/api/protocol.zod.ts are beyond the claimed surface because the save door assembles advisories there.
  5. Out of scope: A connector auth key drained on save (#28640 ②B) still sits in cleartext at rest in sys_metadata_history snapshots #29484 (sys_metadata_history snapshots keep the drained key in cleartext at rest; served redacted, no cleartext exit). connectors: os validate and the save door warn on a credential-shaped literal in a connector header or env map (#28640 ruling 1A) #29341 and packages: a package export marks each withheld connector header/env map, so an import knows it is incomplete (#28640 ruling 3A) #29342 are the card's other slices (domain:server).
  6. Landing: not Tier H and under 3000 lines. It lands only after the name ruling: if both names stay, the seat removes needs-user-decision and lands with pr_ready and auto-merge; after merge, a landing note on [Decision] connector credential follow-ups after #28596: header-value advisory, legacy auth keys at rest, whole-map withholding #28640 (which stays open for its other slices).

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut

Withdrawn: the name question on this PR. The pm-dispatch skill carries no rule that a new public name needs the maintainer's approval; that line lived only in this seat's standard order. Under SKILL.md 〈升级与决策〉 a name escalates only when the options truly diverge on product semantics or contract shape and no existing norm decides; here the dev's name is fit and no fork exists, so it is the seat's call: the name stays as built. needs-user-decision is removed and the seat lands this PR on its ACCEPT.

Prompted by the maintainer's question at 2026-11-02T17:03Z.

The merge kept the branch's side of the generated protocol reference;
regenerating from the merged source brings main's rows back beside this
branch's advisories description.

Claude-Session: https://claude.ai/code/session_01ADHkmS7ggoatyXpxRfHq4w
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 88387b3311c7510472e8794636ca541f4d09fb0e
Local-runs: none

This is a DELTA record for the pushes after the delta PASS 6582978233 (head 9c87c39e). Two commits: b1b59f7d merges main after server seat 5 lifted the protocol.ts freeze (#29590, e9d95490, lifting note on #28677), and 88387b33 regenerates content/docs/references/api/protocol.mdx on the merged tree.

  • The change set did not move. Against its merge base the PR is still 12 files, +584/−39. Outside the generated reference page, the added and removed lines (621) are the same multiset as at 9c87c39e against its own base, so no hunk was dropped, duplicated or rewritten by the merge.
  • No hunk sits in a moved member. [maintainer] refactor(metadata-protocol): split src/protocol.ts (33,065 lines, +10,169 in 30 days) along its ObjectStackProtocolImplementation seams, one move-only slice per PR #25467 E2 §1 moved the bodies readItemFromMetadataService … getUiView into the protocol-meta-*.ts modules. This PR's three protocol.ts hunks (the import, the save door's advisories assembly, and the two carry-forward call sites) apply unchanged on the merged tree, which they could not do if they touched a moved body. The dev's report says the same.
  • Every judgment in 6581889597 and 6582978233 carries unchanged: semver, Clause-②: yes (widening), Metadata-protocol: none, and the names kept by the seat (6583315283).

Check-runs on 88387b33: ci-failure.mjs GREEN, 65 check-runs, none failed.

Implemented-by: claude/project-thread-5z4z6j
Reviewed-by: session_01ADHkmSduNGC2SZXszyrNut

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut

Head-sha: 88387b3311c7510472e8794636ca541f4d09fb0e · read 2026-11-02T21:45Z

维护者速读

  • 改了什么:连接器认证配置里残留的未声明密钥(例如旧版 auth.token)读出时本来就被隐藏;现在作者把读到的表单再保存一次,这个键会被丢掉而不是被悄悄带回,保存响应里多一条提示说明丢了哪个键(只写键名,从不写值)。
  • 为什么改:以前读出隐藏、保存又原样带回,这类明文残留永远清不掉。按总监裁定 ②B(6566912252)落实。
  • 风险与回滚:只影响带这类残留键的连接器行;已声明的凭据位置与四个 header/env 映射照旧带回(有对照测试);数据源行不变。回滚就是 revert 本 PR;丢掉的残留值本就不该存在,不需要恢复。
  • 先看:packages/spec/src/integration/connector-credential-redaction.ts 与 packages/metadata-protocol/src/protocol-credential-carry-forward.ts。
  • 两分钟验证:pnpm --filter @objectstack/metadata-protocol exec vitest run src/protocol.metadata-redaction.test.ts,预期全部通过。
  • 需要你做的:无,席位落地(两个新名字由席位按现实现保留,6583315283)。

席位意见:可落地。契约复审在 04ba73c 判 PASS(6581889597),delta 在 9c87c39 判 PASS(6582978233),合 main 后的 delta 在现 head 判 PASS(6589251805),CI 全绿。

席位核对 — ACCEPT · domain:spec#2 · head 88387b33

  1. Shape: draft, base main, Part of #28640 (the card stays open for connectors: os validate and the save door warn on a credential-shaped literal in a connector header or env map (#28640 ruling 1A) #29341 and packages: a package export marks each withheld connector header/env map, so an import knows it is incomplete (#28640 ruling 3A) #29342). Clause-②: yes (widening) and Metadata-protocol: none sit at line start in the body and the changeset (@objectstack/spec and @objectstack/metadata-protocol, each patch). 12 files, +584/−39, inside claim 6578360910 plus the two files the record accepted (protocol.ts, protocol.zod.ts). No governed path.
  2. Premise measured: on main a stored connector row with an undeclared auth.token is served without it and a save of the served form re-grafts it. On the head the same save stores no auth.token and answers one withheld-key-dropped advisory naming connectors[0].auth.token, on a draft save too, with no value in any string of the response. The dev's ablations turn 6 and 7 red.
  3. Gates: ci-failure.mjs --sha 88387b33… GREEN: 65 check-runs, none failed.
  4. Accepted deviations: notCarriedForwardKeys is a subset of redactedKeys, not the delta's disjoint list (a disjoint list fails open at four readers, named in the record); cleared undeclared auth keys are withheld too (the round trip of the served form was a 422 before); packages/metadata-protocol/src/protocol.ts and packages/spec/src/api/protocol.zod.ts are beyond the claimed surface because the save door assembles advisories there.
  5. Out of scope: A connector auth key drained on save (#28640 ②B) still sits in cleartext at rest in sys_metadata_history snapshots #29484 (sys_metadata_history snapshots keep the drained key in cleartext at rest; served redacted, no cleartext exit). connectors: os validate and the save door warn on a credential-shaped literal in a connector header or env map (#28640 ruling 1A) #29341 and packages: a package export marks each withheld connector header/env map, so an import knows it is incomplete (#28640 ruling 3A) #29342 are the card's other slices (domain:server).
  6. Landing: not Tier H and under 3000 lines. The protocol.ts freeze is lifted (refactor(metadata-protocol): move the metadata item reads and the layered read through the protocol host (#25467 E2 cut 1b) #29590), main is merged in, and the names are kept, so the seat runs pr_ready and auto-merge now. After merge: a landing note on [Decision] connector credential follow-ups after #28596: header-value advisory, legacy auth keys at rest, whole-map withholding #28640, which stays open for connectors: os validate and the save door warn on a credential-shaped literal in a connector header or env map (#28640 ruling 1A) #29341 and packages: a package export marks each withheld connector header/env map, so an import knows it is incomplete (#28640 ruling 3A) #29342.

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.

1 participant