← Design · c133

authoring.md

c133 · Authoring a training (Phase 3, 2026-11-11)

For: whoever writes the next training (the /training skill carries the loop; this is the format). Rulings it follows: D4 (the design site is the studio), D7 (illustrations and short animations, no video), D11 (English for the pilots, one file per language), D12 (the operator's words as written), R1–R6 (plan.md).

Where a training lives

One markdown file per training and language: app/data/trainings/<lang>/<slug>.md. The files sit beside the model and the parser, not under content/: Nuxt Content hides content/ from Vite's watcher, so a file there never reached the lab's preview until a restart (found in Phase 4; the collection reads this folder through its cwd source, */*.md). The folder is the language, the file name is the slug; the parser checks both against the frontmatter, so a translation can never shadow another training. A translation keeps the id and the version of the file it translates. Operator text that a slot fills (an exception's reason, a policy's name) is shown as written, never translated by us (D11).

Two readers, one parser (app/data/trainings/parse.ts): the lab reads the files at build time (app/data/trainings/index.ts, so an edit reloads the mock), and the API reads the content collection trainings (content.config.ts) and serves what the apps read (publish.md). A file with problems is withheld from both and listed with its problems on the preview page, /pages/phase4/trainings.

The file

---
id: T-MCP-1
slug: unapproved-mcp-servers
title: Unapproved MCP servers
policyId: P-MCP
minutes: 3
version: 1
lang: en
contact:
  owner: "{owner}"
  how: "Contact IT from the message that brought you here, or reply to it; {owner} reads the replies for this policy."
---

<!-- the author's notes: which finding it was written for, what was checked -->

# Assignment

happened: On {date}, {agent} on {device} connected to the {server} server, which is not sanctioned for {team}; {blocked}.
matters: A {minutesWord} training shows what an MCP server can do on your behalf and how to get one sanctioned when your work needs it.

# Lesson

## What happened on {device}

id: what-happened
scene: connection
animated: yes
alt: {agent} on {device} connects to the {server} server, which sits outside the sanctioned list for {team}.

On {date} at {time}, {agent} on {device} connected to …

# Check

## {server} is sanctioned for {exceptionTeam}. Is it sanctioned for {team}?

id: q-per-team
step: why-the-list
explain: Sanctioning follows a review for one team's data; another team asks for its own.

- [ ] Yes, a sanctioned server is sanctioned for everyone
- [x] No, servers are sanctioned per team after a review of what they reach
- [ ] Yes, once anyone on {exceptionTeam} has used it

# Library

## What happens when an agent connects a server

id: what-happened
alt: {agent} on {device} connects to the {server} server, which sits outside the sanctioned list for {team}.

An agent can connect an MCP server in the middle of a session …
PartMaps onto (types.ts)Rule
idTraining.idT-<HAZARD>-<n>, as c137's E7 names it
slugslugkebab-case; the file's name
titletitlethe person's words, 40 characters at most
policyIdpolicyIdthe policy that assigns it (D2); one training per policy
topic.id, topic.label (Phase 7, D49)topicin place of policyId for a training on a topic, one of the c125 guide's concepts (D50): a kebab-case id and the person's words, 40 characters at most. A training has a policy or a topic, never both. A topic training answers no finding: its # Assignment needs only matters:, it is written for anyone with only the reader's slots (device, team, agent), it needs no # Library, and its contact owner is IT. The first is en/observe-and-enforce.md (T-MODES-1)
minutesminutesone to three (R1)
versionversiona whole number; bump it when the meaning of a step, a question or a right answer changes (a record names the version it was passed against); a typo fix does not bump
langlangtwo letters; the folder's name (D11)
contact.owner, contact.howcontactthe path to a person (R5)
# Assignment · happened: matters:assignmentthe message's first two sentences (c137 R2); the third, what to do next, is every training's
# Lesson · ## <heading>lesson[]three to five steps; the first names what happened on this machine, one says what to do instead (instead: yes, R2)
the step's block: id: scene: alt: animated: instead:LessonStep.id, illustrationevery step has an id, a scene and an alt sentence (the caption and what a screen reader hears)
the step's paragraphsbody[]one to three short paragraphs; a soft-wrapped line joins its paragraph
# Check · ## <question>check[]three to five questions on the idea, not the sentences (R3)
the question's block: id: step: explain:Question.id, stepId, explainstep: names the lesson step it verifies (a retake points back there, D15); explain: is shown only once the check is passed
the choices, - [ ] and - [x]choices[]two to four, exactly one [x] (D3); their ids are a, b, c, d by position
# Library (optional; Phase 6, D38) · ## <heading>library.lesson[], library.check[]words for a reading with no finding (a training taken on its own from the library). Each entry names by id: the step or question it replaces and is written in that one's grammar: a step's heading, alt: and one to three paragraphs, keeping its scene, motion and instead unless it says scene: or animated:; a question's prompt, explain: and choices, keeping the step it verifies unless it says step:. Write one for every step or question that tells the finding (what happened on this machine, advice that depends on whether the thing was blocked); the rest read as written. Use only the slots the library fills (library.ts librarySlots: the reader, the policy and its configuration). The preview page says how a training reads on its own and what it still leaves out

Quote a frontmatter value that starts with { or holds : (owner: "{owner}"). HTML comments are the author's notes and never reach the person. The body's lines are plain text: no emphasis, no links, no lists in a step.

Slots

A training is written once and filled from the finding (plan §Patterns). {name} stays as written in the file and in the API; the lab fills it from the world in app/data/trainings/slots.ts; an app fills it from its own finding (publish.md §Slots). A slot the finding's kind does not provide stays visible as {name}, and the preview page names it.

SlotsRead fromFor
date time agent device team person session evidence policy owner resourcethe finding, its session, its policy, the policy's last human changeevery training
minutesWordthe training's minutes ("three-minute")every training
server sanctioned exceptionTeam exceptionSentence outcome blockedthe MCP-server finding, the policy's sanctioned list (c121 configuration), the team exception that allows the server elsewhereMCP servers (T-MCP-1)

A new hazard adds its kind's slots to KIND_SLOTS in slots.ts (keyed by the finding's resource.kind) and its words in a file. Phase 4 adds the kinds account and dataClass; their slots are listed in the review notes and in slots.ts.

When a policy's findings share a kind with another policy's but the kind's slots would say the wrong thing, the policy keys its own in POLICY_SLOTS, which wins over the kind. The first (2026-11-12, T-INJECT-1 for c144 S1) is P-PROMPT-INSPECT, whose finding names the MCP server the content came through: via, heuristics, heuristicsOffSentence, outcome, blocked. The library fills heuristics and heuristicsOffSentence from the policy's configuration.

Four more followed on 2026-11-13, for the trainings c144's wave 1 wanted:

  • P-PROVIDERS: provider, approved, providerGot, outcome, blocked (its kind, provider, is shared with P-SPEND's spend).
  • P-PROVENANCE: component, pinned, pinnedSentence, outcome, blocked.
  • P-TOOL-APPROVAL: tool, outcome, blocked.
  • P-SPEND: ceiling, ceilings, outcome, blocked.

The slots a policy's configuration alone fills (the approved list, the pinned list, the ceilings, the heuristics) are POLICY_CONFIG_SLOTS. The library reads that map for any policy in POLICY_SLOTS, so a new policy with no finding to fill from adds one entry there. A scene that names the finding's own thing (the provider, the skill, the tool) names it in general when the slot is missing, and leaves that slot out of its reads, so the library keeps the step.

A training's contact, {owner}, is the last person to change the policy. Change actors that are not people are skipped: qdash, clock, and c144's Theatre.

Scenes

Decision (Phase 3): a scene stays a component, one file per scene, in a catalogue. Each drawing is app/components/training/scenes/<Name>.vue, catalogued in app/data/trainings/scenes.ts under a kebab-case name that says what the picture shows (sanctioned-list, not step-3), with the training that drew it (from), the slots it reads and whether it moves. Scene.vue frames it (the sunken card and the caption) and maps the name to the drawing; a catalogued scene without a drawing is a type error, and a file that names a scene the catalogue lacks is drawn as a dashed placeholder and flagged on the preview page.

Why a component and not an asset file: the scenes read the finding (the server's name, the team, the owner, the sanctioned list, the account) and the theme's tokens, so the same drawing is true for every finding of its kind and in light and dark. An exported SVG or an animation file can do neither.

  • Inspiration: Rob Genova's informational drawings in The Hidden Agent Surface and Your Agents Need a Dynamic Sandbox (named by Mark, 2026-11-12). Each makes one idea per drawing: flat, labelled, the pixel agent pawn, an attempt as an arrow ending in what happened. Look at them before drawing a new scene. All twelve current scenes are drawn together at the bottom of the preview page.
  • The cast: the layer's pixel pawns (AgentPawn, EndpointPawn, and the rest; art through <img>, never UxIcon) and the sketch kit (SketchBox, its kinds and tones). New art per lesson is the exception, not the rule (D7, plan §Patterns).
  • Motion: one kind, the flow along TrainingFlowArrow (dashes moving along the line), switched on per step with animated: yes. It stops under prefers-reduced-motion. A new motion goes behind the same media query, inside the scene's own scoped style, or it does not ship.
  • Shared scenes: a scene any training can use says from: 'shared'; reuse one before drawing another.
  • Hand-drawn art (Mark's drawings may replace a sketch, CLAUDE.md c130): the file goes in public/trainings/<slug>/<scene>.svg and a scene component shows it as an <img> with the step's alt, so the catalogue stays the one index of scenes. None exists yet.

Voice

Every sentence goes through the brand MCP's check-brand-voice (context productUI) with zero violations before the file is committed: the file as written and filled for at least one real finding. The preview page reruns the same rules (app/data/trainings/voice.ts, mirrored from mcp/src/tools.ts) on the file and on every finding of its policy, so a later edit cannot slip a violation in unseen. The register: a safety briefing, not a reprimand (R6); the brand's essential guide (brand/qpoint_brand_essential_guide_v1.5.md): plain, precise, no hype, no em dashes.

What the parser refuses

A file is withheld, with the reason, when: an id, slug, policy, lang or version is malformed; the title is over 40 characters; minutes are outside one to three; contact is missing; a section is missing; a step has no id, scene, alt or paragraph, has more than three paragraphs, or a list; there are fewer than three or more than five steps or questions; no step says what to do instead; a question has no step, names a step the lesson lacks, has no explain, has a paragraph, has fewer than two or more than four choices, or not exactly one right; two steps or two questions share an id; the folder or file name disagrees with the frontmatter; a library entry has no id, names an id the lesson and the check lack, appears twice, or breaks the grammar of the step or question it replaces; a file has both a policy and a topic, or neither, or a topic whose id isn't kebab-case or whose label is missing or over 40 characters.

Qpoint Brand Style Guide