Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ jobs:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
steps:
- uses: actions/checkout@v4
with:
# `coherence log` materializes the base ref in a throwaway worktree to
# diff the invariant/boundary set — it needs history, not a depth-1 tip.
fetch-depth: 0

- uses: actions/setup-node@v4
with:
Expand All @@ -50,6 +54,17 @@ jobs:

- run: npm ci

# Structural ledger: a PR may not silently DROP an invariant or a boundary
# (a removed chokepoint/oracle anchor) — exactly the loss a prose review
# misses. `--strict` exits nonzero on a loss; a deliberate removal re-pins by
# being a normal, reviewed diff (the gate reports WHAT was dropped, not that
# nothing may change). PR-only: a push has no base ref to diff against.
- name: No dropped boundaries (structural ledger)
if: github.event_name == 'pull_request'
run: |
git fetch --no-tags --depth=1 origin "${{ github.event.pull_request.base.ref }}"
npm run coherence:log -- FETCH_HEAD HEAD --strict

# The worker entry statically imports the render-fragment bundle from
# dist/ (gitignored), so it must exist before vitest loads the worker.
- name: Build worker page bundles
Expand Down Expand Up @@ -103,6 +118,14 @@ jobs:
- name: Coherence
run: npm run coherence:verify

# The committed generated artifacts (AGENTS.md + docs/coherence/*) must
# match the spec tree. coherence:verify checks claims-vs-code, NOT
# docs-vs-specs, so without this a spec edit that isn't followed by
# `npm run coherence:docs` lands a stale agent map (timestamps normalized
# out of the diff). Symmetric with the atlas:check drift gate above.
- name: Coherence docs freshness
run: npm run coherence:docs:check

mcp-smoke:
name: live /mcp surface
runs-on: ubuntu-latest
Expand Down
7 changes: 4 additions & 3 deletions mnemion-js/AGENTS.md

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions mnemion-js/docs/coherence/_graph.html

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions mnemion-js/docs/coherence/_overview.html

Large diffs are not rendered by default.

200 changes: 124 additions & 76 deletions mnemion-js/docs/coherence/graph.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions mnemion-js/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion mnemion-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@
"setup": "bash scripts/setup.sh",
"enable-documents": "bash scripts/enable-documents.sh",
"coherence:docs": "coherence docs",
"coherence:docs:check": "coherence docs --check",
"coherence:claude": "coherence claude",
"coherence:verify": "coherence verify --fast",
"coherence:verify:full": "coherence verify",
"coherence:verify:staged": "coherence verify --fast --staged",
"coherence:log": "coherence log",
"coherence:decompose": "coherence decompose",
"coherence:drift": "coherence drift",
"coherence:onboard": "coherence onboard",
Expand Down Expand Up @@ -59,7 +62,7 @@
"@types/react-dom": "^19.2.0",
"@vitejs/plugin-react": "^5.0.0",
"ai": "^6.0.206",
"coherence-harness": "github:daniloc/coherence#v0.3.5",
"coherence-harness": "github:daniloc/coherence#v0.4.0",
"concurrently": "^9.0.0",
"typescript": "^6.0.3",
"vite": "^8.0.16",
Expand Down
13 changes: 12 additions & 1 deletion mnemion-js/scripts/conventions.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -202,5 +202,16 @@ if (mode === "--check") {
console.error(" Convert it to a contract, or (if intentional) re-pin with --update-baseline.\n");
process.exit(1);
}
console.log("\n ✓ convention ratchet held — no new conventions.\n");
console.log("\n ✓ convention ratchet held — no new conventions.");
// Surface the STANDING debt, not just the delta. The ratchet only blocks
// GROWTH; without this line a baselined convention can sit forever, invisible.
// Each entry is an un-enshrined crossing — the goal is to drain this to zero.
if (base.length) {
console.log(`\n Baselined debt: ${base.length} convention(s) tolerated (toward zero):`);
for (const b of [...base].sort((a, z) => z.sites - a.sites)) {
const live = conventions.find((c) => c.name === b.name);
console.log(` - ${b.name} (${b.sites} sites)${live ? "" : " — gone from code; drop from baseline"}`);
}
}
console.log("");
}
20 changes: 19 additions & 1 deletion mnemion-js/scripts/injection-lint.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -112,5 +112,23 @@ if (mode === "--check") {
console.error(" and safe — re-pin with --update-baseline.\n");
process.exit(1);
}
console.log(" ✓ injection ratchet held — no new raw interpolation sites.\n");
console.log(" ✓ injection ratchet held — no new raw interpolation sites.");
// Standing debt: each baselined site is a raw interpolation enshrinement (a
// quoteIdent-style fusion) would retire. The ratchet only blocks NEW sites; print
// the tolerated count + per-file breakdown so the surface stays visible and the
// goal (drain toward zero) is legible. A baselined site gone from code is dead
// weight to drop.
const byFile = {};
let stale = 0;
for (const k of base) {
const file = k.split("|")[1] ?? "?";
byFile[file] = (byFile[file] ?? 0) + 1;
if (!current.has(k)) stale++;
}
console.log(`\n Baselined debt: ${base.size} reviewed interpolation site(s) tolerated (toward zero):`);
for (const [f, c] of Object.entries(byFile).sort((a, z) => z[1] - a[1])) {
console.log(` ${String(c).padStart(3)} ${f}`);
}
if (stale) console.log(` (${stale} baselined site(s) no longer in code — re-pin with --update-baseline to drop them)`);
console.log("");
}
29 changes: 29 additions & 0 deletions project-docs/archived/self-enforcing-declarations.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,35 @@ A declaration is self-enforcing when it has all five. Missing any one reintroduc

Measure architectural quality by **blast radius**: *how many files must an agent read, and keep in sync, to safely add one pattern, one tool, one route, one invariant?* Drive that number toward one. A change whose correctness depends on remembering to touch six unguarded lists is a change that will eventually be made wrong. The totality check is what lets the number be one *in practice* — it turns "six files you must remember" into "one file; the test names the rest if you forget."

## The `## why` discipline (the non-derivable residue)

The same blast-radius logic governs the spec layer. A boundary's *mechanism* —
its chokepoint symbol, its oracle, the fact that the oracle iterates a live
domain and fails the build — is **derivable**: it is already carried by the
`## invariants` list and the `boundary "<name>" at <chokepoint> via test/guard
"<oracle>"` claims, and the harness renders it into `AGENTS.md` and the trust
atlas from those claims. Restating it in `## why` prose is a second copy that
can drift — and the only copy a totality check can't keep honest, because prose
isn't derived.

So `## why` carries **only the non-derivable residue**: the bug each boundary
was built to kill, or the rejected alternative it rules out. That is the one
thing no claim, oracle, or generated doc can reconstruct from the code. The rule:

> If a sentence in `## why` could be regenerated from the boundary claims, it
> doesn't belong in `## why`. Name a mechanism only as far as the *rationale*
> needs it ("`_access_tokens` was `patch_only`, so an injected agent could mint
> a `*` token" — the bug); never to *re-document* it ("the oracle iterates
> `SENSITIVE_COLUMNS` and fails the build" — derivable, drop it).

Commit `90aa607` ("trim the Hive spec `## why` to the non-derivable rationale")
applied this by hand; the spec's own meta-note records the split. The durable
form is a harness lint — *flag a `## why` sentence that names a chokepoint or
oracle symbol already anchored in a claim alongside an oracle-verb ("iterates",
"totality", "fails the build")* — which belongs upstream in `coherence`, not in
another repo-local sidecar (see `project-docs/coherence-tooling-upstream.md`).
Until then it is authored discipline, enforced in review.

## The per-pattern unification (the open work)

Today, per-pattern truth is spread across four files: `schema.ts` (DDL/facets/description), `policy.ts` (write-class/consent/prime/audit), `kernel.ts` (immutable fields + `ON_CREATE` validation), `hive.ts` (lifecycle hooks). Each is individually clean, but the *organism* — "what is `_documents`, fully" — is not described in one place. That tax grows with every new facet of behavior.
Expand Down
83 changes: 83 additions & 0 deletions project-docs/coherence-tooling-upstream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Upstreaming the coherence meta-tooling

Status: proposal. Target: `github:daniloc/coherence` (the harness this repo pins).

## The problem

Three coherence-shaped tools live as repo-local scripts in `mnemion-js/scripts/`,
not in the harness:

| script | lines | role | state it owns |
| --- | --- | --- | --- |
| `atlas.mjs` | ~380 | derives the trust atlas (charts + tiered transition maps) from the `## works when` boundary claims | — (pure derivation) |
| `conventions.mjs` | ~206 | convention-vs-contract detector + growth ratchet | `conventions-baseline.json` |
| `injection-lint.mjs` | ~116 | raw-SQL/HTML interpolation ratchet | `injection-baseline.json` |

All three **re-parse the spec tree and/or `graph.json` by hand**. That is a hidden
coupling to harness output: the harness already builds that graph internally, and
any change to its claim grammar or JSON shape can silently break a sidecar with no
test catching it. The atlas in particular is *pure derivation from boundary claims*
— the same input `coherence verify` already consumes — yet it lives as a 380-line
parallel parser.

This is the sprawl the project's own doctrine warns about, one level up: the
coherence apparatus is itself enforced by a set of conventions (run these scripts,
keep these baselines) rather than by one tool.

## The proposal

Promote the three to first-class harness subcommands, so they share the harness's
graph build, claim parser, and `--check`/`--update-baseline` conventions:

- **`coherence atlas`** — render the trust atlas (`atlas.md`) from the boundary
claims; `--check` is the drift gate. Inputs it needs that the spec doesn't yet
carry as first-class fields: the **chart** a chokepoint moves *from → to*, and
its **tier**. Today `atlas.mjs` infers these from a curated map. Upstreaming
means adding two optional claim fields — e.g.
`boundary "<n>" at <chk> via test "<o>" from <chartA> to <chartB>` — so the atlas
derives with no sidecar map. The chart vocabulary (`owner-trusted`,
`served-untrusted`, `agent-mcp`, `public-egress`, `federated`, `storage`) becomes
a small declared table in the root spec.
- **`coherence conventions`** — the guard-vs-contract detector + ratchet, baseline
managed by the harness. The guard-verb lexicon and the curated seed move into
config. Output gains the standing-debt view this branch added (P4).
- **`coherence lint-injection`** (or a generic `coherence lint-sinks`) — the
interpolation-surface ratchet, with the sink contexts (sql-ident, html-value) as
config.

## Why upstream rather than keep local

1. **One version pin, one parser.** The sidecars track harness internals informally;
as subcommands they move with the harness and break loudly (its own tests) if the
graph shape changes.
2. **Baselines become a harness concern** — `--update-baseline` / drift semantics are
identical across all three today; the harness already implements exactly this for
`docs --check`. Don't reimplement it three more times per consuming repo.
3. **Every coherence consumer inherits them.** The trust atlas and the
convention/sink ratchets aren't mnemion-specific ideas; they're the natural
companions to the boundary ratchet the harness already ships.
4. **Closes the meta-gap.** The harness gains the affordances that let it police its
*own* doctrine, instead of each project re-deriving them.

## Companion: the `## why` lint (P3)

Same home, same input. The harness already parses both `## why` prose and the
boundary claims. A coverage check should **flag a `## why` sentence that names a
chokepoint or oracle symbol already anchored in a claim** alongside an oracle-verb
("iterates", "totality", "fails the build") — i.e. prose restating derivable
mechanism instead of carrying the non-derivable rationale. See
`project-docs/archived/self-enforcing-declarations.md` ("The `## why` discipline").
This belongs upstream precisely because it correlates two things the harness already
holds; a repo-local version would be a fourth sidecar re-parsing the spec tree.

## Migration shape (non-breaking)

1. Land the subcommands in the harness behind the existing config; keep the scripts as
thin shims that call them, so CI keeps working through one release.
2. Add the optional claim fields (chart/tier) — atlas falls back to "untiered" when
absent, so no spec is forced to change on day one.
3. Once the harness release is pinned, delete the sidecars and their baselines (the
harness owns the baselines), and point the `*:check` npm scripts at the subcommands.

Until that lands, the scripts stay — but treat them as **debt to retire**, not the
end state.
Loading