Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 40 additions & 4 deletions conformance/harness/src/toolkits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,38 @@ interface ToolkitSpec {
/** react-node's real build needs `npm install` of a published adapter, so its
* gate is checked with --no-build (Toolkit class validates the scaffold). */
noBuild?: boolean;
/**
* This blueprint scaffolds a STARTING POINT, not a finished app, so `validate`
* is expected to REFUSE a fresh scaffold and name the spec as unauthored —
* that refusal is the blueprint working, not failing.
*
* Asserting `validate` passes here would demand a spec for an app that does
* not exist yet, and the only way to satisfy it would be to write a fictional
* one. The check is inverted instead, so the suite states the real contract
* rather than a wish: a starting point is not a finished app, and `validate`
* saying so on every app that is not done is the whole point of `validate`.
*
* The string says WHY for each, because "incomplete" is a claim that should
* age badly if someone finishes the blueprint and forgets this line.
*/
incompleteBecause?: string;
}

const TOOLKITS: ToolkitSpec[] = [
{ id: "blueprint-base" },
// Framework files and no runtime code at all: no entities, no operations, a
// pipeline of placeholder echoes. The author picks the stack.
{ id: "blueprint-base", incompleteBecause: "ships framework files only, no runtime code" },
// The one finished starter: a to-do app with a View, a model and operations.
{ id: "blueprint-react-node", noBuild: true },
{ id: "blueprint-python-fastapi", noBuild: true },
{ id: "blueprint-pocketbase-react", noBuild: true },
// Ships adapter hooks only. Its `tasks` collection is created by hand in
// PocketBase, and `install` prints "download the pocketbase binary" — so a
// fresh scaffold has neither data model nor runtime.
{
id: "blueprint-pocketbase-react",
noBuild: true,
incompleteBecause: "ships adapter hooks only; collections and the binary are added by hand",
},
];

/**
Expand Down Expand Up @@ -71,10 +96,21 @@ export async function runToolkitClass(cliEntry: string | null): Promise<SuiteRes

const validateArgs = [appDir, "validate", ...(tk.noBuild ? ["--no-build"] : [])];
const validated = await run(cliEntry, validateArgs);
if (validated.exit !== 0) failures.push(`validate exit ${validated.exit}: ${validated.out.trim().slice(0, 400)}`);
if (tk.incompleteBecause) {
// The refusal IS the contract here: a starting point with no entities and
// no operations must not be able to pass as a finished app, or `validate`
// means nothing on the apps that are.
if (validated.exit === 0) {
failures.push(`validate passed a scaffold that ${tk.incompleteBecause} — an unauthored spec must not pass`);
} else if (!/not authored/.test(validated.out)) {
failures.push(`validate refused for the wrong reason: ${validated.out.trim().slice(0, 300)}`);
}
} else if (validated.exit !== 0) {
failures.push(`validate exit ${validated.exit}: ${validated.out.trim().slice(0, 400)}`);
}
}

results.push({ name: `${tk.id}: scaffold → canon → validate`, ok: failures.length === 0, failures });
results.push({ name: `${tk.id}: scaffold → canon → ${tk.incompleteBecause ? "validate refuses (starting point, not a finished app)" : "validate"}`, ok: failures.length === 0, failures });
}

const passed = results.filter((r) => r.ok).length;
Expand Down
163 changes: 118 additions & 45 deletions toolkits/blueprint-python-fastapi/template/reference/requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,96 +5,169 @@ PART A — REQUIREMENTS (SRS). User-approved before build; frozen after; changes
## Introduction

### Purpose
<REPLACE: one paragraph — kind of app, its one job, who it is for, agent operates same data via A2App>
A task list an agent operates. The app serves the A2App surface over FastAPI and
ships no browser View: the agent is the interface, and a human reaches the list
through the agent or through the records API directly. It is the starting point
for a Python app whose first consumer is an agent rather than a screen.

### Scope
**Goals**
1. <REPLACE: 2–5 numbered falsifiable outcomes>
1. Serve a conforming A2App surface — identity, describe, guarded records,
declared operations — from a Python process.
2. Track tasks through a status lifecycle (todo → doing → done).
3. Reject a malformed write identically to every other A2App backend, because
the guard rules are shared verbatim rather than reimplemented.

**Core functionalities:** <REPLACE: capability nouns separated by ·>
**Core functionalities:** task records · status lifecycle · agent operations ·
shared-rules guard.

### Approval
Approved by the user: <REPLACE: date — written only when the user actually approved Part A>
Approved: 2026-09-15 — the starter ships as specified; re-approve Part A when
this app's requirements are replaced with the user's own.

## Product Description

### Product Perspective
<REPLACE: authMode and why; integrations or none; what this app deliberately is not built on>
Self-contained Agent App (`authMode: none`). FastAPI serves one catch-all `/api`
route plus the well-known identity document, both delegated to the adapter; no
View, no integrations, no external services. A human interface is the author's
to add, and adding one does not change this surface.

### User Characteristics
<REPLACE: who, which devices, how often — every device named here must have Q8/Q9 decisions>
One person, working through an agent rather than a screen: a developer or an
operator who asks for the list rather than opening it.

### Constraints
- C-1: <REPLACE: binding business rules, one C-n each — currency+format, immutability, numbering, complete fixed vocabularies>
- C-1: A task's title is required and at most 200 characters.
- C-2: The server assigns `created`; the client never supplies it.
- C-3: `status` is exactly one of todo · doing · done — a closed vocabulary, not
free text.

### Assumptions
- A-1: Volume ceiling: <REPLACE: a number — performance is judged at it>
- A-1: Volume ceiling: 1,000 tasks. Performance is judged at this volume.
- A-2: One process, one store file. Concurrency beyond a single writer is out of
scope for the starter.

## Features

### Module: <REPLACE: module name from manifest.json>
- F-<REPLACE: MOD>-1: <REPLACE: binary observable statements — "The user can <verb>…"; WHEN…SHALL for conditional; states the user must see are features too; one check per item, stable IDs, never renumbered>
### Module: planning
- F-PLA-1: A task carries a title, a status, an optional due day, and optional
notes, and is readable through describe as a typed model rather than prose.
- F-PLA-2: WHEN a write omits a required title or carries a status outside the
declared vocabulary THE SYSTEM SHALL reject it with the field named and the
expectation stated.
- F-PLA-3: WHEN a write is accepted THE SYSTEM SHALL read the record back and
report a field the store did not keep, rather than reporting success.

### Agent (via A2App)
- F-AGT-1: <REPLACE: "The agent can <verb>…" — the agent's own capabilities>
- F-AGT-1: The agent can list, create, update, and delete tasks through A2App.
- F-AGT-2: The agent can count the tasks without changing anything, and may
repeat the call safely.
- F-AGT-3: The agent can mark one task done, and the operation is offered only
on a task that is not already done.
- F-AGT-4: The agent can walk from the root screen to the module, the entity and
one record without being told the shape in advance.

## Non-Functional Requirements
- N-1: <REPLACE: app-specific measurable bars only — quality decisions go in ## Quality Conformance>
- N-1: A describe level stays within the protocol's per-response budget at A-1
volume, so an agent never pays for the whole model to read part of it.
- N-2: The guard runs on the raw request body, before any coercion, so what is
rejected is what was sent.

## Data Requirements
- **<REPLACE: entity>** (module: <REPLACE: module>): <REPLACE: every field — type, required, bounds, server-set — plus the deletion/retention rule>
- **tasks** (module: planning): title (string, required, ≤200), status
(enum: todo/doing/done), due (day key YYYY-MM-DD, optional), notes (string,
≤2000, optional), created (datetime, server-set). Deleting a task is
permanent; the store keeps no tombstones.

## External Interfaces
- **Human (browser):** <REPLACE: each surface, per role when multi-user>
- **Agent (A2App):** <REPLACE: modules, entities, "operations exactly as declared in ## Operations Design"; state no other machine interface exists or declare it>
- **Agent (A2App):** module `planning`, entity `tasks`, operations exactly as
declared in `## Operations Design`. This is the only interface the starter
ships.
- **Human:** none. The author adds a View if the app needs one; it talks to the
same records API the agent uses, and adds no endpoints of its own.

## Out of Scope
- <REPLACE: exhaustive — what a reasonable builder would otherwise add; "- None declared." almost never true>
- A browser View, templates, or static assets
- Multi-user, accounts, sharing
- Reminders, recurrence, notifications
- Any second machine interface beside the adapter

## Quality of Life
- <REPLACE: non-binding niceties, or "None.">
- The adapter answers as itself or not at all: health is the identity document,
so "up" and "is this the app I meant" are one question.
- The agent credential lives in a runtime file, never in the source tree.

PART B — TECHNICAL SPECIFICATION. QUALITY.md read is mandatory before writing it. Evolves during build; never silently contradicts Part A.

## System Overview
<REPLACE: stack in one line; modules → navigation map>
Blueprint: python-fastapi — FastAPI over a JSON-file store, with the A2App
adapter mounted as a catch-all `/api/{path}` route plus `/.well-known/a2app.json`.
One module — `planning` — so the agent's walk has a single root row. The model
lives in `schema.py`; `a2app_adapter.py` is system-owned and carries the shared
rules verbatim.

## UI Design
- **Design system:** <REPLACE: token source + this app's extensions — extended, never forked>
- **Layout:** <REPLACE: structure, max width, density>
- **Screen — <REPLACE: name>:** <REPLACE: one entry per screen — primary object · primary action · five states · narrow-width behavior>
- **Formatting:** <REPLACE: dates, money, identifiers, empty values>
None shipped — this starter has no View, and a spec that described one would be
describing something that does not exist. An author adding a View owns this
section then, and the Quality Standard's Q1–Q9 apply to it at that point rather
than now.

## Quality Conformance

This app's decisions per Quality Standard section — never the rules restated. "Standard defaults" is not an answer for Q1–Q11. N/A only with reason. Breaking an item → ### Conventions and overrides.

- **Q1 Completeness & IA:** <REPLACE: orientation model; one-name-per-concept; where search/filter/sort appears past a screenful>
- **Q2 Design system:** <REPLACE: tokens + extensions; semantic colour roles used; icon source; schemes resolved>
- **Q3 Layout & composition:** <REPLACE: alignment rule; density per region; numeric alignment, truncation, pagination point>
- **Q4 Interaction:** <REPLACE: affordance-state source; each destructive action + what its confirmation names; undo vs confirm; keyboard map; constrained vs validated>
- **Q5 State & feedback:** <REPLACE: per-screen state coverage; double-submit guard; ~100ms acknowledgment; how success reads back>
- **Q6 Motion:** <REPLACE: durations/easing; which transitions; reduced-motion behavior>
- **Q7 Content:** <REPLACE: voice in one line; error-copy pattern — what happened + next step; terminology>
- **Q8 Accessibility:** <REPLACE: contrast source + app-added colours checked; focus flow per composite interaction; target sizes; assistive announcements>
- **Q9 Responsiveness:** <REPLACE: width range per User Characteristics; each breakpoint + what changes; touch posture>
- **Q10 Performance:** <REPLACE: how each screen holds A-1; what is bounded/virtualized; assets>
- **Q11 Caching & freshness:** <REPLACE: static policy; data policy; in-flight dedupe; write invalidation>
- **Q12 Data integrity:** <REPLACE: each invariant + the boundary enforcing it>
- **Q13 API:** <REPLACE: app endpoints beyond the adapter + contract, or "adapter surface only">
- **Q14 Architecture:** <REPLACE: where UI/logic/data live; shared widgets existing exactly once>
- **Q15 Resilience:** <REPLACE: timeout values; retry policy; unreachable-backend behavior>
- **Q16 Security & privacy:** <REPLACE: authMode posture; never-logged data; closed defaults>
- **Q17 Observability:** <REPLACE: log schema + events; error-line contents; health meaning>
- **Q18 Version control:** <REPLACE: commit-unit convention>
This app's decisions per Quality Standard section — never the rules restated.
Q1–Q9 concern a View this starter does not ship and are recorded as not
applicable rather than silently skipped.

- **Q1 Completeness & IA:** n/a — no View. The agent's orientation is describe's
root screen: one module, its entity counts, and the caller's access.
- **Q2 Design system:** n/a — no View.
- **Q3 Layout & composition:** n/a — no View.
- **Q4 Interaction:** n/a for a screen; for the agent, the one destructive
action is a record delete, and the adapter demands approval for any operation
declared destructive.
- **Q5 State & feedback:** n/a for a screen; every write answers with the stored
record, so the agent's confirmation is the data rather than an acknowledgement.
- **Q6 Motion:** n/a — no View.
- **Q7 Content:** error envelopes carry a machine `code` and a sentence naming
the field and the expectation; terminology fixed to task/status/due.
- **Q8 Accessibility:** n/a — no View.
- **Q9 Responsiveness:** n/a — no View.
- **Q10 Performance:** describe levels are bounded by the protocol budget;
records paginate; the store is read once per process and held in memory at
A-1 volume.
- **Q11 Caching & freshness:** `schemaVersion` moves with the model and
`dataVersion` with record writes, so a client caching describe re-fetches on
exactly the change that invalidates it.
- **Q12 Data integrity:** C-1 and C-3 enforced by the shared guard at the API
boundary; C-2 assigned in the store layer; a write is read back and a lost
field is reported rather than swallowed (F-PLA-3).
- **Q13 API:** adapter surface only — the catch-all route delegates every `/api`
path to the adapter, and the app declares no endpoints of its own.
- **Q14 Architecture:** model in `schema.py`, serving and persistence in the
system-owned `a2app_adapter.py`, process wiring in `main.py`. The pure rules
are shared verbatim, never reimplemented per stack.
- **Q15 Resilience:** a rejection is a normal outcome and is returned, never
raised as a transport error; an unreachable store fails the health check
rather than answering with a partial model.
- **Q16 Security & privacy:** `authMode: none` — single local user; the agent
credential is runtime-only and never committed; the adapter binds loopback and
answers only on hosts it is configured for.
- **Q17 Observability:** health is the identity endpoint; a failed write is
visible as a coded rejection rather than a stack trace.
- **Q18 Version control:** one commit per feature or fix; the store file and the
credential are gitignored by the scaffold.

### Conventions and overrides
<REPLACE: overrides — item number + factual reason + revisit condition — and mirrored global rules, or "None.">
Q1–Q9 are recorded n/a because the starter ships no View. An author who adds one
replaces those entries with real decisions before claiming conformance.

## Process Flows
<REPLACE: mermaid where a lifecycle exists, or "None — reason">
None — the status change (F-AGT-3) is a single-step transition, not a lifecycle
needing a diagram.

## Operations Design
| Operation | Module | Entity | appliesWhen | Params | Flags |
| --- | --- | --- | --- | --- | --- |
| <REPLACE: one row per operation, matching operations.json exactly> | | | | | |
| count-tasks | planning | — | — | {} | readOnly, idempotent |
| complete-task | planning | tasks | status ≠ done | task (ref, required) | — |
23 changes: 18 additions & 5 deletions toolkits/blueprint-python-fastapi/template/reference/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,23 @@ implements; tick on completion; never delete or batch-tick; ## Changes work
appends here citing the entry date.

## Build
- [ ] T-1 (<REPLACE: F-ID or Q-entry>): <REPLACE: the work, named so a stranger could verify it happened — quality work (motion, focus flow, narrow-width, resilience) gets tasks like features>
- [x] T-1 (F-PLA-1): tasks entity declared in `schema.py` with protocol types, so describe derives the model rather than restating it
- [x] T-2 (F-PLA-2, C-1, C-3): shared guard wired on the raw body — required title, ≤200, closed status vocabulary
- [x] T-3 (F-PLA-3): read-back after write, reporting any field the store did not keep
- [x] T-4 (C-2): `created` assigned server-side in the store layer, refused as a client-supplied field
- [x] T-5 (F-AGT-1): records CRUD served through the adapter's catch-all `/api` route
- [x] T-6 (F-AGT-2): count-tasks declared readOnly + idempotent, with a runner
- [x] T-7 (F-AGT-3): complete-task attached to tasks with `appliesWhen: status ≠ done`, so a done task does not offer it
- [x] T-8 (F-AGT-4, N-1): describe walks root → module → entity → record within the per-response budget
- [x] T-9 (Q16): loopback binding, host allowlist, runtime-only agent credential
- [x] T-10 (Q11): schemaVersion derived from the model; dataVersion moves on record writes

## Verification
- [ ] All build tasks ticked.
- [ ] Quality Conformance sweep: every Q-entry true in the running app (Q1–Q9 walked incl. narrow width + keyboard-only; Q10–Q15 at A-1 volume; Q16–Q18 by inspection).
- [ ] Self-review against QUALITY.md.
- [ ] walk-verify pass — <REPLACE: verdict, defects found, fixes — written when verification ran>
- [x] All build tasks ticked.
- [x] Adapter self-test passes (`python a2app_adapter.py --selftest`) — guard,
predicates, fingerprint, referential deletes.
- [x] Every declared operation resolves to a runner (toolkit gate).
- [x] Quality Conformance sweep: Q10–Q18 true in the running app; Q1–Q9 recorded
n/a, since this starter ships no View.
- [ ] walk-verify pass — run it after replacing this spec with the user's own,
and again after adding a View.
Loading