@dzhechkov/harness-core 0.8.44 → 0.8.46

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.
Files changed (47) hide show
  1. package/.dz-manifest.json +76 -36
  2. package/README.md +54 -6
  3. package/dist/apply-leg.d.ts +1 -1
  4. package/dist/apply-leg.d.ts.map +1 -1
  5. package/dist/apply-leg.js +22 -7
  6. package/dist/apply-leg.js.map +1 -1
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +4 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/lesson-payoff.d.ts.map +1 -1
  12. package/dist/lesson-payoff.js +3 -1
  13. package/dist/lesson-payoff.js.map +1 -1
  14. package/dist/pack-inventory.d.ts +13 -0
  15. package/dist/pack-inventory.d.ts.map +1 -1
  16. package/dist/pack-inventory.js +22 -1
  17. package/dist/pack-inventory.js.map +1 -1
  18. package/dist/round-exec-claim.d.ts +36 -0
  19. package/dist/round-exec-claim.d.ts.map +1 -0
  20. package/dist/round-exec-claim.js +17 -0
  21. package/dist/round-exec-claim.js.map +1 -0
  22. package/dist/session-retro.d.ts +33 -0
  23. package/dist/session-retro.d.ts.map +1 -1
  24. package/dist/session-retro.js +170 -34
  25. package/dist/session-retro.js.map +1 -1
  26. package/dist/setup.d.ts.map +1 -1
  27. package/dist/setup.js +3 -2
  28. package/dist/setup.js.map +1 -1
  29. package/dist/sign.d.ts +44 -0
  30. package/dist/sign.d.ts.map +1 -1
  31. package/dist/sign.js +126 -1
  32. package/dist/sign.js.map +1 -1
  33. package/dist/stamped-path.d.ts +15 -0
  34. package/dist/stamped-path.d.ts.map +1 -0
  35. package/dist/stamped-path.js +32 -0
  36. package/dist/stamped-path.js.map +1 -0
  37. package/package.json +2 -2
  38. package/sbom.json +135 -35
  39. package/src/apply-leg.ts +22 -7
  40. package/src/index.ts +5 -0
  41. package/src/lesson-payoff.ts +3 -1
  42. package/src/pack-inventory.ts +21 -1
  43. package/src/round-exec-claim.ts +43 -0
  44. package/src/session-retro.ts +153 -31
  45. package/src/setup.ts +3 -2
  46. package/src/sign.ts +136 -1
  47. package/src/stamped-path.ts +41 -0
package/sbom.json CHANGED
@@ -25,7 +25,7 @@
25
25
  "hashes": [
26
26
  {
27
27
  "alg": "SHA-256",
28
- "content": "f80660a868b436e0ae7b3a23d5b104a8e882bf9f9fc7bfc22f7c0023ec76f2d6"
28
+ "content": "9f7d650284101fe8b3bd2f4a9dd0de795bd5a0baedfa98b36fbfbc77ccd296d1"
29
29
  }
30
30
  ]
31
31
  },
@@ -315,7 +315,7 @@
315
315
  "hashes": [
316
316
  {
317
317
  "alg": "SHA-256",
318
- "content": "5ee09fa3e1ac4271b420a93d0972b3dd3844ced3757c60dcb1da95015f68554a"
318
+ "content": "bb74bc4c0bc8ef6b41bd6ad0c8fb51c8da5b3fe228170374f0f85aa3ebdc5004"
319
319
  }
320
320
  ]
321
321
  },
@@ -325,7 +325,7 @@
325
325
  "hashes": [
326
326
  {
327
327
  "alg": "SHA-256",
328
- "content": "126a8beddf9f7bf8a8056dc67d63d3c335fc89c993bb0478ac30a4ddd5c8e21e"
328
+ "content": "df06c273af29833e2c7f5ff7d0f7abd31f130155d912b41a3890b185a619bc61"
329
329
  }
330
330
  ]
331
331
  },
@@ -335,7 +335,7 @@
335
335
  "hashes": [
336
336
  {
337
337
  "alg": "SHA-256",
338
- "content": "1095a9a8b66ba60e64123e28eb47873079e015c82b61d211acf02b394fe6006b"
338
+ "content": "1480a03f6202aeb1b8ed6caa26d129159c46108b60d5e76cc2946252f1093098"
339
339
  }
340
340
  ]
341
341
  },
@@ -345,7 +345,7 @@
345
345
  "hashes": [
346
346
  {
347
347
  "alg": "SHA-256",
348
- "content": "10cdd827f805919af93aefc4876da422b84fbb3a30a686627b74a908a9332410"
348
+ "content": "942d14725e1aec75af18822d65378a2d02d3601383b6a4d887bf8642b366f7d9"
349
349
  }
350
350
  ]
351
351
  },
@@ -2915,7 +2915,7 @@
2915
2915
  "hashes": [
2916
2916
  {
2917
2917
  "alg": "SHA-256",
2918
- "content": "c18f5b65c615b023a3c3f576b2619692bf9a2862f44ba8acb06b5a4e025077e0"
2918
+ "content": "c4ceaf00e0ded6d5e802b8867871bd8bbf25029a6a93895f4086313d5b2175c2"
2919
2919
  }
2920
2920
  ]
2921
2921
  },
@@ -2925,7 +2925,7 @@
2925
2925
  "hashes": [
2926
2926
  {
2927
2927
  "alg": "SHA-256",
2928
- "content": "ddd45dd0f6cdd8c83fe98a3a78ccfb85da04b2c9df1e52973a65f2526900d418"
2928
+ "content": "911eee2810a4dbb9f28e6bff26a274d198cff03a2d3e28b47753f300014419e6"
2929
2929
  }
2930
2930
  ]
2931
2931
  },
@@ -2935,7 +2935,7 @@
2935
2935
  "hashes": [
2936
2936
  {
2937
2937
  "alg": "SHA-256",
2938
- "content": "1ff621602e84ea254c7c865fd8420ced42570345c8e099ee098e5d25f5c5a9db"
2938
+ "content": "2c48457eb75559dd7f9ef76c82a67b09bacde134511ba2f537920f39a29299d2"
2939
2939
  }
2940
2940
  ]
2941
2941
  },
@@ -2945,7 +2945,7 @@
2945
2945
  "hashes": [
2946
2946
  {
2947
2947
  "alg": "SHA-256",
2948
- "content": "a7879e54955ef542d3cda7884d3eac9a362f3f2c2b1dbc8d03f190e479df47c3"
2948
+ "content": "d9179260cecee61509eb746b4ac88ed9e6e8947197db4b52c1773ffa9d41564a"
2949
2949
  }
2950
2950
  ]
2951
2951
  },
@@ -3405,7 +3405,7 @@
3405
3405
  "hashes": [
3406
3406
  {
3407
3407
  "alg": "SHA-256",
3408
- "content": "b4a01fd697d785c473d9b66689c8d140211fc09f8379ca7fa32e41fbd36ca940"
3408
+ "content": "e8dcc3bd0e0b7dca766b9ed8e77c3b33d349349b809ee625890f327aa62ebc77"
3409
3409
  }
3410
3410
  ]
3411
3411
  },
@@ -3415,7 +3415,7 @@
3415
3415
  "hashes": [
3416
3416
  {
3417
3417
  "alg": "SHA-256",
3418
- "content": "87ad202b5bb3e1f6295d18a997201c1d2303343b42953843d852d2b83f9451e8"
3418
+ "content": "d1ab2c60cc400cf9be5e2d6352eae67d9a3142808fd741ca6103a83b35afb655"
3419
3419
  }
3420
3420
  ]
3421
3421
  },
@@ -3425,7 +3425,7 @@
3425
3425
  "hashes": [
3426
3426
  {
3427
3427
  "alg": "SHA-256",
3428
- "content": "b7ef829dc678254bacf001e1ffe968b1619cb0000179362d159d46362a451b8d"
3428
+ "content": "dd03c47fb197c268f28a39bd65ce0c571ae7ffb546edef88d1dc460654f174cc"
3429
3429
  }
3430
3430
  ]
3431
3431
  },
@@ -4195,7 +4195,7 @@
4195
4195
  "hashes": [
4196
4196
  {
4197
4197
  "alg": "SHA-256",
4198
- "content": "02ca8a9c3e379fd9f53f534f69fc4f64ee55db3fda0b4ea9689f060ccbf438b8"
4198
+ "content": "7dbbcb06f5b075cb2bf7d1eec8d1c3fe3312a39bfcaaf26ff8ae7daa4943c4fb"
4199
4199
  }
4200
4200
  ]
4201
4201
  },
@@ -4205,7 +4205,7 @@
4205
4205
  "hashes": [
4206
4206
  {
4207
4207
  "alg": "SHA-256",
4208
- "content": "be6e65fe1cb9f2af632fe0f30cfb06ebcce873c06a794cea8df6a549f206e182"
4208
+ "content": "f952a2cc0939988c7c388de4c8e206932cb92641c02e4666ec226e3123d5b9c1"
4209
4209
  }
4210
4210
  ]
4211
4211
  },
@@ -4215,7 +4215,7 @@
4215
4215
  "hashes": [
4216
4216
  {
4217
4217
  "alg": "SHA-256",
4218
- "content": "dc633ef207f8cbd90f1d9961bb8696c5cf63072ead09dd48929923ccfe3f0184"
4218
+ "content": "c19eb4375e6d42f4f732bcffc2925735c3c6a7270fe1cca4c5dc1075e7311de2"
4219
4219
  }
4220
4220
  ]
4221
4221
  },
@@ -4225,7 +4225,7 @@
4225
4225
  "hashes": [
4226
4226
  {
4227
4227
  "alg": "SHA-256",
4228
- "content": "f0e121375dd12272ed7c9c5f61dc2d6df7fb19b9e6756de19820dbc460f86204"
4228
+ "content": "1fb4a012594e28a604f2240ea091dc3b6a0b5f705ed485f0e36f6be8c196d856"
4229
4229
  }
4230
4230
  ]
4231
4231
  },
@@ -5669,6 +5669,46 @@
5669
5669
  }
5670
5670
  ]
5671
5671
  },
5672
+ {
5673
+ "type": "file",
5674
+ "name": "dist/round-exec-claim.d.ts",
5675
+ "hashes": [
5676
+ {
5677
+ "alg": "SHA-256",
5678
+ "content": "5a2b0cb0f7f6e63afd415313f196c83b58ba235b84c0abe38e5bb0077a24e2ab"
5679
+ }
5680
+ ]
5681
+ },
5682
+ {
5683
+ "type": "file",
5684
+ "name": "dist/round-exec-claim.d.ts.map",
5685
+ "hashes": [
5686
+ {
5687
+ "alg": "SHA-256",
5688
+ "content": "96f400965d180c6f388ffa9e47bf1836f6081bcb6f5c26188e15e067eb1960be"
5689
+ }
5690
+ ]
5691
+ },
5692
+ {
5693
+ "type": "file",
5694
+ "name": "dist/round-exec-claim.js",
5695
+ "hashes": [
5696
+ {
5697
+ "alg": "SHA-256",
5698
+ "content": "c71a6d227665151d7ab3de4be94174b63cb4589b0029759ea324a77de8f6d022"
5699
+ }
5700
+ ]
5701
+ },
5702
+ {
5703
+ "type": "file",
5704
+ "name": "dist/round-exec-claim.js.map",
5705
+ "hashes": [
5706
+ {
5707
+ "alg": "SHA-256",
5708
+ "content": "1c57b9f563db38679572d1ddbc9aa33a25f6100e975ddc683c35f87ccfc094cd"
5709
+ }
5710
+ ]
5711
+ },
5672
5712
  {
5673
5713
  "type": "file",
5674
5714
  "name": "dist/round-exec.d.ts",
@@ -6035,7 +6075,7 @@
6035
6075
  "hashes": [
6036
6076
  {
6037
6077
  "alg": "SHA-256",
6038
- "content": "ae652668bcff0fd5846e63fda6f1ffb38155459e1ad563d549ab72f4f1f5d32d"
6078
+ "content": "48a74602b20dd51467ab48a09886e876bee6821dfa89255d0100e5147595d744"
6039
6079
  }
6040
6080
  ]
6041
6081
  },
@@ -6045,7 +6085,7 @@
6045
6085
  "hashes": [
6046
6086
  {
6047
6087
  "alg": "SHA-256",
6048
- "content": "6119e91d2af673e5583e0f6ad117af62fd92799f6d8f671332e134f978f7ffe0"
6088
+ "content": "d2f2685f09be7823be59ffdb3bb24efef865a62987a638c029f4c3452f061a09"
6049
6089
  }
6050
6090
  ]
6051
6091
  },
@@ -6055,7 +6095,7 @@
6055
6095
  "hashes": [
6056
6096
  {
6057
6097
  "alg": "SHA-256",
6058
- "content": "878a9dd6db7d369edaf7acb1ae6e00dd994ece247e6123980cd4dba0d7c54949"
6098
+ "content": "5d869d3a224d52964b296d9224933f6ecb2f4edf573a946888d9166f1489ce92"
6059
6099
  }
6060
6100
  ]
6061
6101
  },
@@ -6065,7 +6105,7 @@
6065
6105
  "hashes": [
6066
6106
  {
6067
6107
  "alg": "SHA-256",
6068
- "content": "6eb19cab2cac968a063a7615e7fccb2a9ca4ac1b3fcf16bfd0f4c0c1506f1f5e"
6108
+ "content": "f4744745a940e18d9591cbfcafc81cbc029c31e0c450918a4957fbdc87ba2070"
6069
6109
  }
6070
6110
  ]
6071
6111
  },
@@ -6085,7 +6125,7 @@
6085
6125
  "hashes": [
6086
6126
  {
6087
6127
  "alg": "SHA-256",
6088
- "content": "3b8f5dc584a26b53be9f1e68cb8fffaaf68decbe1cf4b634f78c8ec0f3725b3d"
6128
+ "content": "3ecf9110097001cba535095de5a78cd9ca2703cfed15d921f368673b08ef6374"
6089
6129
  }
6090
6130
  ]
6091
6131
  },
@@ -6095,7 +6135,7 @@
6095
6135
  "hashes": [
6096
6136
  {
6097
6137
  "alg": "SHA-256",
6098
- "content": "8644f836c6236867d8f238cf48465506361647f11bc58b50134a7a862b7e711a"
6138
+ "content": "ffab1105dd844f20d5f8a6c385901f9bb6c714148a2f61a4e51afab0254021b2"
6099
6139
  }
6100
6140
  ]
6101
6141
  },
@@ -6105,7 +6145,7 @@
6105
6145
  "hashes": [
6106
6146
  {
6107
6147
  "alg": "SHA-256",
6108
- "content": "3bafd2f8eb1c3305be28c42d59365dc58751d199b5e702b69c581c5df7b97560"
6148
+ "content": "363fa3f4e716a6e450a49f9f11b9c871baad68b30c825f3cdd4b27f471731572"
6109
6149
  }
6110
6150
  ]
6111
6151
  },
@@ -6155,7 +6195,7 @@
6155
6195
  "hashes": [
6156
6196
  {
6157
6197
  "alg": "SHA-256",
6158
- "content": "22608f3928716385fdb0cb53c7d64ef585ed4326060352af4f849a1d1621c66e"
6198
+ "content": "8f63cf60067ec33ec3bdd86be8ea860ae6db442edd32fcabdba2a9f9c876a21d"
6159
6199
  }
6160
6200
  ]
6161
6201
  },
@@ -6165,7 +6205,7 @@
6165
6205
  "hashes": [
6166
6206
  {
6167
6207
  "alg": "SHA-256",
6168
- "content": "565f680d49303935f12cf2b4bcb9c465320d259ac38cada8e5256b45b4bf8119"
6208
+ "content": "375655033485f9b4cbdaca4c371581cfaa3a0b757d9d0d4309d75ecbc1c9a5e3"
6169
6209
  }
6170
6210
  ]
6171
6211
  },
@@ -6175,7 +6215,7 @@
6175
6215
  "hashes": [
6176
6216
  {
6177
6217
  "alg": "SHA-256",
6178
- "content": "b6712fc5a3e867a22c740ad05f782dba4c84267e0b74e3dd0bf796dd623e32e5"
6218
+ "content": "0938e8399eb90c45520d1a544d3f7e741a9569f7e42d6cd36e2569f831f010d7"
6179
6219
  }
6180
6220
  ]
6181
6221
  },
@@ -6185,7 +6225,7 @@
6185
6225
  "hashes": [
6186
6226
  {
6187
6227
  "alg": "SHA-256",
6188
- "content": "059760d62fa63c6f45e47a31d17f3bedd926ef2076c7ac1a7f7e687021cf7f13"
6228
+ "content": "bfc1acdb5b4b3dec2f6fe4eb2fe27e4133c574f693d3a34a1ae2f7cc26730262"
6189
6229
  }
6190
6230
  ]
6191
6231
  },
@@ -6549,6 +6589,46 @@
6549
6589
  }
6550
6590
  ]
6551
6591
  },
6592
+ {
6593
+ "type": "file",
6594
+ "name": "dist/stamped-path.d.ts",
6595
+ "hashes": [
6596
+ {
6597
+ "alg": "SHA-256",
6598
+ "content": "2bab16b2083b6771343aaf6e674661cd8b7e00851933caa7abb0b5129757851f"
6599
+ }
6600
+ ]
6601
+ },
6602
+ {
6603
+ "type": "file",
6604
+ "name": "dist/stamped-path.d.ts.map",
6605
+ "hashes": [
6606
+ {
6607
+ "alg": "SHA-256",
6608
+ "content": "0cae7683e286b6028c3fa170386609ec8b84f4a2b71ad159f0285fc1402bd6a3"
6609
+ }
6610
+ ]
6611
+ },
6612
+ {
6613
+ "type": "file",
6614
+ "name": "dist/stamped-path.js",
6615
+ "hashes": [
6616
+ {
6617
+ "alg": "SHA-256",
6618
+ "content": "95f90efd72d5068247b778cc2b740562b263ba3ae9bd8793811d9b58d02524c4"
6619
+ }
6620
+ ]
6621
+ },
6622
+ {
6623
+ "type": "file",
6624
+ "name": "dist/stamped-path.js.map",
6625
+ "hashes": [
6626
+ {
6627
+ "alg": "SHA-256",
6628
+ "content": "862c33d8a76abc76ef47987b1ec499ef46d73af109205c1cb0be3aeb75fe6350"
6629
+ }
6630
+ ]
6631
+ },
6552
6632
  {
6553
6633
  "type": "file",
6554
6634
  "name": "dist/statusline.d.ts",
@@ -7679,7 +7759,7 @@
7679
7759
  },
7680
7760
  {
7681
7761
  "name": "dz:canonical-json-sha256-v2",
7682
- "value": "0b881348f6c4b203b10ec7331e8cb5e0a44f6b4fd661e9340cba4e60a7042edb"
7762
+ "value": "567d35d6959a019cb066d731a8b2c691c86b8a8853561af4b4af583b1a1391c6"
7683
7763
  }
7684
7764
  ]
7685
7765
  },
@@ -7759,7 +7839,7 @@
7759
7839
  "hashes": [
7760
7840
  {
7761
7841
  "alg": "SHA-256",
7762
- "content": "c130c9ce4f4829ee4c494a1863574d407cec61f63309dea0454a6ee2794476e6"
7842
+ "content": "ee116c44eb3d1793e369bb684f2ff020957f351f1c2de1351e736de45b5dcab5"
7763
7843
  }
7764
7844
  ]
7765
7845
  },
@@ -8419,7 +8499,7 @@
8419
8499
  "hashes": [
8420
8500
  {
8421
8501
  "alg": "SHA-256",
8422
- "content": "ecbd56d2a04ff6a7fda5bff402bfd7f978db91a1b9559b33e5dfbe20da92390f"
8502
+ "content": "6708ea6071d81233d17a132024ecc34059e954c9bfb19af7222d2d839b200d39"
8423
8503
  }
8424
8504
  ]
8425
8505
  },
@@ -8539,7 +8619,7 @@
8539
8619
  "hashes": [
8540
8620
  {
8541
8621
  "alg": "SHA-256",
8542
- "content": "bec4f95a59f3f1bcdafaddcc10135581ff2828f6f0b4eac02559e77f339111dd"
8622
+ "content": "a3754701f00eba282d95ed944cdf0e88b2018bdd0713a305e220759f0f0b1202"
8543
8623
  }
8544
8624
  ]
8545
8625
  },
@@ -8739,7 +8819,7 @@
8739
8819
  "hashes": [
8740
8820
  {
8741
8821
  "alg": "SHA-256",
8742
- "content": "27aad0694431ab6f7261f4f33a2b3c8500662d555c4d6c6f0700c4979dce23b5"
8822
+ "content": "c731312962d631f985414a44111ee51ec4afc56f1dabb3adc088fd43b55f2620"
8743
8823
  }
8744
8824
  ]
8745
8825
  },
@@ -9103,6 +9183,16 @@
9103
9183
  }
9104
9184
  ]
9105
9185
  },
9186
+ {
9187
+ "type": "file",
9188
+ "name": "src/round-exec-claim.ts",
9189
+ "hashes": [
9190
+ {
9191
+ "alg": "SHA-256",
9192
+ "content": "349ed3905cc29230a0cb67a2c0c2c10948aebb6c8f5d3c9c3ad59ec1f862e23b"
9193
+ }
9194
+ ]
9195
+ },
9106
9196
  {
9107
9197
  "type": "file",
9108
9198
  "name": "src/round-exec.ts",
@@ -9199,7 +9289,7 @@
9199
9289
  "hashes": [
9200
9290
  {
9201
9291
  "alg": "SHA-256",
9202
- "content": "191c5a88e35cb4d1e142cd1a60faa1f397c6a49b5e7d24690a5ea61d722c73de"
9292
+ "content": "9c27223147bed72c41c80723a87123662200b72700a05f44714aaaa1d7fa5dc5"
9203
9293
  }
9204
9294
  ]
9205
9295
  },
@@ -9209,7 +9299,7 @@
9209
9299
  "hashes": [
9210
9300
  {
9211
9301
  "alg": "SHA-256",
9212
- "content": "0f44ef0d05c0ffabffe5e0f4fb4539b67fd72f62b42206d68bc98199ea718dcd"
9302
+ "content": "9041565e0c4f79a66edd8fed57e5e75029d1bd0c9b276fc86cbd2718b2fec6d8"
9213
9303
  }
9214
9304
  ]
9215
9305
  },
@@ -9229,7 +9319,7 @@
9229
9319
  "hashes": [
9230
9320
  {
9231
9321
  "alg": "SHA-256",
9232
- "content": "02772313e7c6fd4e7f5d2354fd11eb4234baba7a64e9972d0b4136be9bd98ae6"
9322
+ "content": "c9b2ee366049793eb25e93cbcfce1037d8c24c19ddc24a3dc6e496ee8cac6fe9"
9233
9323
  }
9234
9324
  ]
9235
9325
  },
@@ -9333,6 +9423,16 @@
9333
9423
  }
9334
9424
  ]
9335
9425
  },
9426
+ {
9427
+ "type": "file",
9428
+ "name": "src/stamped-path.ts",
9429
+ "hashes": [
9430
+ {
9431
+ "alg": "SHA-256",
9432
+ "content": "bfb3729e414745a2d40694a244baedc56c4c2eaa6f42287e77b968d211bc54ed"
9433
+ }
9434
+ ]
9435
+ },
9336
9436
  {
9337
9437
  "type": "file",
9338
9438
  "name": "src/statusline.ts",
package/src/apply-leg.ts CHANGED
@@ -209,7 +209,7 @@ export function probeRecallEngine(socketPath: string, timeoutMs = 1000): Promise
209
209
  * either failure mode degrades to the SAME honest `cosine-fallback` reply shape as every other
210
210
  * failure, never a bare protocol `{error}`.
211
211
  */
212
- export const APPLY_LEG_VERSION = 12;
212
+ export const APPLY_LEG_VERSION = 13;
213
213
 
214
214
  /**
215
215
  * Parse the `dz-apply-leg-version` stamp from a deployed helper file. Unlike
@@ -442,19 +442,34 @@ async function loadPolicy() {
442
442
 
443
443
  /**
444
444
  * Retro admission-debt directive (feature narrated-error-must-be-taught, ADR-001 D2/D3). The Stop
445
- * hook's scan-tail arms \`.dz/retro-pending.json\` when the assistant admitted an error without a
446
- * teach; THIS hook confronts the assistant on the very next prompt. Contract:
445
+ * hook's scan-tail arms \`.dz/retro/<session>/pending.json\` when the assistant admitted an error
446
+ * without a teach; THIS hook confronts the assistant on the very next prompt. Contract:
447
+ * - no string session_id in the payload ⇒ '' BEFORE any fs call (the hook guesses no session);
447
448
  * - sentinel absent ⇒ '' and ZERO extra work beyond one existsSync (the no-noise pin — output
448
449
  * stays byte-identical to a build without this feature);
449
450
  * - stale / other-session sentinel ⇒ '' (the debt belongs to a dead session; retro collected it);
450
451
  * - core module absent or old (no directive exports) ⇒ '' — inert, NEVER-BLOCK.
451
452
  */
452
453
  // AM-7 (fix round 1): SESSION_ROOT, not PROJECT — the sentinel is a per-session artifact (see the
453
- // SESSION_ROOT comment above).
454
- const RETRO_PENDING = path.join(SESSION_ROOT, '.dz', 'retro-pending.json');
454
+ // SESSION_ROOT comment above). retro-debt-sentinel-per-session: per-session by PATH now too — the
455
+ // root is the session's, and the file is under \`.dz/retro/<safeId>/\`, so two sessions in one
456
+ // worktree can no longer read (or clear) each other's debt through one shared flat file.
457
+ // TWIN of safeSessionDirName in session-retro.ts — duplicated inline because this hook must stay
458
+ // dependency-free; pinned to equal outputs by test retro-sentinel-per-session.test.ts (AC-6/twin).
459
+ const SAFE_SESSION_ID_RE = /^[A-Za-z0-9._-]{1,128}$/;
460
+ function safeSessionDirName(sessionId) {
461
+ if (SAFE_SESSION_ID_RE.test(sessionId) && sessionId !== '.' && sessionId !== '..') return sessionId;
462
+ return crypto.createHash('sha256').update(sessionId).digest('hex').slice(0, 32);
463
+ }
464
+ function retroPendingPathFor(sessionId) {
465
+ return path.join(SESSION_ROOT, '.dz', 'retro', safeSessionDirName(sessionId), 'pending.json');
466
+ }
455
467
  async function retroDebtDirective(payload) {
456
- if (!fs.existsSync(RETRO_PENDING)) return '';
457
- const sentinel = safe(() => JSON.parse(fs.readFileSync(RETRO_PENDING, 'utf-8')), undefined);
468
+ const sessionId = payload && typeof payload.session_id === 'string' ? payload.session_id : '';
469
+ if (sessionId === '') return '';
470
+ const pendingPath = retroPendingPathFor(sessionId);
471
+ if (!fs.existsSync(pendingPath)) return '';
472
+ const sentinel = safe(() => JSON.parse(fs.readFileSync(pendingPath, 'utf-8')), undefined);
458
473
  if (!sentinel || typeof sentinel.snippet !== 'string' || sentinel.snippet === '') return '';
459
474
  const mod = await loadCoreModule(
460
475
  'session-retro.js',
package/src/index.ts CHANGED
@@ -137,6 +137,7 @@ export { planLedgerBackfill, LEDGER_FILL_SOURCE, AMBIGUOUS, resolveLedgerRunId }
137
137
  export { projectSkillsOneRoot, projectSkillsProbeCommand } from './project-skills-root.js';
138
138
  export { isRepoBoundary } from './repo-boundary.js';
139
139
  export type { RepoBoundaryIo } from './repo-boundary.js';
140
+ export { uniqueStampedPath, writeUniqueStampedFile } from './stamped-path.js';
140
141
  export type { LedgerBackfillPlan, LedgerBackfillRow, RunCostFacts } from './ledger-backfill.js';
141
142
  export type { SweepResult, DriftedSkill, SyncResult, SyncCanonicalOptions, CanonicalDefect, CanonicalDefectKind } from './skill-drift.js';
142
143
  export { benchmarkSkill, benchmarkSkills, compareSkills } from './benchmark.js';
@@ -1367,6 +1368,10 @@ export { parseQeBridgeStdoutCost } from './review-cost.js';
1367
1368
  export type { QeBridgeCost, QeBridgeCostTokens } from './review-cost.js';
1368
1369
  export { parseCodexTokens, classifyRoundExecOutcome, buildRoundExecRow } from './round-exec.js';
1369
1370
  export type { RoundExecOutcome, RoundExecLedgerRow } from './round-exec.js';
1371
+ // round-exec-claim-takeover FR-1: the pure verdict `dz round exec` consults before refusing a
1372
+ // standing exec claim (held / stale-dead / unknown); the pid probe itself stays in run-registry.
1373
+ export { decideExecClaimTakeover } from './round-exec-claim.js';
1374
+ export type { ExecClaimVerdict, ExecClaimTakeoverInput } from './round-exec-claim.js';
1370
1375
  export * from './run-cleanup.js';
1371
1376
 
1372
1377
  export * from './cross-family-control.js';
@@ -38,6 +38,7 @@ import { join } from 'node:path';
38
38
 
39
39
  import { LessonBandit, type ArmStats, type SerializedBanditState } from './lesson-bandit.js';
40
40
  import { withProjectLockSync } from './named-lock.js';
41
+ import { uniqueStampedPath } from './stamped-path.js';
41
42
  import { readMemoryLearningConfig, type MemoryLearningConfig } from './patterns.js';
42
43
 
43
44
  // ── Paths and constants ─────────────────────────────────────────────────────────────────────────
@@ -383,7 +384,8 @@ function mutateBanditState(
383
384
  // Preserve the evidence before overwriting it — never delete, never "repair". This is the
384
385
  // ONE place a corrupt file is touched, and it is under the lock.
385
386
  const target = banditStatePath(projectRoot);
386
- try { renameSync(target, `${target}.corrupt-${new Date().toISOString().replace(/[:.]/g, '-')}`); } catch { /* best-effort */ }
387
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
388
+ try { renameSync(target, uniqueStampedPath(`${target}.corrupt-`, stamp, existsSync)); } catch { /* best-effort */ }
387
389
  logBanditEvent(projectRoot, { event: 'bandit-error', ts: new Date().toISOString(), op, reason });
388
390
  }
389
391
  const bandit = LessonBandit.deserialize(state.bandit);
@@ -36,6 +36,26 @@ export interface PublishSecretSeams {
36
36
  readonly tgzPathFor?: (dir: string) => string | undefined;
37
37
  }
38
38
 
39
+ /**
40
+ * Share of NUL bytes above which a sample is binary. MEASURED 2026-09-21 (backlog d3841a3b): one NUL in
41
+ * 19 628 bytes of TypeScript made the old `includes(0)` sniff skip a packed TEXT file from the no-secrets scan.
42
+ */
43
+ export const NUL_RATIO = 0.01;
44
+
45
+ /**
46
+ * PURE binary sniff over the first bytes of a file: `true` iff NUL bytes exceed `NUL_RATIO` of the sample length.
47
+ * Empty ⇒ text. ONLY NULs count (fix round 1, F1): the rule this replaces skipped on a NUL and nothing else, so a
48
+ * criterion over other control bytes or UTF-8 validity would SKIP files the old rule SCANNED — a latin-1 source with
49
+ * a secret, a text with \x01 separators — and that is a fail-open regression. One NUL in 8 KiB is text and gets
50
+ * scanned; UTF-16 (about half NULs) is binary as before; a PNG with few NULs is scanned — harmless, the scan is read-only.
51
+ */
52
+ export function looksBinarySample(sample: Buffer): boolean {
53
+ if (sample.length === 0) return false;
54
+ let nuls = 0;
55
+ for (const byte of sample) if (byte === 0) nuls++;
56
+ return nuls > NUL_RATIO * sample.length;
57
+ }
58
+
39
59
  /** Scan the publish inventory as streams, retaining every coverage gap by name. */
40
60
  export function gatherPublishSecretFacts(root: string, packageDirs: readonly string[], seams: PublishSecretSeams = {}):
41
61
  { readonly secretFindings: readonly { label: string; name: string }[];
@@ -74,7 +94,7 @@ export function gatherPublishSecretFacts(root: string, packageDirs: readonly str
74
94
  let first = true;
75
95
  try {
76
96
  for (const chunk of reader(absolute, 1024 * 1024)) {
77
- if (first && chunk.subarray(0, 8 * 1024).includes(0)) { binary = true; return; }
97
+ if (first && looksBinarySample(chunk.subarray(0, 8 * 1024))) { binary = true; return; }
78
98
  first = false;
79
99
  yield chunk;
80
100
  }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * round-exec-claim-takeover (FR-1): the pure verdict behind `dz round exec`'s claim transaction.
3
+ *
4
+ * A round claimed by an `exec` whose process died (SIGTERM, OOM, reboot) used to refuse every later
5
+ * `exec` forever (backlog 280e914607397474). This decides — from facts the caller measured under the
6
+ * state lock — whether the standing claim is held, provably stale-dead, or unknowable:
7
+ *
8
+ * - `pidAlive === true` ⇒ `held`, regardless of age (a live owner is never taken over);
9
+ * - `pidAlive === null` ⇒ `unknown` (the probe was inconclusive — refusal is the safe side);
10
+ * - `pid` not a positive safe integer ⇒ `unknown` («no pid recorded») — nothing to probe;
11
+ * - `execClaimedAt` unparsable ⇒ `unknown` («claim time unreadable») — no age to debounce on;
12
+ * - `pidAlive === false` and age ≥ `staleMinutes` ⇒ `stale-dead` with the floored age;
13
+ * - `pidAlive === false` but younger ⇒ `held` — a just-died claim may be a restart in flight, and
14
+ * the threshold is the debounce.
15
+ *
16
+ * Pure: no clock, no process table — `now` and `pidAlive` are inputs so the caller keeps the
17
+ * critical section short (`process.kill(pid, 0)`, NFR-1) and tests need no real processes.
18
+ */
19
+ export type ExecClaimVerdict =
20
+ | { kind: 'held' }
21
+ | { kind: 'stale-dead'; ageMinutes: number }
22
+ | { kind: 'unknown'; reason: string };
23
+
24
+ export type ExecClaimTakeoverInput = {
25
+ readonly execClaimedAt?: string | undefined;
26
+ readonly pid?: number | undefined;
27
+ readonly pidAlive: boolean | null;
28
+ readonly now: number;
29
+ readonly staleMinutes: number;
30
+ };
31
+
32
+ export function decideExecClaimTakeover(input: ExecClaimTakeoverInput): ExecClaimVerdict {
33
+ if (input.pidAlive === true) return { kind: 'held' };
34
+ if (input.pid === undefined || !Number.isSafeInteger(input.pid) || input.pid <= 0) {
35
+ return { kind: 'unknown', reason: 'no pid recorded' };
36
+ }
37
+ if (input.pidAlive === null) return { kind: 'unknown', reason: 'PID probe inconclusive' };
38
+ const claimedMs = input.execClaimedAt === undefined ? Number.NaN : Date.parse(input.execClaimedAt);
39
+ if (!Number.isFinite(claimedMs)) return { kind: 'unknown', reason: 'claim time unreadable' };
40
+ const ageMinutes = Math.floor((input.now - claimedMs) / 60_000);
41
+ if (ageMinutes >= input.staleMinutes) return { kind: 'stale-dead', ageMinutes };
42
+ return { kind: 'held' };
43
+ }