Two questions a reader of any fact needs answered: who owns it, and how sure are we. The principle behind the first is in foundation/self-expressive-project.md; this file is the method for making it executable.
Source-of-truth gate
Problem. A fact recorded twice will eventually disagree with itself. The second copy is usually there for a good reason: a client bundle cannot import the server schema, a public page must show the policy a job enforces, a script runs without the app's bundler. "Keep them in sync" is prose, and prose drifts.
Method. Every fact gets exactly one owner. Every other place that needs the fact holds one of these, listed from cheapest to most expensive:
| Binding | How the copy stays true | v10r instance |
|---|---|---|
| Typed | The compiler refuses a missing or extra key | retention-copy.ts is typed Record<RetentionRuleId, …>, so a rule with no reader-facing copy does not compile |
| Imported | The copy is the owner, reached by import | doc-filter.ts is imported by both the /docs routes and scripts/db/ingest-docs.ts; content-hash.ts is the one hash used by blog, retrieval and content:check; pattern-library/query.ts is the one implementation of the registry tools behind both MCP transports (mcp/patterns/parity.test.ts executes both and diffs the answers) |
Generated + --check |
The copy is rebuilt from the owner and the gate diffs the bytes | README Pattern Index and docs/pattern-library/ (patterns:check), mcp/public-excerpts.snapshot.json (mcp:excerpts:check) |
| Drift test | The copy is hand-held, and a test compares it to the owner | db-enums.drift.test.ts (client enum mirror vs pgEnum, member order included), schema-filter.gate.test.ts (drizzle.config.ts vs declared pgSchemas), credits/registry.gate.test.ts (credited SPDX id vs the installed package) |
Two rules make a drift test trustworthy:
- A non-empty control. A gate that iterates over a list passes vacuously when the list is
empty.
credits/registry.gate.test.tsasserts each scan found entries, andnaming.gate.test.tsasserts it scanned more than a thousand files. - Allowances that only shrink. An exemption list is an argument for keeping something,
not a place to hide it.
naming.gateandarchitecture.gatefail on a stale allowance as well as on a new violation, so the list cannot outlive its reason.
Fail closed when the owner cannot be read. Precedence between two sources is resolved at
design time by choosing one owner, never at runtime by falling back. loadProviderRegistry
(src/lib/server/ai/index.ts) is the reference: the database is the only source of AI
provider configuration. An unreadable table is an error, and a key that will not decrypt
marks the registry degraded. Neither falls back to environment variables, and neither
reads as "nothing configured". A fallback to a second source is exactly the second source of
truth this pattern exists to remove.
Invariants.
- One fact, one owner. A second writable copy is a defect, not a configuration choice.
- Every non-owner copy is typed, imported, generated or drift-tested, and no other kind exists.
- A drift test proves its own scan was non-empty.
- Exemption lists fail when an entry no longer applies.
- Reading the owner either succeeds or fails loudly; it never silently substitutes another source.
Do not use for a value read in one place only: the owner and the reader are the same line, and a gate around it is ceremony. And do not build a runtime "authority service" to arbitrate between sources. If two candidates need arbitrating, there is one owner too many.
Evidence status dimensions
Problem. "How sure are we?" is several questions, and collapsing them into one score makes every one of them unanswerable. A 0.8 does not say whether the model was confident, whether a human checked, whether all sources responded, or whether the answer is a year old.
Method. Keep each dimension in its own field, named for what it measures:
| Dimension | Question | v10r instance |
|---|---|---|
| Verdict / signal | What does the evidence suggest? Named so it never claims more than that | NameConflictSignal (level + reasons), deliberately not risk score, verdict or safe (src/lib/name-check/report.ts) |
| Coverage | How far did we look, and what could not be checked? | NameCoverageStatus per source: complete, unavailable, quota_exhausted, credentials_missing, timed_out, manual_only |
| Method and confidence | Who or what produced it, and how confident was the producer? | ai_proposal.confidence (the model's own tier per field), kept apart from fieldProvenance (empty / ai-draft / human), the record of who wrote the value (src/lib/schemas/image-metadata.ts) |
| Use | How far did a source get into an output? | TurnItemState: available → considered → included → executed → cited. Inclusion never claims influence (src/lib/types/turn-trace.ts) |
| Grounding | Does the claimed reference still resolve? | CatalogCitationStatus: exists / drifted / none (src/lib/server/ai/catalog-citations.ts) |
| Proof grade | What machine-checkable evidence backs it? | PatternMaturity: proven holds only with a linked test or showcase plus verifiedAt (pattern-library/schema.ts) |
| Freshness | When was it checked, and when does it go stale? | retrievedAt on name-check matches, verifiedAt plus the 180-day staleness warning in patterns:validate |
| Aspiration vs floor | Is this where we want to be, or a line we must not cross? | Perf targets (reported, never fail a build) vs ratchets (fail the gate) in src/lib/server/perf/budgets.ts |
Invariants.
- Distinct dimensions get distinct fields. A single score that merges confidence, coverage, provenance and freshness is never stored.
- A grade is only as fine as a machine can verify. Rungs that rest on self-declaration rot,
which is why
PatternMaturityhas three values and not six. - Field and type names describe the evidence, not a conclusion (
signal,coverage,drifted, notsafe,verified,valid). - Self-reported confidence never stands in for acceptance. A model's
highand a human's approval are separate facts. - Missing coverage is a value, not an absence. An unanswered source is recorded as such, never left out of the count.
Do not use for internal values nobody reads as evidence (a cache hit flag needs no confidence model), or where one boolean is honestly the whole story.
Asset rights manifest
Problem. An app that ships third-party models, photos or fonts owes their authors the
attribution their licenses require. In v10r that obligation lived in two hand-kept
ATTRIBUTION.md files beside the assets, while the code that displays the assets carried a
second, partly different credit string. No gate tied the two together, and two CC BY 4.0 models
were shown in the app with no attribution at all.
Method. The module that owns an asset owns its rights record, beside the data that uses it:
interface AssetRights { author: string; license: string /* SPDX id */; source: string /* https */ }
#lib/3d/models.ts(Model3D.rights) and#lib/3d/parts.ts(PartPhoto.rights) are the owners.#lib/credits/asset-rights.tsholds the type and the reading helpers (requiresAttribution,licenseLabel,creditLine).#lib/credits/registry.tsderivesassetCreditsfrom the owners. Nothing is re-typed.- Every surface that shows an asset renders
creditLine(rights): the part-photo lightbox, and/creditsfor everything. credits/registry.gate.test.tslists every file understatic/models/andstatic/images/parts/and fails unless each is credited exactly once. It also checks every license is a well-formed SPDX id (LicenseRef-PublicDomainwhere a source dedicates no license) and every attribution-requiring license carries an author and an https source.- The license identifiers come from the SPDX License List. For repository files outside the app, REUSE sidecars are the equivalent convention.
What the gate does not prove. That a license permits the use. Compatibility depends on how an asset is used (modified, redistributed, commercial), which an identifier does not encode, and automated compatibility checkers answer "uncertain" for exactly the interesting cases. The gate checks that the metadata is present, well-formed and rendered. Whether the use is allowed stays a human, and sometimes legal, judgement.
Invariants.
- Every shipped third-party file has exactly one rights record, owned by the module that uses it.
- No second attribution copy exists beside the files. The record is derived into every surface.
- A license that requires attribution is rendered wherever the asset is credited.
- Rights metadata is never presented as a legal conclusion.
Do not use for first-party assets the project made itself, or for npm dependencies, whose
license metadata the package already carries (the credits registry checks those against
node_modules instead).