2026-08-19 09:52:07 -07:00
|
|
|
|
# M0.8 — M0 composition gate
|
|
|
|
|
|
|
|
|
|
|
|
| Field | Value |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| Phase | M0 — Read-only spine |
|
|
|
|
|
|
| Size | M — 1–3 days |
|
2026-08-22 21:43:23 -07:00
|
|
|
|
| Status | ✅ Done |
|
2026-08-19 09:52:07 -07:00
|
|
|
|
| Flags | gate |
|
|
|
|
|
|
| Spec | inlined below |
|
|
|
|
|
|
| Blocks | all of M0 |
|
|
|
|
|
|
|
|
|
|
|
|
## Goal
|
|
|
|
|
|
|
|
|
|
|
|
Prove the parts compose and that the swappable part is genuinely swappable —
|
|
|
|
|
|
the properties no single M0 task owns.
|
|
|
|
|
|
|
|
|
|
|
|
## Facts (inlined — no spec read needed)
|
|
|
|
|
|
|
|
|
|
|
|
The claim this phase makes: **a new input kind is added by implementing
|
|
|
|
|
|
`RecordSource`, and nothing downstream changes.** If that is false, the crate
|
|
|
|
|
|
split bought nothing and the streaming work later will be a rewrite.
|
|
|
|
|
|
|
|
|
|
|
|
The gate proves it by adding a third source that resembles nothing already
|
|
|
|
|
|
supported, and asserting the rest of the pipeline is untouched.
|
|
|
|
|
|
|
|
|
|
|
|
Second claim: the whole phase is free. No model calls, so the gate is a CI job
|
|
|
|
|
|
that runs on every push without a gateway or credentials.
|
|
|
|
|
|
|
|
|
|
|
|
## Steps
|
|
|
|
|
|
|
|
|
|
|
|
1. Implement `SyntheticSource` in the test tree only — generates records from a
|
|
|
|
|
|
seed, has no file format, and deliberately produces a record larger than the
|
|
|
|
|
|
chunk budget and a run of empty-text records.
|
|
|
|
|
|
2. Assert `chunks()` handles it with **no change** to `mem-chunk`.
|
|
|
|
|
|
3. Assert dependency direction still holds (M0.1 assertion 3) after three sources
|
|
|
|
|
|
exist — this is when someone is tempted to reach backwards.
|
|
|
|
|
|
4. Run `mem ingest --dry-run` over all three sources for one project and diff the
|
|
|
|
|
|
summary against a committed expected file.
|
|
|
|
|
|
5. Wire the whole thing as a required CI job.
|
|
|
|
|
|
|
|
|
|
|
|
## Acceptance
|
|
|
|
|
|
|
|
|
|
|
|
- Three sources, one `RecordSource`, zero source-specific branches in `mem-chunk`
|
|
|
|
|
|
or `mem-cli`.
|
|
|
|
|
|
- Dry-run summary diffs empty against the committed expectation.
|
|
|
|
|
|
- CI job passes with no network and no credentials.
|
|
|
|
|
|
|
|
|
|
|
|
## Verify
|
|
|
|
|
|
|
|
|
|
|
|
**Harness:** committed expected-output file, diffed. The script's output is the
|
|
|
|
|
|
review artifact.
|
|
|
|
|
|
|
|
|
|
|
|
**Integration test** — `tests/it_m0_gate.rs`:
|
|
|
|
|
|
1. `a1_third_source_needs_no_downstream_change` — `SyntheticSource` flows through
|
|
|
|
|
|
`chunks()`; assert `git diff --stat crates/mem-chunk crates/mem-cli` is empty
|
|
|
|
|
|
for the commit that added it (enforced by a CI step, not by the test binary).
|
|
|
|
|
|
2. `a2_no_source_specific_branches` — grep `crates/mem-chunk` and `crates/mem-cli`
|
|
|
|
|
|
for the strings `pi:`, `claude:`, `sessionId`, `toolResult`; assert none
|
|
|
|
|
|
appear outside `mem-ingest`.
|
|
|
|
|
|
3. `a3_dry_run_golden` — run the dry run over all three sources, diff against
|
|
|
|
|
|
`expected/m0-gate.txt`; empty diff is the only pass.
|
|
|
|
|
|
4. `a4_offline` — the entire gate runs with networking disabled.
|
|
|
|
|
|
5. `a5_lossless_end_to_end` — records in equals records out, across all three
|
|
|
|
|
|
sources composed.
|
|
|
|
|
|
|
|
|
|
|
|
**Command:** `cargo test --workspace m0_gate`
|
|
|
|
|
|
|
|
|
|
|
|
**False pass:**
|
|
|
|
|
|
- Adding `SyntheticSource` in a way that mirrors the pi format. It then exercises
|
|
|
|
|
|
the same code path and proves nothing about generality. It must have no file,
|
|
|
|
|
|
no header line, and an oversized record.
|
|
|
|
|
|
- Assertion 3 passing because the expected file was regenerated in the same
|
|
|
|
|
|
commit. A changed `expected/` file in a diff is a claim that the contract
|
|
|
|
|
|
changed, and must be reviewed as one.
|
|
|
|
|
|
|
|
|
|
|
|
## Traps
|
|
|
|
|
|
|
|
|
|
|
|
- Skipping assertion 2 because "obviously there are no source-specific branches".
|
|
|
|
|
|
There will be — the first `if provenance.starts_with("pi:")` gets added to fix
|
|
|
|
|
|
a real bug and is entirely reasonable in isolation.
|
|
|
|
|
|
- Letting the gate need credentials. The value of an offline gate is that it runs
|
|
|
|
|
|
on every push; the moment it needs a gateway key it becomes a nightly job that
|
|
|
|
|
|
nobody watches.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
Background: [DESIGN.md](../DESIGN.md) — `mem-chunk`, Verification
|