[OCaml] High Intensity Training Online
docs Correct architecture doc and apply STE100
Update ARCHITECTURE.org against the current implementation: Service and Handlers are functors over Repository.S and Password_hash.S; name the Password_hash port and the Bcrypt_hash adapter in the layer diagram; add View_model to the web tier; and record the test_architecture and test_bcrypt_hash suites. Apply Simplified Technical English to the prose: split every joined clause that used a semicolon into two sentences (semicolons remain only inside code spans), and replace the one phrasal verb. No contractions or Latin abbreviations were present.
ARCHITECTURE.org
@@ -28,7 +28,7 @@
28
28
29
29
* First principles
30
30
31
Removed:
Six commitments. Each is a claim from HD1; each has a consequence the code is
31
Added:
Six commitments. Each is a claim from HD1. Each has a consequence the code is
32
32
built to satisfy.
33
33
34
34
** Intensity is categorical, not scalar
@@ -50,14 +50,14 @@
50
50
no pause.
51
51
52
52
/Consequence./ There is no set count in the model, on either side of the
53
Removed:
plan/record divide. Volume is not a number a caller supplies; it is the length
53
Added:
plan/record divide. Volume is not a number a caller supplies. It is the length
54
54
of a list. A prescription therefore cannot ask for more work than one drive per
55
55
slot, because there is no field in which to say so. Delivery is =Single= or
56
56
=Pre_exhaust=, and nothing else.
57
57
58
58
** Recovery precedes growth
59
59
60
Removed:
Training stimulates growth; recovery produces it. Training before reserves are
60
Added:
Training stimulates growth. Recovery produces it. Training before reserves are
61
61
replenished is HD1's primary error, and its effect is systemic rather than
62
62
local.
63
63
@@ -65,7 +65,7 @@
65
65
=Recovery.clearance=. It cannot be constructed by a caller who feels ready. It
66
66
is either earned — =clear= returns =Some= only when recovery is complete — or
67
67
taken deliberately through =override=, which records how long you had actually
68
Removed:
rested against what was recommended. The override is not an escape hatch; it is
68
Added:
rested against what was recommended. The override is not an escape hatch. It is
69
69
evidence, and it is what allows a later stall to be attributed to training
70
70
under-recovered.
71
71
@@ -89,7 +89,7 @@
89
89
extra rest. There is no additive remedy to choose by mistake, because none
90
90
exists to name. Rising volume is a diagnostic warning, never an achievement.
91
91
92
Removed:
** The record states facts; one module judges them
92
Added:
** The record states facts, and one module judges them
93
93
94
94
Progress is knowable only from what was performed. A log that argues with
95
95
history is useless.
@@ -97,7 +97,7 @@
97
97
/Consequence./ =Evidence= records and never interprets. =Progression= is the
98
98
only judge, and it may decline: =Insufficient_data= rather than a guess, so two
99
99
flat sessions a day apart never condemn a routine. Invariants belong on plans,
100
Removed:
which are *authored*; not on history, which is *observed*.
100
Added:
which are *authored*, and not on history, which is *observed*.
101
101
102
102
* What the principles force on the structure
103
103
@@ -116,7 +116,7 @@
116
116
and nowhere else.
117
117
118
118
There is no =Principle= module. A module cohering around "things that are true"
119
Removed:
is a constants bag; a principle stated in one place is a principle that can be
119
Added:
is a constants bag. A principle stated in one place is a principle that can be
120
120
ignored at every call site. Each is instead enforced where it bites:
121
121
122
122
| Principle | Enforced at | Mechanism |
@@ -145,11 +145,11 @@
145
145
hito.web Dream · dream-html · js_of_ocaml client
146
146
| HTTP, HTML, sessions, CSRF, wall-clock time
147
147
|
148
Removed:
| hito.persistence Sqlite_repo · Memory_repo · Codec · Migrations
149
Removed:
| | Caqti, the SQLite driver, stored-fact serialization
148
Added:
| hito.persistence Sqlite_repo · Memory_repo · Codec · Migrations · Bcrypt_hash
149
Added:
| | Caqti, the SQLite driver, serialization, hashing
150
150
v v
151
Removed:
hito.app Trainee · Repository (port) · Service · Catalog
152
Removed:
| identity, the port, recovery policy, Lwt — no database
151
Added:
hito.app Trainee · Repository · Password_hash (ports) · Service · Catalog
152
Added:
| identity, the ports, recovery policy, Lwt — no database
153
153
v
154
154
hito.core Exercise · Recovery · Prescription · Evidence · Progression
155
155
pure domain: no framework, no database, no serialization
@@ -171,7 +171,7 @@
171
171
** Exercise — the vocabulary
172
172
173
173
A curated catalog, not an open string space. An entry is opaque and obtainable
174
Removed:
only by lookup, keyed by /equipment × movement × variation/; a stable =private
174
Added:
only by lookup, keyed by /equipment × movement × variation/. A stable =private
175
175
string= id exists solely for external boundaries.
176
176
177
177
Two relations matter, and both are author-defined rather than inferred:
@@ -230,7 +230,7 @@
230
230
=Feedback= is a typed vocabulary for reported signals — sleep, appetite,
231
231
readiness, motivation, difficulty, pain, injury, insufficient preparation — with
232
232
duplicate categories rejected. Leveled signals carry a five-point score, 1 (very
233
Removed:
poor) to 5 (very good); these are wellness self-reports, never a measure of set
233
Added:
poor) to 5 (very good). These are wellness self-reports, never a measure of set
234
234
intensity. The application stores reports independently of workouts, and the web
235
235
tier records them through a sequential optional flow. The flow keeps its partial
236
236
answers: Back and Skip preserve them, Close cancels the flow, and a report may be
@@ -279,7 +279,7 @@
279
279
Adapters: =Memory_repo= for tests, =Sqlite_repo= over Caqti for production.
280
280
=Migrations= is a versioned ledger — each migration numbered and named, applied
281
281
inside a transaction, recorded in =schema_migrations= so a reconnect applies
282
Removed:
only what is missing; statements still use =IF NOT EXISTS= so a file from an
282
Added:
only what is missing. Statements still use =IF NOT EXISTS= so a file from an
283
283
earlier, ledger-less build stays safe. It also creates the =dream_session= table
284
284
Dream's SQL sessions expect.
285
285
@@ -301,11 +301,11 @@
301
301
302
302
** Policy
303
303
304
Removed:
=Service= is a functor over =Repository.S= and is the API a client calls — no
305
Removed:
HTML, no serialization. It is the only module that decides how a clearance is
306
Removed:
obtained: earned, or taken through =begin_workout='s =?override=
307
Removed:
acknowledgment. A native client added later therefore cannot quietly adopt
308
Removed:
looser rules than the web one. It holds no mutable state of its own.
304
Added:
=Service= is a functor over =Repository.S= and =Password_hash.S=. It is the API
305
Added:
a client calls — no HTML, no serialization. It is the only module that decides
306
Added:
how a clearance is obtained: earned, or taken through =begin_workout='s
307
Added:
=?override= acknowledgment. A native client added later therefore cannot quietly
308
Added:
adopt looser rules than the web one. It holds no mutable state of its own.
309
309
310
310
** Accounts and sessions
311
311
@@ -319,7 +319,7 @@
319
319
320
320
Passwords carry no length or content policy, so they never refuse registration.
321
321
Public sign-up is closed by default: =Handlers.make ?registration_open=
322
Removed:
defaults to =false=, which removes the =/register= routes and hides the link;
322
Added:
defaults to =false=, which removes the =/register= routes and hides the link.
323
323
=bin/main.ml= seeds one account idempotently from =HITO_SEED_PASSWORD=.
324
324
325
325
A session holds the trainee id in a signed cookie. Every application route
@@ -352,17 +352,17 @@
352
352
interprets.
353
353
354
354
The dashed arrow is the one link nothing
355
Removed:
automates. =Progression.remedy= returns a remedy; no code applies it
355
Added:
automates. =Progression.remedy= returns a remedy. No code applies it
356
356
to a routine. Acting on it belongs to a tier that does not exist yet,
357
357
and inventing one silently would violate the rule that plans are
358
358
authored.
359
359
360
360
* The web tier
361
361
362
Removed:
=Routes=, =Decode=, =Pages=, =Handlers=, and a small =js_of_ocaml=
363
Removed:
client. Dream and dream-html appear nowhere else. =Handlers= is a
364
Removed:
functor over =Repository.S=, so production runs on SQLite while tests
365
Removed:
run in memory.
362
Added:
=Routes=, =Decode=, =View_model=, =Pages=, =Handlers=, and a small =js_of_ocaml=
363
Added:
client. Dream and dream-html appear nowhere else. =Handlers= is a functor over
364
Added:
=Repository.S= and =Password_hash.S=, so production runs on SQLite with Bcrypt
365
Added:
while tests run in memory.
366
366
367
367
Every domain and service error becomes a user-facing sentence in one
368
368
=Present= module, so a handler renders a message rather than inventing
@@ -432,7 +432,7 @@
432
432
433
433
| Marker | Meaning |
434
434
|----------------------------+-----------------------------------------------|
435
Removed:
| =data-hito-app-shell= | the replaceable shell; carries the page title |
435
Added:
| =data-hito-app-shell= | the replaceable shell, carrying the page title |
436
436
| =data-hito-app-content= | the page surface inside it |
437
437
| =data-hito-app-link= | a link eligible for in-place navigation |
438
438
| =data-hito-app-form= | a form eligible for in-place submission |
@@ -444,7 +444,7 @@
444
444
Behaviour: the client fetches the same URL the link or form would have
445
445
used, extracts the marked region from the response, and swaps
446
446
it. Successful transitions push the final response URL, so redirects
447
Removed:
stay reflected in the address bar; server-rendered validation
447
Added:
stay reflected in the address bar. Server-rendered validation
448
448
responses render in place *without* a history entry. A monotonic
449
449
request counter makes the newest interaction win, and =aria-busy=
450
450
marks the shell while one is pending. Recording a stimulus returns to
@@ -462,7 +462,7 @@
462
462
logbook, the profile, or the app-feedback tab — so a refresh never
463
463
re-posts. The redirect target carries any message two ways: a transient
464
464
success or error note rides a one-shot session flash (=hito.flash=),
465
Removed:
read once and shown as a =data-hito-toast=; a confirmation that must
465
Added:
read once and shown as a =data-hito-toast=. A confirmation that must
466
466
survive a bookmarked reload rides a query flag such as
467
467
=/profile?changed=username=. Field-level validation is the one
468
468
exception: a rejected login, registration, or profile edit re-renders
@@ -486,22 +486,28 @@
486
486
| =HITO_PORT= | TCP port | =8080= |
487
487
| =HITO_SEED_PASSWORD= | Password for the seeded account | none — refuses to start |
488
488
489
Removed:
Set =HITO_SECRET= in production; unset, sessions do not survive a
489
Added:
Set =HITO_SECRET= in production. When it is unset, sessions do not survive a
490
490
restart.
491
491
492
492
Dream runs its own SQL pool for sessions against the same file as
493
493
=Sqlite_repo=. SQLite serializes writers, so this is safe for local
494
Removed:
use; a separate session store is the path to higher concurrency.
494
Added:
use. A separate session store is the path to higher concurrency.
495
495
496
496
* Verification
497
497
498
498
One Alcotest suite per module, =test/test_<module>.ml=, each exposing
499
Removed:
=suite=, registered in =test/test_hito.ml= in layer order: vocabulary,
500
Removed:
plan, record, judgment, service, serialization, storage, decoding,
501
Removed:
web.
499
Added:
=suite=, registered in =test/test_hito.ml=. The architecture guard runs first,
500
Added:
then the suites in layer order: vocabulary, plan, record, judgment, service,
501
Added:
serialization, storage, decoding, web.
502
502
503
503
What the less obvious suites are for:
504
504
505
Added:
- =test_architecture= — the Clean Architecture guard. It reads the source tree
506
Added:
and fails the build if a dependency points outward, or if the presenter names
507
Added:
a core entity. The Dependency Rule is a test, not a convention.
508
Added:
- =test_bcrypt_hash= — the hashing adapter satisfies the =Password_hash.S=
509
Added:
port: a hash verifies against itself, a wrong password fails, and a malformed
510
Added:
stored hash is rejected rather than raised.
505
511
- =test_codec= — a stored workout round-trips faithfully, and
506
512
malformed or incomplete text is *reported*, not raised.
507
513
- =test_sqlite_repo= — state survives a reconnect, trainees stay
@@ -525,7 +531,7 @@
525
531
Recorded rather than resolved, because HD1 does not settle them.
526
532
527
533
*48h against 72h.* Chapter 3 says up to 72 hours of rest, sometimes
528
Removed:
more, is needed for growth; the Ideal Routine prescribes training
534
Added:
more, is needed for growth. The Ideal Routine prescribes training
529
535
every other day. Mentzer does not reconcile these. We encode the
530
536
routine — 48h within a cycle, 72h after it — and note the discrepancy
531
537
here.
@@ -541,11 +547,11 @@
541
547
*Extra volume is recordable.* HD1 forbids it in the strongest terms,
542
548
but a log that refuses to state what happened is worse than one
543
549
recording an error. The constraint lives on the prescription side,
544
Removed:
which cannot ask for extra work; performing extra shows up as more
550
Added:
which cannot ask for extra work. Performing extra appears as more
545
551
stimuli than slots.
546
552
547
553
*Extension rarity is a diagnostic, not an invariant.* Same reason. HD1
548
Removed:
says never to extend every exercise of a workout; if you did, the
554
Added:
says never to extend every exercise of a workout. If you did, the
549
555
record must say so.
550
556
551
557
*Deadlifts substitute for hyperextensions* across mechanics, because
@@ -553,7 +559,7 @@
553
559
other a compound.
554
560
555
561
*The log form offers one extension, not a stack.* =Evidence.Stimulus=
556
Removed:
permits forced reps /then/ negatives, as HD1 describes; the web form
562
Added:
permits forced reps /then/ negatives, as HD1 describes. The web form
557
563
currently offers a single choice. A limitation of the form, not the
558
564
model.
559
565