Repository navigation
fix(spec): the connector read withholds root keys the entry shape does not declare - #28764
Conversation
…ls and non-object auth on read Claude-Session: https://claude.ai/code/session_017xRBcVy4VXmkQjn2MGeaWk Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017xRBcVy4VXmkQjn2MGeaWk Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017xRBcVy4VXmkQjn2MGeaWk Co-authored-by: Claude <noreply@anthropic.com>
…or-undeclared-keys Claude-Session: https://claude.ai/code/session_017xRBcVy4VXmkQjn2MGeaWk Co-authored-by: Claude <noreply@anthropic.com>
…he connector entry root is closed Claude-Session: https://claude.ai/code/session_017xRBcVy4VXmkQjn2MGeaWk Co-authored-by: Claude <noreply@anthropic.com>
…s not declare Claude-Session: https://claude.ai/code/session_01AavokzJ5DndAwitDXvKy4U Co-authored-by: Claude <noreply@anthropic.com>
…shape does not declare Claude-Session: https://claude.ai/code/session_01AavokzJ5DndAwitDXvKy4U Co-authored-by: Claude <noreply@anthropic.com>
…ape does not declare Claude-Session: https://claude.ai/code/session_01AavokzJ5DndAwitDXvKy4U Co-authored-by: Claude <noreply@anthropic.com>
…or-read-undeclared-keys Claude-Session: https://claude.ai/code/session_01AavokzJ5DndAwitDXvKy4U Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check10 anchor(s) derived from 2 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. 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 8391aee879c0b380956e17957b8263db91214084 && git checkout 8391aee879c0b380956e17957b8263db91214084
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b48eeb16c48ec2da1b39016fd3718a8da11bd762 3203b0379058ca0f1389211e58ce8ac36a6c128a && git checkout -B drift-repro b48eeb16c48ec2da1b39016fd3718a8da11bd762 && git merge --no-ff 3203b0379058ca0f1389211e58ce8ac36a6c128a
node scripts/docs-audit/affected-docs.mjs --json b48eeb16c48ec2da1b39016fd3718a8da11bd762 |
|
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U {
"issue": null,
"status": "done",
"branch": "claude/harden-connector-read-undeclared-keys",
"pr": "https://github.com/objectstack-ai/objectstack/pull/28764",
"session": "session_01AavokzJ5DndAwitDXvKy4U",
"premise_still_valid": true,
"summary": "The built-in connector read redactor (packages/spec/src/integration/connector-credential-redaction.ts) now withholds whole every top-level key the closed connector entry does not declare. The declared set comes from DeclarativeConnectorEntrySchema's own read-through shape (built from connectorBaseShape), not a hand-written list, and the read decorations (METADATA_READ_DECORATIONS) are left alone. Each withheld key goes into redactedKeys, so the protocol carry-forward keeps the stored value at rest when the served form is saved. Under providerConfig, an array holding a URL with credential parts at any depth is now withheld whole; the earlier test that kept such arrays served was flipped on purpose. #28688 merged while this work was in flight, so origin/main was merged into the branch (no rewrite) and the PR diff against main is now 6 files.",
"tests": "Before the merge (head 7e0c1cb4b1): pnpm --filter @objectstack/spec test, 783 files / 23158 passed / 1 todo, exit 0; pnpm --filter @objectstack/metadata-protocol test, 291 files passed + 4 skipped / 21952 passed + 26 skipped, exit 0. After the merge (head acbd2ecbf3, after pnpm install --frozen-lockfile and a rebuild of the dogfood dependency closure): spec src/integration/ 14 files / 261 passed; metadata-protocol protocol.metadata-redaction + protocol.diff-credential-redaction 65 passed; dogfood connector-meta-door-root-key-read + both connector-meta-door siblings 3 files / 25 passed; check:generated reports all 14 artifacts up to date. Ablations used scripts/ablation-replace.mjs (anchor count 1 to 0, restore proved by blob == HEAD and an empty git diff HEAD). ROOT-KEY fix ablated: spec suite 5 failed / 41; metadata-protocol 5 failed / 62 (item, list, layered, history diff, save-keeps-at-rest) after a spec rebuild, with ablation-dist-preflight --absent showing the marker gone from all 588 dist files; dogfood 3 failed / 5. ARRAY fix ablated: spec suite 1 failed / 41. Restore leg: spec rebuilt, preflight finds the marker in 4 dist files, metadata-protocol 62/62 and dogfood 5/5 green again.",
"gates": "Local, on the merged head acbd2ecbf3: node scripts/pm/dispatch-gates.mjs --commands derived 80 families from 6 paths. All 80 ran, each exit 0, with exit codes captured before any pipe. --ran reconciliation: 80 derived, 80 run, 0 NOT-MEASURED (a derived zero). Before the merge, the stacked diff (17 paths) derived 87 families: 86 exit 0, and check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET, missing dist for 7 packages, so NOT MEASURED). On the merged head it ran with exit 0. typecheck exit 0 for spec, metadata-protocol and dogfood. CI check-runs on PR 28764: not read; they were in_progress at report time. pnpm lint (eslint over the whole repo) was not run locally: os-dev's local-verification scope gives repo-wide scans to CI. Deviation: two one-file spec vitest probes and the first run of the spec redaction suite ran outside os-verify-lock.sh; every later build and test ran under the lock.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "0",
"api_writes": "2 relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches), executed as objectstack-fleet[bot]: pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/28764/labels (needs:contract-review); this report = POST /repos/objectstack-ai/objectstack/issues/28764/comments. git push is not counted as a REST write.",
"open_questions": [
{
"question": "The dispatch asked for a stacked branch with the body line 'Stacked on #28688; rebase onto main after it lands'. AGENTS.md (Multi-agent discipline section 2) lists a stacked series as an unsupported form. The dispatch also had a conditional: base on main if #28688 has merged. #28688 merged after the branch was first pushed, so the branch merged origin/main and the body states that it branched from #28688's head, which has since landed. Is that wording acceptable?",
"options": [
"A keep as is: the PR has no stacked dependency now",
"B the seat rewrites the first body line"
],
"recommendation": "A, because the PR diff against main is only this change and the dispatch's conditional covers this exact case"
},
{
"question": "A top-level key spelled with a dot is withheld on read, but the carry-forward addresses positions by dotted path and cannot reach it, so saving the served form does not keep such a key at rest. Keep this posture?",
"options": [
"A keep: withhold on read (fail closed) and accept losing the key at rest on save. The key is undeclared and nothing at runtime reads it",
"B serve dotted root keys: this reopens the read for them"
],
"recommendation": "A, because withholding is the safe direction and the key has no runtime effect. It is recorded in the PR's Acceptance notes."
}
],
"out_of_scope_findings": [
"carrier: none · noted, not filed: the history diff keeps a withheld key's path in its added/removed/changed bucket with the value absent. This is the existing posture for every redacted position (diff raw, emit redacted values), so this PR does not change it."
]
} |
|
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U Contract-review-claim: This PR was dispatched by the triage seat. The review runs at Generated by Claude Code |
|
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U Contract reviewServed-tier: Inputs read: the PR body, its six files, comments 6561953562, 6561962717 and 6561982303, the net diff of the head against ① Derived judgments
② Semver level
③ Boundary flagsAuthor is
④ Design judgment
Findings: no must-fix. Two should-fix: ① 6 (cleared undeclared root key served, round-trip refused) and ③ (2) (dotted root key lost at rest; the carry-forward can keep it; the dot-spelled Implemented-by: VERDICT: PASS Generated by Claude Code |
…; carry-forward keeps dot-spelled root keys at rest Claude-Session: https://claude.ai/code/session_01AavokzJ5DndAwitDXvKy4U Co-authored-by: Claude <noreply@anthropic.com>
…lled key withholding Claude-Session: https://claude.ai/code/session_01AavokzJ5DndAwitDXvKy4U Co-authored-by: Claude <noreply@anthropic.com>
|
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U Contract-review-claim: The head moved past this seat's PASS 6562139912. The delta re-review is this seat's, at Generated by Claude Code |
|
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U Contract reviewServed-tier: Delta record. It covers the interdiff from Inputs read: the compare API for the interdiff, the six files at the head, the previous record, the PR body and changeset at the head, the cited callers on Interdiff shape. Two commits, ① Derived judgmentsItems 1, 2, 3 and 5 of the previous record stand; the delta does not touch them. Items 4 and 6 move.
② Semver level
③ Boundary flagsAuthor is
④ Design judgment
Findings: no must-fix. One should-fix as a follow-up card, ③ (2): same-spelling collision under Implemented-by: VERDICT: PASS Generated by Claude Code |
|
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U Hand-over: this PR's landing goes to
|
|
domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo 席位核对ACCEPT · The maintainer's standing authorisation for landing reviewed PRs, verbatim: 「按照 pm-dispatch 规则复核过的都授权」.
Generated by Claude Code |
|
domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo Landing record · PR #28764
Generated by Claude Code |
Branched from the head of #28688, which has since landed;
mainis merged in, so the diff againstmainis this change alone.Clause-②: no
Metadata-protocol: none
Skills-docs: none
维护者速读
redactConnectorItem)现在整键扣下条目 schema 未声明的顶层键;providerConfig下装有带凭据 URL 的数组也整体扣下。redactedKeys,保存时由 carry-forward 原样保留在库里,运行时执行读不受影响。回滚即 revert 本 PR。packages/spec/src/integration/connector-credential-redaction.ts(credentialPathsOf顶层键循环与credentialUrlRewritesOf数组分支)→packages/qa/dogfood/test/connector-meta-door-root-key-read.dogfood.test.ts→packages/metadata-protocol/src/protocol.metadata-redaction.test.ts末尾新块。pnpm --filter @objectstack/spec exec vitest run src/integration/connector-credential-redaction.test.ts,预期 41 个用例全绿。What changes
Read-side hardening of the built-in
connectorredactor in@objectstack/spec:DeclarativeConnectorEntrySchema's read-throughshape, built fromconnectorBaseShape), never a hand-written list, so a key the entry declares later is served with nothing to edit. The read decorations (METADATA_READ_DECORATIONS:_diagnostics,_draft) are the response's and are left alone; the ADR-0010 protection envelope is declared in the shape and is served as before. A cleared value (null,'', an empty map) is served as written and not reported, the module's existing convention.redactedKeys, so the protocol's carry-forward keeps the stored value at rest on a save of the served form. The save door order is unchanged: strip read decorations, then the schema gate judges the author's body, then the carry-forward restores the stored key.providerConfig, an array that holds (at any depth) a URL whose credential parts the URL rewrite would remove is withheld whole, the posture a non-objectauthalready has. An array holding no such URL is served as stored. The previous pin that served such arrays is flipped deliberately.No public export changes; nothing an author can write changes.
Tests
packages/spec/src/integration/connector-credential-redaction.test.ts: withholds undeclared root keys on read (credential-shaped scalar, several keys, nested container, plain key); the declared set equals the entry schema's shape; read decorations untouched; cleared forms served; combined positions each named once; the array pin flipped (array withheld whole at any depth, a URL-free array served).packages/metadata-protocol/src/protocol.metadata-redaction.test.ts: the item read, the list read (both spellings) and the layered read withhold the keys; a save of the served form keeps them at rest; a key the author types is refused withINVALID_METADATA/ 422 and the row is unchanged; identity-redactor ablation arm.packages/metadata-protocol/src/protocol.diff-credential-redaction.test.ts: the history diff, all three buckets, reports the path and serves neither value; identity-redactor ablation arm.packages/qa/dogfood/test/connector-meta-door-root-key-read.dogfood.test.ts: the showcase's HTTP read doors (/meta/connector/:name,/layers,/meta/connector,/meta/connectors,/diff) for a member and an administrator, with the row seeded directly in storage; a save of the served form keeps the keys at rest.Acceptance notes
origin/mainwas merged in (a merge, no rewrite), so this PR carries no stacked dependency.Verification
Every reading below is on head
acbd2ecbf3(after mergingorigin/mainat7a48bf73f5).Package suites and typecheck
pnpm --filter @objectstack/spec test: 783 files, 23158 passed, 1 todo (measured on the pre-merge head7e0c1cb4b1); after the mergesrc/integration/: 14 files, 261 passed.pnpm --filter @objectstack/metadata-protocol test: 291 files passed, 4 skipped; 21952 tests passed, 26 skipped (pre-merge head); after the merge the two touched suites: 65 passed.typecheckexit 0 for@objectstack/spec,@objectstack/metadata-protocol(after the merge) and@objectstack/dogfood.pnpm --filter @objectstack/spec check:generated: all 14 generated artifacts up to date.Ablation (each fix, rebuilt and proved on disk)
Done with
scripts/ablation-replace.mjs(anchor hit 1 to 0, restore proved: blob equals HEAD andgit diff HEADempty) andscripts/ablation-dist-preflight.mjs.dist/, marker absent from all 588 built files)dist/)providerConfigRestore leg: spec rebuilt, preflight reads the marker present in 4 built files, metadata-protocol suites 62/62 and dogfood 5/5 green again.
Derived gate families
node scripts/pm/dispatch-gates.mjs --commandson the merged head derives 80 families from the 6 changed paths; all 80 run locally, each exit 0, and the--ranreconciliation reads 80 derived, 80 run, 0 NOT-MEASURED. Repo-wide scans (pnpm lintand the rest of the lint workflow) are left to CI.Generated by Claude Code