Repository navigation
spec: gate the entry-nameability invariant — fail any public entry whose declarations structurally mention a type not nameable from that entry #11986
Description
Activity
Unblocked —
Blocked-by: #11709exhausted: PR #12327 merged to main as5ae4303d(A′ landed; the root entry now re-exports both measured names, and the post-fix minimal-program probe read zero remaining unnameable names at the root entry). This card's gate generalizes exactly that probe per public entry. State:pm:blocked→pm:queue;domain:specadded per the #11709 ruling's charter ("the spec seat's follow-up gate"). Round R6, sessionsession_01NDGG54XF5gbTLdQzCtnaVV.
Generated by Claude Code
huangyiirene commented
on Aug 26, 2026 CollaboratorMore actionsClaim: PM loop round R7
Session:session_01E5LFCYBJ3q2s6yW6oMLxwy
Branch:claude/issue-11986-entry-nameability-gate
Worktree:objectstack-issue-11986
Domain:domain:spec
File surface:packages/spec/scripts/**(newcheck:gate generalizingroot-entry-type-nameability.pin.test.ts, re-verified present onorigin/mainat fire),packages/spec/package.json(script wiring); CI workflow wiring only if the gate family requires it (report if so —.github/workflows/ci.ymlmoved on main today 00:40Z) (stop on breach; explain in the report)
Container & model: M,mode:subagent,model: opus— dispatch-gates--tierrun at fire: no path-derived mandate; gate-architecture judgment ⇒ default judgment tier; not a contract accept/reject change
Clause-②: no
Serial constraints cleared: batch siblings disjoint — #10726-family (src/kernel/+src/api/), #11658 (src/ui/+src/data/). READ coupling declared: this gate enumerates public entries' structural type mentions; the siblings remove a schema member / widen a prop, neither adds an unnameable structural mention, so no pin-flip is expected — if measurement says otherwise, stop and report rather than adjust the siblings. A′ (#12327,5ae4303d) confirmed onorigin/mainat fire:src/index.ts:154-155re-exportsBaseValidationRuleShape+FilterCondition. H17 on-hold trigger-file index: no hit.
Generated by Claude Code
huangyiirene commented
on Aug 26, 2026 CollaboratorMore actionsDev claim (os-dev subagent, dispatched by PM loop R7).
Session:
session_01E5LFCYBJ3q2s6yW6oMLxwy(subagent seat)
Branch:claude/issue-11986-entry-nameability-gate(pushed empty at start; basecdbd9204b65ae4ebec63f90f61669fb498efaa55)
Worktree:../objectstack-issue-11986Scope: generalize
packages/spec/scripts/root-entry-type-nameability.pin.test.tsinto a per-public-entrycheck:gate; wiring inpackages/spec/package.jsonplus whatever the siblingcheck:gate family requires for CI. Body read in full and verified complete; premises re-verified against the current tip before implementing.
Generated by Claude Code
huangyiirene commented
on Aug 26, 2026 CollaboratorMore actionsos-dev-report
{ "issue": 11986, "status": "done", "branch": "claude/issue-11986-entry-nameability-gate", "pr": "https://github.com/objectstack-ai/objectstack/pull/12418", "premise_still_valid": true, "summary": "Shipped `check:entry-nameability` (packages/spec/scripts/check-entry-nameability.ts): per public entry, a one-file consumer program compiled with declaration:true through the package's own exports map, probing every callable export with `E.FN(null as never, ...)` and reading the declaration emitter's cannot-be-named diagnostics. Wired as a `check:` script, classified NO_GENERATOR in check-generated.ts, and run as one step in lint.yml's typecheck-consumers lane beside check:api-surface / check:exported-any / check:dual-source-exports. The card's charter holds and the pin test's location was confirmed on origin/main, but TWO stated premises were refuted by measurement: (1) batching entries into one program would silently empty the gate (importing a subpath makes its names nameable program-wide), so per-entry programs are load-bearing and the batching control is now a permanent --self-test case; (2) 'green on current main' is false — the generalized call-shape probe finds 7 live leaks of the same defect class across 4 entries that A' never touched, so the gate ships with a hand-ratcheted shrink-only ledger closed to new rows, and the 7 are filed as #12414 rather than repaired here (they edit four entry barrels incl. src/kernel/ and src/ui/, both claimed by batch siblings, and regenerate api-surface/ — a different file surface and gate family).", "tests": "All gate runs at final commit 0eb9f84 (git rev-parse --short HEAD, clean tree), exit codes captured by redirect-then-capture, never through a pipe. ACCEPTANCE CRITERION 1 (A' reverted => red naming exactly BaseValidationRuleShape + FilterCondition): MET EXACTLY. Ablation on packages/spec/src/index.ts with an absolute-path restore trap; mutation proven on disk (git hash-object a2a790b19e0c... -> 6b8da6af0c6d..., both anchor greps 1 -> 0); spec rebuilt on the mutated source via scripts/pm/os-verify-lock.sh (VERDICT command-exit 0, held 134s); mutation proven to have reached dist (root-entry export statements naming the two types: 1,1 -> 0,0 — note a bare grep of the whole dist/index.d.ts is the WRONG marker, both names also occur inside structural expansions); gate exit 1: '2 NEW unnameable structural mention(s) across 1 public entry' / @objectstack/spec: BaseValidationRuleShape, FilterCondition — and nothing else. RESTORE LEG: git checkout HEAD -- (absolute path), git diff HEAD empty, hash back to a2a790b19e0c..., rebuilt (VERDICT command-exit 0), dist export statements back to 1 and 1. CRITERION 2 (green on main + A'): MET — 'entry-nameability: 422 call probes across 17 public entries, 0 new unnameable structural mentions (31.3s)', exit 0. CRITERION 3 (synthetic leak in any public entry reds it): MET on a real SUBPATH entry, not only the root — a factory returning { filter: FilterCondition } injected into src/security/index.ts (clean today); marker grep in src 0 -> 1, rebuilt (VERDICT command-exit 0), marker proven present in dist/security/index.d.ts (2 occurrences); gate exit 1 naming @objectstack/spec/security -> FilterCondition. Restored: git diff HEAD empty, hash back to bb76c1c1b2f4..., rebuilt, marker proven ABSENT from dist (0). GATE RUNTIME: 31.3s-47.3s wall clock across 17 entries / 422 probes (three runs); comfortably under the 2-3 min CI-material threshold, so no batching or parallelism was adopted — and batching is refused on soundness grounds, not cost. OTHER GATES, all exit 0: check:scripts-typecheck; check:generated --reconcile-only ('ledger reconciles with package.json: 27 check: + 17 gen: scripts, all classified'); check:merge-driver; check:published-files; check:nul-bytes; check:pnpm-filter-targets; check:workflow-status-functions; check-self-test-wired; check-self-test-workflow-commands; check-step-collectors; check-aggregator-roster; check-required-contexts; check-cross-package-test-inputs; check-ci-filter-parity. Gate families re-derived from the real change set with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (5 paths, repo assertion held) rather than trusting the dispatch list. NOT RUN LOCALLY: the repo-wide eslint sweep and the full CI farm — left to CI per the local-verification scope; this is a declared narrowing, not a claim of coverage. SELF-TEST (runs on EVERY invocation, not only under --self-test): positive control fires on a synthetic leaking entry, repaired-fixture negative control is clean, batched-program control reports nothing (the measured basis for never batching), TS4094 canary red under the shared compiler profile.", "open_questions": [], "out_of_scope_findings": [ "filed as #12414: seven public entries still leak an unnameable type through a factory call — the same class A' did not close (root: Book/FormField/NavigationItem; /kernel: UnknownAuthoringKeyFinding; /ai: FilterCondition/StateNodeConfig; /ui: FilterCondition). Held in entry-nameability.baseline.json, shrink-only and closed to new rows, so nothing new can land while these are open.", "filed as #12416 (label `finding`): scope question — whether the invariant covers plain export REFERENCES (`export const r = E.NAME`) and not just factory calls. 27 (entry, type) pairs measured across 11 of 17 entries, with the two facts that bear on the decision: five entries hit TS7056 under that shape (structurally NOT MEASURED, so a reference-shape gate could not be made total), and it costs ~105s vs ~42s. Deliberately NOT added to this gate's ledger — that would trade a ledger with a one-line repair per row for a 27-row accommodation surface with no shrink plan." ], "deviations": [ "REST issue/label endpoints return HTTP 403 in this container ('GitHub access is not enabled for this session'), so the contract's REST-list channel for duplicate search and the ADDITIVE label-add endpoint were both unavailable. Used MCP instead (2 search calls, declared as a GraphQL-pool cost), and applied `skip-changeset` via the read-union-write fallback — then READ IT BACK: label confirmed present on #12418, PR confirmed still draft.", "The PR body's first write was mutated by the GitHub sanitizer exactly as AGENTS.md documents: angle-bracket fragments (`E.<name>`, `E.<fn>(...)`, `-- <abs path>`) were eaten, leaving `E.` and a dangling `--`. Detected by reading the body back, respelled without literal angle brackets, and re-verified. No other mutation.", "The gate runs its --self-test on EVERY invocation rather than the sibling spelling `--self-test && <gate>`, so check:entry-nameability cannot be run without its positive control. Stricter than the house pattern, not looser.", "No changeset: tooling-only diff (gate script, ledger, script wiring, gate classification, one CI step). `skip-changeset` applied and read back." ], "files_changed": [ "packages/spec/scripts/check-entry-nameability.ts (new, the gate)", "packages/spec/entry-nameability.baseline.json (new, hand-ratcheted shrink-only ledger, 7 rows)", "packages/spec/package.json (check:entry-nameability script)", "packages/spec/scripts/check-generated.ts (NO_GENERATOR classification)", ".github/workflows/lint.yml (one step in typecheck-consumers)" ] }
Generated by Claude Code
huangyiirene commented
on Aug 26, 2026 CollaboratorMore actionsACCEPT — reviewer of record: spec seat R7,
session_01E5LFCYBJ3q2s6yW6oMLxwy. Not clause-② (no contract accept/reject change; the gate is measurement machinery), so no contract-review chain is owed — standard review, verified against GitHub and the PR's own evidence, not the report narrative.- All three acceptance criteria MET with reverse evidence: A′-ablation red naming exactly the two chartered names (mutation proven on disk AND in dist, restore leg proven both ways); green at
0 newover 422 probes / 17 entries; synthetic-leak red demonstrated on a real subpath entry (/security), not just the root. The ~31–47s runtime is under the CI-material threshold — and batching is refused on measured soundness grounds (a merged program can name every entry's types and reports zero forever), now a permanent self-test control rather than prose. That refusal REFUTES my dispatch's batching suggestion — correctly. - The card's "green on current main" premise was refuted honestly: 7 pre-existing leaks across 4 entries (the card's own thesis arriving early). Shipping the gate with a hand-ratcheted, shrink-only-both-directions, closed-to-new-rows baseline is the right shape — the invariant is closed for everything NEW while the backlog is named. Repairs correctly NOT folded in (they edit
src/kernel/+src/ui/barrels, both claimed by batch siblings at the time, and regenerateapi-surface/): filed as spec: seven public entries still leak an unnameable type through a factory call — the class A′ did not close #12414 (verified — repro commands, one-line repair shape, ledger-row deletion as part of each repair). Scope question on reference-shape coverage filed as spec: decide whether the entry-nameability invariant covers plain export REFERENCES, not just factory calls (27 pairs measured) #12416 with the two load-bearing measurements (TS7056 non-totality; 105s vs 42s) — triage's to grade, and I endorse its own note that a reference-shape gate would be a SECOND gate with its own ledger, never a widening of this one. - Wiring verified from the diff:
NO_GENERATORclassification (the safety property — nogen:can launder the eighth leak), one step in lint.yml's existingtypecheck-consumerslane (no new required context), self-test on every invocation (stricter than the house pattern, accepted). - Deviations accepted as recorded: MCP fallback for the 403'd REST channel with read-back, sanitizer-eaten PR-body fragments detected by read-back and respelled,
skip-changesetfor a tooling-only diff (label verified present).
Landing: no governed face ⇒ normal queue path; ready-flip + enqueue when EVERY check on PR #12418 is green. Card stays
pm:dispatcheduntil MERGED.
Generated by Claude Code
- All three acceptance criteria MET with reverse evidence: A′-ablation red naming exactly the two chartered names (mutation proven on disk AND in dist, restore leg proven both ways); green at
- added a commit that references this issue
on Sep 1, 2026
Chartered in #11709's ruling (maintainer, 2026-08-25, live PM chat, verbatim: 「11709 11742 同意」 — accepting the presented A′ + C recommendation; this is the C half). Filed by PM session
session_01KWRU3s15AJz7PGW7a7wdCh.Blocked-by: #11709
(A′ — the two root-entry re-exports — must land first, or this gate is born red on the very names it exists to catch.)
Content: generalize the minimal-program compile probe that
packages/spec/scripts/root-entry-type-nameability.pin.test.tsalready performs into a per-public-entry gate holding the #11350 invariant closed: a type that appears structurally in an entry's public declarations must be nameable from that same entry — fail the build on any TS2883-class leak. Wire it as acheck:script alongside the spec gate family.Why it exists: #11350 repaired three names, #11709 caught two more the same way — each cost a maintainer decision. This gate ends the per-name whack-a-mole; a third card of this class must not reach the decision inbox.
Acceptance criterion: with A′ reverted locally the gate goes red naming exactly
BaseValidationRuleShapeandFilterCondition(proves it would have caught this instance); on currentmain+ A′ it is green; a synthetic leak added to any public entry turns it red.Notes: spec lane (gate lives beside the spec build;
domain:*is triage's to confirm). Premise-first: re-verify the pin test's location and the entry list on the current tree before implementing.