supercode-core 0.2.1

A lightweight, fully-customizable AI coding agent SDK in Rust. Talks to any model via OpenRouter or any OpenAI-compatible endpoint.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
//! §4.1 — the TRANSLATION FIDELITY MATRIX: the N×N (5 harnesses) conformance
//! metric test (`docs/interop/opencode-pi-spec.md` §4.1). Mirrors
//! `roundtrip_regression.rs`'s "runs under plain `cargo test`, against
//! committed fixtures, no env gate, no `#[ignore]`" discipline.
//!
//! Every assertion here uses [`interop_common::msg_eq_multimodal`] (S2), not
//! the weaker `msg_eq` — the corpus deliberately includes multimodal
//! messages (the pi/opencode fixtures each carry an image), and a comparator
//! blind to `content_parts` would score a loader that drops every image at
//! 100%.
//!
//! Residue is MEASURED (S7), not hand-tabulated: [`interop_common::measure_cell`]
//! computes, for every cell, the real dropped-message and dropped-metadata-key
//! sets, and this test asserts the measured [`interop_common::CellResidue`]
//! equals a frozen constant per cell — so a loader that starts dropping (or
//! stops dropping) something fails the test instead of passing silently. The
//! frozen tables below were DERIVED from the first correct run of this test
//! (measured, then committed), per the build brief.

mod interop_common;

use std::path::{Path, PathBuf};

use interop_common::{core, measure_cell, replay_eligible, CellResidue};
use supercode::audit::{audit_dir, Corpus};
use supercode::configfile::{resolve, ResolveOptions};
use supercode::session::{Session, SessionFormat};
use supercode::ChatMessage;

fn fixture(name: &str) -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("tests/fixtures")
        .join(name)
}

const N: usize = 5;
const FORMATS: [SessionFormat; N] = [
    SessionFormat::ClaudeCode,
    SessionFormat::Codex,
    SessionFormat::OpenCode,
    SessionFormat::Pi,
    SessionFormat::Grok,
];

fn fname(i: usize) -> &'static str {
    ["claude", "codex", "opencode", "pi", "grok"][i]
}

fn idx(f: SessionFormat) -> usize {
    match f {
        SessionFormat::ClaudeCode => 0,
        SessionFormat::Codex => 1,
        SessionFormat::OpenCode => 2,
        SessionFormat::Pi => 3,
        SessionFormat::Grok => 4,
    }
}

fn fixture_file_for(f: SessionFormat) -> &'static str {
    match f {
        SessionFormat::ClaudeCode => "claude_code_session.jsonl",
        SessionFormat::Codex => "codex_session.jsonl",
        SessionFormat::OpenCode => "opencode_session.jsonl",
        SessionFormat::Pi => "pi_session.jsonl",
        SessionFormat::Grok => "grok_session/chat_history.jsonl",
    }
}

/// Load the committed fixture for `f` with `f`'s OWN loader (never
/// auto-detected — each cell's `S1` must come from the format-specific
/// entry point, matching how a real user would load that harness's log).
fn load_fixture(f: SessionFormat) -> Session {
    let path = fixture(fixture_file_for(f));
    match f {
        SessionFormat::ClaudeCode => Session::from_claude_code(path).unwrap(),
        SessionFormat::Codex => Session::from_codex(path).unwrap(),
        SessionFormat::OpenCode => Session::from_opencode(path).unwrap(),
        SessionFormat::Pi => Session::from_pi(path).unwrap(),
        SessionFormat::Grok => Session::from_grok(path).unwrap(),
    }
}

fn non_empty_lines(s: &str) -> Vec<&str> {
    s.lines().map(str::trim).filter(|l| !l.is_empty()).collect()
}

fn strip_native_header(native: &str) -> &str {
    let idx = native
        .find('\n')
        .expect("native output must have a header line");
    &native[idx + 1..]
}

fn keys(names: &[&str]) -> std::collections::BTreeSet<String> {
    names.iter().map(|s| s.to_string()).collect()
}

// ---------------------------------------------------------------------------
// Frozen per-cell floors (§4.1 "off-diagonal must meet a frozen per-cell
// floor") and the frozen, MEASURED residue table (§4.1/S7). DERIVED from the
// first correct run of this test against the committed fixtures — a
// regression that drops a cell's %-lossless below its floor, or changes the
// measured residue set, fails the test.
// ---------------------------------------------------------------------------

/// `FLOOR[a][b]` = minimum acceptable `%-lossless(a->b)`. DERIVED from the
/// first correct (green) run of [`fidelity_matrix_5x5_and_residue`] against
/// the committed fixtures (rounded DOWN from the measured value, never up,
/// so it stays a true floor):
/// - `claude`/`codex` as source: pure text/tool-call cells hit 100%; the
///   only sub-100 cells are `-> opencode`/`-> pi`, whose measured residue is
///   named below (a Tool-message `name` backfill asymmetry for claude, plus
///   Codex's dropped `phase`/`reasoning_encrypted` metadata for codex).
/// - `opencode`/`pi` as source: the compacted fixture's own
///   `compacted_out`-excluded prefix (§2.1/S3, expected/correct, not a
///   defect) dominates the floor, on top of the overflow metadata each
///   format's `to_jsonl` writer can't restore (§2.2/§2.3).
///
/// **Post IX-5/IX-6 fix (this wave):** `opencode -> codex` rose from 40.0 to
/// 50.0 (the codex-loader combined-turn split, IX-6, no longer contributes a
/// 2nd `other_dropped_messages` — the cell now matches `opencode -> claude`'s
/// floor exactly), and `pi -> claude`/`pi -> codex` rose from 50.0 to 60.0
/// (the Claude/Codex writers now serialize `content_parts`, IX-5, so pi's
/// replay-eligible image message — `eimg0001` — survives instead of vanishing).
///
/// **Post WAVE-2 item 1 (real per-message timestamps, this wave):** every
/// `%-lossless` figure here is UNCHANGED — this metric is purely a
/// whole-message match count ([`interop_common::msg_eq_multimodal`], blind to
/// `metadata`), and threading real timestamps only affects `metadata` key
/// presence, not message identity. What DID change is the pi-sourced
/// `dropped_metadata_keys` residue (see `frozen_residue`'s `pi_dropped_keys_cross`
/// below): the canonical `timestamp` key (entry-level ISO-8601) is no longer
/// dropped translating pi -> claude/codex/opencode, since every writer now
/// emits it and every loader now captures it back. `pi_msg_timestamp` (pi's
/// DISTINCT message-level unix-ms clock) STAYS in the residue — a fidelity-
/// regression fix landed in the same wave restored its capture (it was
/// wrongly deleted alongside the `timestamp` wiring, corrupting pi's own
/// native round-trip in the process) and confirmed it is INHERENT residue on
/// every cross-format hop: claude/codex/opencode have only one timestamp
/// slot per message and cannot represent pi's second one. So the pi-sourced
/// cross cells' residue count went 24 (pre-item-1) -> 22 (item-1, a
/// regression that wrongly dropped 2 keys) -> 23 (this fix: only `timestamp`
/// is legitimately removed, `pi_msg_timestamp` is restored/retained).
///
/// **Post Claude active-history projection:** adjacent Claude assistant
/// records with the same API message id are streamed chunks of one response,
/// not standalone turns. Coalescing the fixture's reasoning chunk with its
/// tool-use chunk restores Claude -> Codex to 100% message fidelity: Codex
/// retains the combined turn even though its wire format cannot retain the
/// Claude-only thinking provenance metadata. OpenCode and Pi remain at 75%
/// because of the pre-existing tool-result `.name` asymmetry.
const FLOOR: [[f64; N]; N] = [
    // to:        claude  codex  opencode   pi    grok
    /* claude   */
    [100.0, 100.0, 75.0, 75.0, 100.0],
    /* codex    */ [100.0, 100.0, 71.4, 71.4, 100.0],
    /* opencode */ [50.0, 50.0, 60.0, 60.0, 60.0],
    /* pi       */ [60.0, 60.0, 60.0, 60.0, 60.0],
    /* grok     */ [100.0, 100.0, 100.0, 100.0, 100.0],
];

/// `frozen_residue(a, b)` = the measured residue this cell must reproduce
/// exactly (S7). DERIVED from the first correct run of
/// [`fidelity_matrix_5x5_and_residue`] against the committed fixtures — see
/// that test's own doc comment. A future run that measures anything
/// DIFFERENT here (a loader newly dropping OR newly preserving something)
/// fails the test instead of passing silently.
fn frozen_residue(a: usize, b: usize) -> CellResidue {
    // claude's Tool messages never carry `.name` (Claude Code's native
    // `tool_result` block has no name field); pi/opencode always do (their
    // `tool`/`toolResult` records carry the tool name alongside the call).
    // Reversible (round-trips back to None going back to claude/codex), so
    // it never breaks A->B->A identity — but it IS one whole message that
    // fails an exact `msg_eq_multimodal` match (name differs), hence
    // `other_dropped_messages`.
    let claude_cc_keys = keys(&["sourceToolAssistantUUID"]);
    let codex_dropped_keys = keys(&["phase", "reasoning_encrypted"]);
    let opencode_dropped_keys_to_cc = keys(&[
        "agent",
        "cost",
        "finish",
        "is_summary",
        "model",
        "oc_message_id",
        "tokens",
    ]);
    // WAVE-2 fidelity item 2: the opencode->opencode diagonal writer
    // (`append_synthesized_opencode_messages`) now re-emits `agent`/`cost`/
    // `finish`/`is_summary`/`model`/`tokens` in opencode's own native `info`
    // shape (see `opencode_restore_agent_model_fields` in `session.rs`), so
    // none of the six are dropped on THIS cell anymore — they only drop on
    // CROSS-format hops (`opencode_dropped_keys_to_cc`/`_to_pi` above/below,
    // unchanged: the claude/codex/pi writers have no slot for opencode's
    // native shape). The synthesized document does regenerate message/part
    // ids, however, so their source values are semantic residue even though
    // the keys remain present.
    let opencode_dropped_keys_diag = keys(&["oc_message_id", "oc_part_id"]);
    let opencode_dropped_keys_to_pi = keys(&[
        "agent",
        "cost",
        "finish",
        "is_summary",
        "model",
        "oc_message_id",
        "oc_part_id",
        "tokens",
    ]);
    // `pi_msg_timestamp` (pi's message-level unix-ms clock) is INHERENT
    // residue here, not a WAVE-2 regression to re-close: Claude Code, Codex,
    // and OpenCode each have exactly ONE timestamp slot per message, and pi
    // is the only one of the four formats with a SECOND, message-level clock
    // reading distinct from its entry-level timestamp (the canonical
    // `metadata["timestamp"]` ISO field, wired cross-format below — see the
    // WAVE-2 item 1 fidelity-fix note on `(3, 0) | (3, 1) | (3, 2)`). None of
    // the other three writers has anywhere to put a second timestamp, so
    // this key can never survive a pi -> claude/codex/opencode hop no matter
    // how faithful the loader/writer pair is.
    let pi_dropped_keys_cross = keys(&[
        "pi_api",
        "pi_bash_cancelled",
        "pi_bash_command",
        "pi_bash_exit_code",
        "pi_bash_output",
        "pi_bash_truncated",
        "pi_custom_type",
        "pi_details",
        "pi_display",
        "pi_entry_id",
        "pi_first_kept_entry_id",
        "pi_from_id",
        "pi_msg_timestamp",
        "pi_parent_id",
        "pi_provider",
        "pi_stop_reason",
        "pi_tokens_before",
        "pi_type",
        "pi_usage",
    ]);
    let pi_dropped_keys_diag = keys(&[
        "pi_bash_cancelled",
        "pi_bash_command",
        "pi_bash_exit_code",
        "pi_bash_output",
        "pi_bash_truncated",
        "pi_custom_type",
        "pi_details",
        "pi_display",
        "pi_entry_id",
        "pi_first_kept_entry_id",
        "pi_from_id",
        "pi_parent_id",
        "pi_tokens_before",
        "pi_type",
    ]);

    match (a, b) {
        // claude -> claude: T1-value-ish view round-trip; only
        // `sourceToolAssistantUUID` (Claude-only linkage metadata) has no
        // home in the writer. The fixture's standalone `thinking`-only
        // assistant record (PARITY-11) now survives here too — Claude
        // Code's OWN format has a real `thinking` block slot, and
        // `write_claude_code_records` re-emits the retained metadata (see
        // its PARITY-11 comment) — so this cell's `other_dropped_messages`
        // is UNCHANGED at 0.
        (0, 0) => CellResidue {
            compacted_out_excluded: 0,
            other_dropped_messages: 0,
            dropped_metadata_keys: claude_cc_keys,
        },
        // claude -> codex: the fixture's adjacent reasoning and tool-use
        // records share an API message id, so the Claude loader coalesces
        // them into one streamed assistant response. Codex preserves that
        // complete canonical turn, but has no native slots for Claude's
        // native turn id, generating model, reasoning provenance, or
        // source-tool linkage metadata. These are retained by the Claude
        // diagonal and the namespaced Grok envelope, so naming them here is
        // deliberate cross-format residue rather than silent loss.
        (0, 1) => CellResidue {
            compacted_out_excluded: 0,
            other_dropped_messages: 0,
            dropped_metadata_keys: keys(&[
                "claude_uuid",
                "model",
                "sourceToolAssistantUUID",
                "thinking",
                "thinking_blocks",
                "thinking_signature",
            ]),
        },
        // claude -> opencode/pi: the pre-existing subagent tool-result
        // message's `.name` asymmetry (see above) is the only unmatched
        // message. Both formats have a native reasoning/thinking part, so
        // the standalone thinking-only record now survives. Claude's exact
        // multi-block provenance list, native turn id, and generating model
        // have no native counterparts, leaving those metadata keys as
        // explicit residue.
        (0, 2) | (0, 3) => CellResidue {
            compacted_out_excluded: 0,
            other_dropped_messages: 1,
            dropped_metadata_keys: keys(&["claude_uuid", "model", "thinking_blocks"]),
        },
        // codex -> claude/codex: `phase`/`reasoning_encrypted` (Codex-only
        // provenance) have no home in either writer.
        (1, 0) | (1, 1) => CellResidue {
            compacted_out_excluded: 0,
            other_dropped_messages: 0,
            dropped_metadata_keys: codex_dropped_keys.clone(),
        },
        // codex -> opencode/pi: same metadata drop, plus the SAME `.name`
        // asymmetry on codex's 2 tool-result messages (codex never sets
        // `.name`; pi/opencode do).
        (1, 2) | (1, 3) => CellResidue {
            compacted_out_excluded: 0,
            other_dropped_messages: 2,
            dropped_metadata_keys: codex_dropped_keys,
        },
        // opencode -> claude/codex: compacted-out prefix (4, expected/correct)
        // + the `.name` asymmetry on its 1 non-excluded tool result, on top of
        // opencode's own overflow metadata. (IX-6 FIXED this wave: the
        // codex-loader combined-turn split that used to contribute a 2nd
        // `other_dropped_messages` to `opencode -> codex` specifically is
        // gone — the lookback-merge in `push_codex_item` now reconstructs
        // `msg_a0006` ("Sure." + the bash call) as ONE `ChatMessage`, exactly
        // matching `opencode -> claude`'s residue.)
        (2, 0) | (2, 1) => CellResidue {
            compacted_out_excluded: 4,
            other_dropped_messages: 1,
            dropped_metadata_keys: opencode_dropped_keys_to_cc,
        },
        // opencode -> opencode (diagonal view round-trip): non-empty
        // residue(A,A) as frozen (S7) — the compacted-out prefix; no
        // `.name`/message-count issue since both sides are opencode.
        // WAVE-2 item 2 FIXED this wave: `dropped_metadata_keys` went from
        // the 6-key set (`agent`/`cost`/`finish`/`is_summary`/`model`/
        // `tokens`) to the two regenerated id values — the export-doc writer
        // re-emits all six in opencode's own native `info` shape, while the
        // stricter meter still names identity drift on this diagonal.
        (2, 2) => CellResidue {
            compacted_out_excluded: 4,
            other_dropped_messages: 0,
            dropped_metadata_keys: opencode_dropped_keys_diag,
        },
        // opencode -> pi: compacted-out prefix + overflow metadata (pi
        // additionally can't restore `oc_part_id`, unlike claude/codex which
        // never had a part-id slot to try).
        (2, 3) => CellResidue {
            compacted_out_excluded: 4,
            other_dropped_messages: 0,
            dropped_metadata_keys: opencode_dropped_keys_to_pi,
        },
        // pi -> claude/codex/opencode: compacted-out prefix + pi's own
        // overflow metadata; no message-level drop. (IX-5 FIXED this wave:
        // the Claude/Codex writers now serialize `content_parts`, so pi's
        // replay-eligible image message — `eimg0001` — survives into
        // claude/codex exactly like it already did into opencode.)
        // (WAVE-2 item 1 FIXED this wave: `timestamp` is REMOVED from
        // `pi_dropped_keys_cross` — every writer now emits
        // `metadata["timestamp"]` (the canonical real per-message ISO-8601
        // timestamp pi's loader already populated) instead of the SYNTH_TS
        // placeholder, and every loader now captures it back, so the key
        // survives translation instead of vanishing. `pi_msg_timestamp`
        // STAYS — see `pi_dropped_keys_cross`'s own doc comment: it's pi's
        // DISTINCT message-level unix-ms clock, and claude/codex/opencode
        // each have only one timestamp slot per message, so it's INHERENT
        // residue, not a defect. A same-wave fidelity-regression fix
        // restored its capture — a preceding change had wrongly deleted it
        // entirely, which both silently lost pi's real per-message clock
        // AND corrupted pi's own native round-trip (the writer's nested
        // `message.timestamp` was deriving from the entry-level ISO instead
        // of preserving the source message-level value) — see
        // `msg_pi_native_timestamp_ms` in `session.rs`.)
        (3, 0) | (3, 1) | (3, 2) => CellResidue {
            compacted_out_excluded: 4,
            other_dropped_messages: 0,
            dropped_metadata_keys: pi_dropped_keys_cross,
        },
        // pi -> pi (diagonal view round-trip): non-empty residue(A,A) as
        // frozen (S7).
        (3, 3) => CellResidue {
            compacted_out_excluded: 4,
            other_dropped_messages: 0,
            dropped_metadata_keys: pi_dropped_keys_diag,
        },
        // Grok's namespaced canonical envelope retains fields absent from
        // its stock schema. Claude/Codex have no replay-excluded prefix, and
        // the Grok fixture is lossless through every target.
        (0, 4) | (1, 4) | (4, 0) | (4, 1) | (4, 2) | (4, 3) | (4, 4) => CellResidue {
            compacted_out_excluded: 0,
            other_dropped_messages: 0,
            dropped_metadata_keys: keys(&[]),
        },
        // OpenCode/Pi retain every replay-eligible field through Grok; only
        // their deliberately excluded pre-compaction prefix remains residue.
        (2, 4) | (3, 4) => CellResidue {
            compacted_out_excluded: 4,
            other_dropped_messages: 0,
            dropped_metadata_keys: keys(&[]),
        },
        _ => unreachable!("all 25 cells covered above"),
    }
}

#[test]
fn fidelity_matrix_5x5_and_residue() {
    let sessions: Vec<Session> = FORMATS.iter().map(|&f| load_fixture(f)).collect();

    println!("\n=== §4.1 TRANSLATION FIDELITY MATRIX (5x5, msg_eq_multimodal) ===");
    let mut header = format!("{:<10}", "from\\to");
    for i in 0..N {
        header.push_str(&format!("{:>16}", fname(i)));
    }
    println!("{header}");

    let mut cells: Vec<Vec<(f64, usize, CellResidue)>> = Vec::with_capacity(N);

    for (ai, a) in FORMATS.iter().enumerate() {
        let s1 = &sessions[ai];
        let m1 = core(&s1.messages);
        let mut row = Vec::with_capacity(N);
        let mut line = format!("{:<10}", fname(ai));
        for b in FORMATS.iter() {
            let exported = s1
                .to_jsonl(*b)
                .unwrap_or_else(|e| panic!("{a:?}->{b:?}: to_jsonl failed: {e}"));
            let s2 = Session::load_str(&exported, *b)
                .unwrap_or_else(|e| panic!("{a:?}->{b:?}: reload failed: {e}"));
            let m2 = core(&s2.messages);
            let metric = measure_cell(&m1, &m2);
            line.push_str(&format!(
                "{:>10.1}%(r{})",
                metric.pct(),
                metric.residue.count()
            ));
            row.push((metric.pct(), metric.residue.count(), metric.residue));
        }
        println!("{line}");
        cells.push(row);
    }

    // Print the measured residue detail (not just the count) for every cell.
    println!("\n=== residue detail per cell (measured, S7) ===");
    for (ai, _a) in FORMATS.iter().enumerate() {
        for (bi, _b) in FORMATS.iter().enumerate() {
            let (pct, count, residue) = &cells[ai][bi];
            println!(
                "  {} -> {}: {:.1}% lossless, residue={} \
                 (compacted_out_excluded={}, other_dropped_messages={}, dropped_metadata_keys={:?})",
                fname(ai),
                fname(bi),
                pct,
                count,
                residue.compacted_out_excluded,
                residue.other_dropped_messages,
                residue.dropped_metadata_keys
            );
        }
    }

    // Assertions: floor + frozen-residue-equality, per cell.
    for ai in 0..N {
        for bi in 0..N {
            let (pct, _count, residue) = &cells[ai][bi];
            assert!(
                *pct >= FLOOR[ai][bi] - 1e-9,
                "{} -> {}: {:.2}% lossless is below the frozen floor {:.2}%",
                fname(ai),
                fname(bi),
                pct,
                FLOOR[ai][bi]
            );
            assert_eq!(
                *residue,
                frozen_residue(ai, bi),
                "{} -> {}: measured residue drifted from the frozen table",
                fname(ai),
                fname(bi)
            );
        }
    }
}

// ---------------------------------------------------------------------------
// Diagonal — NATIVE round-trip: the real "100%" (T1-byte for cc/codex/pi,
// T1-value for opencode). Distinct code path from the to_jsonl(self) view
// round-trip measured as the (a,a) cell above.
// ---------------------------------------------------------------------------

#[test]
fn diagonal_native_round_trip_is_the_real_100_percent() {
    for &f in &FORMATS {
        let s1 = load_fixture(f);
        let original = std::fs::read_to_string(fixture(fixture_file_for(f))).unwrap();
        let native = s1.to_native_jsonl();
        let body = strip_native_header(&native);

        match f {
            SessionFormat::ClaudeCode
            | SessionFormat::Codex
            | SessionFormat::Pi
            | SessionFormat::Grok => {
                assert_eq!(
                    body, original,
                    "{f:?}: T1-byte native round-trip must reproduce the ORIGINAL fixture bytes"
                );
                println!(
                    "  native[{}]: T1-byte byte-equal ({} bytes) — PASS (the real \"100%\")",
                    fname(idx(f)),
                    original.len()
                );
            }
            SessionFormat::OpenCode => {
                let a_lines = non_empty_lines(&original);
                let b_lines = non_empty_lines(body);
                assert_eq!(
                    a_lines.len(),
                    b_lines.len(),
                    "opencode: T1-value envelope count must survive"
                );
                for (i, (x, y)) in a_lines.iter().zip(&b_lines).enumerate() {
                    let va: serde_json::Value = serde_json::from_str(x)
                        .unwrap_or_else(|e| panic!("original envelope {i} invalid: {e}"));
                    let vb: serde_json::Value = serde_json::from_str(y)
                        .unwrap_or_else(|e| panic!("reconstructed envelope {i} invalid: {e}"));
                    assert_eq!(
                        va, vb,
                        "opencode: T1-value envelope {i} must be parsed-JSON-equal"
                    );
                }
                println!(
                    "  native[opencode]: T1-value parsed-JSON-equal ({} envelopes) — PASS \
                     (the real \"100%\")",
                    a_lines.len()
                );
            }
        }

        // The from_native_str reload must also agree with a fresh parse of
        // the session at the `Session.raw` level (coherence, not just the
        // reconstructed-bytes/value comparison above).
        let reloaded = Session::from_native_str(&native).unwrap();
        assert_eq!(
            s1.raw.len(),
            reloaded.raw.len(),
            "{f:?}: native round-trip raw line count must survive"
        );
    }
}

// ---------------------------------------------------------------------------
// RESOLVED, formerly PRE-EXISTING defects in the Claude Code/Codex
// writers and Codex's own loader — discovered BY this build's stronger
// `msg_eq_multimodal` comparator + the new pi/opencode fixtures (living in
// `write_claude_code_records`/`write_codex_records`/`push_codex_item`,
// pre-dating this branch; Waves A/B only touched the Pi/OpenCode loaders/
// writers). Named IX-5/IX-6 in `docs/interop/build-followups.md`, both are
// now FIXED (this wave) rather than pinned-and-reported:
//
// 1. **IX-5 (FIXED): Claude Code's and Codex's writers now serialize
//    `content_parts`** (`write_claude_code_records`'s `Role::User` arm calls
//    `claude_user_content_value`; `push_codex_message` calls
//    `codex_message_content_blocks`) — a multimodal (image) User message
//    survives export to either format as that harness's native image-block
//    shape (Claude: `{"type":"image","source":{...}}`; Codex:
//    `{"type":"input_image","image_url":...}`), and BOTH loaders
//    (`push_claude_user`'s `image` arm via `claude_image_block_to_part`;
//    `push_codex_item`'s `"message"` arm via `codex_extract_images`) parse
//    those blocks back into `content_parts` on load, instead of skipping
//    them. Pi's fixture carries a REPLAY-ELIGIBLE image message
//    (`eimg0001`, not compacted_out); `Pi -> ClaudeCode` and `Pi -> Codex`
//    now both preserve that message (see the updated `FLOOR`/
//    `frozen_residue` for cells `(3,0)`/`(3,1)`).
// 2. **IX-6 (FIXED): Codex's loader now remerges a `message`(assistant)
//    response_item with an immediately-following `function_call`
//    response_item sharing one turn back into a single `ChatMessage`**
//    (`push_codex_item`'s `"message"` arm tags the pushed assistant message
//    with an internal `__codex_open_turn` marker — stripped again before
//    `from_codex_str` returns, so it never leaks as visible metadata — and
//    the `"function_call"` arm merges into that SAME message when it's
//    still `out.last()`, instead of always calling `push_assistant` with a
//    fresh message; a bare `function_call` with no such preceding text, or
//    one separated by a genuine turn boundary — e.g. an intervening `user`
//    message, which becomes the new `out.last()` and has no marker — is
//    unaffected). OpenCode's fixture carries a replay-eligible combined
//    text+tool-call assistant turn (`msg_a0006`, "Sure." + the `bash`
//    call); round-tripping it through Codex now reconstructs ONE combined
//    message (see the updated `FLOOR`/`frozen_residue` for cell `(2,1)`).
//
// Both were genuine LOADER/WRITER defects, not Pi/OpenCode residue — the fix
// lives entirely in `crates/core/src/session.rs` (the four writer/loader
// sites named above), with NO change to the pi/opencode loaders/writers and
// NO weakening of the comparator. The residue/floor tables above were
// re-measured against the fixed loaders/writers and the new values recommitted.
// ---------------------------------------------------------------------------

// ---------------------------------------------------------------------------
// Claude active-history projection coalesces adjacent assistant chunks with
// the same API message id before translation. This resolves the former
// PARITY-11 Claude -> Codex count delta: reasoning belongs to the same
// assistant response as the following tool-use block, so no standalone turn
// needs to be synthesized or dropped.
// ---------------------------------------------------------------------------

/// Net message-count delta a `source -> target` round trip is CURRENTLY
/// known to introduce. `0` for every pair. Kept as a function
/// (rather than a bare table) so `a_to_b_to_a_round_trip_identity` and
/// `compacted_session_cell_exported_context_length_matches_replay_slice`
/// don't need their own special cases. A future regression reintroducing a
/// count delta fails the ordinary identity path.
fn known_defect_count_delta(_source: SessionFormat, _target: SessionFormat) -> i64 {
    0
}

// ---------------------------------------------------------------------------
// Compacted-session cell (S3): the exported context length (messages after
// excluding compacted_out) must equal the source harness's own replay slice
// — catches double-inclusion (full history + summary materialized together).
// ---------------------------------------------------------------------------

#[test]
fn compacted_session_cell_exported_context_length_matches_replay_slice() {
    // Both pi and opencode fixtures are compacted sessions (§5.1e/§5.2d) —
    // exercise the assertion from each, into every target format.
    for &source in &[SessionFormat::Pi, SessionFormat::OpenCode] {
        let s1 = load_fixture(source);
        let replay_slice = replay_eligible(&core(&s1.messages));
        assert!(
            replay_slice.len() < core(&s1.messages).len(),
            "sanity: the {source:?} fixture must actually have compacted-out history \
             (else this assertion is vacuous)"
        );
        for &target in &FORMATS {
            let exported = s1.to_jsonl(target).unwrap();
            let s2 = Session::load_str(&exported, target).unwrap();
            let exported_context = core(&s2.messages);
            // `known_defect_count_delta` is always 0 now (IX-5/IX-6 both
            // fixed this wave) — kept in the expression so a future
            // regression that reintroduces either defect fails this
            // assertion instead of passing silently.
            let delta = known_defect_count_delta(source, target);
            let expected_len = (replay_slice.len() as i64 + delta) as usize;
            assert_eq!(
                exported_context.len(),
                expected_len,
                "{source:?} -> {target:?}: exported context length ({}) must equal the source \
                 harness's OWN replay slice ({}) — not the full linearization (double-inclusion \
                 would inflate this; a nonzero delta here would mean a known defect reappeared)",
                exported_context.len(),
                replay_slice.len()
            );
            if delta != 0 {
                println!(
                    "  {source:?} -> {target:?}: context length {} (replay slice {} {delta:+}) \
                     — UNEXPECTED drift from 0 (see this test's module-level doc comment)",
                    exported_context.len(),
                    replay_slice.len(),
                );
            }
        }
        println!(
            "compacted-session cell: {source:?}'s replay slice ({} messages) reproduced \
             (no double-inclusion) across all {N} export targets — PASS",
            replay_slice.len()
        );
    }
}

// ---------------------------------------------------------------------------
// A->B->A round-trip identity: the shared canonical core must survive the
// loop through every intermediate format.
// ---------------------------------------------------------------------------

#[test]
fn a_to_b_to_a_round_trip_identity() {
    println!("\n=== §4.1 A->B->A round-trip identity ===");
    let mut failures: Vec<String> = Vec::new();

    for &a in &FORMATS {
        let s1 = load_fixture(a);
        for &b in &FORMATS {
            let exported_b = s1.to_jsonl(b).unwrap();
            let s2 = Session::load_str(&exported_b, b).unwrap();
            let exported_a = s2.to_jsonl(a).unwrap();
            let s3 = Session::load_str(&exported_a, a).unwrap();

            // Both hops drop replay-excluded (compacted_out) messages by
            // design (§2.1 S3) — the identity that must hold is over the
            // REPLAY-ELIGIBLE subset of S1, which is exactly what two
            // successive `to_jsonl` exports carry forward.
            let expected = replay_eligible(&core(&s1.messages));
            let got = core(&s3.messages);

            let leg1 = measure_cell(&core(&s1.messages), &core(&s2.messages));
            let leg2 = measure_cell(&core(&s2.messages), &got);

            let delta = known_defect_count_delta(a, b);
            if delta == 0 {
                let ok = expected.len() == got.len()
                    && expected
                        .iter()
                        .zip(&got)
                        .all(|(x, y)| interop_common::msg_eq_multimodal(x, y));
                println!(
                    "  {} -> {} -> {}: {} (leg1 residue={}, leg2 residue={})",
                    fname(idx(a)),
                    fname(idx(b)),
                    fname(idx(a)),
                    if ok { "PASS" } else { "FAIL" },
                    leg1.residue.count(),
                    leg2.residue.count(),
                );
                if !ok {
                    failures.push(format!(
                        "{a:?}->{b:?}->{a:?}: identity broken (expected {} messages, got {}); \
                         this pair has NO pinned known-defect delta — this would be a NEW \
                         regression",
                        expected.len(),
                        got.len()
                    ));
                }
            } else {
                // A pinned, KNOWN, pre-existing Claude/Codex writer/loader
                // defect (see `known_defect_count_delta`'s doc comment) —
                // assert the EXACT current drift (both the net message-count
                // delta and that it is caused by exactly one unmatched
                // expected message, not more), so any change to the defect
                // (better OR worse) fails this test rather than passing
                // silently.
                let expected_len = (expected.len() as i64 + delta) as usize;
                let pin_metric = measure_cell(&expected, &got);
                let pinned_ok = got.len() == expected_len
                    && pin_metric.residue.compacted_out_excluded == 0
                    && pin_metric.residue.other_dropped_messages == delta.unsigned_abs() as usize;
                println!(
                    "  {} -> {} -> {}: PINNED KNOWN DEFECT (delta={delta:+}) — {} matched / {} \
                     expected, got {} messages, unmatched={}{}",
                    fname(idx(a)),
                    fname(idx(b)),
                    fname(idx(a)),
                    pin_metric.matched,
                    expected.len(),
                    got.len(),
                    pin_metric.residue.other_dropped_messages,
                    if pinned_ok {
                        "PINNED (see module doc comment)"
                    } else {
                        "DRIFTED"
                    },
                );
                if !pinned_ok {
                    failures.push(format!(
                        "{a:?}->{b:?}->{a:?}: KNOWN-DEFECT pin drifted — expected len \
                         {expected_len} (delta {delta:+}), got len {} with {} unmatched \
                         (expected exactly {})",
                        got.len(),
                        pin_metric.residue.other_dropped_messages,
                        delta.unsigned_abs()
                    ));
                }
            }
        }
    }

    assert!(
        failures.is_empty(),
        "A->B->A round-trip identity failures:\n  {}",
        failures.join("\n  ")
    );
}

// ---------------------------------------------------------------------------
// Completeness guard (mirrors schema_coverage.rs): no record/part
// discriminant may fall into an Unknown bucket for pi/opencode over the
// committed fixtures.
// ---------------------------------------------------------------------------

#[test]
fn completeness_guard_no_unknown_discriminants_for_pi_or_opencode() {
    for (file, corpus) in [
        ("pi_session.jsonl", Corpus::Pi),
        ("opencode_session.jsonl", Corpus::OpenCode),
    ] {
        let tmp = std::env::temp_dir().join(format!(
            "sc-matrix-cov-{}-{}",
            std::process::id(),
            file.replace('.', "_")
        ));
        std::fs::create_dir_all(&tmp).unwrap();
        std::fs::copy(fixture(file), tmp.join(file)).unwrap();

        let report = audit_dir(&tmp, corpus, None);
        assert_eq!(report.parse_errors, 0, "{corpus:?}: typed parse errors");
        let unknown: Vec<_> = report
            .records
            .keys()
            .filter(|k| {
                k.starts_with("<line>/")
                    || k.contains("UnknownRole")
                    || k.contains("UnknownType")
                    || k.contains("UnknownStatus")
                    || k.contains("UnknownImageShape")
            })
            .collect();
        assert!(
            unknown.is_empty(),
            "{corpus:?}: unknown/unmodeled discriminants: {unknown:?}"
        );
        std::fs::remove_dir_all(&tmp).ok();
    }
    println!("completeness guard: no Unknown discriminant bucket for pi or opencode — PASS");
}

// ---------------------------------------------------------------------------
// Fixture-floor (S2): each interop fixture must carry >=1 multimodal
// message, or the multimodal path is untested and this guard fails.
// ---------------------------------------------------------------------------

#[test]
fn fixture_floor_pi_and_opencode_are_multimodal() {
    let pi = load_fixture(SessionFormat::Pi);
    assert!(
        pi.messages.iter().any(|m| m.content_parts.is_some()),
        "pi fixture must contain >=1 multimodal message"
    );
    let oc = load_fixture(SessionFormat::OpenCode);
    assert!(
        oc.messages.iter().any(|m| m.content_parts.is_some()),
        "opencode fixture must contain >=1 multimodal message"
    );
    println!("fixture floor: pi + opencode fixtures both carry >=1 multimodal message — PASS");
}

/// A supercode reload is deliberately tolerant and therefore cannot prove
/// that a synthesized OpenCode record satisfies OpenCode's own schema. Pin
/// the required native `reasoning.time.{start,end}` shape explicitly so the
/// standalone-thinking parity fix remains importable by stock OpenCode.
#[test]
fn claude_standalone_thinking_emits_native_opencode_reasoning_time() {
    let claude = load_fixture(SessionFormat::ClaudeCode);
    let exported = claude.to_jsonl(SessionFormat::OpenCode).unwrap();
    let doc: serde_json::Value = serde_json::from_str(&exported).unwrap();
    let reasoning = doc["messages"]
        .as_array()
        .unwrap()
        .iter()
        .flat_map(|message| message["parts"].as_array().unwrap())
        .find(|part| part["type"] == "reasoning")
        .expect("Claude thinking-only turn must emit an OpenCode reasoning part");

    let start = reasoning["time"]["start"]
        .as_i64()
        .expect("OpenCode reasoning.time.start must be an integer unix-ms timestamp");
    let end = reasoning["time"]["end"]
        .as_i64()
        .expect("OpenCode reasoning.time.end must be an integer unix-ms timestamp");
    assert_eq!(start, end, "synthesized reasoning is a zero-duration span");
}

// ---------------------------------------------------------------------------
// P6 gate 3 (`docs/composable-harness/COMPOSABLE-HARNESS-DESIGN.md` §5.2):
// "the N×N translation matrix extended with per-preset behavioral vectors
// (permission golden vectors, stock-resume acceptance per §4's measurement
// paragraphs)."
//
// §4.1's ideal measurement is: run the SAME scripted task under stock `pi
// --mode json` and under `supercode extends = "pi-core"` in JSONL mode, then
// diff the event streams/session trees modulo timestamps/ids. That requires
// the real stock CLIs (pi/opencode/claude/codex/grok), accounts, and provider
// credentials. That independent acceptance is owned by
// `scripts/stock-resume-matrix-probe.mjs` and its content-free dated receipt;
// it is intentionally not duplicated in this always-run corpus test.
//
// What CAN run in CI, unconditionally, is the CORPUS-BASED substitute this
// gate builds: for each of the four PARITY PRESETS (`pi-core`, `cc-parity`,
// `cx-parity`, `oc-parity` — each named, in the design doc's own §4.x
// sections, as targeting exactly one harness's native session format), (1)
// resolve the preset through the SAME `configfile::resolve` entry point
// `composable_presets.rs`'s golden tests use — a broken preset fails here
// before the corpus check even runs — and (2) reuse
// `interop_emulate_continue.rs`'s own splice mechanism (load the real
// committed fixture with that harness's native loader, append a synthetic
// continuation turn via the native-v2 sidecar, splice-export back to that
// harness's format) to prove the imported prefix survives and the appended
// turn reloads — the behavioral acceptance bar §4.1 names, corpus-based
// rather than live-CLI-based. A regression in EITHER the preset resolver OR
// the emulate-continue splice path for any of these four formats fails this
// gate.
// ---------------------------------------------------------------------------

/// Preset -> the harness format it targets -> the committed real-corpus
/// fixture file for that format (prefers a genuine real-corpus fixture over
/// the plain synthetic one where one exists, for the strongest available
/// behavioral signal).
const PRESET_BEHAVIORAL_VECTORS: [(&str, SessionFormat, &str); 4] = [
    (
        "pi-core",
        SessionFormat::Pi,
        "pi_real_corpus_tool_call.jsonl",
    ),
    (
        "cc-parity",
        SessionFormat::ClaudeCode,
        "claude_code_session.jsonl",
    ),
    (
        "cx-parity",
        SessionFormat::Codex,
        "codex_real_rollout_tools.jsonl",
    ),
    (
        "oc-parity",
        SessionFormat::OpenCode,
        "opencode_session.jsonl",
    ),
];

fn load_by_format(format: SessionFormat, file: &str) -> Session {
    let path = fixture(file);
    match format {
        SessionFormat::ClaudeCode => Session::from_claude_code(path).unwrap(),
        SessionFormat::Codex => Session::from_codex(path).unwrap(),
        SessionFormat::OpenCode => Session::from_opencode(path).unwrap(),
        SessionFormat::Pi => Session::from_pi(path).unwrap(),
        SessionFormat::Grok => Session::from_grok(path).unwrap(),
    }
}

#[test]
fn preset_behavioral_vectors_corpus_based_stock_resume_acceptance() {
    for (preset, format, file) in PRESET_BEHAVIORAL_VECTORS {
        // (1) the preset must still resolve — a regression that breaks
        // preset resolution (a bad module dep/conflict edit, a typo'd
        // built-in table) fails here before the corpus check even runs.
        let resolved = resolve(
            &format!("extends = \"{preset}\"\n"),
            None,
            &ResolveOptions::default(),
        )
        .unwrap_or_else(|e| panic!("preset `{preset}` failed to resolve: {e}"));
        assert!(
            !resolved.modules.is_empty(),
            "preset `{preset}` resolved with an empty module activation set — the resolver \
             regressed to a no-op"
        );

        // (2) corpus-based behavioral acceptance: load the real fixture
        // with `format`'s own loader, append a synthetic continuation turn
        // through the SAME native-v2 sidecar mechanism
        // `interop_emulate_continue.rs`'s `build_splice` uses, splice-export
        // back to `format`, and assert the imported prefix's message count
        // is preserved AND the appended turn reloads with `format`'s own
        // loader — the corpus-based stand-in for "run stock CLI, diff event
        // streams" this preset's own doc section (§4.1-§4.5) names as its
        // fidelity bar.
        let original = load_by_format(format, file);
        let original_len = original.messages.len();
        assert!(
            original_len > 0,
            "{preset} ({format:?}): fixture {file} must have >=1 message to prove anything"
        );

        let appended = vec![
            ChatMessage::user(format!(
                "{preset} behavioral-vector synthetic continuation turn"
            )),
            ChatMessage::assistant(format!(
                "{preset} behavioral-vector synthetic continuation reply"
            )),
        ];
        let sidecar = original.to_native_jsonl_v2(&appended);
        let reconstructed = Session::from_sidecar_str(&sidecar).unwrap_or_else(|e| {
            panic!("{preset} ({format:?}): native-v2 sidecar failed to reload: {e}")
        });
        let out = reconstructed
            .to_jsonl_spliced(format, None)
            .unwrap_or_else(|e| panic!("{preset} ({format:?}): to_jsonl_spliced failed: {e}"));

        let reloaded = Session::load_str(&out, format).unwrap_or_else(|e| {
            panic!(
                "{preset} ({format:?}): spliced output failed to reload with its own loader: {e}"
            )
        });
        assert!(
            reloaded.messages.len() > original_len,
            "{preset} ({format:?}): spliced session must carry MORE messages than the imported \
             prefix alone (imported {original_len}, reloaded {})",
            reloaded.messages.len()
        );
        let has_user_turn = reloaded.messages.iter().any(|m| {
            m.content
                .as_deref()
                .is_some_and(|c| c.contains("behavioral-vector synthetic continuation turn"))
        });
        let has_assistant_turn = reloaded.messages.iter().any(|m| {
            m.content
                .as_deref()
                .is_some_and(|c| c.contains("behavioral-vector synthetic continuation reply"))
        });
        assert!(
            has_user_turn && has_assistant_turn,
            "{preset} ({format:?}): the appended continuation turn must survive the \
             preset-targeted format's own splice+reload round trip"
        );
    }
    println!(
        "preset behavioral vectors: {} presets, all resolved + corpus-based stock-resume \
         acceptance passed. NOTE: independent stock-CLI acceptance is exercised by \
         scripts/stock-resume-matrix-probe.mjs for claude/codex/opencode/pi/grok and \
         recorded in its content-free dated receipt.",
        PRESET_BEHAVIORAL_VECTORS.len()
    );
}