Skip to content

CENSUS-RATCHET: entity-bound contracts never drop and rise every release #4709

Open

Description

@noahgift

Operator C303, verbatim: "you need to include a ratchet for every release to close the - pv census: counts contracts. Only 48 of 1,889 are tied to a real thing so far, which is the biggest gap."

Parent: epic #4678 (0.70.2). Cross-link: ONT epic #3269.

Baseline (measured, not typed)

pv census --format json contracts at origin/main 316dee2, pv 0.70.1: n_parsed 1889, by_anchoring unanchored 1841, class 9, instance 39 → bound = 48 (class + instance), unbound share 1841/1889 = 97.46%.

The gate (scripts/check_census_ratchet.sh, measured by pv census, no Python, no stored limit)

  1. Every PR (never-worse). It runs head vs base, with the same pv binary in the same run (base = scripts/lib/resolve_base.sh, extracted with git archive). RED if bound(head) < bound(base), or if unbound(head)/n(head) > unbound(base)/n(base). The shares are compared by integer cross-multiplication. guard-tree runs it on every PR (it lives under scripts/check_*.sh, so it needs no workflow edit).
  2. Every release. It runs --release <version> --commit <sha>, called from autopilot.sh step readiness before any tag exists. It requires bound(release) ≥ bound(previous tag) + STEP, where STEP = min(100, unbound(previous tag)). 0.70.2 is the baseline and pays no step; the step starts at 0.71.0.
  3. It never passes vacuously. pv missing or without census, a non-zero exit, unparseable JSON, 0 contracts, parse errors, or anchoring counts that do not sum to n_parsed: each one gives NOT_MEASURED and a FAIL.
  4. Red/green proof. --self-test runs the real pv on fixture repos. Unbinding one contract → RED. Adding an unbound contract → RED. Binding one → GREEN. A release short of the step → RED. A stub pv that prints garbage or 0 contracts → NOT_MEASURED RED.

Projection

unbound = 1841 at baseline. At 100 per train from 0.71: 0.71 → 148 bound, 0.72 → 248, …, 0.88 → 1848, and 0.89 binds the last 41 (1889/1889). That is 19 trains (0.71–0.89), provided the never-worse share rule holds, so no new unbound debt arrives.

Step owners

The binding work (~100 contracts → entity: per train) goes to the look-ahead lanes. Proposed first owner: L1 0.71 la-71.

Done when

  • The guard is on main, its self-test is green, and the mutation RED is shown in the PR
  • autopilot readiness calls --release
  • The baseline row above is recorded in the PR

Activity

  1. added this to the 0.70.2 milestone on Oct 4, 2026
  2. 5 remaining items

  3. added this to the backlog milestone on Oct 7, 2026
  4. noahgift commented on Oct 7, 2026

    @noahgift
    ContributorAuthor

    slipped_from: 0.70.2 -- carried to backlog at the 0.70.2 cut by the release autopilot (not must-carry, and not listed by the 0.71.0 epic)

  5. modified the milestones: backlog, 0.71.0 on Oct 8, 2026
  6. modified the milestones: 0.71.0, 0.72.0 on Oct 9, 2026
  7. noahgift commented on Oct 14, 2026

    @noahgift
    ContributorAuthor

    Plan (la-0.72, owner from 2026-10-14)

    Baseline, re-measured

    I ran pv census --format json contracts (pv 0.71.2 f0a7b07) on git archive <ref> contracts. bound = class + instance.

    ref n_parsed parse errors unanchored class instance bound
    v0.70.1 1889 0 1841 9 39 48
    v0.70.2 1895 0 1847 9 39 48
    v0.71.0 1903 0 1855 9 39 48
    v0.71.1 1903 0 1855 9 39 48
    v0.71.2 1911 0 1861 10 40 50
    main d97936f 1913 0 1863 10 40 50

    So 0.71.0 owed bound ≥ 148 under the rule as written and shipped 48. Between v0.70.1 and main, n grew by 24 and bound by 2, so 22 of the 24 new contracts arrived unbound. The share barely moved (97.46% → 97.39%) only because 2 of the 24 were bound. That is the debt the never-worse share rule exists to stop.

    What is already there

    scripts/check_ont_ratchet.sh already measures base and head with one instrument (resolve_base + git archive) and refuses a fall in contracts_anchored. But it counts entity: lines, not pv census anchoring, and it has no share rule, no release step, and no CI runner (only make ont-ratchet). The new guard reuses its base resolution and leaves its counters alone.

    Build: scripts/census_ratchet.sh (deliberately NOT check_*.sh at first)

    As specified above: head vs base with the same pv in one run; bound never falls; the unbound share never rises (integer cross-multiplication); --release <ver> --commit <sha> requires bound ≥ bound(previous tag) + min(100, unbound(previous tag)); no stored limit. Any of the following is NOT_MEASURED → FAIL, never a pass:

    • pv missing or without census
    • a non-zero exit
    • unparseable JSON
    • 0 contracts
    • parse errors
    • anchoring counts that do not sum to n_parsed

    --self-test runs the real pv on fixture trees, with these rows:

    • unbind one → RED
    • add an unbound contract → RED
    • bind one → GREEN
    • a release short of the step → RED
    • a stub pv printing garbage or 0 contracts → NOT_MEASURED RED

    A planted mutant shows that each row can go RED.

    Why not check_*.sh on day one (C324)

    guard-tree runs every git ls-files 'scripts/check_*.sh' on every PR. So naming it check_census_ratchet.sh would make it a merge GATE on landing. Under C324 a new gate needs the yes ruling, three green nights on main first, and one gate retired. Order:

    1. PR 1: the script plus its self-test, run nightly as a LAB step in guards-nightly. A red there opens or updates this issue, and nothing else. The baseline row goes in the PR body.
    2. After three green nights: a promotion round asks for the yes ruling and names the gate it retires. Promotion is a rename to check_census_ratchet.sh, which puts it in guard-tree, and an autopilot readiness call with --release.

    The release step needs a decision before it can arm

    The step was meant to start at 0.71.0 and was never measured, so today --release 0.71.3 would demand 148 against 50, and 0.72.0 would demand at least 150. Proposal: the step starts at the first release cut after promotion, from the bound of the tag before it, with no back-debt for 0.71.x. That ruling goes to the quorum with the promotion round. The binding work (~100 entity: bindings per train) needs owners per train. That is coordinated with the pv P0 lane, because empty contracts change n and the share.

    Timing

    PR 1 waits on an a3 slot word (open PRs are near the cap). The self-test and the planted RED are built and run before then.

  8. noahgift commented on Oct 15, 2026

    @noahgift
    ContributorAuthor

    Evidence, night 1 (NOT_MEASURED). The census ratchet's first scheduled night ran.

    This is not a measured night. #4709 stays open until the first night after #5295 measures GREEN or RED; a second evidence comment follows that run.

  9. noahgift commented on Oct 15, 2026

    @noahgift
    ContributorAuthor

    Evidence (la-0.71), the "Done when" items checked against main 8f601ca. I am not closing the issue.

    Measured on a fleet host, worktree detached at 8f601ca, with pv 0.71.3 built from that tree (cargo build --release -p aprender-contracts-cli --bin pv, rc 0).

    1. The guard is on main, its self-test is green, and a mutation RED is shown in the PR: MET, as a LAB check.

    2. autopilot readiness calls --release: NOT MET, and not built.

    • scripts/release/autopilot.sh at 8f601ca has no census call. Under the standing policy (AP_POLICY, line 172), readiness (R8) is not run at all; CRUX smoke is the release gate.
    • Wiring it in would add a release GATE. C324 item 7 allows that only with the operator's yes, three green nights on main first, and one gate retired for each one added (C333 caps release-stopping checks at 20). None of those three exists yet, so I built nothing for this item.
    • Release mode measured today: --release 0.72.0 --commit 8f601caff2 against v0.71.3 gives RED bound 50 < 150 (prev 50 + step min(100, 1909)), rc 1. As a gate it would stop 0.72.0. The ~100 contracts→entity: binding step for this train has not been done.

    3. The baseline row is recorded in the PR: MET, with a newer measurement.

    What is left (an operator ruling, not a lane's call):

    • (a) A yes to make --release a release GATE, after three green nights, with one gate retired. Or the release-mode check stays LAB.
    • (b) An owner for the binding work: 100 contracts per train, starting at 0.71. Bound has moved 48→50 since the baseline.
  10. noahgift commented on Oct 15, 2026

    @noahgift
    ContributorAuthor

    Folded in (cop batch 10:31Z): #5366 — each is now a checklist row of this master.

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

    P0Critical priorityowner:la-0.71Owned by the 0.71 look-ahead slotsize:SK̂ ≤ 2 h

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions