Перейти к основному содержимому

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.ts asserts each scan found entries, and naming.gate.test.ts asserts 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.gate and architecture.gate fail 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 PatternMaturity has three values and not six.
  • Field and type names describe the evidence, not a conclusion (signal, coverage, drifted, not safe, verified, valid).
  • Self-reported confidence never stands in for acceptance. A model's high and 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.ts holds the type and the reading helpers (requiresAttribution, licenseLabel, creditLine).
  • #lib/credits/registry.ts derives assetCredits from the owners. Nothing is re-typed.
  • Every surface that shows an asset renders creditLine(rights): the part-photo lightbox, and /credits for everything.
  • credits/registry.gate.test.ts lists every file under static/models/ and static/images/parts/ and fails unless each is credited exactly once. It also checks every license is a well-formed SPDX id (LicenseRef-PublicDomain where 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).

← Back to Blueprint

Думаете, этот паттерн можно сделать лучше? Расскажите как.

Оставить отзыв