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 +19 -13
- package/PROVENANCE.json +1 -1
- package/README.txt +5 -5
- package/RELEASE.json +53 -53
- package/package.json +1 -1
- package/scripts/npm-release-r47.mjs +21 -0
- package/src/observation-output-contracts.mjs +9 -8
- package/src/observation-runtime.mjs +47 -17
- package/src/operation-history.mjs +95 -0
- package/src/operation-lifecycle.mjs +63 -0
- package/src/process-output-contracts.mjs +24 -1
- package/src/process-readiness.mjs +120 -0
- package/src/process-runtime.mjs +178 -6
- package/src/release-slot.mjs +294 -293
- package/src/version.mjs +1 -1
- package/test/observation-runtime.test.mjs +28 -0
- package/test/operation-history.test.mjs +61 -0
- package/test/operation-lifecycle.test.mjs +72 -0
- package/test/process-readiness.test.mjs +32 -0
- package/test/process-runtime.test.mjs +89 -0
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1401
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1646
|
-
|
|
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
|
-
|
|
1655
|
+
3c5d6709a9e40db7e184711636bc832a892f746f1df471cad9783f8062c09877 src/process-output-contracts.mjs
|
|
1654
1656
|
f5322664c669bc193214a803f9723e3728053f60bc008530db39a9af7f7bab75 src/process-policy.mjs
|
|
1655
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
12
|
-
npx -y deadbyte-mcp@0.15.
|
|
13
|
-
npx -y deadbyte-mcp@0.15.
|
|
14
|
-
npx -y deadbyte-mcp@0.15.
|
|
15
|
-
npx -y deadbyte-mcp@0.15.
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
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.
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
228
|
-
|
|
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
|
|
246
|
-
if(cursor!==null){
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
+
}
|