Skip to Content

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-journeys

Each 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.

ClaimDeclared inHostsMust citeCatches
schema-modelsbackend test configPrisma modelsrequirementssomething the spec says to persist and nothing stores
api-operationsbackend test configcontroller functionsrequirements, Prisma modelsa missing feature, and a table nothing exposes
backend-testsbackend test configtest functionsrequirements, SDK operationsa published operation with no test
dto-typesAPI package configexported DTO typesrequirements, Prisma modelsa domain that never reaches the wire
dto-propertiesAPI package configDTO propertiesPrisma columnsa column that silently never ships
frontend-hooksfrontend confighook functionsSDK operationsan operation no client reaches
frontend-screensfrontend configpage componentsrequirements, hooksan API wired up with no screen behind it
frontend-journeysfrontend configE2E journeysrequirements, screensa 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

packages/backend/test/lint.config.ts
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:

packages/backend/test/features/api/health/test_api_health.ts
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

packages/api/lint.config.ts
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

packages/frontend/lint.config.ts
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.

Last updated on