← Design · c137

bones.md

c137 Phase 1: the bones

Written: 2026-11-09. Reader: c131's executor (the types entry lands with c131 Phase 1 in ../qnative; nothing is written there by this cycle), c134's executor (the plane's types/api/messages.ts twin), c133 and c136 (their one type each), Mark at the review stop. Status: a specification, written as the wire shape. Field names are snake_case on the wire; Rust and TypeScript follow their own casing. Timestamps are RFC 3339 in UTC; the app renders them in the machine's zone. Rules it carries: R1 one schema for every message · R2 three sentences · R3 the citizen controls delivery, the overseer controls content · R4 nothing is alarming · R5 every message ends · R6 the record is honest and bounded · R7 use the OS.

1. The message

Message {
  id:            MessageId            // ULID: sortable by creation time, unique across planes and local mode
  type:          MessageType           // §2
  severity:      'info' | 'attention' | 'required'   // §3; `required` is also the one override (§6)
  from:          Sender                // §7
  to:            Recipient             // §7
  batch?:        BatchId               // a send to a team fans out to one message per endpoint; they share a batch

  subject:       string                // one line, 60 characters at most; the OS notification's title
  body: {                              // R2: three sentences, in this order, each 200 characters at most
    happened:    string                //   what happened
    matters:     string                //   why it matters
    next:        string                //   what to do next
  }
  verbatim:      boolean               // D6: a censure is HQ's words, shown as written and labelled; never rewritten by us
  actions:       Action[]              // 0 to 3; §4
  links?: {                            // what the message points at, for `open-page` and for the operator's record
    finding?: FindingId, policy?: PolicyId, training?: TrainingId, grant?: GrantId, endpoint?: EndpointId, page?: Route
  }
  why:           string                // "why am I seeing this": the policy or the person that caused it; fixed at send time

  requires_ack:  boolean               // the person must press Acknowledge for the message to end
  expires_at?:   Timestamp             // §5: none for `required`; per-type default otherwise
  created_at:    Timestamp             // when the sender created it
  delivered_at?: Timestamp             // when this app received it (local mode: equals created_at)

  state:         State                 // §5
  state_at:      Timestamp
  state_reason?: 'recalled' | 'expired' | 'declined'

  thread:        MessageId             // the root message of the conversation this belongs to (itself for a root)
  in_reply_to?:  MessageId             // set on replies
  mute_key?:     string                // what a mute attaches to: `policy:<id>` or `thread:<id>` (§8)
  local:         boolean               // true when no plane is involved: the record exists on this machine only
}

What is not in the message: a read time, a dwell time, a device fingerprint, the person's reply drafts, the delivery channel it took (the channel is the citizen's setting, not the record's business: R3, R6).

2. Types

The fifteen kept in inventory.md. The wire word is the type id.

Type idDirectionDefault deliveryrequires_ackrequired allowedOwner
enforcement-noticeplane → person, or qcontrol → person (local)notifynoyes (D4)c137
hq-noticeplane → personinboxnoonly if the plane allows itc137
identity-confirmationplane → persontraynonoc137, c131 (the V1 flow)
censureplane → personnotifyyesyes (D4)c137
responsibilityplane → persontrayyesnoc137
training-assignmentplane → persontraynono (c133 D9)c133
diagnostics-requestplane → persontrayno (the actions end it)noc137
sensor-stateqcontrol → persontraynonoc137, c131 (the tray)
replyeither way, in a threadtray (person); inbox (operator)nonoc137
acknowledgementperson → planen/a: a state carried back——c137
contact-itperson → planeoperator inbox——c137
training-completedapp → planeoperator inbox (quiet) + audit——c133
diagnostics-sentperson → plane (also carries declined)operator inbox——c137
connect-requestapp → plane, on approval planes onlyoperator inbox——c131, c134
grant-noticeplane → operatoroperator inboxno—c136

3. Severity

Three words, and only these three reach the citizen's UI (R4): info, attention, required. Never danger, critical, urgent or alert.

SeverityMeansDelivery it asks forColour (the brand's rule: orange for work left, red only for blocked or failed)
infoa fact: a receipt, a letterthe type's defaultgrey pill; no colour
attentionsomething for the person to look at or dothe type's defaultorange pill ("Attention")
requiredthe one override: passes quiet hours and Focus, cannot be turned off, must be acknowledged, is counted (§6)one OS notification, whatever the settings sayorange pill ("Required"), the count beside it

A blocked action inside a body ("qcontrol blocked a paste") is the only place the error tone appears, on that word, because something was blocked. The message card itself is never red.

4. Actions

Action { label: string, kind: ActionKind, target?: string }
ActionKind = 'open-page' | 'acknowledge' | 'start-training' | 'contact-it' | 'allow-diagnostics' | 'decline' | 'dismiss'
  • At most three, in the order shown; the first is the primary. acknowledge is added by the app when requires_ack is true, never by the sender.
  • open-page targets a route in the native app (status, connect) or on the web app (a finding, a policy, a training); the web app opens in the browser, signed in through the plane connection (c131 D13).
  • contact-it opens the reply form (§7) with the message's context attached. In local mode it opens the IT contact named in qcontrol's configuration (it_contact: a mailto or URL); absent, the form says so.
  • allow-diagnostics shows the package before anything is sent; decline ends the message as done with state_reason: declined and tells the sender.
  • dismiss exists only for info; it ends the message as done.
  • "Why am I seeing this" is not an action; it is a line on every card (why).

5. States and transitions

new ──opened──▶ seen ──acknowledge──▶ acknowledged ──action completes──▶ done
 │                │                        │
 │                ├──action completes / dismiss (info)──▶ done
 │                │
 └────────────────┴──expires_at passes · operator recalls──▶ expired
EventFromToWho
delivered (the app received it; the OS notification, if any, is posted now)—newthe app
opened (in the inbox, or the notification was clicked)newseenthe person
Acknowledge pressednew, seenacknowledgedthe person
the linked action completes (training-completed, diagnostics-sent, contact-it sent, open-page for a confirmation)new, seen, acknowledgeddonethe app
Dismiss (info only)new, seendonethe person
expires_at passesnew, seenexpired (state_reason: expired)the app
the operator recalls it (sent by mistake; §9)new, seenexpired (state_reason: recalled); the card says sothe operator
  • Terminal states: done, expired, and acknowledged when the message has no completion action (a censure, a responsibility). R5: every message reaches one.
  • expires_at defaults: info 30 days, attention 90 days, required never (it stays, and the tray count holds it, until acknowledged). The sender may set a shorter one (a notice about Monday expires Monday).
  • One re-surface, not reminders: a message with expires_at appears once more at the top of the tray's one line the day before it expires. Never a second OS notification (inventory row 21).
  • What the plane records (R6): created, delivered, acknowledged, done, expired with their timestamps; seen only for messages with requires_ack (so the operator can tell "seen, not acknowledged" from "never opened"); nothing else. Default for plan Q2; Tyler or Peterson may draw it tighter.
  • Inbox order: open threads first (newest activity on top), then ended ones under a fold; ended messages are kept, not deleted. The inbox is a record, not a pile.

6. The required rule and its count

severity: required is the single override, and it is counted. Honesty in both directions.

  • Who may set it: the sender, only on enforcement-notice and censure (D4); on hq-notice only when the plane's messages.required_on_notices setting is on (off by default). A policy may set it for its enforcement notices; a person sets it per message. Any other type: the plane refuses the send.
  • What it does: one OS notification even during quiet hours and the OS's Focus (R7 permitting: §ports on time-sensitive delivery); ignores a type set to off; cannot be muted; implies requires_ack; never expires; holds a place in the tray count until acknowledged.
  • What it does not do: repeat, escalate, sound different, or change colour beyond the "Required" pill. One notification, then the inbox.
  • The count: required_this_month per recipient per calendar month, incremented at delivery. The citizen's settings page shows it: "HQ has sent this machine 2 required messages this month." The operator's send form shows it before sending: the fleet total this month and the highest count any chosen recipient already has.
  • The soft ceiling: the plane setting messages.required_soft_limit (default 3 per recipient per month). Above it the send form asks for a one-line reason that is shown to the citizen on the card. Not a hard limit; an incident is an incident.
  • In local mode: the policy file sets required per policy; the count is kept locally and shown the same way ("This machine's configuration has sent 1 required message this month").

7. Sender, recipient, thread

Sender    = { kind: 'plane', label: string, operator?: Person }   // label: the plane's sender label, default "HQ" (§7.1)
          | { kind: 'qcontrol' }                                    // local mode: "qcontrol on this machine"
          | { kind: 'person', person: Person, endpoint: EndpointRef }   // replies from the citizen
Recipient = { kind: 'endpoint', endpoint: EndpointRef, person?: Person }   // the citizen; person when known
          | { kind: 'plane' }                                       // the operator's inbox
Person    = { id, name }      EndpointRef = { id, name }

7.1 The sender label

A plane-set string, messages.sender_label, default "HQ" (Mark's prompt; c0's proposal: each customer writes their own word, "Security", "IT", "HQ"). Shown alone for automatic messages ("HQ", with why naming the policy) and with the person when one wrote it ("HQ · Dana Whitfield"). Local mode shows "qcontrol on this machine". Overseer and citizen never appear (D7).

7.2 The thread

  • A root message's thread is its own id. A reply (contact-it, reply, diagnostics-sent, training-completed) carries thread = the root and in_reply_to = the message it answers.
  • The citizen's Contact IT is a contact-it message to { kind: 'plane' } with the root's context attached automatically (policy, finding, endpoint, time): the person writes under three prompts (what were you doing · what do you need · anything else) in their own words; R2 does not bind the person.
  • The operator's answer is a reply in the same thread, delivered per the reply type's setting (default tray). The inbox shows one row per thread.
  • A thread ends when its root ends; replies inherit the root's mute_key.

8. Mute keys

A mute is the citizen's, for a day (plan): mute_key is policy:<id> on automatic messages (so "mute this policy's notices for today" catches the next one) and thread:<id> on everything else. A required message ignores mutes but still carries the key, so the settings page can say what was muted and what came through anyway.

9. The send side (for c134)

The operator creates a message with type, recipients (a person, an endpoint, a team, everyone), subject, the three sentences, up to three actions, severity, expires_at, and for a censure verbatim: true. The plane validates: the lengths, the type's required permission, the soft ceiling, the sentence count; the brand voice rules run as a lint on the three sentences (the pattern from the plan: the ten examples below are the fixtures). A send to a team becomes one message per endpoint under one batch. Recall withdraws every message in the batch that has not ended. The operator's inbox lists the replies and the states; the audit log keeps message.sent, message.acknowledged, message.done, message.expired, message.recalled (R6).

10. Ten example messages

One per important type, each body in the brand's three sentences: what happened, why it matters, what to do next. Subjects are 60 characters or fewer. Times are the machine's local time; the fixture world is Acme's, with Dana Whitfield at HQ and Mark Parson on MBP-2291. These ten are the lab mock's data (app/data/messaging-fixture.ts) and the voice fixtures for every message type that follows.

E1 · enforcement-notice, from the plane · attention · V1 if ruled in

From HQ (policy Secrets in prompts) · Why "Policy Secrets in prompts, enforced for Engineering since 3 November" Subject A paste into ChatGPT was blocked

  1. At 14:02 qcontrol blocked a paste into chat.openai.com because the text held an AWS access key.
  2. The key stayed on this machine; your company's policy Secrets in prompts keeps credentials out of AI tools.
  3. If this was work you needed to do, contact IT from here; otherwise nothing more happens.

Actions Contact IT · See the policy · Links finding F-2291-0412, policy P-SECRETS

E2 · enforcement-notice, local mode (from: qcontrol) · attention · V1 if ruled in

From qcontrol on this machine · Why "Policy Personal data in prompts, from this machine's qcontrol configuration; no HQ connected" Subject An upload to Claude was blocked

  1. At 09:41 qcontrol blocked the upload of customer-export.csv to claude.ai because the file holds email addresses and phone numbers.
  2. The file stayed here; this machine's policy keeps personal data out of AI tools.
  3. If this upload was work you needed to do, write to it-help@acme.example, the contact named in this machine's qcontrol configuration.

Actions Write to IT (mailto) · See the policy (local) · local: true

E3 · hq-notice · info · later

From HQ · Dana Whitfield · Why "Dana Whitfield at HQ sent this to every team" Subject AI tools: what changes on Monday

  1. From Monday 17 November, pasting source code into AI tools other than GitHub Copilot and Claude for Work will be blocked on company laptops.
  2. The change comes out of October's security review and applies to every team.
  3. Nothing to do now; if your work needs another tool, ask IT before Monday.

Signed Dana Whitfield, HQ · Actions Ask IT · See the policy · expires_at Monday 17 November 09:00

E4 · identity-confirmation · info · the V1 flow

From HQ · Why "You connected this machine from Connect at 11:20" Subject This machine is connected to HQ

  1. MBP-2291 is now connected to HQ at hq.acme.example as mark@acme.example.
  2. HQ manages this machine's configuration from there, and what qcontrol finds here is reported to it.
  3. Nothing to do; Status shows what is reported, and Connect lets you disconnect if this is not your machine.

Actions Open Status · Open Connect · the same type carries "sign in again" (the credential expired) and "HQ disconnected this machine"

E5 · censure · required · later

From HQ · Dana Whitfield · verbatim: true · Why "Written by Dana Whitfield at HQ and shown as sent; Qpoint does not edit censures" Subject From HQ: stop using a personal ChatGPT account for work

  1. Over the last two weeks, work documents were pasted into a personal ChatGPT account from this machine.
  2. Company data in a personal account sits outside our agreement with OpenAI and cannot be recalled.
  3. Stop, use the company's Claude for Work account instead, and acknowledge this notice.

Actions Acknowledge (added by the app) · Contact IT · the "Required" pill with "1 of 3 this month"

E6 · responsibility · attention · requires acknowledgement · later

From HQ · Dana Whitfield · Why "Dana Whitfield at HQ assigned this to you" Subject You now own the Jira MCP server

  1. HQ made you the owner of the Jira MCP server the Platform team uses, from today.
  2. The owner is asked before the server is blocked and reviews its tool list when it changes.
  3. Acknowledge to take it on, or reply if this should be someone else.

Actions Acknowledge · Reply · See the server · Links the MCP server's page

E7 · training-assignment (c133) · attention · later

From HQ (policy Secrets in prompts) · Why "Policy Secrets in prompts assigns this training after two blocked pastes" Subject A three-minute training: secrets in prompts

  1. Twice this week a credential was pasted into an AI tool from this machine, and both pastes were blocked.
  2. A three-minute training shows what to paste instead and how to get a key rotated when one slips.
  3. Take it when you have a moment; completion goes into the audit log as proof of correction.

Actions Start training · Links training T-SECRETS-1, findings F-2291-0412 and F-2291-0415 · ends as done on training-completed

E8 · diagnostics-request · attention · later

From HQ · Dana Whitfield · Why "Dana Whitfield at HQ asked, about the slow starts you reported on 8 November" Subject HQ asks for diagnostics from this machine

  1. Dana at HQ asked for this machine's qcontrol logs and effective configuration from the last 24 hours, to look into the slow starts you reported.
  2. The package holds qcontrol's own logs and settings and nothing of your own files or messages; you can read it before it goes.
  3. Review and send, or decline; Dana is told either way.

Actions Review and send · Decline · ends as done on diagnostics-sent (sent or declined)

E9 · sensor-state, degraded · attention · later (the tray is c131 Phase 3)

From qcontrol on this machine · Why "qcontrol lost its tap on Safari at 10:15 and it has stayed lost for 5 minutes" Subject qcontrol stopped seeing Safari

  1. Since 10:15 qcontrol has not seen traffic from Safari on this machine; other apps are still covered.
  2. Policies cannot act on what is not seen, so Safari is unprotected until this is fixed, and HQ sees the same gap.
  3. Quit and reopen Safari; if the Status line stays amber, contact IT from here.

Actions Open Status · Contact IT · ends as done when the tap returns

E10 · contact-it, the citizen's reply to E1 · later with E1

From Mark Parson · MBP-2291 · To the plane · in_reply_to E1 · the person's own words under three prompts; R2 does not bind the person Subject Re: A paste into ChatGPT was blocked

  • What were you doing? Pasting a config file to get help with a deploy script; I did not see the key in it.
  • What do you need? A way to share config files safely, or an exception for this project.
  • Anything else? The key belongs to a test account.

Attached automatically the policy, the finding, this machine and the time · lands in the operator's inbox as a reply on E1's thread

Two more, for the operator's inbox (not among the ten)

grant-notice (c136) · from the plane · to the operator · info · Subject Qpoint has access to this plane until Thursday 14:00 · A grant you approved opened at 09:00: fix, operate level, scoped to the ClickHouse layer, for Priya Nair at Qpoint. Every action under it carries grant G-142 in the audit log, and you can revoke it at any moment. Nothing to do; a summary arrives when it closes. · Actions See the grant · Revoke

reply, Dana's answer to E10 · from HQ · Dana Whitfield · to the person · tray · Thanks, that explains it. From today, config files in the deploy repo are excepted for the Platform team. If a key slips again you will still see a notice, and that is all that happens.

11. Voice check

Checked on 2026-11-09 with the brand MCP's check-brand-voice tool (this session had it): E1, E2 and E9 under the errors context, the rest under productUI. Twelve texts (the ten plus the two operator-side ones), zero violations across the 12 to 13 rules each (em dashes, rule-of-three patterns, false oppositions, AI-favoured vocabulary, superlatives, exclamation points in UI, and the rest). Two hand rules the tool does not check, applied while writing: no severity word stronger than "attention" or "required" (R4), and each body answers the three questions in order (R2). The lint the operator's send form runs (§9) is the same tool over the three sentences.

12. What lands where

  • ../qnative, types crate: messages.rs with Message, MessageType, Severity, Sender, Recipient, Action, State; with c131 Phase 1, written from this document, serde names as the wire shape above. The inbox store (SQLite) and the OS plumbing follow the same shapes.
  • The plane (c134 Phase 2): types/api/messages.ts, the messages resource (list, send, recall, reply, state changes), the three settings (sender_label, required_on_notices, required_soft_limit), the audit events in §9.
  • The design site: the lab mock (Phase 2) renders these ten across the five states from app/data/messaging-fixture.ts.

Qpoint Brand Style Guide