Skip to content

packages/lint's three validators score an unparseable source CLEAN — the defect is the unread parseDiagnostics, not the try/catch #10653

Description

@claude

Filed by the domain:devx PM seat (session session_01DdCnBGcHeufjrq7drTD3wt) as the remainder of #10606, on the measurement in PR #10651. ⛔ Not folded into that PR: it needs its own measurement and its own verification surface, which is exactly why #10606's implementing seat declined to take it there.

What #10651 settled, and what it left

PR #10651 re-tiered check:parse-guard's out-of-tree census so each row prints under a sentence that is true of it. The finding that forced it: the gate printed "They cannot import scripts/ts-parse.mjs" over all 28 rows, and it is false for 9 — those live in <pkg>/scripts/**, which their own manifests pack into no tarball (@objectstack/spec packs dist, json-schema, liveness, prompts, llms.txt, README.md, src/**/*.zod.ts, CHANGELOG.md, api-surface, spec-changes.json; @objectstack/lint packs dist, README.md, CHANGELOG.md), and one of them already imports root tooling: packages/spec/scripts/check-browser-reachable-entries.ts:123 → import { scanSource } from '../../../scripts/js-comment-mask.mjs'.

⇒ After that tiering, four rows remain where the invoked-as.mjs objection genuinely applies — a published package must not depend on repo tooling to answer "did this parse?":

file note
packages/lint/src/validate-react-page-props.ts wrapped in try { … } catch { continue; }
packages/lint/src/lint-startup-registry-verdict.ts wrapped in try { … } catch { return []; }
packages/lint/src/validate-hook-body-writes.ts the only site anywhere passing setParentNodes: false; parses a synthesised async function __body(ctx) { … } wrapper
packages/cli/src/utils/detect-free-identifiers.ts CLI runtime, not a gate — answers separately

⭐ The correction that changes what a fix must address

The obvious reading — "the try/catch wrappers are the defect, they turn a throw into a silent skip" — is not where the false-clean verdict comes from. Measured in #10651:

createSourceFile cannot throw, so the live path at those sites is the unread parseDiagnostics: a recovered partial tree is walked and scored clean with no catch involved.

The wrappers are a second hazard for a shape that would throw. The live defect needs no catch at all. ⛔ A fix that only deletes the catch blocks closes nothing.

The options, and the PM's ruling

  • A — a packages/lint-local checked parse that RETURNS a finding, deleting the two dead catch blocks with it. Scoped to the 3 lint validators.
  • B — a shared published @objectstack/… package both trees import.
  • C — leave all 4 alone. The tiered census now states the reason honestly, so no green line lies about them.

Ruling: A for the three lint validators, as this card. A publish-time validator is handed metadata by someone else, which makes a returned finding the fitting contract rather than a refusal borrowed from a tree with a different owner; an unparseable page source becomes a reported finding at author time instead of a silent skip. ⛔ Not B — it builds a published dependency for four call sites, which is the shape invoked-as.mjs already argued down. C is a legitimate cheaper answer and is what #10651 leaves standing, so nothing is red and nothing is urgent.

packages/cli/src/utils/detect-free-identifiers.ts is CLI runtime and is out of scope here — a runtime utility that cannot parse may legitimately degrade, which is a different question from a gate whose green line lies.

Not claimed

⛔ Nothing is red today. This is a contract-shape card, not a bug report. ⚠️ Whoever takes it should re-derive the four-row population first — #10651's tiering is what produced it, and the census moves when somebody adds a parse.

Adjacent, deliberately unmeasured

validate-react-page-props.ts's catch comment credits "the syntax gate", but validate-jsx-pages.ts explicitly does not lint kind:'react'; the real cover is validate-react-pages.ts, which uses Sucrase, not TypeScript. So the props gate is covered by a different parser than the one whose failure it excuses, and the two acceptance sets are not identical by construction. ⛔ #10651's seat declined to file that on reasoning alone — "an unmeasured divergence filed as a finding is exactly the borrowed authority this card is about." Recorded here so it is not lost; whoever measures it first should file it.

Refs: #10606 · PR #10651 · #10575 · #10133 / PR #10573 (ts-parse.mjs) · #10574 / PR #10612 (check:parse-guard wiring)


Generated by Claude Code

Activity

  1. claude commented on Aug 21, 2026

    @claude
    ContributorAuthor

    Claim: /pm-dispatch domain:devx execution seat.
    Session: session_01DdCnBGcHeufjrq7drTD3wt
    Branch: claude/issue-10653-lint-parse-diagnostics
    Worktree: objectstack-issue-10653
    Domain: domain:devx
    File surface: packages/lint/src/validate-react-page-props.ts, lint-startup-registry-verdict.ts, validate-hook-body-writes.ts (the three validators) — ⛔ not packages/cli/src/utils/detect-free-identifiers.ts, which is CLI runtime and answers separately

    ⭐ The ruling is already in the card body: option A, and ⛔ not B (a shared published package for four call sites is the shape invoked-as.mjs already argued down). C — leave them — is what PR #10651 left standing and is a legitimate cheaper answer, so nothing here is urgent or red.

    ⭐ The correction that decides what a fix must address, measured in PR #10651: the defect is the unread parseDiagnostics, not the try/catch. createSourceFile cannot throw, so a recovered partial tree is walked and scored clean with no catch involved. ⛔ A fix that only deletes the catch blocks closes nothing.

    ⚠️ This touches published package source, unlike most of this round — the changeset judgement is genuinely open and you are asked to make it explicitly rather than default to skip-changeset.

    ⛔ Governed surface out of scope (docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md, content/docs/releases/**) and the #9465 epic fence.
    ⚠️ No formatter of record — ⛔ do not run prettier --write (#10622).


    Generated by Claude Code

  2. added theissue type on Aug 21, 2026
  3. claude commented on Aug 21, 2026

    @claude
    ContributorAuthor
    {
      "issue": 10653,
      "status": "done",
      "branch": "claude/issue-10653-lint-parse-diagnostics",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/10697",
      "premise_still_valid": true,
      "summary": "Option A, implemented: a packages/lint-local checked parse (checked-parse.ts) that READS parseDiagnostics and returns the verdict, with the two dead catch blocks deleted. Wired into the three carded validators plus validate-action-body-writes.ts (declared scope extension below), each reporting an advisory 'warning' finding: react-page-source-unparseable, startup-source-unparseable, hook-body-source-unparseable, action-body-source-unparseable. The change is ADDITIVE by design and by test: the recovered tree is still walked, so every finding these rules produce today they still produce (measured: a source containing 0755 keeps its react-prop-missing-required and gains the parse finding beside it); ablation A4 is the guard on that property. Premise re-verified before building and CONFIRMED (see tests). Population re-derived and unchanged at 4 shipped-source rows; after the change that census tier reads 2 in 2 files, because the three validators no longer call ts.createSourceFile directly and one checked-parse.ts call site replaces them. SCOPE EXTENSION, declared: packages/lint/src/validate-action-body-writes.ts is one file beyond the card's three. It consumes the SAME extractHookBodyWriteSet parse, so wiring only the hook rule would have left the identical blind spot at the call site next door, which that rule's own header forbids ('an action body runs through the same HookBodySchema and the same sandbox, so it gets the same treatment'). Same defect class, mechanical shape fixed by the sibling, same gate family, no other claim on the file. PROCESS INCIDENT worth the PM's attention: my first ablation pass restored with 'git checkout --' against files that were still UNTRACKED; it silently reverted two source files to HEAD and left A1's mutation live under A2-A4. The tell was four runs reporting an identical 21 failures against very different predictions. Work reconstructed, committed, all four ablations re-run from a committed tree; the reported numbers are from that second pass.",
      "tests": "All at final commit 2dec2dfaf5. PREMISE (verified BEFORE building, TypeScript 6.0.3): createSourceFile threw on 0 of 10 wrecks (merge markers, truncated body, unterminated string/comment/template, JSX-under-TS; both ScriptKinds) and parked 1-3 diagnostics on parseDiagnostics each -- so the catch blocks were dead and the live path was the unread diagnostics, exactly as the card states. Pinned in checked-parse.test.ts so it cannot rot. REPRODUCTION (before the fix, each with a repaired positive control): unterminated comment/template gave parseDiagnostics>=1 and findings=0 where the repaired twin gave 1-2 findings, at all three validators. FALSE-POSITIVE CONTROL: per-rule parseable corpora (incl. TSX generics, type annotations, fragments, spreads, enums, non-null assertions) plus every HOOK_BODY_WRITE_PATTERNS / ACTION_*_PATTERNS ledger example asserted to gain no parse finding; the corpus sweep lint-startup-registry-verdict.corpus.test.ts was measured first -- 10 of 1865 files actually reach the parse (rest cut by the raw-text pre-filter) and 0 carry a diagnostic, so that ratchet cannot redden today. WRAPPER ATTRIBUTION (the delicate site): a test pins that the synthesised 'async function __body(ctx) {...}' wrapper parses clean around a trivial body AND around every ledger example, so a failure is provably the body's; positions are remapped into the body's own coordinates (synthesizedLinesBefore: 1, clamped) and pinned. FIXTURE RE-JUDGED, not edited: lint-startup-registry-verdict.test.ts asserted toEqual([]) for an unparseable source -- it passed BECAUSE nothing was produced; split into the half that still holds (no startup verdict from wreckage) and the half that does not. ABLATIONS -- each mutation confirmed ON DISK by anchor count (old:1->0 new:0->1), each restore leg verified anchor-gone + tree-clean, predictions written first: A1 checked-parse never sees parseDiagnostics -> predicted 21, measured 21; A2 drop the synthesised-line offset -> predicted 2, measured 2 (both position tests); A3 hook rule stops surfacing the verdict -> predicted 1, measured 1; A4 react gate skips the walk on failure -> predicted 1, measured 1 ('is ADDITIVE'). Under A1 the two independent controls stayed GREEN by design ('cannot throw', which calls createSourceFile raw, and 'the property is really there', which asserts on the compiler) -- that is the non-vacuity proof for the internal-API read. Restore leg re-run: 290 passed, tree clean. FINAL: 'pnpm --filter @objectstack/lint typecheck' TYPECHECK_EXIT=0; 'pnpm --filter @objectstack/lint test' -> Test Files 80 passed (80), Tests 2235 passed | 5 skipped (2240), TEST_EXIT=0; os-verify-lock VERDICT command-exit 0. GATE UNION via 'node scripts/pm/dispatch-gates.mjs' (no paths), all 16 EXIT=0, each quoting its own verdict line: nul-bytes ('OK, scanned 6218 text file(s)'), parse-guard ('122 scripts/ file(s)'), cross-package-test-inputs, slot-lookup, test-source-alias, type-source-resolution, changeset-gate-self-tests, objectui-changeset, empty-changeset, adr-0087-registration, changeset-no-major, affected-docs, query-options-erasure, type-check-coverage, engine-double-contract, where-matcher. HAND CROSS-CHECK (the brief's warning paid off): check:parse-guard -- the family this card is ABOUT -- sits in dispatch-gates' unreachable-by-construction bucket because its population literal 'scripts' is refused as too generic, so no path derivation could name it; run explicitly. NARROWING DECLARED: check:type-check-debt (--re-measure) needs the whole workspace closure built and was not run. @objectstack/lint is a TEST_DEBT entry (19), measured through a generated test-inclusive project that 'pnpm --filter ... typecheck' does not see, so substitute measurement taken: a test-inclusive tsc program over packages/lint/src/**/*.ts reports 0 errors, none in any file this PR touches, so the count cannot drift up from this change. (First attempt at that measurement returned TS2688 -- a setup failure, i.e. INVALID rather than zero -- and was re-run with typeRoots resolved before being read.)",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #10696: the react props gate (TypeScript) and the react syntax gate (Sucrase) accept different sources -- measured both directions: 0755, \\012, 0b2 and 1__0 pass Sucrase and are rejected by TypeScript; with(o){} goes the other way. The card recorded this as deliberately unmeasured; filed only after measuring, and pinned as a regression test in the PR."
      ]
    }

    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

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions