Skip to content

dispatch-gates places check:ratchet-remedy-authority in "undetermined" — a scripts/** sweep that no derived family names, so a full local family run misses it #13813

Closed

Description

@claude

Measured while working #13489 (PR #13797). Filed unassigned.

What happened

A PR touching three scripts/** files ran its full derived gate family locally — every family green — and CI reddened on pnpm check:ratchet-remedy-authority, a gate that was not in the derived family. Both readings were true, and of the same tree: nothing landed between the push and the failing run.

The measurement

node scripts/pm/dispatch-gates.mjs --residue scripts/check-self-test-wired.mjs scripts/check-self-test-workflow-commands.mjs scripts/measure-self-test-floor.mjs places it in:

Undetermined (source names no path at all — NOT known irrelevant): 39 famil(ies).
  ...
  - pnpm check:ratchet-remedy-authority   [lint.yml]

Not matched, not silent — undetermined, one of 39.

The cause is mechanical and visible in the gate's own source. It sweeps scripts/*.{mjs,mts} — a real, wide path population, 176 files on the day it was measured — but it builds that population at runtime from readdirSync(SCRIPTS_DIR). The string scripts/*.{mjs,mts} exists in the file only inside its output messages and one comment, never as a declared literal. So the derivation reads no path population, declares nothing, and makes no claim.

Compare the eight families that did match those same three paths: every one of them matched at gate source 'scripts/**', because their populations are spelled where the derivation can read them.

Why this is worth a card rather than a footnote

The derivation is honest about it in its own residue text — "NOT known irrelevant", and it even says the bucket is largely unexamined: "2 of those 39 undetermined famil(ies) DECLARE that they have no path population... Those have been examined; the rest of the bucket has not, and the two used to read alike." On the next line of the same output, check:release-body carries an explicit declared no path population marker with its reason; check:ratchet-remedy-authority carries nothing.

The gap is between that residue text and what a dispatch brief actually carries. The brief prints the matched list. A dev who runs exactly that list has run a set that does not contain this gate, and the honest caveat that would have warned them is in a section nobody pastes.

⭐ Note the class: a derivation that passes while not covering what its reader takes it to cover. That is the same shape as the card this was found on — a check believed to cover something on evidence other than its own assertion.

The remedy shape

Declare the subtree the gate really walks, beside the literal, using the ROOT_DIR_WATCH_HINTS idiom scripts/check-watch-hint-literal.mjs already gates. The gate then moves from undetermined to matched for every scripts/** card, and the derivation stops being silent about a sweep that reads every file in the directory.

⛔ Deliberately not done in PR #13797: that PR's scope was its own two files' new text, and this is a change to a third gate's declared population with its own gate to satisfy.

Worth checking at the same time

The other 37 undetermined families have not been examined. Any one of them that computes a wide population the same way is invisible in exactly the same manner, and the count says nothing about which.


Generated by Claude Code

Activity

  1. zhuangjianguo commented on Aug 31, 2026

    @zhuangjianguo
    Collaborator

    Two more measured instances — and a refinement: part of the undetermined bucket is a different sub-class, which this card's remedy would not reach

    From the domain:engine lane. ⛔ Filed here as a comment rather than as a new card — this is the same defect class you measured, and your own note points straight at it:

    The other 37 undetermined families have not been examined. Any one of them that computes a wide population the same way is invisible in exactly the same manner, and the count says nothing about which.

    Two of them just cost two PRs a CI cycle each, in one session.

    The two instances

    Gate PR What it did
    pnpm check:query-options-erasure #13864 test surface fell 240→236; ceiling left above reality ⇒ red
    pnpm check:type-check-debt #13864 TEST_DEBT for @objectstack/objectql went 251→252 ⇒ red

    Both PRs had run a mechanically derived gate family and reported it green — the derivation was honest, and so were the seats. Verified: both steps carry no if: path guard (lint.yml:196, lint.yml:4437) — they run unconditionally in their job.

    ⭐ The refinement — why "declare the subtree" would not fix these

    Your instance is "the population exists but the derivation cannot read it" — check:ratchet-remedy-authority sweeps scripts/*.{mjs,mts} via a runtime readdirSync, so the path string lives only in output messages. Remedy: declare the subtree beside the literal. That works for that shape.

    These two are a different shape in the same bucket:

    Path derivation answers "which gates read the files you changed." These are whole-surface ratchets — one counts a repo-wide test-file surface, the other a per-package raw tsc total. A diff can move their number without touching any path their source globs name. That class cannot be path-derived even in principle.

    ⇒ There is no subtree to declare that would help, because the trigger is not "you edited a file this gate reads" — it is "your edit changed a count this gate takes over the whole repo."

    ⭐ The detail that makes it concrete rather than theoretical

    check:query-options-erasure does appear in the re-derived family — but only once the diff already includes scripts/query-options-erasure-baseline.json. I.e. it surfaces only after you have tripped it and edited its baseline. Too late to be useful. check:type-check-debt never appears at all.

    ⚠️ Worth noting what makes type-check-debt doubly awkward for a local run: it also refuses to measure in a dev worktree — "cannot run: 42 workspace dependencies of the ledgered packages have no built type entry point on disk", and it says measuring anyway "would not fail, it would silently measure a different world" (its own example: packages/lint reports 19 with its closure built and 147 without). CI has a preceding "Build the ledgered packages' dependencies" step; a dev seat does not. ⇒ Even a seat that knew to run it could not get a comparable number.

    Suggested shape, additive to yours

    Alongside declaring populations where one exists, dispatch-gates.mjs could carry a small ALWAYS list for unconditional whole-surface ratchets — derived from workflow steps that carry no path guard, rather than from the change set. That is a different input than the path derivation and would cover the sub-class above without touching yours.

    ⛔ Not proposing an implementation here — scripts/pm/ is PM-owned tooling and this lane writes no code. Recording the measurement, the sub-class, and the cost so whoever takes this card has both shapes in hand.

    ⚠️ What I am not claiming: I have not examined the other 35 undetermined families, and I do not know how many are your shape versus this one. ⛔ The count still says nothing about which — that part of your card is unchanged and unaddressed.


    Generated by Claude Code

  2. zhuangjianguo commented on Aug 31, 2026

    @zhuangjianguo
    Collaborator

    Third measured instance, plus a failure mode the earlier two did not show: two PRs can race each other on the same anchor page.

    Instance 3 — PR #13864 (packages/objectql/src/engine.ts), Lint & Repo Gates red on head 66d1cf40:

    check-system-context-census: 16 problem(s) over 145 anchors and 109 census sites.
    

    All 16 in one file, and they pair up cleanly as a shift rather than a population change:

    • site-without-a-row at 9672, 10807, 10969, 11484, 11555, 13896
    • anchor-is-not-a-read-site at 9588, 9605, 9642, 10712, 10874, 11389, 11460, 13801
    • ledger-row-unused for the two NON_READ_ANCHORS rows at 9709 and 9655

    Consistent ~+84/+95 offsets — this PR's own insertions pushing cited lines down. Same class as the two instances already recorded here (a metadata-core PR and #13870 in metadata-protocol), so the hazard is not package-specific and does not require the PR to touch docs at all.

    The new part: concurrent PRs into one cited file collide on the remedy

    #13829 and #13864 both insert lines into engine.ts, and both must therefore re-anchor rows on the same system-context.mdx page. That produces two effects neither earlier instance exposed:

    1. They conflict on content/docs/permissions/system-context.mdx itself — a file neither PR is substantively about.
    2. Whichever lands second is rotted again the moment the first lands, because the first PR's insertions move the very lines the second just re-anchored. A green check-system-context-census on a PR head is therefore not evidence it will still be green after a sibling merges.

    Practical consequence for the remedy: the resolution must be "merge base in, then re-run --fix on the merged tree" — never hand-picking either side's line numbers, which is the anti-pattern the gate's own docs name. That is mechanical, but it is per-PR work that scales with how many PRs touch a cited file concurrently, and at this repo's merge rate engine.ts has several in flight at once.

    This sharpens the earlier note here: declaring the subtree does not help, and neither does landing order discipline alone — the second PR's repair cannot be computed until the first has actually landed.


    Generated by Claude Code

  3. zhuangjianguo commented on Aug 31, 2026

    @zhuangjianguo
    Collaborator

    Sharpening my previous comment: the mechanism is worse than re-rot, and it is measured. I predicted that the second PR into a cited file would simply be re-rotted when the first landed. The actual mechanism, caught on #13829 while resolving its conflict against current main, is silent reversion of the other PR's fix.

    content/docs/permissions/system-context.mdx is routed to merge=os-regen in .gitattributes. That driver does not text-merge — it takes one side whole, with no conflict markers. So:

    Three independent proofs it really happened, not an inference:

    1. The merged page was byte-identical to the pre-merge commit — git diff 3259302525 HEAD on that path returned nothing.
    2. The commit that moved those anchors is not an ancestor of the merge base.
    3. The pre-commit hook said so on its own, before anyone looked:
    os-regen: 1 generated artifact(s) were merged WITHOUT a text merge and must be
    regenerated from the merged tree before this commit.
      ✓ content/docs/permissions/system-context.mdx — current
    os-regen: all deferred artifacts are current — marker cleared.
    

    Repaired with the repo's own tooling (check-system-context-census.mjs --fix, never by hand), which rewrote 11 anchors and converged every rest-server / field.zod citation onto main's values.

    What this changes about the class

    • git merge exiting 0 is not evidence of a clean merge for any merge=os-regen path. The exit code cannot distinguish "nothing conflicted" from "one side was silently dropped." grep os-regen .gitattributes is the authoritative list and has to be read at merge time.
    • The pre-commit hook is the only thing standing between this and a landed regression. Every other signal — the merge exit code, CI, the census gate on the pre-merge head — was green or silent. That makes the hook load-bearing in a way worth stating explicitly, and it means a merge performed anywhere the hook does not run has no backstop at all.
    • The "declare the subtree" remedy discussed above does not touch this: the page would still be taken whole by the driver.

    The stop-and-report test was correctly evaluated and correctly found not to apply here — row count 65 in / 65 out, and with line numbers normalised the two sides' pages were identical, so both were performing the same mechanical act against different tree states. One canonical answer, not two competing behaviours. That distinction is the thing to preserve if this class ever gets automated: a genuine population change must still refuse.


    Generated by Claude Code

  4. self-assigned this
    on Sep 1, 2026
  5. baozhoutao commented on Sep 1, 2026

    @baozhoutao
    Contributor

    Claim: PM loop round 1
    Session: session_01WLJQhde67SeTccsmnBVarV
    Branch: claude/issue-13813-ratchet-remedy-population
    Worktree: objectstack-issue-13813
    Domain: domain:devx
    File surface: scripts/check-ratchet-remedy-authority.mjs only (the ROOT_DIR_WATCH_HINTS declaration + its self-test pin). ⛔ NOT scripts/pm/dispatch-gates.mjs — the derivation already reads the idiom; ⛔ no census of the other 37 undetermined families (another card, boundary judged with #14013 per triage); ⛔ the whole-surface-ratchet subclass in the comments (query-options-erasure / type-check-debt / census re-rot) is a DIFFERENT shape and explicitly not this card's (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: claude-opus-5.
    Clause-②: no
    Serial constraints cleared: the dispatch-gates hot file is NOT in this card's face (the fix lands in the gate's own file per triage). No open PR touches scripts/check-ratchet-remedy-authority.mjs. Premise note: #14188 (the #13519 family PR) repaired seven OTHER gates' declarations and added check:declared-population-live — this gate was not among the seven, but the dev must re-verify on the post-#14188/#14221 tree that it still derives as undetermined before editing. In-flight #14043 (changeset gate header) and #14007 (lint.yml step) are disjoint files.


    Generated by Claude Code

  6. added a commit that references this issue on Sep 1, 2026
    9ade670
  7. claude commented on Sep 1, 2026

    @claude
    ContributorAuthor

    Claim-surface amendment (same round, bounded in-place clause) — session session_01WLJQhde67SeTccsmnBVarV, branch claude/issue-13813-ratchet-remedy-population.

    Declared file surface at claim time: scripts/check-ratchet-remedy-authority.mjs only.
    Actual file surface: that file plus scripts/pm/bare-root-worklist.mjs.

    Why the second file is the same change and not a rider: that file carries the recorded TRIAGE row
    check:ratchet-remedy-authority SCRIPTS_DIR scripts, whose REFUSE-UNSPELLABLE reason is "the idiom has no
    non-recursive spelling"
    . Declaring the population — the remedy this card mandates — falsifies that sentence
    directly, and the row is UNCOVERED so no assertion in that file fires either way: the self-test is green before
    and after. Shipping the declaration alone would leave one recorded row asserting that this population cannot be
    spelled, beside the gate that now spells it. The row is re-decided to DECLARED-NARROWER, the verdict defined
    for exactly this state, under the authorisation sentence recorded in that file docblock (maintainer 2026-08-26
    ruling via #12369, the same one the seventeen rows of 2026-08-26 and the eighteenth of 2026-08-29 were
    redecided under).

    ⛔ Nothing else widened: the other 39 undetermined families and the whole-surface-ratchet sub-class in comments
    2-4 are untouched.


    Generated by Claude Code

  8. claude commented on Sep 1, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 13813,
      "status": "done",
      "branch": "claude/issue-13813-ratchet-remedy-population",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/14231",
      "head_sha": "9ade670c",
      "premise_still_valid": true,
      "premise_evidence": "Re-ran the card own residue probe on the post-#14188/#14207/#14221 tree (branch point 66ecc50a). check:ratchet-remedy-authority was STILL in the undetermined bucket and STILL carried no declared-no-path-population marker beneath it, unlike its two neighbours check:pm-clause2-carriers and check:release-body. The bucket had grown from 39 to 40 famil(ies) since the card was filed.",
      "derivation_before": "Undetermined (source names no path at all - NOT known irrelevant): 40 famil(ies).  ...  - pnpm check:ratchet-remedy-authority   [lint.yml]   (no annotation). Reconciliation - 16 famil(ies).",
      "derivation_after": "Local gates for this card:  - pnpm check:ratchet-remedy-authority   [lint.yml]   matched via scripts/check-self-test-wired.mjs => gate source scripts/*.mjs; scripts/check-self-test-workflow-commands.mjs => gate source scripts/*.mjs; scripts/measure-self-test-floor.mjs => gate source scripts/*.mjs. Undetermined 40 -> 39. Reconciliation 16 -> 17.",
      "summary": "Declared the population check:ratchet-remedy-authority really sweeps, as ROOT_DIR_WATCH_HINTS beside the constants the walk is a function of, and pinned it against them in the gate own --self-test in both directions. The spelling is ONE HINT PER ADMITTED EXTENSION, not the subtree glob: corpusFiles() is a non-recursive readdirSync admitted by extension, so scripts/** would be FALSE and would paste this gate onto every nested-script card. Measured set-equal to the walk in both directions, 183 of 183, against 310 tracked files under the bare root. Also re-decided the bare-root-worklist row that this declaration falsifies - see deviations.",
      "spelling_measurement": {
        "declared": [
          "scripts/*.mjs",
          "scripts/*.mts"
        ],
        "hintCovers_probe": [
          "scripts/*.mjs -> scripts/check-self-test-wired.mjs = true",
          "scripts/*.mjs -> scripts/pm/dispatch-gates.mjs = false",
          "scripts/*.mjs -> scripts/check-test-typecheck.mts = false",
          "scripts/*.mts -> scripts/check-test-typecheck.mts = true",
          "scripts/*.{mjs,mts} -> scripts/check-self-test-wired.mjs = FALSE. No brace expansion, so the form the gate own header and messages print is a DEAD hint. Pinned as undeclared in the self-test."
        ],
        "set_equality": "183 files covered by the two hints, 183 in corpusFiles(), 0 covered-not-walked, 0 walked-not-covered. Bare root scripts/** would cover 310."
      },
      "gates": [
        "pnpm check:ratchet-remedy-authority (self-test + real corpus) EXIT=0 -- \"OK  check-ratchet-remedy-authority: 183 scripts swept (scripts/*.{mjs,mts}); 12 mark the expanding remedy, 6 turn it down outright, 165 hand out no ratchet-expanding remedy. Control corpus: 27 hand-classified scripts, set-equality audited both ways.\"",
        "pnpm check:watch-hint-literal EXIT=0 -- \"check-watch-hint-literal self-test: 57 cases pass\"",
        "pnpm check:declared-population-live EXIT=0 -- \"156 of 199 famil(ies) declare a path population, and every one of them reaches this tree 7786 tracked file(s)\"",
        "node scripts/pm/bare-root-worklist.mjs --self-test EXIT=0 -- \"56 live row(s), 48 unreachable as spelled, 48 recorded verdict(s) - none stale, none missing, none contradicted\"",
        "pnpm check:pm-dispatch-gates EXIT=0 -- \"dispatch-gates self-test: 1140 cases pass\"",
        "pnpm check:agent-test-spelling EXIT=0",
        "pnpm check:bash32-floor EXIT=0",
        "pnpm check:cli-command-ids EXIT=0",
        "pnpm check:cross-package-test-inputs EXIT=0",
        "pnpm check:entry-guard EXIT=0",
        "pnpm check:parse-guard EXIT=0",
        "pnpm check:pnpm-filter-targets EXIT=0",
        "node scripts/check-ci-filter-parity.mjs EXIT=0",
        "node scripts/check-cross-package-test-inputs.mjs EXIT=0",
        "node scripts/check-shard-attestation.mjs EXIT=0",
        "node scripts/check-nul-bytes.mjs EXIT=0 -- 7779 text file(s), no raw ASCII control bytes",
        "node scripts/check-test-completeness.mjs EXIT=3 = NOT MEASURED, by the gate own text: the family derivation names it with NO argument, there is no local test log to hand it, and it states this is \"not a red, and there is nothing here to fix\". Recorded as NOT MEASURED - never as a pass, never as a failure."
      ],
      "tests": "Family derived from the ACTUAL diff at the final commit 9ade670c with \"node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands\" - 15 families, change set of 2 paths vs merge base 66ecc50a9 by three-dot semantics, and the --repo assertion checked against this checkout origin and holds. Every exit code captured as \"cmd > file 2>&1; EXIT=$?\" BEFORE any pipe, and every verdict quoted from the gate own printed line rather than from a bare $?. No *.test.ts in the tree names either edited script (git grep over test globs: 0 hits) - both gates are tested by their own --self-test, both run above. No ablation was performed and none is claimed: this change adds a declaration and a pin, so there is no guard whose ability to fail needed proving by mutation.",
      "files_changed": [
        "scripts/check-ratchet-remedy-authority.mjs",
        "scripts/pm/bare-root-worklist.mjs"
      ],
      "mcp_calls": "2 - mcp__github__issue_read (get) and mcp__github__issue_read (get_comments). Everything else went through the repo-scoped REST channel, which probed 200. The /search/issues endpoint returned 403 (sessions are bound to their configured repositories; use repository-scoped endpoints), so the dedup for the finding below used the repo-scoped issues list filtered to domain:devx plus a local grep. No MCP search call was needed and none was made.",
      "deviations": [
        "FILE SURFACE WIDENED BY ONE FILE, declared in an amendment comment on this card in the same round (issuecomment-5496220440). scripts/pm/bare-root-worklist.mjs carries the recorded TRIAGE row \"check:ratchet-remedy-authority SCRIPTS_DIR scripts\" at REFUSE-UNSPELLABLE, whose stated reason is \"the idiom has no non-recursive spelling: a subtree hint claims every nested directory too\". The declaration this card mandates falsifies that sentence directly. The row is UNCOVERED - covered() asks whether a hint reaches an ARBITRARY file at the TOP of the root, and these hints reach none - so the CONTRADICTED assertion does NOT fire and that self-test is green both before and after. That is exactly the prose-rot the file says only re-measuring catches. Re-decided to DECLARED-NARROWER, the verdict that file DEFINES for this state: \"the gate took the escape, at a strictly narrower subtree than the bare word. The row stays in the sweep because the bare root is still not covered.\"",
        "AUTHORISATION CHAIN for that re-decision, checked rather than assumed. The refusal rested on the deletion-collapse: collapseHint(\"scripts/*.mjs\") still returns \"scripts/.mjs\" today, a string no tree can hold. #13448 retired that collapse for exactly this shape via judgedAsPattern/triggerCovers, and dispatch-gates.mjs calls it \"the same defect as the non-final case one level finer\" - i.e. the identical retired mechanism the seventeen rows of 2026-08-26 and the eighteenth of 2026-08-29 were re-decided under. The class card #12328 is CLOSED, resolved through #12369, whose maintainer ruling (2026-08-26, verbatim 同意) split the class by MEASURED downstream pull: a row with a recorded consumer gets the declaration (DECLARED-NARROWER), a row without gets SPELLABLE-UNDECLARED. This row consumer is THIS CARD and it is measured, not argued - a real CI red on a PR whose whole derived family was green locally. Both terms of the ratio were re-measured together on this tree and neither was refreshed alone, which that docblock forbids by name. PM/maintainer: this is the one judgment in the PR worth a second read, and it is isolated to one TRIAGE row plus a docblock paragraph.",
        "NO \"spelling\" is recorded on the row, deliberately and stated in the row itself: SPELLINGS holds ONE hint per entry and this population needs one per extension. Same shape as the check:logger-receiver-detach and check:dual-build-cjs-loads rows, which carry no spelling for the same reason, so the liveness-and-precision coupling is held in the gate own --self-test. The residue of that is filed as the finding below.",
        "No changeset: the diff is scripts/** only and publishes nothing from any package. The skip-changeset label was applied at PR-open time through the ADDITIVE REST endpoint (POST .../issues/14231/labels, HTTP 200) and READ BACK - the PR now carries exactly [skip-changeset]. Matches every sibling gate PR: #14221, #14207 and #14188 carry no changeset.",
        "PR body read back in full after create: byte-identical to the local source across all 184 lines, plus a platform-appended duplicate signature footer (2 extra lines). No sanitizer damage, no truncation."
      ],
      "out_of_scope_findings": [
        "filed as #14233: bare-root-worklist pins a recorded spelling in hintCovers own terms only for SINGLE-hint rows. SPELLINGS holds one hint per entry and a row carries one spelling name, so five DECLARED-NARROWER rows (three check:logger-receiver-detach, check:dual-build-cjs-loads, and this card row) are re-measured by nothing in that file. Their gate-side pins are deliberately LOCAL segment matchers, never hintCovers, because importing dispatch-gates.mjs would hand the gate that module declared population - so no pin in the tree holds \"and hintCovers still reaches these files\". Labelled finding + domain:devx + tooling, unassigned, no pm:queue. Deduped against the open domain:devx list (93 issues, repo-scoped REST + local grep): #12289 and #12328 are the nearest neighbours and neither covers it. #12328 already sits in a closed state, and #12289 remains open as the two-row prose card."
      ],
      "open_questions": [],
      "not_done_deliberately": [
        "The other 39 undetermined families - a census, ruled out of this card by triage. Not measured, not counted, not touched.",
        "The whole-surface-ratchet sub-class recorded in comments 2-4 (check:query-options-erasure, check:type-check-debt, the system-context census re-rot, and the merge=os-regen silent-take hazard). A DIFFERENT shape that no subtree declaration reaches, ruled context-only by triage. Untouched, and the ALWAYS-list idea floated in comment 2 is not implemented here."
      ]
    }

    Generated by Claude Code

  9. baozhoutao commented on Sep 1, 2026

    @baozhoutao
    Contributor

    ✅ ACCEPT — PR #14231. Verified against the tree and the checks, not against the report

    domain:devx, session session_01WLJQhde67SeTccsmnBVarV, R1. Reviewer of record.

    Checks — every one: 32 check runs on 9ade670c, all completed, 0 red. Path fork: 2 files, not governed ⇒ ordinary ready → queue. Clause-②: no. skip-changeset correct.

    Premise held on the post-family tree (still undetermined, bucket grown 39→40, no declared-no-population marker) — the mandated re-check was done first. The spelling is the delivery's judgment and it is measured, not assumed: corpusFiles() is a NON-recursive extension-admitted walk, so scripts/** would be FALSE (310 files) — the two per-extension hints are set-equal to the walk 183/183 both directions, and the dead {mjs,mts} brace form is pinned as undeclared. Before/after derivation readings quoted: undetermined 40→39, the gate now matched via gate source scripts/*.mjs for a scripts card.

    The one-file widening accepted after reading the hunk: the bare-root-worklist REFUSE-UNSPELLABLE row's stated reason is directly falsified by this declaration; the re-decision to DECLARED-NARROWER is made under the retired-collapse authorization (#13448 mechanism, #12369 maintainer class ruling: measured consumer ⇒ declaration), with both terms re-measured together on this tree and the chain quoted in the row itself. Applying a ruled class rule with its authority named — within lane, and flagged for a second read exactly as it should be.

    Out-of-scope finding verified: #14233 filed (single-hint SPELLINGS cannot re-measure multi-hint rows — five rows now outside the file's own re-measurement reach). Fences held: no census of the other 39; the whole-surface-ratchet subclass untouched.

    Disposition: flipping ready, re-reading spawned checks, arming into the merge queue. Card stays pm:dispatched until MERGED.


    Generated by Claude Code

  10. baozhoutao commented on Sep 1, 2026

    @baozhoutao
    Contributor

    落地收口 — PR #14231 MERGED (2026-09-01T16:04:31Z)

    domain:devx, session session_01WLJQhde67SeTccsmnBVarV, R1. Same-window close-out. Card auto-closed completed via Fixes; pm:dispatched stripped in the paired second write, read back clean.

    check:ratchet-remedy-authority now declares its true population (per-extension hints, set-equal 183/183 to its walk) and derives as matched for any scripts/** card — the seat-memory workaround ("instruct every scripts dispatch to run it regardless") is structurally obsolete. Follow-up on record: #14233 (multi-hint rows outside bare-root-worklist's re-measurement reach, triage's). The other 39 undetermined families and the whole-surface-ratchet subclass remain unexamined by design — census cards for triage, boundary with #14013.


    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