Blueprint Conformance — Engine Specification#
apiVersion: blueprint-conformance/v1alpha1
Status: draft (v1alpha1 — pre-1.0; see §10 for the versioning policy that governs change)
The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL in this document are to be interpreted as described in BCP 14 (RFC 2119 / RFC 8174) when, and only when, they appear in all capitals.
This specification is implementation-neutral: it defines the artifacts, semantics, and
contracts a conforming Blueprint Conformance engine implements. bce-engine (this repository)
is the reference implementation; nothing here depends on TypeScript, Node.js, or any specific
extractor technology except where explicitly marked as a property of the reference
implementation.
1. Overview and terminology#
| Term | Meaning |
|---|---|
| Blueprint | An authored, versioned, human-reviewed declaration of a subsystem's INTENDED architecture: components, relationships, and machine-checkable constraints. |
| Observed graph | A machine-extracted graph of a repository's ACTUAL architecture (components + real dependency/call edges), with a mandatory coverage honesty envelope. |
| Conformance run | The deterministic evaluation of a blueprint against an observed graph, producing a scored, evidenced compliance report. |
| Gate | The CI-facing mode: discover blueprints in a repository, evaluate those a change touches, and fail the build on any non-pass verdict. |
| Evidence record | One immutable, hash-chained link in the append-only ledger of conformance runs. |
| Enforcing constraint | A constraint type the engine grades against evidence and can redden. |
| Reserved constraint | A constraint type that is part of the taxonomy but not yet graded: it MUST be recorded as skipped, and MUST NOT be treated as satisfied. |
The engine is a conformance engine, not a compiler: it never modifies the repository it grades. Its outputs are reports, evidence, and proposed remediation objects (§12).
2. Artifact model and identity#
- Every authored artifact MUST declare
apiVersion: "blueprint-conformance/v1alpha1"and akindof eitherEngineeringBlueprint(per-repository) orPortfolioBlueprint(fleet-level). An artifact with an unrecognizedkindMUST be rejected (fail-closed), never silently ignored. - Authored artifact files use the suffix
.blueprint.json. The default discovery directory for gate mode is.blueprints/at the repository root; discovery is recursive, MUST excludenode_modulesand.git, and MUST produce a deterministic (sorted) ordering. - The normative structural contracts are the published JSON Schemas (§14):
engineering-blueprint.schema.jsonportfolio-blueprint.schema.jsoncompliance-report.schema.jsonevidence-record.schema.jsonarchitecture-graph.schema.jsonremediation-work-order.schema.json
2.1 Authored-artifact structural floor (EngineeringBlueprint)#
An EngineeringBlueprint:
- MUST reject unknown top-level keys (a typo in the source-of-truth artifact is a hard validation error, never a silent no-op).
- MUST carry
metadata.id(non-empty),metadata.version(semverx.y.z), andmetadata.status∈draft | proposed | approved | deprecated | retired. - MUST carry
intentRefswith at least one entry — every blueprint traces to at least one business intent. - MUST carry
constraintswith at least one entry — a blueprint that declares zero constraints enforces nothing and MUST be rejected at authoring/validation time. - MAY carry an
extractionblock selecting the extraction profile and scan surface; absent, the engine's documented historical default profile applies. - MAY carry
minEngineVersion(semverx.y.z) — the minimum engine version the blueprint requires (§8.1).
A forbiddenPattern constraint MUST declare a non-empty pattern that (a) compiles as a
regular expression and (b) passes the engine's unsafe-pattern guard (length cap +
catastrophic-backtracking shape rejection). An invalid or unsafe pattern is a hard
validation error at authoring time — never an evaluate-time skip, and never a live
denial-of-service sink. (This refinement is enforced by the engine but is beyond the
mechanical expressiveness of the published JSON Schema — see §14.2.)
For extraction.profile: "typescript-module-graph", minEngineVersion MUST be at least 0.3.0,
and extraction.paths plus an explicit positive extraction.minFiles are REQUIRED.
extraction.tsconfig MAY name one repository-relative,
non-glob config file. guardSymbols, forbiddenImports, forbiddenEgressHosts, and
governedModules MUST be absent or empty because extraction is policy-independent. Each
requiredDependency / forbiddenDependency MUST declare non-empty importer scopePaths and a
canonical to selector: module:<repo-path-or-glob>, package:<npm-root>, or
builtin:<node-name>. A module-profile requiredDependency MUST declare
component: "typescriptModule". A module-profile forbiddenDependency.from MUST be absent or
"*" because scopePaths is the sole importer selector. forbiddenEgress is not supported by
this profile and MUST be rejected at authoring time rather than evaluated against missing facts. A
module-profile requiredComponent MUST declare component: "typescriptModule". Portfolio members
using this profile MUST each pin an engine version of at least 0.3.0.
For extraction.profile: "python-module-graph", minEngineVersion MUST be at least 0.3.0,
extraction.paths plus an explicit positive extraction.minFiles are REQUIRED, and
extraction.pythonRoots MUST contain at least one repository-relative, non-glob import-root
directory. Every scanned file MUST belong to exactly one root. extraction.tsconfig and the
component-profile policy fields named above MUST be absent or empty. C2/C3 MUST carry importer
scopePaths and a to selector of module:<repo-path-or-glob> or
package:<python-import-root>; builtin: is not supported. C1/C2 component types MUST be
pythonModule, C3 from MUST be absent or "*", and forbiddenEgress MUST be rejected.
Portfolio members using this profile MUST each pin an engine version of at least 0.3.0.
2.2 PortfolioBlueprint#
A PortfolioBlueprint additionally:
- MUST declare
governance(fleet version, skew grace days, minimum-member floor),members(≥ 1), andfleetConstraints(≥ 1, same element type as per-blueprint constraints). - MUST declare
extractionexplicitly — a fleet artifact MUST NOT silently inherit one subsystem's scan defaults across N repositories. - MUST declare
coverage.unsupportedwith at least one entry — portfolio membership requires a DECLARED honest envelope; an artifact claiming blanket coverage MUST be rejected at authoring time.
3. Constraint taxonomy — 11 types#
The taxonomy has eleven constraint types: eight enforcing and three reserved (explicit-skip). The enum is widen-only (§10): it only ever gains members.
| # | Type | Class | Evidence class | Graded against |
|---|---|---|---|---|
| 1 | requiredComponent | ENFORCING | staticAst | observed component set |
| 2 | requiredDependency | ENFORCING | staticAst | observed components + edges |
| 3 | forbiddenDependency | ENFORCING | staticAst | observed import edges |
| 4 | forbiddenPath | ENFORCING | staticAst | extracted component paths |
| 5 | forbiddenFile | ENFORCING | staticAst | raw scanned-file set |
| 6 | forbiddenEgress | ENFORCING | staticAst | observed egress (network-call) edges |
| 7 | forbiddenPattern | ENFORCING | staticAst | per-line content-pattern scan |
| 8 | behavioralInvariant | ENFORCING | behaviorObservation | served-runtime observations |
| 9 | requiredEvidence | RESERVED | — | (explicit skip) |
| 10 | minimumMetric | RESERVED | — | (explicit skip) |
| 11 | customPolicy | RESERVED | — | (explicit skip) |
3.1 Enforcing semantics (normative)#
requiredComponent — at least one observed component of the named type (component) MUST
exist in the scanned surface. Zero → one violation.
requiredDependency — every observed component of the constraint's target type MUST have at
least one outgoing satisfying edge of the constraint's edge type. Target-type/edge-type
resolution is profile-aware (the constraint names WHICH component class it governs, so one
engine serves multiple surface shapes). Fail-closed on zero targets: a requiredDependency
that finds NO component of its target type is a violation, never a vacuous pass — a
"must register through the governed path" constraint with nothing to check is a drift signal.
Under a module-graph profile, importer scopePaths select typescriptModule or pythonModule
components and to selects a direct imports target. Every selected source MUST carry a matching
direct edge. An unresolved import cannot satisfy the requirement.
forbiddenDependency — one violation for EACH observed import edge to the forbidden module
(to). from MAY be a component id, or */absent meaning any component. An edge whose
source is an unattributable file pseudo-identity MUST match any named from (a forbidden
import is drift regardless of whether the file minted a recognized component). An OPTIONAL
scopePaths glob list NARROWS which importer files may fire the constraint (absent/empty →
every importer counts).
Under a module-graph profile, only imports edges are graded; scopePaths select importer modules
and to uses that profile's canonical selector grammar above. A selected source with an unresolved
or computed import produces a fail-closed violation because absence of the forbidden target cannot
be proven.
forbiddenPath — one violation for each extracted component whose path matches the
constraint's path glob.
forbiddenFile — one violation for each file in the raw scanned-file set matching the
path glob. Distinct from forbiddenPath by design: it catches a file regardless of export
shape (a file that mints zero components is invisible to forbiddenPath but present here).
Conditional skip: against a graph whose coverage carries no scanned-file list, the
constraint MUST be recorded as skipped (§7.3) — never a silent pass.
forbiddenEgress — grades observed network-egress edges (raw HTTP-client calls whose
target host resolved statically), which carry no import edge and are invisible to
forbiddenDependency. Two mutually exclusive modes, selected by which fields the constraint
declares:
- ALLOWLIST (
governedHostsnon-empty): an egress edge whose host is NOT an exact match or proper subdomain of a governed host is a violation (catches drift to an unanticipated host). - BLOCKLIST (
governedHostsabsent/empty): an egress edge whose host equals or is a subdomain of any entry into∪forbiddenEgressHostsis a violation.
The extractor is a pure detector (it records every resolved egress edge); the constraint is
the policy. An implementation whose extractor cannot resolve call-target hosts (e.g. a plain
line scanner) MUST refuse to grade a forbiddenEgress blueprint LOUDLY (§7.5) rather than
silently score zero egress edges as a pass.
forbiddenPattern — evaluates the constraint's pattern regex PER LINE over the raw
scanned-file set, one violation per (file, line) hit. An OPTIONAL sibling path glob narrows
which files' hits redden. Conditional skip: against a graph whose coverage carries no
pattern scan — or one whose scan never included THIS pattern — the constraint MUST be recorded
as skipped (§7.3), never a silent pass.
behavioralInvariant — the runtime substance constraint: the DEPLOYED artifact, driven
with ≥ 2 distinct stimuli that the intent says should produce different observable output,
must actually produce different output AND satisfy each stimulus's property oracle. Facts come
from a runtime observation set (identified by the constraint's behaviorRef), encoded as
observation nodes carrying an output hash and an oracle-satisfied flag per stimulus.
Violations (all fail-closed):
- no
behaviorRefdeclared → violation (nothing to grade can never pass); - fewer than 2 observations for the ref → violation (cannot prove input-conditioned variation);
- all observations share one output hash → violation (the constant-function / mock signature);
- any observation's oracle flag is false → one violation per failed observation.
3.2 Reserved types (explicit skip)#
requiredEvidence, minimumMetric, and customPolicy are reserved: valid to author,
not yet graded. An engine encountering them MUST record them as skipped and surface the
skip in the report summary. They MUST NOT count as satisfied, MUST NOT count as enforcing for
the empty-evaluation floor (§6.3), and MUST NOT be silently dropped.
3.3 Malformed enforcing constraints#
An enforcing-type constraint whose required argument field is absent (e.g. a
forbiddenDependency with no to, a requiredComponent with no component, a
forbiddenPath/forbiddenFile with no path) MUST be treated as skipped at evaluation time
(counting toward the empty-evaluation floor, §6.3) and SHOULD be refused at authoring time.
It MUST NOT be treated as satisfied.
4. The observed graph#
The persisted observed graph (architecture-graph.schema.json) MUST carry:
ctRepoRevision— the revision provenance of the scanned tree (§5.3);components— extracted component nodes{id, type, path, line};guardEdges— real observed edges{from, to, type, evidenceRef}whereevidenceRefanchors the observation topath#L<line>;coverage— the mandatory honesty envelope: which extractor ran, how many files were scanned, and anunsupportedlist naming what the extractor could NOT see. An extractor MUST declare its fidelity limits; claiming blanket coverage is non-conforming.
The typescript-module-graph profile MUST emit one typescriptModule component per scanned TS/JS
file and an imports edge for each statically named import, type-only import, re-export,
JavaScript JSDoc @import, TypeScript import-equals, unshadowed literal require / require.resolve, and literal dynamic
import, plus literal TypeScript import types (import("...")). Internal targets use module:,
declared npm subpaths normalize to their package: root, and syntactically valid node: imports
plus public Node 22 unprefixed built-ins normalize to builtin: without the node: prefix from a
source-pinned vocabulary. coverage.unresolvedImports MUST
locate observed computed or unresolved import forms used by the fail-closed C2/C3 semantics.
Bare roots not declared by an enclosing repository-owned package.json are unresolved rather than
guessed to be packages. Line-scan, invalid source syntax, invalid/escaping tsconfig resolution,
external/package-based tsconfig inheritance, and imports that escape the repository are refusals.
This profile does not claim transitive reachability or cycle analysis.
The python-module-graph profile MUST emit one pythonModule component per scanned .py file and
an imports edge for every statically declared import located by the structured parser. Explicit
pythonRoots define repository import identities: ordinary modules map to their root-relative
dotted name and __init__.py maps to its containing package. Resolved repository targets use
module:<repo-path>; canonical external names use package:<top-level-import-namespace>.
Dynamic __import__, importlib.import_module, exec, eval, and runtime import-state mutation
MUST be located in coverage.unresolvedImports, so relevant C2/C3 constraints fail closed.
Invalid syntax, non-UTF-8 source contracts, symlinked or root-escaping source, ambiguous roots or
import identities, over-dotted relative imports, and line-scan requests are refusals. The profile
does not claim PyPI distribution mapping, installed metadata, custom import hooks, .pyi semantics,
call or egress graphs, transitive reachability, or cycle analysis.
Determinism is a construction requirement: no wall-clock anywhere in the body; all arrays sorted before serialization; same tree in → byte-identical graph out.
Statically unanalyzable classes MUST be disclosed in coverage.unsupported. Extractors that grade
unknown destinations fail closed and SHOULD also itemize located facts (for example
unresolvedImports or unresolvedEgress) rather than presenting absence as proof.
5. Modes#
5.1 run — the authoritative grader#
run validates the blueprint strictly (any schema violation is a hard failure), scans the
target at a pinned revision (materializing a clean tree from the revision, not the working
tree, in the reference implementation's default mode), evaluates, and writes the report.
run is the only mode that grades behavioralInvariant constraints, by ingesting a runtime
observation set (--observations in the reference CLI) produced by a separate runtime probe.
Observation facts gate the verdict but MUST NOT fold into the graph's content-addressed
evidence hash (they are runtime facts, not tree facts).
5.2 gate — the pinned CI gate#
gate discovers every blueprint under the discovery directory, selects those whose scope
intersects the change's file list (absent list → full sweep), evaluates each against the
repository tree in place, and fails on any non-pass verdict. Gate mode differs from run in
exactly three tolerances, each of which MUST be explicit and surfaced (§9):
- Version-skew tolerant parse (§8.2) — unknown constraint types are dropped with an advisory instead of whole-file parse rejection.
- Run-only skip —
behaviorObservation-class constraints (e.g.behavioralInvariant) are not gradeable statically; each MUST be skipped with an explicit advisory naming the constraint, never silently passed and never failed for evidence the mode structurally cannot have. A blueprint whose constraints are ALL run-only produces an explicit-skip pass whose summary and coverage name every skipped constraint ("NOT a graded pass"). - Revision honesty — gate mode scans the working tree; its report MUST carry the tree's
real head revision when resolvable, else an explicit
unpinnedmarker. The revision does not encode uncommitted changes; this limit is documented, not hidden.
5.3 Selection#
A blueprint is selected for a changed-file list when any changed path matches any of the blueprint's scan globs (its extraction paths, else its scope paths). A blueprint declaring no path scoping MUST be selected conservatively (run it).
6. Scoring and verdict#
6.1 Score#
Each violation carries the severity of its constraint. Severity weights are fixed:
| severity | weight |
|---|---|
critical | 40 |
high | 20 |
medium | 10 |
low | 5 |
info | 0 |
score = max(0, 100 − Σ weight(violation)) — an integer in [0, 100].
6.2 Verdict — zero-violations-pass#
verdict = pass if and only if the violation set is EMPTY. The score does not decide the
verdict: an info-only violation set scores 100 yet fails. A report in that state SHOULD say so
explicitly in its summary (score 100 must never be read as a pass).
6.3 The empty-evaluation floor#
If zero enforcing constraints actually ran — every constraint was reserved-skipped,
conditionally skipped (§7.3), or malformed-skipped (§3.3) — the engine MUST emit a synthetic
critical violation (constraint id __no-enforcing-constraints__) so the verdict is fail.
A green verdict MUST mean something was proven, never nothing was checked.
7. Fail-closed semantics (enumerated)#
The engine fails closed at every layer where evidence is missing, partial, or unparseable:
- Malformed blueprint — a blueprint that fails validation is a run failure / a gate
failure (a score-0
failreport naming the parse error), never a silent skip. - Scan floor — the scan MUST resolve at least the blueprint's minimum file count
(
extraction.minFiles, defaulting to the resolved-path count of the profile). An empty/partial scan can never score 100: below the floor is a hard refusal (run/scan) or a score-0failreport (gate). - Missing evidence surface → honest skip — a
forbiddenFileagainst a graph without a scanned-file list, or aforbiddenPatternagainst a graph without a matching pattern scan, MUST be recorded as skipped. Skips do not count as enforcing, so a blueprint whose only constraints are skipped falls to the §6.3 floor. - Behavioral evidence floor — in the authoritative mode, a
behavioralInvariantwith no observation set, or fewer than 2 observations, is a violation (§3.1). - Extractor capability refusal — an extractor structurally unable to honor a
constraint's semantics (host resolution for
forbiddenEgress; symbol resolution for governed-module crediting) MUST be refused LOUDLY with guidance (an error inrun; a score-0failreport ingate) — never a silent false pass, and never a silent false reject of a conformant change. - Unresolvable engine self-version — an engine that cannot prove its own version MUST
compare as BELOW every authored
minEngineVersionpin (§8.1): an engine that cannot identify itself must never silently grade a blueprint that demands a minimum. - Unrecognized artifact kind — rejected, never a no-op (§2.1).
8. Version-skew honesty#
Two skew directions, two mechanisms, both explicit:
8.1 Blueprint requires a NEWER engine — minEngineVersion#
A blueprint MAY pin the minimum engine version it needs. The gate MUST read the pin off the
RAW artifact before full schema parsing (the whole point is a future blueprint this engine
may not even parse) and, when its own version is below the pin, emit a CLEAR score-0 fail
report saying upgrade the pinned engine — never a raw validation stack trace. A
present-but-malformed pin does not bypass validation (fail-closed).
8.2 Blueprint authored for a NEWER engine — tolerant gate parse#
gate runs a PINNED engine against blueprints that may carry constraint types this engine
does not know. The tolerant parse is deliberately NARROW:
- Strict parse first — a fully valid artifact takes the exact strict path.
- On failure, drop ONLY constraint entries whose
typeis a string outside this engine's enum, then re-validate the remainder STRICTLY. Any other malformation still fails (tolerance never widens past the unknown-type drop). - Every dropped constraint MUST surface as an explicit advisory naming its id, its type, and the upgrade path — counted, never silent.
- If ALL constraints are unknown, that is a hard parse failure: this engine can grade nothing in the blueprint, and a green gate must mean something was proven.
run MUST keep the strict parse — the authoritative grader gets no tolerance.
9. Mode doctrine — advisory vs enforcing#
"Advisory" carries two distinct, non-blurring senses in this spec: an output class (§9.1 — a single line in a report is enforcing or advisory) and an adoption mode (§9.2 — a whole gate invocation is enforcing or advisory). Both obey the same governing rule: nothing is ever silently softened.
9.1 Advisory vs enforcing OUTPUTS#
Every engine output is one of two classes, and the classes MUST NOT blur:
- ENFORCING: verdicts, violations, scores, fail-closed refusal reports, exit codes. These gate builds.
- ADVISORY: warnings (e.g. a repo-identity mismatch between the gate invocation and a blueprint's declared repositories), skip notices (run-only, unknown-type, reserved-type), and skip counters. Advisories MUST be surfaced (in the report summary, the coverage envelope, and/or the gate's warning stream) and MUST NOT, by themselves, fail a run.
The rule for every tolerance in this specification: a skip is always explicit — surfaced, counted, and attributable. Silent skips are non-conforming in both directions (silently passing AND silently failing).
9.2 Advisory vs enforced MODE — the adoption posture (config-file, never a flag)#
A repository adopting the gate on an existing (dirty) codebase needs to run the gate BEFORE it can pass it. The doctrine answer is an adoption MODE, not a skip flag — and the distinction is load-bearing:
- The mode is a COMMITTED config file, never a CLI flag. The reference CLI reads
.bce-mode.json({"mode": "enforced" | "advisory"}, an optionalrationaleRef) from the repo root. There is no--advisory/--skip/--no-verify-class flag ongate, and introducing one is non-conforming. A flag is invisible in the repo and trivially added to a CI line under adoption pressure; a committed, PR-reviewed file makes the posture a visible, governed fact of the repo. - ENFORCED is the default and the product. An ABSENT config resolves to
enforced— and MUST be byte-identical to a pre-mode engine (nomodefield on any report, no banner), so mode is a strictly widen-only addition (§10). A non-pass verdict fails the build (the §13 exit contract, unchanged). - ADVISORY computes the identical verdict and prints it in full, stamps a machine-readable
mode: "advisory"on every report, emits an unmissable banner declaring the violations do not block, and exits 0 regardless of verdict. Advisory changes ONLY the exit code — never the score, never the violation set, never what is printed. It is therefore not a skip flag and not a suppression: nothing is hidden; the full red is surfaced every run. Mistaking advisory for enforced is impossible by construction — the banner, the reportmodefield, and the exit code all agree. - A malformed config fails closed — an unparseable or unknown-valued
.bce-mode.jsonis a LOUD error, never a silent default to either posture (§7 fail-closed discipline).
Graduation is ONE-WAY and RECORDED (the widen-only ratchet applied to adoption posture):
- advisory → enforced (a TIGHTENING) is free: a graduation ceremony (
bce graduatein the reference CLI) appends an entry to an in-repo rationale record (.blueprints/GRADUATION.md) and flips the config toenforced. - enforced → advisory (a RELAX) is REFUSED unless the same rationale record is written — with a human-supplied reason. A gate MAY tighten silently; it MUST NOT relax silently (§10.3). Every transition, both directions, is recorded, so the posture's history is always auditable in-repo.
Neither the mode nor the graduation ceremony is a bypass of the grader: the grader is fail-closed at all times (§6.3 zero-blueprints/empty-scan FAIL, §7 refusals), independent of mode. Advisory decides only whether a red verdict blocks the build — the verdict itself is always honestly computed.
The baseline verb (§9.3) is the complementary adoption lever: advisory ungates the WHOLE
verdict's exit while surfacing everything; baseline keeps the gate enforcing but shrink-only against a
recorded pre-existing set. Neither suppresses a NEW violation — advisory prints it (and the score
still shrinks), baseline still fails on it.
9.3 Baseline — the shrink-only pre-existing-violation lever (config-file, never a flag)#
A repository adopting the gate on a codebase that already drifts needs a way to enforce from day one
without a red build for debt that predates the gate. The doctrine answer is a baseline: a
committed, PR-reviewed record of the accepted pre-existing violation set — and, like advisory mode,
it is a file, never a flag. The reference CLI reads/writes .blueprints/baseline.json.
- ABSENT baseline → enforce everything. No file resolves to "nothing is accepted" — every violation is NEW — and MUST be byte-identical to a pre-baseline engine (widen-only, §10).
- A baseline keeps the gate ENFORCING. With a baseline present, each run's violations partition into two sets: NEW (identity not in the baseline) — which fail the build, exactly as without a baseline; and BASELINED (identity in the file) — which are surfaced, counted, and stamped non-blocking. A baselined violation is never hidden (a skip is always explicit, §9.1): the graded verdict and score are the real fail, and the gate labels the blueprint non-blocking so it can never be mistaken for a graded green. A baseline never suppresses a NEW violation.
- Violation identity is content-addressed. A violation's baseline identity is the tuple
(blueprintRef, constraintId, component)— hashed — NOT its line number or message prose. A reformatting/line shift does not lose a baselined violation; a violation that moves to a different component, or a different constraint firing, is a NEW identity (correctly un-baselined). A written baseline entry carries the human-legible tuple so the committed file is reviewable, and a reader recomputes each entry's hash from its own fields — a hand-edited file whose stored identity does not match its fields is a fail-closed error (no smuggled broader identity).
The baseline is SHRINK-ONLY (the widen-only ratchet, §10.3, applied to the burndown wall):
- The FIRST write (no file yet) records every current violation — the only path that can GROW the accepted set, and it is PR-visible (a new file appears in the diff).
- Every SUBSEQUENT write produces a subset of the existing file: an entry whose violation still exists is kept; one whose violation has since disappeared is auto-removed (the wall burns down); a current violation NOT already baselined is refused entry (a re-write never grows).
- To GROW the wall you MUST delete the file and re-create it — a PR-visible act. There is deliberately no in-place "add to baseline" affordance; that affordance would be the bypass.
A baseline never turns a fail-closed refusal green. A refusal (empty scan below the floor, a malformed blueprint, a minEngineVersion miss — §7) has no violation identity to accept, so it is not a "pre-existing accepted violation": it always blocks, independent of the baseline. The baseline overlays only the graded-violation reds; the grader stays fail-closed at all times.
Baseline and advisory COMPOSE cleanly and independently: a refusal always blocks → a baseline narrows the graded-violation reds to only NEW ones → advisory (if set) then ungates the exit entirely. Every layer is legible in the output; none silently softens another.
10. Widen-only versioning policy#
The engine's compatibility contract, in force for every change:
- Enums only gain members. The constraint-type enum (and every published enum) only ever adds values. Existing authored artifacts — which cannot already contain a new value — are unaffected by construction.
- New fields are optional, with omit-not-empty discipline. An additive field MUST be optional and MUST be omitted (not emitted empty) when unset, so every pre-existing serialized artifact — and every downstream content hash — stays byte-identical.
- Gates tighten, never silently relax. A guard, floor, or refusal MAY become stricter; it MUST NOT be weakened or given a bypass flag. Tolerances (like §8.2) are added narrowly, explicitly, and with every skip surfaced.
- Behavior-preserving defaults. When a new capability needs configuration, the absent configuration MUST resolve to the prior behavior (a blueprint without the new block validates and scores byte-identically).
- Version identity. The engine version is semver. Within
v1alpha1, breaking changes to any published schema are not permitted; a breaking change requires a new apiVersion.
11. The report contract#
A compliance report (compliance-report.schema.json) MUST carry: schemaVersion,
blueprintRef (<id>@<version>), ctRepoRevision, score, verdict, violations,
evidenceRef, summary, and coverage (extractor, files scanned, unsupported list). It MAY
carry an additive repo identity stamp (omit-not-empty).
Determinism: same (blueprint, graph) in → byte-identical report out. To make that checkable:
- Canonical serialization is deterministic JSON: object keys sorted recursively, two-space indentation, a single trailing newline.
- Violations are sorted by (constraintId, component).
- The report's
evidenceRefis a content-addressed pointer to the exact observed graph bytes it was evaluated against (architecture-graph.json@sha256:<hex>), computed over the graph's canonical serialization. Fail-closed reports that never scanned use the explicit markern/a.
Each violation carries: constraintId, severity, component, evidenceType,
evidenceRef (anchor path#L<line> where applicable), observed, and expected — the
observed fact and the expectation it violated, both stated concretely.
The published report schema describes the CURRENT engine version's canonical output. Consumers aggregating reports across engine versions SHOULD read tolerantly (ignore unknown fields), per §10.2.
12. Evidence and remediation contract#
12.1 The evidence hash-chain#
Each conformance run MAY emit one evidence record (evidence-record.schema.json) chained
over prior runs:
previousHash— the SHA-256hashof the prior record, or the genesis sentinel (64 zero hex characters) for the first record in a chain.hash— the SHA-256 of THIS record's canonical body (all fields exceptidandhashitself,previousHashincluded), over the canonical serialization of §11.id— content-derived:evidence:<blueprintRef>:<first 16 hex of hash>.traceId— the chain key (the blueprint id; one chain per subsystem).
No wall-clock anywhere in the body: the record's identity is its content hash, so the same report + same previousHash always yields the same record.
Chain verification: a chain is intact iff, walking from genesis, each record's
previousHash equals the prior record's hash AND each record's hash re-derives from its
body. Verification MUST report the index of the first broken link. An empty chain is vacuously
intact.
12.2 Remediation work orders (propose-not-apply)#
A run MAY auto-generate one structured remediation object per violation
(remediation-work-order.schema.json). Every auto-generated object MUST start in state
PROPOSED and MUST advance only through the governed transition matrix:
| from | permitted to |
|---|---|
PROPOSED | ACKNOWLEDGED, REJECTED |
ACKNOWLEDGED | APPROVED, REJECTED |
APPROVED | RESOLVED, REJECTED |
RESOLVED | (terminal) |
REJECTED | (terminal) |
Nothing auto-advances past PROPOSED: a violation becomes a governed proposal, never an
auto-applied change. Object ids are deterministic (derived from constraint, component, and
evidence anchor), so identical runs yield identical proposals.
13. Exit-code contract (reference CLI)#
| command | 0 | 1 | 2 |
|---|---|---|---|
bce validate | blueprint valid | not found / bad JSON / schema-invalid | — |
bce author | draft written + self-validated | usage / flag error | author sanity: authored scope matches 0 files (draft left on disk for editing) |
bce propose | draft-only candidate and deterministic review packet written to quarantine | usage / flag error | provider failure/refusal, invalid plan, stale context, unsafe path, or deterministic review refusal (failed attempt retained) |
bce review show | fresh canonical packet and optional bound decision render | usage / format error | packet schema, canonical-byte, integrity, live-repository, or tool-identity verification fails |
bce review verify | packet and optional decision integrity plus live repository/tool identity verify | usage error | tampered, stale, malformed, or unreproducible packet/decision |
bce review decide | SCM-authenticated decision record written beside the packet; no policy mutation | usage error | GitHub review authentication, binding, state, identity, or freshness fails |
bce scan | graph written | usage error | fail-closed scan floor (files scanned < minimum) |
bce run | verdict pass | verdict fail; usage error; extractor capability refusal; invalid observations | fail-closed scan floor |
bce teeth | TOOTHED (≥ 1 constraint has extractor-real teeth) or EVALUATOR-REFUTABLE (refutable in principle only — synthetic-evidence mutations, explicitly NOT evidence of real teeth; surfaced as a warning, never a falsification) | usage error | TOOTHLESS (a green run proves nothing) |
bce gate (enforced — default) | every selected blueprint passes (a baselined-only red is a non-blocking pass, §9.3) | any NEW (un-baselined) graded violation; usage/config error | structural or extractor refusal: the repository could not be honestly graded |
bce gate (advisory mode, §9.2) | pass, or a graded violation reported without blocking; the full verdict carries mode:"advisory" | usage/config error | structural or extractor refusal: advisory never turns an ungradeable run green |
bce baseline (§9.3) | baseline written; with --check, current baseline is clean | usage/malformed baseline; --check finds shrink-needed or unaccepted-new debt | --check observes a gate refusal, or a write cannot be safely completed |
bce graduate | advisory → enforced recorded + config flipped (or an idempotent no-op) | enforced → advisory without --rationale; usage error | — |
bce doctor | repository is ready | repository is gradeable but needs a lifecycle action | repository cannot yet be honestly graded |
bce adopt / bce onboard | advisory proposal written; still unratified | malformed input / usage error | unsafe path, overwrite, non-draft blueprint, or mutable/unsupported engine reference refused |
bce ratify / bce amend | attended packet-bound policy ceremony recorded | malformed input / usage error | stale/invalid packet, non-approving or unreproducible SCM decision, missing extractor-real teeth, or unsafe policy transition refused |
bce upgrade --check | candidate is compatible | — | mutable/malformed candidate or incompatible engine floor refused |
bce verify-bundle | hashes and verdict reproduce | bundle malformed or integrity/reproduction check fails | — |
bce portfolio compile | overlays written | validation / usage error | — |
bce portfolio collect | rollup produced | refusal (missing/extra repo, member floor) or validation error | — |
| (unknown command) | — | usage printed, non-zero | — |
Normative shape: 0 = proven green, 1 = red or user error, 2 = fail-closed
refusal (the run could not honestly grade — which MUST be distinguishable from a graded
red). Gate preserves that distinction in both its process exit and machine report; exit 1 and
exit 2 both block an enforced CI check.
Advisory mode (§9.2) is the sole exception to "1 = red": it exits 0 on a red VERDICT by
design (the adoption posture), yet still exits 1 on a config/usage error — the mode ungates the
verdict, never the tool's own honesty.
14. Schema publication#
14.1 $id base#
Every published schema declares
$id: https://blueprint-conformance.github.io/bce/schemas/<name>.schema.json (JSON
Schema draft-07). These URLs are served by the organization's GitHub Pages site and therefore
resolve once the repository's public flip activated that site. Before the flip the $ids
were stable identifiers that did not yet dereference — a documented, expected state, not an
error. The publishing workflow (.github/workflows/publish-schemas.yml) shipped in the tree
dormant by design (disabled with an explicit comment) until the flip, and has been active
since it: this repository describes only machinery that runs.
14.2 Generation and parity#
The schemas under spec/schemas/ are generated, never hand-edited
(npm run generate:schemas, from scripts/generate-schemas.ts):
- The authored kinds (
EngineeringBlueprint,PortfolioBlueprint) are mechanically derived from the engine's source-of-truth validation schemas. - The emitted kinds (report, evidence record, graph, work order) are authored field-for-field against the engine's output types and proven against REAL engine output.
A CI-red parity test (tests/schema-parity.test.ts) enforces: byte-identical regeneration
(drift = red), validator agreement between the engine validator and the published schema on
an accept/reject matrix, and validation of real engine output against the published schemas.
Known, pinned divergence class: engine-side refinements exceed the mechanical conversion.
This includes §2.1's compile-and-safety check on forbiddenPattern.pattern and the
module-graph cross-field rules (minimum engine, supported constraints, canonical targets/scopes,
extractor configuration, Python roots, and portfolio member pins). The published JSON Schema is
the STRUCTURAL floor, and the engine parser is normative where the two diverge. Each refinement
family is pinned by an explicit test assertion so it can never silently widen.
15. Conformance#
An independent implementation conforms to this specification iff:
- it validates authored artifacts per §2 (structural floor + the §2.1 refinements);
- it implements the 11-type taxonomy per §3 — all eight enforcing semantics, explicit skips for the three reserved types, and the malformed-constraint rule;
- its reports satisfy §6 (weights, formula, zero-violations-pass, empty-evaluation floor) and §11 (determinism, canonical serialization, content-addressed evidence);
- it fails closed per §7 and surfaces every tolerance per §8–§9;
- its evidence chains verify per §12 and its remediation objects honor propose-not-apply;
- it distinguishes graded-red from fail-closed-refusal in its exit surface per §13;
- it evolves under the widen-only policy of §10.
Example (minimal authored artifact)#
{
"apiVersion": "blueprint-conformance/v1alpha1",
"kind": "EngineeringBlueprint",
"metadata": { "id": "example-surface", "version": "0.1.0", "status": "draft" },
"intentRefs": ["intent/keep-provider-traffic-governed"],
"scope": { "repositories": ["example-org/example-repo"], "paths": ["src/**/*.ts"] },
"architecture": { "components": [], "relationships": [] },
"constraints": [
{
"id": "no-direct-provider-egress",
"type": "forbiddenEgress",
"severity": "critical",
"from": "*",
"governedHosts": ["api-gateway.internal"]
}
],
"evidenceRequirements": [],
"approvals": [],
"extraction": {
"profile": "plugin-surface",
"paths": ["src/**/*.ts"],
"minFiles": 1
}
}