@sensigo/realm-testing 0.37.0 → 0.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -405,6 +405,10 @@ function terminalRefusalCases(adapter) {
405
405
  await adapter.store.update({
406
406
  ...run,
407
407
  terminal_state: true,
408
+ // issue #367: a terminal record names its arm. These fixtures mean "this run is over";
409
+ // their opaque `tck` prose is deliberately unclassifiable, so the boundary's coherence
410
+ // check abstains and the fixture stays a pure terminal-precondition seeder.
411
+ sealed_by: { arm: 'complete' },
408
412
  terminal_reason: 'tck-terminal',
409
413
  });
410
414
  const result = await settleStep(run.id, {
@@ -432,6 +436,23 @@ function terminalRefusalCases(adapter) {
432
436
  // Reintroducing either trio disjunct into `isTerminal` reds this law; genuine terminal refusal is
433
437
  // TERMINAL_REFUSAL's job (`terminal_state: true` — which abandon/abort ALWAYS set atomically,
434
438
  // abandon-run.ts:66-71 / execution-loop.ts:1803-1811).
439
+ //
440
+ // REPINNED 2026-08-20 (issue #367) — ARM-WINS. Each case below now also asserts the DERIVED PHASE
441
+ // of its product record, and that phase CHANGED with #367: the run is settled `complete`, so it
442
+ // stamps `sealed_by: {arm: 'complete'}`, and the recorded arm outranks the seeded marker. Before
443
+ // #367 the same record derived `abandoned`/`aborted` from the marker alone.
444
+ //
445
+ // These two product records are the ONLY engine-constructible arm-vs-marker disagreements in the
446
+ // suite, which makes them the keystone: revert the sealed-wins branch in `deriveRunPhase` and the
447
+ // new assertions below are what reds. Nothing else observes that revert — the arm path and the
448
+ // legacy path agree on every honest record.
449
+ //
450
+ // The seeded foreign marker violates nothing: `SEAL_MARKERS_AGREE` is transform-scoped and
451
+ // ONE-DIRECTIONAL (an arm requires its own marker; it never forbids a marker it did not write).
452
+ // Do not "fix" these fixtures by removing the marker — the disagreement IS the test.
453
+ //
454
+ // (The law's own premise comment above — "resume never strips abandoned_at" — has been stale since
455
+ // #281; the honest production channel for this shape is a mixed-version fleet.)
435
456
  // ---------------------------------------------------------------------------
436
457
  function terminalStateOnlyCases(adapter) {
437
458
  const { minimalDefinition } = adapter.settlementFixture;
@@ -460,6 +481,12 @@ function terminalStateOnlyCases(adapter) {
460
481
  if (!result.run.completed_steps.includes('a')) {
461
482
  throw new Error(`expected 'a' to land in completed_steps, got: ${JSON.stringify(result.run.completed_steps)}`);
462
483
  }
484
+ // issue #367 KEYSTONE: the run sealed `complete`, so the recorded arm decides — even
485
+ // though `abandoned_at` is still on the record. Pre-#367 this derived 'abandoned'.
486
+ const phase = deriveRunPhase(result.run);
487
+ if (phase !== 'completed') {
488
+ throw new Error(`arm-wins: expected derived phase 'completed' from sealed_by.arm '${String(result.run.sealed_by?.arm)}' despite the seeded abandoned_at, got '${phase}'`);
489
+ }
463
490
  },
464
491
  },
465
492
  {
@@ -486,6 +513,11 @@ function terminalStateOnlyCases(adapter) {
486
513
  if (!result.run.completed_steps.includes('a')) {
487
514
  throw new Error(`expected 'a' to land in completed_steps, got: ${JSON.stringify(result.run.completed_steps)}`);
488
515
  }
516
+ // issue #367 KEYSTONE (the aborted half): same disagreement, other marker.
517
+ const phase = deriveRunPhase(result.run);
518
+ if (phase !== 'completed') {
519
+ throw new Error(`arm-wins: expected derived phase 'completed' from sealed_by.arm '${String(result.run.sealed_by?.arm)}' despite the seeded aborted_at, got '${phase}'`);
520
+ }
489
521
  },
490
522
  },
491
523
  ];
@@ -494,6 +526,607 @@ function terminalStateOnlyCases(adapter) {
494
526
  // L7 CS-purity — structural: `options` carries VALUES only ({now}); no callback, no registry.
495
527
  // In-repo source-text guard (the calling test file greps applySettlement's own signature).
496
528
  // ---------------------------------------------------------------------------
529
+ // ---------------------------------------------------------------------------
530
+ // issue #367 — the seal-integrity boundary laws. An assertion-capable store (one declaring
531
+ // `settleStep`) must REFUSE each of the four violations rather than persisting them: a record that
532
+ // lies about how it ended is permanent, and an advisory would report success on a write that broke
533
+ // its own invariant.
534
+ // ---------------------------------------------------------------------------
535
+ function sealIntegrityCases(adapter) {
536
+ const { minimalDefinition } = adapter.settlementFixture;
537
+ requireSettleStep(adapter.store); // assertion-capable stores only
538
+ /** Runs `write`, expecting it to reject with `code`. */
539
+ async function expectRefusedWith(code, what, write) {
540
+ let threw;
541
+ try {
542
+ await write();
543
+ }
544
+ catch (err) {
545
+ threw = err;
546
+ }
547
+ if (threw === undefined) {
548
+ throw new Error(`expected ${what} to be REFUSED with ${code}, but the write succeeded`);
549
+ }
550
+ const actual = threw.code;
551
+ if (actual !== code) {
552
+ throw new Error(`expected ${what} to be refused with ${code}, got '${String(actual)}'`);
553
+ }
554
+ }
555
+ async function freshRun(name) {
556
+ const def = minimalDefinition(['a']);
557
+ const { run } = await adapter.store.create({
558
+ workflowId: `${def.id}-${name}`,
559
+ workflowVersion: 1,
560
+ params: {},
561
+ });
562
+ return run;
563
+ }
564
+ return [
565
+ {
566
+ law: 'SEAL_FRESH_WRITE_REFUSED',
567
+ name: `[${adapter.storeName}] a live → terminal write carrying NO sealed_by is refused`,
568
+ run: async () => {
569
+ const run = await freshRun('unstamped');
570
+ await expectRefusedWith('STATE_SEAL_UNSTAMPED', 'an unstamped fresh seal', () =>
571
+ /* eslint-disable-next-line no-restricted-syntax --
572
+ * issue #367 (part 2), AUTHORIZED: this violation is the LAW. SEAL_FRESH_WRITE_REFUSED
573
+ * exists to prove the store refuses an unstamped fresh seal, so the fixture has to
574
+ * construct one. (This file is a published contract source, not a `.test.ts`, which is
575
+ * why the test-file scoping does not cover it.)
576
+ */
577
+ adapter.store.update({ ...run, terminal_state: true, terminal_reason: 'tck' }));
578
+ },
579
+ },
580
+ {
581
+ law: 'SEAL_FRESH_WRITE_REFUSED',
582
+ name: `[${adapter.storeName}] NEGATIVE CONTROL: re-writing an ALREADY-terminal unstamped record passes (the legacy population stays usable)`,
583
+ run: async () => {
584
+ const run = await freshRun('legacy-repersist');
585
+ const sealed = await adapter.store.update({
586
+ ...run,
587
+ terminal_state: true,
588
+ sealed_by: { arm: 'complete' },
589
+ terminal_reason: 'tck',
590
+ });
591
+ // A terminal → terminal rewrite. The forward clause is transition-scoped, so this is fine.
592
+ await adapter.store.update({ ...sealed, terminal_reason: 'tck (touched)' });
593
+ },
594
+ },
595
+ {
596
+ law: 'SEAL_ORPHAN_REFUSED',
597
+ name: `[${adapter.storeName}] a terminal → live write that RETAINS sealed_by is refused`,
598
+ run: async () => {
599
+ const run = await freshRun('orphan');
600
+ const sealed = await adapter.store.update({
601
+ ...run,
602
+ terminal_state: true,
603
+ sealed_by: { arm: 'complete' },
604
+ terminal_reason: 'tck',
605
+ });
606
+ await expectRefusedWith('STATE_SEAL_ORPHANED', 'a resume that kept the seal', () => adapter.store.update({ ...sealed, terminal_state: false }));
607
+ },
608
+ },
609
+ {
610
+ law: 'SEAL_ORPHAN_REFUSED',
611
+ name: `[${adapter.storeName}] NEGATIVE CONTROL: the same transition WITH the seal stripped passes`,
612
+ run: async () => {
613
+ const run = await freshRun('orphan-control');
614
+ const sealed = await adapter.store.update({
615
+ ...run,
616
+ terminal_state: true,
617
+ sealed_by: { arm: 'complete' },
618
+ terminal_reason: 'tck',
619
+ });
620
+ const { sealed_by: _dropped, ...base } = sealed;
621
+ await adapter.store.update({ ...base, terminal_state: false });
622
+ },
623
+ },
624
+ {
625
+ law: 'SEAL_ERASE_REFUSED',
626
+ name: `[${adapter.storeName}] a terminal rewrite that DROPS a stored sealed_by is refused`,
627
+ run: async () => {
628
+ const run = await freshRun('erase');
629
+ const sealed = await adapter.store.update({
630
+ ...run,
631
+ terminal_state: true,
632
+ sealed_by: { arm: 'complete' },
633
+ terminal_reason: 'tck',
634
+ });
635
+ const { sealed_by: _erased, ...withoutSeal } = sealed;
636
+ await expectRefusedWith('STATE_SEAL_ERASED', 'a terminal rewrite dropping the seal', () => adapter.store.update({ ...withoutSeal, terminal_reason: 'tck (erased)' }));
637
+ },
638
+ },
639
+ {
640
+ law: 'SEAL_ERASE_REFUSED',
641
+ name: `[${adapter.storeName}] NEGATIVE CONTROL: the same rewrite KEEPING the seal passes`,
642
+ run: async () => {
643
+ const run = await freshRun('erase-control');
644
+ const sealed = await adapter.store.update({
645
+ ...run,
646
+ terminal_state: true,
647
+ sealed_by: { arm: 'complete' },
648
+ terminal_reason: 'tck',
649
+ });
650
+ await adapter.store.update({ ...sealed, terminal_reason: 'tck (kept)' });
651
+ },
652
+ },
653
+ {
654
+ law: 'SEAL_REWRITE_REFUSED',
655
+ name: `[${adapter.storeName}] a stored arm may NOT be changed while the run stays terminal`,
656
+ run: async () => {
657
+ const run = await freshRun('rewrite');
658
+ const sealed = await adapter.store.update({
659
+ ...run,
660
+ terminal_state: true,
661
+ sealed_by: { arm: 'complete' },
662
+ terminal_reason: 'Workflow completed.',
663
+ });
664
+ await expectRefusedWith('STATE_SEAL_REWRITTEN', 'a seal-arm rewrite', () => adapter.store.update({ ...sealed, sealed_by: { arm: 'guard_pass_complete' } }));
665
+ },
666
+ },
667
+ {
668
+ law: 'SEAL_REWRITE_REFUSED',
669
+ name: `[${adapter.storeName}] a rewrite WITH truthful adjudication provenance is ACCEPTED`,
670
+ run: async () => {
671
+ // The lawful key, published day-one so this law never has to be loosened later. The arm
672
+ // pair is deliberately SAME-PHASE: a cross-phase ruling on a record whose prose still says
673
+ // otherwise is refused by SEAL_COHERENT instead, which is a different law's job.
674
+ const run = await freshRun('adjudicated');
675
+ const sealed = await adapter.store.update({
676
+ ...run,
677
+ terminal_state: true,
678
+ sealed_by: { arm: 'complete' },
679
+ terminal_reason: 'Workflow completed.',
680
+ });
681
+ const ruled = await adapter.store.update({
682
+ ...sealed,
683
+ sealed_by: {
684
+ arm: 'guard_pass_complete',
685
+ adjudicated: {
686
+ by: 'tck-operator',
687
+ at: '2026-01-01T00:00:00.000Z',
688
+ previous_arm: 'complete',
689
+ reason: 'the guard closed this run',
690
+ },
691
+ },
692
+ });
693
+ if (ruled.sealed_by?.arm !== 'guard_pass_complete') {
694
+ throw new Error(`the adjudicated arm did not land: ${JSON.stringify(ruled.sealed_by)}`);
695
+ }
696
+ const reread = await adapter.store.get(run.id);
697
+ // The FULL object, field by field: a store that drops `by` or `reason` keeps the ruling's
698
+ // arm and loses who made it and why, which is most of what a ruling IS.
699
+ const expected = {
700
+ by: 'tck-operator',
701
+ at: '2026-01-01T00:00:00.000Z',
702
+ previous_arm: 'complete',
703
+ reason: 'the guard closed this run',
704
+ };
705
+ if (JSON.stringify(reread.sealed_by?.adjudicated) !== JSON.stringify(expected)) {
706
+ throw new Error(`the ruling did not survive the round trip in full: ` +
707
+ `${JSON.stringify(reread.sealed_by?.adjudicated)} — expected ${JSON.stringify(expected)}`);
708
+ }
709
+ },
710
+ },
711
+ {
712
+ law: 'SEAL_REWRITE_REFUSED',
713
+ name: `[${adapter.storeName}] a rewrite whose adjudication LIES about previous_arm is refused`,
714
+ run: async () => {
715
+ const run = await freshRun('adjudicated-lying');
716
+ const sealed = await adapter.store.update({
717
+ ...run,
718
+ terminal_state: true,
719
+ sealed_by: { arm: 'complete' },
720
+ terminal_reason: 'Workflow completed.',
721
+ });
722
+ await expectRefusedWith('STATE_SEAL_REWRITTEN', 'a lying adjudication', () => adapter.store.update({
723
+ ...sealed,
724
+ sealed_by: {
725
+ arm: 'guard_pass_complete',
726
+ adjudicated: { by: 'tck', at: 'now', previous_arm: 'step_failure' },
727
+ },
728
+ }));
729
+ },
730
+ },
731
+ {
732
+ law: 'SEAL_REWRITE_REFUSED',
733
+ name: `[${adapter.storeName}] a SAME-arm write minting lying provenance is refused; a truthful acknowledgment is accepted`,
734
+ run: async () => {
735
+ const run = await freshRun('adjudicated-same');
736
+ const sealed = await adapter.store.update({
737
+ ...run,
738
+ terminal_state: true,
739
+ sealed_by: { arm: 'complete' },
740
+ terminal_reason: 'Workflow completed.',
741
+ });
742
+ await expectRefusedWith('STATE_SEAL_REWRITTEN', 'a same-arm lying mint', () => adapter.store.update({
743
+ ...sealed,
744
+ sealed_by: {
745
+ arm: 'complete',
746
+ adjudicated: { by: 'tck', at: 'now', previous_arm: 'guard_abort' },
747
+ },
748
+ }));
749
+ // The acknowledgment channel: "looked at it, it stands."
750
+ await adapter.store.update({
751
+ ...sealed,
752
+ sealed_by: {
753
+ arm: 'complete',
754
+ adjudicated: { by: 'tck', at: 'now', previous_arm: 'complete' },
755
+ },
756
+ });
757
+ },
758
+ },
759
+ {
760
+ law: 'SEAL_REWRITE_REFUSED',
761
+ name: `[${adapter.storeName}] a terminal rewrite dropping stored adjudication provenance is refused`,
762
+ run: async () => {
763
+ const run = await freshRun('adjudicated-erase');
764
+ const sealed = await adapter.store.update({
765
+ ...run,
766
+ terminal_state: true,
767
+ sealed_by: { arm: 'complete' },
768
+ terminal_reason: 'Workflow completed.',
769
+ });
770
+ const ruled = await adapter.store.update({
771
+ ...sealed,
772
+ sealed_by: {
773
+ arm: 'complete',
774
+ adjudicated: { by: 'tck', at: 'now', previous_arm: 'complete' },
775
+ },
776
+ });
777
+ await expectRefusedWith('STATE_SEAL_REWRITTEN', 'erasing a recorded ruling', () => adapter.store.update({ ...ruled, sealed_by: { arm: 'complete' } }));
778
+ },
779
+ },
780
+ // --- Direction 6: an operator's FIRST stamp of a parked record, three legs. ---
781
+ ...(adapter.seedLegacyTerminal === undefined
782
+ ? []
783
+ : [
784
+ {
785
+ law: 'SEAL_REWRITE_REFUSED',
786
+ name: `[${adapter.storeName}] a terminal UNSTAMPED record accepts a first stamp with previous_arm: null`,
787
+ run: async () => {
788
+ // The parked-unclassifiable population's only attributed landing channel. `null` is
789
+ // the truthful statement "no arm existed before this ruling".
790
+ const seeded = await adapter.seedLegacyTerminal('tck-adjudicate-first');
791
+ const ruled = await adapter.store.update({
792
+ ...seeded,
793
+ sealed_by: {
794
+ arm: 'complete',
795
+ adjudicated: {
796
+ by: 'tck-operator',
797
+ at: '2026-01-01T00:00:00.000Z',
798
+ previous_arm: null,
799
+ reason: 'operator judgement',
800
+ },
801
+ },
802
+ });
803
+ if (ruled.sealed_by?.adjudicated?.previous_arm !== null) {
804
+ throw new Error(`the null first-stamp provenance did not land: ${JSON.stringify(ruled.sealed_by)}`);
805
+ }
806
+ const reread = await adapter.store.get(seeded.id);
807
+ if (reread.sealed_by?.adjudicated?.by !== 'tck-operator') {
808
+ throw new Error(`the ruling did not survive the round trip: ${JSON.stringify(reread.sealed_by)}`);
809
+ }
810
+ },
811
+ },
812
+ {
813
+ law: 'SEAL_REWRITE_REFUSED',
814
+ name: `[${adapter.storeName}] a first stamp claiming a NON-NULL previous_arm is refused`,
815
+ run: async () => {
816
+ const seeded = await adapter.seedLegacyTerminal('tck-adjudicate-first-lying');
817
+ await expectRefusedWith('STATE_SEAL_REWRITTEN', 'a fabricated first-stamp claim', () => adapter.store.update({
818
+ ...seeded,
819
+ sealed_by: {
820
+ arm: 'complete',
821
+ adjudicated: { by: 'tck', at: 'now', previous_arm: 'step_failure' },
822
+ },
823
+ }));
824
+ },
825
+ },
826
+ ]),
827
+ {
828
+ law: 'SEAL_REWRITE_REFUSED',
829
+ name: `[${adapter.storeName}] a LIVE run's first seal cannot claim adjudication, even with null`,
830
+ run: async () => {
831
+ // The terminal-scope half of the first-stamp rule: nothing had happened yet to rule on.
832
+ const run = await freshRun('adjudicate-live');
833
+ await expectRefusedWith('STATE_SEAL_REWRITTEN', 'adjudication on a live first seal', () => adapter.store.update({
834
+ ...run,
835
+ terminal_state: true,
836
+ terminal_reason: 'Workflow completed.',
837
+ sealed_by: {
838
+ arm: 'complete',
839
+ adjudicated: { by: 'tck', at: 'now', previous_arm: null },
840
+ },
841
+ }));
842
+ },
843
+ },
844
+ {
845
+ law: 'SEAL_REWRITE_REFUSED',
846
+ name: `[${adapter.storeName}] a ruling SUPERSEDES the record's own prose, and keeps doing so`,
847
+ run: async () => {
848
+ // Direction 7. The scar rides the ruling write because the boundary itself refuses
849
+ // parked-scar creation through public channels — that population is minted by old binaries
850
+ // this suite cannot reproduce. The stored-scar half is pinned in the engine's own cells.
851
+ const run = await freshRun('adjudicate-supersedes');
852
+ const sealed = await adapter.store.update({
853
+ ...run,
854
+ terminal_state: true,
855
+ sealed_by: { arm: 'complete' },
856
+ terminal_reason: 'Workflow completed.',
857
+ });
858
+ const ruled = await adapter.store.update({
859
+ ...sealed,
860
+ failed_steps: ['x'],
861
+ terminal_reason: 'a scar this classifier cannot place',
862
+ sealed_by: {
863
+ arm: 'complete',
864
+ adjudicated: { by: 'tck-operator', at: 'now', previous_arm: 'complete' },
865
+ },
866
+ });
867
+ if (ruled.terminal_reason !== 'a scar this classifier cannot place') {
868
+ throw new Error('the prose was rewritten — a ruling must never falsify the record');
869
+ }
870
+ // Leg 2: the exemption is PERMANENT. A store exempting only the ruling write wedges the
871
+ // operator on every later spread of the same record.
872
+ await adapter.store.update({ ...ruled, updated_at: '2099-01-01T00:00:00.000Z' });
873
+ },
874
+ },
875
+ {
876
+ law: 'SEAL_REWRITE_REFUSED',
877
+ name: `[${adapter.storeName}] an arm change RIDING a prior ruling is refused — a change needs a FRESH ruling`,
878
+ run: async () => {
879
+ // Direction 8.
880
+ const run = await freshRun('adjudicate-riding');
881
+ const sealed = await adapter.store.update({
882
+ ...run,
883
+ terminal_state: true,
884
+ sealed_by: { arm: 'complete' },
885
+ terminal_reason: 'Workflow completed.',
886
+ });
887
+ const ruled = await adapter.store.update({
888
+ ...sealed,
889
+ sealed_by: {
890
+ arm: 'complete',
891
+ adjudicated: {
892
+ by: 'tck-operator',
893
+ at: '2026-02-02T00:00:00.000Z',
894
+ previous_arm: 'complete',
895
+ },
896
+ },
897
+ });
898
+ await expectRefusedWith('STATE_SEAL_REWRITTEN', 'an arm change riding a prior ruling', () => adapter.store.update({
899
+ ...ruled,
900
+ sealed_by: { arm: 'guard_pass_complete', adjudicated: ruled.sealed_by.adjudicated },
901
+ }));
902
+ },
903
+ },
904
+ {
905
+ law: 'SEAL_REWRITE_REFUSED',
906
+ name: `[${adapter.storeName}] NEGATIVE CONTROL: a rewrite keeping the SAME arm passes`,
907
+ run: async () => {
908
+ const run = await freshRun('rewrite-control');
909
+ const sealed = await adapter.store.update({
910
+ ...run,
911
+ terminal_state: true,
912
+ sealed_by: { arm: 'complete' },
913
+ terminal_reason: 'Workflow completed.',
914
+ });
915
+ // Every ordinary terminal rewrite spreads the record — without this passing, the clause
916
+ // would wedge the engine.
917
+ await adapter.store.update({ ...sealed, terminal_reason: 'Workflow completed. (touched)' });
918
+ },
919
+ },
920
+ {
921
+ law: 'SEAL_UNKNOWN_ARM_REFUSED',
922
+ name: `[${adapter.storeName}] an arm outside SEAL_ARMS never persists`,
923
+ run: async () => {
924
+ const run = await freshRun('unknown-arm');
925
+ await expectRefusedWith('STATE_SEAL_UNKNOWN_ARM', 'a foreign arm', () => adapter.store.update({
926
+ ...run,
927
+ terminal_state: true,
928
+ // Deliberately outside the closed set — the shape a record written by a FUTURE
929
+ // binary would have. The cast is the point: the type system cannot stop a foreign arm
930
+ // arriving from disk or from another version, which is why the boundary checks it.
931
+ sealed_by: { arm: 'from_the_future' },
932
+ terminal_reason: 'tck',
933
+ }));
934
+ },
935
+ },
936
+ ];
937
+ }
938
+ // ---------------------------------------------------------------------------
939
+ // issue #367 part 3 — the `stampSeal` laws. A store that declares the verb is promising a very
940
+ // specific write: the seal arm lands, `version` moves so the CAS protocol can see it, and
941
+ // `updated_at` does NOT move because stamping is not activity. Get that split wrong in either
942
+ // direction and either the stamp is silently erasable or every migrated record looks freshly
943
+ // touched to retention.
944
+ // ---------------------------------------------------------------------------
945
+ function stampSealCases(adapter) {
946
+ const stampSeal = adapter.store.stampSeal;
947
+ if (stampSeal === undefined)
948
+ return []; // dormant store — the laws bind declarers only
949
+ const stamp = stampSeal.bind(adapter.store);
950
+ /**
951
+ * A terminal, ALREADY-STAMPED record — the refusal legs' fixture. (An earlier version of this
952
+ * comment claimed the seal was "then removed by a direct write"; no such removal existed, and
953
+ * the boundary's ERASED clause would refuse one. Corrected rather than reworded.)
954
+ */
955
+ async function stampedTerminal(name) {
956
+ const { run } = await adapter.store.create({
957
+ workflowId: `tck-stamp-${name}`,
958
+ workflowVersion: 1,
959
+ params: {},
960
+ });
961
+ return adapter.store.update({
962
+ ...run,
963
+ completed_steps: ['a'],
964
+ terminal_state: true,
965
+ sealed_by: { arm: 'complete' },
966
+ terminal_reason: 'Workflow completed.',
967
+ });
968
+ }
969
+ return [
970
+ {
971
+ law: 'STAMP_PRESERVES_UPDATED_AT',
972
+ name: `[${adapter.storeName}] stampSeal leaves updated_at byte-identical`,
973
+ run: async () => {
974
+ const sealed = await stampedTerminal('clock');
975
+ // Already stamped, so this exercises the refusal path's clock too — the record must not be
976
+ // touched at all.
977
+ const before = await adapter.store.get(sealed.id);
978
+ await stamp(sealed.id, { arm: 'complete' }, before.version);
979
+ const after = await adapter.store.get(sealed.id);
980
+ if (after.updated_at !== before.updated_at) {
981
+ throw new Error(`stampSeal moved updated_at (${before.updated_at} -> ${after.updated_at}) — stamping ` +
982
+ `is not activity; the retention clock must not move`);
983
+ }
984
+ },
985
+ },
986
+ // --- SUCCESS legs. These need an UNSTAMPED terminal record, which is why the seed hook
987
+ // exists: without them a store that NEVER WRITES conforms to all five laws.
988
+ ...(adapter.seedLegacyTerminal === undefined
989
+ ? [
990
+ {
991
+ law: 'STAMP_PRESERVES_UPDATED_AT',
992
+ name: `[${adapter.storeName}] ADAPTER_WIRING: a stampSeal-declaring store must supply seedLegacyTerminal`,
993
+ run: async () => {
994
+ throw new Error('This store declares stampSeal but the adapter has no `seedLegacyTerminal` hook, ' +
995
+ 'so the laws cannot observe an ACTUAL stamp — only its refusals. A store that ' +
996
+ 'never writes would pass. Supply the hook.');
997
+ },
998
+ },
999
+ ]
1000
+ : [
1001
+ {
1002
+ law: 'STAMP_PRESERVES_UPDATED_AT',
1003
+ name: `[${adapter.storeName}] a SUCCESSFUL stamp leaves updated_at byte-identical`,
1004
+ run: async () => {
1005
+ const seeded = await adapter.seedLegacyTerminal('tck-stamp-success-clock');
1006
+ const result = await stamp(seeded.id, { arm: 'complete', classified: true }, seeded.version);
1007
+ if (!result.stamped) {
1008
+ throw new Error(`expected an unstamped terminal record to be STAMPED, got refusal ` +
1009
+ `'${result.reason}' — a store that never writes is not conformant`);
1010
+ }
1011
+ const after = await adapter.store.get(seeded.id);
1012
+ if (after.sealed_by?.arm !== 'complete') {
1013
+ throw new Error(`the arm did not land: ${JSON.stringify(after.sealed_by)}`);
1014
+ }
1015
+ if (after.updated_at !== seeded.updated_at) {
1016
+ throw new Error(`stamping moved updated_at (${seeded.updated_at} -> ${after.updated_at}) — ` +
1017
+ `stamping is not activity`);
1018
+ }
1019
+ },
1020
+ },
1021
+ {
1022
+ law: 'STAMP_BUMPS_VERSION_ONCE',
1023
+ name: `[${adapter.storeName}] a SUCCESSFUL stamp bumps version exactly once`,
1024
+ run: async () => {
1025
+ const seeded = await adapter.seedLegacyTerminal('tck-stamp-success-version');
1026
+ await stamp(seeded.id, { arm: 'complete', classified: true }, seeded.version);
1027
+ const after = await adapter.store.get(seeded.id);
1028
+ if (after.version !== seeded.version + 1) {
1029
+ throw new Error(`expected version ${seeded.version + 1}, got ${after.version} — the CAS ` +
1030
+ `protocol is what stops a stale writer erasing the stamp`);
1031
+ }
1032
+ },
1033
+ },
1034
+ {
1035
+ law: 'STAMP_CLASSIFIED_ROUNDTRIP',
1036
+ name: `[${adapter.storeName}] a \`classified: true\` stamp survives write → read byte-for-byte`,
1037
+ run: async () => {
1038
+ // The provenance marker is what keeps a classifier-minted stamp distinguishable from
1039
+ // a writer's own assertion FOREVER. A store that drops it looks conformant on every
1040
+ // other law while quietly destroying that distinction.
1041
+ const seeded = await adapter.seedLegacyTerminal('tck-stamp-classified');
1042
+ await stamp(seeded.id, { arm: 'complete', classified: true }, seeded.version);
1043
+ const after = await adapter.store.get(seeded.id);
1044
+ if (after.sealed_by?.classified !== true) {
1045
+ throw new Error(`the \`classified\` provenance marker did not survive the round trip: ` +
1046
+ `${JSON.stringify(after.sealed_by)} — a vehicle-minted stamp must stay ` +
1047
+ `distinguishable from a writer-asserted one`);
1048
+ }
1049
+ },
1050
+ },
1051
+ ]),
1052
+ {
1053
+ law: 'STAMP_RETURNS_NOT_THROWS_PREDICATES',
1054
+ name: `[${adapter.storeName}] a NON-TERMINAL record RETURNS 'not_terminal', never throws`,
1055
+ run: async () => {
1056
+ // No seed hook needed: `create()` makes a live record, which is exactly the shape.
1057
+ const { run } = await adapter.store.create({
1058
+ workflowId: 'tck-stamp-live',
1059
+ workflowVersion: 1,
1060
+ params: {},
1061
+ });
1062
+ const result = await stamp(run.id, { arm: 'complete' }, run.version);
1063
+ if (result.stamped !== false || result.reason !== 'not_terminal') {
1064
+ throw new Error(`expected a RETURNED {stamped:false, reason:'not_terminal'}, got ` +
1065
+ `${JSON.stringify(result)}`);
1066
+ }
1067
+ },
1068
+ },
1069
+ {
1070
+ law: 'STAMP_RETURNS_NOT_THROWS_PREDICATES',
1071
+ name: `[${adapter.storeName}] an already-stamped record RETURNS, never throws`,
1072
+ run: async () => {
1073
+ const sealed = await stampedTerminal('already');
1074
+ const fresh = await adapter.store.get(sealed.id);
1075
+ const result = await stamp(fresh.id, { arm: 'complete' }, fresh.version);
1076
+ if (result.stamped !== false || result.reason !== 'already_stamped') {
1077
+ throw new Error(`expected a RETURNED {stamped:false, reason:'already_stamped'}, got ` +
1078
+ `${JSON.stringify(result)} — a predicate refusal is not an exceptional condition`);
1079
+ }
1080
+ },
1081
+ },
1082
+ {
1083
+ law: 'STAMP_BUMPS_VERSION_ONCE',
1084
+ name: `[${adapter.storeName}] a refused stamp does not bump version`,
1085
+ run: async () => {
1086
+ const sealed = await stampedTerminal('version');
1087
+ const before = await adapter.store.get(sealed.id);
1088
+ await stamp(before.id, { arm: 'complete' }, before.version);
1089
+ const after = await adapter.store.get(before.id);
1090
+ if (after.version !== before.version) {
1091
+ throw new Error(`a refused stamp moved version (${before.version} -> ${after.version}) — only a real ` +
1092
+ `write bumps it`);
1093
+ }
1094
+ },
1095
+ },
1096
+ {
1097
+ law: 'STAMP_REFUSES_ON_VERSION_MOVE',
1098
+ name: `[${adapter.storeName}] a stale expectedVersion THROWS STATE_SNAPSHOT_MISMATCH`,
1099
+ run: async () => {
1100
+ const sealed = await stampedTerminal('cas');
1101
+ let code;
1102
+ try {
1103
+ await stamp(sealed.id, { arm: 'complete' }, sealed.version + 99);
1104
+ }
1105
+ catch (err) {
1106
+ code = err.code;
1107
+ }
1108
+ if (code !== 'STATE_SNAPSHOT_MISMATCH') {
1109
+ throw new Error(`expected STATE_SNAPSHOT_MISMATCH on a version move, got '${String(code)}' — the ` +
1110
+ `sweep classified a record that has since changed, and must not write over it`);
1111
+ }
1112
+ },
1113
+ },
1114
+ {
1115
+ law: 'STAMP_IDEMPOTENT',
1116
+ name: `[${adapter.storeName}] re-stamping leaves the record byte-identical`,
1117
+ run: async () => {
1118
+ const sealed = await stampedTerminal('idem');
1119
+ const before = JSON.stringify(await adapter.store.get(sealed.id));
1120
+ await stamp(sealed.id, { arm: 'complete' }, sealed.version);
1121
+ await stamp(sealed.id, { arm: 'complete' }, sealed.version);
1122
+ const after = JSON.stringify(await adapter.store.get(sealed.id));
1123
+ if (after !== before) {
1124
+ throw new Error(`re-stamping changed the record:\n before ${before}\n after ${after}`);
1125
+ }
1126
+ },
1127
+ },
1128
+ ];
1129
+ }
497
1130
  function csPurityCases(_adapter) {
498
1131
  return [
499
1132
  {
@@ -1003,6 +1636,10 @@ function refusalSweepCases(adapter) {
1003
1636
  const terminal = await adapter.store.update({
1004
1637
  ...run,
1005
1638
  terminal_state: true,
1639
+ // issue #367: a terminal record names its arm. These fixtures mean "this run is over";
1640
+ // their opaque `tck` prose is deliberately unclassifiable, so the boundary's coherence
1641
+ // check abstains and the fixture stays a pure terminal-precondition seeder.
1642
+ sealed_by: { arm: 'complete' },
1006
1643
  terminal_reason: 'tck',
1007
1644
  });
1008
1645
  await expectRefusalUnchanged(run.id, { kind: 'settle_step', step: 'b', outcome: 'complete', claimToken: 'x', evidence: [] }, def, 'run_terminal', terminal.version, 'run_terminal');
@@ -1071,6 +1708,10 @@ function refusalSweepCases(adapter) {
1071
1708
  const terminal = await adapter.store.update({
1072
1709
  ...run,
1073
1710
  terminal_state: true,
1711
+ // issue #367: a terminal record names its arm. These fixtures mean "this run is over";
1712
+ // their opaque `tck` prose is deliberately unclassifiable, so the boundary's coherence
1713
+ // check abstains and the fixture stays a pure terminal-precondition seeder.
1714
+ sealed_by: { arm: 'complete' },
1074
1715
  terminal_reason: 'tck',
1075
1716
  });
1076
1717
  await expectRefusalUnchanged(run.id, {
@@ -1094,6 +1735,10 @@ function refusalSweepCases(adapter) {
1094
1735
  const seeded = await adapter.store.update({
1095
1736
  ...run,
1096
1737
  terminal_state: true,
1738
+ // issue #367: a terminal record names its arm. These fixtures mean "this run is over";
1739
+ // their opaque `tck` prose is deliberately unclassifiable, so the boundary's coherence
1740
+ // check abstains and the fixture stays a pure terminal-precondition seeder.
1741
+ sealed_by: { arm: 'complete' },
1097
1742
  terminal_reason: 'tck',
1098
1743
  finalizer_ledger: { fin: { status: 'completed', rank: 0 } },
1099
1744
  });
@@ -1113,6 +1758,10 @@ function refusalSweepCases(adapter) {
1113
1758
  await adapter.store.update({
1114
1759
  ...run,
1115
1760
  terminal_state: true,
1761
+ // issue #367: a terminal record names its arm. These fixtures mean "this run is over";
1762
+ // their opaque `tck` prose is deliberately unclassifiable, so the boundary's coherence
1763
+ // check abstains and the fixture stays a pure terminal-precondition seeder.
1764
+ sealed_by: { arm: 'complete' },
1116
1765
  terminal_reason: 'tck',
1117
1766
  finalizer_ledger: { fin: { status: 'pending', rank: 0 } },
1118
1767
  });
@@ -1135,6 +1784,10 @@ function refusalSweepCases(adapter) {
1135
1784
  const seeded = await adapter.store.update({
1136
1785
  ...run,
1137
1786
  terminal_state: true,
1787
+ // issue #367: a terminal record names its arm. These fixtures mean "this run is over";
1788
+ // their opaque `tck` prose is deliberately unclassifiable, so the boundary's coherence
1789
+ // check abstains and the fixture stays a pure terminal-precondition seeder.
1790
+ sealed_by: { arm: 'complete' },
1138
1791
  terminal_reason: 'tck',
1139
1792
  finalizer_ledger: {
1140
1793
  first: { status: 'pending', rank: 0 },
@@ -1162,6 +1815,10 @@ function refusalSweepCases(adapter) {
1162
1815
  await adapter.store.update({
1163
1816
  ...run,
1164
1817
  terminal_state: true,
1818
+ // issue #367: a terminal record names its arm. These fixtures mean "this run is over";
1819
+ // their opaque `tck` prose is deliberately unclassifiable, so the boundary's coherence
1820
+ // check abstains and the fixture stays a pure terminal-precondition seeder.
1821
+ sealed_by: { arm: 'complete' },
1165
1822
  terminal_reason: 'tck',
1166
1823
  finalizer_ledger: { fin: { status: 'pending', rank: 0 } },
1167
1824
  });
@@ -1184,6 +1841,10 @@ function refusalSweepCases(adapter) {
1184
1841
  const terminal = await adapter.store.update({
1185
1842
  ...run,
1186
1843
  terminal_state: true,
1844
+ // issue #367: a terminal record names its arm. These fixtures mean "this run is over";
1845
+ // their opaque `tck` prose is deliberately unclassifiable, so the boundary's coherence
1846
+ // check abstains and the fixture stays a pure terminal-precondition seeder.
1847
+ sealed_by: { arm: 'complete' },
1187
1848
  terminal_reason: 'tck',
1188
1849
  });
1189
1850
  await expectRefusalUnchanged(run.id, {
@@ -1208,6 +1869,10 @@ function refusalSweepCases(adapter) {
1208
1869
  await adapter.store.update({
1209
1870
  ...run,
1210
1871
  terminal_state: true,
1872
+ // issue #367: a terminal record names its arm. These fixtures mean "this run is over";
1873
+ // their opaque `tck` prose is deliberately unclassifiable, so the boundary's coherence
1874
+ // check abstains and the fixture stays a pure terminal-precondition seeder.
1875
+ sealed_by: { arm: 'complete' },
1211
1876
  terminal_reason: 'tck',
1212
1877
  finalizer_ledger: { fin: { status: 'pending', rank: 0 } },
1213
1878
  });
@@ -1239,6 +1904,10 @@ function refusalSweepCases(adapter) {
1239
1904
  const seeded = await adapter.store.update({
1240
1905
  ...run,
1241
1906
  terminal_state: true,
1907
+ // issue #367: a terminal record names its arm. These fixtures mean "this run is over";
1908
+ // their opaque `tck` prose is deliberately unclassifiable, so the boundary's coherence
1909
+ // check abstains and the fixture stays a pure terminal-precondition seeder.
1910
+ sealed_by: { arm: 'complete' },
1242
1911
  terminal_reason: 'tck',
1243
1912
  finalizer_ledger: { fin: { status: 'voided', rank: 0 } },
1244
1913
  });
@@ -1264,6 +1933,10 @@ function refusalSweepCases(adapter) {
1264
1933
  await adapter.store.update({
1265
1934
  ...run,
1266
1935
  terminal_state: true,
1936
+ // issue #367: a terminal record names its arm. These fixtures mean "this run is over";
1937
+ // their opaque `tck` prose is deliberately unclassifiable, so the boundary's coherence
1938
+ // check abstains and the fixture stays a pure terminal-precondition seeder.
1939
+ sealed_by: { arm: 'complete' },
1267
1940
  terminal_reason: 'tck',
1268
1941
  finalizer_ledger: { fin: { status: 'pending', rank: 0 } },
1269
1942
  });
@@ -1373,8 +2046,13 @@ function mintFreshCases(adapter) {
1373
2046
  // dropped, and the STILL-PENDING... there are none pending now (onFail was already
1374
2047
  // marked) — this fixture specifically exercises re-mint on a run with NO pending entries
1375
2048
  // left post-void, proving mintFresh re-arms fresh regardless of prior history.
2049
+ // issue #367: a real `applyResume` STRIPS `sealed_by` in the same write that flips the run
2050
+ // live — a live run carrying a seal is an orphan, and the store boundary refuses it. This
2051
+ // hand-authored fixture mirrors that, or it would stop mirroring the function it stands in
2052
+ // for.
2053
+ const { sealed_by: _voidSeal, ...markedBase } = marked.run;
1376
2054
  const postResumeVoid = await adapter.store.update({
1377
- ...marked.run,
2055
+ ...markedBase,
1378
2056
  terminal_state: false,
1379
2057
  in_progress_steps: [],
1380
2058
  failed_steps: [],
@@ -2000,6 +2678,10 @@ function gateMismatchCases(adapter) {
2000
2678
  ...run,
2001
2679
  completed_steps: ['a'],
2002
2680
  terminal_state: true,
2681
+ // issue #367: a terminal record names its arm. These fixtures mean "this run is over";
2682
+ // their opaque `tck` prose is deliberately unclassifiable, so the boundary's coherence
2683
+ // check abstains and the fixture stays a pure terminal-precondition seeder.
2684
+ sealed_by: { arm: 'complete' },
2003
2685
  terminal_reason: 'Workflow completed.',
2004
2686
  pending_gate: {
2005
2687
  gate_id: gateId,
@@ -2672,8 +3354,11 @@ function cwfsSecondEpochCases(adapter) {
2672
3354
  // 'onFail' — a FINALIZER, unresumable — stays in failed_steps (the M1 scenario's own
2673
3355
  // premise). terminal_state:false / in_progress_steps:[] / settled:{} mirror the
2674
3356
  // mintFreshCases precedent's own void shape.
3357
+ // issue #367: same as the mintFreshCases precedent — the seal fact leaves in the write
3358
+ // that flips the run live again.
3359
+ const { sealed_by: _voidSeal2, ...markedFailedBase } = markedFailed.run;
2675
3360
  const postResumeVoid = await adapter.store.update({
2676
- ...markedFailed.run,
3361
+ ...markedFailedBase,
2677
3362
  terminal_state: false,
2678
3363
  in_progress_steps: [],
2679
3364
  failed_steps: ['onFail'],
@@ -3227,6 +3912,26 @@ function expireDefaultResolveCases(adapter) {
3227
3912
  */
3228
3913
  export function settlementContract(adapter) {
3229
3914
  if (adapter.store.settleStep === undefined) {
3915
+ // A store that declares NEITHER verb genuinely has nothing here to conform to — the
3916
+ // established optional-capability idiom, and a real vacuous pass.
3917
+ //
3918
+ // But a store declaring `stampSeal` WITHOUT `settleStep` would also have received zero cases,
3919
+ // so its stamp and seal-integrity conformance — every law in this file that binds it — would
3920
+ // report green while never running. That is a wiring gap wearing a vacuous pass's clothes.
3921
+ if (adapter.store.stampSeal !== undefined) {
3922
+ return [
3923
+ {
3924
+ law: 'ADAPTER_WIRING',
3925
+ name: `[${adapter.storeName}] declares RunStore.stampSeal but NOT settleStep — every law in this suite would pass without running`,
3926
+ run: async () => {
3927
+ throw new Error(`[${adapter.storeName}] settlementContract: the store declares stampSeal but not ` +
3928
+ 'settleStep, and this suite is gated on settleStep — so it would return zero ' +
3929
+ 'cases and report green having tested nothing. Declare settleStep, or run the ' +
3930
+ 'stamp laws through a suite that binds them on their own.');
3931
+ },
3932
+ },
3933
+ ];
3934
+ }
3230
3935
  return [];
3231
3936
  }
3232
3937
  if (adapter.settlementFixture === undefined) {
@@ -3251,6 +3956,8 @@ export function settlementContract(adapter) {
3251
3956
  ...drainMarkDedupCases(adapter),
3252
3957
  ...terminalRefusalCases(adapter),
3253
3958
  ...terminalStateOnlyCases(adapter),
3959
+ ...sealIntegrityCases(adapter),
3960
+ ...stampSealCases(adapter),
3254
3961
  ...csPurityCases(adapter),
3255
3962
  ...neverDowngradeCases(adapter),
3256
3963
  ...settleOutcomeIntegrityCases(adapter),