Repository navigation
feat(spec): a save of a connector's served form drops a leftover undeclared auth key and names it - #29456
Conversation
…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>
Claude-Session: https://claude.ai/code/session_01ADHkmS7ggoatyXpxRfHq4w Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADHkmS7ggoatyXpxRfHq4w Co-authored-by: Claude <noreply@anthropic.com>
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>
📓 Docs Drift CheckThis PR changes 2 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 169 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 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
|
|
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 |
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Contract reviewServed-tier: 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 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: ① Derived judgments
② Semver level
Clause-②: yes (widening) 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; ③ Boundary flagsDev flags (os-dev-report 6581427705), each answered or escalated:
④ Design judgment
Should-fix (one, low): the notice's Observations, not findings: (i) the Implemented-by: VERDICT: PASS |
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut 待裁:两个新公开名字
|
… 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>
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Contract reviewServed-tier: This is a DELTA record for the push after the PASS record 6581889597 (head Implemented-by: VERDICT: PASS |
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Head-sha: 维护者速读
席位意见:可落地,只等名字。契约复审在 04ba73c 判 PASS(6581889597),在现 head 的 delta 判 PASS(6582978233),CI 全绿。 席位核对 — ACCEPT ·
|
|
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. 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>
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Contract reviewServed-tier: This is a DELTA record for the pushes after the delta PASS 6582978233 (head
Check-runs on Implemented-by: VERDICT: PASS |
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Head-sha: 维护者速读
席位意见:可落地。契约复审在 04ba73c 判 PASS(6581889597),delta 在 9c87c39 判 PASS(6582978233),合 main 后的 delta 在现 head 判 PASS(6589251805),CI 全绿。 席位核对 — ACCEPT ·
|
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逐个点名被清掉的键。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
MetadataRedactionResultgainsnotCarriedForwardKeys?: string[](@objectstack/spec/kernel). It names the positions amongredactedKeysthat are withheld AND not carried forward. It is a subset ofredactedKeys, not a disjoint list:redactedKeysstill names every withheld position, so every existing reader that asks "was anything withheld?" throughredactedKeys(redactStoredMetadataBody,redactMetadataItem,metadata-manager's served-document walk) withholds exactly what it did before. Optional, and absent means none, so thedatasourcebuilt-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.authkeys (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 servingauth.token: nullback made a plain read then save fail withunrecognized_keys. Every other withheld connector position (the four maps,authentication, credential URLs, undeclared top-level,retryConfigandactionskeys, a non-objectauth) stays carried forward.metadata-protocol).planCarryForwardnever 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.carryForwardRedactedValuesandredactedPathsCarriedForwardkeep their signatures; a new package-internalcarryForwardRedactedValuesReportingDropsreturns the body and the dropped paths.carryForwardRedactedCredentials(the private save-door body) now answers{ item, dropped }.advisories, the existing channel a 2xx save carries its non-gating findings on (no second channel). Onewarningper dropped position: rulewithheld-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, andcontent/docs/references/api/protocol.mdxis 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 withoutauth.token→ save of the served form stores none andadvisoriesnamesconnectors[0].auth.token(value absent from the answer); CONTROL the header map beside it round-trips unchanged; the drain converges (second save: noadvisorieskey); a draft save drains and reports; a clearedauth.token: nullrow now saves; CONTROL a datasource row's carry-forward is unchanged and its answer carries noadvisories.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'sredactedPathsCarriedForwardno longer names it; the author's own value at that position (a switchedauthkind that declares the key) is not a drop; removingauthdrops 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 undeclaredauthkey; unknowntype; the three cleared forms are withheld and the served form then passesDeclarativeConnectorEntrySchema(it fails before redaction); CONTROL every other withheld position leaves the member absent; CONTROL a non-objectauthis still carried; only theauthkey 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
MetadataRedactionResultcontract change (spec + metadata-protocol) and takes aCONTRACT_REVIEW_TIERrecord; that record is the reviewing seat's, not this PR's.notCarriedForwardKeysand the advisory rule idwithheld-key-dropped. Alternatives considered for the member:droppedOnSaveKeys,drainedKeys.authkeys land "in the new list, not inredactedKeys". They land in both (subset). A disjoint list fails open: three existing readers decide "nothing withheld" fromredactedKeys.length === 0(redactStoredMetadataBodyandredactMetadataItem, andpackages/metadata/src/metadata-manager.tsredactServedDocument), so a connector row whose only withheld position was an undeclaredauthkey 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.authkeys 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.packages/metadata-protocol/src/protocol.ts(the save door threadsdroppedintoadvisories: 3 hunks) andpackages/spec/src/api/protocol.zod.ts(theadvisoriesdescription).content/docs/automation/connectors.mdxalready sends credentials toauth.credentialRef), so the regeneratedprotocol.mdxand the changeset are the documentation change.auth(item 4) and everyauthentication.KEY(item 2) are still carried forward;sys_metadata_historysnapshots 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, treeobjectstack-ai/objectstack.All on the merged head (main at
7f277a86merged in), each heavy run underscripts/pm/os-verify-lock.sh(VERDICT command-exit 0on every one).turbo run build --filter='./packages/**'@objectstack/specfocused (redaction + protocol tests)@objectstack/metadata-protocolfocused (5 redaction / carry-forward files)@objectstack/spectest (full)@objectstack/spectypecheck (incl. scripts + test typecheck)@objectstack/metadata-protocoltest (full)@objectstack/metadata-protocoltypecheck (src/**/*, tests included)@objectstack/objectqlsave / publish / advisory answer conformance@objectstack/metadataredaction bookkeeping + service@objectstack/service-automationflow / connector credential files@objectstack/runtimeflow credential files--no-inline-configon every changed.tspnpm --filter @objectstack/spec check:generatedcheck-changeset-no-major.mjs --eventon this bodyyes (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
notCarriedForwardKeyspush turns 7 red.Derived gates —
dispatch-gates.mjs --ran: 106 derived, 106 run, 0 NOT-MEASURED, 0 UNRUN; every one exit 0Human Minimum
Each item is one rule from
CONTRIBUTING.md › Human Minimum,
which links the AGENTS.md text that states it.
packages/specholds no runtime logic. The new member is a type on the existing redaction contract; the connector edit extends the pure redactorpackages/specalready hosts, and the carry-forward that acts on the mark lives inmetadata-protocol.notCarriedForwardKeyscamelCase member;withheld-key-droppedkebab-case rule id, like the gate's own rule ids).pnpm lintcommand,--no-inline-config) is clean on every changed.tsfile.main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed (content/docs/references/api/protocol.mdx, regenerated again on the merged tree).content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.## 维护者速读digest is the maintainer's requested Chinese summary).Generated by Claude Code