deadbyte-mcp 0.15.14 → 0.15.15

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.
package/MANIFEST.SHA256 CHANGED
@@ -1329,7 +1329,7 @@ db605a698f917c4006462596e6526fe6a9c6eff9b8409e8472fe545d4b70027c node_modules/z
1329
1329
  f67a25ded944912bd4621b68df17c7c70a52aae117278c076c0aabbc32a3abba node_modules/zod/v4/mini/schemas.d.ts
1330
1330
  51b3a7869df2d3a1bd98acb9d5b6a61bbccc7653d7a3d8fca7f0e0db7e84e284 node_modules/zod/v4/mini/schemas.js
1331
1331
  3e3125b5a537a24c3632c1c5c09640ce7bcb3935901b127ad8ae3c37a27f133f node_modules/zod/v4/package.json
1332
- 1372576b26135bde7220f29d6a664dc762ca006bfbf119455a2f02f5a7267270 package.json
1332
+ 0792082051518fb772aaaead59d51b32d03aebc917097371dbcceaab5911d55a package.json
1333
1333
  8177e3a354721a73df889f386a97e7744a2e017d150aecf4bf309f5177d18690 proof/CONTAINMENT-BUILD.txt
1334
1334
  a8efa13e44ff50902aee64cfca542d19605a1ae23db95280ad2d44b324f3eae7 proof/MCP-SMOKE-STATUS.txt
1335
1335
  7c115069cc4a343ca21b0ebdee74bd8f544b02d4aa906ffa13308725825d891a proof/NODE-SYNTAX.txt
@@ -1389,7 +1389,7 @@ d433aaea5cccd1e184775ac4d915f34e0b39c6c99916dddd009731ad5fd6443f proof/V073-WIN
1389
1389
  bd0773a9e02e8ab11475c9cc5a7db29a8313233b7eab7f837922737e75ec5938 proof/VERIFY.txt
1390
1390
  2c821808d4596965c577f40191daee224d3aea81989778c4af97f53a83b487db proof/WINDOWS-CONTAINED-STATUS.txt
1391
1391
  f8bc839c7b4fcb0ea7b7e7ad9448847c3beae6f95f2eb4d407d1b284aaa4ff68 proof/WINDOWS-CONTAINMENT-STATUS.txt
1392
- c7a11729f77023dd1d741d61b35ea0b75cca061a28f3a4cbe0062ca6a536f4ab PROVENANCE.json
1392
+ cde0c97bae86bcee9c3c1a7583d11a91423f1153cd90fc3bb9a03095fba3defd PROVENANCE.json
1393
1393
  27ca0f3c9ee3a1b6374a9c8dd98008decdd0c7659c7a569c70ad7b9734c74655 R34-BASELINE.json
1394
1394
  0b7ec5ee347ddfa6cca59740fc7a8fe3fc5d51406265bb12189e82fa106319d2 R41-PARENT.json
1395
1395
  bd2a789b0aab5663f0d208af2ca8e4ec98179bc8c7b57e70a962d4e06a3dcfc4 R42-PARENT.json
@@ -1397,8 +1397,8 @@ bd2a789b0aab5663f0d208af2ca8e4ec98179bc8c7b57e70a962d4e06a3dcfc4 R42-PARENT.jso
1397
1397
  f932ef78b7a72b1bb0d7aedb5fe42fbb9951c2492eb528b72442f46ddff81a6c R44-DONORS.json
1398
1398
  731b4c559fc0d25dea701e4474f02400b27e8e3f4cac9f230d5214497d4490a9 R44-PARENT.json
1399
1399
  d33ec5d167dd44800ba4e68372b08db43deb8d01df2ef71dd435ff004ac66c8b R45-PARENT.json
1400
- 5d81b599350d12f019586380d0983054c48fd89a7b948b335a4cec8fb9077ad7 README.txt
1401
- 5c2c00d7c5f4e593c017cf6eaa2dd85ca3350ae031247070fc313a9acc54fef4 RELEASE.json
1400
+ 96e0fca6d344384cd69ca3b675a3a19da57bac2d02507c9fc404563fcb7ab117 README.txt
1401
+ e4e39e1006db0b44c9fe36811100cfcca5e76b614f736a88421031cdc3c21239 RELEASE.json
1402
1402
  507a42441c8fbb0b1bef63a13fad9e1680262cd6d34cc176b7ed0c5423ddbcc7 SBOM.cdx.json
1403
1403
  bd138fe5a64088e8e1a7a5bf6104c5b17317bdc8cf3ad8191ea00ed82ace7d9b scripts/agent-eval-r42.mjs
1404
1404
  8d90256a6863f3bbbff02e95cb12389fc6f9e8dbcb77bf1c20c902de371425e8 scripts/archive-source-zips.mjs
@@ -1486,7 +1486,7 @@ a0ad824eed1d8be051d5f731579f9bf4115b311f202b75ea0d03aa257b13a671 scripts/gate-w
1486
1486
  de6142bd5693cf4edb8725c9c43fc77e9bdc12f5b398a757006d778d3ee3f8ed scripts/inspect-r46-active.mjs
1487
1487
  7ea8cf6bf094a1612603bc716228a3753e5f06fca8a0684d2da15a04d0710c64 scripts/list-source-zips.mjs
1488
1488
  9d12d15f7c83ddf272834072ed37d618a232bd8a3fd36c339761fe08bb9380d8 scripts/npm-publish-r47.ps1
1489
- 2a5b53ee74619814130fcdaf6c96d75bc08efb96d4004c391ab07a1b2454b7b9 scripts/npm-release-r47.mjs
1489
+ 133a29979ef92d46b9939701dbf14ded1438e34c0192571f8df7bb902492ae1a scripts/npm-release-r47.mjs
1490
1490
  453dd636b452f4343a36f31a93a763e97f58ed5d6c9df3d2ba05b236934a3a1f scripts/probe-source-zips.mjs
1491
1491
  8f9d97a4d23e9d05e7cadfb6a519b1ddb9da04894bca98bdc293fc3797e8423c scripts/probe-zips.mjs
1492
1492
  e19a1d591ccd07af2e87d930ce23b0cc9b814bc2a395408045543847154097d7 scripts/r40-compact-ledger-probe.mjs
@@ -1642,17 +1642,20 @@ cde7c813f5a2e9a69f09849455d1504e766963845a1f4b201ecc84090fa260d5 src/memory-r41
1642
1642
  2b2293a90db419dd1b548d766475c7e9e1bf243d3e9b0011969f1622ba803e74 src/native-diff.mjs
1643
1643
  79fb3b2cea86ef20b7c8a642f8842f686d6c91ae9e7933d546736085f6088179 src/observation-lease.mjs
1644
1644
  ed638caae618bb2f736b43f350fd9354ef32c6998eecca1b585bc2f289b23c59 src/observation-mcp-tools.mjs
1645
- 6a7adafe5c0c1f319f0704734c8ffbfb8c2e2051649fd5fd911e2aecd6a9d8c3 src/observation-output-contracts.mjs
1646
- 4e6d59de5c390f38d16b72c2094d0be5aec57be5a40e0516694e826e87a1ec0b src/observation-runtime.mjs
1645
+ 55fc05e62bea43a3343147b99b362bec59b22e0afe0b48a89c1523ecdc9af90c src/observation-output-contracts.mjs
1646
+ 2ec3939884567a1b8ba9dd1142b031de72d491a6e9fe7232b25f1bce2026377a src/observation-runtime.mjs
1647
1647
  e7d6f2f9b783df009ee64b99197821f783b1cb2488e58ec605398a1fa1a83cb8 src/observation-smoke-client.mjs
1648
+ 7d215399327b7e5a8b5e3c89bbe304c086c9b733dd5c7526b31f101afe97b783 src/operation-history.mjs
1649
+ 65dc466392ab317dad527e7f08004cc975b4a77f66509e10d786c691fed7bfbb src/operation-lifecycle.mjs
1648
1650
  96c29ce8f90a717ebc311155acfa075c7ea8f6f63401a37269586fbe1419376f src/operation-policy.mjs
1649
1651
  e541a1dba48034b914dc15eb789a6fa0bc43f60439dda61d31a7a99de9ff1a9f src/operator-surface.mjs
1650
1652
  b8e4e38fc38bd8d2efb01b58028944bc8133dcc42de12b1035784c5e01beca35 src/output-contracts.mjs
1651
1653
  21b76d428e4f15a7c7c5c8ddaf6862ecd8bd32153fac27167dbb6e39e15950f7 src/path-policy.mjs
1652
1654
  974b5e9a5f60c4b2ac8bed629fb39a4e3c06b591995bec3f5f4756da8f3fd2e1 src/process-mcp-tools.mjs
1653
- 4c90265628194d989ac78f2b06eadbfffceb9c006d5b6c2e982f5667f7a385ed src/process-output-contracts.mjs
1655
+ 3c5d6709a9e40db7e184711636bc832a892f746f1df471cad9783f8062c09877 src/process-output-contracts.mjs
1654
1656
  f5322664c669bc193214a803f9723e3728053f60bc008530db39a9af7f7bab75 src/process-policy.mjs
1655
- 622b984e577932ed0c342a6d7e0e8a17d08e888caa2ca67c5ecc1bbcca16042a src/process-runtime.mjs
1657
+ ba3eafd91b69e75700824c1a6103ff0e2f6a6cad162a607e1b12250d6bc52142 src/process-readiness.mjs
1658
+ f364076cad1cead310c7d43312a2c92d18f8ffc966bf50781436f37347cc0b1d src/process-runtime.mjs
1656
1659
  65d81b6cbdf4f4ce34ccef11793e79cfed6883288288915d077c11674f0873bb src/process-smoke-client.mjs
1657
1660
  bb4d1532684b8f81a42377c4b0a9d4c61207856edfff496e878ffbb885b52442 src/proof-cli.mjs
1658
1661
  be4a4f7f16e473ac842559a5a18eccb59e3693bded59a1abc71e75ff448cc755 src/proof-worker.mjs
@@ -1667,7 +1670,7 @@ b04cadd638861a2d75d86fede83b8d4cbe7065019c13ecd7a4ec0f9a9e0c06e5 src/r44-compac
1667
1670
  8addaac4895359816fbef5990a903e460313a1d72968c33757b312192f1e03f8 src/r45-full-surface-probe.mjs
1668
1671
  afc638b797d81b16c620ca2226eacc4cb1a36562a402219cdac3376867170610 src/receipt.mjs
1669
1672
  a1a0ac13b2d23fa7e0cbe1c093052b68ff3498982d9abeb27dba4dec404a8908 src/release-generation.mjs
1670
- a7a63ea58291e0676c7199633ae234030f46285840cdfc519c879c279ae8bb14 src/release-slot.mjs
1673
+ d781df334bfaa84d249112f5b0c480b14bf28759f77754307f8a8872ff17337f src/release-slot.mjs
1671
1674
  3f41fa32ddeed403b5431a338821b62e0c1946a9d808f06d0edb2d9341ca0121 src/remote-console.mjs
1672
1675
  ce6ec7ec794d1603f95311eac4712a0f8b26e7e96bc3617f88fba4297cc8b376 src/remote-device-r44.mjs
1673
1676
  123a157a5149992a3eee85ffa2ea4ef420efc67e9f6bb394b87fbe164dd56db2 src/remote-live-log.mjs
@@ -1707,7 +1710,7 @@ bac271c6f5218752ac5ff4decf5249e3893ddb35c63c092ce17238c27fc4d35b src/trust-init
1707
1710
  16df134040e3ff1e73df722b568d3e8ac0a6923b097696cead7580da75aa8d2a src/tunnel-stdio-compat.mjs
1708
1711
  642cb67d1854850e336ee73fbd71b329056bdcbdab35cea19c1a995a155ff5d9 src/verifier-cli.mjs
1709
1712
  8f818a4852ce3be3a380cd4736c3073a7a3e0e45cd7c7ca90588a6b843d3d022 src/verifier.mjs
1710
- 52a36e0a261fab6d40aa96b523efbaa87738600bacc6cd8b8d68047a261828cd src/version.mjs
1713
+ 5f91722767fdc9df81616509ca7c5362846ef8161191499ace7ac2b99a78ea30 src/version.mjs
1711
1714
  7e09c57a97289360aabfaf3d531208e54872efbfb3182f3c18203d2cb88c46b5 src/worker-registry.mjs
1712
1715
  76361d5ae9a710273cf64a7525082197b692236bf23689875e5fd1ef4aa96d2b src/worker-scheduler.mjs
1713
1716
  4da1b32f5198bb089bc68b7eb6de21ea296678b5020018a73742bf0df57d62fa src/workload-substrate.mjs
@@ -1885,7 +1888,9 @@ e1f9097b7f7d22a498b8cb2a8cafbb45ba69f613e2be58e09a3beb4be1e19db7 test/memory-pr
1885
1888
  80536bffc3bd3d05bcc863908fcf124adc843837efeba0fec8aad77c433158f9 test/native-diff.test.mjs
1886
1889
  3c68c05a68b4c8ab24839d82ff457cd835ac0cb9527b915c24ee684ce47338f7 test/observation-integration.test.mjs
1887
1890
  1147cf367361b6ac30e0f903da133dc169cc34585101e1b67d7e47a7034587a4 test/observation-mcp-tools.test.mjs
1888
- dbc64f9e39b21b3a9e55be6a276ceb1e0c523daf59dc72104c081df993cf3269 test/observation-runtime.test.mjs
1891
+ 42c03845d304636218d456162b965ace12cc450ab3eeb62d28c7eeef70f363cf test/observation-runtime.test.mjs
1892
+ ea60bf617122536ed10f546dc660491522e79c70a05d827efe5b50f09de5711f test/operation-history.test.mjs
1893
+ 874924c2d2e4073b1032e3e4a81af809efd2919044e19c0f862535a2d7442392 test/operation-lifecycle.test.mjs
1889
1894
  d0eacc6d75b692b3a7e3bfc8e543b6c075cfad84d9373051f36d6eefdbce6339 test/output-schema.test.mjs
1890
1895
  b25e37f3224fc63b020a84468b8955a5de68c7f2751c88de23832c23e343fea3 test/package-boundary.test.mjs
1891
1896
  8f28e3990b79cc58bf52a646a88f374270396c9bc8738f5eceb156f721ae8a81 test/process-longrun-r36.test.mjs
@@ -1893,8 +1898,9 @@ ddac61c12ab29be1736885c01c999ca0346ba44c712cc2c8a48a7eea1ed62a53 test/process-m
1893
1898
  b84622d07d2ecfe27b55e45f7e89949f29a2d14184d8c6f6f82ed7f1e5fa59be test/process-native.test.mjs
1894
1899
  39d66e1d69a4afcfde6c9e22c77f3df6d404450376b475d1a81f3b3c99488e20 test/process-policy-refresh.test.mjs
1895
1900
  8c5daf35a5aeb6e0b8327ce64cffc2c87bb400e731f7e174fdcac066c27256c4 test/process-policy.test.mjs
1901
+ e13275898e892ca70455cb53bb0a8c936c0f1a0a1c82ece671a314aefe106367 test/process-readiness.test.mjs
1896
1902
  510f51a6288a1030387cf372951499aea6dba2a35362ad89510bf7480e2f8004 test/process-release-gate.test.mjs
1897
- 548da6269630e40afb38b5906326eb14faff71c2b7f266503989fb5f63d41344 test/process-runtime.test.mjs
1903
+ 56f0e586a6b62b48623026078de4948cdbafe94e3213f5e9f166b1b89d34dba9 test/process-runtime.test.mjs
1898
1904
  e53d37bf4746495ecc30eaff9bf5bc92456d390edc6f54193d5067eae8f95588 test/process-supervision.test.mjs
1899
1905
  8b1c58809472a0ea65bf7fb90da0d4f21ce0bca948c3b1d04c6f6110be184a2e test/production-docs.test.mjs
1900
1906
  8d7a03c03c9070cc81401f0c2bf22eab28f4a4970a4701dd7a1095845ae500d1 test/prompts-resources.test.mjs
package/PROVENANCE.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schema": "deadbyte.provenance.v1",
3
- "version": "0.15.11",
3
+ "version": "0.15.15",
4
4
  "builder": "deadbyte-r46-fileless-powershell",
5
5
  "source_model": "development-root -> MANIFEST.SHA256 -> immutable release slot -> generation bundle -> signed final closure",
6
6
  "signing": "ed25519",
package/README.txt CHANGED
@@ -8,11 +8,11 @@ NPX PRIMARY OPERATOR UX
8
8
  Use the npm CLI entry point instead of the legacy CMD convenience wrapper:
9
9
 
10
10
  npx -y deadbyte-mcp@0.15.11 mcp
11
- npx -y deadbyte-mcp@0.15.11 remote
12
- npx -y deadbyte-mcp@0.15.11 remote status
13
- npx -y deadbyte-mcp@0.15.11 remote restart
14
- npx -y deadbyte-mcp@0.15.11 audit status
15
- npx -y deadbyte-mcp@0.15.11 audit verify
11
+ npx -y deadbyte-mcp@0.15.15 remote
12
+ npx -y deadbyte-mcp@0.15.15 remote status
13
+ npx -y deadbyte-mcp@0.15.15 remote restart
14
+ npx -y deadbyte-mcp@0.15.15 audit status
15
+ npx -y deadbyte-mcp@0.15.15 audit verify
16
16
 
17
17
  The `mcp` command is the quiet stdio server entry for local MCP clients; stdout is reserved for MCP.
18
18
  The remote CLI invokes the stable active-release PowerShell launcher directly with shell=false.
package/RELEASE.json CHANGED
@@ -1,53 +1,53 @@
1
- {
2
- "schema": "deadbyte.release.v1",
3
- "name": "deadbyte-mcp",
4
- "version": "0.15.13",
5
- "train": "R46",
6
- "label": "FILELESS POWERSHELL PRODUCT BASELINE",
7
- "protocol_revision": "2026-07-28",
8
- "parent": {
9
- "release_id": "v0.15.2-0fb46542f6a4345f",
10
- "manifest_sha256": "0fb46542f6a4345f1c527565d06bf6e9b8e283260e47bf684fb70a60308fcf34",
11
- "generation_manifest_sha256": "00cde7bf540cc2804d679a39040d5b07193c5f8a0d49cac3b882402f8f4e6ad8"
12
- },
13
- "platform": {
14
- "primary": "win32",
15
- "architectures": [
16
- "x64"
17
- ]
18
- },
19
- "capabilities": {
20
- "host_execution_modes": [
21
- "script",
22
- "inline",
23
- "native"
24
- ],
25
- "hardware_queries": [
26
- "cpu",
27
- "gpu",
28
- "memory",
29
- "storage",
30
- "network",
31
- "pci"
32
- ],
33
- "compact_tool_count": 43,
34
- "full_tool_count": 83,
35
- "process_profiles": [
36
- "ping_hold",
37
- "powershell",
38
- "powershell_repl"
39
- ],
40
- "powershell_fileless": true,
41
- "powershell_source_max_arg_bytes": 8192,
42
- "powershell_persistent_repl": true,
43
- "process_raw_input_profile_opt_in": true,
44
- "process_tail_reads": true,
45
- "process_wait_reads": true,
46
- "process_chunked_output_ring": true,
47
- "remote_reachability_ttl": true,
48
- "remote_probe_timeout_retry": true,
49
- "remote_dispatch_timeout_no_retry": true,
50
- "observation_search_session_ttl": true,
51
- "observation_search_session_cap": 128
52
- }
53
- }
1
+ {
2
+ "schema": "deadbyte.release.v1",
3
+ "name": "deadbyte-mcp",
4
+ "version": "0.15.15",
5
+ "train": "R46",
6
+ "label": "FILELESS POWERSHELL PRODUCT BASELINE",
7
+ "protocol_revision": "2026-07-28",
8
+ "parent": {
9
+ "release_id": "v0.15.2-0fb46542f6a4345f",
10
+ "manifest_sha256": "0fb46542f6a4345f1c527565d06bf6e9b8e283260e47bf684fb70a60308fcf34",
11
+ "generation_manifest_sha256": "00cde7bf540cc2804d679a39040d5b07193c5f8a0d49cac3b882402f8f4e6ad8"
12
+ },
13
+ "platform": {
14
+ "primary": "win32",
15
+ "architectures": [
16
+ "x64"
17
+ ]
18
+ },
19
+ "capabilities": {
20
+ "host_execution_modes": [
21
+ "script",
22
+ "inline",
23
+ "native"
24
+ ],
25
+ "hardware_queries": [
26
+ "cpu",
27
+ "gpu",
28
+ "memory",
29
+ "storage",
30
+ "network",
31
+ "pci"
32
+ ],
33
+ "compact_tool_count": 43,
34
+ "full_tool_count": 83,
35
+ "process_profiles": [
36
+ "ping_hold",
37
+ "powershell",
38
+ "powershell_repl"
39
+ ],
40
+ "powershell_fileless": true,
41
+ "powershell_source_max_arg_bytes": 8192,
42
+ "powershell_persistent_repl": true,
43
+ "process_raw_input_profile_opt_in": true,
44
+ "process_tail_reads": true,
45
+ "process_wait_reads": true,
46
+ "process_chunked_output_ring": true,
47
+ "remote_reachability_ttl": true,
48
+ "remote_probe_timeout_retry": true,
49
+ "remote_dispatch_timeout_no_retry": true,
50
+ "observation_search_session_ttl": true,
51
+ "observation_search_session_cap": 128
52
+ }
53
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deadbyte-mcp",
3
- "version": "0.15.14",
3
+ "version": "0.15.15",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Authenticated MCP agentic runtime with signed skills, persistent model backends, identity-bound remote devices, durable job supervision, immutable release slots, and separately revocable machine authority.",
@@ -64,6 +64,27 @@ async function main() {
64
64
  const version = pkg.version;
65
65
  if (pkg.name !== 'deadbyte-mcp') throw new Error(`unexpected package name: ${pkg.name}`);
66
66
 
67
+ const releaseBytes = await readFile(path.join(root, 'RELEASE.json'));
68
+ if (releaseBytes.subarray(0, 3).equals(Buffer.from([0xEF, 0xBB, 0xBF]))) {
69
+ throw new Error('RELEASE.json must be UTF-8 without BOM');
70
+ }
71
+ const release = JSON.parse(releaseBytes.toString('utf8'));
72
+ const provenance = JSON.parse(await readFile(path.join(root, 'PROVENANCE.json'), 'utf8'));
73
+ if (release.schema !== 'deadbyte.release.v1' || release.name !== pkg.name) {
74
+ throw new Error('RELEASE.json schema/package identity mismatch');
75
+ }
76
+ if (release.version !== version) {
77
+ throw new Error(`RELEASE.json version mismatch: package=${version} release=${release.version}`);
78
+ }
79
+ if (provenance.version !== version) {
80
+ throw new Error(`PROVENANCE.json version mismatch: package=${version} provenance=${provenance.version}`);
81
+ }
82
+ if (release.parent?.release_id !== provenance.parent_release_id ||
83
+ release.parent?.manifest_sha256 !== provenance.parent_manifest_sha256 ||
84
+ release.parent?.generation_manifest_sha256 !== provenance.parent_generation_manifest_sha256) {
85
+ throw new Error('RELEASE.json parent identity does not match sealed PROVENANCE.json lineage');
86
+ }
87
+
67
88
  const existing = spawnSync(node, [npmCli, 'view', `deadbyte-mcp@${version}`, 'version', '--json'], {
68
89
  cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], shell: false, windowsHide: true,
69
90
  });
@@ -5,7 +5,8 @@ const hex64=z.string().regex(/^[0-9a-f]{64}$/);
5
5
  const receiptId=z.string().regex(/^[0-9a-f]{24}$/);
6
6
  const searchState=z.enum(['pending','running','completed','cancelled','failed']);
7
7
  const evidenceShape={request_sha256:hex64,result_head_sha256:hex64,receipt_id:receiptId.nullable(),receipt_sha256:hex64.nullable()};
8
- const resultSchema=z.strictObject({path:z.string().min(1).max(512),line:z.number().int().min(1),column:z.number().int().min(1),preview:z.string().max(512)});
8
+ const overflowSchema=z.strictObject({occurred:z.boolean(),reason:z.enum(['result_cap','byte_cap','time_limit','producer_failure']).nullable(),emitted:z.number().int().nonnegative(),dropped:z.union([z.number().int().nonnegative(),z.literal('unknown')]),resume_supported:z.boolean()});
9
+ const resultSchema=z.strictObject({result_id:hex64,path:z.string().min(1).max(512),file_sha256:hex64,line:z.number().int().min(1),column:z.number().int().min(1),preview:z.string().max(512)});
9
10
  const processSchema=z.strictObject({pid:z.number().int().positive(),parent_pid:z.number().int().nonnegative(),name:z.string().max(260),
10
11
  ownership:z.enum(['deadbyte-runtime','deadbyte-session','external']),executable_path:z.string().max(2048).nullable(),command_line:z.string().max(4096).nullable()});
11
12
 
@@ -17,18 +18,18 @@ function statusContract(shape,successRequired=[]){
17
18
  }
18
19
 
19
20
  export const observationSearchStartOutputSchema=z.strictObject({
20
- status:z.enum(['accepted','failed']),error:z.string().optional(),search_id:hex64.optional(),state:searchState.optional(),request_sha256:hex64.optional()
21
- }).superRefine((v,ctx)=>{if(v.status==='accepted'){for(const k of ['search_id','state','request_sha256']) if(v[k]===undefined) ctx.addIssue({code:'custom',path:[k],message:`${k} required`});} else if(!v.error) ctx.addIssue({code:'custom',path:['error'],message:'error required'});});
21
+ status:z.enum(['accepted','failed']),error:z.string().optional(),search_id:hex64.optional(),snapshot_id:hex64.optional(),state:searchState.optional(),request_sha256:hex64.optional()
22
+ }).superRefine((v,ctx)=>{if(v.status==='accepted'){for(const k of ['search_id','snapshot_id','state','request_sha256']) if(v[k]===undefined) ctx.addIssue({code:'custom',path:[k],message:`${k} required`});} else if(!v.error) ctx.addIssue({code:'custom',path:['error'],message:'error required'});});
22
23
  export const observationSearchPageOutputSchema=statusContract({
23
- search_id:hex64.optional(),state:searchState.optional(),results:z.array(resultSchema).max(500).optional(),next_cursor:z.string().max(512).nullable().optional(),
24
+ search_id:hex64.optional(),snapshot_id:hex64.optional(),state:searchState.optional(),results:z.array(resultSchema).max(500).optional(),next_cursor:z.string().max(1024).nullable().optional(),sequence:z.number().int().min(0).optional(),overflow:overflowSchema.optional(),
24
25
  ...Object.fromEntries(Object.entries(evidenceShape).map(([k,v])=>[k,v.optional()]))
25
- },['search_id','state','results','next_cursor','request_sha256','result_head_sha256','receipt_id','receipt_sha256']);
26
+ },['search_id','snapshot_id','state','results','next_cursor','sequence','overflow','request_sha256','result_head_sha256','receipt_id','receipt_sha256']);
26
27
 
27
28
  export const observationSearchStatusOutputSchema=statusContract({
28
- search_id:hex64.optional(),state:searchState.optional(),result_count:z.number().int().nonnegative().optional(),files_scanned:z.number().int().nonnegative().optional(),
29
- bytes_scanned:z.number().int().nonnegative().optional(),truncated:z.boolean().optional(),search_error:z.string().nullable().optional(),
29
+ search_id:hex64.optional(),snapshot_id:hex64.optional(),state:searchState.optional(),result_count:z.number().int().nonnegative().optional(),files_scanned:z.number().int().nonnegative().optional(),
30
+ bytes_scanned:z.number().int().nonnegative().optional(),truncated:z.boolean().optional(),overflow:overflowSchema.optional(),search_error:z.string().nullable().optional(),
30
31
  ...Object.fromEntries(Object.entries(evidenceShape).map(([k,v])=>[k,v.optional()]))
31
- },['search_id','state','result_count','files_scanned','bytes_scanned','truncated','search_error','request_sha256','result_head_sha256','receipt_id','receipt_sha256']);
32
+ },['search_id','snapshot_id','state','result_count','files_scanned','bytes_scanned','truncated','overflow','search_error','request_sha256','result_head_sha256','receipt_id','receipt_sha256']);
32
33
 
33
34
  export const observationSearchCancelOutputSchema=statusContract({
34
35
  search_id:hex64.optional(),state:searchState.optional(),cancel_requested:z.boolean().optional()
@@ -51,6 +51,8 @@ function decodeCursor(value){
51
51
  try { return JSON.parse(Buffer.from(String(value),'base64url').toString('utf8')); }
52
52
  catch { throw new Error('invalid observation cursor'); }
53
53
  }
54
+ function cursorTag(secret, body){ return sha256Bytes(Buffer.concat([secret,Buffer.from(canonicalJson(body),'utf8')])); }
55
+ function makeStableResultId({path,fileSha256,line,column}){ return sha256Object({schema:'deadbyte.search-result.v1',path:path.replaceAll('\\\\','/'),file_sha256:fileSha256,line,column}); }
54
56
  async function writeReceipt(runtime,kind,requestSha,resultHead,summary){
55
57
  const receiptId=randomBytes(12).toString('hex');
56
58
  const body={schema:OBSERVATION_RECEIPT_SCHEMA,receipt_id:receiptId,kind,
@@ -140,6 +142,9 @@ export function createObservationRuntime({policy,signingKeyPath,verifyKeyPath,pr
140
142
  if(!Number.isInteger(maxSearchSessions)||maxSearchSessions<1||maxSearchSessions>4096) throw new Error('maxSearchSessions out of range');
141
143
  if(typeof clockMs!=='function') throw new Error('clockMs must be a function');
142
144
  const searches=new Map(),snapshots=new Map();
145
+ const searchCursorSecrets=new Map();
146
+ const SEARCH_CURSOR_SCHEMA='deadbyte.search-cursor.v1';
147
+ const overflowReason=Object.freeze({result_cap:'result_cap',byte_cap:'byte_cap',time_limit:'time_limit',producer_failure:'producer_failure'});
143
148
  let signingPromise=null;
144
149
  const runtime={policy,signing:()=>signingPromise??=(loadPrivateSigningKey(signingKeyPath))};
145
150
  const terminalSearchState=state=>['completed','cancelled','failed'].includes(state);
@@ -175,7 +180,7 @@ export function createObservationRuntime({policy,signingKeyPath,verifyKeyPath,pr
175
180
  session.state='running';
176
181
  try {
177
182
  const start=await resolveObservationRoot(policy,session.root_id,session.path);
178
- const files=[]; const queue=[{dir:start.resolved,rel:start.reported==='.'?'':start.reported}];
183
+ const snapshotId=session.snapshot_id; const queue=[{dir:start.resolved,rel:start.reported==='.'?'':start.reported}]; const files=[];
179
184
  while(queue.length&&files.length<session.max_files&&!session.cancel_requested){
180
185
  const current=queue.shift(); const children=await readdir(current.dir,{withFileTypes:true});
181
186
  children.sort((a,b)=>a.name.localeCompare(b.name));
@@ -189,28 +194,30 @@ export function createObservationRuntime({policy,signingKeyPath,verifyKeyPath,pr
189
194
  }
190
195
  }
191
196
  files.sort((a,b)=>a.path.localeCompare(b.path));
192
- if(queue.length) session.truncated=true;
197
+ if(queue.length){ session.truncated=true; session.overflow={occurred:true,reason:overflowReason.result_cap,emitted:session.results.length,dropped:'unknown',resume_supported:false}; }
193
198
  const needle=session.case_sensitive?session.pattern:session.pattern.toLowerCase();
194
199
  for(const file of files){
195
200
  if(session.cancel_requested) break;
196
201
  if(file.bytes>session.max_file_bytes) continue;
197
- if(session.bytes_scanned+file.bytes>session.max_bytes){ session.truncated=true; break; }
198
- const bytes=await readFile(file.full); let text;
202
+ if(session.bytes_scanned+file.bytes>session.max_bytes){ session.truncated=true; session.overflow={occurred:true,reason:overflowReason.byte_cap,emitted:session.results.length,dropped:'unknown',resume_supported:false}; break; }
203
+ const bytes=await readFile(file.full); const fileSha256=sha256Bytes(bytes); let text;
199
204
  try { text=decodeUtf8Strict(bytes); } catch { continue; }
200
205
  session.files_scanned++; session.bytes_scanned+=bytes.length;
201
206
  const lines=text.split(/\r?\n/);
202
207
  for(let i=0;i<lines.length;i++){
203
208
  const hay=session.case_sensitive?lines[i]:lines[i].toLowerCase(); const index=hay.indexOf(needle);
204
209
  if(index<0) continue;
205
- const result={path:file.path,line:i+1,column:index+1,preview:lines[i].slice(Math.max(0,index-40),index+Math.min(200,session.pattern.length+120))};
210
+ const resultBase={path:file.path,file_sha256:fileSha256,line:i+1,column:index+1,preview:lines[i].slice(Math.max(0,index-40),index+Math.min(200,session.pattern.length+120))};
211
+ const result={...resultBase,result_id:makeStableResultId(resultBase)};
206
212
  session.results.push(result); session.result_head_sha256=sha256Object({previous_result_sha256:session.result_head_sha256,result});
207
- if(session.results.length>=session.max_results){ session.truncated=true; break; }
213
+ if(session.results.length>=session.max_results){ session.truncated=true; session.overflow={occurred:true,reason:overflowReason.result_cap,emitted:session.results.length,dropped:'unknown',resume_supported:false}; break; }
208
214
  }
209
215
  if(session.results.length>=session.max_results) break;
210
216
  await new Promise(resolve=>setImmediate(resolve));
211
217
  }
218
+ if(session.cancel_requested){ session.overflow={occurred:session.truncated,reason:session.truncated?overflowReason.result_cap:null,emitted:session.results.length,dropped:session.truncated?'unknown':0,resume_supported:false}; }
212
219
  await finalizeSearch(session,session.cancel_requested?'cancelled':'completed');
213
- } catch(error){ await finalizeSearch(session,'failed',error); }
220
+ } catch(error){ session.overflow={occurred:true,reason:overflowReason.producer_failure,emitted:session.results.length,dropped:'unknown',resume_supported:false}; await finalizeSearch(session,'failed',error); }
214
221
  }
215
222
  async function searchStart({rootId,relativePath='.',pattern,caseSensitive=false,maxResults=200,maxFiles=2000,maxBytes=4*1024*1024,maxFileBytes=262144}){
216
223
  if(typeof pattern!=='string'||pattern.length<1||pattern.length>256) throw new Error('search pattern must be 1-256 characters');
@@ -224,14 +231,16 @@ export function createObservationRuntime({policy,signingKeyPath,verifyKeyPath,pr
224
231
  const args={root_id:rootId,path:relativePath,pattern,case_sensitive:caseSensitive,max_results:maxResults,max_files:maxFiles,max_bytes:maxBytes,max_file_bytes:maxFileBytes};
225
232
  const session={search_id:randomBytes(32).toString('hex'),root_id:rootId,path:relativePath,pattern,case_sensitive:caseSensitive,
226
233
  max_results:maxResults,max_files:maxFiles,max_bytes:maxBytes,max_file_bytes:maxFileBytes,state:'pending',cancel_requested:false,
227
- results:[],files_scanned:0,bytes_scanned:0,truncated:false,result_head_sha256:ZERO,error:null,receipt_id:null,receipt_sha256:null,
228
- created_at_ms:Number(clockMs()),completed_at_ms:null,request_sha256:requestSha(policy,'machine_search_start',args)};
234
+ snapshot_id:randomBytes(32).toString('hex'),snapshot_expires_at_ms:Number(clockMs())+searchSessionTtlMs,
235
+ results:[],files_scanned:0,bytes_scanned:0,truncated:false,overflow:{occurred:false,reason:null,emitted:0,dropped:0,resume_supported:true},result_head_sha256:ZERO,error:null,receipt_id:null,receipt_sha256:null,
236
+ created_at_ms:Number(clockMs()),completed_at_ms:null,max_ack_position:0,last_cursor:null,last_cursor_response:null,request_sha256:requestSha(policy,'machine_search_start',args)};
237
+ searchCursorSecrets.set(session.search_id,randomBytes(32));
229
238
  searches.set(session.search_id,session); setImmediate(()=>{ void executeSearch(session); });
230
- return {status:'accepted',search_id:session.search_id,state:session.state,request_sha256:session.request_sha256};
239
+ return {status:'accepted',search_id:session.search_id,snapshot_id:session.snapshot_id,state:session.state,request_sha256:session.request_sha256};
231
240
  }
232
241
  async function searchStatus({searchId}){
233
- const s=searchFor(searchId); return {status:'ok',search_id:s.search_id,state:s.state,result_count:s.results.length,files_scanned:s.files_scanned,
234
- bytes_scanned:s.bytes_scanned,truncated:s.truncated,request_sha256:s.request_sha256,result_head_sha256:s.result_head_sha256,
242
+ const s=searchFor(searchId); return {status:'ok',search_id:s.search_id,snapshot_id:s.snapshot_id,state:s.state,result_count:s.results.length,files_scanned:s.files_scanned,
243
+ bytes_scanned:s.bytes_scanned,truncated:s.truncated,overflow:s.overflow,request_sha256:s.request_sha256,result_head_sha256:s.result_head_sha256,
235
244
  receipt_id:s.receipt_id,receipt_sha256:s.receipt_sha256,error:s.error};
236
245
  }
237
246
  async function searchCancel({searchId}){
@@ -241,12 +250,33 @@ export function createObservationRuntime({policy,signingKeyPath,verifyKeyPath,pr
241
250
  }
242
251
  async function searchPage({searchId,limit=100,cursor=null}){
243
252
  const s=searchFor(searchId);
253
+ const now=Number(clockMs());
254
+ if(now>=s.snapshot_expires_at_ms) throw new Error('search snapshot expired');
244
255
  if(!Number.isInteger(limit)||limit<1||limit>500) throw new Error('search page limit must be in [1,500]');
245
- let index=0;
246
- if(cursor!==null){ const parsed=decodeCursor(cursor); if(parsed.search_id!==searchId||!Number.isInteger(parsed.index)||parsed.index<0) throw new Error('search cursor mismatch'); index=parsed.index; }
247
- const results=s.results.slice(index,index+limit); const next=index+results.length<s.results.length?encodeCursor({search_id:searchId,index:index+results.length}):null;
248
- return {status:'ok',search_id:s.search_id,state:s.state,results,next_cursor:next,result_head_sha256:s.result_head_sha256,
249
- request_sha256:s.request_sha256,receipt_id:s.receipt_id,receipt_sha256:s.receipt_sha256};
256
+ let position=0, nextSequence=1;
257
+ if(cursor!==null){
258
+ const parsed=decodeCursor(cursor);
259
+ const body={schema:SEARCH_CURSOR_SCHEMA,search_id:parsed.search_id,snapshot_id:parsed.snapshot_id,next_sequence:parsed.next_sequence,position:parsed.position};
260
+ const secret=searchCursorSecrets.get(searchId);
261
+ if(!secret||parsed.search_id!==searchId||parsed.snapshot_id!==s.snapshot_id||!Number.isInteger(parsed.next_sequence)||!Number.isInteger(parsed.position)||parsed.position<0||parsed.next_sequence<1||parsed.next_sequence!==parsed.position+1||parsed.integrity!==cursorTag(secret,body)){
262
+ throw new Error('invalid, tampered, rewound, or mismatched search cursor');
263
+ }
264
+ position=parsed.position; nextSequence=parsed.next_sequence;
265
+ }
266
+ if(position>s.results.length) throw new Error('search cursor is ahead of producer boundary');
267
+ if(position<s.max_ack_position){
268
+ if(cursor!==null && cursor===s.last_cursor && s.last_cursor_response) return s.last_cursor_response;
269
+ throw new Error('rewound search cursor rejected');
270
+ }
271
+ const results=s.results.slice(position,position+limit);
272
+ const nextPosition=position+results.length;
273
+ s.max_ack_position=Math.max(s.max_ack_position,nextPosition);
274
+ const next=nextPosition<s.results.length ? (()=>{ const body={schema:SEARCH_CURSOR_SCHEMA,search_id:s.search_id,snapshot_id:s.snapshot_id,next_sequence:nextPosition+1,position:nextPosition}; const integrity=cursorTag(searchCursorSecrets.get(searchId),body); return encodeCursor({...body,integrity}); })() : null;
275
+ const response=Object.freeze({status:'ok',search_id:s.search_id,snapshot_id:s.snapshot_id,state:s.state,results,next_cursor:next,
276
+ sequence:nextSequence+results.length-1,overflow:s.overflow,result_head_sha256:s.result_head_sha256,
277
+ request_sha256:s.request_sha256,receipt_id:s.receipt_id,receipt_sha256:s.receipt_sha256});
278
+ if(cursor!==null) { s.last_cursor=cursor; s.last_cursor_response=response; }
279
+ return response;
250
280
  }
251
281
  async function captureProcessSnapshot(includeExternal){
252
282
  const observedAt=nowUtc(); const rows=await processSnapshotProvider();
@@ -0,0 +1,95 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { canonicalJson } from './canonical-json.mjs';
3
+
4
+ const ZERO_HASH='0'.repeat(64);
5
+ const sha256=value=>createHash('sha256').update(value).digest('hex');
6
+ const objectHash=value=>sha256(Buffer.from(canonicalJson(value),'utf8'));
7
+
8
+ export const OPERATION_HISTORY_SCHEMA='deadbyte.operation-history.v1';
9
+
10
+ export function createOperationHistory({maxEvents=1024,maxBytes=1024*1024,clockMs=()=>Date.now(),operationId=randomBytes(16).toString('hex')}={}){
11
+ if(!Number.isInteger(maxEvents)||maxEvents<1||maxEvents>100000) throw new Error('maxEvents out of range');
12
+ if(!Number.isInteger(maxBytes)||maxBytes<1024||maxBytes>64*1024*1024) throw new Error('maxBytes out of range');
13
+ if(typeof clockMs!=='function') throw new Error('clockMs must be a function');
14
+ if(typeof operationId!=='string'||operationId.length<1||operationId.length>128) throw new Error('invalid operationId');
15
+ const events=[];
16
+ let bytes=0,sequence=0,head=ZERO_HASH;
17
+ let overflow={occurred:false,reason:null,dropped:0,dropped_bytes:0,resume_supported:true};
18
+
19
+ function append(eventType,payload={}){
20
+ if(typeof eventType!=='string'||eventType.length<1||eventType.length>128) throw new Error('invalid eventType');
21
+ const payloadDigest=objectHash(payload);
22
+ const body={
23
+ schema:OPERATION_HISTORY_SCHEMA,
24
+ operation_id:operationId,
25
+ sequence:++sequence,
26
+ event_type:eventType,
27
+ at_ms:Number(clockMs()),
28
+ payload_digest:payloadDigest,
29
+ previous_event_hash:head
30
+ };
31
+ const eventHash=objectHash(body);
32
+ const frozenPayload=JSON.parse(canonicalJson(payload));
33
+ const event=Object.freeze({...body,event_hash:eventHash,payload:Object.freeze(frozenPayload)});
34
+ const size=Buffer.byteLength(canonicalJson(event),'utf8');
35
+ if(size>maxBytes) throw new Error('operation history event exceeds byte cap');
36
+ while(events.length&&bytes+size>maxBytes){
37
+ const dropped=events.shift();
38
+ bytes-=dropped.__bytes;
39
+ overflow={occurred:true,reason:'byte_cap',dropped:overflow.dropped+1,dropped_bytes:overflow.dropped_bytes+dropped.__bytes,resume_supported:false};
40
+ }
41
+ events.push(Object.freeze({...event,__bytes:size}));
42
+ bytes+=size; head=eventHash;
43
+ if(events.length>maxEvents){
44
+ const dropped=events.shift();
45
+ bytes-=dropped.__bytes;
46
+ overflow={occurred:true,reason:'event_cap',dropped:overflow.dropped+1,dropped_bytes:overflow.dropped_bytes+dropped.__bytes,resume_supported:false};
47
+ }
48
+ return event;
49
+ }
50
+
51
+ function read({afterSequence=0,limit=100}={}){
52
+ if(!Number.isInteger(afterSequence)||afterSequence<0) throw new Error('invalid history cursor');
53
+ if(!Number.isInteger(limit)||limit<1||limit>1000) throw new Error('history limit out of range');
54
+ const earliest=events[0]?.sequence??sequence+1;
55
+ if(afterSequence<earliest-1&&events.length) throw new Error('history cursor expired by bounded retention');
56
+ const selected=events.filter(e=>e.sequence>afterSequence).slice(0,limit).map(({__bytes,...e})=>e);
57
+ return {
58
+ schema:OPERATION_HISTORY_SCHEMA,operation_id:operationId,events:selected,
59
+ next_sequence:selected.length?selected.at(-1).sequence:afterSequence,
60
+ head_event_hash:head,earliest_sequence:earliest,
61
+ overflow:Object.freeze({...overflow})
62
+ };
63
+ }
64
+
65
+ function verify(){
66
+ let previous=events.length&&overflow.occurred ? events[0].previous_event_hash : ZERO_HASH;
67
+ for(let i=0;i<events.length;i++){
68
+ const {event_hash,payload,__bytes,...body}=events[i];
69
+ if(body.operation_id!==operationId||body.sequence!==events[i].sequence||body.previous_event_hash!==previous) return {ok:false,reason:'continuity_mismatch'};
70
+ if(body.payload_digest!==objectHash(payload)||event_hash!==objectHash(body)) return {ok:false,reason:'event_hash_mismatch'};
71
+ previous=event_hash;
72
+ }
73
+ return {ok:true,head_event_hash:head,earliest_sequence:events[0]?.sequence??sequence+1,next_sequence:sequence+1,overflow:{...overflow}};
74
+ }
75
+
76
+ return Object.freeze({
77
+ operationId,
78
+ append,
79
+ read,
80
+ verify,
81
+ snapshot:()=>read({afterSequence:Math.max(0,(events[0]?.sequence??sequence+1)-1),limit:Math.min(maxEvents,1000)})
82
+ });
83
+ }
84
+ export function operationHistoryReceipt(history){
85
+ const verified=history.verify();
86
+ if(!verified.ok) throw new Error('operation history integrity verification failed');
87
+ return Object.freeze({
88
+ schema:OPERATION_HISTORY_SCHEMA,
89
+ operation_id:history.operationId,
90
+ head_event_hash:verified.head_event_hash,
91
+ earliest_sequence:verified.earliest_sequence,
92
+ next_sequence:verified.next_sequence,
93
+ overflow:verified.overflow
94
+ });
95
+ }
@@ -0,0 +1,63 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { createOperationHistory, operationHistoryReceipt } from './operation-history.mjs';
3
+
4
+ export const OPERATION_LIFECYCLE_SCHEMA='deadbyte.operation-lifecycle.v1';
5
+ const TERMINAL=new Set(['success','failure','cancelled','timed_out']);
6
+ const ACTIVE=new Set(['start','running','cancel_requested','canceling','output','error']);
7
+ const ALLOWED=new Map([
8
+ ['start',new Set(['running','cancel_requested','output','error','success','failure','cancelled','timed_out'])],
9
+ ['running',new Set(['running','cancel_requested','canceling','output','error','success','failure','cancelled','timed_out'])],
10
+ ['cancel_requested',new Set(['canceling','cancelled','timed_out','output','error'])],
11
+ ['canceling',new Set(['cancelled','timed_out','output','error'])],
12
+ ['output',new Set(['running','cancel_requested','canceling','output','error','success','failure','cancelled','timed_out'])],
13
+ ['error',new Set(['running','cancel_requested','canceling','success','failure','cancelled','timed_out','error'])],
14
+ ['success',new Set()], ['failure',new Set()], ['cancelled',new Set()], ['timed_out',new Set()]
15
+ ]);
16
+
17
+ export function createOperationLifecycle({operationId=randomBytes(32).toString('hex'),clockMs=()=>Date.now(),maxEvents=1024,maxBytes=1024*1024}={}){
18
+ const history=createOperationHistory({operationId,clockMs,maxEvents,maxBytes});
19
+ let state='new';
20
+ let terminalSequence=null;
21
+ let terminalEventHash=null;
22
+ let terminalType=null;
23
+
24
+ function emit(type,payload={}){
25
+ if(typeof type!=='string'||(!ACTIVE.has(type)&&!TERMINAL.has(type))) throw new Error('invalid lifecycle event');
26
+ if(terminalType) {
27
+ if(TERMINAL.has(type)) throw new Error('terminal transition already committed');
28
+ throw new Error('late operation event rejected after terminal');
29
+ }
30
+ if(type==='start' && state!=='new') throw new Error('duplicate operation start');
31
+ const allowed=state==='new' ? new Set(['start']) : ALLOWED.get(state);
32
+ if(!allowed?.has(type)) throw new Error('invalid operation transition '+state+' -> '+type);
33
+ const event=history.append(type,payload);
34
+ state=type;
35
+ if(TERMINAL.has(type)){ terminalSequence=event.sequence; terminalEventHash=event.event_hash; terminalType=type; }
36
+ return Object.freeze({schema:OPERATION_LIFECYCLE_SCHEMA,operation_id:operationId,state,event});
37
+ }
38
+
39
+ function terminal(type,payload={}) {
40
+ if(!TERMINAL.has(type)) throw new Error('terminal type required');
41
+ return emit(type,payload);
42
+ }
43
+
44
+ function snapshot(){
45
+ const verified=history.verify();
46
+ return Object.freeze({
47
+ schema:OPERATION_LIFECYCLE_SCHEMA,operation_id:operationId,state,
48
+ terminal:{type:terminalType,sequence:terminalSequence,event_hash:terminalEventHash},
49
+ history_head_sha256:verified.head_event_hash,
50
+ next_sequence:verified.next_sequence,
51
+ overflow:verified.overflow
52
+ });
53
+ }
54
+
55
+ function receipt(){
56
+ if(!terminalType) throw new Error('terminal receipt unavailable before terminal transition');
57
+ const h=operationHistoryReceipt(history);
58
+ if(h.next_sequence!==terminalSequence+1 || h.head_event_hash!==terminalEventHash) throw new Error('terminal receipt/history mismatch');
59
+ return Object.freeze({...h,terminal_type:terminalType,terminal_sequence:terminalSequence,terminal_event_hash:terminalEventHash});
60
+ }
61
+
62
+ return Object.freeze({operationId,history,emit,terminal,snapshot,receipt,isTerminal:()=>Boolean(terminalType)});
63
+ }