← Design · c133

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.ts with 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 is authoring.md.
  • The JSON: GET /api/trainings and GET /api/trainings/<id> on the design site (server/api/trainings/, server/data/trainings.ts); the shape, the slots, the version and the content hash in publish.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 version moves on a change of meaning (a step, a question, a right answer); the contentHash moves 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 now sha256: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)

WhatRowWho writes it
The assignmentmessage.sent, type training-assignment (c137 bones §9)the plane, on detection (D2, D18)
The completiontraining.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 endingmessage.donethe 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, type and payload, dotted names); the event stays small and the plane's richer record object consumes it (D23). The fields, from event-shape.md §2 with §8's amendments: record_id, training_id, training_title (D25), training_version and content_hash (D26), finding_id, policy_id, person, endpoint, result: "passed" or proof_of_understanding: true (Tyler names it), check: { right, total }, completed_at, message_id. No assigned_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, no message_id and the type training.completed.self. Whether D24's association reaches it is call 2 in review-5.md.
  • The lab's twin of the record: TrainingRecord and AuditEvent in app/data/trainings/types.ts; recordFor, auditFor in index.ts; selfRecordFor, selfAudit in library.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's extra prop, useTrainingMock's historyEvents; the event kind training-completed in governance-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, id corrections; 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.sent of type training-assignment and training.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-assignment whose first sentence is the operator's, recorded as training.completed.operator naming the topic); later the c127 guide suggests one when a person asks about the same area twice.
  • The record: the same record with topic in place of policy_id and no finding: training.completed.self from 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 matters sentence) 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 # Library section, served as library), 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

  1. 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).
  2. The person's frame (plan, question 5): where the training page and the library live for a person signed in through the plane connection.
  3. 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).
  4. 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 the training field.
  5. The policy's owner: the lab reads the last person who changed the policy; an owner on the policy (c121) would be honest.
  6. 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 # Assignment section, 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: "Policy assigns this training on a finding". expires_at: 90 days. mute_key: policy:<id>.
  • Built in the lab by assignmentFor in app/data/trainings/index.ts, rendered by c137's MessagingCard on 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), seen is not reported because it needs no acknowledgement (bones §5, D13).
  • start-training opens 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 as done (bones §5: the linked action completes).
  • A quiet training-completed reply 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 done is Tyler's (open item 1).

Whose is what

PieceWhose
The message schema, states, inbox, tray, OS notification, per-type controlsc137 (design), qnative c4 (build)
TrainingAssignment, TrainingCompleted, StartTraining as typesqnative c4, done (crates/types/src/messages.rs)
Start training's handlerqnative 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 connectionc131 D13, with c134 (the credential)
The assignment's words, actions and links; the record; the eventc133
The training page, the record's homes, Reports, the libraryc134, 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.

Qpoint Brand Style Guide