Exhaustive walks

A journey proves one path works. The walks prove there is no path that breaks. They drive the Advocate the way people do, from every state they can reach, until nothing new is left.

The algorithm

flowchart TD
  seed[Seed: a student, at a depth] --> sig0[Signature of the starting state] --> q[(Queue)]
  q --> pop{Anything left?}
  pop -- no --> done([Fixpoint: every reachable state visited])
  pop -- yes --> node[Take a state]
  node --> acts[Every button on screen + navigation + things people type]
  acts --> fork[Fork the world]
  fork --> perform[Do the action]
  perform -- throws --> v1[Violation: crashed, with the exact path]
  perform --> check{Invariants}
  check -- fail --> v2[Violation, with the exact path]
  check --> sig{Seen this state before?}
  sig -- yes --> pop
  sig -- no --> q

Two walks

Walk Drives Actions from each state Checked at every step
Button walk (tools/explore.ts) The Advocate directly Every button on the last message, app navigation, help tiles, 16 things students type (including "1", "2", "3") No dead end; no crash; tone gate passes at the student's depth and grade; no doubled periods or template holes; no citizenship question; no sealed fact; no other student's name; idempotent buttons are idempotent
Feature walk (tools/crawl.ts) The web app over HTTP Every link and every form on the page Status 200; one title and one h1; a main landmark; every control labeled; every link and button named; no "undefined", "NaN" or template holes; no other student's data; no dead-end page

Each runs for all twelve students, at their own depth and again at brief and walk-me-through, every night. Only that full walk can certify a phase. On a push, the walk carries forward when nothing it executed has changed (below); otherwise a spot check walks four students whose settings differ most, bounded at 150 states per seed, and fails the build on any violation. Each student at each depth is an independent walk, so the full set runs one per CPU: more cores shorten the night, while the total compute stays the same.

Why it ends

The exact state of a student multiplies independent features together (every combination of drafts, reminders and held time) and never reaches a fixpoint. The walk uses an abstract state: each deadline's status, each playbook's step, whether a draft is waiting, whether a question is pending, which sources are on, and the buttons on screen. Settings are independent switches, so they are set by the seeds and walked on their own instead of being multiplied into every path.

flowchart LR
  exact[Exact state<br/>ids, timestamps, counts] -->|abstraction| abstract[Abstract state<br/>what changes behavior]
  abstract --> finite[Finite: the walk ends]
  settings[Settings switches] -->|seed variants + own walk| finite

Walk only when the walk could see the change

A walk runs under coverage, which records every source line it executed. On a later commit the change is diffed against the walked commit, and a walk is needed only if a hunk touches or borders an executed line, or changes data the app reads while it runs (playbooks, colleges, scouts, dependency versions). A change inside a function the walk never ran, such as an API handler the page walk never calls, cannot alter what the walk saw, so the earlier report is carried forward for the new commit and says so. The nightly run walks every student regardless and saves the coverage that pushes are compared against.

flowchart TB
  push([A push]) --> last[(Last full walk and the lines it executed)]
  last --> diff{Diff since the walked commit}
  diff -- touches an executed line --> walk[Walk under coverage]
  diff -- changes data the app reads --> walk
  diff -- only lines the walk never ran --> carry[Carry the report forward, naming the walked commit]
  walk --> rec[Record executed lines with the report]
  carry --> cert[Certificate for this commit]
  rec --> cert
  nightly([Nightly]) --> full[Walk all twelve students under coverage] --> save[(Saved as the last full walk)]

The one sanctioned skip is marked in the pipeline script, and the `zero-bypass` gate reads the scripts the required steps run, so any other way of skipping a walk fails the build.

What the walks found and fixed

Every finding is a fixture now; it cannot come back.

Finding Walk Fix
About twenty buttons went nowhere button One action registry for app, text and API; no-dead-ends gate
Replying "1" by text did nothing button Numbered replies run the last message's choices
Crisis plans too long for brief answers button Brief plans list names and numbers only
Catalog steps above an eighth-grade reading level button Catalog rewritten; web addresses no longer count as words
"1:30 p.m.." doubled periods feature, then tone gate sentence() helper; the tone gate rejects doubled periods
Re-uploading a syllabus duplicated every date feature Same title, date and course is the same deadline
Pages without a heading feature Every page has exactly one h1
Holding the same time twice made two holds button Holds, reminders and drafts are idempotent

Verified by