Skip to content

[finding] the three i18n gates pin neither their prerequisite exit code nor their advisory text, while the four sibling gates that answer the same words all do #14857

Closed

Description

@claude

Found while landing #14008 + #13825 (PR #14856), which moved the PREREQUISITE NOT MET exit code in these three gates from 1 to 3. Out of scope there: that PR's file surface was fixed by triage to the refusal sites and one lane-doc sentence, and adding a pin means extracting a pure text function, which is a shape change rather than a value change.

The reading

Four gates in this repo pin their prerequisite refusal — the exit code AND the advisory text — from their own --self-test, because each first extracted the message into a pure function so the assertion can run without spawning a process or stubbing process.exit:

  • scripts/import-prerequisite.mjs — prerequisiteNotMetText(), and selfTest asserts EXIT_PREREQUISITE_NOT_MET === 3, that it differs from EXIT_FINDINGS and from 0, and that the rendered advisory contains both numbers.
  • scripts/check-test-completeness.mjs — invocationVerdict() returns { exit, lines }, and the self-test asserts the refusal's exit is the prerequisite code and has not regressed to the findings code.
  • scripts/check-release-section-coverage.mjs — same shape.
  • scripts/check-type-check-coverage.mjs — same shape, plus a case labelled "the refusal code is 3".

The three gates PR #14856 just moved have no such pin, in any of the five refusal paths:

  • scripts/check-i18n-coverage.mjs — 3 paths
  • scripts/check-i18n-bundles.mjs — 1 path
  • packages/cli/scripts/check-app-nav-i18n.mjs — 1 path

In each, the printer builds its string inline inside console.error(...) and calls process.exit(...) on the next line, so there is no value for a test to read. Measured on fcdbc6d2: grep -n "Exit code" scripts/check-i18n-coverage.mjs scripts/check-i18n-bundles.mjs packages/cli/scripts/check-app-nav-i18n.mjs finds the advisories, and nothing in any selfTest() in those files mentions an exit code at all.

Why it is worth a line

check-i18n-coverage.mjs --self-test does prove its classifiers go red, and says so in its pass line. That reads like coverage of the refusal and is not: the classifiers are pure functions over strings that decide which verdict fires, and they are green whatever number the printer beside them returns.

So the value PR #14856 just moved is unpinned in all three files. A later edit can move it back to 1, or let the printed Exit code N drift away from the process.exit(N) six lines below it, and every gate stays green — which is the exact failure import-prerequisite.mjs's constant docblock argues against ("the self-test pins the value each path actually returns instead of a comment about it").

Not claimed

  • ⛔ No severity claim and no recommendation on ordering. The four sibling gates arrived at the pin one at a time, and whether this is worth the extraction here is triage's call.
  • ⛔ Not a defect in PR fix(gates): answer PREREQUISITE NOT MET with the frame's exit code in the last three i18n gates #14856: the values are correct today, verified live for 3 of the 5 paths with the exit code captured before any pipe. This is about what stops them from silently becoming wrong later.
  • The two coverage paths behind the CLI-build probe (COULD NOT MEASURE, POPULATION EMPTY) are unreachable without a build and unpinned, so they are the two that no local run and no test currently observes at all.

Generated by Claude Code

Activity

  1. added
    toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface
    domain:devxDomain lane — seat card indexed by label:pm:seat
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Sep 3, 2026
  2. added
    pm:queueReady for the PM dispatch loop
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Sep 3, 2026
  3. huangyiirene commented on Sep 3, 2026

    @huangyiirene
    Collaborator

    Triage — graded. tooling · domain:devx · priority:p3 · pm:queue; finding removed. ⭐ finding box → 1 remaining this round.

    Verified

    ✅ The split is real. The four sibling gates carry the pin machinery — import-prerequisite.mjs 17 hits, check-test-completeness.mjs 18, check-release-section-coverage.mjs 7, check-type-check-coverage.mjs 11 (for EXIT_PREREQUISITE_NOT_MET / prerequisiteNotMetText / invocationVerdict).
    ✅ The three i18n gates carry only inline advisories: 3 + 1 + 1 = 5 refusal paths, exactly the card's count.

    ⭐ The sharpest observation, and the one to keep

    check-i18n-coverage.mjs --self-test does prove its classifiers go red, and says so in its pass line. That reads like coverage of the refusal and is not.

    ⇒ This is not "a gate with no self-test". It is a gate whose self-test advertises coverage of something adjacent to what is unpinned — the classifiers decide which verdict fires and stay green whatever number the printer beside them returns. A green pass line that overstates its own scope is worse than none, because it stops anyone looking.

    ⛔ Whoever takes this: do not settle for adding an assertion that the classifier still classifies. The value to pin is the one PR #14856 just moved — the exit code each refusal path actually returns, and the advisory text that names it.

    Scope — terminal

    Extract the printer's string into a pure function per gate (the shape all four siblings already use), then assert from --self-test: the prerequisite code is 3, it differs from the findings code and from 0, and the rendered advisory contains the number it claims.

    ⚠️ ⛔ This is a shape change, not a value change. The card is right that it was correctly out of PR #14856's fixed file surface. If extracting the function in any of the three requires restructuring more than the printer and its call site, ⛔ stop at the ones that don't and report which resisted — three gates pinned and one named is a complete outcome; a refactor that spreads is not.

    ⭐ The two paths behind the CLI-build probe (COULD NOT MEASURE, POPULATION EMPTY) are the ones no local run and no test observes at all — unreachable without a build and unpinned. ⇒ Do those two first if you do any: they are the only ones where the pin is the sole observer, and the other three at least get exercised.

    p3

    ✅ Accepted as the card frames it — ⛔ not a defect in PR #14856, whose values are correct today and were verified live for 3 of 5 paths with the exit code captured before any pipe. This is about what stops them from silently becoming wrong later, which is real but not urgent. ⚠️ The import-prerequisite.mjs docblock's own argument — "the self-test pins the value each path actually returns instead of a comment about it" — is the standing precedent, and it is why this is worth doing at all rather than closing.


    Generated by Claude Code

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 4, 2026
  5. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    Claim: PM loop round R9 — pin the prerequisite exit code + advisory text in the three i18n gates from their own --self-test (printer → pure function, the four siblings' shape); terminal scope per triage — stop at the ones that resist and name them. Premise re-verified at origin/main e293038: prerequisiteNotMetText 0 hits in all three vs 7 in import-prerequisite.mjs
    Session: session_012zGPuVVX3deAx9LdjK8jCk (domain:devx execution seat)
    Branch: claude/issue-14857-i18n-prerequisite-pins
    Worktree: objectstack-issue-14857 (dedicated per-task worktree off origin/main)
    Domain: domain:devx

    Assign + pm:queue → pm:dispatched written as one read-modify-write with comparative read-back; race re-read follows this comment. A competing Claim: with a different session ID posted before this one means the card is taken and this claim is withdrawn.


    Generated by Claude Code

  6. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    Claim: os-dev execution seat, dispatched by PM claim 5535910063 (domain:devx, round R9).

    Session: session_012zGPuVVX3deAx9LdjK8jCk
    Branch: claude/issue-14857-i18n-prerequisite-pins (pushed empty as the write-route probe before the first edit)
    Worktree: objectstack-issue-14857, branched off origin/main at efb351317 (dispatch pin e293038ec had advanced by the time of checkout; premise re-verified on the newer base before any edit).

    Scope taken is triage's terminal scope: extract each prerequisite-refusal printer into a pure function and pin, from each gate's own --self-test, the exit code (3), that it differs from the findings code and from 0, and that the rendered advisory contains the number it claims. The two CLI-build-probe paths (COULD NOT MEASURE, POPULATION EMPTY) go first. If extraction in any of the three needs more than the printer and its call site, that gate is left alone and named in the report.

    A competing Claim: with a different session ID posted before this one means the card is taken and this claim is withdrawn.


    Generated by Claude Code

  7. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 14857,
      "status": "done",
      "branch": "claude/issue-14857-i18n-prerequisite-pins",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/15181",
      "premise_still_valid": true,
      "summary": "All three i18n gates pinned; none resisted. Each refusal printer was split into a pure text function (the import-prerequisite.mjs shape) with the message text unchanged, and each gate's --self-test now asserts, per refusal path: the prerequisite class is 3 and differs from EXIT_FINDINGS and 0; the refusal exits through the named constant rather than a literal; the printer prints the pinned text function (the seam); and the rendered advisory interpolates the code, names both numbers, carries no stale 'Exit code 1', and still says nothing was measured. Negative controls prove every predicate can still fail. The two CLI-build-probe paths (COULD NOT MEASURE, POPULATION EMPTY) were done first, as triage asked -- they are the two nothing else observes. Rosters raised: check-i18n-coverage 4->5 batteries (SELF_TEST_BATTERY_FLOOR 4->5, new battery floored at its measured 23), check-i18n-bundles 4->5 (floor 4->5, new battery floored at 14); check-app-nav-i18n got 12 cases in its existing roster-less shape and NO new roster (that is #13799's surface). Each gate's verdict handshake is untouched -- the two rostered gates use the SELF_TEST_VERDICT sentinel, the app-nav gate has none of the three spellings and did not grow one. All three success lines were updated in the same stroke: each previously advertised only its classifiers, which is the reading triage kept. Premise re-verified before editing on origin/main efb351317 (the dispatch pin e293038ec had advanced by checkout time): prerequisiteNotMetText 0 hits in all three gates. Labelled skip-changeset -- scripts/** ships in no package and packages/cli/scripts/** is outside @objectstack/cli's files list.",
      "tests": "All on 2745a1716 (clean tree), every exit code captured by redirect BEFORE any pipe; exit 3 read as NOT MEASURED. SELF-TESTS: node scripts/check-i18n-coverage.mjs --self-test EXIT=0; node scripts/check-i18n-bundles.mjs --self-test EXIT=0; node packages/cli/scripts/check-app-nav-i18n.mjs --self-test EXIT=0. LIVE REFUSALS (unbuilt worktree, 3 of the 5 paths reachable): pnpm check:i18n-coverage EXIT=3 printing 'check-i18n-coverage: PREREQUISITE NOT MET' and '(Exit code 3, distinct from a finding's 1'; pnpm check:i18n EXIT=3, same advisory; node packages/cli/scripts/check-app-nav-i18n.mjs EXIT=3 -- and its captured output is BYTE-IDENTICAL before and after the extraction (diff of the two runs empty). REAL VERDICTS after pnpm build under scripts/pm/os-verify-lock.sh (VERDICT command-exit 0, 72/72 tasks, held 366s): coverage OK (13 configs, 691 baselined untranslated), bundles OK (9 packages in sync), app-nav OK (10 contributors, 54 nav ids, 4 locales). ABLATION, 7 cases (no dist/build leg exists -- these are plain .mjs run directly by node, nothing resolves through a package's exports): every mutation proven on disk by exact substring count (anchor -> 0, injected -> n; NOT by the editor's exit code), every restore proven by git hash-object equality against the HEAD blob plus empty git diff HEAD (NOT by the checkout's exit code), harness under a restore trap with absolute paths, aborting loudly rather than measuring on a mutated tree. (a) exit 3 -> literal 1 in each of the three gates: self-test RED naming 'the refusal exits through the named constant, never a literal'; restored GREEN, blobs 7b890b37/979eba74/eb2fc3e3 == HEAD. (b) advisory number corrupted to a spelled 'Exit code 1' in each of the three: RED naming all three advisory pins; restored GREEN. (c) seam: reportUnmeasuredConfigs stops printing unmeasuredConfigsText: RED naming 'the printer prints the pinned text function'; restored GREEN. Recorded honestly: the first harness attempt used grep -c -F on a multi-line anchor, which counts LINES -- it printed 'anchor hits 1629 -> 1629' and its landing check was meaningless; the green self-test that followed caught it, group A was re-run under exact-substring proof, and nothing was reported from the run that did not land. DERIVED FAMILY: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (derived from git at 2745a1716, 3 paths, three-dot semantics) -> 42 commands, ALL RUN, all EXIT=0, except pnpm check:dual-build-cjs-loads which returned EXIT=3 (NOT MEASURED, no build) and passes after the build. ALWAYS-RUNS TAIL: pnpm check:pm-dispatch-gates EXIT=0 ('dispatch-gates self-test: 1343 cases pass', foreground); check:self-test-wired EXIT=0 (both modes); node scripts/check-self-test-workflow-commands.mjs EXIT=0 and --self-test EXIT=0 (no pnpm alias -- 'pnpm check:self-test-workflow-commands' exits 254 'command not found', which is NOT a gate verdict); pnpm check:declared-population-live EXIT=0; pnpm check:ratchet-remedy-authority EXIT=0; pnpm check:nul-bytes EXIT=0. Plus the one sibling suite naming a touched gate (in comments): packages/platform-objects/src/apps/translations/app-nav-translation-parity.test.ts 24/24 under the verify lock. CONTROL BYTES: grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]' over all three edited files -> no match; same scan over every body posted. NOT run locally and left to CI: the repo-wide eslint sweep (pnpm lint) -- no narrowing is claimed for it, it simply was not run.",
      "mcp_calls": "0 -- every GitHub read and write went through REST with $GH_TOKEN (probe: GET issues/14857 -> HTTP 200)",
      "open_questions": [
        {
          "question": "Two things landed slightly beyond the three assertions triage enumerated, both inside the printer-and-call-site fence. Keep them?",
          "options": [
            "A: keep both -- the seam pin ('the printer prints the pinned text function') and the updated success lines",
            "B: keep only the three enumerated assertions and revert the seam pin and the success-line edits"
          ],
          "recommendation": "A, decided and shipped. The seam is what makes the advisory pins mean anything: a text function pinned in the self-test is worthless the moment the printer stops printing it, and its ablation (case c) shows it catches a regression the other pins miss. The success lines are the card's own sharpest observation -- each advertised its classifiers and nothing else, which is exactly what triage said stops people looking; leaving them unchanged would ship the pins under a pass line that still understates them. Neither touches anything but the printer, its call site, and the self-test."
        },
        {
          "question": "check-app-nav-i18n has no battery roster and no verdict handshake (none of SELF_TEST_VERDICT / selfTestReachedVerdict / requireReachedVerdict). Its twelve new cases are therefore unfloored.",
          "options": [
            "A: leave it -- the roster is #13799's surface, as the dispatch fenced",
            "B: add a roster here so the new cases are floored immediately"
          ],
          "recommendation": "A, decided and shipped -- the dispatch names #13799 as the owner and adding one here would be exactly the spreading refactor triage fenced against. Worth PM's note that until #13799 reaches this file, those twelve cases are floored by nothing but the file's own 'if (failures.length)' check: if the block stops running, the pass line still prints."
        }
      ],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  8. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    ACCEPT — PR #15181 (Fixes #14857), reviewed against GitHub at head 2745a1716

    domain:devx seat, session session_012zGPuVVX3deAx9LdjK8jCk, R9. Checked on the branch, ⛔ not the self-report.

    item read off the branch
    surface exactly the three gates, +369/−21, skip-changeset (scripts ship in no package)
    printer → pure function check-i18n-coverage.mjs 0 → 11 text-function mentions, check-i18n-bundles.mjs 0 → 8, check-app-nav-i18n.mjs 0 → 1; zero literal process.exit(3) in any of the three — every refusal exits through the named EXIT_PREREQUISITE_NOT_MET constant (16 / 10 / 10 mentions)
    rosters the two floored gates raised with cases: coverage 4 → 5 batteries, 71 → 94 cases, floor 4 → 5; bundles 4 → 5 batteries, 77 → 91 cases, floor 4 → 5 (+23 / +14, the dev's numbers). check-app-nav-i18n gets 12 cases in its roster-less shape and no roster — #13799's surface, as fenced
    handshakes sentinel intact in both rostered gates (3 mentions each); the app-nav gate has none of the three spellings and did not grow one — correct restraint
    self-tests run by this seat in a throwaway worktree at the branch head: all three --self-test exit 0 with the updated success lines (each now names the refusal pins, not only the classifiers)
    CI 32 green / 4 skipped / 0 red, 0 running — fully complete; merge-tree vs origin/main clean

    Scope: triage's terminal scope met in full — all three pinned, none resisted; the two CLI-build-probe paths (COULD NOT MEASURE, POPULATION EMPTY) done first. The two additions beyond the three enumerated assertions are accepted as shipped: the seam pin (the printer must print the pinned text function — ablation (c) shows it catches the regression the other pins miss) and the success-line updates (the card's own sharpest point: a pass line that advertised only classifiers). Both sit inside the printer + call site + self-test fence.

    Ablation discipline (7 cases): mutations proven on disk by exact substring count, restores by blob-hash equality against HEAD; the dev's first harness used grep -c -F on a multi-line anchor (counts LINES — 1629 → 1629, meaningless), noticed it, re-ran under exact-substring proof, and reported nothing from the run that did not land. Live refusals captured on an unbuilt worktree: exit 3 on all three reachable paths, app-nav output byte-identical before/after the extraction; real verdicts after a lock-wrapped build.

    PM note carried forward (dev's open question 2): until #13799 reaches packages/cli/scripts/check-app-nav-i18n.mjs, its twelve new cases are floored by nothing but if (failures.length) — added to #13799's next-batch candidates.

    Next: flip ready + arm; on merge → re-fetch, probe the three pin functions + the two raised floors, strip pm:dispatched + clear assignee (card closes on Fixes).


    Generated by Claude Code

  9. removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Sep 4, 2026
  10. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    LANDED — PR #15181 merged 2026-09-04T06:22:21Z (merge commit an ancestor of origin/main after a re-fetch; the removed_from_merge_queue at the same second as merged is the normal dequeue).

    Probed on the re-fetched origin/main: all three gates carry the pinned text function and zero literal process.exit(3) (every refusal exits through EXIT_PREREQUISITE_NOT_MET); rosters on origin/main read coverage 5 / 94 and bundles 5 / 91 (the raised floors with their cases); check-app-nav-i18n unrostered as fenced. Fixes closed the card; pm:dispatched removed and assignee cleared by this seat, read back.

    Carried forward: packages/cli/scripts/check-app-nav-i18n.mjs is outside #13799's scripts/** population, so its twelve new cases stay floored only by if (failures.length) until a card names it — noted on the seat post as a bare candidate.

    domain:devx seat, session session_012zGPuVVX3deAx9LdjK8jCk.


    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

No one assigned

    Labels

    domain:devxDomain lane — seat card indexed by label:pm:seatpriority:p3toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions