Read this once to orient, then drill into the module references in this directory. It uses the Factory's one language and British spelling. Checked against the source at 574e70c on 28 September 2026, then updated for the dashboard brand increment merged from main at f5a8991, which changes no file under src/, for the Producers module (branch factory/producers), accepted on its independent implementation review (Astra round 9 PASS) with both live transports unavailable, and for step 1, increments 1–2 (Repository Assurance, Product gate and delivery records; branch factory/delivery, merged with main at d7e4001), accepted on their independent implementation review (Astra round 6 PASS) and composed into nothing, and for step 1, increment 3 (Repository collector, same branch), accepted on its independent implementation review (Astra round 2 PASS) and composed into nothing, and for step 1, increment 3b (the additive guard on Repository invokeOnce, same branch), accepted on its independent implementation review (Astra round 2 PASS) and composed into nothing, and for lifecycle increment 0 (branch factory/lifecycle, from e62abe6), which changes only src/intelligence.ts (planned declarations for the proposed lifecycle design) and passed independent review (Astra round 2 PASS), and for lifecycle increment 1 (branch factory/lifecycle-schedule, from 217887b), which adds the Lifecycle schedule (src/lifecycle-schedule.ts) and moves its declaration into MODULE_INTELLIGENCE, and passed independent review (Astra round 4 PASS), and for lifecycle increment 2 (branch factory/lifecycle-sources, from 217887b), which adds the Discovery sources collector (src/discovery-sources.ts, src/discovery-text.ts), moves its Intelligence declaration into the accepted list with its files, and passed independent review (Astra round 5 PASS), and for the merge of lifecycle increments 0–2 with main (branch factory/lifecycle-merge), which changes in src/intelligence.ts only where the step-planned declarations render (the Intelligence reference, not the README), and for increment 1 of the Service access module (branch factory/service-access, from main at 985b5f9), built on test fixtures and revised after an adversarial test pass and Astra round 1's FAIL, provisionally accepted — Fable 5.1 (max) round 1; Astra review pending (Codex usage limit until 2026-10-03 18:00) (Fable round 1 PASS, <local evidence archive>; from 3 October 2026 the highest-numbered <local evidence archive> report holds the verdict), and for its merge with main (branch factory/service-access-merge), which adds its declaration to src/intelligence.ts without moving a line the lifecycle cites (the Intelligence reference; independent review pending); the module references, the build review and the receipts under docs/ are the evidence behind every claim here. Where older prose and observed behaviour disagree, trust observed behaviour and accepted receipts.
Verification: independent Astra reviews of this model, AGENTS.md, CLAUDE.md and the docs site are archived at <local evidence archive>; the corrections from rounds 1–9 are applied here, and the highest-numbered report holds the current verdict.
1. Purpose and current truth
- Purpose. Turn customer needs into small, independently verified improvements across several Products, and keep them healthy (design, values). Agents act on behalf of a domain; they are never the system of record.
- What exists. Accepted local infrastructure only: the independently accepted modules in section 3 (fifteen references and sixteen accepted build review rows besides Producers: the review splits Repository into sealing / export, integration and the integration guard seam, and joins Project guidance with Required actions) in one Node.js application over local SQLite and Git, on one macOS host. Intelligence, accepted later, is not counted there, nor are the lifecycle's two modules or Service access below; each has its own build review row (Intelligence since lifecycle increment 0). Producers is accepted as code under independent review, but both of its live transports are unavailable and nothing composes it (section 3). Repository Assurance and the Product gate and delivery records are accepted as step 1, increments 1–2; only tests call them. The Repository collector is accepted as increment 3; only tests call it. The repository
guardseam is accepted as increment 3b; nothing insrc/passes a guard. Delivery composition, the delivery CLI and the live Slice (increments 4–8) are not built. The Lifecycle schedule (lifecycle increment 1) is implemented on fixtures with a fake clock and passed independent review (Astra round 4 PASS); only tests call it, and nothing runs on a schedule. Discovery sources, lifecycle increment 2, is implemented, tested offline against a loopback corpus and accepted locally on independent review (Astra round 5 PASS); nothing composes it. Service access increment 1 (a names-only catalogue of Service kinds, Service map and readiness, on test-made fixtures) is built and provisionally accepted — Fable 5.1 (max) round 1; Astra review pending (Codex usage limit until 2026-10-03 18:00) (Astra round 1 FAIL, fixed; Fable round 1 PASS; from 3 October 2026 the highest-numbered<local evidence archive>report holds the verdict); it reads no live source and nothing composes it. The proposed production stack (Temporal, PostgreSQL) is a proposal; nothing of it runs here. - What does not exist. No active CI:
.github/workflows/check.ymlis configured on the private originchrismitchelmore/software-factorybut waits for a runner (hosted minutes are blocked by billing; a self-hosted Mac runner needs the owner). No production, pilot, release or customer (older prose calls the fixture path a "local pilot"; it is local synthetic-fixture infrastructure, not a pilot). No provider transport is qualified: the Producers module is accepted, but Claude's rules revision 5 needs re-qualification (the revision-4 live check decidedunavailable) and OpenAI has no Platform credential. No nightly or weekly automation is active. No composed Product gate (the gate exists, but nothing calls it), Action gateway or planner, and no Assessment of any real Slice (delivery records can derive one; nothing has). - All 27 customer-value epics (E01–E27) remain open. Accepted modules, brand artwork and documentation close none of them (outcome map).
- Last accepted full verification (build review): strict typecheck, 350/350 tests with none skipped, frontend build, and real desktop and 390 px browser checks. The later dashboard brand increment (
0ab94ed–7b499ea) is recorded only in the handover: Astra high PASS on round 2, root 355/355 and dashboard package 10/10 tests; it has no receipt or build review row. The Producers acceptance (Astra round 9 PASS,<local evidence archive>) rests on 397/397 focused offline tests run locally and Astra's own 128/128 read-only tests and 32 in-memory probes; it is not a live transport qualification (344/344 at qualification time, Claude receipt). The step 1, increments 1–2 acceptance (Astra round 6 PASS,<local evidence archive>) rests on 73/73 increment tests and a 428/428 full check on the pre-merge basef5a8991; after mergingmainatd7e4001, the full check passes 851/851 with none skipped. At that merge one boundary test was adapted so that the Producers module may import its own contract; that change is not independently reviewed. No receipt or reference review exists for them yet. The step 1, increment 3 acceptance (Astra round 2 PASS,<local evidence archive>) rests on 50/50 focused tests run locally (collector, adversarial, delivery boundaries and Repository Assurance, none skipped) and Astra's own read-only typecheck, boundary tests and placement probes; the full check then passed 881/881 with none skipped. Test counts describe the tested boundary, not release or customer value. - Six Products are registered for metadata inspection only (JustSpeakToIt, Pasta, Family Watch, Tally, Focus Sentinel, Factory). Their state is
<local Portfolio journal>(ignored, local data): preserve it and never re-run onboarding to recreate it. - Canonical changing status is the Notion Factory record linked from handover; the active task capsule is
.intent/tasks/local-factory-completion.md.
2. Domain language as used in code
One line per term. Status says how far the term exists in src/: implemented, narrow (real but limited), pinned only (an identity string, no behaviour) or designed, not implemented.
| Term | In code today | Status |
|---|---|---|
| Product | productId registry entry in Portfolio (ProductRecord, one verified checkout, operationScope: "metadata-inspection", deliveryAuthority: "absent"); named in Repository identities and manifests and in Guidance aggregates. The Product gate (gate:product:<productId>) binds a Product once to a Factory-owned store outside every registered checkout and admits its delivery work; only tests call it. | narrow |
| Objective | Only the objective text of an imported Portfolio focus fact. No Planning. | narrow |
| Signal | Not present. | designed, not implemented |
| Capture | Capture (sf-capture/1) in Discovery sources (lifecycle increment 2, accepted locally): one item of at most 4 096 bytes of normalised text from a commissioned Source, with its SHA-256, SimHash and novelty (new, changed, repeat, unchanged); larger text is refused, never cut short, and only its hashes are journalled, the text staying in a cache that is readable for 35 days and pruned at each collection (§8). A Capture handed back is trusted only as its occurrence's record holds it. Tested only on a loopback corpus; nothing composes it. | narrow |
| Citation | Citation (sf-citation/1) in Discovery sources: a verbatim quote of at most 15 words (counted strictly) that must occur in a recorded Capture's text and carry no address, handle or phone number; it re-verifies from those bytes and the occurrence's record, so a quote moved to another URL, time or occurrence is rejected, and is unverifiable (unknown, not failed) once they expire at 35 days. Not registered with the Evidence store; nothing makes one yet but tests. | narrow |
| Cluster | Not present. Proposed lifecycle §4: a Product's Signals under one problem key (signal-intake), scored for synthesis. | designed, not implemented |
| Occurrence | LifecycleSchedule in lifecycle-schedule.ts: one scheduled discovery pass with a stable ID (discovery/<product>/basic/<local date>, …/full/<ISO week>, discovery/portfolio/<rhythm>/<ISO week>), admitted, deferred, reconciled, resumed and settled once, each transition its own journal command; missed ones coalesce into the next. Only tests call it. | narrow |
| Opportunity | Not present. Proposed lifecycle §4: a falsifiable need drafted from Clusters (opportunity-synthesis) that the opportunity gate admits, parks or rejects; admission never selects it into a Slice. | designed, not implemented |
| Outcome | Not present. Dashboard measuredValue is always unknown ["no-outcome-assessment"]. (Outcome in factory-support.ts is the CLI result word, not the domain Outcome.) | designed, not implemented |
| Capability | Not present (disposition versus health). Dashboard capabilities: {inspect, guidance} are server feature flags, a different sense. | designed, not implemented |
| Slice | FrozenInputs.slice {id, revision} and IntegrationPayload.slice in Repository; sliceId on Portfolio observations. The Product gate freezes a SliceEntry (scope, operations, frozen-input digests, allowance, deadline) under a grant, and the delivery records keep the frozen SliceRecord (sf-slice/1: Need, scope, non-goals, acceptance, recipe, environment, brief with an optional guidance pin, assessment plan); only tests create them. No planning. | narrow |
| Product Pack | Not present. | designed, not implemented |
| Recipe | recipeRevision pin. The one executable recipe is guarded fixture verification sf-local-fixture-verify/2 (/1 is read and settled, never dispatched). Repository scope defines a sf-repository-verify/1 recipe record (collector closure and executor bounds) that nothing runs yet. | narrow |
| Run | Execution aggregate run:<runId>: queued, running, completed, exhausted, cancelled; pins and Budget fixed at submit. completed means the recipe ran to its end, never a Verdict. | implemented |
| Attempt | <runId>/<epoch>, epochs 1–20, running, completed, failed or interrupted; one owner; ends only by the owner's report or a trusted cleanup confirmation for its epoch. | implemented |
| Candidate | Two narrow forms: the fixture Candidate digest (sf-local-fixture/2, from fixture name and execution manifest digest) and Repository's SealedCandidate (sf-candidate/1: manifest plus exact bytes). The accepted Producers module can return an untrusted Proposal for seal, but nothing composes it and both live transports are unavailable, so no producer proposes anything yet. | narrow |
| Evidence | EvidenceStore: content-addressed sf-evidence/1 records with eight EvidencePins and a ProcessObservation; deliberately no passed flag. | implemented |
| Verdict | judge() → Verified, Failed or Inconclusive with scope: "local-fixture". judgeRepository() gives the same three results with scope: "local-repository" over store-written repository records, only in tests; the Repository collector (accepted, not composed) writes such records from real sandboxed runs, also only in tests. Portfolio verification facts carry a Verdict word plus a separate validity. | narrow |
| Action | The integration occurrence key action {id, occurrence} in Repository; the Product gate admits integrate-local dispatches under integrate-local:<productId>:<sliceRef> and records their outcomes; only tests call it. No Action gateway. | narrow |
| Action grant | Not present. Proposed lifecycle §4: the owner's single-use permission for one outward Action (owner-decisions), bound to an artefact digest and channel. | designed, not implemented |
| Receipt | IntegrationReceipt {id, target}: the receipt commit after a confirmed target update; the delivery records' create-once ReceiptRecord keeps the sender's own IntegrationObservation of a dispatched move. Two other senses exist: the journal's command receipt (a stored row) and Portfolio's UsageReceipt (provider usage). | narrow |
| Assessment | deriveAssessment in the delivery records derives supported, inconclusive or rejected from a frozen Slice record, a repository Verdict and the records it judged, Verdict-first; nothing stores a conclusion and only tests call it. No Supported, Rejected or Inconclusive record exists. | narrow |
| Authority | The Product gate's grants (paths, operations, limits), revocations, stop intent and one fence-proved owner; only tests exercise them, and Portfolio still reports deliveryAuthority: "absent". confirmedBy and collector names are attribution, not authentication. | narrow |
| Policy | Not present (only HTTP Permissions-Policy headers, unrelated). | designed, not implemented |
| Budget | Budget {maxAttempts, attemptTimeoutMs} in Execution; never extended; no cost field. The Product gate's per-Slice allowance (GrantLimits: production runs, check runs, attempts per check, integrations, elapsed time) is counted separately and never decreases. The Lifecycle schedule's discovery-window ledger counts estimated tokens per ISO week against a ceiling set when the week opens and never raised; an unknown call stays debited at its bound; only tests call it. A shared Slice allowance across providers is planned. | narrow |
| Escalation | Not present. Nearest thing: an attention item with ownerRequired, derived from a source-reported blocker that names the owner and an exhaustion; the Factory records none itself. | designed, not implemented |
| Command, Event | CommandJournal.execute(commandId, payload, expectedVersion, aggregateId, decide): one atomic transaction records state, receipt, 1–100 Events and outbox rows. Nothing delivers the outbox. | implemented |
| epoch | An Attempt's position, 1–20, and the ownership version scope owners check; mirrored by MAX_FENCE_EPOCH and the ledger's MAX_EPOCH. | implemented |
| pins | RunPins {candidateDigest, recipeRevision, scenarioRevision}; EvidencePins add runId, attemptId, epoch, environment, collector; RepositoryPins (Product, store, base, commit, Slice, snapshots, frozen records, Attempt) map to repository-scope EvidencePins through evidencePinsOf; GuidancePin {productId, revision, digest}. Any input change needs a fresh Verdict. | implemented |
| Scenario | scenarioRevision = "local-fixture-scenario/" + digest(environment, timeoutMs, maxOutputBytes); the environment pin is node-<version> <platform>-<arch>. Repository scope uses sf-repository-scenario/1/<hex> over the Product, store, base, Candidate, Slice, snapshots and acceptance. | narrow |
| Fence, Workspace | FenceIdentity: a one-row SQLite file held by a kernel-released read lock, open → fenced one way. WorkspaceRef: one fresh 0700 directory bound to that fence. fenced proves protected work cannot resume, not process exit. | implemented |
| Collector | An attribution label on Evidence and Portfolio facts (sf-guarded-fixture-worker/1, local-git, claude-code-result); never authenticated. The repository collector (sf-repository-scenario-collector/1, pinned with the digest of its source closure) is accepted as step 1, increment 3, and nothing composes it. | implemented |
| Observation, Fact | Portfolio Observation {collector, recordId, revision, productId, sliceId, observedAt, capturedAt, fact}; FactKind: focus, source-coverage, activity, verification, release, measurement, blocker, repository, usage-invocation, usage-session, retraction. | implemented |
| Count | exact {value}, partial {atLeast, reasons} or unknown {reasons}; only an exact Count ever shows 0. | implemented |
| Guidance | ProductGuidance: 0–6 values per Product, immutable digest-bound revisions, resolvePin. The Dashboard reads and edits it; no planner or Run briefing consumes a guidance pin yet. | implemented |
| Service | A third-party system a Product uses through a vendor account. Service access detects one from catalogue signatures (ServiceEntry in a Service map); nothing acts on a Service. Increment 1. | narrow |
| Service kind | A reviewed, versioned, frozen catalogue entry (vercel/1): signatures, credential slots, a 1Password item template, purposes and routes, desired settings, runbook templates, digested; eleven exist. Increment 1. | narrow |
| Credential reference | Where a credential lives and what it is for, never its value: CredentialReference (location, kind, instance, slot, class, presence, usage, consumers) with a reference id hashed from the Product and the location. Increment 1. | narrow |
| Service map | sf-service-map/1: one Product's Services, Credential references, CI jobs, findings and Counts at one commit, derived inside the journal decision from names-only scans and listings; only test fixtures produce one. Increment 1. | narrow |
| Product vault | A dedicated 1Password vault per Product, owner-created and bound by id, the source of truth for its secrets. Only its store binding (onepassword-vault) and location type exist; nothing reads a vault. | designed, not implemented |
| Blocker | Portfolio blocker fact (open or resolved, prerequisite, affected, remediesTried, nextAction, optional details) → an attention action. Resolution is the source's report, never verified. | implemented |
3. Module map
Arrows are real import statements between src/ files, grouped by module. A dotted arrow is a type-only import or an HTTP call. factory-support.ts, local-factory.ts and cli.ts are composition files and belong to Guarded verification's reference.
flowchart LR
subgraph records["Records and runs (plate 1)"]
CJ["Command journal<br/>journal.ts · canonical-json.ts · errors.ts"]
EA["Evidence / Assurance<br/>evidence.ts · assurance.ts"]
EX["Execution<br/>execution.ts"]
LW["Local fixture worker<br/>local-worker.ts · fixture-child.ts · fixture-gate.ts · fixture-manifest.ts"]
AF["Attempt fence / workspace<br/>attempt-fence.ts · attempt-workspace.ts"]
GV["Guarded verification / recovery<br/>guarded-verification.ts · attempt-ledger.ts · factory-support.ts"]
LF["Local factory CLI<br/>local-factory.ts · cli.ts"]
end
subgraph delivery["Isolation and delivery (plate 2)"]
CE["Confined executor<br/>confined-executor.ts"]
RP["Repository sealing / export and integration<br/>repository.ts"]
end
subgraph visibility["Visibility (plate 2 and unplated)"]
PF["Portfolio<br/>portfolio.ts · portfolio-inventory.ts"]
PG["Project guidance<br/>project-guidance.ts"]
AT["Required actions and blockers<br/>attention.ts"]
DC["Dashboard contract<br/>dashboard-contract.ts"]
DS["Dashboard server and view<br/>dashboard-server.ts · dashboard-view.ts"]
BA["Browser app<br/>apps/dashboard/src"]
end
subgraph providers["Providers (accepted; live transports unavailable)"]
PR["Producers<br/>producer.ts · claude-producer.ts · openai-producer.ts"]
end
subgraph access["Service access (increment 1; not composed)"]
SA["Service access<br/>service-access.ts · service-discovery.ts · service-kinds.ts"]
end
subgraph core["Delivery core (step 1, increments 1–3; not composed)"]
RA["Repository Assurance<br/>repository-assurance.ts"]
GD["Product gate and delivery records<br/>product-gate.ts · delivery-records.ts"]
RC["Repository collector<br/>repository-collector.ts"]
end
subgraph lifecycle["Lifecycle (increments 1–2; not composed)"]
LS["Lifecycle schedule<br/>lifecycle-schedule.ts"]
DSC["Discovery sources<br/>discovery-sources.ts · discovery-text.ts"]
end
EX --> CJ
LW --> EA
LW --> AF
GV --> EX
GV --> AF
GV --> LW
GV --> EA
GV --> CJ
LF --> GV
LF --> EX
LF --> EA
LF --> LW
LF --> CJ
RP -->|canonical JSON only| CJ
RP -->|snapshotDigest| CE
PF --> CJ
PG --> CJ
PG --> PF
AT --> DC
AT -.->|types| PF
DS --> AT
DS --> DC
DS --> PF
DS --> PG
DS --> CJ
BA --> DC
BA -.->|HTTP, loopback| DS
PR -->|canonical JSON only| CJ
PR -->|types and limits only| RP
RA -->|canonical JSON only| CJ
RA -->|SNAPSHOT_PROTOCOL only| CE
RA -->|PIN_FIELDS, isStoreRecord| EA
RA -->|parseRepositoryIdentity| RP
GD --> CJ
GD -->|parseFenceIdentity only| AF
GD --> RA
GD -.->|types| RP
GD -.->|types| EA
RC -->|run, runtime and profile| CE
RC -->|EvidenceStore| EA
RC -->|pins and observation encoding| RA
RC -->|validatedHoldIdentity| AF
RC -->|canonical JSON only| CJ
RC -.->|types| RP
RC -.->|SnapshotManifest type| GD
LS -->|journal and canonical JSON| CJ
DSC -->|journal and canonical JSON only| CJ
SA --> CJ
SA -.->|Count and coverage types| PF
Facts the graph cannot show: only the Repository collector calls ConfinedExecutor.run, nothing in src/ calls the collector, and nothing in src/ calls repository.seal; the src/ importers of repository.ts are the Producers contract (src/producer.ts, for FileEdit, Proposal, SourceFile and REPOSITORY_LIMITS), Repository Assurance (parseRepositoryIdentity and types) and the Product gate, delivery records and Repository collector (types only); nothing in src/ or apps/ imports the Producers module, and nothing outside the delivery core (Repository Assurance, the Product gate and delivery records, and the Repository collector) imports any of its four files; nothing imports the Lifecycle schedule; nothing imports Discovery sources, and it imports only the Command journal, canonical JSON and Node built-ins; tests compose Repository with the executor (one in the Repository suite, and the collector's suite, which seals real Candidates and runs them). Nothing in src/ imports the three Service access files. npm run factory never starts the dashboard. The Dashboard's repository check is Portfolio.inspectProduct over an existing checkout, not Repository integration. Command IDs are journal-wide, so every module's prefixes must stay distinct.
| Module | Responsibility | Disposition (build review) | Agent reference | Human guide |
|---|---|---|---|---|
| Command journal | Atomic command → state, receipt, Events, outbox; replay by command ID; read-only inspection | Accepted local reads and writes | command-journal | guide |
| Evidence / Assurance | Content-addressed observations; pure three-valued Verdict fold | Accepted for trusted local fixtures | evidence-assurance | guide |
| Execution | Run and Attempt ownership, Budget, durable cancellation; starts nothing | Accepted for trusted local supervision | execution | guide |
| Local fixture worker | One built-in fixture as a real bounded process, observed as Evidence | Accepted for fixed trusted fixtures | local-fixture-worker | guide |
| Attempt fence / workspace | Per-Attempt ownership gate and private workspace | Accepted for trusted local POSIX fixtures | attempt-fence | guide |
| Guarded verification / recovery | Verify a fixture under exclusive ownership; settle orphaned Attempts within Budget; the verify, inspect, cancel and recover CLI | Accepted for trusted fixtures | guarded-verification | guide |
| Confined executor | One bounded Node program against read-only snapshots in a macOS sandbox; observes, never judges | Accepted for the tested stateless macOS boundary | confined-executor | guide |
| Repository sealing / export and integration | Seal bounded text edits as an immutable Candidate; export exact bytes; move the target ref B → M at most once per prepared Action, optionally only after a caller's pre-move guard allows it | Sealing / export: fresh owned local repositories. Integration: a local effect primitive. The guard seam (step 1, increment 3b): accepted, not composed | repository | guide |
| Portfolio | Registry of local Products bound to one checkout each; read-only views of sourced facts and usage | Accepted for local metadata registration and sourced facts | portfolio | guide |
| Dashboard | Neutral contract, pure projection, loopback HTTP with two guarded commands, React presentation | Accepted local browser interface | dashboard | guide |
| Project guidance | Up to six versioned values per Product with immutable pins | Accepted local domain and browser interface | project-guidance | guide |
| Required actions and blockers | Pure projection of current sourced conditions into actions, notices and blocker detail | Accepted local domain and browser interface (with Project guidance) | attention | guide |
| Repository Assurance | Pure judgement of repository-scope Evidence for one sealed Candidate (Candidate-as-program checks, pinned stdout digests, authenticated manifest and base) into a local-repository Verdict; never relabels fixture proof | Accepted as step 1, increment 1 (Astra round 6 PASS, 2026-09-29, with increment 2; rounds 1–5 FAIL). Report: <local evidence archive>. No collector feeds it; nothing composes it. No receipt | repository-assurance | guide |
| Repository collector | Run each frozen process check of one sealed Candidate once under the Confined executor (the base or Candidate tree as the program, the named fixture input, nothing else) and store exactly one repository-scope Evidence record per run; refuse, before anything runs, pins the sealed Candidate was not sealed under, and before each launch when the gate, the hold, the environment, placement of protected paths (also checked at setup; by name and by device and inode), the entry, size or deadline says so; a write-once snapshot store for fixture inputs | Accepted as step 1, increment 3 (Astra round 2 PASS). Not composed. No receipt | repository-collector | guide |
| Product gate and delivery records | One journal aggregate per Product: store binding, grants and revocations, stop intent, fence-proved owner, frozen Slices with allowances, admissions and outcomes, with integration admitted only on a Verdict re-derived inside the decision; beside it, create-once delivery records and a pure Assessment derivation | Accepted as step 1, increment 2 (Astra round 6 PASS, 2026-09-29, with increment 1; rounds 1–5 FAIL). Report: <local evidence archive>. Nothing composes it. No receipt | product-gate | guide |
| Producers (Claude / OpenAI) | Turn one validated request into at most one provider exchange and return an untrusted Proposal for Repository sealing, or an explicit non-success; never applies, integrates or verdicts | Accepted: provider-neutral producer contract, Claude CLI and OpenAI Responses adapters under independent review (Astra round 9 PASS, 2026-09-28). Live transports unavailable: Claude (rules revision 5 needs re-qualification; 5 built-in agents + 2 plugins lack established inert meaning), OpenAI (no Platform credential). Nothing composes it yet. Report: <local evidence archive> (rounds 1–8 FAIL). The Claude qualification receipt is for rules revision 4 (Claude receipt); the OpenAI receipt is for rules revision 4 and records no live request (OpenAI receipt) | producer | guide |
| Lifecycle schedule | Occurrence identity, the discovery-window ledger and deferral for the proposed discovery design: one journal aggregate over an injected clock; admits each provider call before contact and runs none | Implemented as lifecycle increment 1 (branch factory/lifecycle-schedule); an independent adversarial pass found 12 defects, all fixed, and the fixed revision passed independent review (Astra round 4 PASS; rounds 1-3 FAIL, every finding fixed). Tests under a fake clock, including real-process races; nothing composes it, no LaunchAgent is written or loaded, and every commission value is a proposal. No receipt review | lifecycle-schedule | guide |
| Discovery sources (lifecycle increment 2) | Fetch a Product's commissioned public Sources (allow-listed https URLs, robots.txt first, fixed headers, no cookies, credentials or redirects) into Captures with hashes and novelty, a coverage status per Source, and 15-word Citations that re-verify from a 35-day cache; limits refuse, never truncate | Accepted locally on independent implementation review (branch factory/lifecycle-sources): Astra round 5 PASS after rounds 1–4 FAIL, every finding fixed. An independent adversarial pass found 17 defects, all fixed. Tested offline only (a loopback corpus, 76 tests, 17 of them the adversary's); its HTTPS transport has never fetched a real page, and nothing composes it. Logs and dispositions: <local evidence archive> | discovery-sources | guide |
| Service access | Names-only map of a Product's Services and where their credentials live (catalogue of Service kinds, deny-first tree scanning, listing and sign-in parsing, the Service map fold), exclusive store bindings, declarations, confirmations, route pins and readiness; built never to read or record a value (established for the tested positions and forms) | Increment 1 (built 2026-09-29 and revised the same day on Fable 5.1's feedback and an adversarial test pass); provisionally accepted — Fable 5.1 (max) round 1; Astra review pending (Codex usage limit until 2026-10-03 18:00): Astra round 1 FAIL (eight findings, fixed), round 2 stopped at the usage limit; Fable 5.1 round 1 PASS (<local evidence archive>); from 3 October 2026 the highest-numbered <local evidence archive> report holds the verdict. Test fixtures only; nothing composes it. Receipt and build review row exist. Design: Astra round 5 PASS (<local evidence archive>) | service-access | guide |
| Intelligence | One registry of where a model takes part: each module's decision (none, optional, required) and why, one pinned model and effort per task, dated model facts per provider route; a pure selector that refuses rather than substitutes. Imports only canonical JSON; nothing imports it | Accepted on its independent implementation review (Astra high, round 2 PASS, 2026-09-29) and merged to main at 298726b; not counted among the fifteen references in section 1, and it has no reference-review report; since lifecycle increment 0 it has its own build review row. Reports: <local evidence archive> (the highest-numbered report holds the current verdict). Nothing composes it. Lifecycle increment 0 changes it and passed its own independent review (Astra round 2 PASS): nine planned lifecycle declarations with five extension tasks and the three Astra tasks declared blocked until codex-cli's audit, since no effort is established on it, an efficient task refused until its own measured comparison, the weekly search pinned to a separate, unavailable research profile, the discovery-window allowance, and codex-cli as OpenAI's one route, declared unqualified, so the accepted OpenAI Responses adapter is kept but unpinned; the six earlier tasks' policy digests are unchanged (receipt). Increments 1 and 2 each move one landed declaration into MODULE_INTELLIGENCE (Astra rounds 4 and 5 PASS, with their modules), and the merge with main renders the step-planned declarations in the reference instead of the README, so the README section stays within its 25 lines (merge receipt). Merging service access increment 1 adds its none declaration without moving a line the lifecycle cites; that merge's independent review is pending (service access merge receipt). No model is qualified, so every task refuses | intelligence | guide |
Reference review. Each accepted module's agent reference, except Producers, Repository Assurance, the Product gate, the Repository collector, Intelligence, the Lifecycle schedule, Discovery sources and Service access, was checked independently against the source by Astra (gpt-6-astra, high effort); the reports are under <local evidence archive>, and the highest-numbered report for a slug holds its current verdict. Every one of those references has a PASS report: Execution, Local fixture worker and Required actions (round 3); Command journal, Project guidance and Repository (round 4); Attempt fence, Guarded verification and Portfolio (round 5); Confined executor, Dashboard and Evidence / Assurance (round 6). Producers was accepted on its implementation review instead: rounds 1–8 FAIL and round 9 PASS (<local evidence archive>; the current verdict is <local evidence archive>). Its agent reference has no separate reference-review report yet. Repository Assurance and the Product gate and delivery records were likewise accepted on their implementation review: rounds 1–5 FAIL and round 6 PASS (<local evidence archive>); their agent references have no reference-review report yet. The Repository collector was accepted on its implementation review, after an independent adversarial pass, rounds 1 FAIL and 2 PASS (<local evidence archive>); its agent reference has no reference-review report yet. Intelligence was accepted on its implementation review, rounds 1 FAIL and 2 PASS (<local evidence archive>); its agent reference has no reference-review report yet. The Lifecycle schedule passed its implementation review likewise, after an independent adversarial pass: rounds 1–3 FAIL and round 4 PASS (<local evidence archive>); its agent reference has no reference-review report yet. Discovery sources was likewise accepted on its implementation review: rounds 1–4 FAIL and round 5 PASS (<local evidence archive>); its agent reference has no reference-review report yet. Service access (increment 1) is provisionally accepted — Fable 5.1 (max) round 1; Astra review pending (Codex usage limit until 2026-10-03 18:00): Astra round 1 FAIL, Fable 5.1 interim round 1 PASS (<local evidence archive>), and from 3 October 2026 the highest-numbered <local evidence archive> report holds the verdict; its agent reference has no reference-review report. A reference review checks the documentation, not the module's behaviour or acceptance.
Each accepted module has one labelled position on one of two brand plates; Project guidance, Required actions, Producers, Repository Assurance, the Product gate, the Repository collector and Intelligence have no plate position yet, and neither have the Lifecycle schedule, Discovery sources and Service access (increment 1). Show a plate whole, name the position in text, and never crop it.
4. Lifecycle: what exists and what is planned
The designed loop is Need → Slice → Candidate → Verdict → Receipt → Assessment. The middle of it runs today only for four synthetic fixtures. Everything in the Planned column comes from the handover next steps 1–4; none of it is accepted, and nothing here claims otherwise.
| Transition | In code today | Planned (not accepted) |
|---|---|---|
| Need → Slice | Portfolio can hold imported focus facts (an objective and a sliceId) as sourced statements. The accepted Product gate binds a Product to a Factory-owned store, grants and revokes, and freezes a Slice with its scope, frozen-input digests, allowance and deadline; the delivery records keep the frozen SliceRecord (Need, brief with an optional guidance pin, assessment plan). Only tests call them. | Step 1 (the gate and records exist; the composition that binds from the Portfolio, plans a Slice and runs it does not): a Product authority and admission boundary around the accepted Journal, Execution, Assurance and Repository interfaces, binding Product, source snapshot, integration destination and ref, Slice, exact baseline and candidate commits, acceptance and environment identities, independent evidence and a guidance pin. Portfolio checkout identity and owned bare-repository identity stay distinct. Step 3: briefs that pin {productId, revision, digest} from guidance, present the applicable rules and explain trade-offs; old briefs keep their pins; guidance grants no authority and weakens nothing. |
| Slice → Candidate | Repository seal(FrozenInputs, Proposal) turns 1–64 bounded text edits into an immutable SealedCandidate in a fresh Factory-owned bare repository. The proposal comes from the caller; only tests call it. The fixture path derives a fixture Candidate digest instead. Accepted, with both live transports unavailable: the Producers contract turns one validated request into at most one provider exchange and an untrusted Proposal for seal; nothing composes it. The accepted Product gate admits one production per proposal digest and records the Candidate it sealed; nothing composes it with seal. | Step 2: proposal-only providers, qualified by exact model, tool boundary, strict bounded text-edit proposals, refusals and malformed or truncated output, timeout and cancellation, and unchanged host canaries. Tool-enabled construction sessions are not adapter qualification. The accepted Producers module is this step's first part, not its completion; still open: a person's completeness audit of the CLI's 5 built-in agents and 2 plugins followed by re-qualification under rules revision 5, and, for OpenAI, a Platform credential plus one bounded live qualification, or instead the Codex CLI route on the owner's ChatGPT sign-in, never a Platform key, which the proposed lifecycle takes as decision 3's default (open) and audits in its increment 7. The DispatchGate a producer needs is not built: the accepted Product gate has no producer dispatch kind (step-1 spec §7 D1 defers it to step 2's composition), and a durable Slice allowance shared across providers is still to come; unknown remote spend stays unknown. |
| Candidate → Verdict | For the built-in fixtures only: create and hold a fence → provision the workspace → durably record preparation → Execution claims an Attempt → recheck admission → LocalFixtureWorker.runGuarded → Evidence → judge → Verdict local-fixture, with controller-death recovery within Budget. The Confined executor can run an exported repository Candidate (one permanent test), but no Run composes it. Accepted and not composed: Repository Assurance's pure judgeRepository gives local-repository Verdicts over store-written repository records (tests only), and the Product gate admits a check and each of its collector dispatches within the Slice's allowance. Accepted and not composed: the Repository collector runs each check of a sealed test Candidate through the Confined executor and stores one record per run, which its tests judge. | Step 1, increment 4: the collector's composition under the Product gate (gate reads, re-exports around a collection, Verdict retention), after the collector's independent review. Proven so far at module level: wrong-Product and wrong-proof rejection, concurrent ownership and revocation. Still to prove in the composition: moved targets and lost acknowledgements. |
| Verdict → Receipt | Repository prepareIntegration → invokeOnce → IntegrationObservation. After submission, Git exit 0 plus an exact readback of M confirms the invocation; receipt retention is separate, and its failure returns confirmed with receipt: null and a problem; any other submitted outcome stays unknown. Before submission the observation can be not-dispatched, or, from a call given a guard that did not allow the move after the claim, withheld (the claim is kept and reads as unknown afterwards; the guard seam is accepted as increment 3b and not composed). The payload's proof is bound and retained, not checked as a Verdict. Nothing in src/ calls it. Accepted and not composed: the Product gate admits an integrate-local dispatch only after re-deriving the proof's Verdict inside the decision and checking that the Candidate changes nothing outside the Slice's scope, blocks every later integration of the Product while one is unknown, and the delivery records keep the prepared intent and the sender's receipt. | Step 1, increments 4–8: delivery composition under the Product gate, passing the guard seam (3b, accepted) a repeat of the final read. Prove it first in a disposable repository, then deliver one evidenced useful local Slice without touching a checked-out branch or unrelated work. |
| Receipt → Assessment | Portfolio imports release and measurement facts as sourced statements and never sums measurements; the Dashboard's measured value stays unknown. The delivery records' pure deriveAssessment derives supported, inconclusive or rejected from a frozen Slice record, a repository Verdict and the records its judgement reproduces, Verdict-first and never stored; only tests call it. | Step 4: collectors only for a real fact the useful Slice needs, keeping correction, deduplication, capture time and coverage; notification state stays derived. Capability requirement and retirement separate from observed health; immutable Outcomes; independently verified Supported, Rejected or Inconclusive Assessments; research and cleanup proposals that enter ordinary verified Slices. Regression restoration, duplicate and crash handling and bounded budgets are proven before any schedule is activated. |
Proposed, two parts built: discovery and post-development. The lifecycle design (Astra round 3 PASS on the design only) would add nine modules: scheduled discovery of public Sources, triage into Signals and Clusters, at most five Opportunities a week that a rule-first gate admits, parks or rejects, a weekly owner bundle with single-use Action grants, release drafts with a claim trace, outcome validation and Capability retirement. Increment 0 adds only their planned Intelligence declarations; increment 1 builds the Lifecycle schedule on fixtures (Astra round 4 PASS; nothing composes it); increment 2 builds the Discovery sources collector (Astra round 5 PASS), which runs only against a loopback corpus, and nothing schedules or reads it; increments 3–11 build the rest on fixtures before any live run, and every owner decision in its §11 is open. It depends on step 1's delivery composition (its A12).
5. Cross-cutting invariants every agent preserves
Each line names where the rule already lives, so a change can be checked against it.
- Unknown is not zero, and not failed. Portfolio Counts are exact only under declared complete coverage; the Dashboard renders unknown as
—; Assurance folds a timeout, signal, collector error or missing reference to Inconclusive; the executor'sunresolvedand Repository'sunknownare neither failed nor done. - A historical Verdict is not current validity. Guarded verification shows
historicalVerdictas issued and recomputescurrentValidityfrom the Evidence and the source digests now; any source drift invalidates without rewriting the Run. - SQLite and Git (or any provider) are never one transaction. Journal commands wrap no external effect; Repository's claim, target move and receipt are separate single-ref updates with no crash atomicity claimed; the journal's replay avoids re-running the decision only, so a caller's other effects can repeat.
- An unknown effect never permits a blind retry.
invokeOncethrows only before submission and never downgradesunknownto "not applied"; after a lost acknowledgement, repeat the same command ID with identical inputs (journal, Execution, guidance, Product gate) and let the replay say whether it committed. The Product gate blocks every later integration of a Product while one is null orunknown, and itsREPLAYEDanswer is history, never a permit. - The implementer is never the verifier, and output has no authority. The worker and the executor observe and never judge; a producer's
passedflag, printed control text or exit claim never changes admission or a Verdict; hand-built records never qualify; Opus builds, Astra verifies. - Products stay isolated. One repository binds to one Product; a Candidate manifest includes its repository identity; guidance is never copied between Products; the executor denies reads of another Product's files; usage is attributed once and never moves; the Product gate keys every record, ID and pin by Product, so one Product cannot integrate with another's Candidate, proof or store; Service access (increment 1) binds each repository and vault to one Product and hashes the Product into every reference id.
- Everything is bounded; input limits refuse rather than truncate.
JOURNAL_LIMITS,EXECUTION_LIMITS, worker and executor bounds,REPOSITORY_LIMITS,PORTFOLIO_LIMITS,GUIDANCE_LIMITS,DASHBOARD_LIMITS,REPOSITORY_ASSURANCE_LIMITS,GATE_LIMITS,RECORD_LIMITS,SCHEDULE_LIMITSand (Service access, increment 1)ACCESS_LIMITSbound what modules accept, and oversized input is refused, not cut short; so doesDISCOVERY_LIMITS(lifecycle increment 2), for fetched bodies, items and Captures. Presentation is capped instead: the worker and the executor keep a bounded prefix of process output, report the overflow and request termination (local-worker.ts:601-612,confined-executor.ts:1022-1031); diagnostic and error messages are shortened to fixed lengths (for exampleDASHBOARD_LIMITS.maxMessageLength); an attention item lists at mostMAX_WINDOWS(5) source windows. A Budget is never extended; recovery loops have round counts; there are no leases, heartbeats or takeover by time. - No human gate for self-solvable work. Attention infers nothing: empty guidance, unresolved verification, dirty files and zero counts make no item;
ownerRequiredneeds a source that names the owner and an exhaustion; a missing fact is never an owner approval request. - Never weaken tests, acceptance or pins. Drift blocks dispatch by design; never re-pin silently; never relax a
corruptcheck; a stored-shape change is a protocol revision, not an edit in place; a reused command ID with other content is a conflict, not an update. - Validate before writing, fail closed, and never adopt, recreate or repair silently. Validation order is module-specific: modules check input before writing, but journal
openconfigures WAL before it detects missing tables (journal.ts:161-162,:354-357; see command journal). Missing or replaced fences and repository roots are refused, never recreated, and the executor quarantines a changed namespace. For journals,openExistingandopenReadOnlyrefuse a missing file whileopenmay create one, and storage identity is not fenced: a valid replacement journal is accepted. Cleanup is non-recursive and anything uncertain is quarantined and reported. - Local proof is not release, and neither is customer value. Fixture recovery is not customer-release recovery; a merge, an upload, a screenshot or a green suite establishes nothing about availability or benefit; all 27 epics stay open until their own evidence closes them.
- Plumb is editorial. The character belongs in docs, onboarding and calm empty states; in the dashboard the mark sits beside the wordmark and Plumb appears only on an empty Portfolio that a successful current read confirms is calm (
calmEmptyPortfolioinapps/dashboard/src/brand.ts); failures, security, destructive actions, blockers, privacy, spend, uncertain effects, Verdicts and Assessments use plain words, no character, no colour-only meaning (brand guide).
6. Trust scope summary
Condensed from the module references. Every established item holds for trusted local code on one macOS arm64 host with Node 26.8.1, a local POSIX filesystem and process-crash tests.
Established locally
- Atomic, idempotent journal commands under real process races and SIGKILL on either side of a commit;
openExistingandopenReadOnlyrefuse foreign, corrupt, empty or missing stores (openrefuses foreign or unknown schemas but creates or initialises a missing or empty one); read-only inspection that initialises nothing. - Content-addressed Evidence that survives a killed writer; Verdicts that producer flags and hand-built records cannot forge.
- One owner per Attempt under racing claims; stale reports refused; durable cancellation decided by commit order alone.
- Real bounded fixture processes with an explicit environment; fence-held admission; drift refusal; quarantine instead of claimed cleanup.
- Recovery from controller death at every protected boundary within the pinned Budget, including actual CLI crash recovery, with one fresh payload per epoch and no resurrection of terminal Runs.
- A stateless sandbox boundary with tested denials of writes, foreign reads, network, subprocesses and signals, judged from host-side evidence with negative controls.
- Exact sealing and export with fail-closed handling of corruption and forged manifests; one integration invocation per intent across repeats, concurrent processes and kills, with truthful unknowns and read-only reconciliation; a native
HEAD.locktolerated, never taken. - Metadata-only registration of six local Products with checkouts left unchanged; globally deduplicated usage; exact, partial and unknown Counts that never collapse to 0.
- A loopback-only dashboard whose reads never write or run Git, with Host, Origin and token guards, two guarded commands, and checks at desktop and 390 px widths; decorative brand placement tested by the root suite and the dashboard package's rendered read states (no receipt, and no browser check of it is recorded).
- Revisioned guidance with exact replay, stale refusal and immutable pins; source-derived attention with no inferred gates and no resolve route.
- Service access increment 1 (test-made fixtures only; provisionally accepted — Fable 5.1 (max) round 1; Astra review pending (Codex usage limit until 2026-10-03 18:00), as above): names-only discovery that opens no deny-listed path, reads no line inside a workflow or env-template value and no text inside a manifest's string, an expression's literal or a comment as a name, records no path, name or identifier shaped like a credential, and, for every position and form its canary, adversarial and regression suites plant, records no value in any journal byte, event, view, answer or error (evidence for those forms, not proof for every construct); absence only from a complete, authenticated, non-empty listing and a use that shows where the name resolves (never from an unknown environment, a reusable workflow or a file not scanned whole), and an environment job's name resolved only through a conclusive listing of its environment; exclusive bindings under racing processes, one repository per Product; readiness that is presence, never validity, and says when only the owner can act.
- At module level, with no composition: Product gate admissions linearised against revocation in real processes, ownership passed only by fence proof, integration refused unless its Verdict re-derives inside the decision and its Candidate stays inside the Slice's scope, separate allowance counters and refusal of corrupt state; repository-scope Verdicts over store-written records that refuse fixture proof and omitted or forged records; create-once delivery records.
Not established
- Power-loss durability, other platforms or hosts, network filesystems, multiple hosts, CI, remote or production reliability.
- Sandboxing of arbitrary code: the fence and worker are not sandboxes; the executor has no hard RAM, disk or CPU-time stop and does not reclaim an orphan after controller loss; Node's permission model is defence in depth only.
- Authentication or authority: collectors,
confirmedByand provenance are attribution; anyone with write access to the journal, Evidence store, fence directory or repository root can forge consistent records; the Product gate's grants, revocation and owner exist but nothing composes them, its proofs are re-derivation over trusted local files, and it relies on the caller's fence check. - Outbox delivery, schema migrations, retention or compaction; background recovery or any automation; blocker rechecks; automatic collection of any source.
- Any provider adapter qualification (both Producers transports are unavailable), remote spend accounting, planner, or consumer of guidance pins beyond the Dashboard's own reads and edits.
- Any live credential source: no reader lists a repository's secrets or reads a vault, no registration runbook or broker exists, and Service access's bindings, declarations and pins are attribution, not authentication.
- A Verdict on a real Slice's repository Candidate (the
local-repositoryjudge and the collector exist and are accepted, but nothing composes them), an Assessment of a real Slice, Outcomes, releases, customer availability or customer value. - Any lifecycle module other than the schedule (implemented on fixtures, independently reviewed, Astra round 4 PASS, and composed by nothing) and Discovery sources (accepted locally on independent review, Astra round 5 PASS, but tested offline only: its HTTPS transport has never fetched a real page; composed by nothing); the
codex-clitransport or its audit, a qualified subscription route for either provider, a LaunchAgent, sign-in from launchd, the owner's signed schedule commission, and any scheduled discovery or post-development run.
7. How agents work here
Commands (Node 26.8.1, npm 11; dependencies are already installed; the root has no runtime dependency, and apps/dashboard has its own manifest and lockfile that the root suite never needs):
npm run check # strict typecheck + node:test suite (the final stage runs it; do not run mid-task unless told)
npm --prefix apps/dashboard run build # browser typecheck + Vite bundle; restart the server afterwards (assets load at startup)
npm --prefix apps/dashboard run check # build, then the dashboard package's own tests (apps/dashboard/test)
node src/dashboard-server.ts --journal <local-portfolio-journal> --port 4317 # loopback only; check whether a server already owns the port
npm run factory -- verify pass --run-id demo-pass --state-dir .factory # fixtures: pass, fail, hang, noisy; then inspect / cancel / recover
rm -rf site && npm run docs # regenerates site/ from docs/ (the build only creates files; it refuses a differing site/); commit site/ with any docs change (test/docs-site.test.ts fails on drift)
Focused suites per module are listed under each reference's Changing it safely. Test fixtures use disposable journals and repositories, never <local Portfolio journal> or a registered checkout. Bulky logs and regenerable output go under <local evidence archive>, never /tmp.
Worktrees. Parallel producers work in separate worktrees under <home directory path>, each on its own branch from main. Never edit the main checkout or a worktree another coordinator owns; touch only the files assigned to you; node_modules is symlinked, so install nothing. Preserve dirty work, <local Portfolio journal> and the ignored evidence under <local evidence directory>. Do not commit unless told.
Review protocol. Opus 5.5 implements (exact claude-opus-5-5; report the effort actually used and never substitute a model silently). Fable 5.1 collaborates on documentation, review and composition. Astra (gpt-6-astra) verifies independently, for example codex exec -m gpt-6-astra -c model_reasoning_effort=high ...; the accepted reviews in the build review record Astra at xhigh. Prove each component before composing dependants, and refine an interface when an observed failure reveals a weakness. A module is complete only when independent verification, its docs/agents/<slug>.md reference and its docs/guide/<slug>.md page all exist and the docs site is rebuilt.
Receipts. Each acceptance has a receipt under docs/*-receipt.json, but they are not uniform. Some pin source and test hashes directly (files or final_sources); some hash only review and browser evidence (evidenceSha256, often files under the ignored <local evidence directory>); guidance relies on a source-hash list that is not in Git. Before saying a module's current files are covered, compare its receipt's actual coverage with the files now; each module reference says which receipt still matches. Any edit to a covered file makes that coverage stale: get a fresh independent review, add a new receipt (never rewrite hashes in an old one), and update the build review row, the module's narrative section and its references. Keep old receipts as history.
Local-only authority. Only the coordinator pushes verified main to the private origin; CI runs deterministic checks only, never a model, sign-in or secret. No production change, public release, customer account, advertising or broader credential. Registration permits metadata inspection, not delivery. Do not extract OAuth credentials, infer Platform API access from a sign-in, or substitute a transport or model. Resolve self-solvable blockers within existing authority and keep independent work moving; escalate only a genuinely unavailable decision, access or resource, with what was tried.
Writing. British spelling, the one language above, and the brand voice: warm and brief where calm, plain where it matters. Never overclaim: say accepted local scope, say what is not established, keep unknown distinct from zero, and never call an epic complete, a release safe or a customer helped without the evidence.
8. Design language
Read this before building or changing any UI or docs page. The design system is Factory · Plumb (version 1790613544-ec35), iterated in Claude Design; its link is in the Notion status, not in this repository. docs/brand/tokens.json is the canonical copy of its tokens, byte for byte as exported; design language says where each token goes and how to re-sync.
- Tokens only. Use the token names and values (
--ink,--blue,--space-4,--radius-md,--rail-width,--focusand the rest). Add no colour, family, type size, spacing, radius, shadow or layout size that is not a token, and never edittokens.jsonby hand: change the design system, then copy its export over. The design system has no token for a reading measure or a minimum width yet; the docs site names its few such measures once (SITE_MEASURESin the builder), and its only other lengths are 1 to 3 px lines and offsets. - Light only. One theme. Add no dark scheme and invent no dark palette until the design system has one.
- Colour. Flat surfaces: no gradients, glow, texture or raised shadows.
blueis for links and the one primary action per view.ok,warnandproblemmean status only, on their status grounds, always beside a word and never in artwork;okis for icons and borders, not text.rail,ruleandselectare decorative, never the only boundary or state. - Type. The
sansandmonosystem stacks, no font files; weights 400, 500, 650 and 750 only; the named type styles (page-titleonce per view,body,supporting,codeand the rest). The wordmark is live text, never an image. - Space and shape. Spacing
space-1tospace-12(4, 8, 12, 16, 24, 32, 40, 48 px) with nothing in between; radiiradius-sm,radius-mdandradius-lg(4, 6, 8 px), and nothing is a pill. Controls arecontrol-height; targets are at leasttouch-min. At 1280 px and below the rail is 224 px and the inset 32 px; at 960 px and below the rail becomes a top bar and the inset 20 px. - Focus. Every control shows the
focusring, the only shadow, on keyboard focus; links and linked rows show a 3 pxblueoutline. - Status. Always a word, never colour or an icon alone. Unknown is an em dash with its reason, never zero.
- Plumb. Docs, onboarding and calm empty states only, at
illustrationsize, below the words, never replacing them, never clickable, with empty alt text when the words carry the meaning. Never in tables, metrics, navigation (the mark excepted), decisions, blockers, failures, spend or unknowns. The mark is never belowmark-min, and the favicon staysfavicon.svg. Module plates are shown whole, never cropped; nicknames are captions only. - Voice. Warm, brief and curious, in British spelling and the Factory's domain words; plain words for limits, status and anything that matters; no emoji, exclamation marks or hype. The say-and-not table is in the brand guide.
How parity is tested. test/design-tokens.test.ts fails when tokens.json stops being one light theme with system fonts, and reads both stylesheets and the docs' drawings with allow-lists, so a value it does not know fails. The allow-lists cover colour, type, radius, spacing, shadow and outline; custom properties and var(); resets; at-rules; the responsive values at 1280 and 960 px; and the drawings' elements and fills. The design language lists the rules; scripts/render_docs.py draws the diagrams from tokens.json. Its mutation cases show each check failing on altered copies. test/docs-site.test.ts checks the site's status words, notices, Plumb placement and whole plates on every page. After any UI or docs change, run node --test test/design-tokens.test.ts test/docs-site.test.ts and rebuild the site.
Where it is iterated. Visual changes start in Claude Design (link in the Notion status) and reach this repository only through a re-sync of tokens.json, followed by the stylesheets that use it.
