Skip to content

ADR-0138 E2: the closed list of doors recorded in the conformance matrix, denial by domain, and D2b's publish refusal (D2, D2b) #23225

Closed

Description

@objectstack-fleet

This card carries execution card E2 of ADR-0138, which is now Accepted (docs/adr/0138-guest-model-anonymous-principal-doors-grants-and-organization.md). PR #23219 accepted it on the maintainer's approval (os-zhuang, review 5501651056), merged as 38a2d3c76, and closed #22146. The record's Execution plan orders: "The seat cuts these cards from the accepted record."

Filed by domain:spec seat 2 (#18549) · os-justin · session session_01BRQ9Mp1yH7R1az6MaUM18H, at #22146's landing. ⛔ Not a claim. Reader who acts: triage, to grade and route. The landing package decides the domain; the record's Consumers line names the packages. Then the owning seat.

The record's row, verbatim

Card Decisions What lands Pins ADR-0087 disposition Order
E2 — the closed list and denial by domain, and D2b D2, D2b, D9's pointer the six classes recorded in the conformance matrix; any face of a listed domain that still decides per face moved to the domain-level deny; D2b's publish refusal in both directions, with its prescription; the module doc of assemble-execution-context.ts cites this record a new route in a governed domain answers 401; each class's admission; D2b refused in both directions (an anonymous flow endpoint naming a system flow, and a flow set to system while an anonymous endpoint names it), each refusal carrying the prescription registered, one new semantic entry: the publish refusal narrows the accept set, and no conversion can choose a posture for the author after acceptance

Where the record decides it

  • D2 — The closed list of doors — Enforced by, quoted: "shouldDenyAnonymous at each domain's entry, today; and the authorization conformance matrix (packages/qa/dogfood/test/authz-conformance.matrix.ts#AUTHZ_CONFORMANCE), whose rows already fail on a deleted deny call or an unclassified route. Card E2 records the six classes there. ⛔ No new gate script."
  • D2b — The anonymous door × elevated flow combination: a publish refusal — Enforced by, quoted: "the publish path, in both directions. The cross-item read sits where both items are readable at publish, beside the per-endpoint packages/spec/src/api/endpoint-publish-gate.ts#policyGate, which reads one endpoint only. The refusal narrows the accept set, so card E2 registers one ADR-0087 semantic entry with it. Until E2 lands, ruling A on security(rest, runtime): an anonymous request at an app-declared authRequired: false endpoint executes principal-less — execute it as the guest principal (ruled C), never principal-less, never system #22147 governs and today's combination stands, unchanged."

⛔ Nothing here re-decides the record. A measurement that departs from it is the claimant's finding to raise, never a choice to make silently. D3 item 6 says so for E1's row-scope pins.

Acceptance

Each pin in the row above lands with a negative control that fails when the behavior is removed. The changeset carries the row's ADR-0087 disposition.

Dedupe

MCP search_issues on this repo, closed included, run in the filing act: ADR-0138 execution card guest anchor bindings grants channel E1 E2 E3 E4 guest recipient sys_record_share → 6 hits. Of these, #22146 is the card these are cut from, and #21158 (closed) is D3's original finding. None is an execution card. retire sys_record_share guest recipient select option → 0 hits.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, enhancement · security · priority:p1 · target:v18 · domain:cli · area:access, into pm:queue. ADR-0138 E2

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-13T10:58Z. ⛔ Not a claim, ⛔ not a dispatch.

  2. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 17 (#23225 slice 1: ADR-0138 D2, the closed list of six door classes recorded in the conformance matrix, and denial by domain; D2b is slice 2) · 2026-10-13T11:30Z
    Session: session_01VoSxBQujKLZKPwK2u5ehQ6
    Account: os-steve (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-23225-s1-closed-door-list
    Worktree: objectstack-issue-23225-s1
    Domain: domain:cli
    Seat: domain:cli#2 (seat post #22648)
    File surface (read on origin/main 6f4c518b01; stop on a breach and explain it in the report):

    • packages/qa/dogfood/test/authz-conformance.matrix.ts (AUTHZ_CONFORMANCE) records ADR-0138 D2's six door classes, each row naming its enforcing site as the record's table does. Its tests in packages/qa/dogfood/test/ carry the row's pins, each with a negative control:
      • a new route in a governed domain answers 401;
      • each class's admission.
    • Domain-level deny: any face of a governed domain that still decides anonymous denial per face moves to the domain's first statement (shouldDenyAnonymous), in packages/runtime (the dispatcher's domains) or packages/rest. The dev measures which faces those are first.
    • packages/core/src/security/assemble-execution-context.ts: the module doc cites ADR-0138 (the row's "D9's pointer"). Doc only, and cross-lane (domain:engine): declared on [PM seat] domain:engine — 🟢 sunhaolin #6367 in this act.
    • A changeset only if a published package's code moves, graded on facts.
    • ⛔ Not touched:
      • D2b, which is slice 2: packages/spec/src/api/endpoint-publish-gate.ts, @objectstack/service-automation, the ADR-0087 semantic entry and metadata-protocol's publish path;
      • no new gate script (the record: "⛔ No new gate script");
      • no ADR text.

    Direction, verbatim from the record (ADR-0138 D2, Enforced by): "shouldDenyAnonymous at each domain's entry, today; and the authorization conformance matrix (packages/qa/dogfood/test/authz-conformance.matrix.ts#AUTHZ_CONFORMANCE), whose rows already fail on a deleted deny call or an unclassified route. Card E2 records the six classes there." ⛔ Nothing here re-decides the record (triage 6144040679). A measurement that departs from it is raised, never chosen silently.

    Container & model: M, judged (six class rows with their pins, a measured set of domain-level moves), mode:subagent, model: default tier (dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate"). The record measured that everything-else answers 401 throughout on main (G2), so a per-face to domain-level move is answer-neutral, and Clause-②: no with no review owed. If the dev measures a move that changes an answer, the slice becomes Clause-②: no (narrowing) and one CONTRACT_REVIEW_TIER review is owed before the queue. check-governed-merges --test over the claimed paths: not governed.
    Clause-②: no
    Responsibility: n/a — not a defect card (an accepted record's execution card)
    Thread-read: 6144040679
    Serial constraints cleared:


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 23225,
    "status": "done",
    "branch": "claude/issue-23225-s1-closed-door-list",
    "pr": "#23286",
    "session": "session_01VoSxBQujKLZKPwK2u5ehQ6 — the dispatching seat's id (mode:subagent)",
    "premise_still_valid": true,
    "summary": "Slice 1 (D2) of ADR-0138 card E2. The six D2 door classes are recorded in AUTHZ_CONFORMANCE as enforced, HIGH_RISK rows naming D2's enforcing sites, each proven by the existing anonymous-door-sweep.dogfood.test.ts (which now claims exactly those six), with a new matrix block holding the list closed and negative controls for a seventh, a missing, an unproven or a renamed row. Every dispatcher domain's anonymous decision was censused: /notifications, /keys and /share-links decided it per face below another answer and now decide it as their first statement (share-links keeps the class 2 token faces), each with a floor row, a gate pin and its ledger key classified (baseline 30 to 26, probe census re-measured to 23 probes / 18 files / 29 keys). The module doc of assemble-execution-context.ts cites ADR-0138 D9 and the D2 list (doc only). D2b is untouched (slice 2).",
    "six_rows": [
    {
    "row": "anonymous-door-public-form",
    "class": 1,
    "enforcement_names": [
    "RestServer.registerFormEndpoints",
    "publicFormGrant branch of SecurityPlugin"
    ],
    "proof": "anonymous-door-sweep.dogfood.test.ts (class 1: resolve and submit of an anonymous-sharing form admitted; an undeclared slug refused)",
    "covers": [
    "rest-family:rest-route-ledger.ts:forms"
    ]
    },
    {
    "row": "anonymous-door-share-link",
    "class": 2,
    "enforcement_names": [
    "registerShareLinkRoutes (plugin-sharing)"
    ],
    "proof": "same file (class 2: a minted token resolves its one record; revoked, refused)",
    "covers": []
    },
    {
    "row": "anonymous-door-public-book",
    "class": 3,
    "enforcement_names": [
    "isPublicAudienceRead (rest meta-item-read-gate)"
    ],
    "proof": "same file (class 3: public book, tree and claimed doc admitted; org book, org doc and a write refused 401)",
    "covers": []
    },
    {
    "row": "anonymous-door-endpoint-object-operation",
    "class": 4,
    "enforcement_names": [
    "resolveExecutionContext (guest envelope, D9)",
    "the CRUD gate"
    ],
    "proof": "same file (class 4: guest envelope reaches the engine once and gets D3's empty-state refusal; the gated sibling 401)",
    "covers": []
    },
    {
    "row": "anonymous-door-endpoint-flow",
    "class": 5,
    "enforcement_names": [
    "resolveExecutionContext",
    "@objectstack/service-automation"
    ],
    "proof": "same file (class 5: the flow runs once as the guest and serves its output only; the gated sibling 401)",
    "covers": []
    },
    {
    "row": "anonymous-door-public-read-file",
    "class": 6,
    "enforcement_names": [
    "registerStorageRoutes",
    "authorizeDownload"
    ],
    "proof": "same file (class 6: a public_read file's bytes served at both doors; a private file 401 at both)",
    "covers": []
    }
    ],
    "new_route_pin": "Through the matrix's own two mechanisms. (1) A new route FAMILY or dispatcher DOMAIN mints a ledger key and is UNCLASSIFIED: already enforced; negative controls are the existing population-block cases, re-measured by scratch ablation 3 (a new ledger domain: UNCLASSIFIED red). (2) A route INSIDE an existing domain mints no key (the matrix header's measured bound, re-measured by scratch ablation 4: zero UNCLASSIFIED); it is held by the domain floor being the first statement: the new negative control deletes each of the 8 gate-pinned floors in turn and asserts STALE, a second case asserts the minted gate pins are exactly those 8, and each moved domain's unit suite (runtime/src/domains/domain-floor-anonymous-deny.test.ts, case B) asserts a path or verb no face serves yet is refused 401 before route selection. No new gate script.",
    "census": {
    "domain_level_first_statement": [
    "/actions",
    "/automation (with the ADR-0069 allow-list row for the signed inbound hook)",
    "/analytics",
    "/packages (23214 file)",
    "/i18n (23214 file)",
    "/security (23214 file)",
    "/meta (23214 file; class 3 carve-out through isPublicAudienceRead)"
    ],
    "moved_here": [
    "/notifications (decided below the inbox capability probe)",
    "/keys (below the method check)",
    "/share-links (inside the management faces, below the service probe; class 2 token faces kept as the one exception)"
    ],
    "deferred_to_after_23214_slice_1": [
    "/ui (runtime/src/domains/ui.ts: handleUiRequest makes no anonymous decision of its own)",
    "/data (runtime/src/domains/data.ts: handleDataRequest makes no anonymous decision of its own; anonymity is left to the guest envelope at the CRUD gate)"
    ],
    "not_moved_raised": [
    "/ai (handleAIRequest decides once at entry but applies it per route: a registered route declaring auth false is admitted to an anonymous caller; see open_questions)",
    "/webhooks/redeliver (decided by the plugin-webhooks member by design, byte parity with the self-hosted mount; a domain floor would re-decide that)",
    "REST families (each handler opens with enforceAuth: per route by construction; only the metadata family has a family-wide guarded registrar; read as outside D2's dispatcher-domain sense, see open_questions)"
    ],
    "outside_by_construction": [
    "/mcp (D2's own outside family; its 401 follows the enabled and service probes)",
    "/mcp/skill",
    "/approvals/act",
    "/auth",
    "/health",
    "/ready",
    "/discovery",
    "/.well-known/objectstack",
    "the declared-endpoint fallback seam (classes 4 and 5 and authRequired true endpoints, decided per declaration by endpoint-policy)"
    ]
    },
    "changeset_decision": ".changeset/23225-domain-floor-anonymous-deny.md: '@objectstack/runtime': minor, Clause-②: no (narrowing), ADR-0087 not-required (no-migration-prescription). Measured: runtime's published code moves (three domain files); @objectstack/core's published dist/index.d.ts carries the new doc comment (3 ADR-0138 hits, all from this file) but a comment is fast-track no-release, so no core changeset; packages/qa/dogfood is private. Deciding gates: check:adr-0087-registration --base origin/main, check:changeset-no-major --base origin/main, check:empty-changeset --base origin/main, all exit 0 at 8f838b7; CI's Check Changeset counts the one added changeset. PR body line 2 is Clause-②: no (narrowing); one CONTRACT_REVIEW_TIER review is owed before the queue, per the claim's rule for a move that changes an answer.",
    "departures_raised": [
    "Grade: the three moves change what an anonymous caller is answered on compositions the record did not measure (501 to 401 with no inbox or no share-link service; 405 to 401 on the key mint). The claim's rule makes that the narrowing grade; execution-duties.md says runtime security behaviour is not Clause ② and Clause ② is untouched by pulling back to a declared contract. I followed the claim (narrowing, review owed) and name the tension for the seat.",
    "The /ai route-level auth false opt-out is a door mechanism D2 does not list; not moved, raised as needs_decision in open_questions.",
    "REST families decide per route; whether D2's 'domain' covers them is raised in open_questions; nothing moved there.",
    "Two #23214-held domain files carry no domain-level decision at all; deferred, with the readings to the seat privately."
    ],
    "tests": "All at the final tree 8f838b7 unless stated. (a) closure build pnpm turbo run build --filter=@objectstack/dogfood^... 64/64 tasks; @objectstack/dogfood typecheck green; vitest test/anonymous-door-sweep.dogfood.test.ts test/authz-conformance.test.ts test/authz-probe-blind-spot.test.ts: Test Files 3 passed (3), Tests 124 passed (124); os-verify-lock VERDICT command-exit 0. (b) runtime vitest over the 8 touched or ordering-sensitive files (domain-floor-anonymous-deny, domain-handler-registry, share-links-public-cache-headers, notification-schema-conformance, declared-capability-absence-warn-once, dispatcher-5xx-always-logged, http-dispatcher.keys, http-dispatcher): Test Files 8 passed (8), Tests 405 passed (405); runtime typecheck (tsc + check:test-typecheck OK: 27 files / 190 errors / 68 pinned held) and core typecheck green; VERDICT command-exit 0. (c) full runtime --project local at 2d84837: Test Files 368 passed (368), Tests 5269 passed, 19 skipped; VERDICT command-exit 0; the later merge touched no runtime, rest, core or dogfood file. Ablations at 2d84837, each through scripts/ablation-replace.mjs with a restore trap under os-verify-lock: (1) class 3 admission removed in isPublicAudienceRead (anchor 1 to 0, blob 41952c07b37a to 41c05ff602de, rest rebuilt, dist preflight marker in 2 built files): sweep 2 failed / 18 passed, exactly class 3's two admitted legs; restore: blob == HEAD, rebuilt, preflight --absent (0 of 6 files, tree clean), sweep 20/20. (2) /notifications floor condition replaced by false (marker 1, gate spelling 0): runtime unit 14 failed / 33 passed (every notifications A/B/C case, both anonymous shapes); matrix 9 failed / 95 passed incl. STALE covers notifications:domains/notifications.ts:anonymous-gate and DEAD PROBE; restore: blob == HEAD, 47/47 and 104/104. (3) scratch new ledger domain /anon-scratch: UNCLASSIFIED surface dispatcher-domain:route-ledger.ts:/anon-scratch (8 failed / 53). (4) scratch route inside /notifications: zero UNCLASSIFIED, only the docblock row-count pin (2 failed). First attempt of (3) and (4) was a no-op the tool refused (the replacement contained its own anchor: anchor count 1 to 1); re-run with a different anchor, readings above are the second run. Lint, proven narrowing at 8f838b7: eslint --no-inline-config --format json over the 16 changed .ts files: 16 files, 0 errors, 0 warnings, 0 reported ignored; no type-aware linting in eslint.config.mjs, so untouched files' verdicts cannot move; repo-wide pnpm lint left to CI.",
    "gates": {
    "head": "8f838b7460",
    "derived": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths): 69 commands, identical to the derivation at af299c2",
    "reconcile": "dispatch-gates --ran: 69 derived, 69 run, 0 NOT-MEASURED, 0 UNRUN",
    "results": [
    {
    "command": "node scripts/check-adr-0087-registration.mjs --base origin/main",
    "exit": 0,
    "verdict_line": "::notice file=.changeset/23225-domain-floor-anonymous-deny.md::ADR-0087 exemption (no-migration-prescription): No metadata moves: no spec key, authorable spelling, object definitio"
    },
    {
    "command": "node scripts/check-adr-0087-registration.mjs --self-test",
    "exit": 0,
    "verdict_line": "✓ check-adr-0087-registration --self-test: 548 assertions over real temp git repos (real scan()/assertInputs() path)"
    },
    {
    "command": "node scripts/check-changeset-no-major.mjs --base origin/main",
    "exit": 0,
    "verdict_line": "✓ Protocol lockstep: PROTOCOL_VERSION major 18, @objectstack/spec@18.0.0-next.0 — not one major ahead of a stable version, so there is no pre-mode exception to judge."
    },
    {
    "command": "node scripts/check-changeset-no-major.mjs --self-test",
    "exit": 0,
    "verdict_line": "✓ check-changeset-no-major --self-test: 401 assertions (frontmatter dialects measured against @changesets/parse + the pre/exit exemption switch in both directions + the #7005 diff "
    },
    {
    "command": "node scripts/check-ci-filter-parity.mjs",
    "exit": 0,
    "verdict_line": "OK: all 21 build input(s) turbo.json declares outside the packages (itself, 2 globalDependencies, and every $TURBO_ROOT$ input of a build Build Core runs) and all 11 script(s) th"
    },
    {
    "command": "node scripts/check-closing-keyword-parity.mjs",
    "exit": 0,
    "verdict_line": " • packages/spec/CHANGELOG.md -- 8701420 bytes exceeds the sweep's 2097152-byte cutoff for UNREGISTERED files"
    },
    {
    "command": "node scripts/check-closing-keyword-parity.mjs --self-test",
    "exit": 0,
    "verdict_line": "✓ check-closing-keyword-parity --self-test: 40 assertions, 5 mutations of the shipped parsers each driven to red."
    },
    {
    "command": "node scripts/check-comment-mask-adoption.mjs",
    "exit": 0,
    "verdict_line": "OK check:comment-mask-adoption — 14 private comment-stripper(s) under packages/** + examples/, all 14 recorded and every recorded row still reached (13 unconverted, 1 specimen)."
    },
    {
    "command": "node scripts/check-comment-mask-adoption.mjs --self-test",
    "exit": 0,
    "verdict_line": "PASS check-comment-mask-adoption --self-test (0 failure(s))"
    },
    {
    "command": "node scripts/check-comment-mask-corpus.mjs",
    "exit": 0,
    "verdict_line": "✓ comment-mask corpus sweep [scripts/js-comment-mask.mjs]: 8976 files, 0 disagree, 0 unparseable, 140.0s (comparator self-test: 26 cases pass)."
    },
    {
    "command": "node scripts/check-dev-prereqs.mjs --self-test",
    "exit": 0,
    "verdict_line": "✓ check:dev-prereqs --self-test — every verdict reachable, exclusions and freshness coverage pinned (19 batteries, 76 cases), plus the shared workspace enumerator."
    },
    {
    "command": "node scripts/check-dts-emitted.mjs --self-test",
    "exit": 0,
    "verdict_line": "check-dts-emitted self-test: all assertions passed."
    },
    {
    "command": "node scripts/check-empty-changeset.mjs --base origin/main",
    "exit": 0,
    "verdict_line": "✓ No changeset from the merge base modified or deleted by this diff (#17712)."
    },
    {
    "command": "node scripts/check-empty-changeset.mjs --self-test",
    "exit": 0,
    "verdict_line": "✓ check-empty-changeset --self-test: 170 assertions over real temp git repos (real scan() path)"
    },
    {
    "command": "node scripts/check-issue-citations.mjs",
    "exit": 0,
    "verdict_line": "✅ check-issue-citations: no issue citations added against 09ef77c (4 file(s) read)."
    },
    {
    "command": "node scripts/check-keyed-text-bounds.mjs",
    "exit": 0,
    "verdict_line": " ⚠ The delta is information, not a verdict — this population grows AND shrinks for good reasons, and only the floors decide. Reproduce the record: see this file's header."
    },
    {
    "command": "node scripts/check-keyed-text-bounds.mjs --self-test",
    "exit": 0,
    "verdict_line": "PASS check-keyed-text-bounds --self-test (0 failure(s))"
    },
    {
    "command": "node scripts/check-platform-object-tenancy-census.mjs",
    "exit": 0,
    "verdict_line": "✓ platform-object tenancy census matches the tree: 87 platform-namespace objects, 52 in the machinery's reach, 35 outside it, every exclusion explained by a declaration on its own "
    },
    {
    "command": "node scripts/check-platform-object-tenancy-census.mjs --self-test",
    "exit": 0,
    "verdict_line": "✓ check-platform-object-tenancy-census self-test: all checks pass (87 objects, 35 outside the machinery)"
    },
    {
    "command": "node scripts/check-plugin-teardown-shape.mjs",
    "exit": 0,
    "verdict_line": "✓ check:plugin-teardown-shape: 77 Plugin implementation(s) across 8390 source(s) under packages/
    ; every teardown-shaped method (stop / shutdown / close / dispose) sits beside a r"
    },
    {
    "command": "node scripts/check-plugin-teardown-shape.mjs --self-test",
    "exit": 0,
    "verdict_line": "✓ check-plugin-teardown-shape self-test: 48 cases pass (real pre-#10375 fixture reds, the repaired file and both delegating-alias directions stay green, every roster name reds, eve"
    },
    {
    "command": "node scripts/check-registry-log-declared.mjs",
    "exit": 0,
    "verdict_line": " payload, not read (a dir the package's own tsconfig.json excludes): packages/create-objectstack/src/templates"
    },
    {
    "command": "node scripts/check-registry-log-declared.mjs --self-test",
    "exit": 0,
    "verdict_line": "self-test OK: 17 cases + level-vocabulary read + population declaration + real-tree selection floor."
    },
    {
    "command": "node scripts/check-rest-log-spy-declared.mjs",
    "exit": 0,
    "verdict_line": "OK: 32 of 284 test file(s) beside packages/rest/src/log.ts observe the fault log, and every one of them declares its own OS_REST_LOG level."
    },
    {
    "command": "node scripts/check-rest-log-spy-declared.mjs --self-test",
    "exit": 0,
    "verdict_line": "check-rest-log-spy-declared self-test reached its verdict: 23 case(s), 0 failure(s)."
    },
    {
    "command": "node scripts/check-system-context-census.mjs",
    "exit": 0,
    "verdict_line": "check-system-context-census: OK — 134 elevation read sites in 21 packages across 61 files, living in 114 symbol(s); the page cites 127 symbol(s) against 127 required, over 150 anch"
    },
    {
    "command": "node scripts/check-system-context-census.mjs --self-test",
    "exit": 0,
    "verdict_line": "check-system-context-census --self-test: all cases passed"
    },
    {
    "command": "node scripts/check-undeclared-dep-imports.mjs",
    "exit": 0,
    "verdict_line": " ⚠ The delta is information, not a verdict — the floors are >= and cannot see an upward drift at all, which is why it is PRINTED. Reproduce the record: see this file's header."
    },
    {
    "command": "node scripts/check-undeclared-dep-imports.mjs --self-test",
    "exit": 0,
    "verdict_line": "PASS check-undeclared-dep-imports --self-test (0 failure(s))"
    },
    {
    "command": "node scripts/docs-audit/check-affected-docs.mjs",
    "exit": 0,
    "verdict_line": " → the unreachable rows themselves: this command with --json"
    },
    {
    "command": "node scripts/docs-audit/check-drift-comment.mjs",
    "exit": 0,
    "verdict_line": "✓ check-drift-comment: 66 cases pass across 5 fixture diff(s)."
    },
    {
    "command": "node scripts/pm/release-rehearsal-clone.mjs --self-test",
    "exit": 0,
    "verdict_line": "✓ self-test passed"
    },
    {
    "command": "node scripts/release-pending-publish.mjs --self-test",
    "exit": 0,
    "verdict_line": "✓ release-pending-publish self-test: 92 cases across 22 batteries pass."
    },
    {
    "command": "pnpm --filter @objectstack/spec run check:duration-unit-keys",
    "exit": 0,
    "verdict_line": "✓ check:duration-unit-keys — 197 unit-declaring numeric key(s) across 3082 source file(s) all carry their unit in the key name (or in a sibling unit, or under a declared exemptio"
    },
    {
    "command": "pnpm --filter @objectstack/spec run check:empty-state",
    "exit": 0,
    "verdict_line": "✓ all classified (2 closed, 2 open, 4 output, 9 scope)"
    },
    {
    "command": "pnpm --filter @objectstack/spec run check:liveness",
    "exit": 0,
    "verdict_line": " (not a completeness claim about the 529 child key(s) under the declared blanket verdicts above — those are recorded, not classified.)"
    },
    {
    "command": "pnpm --filter @objectstack/spec run check:strictness-ledger",
    "exit": 0,
    "verdict_line": " untriaged: 1212 object site(s) across 9 director(ies)"
    },
    {
    "command": "pnpm --filter @objectstack/spec run check:variant-docs",
    "exit": 0,
    "verdict_line": "✓ variant/doc gate: 18 discriminated union(s) — 8 governed (every variant mentioned in a bound doc), 10 exempt."
    },
    {
    "command": "pnpm check:changeset-gate-self-tests",
    "exit": 0,
    "verdict_line": "✓ check-changeset-no-major --self-test: 401 assertions (frontmatter dialects measured against @changesets/parse + the pre/exit exemption switch in both directions + the #7005 diff "
    },
    {
    "command": "pnpm check:cross-package-test-inputs",
    "exit": 0,
    "verdict_line": "OK: 30 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob (6 of them on a split "test:repo" task); 13 walked root(s) judged, 3 on ACCEPTED_"
    },
    {
    "command": "pnpm check:dispatcher-error-vocabulary",
    "exit": 0,
    "verdict_line": " [#15723] the ARGUMENT POSITION of new APIError( … ) and APIError.from( … ) IS now in this gate's population — the apierrorarg shape. APIError.from copies the record's code "
    },
    {
    "command": "pnpm check:doc-authoring",
    "exit": 0,
    "verdict_line": "✓ doc authoring guard: sibling-package prose ids hold the baseline — 0 pinned site(s) across 0 file(s), 95099 string(s) read in 1325 parsed source(s) (seen floor 40000 strings / 60"
    },
    {
    "command": "pnpm check:driver-memory-census",
    "exit": 0,
    "verdict_line": "check-driver-memory-census: OK — every declaration is ledgered, every ledger entry is live, and every ruled file states "#6664 census: 2 ruled consumers". This gate polices the cen"
    },
    {
    "command": "pnpm check:dts-closure",
    "exit": 0,
    "verdict_line": "check-dts-closure: 73 built package(s) swept - 173/173 declared declaration file(s) present across 72 package(s); 1 built package(s) declare no declaration entry point and owe none"
    },
    {
    "command": "pnpm check:dual-build-cjs-loads",
    "exit": 0,
    "verdict_line": " ⚠ The delta is information, not a verdict — this population grows AND shrinks for good reasons, and only the floors decide. Reproduce the record: see this file's header."
    },
    {
    "command": "pnpm check:engine-double-contract",
    "exit": 0,
    "verdict_line": " ⚠ The delta is information, not a verdict — this population grows AND shrinks for good reasons, and only the floors decide."
    },
    {
    "command": "pnpm check:gitlink-declared",
    "exit": 0,
    "verdict_line": "check-gitlink-declared: OK (11259 index entries -- 0 gitlink(s) at mode 160000; no .gitmodules in the index, so nothing is declared; nothing to declare)."
    },
    {
    "command": "pnpm check:issue-citations",
    "exit": 0,
    "verdict_line": "✅ check-issue-citations --self-test: grammar narrowed, every spelling enumerated, qualifier a closed set of repositories, four 404 causes kept apart, both board strategies agree, d"
    },
    {
    "command": "pnpm check:kernel-hook-pairs",
    "exit": 0,
    "verdict_line": "✓ kernel hook pin pairing: 4 dispatched kernel:* hook(s), each pinned in both kernel.test.ts and lite-kernel.test.ts"
    },
    {
    "command": "pnpm check:lean-entry-closure",
    "exit": 0,
    "verdict_line": " Admitted set held exactly (15 packages); 6 denied names absent."
    },
    {
    "command": "pnpm check:logger-receiver-detach",
    "exit": 0,
    "verdict_line": " control corpus fired on all five detach shapes in this same run, and stayed silent on the measured console and options-callback populations -- so the zero above is a reading."
    },
    {
    "command": "pnpm check:nul-bytes",
    "exit": 0,
    "verdict_line": "check-nul-bytes: OK (scanned 11250 text file(s) -- 11250 tracked, 0 untracked-not-ignored; skipped 9 binary; no raw ASCII control bytes)."
    },
    {
    "command": "pnpm check:objectql-double-limit",
    "exit": 0,
    "verdict_line": " baseline key set verified against 09ef77c: no files added."
    },
    {
    "command": "pnpm check:objectui-changeset",
    "exit": 0,
    "verdict_line": "✓ objectui-range --self-test: all checks passed"
    },
    {
    "command": "pnpm check:org-identifier",
    "exit": 0,
    "verdict_line": "check-org-identifier: OK (3401 author-facing source file(s), 18 session binding(s) resolved, no removed session.tenantId alias)."
    },
    {
    "command": "pnpm check:page-declaration-shape",
    "exit": 0,
    "verdict_line": " blind spot: 1 computed carrier(s) no source scan can enumerate — examples/app-crm/objectstack.config.ts:85."
    },
    {
    "command": "pnpm check:pm-changeset-deadline-census",
    "exit": 0,
    "verdict_line": "✓ changeset-deadline-census --self-test: all cases passed across 5 batteries (what counts as a named target, the controls that make a zero a reading, the verdict truth table includ"
    },
    {
    "command": "pnpm check:published-files",
    "exit": 0,
    "verdict_line": "✓ check:published-files — 71 publishable package(s) of 82 workspace member(s) declare a files whitelist that covers every entry point plus CHANGELOG.md and admits no test, test-h"
    },
    {
    "command": "pnpm check:query-options-erasure",
    "exit": 0,
    "verdict_line": " baseline key set verified against 09ef77c: no files added."
    },
    {
    "command": "pnpm check:refd-timer-probe",
    "exit": 0,
    "verdict_line": " 1 code site(s), all inside the approved module, which is present and still reads it."
    },
    {
    "command": "pnpm check:route-envelope",
    "exit": 0,
    "verdict_line": " read/write discriminator: 13 file(s) skipped as fetch readers (79 zero-argument res.json() call(s), none swept in)"
    },
    {
    "command": "pnpm check:slot-lookup",
    "exit": 0,
    "verdict_line": " baseline key set verified against 09ef77c: no files added."
    },
    {
    "command": "pnpm check:sourcemap-no-sources-content",
    "exit": 0,
    "verdict_line": "check-sourcemap-no-sources-content: 70 built package(s) swept - 554 map(s), none embed source text."
    },
    {
    "command": "pnpm check:test-source-alias",
    "exit": 0,
    "verdict_line": "check-test-source-alias OK — 74 packages with tests scanned; 60 registered as still resolving a workspace dep through dist/; 53 published subpath(s) resolved through every alias "
    },
    {
    "command": "pnpm check:tier-file-adoption",
    "exit": 0,
    "verdict_line": " @objectstack/cli — 82 file(s); imports readTierMode, selectTierFiles from scripts/nightly-tiers.mjs (via packages/cli/vitest-tiers.ts)"
    },
    {
    "command": "pnpm check:type-check-coverage",
    "exit": 0,
    "verdict_line": " ⚠ The delta is information, not a verdict — this population grows AND shrinks for good reasons, and only the floors decide."
    },
    {
    "command": "pnpm check:watch-hint-literal",
    "exit": 0,
    "verdict_line": "✓ check-watch-hint-literal: 73 declaration(s) across 4 rostered name(s) -- ROOT_DIR_WATCH_HINTS 49, ROOT_FILE_WATCH_HINTS 13, ROOT_WATCH_HINTS 3, DECLARED_WATCH_HINTS 8 -- every on"
    },
    {
    "command": "pnpm check:where-matcher",
    "exit": 0,
    "verdict_line": " baseline key set verified against 09ef77c: no files added."
    },
    {
    "command": "pnpm check:type-check-debt",
    "exit": 0,
    "verdict_line": "check-type-check-coverage --re-measure: OK — 1 ledger entr(ies) re-measured in 93.1s, 26 raw tsc error(s) total, none above its recorded number."
    }
    ],
    "added": [
    "pnpm --filter @objectstack/dogfood exec vitest run test/authz-conformance.test.ts test/authz-probe-blind-spot.test.ts test/anonymous-door-sweep.dogfood.test.ts: exit 0",
    "pnpm --filter @objectstack/dogfood run typecheck: exit 0",
    "pnpm --filter @objectstack/runtime run typecheck: exit 0",
    "pnpm --filter @objectstack/core run typecheck: exit 0",
    "check:system-context-census: exit 0 in the derived set (it went red once mid-slice when the floors passed isSystem: three new elevation read sites; fixed by asking the floors about the user id alone)"
    ]
    },
    "line_budget": "17 files, +845 / -59 against origin/main (no generated file); no governed surface (no .claude, docs/adr, skills, AGENTS.md or CLAUDE.md path); no skills/** budget applies",
    "files_changed": [
    ".changeset/23225-domain-floor-anonymous-deny.md",
    "packages/core/src/security/assemble-execution-context.ts",
    "packages/qa/dogfood/test/anonymous-door-sweep.dogfood.test.ts",
    "packages/qa/dogfood/test/authz-conformance.matrix.ts",
    "packages/qa/dogfood/test/authz-conformance.test.ts",
    "packages/qa/dogfood/test/authz-ledger-population.baseline.ts",
    "packages/qa/dogfood/test/authz-probe-blind-spot.census.ts",
    "packages/qa/dogfood/test/authz-probe-blind-spot.test.ts",
    "packages/runtime/src/declared-capability-absence-warn-once.test.ts",
    "packages/runtime/src/dispatcher-5xx-always-logged.test.ts",
    "packages/runtime/src/domain-handler-registry.test.ts",
    "packages/runtime/src/domains/domain-floor-anonymous-deny.test.ts",
    "packages/runtime/src/domains/keys.ts",
    "packages/runtime/src/domains/notifications.ts",
    "packages/runtime/src/domains/share-links-public-cache-headers.test.ts",
    "packages/runtime/src/domains/share-links.ts",
    "packages/runtime/src/notification-schema-conformance.test.ts"
    ],
    "deviations": [
    "A backgrounded '&&' chain assigned the scratch-dir variable inside the background subshell, so its pid file landed at the filesystem root: /build-closure.pid (6 bytes, a pid). The harness's safety check refused my rm of it; it needs deleting by the seat or a person.",
    "Five existing runtime tests that used an anonymous caller only incidentally (a 405, a 501, a thrown-error propagation, a declared-absence 501 fixture) were moved to a signed-in caller beside an added anonymous-401 assertion, the pattern the /analytics move used; within the moved faces' tests.",
    "The census file, its test and the population baseline in packages/qa/dogfood/test were edited as the matrix's companions (figures, rows, dated notes); within the dogfood test surface.",
    "Zero label writes: the dispatch's write budget names no label, and skip-changeset does not apply (a changeset is added).",
    "Lock queue timeouts (exit 99) on two runs were NOT MEASURED and re-run; a failed closure build left cloud-connection without declarations, which made check:type-check-debt read +1 and dogfood typecheck TS7016 transiently; after a clean rebuild both are green at the final tree."
    ],
    "mcp_calls": "0 — no MCP GitHub tool used",
    "api_writes": "2 relay strokes through scripts/pm (fleet-write repository_dispatch, executed as objectstack-fleet[bot]): (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/23286/assignees (os-steve), read back 11600 bytes identical; (2) this os-dev-report comment = POST /repos//issues/23225/comments. git push is not a REST write. Reads: GET issues/23225 and its comments, GET pulls/23286.",
    "open_questions": [
    {
    "question": "The /ai dispatcher domain (handleAIRequest) and the dispatcher-plugin mount (mountRouteOnServer) admit an anonymous caller on any registered AI route that declares auth false. ADR-0138 D2 lists six door classes and says a new door class is an amendment. Move /ai to the domain-level deny (retiring the route-level opt-out), or record the opt-out?",
    "options": [
    "A: retire the route-level auth false opt-out and move /ai to the domain-level deny. Business need: zero in-repo producers (measured: no non-test 'auth: false' route in this repository); the Cloud/EE service-ai route table was not read. Long-term: closes the last per-route anonymous opt-out on the dispatcher, matching D2. AI-authoring safety: strongest, no unlisted door can be declared. Startup focus: one domain edit plus the retirement of a runtime contract the tests pin today; needs the cloud consumer census first. Cost: small code, a cross-repo measurement.",
    "B: keep the opt-out and amend ADR-0138 with a seventh class for declared public AI routes. Business need: only if cloud's service-ai declares one. Long-term: a second declaration-derived door. AI-authoring: weaker, a route can open itself. Startup focus: an ADR amendment for an unmeasured need.",
    "C: leave as is, record the gap. Cheapest, but it is a declared exit from the closed list that nothing enforces."
    ],
    "recommendation": "A, after a read of cloud's service-ai route table: no in-repo producer exists, the record's closed list and the startup axis both argue against a door kept for an unmeasured user, and A is the only option where what is declared is what is enforced. Seat or maintainer call; not decided here."
    },
    {
    "question": "Do D2's 'domains' include the REST server's route families? They decide per route by construction (each handler opens with enforceAuth); only the metadata family has a family-wide guarded registrar, so a new REST route that forgets enforceAuth is caught only by the booted sweep.",
    "options": [
    "A: no — D2's 'domain' is the dispatcher domain the runtime face's guest envelope reaches (D2's own sentence about the envelope), and the REST seam stays per route, held by the sweep and the per-route census.",
    "B: yes — give each REST family a guarded registrar (the registerMetadataEndpoints shape) in a separate card; a large rest-server.ts change and a re-measure of the probe census's enforceAuth counts."
    ],
    "recommendation": "A for this card (it is the reading the record's text supports and nothing in it measured otherwise); B, if wanted, is its own card with its own measurement."
    }
    ],
    "out_of_scope_findings": [
    "class: a · reach: exception: security (possible schema disclosure to an anonymous caller on a composition that routes to the dispatcher's /ui domain; not reproduced end to end) · evidence: runtime/src/domains/ui.ts handleUiRequest makes no anonymous decision and calls the protocol's getUiView, which reads the object's schema with no principal; an existing unit test dispatches the domain with an empty context and asserts getUiView is called; the standalone showcase never routes there; readings to the seat privately · fix lands as the deferred /ui move after 23214 slice 1 (that slice holds the file) · dedupe words: dispatcher ui domain anonymous getUiView, handleUiRequest shouldDenyAnonymous, ui view schema guest",
    "class: b · reach: named producer: the runtime dispatcher's /data domain on a composition without @objectstack/rest · evidence: runtime/src/domains/data.ts handleDataRequest makes no anonymous decision, so an anonymous caller reaches the guest envelope's CRUD gate (a 403) instead of D2's 401; every /data ledger route is shadowed by REST where REST is mounted · Seam: spec:ADR-0138 D2 everything-else 401 → runtime:handleDataRequest · deferred /data move after 23214 slice 1 · dedupe words: dispatcher data domain anonymous 401, handleDataRequest floor",
    "carrier: 23214 slice 1 (domain:cli seat) · the /ui and /data moves above land in that slice's files once it merges; noted, not filed"
    ]
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT: PR #23286 at 8f838b7460 (#23225 slice 1, ADR-0138 D2; Part of #23225). It queues after one CONTRACT_REVIEW_TIER PASS on this head and a green head

    domain:cli seat 2 (#22648) · os-steve · session session_01VoSxBQujKLZKPwK2u5ehQ6 · 2026-10-13T14:27Z · holder of claim 6144558082. Report: os-dev-report 6147723822. Thread-read: 6147723822. This note stays at function level.

    Checklist, read against GitHub:

    • PR shape: draft, base main, line 1 Part of #23225 (no closing keyword), line 2 Clause-②: no (narrowing), assignee os-steve.

      • Title corrected by the seat in this act: it was feat(runtime,dogfood): …, and is now fix(runtime)!: …, matching the changeset's narrowing grade. A squash takes the title as its subject. check-changeset-no-major --base origin/main --event on the corrected title and body exits 0 (level axis no (narrowing), the launch-window arm).
    • Scope: 17 files, +845 / −59, all inside the claim:

      • packages/qa/dogfood/test/ (the matrix, its tests, the census and the baseline);
      • three dispatcher domains, keys.ts, notifications.ts and share-links.ts, with their tests;
      • assemble-execution-context.ts, doc only, cross-lane as declared on [PM seat] domain:engine — 🟢 sunhaolin #6367 (6144567590);
      • one changeset.

      No file hono adapter: createHonoApp's catch-all flattens a repeated query name to its last value, so every dispatcher arity rule is unreachable on a @objectstack/hono host #23214 slice 1 held is touched, no D2b path, no gate script and no ADR text. check-governed-merges --pr 23286, run from current main's script, reports it not governed. git merge-tree against today's origin/main (042ef51530) is clean: the commits since its base touch other runtime domain files, not these.

    • D2's six door classes, recorded: AUTHZ_CONFORMANCE has six enforced HIGH_RISK rows, one per class (public form, share link, public book, object_operation endpoint, flow endpoint, public_read file). Each names D2's enforcing site, and each is proven by the booted anonymous-door sweep. A new matrix block keeps the list closed, with negative controls for a seventh row, a missing row, an unproven row and a renamed row.

    • Denial by domain: the census read every dispatcher domain's anonymous decision. Three decided it per face, below another answer, and each now decides it as its first statement through shouldDenyAnonymous:

      • /notifications (below its inbox capability probe);
      • /keys (below its method check);
      • /share-links' management faces (below the service probe). The two class-2 token faces keep their admission.

      Each has a floor row and a gate pin. The ledger's unclassified baseline goes from 30 to 26.

    • The answers that change, and the grade: on compositions the record did not measure, an anonymous caller now gets 401 where it got 501 (no inbox, no share-link service) or 405 (a non-POST key verb). A signed-in caller's answers are unchanged. The claim's rule for a move that changes an answer applies: Clause-②: no (narrowing), and one CONTRACT_REVIEW_TIER review is owed. The changeset is @objectstack/runtime minor, fix(runtime)!:, Clause-②: no (narrowing), with one ADR-0087 not-required (no-migration-prescription) marker and the launch-window BREAKING line. @objectstack/core's change is a doc comment, so it ships no release.

    • New-route pin: a new route family or domain mints an unclassified ledger key, and the matrix reds it. A route inside an existing domain is held by the domain floor being the first statement: deleting any of the 8 gate-pinned floors reads STALE, and each moved domain's unit suite refuses a not-yet-served path 401 before route selection.

    • Ablations:

      • the class-3 admission removed: exactly class 3's two admitted legs red;
      • the /notifications floor removed: its unit cases and the matrix's STALE / DEAD PROBE red;
      • a new ledger domain: UNCLASSIFIED reds;
      • a route inside an existing domain: no UNCLASSIFIED, as the matrix header states.

      Every restore was proven against HEAD.

    • Tests and gates at 8f838b7460:

      • dogfood: 3 files / 124 tests;
      • runtime: 8 touched files / 405 tests, and the full local project 368 / 5269 at 2d84837006, where the later merge touched no runtime, rest, core or dogfood file;
      • the runtime, core and dogfood typechecks exit 0;
      • 69 gates derived with no paths, 69 run, all exit 0, reconciled with --ran;
      • lint narrowed, with the three-part proof.
    • CI on 8f838b7460 at this stamp: latest run per name: 24 success, 6 skipped and 9 in progress, with no failure.

    • Open questions:

      1. The /ai domain's route-level auth: false opt-out is a door mechanism D2's closed list does not name. Changing it either retires a runtime contract (A, which needs a cloud consumer census first) or amends the record with a seventh class (B). Both re-decide ADR-0138, so the seat does not rule. It goes to the maintainer as asked, and this slice does not wait: nothing in its diff depends on it.
      2. Whether D2's "domain" covers the REST server's route families: read as A by the seat. D2's domain is the dispatcher domain the runtime face's guest envelope reaches, which is the record's own sentence, and REST's per-route enforceAuth stays held by the sweep and the probe census. Nothing moves. A family-wide guarded registrar (B) would be its own card with its own measurement, and nobody is pulling for it.
    • Deferred moves, now unblocked: two dispatcher domains in files hono adapter: createHonoApp's catch-all flattens a repeated query name to its last value, so every dispatcher arity rule is unreachable on a @objectstack/hono host #23214 slice 1 held make no anonymous decision of their own: handleUiRequest (ui.ts) and handleDataRequest (data.ts). The claim deferred them. hono adapter: createHonoApp's catch-all flattens a repeated query name to its last value, so every dispatcher arity rule is unreachable on a @objectstack/hono host #23214 slice 1 landed as d45d0e938a, so they are this card's next slice and come before D2b. handleUiRequest's missing floor is the security-relevant one. It is read at unit level only and not reproduced end to end, so the next slice measures its reach first. The route-level readings stay with the seat.

    • Writes: mcp_calls 0; api_writes 2 relay strokes (pr_create with its assignee, and the report).


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #23286 → 01d503432b (slice 1, Part of #23225, ADR-0138 D2). The card goes back to pm:queue for its next slices

    domain:cli seat 2 (#22648) · os-steve · session session_01VoSxBQujKLZKPwK2u5ehQ6 · 2026-10-13T15:23Z · holder of claim 6144558082, released by this landing. This note stays at function level.

    • Landed: through the merge queue at 2026-10-13T15:22:56Z as 01d503432b, a squash with one parent.

      • The closed list is recorded. ADR-0138 D2's six door classes are six enforced rows of AUTHZ_CONFORMANCE, each naming its enforcing site and proven by the booted anonymous-door sweep. A seventh, missing, unproven or renamed row reds the matrix.
      • Denial by domain. /notifications, /keys and /share-links' management faces decide anonymous denial as their first statement, so a face added to them later is refused to an anonymous caller from the day it lands.
      • What changes for a caller: an anonymous caller is answered 401 on those domains whatever the deployment carries. Before, a deployment with no inbox or no share-link service answered 501 first, and a non-POST key verb answered 405 first. A signed-in caller's answers are unchanged.
    • Review chain:

      The changeset is @objectstack/runtime minor, fix(runtime)!:, Clause-②: no (narrowing), with one ADR-0087 not-required (no-migration-prescription) marker and the launch-window BREAKING line.

    • First CI verdict on the matrix block is main's battery. No pull-request check runs @objectstack/dogfood: the dogfood shards skip on pull_request, and Test Core excludes the package. So the closed-list block, the census and the sweep rest on the dev's local run (3 files / 124 tests) and four ablations, with the record reading the test code. A red on main's next full battery in authz-conformance, authz-probe-blind-spot or anonymous-door-sweep is this slice's, and the next holder takes it first.

    • Next on this card, in order (not dispatched: this seat goes off shift with this landing, and the next holder claims fresh):

      1. The deferred domain floors. handleUiRequest (ui.ts) and handleDataRequest (data.ts) make no anonymous decision of their own. Slice 1's claim deferred them because hono adapter: createHonoApp's catch-all flattens a repeated query name to its last value, so every dispatcher arity rule is unreachable on a @objectstack/hono host #23214 slice 1 held those files, and that slice landed as d45d0e938a, so they are free now. handleUiRequest's missing floor is the security-relevant one, read at unit level only. The slice measures its reach first, at a public door, and keeps public notes at function level.
      2. D2b (slice 2): endpoint-publish-gate.ts in packages/spec, @objectstack/service-automation, the ADR-0087 semantic entry and metadata-protocol's publish path. It is cross-lane (domain:spec, domain:services), and one contract review is owed.
    • Open, with the maintainer: the /ai domain admits an anonymous caller on a registered route that declares auth: false, a door mechanism D2's closed list does not name. The options are A, retire the opt-out (after a census of cloud's AI routes), B, amend the record with a seventh class, or C, record the gap. Either change re-decides ADR-0138, so the seat does not rule. The dev recommends A, and the question is put to the maintainer in this seat's close-out.

    • Read by the seat, not re-decided: D2's "domain" is the dispatcher domain, so REST's per-route enforceAuth stays held by the sweep and the probe census. A family-wide guarded REST registrar would be its own card with its own measurement.

    • Holds released: the claim's files: packages/qa/dogfood/test/ (the matrix, its tests, the census and the baseline), keys.ts, notifications.ts, share-links.ts and their tests, assemble-execution-context.ts's module doc, and the changeset.


    Generated by Claude Code

  6. 12 remaining items

  7. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: retriage answered. D2b moves whole to domain:spec (domain:cli removed, pm:retriage removed). No split: the other two edits are call sites of the spec gate

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-14T02:54Z. ⛔ Not a claim, ⛔ not a dispatch.

    • Read: the landing and release 6159719208. D1 and D2 are done: slice 1 is 01d503432b, and slice 2 (PR fix(runtime)!: the /ui and /data dispatcher domains deny an anonymous caller as their first statement (ADR-0138 D2) #23364) is f24574ae0. What remains is D2b alone: the publish refusal in both directions, its prescription, and the registered ADR-0087 semantic entry.
    • Answer: the spec lane claims D2b whole.
      • The contract and the entry live in packages/spec (src/api/endpoint-publish-gate.ts, src/migrations/entries/semantic/).
      • The service-automation and metadata-protocol publish-path edits are where the gate is consulted, so they belong to the same PR, declared in the claim.
      • A split would leave a narrowing half-landed on one door, and a Blocked-by chain for one judgment.
    • Clause ②: no (narrowing), with contract-tier review, in-seat for this lane.
    • Grade kept: p1 security target:v18.
    • Carried for the next holder: the slice-2 census note on system-context in-process callers of /data and /ui with no user id; cloud was not read.
  8. added and removed
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Oct 14, 2026
  9. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (#23225 D2b: ADR-0138 D2b's publish refusal, in both directions, with its prescription and its registered ADR-0087 semantic entry; triage 6159841787 routed D2b whole to this lane) · 2026-10-14T04:06Z
    Session: session_01YRp5z4kK6vgYkrdH97ztsy
    Account: huangyiirene (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-23225-d2b-publish-refusal
    Worktree: objectstack-issue-23225-d2b
    Domain: domain:spec
    Seat: domain:spec#1 (seat post #6017)
    File surface (at origin/main 6eb6f7d65 or later, which carries slices 1 01d503432b and 2 f24574ae0; stop on a breach and explain it in the report):


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 23225,
    "status": "done",
    "branch": "claude/issue-23225-d2b-publish-refusal",
    "pr": "#23411",
    "session": "session_01YRp5z4kK6vgYkrdH97ztsy — the dispatching seat's id (mode:subagent)",
    "premise_still_valid": true,
    "summary": "ADR-0138 D2b, the card's last leg. One pure cross-item judge, anonymousElevatedFlowPairs (endpoint-publish-gate.ts section f, beside policyGate), pairs an authRequired: false type: 'flow' endpoint with a target flow declaring runAs: 'system'. Two sentences word the refusal per direction, each carrying the record's prescription: an authenticated endpoint, the signed inbound hook (ADR-0041), or a public form; 'automation' is not prescribed before ADR-0073 M2. Every measured publish door calls the judge: the stack parse (applyApiEndpointGates, both directions as one input), MetadataManager.publishPackage (both directions), and the metadata protocol's flow publish (saveMetaItem in publish mode, promoteDraftForPublish, so the per-item publish and publishPackageDrafts). Also landed: the registered ADR-0087 semantic entry api-endpoint-anonymous-elevated-flow-refused, the flow runAs description corrected (H4), one ADR anchor, and one BREAKING changeset (spec, metadata, metadata-protocol minor). #23375 (eeb95f7) is merged and composes: the D2b leg runs inside applyApiEndpointGates, and applyPermissionSetOwnerBindingGate is chained after it, unchanged and unreordered. #23375 covers none of D2b.",
    "measurements": {
    "H1": "Partly falsified. The entries are as stated: validateApiEndpointDeclarations :155, policyGate :499. But @objectstack/metadata imports the gate. It calls validateApiEndpointDeclarations in MetadataManager.gateApiItemsForPublish (POST /packages/:id/publish), the endpoint publish door pinned by publish-endpoint-gate.test.ts, and identityFreeEndpointGateFailure in the endpoint matcher's load-time index. api is code-only (allowRuntimeCreate: false; the code-only inlet refuses it at the protocol). So the endpoint publish doors are the stack parse and the package publish, and both run D2b. flow is allowRuntimeCreate: true, so its doors are the stack parse, the package publish, and the protocol's publish-mode save plus draft promotion.",
    "H2": "Falsified for service-automation. It judges no publish: it registers a credential channel, a publish materializer and a mutation projector on the protocol, and arms flows after publish (registerFlow, the mutation sync). A stored flow's publish is judged in metadata-protocol, so service-automation is unchanged and is read as a consumer. Its credential migration re-saves rows through saveMetaItem with source migrate-stored, so the protocol leg skips that source, as the runtime authoring gate does.",
    "H3": "Holds; no new code minted. Stack parse: a zod custom issue at apis[i].authRequired, and defineStack throws STACK_SCHEMA_INVALID / 422. Package publish: the existing success:false validationErrors. Protocol: INVALID_METADATA / 422 with issues[0].path 'runAs', the envelope of that door's schema and authoring-gate refusals; it is deliberately outside the OS_ALLOW_UNLINTED_METADATA_WRITES escape hatch.",
    "H4": "The description misled: it prescribed system for user-less triggers including 'api'. It now names the anonymous-endpoint exception and the three serving channels. The two content/docs/references pages are regenerated. The hook runAs text is untouched (hooks are not endpoint-triggered)."
    },
    "tests": "Merged head 1bd1ba1 unless stated. New pins: (1) spec src/api/anonymous-elevated-flow-gate.test.ts, 14 cases. Each direction is the edit that completes the pair from an accepted stack, asserted as code custom at ['apis',0,'authRequired']; defineStack gives STACK_SCHEMA_INVALID / 422. Controls: an authenticated endpoint on a system flow, and an anonymous endpoint on a user or default-runAs flow. Also the judge's edges, and the prescription in both sentences. (2) metadata src/publish-anonymous-elevated-flow-gate.test.ts, 8 cases: the endpoint direction with the flow in the package and in another package; the flow direction; validate:false does not disable it; envelope peel; two controls; nothing published on a refusal. (3) metadata-protocol section 5 of protocol.item-name-every-door.test.ts, on that file's pinned double (no engine-double ledger change), 10 cases: the publish-mode save, publishMetaItem and publishPackageDrafts, each with its control; the endpoint read from the metadata service; a failing metadata-service read fails the publish; draft and migrate-stored saves not judged. Consumer readings (each package's own exit 0, os-verify-lock VERDICT command-exit 0): @objectstack/spec pnpm test 674 files / 20475 tests (1 todo); @objectstack/metadata 62 / 888; @objectstack/metadata-protocol 235 passed + 3 skipped / 28373 passed + 19 skipped; @objectstack/service-automation 186 / 2486; @objectstack/lint 138 / 6699; @objectstack/cli --project unit 287 / 4307 (integration declared to CI). Typecheck exit 0 for spec (test layer held at its pinned debt), metadata and metadata-protocol. Ablations at pre-merge head f66d8c1, through scripts/ablation-replace.mjs with its restore trap, expected direction (red) declared first; no build leg, because each suite reads the mutated file from src. A1, the stack leg fed no flows: 5 failed / 9 passed, the five stack-parse refusals. A2, the save-door call removed: 3 failed / 36 passed. A3, the promotion call removed: 2 failed / 37 passed. A4, the package-publish call removed: 5 failed / 3 passed. Each anchor went 1 to 0, each restore was proven (blob == HEAD, git diff HEAD empty), and the merges touched none of the four sites. Lint, as a proven narrowing at 1bd1ba1: eslint --no-inline-config --format json over the 10 changed .ts files gives 10 files, 0 errors, 0 warnings, 0 ignored. eslint.config.mjs enables no type-aware linting (no parserOptions.project, :328), so untouched files' verdicts cannot move. Repo-wide pnpm lint is left to CI.",
    "gates": {
    "head": "1bd1ba175",
    "derived": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths): 125 commands at 1bd1ba1, identical to the pre-merge derivation. The PM's lead file held 80 at 6eb6f7d; the difference comes from the docs, ADR-anchor, changeset and @objectstack/metadata paths.",
    "reconcile": "dispatch-gates --ran: '125 derived famil(ies) accounted for, 125 run, 0 NOT-MEASURED, 0 UNRUN' (exit 0).",
    "results": "All 125 exit 0 at 1bd1ba1. Two first answered PREREQUISITE NOT MET (exit 3, nothing measured): pnpm --filter @objectstack/spec run check:skill-examples (client-react had no .d.ts) and pnpm check:dual-build-cjs-loads (nine packages unbuilt). Each was re-run on the same head after building its prerequisites: exit 0, verdict lines '263 prose examples type-check across 3 surface(s)' and the cjs provenance floors line. Notable verdict lines: check:generated 'All 14 generated artifacts are up to date'; check:api-surface 'public API surface + factory signatures unchanged'; check:adr-anchors exit 0 (new anchor); check:issue-citations 'no issue citations added against e4ec6ec (7 file(s) read)'; check:nul-bytes OK over 11379 files; check:system-context-census OK (no new elevation read site); check:engine-double-contract exit 0; check:type-check-debt exit 0 ('every entry sits exactly at its measurement'); check-adr-0087-registration --base origin/main exit 0 (the changeset reads BREAKING+bang+clause-2-narrowing, registered api-endpoint-anonymous-elevated-flow-refused).",
    "added": [
    "node scripts/check-changeset-no-major.mjs --base origin/main --event (this PR's title and body): exit 0, 'LEVEL AXIS: this PR declares clause-② no (narrowing)'; the same with the line amended to yes (narrowing): exit 0",
    "node scripts/pm/check-governed-merges.mjs --test (16 paths, --additions 1218 --deletions 14): exit 0, 0 of 16 paths hit the register, NOT governed",
    "pnpm --filter @objectstack/{spec,metadata,metadata-protocol} run typecheck: exit 0 each"
    ],
    "ci_at_report": "not read: the report is delivered at the end of local verification, and CI convergence is the PM's"
    },
    "line_budget": "n/a (no skills/**). 16 files, +1218 / -14 against the merge base; no governed surface.",
    "files_changed": [
    ".changeset/23225-d2b-anonymous-elevated-flow-refused.md",
    "content/docs/references/api/automation-api.mdx",
    "content/docs/references/automation/flow.mdx",
    "packages/metadata-protocol/src/protocol.item-name-every-door.test.ts",
    "packages/metadata-protocol/src/protocol.ts",
    "packages/metadata/src/metadata-manager.ts",
    "packages/metadata/src/publish-anonymous-elevated-flow-gate.test.ts",
    "packages/spec/api-surface/api.json",
    "packages/spec/export-origins/api.json",
    "packages/spec/src/api/anonymous-elevated-flow-gate.test.ts",
    "packages/spec/src/api/endpoint-publish-gate.ts",
    "packages/spec/src/api/index.ts",
    "packages/spec/src/automation/flow.zod.ts",
    "packages/spec/src/migrations/entries/semantic/18.api-endpoint-anonymous-elevated-flow-refused.ts",
    "packages/spec/src/stack.zod.ts",
    "scripts/adr-anchors/packages__spec__src__api__endpoint-publish-gate.ts.json"
    ],
    "deviations": [
    "Clause-② line, conflict named rather than chosen silently. The claim says no (narrowing), and the PR body's line 2 copies it verbatim as the order requires. The measured reading is yes (narrowing): the judge must be callable from metadata and metadata-protocol, so 5 functions and 3 interfaces join @objectstack/spec/api (api-surface +8), and landed narrowing-plus-export changesets (23015, 23072, 23165) declare yes (narrowing). The changeset carries yes (narrowing). Both values pass check-changeset-no-major (all three packages are minor). See open_questions.",
    "File surface widened under the order's measured-producer clause. packages/metadata/src/metadata-manager.ts and its new test: MetadataManager.publishPackage is a measured endpoint publish door, so it was not on the claim. service-automation is unchanged, because H2 measured no publish door there. The PR body names the landing and the reason.",
    "scripts/adr-anchors/packages__spec__src__api__endpoint-publish-gate.ts.json added: one new anchor file, per AGENTS.md PD13. It is data for the existing check:adr-anchors and is not a gate script.",
    "Protocol pins were added as section 5 of the existing protocol.item-name-every-door.test.ts rather than a new file, to reuse its pinned engine double and keep the engine-double ledger unchanged.",
    "The PR body was written once at opening, when 71 of 125 gates had run, and it says so. The final gate figures are in this report. The seat may rewrite that section if wanted.",
    "Lock queue: two calls timed out in the queue (exit 99, NOT MEASURED) behind pid 5617 (the issue-23357 spec/mp/lint suites, about 30 min), then the #23297 dev's build. Both were re-queued under the same slot and acquired.",
    "The two merges of origin/main (0add8a4, then e4ec6ec carrying eeb95f7) are git default merge commits with no trailer pair. My own commits carry the model-free pair AGENTS.md names; the harness reminder's model-named trailer was not used, because AGENTS.md's rule takes precedence.",
    "Zero label writes: the order names no label, and skip-changeset does not apply (a changeset ships).",
    "Worktree /home/user/objectstack-issue-23225-d2b removed after the push (node_modules first, no --force). The report comment is posted with the shared checkout's scripts/pm/fleet-write/dispatch.mjs, which cmp shows is byte-identical to the branch's."
    ],
    "mcp_calls": "0 — no MCP GitHub tool used",
    "api_writes": "2 relay strokes through scripts/pm/fleet-write/dispatch.mjs (repository_dispatch, executed as objectstack-fleet[bot]). (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/23411/assignees (huangyiirene); the read-back stored 11521 bytes against 11521 sent, identical, draft true, assignee listed. (2) This os-dev-report comment = POST /repos//issues/23225/comments. git push is not a REST write. Reads: GET issues/23225 and its comments, GET pulls/23375/files, GET pulls/23411.",
    "open_questions": [
    {
    "question": "Should PR #23411's Clause-② line stay the claim's no (narrowing) or become the measured yes (narrowing)? The PR adds 8 exports to @objectstack/spec/api, a public-surface expansion, beside the accept-set narrowing.",
    "options": [
    "A: the seat amends the claim and the PR body line to yes (narrowing). The changeset already says so. check-changeset-no-major passes, because every moved package is minor. This matches the 23015, 23072 and 23165 precedents.",
    "B: keep no (narrowing) and edit the changeset's line to match. This declares no surface expansion while api-surface/api.json grows by 8."
    ],
    "recommendation": "A, because the exports are inherent to the record's design (one judge in spec, called from two other packages' doors). Declaring them is what makes the semver reading true, and it costs nothing at the gates."
    }
    ],
    "out_of_scope_findings": [
    "carrier: none (承接者:无) · noted, not filed. The restore doors (rollbackMetaItem; revertCommit's restore limb) can re-activate a stored flow version declaring runAs: 'system' while an anonymous endpoint names it, and D2b does not judge them. The repository's deriveRestoredBody is synchronous, so it cannot ask the endpoints. The record scopes D2b to 'the publish path', and no cross-item refusal sits on restore today (the default-record-page refusal does not either). Dedupe words: restore rollback anonymous elevated flow, deriveRestoredBody cross-item, revertCommit runAs system.",
    "carrier: none (承接者:无) · noted, not filed. The PR's Acceptance notes also name what is not a publish door, so not judged: the endpoint matcher's load-time index; a flow registered straight into the engine; a pair split across two packages of one composed artifact at the stack parse, which reads top-level apis and flows only (the same scope as the per-endpoint gates; the package publish does judge across packages). And there is no full runtime counterpart: refusesElevatedSelfTriggeredStart covers self-triggered types only."
    ]
    }


    Generated by Claude Code

  11. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT (seat review): PR #23411 at 1bd1ba175 (Fixes #23225, ADR-0138 D2b, the card's last leg). It queues after one CONTRACT_REVIEW_TIER PASS on its head and a green head · 2026-10-14T07:03Z

    domain:spec seat 1 (#6017) · huangyiirene · session session_01YRp5z4kK6vgYkrdH97ztsy. The dev report is 6162772892; the claim is 6160651537, amended in place in this act.

    Read on GitHub, not from the report:

    • PR shape: draft, base main, first body line Fixes #23225. The body has no other closing keyword next to a card number. Slices 1 and 2 landed as Part of, and D2b is the last leg.
    • Files: 16 changed (+1218 / -14).
    • Landing class: check-governed-merges --pr 23411 reads NOT governed.
    • The changeset, read sentence by sentence against the diff: @objectstack/spec, @objectstack/metadata and @objectstack/metadata-protocol minor, BREAKING. It carries the FROM → TO table, the one-line remedy, and the ADR-0087 disposition registered api-endpoint-anonymous-elevated-flow-refused, a new step-18 entry in the diff. Its "What is refused, and where" names the three doors the diff edits:
      • stack.zod.ts applyApiEndpointGates;
      • MetadataManager.publishPackage;
      • saveMetaItem publish mode and promoteDraftForPublish.
    • Composition with main: PR fix(spec,objectql)!: a permission set's body packageId that disagrees with the package shipping it is refused at the build and registration doors; both readers read the binding only #23375 (eeb95f7ff) is merged into the branch, and applyPermissionSetOwnerBindingGate still chains after applyApiEndpointGates, unchanged and unreordered (the dev's reading; the contract review re-reads it).

    Seat amendments in this act:

    • Clause-② → yes (narrowing), the dev's open question, answered A. The judge joins @objectstack/spec/api as 8 published exports, so a new public surface sits beside the narrowing, and declaring yes is never an error.
      • The claim's line and the PR body's line 2 are amended to match the changeset.
      • The repo's one reader, clause2-line.mjs readClause2Line, reads both as declared / yes / narrowing.
    • File surface: the claim gains packages/metadata/src/metadata-manager.ts and its test (a measured publish door, cross-lane declared on [PM seat] domain:engine — 🟢 sunhaolin #6367 6162837793), the scripts/adr-anchors/ data file, and two regenerated references pages. @objectstack/service-automation is unchanged by measurement (H2).

    Out-of-scope findings:

    • The restore doors (rollbackMetaItem, revertCommit's restore limb) can re-activate a stored runAs: 'system' flow version while an anonymous endpoint names it: Acceptance notes, not filed. The record scopes D2b to "the publish path", and no cross-item refusal sits on restore today. The seat names it in its round report for the maintainer.
    • The non-publish paths the PR's Acceptance notes list (the matcher's load-time index, an engine-registered flow, a pair split across two packages of one composed artifact at the stack parse): Acceptance notes.

    Next:

    • A CONTRACT_REVIEW_TIER review runs when a slot frees: the seat runs at batch 2 on the maintainer's word.
    • The PR touches regenerated paths (packages/spec/api-surface/**, packages/spec/export-origins/**, content/docs/references/**), so it is synced through scripts/pm/os-regen-merge.sh before the queue, if main has moved them.

    Generated by Claude Code

  12. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #23411 → c857a93ee (Fixes #23225, ADR-0138 D2b, the card's last leg). The card closed completed on the merge · 2026-10-14T07:48Z

    domain:spec seat 1 (#6017) · huangyiirene · session session_01YRp5z4kK6vgYkrdH97ztsy · holder of claim 6160651537.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsdomain:specenhancementNew feature or requestpriority:p1High: required for production / M2securitytarget:v18

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions