Skip to content

check-skill-line-ratchet: an ordinary ruled raise on a destination that carries a live CROSS_FILE_MOVES declaration is priced as the move's raise — separate the two quantities instead of carrying was forward #15191

Closed

Description

@claude

Filed by the domain:skills seat (session session_019RfFHiRCSs3JXLK4cwcfox, os-steve) from the review of PR #15190 (#14133 second increment), where the dev measured it and took the one-line workaround.

What

CROSS_FILE_MOVES computes a move's raise as ceiling − was. When the SAME destination later takes an ORDINARY ruled raise (the #15013 ruling: platform-readings.md 324 → 358), the gate reads that raise as the move's (314 → 358 = +44 against −11) and reds twice — the mechanism the declaration's docblock anticipated only for a later raise at a SOURCE. PR #15190 carried the destination's was forward (314 → 348) with the reasoning beside it, so the move's own verdict stays +10 against −11, and the reviewer's completeness check on was changes shape (stated in the comment). That is a data workaround for a script that has no words for the case.

Ask (S, scripts/pm/check-skill-line-ratchet.mjs only)

Teach the gate that an ordinary ruled raise and a move's raise are separate quantities — for example a per-entry list of ruled raises (each carrying its quoted ruling) that the move verdict subtracts before comparing against the sources' net decrease — so a destination's was stays its literal pre-move value and a later ruled raise never reads as the move's. Self-test: the PR #15190 shape (a live move plus an ordinary raise on the same entry) passes with was at its pre-move value; a raise carrying no ruling still reds; the platform-readings.md entry is migrated back to was: 314 in the same PR with the +34 recorded as a ruled raise. Not governed (scripts/pm/**).

Refs

PR #15190 (the workaround and the arithmetic) · #15013 (the ruling) · #14685 item 5 / PR #14860 (the move rule).


Generated by Claude Code

Activity

  1. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    Claim: PM loop round 5
    Session: session_019RfFHiRCSs3JXLK4cwcfox
    Branch: claude/issue-15191-ratchet-ruled-raise-vs-move
    Worktree: objectstack-issue-15191
    Domain: domain:skills
    File surface: scripts/pm/check-skill-line-ratchet.mjs only — teach the gate that an ordinary ruled raise and a cross-file move's raise are separate quantities (the card's example shape: a per-entry record of ruled raises, each carrying its quoted ruling, that the CROSS_FILE_MOVES verdict subtracts before comparing against the sources' net decrease), so a destination's was stays its literal pre-move value; in the same PR migrate the platform-readings.md declaration back to was: 314 with the +34 (#15013 ruling) recorded as a ruled raise; self-test pins the PR #15190 shape (a live move plus an ordinary raise on the same entry) green with was at its pre-move value, and a raise carrying no ruling still red. ⛔ No ceiling row changes beyond that migration (358 stays 358), ⛔ no .md, ⛔ no other script.
    Container & model: S, mode:subagent, model: opus (tier: scripts/pm/check-skill-line-ratchet.mjs — no path-derived mandate, default opus; the shape is a gate-semantics design choice)
    Clause-②: no (a PM tooling gate; no contract accept/reject behaviour)
    Serial constraints cleared: PR #15190 (#14133 family) MERGED 96b4af88 06:46:32Z — the was: 348 workaround this card retires is on main; PR #15057 (#14869) MERGED 1bc3c092 06:49:25Z; nothing in flight edits the ratchet script (PR #15203 edits AGENTS.md / SKILL.md, files the ratchet reads and this card does not touch). Not governed (scripts/pm/**, no .md) ⇒ draft PR, in-seat contract-tier review, ready + auto-merge by the seat.

    Decision re-read (07:20Z): no ruling is owed — the card is this seat's own review finding from PR #15190 and asks for a separation the gate's docblock already anticipated; the Ask's shape is a suggestion (建议路线区), the pins are the requirement.


    Generated by Claude Code

  2. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 15191,
      "status": "done",
      "branch": "claude/issue-15191-ratchet-ruled-raise-vs-move",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/15266",
      "premise_still_valid": true,
      "summary": "Premise verified on origin/main before editing, exactly as the card describes: with the declaration's `was` at its literal pre-move 314, the gate priced the destination's ORDINARY ruled raise as the move's and went red twice (314->358 = +44 against -11, plus a map total up 33). SHAPE CHOSEN: a `ruledRaises` field ON the move declaration — one record per ordinary ruled raise taken on the destination since the move, each carrying the ruling's own words and its date — subtracted before the move's raise is measured and restored alongside `was` in the map-wide total. I considered the card's alternative reading of a separate per-path ledger of ruled raises and rejected it on the long-term-soundness axis (weighted highest): a ledger keyed by path would necessarily be half-populated, since almost every ruled raise in this map belongs to an entry with no move at all, and this file's own header warns that an entry's absence from a map is read as a fact to check. A field on the declaration is precise when EMPTY — 'no ordinary raise has landed on this move' — and it cannot be misread as a corpus-wide record. On the AI-error axis the same choice is contract-tightening rather than consumer-tolerant: the subtraction is a licence to grow, so it carries two refusals of its own (a record quoting no ruling reds; a set of records claiming more lines than the ceiling actually stands above `was` reds, which is what stops an inflated record from driving the arithmetic negative and reading as PAID DOWN forever). Neither bar proves the maintainer said the words and the docblock says so — that stays the reviewer's completeness check, exactly as it already is for every `was`. On the startup-focus axis nothing speculative was built: no new ledger, no date arithmetic, no ordering model; the optional field is inert on every declaration that has not needed it, and the existing cases pin that. The quotation bar accepts BOTH spellings the map already uses, which was a measured correction — the 2026-08-25 generated-artifact carve-out beside the 43->44 ceiling quotes an ENGLISH ruling in double quotes, so a corner-bracket-only bar would have rejected a real ruling. DATA: the live declaration is migrated back to `was: 314` with the +34 recorded as a ruled raise whose ruling text is copied verbatim from the comment beside the 358 ceiling row; the ceiling row stays 358 with its ruling, and no other ceiling or pin moves. The four standing rules are kept verbatim and every pre-existing refusal assertion is unchanged. File surface is exactly the declared one: scripts/pm/check-skill-line-ratchet.mjs, nothing else. TWO THINGS FOR THE SEAT TO NOTE. (a) The gate's live output changes by exactly one line (the move verdict), which is disclosed in the PR body with the diff. (b) The PR body carries a DUPLICATED signature footer: I wrote the session-URL footer as the dispatch contract requires, and the platform appended an identical one after a `---` rule. I left it rather than PATCHing, because a PATCH on the body is the operation that downgrades a session-URL footer to the bare form and would cost the durable attribution; nothing was truncated — the body reads byte-identical to what I wrote through to its last line, the extra 90 bytes being only that appended block.",
      "tests": "All at the final commit e69c9623 (git rev-parse --short HEAD from the same runs). (1) `pnpm check:pm-skill-ratchet` exit 0 — verdict lines: 'check-skill-line-ratchet self-test: 155 cases pass.' (136 before; 19 new, every prior refusal assertion unchanged) and 'cross-file move into .claude/skills/pm-dispatch/references/platform-readings.md: +10 (314->358, less 34 lines of ordinary ruled raise) against a net source decrease of 11 (... cli.md 35->31, ... services.md 30->28, ... rest-channel.md 93->88); authorised by #14685 item 5 (comment 5520452691).' and 'declared cross-file moves: 1, total ceilings down 1 line.' (2) PREMISE CHECK (the dispatch's 机制假设): with `was` set back to 314 on the UNCHANGED origin/main script, the gate reds exactly as described — 'raises its ceiling 314->358 (+44) against a net source decrease of 11' and 'raise the map's total by 33 lines'; gate exit 1. Mutation proved on disk (grep -c injected 1 / removed 0, blob hash ef188c31 vs HEAD a0631c2a); restored via `git checkout HEAD --`, git diff HEAD empty, hash back to a0631c2a. (3) GATE-OUTPUT DIFF vs origin/main: full 60-line output diffed both sides; exactly ONE line differs (59c59, the move verdict: '+10 (348->358)' becomes '+10 (314->358, less 34 lines of ordinary ruled raise)'). Every per-file verdict byte-identical, including 'AGENTS.md is 1160 lines (ceiling 1162; headroom 2)', '.claude/skills/pm-dispatch/SKILL.md is 970 lines (ceiling 1005; headroom 35)', 'platform-readings.md is 358 lines (ceiling 358; headroom 0)' and the totals line. (NB: the dispatch anticipated 1161/971 from PR #15203; that PR has not landed, so main still reads 1160/970 — the diff is against origin/main's own output either way.) (4) ABLATION from the COMMITTED state, restore trap on absolute paths (REPO_ROOT via git rev-parse --show-toplevel): reverting the separation (`const raise = gross - ruled.total` -> `const raise = gross`, and the total's baseline `move.was + ruledRaiseSum(move)` -> `move.was`) while KEEPING `was: 314` reds the live gate twice — '314->358, less 34 lines of ordinary ruled raise (+44) against a net source decrease of 11' and 'raise the map's total by 33 lines', exit 1 — and reds 5 of 155 self-test cases. Mutation proved on disk by grep counts on BOTH the removed text (0) and the injected text (1) for both legs, plus a changed blob hash (636238ba vs HEAD 5fd99aef). Restore leg: `git checkout HEAD -- ABSPATH`, `git diff HEAD` line count 0, hash back to 5fd99aef, and the restored tree runs the gate green (exit 0). NO BUILD/DIST LEG APPLIES: the gate is a dependency-free node script executed from source (imports only node:fs, node:process and ../invoked-as.mjs) — there is no dist/ for a stale artifact to hide in, so the ablation-dist preflight has nothing to assert. (5) DERIVED GATES + --ran RECONCILIATION: re-derived for the ACTUAL changed path with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (change set taken by the script itself from merge-base a23603ea, 1 path). 22 families = 14 by path + 2 by change KIND + 6 whole-tree. All 22 run, all exit 0. `--ran` verdict line: 'dispatch-gates --ran: 22 derived famil(ies) accounted for — 22 run, 0 NOT-MEASURED.' and 'Run reconciliation — 22 derived, 22 run, 0 NOT-MEASURED, 0 UNRUN.' (6) BEYOND THE DERIVATION, all green: `pnpm check:skill-frame-sync`; `pnpm check:ratchet-remedy-authority` — verdict line '202 scripts swept (scripts/*.{mjs,mts} + scripts/pm/*.{mjs,mts}); 14 mark the expanding remedy MAINTAINER-ONLY, 6 turn it down outright, 182 hand out no ratchet-expanding remedy', i.e. this script still classifies `excluded` (my new refusal messages introduce no OFFER_VERB, which that gate's header pins as the reason it is excluded); `scripts/check-skills-token-ratchet.mjs` + its self-test; `scripts/check-published-list-mirrors.mjs`. Those four are the only files outside the script that name it, and none is a vitest suite — the script has NO dedicated *.test.ts anywhere in the tree (git grep over test globs: zero hits), so its `--self-test` is its test suite and `check:self-test-wired` (green) is what holds it wired. (7) WHOLE-REPO LINT, not narrowed, through the shared lock: `OS_VERIFY_LOCK_SLOT=issue-15191-lint bash scripts/pm/os-verify-lock.sh -c 'pnpm lint'` — 'os-verify-lock: VERDICT command-exit 0 · held the lock 81s (1m21s) · waited 26s · SHARED-BOX SECONDS'. (8) Control-byte self-scan on the changed file and on the PR body: `grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` — zero hits on both; `pnpm check:nul-bytes` green. Exit codes throughout were redirected to a file and captured BEFORE any pipe; every verdict quoted above is the gate's own printed line, never a bare $?.",
      "mcp_calls": "0 — zero GitHub MCP calls for the whole run; the issue read, the PR create, the label write and every read-back went through repo-scoped REST with $GITHUB_TOKEN, and history through git.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  3. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    ACCEPT — contract-tier review of PR #15266, head e69c9623 (skills seat, 2026-09-04T07:52Z).

    Implemented-by: claude/issue-15191-ratchet-ruled-raise-vs-move (os-dev subagent, task a1f1676dc42ed02a5)
    Reviewed-by: session_019RfFHiRCSs3JXLK4cwcfox (skills seat)

    Verified in a detached compare worktree at e69c9623 (merge base a23603ea; git merge-tree against origin/main 460134af clean), not from the report:

    • Shape: ruledRaises on the move declaration, one {ruling, date, delta} record per ordinary ruled raise taken on the destination since the move, subtracted before the move's raise is priced and restored beside was in the map-wide total. Accepted over a per-path ledger for the reason the dev gave on the long-term axis: a field on the declaration is precise when empty; a half-populated ledger keyed by path would read as a corpus fact this file's header warns against. The subtraction's licence-to-grow risk is barred in the three directions the dev named: a record quoting no ruling reds (quotation bar, both spellings the map already uses — the 2026-08-25 carve-out quotes an English ruling), a record without a date reds, and a set of records claiming more than the ceiling stands above was reds as an over-claim (the case that would otherwise read PAID DOWN forever). A declaration carrying no record behaves exactly as before, pinned.
    • Data: the live declaration back to was: 314; the +34 recorded with the ruling text copied from the comment beside the 358 row; the ceiling row itself untouched; no other ceiling or pin moves. File surface exactly scripts/pm/check-skill-line-ratchet.mjs, +197/−27.
    • Gates on the head, run by the seat: node scripts/pm/check-skill-line-ratchet.mjs exit 0; --self-test → check-skill-line-ratchet self-test: 155 cases pass (136 → 155, every prior refusal assertion unchanged in the diff); the full gate output diffed against origin/main's: exactly one line differs — the move verdict, +10 (348→358) → +10 (314→358, less 34 lines of ordinary ruled raise); every per-file line byte-identical (AGENTS.md 1160 of 1162, SKILL.md 970 of 1005, platform-readings 358 of 358 on today's main). The dev's premise check (was: 314 on the unchanged script reds +44 against −11 and total by 33) and ablation (separation reverted with was: 314 kept → the live gate reds twice and 5 of 155 cases red; restored by hash) are quoted with exit codes; --ran reconciles 22 of 22 derived families; whole-repo lint through the lock exit 0; check:ratchet-remedy-authority still classifies the script excluded (202 swept).
    • CI on e69c9623 at 07:50Z: 20 success, 11 skipped, 2 in progress (Test Core 1/6, Lint & Repo Gates).

    Landing: pure code (scripts/pm/**) ⇒ the seat flips ready and arms auto-merge SQUASH once every check is green — not before. Note for the #15192 flight (measurement-first, in the air): its ceiling raise, when ruled, is recorded as a second ruledRaises record on this declaration, not by moving was — this PR is the serial predecessor of that patch round.


    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

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions