Repository navigation
metadata-protocol, objectql: the two non-blocking contract-review notes on #27535 — guard the promotion door's permission-set name judge with draftForGate, and note the metadata-protocol caller on the registry member #27707
Description
domain:devx#1 · sunhaolin · session_017xRBcVy4VXmkQjn2MGeaWk
Filing gate: ③ a maintainer-directed task. The maintainer, in the PM session session_017xRBcVy4VXmkQjn2MGeaWk on 2026-10-29, said (verbatim): 「其他任务正常提交 issue」. This card carries the two non-blocking notes the record left on PR #27535 (merged), filed as a normal card.
Business reason: #27535 landed with two notes its contract review (comment 6501292842, PASS) marked non-blocking and the seat's ACCEPT (comment 6502611799) set aside "for a follow-up, not held against landing". One makes the promotion door's refusals follow the order its sibling judges already use; the other makes a future rename of a private registry member fail at its source.
Reader: the dev who takes this card from the queue (packages/metadata-protocol/src/protocol.ts and packages/objectql/src/registry.ts).
Dedupe: searched the open and closed issues (GitHub issue search scoped to objectstack-ai/objectstack) for "permission set name collision promotion draftForGate registry packageHeldSecurityCatalogNames" (6 hits) and "NAMESPACE_CONFLICT permission set publish NO_DRAFT refusal order follow-up 27535" (24 hits). None covers either note.
What to change
Note 2: the promotion-door ordering
The ACCEPT names it: "Note 2: the promotion-door ordering, a refusal-for-refusal change with no accept-set effect." The contract review (should-fix 2, "Refusal order at the promotion"): "permissionSetNameCollisionRefusal at :23044 runs unguarded, while its three siblings (permissionSetOwnerBindingRefusal, defaultRecordPageSaveRefusal, the name judge) run under if (draftForGate), and repo.promoteDraft's NO_DRAFT (sys-metadata-repository.ts:1552) comes after. […] Refusal for refusal, no accept-set effect; guard it with draftForGate like the siblings so the first fact the caller is told is the one about its own state, or record in the comment why this judge runs first."
At origin/main a75ddba1, packages/metadata-protocol/src/protocol.ts:
:22935:draftForGateis read.:23019(name judge) and:23031(permissionSetOwnerBindingRefusal): each runs underif (draftForGate).:23046–23051: thepermissionSetNameCollisionRefusalcall sits in a bare{ … }block, not underif (draftForGate).:23058:defaultRecordPageSaveRefusalruns underif (draftForGate).packages/metadata-protocol/src/sys-metadata-repository.ts:1570:promoteDraftsetsNO_DRAFT(the review cited:1552at the PR head).
The ask: put the :23046 block under if (draftForGate) like its siblings, so a promotion with no draft at the address gets the repository's NO_DRAFT answer first. If there is a reason it must run first, record that reason in the comment above it (:23037–23045) instead. Either way, add one pin for the order: a per-item publish with no draft answers NO_DRAFT, with the existing collision pins still green.
Note 3: a one-line comment on the registry member
The ACCEPT names it: "Note 3: a one-line comment on the registry member." The contract review (should-fix 3, "Private member reached structurally"): "packageHeldSecurityCatalogNames is private on SchemaRegistry, and the door reaches it by duck-typing (read.call(registry)), as the exported findPackageHeldSecurityCatalogNames itself does with bracket access. […] a one-line note on the registry member naming the metadata-protocol caller would make a future rename red at the source as well as in the test."
At origin/main a75ddba1:
packages/objectql/src/registry.ts:3503:private packageHeldSecurityCatalogNames(); its doc comment (:3491–3502) namesfindPackageHeldSecurityCatalogNames(:2370–2374) as its exporter but not the metadata-protocol caller.packages/metadata-protocol/src/protocol-save-door-refusals.ts:283and:296–297: the caller that reads the member by name.
The ask: add one line to the doc comment at registry.ts:3491–3502 naming protocol-save-door-refusals.ts (registryPackageHoldersOf) as a by-name reader, so renaming the member means updating that caller and its objectql test.
Out of scope: any change to what the doors accept or refuse. Both notes are ordering and comment work only.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 29, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 真挡得住的权限 | none | P②
Triage: p2 ·
domain:server·area:access· 2026-10-29T19:55Z⛔ Not a claim.
metadata-protocol,objectql). GuardpermissionSetNameCollisionRefusalwithdraftForGatelike its three siblings: a refusal-for-refusal reorder with no accept-set effect, pinned by the order the caller is told. The registry member gets its caller note.protocol.tsis a hot file, so run the in-flight intersection check.pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
Claim: PM loop round 4
Session:
session_01L6bhgUCizoYWQVJWjhdR6FAccount:
os-salesBranch:
claude/issue-27707-promotion-guard-orderWorktree:
objectstack-issue-27707Domain:
domain:serverSeat:
domain:server#2File surface:
packages/metadata-protocol/src/protocol.ts(the promotion door'spermissionSetNameCollisionRefusalcall only),packages/objectql/src/registry.ts(a caller note on the private member only), their tests,.changeset/*(stop on breach; explain in the report)Container & model:
S,mode:subagent,model: default (dispatch-gates --tier: no path-derived mandate),effort: mediumClause-②: no
Metadata-protocol: none
Responsibility:
metadata-protocol promotion door refusal order (this lane) | the three sibling judges already run under draftForGate | every caller promoting a permission setThread-read: 6506884031
Premise: holds (read on main
6d9419bd2026-10-29T23:00Z:permissionSetNameCollisionRefusalruns outside theif (draftForGate)block its siblings use)Serial constraints cleared:
protocol.tsis also edited by this seat's #27444 (draft-verdict wiring, a different hunk);git merge-treeagainst it beforepr_create.objectstack-fleet commentedon Oct 30, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
os-dev-report
{ "issue": 27707, "status": "done", "branch": "claude/issue-27707-promotion-guard-order", "pr": "https://github.com/objectstack-ai/objectstack/pull/27825", "session": "session_01L6bhgUCizoYWQVJWjhdR6F", "premise_still_valid": true, "summary": "The draft promotion in packages/metadata-protocol/src/protocol.ts now runs permissionSetNameCollisionRefusal under if (draftForGate), like its four sibling draft judges. Promoting with no draft while another owner holds the name used to answer NAMESPACE_CONFLICT/422 and now answers NO_DRAFT/404. The accept set does not change: with no judged draft, repo.promoteDraft gets expectedDraftHash null and answers NO_DRAFT or DraftConflictError, so it never succeeds either way; with a draft, the same refusals run in the same order (Clause-②: no confirmed). The private SchemaRegistry.packageHeldSecurityCatalogNames doc comment in packages/objectql/src/registry.ts now names its by-name metadata-protocol caller (registryPackageHoldersOf) and the objectql test. This is a comment only. The card assignee was not touched. Head 98bfa9de includes one merge of origin/main 8f25cee2.", "tests": "New pin in protocol.permission-set-name-cross-package.test.ts through the real publishMetaItem door. Red first at 83e4daac (Expected NO_DRAFT, Received NAMESPACE_CONFLICT; 1 failed, 17 passed), green after the fix (18/18). Controls: the existing draft-plus-collision pins (publishMetaItem and publishPackageDrafts answer NAMESPACE_CONFLICT) and the clean-promote CONTROL stay green. Ablation via scripts/ablation-replace.mjs: anchor x1 to x0, blob 5fb0787b to 2d3ef72c, the new pin goes red, restored blob == HEAD 5fb0787b with git diff HEAD empty. The first ablation attempt was refused by the tool before running (the replacement was a substring of the anchor), so it was a no-op and is not a reading. At 98bfa9de: metadata-protocol test 280 files passed, 4 skipped, 21803 tests passed, 26 skipped, exit 0; objectql test 490/490 files, 9244/9244 tests, exit 0; typecheck for both packages exit 0.", "gates": "At 98bfa9de: dispatch-gates --commands derived 63 families. 61 exited 0 on the first sweep. check:dual-build-cjs-loads and check:type-check-debt exited 3 (PREREQUISITE NOT MET: no dist, and lock queue-timeout); both exited 0 when re-run after the full build. --ran reconciliation: 63 run, 0 NOT-MEASURED (a derived zero, every line carries an exit code). eslint --no-inline-config on packages/metadata-protocol and packages/objectql exited 0 each. git merge-tree against #27780 (claude/issue-27444-draft-verdict-package-prefix) is clean; no other open PR touches these files. CI check-runs on 98bfa9de: in_progress when this report was written, not awaited per contract.", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "0", "api_writes": "2 — relay pr_create (repository_dispatch to fleet-write, created PR #27825 as a draft with no assignees, reviewers or labels); post-stamped comment POST /repos/objectstack-ai/objectstack/issues/27707/comments (this report). git push is not counted.", "line_budget": "33 insertions, 1 deletion across 4 files", "deviations": [ "Commit trailers use the model-free pair AGENTS.md requires, not the harness model-named Co-Authored-By.", "post-stamped requires the seat signature as the first line, so the os-dev-report marker is on line two.", "The objectql change is comment-only and its suite ran in full, but the objectql test:repo project was not run locally; that is left to CI Test Core." ], "files_changed": [ "packages/metadata-protocol/src/protocol.ts", "packages/metadata-protocol/src/protocol.permission-set-name-cross-package.test.ts", "packages/objectql/src/registry.ts", ".changeset/27707-promotion-no-draft-before-name-collision.md" ], "open_questions": [], "out_of_scope_findings": [ "none — carrier: none" ] }objectstack-fleet commentedon Oct 30, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
ACCEPT → PR #27825 · head
98bfa9de· 2026-10-30T01:21Z · landing sent.