Skip to content

Prototype specs

Every prototype should ship a spec: a small set of documents next to the code that lets any agent or developer understand the product and rebuild it as a real application. This page defines that harness: which files exist, what each one contains, where its facts come from, and which parts are generated from code. The decision is recorded in ADR-019.

The layout follows the per-module and per-feature tiers of the DeepWorkPlan documentation standard (§2 for AGENTS.md, §4 and §4.1 for nested docs/). A prototype is a self-contained sub-app with its own contracts (schema, policy, scenarios), so it qualifies for its own AGENTS.md and docs/ folder.

Status

PartState today
This standard (files, sections, sources, markers)Defined (this page)
Hand-written spec files in a prototypeCan be written now, by hand, following this page
`protoyard spec <slug\--all> [--write\
protoyard validate spec check (--strict-spec)Available. Missing spec files warn (fail with --strict-spec); stale generated regions and invalid markers always fail.
Statically importable route table (src/routes.ts) for SCREENS.mdAvailable: defineRoutes in @protoyard/renderer-svelte; crm-demo and the dashboard template use it. Other prototypes still declare routes in src/App.svelte.
Fact extraction for generated regionsAvailable: inspectSpec() in packages/cli/src/lib/inspect.ts runs the definition in a child process (inspect runner spec mode); protoyard spec renders from it.
Spec skeletons in templates (protoyard new)Available. Every template ships AGENTS.md and docs/ (TODO prompts, plus the template's description under Problem). protoyard new retitles them for the new prototype and refreshes the generated regions for its slug; if the definition cannot load yet (before pnpm install), new still succeeds and says to run spec <slug> --init --write.

Start a spec with pnpm protoyard spec <slug> --init, write the prose, and keep the facts current with --write (--check in CI or review). Update this table when the remaining tooling ships.

The files

prototypes/<slug>/
├── AGENTS.md              lean entry point for agents: what it is, rules, commands, doc index
├── README.md              short human intro: what it is, how to run it, links to docs/
└── docs/
    ├── SPEC.md            problem, users, capabilities, non-goals, honest boundaries, rebuild path
    ├── DATA_MODEL.md      tables, keys, indexes, relations, schema versions and migrations
    ├── FLOWS.md           user flows, each mapped to a service, a capability and the tables it writes
    ├── SCREENS.md         every route: purpose, data shown, actions, states, required capability
    └── PERMISSIONS.md     roles × capabilities matrix, record-level rules, personas

All six files are required for a prototype with "status": "ready". Others may start with AGENTS.md and docs/SPEC.md. The prototype README.md stays short and links into docs/ instead of repeating tables.

AGENTS.md (per prototype)

The same three roles as the root AGENTS.md, scoped to one prototype and kept under about 80 lines:

  1. Index: one line per docs/ file.
  2. Rules: only what is specific to this prototype (for example "money is

integer cents in accounts.balanceCents" or "reps edit only their own deals"). Link to the root rules; do not copy them.

  1. Quick commands: pnpm protoyard dev <slug>,

pnpm vitest run prototypes/<slug>, pnpm protoyard validate <slug> --build, pnpm protoyard export <slug>.

docs/SPEC.md

SectionContentSource
ProblemThe product problem in two or three sentencesHand-written
UsersWho uses it, mapped to the prototype's rolesHand-written
CapabilitiesWhat a user can do, one line each, linked to FLOWS.mdHand-written
Non-goalsWhat the prototype deliberately does not doHand-written
ScenariosCatalogue: id, name, group, description, seed, densityGenerated (scenarios)
Honest boundariesEverything simulated, named as a simulation (see below)Hand-written
Rebuild as a real appHow each simulated part maps to real infrastructure (see below)Hand-written
Manifest factsid, slug, platform, category, default scenario/theme, storage namespace, schema versionGenerated (manifest)

docs/DATA_MODEL.md

SectionContentSource
OverviewThe domain in words, and why the model looks this wayHand-written
TablesPer table in the latest version: primary key and indexesGenerated (tables) from database.versions in src/database/schema.ts
Schema historyEach version and the tables it adds or changesGenerated (versions)
RelationsForeign keys and cardinalities (an ER sketch is welcome)Hand-written: Dexie store strings do not declare relations
Field notesUnits (integer cents, ISO dates), enums, invariantsHand-written, from the row types and integrity rules
MigrationsWhat each upgrade does to existing rowsHand-written

Reserved platform tables (py_*) are listed once, as platform tables, not as domain tables.

docs/FLOWS.md

Entirely hand-written: there is no reliable way to infer intent from code. Each flow is a short numbered list that ends in the service layer:

### Move a deal to another stage
1. Pipeline → drag the card, or "Move to…" on the card.
2. Service: `deals.moveStage(id, stage)` in src/domain/services.ts
3. Capability: `deals:update` (reps: own deals only)
4. Writes (one transaction): `deals` (stage, probability, lostReason, closedAt, updatedAt), `activities` (a stage-change entry)
5. Failure states: permission denied, simulated offline (nothing written)

Every mutation in src/domain/services.ts should appear in at least one flow. That is the path agents follow when they rebuild the app: UI → service → session.assert → zod → environment.run → db.transaction.

docs/SCREENS.md

SectionContentSource
Route listPath, title, required capabilityGenerated (routes) once routes are importable; hand-written until then
Per screenPurpose, data shown, actions, empty / loading / error / denied statesHand-written
ShellDevice or host shell and viewport the prototype is designed forHand-written, from preview in protoyard.json

docs/PERMISSIONS.md

SectionContentSource
Capability matrixRoles × capabilities (✓ / blank)Generated (capability-matrix) from policy.roles
Record-level rulesRule names and what they meanRule names generated (rules). Meaning hand-written: rules are functions.
PersonasThe personas of the default scenario and their rolesGenerated (personas)
Boundary"Simulated permissions: UX only, not security."Hand-written, always present

Generated regions

A generated region is fenced by an opening and a closing marker with the same id:

<!-- protoyard:generated:capability-matrix -->
| Capability | owner | admin | rep | viewer |
| --- | --- | --- | --- | --- |
| `deals:update` | ✓ | ✓ | ✓ |  |
<!-- /protoyard:generated:capability-matrix -->

Rules:

  • Text between the markers belongs to the generator. A refresh replaces

it, and the check fails when it differs from what the code produces.

  • Text outside the markers is hand-written and is never touched.
  • Markers are not nested, and each id appears at most once per file.

Unbalanced or duplicate markers are an error, not something to repair silently.

  • Generated content is deterministic: rows sorted by a stable key, no

timestamps, no machine paths. Regenerating twice gives identical bytes.

  • Facts come only from the prototype definition (src/prototype.ts and what

it imports), loaded in a child process as validate does today. Nothing is scraped from compiled output.

Region ids:

IdFileFrom
manifestSPEC.mdprotoyard.json
scenariosSPEC.mddefinition.scenarios (resolved against presets)
tablesDATA_MODEL.mdthe live store set after every entry of definition.database.versions
er-diagramDATA_MODEL.mdthe same tables as a Mermaid erDiagram; relations inferred from indexed …Id fields that match a table name
versionsDATA_MODEL.mdevery entry of definition.database.versions
capability-matrixPERMISSIONS.mddefinition.policy.roles
rulesPERMISSIONS.mdkeys of definition.policy.rules
personasPERMISSIONS.mdpersonas generated by the default scenario
routesSCREENS.mdthe route table, once it is importable outside Svelte
commandsAGENTS.mdthe slug (the standard command list)

Rebuild kit

pnpm protoyard spec <slug> --bundle writes artifacts/<slug>/<slug>-rebuild-kit.zip: one file to hand to a team or an agent building the prototype as a real app. It contains a generated REBUILD.md (contents with sizes and sha256, how to use the kit, and a starting prompt that asks before choosing any vendor), then AGENTS.md, README.md, docs/**/*.md, protoyard.json, src/prototype.ts, src/routes.ts, src/database/schema.ts, and the .ts, .md and .json files under src/domain/ and src/scenarios/ (tests excluded). Nothing outside that allow-list is read: no screens, components, assets, tests or environment files. The command prints the ZIP's sha256. The same files give the same bytes.

The kit is refused, and nothing is written, when the spec is missing or stale, when a symlink leaves the prototype folder or sits on the allow-list, or when a file matches the repository's public-hygiene rules (secret shapes, personal paths, private emails). Findings are reported as path:line rule, never with the matched text. The kit lives under artifacts/, never in dist/, so it is not part of any hosted build.

Honest boundaries

SPEC.md names every simulated part plainly. A prototype has no backend:

ConcernIn the prototypeNever claim
DataIndexedDB in the visitor's browser, per origin. Nothing is shared between visitors.Sync, multi-user collaboration, backups
IdentityPersonas you switch between. Simulated sign in, sign up, SSO and MFA when a prototype uses them (ADR-018).Authentication, account security
PermissionsCapabilities checked in services (session.assert)Authorization or security
PaymentsInteger-cent amounts and simulated billing statesCharging anyone, PCI scope
Network and AISimulated latency, failures and offline mode (environment.run). Scripted or simulated assistants.Real API calls or a real model
TimeThe simulation clock and timelineReal schedules or background jobs

Rebuild as a real app

This section of SPEC.md is the hand-off to a team or an agent building the production version. For each row below, it states what the prototype does and what a real implementation needs. It never names a vendor as if it were already chosen:

Prototype pieceReal counterpart to design
src/database/schema.ts tables and indexesServer database schema. Ids, indexes and integer-cent money carry over.
Schema versions and upgrade functionsServer migrations
src/domain/services.ts (zod inputs, session.assert, transactions)API endpoints or server actions with the same inputs, checks and transaction boundaries
policy.roles and rulesServer-side authorization, enforced on every request
Personas and simulated sign-inA real identity provider and session management
environment.run failure modesThe error, retry and offline states the real UI must still handle
Scenarios and generatorsSeed data for staging, demo accounts and test fixtures
Screens and flowsThe UI to keep. The states listed in SCREENS.md are the acceptance list.

Writing a spec

  1. pnpm protoyard spec <slug> --init creates AGENTS.md and the docs/

files that are missing (existing files are never overwritten), with TODO prompts for the prose and the generated regions already filled.

  1. Replace the TODOs. Move long tables out of README.md into docs/ and

link to them.

  1. After changing the schema, policy, scenarios or routes, run

pnpm protoyard spec <slug> --write; --check fails (exit 4) while a region is stale.

  1. Check that every relative link resolves and every command shown runs.

The crm-demo README already contains most of the facts a first spec needs (screens, routes, data model).