Skip to content

[finding] ManifestSchema.version's own TSDoc @example "2.1.0-beta.1" is refused by its regex — copy the documented example and parse throws #17461

Description

@os-bill

Filed by the domain:spec execution seat while implementing #17070, using Claude Code. ⛔ Deliberately not repaired there: #17070's declared file face is PluginSchema.version and PluginLoader's predicate, and this key is neither. No severity asserted, no lane asserted — both are triage's.

The finding

ManifestSchema.version (packages/spec/src/kernel/manifest.zod.ts) documents two @example values in its TSDoc. Its regex accepts one of them and refuses the other.

  /**
   * Package version following semantic versioning (major.minor.patch).
   *
   * @example "1.0.0"
   * @example "2.1.0-beta.1"      <-- refused by the regex two lines below
   */
  version: z.string().regex(/^\d+\.\d+\.\d+$/).describe('Package version (semantic versioning)'),

Reproduce, no build needed:

node -e "const re=/^\d+\.\d+\.\d+$/; for (const v of ['1.0.0','2.1.0-beta.1']) console.log(re.test(v), JSON.stringify(v))"

true "1.0.0"
false "2.1.0-beta.1"

An author who copies the second documented example verbatim gets a ZodError from ManifestSchema.parse. This is a documented example that fails when copied, not a missing member or a stale prose nuance.

It is not an accident of the regex — the refusal is pinned

manifest.test.ts already pins the same class as invalid, deliberately:

const invalidVersions = ['1.0', '1', 'v1.0.0', '1.0.0-beta'];

So the schema, its test, and its prose sentence (major.minor.patch) all agree that prerelease suffixes are refused. The only artifact that disagrees is the @example line — which is why this reads as an editing residue in the TSDoc rather than a question about what the key should accept.

Why it is being filed rather than resolved in place

#17070 rules on PluginSchema.version and PluginLoader, and its dispatch declared a file face that does not include manifest.zod.ts. This key is also the precedent #17070 was told to copy — its TSDoc spells (major.minor.patch) explicitly instead of leaning on the word SemVer — so touching it in the same change would have edited the yardstick and the thing being measured at once.

The shape of the decision

  • Delete or correct the @example. Cheapest, and consistent with the pinned refusal: replace "2.1.0-beta.1" with a value the regex accepts. Nothing published moves.
  • Widen the regex to admit prerelease/build, matching what PluginSchema.version now accepts. ⚠️ This changes an accept set on a published schema and contradicts the existing pin, so it wants its own statement — and note the two keys are deliberately different grammars today.
  • Do nothing, on the ground that one wrong example in a TSDoc costs little.

⛔ This seat does not grade which.

Adjacent

Activity

  1. added theissue type on Sep 10, 2026
  2. os-litant commented on Sep 10, 2026

    @os-litant
    Collaborator

    Triage: lands in packages/spec/src/kernel/manifest.zod.ts ⇒ domain:spec; type Bug, priority:p3, pm:queue.

    Grading

    Class (a), squarely. The boundary for (a) is wrong vs incomplete, not docs vs code, and the worked example is the canonical case: 「示例照抄即失败是 (a)」. An author copying @example "2.1.0-beta.1" verbatim gets a ZodError. ⛔ Not a missing member, ⛔ not a stale prose nuance.

    Also declared ≠ enforced with citable text on both sides, which is why the type is Bug rather than Task: the @example line declares a value as valid and the regex two lines below refuses it. A documentation correction whose declared text contradicts the enforcement is a bug, not a chore.

    ⭐ The card resolves its own ambiguity, which is what makes it dispatchable. Three artifacts agree that prerelease suffixes are refused — the regex, the prose (major.minor.patch), and manifest.test.ts, which pins '1.0.0-beta' in invalidVersions. Only the @example dissents. ⇒ This is an editing residue in the TSDoc, ⛔ not an open question about what the key should accept.

    Scope fence — the second option in the card is not on the table without a ruling

    The card lists two shapes. Only the first is dispatchable:

    • ✅ Correct or delete the @example so it is a value the regex accepts. Nothing published moves; the pinned test is untouched.
    • ⛔ Widening the regex to admit prereleases would contradict a test that pins the refusal on purpose, and would enlarge a published schema's accepted set. That is a contract change and a maintainer floor. ⇒ If a dev concludes the regex is what is wrong, stop, write no code, and report — the card converts to needs-user-decision.

    ⚠️ Related but not to be folded: #17070 rules on PluginSchema.version and PluginLoader, and this key is the precedent that card was told to copy. The filing seat was right not to touch it in the same change — editing the yardstick and the thing being measured at once. ⛔ Do not fold them now either, for the same reason.

    priority:p3: the failure is loud — a ZodError at parse, not a silent acceptance — so nobody proceeds on a wrong belief. Cost is one author's wasted minute and a one-line fix.

    Triage seat · session_017VGfRocA8VjczSe84fgjY3 · R+168 · 2026-09-10T16:07Z (timestamp taken in the same tool call that posts) · comment from the triage seat


    Generated by Claude Code

  3. self-assigned this
    on Sep 12, 2026
  4. os-bill commented on Sep 12, 2026

    @os-bill
    CollaboratorAuthor

    Claim: PM dispatch by the domain:spec execution seat, session session_01MkQhmuuJAVDjmeWNixwDDH, at 2026-09-12T00:32Z. The assignee and this comment are both written by the PM; the os-dev round that implements this card inherits them and posts no second claim.

    Branch: claude/issue-17461-manifest-version-example

    Declared file face (region level) — packages/spec/src/kernel/manifest.zod.ts — the version key's TSDoc @example lines only. The regex, the .describe() string and manifest.test.ts are READ-ONLY.

    • Clause-②: no — this PR puts no new key on any published payload.

    Concurrency at dispatch time (2026-09-12T00:32Z): scripts/pm/os-verify-lock.sh --status read lock is free, queue: empty ⇒ arrival depth 0. Sibling dispatches in this batch declare disjoint file faces: #17574 (packages/spec/src/data/search-fields.ts), #17461 (packages/spec/src/kernel/manifest.zod.ts), #17595 (packages/lint/src/validate-component-types.ts). The one PR still open from this seat, PR #17711, touches packages/spec/src/data/filter-text-operator-declared-type.ts and packages/spec/src/data/index.ts — disjoint from all three.

    Changeset filename must be issue-scoped — .changeset/issue-17461-<slug>.md, never the random two-word default. Rationale and the measured collision that produced this rule: #17712.

    domain:spec execution seat · session_01MkQhmuuJAVDjmeWNixwDDH · claim posted 2026-09-12T00:32Z


    Generated by Claude Code

  5. os-bill commented on Sep 12, 2026

    @os-bill
    CollaboratorAuthor

    os-dev-report

    {
      "card": 17461,
      "issue": 17461,
      "status": "done",
      "branch": "claude/issue-17461-manifest-version-example",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/17741",
      "head_sha": "dfcb15921189ee953e8faafffa5c01c863007615",
      "files_changed": [
        "packages/spec/src/kernel/manifest.zod.ts (1 line: @example \"2.1.0-beta.1\" -> @example \"2.1.0\")",
        ".changeset/issue-17461-manifest-version-example.md (new, patch @objectstack/spec)"
      ],
      "changeset_file": ".changeset/issue-17461-manifest-version-example.md",
      "premise_still_valid": true,
      "premise_readings": {
        "base": "worktree created off origin/main at 431c757120907a7b166e5257df7447ad80261eef - identical to the commit the PM measured on",
        "offending_region": "packages/spec/src/kernel/manifest.zod.ts:316-322 reproduced verbatim on my head: @example \"1.0.0\" / @example \"2.1.0-beta.1\" above version: z.string().regex(...)",
        "lit_control_ManifestSchema": "grep -c 'ManifestSchema' packages/spec/src/kernel/manifest.zod.ts == 8 - matches the PM's reading exactly",
        "refusal_reproduced": "node -e with the regex over ['1.0.0','2.1.0-beta.1'] -> true \"1.0.0\" / false \"2.1.0-beta.1\". The false arm IS the defect; the true arm is the lit control in the same command.",
        "test_pin_confirmed": "packages/spec/src/kernel/manifest.test.ts:29 reads: const invalidVersions = ['1.0', '1', 'v1.0.0', '1.0.0-beta'];  - the deliberate refusal pin the triage comment relies on is present and untouched",
        "claim_confirmed": "newest Claim: comment id 5642201649 names branch claude/issue-17461-manifest-version-example. No second claim posted; assignee never written."
      },
      "scope_fence_respected": {
        "regex_untouched": "the line version: z.string().regex(...).describe(...) does not appear in the diff at all - git diff 431c757120..dfcb159211 on that file is exactly one removed and one added @example line",
        "describe_untouched": "same evidence - the .describe('Package version (semantic versioning)') string is not in the diff",
        "manifest_test_untouched": "manifest.test.ts is not in the changed-file list (git diff --stat shows exactly 2 files)",
        "17070_not_folded": "PluginSchema.version and PluginLoader are not in the diff",
        "releases_untouched": "content/docs/releases/ is not in the diff",
        "regex_widening_NOT_done": "the hard stop was not reached - the regex is correct and the @example was the artifact in error, which is the dispatchable shape"
      },
      "verification": [
        {
          "claim": "the corrected example is accepted by the unchanged regex",
          "command": "node -e \"const re=/^d+.d+.d+$/ (spelled out); ['1.0.0','2.1.0']\"",
          "reading": "true \"1.0.0\", true \"2.1.0\"",
          "control": "same command on '2.1.0-beta.1' returns false - the predicate can come back the other way"
        },
        {
          "claim": "spec builds",
          "command": "scripts/pm/os-verify-lock.sh -c 'pnpm --filter @objectstack/spec build'",
          "reading": "VERDICT command-exit 0 (its own verdict line, not a bare $?)",
          "control": "n/a - exit read from the wrapper's printed VERDICT"
        },
        {
          "claim": "spec typecheck + full spec test suite pass",
          "command": "scripts/pm/os-verify-lock.sh -c 'pnpm --filter @objectstack/spec typecheck && pnpm --filter @objectstack/spec test'",
          "reading": "VERDICT command-exit 0; Test Files 473 passed (473); Tests 13435 passed (13435)",
          "control": "a first attempt at this same command returned VERDICT queue-timeout (exit 99) = NOT MEASURED; it is recorded as neither pass nor fail and was re-run to completion under the same OS_VERIFY_LOCK_SLOT"
        },
        {
          "claim": "the deliberate invalidVersions pin still passes unchanged",
          "command": "pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/kernel/manifest.test.ts",
          "reading": "EXIT=0 (captured before any pipe); Test Files 1 passed (1); Tests 41 passed (41)",
          "control": "n/a"
        },
        {
          "claim": "no generated artifact goes stale from this TSDoc edit",
          "command": "pnpm --filter @objectstack/spec check:generated",
          "reading": "exit 0; 'All 15 generated artifacts are up to date', check:docs among them",
          "control": "n/a"
        },
        {
          "claim": "the @example line does not reach content/docs/references/**",
          "command": "grep -roiE '2.1.0-beta.1' content/docs/references/ | wc -l",
          "reading": "0",
          "control": "LIT: grep -roiE 'Package version \\(semantic versioning\\)' content/docs/references/ | wc -l == 12 in the same tree - the neighbouring .describe() string IS extracted, so a zero for the @example is a reading, not an empty search"
        },
        {
          "claim": "the corrected line is genuinely published",
          "command": "grep -roE '@example \"2.1.0\"' packages/spec/dist/ | wc -l  and the same for the old value",
          "reading": "corrected 16, old 0",
          "control": "LIT: grep -roE '@example \"1.0.0\"' packages/spec/dist/ | wc -l == 16 - the untouched neighbour emits at the same 16 sites, so the 0 is a reading. Plus @objectstack/spec files[] carries src/**/*.zod.ts, so the edited source file ships verbatim too."
        },
        {
          "claim": "lint is green repo-wide - NOT a narrowed run",
          "command": "pnpm exec eslint . --no-inline-config --format json",
          "reading": "exit 0; 6636 files linted; 0 errors; 0 warnings (file count read from the --format json output itself)",
          "control": "n/a - no narrowing was claimed, so no narrowing evidence is owed"
        },
        {
          "claim": "the derived gate family is accounted for",
          "command": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, then --ran with per-command exit codes",
          "reading": "76 derived, 73 run green, 3 NOT-MEASURED, 0 UNRUN",
          "control": "the reconciler recomputes the 76 itself rather than trusting my list; it refused to credit the first --ran file because it carried no exit codes, and only classified the 3 after I recorded ':: exit 3' per line"
        },
        {
          "claim": "no other @example in this file is refused by its own validator",
          "command": "node -e over /^[a-z][a-z0-9_]{1,19}$/ with ['todo','crm'] and /^[the range class]*d+.d+.d+/ with ['>=3.0.0']",
          "reading": "all documented examples accepted",
          "control": "LIT: the same two predicates return false for 'todo_task_is_way_too_long_here' and 'not-a-version' - both can come back the other way"
        },
        {
          "claim": "commit carries no card relation and no model identifier",
          "command": "git log --format='%B' -1 | grep -oiE '(bare closing stems)' | wc -l",
          "reading": "0 closing stems",
          "control": "LIT: the same grep shape for 'regex' on the same message returns 4. A separate model-identifier scan returned 1 match which inspection showed to be the literal 'Claude-Session' trailer name, not a model - the trailer pair is model-free as dispatched."
        },
        {
          "claim": "PR body stored exactly as sent",
          "command": "GET /repos/objectstack-ai/objectstack/pulls/17741 and byte-compare against the sent file",
          "reading": "byte-identical True (3755 sent / 3754 stored = trailing newline only); exactly one footer; the Clause line present verbatim; the closing keyword line naming this card is the only keyword-plus-issue-ref pair in the body",
          "control": "LIT: the same keyword-plus-ref scan finds #17070 elsewhere in the body with NO keyword before it, so the scan sees more than one candidate"
        },
        {
          "claim": "final text verified on the PUSHED head, not the working tree",
          "command": "git fetch origin BRANCH:refs/verify/i17461 -f; git show refs/verify/i17461:packages/spec/src/kernel/manifest.zod.ts",
          "reading": "pushed head dfcb159211 carries @example \"2.1.0\"; old value occurrence count 0 in that stream",
          "control": "LIT: '@example \"1.0.0\"' == 1 in the same stream"
        },
        {
          "claim": "no control bytes introduced",
          "command": "grep -naP over the changed files for the C0/DEL class; pnpm check:nul-bytes",
          "reading": "0 matches; gate exit 0",
          "control": "LIT: grep -c '@example' on the same file == 15"
        }
      ],
      "not_measured": [
        "pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 3 - PREREQUISITE NOT MET, needs @objectstack/formula and @objectstack/lint built; this diff touches neither package",
        "pnpm check:dual-build-cjs-loads :: exit 3 - PREREQUISITE NOT MET, 87 packages have no dist in this worktree; needs a whole-repo pnpm build",
        "pnpm check:lean-entry-closure :: exit 3 - PREREQUISITE NOT MET, needs @objectstack/objectql built",
        "All three exit 3 by design and print 'this is NOT a pass: nothing was measured'. Declared to CI, which builds the whole repo. They are recorded here as NOT MEASURED, never inside a green count."
      ],
      "mcp_calls": "0 - the repo-scoped REST read probe returned 200 on the first try, so the whole run (issue read, comments read, PR create, PR read-back, report comment) went over REST. No MCP GitHub call was made.",
      "summary": "Corrected the single dissenting artifact: ManifestSchema.version's TSDoc @example \"2.1.0-beta.1\" became @example \"2.1.0\", a value its own unchanged regex accepts. The premise held on my head exactly as the PM measured it, including the lit control (8) and the manifest.test.ts invalidVersions pin on '1.0.0-beta'. The regex, the .describe() string and the pinned test are byte-identical - verified by the diff being exactly one removed and one added comment line across two files. The hard stop was NOT reached: the regex is right and the comment was wrong, which is the dispatchable shape. Draft PR 17741 opened against main, never flipped ready, never enqueued, no auto-merge.",
      "tests": "os-verify-lock VERDICT command-exit 0 for both the spec build and 'typecheck && test' (Test Files 473 passed (473), Tests 13435 passed (13435)); targeted manifest.test.ts EXIT=0 with 41 passed; check:generated 15/15 up to date; eslint repo-wide exit 0 over 6636 files with 0 errors and 0 warnings; derived gate family 76 derived / 73 green / 3 NOT MEASURED (prerequisite build absent). No ablation was performed: this change adds no guard and no new assertion, so there is nothing whose ability to fail needs demonstrating - the defect's own repro (regex false on the old value, true on the new) is the before/after pair.",
      "out_of_scope_findings": [
        "noted, not filed: manifest.zod.ts carries trailing whitespace on the line after the version key and on the opening line of the next docblock. Pre-existing, pure style, not (a)/(b)/(c), and eslint is green over it repo-wide. Successor: 承接者:无.",
        "noted, not filed: the changeset filename convention in .changeset/ is ISSUENUMBER-SLUG.md across every existing file sampled; the dispatch mandated the issue-CARDNUMBER-SLUG.md spelling, which is what I wrote. Both are issue-scoped and collision-free and no gate reads either. Observation only, not a defect. Successor: the PM seat that authored the dispatch rule.",
        "checked and clean, nothing to note: every other @example in manifest.zod.ts was tested against its own validator (namespace, engine.objectstack) and all are accepted, each with a lit negative control. version was the only dissenting example in the file."
      ],
      "open_questions": []
    }
    

    Generated by Claude Code

  6. os-bill commented on Sep 12, 2026

    @os-bill
    CollaboratorAuthor

    Verified on origin/main @ 813f8e9 — PR #17741 merged. Half-state cleared.

    domain:spec execution seat, 2026-09-12T03:11Z. ⛔ A merge is not a verification; these are content readings on the merged tree.

    probe reading
    @example "2.1.0-beta.1" (the refused example) 0
    @example "2.1.0" (accepted by the unchanged regex) 1
    LIT CONTROL @example "1.0.0" 1
    LIT CONTROL @example in the file 15

    The regex, the .describe() string and manifest.test.ts's invalidVersions pin are all untouched — the diff was exactly one removed and one added comment line. ⭐ The round also verified the corrected example is genuinely published: @example "2.1.0" appears 16 times in packages/spec/dist with the untouched neighbour @example "1.0.0" at the same 16 sites as the control. And it did not reach the hard stop — the regex was right and the comment was wrong, which is the dispatchable shape.

    ⚠️ A false reading of my own, recorded rather than buried: my first verification pass put escaped quotes inside a $(...) inside a double-quoted string, and every probe — including the lit control — came back 0. ⛔ I did not conclude the change was missing. A control that will not light is a signal about the instrument, not the file; I re-measured with a pattern file (grep -oFf) and the readings above are that second pass.

    Half-state cleared: GitHub closed this card on the PR's closing keyword, but pm:dispatched and the assignee were left behind. Both are stripped in the same write as this comment, and read back.

    domain:spec execution seat · session_01MkQhmuuJAVDjmeWNixwDDH · readings taken 2026-09-12T03:11Z


    Generated by Claude Code

  7. removed their assignment
    on Sep 12, 2026
  8. added a commit that references this issue on Sep 17, 2026
    af98a04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions