docs Align architecture and steering with the doctrine update

The doctrine-alignment work merged without its documentation step, so the binding steering contradicted the code: domain.md and the doctrine-check skill still listed rest-pause, ocaml.md described three libraries, and ARCHITECTURE.org named the Progression module, eight feedback signals on a five-point score, and a web-layer CSV adapter. Bring each in line with the code as it stands: three quantified HD1 extensions, the Progress module with its Decompensating state, the per-muscle ceiling in the enforcement table, two relative feedback signals, six libraries including hito.import, Present as the one owner of wording, and the seed module beside the composition root.

Commit
5e7db606e5d8a4412aa64501b51334143d9410d2
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
Changed files
.kiro/skills/doctrine-check/SKILL.md
index 237a8166..b9f2a267 100644..100644
@@ -68,7 +68,8 @@
68 68 - **Intensity is categorical, not scalar.** A working set ends at momentary
69 69 muscular failure; that is its definition (HD1, "The Intensity Factor": 0% at
70 70 rest, 100% at failure). Effort past failure is qualitative
71 Removed: (`Beyond_failure`: forced reps, negatives, rest-pause, static hold). Flag any
71 Added: (`Beyond_failure`: forced reps, negatives, static hold — each a count).
72 Added: Rest-pause is not in HD1; flag any attempt to add it. Flag any
72 73 numeric "intensity score".
73 74 - **Recovery produces growth; the gym only stimulates it.** Stimulation is
74 75 immediate; production is not (HD1, "The Economics of Growth and Recovery").
.kiro/steering/domain.md
index d547009a..61c2d200 100644..100644
@@ -16,7 +16,8 @@
16 16 definition (HD1, "The Intensity Factor": the only accurate measures are 0%
17 17 at rest and 100% at failure).
18 18 - Going further is qualitative — `Beyond_failure`: forced reps, negatives,
19 Removed: rest-pause, static hold.
19 Added: static hold. Each is a count (reps, or seconds held). Rest-pause is not in
20 Added: HD1 and is not modeled.
20 21 - Never introduce a numeric "intensity score".
21 22 - **Recovery produces growth; the gym only stimulates it.**
22 23 - Stimulation is immediate; production is not (HD1, "The Economics of Growth
@@ -24,6 +25,8 @@
24 25 - Recovery is systemic and time-based — any hard workout gates the next one.
25 26 - **Volume beyond the minimum is harmful.**
26 27 - One working set per exercise is nominal; three sets per muscle is the ceiling.
28 Added: A prescription that exceeds it is unrepresentable — `Prescription.Workout.make`
29 Added: refuses it.
27 30 - The net negative effect of each extra set increases geometrically.
28 31 - Rising volume is a diagnostic warning — never an achievement or a progress
29 32 target.
@@ -59,6 +62,8 @@
59 62 - A log that refuses to state what happened is worse than one recording an
60 63 error.
61 64 - On a stall, the answer is never more work.
65 Added: - HD1 names two failure states: a *stall* (progress halts, strength holds)
66 Added: and *decompensation* (strength is lost). Both get the same corrective.
62 67 - No remedy may add volume or frequency.
63 68 - Doctrinal remedy (HD1, "Regulating Volume and Frequency"): after two weeks
64 69 of no progress, take a week off, then *reduce* volume and frequency —
.kiro/steering/ocaml.md
index da178e1c..267a2e58 100644..100644
@@ -33,14 +33,32 @@
33 33
34 34 ## Layering
35 35
36 Removed: Three libraries, dependencies pointing inward only:
36 Added: Six libraries, dependencies pointing inward only. The build graph enforces the
37 Added: rule: a library cannot name what it does not link.
37 38
38 Removed: - `hito.core` — pure domain. No framework, database, or serialization
39 Removed: dependencies. Ever.
40 Removed: - `hito.app` — `Repository` port, `Service`, adapters. Knows nothing of Dream.
41 Removed: - `hito.web` — Dream application. The only place Dream and dream-html appear.
39 Added: - `hito.core` — pure domain. No framework, database, serialization, or Lwt
40 Added: dependency. Ever.
41 Added: - `hito.app` — `Repository` and `Password_hash` ports, `Service`, `Catalog`,
42 Added: `Workout_import`. Depends on `hito.core` and `lwt` only. Knows nothing of
43 Added: Dream, Caqti, or any adapter.
44 Added: - `hito.import` — adapters for third-party exports (`Hevy_csv`). Depends on
45 Added: `hito.app` and the parser it needs. No web framework.
46 Added: - `hito.persistence` — adapters that implement the ports: `Sqlite_repo`,
47 Added: `Bcrypt_hash`, `Codec`, `Migrations`. The only place SQL appears.
48 Added: - `hito.presenter` — view models, routes, pages. Depends on Dream and
49 Added: dream-html only. It cannot name a core entity, a service, or an adapter.
50 Added: - `hito.web` — Dream controllers. Maps entities to view models, owns all
51 Added: user-facing phrasing (`Present`), and never reaches an adapter directly.
42 52
53 Added: `bin/` is the composition root: it alone names both an adapter and the use
54 Added: case it plugs in.
55 Added:
43 56 Rules:
44 57
45 Removed: - Ports are module types; adapters implement them.
58 Added: - Ports are module types; adapters implement them; `Service.Make` and
59 Added: `Handlers.Make` are the dependency inversion.
46 60 - Persistence identity is assigned in `hito.app`, not in the core.
61 Added: - Data crossing a boundary is a plain value: a view-model DTO outward, a core
62 Added: type inward. Never an entity into the presenter, never a row into the core.
63 Added: - `pp` functions in the core are debugging output. User-facing wording lives in
64 Added: `hito.web`'s `Present` and nowhere inward.
ARCHITECTURE.org
index 53dba2fa..70ae3011 100644..100644
@@ -39,9 +39,10 @@
39 39 /Consequence./ Reaching failure is an invariant of the recorded type, not a
40 40 field to be filled in. What varies is load, the reps it happened to yield, and
41 41 whether anything carried the effort *past* failure — which is qualitative, not
42 Removed: more of the same: =Beyond_failure= of forced reps, negatives, rest-pause, or a
43 Removed: static hold. There is no numeric intensity score anywhere, and there never
44 Removed: should be.
42 Added: more of the same: =Beyond_failure= of forced reps, negatives, or a static hold,
43 Added: each carrying its count (reps, or seconds held). These three are the whole of
44 Added: HD1's beyond-failure work; rest-pause is later Mentzer and is not modeled. There
45 Added: is no numeric intensity score anywhere, and there never should be.
45 46
46 47 ** The stimulus is the unit of work
47 48
@@ -94,7 +95,7 @@
94 95 Progress is knowable only from what was performed. A log that argues with
95 96 history is useless.
96 97
97 Removed: /Consequence./ =Evidence= records and never interprets. =Progression= is the
98 Added: /Consequence./ =Evidence= records and never interprets. =Progress= is the
98 99 only judge, and it may decline: =Insufficient_data= rather than a guess, so two
99 100 flat sessions a day apart never condemn a routine. Invariants belong on plans,
100 101 which are *authored*, and not on history, which is *observed*.
@@ -108,7 +109,7 @@
108 109 | vocabulary | =Exercise= | what movements exist, and pair |
109 110 | normative | =Prescription= | what should be done |
110 111 | factual | =Evidence= | what was done |
111 Removed: | inferential | =Progression= | what that means |
112 Added: | inferential | =Progress= | what that means |
112 113
113 114 =Recovery= straddles the divide deliberately: it measures a fact (elapsed rest)
114 115 and issues a normative artifact (a clearance). That is the one place the two
@@ -124,13 +125,14 @@
124 125 | one drive to failure per slot | shape of =Prescription.Stimulus= | absence |
125 126 | a recorded effort reached failure | =Evidence.Stimulus.outcome= | type |
126 127 | training requires recovery or acknowledgement | =Evidence.Workout.start= | type |
127 Removed: | a stall is never answered with work | =Progression.remedy= return type | type |
128 Added: | a stall is never answered with work | =Progress.remedy= return type | type |
128 129 | a correction is not extra volume | =Evidence.Workout.replace_stimulus= | type |
129 Removed: | 6–12 calibration window | =Prescription.Rep_range.limits= | runtime |
130 Added: | at most three sets per muscle in a plan | =Prescription.Workout.make= | runtime |
131 Added: | 6–12 authoring envelope for the rep band | =Prescription.Rep_range.limits= | runtime |
130 132 | isolation into compound, sharing a target | =Exercise.may_pre_exhaust= | runtime |
131 133 | substitutions come from author lists | =Exercise.may_substitute= | runtime |
132 134 | 48h between workouts, 72h after the cycle | =Prescription.Routine.recovery_after= | value |
133 Removed: | load rises at twelve reps, by 10–20% | =Progression.judge_load= | value |
135 Added: | load rises at twelve reps, by 10–20% | =Progress.judge_load= | value |
134 136
135 137 The bias is intentional: a principle carried by a type cannot be forgotten, and
136 138 the runtime checks are confined to *authoring* — building a prescription,
@@ -139,27 +141,37 @@
139 141
140 142 * Layers
141 143
142 Removed: Four libraries. Dependencies point inward only.
144 Added: Six libraries. Dependencies point inward only.
143 145
144 146 #+begin_src
145 Removed: hito.web Dream · dream-html · js_of_ocaml client
146 Removed: | HTTP, HTML, sessions, CSRF, wall-clock time
147 Added: hito.web Handlers · Decode · Present
148 Added: | HTTP, sessions, CSRF, wall-clock time, all user-facing wording
147 149 |
148 Removed: | hito.persistence Sqlite_repo · Memory_repo · Codec · Migrations · Bcrypt_hash
149 Removed: | | Caqti, the SQLite driver, serialization, hashing
150 Added: | hito.presenter View_model · Routes · Pages
151 Added: | | Dream · dream-html — renders DTOs, names no domain type
152 Added: | |
153 Added: | hito.import Hevy_csv
154 Added: | | third-party exports into Workout_import; no web framework
155 Added: | |
156 Added: | hito.persistence Sqlite_repo · Codec · Migrations · Bcrypt_hash
157 Added: | | Caqti, the SQLite driver, serialization, hashing
150 158 v v
151 Removed: hito.app Trainee · Repository · Password_hash (ports) · Service · Catalog
159 Added: hito.app Trainee · Repository · Password_hash (ports) · Service · Catalog · Workout_import
152 160 | identity, the ports, recovery policy, Lwt — no database
153 161 v
154 Removed: hito.core Exercise · Recovery · Prescription · Evidence · Progression
162 Added: hito.core Exercise · Recovery · Prescription · Evidence · Progress
155 163 pure domain: no framework, no database, no serialization
156 164 #+end_src
157 165
158 166 The use case (=hito.app=) depends inward on =hito.core= only. It names no
159 167 database library, so the Dependency Rule at the persistence boundary holds by
160 168 construction: the compiler forbids the =Service= from reaching Caqti. The
161 Removed: adapters live one layer out in =hito.persistence=, which depends on =hito.app=
162 Removed: and implements its =Repository= port.
169 Added: adapters live one layer out — =hito.persistence= implements the =Repository=
170 Added: port, =hito.import= turns a vendor export into the import use case — and each
171 Added: depends on =hito.app=, never the reverse. =hito.web= links the app, the import
172 Added: adapter, and the presenter; it never links persistence. =bin/main.ml= is the
173 Added: composition root, the one file that names both an adapter and the use case it
174 Added: plugs into.
163 175
164 176 The core is five modules, unwrapped (=wrapped false=), so they are referred to
165 177 bare: =Prescription.Stimulus=, =Evidence.Workout=, =Evidence.Log=. It has no
@@ -230,30 +242,38 @@
230 242 =Observation= is the canonical insight input: a catalogued exercise, a confirmed
231 243 effort, and a performance time. Native workouts produce observations through
232 244 =Log=. A cleaned import can produce the same type without pretending that it had
233 Removed: a prescription or clearance. =Progression= judges observations and therefore
245 Added: a prescription or clearance. =Progress= judges observations and therefore
234 246 does not know their provenance.
235 247
236 Removed: =Feedback= is a typed vocabulary for reported signals — sleep, appetite,
237 Removed: readiness, motivation, difficulty, pain, injury, insufficient preparation — with
238 Removed: duplicate categories rejected. Leveled signals carry a five-point score, 1 (very
239 Removed: poor) to 5 (very good). These are wellness self-reports, never a measure of set
240 Removed: intensity. The application stores reports independently of workouts, and the web
241 Removed: tier records them through a sequential optional flow. The flow keeps its partial
242 Removed: answers: Back and Skip preserve them, Close cancels the flow, and a report may be
243 Removed: saved as a partial list. The logbook draws a small inline-SVG time graph of the
244 Removed: leveled scores, one line per factor, once two reports carry a factor.
248 Added: =Feedback= is a typed vocabulary for the two self-reports HD1 grounds —
249 Added: motivation (chapter 6, and a named overtraining symptom) and perceived
250 Added: difficulty ("the weights feel much heavier than usual") — with duplicate
251 Added: categories rejected. Each carries a relative reading, not an absolute score:
252 Added: =Worse_than_usual=, =As_usual=, or =Better_than_usual=. Three values resist the
253 Added: false precision of a finer scale, in the spirit of HD1's refusal of spurious
254 Added: measurement gradations, and =Worse_than_usual= is the concerning direction for
255 Added: both. HD1 gauges recovery by time and by effect, so no wider battery of wellness
256 Added: proxies is modeled. The application stores reports independently of workouts,
257 Added: and the web tier records them through a sequential optional flow. The flow keeps
258 Added: its partial answers: Back and Skip preserve them, Close cancels the flow, and a
259 Added: report may be saved as a partial list. The logbook draws a small inline-SVG time
260 Added: graph of the readings, one line per factor, once two reports carry a factor.
245 261
246 Removed: ** Progression — the judgment
262 Added: ** Progress — the judgment
247 263
248 Removed: =assess= reports =Progressing= or =Stalled= from observations, or raises
249 Removed: =Insufficient_data=. =stall_window= is 14 days. =remedy= returns =Some= only for
250 Removed: a stall, and only ever subtracts. =judge_load= reads a single effort against its
251 Removed: prescribed band: below the floor is =Too_heavy=, twelve reps triggers
252 Removed: =Increase=, otherwise =Hold= — the ceiling of the authored band is deliberately
253 Removed: not a trigger, since =load_increase_trigger= is twelve regardless of the range.
254 Removed: =diagnose= reports habits HD1 names as causes of overtraining: recorded volume
255 Removed: beyond the prescription, extensions on every stimulus, and training
256 Removed: under-recovered.
264 Added: =assess= reports =Progressing=, =Stalled=, or =Decompensating= from
265 Added: observations, or raises =Insufficient_data=. =stall_window= is 14 days. Once the
266 Added: window passes without an advance, the latest effort is read against the
267 Added: reference — the peak reached, or the first observation when the record never
268 Added: advanced: strength below it is decompensation (HD1: "lose strength and mass"),
269 Added: strength holding at it is a stall. =remedy= returns =Some= for either, the same
270 Added: corrective HD1 gives both, and only ever subtracts. =judge_load= reads a single
271 Added: effort against its prescribed band: below the floor is =Too_heavy=, twelve reps
272 Added: triggers =Increase=, otherwise =Hold= — the ceiling of the authored band is
273 Added: deliberately not a trigger, since =load_increase_trigger= is twelve regardless
274 Added: of the range. =diagnose= reports habits HD1 names as causes of overtraining:
275 Added: recorded volume beyond the prescription, extensions on every stimulus, and
276 Added: training under-recovered.
257 277
258 278 * Boundaries
259 279
@@ -342,8 +362,10 @@
342 362 ** Workout import
343 363
344 364 Native Hevy CSV import is an application use case, not a core entity. The
345 Removed: =Hevy_csv= web adapter parses the strict 14-column native format and retains the
346 Removed: complete source text. The upload supplies an explicit UTC offset because Hevy
365 Added: =Hevy_csv= adapter in =hito.import= parses the strict 14-column native format
366 Added: and retains the complete source text. It depends on the app and the CSV parser
367 Added: only, so a client other than the web tier can import without linking Dream. The
368 Added: upload supplies an explicit UTC offset because Hevy
347 369 exports local wall time without a timezone.
348 370
349 371 A =Workout_import.batch= owns provisional workouts, ordered warnings, confirmed
@@ -364,7 +386,7 @@
364 386 a batch never deletes promoted evidence. The original CSV remains immutable so
365 387 a later importer can reprocess rows that the current version dropped.
366 388
367 Removed: Promoted observations feed =Progression= with native observations. Provenance
389 Added: Promoted observations feed =Progress= with native observations. Provenance
368 390 does not change judgment. The latest promoted workout also gates recovery with
369 391 the routine's 48-hour training interval when it is newer than native evidence.
370 392 Imports never advance routine rotation, and diagnostics remain native-only
@@ -378,7 +400,7 @@
378 400 WP -->|start, needs clearance| W[Evidence.Workout]
379 401 S[Evidence.Stimulus] --> W
380 402 W -->|finish| L[Evidence.Log]
381 Removed: L -->|dated observations| P[Progression]
403 Added: L -->|dated observations| P[Progress]
382 404 L -->|elapsed| RC[Recovery]
383 405 RC -->|clearance| W
384 406 P -.->|remedy| R
@@ -389,7 +411,7 @@
389 411 interprets.
390 412
391 413 The dashed arrow is the one link nothing
392 Removed: automates. =Progression.remedy= returns a remedy. No code applies it
414 Added: automates. =Progress.remedy= returns a remedy. No code applies it
393 415 to a routine. Acting on it belongs to a tier that does not exist yet,
394 416 and inventing one silently would violate the rule that plans are
395 417 authored.
@@ -524,8 +546,10 @@
524 546 dune build && ./_build/default/bin/main.exe # http://localhost:8080/
525 547 #+end_src
526 548
527 Removed: =bin/main.ml= opens the store, applies migrations, seeds the single
528 Removed: account, and starts Dream. The socket binds *loopback only*.
549 Added: =bin/main.ml= opens the store, applies migrations, runs =Seed.single_user= for
550 Added: the one pre-seeded account, and starts Dream. The seed lives in =bin/seed.ml=,
551 Added: beside the root rather than in a library: it names a concrete adapter and is
552 Added: temporary until public registration returns. The socket binds *loopback only*.
529 553
530 554 | Variable | Purpose | Default |
531 555 |----------------------+---------------------------------------+-------------------------|
@@ -625,7 +649,7 @@
625 649
626 650 * Deferred
627 651
628 Removed: - =Progression.remedy= is computed but never applied to a routine —
652 Added: - =Progress.remedy= is computed but never applied to a routine —
629 653 the dashed arrow above.
630 654 - Equipment granularity, so a suggested load is one a bar can actually
631 655 hold.
@@ -639,13 +663,20 @@
639 663 * Software boundaries
640 664
641 665 The build graph enforces the dependency rule. =hito.core= has no library
642 Removed: dependencies. =hito.app= depends only on =hito.core= and =lwt=. Persistence
643 Removed: adapters depend on the app and core. =hito.web= depends inward on the app and
644 Removed: core, and on =hito.presenter= for rendering.
666 Added: dependencies. =hito.app= depends only on =hito.core= and =lwt=. The persistence
667 Added: and import adapters depend on the app and core, and on their own external
668 Added: library (Caqti, csv) — never on each other or on the web. =hito.web= depends
669 Added: inward on the app, the import adapter, and core, and on =hito.presenter= for
670 Added: rendering. It never links persistence.
645 671
646 672 =hito.presenter= contains routes, view models, and pages. It depends only on
647 673 Dream and dream-html. It cannot name a core entity, a service, or a repository
648 674 adapter. This compiler boundary replaces a source-scanning architecture test.
675 Added:
676 Added: User-facing wording has one owner, =Handlers.Present= in =hito.web=. It matches
677 Added: every core and service error variant itself; the core's =pp= functions are
678 Added: debugging output and are never rendered as copy, so a wording change cannot
679 Added: reach the entity layer.
649 680
650 681 Inside each tier, the large renderer and controller are split into feature
651 682 modules behind a stable façade. =Pages= re-exports the per-feature page modules