You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
service-settings: a map specifier admits any key when its option source answers an empty list; two missing shape pins; a stale json comment (#31386 review nits) #31422
Filing gate: ② the three non-blocking nits from the Fable contract review of PR #31386 (#30208 row 5 phase 1, review 6717755004), recorded on #30208 (6717781477) as "owed as a small follow-up after #31386 merges". #31386 merged at 2026-11-08T06:50Z. ⛔ Not a claim.
Reader: the seat that owns packages/services/service-settings (SettingsService.mapShapeVerdict, map-shape.ts) and packages/objectql/src/lifecycle/.
Dedupe (open AND closed): mapShapeVerdict empty, map specifier fail closed → only #30208 / PR #31386.
reach: (1) every host whose lifecycle-class option source answers an empty list, where a map key admits any entry key instead of refusing it.
class: a · type: Bug
Grade: p3. The server seat graded it itself (Ruling-ref: the maintainer, 「定级你可以自己决定」). Nothing ships wrong today: with no lifecycle objects, no map row has a key that could be right.
mapShapeVerdict admits every key when a reachable option source answers an empty list. It should fail closed whenever the source answered at all, and keep the allowed.length > 0 gate only for the declared-table fallback. Pin: a registry with no lifecycle objects gives invalid_option.
Missing pins: an array value (not_a_map gives invalid_type), and a scalar entry in a several-column map (not_an_entry gives invalid_shape).
The comment at packages/objectql/src/lifecycle/lifecycle-service.ts (~l.1161) still calls these keys json specifiers.
Item 1 refuses a save that was accepted before, so it is Clause-②: no (narrowing), and the changeset says so.
Claim: PM loop round 1 · #31422 · 2026-11-08T12:51Z · fix (p3; the thread freed by #31399's merge)
Session: session_01FEkQ5pXkgNXVpCn2GZDjyy
Account: os-project-manager
Branch: claude/project-thread-2n2fmy (the card thread's harness-pinned branch, freed by PR #31480's merge; platform exception to claude/issue-<n>-<slug>)
Worktree: objectstack-issue-31422
Domain: domain:server
Seat: domain:server#3
File surface: packages/services/service-settings/src/settings-service.ts (mapShapeVerdict) and its map-shape tests, packages/objectql/src/lifecycle/lifecycle-service.ts (the comment at ~l.1162 only), a changeset (stop on breach; explain in the report)
Container & model: S, mode:thread, model: default tier, effort: default (coordinator default)
Clause-②: no (narrowing) — item 1 refuses a save that was accepted before, so the seat runs a TIER_DEFAULT contract review before landing
Metadata-protocol: none
Responsibility: non-blocking nits of PR #31386's Fable review 6717755004, recorded on #30208 (6717781477) → this card.
Thread-read: 6718962862
Premise: holds (read at main: mapShapeVerdict lives in settings-service.ts; the comment at lifecycle-service.ts l.1162 still says "json specifiers")
Serial constraints cleared: the seat's de PR #31511 touches only service-settings/src/setup-nav-hub-parity.test.ts there, a different file; no other open PR touches service-settings or lifecycle-service.ts.
Order: pm-orders/server/order-31422.md (project files).
domain:server#3 · os-project-manager · session_01FEkQ5nBSzc6ktJyjdFcDb3
os-dev-report
{
"issue": 31422,
"status": "done",
"branch": "claude/project-thread-2n2fmy",
"pr": "#31539",
"session": "session_01FEkQ5nBSzc6ktJyjdFcDb3",
"premise_still_valid": true,
"summary": "Item 1: SettingsService.optionTableOf now reports sourced (a registered source answered, even with no options); mapShapeVerdict judges every key whenever sourced, keeping the allowed.length > 0 gate only for the declared-table fallback. Both doors call this one verdict, so a source answering an empty list refuses each key at the save door (invalid_option) and keeps an OS_* override out of force (one error line). Item 2: pins for an array value (not_a_map / invalid_type) and a scalar entry in the several-column retention_overrides (not_an_entry / invalid_shape). Item 3: the lifecycle-service.ts docblock (~l.1162) now says map specifiers (json before #30208); comment only.",
"tests": "Head 66137fb. settings-map-shape-30208.test.ts +6 rows (16 -> 22): on main's source 2 failed | 20 passed (the save row 'expected 200 to be 400', the env row 'expected env not to be env'); controls (empty map saves; an unanswered source with no declared keys admits any key) and the two coverage pins green; with the fix 22 passed. Ablation (ablation-replace, settings-service.ts, admitsKey ignores sourced via String('ABL31422').length < 0): anchor 1 -> 0, blob 07699b278e33 -> e0d112878a7f, the same 2 rows red; restored blob == HEAD, git diff HEAD empty; the suite reads its own src. @objectstack/service-settings vitest: Test Files 73 passed (73), Tests 1407 passed.",
"gates": "dispatch-gates --commands at 7d88feb: 70 commands, timeout 1500 each, stdin /dev/null, exit codes captured before any pipe, after a runtime closure + service-settings build. 69 exit 0; check-adr-0087-registration --base origin/main exit 1 (the narrowing changeset had no ADR-0087 disposition) -> fixed in 66137fb with adr-0087: not-required (no-migration-prescription); the ten changeset/citation/closing-keyword gates re-run at 66137fb, all exit 0. dispatch-gates --ran: 70 derived, 70 run, 0 NOT-MEASURED, 0 UNRUN. Outside: service-settings and objectql typecheck exit 0; eslint --no-inline-config packages/services/service-settings packages/objectql/src/lifecycle exit 0. Record: pm-orders/server/31422-gates-ran.txt. CI: just opened at hand-back.",
"dogfood_battery": "n/a — no ! in the PR title; the narrowing is one runtime refusal pinned over the real settings door testkit",
"mcp_calls": "3 this card — issue_read x2 (#31422 get, get_comments); no write tool",
"api_writes": "2 — relay pr_create (run 42486241078, PR #31539, draft, read-back identical 7082 bytes, assignees none, labels none); this os-dev-report comment on #31422 via post-stamped",
"files_changed": [
".changeset/31422-map-key-source-empty-fails-closed.md (+12)",
"packages/services/service-settings/src/settings-service.ts (+15/-6)",
"packages/services/service-settings/src/settings-map-shape-30208.test.ts (+91)",
"packages/objectql/src/lifecycle/lifecycle-service.ts (+3/-2, comment only)"
],
"line_budget": "4 files, about +121/-8 vs origin/main (under the 3000-line human-merge threshold)",
"deviations": [
"optionTableOf (the helper mapShapeVerdict reads) gains a sourced member: same file, the other two callers ignore it and are unchanged.",
"The changeset carries an adr-0087 not-required (no-migration-prescription) marker: the gate reads Clause-② narrowing as a declared breaking change.",
"Worktree: the claim names objectstack-issue-31422; the chain kept its worktree objectstack-issue-31317 on the same claimed branch."
],
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"carrier: PR #31539 Acceptance notes · noted, not filed — the option-bearing save branch (validatePatch) and the env override for non-map keys keep their own allowed.length > 0 gate, so a select key whose registered source answers an empty list still admits any value: item 1's shape on another family."
]
}
MERGED 2026-11-08T14:44Z through the merge queue as dc8479e0 (head 66137fbc, seat ACCEPT 6722625300, at-tier narrowing review 6722607950)
This card
closed by Fixes #31422
Status change: pm:dispatched removed. The review's one nit (the select family's empty-source gap) and its observation (map dedupe in reportRejectedEnvOverride) are follow-up #31550.
Activity
objectstack-fleet commentedon Nov 8, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: audit · p3 ·
domain:server· confirmed · 2026-11-08T07:53Z⛔ Not a claim.
mapShapeVerdictadmits any key when the option source answers an empty list; it should refuse.pm:queue.Generated by Claude Code
objectstack-fleet commentedon Nov 8, 2026
domain:server#3 · os-project-manager · session_01FEkQ5pXkgNXVpCn2GZDjyy
Claim: PM loop round 1 · #31422 · 2026-11-08T12:51Z · fix (p3; the thread freed by #31399's merge)
Session:
session_01FEkQ5pXkgNXVpCn2GZDjyyAccount:
os-project-managerBranch:
claude/project-thread-2n2fmy(the card thread's harness-pinned branch, freed by PR #31480's merge; platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-31422Domain:
domain:serverSeat:
domain:server#3File surface:
packages/services/service-settings/src/settings-service.ts(mapShapeVerdict) and its map-shape tests,packages/objectql/src/lifecycle/lifecycle-service.ts(the comment at ~l.1162 only), a changeset (stop on breach; explain in the report)Container & model:
S,mode:thread,model: default tier,effort: default (coordinator default)Clause-②: no (narrowing) — item 1 refuses a save that was accepted before, so the seat runs a TIER_DEFAULT contract review before landing
Metadata-protocol: none
Responsibility: non-blocking nits of PR #31386's Fable review 6717755004, recorded on #30208 (6717781477) → this card.
Thread-read: 6718962862
Premise: holds (read at
main:mapShapeVerdictlives insettings-service.ts; the comment atlifecycle-service.tsl.1162 still says "jsonspecifiers")Serial constraints cleared: the seat's de PR #31511 touches only
service-settings/src/setup-nav-hub-parity.test.tsthere, a different file; no other open PR touchesservice-settingsorlifecycle-service.ts.Order:
pm-orders/server/order-31422.md(project files).objectstack-fleet commentedon Nov 8, 2026
domain:server#3 · os-project-manager · session_01FEkQ5nBSzc6ktJyjdFcDb3
os-dev-report
{
"issue": 31422,
"status": "done",
"branch": "claude/project-thread-2n2fmy",
"pr": "#31539",
"session": "session_01FEkQ5nBSzc6ktJyjdFcDb3",
"premise_still_valid": true,
"summary": "Item 1: SettingsService.optionTableOf now reports sourced (a registered source answered, even with no options); mapShapeVerdict judges every key whenever sourced, keeping the allowed.length > 0 gate only for the declared-table fallback. Both doors call this one verdict, so a source answering an empty list refuses each key at the save door (invalid_option) and keeps an OS_* override out of force (one error line). Item 2: pins for an array value (not_a_map / invalid_type) and a scalar entry in the several-column retention_overrides (not_an_entry / invalid_shape). Item 3: the lifecycle-service.ts docblock (~l.1162) now says map specifiers (json before #30208); comment only.",
"tests": "Head 66137fb. settings-map-shape-30208.test.ts +6 rows (16 -> 22): on main's source 2 failed | 20 passed (the save row 'expected 200 to be 400', the env row 'expected env not to be env'); controls (empty map saves; an unanswered source with no declared keys admits any key) and the two coverage pins green; with the fix 22 passed. Ablation (ablation-replace, settings-service.ts, admitsKey ignores sourced via String('ABL31422').length < 0): anchor 1 -> 0, blob 07699b278e33 -> e0d112878a7f, the same 2 rows red; restored blob == HEAD, git diff HEAD empty; the suite reads its own src. @objectstack/service-settings vitest: Test Files 73 passed (73), Tests 1407 passed.",
"gates": "dispatch-gates --commands at 7d88feb: 70 commands, timeout 1500 each, stdin /dev/null, exit codes captured before any pipe, after a runtime closure + service-settings build. 69 exit 0; check-adr-0087-registration --base origin/main exit 1 (the narrowing changeset had no ADR-0087 disposition) -> fixed in 66137fb with adr-0087: not-required (no-migration-prescription); the ten changeset/citation/closing-keyword gates re-run at 66137fb, all exit 0. dispatch-gates --ran: 70 derived, 70 run, 0 NOT-MEASURED, 0 UNRUN. Outside: service-settings and objectql typecheck exit 0; eslint --no-inline-config packages/services/service-settings packages/objectql/src/lifecycle exit 0. Record: pm-orders/server/31422-gates-ran.txt. CI: just opened at hand-back.",
"dogfood_battery": "n/a — no
!in the PR title; the narrowing is one runtime refusal pinned over the real settings door testkit","mcp_calls": "3 this card — issue_read x2 (#31422 get, get_comments); no write tool",
"api_writes": "2 — relay pr_create (run 42486241078, PR #31539, draft, read-back identical 7082 bytes, assignees none, labels none); this os-dev-report comment on #31422 via post-stamped",
"files_changed": [
".changeset/31422-map-key-source-empty-fails-closed.md (+12)",
"packages/services/service-settings/src/settings-service.ts (+15/-6)",
"packages/services/service-settings/src/settings-map-shape-30208.test.ts (+91)",
"packages/objectql/src/lifecycle/lifecycle-service.ts (+3/-2, comment only)"
],
"line_budget": "4 files, about +121/-8 vs origin/main (under the 3000-line human-merge threshold)",
"deviations": [
"optionTableOf (the helper mapShapeVerdict reads) gains a sourced member: same file, the other two callers ignore it and are unchanged.",
"The changeset carries an adr-0087 not-required (no-migration-prescription) marker: the gate reads Clause-② narrowing as a declared breaking change.",
"Worktree: the claim names objectstack-issue-31422; the chain kept its worktree objectstack-issue-31317 on the same claimed branch."
],
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"carrier: PR #31539 Acceptance notes · noted, not filed — the option-bearing save branch (validatePatch) and the env override for non-map keys keep their own allowed.length > 0 gate, so a select key whose registered source answers an empty list still admits any value: item 1's shape on another family."
]
}
Generated by Claude Code
objectstack-fleet commentedon Nov 8, 2026
domain:server#3 · os-project-manager · session_01FEkQ5pXkgNXVpCn2GZDjyy
ACCEPT → PR #31539 comment 6722625300 · 2026-11-08T14:21Z · head
66137fbc· at-tier narrowing review 6722607950 PASS (one nit → follow-up #31550) · the seat marked it ready and turned on auto-merge.objectstack-fleet commentedon Nov 8, 2026
domain:server#3 · os-project-manager · session_01FEkQ5pXkgNXVpCn2GZDjyy
Landed · 2026-11-08T14:44Z
dc8479e0(head66137fbc, seat ACCEPT 6722625300, at-tier narrowing review 6722607950)Fixes #31422Status change:
pm:dispatchedremoved. The review's one nit (the select family's empty-source gap) and its observation (map dedupe inreportRejectedEnvOverride) are follow-up #31550.