review-3.md
c133 · Phase 3 review note: the authoring pipeline (2026-11-11)
For: Mark. What exists: the trainings as a content collection (D4), one parser that both the lab and the API read through, a preview page in the lab, the publish step at /api/trainings (D21), the scene and asset conventions written down, and a /training skill for the next one. The first pilot moved into the collection without a word changed. Mark's D25–D27 (ruled the same night while this was built) are in: the record names the training's title, version and content hash, and the completion is its own event per assigner. Phase 4 follows without a stop; the calls below wait for the end of Phase 4.
Where to look
| What | Where | Why |
|---|---|---|
| The collection, previewed | /pages/phase4/trainings | per training: policy, minutes, steps, questions, lang, version, the content hash, the voice check run live on every sentence as read on each finding of its policy, the slots it reads, the scenes it draws; links that open it on each of those findings in the mock; files with problems listed as withheld; the scene catalogue |
| The file | content/trainings/en/unapproved-mcp-servers.md | how a training reads to an author: frontmatter for its fields, then # Assignment, # Lesson, # Check |
| The format | authoring.md | the file, the slots, the scenes decision, the voice rule, what the parser refuses |
| What the apps read | publish.md and /api/trainings, /api/trainings/T-MCP-1 | the two routes, the shape (the Training type field for field), the slots an app fills, version and hash (D26), languages (D11) |
| The record, after Phase 3 | training?findingId=F-A&at=recorded | training.completed.policy (D27); T-MCP-1 v1 and the hash's first twelve characters (D26); the Reports line now links the Corrections section |
| The loop | .claude/skills/training/SKILL.md | the policy and a real finding, the file with slots, the kind's slots and scenes, the voice check, preview, capture, record |
The captures are in the cycle's Visual record under the thread "Phase 3 · the authoring pipeline" (scripts/shot-programs/c133-pipeline.json: the collection page, the record).
What was decided, and why
- The format: frontmatter for the fields, the body for the words. The training's fields (id, slug, title, policyId, minutes, version, lang, contact) are frontmatter; the assignment's two sentences, the lesson and the check are markdown sections. A step is a
##heading, a short block ofid:scene:alt:lines, then its paragraphs; a question is a##heading,id:step:explain:, then its choices as a task list with[x]on the right one. Paragraphs read as paragraphs, and YAML's traps (a value that starts with{, a:inside a sentence) stay out of the prose. It maps one-to-one ontoTrainingintypes.ts; the parsed file of the first pilot equals Phase 1's TypeScript field for field. - One parser, two readers.
app/data/trainings/parse.tsreads a file. The lab reads the files at build time (index.ts, so an edit reloads the mock); the API reads the content collectiontrainings(its schema checks the frontmatter) and passes the file's raw text to the same parser. The lab and what the apps read cannot disagree. A file with problems is withheld from both, with the reason on the preview page. - One file per language, in a folder per language (D11):
content/trainings/<lang>/<slug>.md; the parser checks the folder and the name against the frontmatter. - The assignment's words moved into the training. Phase 1 built the message's first two sentences in code, about MCP servers; now each training writes its own (
# Assignment), and the third sentence stays every training's. - Slots per finding kind.
slots.tsholds the common slots and one function per kind of thing a finding touches (mcpServertoday; Phase 4 addsaccountanddataClass), each reading only the world. - Scenes stay components, in a catalogue. One file per scene in
components/training/scenes/, catalogued indata/trainings/scenes.tswith what it shows, who drew it, the slots it reads and whether it moves;Scene.vueframes it and maps the name, and a catalogued scene without a drawing is a type error. A scene reads the finding and the theme, which an asset file cannot. Hand-drawn art has a place (public/trainings/<slug>/, shown by a scene component) but none exists. The only motion is the flow arrow, still under reduced motion. - The voice check runs on the preview page too, with the brand MCP's rules mirrored (
voice.ts), on the file and on every finding of its policy, so a later edit cannot slip a violation in. The MCP tool stays the check of record when a training is written. - D26 as built: the content hash is SHA-256 over the
trainingobject as the API serves it, from one implementation (hash.ts) used by the API and by the lab's record. D25: the record carries the training's title. D27: the lab's audit log mock writestraining.completed.policy(Tyler's to rename).
What was checked
- The parsed first pilot equals Phase 1's
unapproved-mcp-servers.tsfield for field; the old file is gone. - Thirteen rendered pages of the mock compared as text before and after the move (F-A at every stage and step, the finding, the Findings report, F-C's absence): identical.
/api/trainingslists the training with its hash;/api/trainings/T-MCP-1returns it whole; the hash equals the one the lab's record names. The SHA-256 matches Node's on test strings.- The preview page: zero voice violations across 77 texts (the file and F-A, F-B, F-E as read); no failed requests in the browser pane.
What changed on screen: the record card and the audit row (D25–D27), the record page's Reports line (now a link; it said "Phase 2"), and the honest-absence copy (it names planned pilots only while any are planned). Phase 1's captures stay as they were, as history; the record is retaken under the Phase 3 thread.
An incident to own
From about 15:14 to 15:40 every page on the shared dev server answered 500 ("IPC connection closed"). The cause was this phase's preview page: a template literal with }} inside a {{ … }} interpolation, which ends the interpolation early, so the page failed to compile and vite-node's SSR worker took every route down with it (the same failure memory records from c122). Restarts could not help until the file was fixed. I stopped the server that had been running since 12:49, restarted it from the launch config (design), found the file with @vue/compiler-sfc, fixed it, and restarted again; every page answers 200 since. Another session's captures in that window would have failed.
The calls for you
- The right answers ship in the published data. The web app grades the check (D3), so
/api/trainings/<id>marks the right choice. Anyone who reads the API can read the answers; the check is a check of understanding, not an exam (R3). The alternative: the plane grades, the API dropsright, and a grading route returns the result. Proposed: keep it as built. - How the apps reach the API. The design site sits behind Cloudflare Access, so the plane cannot read
design.internal.qpoint.iowithout a service token. Options: the plane syncs from the design site with a service token; the publish step writes the JSON into the plane's repository at release; or the trainings move to a public route. Proposed: the publish step's JSON copied into the plane at release, so a training is versioned with the plane that serves it (D5's content modules would then be signed from there). Not built. - A language with no file.
publish.mdsays the app shows the English file and says so. The alternative is to show nothing until a translation exists. Proposed: English with a line saying so. - When the version moves. A change of meaning (a step, a question, a right answer) bumps
version; a typo fix does not, though the hash moves with any change to the served text. Proposed as written inauthoring.md.
What is not done
Phase 4 (the two other pilots) runs next in this session. The static deploy of /api/trainings (prerendered through the preview page's links) is not checked: nothing was deployed. Tyler's answers on D22–D27 are outstanding. event-shape.md still names training.completed in §2 and §3 and is amended by its §8; the lab now follows §8.