# M0.8 — M0 composition gate | Field | Value | |---|---| | Phase | M0 — Read-only spine | | Size | M — 1–3 days | | Status | ✅ Done | | 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