Skip to content

M6 design amendment: forward earnings generator spec (unblocks #173)#175

Merged
MaxGhenis merged 4 commits into
masterfrom
m6-forward-earnings
Jul 13, 2026
Merged

M6 design amendment: forward earnings generator spec (unblocks #173)#175
MaxGhenis merged 4 commits into
masterfrom
m6-forward-earnings

Conversation

@MaxGhenis

Copy link
Copy Markdown
Contributor

M6 design amendment — forward earnings generator spec (unblocks #173)

Adds §2.7 to docs/design/m6_projection_engine.md (design doc revision 2),
specifying the forward earnings generator the engine build left external. Docs-only,
DRAFT; no gates.yaml edit, no floor, no test.

Why (Sol's honest blocker, PR #173)

The engine build composed steps 1–8 and passed the §2.6 re-certification margin
check, but blocked on forward earnings. The certified gate-1 candidate-11 generator
(run_gate1_candidate11.py, byte-identical to candidate-10) is a backward
biennial imputation chain
: it anchors each person's chronologically latest real
earnings, predicts wave t−2 participation from later earnings, and draws earlier
ranks conditional on later ranks. M6 starts from realized 2014 and needs
forward earnings. Sol correctly refused to invent an inverse conditional, a
future-marginal extrapolation, or a two-year→annual kernel (each a different
stochastic law than gate-1 certified), shipping only the admissible ≤2014 refit
plumbing and an explicit external earnings seam (CertifiedEngineInputs still
requires an externally supplied earnings generator). This amendment specifies that
generator so the build can proceed.

What §2.7 specifies

  1. The forward law — a biennial forward conditional-rank chain 2014 → 2016 → 2018 (scored on the {2016, 2018} cells of §4.1; realized 2014 is the anchor).
    It mirrors the certified conditioning structure reversed in time and is fit
    ≤2014 from scratch — not an inversion of the backward conditional; the
    scored path stays biennial (no annual kernel for any gated cell); it applies
    a ≤2014-fit conditional out-of-sample rather than extrapolating a marginal.
    This retires all three of Sol's refused choices. Transfers from candidate-11/10:
    k-NN k=25, distance weights 1/0.5/0.25, fixed λ=0.1 donor blend (Q0-exempt),
    the u_w shrunk-permanent rank, the RegimeGatedQRF participation sign gate, the
    zero-anchor full re-entry pool, the weighted single-record draw. Newly chosen
    (justified from ≤2014 diagnostics only, candidate-blind wrt the holdout, per Candidate 2 design: latent-permanent conditioned chained QRF (research memo) #42
    comment 4948637741): the forward donor pools on ≤2014 forward tuples, the
    forward participation-gate fit, the initial-2014 anchor, and the start-of-chain
    memory ramp.
  2. Annualization — the odd years {2015, 2017, …} are filled non-scored
    (participation carried from the prior biennial wave; positive level log-linear
    interpolated on the NAWI-indexed level between biennial brackets), feeding only
    the pe-us seam / AIME / claiming. The scored earnings-moment cells read only
    the biennial forward draws.
  3. Certification framing — the forward generator is a different stochastic
    law
    than gate-1 certified, so it is not covered by gate_1 and is
    first-certified by gate_m6; §4.2's refit enumeration and decision 10's
    certified-claims language are updated to record this (no gate_1 certificate
    transfers to it).
  4. Implementation contract for the engine lane — inputs (realized ≤2014
    history, ≤T* latent-permanent u_w, age, injected RNG), outputs (biennial
    forward draws for {2016, 2018} + the non-scored annual series), RNG stream
    slots under the §3.2 F3 (module, period) tree (consumed only at biennial steps,
    spawning candidate-7's gate/donor/re-entry sub-streams), and the
    CertifiedEngineInputs seam the build (M6 engine: wave-loop composition per verified design (build only) #173) left external.

Not in scope

No gates.yaml edit, no floor artifact, no test, no code. The forward generator is
implemented by the engine lane against the §2.7.4 contract and first-certified in
the gate_m6 scored run.

🤖 Generated with Claude Code

Add §2.7 to docs/design/m6_projection_engine.md specifying the forward
earnings generator the engine build (Sol, PR #173) correctly left external.
The certified gate-1 candidate-11 chain is a BACKWARD biennial imputation
(anchors the latest real earnings, predicts t-2 participation from later
earnings, draws earlier ranks conditional on later ranks) and cannot be
composed into the M6 forward loop without a new stochastic law.

§2.7 specifies that law:

- a biennial forward conditional-rank chain 2014->2016->2018 (scored on the
  {2016,2018} cells of §4.1), mirroring the certified conditioning structure
  reversed in time and fit <=2014 from scratch -- not an inversion of the
  backward conditional, no annual kernel for any gated cell, no marginal
  extrapolation (retiring Sol's three refused choices);
- transfers k=25, distance weights 1/0.5/0.25, lambda=0.1 (Q0-exempt), the
  u_w shrunk-permanent rank, the RegimeGatedQRF participation gate, and the
  zero-anchor full re-entry pool from candidate-11/10;
- newly chosen items (forward donor pools on <=2014 forward tuples, forward
  participation-gate fit, initial-2014 anchor, start-of-chain memory ramp)
  justified from <=2014 fit-window diagnostics only, candidate-blind with
  respect to the holdout (#42 comment 4948637741 spec-selection disclosure);
- non-scored annualization (participation carried from the prior biennial
  wave; positive level log-linear interpolated on the NAWI-indexed level)
  feeding only the seam / AIME / claiming, never a gated cell;
- certification framing: NOT gate_1-certified (different, forward law),
  first-certified by gate_m6; §4.2 refit enumeration + decision 10 updated;
- implementation contract for the engine lane: inputs, outputs, RNG stream
  slots under the F3 (module, period) tree, and the CertifiedEngineInputs
  seam interface.

Docs-only; no gates.yaml edit, no floor, no test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
social-security-model Ready Ready Preview, Comment Jul 13, 2026 4:03pm

Request Review

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Adversarial review — forward earnings generator spec (revision 2, §2.7; commit df9ccb6)

Verdict: MAJOR REVISION. The forward law itself is sound: it is a genuine ≤2014-derivable time-mirror of the certified conditioning structure (not an inversion, not a marginal extrapolation, not an annual kernel — Sol's three refused choices are all retired), the transfer table is code-true bar one provenance mis-citation, the annualization is provably isolated from every gated cell, and the "first-certified by gate_m6, no gate_1 transfer" framing is correct in the design doc. But two MAJOR items must be fixed before this seeds the build: (1) the §2.7.4/§2.7.5 implementation contract does not match PR #173's real EarningsGenerator seam, so the engine lane cannot implement with "zero further design choices" as claimed; (2) the in-flight gate_m6 block draft (draft_cleared_ready_for_lock_flip, pinned to the pre-amendment design) would lock with a covers that understates what a pass certifies. Neither is a blocker — both are contract/record restatements, not a redesign of the law.

Transfer table — verified against the certified code (not the doc's citations)

claim code check result
k-NN k = 25 run_gate1_candidate7.py:165 K_NEIGHBORS = 25 (imported byte-for-byte by c10) ✅ true
distance weights 1 / 0.5 / 0.25 candidate7:168-170 W_NEXT=1.0, W_NEXT2=0.5, W_ANCHOR=0.25 ✅ true
λ=0.1 donor blend, Q0-exempt (Q0 third term stays |u_A(donor)−u_A(target)|) run_gate1_candidate10.py:231 LAMBDA_FIXED = 0.1; docstring :28-35 (non-Q0 third term 0.1·u_w+0.9·u_A; Q0 memory-exempt) ✅ true
RegimeGatedQRF sign-gate architecture real populace-fit class — run_gate1_candidate2.py:96 import, :307 RegimeGatedQRF(seed=seed); gates via fit_participation_gate/_gate_sign_draw ✅ true (external dep, correctly not in-repo)
zero-anchor full re-entry pool (no restriction) candidate10:18 "no re-entry-pool restriction"; _reentry_draw from run_inner_sweep ✅ true
weighted single-record draw, no smoothing/jitter candidate7:383 _knn_draw ✅ true
age as only other covariate (marital-blind) prior round: 0 marital hits in c11; age_bin the covariate ✅ true
backward direction (anchor = chronologically latest; predict t−2 from later; draw earlier|later; two-step-plus-anchor) run_gate1_baseline.py:137 build_backward_pairs; candidate7:214,1105 anchor = "chronologically last observed period"; candidate10:255-267 "backward k-NN chain" ✅ true
u_w shrunk-permanent rank = "candidate-3 z-decomposition" candidate10:65-66 "the u_w decomposition is candidate 8's (PR #58)"; candidate8:27-38 u_w = Φ(ŵ/σ̂_w) built on "the SAME candidate-3 decomposition (Stage 1c correlated-noise)" ⚠️ mis-cited (finding 3)

The object that transfers (the u_w shrunk-permanent rank coordinate) is real and does transfer; only its lineage is loosely attributed.

Findings

1 — MAJOR. The §2.7.4/§2.7.5 implementation contract does not match PR #173's real earnings seam; the engine lane cannot implement with "zero further design choices." The actual slot the build left external is EarningsGenerator.generate(frame: pd.DataFrame, year: int, rng) -> np.ndarray (origin/m6-engine:src/populace_dynamics/engine/steps.py:164), invoked every projection year by apply_earnings(... model=inputs.earnings) (assembly.py:292-293; apply_earnings docstring "Draw annual earnings"). The doc instead specifies a different function — (realized_history, u_w, age, rng) → biennial_forward_draws, "a pure function ... with no engine state beyond its ≤2014 fit," consuming "RNG ... for each biennial step." Three concrete mismatches:

  • Signature/abstraction: the real seam is per-year, frame-in / ndarray-out. The doc's 4-named-input / biennial-bundle-out signature must be re-expressed as generate(frame, year, rng), stating which frame columns carry realized_history, u_w, and age.
  • Annual call vs biennial draw: generate is called for odd years too. The parity branch (draw on even, deterministic-fill on odd, §2.7.2) is implied but never connected to the real per-year protocol — the generator must branch on year internally, which the contract should state.
  • "No engine state" is false for a chain: the 2018 step conditions on the drawn 2016 rank (rank_{t+2} | rank_t, rank_{t−2} ⇒ for 2018, rank_t = drawn 2016), which the engine threads through the evolving frame (assembly._merge_period_columns), not "realized ≤2014 history." The contract's input list omits this prior-draw conditioning input and mis-states statelessness. Fix: restate §2.7.4/§2.7.5 against generate(frame, year, rng), name the frame columns, specify parity branching, and specify that the prior drawn biennial rank re-enters via the frame.

2 — MAJOR. The in-flight gate_m6 block draft would lock with a covers that understates what a pass certifies. docs/design/gate_m6_block_draft.yaml (master 1818784, status: draft_cleared_ready_for_lock_flip, design_commit: 1d83a221 / design_pr: "170" — i.e. pinned to revision 1, pre-amendment) gates 6 earnings cells on gated_earnings_reference_years: [2016, 2018] and its headline-prominence not_certified/spec_selection_in_sample record covers only the surface selection (coarsening + decompounding). It nowhere records that (a) the earnings generator producing those {2016,2018} draws is the §2.7 forward law, first-certified here and not gate_1-certified, or (b) the forward generator's structural spec-selection (mirroring a full-window-selected certified spec, §2.7.1) — a limitation distinct from the surface-selection one already listed. A reader of the locked block would reasonably assume the gated earnings use the gate_1-certified generator; the amendment reveals they do not. Under the block's own amendment-1 standard ("a PASS statement must claim exactly the scored surface"), locking as-is misstates scope. The amendment updates only m6_projection_engine.md; it must flag as a lock-flip dependency that the block draft's covers/not_certified fold in the §2.7.3 forward-law framing and re-pin design_commit to df9ccb6.

3 — MINOR. u_w provenance mis-cited. §2.7 (and §10 JSON transfers_from_candidate11) attribute "the u_w shrunk-permanent rank" to "candidate-3 z-decomposition." The u_w = Φ(ŵ/σ̂_w) shrunk-permanent rank is candidate-8's construct (run_gate1_candidate8.py:27-38, PR #58); it is built on candidate-3's Stage-1c correlated-noise decomposition. c10 itself says "the u_w decomposition is candidate 8's (PR #58)." Credit candidate-8-on-candidate-3 (the coordinate transfers correctly regardless).

Checks that pass (recorded so the revision does not re-litigate them)

  • ≤2014 derivability of every new choice (question 2) — SATISFIED. Forward donor pools (candidate-7's pool rule applied to ≤2014 forward tuples), forward participation-gate coefficients (≤2014 forward pairs, RegimeGatedQRF architecture/features transferred), the anchor flip (structurally forced — 2014 is the only real forward anchor), and the start-of-chain memory ramp (fixed from the ≤2014 wave calendar: 2014→2016 one-step-plus-anchor, 2016→2018 full two-step) are each determined by ≤2014 data + the mirror rule with no holdout-informed degree of freedom. The Candidate 2 design: latent-permanent conditioned chained QRF (research memo) #42 spec-selection disclosure is present and honest (out-of-sample for fitted parameters, in-sample for structural choice). Note: "candidate-blind wrt the holdout" is a design commitment the floors/pass-verification ceremony must confirm against the actual fit (no code exists yet).
  • Annualization isolation (question 3) — SATISFIED. The scored earnings cells read only the biennial {2016, 2018} draws (block draft gated_earnings_reference_years: [2016, 2018]); the odd-year participation-carry / log-linear-interpolation values carry non_scored_annualization and feed only the pe-us seam / AIME / claiming, none of which is a gated cell (gated = marital/disability/mortality flows + biennial earnings moments). The interp-vs-carry flag is genuinely immaterial to certification (it moves only report-only AIME/benefit levels), so §2.7.2's "floors ceremony need not adjudicate it" is correct.
  • Certification framing, doc side (question 4) — COHERENT. §2.7.3, §4.2, and decision 10 consistently state no gate_1 certificate transfers and a gate_m6 pass is the forward law's sole certification; §4.2 correctly narrows the certified backward chain's admissible ≤2014 refit to plumbing, with earnings left external (confirmed: CertifiedEngineInputs.earnings is a standalone field, not in from_refit_bundle's required bundle set {family, household, modifier, disability, claiming_pmfs}). The gap is only the block-draft artifact (finding 2), not the doc's internal logic.
  • RNG slots (question 5, RNG part) — code-true. candidate10:239 SUBSTREAM_CODES {gate:1, donor-draw:2, re-entry-draw:3} and _substream two-element SeedSequence([seed, code]) (:242-252) match §2.7.5's "three canonical sub-streams ... two-element SeedSequence([·, code]) draw order."

Read-only adversarial review; no repo edits, no merge/ready. Every cited line re-checked against df9ccb6, origin/m6-engine, and master 1818784.

MaxGhenis and others added 2 commits July 13, 2026 11:46
Address the design-referee round on #175 (comment 4959673982, MAJOR
REVISION); the forward law itself was verified code-true, the fixes are
contract/record restatements:

- finding 1 (seam contract): respec §2.7.4/§2.7.5 to PR #173's real
  EarningsGenerator.generate(frame, year, rng) -> np.ndarray seam
  (origin/m6-engine steps.py:164-169) -- per-year, called by apply_earnings
  every projection year, stateful THROUGH the frame. Name the frame columns
  read, the even/odd parity branch (biennial draw on even reference years,
  deterministic fill on odd), and the drawn-prior-2016-rank threading (recovered
  inside generate by re-ranking the carried earnings column, which
  _merge_period_columns threads forward). The forward per-year seam has no
  lookahead, so §2.7.2 annualization is corrected to a carry-forward default
  (log-linear interpolation only via batch-compute + backfill), which resolves
  the revision-1 interp-vs-carry flag; still non-scored (isolation unchanged).
- finding 2 (block draft): update docs/design/gate_m6_block_draft.yaml -- covers
  and not_certified.forward_earnings_law_not_gate1_certified now record that a
  gate_m6 PASS first-certifies the §2.7 forward law (gate_1 does NOT cover it; no
  certificate transfers), including its structural spec-selection distinct from
  the surface selection; design_pr 170->175 and design_commit re-pinned off the
  stale 1d83a22 (note added; the hash is set to this PR's design commit).
- finding 3 (u_w provenance): u_w = Phi(w-hat/sigma-hat_w) is candidate-8's
  construct (PR #58) built on candidate-3's Stage-1c decomposition; cited that
  way in §2.7 and the §10 ledger.

Docs-only; no gates.yaml edit, no floor, no test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Set gate_m6_block_draft.yaml design_commit to d6abb16 (this PR design commit
carrying the §2.7 forward-earnings amendment), completing the finding-2 re-pin
off the stale 1d83a22. design_commit_note records the finalize-at-lock-flip
rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Revision — design referee round addressed (commits d6abb16 + 85f95e7)

All three findings from the adversarial review (comment 4959673982, MAJOR REVISION)
are addressed. The forward law itself was verified code-true (the referee's transfer
table checked out bar the one provenance mis-cite); the fixes are contract/record
restatements, not a redesign. Docs-only; no gates.yaml edit, no floor, no test.

Finding → change

# Sev Finding Fix
1 MAJOR §2.7.4/§2.7.5 contract didn't match PR #173's real EarningsGenerator seam respec'd to generate(frame, year, rng) -> np.ndarray (verified against origin/m6-engine:steps.py:164-169, assembly.py:86,292-293) — §2.7.4
1a signature/abstraction (per-year, frame-in/ndarray-out) §2.7.4 gives the Protocol verbatim + names the frame columns read (earnings, realized-2014 cols, u_w, age, sex)
1b annual-call-vs-biennial-draw parity §2.7.4 parity branch: biennial draw on even reference years, deterministic carry on odd; §2.7.5 RNG consumed only on even years via the per-year person_generator(EARNINGS) stream
1c "no engine state" is false for a chain §2.7.4 names the drawn-prior-2016-rank threading — recovered inside generate by re-ranking the frame's carried earnings column (threaded by _merge_period_columns); generator holds no cross-year state. §2.7.2 annualization corrected to carry-forward (the forward per-year seam has no lookahead; interpolation only via batch+backfill) — resolves the rev-1 interp-vs-carry flag
2 MAJOR block draft would lock with an understated covers docs/design/gate_m6_block_draft.yaml: covers + new not_certified.forward_earnings_law_not_gate1_certified record that a gate_m6 PASS first-certifies the §2.7 forward law (gate_1 doesn't cover it; no certificate transfers) incl. its structural spec-selection distinct from the surface one; design_pr 170→175, design_commit re-pinned off stale 1d83a221 to this PR's design commit (d6abb16) + design_commit_note
3 MINOR u_w provenance cited as candidate-8's u_w = Φ(ŵ/σ̂_w) (PR #58) built on candidate-3's Stage-1c decomposition — §2.7 transfer bullet + §10 ledger

The corrected seam contract, in three sentences

The forward generator implements PR #173's EarningsGenerator.generate(frame, year, rng) -> np.ndarray protocol — per-year, frame-in / ndarray-out — called by
apply_earnings every projection year with each person's engine-owned
ProjectionModule.EARNINGS stream, and it is stateful through the frame, not a
pure function: generate branches on year, drawing the biennial forward value on
even reference years ({2016, 2018}, consuming the candidate-7 gate/donor/re-entry
substreams) and returning the deterministic carry-forward value on odd years, with
the return written to frame["earnings"] and carried to the next year by
_merge_period_columns. The chain's drawn-prior-rank conditioning (rank_{t+2} | rank_t, …) is not an argument but is recovered inside generate by re-ranking the
carried earnings column (0 = non-participation), with the earlier lag and anchor
from the realized-2014 columns, so no chain state lives outside the frame.

Block-draft delta

covers now states the gated {2016,2018} earnings cells ride on the §2.7 forward
law (a different stochastic law than the gate_1-certified backward chain, first- and
sole-certified by gate_m6); a new not_certified margin
forward_earnings_law_not_gate1_certified records this plus the forward law's
structural spec-selection (distinct from the surface-selection margin);
design_pr 170→175, design_commit 1d83a221→d6abb16 with a note that it pins this
PR's amended design and finalizes to the squash-merge at the lock flip. Verified:
tests/test_gate_m6_floors.py passes (23/23) against the edited block — mortality
drift stays named first, all six required not_certified margins remain.

Anything I could not verify against #173's code

Nothing. Every seam fact was re-derived from origin/m6-engine: the generate
signature (steps.py:164-169), the per-year call + frame["earnings"] write
(apply_earnings, steps.py:205-238), the per-person EARNINGS stream
(steps.py:212-224), the standalone external slot (CertifiedEngineInputs.earnings,
assembly.py:86; earnings_step, :292-293), and the cross-year threading
(_merge_period_columns, assembly.py:151-169). The only design commitment the code
cannot yet confirm — noted in the doc — is "candidate-blind wrt the holdout," which
has no implementation yet and is the floors/pass-verification ceremony's to confirm.

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Verification round (round-diff) — forward-earnings revision (d6abb16 + 85f95e7)

Verdict: VERIFIED (ready to merge). All three findings from the MAJOR-REVISION review (comment 4959673982) are genuinely resolved — not renamed — and every seam fact was re-derived from origin/m6-engine directly, not taken from the doc's citations. The one derived correction (carry-forward is forced) checks out and is actually stronger than the doc states. One MINOR, non-blocking precision nit is noted at the end; it does not reopen any finding.

Finding 1 (MAJOR — seam contract) — RESOLVED, re-verified against the real engine

§2.7.4/§2.7.5 now specify PR #173's real protocol, and each fact holds in code:

  • Protocol EarningsGenerator.generate(self, frame: pd.DataFrame, year: int, rng) -> np.ndarraysteps.py:164-169, quoted verbatim in §2.7.4. ✅
  • Per-year, frame-in / ndarray-out, per-person age ≥ 15, engine-owned stream, frame writeapply_earnings (steps.py:205-238): docstring "Draw annual earnings from the refitted chained generator"; eligible = ...age >= 15; person_rng = context.person_generator(ProjectionModule.EARNINGS, person_id); model.generate(frame.iloc[[index]], context.year, person_rng); out["earnings"] = generated. All match §2.7.4/§2.7.5. ✅
  • Stateful through the frame / drawn-prior-rank via re-rank — the period loop for period_index, year in enumerate(range(start_year+1, end_year+1)) (loop.py:203) is forward, one year at a time, no lookahead, and current = self.modules.earnings(current, …) (loop.py:249) threads the frame across periods, so the drawn 2016 level survives in current["earnings"] to the 2018 step's re-rank. The generator holds no cross-year state. ✅
  • Backward-chain / external-slot facts confirmed: refit.py:75-76 predicts earnings_tm2 (two years earlier) — the certified backward chain; CertifiedEngineInputs.earnings (assembly.py:86) is the standalone external slot (earnings_step, :292-293), not in from_refit_bundle's required set. ✅

The new "carry-forward forced" correction (§2.7.2) — VERIFIED coherent and, in fact, doubly forced. The forward per-year loop has no lookahead, so at the 2015 call the 2016 draw does not exist → interpolation-toward-next is impossible without drawing 2016 early on the wrong period stream; carry-forward is the only seam-faithful rule. This is coherent with §2.7.2 and still provably non-scored (gated earnings read only the biennial {2016,2018} draws; odd-year values carry non_scored_annualization and feed only the report-only seam/AIME/claiming). Stronger than the doc claims: carry-forward is also required for chain integrity — 2017 must hold the 2016 level unchanged so the 2018 step re-ranks rank_t = 2016; interpolating 2017 would corrupt that re-rank. The batch-then-backfill interpolation alternative preserves the chain (biennial draws computed first) and is correctly flagged as a non-default departure. Consistent throughout.

Finding 2 (MAJOR — block-draft covers) — RESOLVED, test 23/23

docs/design/gate_m6_block_draft.yaml (now edited in-branch, not merely flagged):

  • covers adds that the gated {2016,2018} earnings cells ride on the §2.7 forward law, "a DIFFERENT stochastic law than the gate_1-certified BACKWARD chain… gate_1 does NOT cover it and no gate_1 certificate transfers." ✅
  • New not_certified margin forward_earnings_law_not_gate1_certified (line 132), distinct from spec_selection_in_sample (line 127): records first-certified-here, no gate_1 transfer, the forward law's structural spec-selection, and "A PASS statement must say the gated earnings use an M6-first-certified forward law, not the gate_1-certified generator." ✅ Claims-exact.
  • design_pr 170→175, design_commit 1d83a221→d6abb16 with a design_commit_note re-pinning off the stale pre-amendment commit and finalizing to the squash-merge at the lock flip. ✅
  • Mortality still first, only-added: mortality_drift is margins[0] (line 82); all six REQUIRED_NOT_CERTIFIED margins remain; the new margin is purely additive (not in the required set, allowed by the <= subset check). I ran tests/test_gate_m6_floors.py myself — 23 passed — and check_not_certified asserts margins[0] == "mortality_drift", the required-subset, and a non-empty detail per margin. ✅

Finding 3 (MINOR — u_w provenance) — RESOLVED

§2.7 now reads "u_w = Φ(ŵ/σ̂_w) shrunk-permanent rank — candidate-8's construct (PR #58, run_gate1_candidate8.py:27-38) built on candidate-3's Stage-1c correlated-noise decomposition"; §10 ledger updated to u_w_shrunk_permanent_rank_candidate8_PR58_on_candidate3_stage1c. Matches the code. ✅

Candidate-blind non-verifiable — honestly scoped

§2.7.1 keeps "justified from ≤2014 diagnostics only, candidate-blind with respect to the holdout" as a design commitment, and the revision comment correctly scopes it as having no implementation yet and deferred to the floors/pass-verification ceremony. Not overclaimed as verified. ✅

One MINOR, non-blocking precision nit (does not reopen finding 1)

§2.7.4 (and the §10 JSON) attribute the cross-year carry of the earnings column to _merge_period_columns. In the real engine the earnings column is not threaded by _merge_period_columnsapply_earnings writes out["earnings"] in place (steps.py:237) and the frame is carried by the loop's current = self.modules.earnings(current, …) reassignment (loop.py:249); _merge_period_columns threads the marital/disability/household update-frames (assembly.py:226/285/315), which produce separate updates DataFrames, whereas earnings does not. The chain-integrity conclusion the doc draws (the drawn 2016 level survives in the frame to the 2018 re-rank) is correct and verified; only the named mechanism for the earnings column is imprecise. Harmless to the engine lane (the real code threads the frame correctly regardless), so it does not block merge — worth a one-line fix in passing.

Round-diff verification, read-only; no repo edits, no merge/ready. Every seam fact re-derived from origin/m6-engine (steps.py, loop.py, assembly.py, refit.py); block test run locally (23/23).

…39799)

Earnings is written in-place by apply_earnings and threaded by the loop's
frame reassignment; _merge_period_columns threads the marital/disability/
household update-frames. Conclusion unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@MaxGhenis MaxGhenis marked this pull request as ready for review July 13, 2026 16:06
@MaxGhenis MaxGhenis merged commit ce9893b into master Jul 13, 2026
7 checks passed
MaxGhenis added a commit that referenced this pull request Jul 13, 2026
…design pin

Follow-up to the #175 block-draft amendment (merged to master as ce9893b: the
design 2.7 forward-earnings law -- first-certified by gate_m6, gate_1 does NOT
cover it). Merges origin/master and RE-APPLIES gates.gate_m6 from the UPDATED
docs/design/gate_m6_block_draft.yaml, still VERBATIM, with the lock-time deltas:

- `locked: false -> true`
- `status: draft_cleared_ready_for_lock_flip -> locked`
- + the `2026-07-13-m6-gate-lock` history entry
- `design_commit` FINALIZED per the draft's own design_commit_note from PR
  #175's design commit `d6abb16b` to the #175 squash-merge `ce9893b`
  (`design_pr: "175"` carried verbatim).

The amendment carries into the live block verbatim: the extended `covers`
(forward-law first-certification framing) and the new `not_certified` margin
`forward_earnings_law_not_gate1_certified` (9 -> 10 margins). The history entry
records the finalization (`design_commit_finalized`: from / to / rule / note)
and narrates it in `content`.

Binding updates (`tests/test_gate_m6_derivations.py`): `design_pr "175"`,
`design_commit ce9893b`, the 10-margin `not_certified` count + the forward-law
margin, and the history finalization record are now bound; +2 mutations
(un-finalized design_commit; dropped forward-law margin) -> 12 mutation checks.
`tests/test_gate_m6_floors.py` verbatim-fidelity check extended to tolerate the
design_commit delta (live == draft modulo locked / status / history /
design_commit; design_pr stays "175").

`runs/legacy_manifest_v1.json` re-recorded via `build_legacy_manifest.py
--transition` (contract_revision + the gates.yaml entry only). Tier counts
unchanged (artifact 944, total 2,684 -- CI-equivalent recount confirms no drift
from the merge). Sibling gates byte-identical to origin/master (ce9893b);
gate_m6 the sole new key.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Jul 13, 2026
…ash-merge

Final re-finalization. §2.7.6 (PR #178, squash-merge 4c6a0f6, twice-verified --
VERIFIED comment 4960583620) COMPLETED the forward rank-to-level + re-ranking
laws -- the forward earnings law the gate_m6 `covers` references. Per the block
draft's own design_commit_note (finalize the pin to the squash-merge at the lock
flip), the live design_commit is RE-FINALIZED from the interim #175 squash
(ce9893b) to the #178 squash (4c6a0f6), so the lock references the COMPLETE law.

Merges origin/master (#178, the §2.7.6 design-doc completion) and regenerates
gates.gate_m6 from the (unchanged) draft VERBATIM with the four lock-time deltas
(locked / status / +history / design_commit). The history
design_commit_finalized record now carries from d6abb16 -> to 4c6a0f6 with
refinalized_from ce9893b and the §2.7.6 reason; the content narrates the
two-step finalization.

Binding updates (tests/test_gate_m6_derivations.py): design_commit 4c6a0f6, the
design_commit_finalized.refinalized_from + the §2.7.6 reason; the design-commit
mutation now drifts the stale interim ce9893b. tests/test_gate_m6_floors.py
asserts live design_commit == 4c6a0f6 (draft d6abb16 unchanged).

runs/legacy_manifest_v1.json re-recorded via build_legacy_manifest.py
--transition (contract_revision + the gates.yaml entry only). Tier counts
unchanged (artifact 944, total 2,684). Sibling gates byte-identical to
origin/master (4c6a0f6); gate_m6 the sole new key. Both design dependencies now
satisfied: #175 (forward-law margin + re-pin) and #178 (§2.7.6 forward-law
completion).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Jul 13, 2026
…177)

* gate_m6 lock flip: temporal-holdout gate added locked (ratified 1818784)

Adds gates.gate_m6 as a NEW LOCKED top-level gate (the gate_w1 / gate_m4
pattern -- added locked, NOT an in-place unlocked->locked flip). The block is
docs/design/gate_m6_block_draft.yaml (the ratified v3 draft) carried VERBATIM
with exactly three lock-time deltas: `locked: true`, `status: locked`, and a
`history` ceremony entry. Every sibling gate (gate_1 / gate_2 [2a + 2b + 2c] /
gate_3 / gate_m4 / gate_w1) is untouched; gate_m6 is the sole new key.

Ceremony (gate_m6.history entry 2026-07-13-m6-gate-lock): the v1
OC-before-lock floor fired the PAUSE honestly (1 gated flow cell, combined
p_gate 0.8449 -- a both-directions failure); the candidate-blind pinned-ladder
redesign (coarsening + earnings decompounding, truth-side power arithmetic
only) was adjudicated on the public record; v2 cleared 0.9067; the adversarial
referee AMEND x4 (comment 4958425437) found the marital-ladder enumeration
incomplete (the asymmetric age-2 rung where remarriage.18-64 clears at 0.403);
fixes + the orchestrator's adjudicated v3 (comment 4958956779) landed 11 gated
cells (5 flow + 6 earnings), p_seed 0.8934 / p_gate 0.9087; re-check PROCEED TO
LOCK (comment 4959310892); ratified 2026-07-13 by merge of PR #172 (squash
1818784) under the maintainer's standing campaign directive of 2026-07-07. The
headline not_certified declaration names mortality drift FIRST; the SSA/NCHS
report-only mortality anchor is required before any M7-seeding claim; ZERO
mortality / widowhood cells are gated on verified power grounds.

ZERO threshold movement vs the frozen floor: the flip READS
runs/m6_holdout_floors_v3.json (sha256 e931c886...) and rewrites nothing.

Bindings go LOCKED-HOT: tests/test_gate_m6_derivations.py binds the live
gates.yaml gate_m6 block to the v3 floor (11-cell registry equality, every
tolerance recompute round(mean + k*sd, 3) capped, OC recompute 0.8934 / 0.9087
from the realized sigmas, not_certified ordering [mortality first], year pins /
shock-window exclusions bound to design_pins, v1/v2/v3 lineage shas, SSA/NCHS
anchor, presence / F6 / split-unit declarations) with >= 6 mutation checks
(tolerance digit, cell add/drop, OC drift, not_certified reorder, year pin,
history sha).

Sibling / manifest hygiene:
- tests/test_gate_m6_floors.py: the draft's untouched-guard inverts at the
  flip to assert gate_m6 is now LOCKED in gates.yaml (+ a verbatim-fidelity
  check: the live block equals the draft modulo the three deltas), mirroring
  the gate_w1 floors-test flip.
- tests/test_gates_derivations.py + tests/test_gate_w1_derivations.py: the
  added-locked-gate master-compare tolerance extended with {"gate_m6"} (the
  a1-era generalization), with attribution.
- tests/test_gate_w1_amendment2_proposal.py: the section-7 subset
  master-compare relaxed to master-side (tolerates the gate_m6 addition;
  every master gate stays present-and-equal in live except gate_w1).
- runs/legacy_manifest_v1.json: recorded via build_legacy_manifest.py
  --transition (only contract_revision + the gates.yaml entry move; the frozen
  legacy gate-2 entries stay byte-identical).
- tier recount (CI-equivalent full collection): artifact 928 -> 944 (+16, the
  new derivations file), total 2,668 -> 2,684 (tests/tier_counts.json +
  tests/README-tiers.md).

Depends on the #175 block-draft amendment (forward-law first-certification in
covers / not_certified + a design_commit re-pin), which lands before the
flip-fidelity round; a follow-up commit will re-apply the updated draft
verbatim + these same three lock-time deltas.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* gate_m6 lock flip: re-apply block from #175-amended draft + finalize design pin

Follow-up to the #175 block-draft amendment (merged to master as ce9893b: the
design 2.7 forward-earnings law -- first-certified by gate_m6, gate_1 does NOT
cover it). Merges origin/master and RE-APPLIES gates.gate_m6 from the UPDATED
docs/design/gate_m6_block_draft.yaml, still VERBATIM, with the lock-time deltas:

- `locked: false -> true`
- `status: draft_cleared_ready_for_lock_flip -> locked`
- + the `2026-07-13-m6-gate-lock` history entry
- `design_commit` FINALIZED per the draft's own design_commit_note from PR
  #175's design commit `d6abb16b` to the #175 squash-merge `ce9893b`
  (`design_pr: "175"` carried verbatim).

The amendment carries into the live block verbatim: the extended `covers`
(forward-law first-certification framing) and the new `not_certified` margin
`forward_earnings_law_not_gate1_certified` (9 -> 10 margins). The history entry
records the finalization (`design_commit_finalized`: from / to / rule / note)
and narrates it in `content`.

Binding updates (`tests/test_gate_m6_derivations.py`): `design_pr "175"`,
`design_commit ce9893b`, the 10-margin `not_certified` count + the forward-law
margin, and the history finalization record are now bound; +2 mutations
(un-finalized design_commit; dropped forward-law margin) -> 12 mutation checks.
`tests/test_gate_m6_floors.py` verbatim-fidelity check extended to tolerate the
design_commit delta (live == draft modulo locked / status / history /
design_commit; design_pr stays "175").

`runs/legacy_manifest_v1.json` re-recorded via `build_legacy_manifest.py
--transition` (contract_revision + the gates.yaml entry only). Tier counts
unchanged (artifact 944, total 2,684 -- CI-equivalent recount confirms no drift
from the merge). Sibling gates byte-identical to origin/master (ce9893b);
gate_m6 the sole new key.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* gate_m6 lock flip: re-finalize design_commit to the §2.7.6 (#178) squash-merge

Final re-finalization. §2.7.6 (PR #178, squash-merge 4c6a0f6, twice-verified --
VERIFIED comment 4960583620) COMPLETED the forward rank-to-level + re-ranking
laws -- the forward earnings law the gate_m6 `covers` references. Per the block
draft's own design_commit_note (finalize the pin to the squash-merge at the lock
flip), the live design_commit is RE-FINALIZED from the interim #175 squash
(ce9893b) to the #178 squash (4c6a0f6), so the lock references the COMPLETE law.

Merges origin/master (#178, the §2.7.6 design-doc completion) and regenerates
gates.gate_m6 from the (unchanged) draft VERBATIM with the four lock-time deltas
(locked / status / +history / design_commit). The history
design_commit_finalized record now carries from d6abb16 -> to 4c6a0f6 with
refinalized_from ce9893b and the §2.7.6 reason; the content narrates the
two-step finalization.

Binding updates (tests/test_gate_m6_derivations.py): design_commit 4c6a0f6, the
design_commit_finalized.refinalized_from + the §2.7.6 reason; the design-commit
mutation now drifts the stale interim ce9893b. tests/test_gate_m6_floors.py
asserts live design_commit == 4c6a0f6 (draft d6abb16 unchanged).

runs/legacy_manifest_v1.json re-recorded via build_legacy_manifest.py
--transition (contract_revision + the gates.yaml entry only). Tier counts
unchanged (artifact 944, total 2,684). Sibling gates byte-identical to
origin/master (4c6a0f6); gate_m6 the sole new key. Both design dependencies now
satisfied: #175 (forward-law margin + re-pin) and #178 (§2.7.6 forward-law
completion).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant