Architecture audit: enforce domain purity, derive the conformance population, document Mongo atomicity - #14
FlashyLabs wants to merge 6 commits into
Conversation
The vendored copy of @flashyos/directory's checker had drifted behind the authoritative EDGE_REQUIRES: it required settled edges to carry only ['sealed'], where packages/directory/src/types.ts (origin/main) requires ['sealed', 'capability']. A lagging copy does not fail — it disagrees silently, validating a settled edge the estate's own spec rejects. Re-vendored byte-identical to flashyos origin/main packages/directory/vendor-check-directory.mjs (the shipping canonical, not a stale working tree). Verified: the re-vendored checker still validates this repo's own directory.fragment.json (16 nodes, 22 edges, 0 problems). The estate-level differential tools/vendored-directory.test.mjs in flashyos is the guard that catches this class of drift. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X8LDnsexvNjSkqc2ihZR7U
The ledger's headline architectural claim — the domain reads no database, calls no clock, and generates no randomness, which is the whole reason it can move onto a chain without its rules changing — was only half enforced. eslint guards the runtime half (Date, Date.now, Math.random banned in src/domain), but nothing guarded the dependency direction: a domain module importing an adapter, a port, node:fs, or a third-party package would pass lint, typecheck and every behavioural test, silently breaking the seam the package is built on. - tests/domain-purity.test.ts reads the real import graph of src/domain with ts.preProcessFile (not a regex) and refuses any specifier that is not a domain sibling or node:crypto. Verified non-vacuous: it goes red when an adapter import or node:fs is injected into a domain file. - docs/INVARIANTS.md gains I-9, making purity a numbered, cited invariant; tests/invariants.test.ts holds the doc and the suite together as before. - CLAUDE.md gains the "what makes this repository different" section the file template asked for: what it is, that it ships as a library with no deploy target, the ports-and-adapters shape, and the invariants an agent must not break. Full suite 198 passing, typecheck clean, new test lint-clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X8LDnsexvNjSkqc2ihZR7U
…city
Two audit findings from the ports-and-adapters layer, each closed with tests
and documentation.
1. The conformance suite's worth is the set of adapters it is pointed at, and
that set was a hand-kept list — the shape of failure this estate has hit
before (pulse.yml over a population of zero). The population is now derived:
- tests/adapters.catalog.ts declares every writable LedgerStore the package
ships, as the single source of truth.
- conformance.test.ts builds its harnesses from the catalog (Mongo skipped
without a database but never dropped from the count) and asserts the
harnessed classes equal the catalog.
- tests/adapter-coverage.test.ts checks the catalog against the package's
real exports, recognising a writable store by structure (an append method)
so the read-only GoldLedgerReader is excluded by that same fact. A new
adapter exported-but-uncataloged, or cataloged-but-unharnessed, now fails
the build. Documented in docs/INVARIANTS.md, "Coverage".
2. The Mongo adapter's refusal to tear a transfer — reject a multi-entry append
when constructed without a client — had no test, because conformance always
passes one. tests/mongo-adapter.test.ts covers it with a fake Db that runs on
any machine, no infrastructure. The concurrency and atomicity design (indexes
as the guarantee, the duplicate-key disambiguation, the session requirement)
is recorded in docs/adr/0004-mongo-concurrency-and-atomicity.md.
Full suite 205 passing, typecheck clean, new files lint-clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X8LDnsexvNjSkqc2ihZR7U
…d test npm run lint (hence npm run check, hence CI) was red before this branch: charter-served.test.ts did `const parsed = JSON.parse(served)` — an `any` — and then `parsed.slug` / `parsed.roles`, three no-unsafe-member-access errors under the repo's strictTypeChecked config (typescript 5.9.3, as the lockfile pins). Not introduced here, but it blocks the repo's own gate and this PR. Typed the parse as Record<string, unknown> and narrowed `roles` through Array.isArray before reading .length — no behaviour change, the two assertions are identical. npm run check now passes: typecheck + lint + coverage (98.24% stmts, above the 90/85/90/90 thresholds). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X8LDnsexvNjSkqc2ihZR7U
|
This PR is currently in conflict with Resolving it is a routine Generated by Claude Code |
…architecture-audit-79az1h # Conflicts: # tests/charter-served.test.ts
|
Conflict resolved — merged The branch is now conflict-free and
I'm not adding the ledger row from here: Once that row lands on Generated by Claude Code |
|
CI on
Both clear from this PR with zero further change once Generated by Claude Code |
…elease row Both were pre-existing on main and red on this PR; the user asked for the brace-expansion fix and for the record to reflect production reality. - Dependency audit: `npm audit fix --package-lock-only` bumps the transitive brace-expansion past the quadratic-expansion / recursion DoS advisories (GHSA-q2hr-2g5m-vwhr and siblings). Lockfile-only, package.json untouched; `npm ci` clean, `npm audit --audit-level=high` reports 0 vulnerabilities, and the full suite still passes (the bump is transitive under eslint's matcher). - releases.test: `package.json` declares 1.0.0 (cf4d9ba) with no ledger row. Measured the publish rather than assuming it: all three publish.yml runs for v1.0.0 (2026-09-25) failed at "Publish to npm" with `npm error code ENEEDAUTH` — the tarball built but never authenticated to npm.pkg.github.com, so 1.0.0 is NOT on the registry. A `v1.0.0` git tag was pushed anyway, which the ledger's own rule forbids (a tag must back a landed publish), so the row records the tag as unbacked, not ✅. The 2026-10-05 note explains the regression and that fixing the publish auth is an operator action. npm run check green: typecheck + lint + coverage (98.24% stmts), 219 tests, 0 vulnerabilities. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X8LDnsexvNjSkqc2ihZR7U
What changed
An architecture audit of
@flashylabs/ledgeragainst its own stated design, closing the real enforcement gaps it surfaced — each with tests and documentation, no behaviour changed. (1) Domain purity was only half-enforced: eslint guards clocks/randomness insrc/domain, but nothing guarded the dependency direction, so a domain module importing an adapter, a port, ornode:fswould pass lint, typecheck and every test while silently dissolving the ports-and-adapters seam. Now pinned as invariant I-9 and enforced bytests/domain-purity.test.ts(reads the real import graph viats.preProcessFile). (2) The conformance population was hand-kept — the shape of failure this estate has hit before — so it is now derived:tests/adapters.catalog.tsis the single source of truth,conformance.test.tsbuilds its harnesses from it, andtests/adapter-coverage.test.tschecks it against the package's real exports. The audit also filled the deliberately-blankCLAUDE.mdsection, documented the Mongo concurrency/atomicity design as ADR 0004, added the one missing Mongo safety test (refusing to tear a transfer without a client), and fixed a pre-existing lint break incharter-served.test.tsthat hadnpm run check(and CI) red before this branch. This branch also carries one prior unmerged commit (directory/1re-vendor).Ledger invariants
src/domain— strengthened: the import-graph half is now enforced (I-9), not only the runtime halfVerification
npm run checkpasses locally — typecheck + lint + coverage all green (205 tests, 17 files; coverage 98.24% stmts / 92.61% branch / 100% func / 100% line, above the 90/85/90/90 thresholds). Lint was red before this branch on a pre-existingany-access incharter-served.test.ts(TS 5.9.3 per the lockfile); fixed here by typing the parse, no behaviour change.Migration impact
None. No entry format change and no change to the
hashEntryinput, so every chain written before this PR verifies unchanged. The work is tests, documentation, one test-support module (tests/adapters.catalog.ts), and a typing fix in a test;src/is untouched.🤖 Generated with Claude Code
https://claude.ai/code/session_01X8LDnsexvNjSkqc2ihZR7U