← Sketches · the native app program

The record: one event, three reads

c1332026-11-11

From the policy's detection to one small event in the plane's audit log, and the three places that read it: the finding, Reports, the operator's inbox.

Source: c133 event-shape.md §1–§8 (the flow, the event, the audit row, the projections, the six questions, Mark's assumptions D22–D27 set 2026-11-11, to validate with Tyler); plan.md D8–D10, D13–D14, D16–D27; review-2.md; c137 bones.md §5 (a message ends when its action completes) and §9 (the audit log keeps message.sent and message.done); R4 (the record is proof, not surveillance).

From detection to Finish · the plane learns nothing in between (D13)
▤A policy detects a finding

Its training field names the training (D2). The plane sends the assignment and writes message.sent; that send is the only assignment record.

message.sent · training-assignment
▣The native app's inbox

The person's notice in c137's three sentences; one action, start-training (D8). No acknowledgement, not even seen (bones §5).

start-training
▭The training page, on the web app

Lesson, check, Finish. The person sees what will be written before it is (D14); nothing reaches the plane before that press (D13). Not passed, no record (D3).

Finish: one event, nothing before it
The plane · keeps the record (c137 D1). The event's shape is ours to propose; the object and the store are Tyler's
⬚training.completed.policy · .operator · .hq

One small event in qevents' existing pattern: a tagged enum, type and payload, dotted names (D23). Its own type per assigner, so the audit log reads who asked without opening the message (D27). The proto; the names are Tyler's to adjust.

type training.completed.policy · .operator · .hq (D27)
record_id TR-<ulid>; the plane assigns it, the person sees it on the result page
training_id · title T-MCP-1 · the training's name beside its id (D25)
training_version · content_hash the published revision and a hash of its JSON (D26)
finding_id the finding it answers; the only link the event carries (D24)
policy_id the policy that assigned it (D2)
person · endpoint { id, name }, as the finding names them
proof_of_understanding true, the only value: a training not passed has no record (D3, R3)
check { right, total }: the final result, n of n
completed_at when Finish was pressed, UTC
message_id the training-assignment it answers (D8)
consumed by
⬓The plane's record object

Wraps the event with the links (record, finding, training, message) and the finding's context: the richer object every read uses (D23). One grouped object per completion, in Mark's words: type training · completed · training { id, name } · proof of understanding true (D25).

written as audit rows · every completion a row, never one row per action (D25)
⬓The audit log · the store

One row per event. The finding reads it by finding_id, Reports sum it; a table of its own only if a read needs it (D22). The two message rows are c137's already (bones §9).

wheneventactorsubject
the sendmessage.sentHQ (the policy)training-assignment to the person on the endpoint
completed_attraining.completed.policythe personT-MCP-1 v1 · the finding · proof of understanding, 4 of 4
completed_atmessage.donethe appthe assignment, ended on training-completed
Three reads · the record is stored once; the rest are reads
▭The finding

Today: one history row, training completed, the status unchanged, the person as actor, the record as the cause (D16, D9). Once Tyler agrees the shape: a Corrections block under History, before Details, as the default; the history row stays as the same event (D19, D20).

Corrections block: the record as the audit log keeps it, the three rows, a link to the record page. Default once agreed (D20).
▭Reports · Corrections

c128's Findings report, counts only, as of the data cutoff (D10, D17): assigned, every finding under a policy with a training, from message.sent (D18); completed, from training.completed, with “in this window” beside it; waiting. Per training. No names; a finding counts as answered once (D25).

▣The operator's inbox

training-completed in the assignment's thread, quiet (c137). The thread's root ends with message.done; bones §5 says the app ends it on the action's completion, one of the open items.

The association · D24: the event carries finding_id only; the plane draws the rest. c134's read, and Phase 5's
▤The person's earlier findings under the same policy

Their Corrections block shows the training taken after a later one: the remediation is visible where the repeat was.

associated by the plane
▤The finding that triggered it

finding_id, the one link the event carries. Its history row and its Corrections block read the record directly.

drives
The policy's next step after a repeat

What remediation comes next, read from the association, not from the event. Phase 5, with c134.

ours to build (the assignment and the page, the event's shape, the three reads)the one key thing: the audit log as the store (D22)exists today (the policy, the finding, the person)the plane's, Tyler's side (the record object, the crate)open: what Tyler decidesplanned (the Corrections block as the default, the next step after a repeat)
Not recorded (R4)
How long the person took. How many tries. Which answers were wrong. Where they took it: the browser, the device. The delivery channel of the assignment. The event carries nothing a later reader could turn into a score; the record keeps the final result only (D3).
Version and hash (D26)
A published training carries its integer version and a content hash, a hash of its published JSON. The record names both, so the plane can prove which text was passed; a later edit never rewrites a pass. The publish step computes the hash (Phase 3, D21) and the API serves both.
What stays Tyler's
Which crate declares the event: qevents' public contracts (the generated schemas and TypeScript) or the plane's own API types. The type names; the three above are proposed. Retention of the audit rows against Reports' windows. Whether the plane or the app ends the assignment message on completion; bones §5 says the app. Everything else here is Mark's assumption, D22–D27, for Tyler to confirm or amend.

Qpoint Brand Style Guide