Full-Stack Wiring
A complete graph over a real monorepo: a NestJS and Prisma backend, a generated SDK package, and a React frontend, with requirements in docs/analysis/**/*.md.
This is the configuration the Evidence Graph benchmark ships in its template. Two things are dropped here: the long file-header comments, and the disabled: true every claim carries there. The template stages claims on one at a time during a run, while this page shows the finished graph; Adoption covers the staging.
Read it as a whole. The interesting decisions are about where a claim lives and what it refuses, not about glob syntax.
The chain
requirements docs/analysis/**/*.md (h2, h3)
cited by schema-models, api-operations, dto-types,
backend-tests, frontend-screens, frontend-journeys
schema prisma/schema/** (model)
cited by api-operations, dto-types
schema prisma/schema/** (column)
cited by dto-properties
SDK @ORGANIZATION/PROJECT-api (operation)
cited by backend-tests, frontend-hooks
hooks src/lib/*/hooks.ts (function)
cited by frontend-screens
screens src/components/*/*-page.tsx (function)
cited by frontend-journeysEach block is one evidence population and the claims that must acknowledge it. A requirement section has to be answered independently by six of them, because implementing it in the schema is not implementing it on the screen.
| Claim | Declared in | Hosts | Must cite | Catches |
|---|---|---|---|---|
schema-models | backend test config | Prisma models | requirements | something the spec says to persist and nothing stores |
api-operations | backend test config | controller functions | requirements, Prisma models | a missing feature, and a table nothing exposes |
backend-tests | backend test config | test functions | requirements, SDK operations | a published operation with no test |
dto-types | API package config | exported DTO types | requirements, Prisma models | a domain that never reaches the wire |
dto-properties | API package config | DTO properties | Prisma columns | a column that silently never ships |
frontend-hooks | frontend config | hook functions | SDK operations | an operation no client reaches |
frontend-screens | frontend config | page components | requirements, hooks | an API wired up with no screen behind it |
frontend-journeys | frontend config | E2E journeys | requirements, screens | a screen no journey walks |
Eight claims, thirteen claim-reference pairs, thirteen independent obligations. The benchmark’s todo subject holds 73 H2 and H3 sections, and six claims cite the requirements, so the requirement side alone opens 438 cells before the model, column, operation, hook, and screen obligations are counted.
What these thirteen obligations measure
Those same thirteen pairs are the edges the benchmark’s coverage score is folded from, so opening a Plain row below names the pair that broke in that codebase.
The Evidence arm is one row because every subject scored alike: a build carrying this configuration does not finish while a pair is open, so it has no edges to open onto. The Plain rows are the same four applications built without it, and their edges are where a codebase that looked finished was not. An operation nothing tests and a hook nothing renders are invisible to a type checker and to a reviewer reading a diff.
Backend
import { evidence } from "@ttsc/evidence";
import type { ITtscLintConfig } from "@ttsc/lint";
export default {
extends: "../lint.config.ts",
ignores: ["lint.config.ts"],
plugins: {
evidence,
},
rules: {
"evidence/singular": "error",
// Flip to "error" once every public-operation test is written; the stubs
// that remain then enumerate the work left to do.
"evidence/todo": "off",
"evidence/graph": [
"error",
{
claims: [
// The schema stores what the requirements say must persist.
{
name: "schema-models",
type: "prisma",
root: "..",
files: [
"prisma/schema/**/*.prisma",
"prisma/schema/exclude.schema",
],
evidenceExcludeCarriers: ["prisma/schema/exclude.schema"],
symbol: "model",
reference: {
type: "markdown",
root: "../../..",
files: ["docs/analysis/**/*.md"],
symbol: ["h2", "h3"],
},
},
// The operations realize the requirements and expose the schema.
{
name: "api-operations",
type: "typescript",
root: "..",
files: ["src/controllers/**/*.ts"],
evidenceExcludeCarriers: [
"src/controllers/CONTROLLER_EVIDENCE_EXCLUDE.ts",
],
symbol: "function",
reference: [
{
type: "markdown",
root: "../../..",
files: ["docs/analysis/**/*.md"],
symbol: ["h2", "h3"],
},
{
type: "prisma",
root: "..",
files: ["prisma/schema/**/*.prisma"],
symbol: ["model"],
},
],
},
// A test answers for the one published operation it proves.
{
name: "backend-tests",
type: "typescript",
root: ".",
files: ["features/**/*.ts"],
evidenceExcludeCarriers: ["features/TEST_EVIDENCE_EXCLUDE.ts"],
symbol: "function",
reference: [
{
type: "markdown",
root: "../../..",
files: ["docs/analysis/**/*.md"],
symbol: ["h2", "h3"],
},
{
type: "typescript",
package: "@ORGANIZATION/PROJECT-api",
files: ["src/functional/**/*.ts"],
symbol: ["function"],
noEvidenceExclude: true,
singleEvidencePerSymbol: true,
},
],
},
],
},
],
},
} satisfies ITtscLintConfig;Four decisions are worth reading.
Why the backend claims live in the test config. test/tsconfig.json compiles ../src together with the tests, so it is the only Program holding controllers and test functions at once. The package Program sees only src/, and a TypeScript claim can select nothing the owning tsconfig.json did not already supply.
root is relative to the package directory. .. is the backend package and ../../.. is the repository root, because every backend command runs from the package directory.
The exclusion carrier is named, so an exclusion cannot hide. Writing @evidenceExclude on a working model or controller is itself a compile error that names the file it belongs in. Reviewing what a claim discharged without building anything means opening one file.
The SDK reference refuses both escapes. noEvidenceExclude denies a published operation the sentence “not applicable”, and singleEvidencePerSymbol makes one test answer for exactly one operation. Without the second, a single omnibus test citing twenty operations would report the suite complete.
That reference is declared as a package rather than by path on purpose. A symbol nothing imports is absent from the Program by definition, and an unconsumed operation is exactly the symbol this obligation has to name, so the generated package is read from disk.
A test cites the accessor through its own import:
import * as api from "@ORGANIZATION/PROJECT-api";
import typia from "typia";
/**
* Validate that the generated health accessor reaches the running backend.
*
* @evidence {@link api.functional.health.get} Exercises the generated health operation.
*/
export async function test_api_health(
connection: api.IConnection,
): Promise<void> {
const healthConnection: api.IConnection = { host: connection.host };
const value: string = await api.functional.health.get(healthConnection);
typia.assert(value);
}API package
import { evidence, type ITtscEvidenceGraphConfig } from "@ttsc/evidence";
import type { ITtscLintConfig } from "@ttsc/lint";
export const graph: ITtscEvidenceGraphConfig = {
claims: [
// A DTO type answers to the requirement it serves and the table it
// represents.
{
name: "dto-types",
type: "typescript",
root: ".",
files: ["src/structures/**/*.ts"],
evidenceExcludeCarriers: ["src/structures/DTO_EVIDENCE_EXCLUDE.ts"],
symbol: "type",
reference: [
{
type: "markdown",
root: "../..",
files: ["docs/analysis/**/*.md"],
symbol: ["h2", "h3"],
},
{
type: "prisma",
root: "../backend",
files: ["prisma/schema/**/*.prisma"],
symbol: ["model"],
},
],
},
// A DTO property answers to the schema column it carries.
{
name: "dto-properties",
type: "typescript",
root: ".",
files: ["src/structures/**/*.ts"],
evidenceExcludeCarriers: ["src/structures/DTO_EVIDENCE_EXCLUDE.ts"],
symbol: "property",
reference: {
type: "prisma",
root: "../backend",
files: ["prisma/schema/**/*.prisma"],
symbol: ["column"],
},
},
],
};
export default {
extends: "../../config/lint.config.ts",
ignores: ["src/functional/**/*.ts"],
plugins: {
evidence,
},
rules: {
"evidence/graph": ["error", graph],
},
} satisfies ITtscLintConfig;The DTO claims sit here for the same Program reason: src/structures/ belongs to this package’s tsconfig.json. The schema they cite belongs to the backend, which is what root: "../backend" reaches.
dto-properties descends to the column. Every column has to be carried by some DTO property, so a field that quietly stopped shipping in the response becomes a compile error instead of a support ticket.
src/functional/** is ignored, because generated accessors are machinery rather than authored contract. They are still a reference population elsewhere; they simply owe nothing themselves.
Frontend
import { evidence, type ITtscEvidenceGraphConfig } from "@ttsc/evidence";
import type { ITtscLintConfig } from "@ttsc/lint";
const graph: ITtscEvidenceGraphConfig = {
claims: [
// The screens deliver the requirements a user can reach. The dev gallery
// is tooling, not delivery.
{
name: "frontend-screens",
type: "typescript",
files: [
"src/components/*/*-page.tsx",
"src/components/SCREEN_EVIDENCE_EXCLUDE.ts",
"!src/components/dev/**",
],
evidenceExcludeCarriers: ["src/components/SCREEN_EVIDENCE_EXCLUDE.ts"],
symbol: "function",
reference: [
{
type: "markdown",
root: "../..",
files: ["docs/analysis/**/*.md"],
symbol: ["h2", "h3"],
},
{
type: "typescript",
files: ["src/lib/*/hooks.ts"],
symbol: ["function"],
noEvidenceExclude: true,
},
],
},
// The journeys walk the requirements end to end, through the screens they
// cite.
{
name: "frontend-journeys",
type: "typescript",
files: ["tests/journeys/**/*.ts"],
evidenceExcludeCarriers: ["tests/journeys/JOURNEY_EVIDENCE_EXCLUDE.ts"],
symbol: "function",
reference: [
{
type: "markdown",
root: "../..",
files: ["docs/analysis/**/*.md"],
symbol: ["h2", "h3"],
},
{
type: "typescript",
files: ["src/components/*/*-page.tsx", "!src/components/dev/**"],
symbol: ["function"],
},
],
},
// The hooks deliver the published API. An operation no hook reaches is a
// missing feature.
{
name: "frontend-hooks",
type: "typescript",
files: ["src/lib/*/hooks.ts"],
symbol: "function",
reference: {
type: "typescript",
package: "@ORGANIZATION/PROJECT-api",
files: ["src/functional/**/*.ts"],
symbol: ["function"],
noEvidenceExclude: true,
},
},
],
};
export default {
extends: "../../config/lint.config.frontend.ts",
plugins: {
evidence,
},
rules: {
"evidence/graph": ["error", graph],
},
} satisfies ITtscLintConfig;The three claims form one chain: a hook answers for the operations it calls, a screen answers for the hooks it uses, and a journey answers for the screens it walks.
Owning an operation is not delivering it. A hook that wraps an accessor no screen ever renders passes frontend-hooks and fails frontend-screens. “The API is fully wired, there is just no UI yet” cannot be a green build.
The two ends differ on whether “no” is an answer. The operation and hook references refuse exclusions, because an unconsumed operation and an unused hook are missing work rather than decisions. The requirement and screen references accept a reviewed one, because a screen outside the journeys is a decision someone has to write down and defend.
The populations stay narrow on purpose. lib/<domain>/hooks.ts is the only place a generated accessor is called, so a hook is the one artifact that can truthfully own an operation. Primitives, layout chrome, and composed providers serve every requirement at once and therefore none in particular, so they are not screens. src/components/dev/** is a development gallery: tooling, not delivery.
Where this graph fails first
Reading the eight claims as a set, the interesting property is which mistakes have nowhere to hide.
- A requirement nobody implemented fails six ways at once, naming the layer each time.
- A model nothing exposes fails
api-operations, so a table added speculatively cannot sit there unused. - A column dropped from a response fails
dto-properties, even though every type still compiles. - An operation with no test fails
backend-tests, and a single omnibus test cannot cover for it. - A backend that shipped without a screen fails
frontend-screens, not at demo time. - A screen no journey walks fails
frontend-journeys, so end-to-end coverage is counted rather than asserted. - Deleting a requirement section breaks every artifact that cited it, immediately and by name.
Next
→ Adoption for how to reach a graph like this from an existing codebase, one claim at a time.