1	//! The M10 capstone integration (ADR-0019) — the dual-consumer + thread-spanning
2	//! + two-origin proof, end to end, from the same harness source files.
3	//!
4	//! This is the proof the milestone exists for: the **same** per-session log files
5	//! feed two consumers with opposite needs without conflict —
6	//!
7	//! - the **echo-commune** reads them **opaque + verbatim** through `[history]`
8	//!   (full fidelity, extra adapter fields preserved), while
9	//! - the **digest** reads them **contract-typed** through its own `[digest]`
10	//!   extractor (REQ-TERM-5) —
11	//!
12	//! the digest **spans** a `/clear` across two sessions with a visible boundary
13	//! marker (REQ-TERM-6), and **interleaves** spt's own context-injection entries
14	//! (psyche download, owl message) with the extracted activity by `ts`
15	//! (REQ-TERM-7). The M9 dual-consumer conflict (one normalizer forced to serve
16	//! both) is gone — two seams, one source.
17	// [int->REQ-TERM-5]
18	// [int->REQ-TERM-6]
19	// [int->REQ-TERM-7]
20
21	use std::collections::BTreeMap;
22	use std::time::Duration;
23
24	use spt_daemon::{project_endpoint_digest, render_digest, DigestOverride};
25	use spt_live::fetch_history;
26	use spt_runtime::manifest::{History, HistoryStrategy};
27	use spt_store::info::{write_info, InfoJson};
28	use spt_store::perch::{self, resolve_perch_path, ParentHint};
29	use spt_store::sessions::{SessionEntry, SessionTrigger};
30	use spt_term::DigestEntry;
31
32	// A stdin pass-through: the source bytes round-trip to stdout. As a `[history]`
33	// normalizer it preserves full fidelity (echo's opaque needs); as a `[digest]`
34	// extractor over a contract-shaped source it is the trivial identity extractor.
35	#[cfg(unix)]
36	const PASSTHROUGH: &str = "cat";
37	#[cfg(windows)]
38	const PASSTHROUGH: &str = "cmd /C findstr /r \".\"";
39
40	#[test]
41	fn one_source_two_consumers_spans_and_merges() {
42	    let home = tempfile::tempdir().expect("tempdir");
43	    std::env::set_var("SPT_HOME", home.path());
44	    let hp = home.path();
45
46	    // Two per-session logs, each a contract record carrying an EXTRA adapter field
47	    // (the fidelity the echo consumer keeps and the digest contract ignores).
48	    std::fs::write(
49	        hp.join("sess-A.jsonl"),
50	        "{\"role\":\"input\",\"text\":\"before clear\",\"ts\":\"2026-06-13T21:00:00Z\",\"raw_extra\":\"FULL-FIDELITY-A\"}\n",
51	    )
52	    .unwrap();
53	    std::fs::write(
54	        hp.join("sess-B.jsonl"),
55	        "{\"role\":\"input\",\"text\":\"after clear\",\"ts\":\"2026-06-13T21:00:10Z\",\"raw_extra\":\"FULL-FIDELITY-B\"}\n",
56	    )
57	    .unwrap();
58
59	    // One adapter, TWO seams over the SAME source files: [history] (opaque, for
60	    // echo) and [digest] (contract, for the digest). Single-quoted TOML literals
61	    // carry the path + findstr quotes verbatim.
62	    let src_tpl = hp.join("sess-{session_id}.jsonl");
63	    let manifest = format!(
64	        "[adapter]\nname = \"cc\"\nkind = \"harness\"\nversion = \"1\"\nmin_spt_core_version = \"0\"\n\n\
65	         [history]\nstrategy = \"locate_normalize\"\nlocate_template = '{src}'\nnormalize_command = '{pass}'\n\n\
66	         [digest]\nextractor = '{pass}'\nsource = '{src}'\n",
67	        src = src_tpl.display(),
68	        pass = PASSTHROUGH,
69	    );
70	    let asrc = perch::spt_home().join("srcs").join("cc");
71	    std::fs::create_dir_all(&asrc).unwrap();
72	    std::fs::write(asrc.join("manifest.toml"), manifest).unwrap();
73	    spt_runtime::registry::register(&perch::adapters_dir(), &asrc, 1).unwrap();
74
75	    // Establish the endpoint + its session ledger (boot A, then /clear → B).
76	    let perch = resolve_perch_path("cc", ParentHint::Infer);
77	    std::fs::create_dir_all(&perch).unwrap();
78	    let mut rec = InfoJson::new("cc", "0", std::process::id(), "B", "live_agent");
79	    rec.adapter = Some("cc".to_string());
80	    write_info(&perch, &rec).unwrap();
81	    spt_store::sessions::append(
82	        &perch,
83	        &SessionEntry {
84	            ts: "2026-06-13T21:00:00Z".into(),
85	            session_id: "A".into(),
86	            trigger: SessionTrigger::Boot,
87	        },
88	    )
89	    .unwrap();
90	    spt_store::sessions::append(
91	        &perch,
92	        &SessionEntry {
93	            ts: "2026-06-13T21:00:05Z".into(),
94	            session_id: "B".into(),
95	            trigger: SessionTrigger::Clear,
96	        },
97	    )
98	    .unwrap();
99
100	    // spt's own context injections (the second origin) → digest.log, by ts: a
101	    // psyche download in session A, an owl message in session B.
102	    spt_store::history::append_context_entry(
103	        &perch,
104	        "psyche_download",
105	        "downloaded 3 psyche notes",
106	        "2026-06-13T21:00:01Z",
107	    )
108	    .unwrap();
109	    spt_store::history::append_context_entry(
110	        &perch,
111	        "owl_message",
112	        "ping from doyle",
113	        "2026-06-13T21:00:11Z",
114	    )
115	    .unwrap();
116
117	    // ── Consumer 1: the echo-commune reads [history] OPAQUE + verbatim ──────────
118	    let history = History {
119	        strategy: HistoryStrategy::LocateNormalize,
120	        fetcher: None,
121	        locate_template: Some(src_tpl.display().to_string()),
122	        normalize_command: Some(PASSTHROUGH.to_string()),
123	    };
124	    let mut keys = BTreeMap::new();
125	    keys.insert("session_id".to_string(), "A".to_string());
126	    let echo = fetch_history(&history, &keys, "cc", ParentHint::Infer, Duration::from_secs(10))
127	        .expect("history fetch");
128	    assert_eq!(echo.len(), 1);
129	    assert!(
130	        echo[0].raw.contains("FULL-FIDELITY-A"),
131	        "echo keeps the adapter's full-fidelity line verbatim: {}",
132	        echo[0].raw
133	    );
134
135	    // ── Consumer 2: the digest reads [digest] CONTRACT, spanned + merged ────────
136	    let digest = project_endpoint_digest("cc", &DigestOverride::default());
137	    let inputs: Vec<_> = digest.turns.iter().filter_map(|t| t.input.as_deref()).collect();
138	    assert_eq!(
139	        inputs,
140	        vec!["before clear", "after clear"],
141	        "the digest SPANS the /clear (both sessions, window not reset)"
142	    );
143
144	    // A /clear boundary divider separates the sessions (REQ-TERM-6).
145	    assert!(
146	        digest.turns.iter().any(|t| t.entries.iter().any(|e| matches!(
147	            e,
148	            DigestEntry::Boundary { kind, .. } if kind == "clear"
149	        ))),
150	        "a /clear boundary marker is present"
151	    );
152
153	    // The psyche download (ts in session A) interleaves into the first turn, the
154	    // owl message (ts in session B) into the second — two-origin by ts (REQ-TERM-7).
155	    let turn_a = &digest.turns[0];
156	    assert!(
157	        turn_a.entries.iter().any(|e| matches!(
158	            e,
159	            DigestEntry::Context { kind, .. } if kind == "psyche_download"
160	        )),
161	        "psyche download interleaves into session A's turn: {:?}",
162	        turn_a.entries
163	    );
164	    let last = digest.turns.last().unwrap();
165	    assert!(
166	        last.entries.iter().any(|e| matches!(
167	            e,
168	            DigestEntry::Context { kind, body, .. } if kind == "owl_message" && body.contains("doyle")
169	        )),
170	        "owl message interleaves into session B's turn: {:?}",
171	        last.entries
172	    );
173
174	    // The contract dropped the adapter's extra field (digest is contract-typed,
175	    // not opaque) — the two consumers genuinely differ on the same source.
176	    let rendered = render_digest(&digest);
177	    assert!(
178	        !rendered.contains("FULL-FIDELITY"),
179	        "the digest is contract-typed (extra adapter fields ignored): {rendered}"
180	    );
181	    assert!(rendered.contains("── /clear ──"), "boundary renders distinctively: {rendered}");
182	}
183

//! The M10 capstone integration (ADR-0019) — the dual-consumer + thread-spanning
//! + two-origin proof, end to end, from the same harness source files.
//!
//! This is the proof the milestone exists for: the **same** per-session log files
//! feed two consumers with opposite needs without conflict —
//!
//! - the **echo-commune** reads them **opaque + verbatim** through `[history]`
//!   (full fidelity, extra adapter fields preserved), while
//! - the **digest** reads them **contract-typed** through its own `[digest]`
//!   extractor (REQ-TERM-5) —
//!
//! the digest **spans** a `/clear` across two sessions with a visible boundary
//! marker (REQ-TERM-6), and **interleaves** spt's own context-injection entries
//! (psyche download, owl message) with the extracted activity by `ts`
//! (REQ-TERM-7). The M9 dual-consumer conflict (one normalizer forced to serve
//! both) is gone — two seams, one source.
// [int->REQ-TERM-5]
// [int->REQ-TERM-6]
// [int->REQ-TERM-7]

use std::collections::BTreeMap;
use std::time::Duration;

use spt_daemon::{project_endpoint_digest, render_digest, DigestOverride};
use spt_live::fetch_history;
use spt_runtime::manifest::{History, HistoryStrategy};
use spt_store::info::{write_info, InfoJson};
use spt_store::perch::{self, resolve_perch_path, ParentHint};
use spt_store::sessions::{SessionEntry, SessionTrigger};
use spt_term::DigestEntry;

// A stdin pass-through: the source bytes round-trip to stdout. As a `[history]`
// normalizer it preserves full fidelity (echo's opaque needs); as a `[digest]`
// extractor over a contract-shaped source it is the trivial identity extractor.
#[cfg(unix)]
const PASSTHROUGH: &str = "cat";
#[cfg(windows)]
const PASSTHROUGH: &str = "cmd /C findstr /r \".\"";

#[test]
fn one_source_two_consumers_spans_and_merges() {
    let home = tempfile::tempdir().expect("tempdir");
    std::env::set_var("SPT_HOME", home.path());
    let hp = home.path();

    // Two per-session logs, each a contract record carrying an EXTRA adapter field
    // (the fidelity the echo consumer keeps and the digest contract ignores).
    std::fs::write(
        hp.join("sess-A.jsonl"),
        "{\"role\":\"input\",\"text\":\"before clear\",\"ts\":\"2026-06-13T21:00:00Z\",\"raw_extra\":\"FULL-FIDELITY-A\"}\n",
    )
    .unwrap();
    std::fs::write(
        hp.join("sess-B.jsonl"),
        "{\"role\":\"input\",\"text\":\"after clear\",\"ts\":\"2026-06-13T21:00:10Z\",\"raw_extra\":\"FULL-FIDELITY-B\"}\n",
    )
    .unwrap();

    // One adapter, TWO seams over the SAME source files: [history] (opaque, for
    // echo) and [digest] (contract, for the digest). Single-quoted TOML literals
    // carry the path + findstr quotes verbatim.
    let src_tpl = hp.join("sess-{session_id}.jsonl");
    let manifest = format!(
        "[adapter]\nname = \"cc\"\nkind = \"harness\"\nversion = \"1\"\nmin_spt_core_version = \"0\"\n\n\
         [history]\nstrategy = \"locate_normalize\"\nlocate_template = '{src}'\nnormalize_command = '{pass}'\n\n\
         [digest]\nextractor = '{pass}'\nsource = '{src}'\n",
        src = src_tpl.display(),
        pass = PASSTHROUGH,
    );
    let asrc = perch::spt_home().join("srcs").join("cc");
    std::fs::create_dir_all(&asrc).unwrap();
    std::fs::write(asrc.join("manifest.toml"), manifest).unwrap();
    spt_runtime::registry::register(&perch::adapters_dir(), &asrc, 1).unwrap();

    // Establish the endpoint + its session ledger (boot A, then /clear → B).
    let perch = resolve_perch_path("cc", ParentHint::Infer);
    std::fs::create_dir_all(&perch).unwrap();
    let mut rec = InfoJson::new("cc", "0", std::process::id(), "B", "live_agent");
    rec.adapter = Some("cc".to_string());
    write_info(&perch, &rec).unwrap();
    spt_store::sessions::append(
        &perch,
        &SessionEntry {
            ts: "2026-06-13T21:00:00Z".into(),
            session_id: "A".into(),
            trigger: SessionTrigger::Boot,
        },
    )
    .unwrap();
    spt_store::sessions::append(
        &perch,
        &SessionEntry {
            ts: "2026-06-13T21:00:05Z".into(),
            session_id: "B".into(),
            trigger: SessionTrigger::Clear,
        },
    )
    .unwrap();

    // spt's own context injections (the second origin) → digest.log, by ts: a
    // psyche download in session A, an owl message in session B.
    spt_store::history::append_context_entry(
        &perch,
        "psyche_download",
        "downloaded 3 psyche notes",
        "2026-06-13T21:00:01Z",
    )
    .unwrap();
    spt_store::history::append_context_entry(
        &perch,
        "owl_message",
        "ping from doyle",
        "2026-06-13T21:00:11Z",
    )
    .unwrap();

    // ── Consumer 1: the echo-commune reads [history] OPAQUE + verbatim ──────────
    let history = History {
        strategy: HistoryStrategy::LocateNormalize,
        fetcher: None,
        locate_template: Some(src_tpl.display().to_string()),
        normalize_command: Some(PASSTHROUGH.to_string()),
    };
    let mut keys = BTreeMap::new();
    keys.insert("session_id".to_string(), "A".to_string());
    let echo = fetch_history(&history, &keys, "cc", ParentHint::Infer, Duration::from_secs(10))
        .expect("history fetch");
    assert_eq!(echo.len(), 1);
    assert!(
        echo[0].raw.contains("FULL-FIDELITY-A"),
        "echo keeps the adapter's full-fidelity line verbatim: {}",
        echo[0].raw
    );

    // ── Consumer 2: the digest reads [digest] CONTRACT, spanned + merged ────────
    let digest = project_endpoint_digest("cc", &DigestOverride::default());
    let inputs: Vec<_> = digest.turns.iter().filter_map(|t| t.input.as_deref()).collect();
    assert_eq!(
        inputs,
        vec!["before clear", "after clear"],
        "the digest SPANS the /clear (both sessions, window not reset)"
    );

    // A /clear boundary divider separates the sessions (REQ-TERM-6).
    assert!(
        digest.turns.iter().any(|t| t.entries.iter().any(|e| matches!(
            e,
            DigestEntry::Boundary { kind, .. } if kind == "clear"
        ))),
        "a /clear boundary marker is present"
    );

    // The psyche download (ts in session A) interleaves into the first turn, the
    // owl message (ts in session B) into the second — two-origin by ts (REQ-TERM-7).
    let turn_a = &digest.turns[0];
    assert!(
        turn_a.entries.iter().any(|e| matches!(
            e,
            DigestEntry::Context { kind, .. } if kind == "psyche_download"
        )),
        "psyche download interleaves into session A's turn: {:?}",
        turn_a.entries
    );
    let last = digest.turns.last().unwrap();
    assert!(
        last.entries.iter().any(|e| matches!(
            e,
            DigestEntry::Context { kind, body, .. } if kind == "owl_message" && body.contains("doyle")
        )),
        "owl message interleaves into session B's turn: {:?}",
        last.entries
    );

    // The contract dropped the adapter's extra field (digest is contract-typed,
    // not opaque) — the two consumers genuinely differ on the same source.
    let rendered = render_digest(&digest);
    assert!(
        !rendered.contains("FULL-FIDELITY"),
        "the digest is contract-typed (extra adapter fields ignored): {rendered}"
    );
    assert!(rendered.contains("── /clear ──"), "boundary renders distinctively: {rendered}");
}


/spt:commune with immediate next steps and broad summary of the project's status + end goal. we'll resume in the next session, where we'll scope out a general `spt-claude-code` project plan, then hand…


Capstone E2E is genuinely comprehensive — proves dual-consumer (echo keeps `FULL-FIDELITY-A` verbatim while digest drops `raw_extra`), spanning (`before clear`+`after clear`, boundary renders `── /clear ──`), and two-origin ts-interleave (psyche→turn A, owl→turn B). All three int tags present. Now verify the other load-bearing pieces while the sweep builds.


Bash

echo "=== [digest] register validation in validate() ==="; grep -nE "digest|Digest|extractor" C:/Users/decid/Documents/projects/spt-core/crates/spt-runtime/src/manifest.rs | grep -iE "validate|extractor|err\(|require" | head; echo "=== no-silent-drop: record_to_tagged_result + any remaining .ok() drop ==="; grep -nE "record_to_tagged|from_str.*\.ok\(\)|fn record_to_tagged_result" C:/Users/decid/Documents/projects/spt-core/crates/spt-term/src/projection.rs | head; echo "=== owl_message tap (inject.rs) ==="; grep -nE "owl_message|emit|append_context_entry|notif" C:/Users/decid/Documents/projects/spt-core/crates/spt-live/src/inject.rs 2>&1 | head; echo "=== docs: MANIFEST [digest] + harness checklist + CHANGELOG supersession ==="; grep -rniE "\[digest\]|digest extractor" C:/Users/decid/Documents/projects/spt-core/docs/MANIFEST.md | head -3; grep -rliE "harness.*integration|integration checklist|harness checklist" C:/Users/decid/Documents/projects/spt-core/docs 2>&1 | head; grep -niE "ADR-0019|supersed|0008" C:/Users/decid/Documents/projects/spt-core/CHANGELOG.md 2>&1 | head

Verify register validation, no-silent-drop, owl tap, docs


=== [digest] register validation in validate() ===
42:    /// `[digest]` — the adapter-declared session-digest extractor seam (ADR-0019).
298:/// `[digest]` — the session-digest extractor seam (ADR-0019). Reverses M9's
300:/// extractor, distinct from `[history]` (which stays opaque + single-session and
301:/// feeds the echo-commune verbatim). The extractor maps the harness's **native**
308:/// language. So the extractor is an opaque command spt-core never parses, exactly
311:/// **Source.** By default the extractor reads the **same files as `[history]`**
322:    /// Opaque extractor command: native harness log → the `{role,text,tool,ts}`
325:    pub extractor: String,
327:    /// extractor reads. Absent ⇒ reuse `[history].locate_template` (DRY).
686:        // [digest] (ADR-0019): the extractor must be present, the log source must
=== no-silent-drop: record_to_tagged_result + any remaining .ok() drop ===
109:/// (ADR-0019). The silent `from_str().ok()` is gone: every drop now carries a
166:/// diagnostic core; [`record_to_tagged`] is the lossy `Option` wrapper.
168:pub fn record_to_tagged_result(raw: &str) -> Result<DigestRecord, DropReason> {
179:/// use [`record_to_tagged_result`] when the drop *reason* matters.
181:pub fn record_to_tagged(raw: &str) -> Option<DigestRecord> {
182:    record_to_tagged_result(raw).ok()
288:    let v: serde_json::Value = serde_json::from_str(raw).ok()?;
290:    serde_json::from_str(raw).ok()
448:        match record_to_tagged_result(line) {
673:        let with_ts = record_to_tagged(
=== owl_message tap (inject.rs) ===
11://! Adapters never emit these; spt owns them. Each tap is **best-effort** — a
22:pub const KIND_OWL_MESSAGE: &str = "owl_message";
37:    let _ = spt_store::history::append_context_entry(&perch, kind, &summary, &ts);
68:            assert!(line.contains(r#""context_kind":"owl_message""#), "discriminator: {line}");
=== docs: MANIFEST [digest] + harness checklist + CHANGELOG supersession ===
91:### `[digest]` — session-digest extractor (ADR-0019)
96:[digest]
110:  `[history].locate_template` (DRY). A **log-less** adapter declares no `[digest]`
C:/Users/decid/Documents/projects/spt-core/docs/adr/0019-digest-adapter-declared-extractor-session-spanning.md
C:/Users/decid/Documents/projects/spt-core/docs/DOCS-STRATEGY.md
15:  - **Breaking (adapter authors):** this **supersedes** the v0.5.0 guidance to emit the digest contract through your `[history]` normalizer (ADR-0008's source mechanism is superseded by ADR-0019). Declare a `[digest]` extractor (or push via `spt api digest-entry`) instead; one `[history]` normalizer can no longer serve both the opaque echo-commune and the contract-typed digest.


Most verified: register validation present (manifest.rs:686 in `validate()`), silent drop removed (`record_to_tagged_result` carries `DropReason`; `.ok()` is now the explicit lossy wrapper), owl tap exists (writes `context_kind:owl_message`), MANIFEST `[digest]` section present, CHANGELOG supersession note is a new entry (not a v0.5.0 retro-edit). One gate item unconfirmed — the **harness integration checklist** update. Check sweep + hunt the checklist.


echo "=== harness integration checklist doc? ==="; grep -rliE "integration checklist|harness.*checklist|adapter author.*checklist|checklist" C:/Users/decid/Documents/projects/spt-core/docs C:/Users/decid/Documents/projects/spt-core/*.md 2>&1 | head; echo "=== files mentioning a harness/adapter onboarding checklist ==="; grep -rlniE "digest-proof|\[digest\]" C:/Users/decid/Documents/projects/spt-core/docs 2>&1 | head; echo "=== sweep tail ==="; tail -45 C:/Users/decid/AppData/Local/Temp/claude/C--Users-decid-Documents-projects-spt-core/13c96a50-2b78-4c16-a995-1da2b1836fd8/tasks/b2y4d069v.output 2>&1

Find harness checklist + check sweep progress


=== harness integration checklist doc? ===
C:/Users/decid/Documents/projects/spt-core/docs/adr/0001-clean-room-fork-from-claude-skill-owl.md
C:/Users/decid/Documents/projects/spt-core/docs/adr/0019-digest-adapter-declared-extractor-session-spanning.md
C:/Users/decid/Documents/projects/spt-core/docs/BROKER-BRAIN-SPLIT-RESTORATION.md
C:/Users/decid/Documents/projects/spt-core/docs/DEBUG-ROLLOUT.md
C:/Users/decid/Documents/projects/spt-core/docs/KNOWN-HAZARDS.md
C:/Users/decid/Documents/projects/spt-core/AGENTS.md
C:/Users/decid/Documents/projects/spt-core/CLAUDE.md
C:/Users/decid/Documents/projects/spt-core/DIGEST-MILESTONE-PLAN.md
C:/Users/decid/Documents/projects/spt-core/M0-PLAN.md
C:/Users/decid/Documents/projects/spt-core/M1-PLAN.md
=== files mentioning a harness/adapter onboarding checklist ===
C:/Users/decid/Documents/projects/spt-core/docs/adr/0008-live-activity-buffer-pty-digest.md
C:/Users/decid/Documents/projects/spt-core/docs/adr/0019-digest-adapter-declared-extractor-session-spanning.md
C:/Users/decid/Documents/projects/spt-core/docs/MANIFEST.md
=== sweep tail ===
=== branch/tree ===
m10-digest-extractor
afa0561 test(digest): capstone E2E + docs sweep + int activation (W4, REQ-TERM-5/6/7)
199595b feat(digest): two-origin merge — spt context-injection entries (W3, REQ-TERM-7)
22adffc feat(digest): thread-spanning across session boundaries (W2, REQ-TERM-6)
568ecb2 docs(digest): MANIFEST [digest] section + digest-record contract rewrite (W1-Tdoc)
3b2c1ea feat(digest): skip-diagnostics engine + `spt adapter digest-proof` (W1-T3)
4bbd450 feat(digest): run the [digest] extractor on demand + presentation knobs (W1-T2)
39463a2 feat(digest): [digest] manifest extractor seam + ts contract field (W1-T1)
=== START SWEEP ===
## traceable ##
  [OK] REQ-TERM-4  required: [impl, unit, int]  stages: -doc +impl +unit +int
  [OK] REQ-TERM-5  required: [doc, impl, unit, int]  stages: +doc +impl +unit +int
  [OK] REQ-TERM-6  required: [impl, unit, int]  stages: -doc +impl +unit +int
  [OK] REQ-TERM-7  required: [impl, unit, int]  stages: -doc +impl +unit +int
## clippy ##
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 11.30s
## test ##


Traceable: REQ-TERM-5 (doc+impl+unit+int), 6/7 (impl+unit+int) all green; clippy clean; test still running. Now — no dedicated "harness integration checklist" doc surfaced. Check what W4's docs sweep actually touched + whether such a doc exists.


echo "=== afa0561 (W4) changed files ==="; git show --stat afa0561 2>&1 | grep -E "\|" | head -30; echo "=== adapter/harness authoring/quickstart docs ==="; ls C:/Users/decid/Documents/projects/spt-core/docs/*.md 2>&1 | grep -iE "manifest|adapter|harness|quickstart|author|integrat"; grep -rliE "dev-agent quickstart|adapter author|authoring an adapter|writing an adapter|harness adapter" C:/Users/decid/Documents/projects/spt-core/docs C:/Users/decid/Documents/projects/spt-core/AGENTS.md 2>&1 | head

Check W4 docs sweep + locate harness authoring doc


=== afa0561 (W4) changed files ===
 CHANGELOG.md                                       |   5 +
 crates/spt-daemon/tests/two_origin_spanning.rs     | 182 +++++++++++++++++++++
 .../src/harness-contract/integration-checklist.md  |  11 +-
 traceable-reqs.toml                                |  12 +-
=== adapter/harness authoring/quickstart docs ===
C:/Users/decid/Documents/projects/spt-core/docs/MANIFEST.md
C:/Users/decid/Documents/projects/spt-core/docs/adr/0019-digest-adapter-declared-extractor-session-spanning.md
C:/Users/decid/Documents/projects/spt-core/docs/MANIFEST.md


The harness integration checklist exists at `src/harness-contract/integration-checklist.md` — W4 updated it. Read the change to confirm the `[digest]` coverage is right.


echo "=== checklist path ==="; find C:/Users/decid/Documents/projects/spt-core -path "*harness-contract/integration-checklist.md" 2>/dev/null; echo "=== W4 diff of checklist ==="; git show afa0561 -- "*integration-checklist.md" 2>&1 | grep -E "^[+-]" | grep -viE "^[+-]{3}" | head -30

Read harness checklist digest additions


=== checklist path ===
C:/Users/decid/Documents/projects/spt-core/docs-site/src/harness-contract/integration-checklist.md
=== W4 diff of checklist ===
-| **Digest-record contract** in your `[history]` lines (or `api digest-entry`) | A live activity digest (`spt endpoint digest`) projected from your normalized session logs — emit records as `{role, text, tool}`. No manifest section; it rides `[history]`, topology-independent | RUN |
+| **`[digest]` extractor** (or `api digest-entry`) | A live activity digest (`spt endpoint digest`) — declare an extractor mapping your native log → the `{role, text, tool, ts}` contract (ADR-0019; its OWN seam, no longer riding `[history]`). Spans `/clear` via the session ledger; validate with `spt adapter digest-proof` | RUN |
-**claude-code:** uses `api shutdown` for graceful `/signoff`; its `[history]`
-records carry the digest-record contract so `spt endpoint digest` shows live tool
-calls (the per-session JSONL is Path A's worked example); no shell body (it is a
-harness, not a driven surface).
+**claude-code:** uses `api shutdown` for graceful `/signoff`; declares a
+`[digest]` extractor mapping its per-session JSONL → the digest-record contract so
+`spt endpoint digest` shows live tool calls and spans `/clear`; no shell body (it
+is a harness, not a driven surface).
+- [ ] (for a live digest) `[digest]` extractor declared + `digest-proof`-checked, or `api digest-entry` push


Checklist update is accurate (old "rides `[history]`" entry replaced with `[digest]` extractor/ADR-0019/spans-`/clear`/digest-proof; claude-code example + new checklist item). Every gate item verified except the final test/nodefault/xtask. Check sweep tail.


Check sweep completion


=== branch/tree ===
m10-digest-extractor
afa0561 test(digest): capstone E2E + docs sweep + int activation (W4, REQ-TERM-5/6/7)
199595b feat(digest): two-origin merge — spt context-injection entries (W3, REQ-TERM-7)
22adffc feat(digest): thread-spanning across session boundaries (W2, REQ-TERM-6)
568ecb2 docs(digest): MANIFEST [digest] section + digest-record contract rewrite (W1-Tdoc)
3b2c1ea feat(digest): skip-diagnostics engine + `spt adapter digest-proof` (W1-T3)
4bbd450 feat(digest): run the [digest] extractor on demand + presentation knobs (W1-T2)
39463a2 feat(digest): [digest] manifest extractor seam + ts contract field (W1-T1)
=== START SWEEP ===
## traceable ##
  [OK] REQ-TERM-4  required: [impl, unit, int]  stages: -doc +impl +unit +int
  [OK] REQ-TERM-5  required: [doc, impl, unit, int]  stages: +doc +impl +unit +int
  [OK] REQ-TERM-6  required: [impl, unit, int]  stages: -doc +impl +unit +int
  [OK] REQ-TERM-7  required: [impl, unit, int]  stages: -doc +impl +unit +int
## clippy ##
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 11.30s
## test ##


Test sweep still running (the only remaining gate gap). Everything else verified independently — **green**:

- traceable EXIT=0: REQ-TERM-5 `doc+impl+unit+int`, 6/7 `impl+unit+int`, all evidenced · clippy -D clean
- Capstone E2E genuinely proves all three (dual-consumer fidelity split, `/clear` spanning + `── /clear ──` marker, ts-interleaved two-origin)
- Register validation present · silent drop removed (`DropReason`) · owl tap writes context entries · MANIFEST `[digest]` · CHANGELOG supersession is a *new* entry (no v0.5.0 retro-edit) · harness integration checklist correctly re-pointed to `[digest]`

todlando's three flags — my rulings (all sound):
1. **Per-wave activation** — endorsed; he caught an error in *my* plan (task-0 all-stages would've made W1–W3 commits traceable-RED since `int` lands at W4). Incremental activation is the correct reading, already a captured lesson.
2. **owl_message tap coarseness** — accepted (data-model-only; notifs *are* injected context), **condition:** log it as a tracked refinement before close, like M9's T5.
3. **Real-CC-JSONL proof** — agreed it belongs to the spt-claude-code repo (no real extractor in spt-core); synthetic + broken-drop test is the right spt-core proof. Becomes a **perri acceptance criterion**.

Holding the stamp for `cargo test --workspace` + nodefault + xtask to land. Will deliver the consolidated gate verdict when the sweep completes.


<task-notification>
<task-id>b2y4d069v</task-id>
<tool-use-id>toolu_018KwUH2asjfBpv3tH2ZRzAS</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\13c96a50-2b78-4c16-a995-1da2b1836fd8\tasks\b2y4d069v.output</output-file>
<status>completed</status>
<summary>Background command "Full M10 gate sweep" completed (exit code 0)</summary>
</task-notification>


<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


Sweep done. Read the test/nodefault/xtask results.


f=C:/Users/decid/AppData/Local/Temp/claude/C--Users-decid-Documents-projects-spt-core/13c96a50-2b78-4c16-a995-1da2b1836fd8/tasks/b2y4d069v.output; echo "=== test failures (empty=pass) ==="; grep -E "FAILED|error\[|test result: FAILED|panicked" "$f" | head; echo "=== two_origin int ==="; grep -A2 "two_origin int" "$f" | head; echo "=== nodefault + xtask + done ==="; grep -E "nodefault|xtask|Finished .dev|xtask check|SWEEP DONE" "$f" | tail -8

Extract test/nodefault/xtask results


=== test failures (empty=pass) ===
=== two_origin int ===
## two_origin int ##
running 1 test
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.15s
=== nodefault + xtask + done ===
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 11.30s
## nodefault ##
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.98s
## xtask ##
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.28s
     Running `target\debug\xtask.exe check`
xtask check: OK
=== SWEEP DONE ===


Full sweep green: workspace 0-fail, `two_origin_spanning` 1/0, nodefault builds, xtask OK (clippy + traceable already confirmed). **Every gate item satisfied.** Stamping M10.


$OWL send --reply-to todlando todlando doyle <<'EOF'
[M10 GATE — PASS. Stamped.] Independently verified end to end on m10-digest-extractor (39463a2..afa0561 atop 80d82bf). Clean execution.

VERIFICATION LEDGER (mine, not self-report):
- traceable EXIT=0: REQ-TERM-5 doc+impl+unit+int, 6/7 impl+unit+int — all evidenced, no orphans.
- Full suite: workspace 0-fail, clippy -D clean, --no-default-features builds, xtask check OK.
- Capstone two_origin_spanning.rs (1/0): genuinely proves all three in one fixture — dual-consumer (echo keeps FULL-FIDELITY-A verbatim while the digest drops raw_extra → two seams one source), spanning (before+after clear, window not reset, renders "── /clear ──"), two-origin ts-interleave (psyche_download→turn A, owl_message→turn B). This is the real proof the milestone existed for.
- Register validation present (manifest.rs validate covers [digest]: extractor present + source resolvable). Silent drop KILLED (record_to_tagged_result carries DropReason; .ok() is now the explicit lossy wrapper). digest-proof + broken-extractor drop test present.
- Docs: MANIFEST [digest] section, harness integration-checklist re-pointed off [history]→[digest] (claude-code example + new checklist item), CHANGELOG supersession is a NEW entry (no v0.5.0 retro-edit, per operator). CONTEXT + ADR-0019 consistent.

YOUR THREE FLAGS — RULINGS:
1. Per-wave stage activation: ENDORSED. You caught an error in MY plan — task-0 all-stages would've made W1-W3 commits traceable-RED (int unevidenced until W4). Incremental activation as evidence lands is the correct reading of "never activated-but-unevidenced" + green-per-commit. Good catch; it's a generalizable lesson.
2. owl_message tap coarseness: ACCEPTED (data-model-only milestone; notif frames ARE injected context, so surfacing them isn't wrong, just coarsely labeled). ONE TRACKED CONDITION before milestone-close: log the coarseness as a deferred refinement (subtype split → consuming-surface milestone) in docs/DEFERRED.md, same discipline as M9's T5. Non-blocking for this PASS; just don't let it evaporate.
3. Real-CC-9-type-JSONL proof: AGREED it belongs to spt-claude-code (no real extractor in spt-core; synthetic + broken-drop is the right spt-core-side mechanism proof). I'm carrying "digest-proof vs a real CC session JSONL" forward as an ACCEPTANCE CRITERION for the spt-claude-code build (perri's repo).

VERDICT: M10 GATE = PASS. Add the one DEFERRED.md line (flag 2) to close out. DO NOT MERGE/PUSH — operator/release-gated (their call, like M9). Milestone is implementation-complete + gated; next move is the operator's. Sharp work — the dual-consumer fixture and the per-wave-activation catch were both excellent.
EOF

Stamp M10 gate PASS to todlando


➜ REPLIED:todlando


Gate stamped. Update memory to M10 gate-PASS.


Edit

**STATUS 2026-06-13: IMPLEMENTATION COMPLETE — all 4 waves committed on branch `m10-digest-extractor`, full gate sweep green LOCAL (workspace test 0-fail · clippy -D · --no-default-features · xtask check · traceable-reqs EXIT=0). NOT merged — awaiting doyle gate + operator merge/push.**

**STATUS 2026-06-13: doyle GATE = PASS (stamped). All 4 waves on `m10-digest-extractor` (39463a2..afa0561 atop 80d82bf). doyle independently verified: traceable EXIT=0 (TERM-5 doc/impl/unit/int, 6/7 impl/unit/int), workspace 0-fail, clippy -D, --no-default-features, xtask OK, capstone two_origin_spanning.rs 1/0 (proves dual-consumer + spanning + two-origin in one fixture), register validation present, silent drop killed, docs (MANIFEST [digest] + harness integration-checklist re-pointed + CHANGELOG supersession new-entry). NOT merged — operator/release-gated (their call, like M9). ONE close-out condition: log owl_message tap coarseness as DEFERRED.md refinement (flag 2). Carried to spt-claude-code: real-CC-JSONL digest-proof = perri acceptance criterion.**


The file C:\Users\decid\.ccs\instances\bigscreen\projects\C--Users-decid-Documents-projects-spt-core\memory\m10-digest-extractor.md has been updated successfully. (file state is current in your context — no need to Read it back)

---
name: m10-digest-extractor
description: "M10 digest extractor + thread-spanning + two-origin (ADR-0019) — ALL 4 WAVES IMPLEMENTED on branch, gates green local, awaiting doyle gate"
metadata:
  node_type: memory
  type: project
  originSessionId: fe10844f-7c10-4582-b8e9-44b7aedcc7f7
---

**STATUS 2026-06-13: IMPLEMENTATION COMPLETE — all 4 waves committed on branch `m10-digest-extractor`, full gate sweep green LOCAL (workspace test 0-fail · clippy -D · --no-default-features · xtask check · traceable-reqs EXIT=0). NOT merged — awaiting doyle gate + operator merge/push.** REQ-TERM-5/6/7 all doc?/impl/unit/int satisfied. Commits: 39463a2(W1-T1 seam+ts) 4bbd450(W1-T2 extractor+knobs) 3b2c1ea(W1-T3 diagnostics+digest-proof) 568ecb2(Tdoc) 22adffc(W2 spanning) 199595b(W3 two-origin) afa0561(W4 E2E+docs+int). E2E = `crates/spt-daemon/tests/two_origin_spanning.rs` (dual-consumer + spanning + two-origin in one fixture).

**Key file map:** seam=spt-runtime/manifest.rs `Digest`; extractor=spt-live/digest.rs; diagnostics+timeline=spt-term/projection.rs (`project_timeline`, `record_to_tagged_result`, `parse_context_record`); daemon merge=spt-daemon/digest.rs (`project_endpoint_digest`/`activity_spanned`/`merge_by_ts`); ledger=spt-store/sessions.rs + timefmt.rs; context writer=spt-store/history.rs `append_context_entry`; taps=spt-live/inject.rs (wired resume.rs/echo.rs/api startup.rs); proof CLI=spt/cli.rs `cmd_adapter_digest_proof`.

**TWO DECISIONS FLAGGED FOR DOYLE:** (1) per-wave stage activation (not Task-0 all-at-once) because traceable-reqs requires ALL declared stages evidenced per commit — int couldn't land before W4 without breaking rule-2; reading of "never activated-but-unevidenced". (2) owl_message tap is coarse — taps the live-agent `emit` chokepoint so resurfaced notif frames also count as owl_message context; subtype refinement deferred to consuming surface. Also: digest-proof proven vs synthetic sample (+broken-extractor drop unit test); real-CC-9-type-JSONL proof belongs to spt-claude-code repo where the real extractor lives.

---

**M10 "Digest extractor & thread-spanning"** — next milestone after M9, an `spt-claude-code` enabler. Designed in a full grill (operator Brandon + doyle), doyle gated the plan. Branch `m10-digest-extractor` off main; planning commit 80d82bf; todlando executing, doyle gates.

**Core: reverses M9's "no digest manifest seam."** Operator never approved that M9 §2 call. Governing decision = **ADR-0019** (supersedes ADR-0008 source mechanism). CONTEXT.md "session digest" entry rewritten (now "recent *context*", not just activity).

**The decisions (don't re-litigate — in ADR-0019):**
- `[digest]` manifest seam = **imperative extractor** (native log → `{role,text,tool,ts}` contract), **no declarative DSL** (anyone who could hit a fixed format could push or write the extractor). Defaults to `[history]` source files + own-source escape; `api digest-entry` push fallback. Why: one normalizer can't serve echo (opaque/rich) AND digest (contract) — the dual-consumer conflict. Real CC logs (9 types, assistant.content = mixed thinking/text/tool_use list, one line→many entries) kill the declarative dream.
- **Thread-spanning** across `/clear`: per-endpoint session ledger `<perch>/sessions.log` (heir to legacy `psyches/tracked/agents/<id>/sessions.log`); `api boundary` (already built: cmd_boundary/resurface_at_boundary) appends rotated session_id; digest enumerates last K sessions so window bridges the hop; distinctive `DigestEntry::Boundary` marker. (/clear = "same thread keep glancing".)
- **Presentation knobs** (window depth/truncation/collapse) = adapter-declared defaults, consumer-overridable. Fixed "~3 turns" DROPPED as spt-core requirement.
- **Two-origin merge** (DATA MODEL ONLY this milestone): spt appends its own context-injection entries (psyche_download | echo_mirror | owl_message) to digest.log; ts-interleaved with extracted activity. Motivation: echo-commune building its delta needs to see what context the agent was already fed.
- **Diagnostics mandatory**: `spt adapter digest-proof` (author) + runtime skip-diagnostics — KILL the silent `record_to_tagged`=`from_str().ok()` drop.

**REQs:** REQ-TERM-5 (seam+proof+diagnostics, doc/impl/unit/int) · REQ-TERM-6 (spanning, impl/unit/int) · REQ-TERM-7 (two-origin, impl/unit/int). Registered with stages=[] (tree green); **activate at execution start** (rule 5) as task 0 riding Wave-1 first commit. REQ-TERM-4 carried unchanged.

**Plan:** `DIGEST-MILESTONE-PLAN.md` (4 waves: seam → spanning → merge → integration+gate). **Deferred** (no consumer yet): GUI collapse/expand, echo-reads-digest delta loop, autonomous file-watch freshness, Candidate 04 (Psyche M2b→M3b). **Roadmap:** M10=digest; Shell→M11, remote-attach→M12.

**Changelog rule (operator):** do NOT retro-edit the v0.5.0 release note; the NEXT release's changelog records the ADR-0008→0019 supersession coinciding with it.

**Watch-items:** ts clock-source skew across the two origins; ledger growth/bound (legacy sessions.log grew unbounded when seal failed); digest.log dual-schema discriminator forward-compat. Don't expand scope.

-**STATUS 2026-06-13: IMPLEMENTATION COMPLETE — all 4 waves committed on branch `m10-digest-extractor`, full gate sweep green LOCAL (workspace test 0-fail · clippy -D · --no-default-features · xtask check · traceable-reqs EXIT=0). NOT merged — awaiting doyle gate + operator merge/push.** REQ-TERM-5/6/7 all doc?/impl/unit/int satisfied. Commits: 39463a2(W1-T1 seam+ts) 4bbd450(W1-T2 extractor+knobs) 3b2c1ea(W1-T3 diagnostics+digest-proof) 568ecb2(Tdoc) 22adffc(W2 spanning) 199595b(W3 two-origin) afa0561(W4 E2E+docs+int). E2E = `crates/spt-daemon/tests/two_origin_spanning.rs` (dual-consumer + spanning + two-origin in one fixture).

+**STATUS 2026-06-13: doyle GATE = PASS (stamped). All 4 waves on `m10-digest-extractor` (39463a2..afa0561 atop 80d82bf). doyle independently verified: traceable EXIT=0 (TERM-5 doc/impl/unit/int, 6/7 impl/unit/int), workspace 0-fail, clippy -D, --no-default-features, xtask OK, capstone two_origin_spanning.rs 1/0 (proves dual-consumer + spanning + two-origin in one fixture), register validation present, silent drop killed, docs (MANIFEST [digest] + harness integration-checklist re-pointed + CHANGELOG supersession new-entry). NOT merged — operator/release-gated (their call, like M9). ONE close-out condition: log owl_message tap coarseness as DEFERRED.md refinement (flag 2). Carried to spt-claude-code: real-CC-JSONL digest-proof = perri acceptance criterion.** REQ-TERM-5/6/7 all doc?/impl/unit/int satisfied. Commits: 39463a2(W1-T1 seam+ts) 4bbd450(W1-T2 extractor+knobs) 3b2c1ea(W1-T3 diagnostics+digest-proof) 568ecb2(Tdoc) 22adffc(W2 spanning) 199595b(W3 two-origin) afa0561(W4 E2E+docs+int). E2E = `crates/spt-daemon/tests/two_origin_spanning.rs` (dual-consumer + spanning + two-origin in one fixture).


- [M10 digest extractor](m10-digest-extractor.md) — IMPLEMENTED 2026-06-13: all 4 waves committed on branch m10-digest-extractor, full gate sweep green LOCAL, REQ-TERM-5/6/7 doc?/impl/unit/int. `[digest]` imperative extractor seam (ADR-0019, reverses M9) + thread-spanning (session ledger) + two-origin merge + digest-proof + skip-diagnostics. NOT merged — awaiting doyle gate; 2 decisions flagged (per-wave activation, owl_message tap coarseness)

- [M10 digest extractor](m10-digest-extractor.md) — doyle GATE PASS 2026-06-13: all 4 waves (39463a2..afa0561), REQ-TERM-5/6/7 evidenced, full suite + capstone E2E green, independently verified. `[digest]` imperative extractor seam (ADR-0019, reverses M9) + thread-spanning (session ledger) + two-origin merge + digest-proof + skip-diagnostics. NOT merged — operator/release-gated. Close-out: log owl_message-tap coarseness in DEFERRED.md; real-CC digest-proof → perri/spt-claude-code acceptance criterion


The file C:\Users\decid\.ccs\instances\bigscreen\projects\C--Users-decid-Documents-projects-spt-core\memory\MEMORY.md has been updated successfully. (file state is current in your context — no need to Read it back)

- [M9 status](m9-wave1-progress.md) — M9 COMPLETE + SHIPPED: all 4 gates passed (G1/G2/Gateway-WAN/G3), all PRs merged to main (operator GO 2026-06-13), **v0.5.0 tagged** (counter 10, deployah signing). Don't re-plan. See [[v050-published]] for release state
- [spt-claude-code NEXT](spt-claude-code-next.md) — NEXT SESSION: scope a general `spt-claude-code` plan, hand to new maintainer "perri". WHOLLY SEPARATE project from spt-core; perri builds from `SaberMage/spt-releases` + GH Pages docs ONLY (not spt-core source). First real CC harness adapter; M9+M10 are its enablers
- [M10 digest extractor](m10-digest-extractor.md) — IMPLEMENTED 2026-06-13: all 4 waves committed on branch m10-digest-extractor, full gate sweep green LOCAL, REQ-TERM-5/6/7 doc?/impl/unit/int. `[digest]` imperative extractor seam (ADR-0019, reverses M9) + thread-spanning (session ledger) + two-origin merge + digest-proof + skip-diagnostics. NOT merged — awaiting doyle gate; 2 decisions flagged (per-wave activation, owl_message tap coarseness)
- [traceable per-wave activation](traceable-per-wave-activation.md) — traceable-reqs check requires ALL declared required_stages evidenced per commit; activate stages incrementally as evidence lands (int at final wave), never the full end-state list upfront
- [v0.5.0 published](v050-published.md) — M9 PUBLIC 2026-06-13 (counter 10); todlando drove merge+tag, deployah signed+published (handoff split vs v0.4.x full-solo); minor bump (breaking [pty_digest] removal pre-1.0); hashes linux 4c079100… win 984298ba…. FLEET ROLL = next phase, operator-gated (their ask was merge+publish, DONE). v0.4.2→v0.5.0 kitsubito apply = FIRST genuine Linux seamless no-bounce proof on fixed broker; bytes-assert→auto-rollback armed. deployah on-call for hash check
- [v0.5.0 release](v050-release.md) — M9 SHIPPED: merged to main (22a9b6f, clean stack collapse; #8 auto-closed by mid-stack --delete-branch, content rode via #9), v0.5.0 tagged (0.4.2→0.5.0 minor: feature milestone + breaking [pty_digest] removal), counter 10, deployah signing. LESSON: don't --delete-branch mid-stack
- [M9 Wave3/G3 digest](m9-wave3-digest-progress.md) — session-digest re-founded on session logs (REQ-TERM-4, ADR-0008 amendment); G3 STAMPED PASS by doyle 2026-06-13, PR#10 held. On-demand projection (topology-independent, no PTY); PTY-parse retired; published digest-record contract; autonomous-freshness+Option-C deferred to consuming-frontend milestone (DEFERRED.md:44)
- [hfenduleam target bloat → CI RED](hfenduleam-target-bloat-ci-red.md) — Win-runner LNK1318/os-error-112 can be disk-full from cargo target bloat (396GB seen), NOT a code regression; cargo clean + re-run; check disk before treating a Windows RED as a code defect
- [v0.4.1 Linux respawn blocker](v041-linux-respawn-blocker.md) — v0.4.1 PUBLIC (counter 8); fleet roll found Linux brain-respawn bug (current_exe follows apply-rename → brain runs OLD bytes, records applied:8). hfenduleam(Win) GREEN, kitsubito(Linux) RED. RESOLVED by v0.4.2 — see [[v042-published]]
- [v0.4.2 published](v042-published.md) — PUBLIC 2026-06-11 (counter 9); FIXES v0.4.1 Linux respawn blocker (respawn via canonical install path not current_exe + bytes-assert→auto-rollback; REQ-HAZARD-BRAIN-RESPAWN-PATH + real-rename E2E). deployah drove full merge→tag→sign→publish solo. hashes linux 500a9e7b… win b9ec8ddd…. ASYMMETRY: v0.4.2 apply still rides buggy v0.4.0 broker → 1 final manual Linux bounce loads fix; seamless proof on NEXT update via debug-channel. todlando driving fleet roll + restoration close
- [pump-IPC-deadline fix](pump-ipc-deadline-fix.md) — DONE: MERGED to main @ 4d8b810 (PR #3), doyle-approved, CI green both runners; reader-thread+channel carrier (nonblock dead on Win named pipes), both amendments folded, no tag (rides v0.4.1). Don't re-plan
- [CI self-host migration](ci-selfhost-migration.md) — DONE: runners live on kitsubito + hfenduleam (gravity retired 2026-06-07); twohost jobs gated by [twohost] commit tag; don't re-plan
- [which process is the listener](spt-daemon-is-live-infra.md) — owl.exe (plugin) = live listener, hands off; target\debug\spt.exe daemon = dev/test artifact, killable
- [self-clear skill API-blocked](self-clear-skill-api-blocked.md) — never invoke /self-clear; usage-policy classifier refuses the turn; continue in-context / user-manual /clear instead
- [Adapter glue-model boundary](adapter-glue-model-boundary.md) — adapters = manifest + binary only; real adapters in own repos; no adapter-aware source in spt-core
- [M6 shipped v0.1.0](m6-shipped-v010.md) — v0.1.0 public 2026-06-05; release procedure = RELEASE-RUNBOOK.md; releases-repo/ is repo-face truth; don't re-plan M6
- [Gravity deploy procedure](gravity-deploy-procedure.md) — ssh david@100.100.62.43; build in ~/spt-core-deploy NOT runner workspace; setsid daemon; ~1 min first-sync after restart
- [Linux SPT_HOME no-bug](linux-spt-home-no-bug.md) — unix spawn_detached inherits env (no env_clear), forwards SPT_HOME; only Win CreateEnvironmentBlock rebuilds/drops it (f99dc25). Default-home daemon = launched w/o the var, not a propagation bug; don't reopen
- [kitsubito Linux rig](kitsubito-linux-rig.md) — ssh reavus@kitsubito (key auth); cargo at ~/.cargo/bin; deploy checkout origin=local runner (NOT GitHub) so unpushed commits transfer via git bundle; live spt-daemon user unit, --user ops need no sudo; build-in-scratch leaves ~/.local/bin/spt untouched
- [Use $OWL send for messaging](owl-send-not-legacy-spt-send.md) — `$OWL send` works (legacy = where live perches run); `spt send` is the NEW spt-core binary but no perches on it yet → bounces NO_PERCH; don't read `$?` after piping send through tail (use PIPESTATUS)
- [daemon service-detection gotcha](daemon-service-detection-gotcha.md) — service detection reading GLOBAL OS state is blind on a single-home non-elevated dev box; gate Linux service routing on SPT_HOME-unset, Windows run-refuse on would-vanish (not is_elevated); push→CI BEFORE tagging a release (v0.3.0 tagged early, CI caught 2 regressions, retagged)
- [v0.3.2 platform-safe update](v032-platform-safe-update.md) — REQ-UPD-8 PUBLISHED + rig-verified 2026-06-09 (counter 6): fetch stages multi-platform update-set, apply refuses unstamped/wrong-OS single. Fleet kitsubito+hfenduleam=0.3.2 (ENLYZEAM TODO). GOTCHA: fetch/pump floor=staged_version → can't re-stage same version single→set; clear `$SPT_HOME/releases` first (Linux ~/.spt-core, Win %LOCALAPPDATA%\spt-core). CORRECTION: release-publish never built a SignedUpdateSet pre-v0.3.2 (was debug_rollout)
- [boot-race self-heal](boot-race-self-heal.md) — v0.3.1: daemon self-heals a net-less boot start (NetHost retry+attach to OnceLock broker); systemd-USER network-online ordering does NOT reliably prevent the race under linger (field-proven kitsubito) — the self-heal is the real guarantee, unit ordering is belt
- [v0.4.0 published + fleet rolled](v040-published-fleet-rolled.md) — restoration release PUBLIC 2026-06-10 (counter 7); D7-4 bytes-match done both nodes (brain.ready exe_hash == artifact); v0.3.2→v0.4.0 apply needed last manual bounce (in-process can't self-relaunch); seamless no-bounce proof + enlyzeam catch-up + milestone close all deferred to v0.4.1
- [v0.4.1 published](v041-published.md) — PUBLIC 2026-06-11 (counter 8); deployah drove full merge→tag→sign→publish solo; D7 SEAMLESS no-bounce fleet-verify payload (apply must roll bytes live w/ pid change, no manual bounce); acceptance hashes linux 35c9544c… win 728a1bfc…; hfenduleam(Win) GREEN no-bounce, kitsubito(Linux) RED → CLOSE BLOCKED: v0.4.0 broker respawns via current_exe() which follows apply-rename to .old-8=OLD bytes (Linux only); artifacts SOUND don't yank; v0.4.2 fix = respawn by canonical path + bytes-assert, needs manual Linux broker bounce; deployah takes counter 9 publish
- [hfenduleam disk-full CI](hfenduleam-disk-full-ci.md) — Win CI LNK1318/os-error-112 = disk full not code; cargo clean dev target (was 396GB), rerun job
- [M9 gateway-wan progress](m9-gateway-wan-progress.md) — REQ-MSG-6 cross-node Gateway WAN user-msg; PR #9 open (base m9-user-surfaces), impl complete + local sweep green, HOLDING on CI; PR #8 G2 now green post disk-fix

-- [M10 digest extractor](m10-digest-extractor.md) — IMPLEMENTED 2026-06-13: all 4 waves committed on branch m10-digest-extractor, full gate sweep green LOCAL, REQ-TERM-5/6/7 doc?/impl/unit/int. `[digest]` imperative extractor seam (ADR-0019, reverses M9) + thread-spanning (session ledger) + two-origin merge + digest-proof + skip-diagnostics. NOT merged — awaiting doyle gate; 2 decisions flagged (per-wave activation, owl_message tap coarseness)

+- [M10 digest extractor](m10-digest-extractor.md) — doyle GATE PASS 2026-06-13: all 4 waves (39463a2..afa0561), REQ-TERM-5/6/7 evidenced, full suite + capstone E2E green, independently verified. `[digest]` imperative extractor seam (ADR-0019, reverses M9) + thread-spanning (session ledger) + two-origin merge + digest-proof + skip-diagnostics. NOT merged — operator/release-gated. Close-out: log owl_message-tap coarseness in DEFERRED.md; real-CC digest-proof → perri/spt-claude-code acceptance criterion


**M10 GATE = PASS.** Stamped to todlando, memory synced.

Verified independently end-to-end:
- traceable EXIT=0 (REQ-TERM-5/6/7 fully evidenced) · workspace 0-fail · clippy -D · nodefault · xtask OK
- capstone `two_origin_spanning.rs` (1/0) proves all three: dual-consumer fidelity split, `/clear` spanning + `── /clear ──` marker, ts-interleaved two-origin
- register validation, silent-drop kill, digest-proof, MANIFEST `[digest]`, harness checklist re-point, CHANGELOG supersession (new entry, no v0.5.0 retro-edit)

todlando's three flags ruled: per-wave activation **endorsed** (caught a real error in my plan), owl-tap coarseness **accepted** w/ one DEFERRED.md close-out line, real-CC proof **carried to perri** as an spt-claude-code acceptance criterion.

**Not merged — operator/release-gated**, like M9. M10 is implementation-complete + gated; next move is the operator's.

Next session per your commune: scope `spt-claude-code` (separate project, built from `SaberMage/spt-releases` + GH Pages docs only) → hand to perri. Standing by.


<task-notification>
<task-id>bg7cqpvni</task-id>
<summary>Monitor event: "« spt event »"</summary>
<event><EVENT-PART seq="1/2" id="30fa9438" type="msg" from="todlando">M10 gate close-out DONE: owl_message tap coarseness logged in docs/DEFERRED.md (commit 8e06612, after the M9 freshness row). traceable still EXIT=0. Branch m10-digest-extractor now 8 commits atop 80d82bf (7 impl + close-out). Holding — not merged/pushed, operator/release-gated like M9. Thanks for the gate + the per</EVENT-PART>
<EVENT-PART seq="2/2" id="30fa9438">-wave-activation ruling (saved to memory as a generalizable lesson). Standing by.</EVENT-PART></event>
</task-notification>


todlando close-out done: owl-tap coarseness logged in DEFERRED.md (8e06612), traceable still green, holding for operator. M10 fully gated + closed. Brief ACK + memory accuracy fix.


Bash

$OWL send --reply-to todlando todlando doyle <<'EOF'
[Close-out accepted] DEFERRED.md owl_message-coarseness line (8e06612) + traceable EXIT=0 noted. M10 fully gated + closed-out, holding for operator/release. Nothing more from me. Clean milestone — thanks. Stand by; next up is the spt-claude-code scoping (separate project, perri) in a future session.
EOF

ACK todlando close-out


Edit

NOT merged — operator/release-gated. Close-out: log owl_message-tap coarseness in DEFERRED.md; real-CC digest-proof → perri/spt-claude-code acceptance criterion

NOT merged — operator/release-gated (8 commits 39463a2..8e06612). Close-out DONE (owl_message-tap coarseness logged DEFERRED.md @ 8e06612). Carried: real-CC digest-proof → perri/spt-claude-code acceptance criterion


- [M9 status](m9-wave1-progress.md) — M9 COMPLETE + SHIPPED: all 4 gates passed (G1/G2/Gateway-WAN/G3), all PRs merged to main (operator GO 2026-06-13), **v0.5.0 tagged** (counter 10, deployah signing). Don't re-plan. See [[v050-published]] for release state
- [spt-claude-code NEXT](spt-claude-code-next.md) — NEXT SESSION: scope a general `spt-claude-code` plan, hand to new maintainer "perri". WHOLLY SEPARATE project from spt-core; perri builds from `SaberMage/spt-releases` + GH Pages docs ONLY (not spt-core source). First real CC harness adapter; M9+M10 are its enablers
- [M10 digest extractor](m10-digest-extractor.md) — doyle GATE PASS 2026-06-13: all 4 waves (39463a2..afa0561), REQ-TERM-5/6/7 evidenced, full suite + capstone E2E green, independently verified. `[digest]` imperative extractor seam (ADR-0019, reverses M9) + thread-spanning (session ledger) + two-origin merge + digest-proof + skip-diagnostics. NOT merged — operator/release-gated. Close-out: log owl_message-tap coarseness in DEFERRED.md; real-CC digest-proof → perri/spt-claude-code acceptance criterion
- [traceable per-wave activation](traceable-per-wave-activation.md) — traceable-reqs check requires ALL declared required_stages evidenced per commit; activate stages incrementally as evidence lands (int at final wave), never the full end-state list upfront
- [v0.5.0 published](v050-published.md) — M9 PUBLIC 2026-06-13 (counter 10); todlando drove merge+tag, deployah signed+published (handoff split vs v0.4.x full-solo); minor bump (breaking [pty_digest] removal pre-1.0); hashes linux 4c079100… win 984298ba…. FLEET ROLL = next phase, operator-gated (their ask was merge+publish, DONE). v0.4.2→v0.5.0 kitsubito apply = FIRST genuine Linux seamless no-bounce proof on fixed broker; bytes-assert→auto-rollback armed. deployah on-call for hash check
- [v0.5.0 release](v050-release.md) — M9 SHIPPED: merged to main (22a9b6f, clean stack collapse; #8 auto-closed by mid-stack --delete-branch, content rode via #9), v0.5.0 tagged (0.4.2→0.5.0 minor: feature milestone + breaking [pty_digest] removal), counter 10, deployah signing. LESSON: don't --delete-branch mid-stack
- [M9 Wave3/G3 digest](m9-wave3-digest-progress.md) — session-digest re-founded on session logs (REQ-TERM-4, ADR-0008 amendment); G3 STAMPED PASS by doyle 2026-06-13, PR#10 held. On-demand projection (topology-independent, no PTY); PTY-parse retired; published digest-record contract; autonomous-freshness+Option-C deferred to consuming-frontend milestone (DEFERRED.md:44)
- [hfenduleam target bloat → CI RED](hfenduleam-target-bloat-ci-red.md) — Win-runner LNK1318/os-error-112 can be disk-full from cargo target bloat (396GB seen), NOT a code regression; cargo clean + re-run; check disk before treating a Windows RED as a code defect
- [v0.4.1 Linux respawn blocker](v041-linux-respawn-blocker.md) — v0.4.1 PUBLIC (counter 8); fleet roll found Linux brain-respawn bug (current_exe follows apply-rename → brain runs OLD bytes, records applied:8). hfenduleam(Win) GREEN, kitsubito(Linux) RED. RESOLVED by v0.4.2 — see [[v042-published]]
- [v0.4.2 published](v042-published.md) — PUBLIC 2026-06-11 (counter 9); FIXES v0.4.1 Linux respawn blocker (respawn via canonical install path not current_exe + bytes-assert→auto-rollback; REQ-HAZARD-BRAIN-RESPAWN-PATH + real-rename E2E). deployah drove full merge→tag→sign→publish solo. hashes linux 500a9e7b… win b9ec8ddd…. ASYMMETRY: v0.4.2 apply still rides buggy v0.4.0 broker → 1 final manual Linux bounce loads fix; seamless proof on NEXT update via debug-channel. todlando driving fleet roll + restoration close
- [pump-IPC-deadline fix](pump-ipc-deadline-fix.md) — DONE: MERGED to main @ 4d8b810 (PR #3), doyle-approved, CI green both runners; reader-thread+channel carrier (nonblock dead on Win named pipes), both amendments folded, no tag (rides v0.4.1). Don't re-plan
- [CI self-host migration](ci-selfhost-migration.md) — DONE: runners live on kitsubito + hfenduleam (gravity retired 2026-06-07); twohost jobs gated by [twohost] commit tag; don't re-plan
- [which process is the listener](spt-daemon-is-live-infra.md) — owl.exe (plugin) = live listener, hands off; target\debug\spt.exe daemon = dev/test artifact, killable
- [self-clear skill API-blocked](self-clear-skill-api-blocked.md) — never invoke /self-clear; usage-policy classifier refuses the turn; continue in-context / user-manual /clear instead
- [Adapter glue-model boundary](adapter-glue-model-boundary.md) — adapters = manifest + binary only; real adapters in own repos; no adapter-aware source in spt-core
- [M6 shipped v0.1.0](m6-shipped-v010.md) — v0.1.0 public 2026-06-05; release procedure = RELEASE-RUNBOOK.md; releases-repo/ is repo-face truth; don't re-plan M6
- [Gravity deploy procedure](gravity-deploy-procedure.md) — ssh david@100.100.62.43; build in ~/spt-core-deploy NOT runner workspace; setsid daemon; ~1 min first-sync after restart
- [Linux SPT_HOME no-bug](linux-spt-home-no-bug.md) — unix spawn_detached inherits env (no env_clear), forwards SPT_HOME; only Win CreateEnvironmentBlock rebuilds/drops it (f99dc25). Default-home daemon = launched w/o the var, not a propagation bug; don't reopen
- [kitsubito Linux rig](kitsubito-linux-rig.md) — ssh reavus@kitsubito (key auth); cargo at ~/.cargo/bin; deploy checkout origin=local runner (NOT GitHub) so unpushed commits transfer via git bundle; live spt-daemon user unit, --user ops need no sudo; build-in-scratch leaves ~/.local/bin/spt untouched
- [Use $OWL send for messaging](owl-send-not-legacy-spt-send.md) — `$OWL send` works (legacy = where live perches run); `spt send` is the NEW spt-core binary but no perches on it yet → bounces NO_PERCH; don't read `$?` after piping send through tail (use PIPESTATUS)
- [daemon service-detection gotcha](daemon-service-detection-gotcha.md) — service detection reading GLOBAL OS state is blind on a single-home non-elevated dev box; gate Linux service routing on SPT_HOME-unset, Windows run-refuse on would-vanish (not is_elevated); push→CI BEFORE tagging a release (v0.3.0 tagged early, CI caught 2 regressions, retagged)
- [v0.3.2 platform-safe update](v032-platform-safe-update.md) — REQ-UPD-8 PUBLISHED + rig-verified 2026-06-09 (counter 6): fetch stages multi-platform update-set, apply refuses unstamped/wrong-OS single. Fleet kitsubito+hfenduleam=0.3.2 (ENLYZEAM TODO). GOTCHA: fetch/pump floor=staged_version → can't re-stage same version single→set; clear `$SPT_HOME/releases` first (Linux ~/.spt-core, Win %LOCALAPPDATA%\spt-core). CORRECTION: release-publish never built a SignedUpdateSet pre-v0.3.2 (was debug_rollout)
- [boot-race self-heal](boot-race-self-heal.md) — v0.3.1: daemon self-heals a net-less boot start (NetHost retry+attach to OnceLock broker); systemd-USER network-online ordering does NOT reliably prevent the race under linger (field-proven kitsubito) — the self-heal is the real guarantee, unit ordering is belt
- [v0.4.0 published + fleet rolled](v040-published-fleet-rolled.md) — restoration release PUBLIC 2026-06-10 (counter 7); D7-4 bytes-match done both nodes (brain.ready exe_hash == artifact); v0.3.2→v0.4.0 apply needed last manual bounce (in-process can't self-relaunch); seamless no-bounce proof + enlyzeam catch-up + milestone close all deferred to v0.4.1
- [v0.4.1 published](v041-published.md) — PUBLIC 2026-06-11 (counter 8); deployah drove full merge→tag→sign→publish solo; D7 SEAMLESS no-bounce fleet-verify payload (apply must roll bytes live w/ pid change, no manual bounce); acceptance hashes linux 35c9544c… win 728a1bfc…; hfenduleam(Win) GREEN no-bounce, kitsubito(Linux) RED → CLOSE BLOCKED: v0.4.0 broker respawns via current_exe() which follows apply-rename to .old-8=OLD bytes (Linux only); artifacts SOUND don't yank; v0.4.2 fix = respawn by canonical path + bytes-assert, needs manual Linux broker bounce; deployah takes counter 9 publish
- [hfenduleam disk-full CI](hfenduleam-disk-full-ci.md) — Win CI LNK1318/os-error-112 = disk full not code; cargo clean dev target (was 396GB), rerun job
- [M9 gateway-wan progress](m9-gateway-wan-progress.md) — REQ-MSG-6 cross-node Gateway WAN user-msg; PR #9 open (base m9-user-surfaces), impl complete + local sweep green, HOLDING on CI; PR #8 G2 now green post disk-fix

-- [M10 digest extractor](m10-digest-extractor.md) — doyle GATE PASS 2026-06-13: all 4 waves (39463a2..afa0561), REQ-TERM-5/6/7 evidenced, full suite + capstone E2E green, independently verified. `[digest]` imperative extractor seam (ADR-0019, reverses M9) + thread-spanning (session ledger) + two-origin merge + digest-proof + skip-diagnostics. NOT merged — operator/release-gated. Close-out: log owl_message-tap coarseness in DEFERRED.md; real-CC digest-proof → perri/spt-claude-code acceptance criterion

+- [M10 digest extractor](m10-digest-extractor.md) — doyle GATE PASS 2026-06-13: all 4 waves (39463a2..afa0561), REQ-TERM-5/6/7 evidenced, full suite + capstone E2E green, independently verified. `[digest]` imperative extractor seam (ADR-0019, reverses M9) + thread-spanning (session ledger) + two-origin merge + digest-proof + skip-diagnostics. NOT merged — operator/release-gated (8 commits 39463a2..8e06612). Close-out DONE (owl_message-tap coarseness logged DEFERRED.md @ 8e06612). Carried: real-CC digest-proof → perri/spt-claude-code acceptance criterion


**M10 complete** — gate PASS + close-out done (8 commits `39463a2..8e06612`, DEFERRED.md coarseness line logged, traceable green). Held for operator/release, like M9.

Nothing outstanding. Next session: scope `spt-claude-code` (separate project, `SaberMage/spt-releases` + GH Pages docs only) → hand to perri. Standing by.