Skip to main content
The PRD lives in prd.json at the app root, alongside the test files that back it:
Every entry is bound 1:1 to a test: the entry names the test file, and the test’s title must equal the entry id. The build fails on an entry with no test, so there is no way to add an unverified claim.

Features vs. API access

The PRD view presents the app through two top-level sections:
  • Features — what the app does: user-visible flows, business behaviors, side-effects, cross-service merges. Verified end-to-end in a browser and recorded.
  • API Access & Rules — the rules the backend enforces: who can call each endpoint, how each endpoint’s rows are scoped to the caller, and the invariants the boundary rejects on. Verified headlessly with real requests, in both directions: the permitted call succeeds and really changes the data, and the denied call is rejected.
The dividing line: is the requirement a rule the boundary enforces, or something the app does? Rules that deny an anonymous caller, filter another user’s rows, or reject a fourth task when three are active belong to API access. Behaviors that send the Slack message, compute the score, or render the dashboard are features, even when conditioned on ownership or permissions. A rule can appear in both, because the two sections verify different things: the API row proves the boundary can’t be bypassed; the feature recording shows what the user experiences. “Users only see their own tasks” is an API row (the query denies) and also a feature assertion (the screen omits).

Anatomy of a feature entry

Each feature belongs to a section and gets exactly one hero test: the canonical end-to-end flow a reviewer watches. Visible details of that flow (“the completed task shows a strikethrough”) are assertions inside the hero, not separate entries. Splitting them out would produce many near-identical videos. A feature may nest one level of children for what the hero flow doesn’t show:
  • A visual child — a distinct state or screen the hero never passes through: the empty state, the error state, the at-limit view. Each gets its own small recording.
  • A calculation child — the internal logic powering the feature (a score formula, tax math, a validation rule), where the input space is combinatorial and there is nothing distinct to watch. Verified as a table of checked values; see Verifying calculations.
A hero test verifies logic too: it asserts what’s on screen and what the flow left in the data, so “the deleted task is gone” is checked in both places. The split decides where the extra cases go. A case with something distinct to watch is a visual child; a case that’s one of many input combinations, where the runs would look identical and only the numbers differ, is a calculation child. A failing child fails its feature, even when the hero flow passed. Nesting stops at one level; a child that would need children of its own is a separate feature, and the build rejects a grandchild.

Anatomy of an access entry

The access side of prd.json carries one entry per backend procedure. Each entry declares a structured access contract: whether the caller must be signed in, which scopes are required, and how rows are scoped to the caller (own rows only, rows reachable through a relationship such as a reporting chain, rows in the caller’s org, and so on). The contract is a machine-checked decision table, not prose. The build derives from it the exact set of scenarios the entry’s test must cover (anonymous denied, wrong scope denied, the owner’s call works, another user’s doesn’t, each business rule rejects) and fails until every one exists. The plain-language “who can do this” text in the PRD view is generated from the contract, never hand-written. See Access verification.

Choosing where something goes