criv documentation
Browse the documentation, architecture decisions, and repository guides.
Architectural Decisions
Accepted decisions live in this directory as MADR-style notes named NNNN-kebab-title.md.
Index
- ADR-0001 - Local CLI vault architecture
- ADR-0002 - Docs and ADRs form the governance graph
- ADR-0003 - Adopt proven foundation crates
- ADR-0004 - Tree-sitter source graph
- ADR-0005 - ast-grep policy search and enforcement
- ADR-0006 - fff source index and incremental watch
- ADR-0007 - Content-addressed state and diffing
- ADR-0008 - Optional semantic note search
- ADR-0009 - Obsidian plugin as state consumer
- ADR-0010 -
criv initinstalls agent runtime skills - ADR-0011 - Embed runtime skill templates as assets
- ADR-0012 - ADR immutability enforcement
- ADR-0013 - Mise managed hk hook toolchain
- ADR-0014 - Tag triggered release binary workflow
- ADR-0015 - Size optimized release profile
- ADR-0016 - Conventional commit SemVer release automation
- ADR-0017 - Deprecate Intel macOS release archives
- ADR-0018 - Offline zizmor Actions security check
- ADR-0019 - Export Usage CLI spec and help
- ADR-0020 - Portable note wikilinks
- ADR-0021 - Audit remediation boundaries
- ADR-0022 - Hosted CI entry point
- ADR-0023 - Do not track generated plugin bundles
- ADR-0024 - Oxlint only JavaScript TypeScript enforcement
- ADR-0025 - Init ships plugin source not generated bundle
- ADR-0026 - Mermaid C4 diagrams as vault content
- ADR-0027 - C4 diagram fenced block extraction scope
- ADR-0028 - C4 standard alignment for Mermaid diagrams
- ADR-0029 - Generated C4 Code architecture
- ADR-0030 - DOT for generated Code architecture
- ADR-0031 - Text first C4 architecture formats
- ADR-0032 - .c4 files as architecture artifacts
- ADR-0033 - Typed Wikilink source references
- ADR-0034 - AST-aware source selectors
- ADR-0035 - VS Code-compatible companion extension
- ADR-0036 - VS Code extension test stack
- ADR-0037 - VS Code JSON diagnostics in hooks
- ADR-0038 - ADR Policy Rule Generation
- ADR-0039 - Inline ADR Policy Rules
- ADR-0040 - Inline Only ADR Policy Rules
- ADR-0041 - ADR-Owned Policy Patterns
- ADR-0042 - Shared Source Index Lifecycle
- ADR-0043 - Hawk visibility analysis
- ADR-0044 - Vault write confinement
- ADR-0045 - Note line identity in generated state
- ADR-0046 - No native linting in criv enforce
- ADR-0047 - Semantic note search stays source only
- ADR-0048 - 2026-07-25 audit findings not actioned
- ADR-0049 - Checks defined in hk, not mise
- ADR-0050 - Repeated Wikilinks are not a defect
- ADR-0051 - Refresh generated agent skills explicitly
- ADR-0052 - Harden generated skill refresh
- ADR-0053 - Claude skills is a link
- ADR-0054 - criv does not install Git hooks
- ADR-0055 - Dependency auditing in hk checks
- ADR-0056 - ADR policy patterns are the only registered patterns
- ADR-0057 - Branch-local ADR publication and reconciliation
- ADR-0058 - Embedded Git repository access
- ADR-0059 - Accepted only ADR policy state
- ADR-0060 - Parallel hosted validation and lean local hooks
- ADR-0061 - Hook-owned local validation and direct CI profile
- ADR-0062 - Canonical generated criv configuration
- ADR-0063 - ADR reconciliation owns its commit
- ADR-0064 - Typed query subcommands
- ADR-0065 - Shared compiled policy scan plans
- ADR-0066 - Capability-directed Wikilink and query resolution
- ADR-0067 - Staged changes are a partial check scope
- ADR-0068 - Bounded local snapshot lifecycle
- ADR-0069 - Repeatable two tier performance evidence
- ADR-0070 - Publish push performance evidence as Git notes
- ADR-0071 - Make Wasm editor projections canonical
- ADR-0072 - Keep performance observation outside core
- ADR-0073 - Effective ADR governance and source reconciliation
- ADR-0074 - LikeC4 as the architecture source and renderer
- ADR-0075 - LikeC4 preview as the default C4 editor
- ADR-0076 - Focused LikeC4 workspace navigation
- ADR-0077 - C4 standard alignment for the LikeC4 workspace
- ADR-0078 - The editor follows preview navigation
- ADR-0079 - No fallback view in the C4 preview
- ADR-0080 - Co-locate primary LikeC4 views with their models
- ADR-0081 - Require material State store performance gains
- ADR-0082 - No standalone search command
- ADR-0083 - Own one loaded State revision per editor workspace
- ADR-0084 - Require Windows hosted validation
- ADR-0085 - Keep the optional editor viewer local
- ADR-0086 - Bundle one editor viewer with criv
- ADR-0087 - Keep editor setup out of init
- ADR-0088 - Share the State wire document
- ADR-0089 - Reconcile effective query and LikeC4 contracts
- ADR-0090 - Enforce runtime boundary decisions
- ADR-0091 - Enforce editor adapter boundaries
Domain Docs
How the engineering skills should consume this repo’s domain documentation when exploring the codebase. This is a single-context repo.
Before exploring, read these
CONTEXT.mdat the repo root, if it exists.docs/adr/— read ADRs that touch the area you are about to work in. There are 47 of them and they are the primary record of how criv behaves and why.AGENTS.mdat the repo root — verification commands and repo conventions.
If CONTEXT.md doesn’t exist, proceed silently. Don’t flag its absence and
don’t suggest creating it upfront. The /domain-modeling skill creates it lazily
when terms actually get resolved.
File structure
/
├── AGENTS.md
├── docs/
│ ├── adr/ ← 47 decisions, criv-governed
│ ├── agents/ ← this configuration
│ ├── architecture/ ← generated C4 artifacts
│ └── query-reference.md
└── src/
Finding the right ADR
docs/adr/ is large enough that reading it linearly is the wrong move. Use criv:
criv query governing src/watch.rs # which ADRs govern this file
criv query governs ADR-0007 # what an ADR controls
rg -n -i "<topic>" docs/ # search note text
ADR conventions specific to this repo
- Accepted ADRs are immutable under ADR-0012. Never edit, delete, or rename
one to change a decision. Write a new ADR with
supersedes:instead. - Get the next number from
criv query next-adr-id, never by guessing. - ADRs carry
governs:path globs, and may carry inlinepolicy.patternsast-grep rules thatcriv checkandcriv enforceevaluate. - Everything under
docs/is a criv vault note and needs frontmatter with at leastid,kind, andtitle, orcriv checkfails and blocks the commit.
Use the project’s vocabulary
When your output names a domain concept — an issue title, a refactor proposal, a hypothesis, a test name — use the term the ADRs already use. Don’t drift to synonyms.
If the concept you need isn’t named anywhere yet, that’s a signal: either you’re
inventing language the project doesn’t use (reconsider) or there’s a real gap
(note it for /domain-modeling).
Flag ADR conflicts
If your output contradicts an existing ADR, surface it explicitly rather than silently overriding:
Contradicts ADR-0007 (content-addressed state) — but worth reopening because…
Because accepted ADRs are immutable here, “reopening” concretely means writing a superseding ADR, not amending the old one.
Issue tracker: GitHub Issues
Issues and specs (you may know a spec as a PRD) for this repo live in GitHub
Issues on TudorAndrei/criv, reached through the gh CLI.
When a skill says “publish to the issue tracker”
gh issue create --title "<title>" --body "<body>" --label "<label>"
When a skill says “fetch the relevant ticket”
gh issue view <number>
gh issue view <number> --comments
gh issue list --label ready-for-agent
The user will normally pass the issue number or URL directly.
When a skill says “comment on the ticket”
gh issue comment <number> --body "<body>"
PRs as a request surface
Off. Skills read and write issues only; incoming pull requests are not part of the triage queue. Flip this section on if that changes.
There is no second tracker
GitHub Issues is the only issue tracker for this repository. An ISSUES.md file
at the repo root previously held a hand-curated audit index; it was migrated to
GitHub Issues on 2026-07-30 and deleted. Do not recreate it, and do not add a
parallel findings file — a second list drifts from the first.
Issues migrated from it carry a footer naming their original ISSUES.md number,
because ADRs and commit messages written before the migration refer to those
numbers.
Issue quality
The migrated issues set the bar for a substantial finding: a one-line statement
of the defect, then Evidence with file:line citations, Impact, a
Fix sketch, and Verification. Match that shape when filing something
non-trivial. A finding without evidence someone else can check is not ready to
file.
Closing an issue
Terminal outcomes are decisions, and decisions in this repository live in
docs/adr/. See docs/agents/triage-labels.md for the rule: closing an issue as
done or as wontfix requires an ADR when it settles a question about how criv
behaves.
Triage Labels
The skills speak in terms of five canonical triage roles. This file maps those roles to the label strings used in this repo’s GitHub Issues.
| Label in mattpocock/skills | Label in our tracker | Meaning |
|---|---|---|
needs-triage | needs-triage | Maintainer needs to evaluate this issue |
needs-info | needs-info | Waiting on reporter for more information |
ready-for-agent | ready-for-agent | Fully specified, ready for an AFK agent |
ready-for-human | ready-for-human | Requires human implementation |
wontfix | wontfix | Will not be actioned |
When a skill mentions a role (e.g. “apply the AFK-ready triage label”), use the corresponding label string from this table.
Only wontfix exists in the repository today; the other four are the GitHub
defaults’ gap and must be created before first use:
gh label create needs-triage --description "Maintainer needs to evaluate this issue"
gh label create needs-info --description "Waiting on reporter for more information"
gh label create ready-for-agent --description "Fully specified, ready for an AFK agent"
gh label create ready-for-human --description "Requires human implementation"
Terminal states are decisions
This repository records decisions as ADRs under docs/adr/, and the two
terminal triage outcomes are decisions.
Closing as done. When the work settled a question about how criv behaves — what a command does, what it refuses to do, what a generated artifact looks like — write an ADR before closing. Pure implementation of an already-decided behavior does not need one; changing or establishing a behavior does.
Closing as wontfix. A wontfix is a decision not to do something, and the
reasoning is worth more than the label. Write an ADR recording what was proposed
and why it was rejected, then close with a link to it.
Both cases follow the normal ADR workflow:
criv query next-adr-idfor the number; never guess it.- Required frontmatter is
id,kind: decision,title,status,date. - Use
governs:for the path globs the decision controls. - Accepted ADRs are immutable under ADR-0012. To change a past decision, write a
new ADR with
supersedes:pointing at the old one — never edit it. - Run
criv checkbefore finishing.
An issue closed as done or wontfix should link the ADR that settled it, so the
reasoning survives the issue being closed.
Dependency evaluations
These evaluations cover foundation crates that are useful but not required for the current parser-backed implementation.
miette
Decision: defer.
miette is a good fit for future source-span diagnostics because it provides a
diagnostic protocol, source snippets, labels, related diagnostics, and optional
fancy reports. criv diagnostics currently carry line numbers but not byte
spans or source snippets, so adopting it now would mostly add dependency weight
without improving output. Revisit once check diagnostics store source offsets.
Reference: https://lib.rs/crates/miette
infer
Decision: defer until plugin asset previews are implemented.
infer detects file types from magic-number signatures and returns MIME and
extension metadata. The CLI already skips binary source files with
content_inspector and records cheap extension MIME hints with mime_guess.
Magic-number detection becomes useful when the Obsidian plugin previews
non-source assets from state.
Reference: https://lib.rs/crates/infer
serde_yaml_ng
Decision: keep serde_norway; do not add serde_yaml_ng now.
serde_yaml_ng is a serde-yaml fork and a viable alternate YAML backend, but
the current implementation already replaced deprecated serde_yaml with
serde_norway. Keep a single YAML parser until frontmatter compatibility tests
show a concrete gap.
Reference: https://lib.rs/crates/serde_yaml_ng
camino
Decision: defer until repo-relative path APIs are refactored.
camino provides UTF-8 path types that avoid repeated lossy conversions. That
matches criv’s repo-relative path invariant, but adopting it cleanly should be
done as a focused path-type refactor across config, vault, state, search, and
query modules.
Reference: https://docs.rs/camino
Cargo Audit Snapshot, 2026-07-23
Decision: document and monitor; do not add a failing cargo audit gate yet.
The pinned cargo-audit v0.22.2 command was run as cargo audit --no-fetch.
It loaded 1,169 advisories from the local advisory database at commit
1abf7a8c1822223a38e99f652bc232071c44a86d (2026-07-23 09:15:03 +02:00)
and scanned 461 locked packages. It reported four allowed warnings, all listed
below. None was classified as a vulnerability by this run.
This is a dated, local posture snapshot rather than a hosted-audit baseline:
--no-fetch intentionally does not update the advisory database, and the
command warned that it could not open the crates.io index cache lock. A future
policy gate needs a reproducible advisory-database update path before it can be
relied on in CI.
Unsound APIs: git2 v0.20.4
RUSTSEC-2026-0183 reports potential undefined behavior when
Remote::list() is called, and RUSTSEC-2026-0184 reports potential undefined
behavior for a Signature obtained from a buffer-created BlameHunk. Both
affect git2 v0.20.4, which reaches criv only through
fff-search v0.10.1:
git2 v0.20.4 <- fff-search v0.10.1 <- criv
criv directly depends on git2 v0.21.0 with default features disabled for
local repository discovery, tree/index/worktree diffs, commit traversal, and
blob reads. It does not create transports or invoke the advisory APIs. The
older git2 v0.20.4 remains an independent transitive dependency of
fff-search; cargo tree -i git2@0.21.0 and cargo tree -i git2@0.20.4
distinguish the two paths.
The locally installed fff-search v0.10.1 source was inspected. Its git path
uses Repository::open, status enumeration, workdir, and status_file; a
source search found no invocation of Remote::list, BlameHunk, or blame APIs.
That is evidence that the two advisory call paths are not reached by the
currently inspected source, not proof that text search alone can rule out every
runtime path or upstream behavior.
Unmaintained crate: bincode
RUSTSEC-2025-0141 marks bincode v1.3.3 unmaintained. Its active default
dependency path is:
bincode v1.3.3 <- heed-types v0.21.0 <- heed v0.22.1 <- fff-search v0.10.1 <- criv
Policy conclusion
The monitor-only decision is unchanged. The current findings are two
unmaintained crates and two potentially unsound-but-unreached APIs, not a new
vulnerability classification or demonstrated runtime exploit path. Do not add
an audit ignore list or failing gate, and do not replace fff-search, without a
separate approved decision. The embedded repository backend is governed by its
own ADR; accepted audit-policy ADRs remain unmodified.
ADR-0055 subsequently runs this same
command as a visible, non-blocking hk monitor. It does not change this Rust
decision or make cargo audit a failing gate.
Embedded Git backend measurement, 2026-08-02
git2 v0.21.0 is a direct MIT OR Apache-2.0 dependency with default features
disabled. Its native libgit2-sys v0.18.7+1.9.6 dependency has the same license
expression. The resolved graph deliberately contains both git2 v0.21.0 for
criv’s local repository boundary and git2 v0.20.4 through fff-search; Cargo
resolves both wrappers onto the same libgit2-sys version.
Same-toolchain, clean, size-optimized release builds on this macOS host measured
the main binary at 12,702,432 bytes in 1:14.62 and this branch at 12,737,152
bytes in 1:59.65. The embedded backend therefore adds 34,720 bytes (0.27%). The
branch build is slower by 45.03 seconds in this cold local comparison; this is
recorded for release review, not treated as a release blocker.
cargo audit --no-fetch on 2026-08-02 reported the same four allowed warnings:
the two git2 v0.20.4 advisory paths and the existing bincode and paste
maintenance warnings. It reported no advisory for direct git2 v0.21.0.
Evidence commands:
cargo audit --no-fetch
cargo metadata --format-version 1 | jq -r '.packages[] | select(.name == "git2" or .name == "libgit2-sys") | "\(.name) \(.version): \(.license // "NOASSERTION")"'
cargo tree -i git2@0.21.0
cargo tree -i git2@0.20.4
cargo tree -i bincode@1.3.3
cargo tree --all-features --target all -e features -i paste@1.0.15
LikeC4 Integration
ADR-0074 defines the architecture contract. This document gives the implementation specification.
Source and ownership
One vault has one LikeC4 workspace at docs/architecture/. All architecture
source files use the .c4 extension. LikeC4 owns the language grammar, model
rules, layout, and visual output. Agents write the source. criv does not read a
Mermaid C4 or DOT format and does not provide a migration command.
The workspace can contain manual models at every C4 level. When a vault enables
[architecture.code], criv writes the configured Code file. A generated file
contains only language modules and import relations:
- Rust crates and
moddeclarations - TypeScript and JavaScript modules and namespaces
- Python modules and packages
- Go packages
The generator does not make nodes for files, classes, functions, methods, or calls. A file is only a source location for a module. Each language gets a focused named view. There is no full Code view.
This repository keeps hand-authored module models and their focused views under
docs/architecture/code/. It does not enable [architecture.code]. Each view
explains one component or workflow instead of copying the complete source
index.
A hand-authored Code model must stay a true roll-up of the component model. If a module in component A imports a module in component B, the component model must also show a relationship from A to B. Cross-cutting helper modules, re-export barrels, and bundler shims stay outside the architecture; name them in a comment at the top of the Code model file.
The workspace has one LikeC4 project. LikeC4 merges every source file into one model. A domain file declares its elements and relationships once and also owns the primary named views that explain that domain. A large Code domain can own more than one focused view. Cross-domain runtime workflows stay in separate view files. This ownership lets an editor select the correct preview from the opened file path without copying model declarations.
docs/architecture/
specification.c4 element kinds, tags, and global style groups
systems.c4 people, systems, and the System Context view
cli.c4 CLI components and their primary view
interactions.c4 cross-container relationships and Container view
deployment.c4 deployment nodes and Deployment views
obsidian.c4 Obsidian components and their primary view
vscode.c4 VS Code components and their primary view
shared-renderer.c4 renderer components and their primary view
state-projection.c4 WebAssembly components and their primary view
code/ language modules and focused Code views
views/dynamic/ cross-domain runtime sequences
Scoped views provide System Context to Container to Component navigation.
Selected containers and components use explicit navigateTo targets. View
title paths group diagrams under Overview, Components, Code, Dynamic, and
Deployment.
Two rules keep the levels readable. External people and systems carry the
external tag, and every view greys them, so the system boundary is visible.
A relationship label starts with a capital letter and a present-tense verb, and
does not end with a preposition; it carries a technology when it crosses a
process, a language, or a storage boundary.
Hosting belongs to the deployment model. A container diagram shows what a container depends on. It does not show which application process contains it.
Module identity and nesting use these rules:
- Rust starts at the nearest
Cargo.tomlpackage. Hyphens become underscores.lib.rsandmain.rsidentify the crate, file-backed modules follow their path belowsrc/, and nestedmoddeclarations append their AST nesting. Asrc/bintarget starts a separate crate identity. - TypeScript and JavaScript use the repository-relative ES module path. Declared namespaces append their AST nesting to that module.
- Python strips the longest configured source root. A normal file is an
importable module. An
__init__.pyfile identifies its package. - Go groups all files with the same directory and
packagedeclaration into one package node.
Private and public modules are both architecture nodes. Architecture describes ownership, not only a public API. A repeated module or Go package has one node; the first repository path in lexical order is its source anchor. Module ids are stable hashes of the language and normalized module identity. Import edges are present only when the imported module resolves to another generated node. External package imports do not create placeholder nodes.
CLI validation flow
When the vault has at least one .c4 file, src/likec4.rs starts the embedded
assets/likec4-bridge.mjs program with the local Node.js command. The bridge
resolves the local likec4 package. It does not use a global LikeC4 command and
does not download a package.
The exact runtime contract is:
| Item | Required value |
|---|---|
| Node.js | 26.5.1 |
| LikeC4 | 1.59.2 |
| React | 19.2.8 |
| React DOM | 19.2.8 |
| Bridge protocol | 1 |
| State schema | criv.state.v1 |
| Process limit | 60 seconds |
| Standard output limit | 16 MiB |
| Standard error limit | 16 MiB |
The bridge uses LikeC4.fromWorkspace, getErrors, and layoutedModel. It
returns one JSON response with the exact runtime versions, revision, errors,
layout model, sorted elements, sorted relations, sorted views, and sorted source
links. Rust reads standard output and standard error at the same time. This
prevents a pipe deadlock when a large layout model fills an operating-system
pipe.
Rust rejects a timeout, too much output, invalid JSON, a protocol difference,
or any runtime version difference. LikeC4 errors use repository paths and
one-based lines in criv output. A vault with no .c4 file does not need Node.js
or LikeC4.
Source links
A LikeC4 element can have one criv source anchor:
link ../../src/likec4.rs 'source'
The source label is case-insensitive. The target is relative to the LikeC4
workspace. The bridge changes it to a repository-relative target. criv then
uses its normal source-target resolver. A missing file, line, symbol, or pattern
is an invalid-likec4-source error. Other LikeC4 links stay normal links.
State and editor flow
criv watch --once writes the normalized layout model to the top-level
architecture field in .criv/state.json. The field has the bridge protocol,
LikeC4 version, workspace path, revision, raw layout model, elements,
relations, views, and source links.
The shared packages/criv-likec4 package adapts this state to LikeC4 React. It
owns model replacement, stale-revision rejection, view selection, pan, zoom,
search, source-link events, disposal, and SVG export. SVG export puts the
LikeC4 shadow DOM in an SVG foreignObject, so the exported file contains the
same LikeC4 view and styles.
The Obsidian and VS Code packages are host adapters. They read criv state,
attach a monotonic host revision, select the host color scheme, and send source
link events to the host file API. Normalized view records contain LikeC4’s
optional sourcePath. Both hosts use it to select the view owned by the opened
file without parsing .c4 source. The shared renderer handles LikeC4
onNavigateTo events and reports the selected view to the host control. VS
Code remembers that selection across state refreshes.
There is no fallback diagram. A domain file opens the named views that it owns.
A shared file such as specification.c4 can own no view and shows an explicit
status message. The status message points to another architecture file that
declares a named view; it does not assume that every view is under views/.
VS Code registers the read-only preview as the default .c4 editor; Reopen
Editor With → Text Editor exposes the DSL. The repository recommends the
official likec4.likec4-vscode extension for DSL language services and maps
.c4 text documents to its likec4 language ID. That extension remains
optional and does not own the default preview. Neither host starts Node.js.
Their installed browser bundles contain the renderer and its assets. The VS
Code webview permits only its local resources under a strict content security
policy.
After an agent saves a .c4 file, the normal watch task validates the complete
workspace and writes a new state model. Each host replaces the old model. A
response with an old revision cannot replace a new model. A closed view always
disposes its React root.
Installation and CI
The root package-lock.json is the only npm lockfile. Use the pinned Node.js
version and run npm ci. Normal validation can then run without network
access. CI must run the Rust workspace tests, the two editor test suites,
criv check, and npm audit.
The 2026-08-04 implementation measurements used Node.js 26.5.1 on an Apple arm64 development host and the repository model that existed during the test. Architecture changes can change its element, view, and source-link counts:
| Measure | Result |
|---|---|
| Node.js empty-process start | 0.02 s |
Full criv check with LikeC4 layout | 3.80 s |
| Obsidian production JavaScript | 2,764,888 bytes |
| VS Code extension JavaScript | 26,311 bytes |
| VS Code LikeC4 webview JavaScript | 2,294,423 bytes |
| Cold browser bundle transfer | 17.5 ms |
| Cold state transfer | 2.9 ms |
| Cold first LikeC4 view | 112.2 ms |
| Test SVG export | 188,499 bytes |
The cold browser test used a local server and a new headless Chromium session. The first-view point was the first LikeC4 React Flow node in the renderer shadow root.
The lockfile license scan found no package without license data except the three local workspaces. The shared workspace now declares MIT. Production bundles keep esbuild legal comments. GPL and MPL packages in the lockfile are development tools and are not in the production renderer bundles. The npm audit after the lockfile update reports zero known vulnerabilities.
Repeatable Performance Evidence
Performance claims in criv use correctness invariants plus repeated external timing samples from an explicit release binary. One timing run on the development checkout is useful for exploration but is not project evidence.
Canonical workloads
The canonical workload set has two maintainer-approved shapes derived from observed criv vaults:
| Manifest | Tier | Notes | Source files | Source bytes | Links and references | Policies | C4 artifacts | Changed sources |
|---|---|---|---|---|---|---|---|---|
barrs-small.toml | small | 23 | 12 | 273,948 | 3 | 0 | 0 | 1 of 12 |
criv-medium.toml | medium | 77 | 119 | 1,291,218 | 212 | 4 | 4 | 1 of 119 |
The checked-in manifests retain repository revision, note split, language/file extension distribution, symbols, link categories, and exact changed fraction. Generated workload content is sanitized and deterministic; it reproduces the shape, not proprietary text or source.
A large tier is deliberately absent. No observed large criv vault was available for approval on 2026-08-03, and extrapolating either current shape would create the invented average prohibited by the evidence policy. Adding a large tier requires a separately reviewed observed manifest.
Docker is an optional execution environment, not a workload. An explicitly invoked Testcontainers for Rust lane builds and runs the harness inside a digest-pinned image, then supplies these same manifests and generated vaults. A future observed large manifest can use that lane without making the container image, an arbitrary checked-out codebase, or container storage into the vault definition.
Required run identity
Harness runs require an explicit executable path and profile identity. Evidence
records the canonical binary path and BLAKE3 digest, repository revision and
dirty status, Cargo profile, rustc --version --verbose, operating system,
release, architecture, processor model when available, UTC start time, harness
schema, workload manifest bytes and digest, command case, declared cache state,
and sample count.
Results from different binaries, workload manifests, profiles, or cache-state contracts are not combined. Machine differences remain visible metadata rather than being normalized into a claim of universal performance.
Containerized evidence additionally records the immutable image digest, Docker server identity, and container architecture. The release binary is built inside that pinned environment; a macOS or Windows host executable is never copied into a Linux container and mislabeled as the same artifact. Host and container rows are distinct machine identities and are not merged.
Sampling and isolation
Every command case declares cold or warm initial local state. A cold sample
starts from a newly generated vault with no .criv/. A warm sample starts from
another newly generated vault, performs its declared untimed seed command, and
then measures the requested command. Each timed sample gets a separate vault;
no sample inherits .criv files or generated outputs from another sample.
An untimed disposable warm-up precedes the recorded samples for each workload and case. The default sample count is five and the minimum evidence count is three. Failed commands remain raw sample rows with exit status and captured output references, make the harness fail, and are excluded from successful timing summaries rather than disappearing.
Raw and summarized evidence
Each run writes a new result directory. samples.jsonl contains one row per
sample, preserving identity, cache state, exit status, elapsed/user/system
seconds, output digests, generated-state and snapshot hashes when present.
summary.json groups compatible successful rows and reports sample count,
minimum, median, maximum, and median absolute deviation without discarding raw
values. report.html is a self-contained derived view with shared-scale timing
ranges, exact-value tables, run identity, and workload provenance. It uses no
external assets or services, and JSON remains the canonical evidence.
Cross-commit comparisons use separate result directories and freshly generated vaults. They compare rows only when workload digest, command case, cache state, sample count, profile, and machine identity match. Results are local artifacts and stay outside source control.
Core boundary
The harness observes criv only as a subprocess. Core code contains no
performance environment protocol, spans, counters, or measurement artifact
writer, and the harness uses the same ordinary release binary distributed to
users. Process resource usage and output identities are the complete runtime
observation surface.
Correctness tests may count work behind cfg(test) to prove invariants such as
partition reuse or a single source enumeration. Those assertions are compiled
only for tests and are not a runtime measurement API. Repeated timing samples
support such claims but do not identify internal operations on their own.
Validation boundary
Performance runs are deliberately absent from normal hk hooks and hosted CI,
as established by
ADR-0049. Harness and generator smoke
tests validate their contracts, while contributors invoke measurements
explicitly through mise run perf.
The Docker/Testcontainers lane is ignored by default and has its own explicit entry point because it requires a Docker-API-compatible runtime and may need to acquire the pinned image. Testcontainers owns container startup and cleanup; the harness still owns sample isolation inside the container.
Push notes
Every successful repository push runs a separate, non-gating host measurement
workflow and publishes its compact JSON summary to
refs/notes/criv-performance. The GitHub job summary shows the headline values
and links to a 30-day workflow artifact containing report.html alongside the
complete raw result directory. The note names the artifact and report path.
Fetch and inspect the durable note with:
git fetch origin refs/notes/criv-performance:refs/notes/criv-performance
git log --notes=criv-performance
git notes --ref=criv-performance show <commit>
The publisher uses the workflow’s scoped token, never force-pushes the notes ref, and retries after concurrent note updates. The job is not part of the CI aggregate or local hooks and does not require Docker.
The governing decision is ADR-0072.
Pre-commit Manager Integration
criv does not install Git hooks or change core.hooksPath. Add criv to the
hook manager already used by your repository. Run the commit commands in order:
criv watch --once
criv check --changed
criv enforce --stage commit
Add this command to a push hook:
criv enforce --stage push
Keep the commit commands serial. criv check --changed reads the state written
by criv watch --once.
hk
local criv_check = new Step {
check = "criv watch --once && criv check --changed"
}
local criv_enforce = new Step {
check = "criv enforce --stage commit"
}
hooks {
["pre-commit"] {
steps {
["criv-check"] = criv_check
["criv-enforce"] = criv_enforce
}
}
["pre-push"] {
steps {
["criv-enforce"] = new Step { check = "criv enforce --stage push" }
}
}
}
lefthook
pre-commit:
parallel: false
commands:
criv-watch:
run: criv watch --once
criv-check:
run: criv check --changed
criv-enforce:
run: criv enforce --stage commit
pre-push:
commands:
criv-enforce:
run: criv enforce --stage push
Husky
Run npx husky init, then replace .husky/pre-commit with:
criv watch --once
criv check --changed
criv enforce --stage commit
Add .husky/pre-push:
criv enforce --stage push
Python pre-commit
Add repository-local hooks to .pre-commit-config.yaml:
repos:
- repo: local
hooks:
- id: criv-watch
name: Refresh criv state
entry: criv watch --once
language: system
pass_filenames: false
stages: [pre-commit]
- id: criv-check
name: Check changed documentation
entry: criv check --changed
language: system
pass_filenames: false
stages: [pre-commit]
- id: criv-enforce
name: Enforce criv policy
entry: criv enforce --stage commit
language: system
pass_filenames: false
stages: [pre-commit]
- id: criv-enforce-push
name: Enforce criv push policy
entry: criv enforce --stage push
language: system
pass_filenames: false
stages: [pre-push]
Then run pre-commit install --hook-type pre-commit --hook-type pre-push.
simple-git-hooks
Add hook commands to package.json:
{
"simple-git-hooks": {
"pre-commit": "criv watch --once && criv check --changed && criv enforce --stage commit",
"pre-push": "criv enforce --stage push"
}
}
Run the manager install command after you update package.json.
See Tooling and Git Hooks for the hook-stage contract.
criv query reference
criv query asks the loaded vault graph focused questions and prints one row
per result. Add --format json to any query for a JSON array of rows:
criv query nodes --kind decision --format json
criv query
- Usage:
criv query <SUBCOMMAND>
criv query next-adr-id
- Usage:
criv query next-adr-id [--format <FORMAT>]
Print the next ADR id after the highest existing ADR id
Flags
--format <FORMAT>
Select text rows or a JSON array of rows
Choices:
textjson
Default: text
criv query callers
- Usage:
criv query callers [--format <FORMAT>] <SYMBOL>
List source symbols that call the requested symbol
Arguments
<SYMBOL>
Source path or symbol selector
Flags
--format <FORMAT>
Select text rows or a JSON array of rows
Choices:
textjson
Default: text
criv query callees
- Usage:
criv query callees [--format <FORMAT>] <SYMBOL>
List source symbols called by the requested symbol
Arguments
<SYMBOL>
Source path or symbol selector
Flags
--format <FORMAT>
Select text rows or a JSON array of rows
Choices:
textjson
Default: text
criv query attack-surface
- Usage:
criv query attack-surface [--format <FORMAT>]
List exported or public source symbols in the source graph
Flags
--format <FORMAT>
Select text rows or a JSON array of rows
Choices:
textjson
Default: text
criv query targets
- Usage:
criv query targets [--format <FORMAT>] <NOTE_ID>
List source and pattern targets declared or linked by a note
Arguments
<NOTE_ID>
Note id or unique note name
Flags
--format <FORMAT>
Select text rows or a JSON array of rows
Choices:
textjson
Default: text
criv query cites
- Usage:
criv query cites [--format <FORMAT>] <NOTE_ID>
List notes, sources, and patterns cited by a note
Arguments
<NOTE_ID>
Note id or unique note name
Flags
--format <FORMAT>
Select text rows or a JSON array of rows
Choices:
textjson
Default: text
criv query cited-by
- Usage:
criv query cited-by [--format <FORMAT>] <NOTE_ID>
List notes that cite the requested note
Arguments
<NOTE_ID>
Note id or unique note name
Flags
--format <FORMAT>
Select text rows or a JSON array of rows
Choices:
textjson
Default: text
criv query orphan-docs
- Usage:
criv query orphan-docs [--format <FORMAT>]
List documentation notes without incoming or outgoing note citations
Flags
--format <FORMAT>
Select text rows or a JSON array of rows
Choices:
textjson
Default: text
criv query references
- Usage:
criv query references [--format <FORMAT>] <SYMBOL>
List notes that reference a source path or symbol
Arguments
<SYMBOL>
Source path or symbol selector
Flags
--format <FORMAT>
Select text rows or a JSON array of rows
Choices:
textjson
Default: text
criv query governs
- Usage:
criv query governs [--format <FORMAT>] <ADR_ID>
List source files governed by a decision
Arguments
<ADR_ID>
ADR id
Flags
--format <FORMAT>
Select text rows or a JSON array of rows
Choices:
textjson
Default: text
criv query governing
- Usage:
criv query governing [--format <FORMAT>] <SYMBOL>
List decisions that govern a source path or symbol
Arguments
<SYMBOL>
Source path or symbol selector
Flags
--format <FORMAT>
Select text rows or a JSON array of rows
Choices:
textjson
Default: text
criv query coverage
- Usage:
criv query coverage [--by <BY>] [--format <FORMAT>]
Summarize source governance coverage
Flags
--by <BY>
Group coverage rows by module or ADR
Choices:
moduleadr
--format <FORMAT>
Select text rows or a JSON array of rows
Choices:
textjson
Default: text
criv query nodes
- Usage:
criv query nodes [FLAGS]
List source, note, or decision nodes
Flags
--kind <KIND>
Restrict nodes to code, documentation, or decisions
Choices:
codedocdecision
--without-docs
Restrict code nodes to symbols that no note references
--format <FORMAT>
Select text rows or a JSON array of rows
Choices:
textjson
Default: text
criv query c4-code
- Usage:
criv query c4-code [--format <FORMAT>] <PATH_GLOB>
Emit focused LikeC4 source for modules in a source path glob
Arguments
<PATH_GLOB>
Source path or component/module glob
Flags
--format <FORMAT>
Select text rows or a JSON array of rows
Choices:
textjson
Default: text
criv query diff
- Usage:
criv query diff [--format <FORMAT>] <REF_A> <REF_B>
Compare two state snapshots or git refs
Arguments
<REF_A>
Left snapshot hash, latest, or git ref
<REF_B>
Right snapshot hash, latest, or git ref
Flags
--format <FORMAT>
Select text rows or a JSON array of rows
Choices:
textjson
Default: text
diff resolves latest through .criv/latest, hex-like values through
.criv/snapshots/<hash>.json, and any other value through
an embedded lookup of .criv/state.json in the requested repository ref. It
does not invoke the git executable.
Releasing criv
Use Cocogitto to calculate the next SemVer version from conventional commits,
then use cargo-release to update workspace Cargo versions.
Before preparing a release, run:
cargo test --workspace
cargo fmt --check
target/debug/criv check
target/debug/criv enforce --stage ci
target/debug/criv watch --once
target/debug/criv query diff latest latest
target/debug/criv check embeds rumdl as a Rust crate, so Markdown formatting
does not require a separate rumdl executable.
Build plugin artifacts when the Obsidian plugin is part of the release:
npm --prefix .obsidian/plugins/criv run build
This wraps the plugin’s npm run build script in mise x rust@1.97.1 so
wasm-pack sees the mise-managed Rust and Cargo toolchain, including the
installed wasm32-unknown-unknown target. If only the Rust CLI is being
released and the generated plugin scaffold is unchanged, document that choice in
the release notes.
Preview the next automatically selected version:
mise run release-plan
For a tag-only release:
mise run release-auto
release-auto is implemented by scripts/release-auto.sh. It requires a
clean main branch, asks Cocogitto for the next version with
cog bump --dry-run --auto, updates workspace Cargo versions, runs the
pre-release checks above, commits the version bump, creates vX.Y.Z and
criv-wasm-vX.Y.Z, and pushes the commit and tags.
Conventional commits drive the automatic bump: fix produces a patch release,
feat produces a minor release, and ! or BREAKING CHANGE: produces a major
release. While criv is still in 0.y.z, Cocogitto will not automatically select
1.0.0; cut that intentionally with a manual versioned release if needed. This
decision is captured in ADR-0016.
The next release remains git-tag-only. Do not publish criv to crates.io until
the CLI API, state schema compatibility policy, and installer story are stable
enough to support registry consumers. The tag-triggered GitHub binary release is
the authoritative distribution path for now.
When crates.io publishing is intentionally enabled later, confirm the package metadata first:
cargo package --workspace --allow-dirty
cargo publish --dry-run
Then run the matching cargo release command without --no-publish. Crates.io
publishing remains manual for now; release-auto only cuts the tag-triggered
binary release.
Current tag names use:
vX.Y.Zfor the root CLI crate.criv-wasm-vX.Y.Zfor the WASM helper crate.
Release binary automation should run only when a v* root CLI release tag is
pushed. The workflow should build criv archives named by Rust target triple:
criv-x86_64-unknown-linux-gnu.tar.gzcriv-aarch64-unknown-linux-gnu.tar.gzcriv-aarch64-apple-darwin.tar.gzcriv-x86_64-pc-windows-msvc.zip
Intel macOS release archives are deprecated by ADR-0017 because the hosted
Intel macOS runner is the slowest release job. Apple Silicon macOS remains the
supported macOS binary target. Reintroduce criv-x86_64-apple-darwin.tar.gz
only if there is measured user demand or a faster runner path.
Release assets should include SHA256SUMS.txt, GitHub build provenance
attestations, and criv --version as the installer smoke test for future aqua
and mise registry entries. This decision is captured in ADR-0014.
Each platform archive also includes vscode-criv.vsix next to the executable.
The release workflow builds that package once and adds the same local-only
viewer to every archive, as required by
ADR-0087.
Release binaries use the workspace release profile in Cargo.toml: symbols
are stripped, size optimization is enabled, LTO runs at link time, codegen uses
one unit, and release panics abort. This keeps downloadable artifacts smaller
without requiring nightly Rust or post-build binary packing. The profile
decision is captured in ADR-0015.
The CLI embeds local Git repository access through its Rust dependency graph;
the release artifact does not require a git executable for query-diff or
enforcement repository reads. Release verification should keep a PATH-without-
Git smoke test alongside the hosted core validation profile. The backend scope
and dependency evidence are recorded by the embedded-repository-access ADR.
Local State And Snapshot Reference
criv watch --once and live watch publication write the current graph to
.criv/state.json. They also keep content-addressed local snapshots for
criv query diff. The snapshot store is ignored local data; source control
remains the durable history boundary.
Configure the maximum number of distinct local snapshots in criv.toml:
[state]
keep = 20
keep must be a positive integer and defaults to 20. The bound is applied
after every successful state publication. Publishing identical content again
does not consume another slot, but makes that hash the newest publication. The
snapshot named by .criv/latest is always protected.
Inspect retained snapshots newest first:
criv state list
criv state list --format json
Preview or apply pruning with the configured bound:
criv state prune --dry-run
criv state prune
criv state prune --keep 5
criv state prune --keep 5 --format json
--keep is a one-command positive override; it does not rewrite criv.toml.
--dry-run selects and reports the same snapshots without changing local
files. Corrupt recognized snapshots fail closed and are not deleted
automatically.
criv query diff <a> <b> resolves latest and retained hashes from the local
store. Other values remain embedded Git-ref lookups of .criv/state.json.
Pruning local snapshots does not inspect or alter Git refs.
The lifecycle and compatibility boundary are defined by ADR-0068.
Tooling and Git Hooks
The repository uses mise.toml to install the project hook runner and
hk.pkl to define the hook behavior. criv.toml also declares project-level
tooling files, including Hawk’s hawk.toml, as source roots. The decision
record is ADR-0013.
Run the initial setup with:
mise install
The mise postinstall hook runs hk install --mise, so Git hook execution goes
through mise x and uses the pinned tool versions, including hk, Rust 1.97.1,
and actionlint. GitHub Actions security analysis is pinned here too: zizmor
checks workflow, composite action, and Dependabot definitions for risky CI/CD
patterns. Hawk uses the same Rust toolchain to check whether public Rust APIs
are required by the shipped criv binary. Release tooling is also pinned here:
Cocogitto calculates the next SemVer version from conventional commits, and
cargo-release updates Cargo workspace versions. HK_PKL_BACKEND=pklr keeps hk
self-contained by avoiding a separate pkl CLI requirement.
Workflow YAML under .github/workflows/ is checked with actionlint in
pre-commit and the full check hook. zizmor runs in offline mode in the same
hooks so local validation does not require a GitHub token or network access.
This follow-up hook decision is ADR-0018.
Running criv from a hook runner
criv does not install Git hooks and does not set core.hooksPath, per
ADR-0054. Setting that config replaces
the hook directory wholesale, which silently disables whichever runner the
repository already uses. Wire criv into your own runner instead.
The commands are stable:
| Stage | Commands |
|---|---|
| commit | criv watch --once, criv check --changed, criv enforce --stage commit |
| push | criv enforce --stage push |
watch --once refreshes .criv/state.json before the changed check. The
changed check is a staged, read-only partial fast path; plain criv check
remains the full manual and hosted authority per
ADR-0067. Run commit commands
in the listed order.
With hk, as this repository does in hk.pkl:
local criv_check_changed = new Step {
glob = List("**/*.md", "criv.toml")
check_first = true
check = "criv check --changed"
fix = "criv check --fix"
}
local criv_enforce_commit = new Step {
check = "criv enforce --stage commit"
}
hooks {
["pre-commit"] {
fix = true
steps {
["criv-check"] = criv_check_changed
["criv-enforce"] = criv_enforce_commit
}
}
}
With lefthook, in lefthook.yml:
pre-commit:
parallel: false
commands:
criv-watch:
run: criv watch --once
criv-check:
run: criv check --changed
criv-enforce:
run: criv enforce --stage commit
pre-push:
commands:
criv-enforce:
run: criv enforce --stage push
Keep parallel: false for the commit stage: check reads the state watch
writes, so they must not overlap.
Manual task entry points are:
mise run commit-msg -- .git/COMMIT_EDITMSG
mise run pre-commit
mise run pre-push
mise run fix
mise run perf
mise run vscode-build
mise run vscode-package
mise run release-plan
mise run release-auto
Pre-commit and pre-push are the automatic local validation boundary. Agents and contributors do not need to replay them with an aggregate command after each commit. The former local aggregate task has been removed.
Hosted CI invokes the complete hk check profile directly. Every step hk runs
is defined in hk.pkl with its command inline, per
ADR-0049. To inspect or debug one core
step explicitly:
hk check --all --plan # list every step
hk check --all --step hawk
Pre-push runs Clippy, workspace tests, Hawk, and criv push enforcement. Hawk is kept in both pre-push and the hosted CI profile so unnecessary public Rust APIs fail before hosted CI during normal local development.
mise run perf generates isolated vaults from the checked-in barrs-small
and criv-medium manifests, then measures an explicit release binary. Build
that binary first and identify its profile at the command line:
cargo build --release
mise run perf -- --binary target/release/criv --profile release
The script records repeated samples for cold, warm, and changed watch --once,
source-index file search startup, validation, CI enforcement, docs-only
next-adr-id, orphan-docs, and nodes --kind doc queries, and a no-op
snapshot diff. Five samples are collected by default; use --samples to
change the count or repeat --case to select cases:
mise run perf -- --binary target/release/criv --profile release --samples 9
mise run perf -- --binary target/release/criv --profile release --case check
Every recorded sample receives a fresh generated vault. Warm cases perform an untimed state-building run inside that vault before the recorded command, and the changed case mutates the manifest’s declared number of supported source files without changing their sizes. A unique result directory preserves the run identity, exact manifest copies, raw JSONL samples, stdout/stderr, and a JSON summary with min/median/max and median absolute deviation. Failed samples remain in the raw evidence and make the harness fail.
mise run perf-container runs one ignored smoke case through Testcontainers.
It builds criv and the harness inside a digest-pinned Rust Linux image. Docker
is only an optional execution environment: the same checked-in manifests and
generator define the vaults, results have their own container machine identity,
and this task is not part of hooks or hosted CI. A Docker-API-compatible runtime
is required. Large-workload measurement remains deferred.
Obsidian and VS Code validation are hosted-CI suites rather than automatic hk
steps. Contributors changing a companion should run its package scripts
directly. Run npm ci from the repository root first. The Obsidian companion
commands are:
npm audit --workspace criv-obsidian-plugin --audit-level=high
npm --prefix .obsidian/plugins/criv run format:check
npm --prefix .obsidian/plugins/criv run lint
npm --prefix .obsidian/plugins/criv test
npm --prefix .obsidian/plugins/criv run build:wasm
npm --prefix .obsidian/plugins/criv run build:plugin
npm --prefix .obsidian/plugins/criv run build
The combined build preserves the release and manual entry point by running the
Wasm build followed by the plugin build. The Wasm command invokes wasm-pack
through mise exec cargo:wasm-pack@0.15.0, so the repository’s pinned tools are
used rather than whichever Rust or wasm-pack appears first in the shell.
It writes criv_wasm.js, criv_wasm_bg.js, and criv_wasm_bg.wasm under
.obsidian/plugins/criv/pkg/. The plugin bundle keeps that runtime import
external, so a distributable plugin must carry the generated pkg/ directory
beside main.js.
If Obsidian reports that the criv Wasm runtime is unavailable, run the combined
build, confirm those three runtime files exist, and reload the plugin. A state
schema or parse error is separate: regenerate .criv/state.json with criv watch --once instead of rebuilding Wasm.
The VS Code-compatible extension lives in extensions/vscode-criv. Its local
tasks are exposed through mise and npm:
npm audit --workspace vscode-criv --audit-level=high
npm --prefix extensions/vscode-criv run build
npm --prefix extensions/vscode-criv run test
npm --prefix extensions/vscode-criv run test:integration
npm --prefix extensions/vscode-criv run lint
npm --prefix extensions/vscode-criv run format:check
npm --prefix extensions/vscode-criv run package
The integration command launches the VS Code extension test host and fails on JSON diagnostics for extension manifest and language configuration files. Its short temporary user-data and extension-installation paths avoid macOS IPC path limits. Hosted CI runs this command under Xvfb; it is not part of local hooks.
The extension uses a read-only LikeC4 preview as the default .c4 editor. It
selects the named view owned by the opened file. There is no fallback view: a
file that owns none states so and points at a file that declares a view.
Navigating inside the
diagram opens the file that owns the target view in the same editor group, so
the tab tracks the diagram. Use
Reopen Editor With → Text Editor to edit the DSL. The preview webview uses packaged extension
resources and does not use a CDN or a global LikeC4 command. Run criv watch --once after architecture or extension source changes. This keeps architecture
state current. The repository Code model and views are hand-authored under
docs/architecture/code/, so watch does not replace them.
npm --prefix extensions/vscode-criv run package and mise run vscode-package build a local vscode-criv.vsix without publishing. mise run vscode-install builds that VSIX and installs it into the local editor; reload
the window afterwards. The
prepublish hook builds the Node.js Wasm target into
extensions/vscode-criv/pkg/; the VSIX includes criv_wasm.js and
criv_wasm_bg.wasm from that directory. If the extension reports an unavailable
Wasm runtime, rebuild build:wasm, recreate the VSIX, confirm those files are
present in the package, and reload the editor window. Regenerate invalid state
with criv watch --once; do not treat a state-validation error as a runtime
packaging failure.
The optional viewer is local-only under ADR-0087. The project does not publish it to the VS Code Marketplace or Open VSX. Release archives put the one viewer package next to the criv executable. Install it with an explicit editor selection:
criv install-editor --editor code
criv install-editor --editor cursor
Use --dry-run to validate the bundled viewer and local editor command without
changing editor state.
Use hk validate after editing hk.pkl. Prefer hk built-ins when one exists;
the commit message hook uses Builtins.check_conventional_commit instead of a
shell wrapper around hk util check-conventional-commit.
The hawk hk step runs Hawk with warnings denied against the shipped criv CLI.
The criv-wasm crate is excluded because its exported functions are consumed by
the separately built WASM artifacts, outside the CLI binary’s Cargo graph. The
pre-push and full check hooks run Hawk whenever Rust sources, Cargo metadata,
or its configuration change. Hawk uses target/hawk for its instrumented Cargo
artifacts so its compiler work stays isolated from the other parallel checks.
This policy is captured in ADR-0043 and the
local/hosted validation boundary is
ADR-0061.