Skip to content

No gate holds the hand-written docs enumerations of @objectstack/spec subpaths and protocol namespaces equal to the exports map #17388

Description

@hotlong

The observation

Three hand-written pages under content/docs/ enumerate @objectstack/spec's subpaths or protocol namespaces, and nothing holds them equal to the package's exports map:

page what it enumerates
content/docs/deployment/troubleshooting.mdx ("Bundle size is too large" → "Available subpaths") the importable subpaths
content/docs/plugins/packages.mdx (@objectstack/spec → Exports) the 15 protocol namespaces
content/docs/getting-started/glossary.mdx ("Protocol Namespace", the layers table, and the per-namespace ### X Protocol sections) the 15 protocol namespaces, twice, plus one section per namespace

When PR #17372 (step 3 of #16325 — which remains open there; this card does not close it) deleted the ./cloud exports entry and added ./marketplace, every mechanical counter of the subpath set moved with it (packages/runtime/src/spec-subpath-alias-coverage.pin.test.ts, packages/spec/scripts/export-origins.test.ts, root-meta.test.ts, llms.txt via check:llms-txt, quick-reference.mdx via check:quick-reference-counts) — and all three pages above kept advertising cloud and never listed marketplace, because no gate reads them. The contract-review pass on that PR caught troubleshooting.mdx; the sweep it asked for found the other two. They were corrected by hand in the PR's patch round (ef1fda259).

Measured on that head: git grep -n -E 'Kernel, Cloud|Security, Cloud|cloud, qa' -- content/docs returns 0 lines (same-shape control naming Marketplace returns 4), so the pages are current today — the finding is that nothing keeps them current.

What a fix could look like

Any one of these; the first is the cheapest:

  1. Extend packages/runtime/src/spec-subpath-alias-coverage.pin.test.ts (it already reads packages/spec/package.json's exports and derives PUBLISHED_SUBPATHS) — or a root check:docs-* gate — to read the three enumerations and hold them equal to the derived set, the way check:quick-reference-counts holds quick-reference.mdx's per-section counts.
  2. Have packages/spec/scripts/build-docs.ts emit a generated subpath / namespace snippet that the three pages include, so the enumeration is produced rather than typed.
  3. Reduce the hand-written enumerations to one page and link the other two to it.

Note the troubleshooting list and the namespace lists are two different sets: the exports map has 16 ./* entries (meta-spelling is a subpath but not a protocol namespace), the namespace lists have 15.


Generated by Claude Code

Activity

  1. added theissue type on Sep 10, 2026
  2. hotlong commented on Sep 10, 2026

    @hotlong
    ContributorAuthor

    First-touch grading — graded pm:queue, routed domain:devx

    Dispatching PM seat, session session_f95e3874-e532-4748-a921-044aa2752a2b, 2026-09-10T12:0xZ.

    ⚠️ An execution seat is grading a finding, which is normally the triage seat's sole output. The authority is the maintainer's direct instruction in this session, verbatim (⛔ quoted, not translated): 「相关任务你都派发处理完」, covering the follow-ups this chain produced. The direct-dispatch channel routes them here. Recorded so the triage seat can see why, and can override.

    Grade: pm:queue. A named landing point and a named cheapest route exist (extend the existing pin that already derives PUBLISHED_SUBPATHS from packages/spec/package.json, or add a root docs gate), so there is nothing to ask before someone can act. It restores an invariant rather than widening a surface, which is the shape pm:queue collects.

    Route: domain:devx. The SUBJECT is documentation correctness held by a gate — content/docs/** and gate scripts are that lane, per the lane table's split of governance-of-docs-quality into devx. ⚠️ Stated because it is arguable: option 1's cheapest landing point is packages/runtime/src/spec-subpath-alias-coverage.pin.test.ts, and packages/runtime belongs to domain:cli; option 2 would land in packages/spec/scripts/build-docs.ts (domain:spec). The dispatch tells the dev to stop and report if its chosen route lands outside content/docs/** plus a gate script, rather than silently crossing a lane boundary.

    ⭐ Type Task — this is the invariant the whole #16325 chain existed to restore, applied to the one surface no gate covers: every mechanical counter of the subpath set moved when ./cloud was deleted, and all three prose enumerations stayed stale until a human sweep caught them.

  3. self-assigned this
    on Sep 10, 2026
  4. hotlong commented on Sep 10, 2026

    @hotlong
    ContributorAuthor

    Claim: PM direct maintainer dispatch (no numbered round — direct-dispatch channel)
    Session: session_f95e3874-e532-4748-a921-044aa2752a2b
    Branch: claude/issue-17388-docs-subpath-enumerations-gated
    Worktree: objectstack-issue-17388
    Domain: domain:devx
    File surface: content/docs/{deployment/troubleshooting,plugins/packages,getting-started/glossary}.mdx · one gate script (scripts/ or the existing packages/runtime/src/spec-subpath-alias-coverage.pin.test.ts) · package.json scripts + the CI workflow that runs the gate family it joins · .changeset/ (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: <default judgment tier> — ⛔ NOT the contract-review tier: this adds a gate over prose, it does not change what any published contract accepts or rejects
    Clause-②: no
    Serial constraints cleared: #17383 is queued in this lane and MAY land in packages/spec/scripts/ — same package as this card's option 2. It is held BEHIND this card rather than run beside it (fold-or-serial answered: serial, because the two share a possible package and neither is urgent enough to buy the risk). No other in-flight card declares content/docs/** or the runtime pin.


    Maintainer authorisation, verbatim (⛔ quoted, not translated): 「相关任务你都派发处理完」.

    Premise at dispatch time

    origin/main 776d64cd3 — the merge commit of #17372, which is what made this finding actionable. The three
    pages are correct today (the patch round ef1fda259 fixed them by hand); ⛔ this card is not a docs fix. It is
    the gate that keeps them correct, and the dev must not mistake a green grep for nothing to do.

    The measurement that motivates it, from the #17372 review: when ./cloud left the exports map, every mechanical
    counter moved with it
    — the anti-vacuity floor pin, the alias-coverage pin, export-origins, root-meta,
    llms.txt, quick-reference counts, ~25 in all — and all three prose enumerations stayed stale, because
    nothing reads them. Step 2 of that chain was FAILed in review by exactly this class of unlisted counter.

    ⚠️ Two sets, not one: the exports map has 16 ./* entries (meta-spelling is a subpath but not a protocol
    namespace); the namespace lists have 15. A gate that conflates them will be wrong on its first run.

  5. hotlong commented on Sep 10, 2026

    @hotlong
    ContributorAuthor

    os-dev-report
    {
    "issue": 17388,
    "status": "done",
    "branch": "claude/issue-17388-docs-subpath-enumerations-gated",
    "pr": "#17435",
    "premise_still_valid": true,
    "files_changed": [
    "scripts/check-docs-spec-enumerations.mjs (new, 1366 lines)",
    "package.json (+1: the check:docs-spec-enumerations script)",
    ".github/workflows/lint.yml (+20: one unconditional step in the Lint & Repo Gates job)"
    ],
    "summary": "Landed the card's route 1 as a ROOT gate over content/docs/, not inside packages/spec/scripts/ or packages/runtime/src/ -- the lane boundary the dispatch named is respected in both directions (those two trees are read-only inputs here). The card's warning was correct and is the whole design: the gate derives TWO sets and never one. SUBPATHS = the 16 bare ./NAME keys of packages/spec/package.json's exports map; NAMESPACES = the 15 of those whose declared CATEGORY_TITLES title ends in ' Protocol' (meta-spelling is an importable subpath titled 'Meta-Spelling Vocabulary', not a protocol). Both counts were re-derived on the tree by the gate itself, not taken from the card. The split is DERIVED rather than typed in: CATEGORY_TITLES in packages/spec/scripts/lib/category-title.ts is already held total over packages/spec/src/ in both directions by categoryTitleCoverage (which stops gen:docs), so display names come from the same declaration -- which is where AI / API / QA / UI come from with no second abbreviation list, and why a 'Qa' in prose is a finding (the #5853 class). Five enumerations are held, each independently: the ordered subpath sentence in troubleshooting.mdx (order included, because the sentence itself claims 'in its order'), the parenthesised namespace list in packages.mdx, and the glossary's namespace sentence, layers table (plus its own partition/no-duplicate rule) and per-namespace sections. Numeric namespace-count claims are swept across content/docs/, excluding content/docs/releases/ so the gate can never demand an edit in a tree AGENTS.md forbids editing. A conflating build of this gate would be red on the real tree on its first run, since the namespace lists hold 15 and the subpath sentence holds 16; the self-test pins that so it cannot be lost. NOT FILED as an out-of-scope finding, and stated here instead: check:pm-dispatch-gates went red on my first commit because the fixture's 'qa' subpath, written as a markdown code span where it has to be, was re-anchored to a watch hint on the real tracked scripts/qa directory this gate never reads. That guard worked -- it went red and named both the gate and the hint -- so there is no defect to card; only the diagnostic distance is long. Fixed in the second commit by picking fixture subpath names against the tracked listing of the gate's own directory; measured after the fix, the gate's hint set is identical with and without a tree.",
    "tests": "GATE ITSELF (final head 65cde5f): pnpm check:docs-spec-enumerations exit 0 -- self-test 66 cases over 9 battery-floored sections, then the live scan: 'OK ... 16 subpath(s) held ORDERED in troubleshooting.mdx; 15 protocol namespace(s) [Data, System, Kernel, AI, Automation, API, UI, Contracts, Integration, Security, Studio, Marketplace, QA, Identity, Shared] held in packages.mdx and three times over in glossary.mdx (sentence, layers table, sections); 4 numeric count claim(s) across content/docs/** agree with 15. The two sets differ by meta-spelling.' ACCEPTANCE / ABLATION, run twice (once per commit, the second time at the final head), both legs with an EXIT INT TERM trap and absolute paths. LEG 1, the SUBPATH set: dropped ', marketplace' from the Available subpaths sentence; on-disk proof anchor before=1 after=0; gate exit 1 with 'content/docs/deployment/troubleshooting.mdx:347 [missing] the Available subpaths sentence does not list marketplace, which the exports map publishes.' LEG 2, the NAMESPACE set, covered independently: added a fictitious Cloud to the glossary layers table; anchor before=1 after=0, mutation before=0 after=1; gate exit 1 with 'content/docs/getting-started/glossary.mdx:22 [unknown] the layers table's Namespaces it includes column lists Cloud, which the exports map does not publish.' RESTORE PROVEN BY BYTES both legs: hash-object == the HEAD blob (2055cfb07539e7e9b38a882dd08234c5422a21f4 and 18de8baba1ca5870fd6f0da51d7d08ef98cbae16), git diff HEAD --quiet exit 0, and the gate exit 0 again on the restored tree. One honest note: leg 1's mutation counter reads before=438 after=438 because the replacement is the empty string and grep -c -F '' matches every line; the load-bearing half is the anchor counter, 1 -> 0. DERIVED GATES: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack over the real change set produced 63 commands; all 62 runnable ones (excluding check:pm-dispatch-gates, whose own header says to run it detached because a full run exceeds the container's foreground cap) were run one at a time with the exit code captured before any pipe, twice -- once per commit -- with identical results: 54 exit 0. Of the 8 non-zero: 5 are exit 3 PREREQUISITE NOT MET on an unbuilt workspace (dts-closure, dual-build-cjs-loads, lean-entry-closure, sourcemap-no-sources-content, type-check-debt) -- NOT MEASURED, not red, declared to CI, which builds first. The other 3 exit 1 (check-step-collectors --self-test, check:bash32-floor, check:merge-driver) were each CONTROLLED against the merge base 9224204 in a separate clean worktree and fail identically there: pre-existing local-environment failures (a bash-3.2 simulation harness that cannot remove a builtin on this box, and two harnesses hitting 127 for a runner not installed here), not my diff. check-step-collectors was additionally controlled inside my own worktree with my lint.yml step reverted to base -- still exit 1, restore proven by blob hash and git diff HEAD exit 0. check:pm-dispatch-gates was run detached at the final head as its header instructs; the one assertion my first commit broke ('and it takes NOTHING away') is green there.",
    "gates": "63 commands derived by node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack against the REAL change set on a fresh tree (3 committed paths, three-dot vs merge base 9224204). All 62 runnable ones were run one at a time, exit code captured before any pipe, TWICE -- once per commit -- with identical results both times: 54 exit 0, including the new pnpm check:docs-spec-enumerations. 5 exit 3 = PREREQUISITE NOT MET on an unbuilt workspace (check:dts-closure, check:dual-build-cjs-loads, check:lean-entry-closure, check:sourcemap-no-sources-content, check:type-check-debt): read as NOT MEASURED, not as red, and declared to CI, which builds first. 3 exit 1 -- node scripts/check-step-collectors.mjs --self-test, pnpm check:bash32-floor, pnpm check:merge-driver -- each CONTROLLED at the merge base 9224204 in a separate clean worktree and failing identically there, so pre-existing and environment-caused on this box (a bash-3.2 simulation harness that cannot remove a builtin here; two harnesses hitting exit 127 for a runner not installed here). check-step-collectors additionally controlled inside my own worktree with my lint.yml step reverted to base: still exit 1, restore proven by blob hash and git diff HEAD exit 0. The 63rd, pnpm check:pm-dispatch-gates, was run DETACHED as its own header instructs and completed in full: 'dispatch-gates self-test: 1678 cases pass.' -- that includes the 'and it takes NOTHING away' assertion my FIRST commit broke and the second fixed. Repo-wide pnpm lint (eslint . --no-inline-config) was run IN FULL, not narrowed: exit 0, zero findings, so there is no narrowing to declare. pnpm check:nul-bytes exit 0, plus a direct control-character sweep (grep -naP over the three changed files): no hits. CI on PR #17435: in_progress at report time -- the report is delivered at draft-PR time and CI convergence is the PM's to read, not mine to wait on.",
    "line_budget": "not applicable -- no skills/** or .claude/** file is touched, so no line/token ratchet applies. For scale: +1387 lines total, of which scripts/check-docs-spec-enumerations.mjs is 1366 (roughly 120 lines of header, ~450 of extraction and comparison, the rest fixtures and the 9-battery self-test) -- in line with its neighbours check-quick-reference-counts.mjs (1022), check-runtime-services-index.mjs (1196) and check-overlay-whitelist-table.mjs (1377), whose structure and battery-floor idiom it follows.",
    "deviations": [
    "No changeset file. This PR publishes nothing -- one root gate script, one root package.json script entry (root package is private: true), one CI step -- and check-empty-changeset.mjs REJECTS a newly added empty-frontmatter changeset, naming the skip-changeset label as the instrument for exactly this case. The label was applied with an additive REST POST and read back: the PR now carries skip-changeset and size/xl, so the size labeler's whole-set PUT did not strip it.",
    "check:pm-dispatch-gates was run DETACHED rather than in the foreground, following the explicit instruction in scripts/pm/check-dispatch-gates.mjs's own header (a full run exceeds the container's foreground command cap and would be SIGTERMed mid-battery).",
    "Five derived gates are declared to CI as NOT MEASURED rather than run: they refuse with PREREQUISITE NOT MET on an unbuilt workspace, and building the workspace is not something this diff's correctness depends on."
    ],
    "mcp_calls": "0 -- every GitHub read and write went through the gh CLI (REST for the label write, which was probed by using it and returned 200; no MCP GitHub call was made at any point)",
    "open_questions": [],
    "out_of_scope_findings": []
    }

  6. hotlong commented on Sep 10, 2026

    @hotlong
    ContributorAuthor

    os-dev-report

    {
      "issue": 17388,
      "status": "done",
      "branch": "claude/issue-17388-docs-subpath-enumerations-gated",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/17435",
      "premise_still_valid": true,
      "summary": "Patch round 1, both review items, on the same claim and branch (65cde5fe1 -> ec579915a, no rebase, no force-push, no second Claim). Item 1: the count sweep now reads prose only -- readFences() marks fenced lines and readCountClaims() skips them, scoped to the sweep alone so the five anchored enumerations keep their scope; 350 of the 387 swept pages carry a fence, so the exposure was corpus-wide. I declined the indented-code half and say so rather than implementing it: measured zero indented blocks outside a fence across all 387 pages, and a line-based reader cannot separate a four-space list continuation from code without block context, so the error would land on the expensive side. The blinding vector the change itself opens is closed -- an unterminated fence on a governed page is now a structural refusal (measured: zero in the corpus today). I agree with the decision on subset sentences and added no heuristic. Item 2: battery 8 declared 12 while registering 16; floors now equal their real counts across every battery (79 cases, 79 declared) and the verdict's structural-case number is read from the run instead of typed, so that prose cannot drift again. The optional wording note was cheap and is taken -- a run-on list item is now reported as a run-on, discriminated by a sentence break, which no subpath or declared display name can contain.",
      "tests": "GATE + SELF-TEST at ec579915a: `pnpm check:docs-spec-enumerations` exit 0 -- 'OK ... 16 subpath(s) ... 15 protocol namespace(s) ... 4 numeric count claim(s) across content/docs/** agree with 15', the same 4 claims as before the change, so the exclusion is a no-op on today's tree and the glossary floor of 3 still holds. `--self-test` exit 0 -- 'OK ... 79 cases over 10 batteries ... and 20 cases hold the structural refusals'. Declared floors sum to 79 (9+8+6+10+4+3+15+20+2+2), equal to the registered count. ITEM 1 MUTATION, BOTH LEGS, real tree, content/docs/deployment/validating-metadata.mdx: LEG A, 'OK spec: 16 protocol namespaces exported' inside a ```text fence -> patched gate exit 0 (PASSES). LEG A-prime, the SAME page under the pre-patch gate restored from 65cde5fe1 -> exit 1, '1 divergence(s)' -- so the fence moved the verdict rather than the line being harmless. LEG B, 'The spec package publishes 16 protocol namespaces today.' as bare prose -> patched gate exit 1 (STILL FAILS). On-disk landing observed per leg by counting the injected marker (1 occurrence, 1 fence line added), never from an editor's exit code. RESTORE PROVEN BY BYTES: HEAD blob c0d9a0dbce1c98395475efc5a6fe1eb174ede079 == on-disk blob after restore, and `git diff HEAD` empty; trap on EXIT INT TERM with absolute paths, probe file removal verified. ITEM 2 ABLATION: deleting the whole refused('the count claims drained away', ...) block (2 cases) -> patched self-test exit 1, naming it precisely: battery 8 'registered 18 case(s), below its pinned floor of 20'. Same deletion applied to the PRE-PATCH copy -> exit 0, printing '64 cases over 9 batteries' and '12 structural cases are refused' -- the reported defect reproduced. Restored: exit 0, blob hash identical to HEAD, `git diff HEAD` empty. NO REBUILD LEG APPLIES AND NONE IS CLAIMED: the gate is a plain .mjs run from source, no build and no dist/ in its path, so the on-disk edit is what runs; anchor uniqueness was asserted (1 occurrence) before mutating and 0 after, so the ablation cannot have been a no-op. GATE FAMILIES: 63 derived by `dispatch-gates --commands`; `--ran` with exit codes recorded per command exits 0 -- 63 accounted for, 0 UNRUN, 58 run, 5 NOT MEASURED. The 5 are exit 3 'PREREQUISITE NOT MET' (dts-closure, dual-build-cjs-loads, lean-entry-closure, sourcemap-no-sources-content, type-check-debt) -- they read dist/, this diff touches no package source, and CI builds them. 3 reds are pre-existing macOS-environment failures that REPRODUCE IDENTICALLY ON THE MERGE BASE 92242041e, a tree carrying none of this work: check-step-collectors --self-test (30 failures, 'got 127') and check:bash32-floor (8 of 153) both because /bin/bash here is 3.2.57 with no mapfile, and check:merge-driver on a runner it cannot find. check:bash32-floor's verdict over the real tree is green ('33 shell file(s) ... 0 finding(s)'), so the lint.yml step from round 1 introduces no bash 4 construct. Filed as #17458. LINT: full repo-wide `pnpm lint` (eslint . --no-inline-config, the entire population, NO narrowing to justify) exit 0 in 27s at ec579915a. AFFECTED PACKAGES: `turbo ls --affected` against merge base 92242041e returns 0 packages, so no package test or typecheck is owed. NUL/control bytes: check:nul-bytes exit 0, plus a manual grep -naP sweep over the diff -- clean. The gate has no separate test suite (git grep for its filename hits only package.json and itself), so its --self-test is the suite and it was run.",
      "mcp_calls": "0 -- no MCP GitHub calls this run; git, the zero-config REST channel via gh api (probed with a control word that returned 5) and gh pr view/edit only",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #17458: two --self-test harnesses assume /bin/bash is bash 4+ and so fail on macOS -- check-step-collectors (30 failures, exit 127 on a mapfile line it faithfully replays from the workflow) and, more pointedly, check-bash32-floor itself (8 of 153: its simulated-3.2 harness cannot verify it removed a builtin that was never present, so the gate defending the bash 3.2 floor cannot self-test on bash 3.2). Both reproduce on merge base 92242041e. Searched first: 'bash32-floor', 'step-collectors', 'mapfile' over open issues; the near neighbour #17141 rules on that gate's BOUNDARY, not its harness, so this is not a duplicate. Filed unassigned and unlabelled for PM triage; both gates are green on CI's Linux."
      ]
    }
    

    Generated by Claude Code

  7. hotlong commented on Sep 10, 2026

    @hotlong
    ContributorAuthor

    ACCEPT — PR #17435 at ec579915a. Adversarial review: PASS; both its ranked findings fixed in patch round 1.

    Reviewer of record: this seat, at the tier this card was dispatched at. ⛔ Not a clause-② card — it publishes
    nothing and changes no contract's accept/reject behaviour — so no contract-review-tier clearance is owed. An
    adversarial review subagent was run anyway, because this gate executes on every PR in this repo.

    Final readings, each reconciled

    item reading
    CI 39 check runs, reconciled against total_count: 39 — 32 success, 7 skipped, 0 otherwise. Lint & Repo Gates, the job carrying the new step, success
    path surface pulls/17435/files paginated: 3 == changed_files 3; 0 governed-surface files
    ⭐ lane boundary 0 files under packages/spec/ or packages/runtime/ — the dispatch's constraint held in both directions, which is why #17383's serial hold was released (comment 5620389931)
    patch delta 65cde5fe1..ec579915a = the gate script alone, +202/−18

    The two derived sets — re-derived by this seat, and the dev was right

    The dispatch called these the single most likely way the card ships broken, so this seat computed them
    independently. A cruder filter gave 18/17; reconciled properly it is 16/15, matching the dev:

    ./* entries 18  −  2 file-like (./openapi.json, ./package.json)   = 16 bare subpaths
    categories titled "… Protocol" 17  ∩  the 16 published            = 15 namespaces
       meta-spelling         published, titled "Meta-Spelling Vocabulary"  → not a namespace
       conversions/migrations  titled Protocol, not published             → not a subpath
    

    ⇒ The split is derived — CATEGORY_TITLES ending in " Protocol", intersected with the published bare
    subpaths — not two typed-in numbers. Both edge cases fall out of the derivation. That is better than the
    dispatch specified, which asked only that the two counts not be conflated.

    The review's findings, ranked as instructed, and what happened to each

    Ranked first — "too loud", because a repo-wide gate people route around is worse than no gate.

    • ⭐ Fixed: the count sweep fired inside fenced code blocks. A page quoting real tool output —
      OK spec: 16 protocol namespaces exported — turned it red. Scale: 350 of the 387 swept pages carry a
      fence
      , so the exposure was corpus-wide, not theoretical. The gate's own future documentation would have
      tripped it.
    • ⛔ Deliberately not fixed: honest subset sentences ("touches 3 protocol namespaces") still fail.
      Rewording passes, the message names file:line, and narrowing the pattern to demand a totality qualifier
      risks the opposite error — missing a real stale total. Recorded, not lost.

    Ranked second — coverage. Fixed: battery 8 declared floor 12 while registering 16, so deleting a
    refused() block left the self-test green and still printing "12 structural cases". Floors now equal real
    counts across all ten batteries (79 declared == 79 registered), and the verdict's number is read from the
    run rather than typed
    , so that prose cannot drift again.

    Two things the dev did that the dispatch did not ask for

    1. ⭐ It closed the blinding vector its own fix opened. Skipping fenced lines creates a way to blind the
      gate: open a fence. An unterminated fence on a governed page is now a structural refusal (measured: zero in
      the corpus today).
    2. ⭐ Its ablation carried a reverse control. Beyond "fenced quote passes, bare prose still fails", it ran
      the same page against the pre-patch gate and got red — proving the fence moved the verdict, not
      that the line was harmless. It did the same for the self-test floor, reproducing the reported defect on a
      pre-patch copy (exit 0, printing the stale "64 cases" / "12 structural"). Restores proven by blob hash and
      an empty git diff HEAD on every leg.

    And it declined the half of item 1 this seat got wrong: indented code blocks. Measured zero outside a
    fence across all 387 pages, and a line-based reader cannot separate a four-space list continuation from code
    without block context — so the change would land on the expensive side. It said so instead of implementing it,
    which is what the dispatch asked for.

    Deviations accepted

    No changeset; skip-changeset label instead. check-empty-changeset.mjs itself routes .github/, content/
    and CI-internal scripts to that instrument and rejects a newly added empty changeset; the root package is
    private: true. The review judged the instrument correct. check:pm-dispatch-gates run detached per its own
    header. Five gates read dist/ and returned PREREQUISITE NOT MET on an unbuilt tree — declared NOT MEASURED,
    not green, and owed to CI, which builds first. Three local reds controlled against merge base 92242041e and
    failing identically there; filed as #17458 (graded pm:queue / domain:devx / Bug).

    Landing

    Ready + queue (ruleset 12119582, SQUASH, ALLGREEN). ⛔ Nothing bypassed. Watching to MERGED — at which
    point the invariant this whole chain existed to restore finally covers the one surface that had no gate at all.

  8. hotlong commented on Sep 10, 2026

    @hotlong
    ContributorAuthor

    MERGED — the gate is on main. pm:dispatched stripped in the same write.

    Dispatching PM seat, 2026-09-11T00:5xZ.

    Two readings, plus a third that checks the thing actually runs.

    1. pulls/17435 ⇒ merged: true, merged_at: 2026-09-10T16:20:59Z, merge commit 0f384078c.
    2. On the ref: git cat-file -e origin/main:scripts/check-docs-spec-enumerations.mjs succeeds.
    3. ⭐ And the part a merge alone does not prove — a gate nothing runs is not a gate:
      package.json registers check:docs-spec-enumerations (1 hit) and .github/workflows/lint.yml carries the
      step (1 hit), both on origin/main. It is wired, not just present.

    Auto-close left pm:dispatched on a closed card — the same half-state #16450, objectui#8225 and #16325 each
    left behind. Stripped; domain:devx stays, because ownership is not state.

    What is now held that was not held before

    Five hand-written enumerations under content/docs/** are checked against packages/spec/package.json's
    exports map on every PR: the ordered subpath sentence in troubleshooting.mdx, the namespace list in
    plugins/packages.mdx, and the glossary's namespace sentence, layers table (with its own partition and
    no-duplicate rules) and per-namespace sections — plus a corpus-wide sweep of numeric namespace-count claims,
    prose only.

    The two sets are derived, not typed: 16 bare ./NAME subpaths, and the 15 of those whose CATEGORY_TITLES
    entry ends in " Protocol". meta-spelling is published but is not a protocol; conversions and migrations
    are protocols but are not published. Both edge cases fall out of the derivation, so the next subpath change
    moves the gate with it instead of leaving prose behind.

    Why this card existed, kept here because the reason is the lesson

    #16325 deleted the @objectstack/spec/cloud subpath. Every mechanical counter of that set moved with it —
    an anti-vacuity floor pin, the alias-coverage pin, export-origins, root-meta, llms.txt, the
    quick-reference counts, around 25 in all. Each moved because something went red and pointed at it.

    All three prose enumerations stayed stale, and were found only because a contract-review pass asked for a
    sweep. One of them, troubleshooting.mdx, was still telling readers that cloud was an importable subpath on
    the day the subpath stopped existing.

    ⇒ That chain spent three steps and three repos restoring the rule that a declaration must match what is real.
    The documentation was the same defect in the one corner no gate covered. It is covered now.

    Follow-up state at close

    card state
    #17383 (ratchet merge hole) pm:queue, serial hold released — this PR touched neither packages/spec/ nor packages/runtime/
    #17458 (two self-tests assume bash 4+) pm:queue, domain:devx, Bug — found while running this card's gates
    #17370 (AGENTS.md row) pm:queue, routed domain:skills
    #17429 (.objectui-sha 584 behind) pm:queue
    #17427, #17387 needs-user-decision — with the maintainer
    objectstack-ai/cloud#2173, objectstack-ai/objectui#8979 pm:on-hold with machine-readable restart conditions, waiting on a spec release
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions