# Self-hosting: the engine gates its own tree

`bce` grades repositories against an authored `EngineeringBlueprint`. This repository is
itself a repository — so it carries its own blueprint
([`.blueprints/engine.blueprint.json`](https://raw.githubusercontent.com/blueprint-conformance/bce/main/.blueprints/engine.blueprint.json)) and CI runs
the engine over the engine on every push and pull request
([`.github/workflows/self-gate.yml`](https://raw.githubusercontent.com/blueprint-conformance/bce/main/.github/workflows/self-gate.yml)).

Self-hosting is not a stunt. It is the cheapest continuous proof that:

1. the engine's authored-artifact path (schema → extraction → evaluate → score → verdict)
   works end-to-end on a real, non-trivial TypeScript repository;
2. the engine's own architecture cannot silently drift — the same fail-closed gate its
   users get is the gate its maintainers live under;
3. dogfooding surfaces real engine gaps (see the `teeth` scopePaths fix below, found the
   first time the engine assessed its own blueprint).

## The architecture this blueprint enforces

Every constraint below was verified against the code **before** being authored — the
blueprint records the real architecture, not an aspiration.

| Claim | Constraint(s) | Mechanism |
| --- | --- | --- |
| Only `src/extractors.ts` may import `ts-morph`. Everything else consumes extracted facts through the seam (below). | `only-extractors-may-import-ts-morph` | `forbiddenDependency` on the AST import graph, `scopePaths` = every src file **except** `extractors.ts` |
| Only `src/python-module-graph.ts` may import the pinned Lezer Python parser packages. | `only-python-module-graph-may-import-lezer` | `forbiddenDependency` on the AST import graph, `scopePaths` = every src file **except** `python-module-graph.ts` |
| Runtime dependency allowlist is exactly `zod`, `ts-morph`, `@lezer/common`, and `@lezer/python`. Any other external package import is a violation. `node:` builtins are allowed globally and narrowed per-file below. | `runtime-dep-allowlist-approved-parsers` | anchored per-line `forbiddenPattern` over `src/**/*.ts` |
| `src/schema.ts` (the single source of truth for the artifact shape) imports only `zod` plus the local `safe-regex` guard. | `schema-imports-only-zod-and-safe-regex` | per-file `forbiddenPattern` |
| The evaluator is pure: `report.ts` may import only `node:crypto` (deterministic hashing) + local modules; `score.ts` and `teeth.ts` import local modules only. No fs, no network, no process, no child processes. | `evaluator-pure--*` (3 constraints) | per-file `forbiddenPattern` |
| Proposal compilation and review semantics remain pure; only the assistant, SCM-authentication, quarantine, and CLI shells own I/O. | `review-core-no-io-imports` plus explicit component relationships | per-file `forbiddenPattern` + authored graph |
| Only the two process-owning bins (`cli.ts` and `mcp-server.ts`) are exempt from the no-exit rule; every one of the 39 library modules returns or throws. | `only-cli-may-call-process-exit--*` (39 per-file constraints) | per-file `forbiddenPattern` |

Design notes, recorded honestly:

- **Per-file constraints instead of glob negation.** A `forbiddenPattern` carries one
  `path` glob and the glob language deliberately has no negation ("every file except
  `cli.ts`" is not expressible). The blueprint therefore enumerates per-file constraints.
  The obvious gap — a *new* src file arriving with no per-file constraint — is closed by
  construction: `tests/self-blueprint.test.ts` (run inside the self-gate workflow) fails
  whenever the actual `src/*.ts` set and the blueprint's coverage disagree, including
  `extraction.minFiles`, which is pinned to the exact file count as a fail-closed scan
  floor.
- **What the content patterns can and cannot see.** The import-allowlist patterns are
  anchored single-line matches (`import … from 'x'`, `export … from 'x'`, and the
  multi-line closer `} from 'x'`). They do not see a dynamic `import('x')` expression.
  The `ts-morph` seam constraint does not rely on them — it rides the AST import graph.
- **Purity of the evaluator vs. wall-clock/randomness.** A content constraint forbidding
  `Math.random(`/`Date.now(` in the evaluator would false-fire today: those tokens appear
  in the evaluator's own *documentation comments* (they describe the mock-detection tooth).
  Determinism is instead proven behaviorally by the determinism test suite. Recorded here
  rather than silently narrowed.

## The extractor / facts seam

The blueprint never talks to `ts-morph`. It talks to **facts**:

- `RepositoryFactsExtractor` (`src/graph.ts`) is the seam interface:
  `extract(repoDir, revision) → ArchitectureGraph`. The provider registry selects the mature
  TypeScript AST, bounded line-scan, legacy Python import-surface, or structured Python module-graph
  implementation. A provider refuses, loudly, any extractor mode or constraint surface it cannot
  honor.
- `ArchitectureGraph` is the entire fact surface the evaluator sees: `components`,
  `guardEdges` (imports / provides / egress edges), and `coverage`
  (`scannedFiles`, `patternScan`, `unsupported` — the declared-honest envelope).
- The blueprint's `extraction` block drives the extractor: `profile`, `paths`,
  `minFiles`; every `forbiddenDependency.to` is auto-unioned into the forbidden-import
  scan set, and every `forbiddenPattern.pattern` into the content scan set — a constraint
  can never be silently unscannable.
- Adding a new language or surface means implementing the same interface and emitting the
  same graph shape; `evaluate()`, `score`, `teeth`, and the CLI are unchanged. That is the
  plugin seam this repository's own blueprint exercises end-to-end.

## Lane A / Lane B

Two lanes can gate this tree:

- **Lane B (live now)** — `self-gate.yml` builds the engine **from the commit under
  review** and runs `gate` + `teeth` + the sync test with it. Fail-closed: any non-pass
  verdict, toothless blueprint, or sync drift fails CI.
- **Lane A (live)** — the last **published** engine, installed from the
  public registry at an **exact version pin**, gates the tree. Lane A is independent of
  the code under review, so it cannot be fooled by a defective change to the engine
  itself.

The bootstrap-0 exception ended with the provenance-backed `bce-engine@0.1.0` publication.
Lane A now installs the exact `bce-engine@0.3.1` registry artifact independently, while Lane B continues to grade
the commit under review with its own build.

**The flip:** the `lane-a-pinned-gate` job already exists in
[`self-gate.yml`](https://raw.githubusercontent.com/blueprint-conformance/bce/main/.github/workflows/self-gate.yml) — it reads the exact pin from
[`.engine-pin.json`](https://raw.githubusercontent.com/blueprint-conformance/bce/main/.engine-pin.json), and is **if-guarded on the pin being published AND
`bce-engine@<pin>` actually resolving on npm**. The guard is open: the job installs
`bce-engine@0.3.1` (exact pin, no range) and runs the same `gate` verb. The job is also a required
branch-protection check.

## The Lane-A pin ceremony (forward reference)

The Lane-A flip is a **pin ceremony**, not a version range — and the distinction is the whole point
of the lane. Lane A must be independent of the code under review, so it installs an **exact** version:

- `bce-engine@0.3.1`, never `^0.3.1` or `~0.3.1` or `@latest`. A range would let a later publish
  silently change the gate the tree is graded by, reintroducing the trusting-trust hole Lane A exists
  to close.
- The pin is bumped only by a deliberate, reviewed PR — the same way any load-bearing dependency pin
  moves. Bumping the Lane-A pin is a governed act, recorded in the diff, because it changes the
  independent grader every contributor is measured against.
- The pinned engine is the **published** artifact from the public registry, so a defective change to
  the engine in the PR under review cannot influence the Lane-A verdict: Lane A rebuilds nothing from
  the branch.

The exact steps are written up in [`docs/pin-ceremony.md`](https://blueprint-conformance.github.io/bce/source/docs/pin-ceremony.md), and the live exact pin
is recorded in [`.engine-pin.json`](https://raw.githubusercontent.com/blueprint-conformance/bce/main/.engine-pin.json). On a pull request, Lane A selects that file
from the event's exact base SHA; on push, it selects the merged tree. `v0.1.0` completed bootstrap;
subsequent pin bumps are therefore admitted by the previously published Lane-A engine.

## Branch-protection incident policy (attended recovery, not a skip flag)

A required, fail-closed check can occasionally block *its own fix* — the classic case is a change that
reddens the gate whose only correct resolution is that very change (a corrected gate, a fixed
extractor). This repository's current branch protection applies required checks to administrators;
there is no ordinary admin-merge bypass. Recovery therefore requires an explicit, temporary change
to branch protection (or a separately reviewed forward fix), and its policy is **pre-written, not
improvised**:

- A temporary protection change is **attended** — a human decides it, in the moment, with the red in
  front of them, records the before-state, and restores that exact state immediately after the
  forward fix lands.
- Every such bypass produces a **mandatory public incident record** in this repository: what was red,
  why the bypass was the correct forward action, and what re-greened the check afterward.

This is an *attended recovery path*, not a skip flag — and the distinction is exact. bce's "no skip
flag" claim is a claim about **the engine**: there is no `--skip` / `--no-verify` / `--force` in `bce`
that turns a red green (the test suite asserts it). It is not a claim that repository administrators
cannot reconfigure GitHub. The incident record and exact restoration are what keep that platform
recovery visible when it is used.

## Reproducing the self-gate locally

```sh
npm ci
npm run build
node dist/cli.js validate --blueprint .blueprints/engine.blueprint.json
node dist/cli.js gate  --repo . --repo-name blueprint-conformance/bce
npm run test:self-teeth-mutations
npx vitest run tests/self-blueprint.test.ts
```

Expected: blueprint VALID, gate score 100 (pass), and `extractor-real-proven` with all 47
constraints killed by 47 separately materialized source-tree mutants. The mutation manifest is
regenerated from the blueprint and is freshness-checked before the real CLI proof runs. Tests are
green. To watch the gate actually bite, add `import { Project } from 'ts-morph';` to
`src/score.ts` and re-run the gate: it exits 1 with two violations (the seam constraint,
via the AST import edge, and the evaluator-purity pattern) — then revert.

## What self-hosting already found

Assessing the engine's own blueprint surfaced a real engine gap on day one: the
`teeth` reddening mutation for `forbiddenDependency` injected its synthetic edge at a
placeholder path that could never match a `scopePaths`-narrowed constraint, so a
genuinely enforcing, scoped constraint was mislabeled `TRIVIALLY_GREEN`. Fixed in
`src/teeth.ts` (the injected edge now lands at a concrete in-scope path) with a
discriminating regression test in `tests/self-blueprint.test.ts`. The former evaluator-only gap is
now closed by `.blueprints/engine.teeth-mutations.json`: every self-blueprint clause maps to one
real create/replace/append/delete mutation, and the CLI refuses missing, duplicate, surviving,
out-of-scope, protected-surface, syntax-invalid, or collateral mutations. This proves the current
47 clauses can bite the current extraction/evaluation path; it does not prove the blueprint is a
complete specification of every desirable property.

## Full self-adoption

The source checkout installs its canonical skills under `.agents/skills/` and runs its built MCP
server through `.mcp.json` and `.codex/config.toml`. CI checks the installed skills for drift and
exercises the actual project MCP configuration. The enforced mode is explicit in `.bce-mode.json`.

[The solo-steward ceremony](https://blueprint-conformance.github.io/bce/source/docs/solo-steward-ratification.md) describes the live GitHub decision path.
`.bce-governance.json` identifies the steward; it establishes no independent review. The acceptance
check for a completed lifecycle is `node dist/cli.js doctor --repo .`: real-source teeth, matching
approved policy and adoption history, exact CI pin, project integrations, and full gate must agree.
A missing ceremony remains a warning until its authenticated history exists. Both blueprints now
carry digest-bound source mutation manifests: 47 engine clauses and 13 skill-standard clauses.
The latter includes exact file evidence for forbidden files, without inventing line numbers.
