Skip to content

docs(references): the generated security category index omits misc, the one page with no .zod.ts behind it #11260

Description

@os-zhuang

Found while implementing #10738 (a gate holding hand-kept section landing indexes to their meta.json). Out of scope there: that gate only holds pages carrying a ## What's in this module heading, and the generated content/docs/references/*/index.mdx pages carry none. Filing rather than fixing — packages/** is claimed this round by #10921.

Measured on origin/main @ 98ea3443f

content/docs/references/security/ ships five pages. Its meta.json declares all five. Its index.mdx grid cards four:

$ cat content/docs/references/security/meta.json
{ "title": "Security Protocol", "pages": ["explain", "misc", "permission", "rls", "sharing"] }

$ grep -o 'href="/docs/references/security/[a-z-]*"' content/docs/references/security/index.mdx
href="/docs/references/security/explain"
href="/docs/references/security/permission"
href="/docs/references/security/rls"
href="/docs/references/security/sharing"

$ ls content/docs/references/security/misc.mdx
content/docs/references/security/misc.mdx

So /docs/references/security/misc exists, is generated, and is routed in the sidebar — but a reader on the category overview cannot reach it.

Swept across all 14 references/* categories: security is the only one whose meta.json carries a misc entry, and the only one affected. Every other category's grid matches its meta.json exactly.

Mechanism

Both files come out of the same generator run, packages/spec/scripts/build-docs.ts (gen:docs), from two different code paths that disagree about the misc catch-all.

The card emitter (~line 806) iterates zodFiles — page names derived from .zod.ts modules:

Array.from(zodFiles).sort().forEach(zodFile => {
    // ... "This aligns the index with `meta.json`, which already lists only generated pages."
    if (!wasEmitted(path.join(DOCS_ROOT, category, `${zodFile}.mdx`))) return;

misc is the catch-all bucket, and the file itself says twice that it has no module behind it (~line 233, "no file behind it (the misc catch-all bucket)"; ~line 409, "the misc catch-all has no file behind it"). So misc is never in zodFiles, the loop never considers it, and the wasEmitted guard — which is what the comment leans on — never runs for it. The meta.json builder (~line 790, via SECTION_GROUPS / the flat list at ~line 569) takes a different route and does include it.

The comment's claim "This aligns the index with meta.json" is therefore false for exactly this bucket, and it is the kind of false that reads as verified.

Suggested shape (not a decision)

Card the pages meta.json actually declares rather than re-deriving from zodFiles, keeping the wasEmitted guard so an undelivered page still cannot be carded. That makes the comment's stated invariant true by construction instead of by coincidence, and closes the whole class rather than special-casing misc.

Worth checking in the same pass whether a category that is all misc would emit an empty grid.

Refs

#10738 (where this was found) · #10834 (open finding on a different defect in the same generated index) · packages/spec/scripts/build-docs.ts

Activity

  1. added theissue type on Aug 23, 2026
  2. os-zhuang commented on Aug 23, 2026

    @os-zhuang
    ContributorAuthor

    Triage: lands in packages/spec/scripts/build-docs.ts (spec docs generator — tooling that orbits the spec contract) ⇒ domain:spec, type Bug (the generated index breaks the invariant its own comment claims, and a routed page is unreachable from its category overview), → pm:queue.

    PM-suggested route (measure first, the card's shape is sound): card the pages meta.json declares, keep the wasEmitted guard — the stated invariant becomes true by construction and closes the class, not just misc. In-card edge to verify: a category that is all-misc must not emit an empty grid.

    Serial/fold note for the spec seat: #10834 is an open finding on a different defect in this same generated index — before dispatching, answer fold-or-serial against it explicitly (five gates; same file, but only fold if it is the same defect shape with the same fix, which the card says it is not — expect serial with declared regions). Regenerated output must come from the generator run, never hand-edited. Size/model suggestion: S–M, opus for the generator-path change.


    Generated by Claude Code

  3. self-assigned this
    on Aug 23, 2026
  4. os-steve commented on Aug 23, 2026

    @os-steve
    Collaborator

    Claim: PM seat session_01T9cDbY2NBiVJWYx3BpWfH2 (/pm-dispatch, spec lane) — dispatching to an os-dev subagent.
    Session: session_01T9cDbY2NBiVJWYx3BpWfH2
    Branch: claude/issue-11260-security-index-misc
    Worktree: ../objectstack-11260 (created by the dev agent, per-task)
    Domain: domain:spec
    File surface: packages/spec/scripts/build-docs.ts (card emitter path, ~line 806) + regenerated content/docs/references/**/index.mdx output (generator-run only, never hand-edited).
    Container & model: os-dev subagent, opus (S–M, generator-path judgment per triage).
    Clause-②: no — docs generator + regenerated docs; no contract acceptance change, no packages/spec/src/** diff expected (stop-and-report if the fix somehow requires it).
    Fold-or-serial answer of record (vs #10834, same generated index): serial — #10834 is a different defect shape in the same file, currently unclaimed and not in any queue, so there is no live overlap; this card fixes only the misc-omission class (card the pages meta.json declares, keep wasEmitted), and #10834 stays an independent finding. The dev must not fold it.
    Serial constraints vs in-flight: none (#11255 in platform-objects; #11250/#11449/#11458 already in landing).


    Generated by Claude Code

  5. claude commented on Aug 23, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 11260,
      "status": "done",
      "branch": "claude/issue-11260-security-index-misc",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/11483",
      "premise_still_valid": true,
      "summary": "Premise re-verified at merge base b372318836 before editing: security/meta.json still declared 5 pages including misc, index.mdx still carded 4, and the emitter still iterated zodFiles. Fixed by construction rather than by special-casing misc: build-docs.ts 2.5 now iterates the page list 2 wrote into each category's meta.json (handed over in a new categoryMetaPages map, the same way categoryPageSchemas is handed to the root index) instead of re-deriving one from the .zod.ts files on disk, and KEEPS the wasEmitted guard - so a page that produced no reference file still cannot be carded into a dangling 404 (9 such slugs across 4 categories are still correctly excluded), while a declared page the run did not emit now STOPS the build instead of silently thinning the grid, which is exactly how misc was lost. The rule was extracted to packages/spec/scripts/lib/category-index.ts (the move #7658 and #4912 made for the same reason) so the all-misc edge could be asserted at all. All-misc edge answer: no such category exists in the tree (security is the only one declaring misc, and it does so alongside 4 zod-derived pages), but it was reachable in TWO broken shapes, both now closed - (a) a category with .zod.ts files whose every published schema falls to the catch-all would have rendered a literally EMPTY <Cards> grid, every zod slug filtered by wasEmitted and misc never considered; (b) a category with NO .zod.ts files but published schemas (security proves plain-.ts declarations reach the catch-all) hit the old 'if (zodFiles.size === 0) return' and got NO index.mdx at all while 2 still wrote its meta.json and pages. The guard is now keyed off the same declared list, so meta.json exists iff index.mdx exists, and an empty grid is unreachable from a non-empty declaration by assertion, not by argument. Docs came out of the generator run (gen:docs), never hand-edited. Diff touches no packages/spec/src/** - generator script, its new lib + test, the regenerated page, and a changeset.",
      "tests": "REVERSE VERIFICATION, both legs from committed source. BEFORE: `pnpm --filter @objectstack/spec check:docs` on the unmodified generator, exit 0, printing '229 generated files in sync with packages/spec' with the security grid at 4 cards - so the omission is live generator behaviour, not stale committed output. AFTER: `pnpm --filter @objectstack/spec gen:docs` exit 0, printing 'Generated 229 files'; `git diff --stat -- content/docs` = 'content/docs/references/security/index.mdx | 1 +  /  1 file changed, 1 insertion(+)' - the whole 14-category post-regen sweep in one measurement, only security moved, every other grid byte-identical. The added line is '<Card href=\"/docs/references/security/misc\" title=\"Misc\" />' with NO description attribute, correct because misc has no source file to point at. Post-regen a probe over all 14 categories shows cards == declared pages everywhere (was 13/14). No ablation/mutation leg was needed or run: the change is in a tsx-executed generator script with no dist on the path under test, so there is no build-staleness trap to defeat here; the equivalent 'can it fail' proof is the unit test's `() => false` case, which drives the undelivered branch directly. BUILD/TYPES/TESTS: `pnpm --filter @objectstack/spec build` exit 0 (full dts, not OS_SKIP_DTS, since check:generated reads the built dist); `pnpm --filter @objectstack/spec typecheck` exit 0 = tsc --noEmit + check:scripts-typecheck + check:test-typecheck, covering both new files; full `pnpm --filter @objectstack/spec test` exit 0, 'Tests 11171 passed (11171)'; the new scripts/category-index.test.ts 7 passed. GATES: list derived by `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (exit 0; 26 path-matched + the test-file convention family + the 5 changeset families; derived from merge base b37231883, not recalled). 37 distinct gate invocations green, including check:docs, check:generated (14/14 artifacts current, after the build), check-section-landing-index, check-doc-frontmatter, check:doc-anchors, check:doc-authoring, check:docs-audit-scope, check:docs-redirects, check:quick-reference-counts, check:role-word, check:published-readme-links, check:published-files, check:merge-driver, check:slot-lookup, check:test-source-alias, check:type-source-resolution, check:cross-package-test-inputs, check-ci-filter-parity, check-plugin-teardown-shape, check-affected-docs, check:empty-state, check:liveness, check:strictness-ledger, check:variant-docs, the 5 changeset gates, and the test-file convention family (check:query-options-erasure, check:type-check-coverage, check:engine-double-contract, check:where-matcher, check:nul-bytes). Every exit code captured by redirect-then-capture, never read through a pipe. DECLARED UNRUN, not silently skipped - 4, all blocked by unbuilt dist in this worktree and all failing BEFORE reading anything this PR touches: check-dev-prereqs.mjs ('67 of 67 workspace packages declare an entry point under dist/ that is not on disk'), @objectstack/lint check:doc-formula-expressions and check:doc-security-posture (ERR_MODULE_NOT_FOUND on @objectstack/formula/dist/index.mjs and packages/lint/dist/index.js), and check:type-check-debt (its --re-measure needs the whole workspace closure built). check:generated was red on the same cause before the spec build and green after, which is the phantom that gate warns about by name. CI runs all four on a built tree. All readings are of commit 5f28297c15: gates ran against the working tree and `git status` reports it identical to HEAD.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #11482: build-docs.ts section 3 builds the ROOT references/meta.json from a THIRD enumeration (.zod.ts counts, the one this PR moved the card grid off), so a category publishing only plain-.ts schemas would get its pages, its meta.json and now its index.mdx while being absent from the sidebar - same class one level up, latent (all 14 categories have .zod.ts files today), unchanged by this PR, labelled finding and deliberately not pm:queue",
        "not filed, noted only: #10834 is the pre-existing open finding on a different defect in this same generated index. Left untouched and independent, per the dispatch's serial answer of record - I did not touch its defect and its shape did not change under this fix"
      ]
    }

    Generated by Claude Code

  6. os-steve commented on Aug 23, 2026

    @os-steve
    Collaborator

    PM verdict: ACCEPT — PR #11483

    Reviewed the actual diff. The fix is the card's by-construction shape done properly: §2 hands the declared page list to §2.5 via categoryMetaPages (filled at the same stroke that writes each meta.json), the grid iterates that list, and wasEmitted is kept — applied inside the extracted categoryGrid() (scripts/lib/category-index.ts, the #7658/#4912 move) so the no-instance all-misc edge is assertable at unit level. Two subtleties verified against the diff:

    1. The undelivered build-stop and the 9 still-excluded slugs don't conflict: unrepresentable .zod.ts slugs never entered meta.json (it's built from emitted pages), so they're absent from the declared list — excluded with no stop. The stop fires only if meta.json declares a page the run didn't emit, which both-lists-one-source makes unreachable on any content state; it guards the future re-split, loudly, which is the right replacement for the silence that shipped this bug.
    2. if (zodFiles.size === 0) → if (declared.length === 0) also closes the empty-grid / missing-index shapes (b) the report names, and couples "meta.json exists ⟺ index.mdx exists" to one map. Regen sweep proves no collateral: 14 categories, git diff --stat = security/index.mdx | 1 + — the misc card, alphabetically placed, correctly without a Source: line. Ordering pinned so the 13 correct grids don't churn.

    Serial ruling honoured (#10834 untouched, shape unchanged under this fix). Out-of-scope finding #11482 (the root references/meta.json third enumeration — same class one level up, latent) correctly filed as a finding, not folded. Declared-unrun gates (4, unbuilt-workspace class) are honest NOT-MEASURED refusals; CI runs them.

    Landing: flipping ready now; enqueue on every-check-green. Fixes #11260 closes this card at merge; hygiene at the landing stroke.


    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

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions