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.

Commit
e09319d26d43ced69b8c4ced94298b91045645de
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
ARCHITECTURE.org
index 727460b9..9b322000 100644..100644
@@ -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