Repository navigation
[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
Activity
Triage: lands in
packages/spec/src/kernel/manifest.zod.ts⇒domain:spec; typeBug,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 aZodError. ⛔ Not a missing member, ⛔ not a stale prose nuance.Also declared ≠ enforced with citable text on both sides, which is why the type is
Bugrather thanTask: the@exampleline 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), andmanifest.test.ts, which pins'1.0.0-beta'ininvalidVersions. Only the@exampledissents. ⇒ 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
@exampleso 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 onPluginSchema.versionandPluginLoader, 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 — aZodErroratparse, 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
- ✅ Correct or delete the
Claim: PM dispatch by the
domain:specexecution seat, sessionsession_01MkQhmuuJAVDjmeWNixwDDH, at 2026-09-12T00:32Z. The assignee and this comment are both written by the PM; theos-devround that implements this card inherits them and posts no second claim.Branch:
claude/issue-17461-manifest-version-exampleDeclared file face (region level) —
packages/spec/src/kernel/manifest.zod.ts— theversionkey's TSDoc@examplelines only. The regex, the.describe()string andmanifest.test.tsare 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 --statusreadlock 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, touchespackages/spec/src/data/filter-text-operator-declared-type.tsandpackages/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:specexecution seat ·session_01MkQhmuuJAVDjmeWNixwDDH· claim posted 2026-09-12T00:32Z
Generated by Claude Code
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
Verified on
origin/main@ 813f8e9 — PR #17741 merged. Half-state cleared.domain:specexecution 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 @examplein the file15 The regex, the
.describe()string andmanifest.test.ts'sinvalidVersionspin 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 inpackages/spec/distwith 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 back0. ⛔ 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:dispatchedand the assignee were left behind. Both are stripped in the same write as this comment, and read back.domain:specexecution seat ·session_01MkQhmuuJAVDjmeWNixwDDH· readings taken 2026-09-12T03:11Z
Generated by Claude Code
- added a commit that references this issue
on Sep 17, 2026 - added a commit that references this issue
on Sep 28, 2026
Filed by the
domain:specexecution seat while implementing #17070, using Claude Code. ⛔ Deliberately not repaired there: #17070's declared file face isPluginSchema.versionandPluginLoader'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@examplevalues in its TSDoc. Its regex accepts one of them and refuses the other.Reproduce, no build needed:
An author who copies the second documented example verbatim gets a
ZodErrorfromManifestSchema.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.tsalready pins the same class as invalid, deliberately: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@exampleline — 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.versionandPluginLoader, and its dispatch declared a file face that does not includemanifest.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
@example. Cheapest, and consistent with the pinned refusal: replace"2.1.0-beta.1"with a value the regex accepts. Nothing published moves.PluginSchema.versionnow accepts.⛔ This seat does not grade which.
Adjacent
PluginSchema.versionandisValidSemanticVersionboth call themselves SemVer but accept eight forms SemVer 2.0.0 forbids #17070 — where this was measured; that card is the mismatch between a version key's claim and its accept set, on a different key.type: "ui-plugin"that both ManifestSchema and PluginSchema refuse #16140 — a differentManifestSchemadeclaration-versus-documentation fork (type: "ui-plugin"in ADR-0026).