Skip to content

feat(planner): progress the dose weekly, habituate a beginner's BASE, and put easy aerobic work on the wall - #126

Merged
kilianmc merged 1 commit into
devfrom
feat/pr2-week-progression-and-base-habituation
Sep 6, 2026
Merged

kilianmc merged 1 commit into
devfrom
feat/pr2-week-progression-and-base-habituation

Conversation

@kilianmc

@kilianmc kilianmc commented Sep 6, 2026

Copy link
Copy Markdown
Owner

What and why

PR 2 of the doctrine-audit tail, at v8.12.0: issues #117 and #118 plus guard 5, ruling 41's authoring, and F11 closed as a declared divergence. Rulings 35–38 and 41 in ct_plan_doctrine.md; rulings 46–48 were taken while building it, and three amendments record what measured differently from the rulings that ordered the work. Server-only — no web/, no migration, no codegen.

piece measured
#117 week-to-week progression dose cells identical across a week-pair 78.9% → 60.6%; 0 of 1192 cells carrying a sourced rule remain identical
#118 + guard 5 BASE occurrences 0 → 70, grade gap 1–2 0 → 28, plans drawing it in BASE 0/60 → 40/60, BASE finger blocks that are max hangs/repeaters 115/115 → 0/115
ruling 41 easy_climbing_flush An Cap weeks with no aerobic work 39.4% → 12.5% (strength 61.1% → 2.8%, power 77.5% → 9.8%)
F11 closed as declared; ruling 38's instruction refused by measurement

Plan digest f95ff03297ba02db → dd29d23ad1962160. Every weekly ceiling holds: hard-energy days >3 outside the taper 0 → 0; An Cap sessions over their phase ceiling 0 → 0. Breadth up, 73.9 → 74.9 distinct exercises per plan, min 52 → 54.

Three findings worth reading before the diff

Ruling 41's row pays into F14 instead of deepening it. general_strength +144 blocks in strength and +120 in power, finger_strength +114 in strength; strength-aspect share of strength minutes 3.87% → 6.14%. A 10-minute wall block fills less of a session than the technique/power blocks it displaces, so the supplementary pass gets more room. This is the opposite of the rejected _try_supplementary override, which cost general_strength 57% — an on-wall row and an off-wall override are not two routes to one outcome.

Ruling 38's instruction was refused by measurement. Seven readings against the whole suite: cap 5 → 14 red, cap 6 → 9 red, cap 8 → co-occurrence red weeks 26 → 128, uncapped → 9 red including ruling 30's out-training invariant, two dense weightings → 8 red each, proportional-to-rank → 5 red. Only the current cap is green. The cause no ruling had: _rotated_pool enters at spread % len(ring), so a 2-session week reads 2 of 16 positions at an offset set by the week number — re-weighting moves presence, not proportion, and presence at 2–3 sessions is what rulings 23/24/30/35 pin. Per ruling 38's own instruction the number was brought back. Closed as a declared divergence (ruling 47); the _spread/_rotated_pool route was deliberately not filed.

PR 2 hollowed one of its own guards, and it stayed green. Ruling 41's row re-based #117's shorter-rest arm from 57 cells to 15, and boulders_on_the_two_minute — 30 of the original 57 — now contributes none, so the arm reads short_rest_boulder_sets alone. Floors re-based 51 → 13 and 448 → 394 with the cause recorded. Recovering the coverage is tied to F26 (ruling 48).

A harness trap, recorded because it nearly cost a wrong conclusion

F11's first measurement round returned all six readings byte-identical — which reads exactly like the five genuinely inert mechanisms already measured in this area. import server.domain.planner.generate as G binds the function re-exported by planner/__init__.py, not the module, so the patch was inert and the harness was measuring nothing. Sabotage the harness and confirm it goes red before trusting a null result here.

CLAUDE.md

  • Section covering this change: Findings inbox (empty, and deliberately left so — both new findings have homes in issuesplan.md and ct_plan_doctrine.md, and duplicating them as prose is what that section exists to prevent)
  • Confirmed still true, unchanged
  • Updated in this PR

Comment budget

  • Baseline allowlist entries cleared: 0
  • BASELINE_RATCHET in tests/test_comment_budget.py lowered to match

Four new [[exception]] rows and one limit raised, each carrying a measured negative rather than a restatement — including why the cheaper _wall_picks-only aliasing fix must not be re-attempted. Two comments went over their caps during review and were trimmed rather than allowlisted, per the guard's own message that an exception is not a licence to keep going.

Checks

  • npm run check green, run once at the end — 1313 passed, ruff and mypy clean
  • Any new guard test was shown to fail before it was trusted

Reds captured with production reverted, then restored and re-confirmed green:

  • traverse_intervals is dosed (5, 40, None, 100) in week 5 and (5, 40, None, 100) in week 6 of the same strength block; its longer_work rule moved nothing.
  • rest_between_sets_seconds fell 100 s -> 90 s. An Cap progresses by harder or longer work and never by less rest.
  • (base, sport, endurance) is a pool of 7 and session 0 draws index {0} in all 3 loading weeks: the week term cancelled out.
  • a 6a beginner at 5x, gap 1 is prescribed ['hangboard_repeaters', 'max_hangs_20mm', 'no_hang_edge_lifts'] in BASE.
  • a 6c 5x plan holds ['hangboard_density_hangs', 'self_resisted_finger_isometrics'] as its whole BASE finger content at gap 1.
  • 22 strength week(s) at 5x a week train anaerobic capacity with no aerobic capacity anywhere in the week
  • MAX_WALL_TURNS is 10, not 4 · the phases whose wall ring is perfectly flat are (), not ('strength', 'taper')
  • easy_climbing_flush is ruling 27's fill at 1200 s and spent 2 sets, not the 1 a 30-minute chunk implies

Prose corrections, none of them catchable by any guard

A comment cited a rest_seconds column CHECK that does not exist (prescription_template checks sets, intensity_pct and target_rpe only — the one-second floor is ours); "these eleven rows" was fourteen; "BASE authors two HOLD rows" — it authors seven, two of them finger; and easy_climbing_flush's user-facing copy said "ten unhurried minutes" while the row is expandable to 1200 s, so it now says "a few" — the same call PR #125 made for lead_endurance_pyramid. Two further false docstrings were corrected by the ranking work, and one test docstring claimed its shape arm "cannot become a hole for a padded authored block" when the rest-free endurance row reached it.

🤖 Generated with Claude Code

… and put easy aerobic work on the wall

PR 2 of the doctrine-audit tail, at v8.12.0. Rulings 35-38 and 41 in
ct_plan_doctrine.md, plus rulings 46-48 taken while building it. Nothing here
was decided in this commit; the amendments record what measured differently.

- #117 (rulings 36, 46): a dose now moves week to week inside a block. The rule
  is keyed on the (aspect_key, protocol_kind) PAIR, never on aspect_key alone —
  `power` holds lactic An Pow AND alactic max-effort work, which the sources
  progress in opposite directions. `power` x `intervals` is the only cell
  holding both, so that cell alone reads work_seconds, split at 15 s, and a
  guard pins the cell to its two rows so a third is a decision, not a pass.
  An Cap scales work AND both rest fields together, so "longer work" and "never
  less rest" hold at once and §7's 2-4x band holds by construction (2.00-3.00x,
  n=1353). Absence from the rule table IS the not-a-volume-multiplier
  invariant, and a guard asserts it. No migration: UniqueConstraint
  (exercise_id, phase) is untouched and week_no reaches the dose seam instead.
  Dose cells identical across a week-pair 78.9% -> 60.6%; 0 of 1192 cells
  carrying a sourced rule remain identical.
  The second, mechanical cause too: `spread % len(pool)` cancelled the week
  term wherever a pool's length matched the stride. All four sites now route
  through one `_pool_index`, `_spread`'s own two terms swapped, no new constant.
- #118 + guard 5 (ruling 35): BASE draws `hangboard_density_hangs`. The
  beginner zero was never the gate — `_fill_finger_strength` returns before it,
  so a beginner's finger work always arrived through the ordinary supplementary
  slot, and 115 of 115 swept BASE finger blocks were max hangs or repeaters.
  So this scales CONTENT, which is what the sources scale: HABITUATION_PROTOCOLS
  / HABITUATION_PHASES filter that one pool. BASE occurrences 0 -> 70, grade gap
  1-2 0 -> 28, plans drawing it in BASE 0/60 -> 40/60. The guard reads PER LEVEL
  because the seven-profile union could never assert one band. Guard 5's strict
  `<` gains its written reason at both ends now that the zero is a decision.
- Ruling 41: `easy_climbing_flush`, the on-wall easy-climbing `endurance` row
  STRENGTH and POWER had no candidate for, at §7's 600 s floor exactly. An Cap
  weeks carrying no aerobic work 39.4% -> 12.5% (strength 61.1% -> 2.8%, power
  77.5% -> 9.8%), POWER_ENDURANCE out of scope by ruling 24. It PAYS INTO F14
  rather than deepening it: general_strength +144 blocks in strength, +120 in
  power, strength share 3.87% -> 6.14% — the opposite of the rejected
  `_try_supplementary` override, which cost general_strength 57%.
- F11 (rulings 38, 47): closed as a DECLARED divergence. Ruling 38's
  instruction was refused by measurement — seven readings against the whole
  suite and only the current cap is green; uncapped turns 9 guards red,
  including ruling 30's out-training invariant. `_rotated_pool` enters at
  `spread % len(ring)`, so a 2-session week reads 2 of 16 positions at an
  offset set by the week number: re-weighting moves presence, not proportion.
  Delivered instead: one shared `aspect_rank()` across both surfaces, three
  guards making the flattening a decision, three false docstrings corrected.

Prose corrections, none of them catchable by any guard: a comment cited a
`rest_seconds` column CHECK that does not exist (the floor is ours; the table
checks sets, intensity_pct and target_rpe only); "eleven rows" was fourteen;
"BASE authors two HOLD rows" — it authors seven, two of them finger; and
`easy_climbing_flush`'s copy said "ten unhurried minutes" while the row is
expandable to 1200 s, so it says "a few" instead.

Two things a reviewer should know. Ruling 41's row re-based #117's shorter-rest
arm from 57 cells to 15 and `boulders_on_the_two_minute` now contributes none,
so that arm reads one exercise and stayed green throughout — recovering it is
tied to F26. And F11's first measurement round read byte-identical across all
six readings because `import server.domain.planner.generate as G` binds the
function re-exported by `planner/__init__.py`, not the module: the harness was
measuring nothing. Sabotage and confirm red before trusting a null result.

Every guard added or touched was shown red first; the red is in the PR body.

Co-authored-by: Kilian Mateo <13885240+kilianmc@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 6, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
climb-trainer Ready Ready Preview Sep 6, 2026 3:59pm UTC

This branch was successfully deployed

1 active deployment
Preview — 4227846a Deployed Sep 6, 2026 by vercel[bot]
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