handoff.md
c133 · The hand-off (Phase 5, 2026-11-12)
For: c134 (the control plane web app: the training page, the record, the library), c131 and qnative c4 (the native app: the notice and the link), Tyler (D22–D27 and the event names). From: c133, Phases 1–5. Status: designed and mocked in the design site's phase-4 lab; nothing is built in the plane or in qcontrol by this cycle. The decisions are D1–D37 in plan.md; the open calls of this phase are in review-5.md.
What is handed over. A training is four things: a trigger (a policy's finding sends the person a training-assignment message), a lesson, a check and a record. The person takes it on the web app, opened from the native app's message; completion writes one audit row, ends the message and shows on the finding, in its history and in Reports as a count. Three pilots are written and published: Unapproved MCP servers (T-MCP-1, on F-A), Personal accounts for work (T-ACCT-1, on F-C), Secrets in prompts (T-SECRETS-1, on F-G). A person can also take any of them on their own from a library.
For c134: the web app
1. The training page
The lab mock is the reference: /pages/phase4/mock/training?findingId=F-A, with ?at=assigned · lesson · check · passed · retake · recorded for each stage.
- What it is: the person's page, not the operator's. It speaks to the person the finding names (R2, R6), takes under three minutes (R1), checks the idea and not the wording (R3), and ends every stage with a path to a person (R5).
- The stages: the assignment as the inbox shows it; the lesson, one step at a time, each with its scene; the check; the result with the explanations and the record shown before it is written (D14); the retake, which keeps what was right and names the step to reread (D3, D15); the record and where it shows.
- What reaches the plane: nothing between the assignment and Finish (D13). The page holds the progress in the browser; the plane learns one thing, the completion.
- Grading: in the web app; the published data marks the right choice (D28).
- The lesson's data: the text arrives with
{slots}and the web app fills them from the finding it answers: the finding, its session, its policy, the policy's configuration (the sanctioned list, the detectors), the exceptions with their reasons as the operator wrote them (D12, D32), the policy's owner.publish.md§Slots lists them and the three that are thin in the product (the sanctioned list for the person's team, the policy's owner, an exception's reason). - The scenes: twelve drawings, one component each, catalogued in
app/data/trainings/scenes.tswith the slots each reads. They port as components; they read the theme's tokens and the finding, so they are true in light and dark and for every finding of their kind. - Where the page lives in the person's reduced frame (signed in through the plane connection) is c134's to rule (plan, clarifying question 5).
2. The trainings, copied in at release (D29)
- The source: the content collection on the design site,
app/data/trainings/<lang>/<slug>.md, one file per training and language (D4, D11); the format isauthoring.md. - The JSON:
GET /api/trainingsandGET /api/trainings/<id>on the design site (server/api/trainings/,server/data/trainings.ts); the shape, the slots, the version and the content hash inpublish.md. - At release the publish step's JSON is copied into the plane's repository; the plane never reads the design site, which sits behind Access (D29). The copy step is not built (open item 3).
- Versions: the integer
versionmoves on a change of meaning (a step, a question, a right answer); thecontentHashmoves with any change, a typo fix included (D26, D31). T-MCP-1 shows the difference: its choices were reordered in Phase 5 (D34), so it stayed v1 with a new hash; Phase 6 added its library words (D38), still v1, and the hash is nowsha256:7f6e1737e972…. - Languages: English for the pilots; a language with no file gets the English one with a line saying so (D30); operator text is shown as written, never translated (D11).
3. The record as audit rows (event-shape.md §8, D22–D27)
| What | Row | Who writes it |
|---|---|---|
| The assignment | message.sent, type training-assignment (c137 bones §9) | the plane, on detection (D2, D18) |
| The completion | training.completed.policy, .operator or .hq (D27); .self for one taken from the library (D40; shown on the person's findings under the policy and answering none, D39; counted in the Compliance report as a strength, D42) | the plane, on the web app's event |
| The message ending | message.done | the app or the plane (open, Tyler) |
- The store is the audit log, one row per completion; the finding reads it by finding id and Reports sum it; a table of its own only if a read needs it (D22).
- The contract is the existing event pattern (qevents': a tagged enum,
typeandpayload, dotted names); the event stays small and the plane's richer record object consumes it (D23). The fields, fromevent-shape.md§2 with §8's amendments:record_id,training_id,training_title(D25),training_versionandcontent_hash(D26),finding_id,policy_id,person,endpoint,result: "passed"orproof_of_understanding: true(Tyler names it),check: { right, total },completed_at,message_id. Noassigned_by: the type carries it (D27). - What is never in it (R4): how long it took, how many tries, which answers were wrong, where it was taken.
- Every completion is a row (D25); Reports count a finding as answered once.
- The association (D24): a completion links to the finding that triggered it, and the plane associates it with the person's earlier findings under the same policy, to show the remediation and to drive what comes next.
- A training taken on its own (Phase 5): the same record with no
finding_id, nomessage_idand the typetraining.completed.self. Whether D24's association reaches it is call 2 inreview-5.md. - The lab's twin of the record:
TrainingRecordandAuditEventinapp/data/trainings/types.ts;recordFor,auditForinindex.ts;selfRecordFor,selfAuditinlibrary.ts.
4. The finding's history row and the Corrections block (D9, D16, D19, D20)
- Today: one row in the finding's history,
training completed → <status unchanged>, Manual, the person as actor, the record as its cause. Completion never changes the finding's status (D9). The lab:mock/finding-detail.vue,GovernancePartFindingHistory'sextraprop,useTrainingMock'shistoryEvents; the event kindtraining-completedingovernance-fixture.ts. - Once Tyler agrees the event shape: the Corrections block (
components/training/Corrections.vue) is shown by default, under History and before Details (D19, D20); the history row stays as the same event. In the lab it is behind?v.corrections=block: the block on F-A. - The strip on the finding (
components/training/FindingStrip.vue) shows what the plane knows and nothing more: Assigned, then Recorded (D13).
5. Corrections in Reports (D10, D17, D18)
- c128's Findings report gains a Corrections section (
reports-c128-specs.ts, idcorrections;components/training/ReportCorrections.vue;app/data/trainings/reports.ts): the section on F-A. - Read as a state as of the data cutoff, like Open now (D17): Assigned (every finding under a policy with a training counts as assigned on detection, D18), Completed, Waiting, and "in this window" beside Completed; per training.
- Counts only (D10): no names.
- The source:
message.sentof typetraining-assignmentandtraining.completed.*. A completion with no finding counts in none. - The window: a window that runs to the reports' last day of data runs to the cutoff, so a record written at the cutoff counts in it (fixed in Phase 5; the lab's two clocks differ).
6. Who may read what (D10)
- The person's name is on the finding (operators who may open it) and in the audit log (its readers).
- Reports show counts. Retention follows the finding's and the audit log's.
- The person sees their own records in the library.
- The open question for Peterson (D10, D33): whether a completed training counts as personnel data in the organisations he knows. If it does, the plane needs a setting for who may read training rows; the rows still exist.
7. Trainings on a topic (D49–D51, Phase 7)
- What: a training can answer a topic in place of a policy: one of the c125 guide's concepts, all 18 as the seed list (D50). It answers no finding and is written for anyone. The first is Observe and Enforce (T-MODES-1,
en/observe-and-enforce.md), with four new scenes. - Who offers it (D51): the library lists it; an operator can send one (a
training-assignmentwhose first sentence is the operator's, recorded astraining.completed.operatornaming the topic); later the c127 guide suggests one when a person asks about the same area twice. - The record: the same record with
topicin place ofpolicy_idand no finding:training.completed.selffrom the library, counted in the Compliance report's "Trainings taken on their own" with the rest (D42, D47). - For c134: the library page lists topics beside policies; the operator's send needs a way to pick a topic and a person (not mocked; c134's by D56).
- For Tyler: T-MODES-1's Enforce step says some policies can only record whatever their mode (a sign-in to a personal account); confirm it with D22–D27 (D55).
8. The library (D2, Phase 5)
The lab mock: /pages/phase4/mock/training-library (app/pages/pages/phase4/mock/training-library.vue, useTrainingLibrary.ts, app/data/trainings/library.ts).
- The list: every published training with its title, minutes, policy, what it covers (the training's own
matterssentence) and the person's record. - Assigned: a finding of theirs under its policy waits for it (D18); Start opens the training on that finding, and the record answers it.
- Recorded: the latest record, for a finding or on their own; Take it again writes another record (D25).
- No record: no finding of theirs asks for it; Take it on your own.
- On its own: a training is written for a finding (R2), so on its own it is filled from the reader and the policy, never from a finding: the reader's machine and team, the policy's name, owner and configuration, a server another team is allowed under an exception, the policy's first detector, and a plain phrase where a slot names the finding's own thing ("a personal account", "your agent"). Since Phase 6 (D38) each file carries its own words for the steps and questions that tell the finding (its
# Librarysection, served aslibrary), so all three read 4 of 4 steps and 4 of 4 questions on their own; a step that still needs a finding would be left out, and the page would say so. - The record has no finding and no assigner; the audit log gets one row,
training.completed.self; no message was sent, so none ends. - Where it lives in the person's frame is c134's (as for the training page).
Files to read (c134)
cycles/c133.remediation-training/: plan.md (D1–D37), event-shape.md (§2, §8), publish.md, authoring.md, review-5.md. The lab: app/data/trainings/ (types.ts, index.ts, slots.ts, parse.ts, hash.ts, scenes.ts, reports.ts, library.ts, the three files in en/), app/composables/useTrainingMock.ts and useTrainingLibrary.ts, app/components/training/, app/pages/pages/phase4/mock/training.vue and training-library.vue, server/api/trainings/. The captures: threads p1-training, p2-record, p3-pipeline, p4-accounts, p4-secrets, p5-reorder and p5-library on the cycle page.
Open for c134
- Tyler on D22–D27 (
event-shape.md§8): the crate, the event names (with.self), retention against report windows, and whether the app or the plane ends the assignment message (bones §5 says the app). - The person's frame (plan, question 5): where the training page and the library live for a person signed in through the plane connection.
- The copy at release (D29): where the JSON lands in the plane's repository, and the step that copies it (a script on the design side is the obvious home; not built).
- D2's field on the policy: the lab derives the policy → training map from each file's
policyId(POLICY_TRAININGS); c121's policy model takes thetrainingfield. - The policy's owner: the lab reads the last person who changed the policy; an
owneron the policy (c121) would be honest. - Peterson: personnel data (D10) and the self-report in the accounts lesson (D33).
For c131 and qnative c4: the native app
The notice
- The message:
training-assignment(c137 bones §2, the type table, and E7): plane to person, attention, no acknowledgement, nothing blocked meanwhile (D9). - Its three sentences (c137 R2): the first two are the training's own (its
# Assignmentsection, filled from the finding); the third is every training's: "Take it when you have a moment; completion goes into the audit log as proof of correction, and nothing is blocked meanwhile." - Its actions: Start training (
start-training, target the training id), then See the finding (open-page, target the finding). - Its links: training, finding, policy.
why: "Policyassigns this training on a finding". expires_at: 90 days.mute_key:policy:<id>. - Built in the lab by
assignmentForinapp/data/trainings/index.ts, rendered by c137'sMessagingCardon the training page's first stage (at=assigned). - In the inbox and tray it is an ordinary attention message: the tray counts it, the OS notification follows the person's per-type control (c137),
seenis not reported because it needs no acknowledgement (bones §5, D13).
The link
start-trainingopens the web app in the browser at the training page for that finding, signed in through the plane connection (c131 D13; bones §4).- The route is c134's; the lab's is
/pages/phase4/mock/training?findingId=<id>. - A training is only ever assigned by a plane, so local mode never shows one.
The ending
- The web app sends the completion; the plane writes
training.completed.*, and the assignment ends asdone(bones §5: the linked action completes). - A quiet
training-completedreply goes to the operator's inbox in the assignment's thread (bones §2, §7). - The native app sees the new state on its next read of the inbox (a poll today, the stream later, gap 26).
- Whether the app or the plane moves the message to
doneis Tyler's (open item 1).
Whose is what
| Piece | Whose |
|---|---|
| The message schema, states, inbox, tray, OS notification, per-type controls | c137 (design), qnative c4 (build) |
TrainingAssignment, TrainingCompleted, StartTraining as types | qnative c4, done (crates/types/src/messages.rs) |
| Start training's handler | qnative c4: today it answers "Trainings arrive with remediation training (c133); not on this build yet." (crates/app/src/messaging.rs:468); next, open the plane's training URL in the browser once c134 names the route and c131 D13 signs it in |
| Opening the web app signed in through the plane connection | c131 D13, with c134 (the credential) |
| The assignment's words, actions and links; the record; the event | c133 |
| The training page, the record's homes, Reports, the library | c134, from c133's mocks |
| An inline training in the native app (D1 "later"; D5 content modules) | not in V1; the same JSON would be the module's payload |
What c133 still owes
- Captures and records: done for Phases 1–5 (the threads above,
CYCLE.md, the story module). - The brief in market-analyst (Phase 0): merged 2026-11-11 as
briefs/enforcement-without-local-context.md(qpoint-io/market-analysis#15); this line was corrected on 2026-11-12. - The calls of this phase: in
review-5.md. - The close: c133 closes once Mark has ruled them and c134 and c131 have taken this hand-off.