deadbyte-mcp 0.15.23 → 0.15.25
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 +49 -40
- package/PROVENANCE.json +11 -10
- package/README.txt +7 -7
- package/RELEASE.json +19 -38
- package/TEST-MATRIX.json +32 -96
- package/package.json +3 -3
- package/proof/commander-closure-20260928/DIFFERENTIAL-CHECKPOINT.json +122 -0
- package/scripts/commander-hardening-gate-r47.mjs +10 -1
- package/scripts/commander-real-performance-benchmark-r47.mjs +12 -3
- package/scripts/gate-windows-r48.ps1 +45 -0
- package/scripts/init-host-policy.ps1 +9 -1
- package/scripts/npm-release-r48.mjs +2 -1
- package/scripts/windows-gate-evidence-r48.mjs +27 -0
- package/src/atomic-mutation.mjs +12 -4
- package/src/coding-mcp-tools.mjs +15 -1
- package/src/coding-output-contracts.mjs +9 -3
- package/src/commander-office-reader-r48.mjs +36 -3
- package/src/commander-pdf-r48.mjs +36 -1
- package/src/commander-wire-adapter-r47.mjs +72 -2
- package/src/host-plane.mjs +10 -31
- package/src/machine-fs-mcp-tools.mjs +7 -1
- package/src/machine-fs-output-contracts.mjs +6 -0
- package/src/machine-fs-smoke-client.mjs +6 -17
- package/src/mcp-host-smoke-client.mjs +3 -2
- package/src/mcp-server.mjs +14 -2
- package/src/mcp-smoke-client.mjs +4 -9
- package/src/observation-smoke-client.mjs +5 -10
- package/src/output-contracts.mjs +6 -4
- package/src/process-smoke-client.mjs +3 -3
- package/src/runtime-catalog-expectation.mjs +37 -0
- package/src/stdio-protocol-guard.mjs +71 -0
- package/src/tunnel-stdio-compat-smoke.mjs +5 -9
- package/src/version.mjs +1 -1
- package/test/atomic-mutation.test.mjs +61 -0
- package/test/coding-catalog-consistency.test.mjs +9 -1
- package/test/coding-mcp-unknown-effect.test.mjs +42 -0
- package/test/commander-biff-reference-r48.test.mjs +38 -0
- package/test/commander-core-bridge-r47.test.mjs +19 -0
- package/test/commander-office-reader-r48.test.mjs +18 -2
- package/test/commander-pdf-r48.test.mjs +35 -0
- package/test/commander-protocol-adversarial-r48.test.mjs +166 -0
- package/test/commander-real-parity-r47.test.mjs +41 -3
- package/test/commander-schema-differential-r48.test.mjs +1 -1
- package/test/commander-wire-contract-r47.test.mjs +32 -0
- package/test/contained.test.mjs +4 -6
- package/test/controller.test.mjs +2 -1
- package/test/machine-fs-mcp-tools.test.mjs +12 -0
- package/test/output-schema.test.mjs +20 -1
- package/test/r48-release-contract.test.mjs +27 -27
- package/test/tunnel-stdio-compat.test.mjs +7 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deadbyte-mcp",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.25",
|
|
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.",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"contained:run": "node src/contained-cli.mjs workspaces/demo input.txt",
|
|
43
43
|
"contained:smoke": "node src/contained-smoke.mjs",
|
|
44
44
|
"mcp:contained:smoke": "node src/mcp-contained-smoke-client.mjs",
|
|
45
|
-
"gate:windows": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/gate-windows-
|
|
45
|
+
"gate:windows": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/gate-windows-r48.ps1",
|
|
46
46
|
"authority:smoke": "node src/authority-smoke.mjs",
|
|
47
47
|
"mcp:remote:smoke": "node src/remote-mcp-smoke-client.mjs",
|
|
48
48
|
"mcp:host:smoke": "node src/mcp-host-smoke-client.mjs",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"r47:catalog:test": "node --test --test-concurrency=1 test/full-max-catalog-r47.test.mjs",
|
|
71
71
|
"r47:hardening:test": "node scripts/commander-hardening-gate-r47.mjs",
|
|
72
72
|
"r48:full:test": "node scripts/full-regression-r48.mjs",
|
|
73
|
-
"r48:release:test": "node --test --test-concurrency=1 test/r48-release-contract.test.mjs test/commander-reference-contract-r48.test.mjs test/commander-extended-wire-r48.test.mjs test/commander-external-process-r48.test.mjs test/commander-office-reader-r48.test.mjs test/commander-pdf-r48.test.mjs test/commander-url-reader-r48.test.mjs test/commander-wire-contract-r47.test.mjs",
|
|
73
|
+
"r48:release:test": "node --test --test-concurrency=1 test/r48-release-contract.test.mjs test/commander-reference-contract-r48.test.mjs test/commander-extended-wire-r48.test.mjs test/commander-external-process-r48.test.mjs test/commander-office-reader-r48.test.mjs test/commander-pdf-r48.test.mjs test/commander-url-reader-r48.test.mjs test/commander-wire-contract-r47.test.mjs test/commander-protocol-adversarial-r48.test.mjs",
|
|
74
74
|
"r48:catalog:test": "node --test --test-concurrency=1 test/full-max-catalog-r48.test.mjs",
|
|
75
75
|
"release:npm": "node scripts/npm-release-r48.mjs"
|
|
76
76
|
},
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "deadbyte.commander-differential-checkpoint.v1",
|
|
3
|
+
"status": "INCOMPLETE",
|
|
4
|
+
"observed_at_utc": "2026-09-29T13:36:00.000Z",
|
|
5
|
+
"source": {
|
|
6
|
+
"requested_path": "D:\\Projects\\deadbyte-mcp-r36-dev",
|
|
7
|
+
"requested_path_version": "0.12.0",
|
|
8
|
+
"actual_successor_workspace": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-r42-agentic-dev",
|
|
9
|
+
"version": "0.15.25",
|
|
10
|
+
"git_head": "f9037d4847a7204ce4e1f808758ee3f0e9e7a738",
|
|
11
|
+
"path_drift_classification": "requested source path is a historical snapshot and cannot produce a successor of active 0.15.23"
|
|
12
|
+
},
|
|
13
|
+
"reference": {
|
|
14
|
+
"path": "D:\\Projects\\desktop-commander-stage-20260922\\desktop-commander",
|
|
15
|
+
"package": "@wonderwhy-er/desktop-commander",
|
|
16
|
+
"version": "0.2.51",
|
|
17
|
+
"server_sha256": "dfc303bb7dd2e8d01debac50fc43bb8644d00089bffdf2887e149497cff8b305",
|
|
18
|
+
"schemas_sha256": "f342345e34e48769dabaefe1b932f09d41e2e207b422f7794a15ee50ffa8f140"
|
|
19
|
+
},
|
|
20
|
+
"live": {
|
|
21
|
+
"release_id": "v0.15.23-c97e3d21e792420e",
|
|
22
|
+
"release_manifest_sha256": "c97e3d21e792420ea90dada3c1360cd56e4c1f221daa86596d611b0d1ac351ec",
|
|
23
|
+
"generation_manifest_sha256": "4d2d3a4dc103576c39171c52db3436366e62d8b872bcafa4c55f2adb6d3a1126",
|
|
24
|
+
"bridge_pid": 2804,
|
|
25
|
+
"cloud_pid": 13704,
|
|
26
|
+
"state": "ONLINE",
|
|
27
|
+
"authority": "Process/Coding/Autonomous/Desktop/Host ARMED standing-grant"
|
|
28
|
+
},
|
|
29
|
+
"findings": [
|
|
30
|
+
{
|
|
31
|
+
"id": "reference-biff8",
|
|
32
|
+
"classification": "REFERENCE_DEFECT",
|
|
33
|
+
"red": "real OLE/BIFF8 fixture is rejected by the reference ExcelJS xlsx reader",
|
|
34
|
+
"green": "independent BIFF8 fixture test proves reference read rejection and OOXML bytes emitted under .xls",
|
|
35
|
+
"fixture_sha256": "096168376d0abf282546749fdf6765309f895d12fe81d45c52a2c54bb6c0d139"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "incremental-pdf-revisions",
|
|
39
|
+
"classification": "REAL_DEADBYTE_DEFECT_FIXED_IN_SOURCE",
|
|
40
|
+
"red": "valid independent-oracle-readable incremental PDF failed with PDF duplicate object 3 0",
|
|
41
|
+
"green": "latest revision accepted only when both revisions are bound by a validated classic xref Prev chain; PDF suite 10/10 PASS"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": "powershell-command-byte-preservation",
|
|
45
|
+
"classification": "OBSERVED_PASS",
|
|
46
|
+
"green": "real policy-bound PowerShell REPL preserved variables, quotes, pipe and Unicode and returned more than 1 MiB through bounded chunk reads"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "atomic-mutation-post-effect-observation",
|
|
50
|
+
"classification": "REAL_DEADBYTE_DEFECT_FIXED_IN_SOURCE",
|
|
51
|
+
"red": "machine filesystem, coding, and Host public boundaries collapsed a post-rename observation failure into a generic denied/failed response without uncertainty identity",
|
|
52
|
+
"green": "shared atomic mutation marks FAILED_AFTER_UNKNOWN_EFFECT with unknown side-effect state and expected-after SHA-256; machine filesystem, coding, and Host structured outputs preserve it"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": "read-file-directory-and-binary-semantics",
|
|
56
|
+
"classification": "REAL_DEADBYTE_DEFECT_FIXED_IN_SOURCE",
|
|
57
|
+
"red": "reference returns a bounded recursive listing for read_file(directory) and explicit binary guidance, while DEADBYTE attempted a file read or lossy UTF-8 decoding",
|
|
58
|
+
"green": "compatibility read_file uses policy-bound stat plus paged listing for directories and deterministic binary classification without replacement-character data loss"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "docx-outline-and-content-search",
|
|
62
|
+
"classification": "REAL_DEADBYTE_DEFECT_FIXED_IN_SOURCE",
|
|
63
|
+
"red": "reference exposes drawing markers, header/footer text, and literal DOCX part search while DEADBYTE omitted those document parts and searched ZIP bytes",
|
|
64
|
+
"green": "native bounded DOCX parsing now exposes drawing/header/footer evidence and searches document/header/footer w:t parts through deterministic compatibility sessions"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "interactive-output-quiescence",
|
|
68
|
+
"classification": "REAL_DEADBYTE_DEFECT_FIXED_IN_SOURCE",
|
|
69
|
+
"red": "interact_with_process returned after the first stream advance and could omit the final END marker from a 1.1 MiB response",
|
|
70
|
+
"green": "bounded stream-quiescence waiting plus unconditional test cleanup passed 20 consecutive real PowerShell repetitions and the expanded 139-test hardening gate"
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
"focused_evidence": [
|
|
74
|
+
{"scope": "schema-search", "tests": 21, "pass": 21, "fail": 0, "skip": 0},
|
|
75
|
+
{"scope": "documents-wire-url", "tests": 49, "pass": 49, "fail": 0, "skip": 0},
|
|
76
|
+
{"scope": "biff-reference", "tests": 1, "pass": 1, "fail": 0, "skip": 0},
|
|
77
|
+
{"scope": "process-adjacent", "tests": 55, "pass": 55, "fail": 0, "skip": 0},
|
|
78
|
+
{"scope": "mcp-protocol", "tests": 9, "pass": 9, "fail": 0, "skip": 0},
|
|
79
|
+
{"scope": "mcp-protocol-adversarial", "tests": 3, "pass": 3, "fail": 0, "skip": 0},
|
|
80
|
+
{"scope": "atomic-mutation-public-boundaries", "tests": 15, "pass": 14, "fail": 0, "skip": 1},
|
|
81
|
+
{"scope": "full-regression-r48", "files": 211, "batches": 8, "fail": 0, "task_receipt_sha256": "7711039e4807f6c2af02bbda897d0032b067e0a6631f789fd3bf801258160d3f"},
|
|
82
|
+
{"scope": "expanded-commander-hardening", "tests": 139, "pass": 139, "fail": 0, "skip": 0},
|
|
83
|
+
{"scope": "powershell-large-output-repeat", "runs": 20, "pass": 20, "fail": 0},
|
|
84
|
+
{"scope": "npm-audit-production", "vulnerabilities": 0},
|
|
85
|
+
{"scope": "real-performance-sample", "iterations_per_workload": 50, "reference_byte_pins_verified": true, "status": "HISTORICAL_ONLY_FRESH_RUN_BLOCKED_REFERENCE_RUNTIME_DEPS",
|
|
86
|
+
"results": {"read_file": {"reference_ops_per_sec": 55, "deadbyte_ops_per_sec": 1058}, "list_directory": {"reference_ops_per_sec": 3818, "deadbyte_ops_per_sec": 1959}, "get_file_info": {"reference_ops_per_sec": 1805, "deadbyte_ops_per_sec": 2231}, "read_multiple_files": {"reference_ops_per_sec": 30, "deadbyte_ops_per_sec": 562}}}
|
|
87
|
+
,{"scope": "filesystem-document-search-adjacent", "tests": 54, "pass": 54, "fail": 0, "skip": 0}
|
|
88
|
+
],
|
|
89
|
+
"changed_file_sha256": {
|
|
90
|
+
"src/commander-pdf-r48.mjs": "23cda18e86daa4163003fb5a80e0069bc19541d454f32a9a38b1d901ee39f26b",
|
|
91
|
+
"src/coding-search.mjs": "7f15b59bc240d896beddc2057354f8c72f296fe189db30c200d2c84a67d65773",
|
|
92
|
+
"src/commander-wire-adapter-r47.mjs": "a32226e302669553d39565176fc7ef9a0ace24834d2c19d3558ba33f13016ba0",
|
|
93
|
+
"src/commander-office-reader-r48.mjs": "7379825118da8fae16d63fe7c4d89b0ddb03e7649f3754adae58a4480f8b317f",
|
|
94
|
+
"test/commander-pdf-r48.test.mjs": "bfc2e6d7e1aa5332c1c81687001cdb5f923a45c5ce64925097fe11fd850e9214",
|
|
95
|
+
"test/commander-biff-reference-r48.test.mjs": "8e8b20604062a0398214e159729b10bedff334cb44bddfa4b4046b1ae396520f",
|
|
96
|
+
"test/commander-schema-differential-r48.test.mjs": "fb82a21e8752ccb82cbd6b631f3366bfe5af9a4c5a433db5a128c0a1fe703c66",
|
|
97
|
+
"test/commander-real-parity-r47.test.mjs": "5709ea1cf4e65c4f1759206123216cac6cb66d8a383c0b0a6c00f4a551d9c727"
|
|
98
|
+
,"test/commander-wire-contract-r47.test.mjs": "d8e884e241c705b12797f911fb967a4a33b1ec6f3e15b433735882b3de4a34e5"
|
|
99
|
+
,"test/commander-office-reader-r48.test.mjs": "abf1eb0977a064101420a0e908a4c2adbf9d606e9873433709910efafa2b4453"
|
|
100
|
+
,"test/commander-core-bridge-r47.test.mjs": "16092ed097420b9da99573c1e84f62d011fcb5db069242d70ffd1649c704fd35"
|
|
101
|
+
,"src/atomic-mutation.mjs": "622ee1d3c34c50a6343788cc7312e47cb43a87bdace78fb37ca7f51d453b19ad"
|
|
102
|
+
,"src/machine-fs-mcp-tools.mjs": "5413fea537f9d72f73e89137b4c750530fbca41598b16bd9ede0e1c272111488"
|
|
103
|
+
,"src/machine-fs-output-contracts.mjs": "d94e4478b7e465fa559065e508f65b9ef3eff815a8f2d7b2481bf2a0b05224c9"
|
|
104
|
+
,"src/coding-mcp-tools.mjs": "c4e07aa6818cd989c43ce8d027dc7d467e05c8e2fe96b5548eea291285d3bbb1"
|
|
105
|
+
,"src/coding-output-contracts.mjs": "059b4bef2ae6a6c2d7a706c463bea6e49d364faeb619372a429ba36c53004ab7"
|
|
106
|
+
,"src/host-plane.mjs": "0ff88bce7ee34b22f48a1494efb16586b68c74bd46c608116eb9d9daa65fdc43"
|
|
107
|
+
,"src/output-contracts.mjs": "6cbe72d14af097dde78e989bc7b28b48b9cb8990c989d0d40783633f3a7be465"
|
|
108
|
+
,"src/mcp-server.mjs": "16e2250e20710b358b304a0bc41bb416ecb9a586c50d5933f6a4eb2230569b21"
|
|
109
|
+
,"test/atomic-mutation.test.mjs": "98de1fc43092c27e0fa85f391607494b11e6cc0d53eaf47b6db1ab457c6d6a4b"
|
|
110
|
+
,"test/coding-mcp-unknown-effect.test.mjs": "f37e260c2def4c48434b3f251a4cb756a1852843784c08cf6c39dba9045dfb69"
|
|
111
|
+
,"scripts/commander-real-performance-benchmark-r47.mjs": "d7806aa408dc7ca74f05feec535ad6cc545f8c61892c51f0fe388bb13d975290"
|
|
112
|
+
,"scripts/commander-hardening-gate-r47.mjs": "6d6e18b2a17448dad569dfbf60ac103c6c8bcfe73c49af93506816c3c0b7fe73"
|
|
113
|
+
,"src/stdio-protocol-guard.mjs": "71e0ddc5e7abaedbb4659bdfb91b6e2db43a642cbec83eee62c16fb069a3c202"
|
|
114
|
+
,"test/commander-protocol-adversarial-r48.test.mjs": "bacb96828ea0431cd5f3a8ecb637894f0bdf80b5f52e9308ba0cbb982f50bd2a"
|
|
115
|
+
},
|
|
116
|
+
"release_created": true,
|
|
117
|
+
"candidate_version": "0.15.25",
|
|
118
|
+
"publish_attempt": {"status":"PENDING_AUTH","npm_whoami":"E401 Unauthorized","registry_predecessor_version":"0.15.24","registry_predecessor_tarball_sha256":"23ea53fb4f331873c742c810f815d5ad18291f74c1bda1fd1d8c08475dbbd4ad"},
|
|
119
|
+
"published": false,
|
|
120
|
+
"activated": false,
|
|
121
|
+
"remaining_scope": "restore npm authentication, registry round-trip, successor activation, exact 0.15.23 rollback, exact successor restore and independent final closure"
|
|
122
|
+
}
|
|
@@ -13,7 +13,16 @@ const suites = [
|
|
|
13
13
|
'test/commander-exhaustive-surface-r47.test.mjs',
|
|
14
14
|
'test/commander-core-bridge-r47.test.mjs',
|
|
15
15
|
'test/commander-wire-contract-r47.test.mjs',
|
|
16
|
-
'test/commander-
|
|
16
|
+
'test/commander-reference-contract-r48.test.mjs',
|
|
17
|
+
'test/commander-schema-differential-r48.test.mjs',
|
|
18
|
+
'test/commander-biff-reference-r48.test.mjs',
|
|
19
|
+
'test/commander-office-reader-r48.test.mjs',
|
|
20
|
+
'test/commander-pdf-r48.test.mjs',
|
|
21
|
+
'test/commander-url-reader-r48.test.mjs',
|
|
22
|
+
'test/commander-external-process-r48.test.mjs',
|
|
23
|
+
'test/commander-protocol-r47.test.mjs',
|
|
24
|
+
'test/commander-protocol-adversarial-r48.test.mjs',
|
|
25
|
+
'test/prompts-resources.test.mjs'
|
|
17
26
|
];
|
|
18
27
|
|
|
19
28
|
const startedAt = new Date().toISOString();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { performance } from 'node:perf_hooks';
|
|
2
|
-
import { mkdtemp, writeFile, rm } from 'node:fs/promises';
|
|
2
|
+
import { mkdtemp, writeFile, readFile, rm } from 'node:fs/promises';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { createHash } from 'node:crypto';
|
|
@@ -9,7 +9,16 @@ import { loadCodingPolicy } from '../src/coding-plane.mjs';
|
|
|
9
9
|
import { createMachineFsRuntime } from '../src/machine-fs.mjs';
|
|
10
10
|
|
|
11
11
|
const N=50;
|
|
12
|
-
const
|
|
12
|
+
const referenceRoot=path.resolve(process.env.DEADBYTE_COMMANDER_REFERENCE_RUNTIME??'D:/Projects/desktop-commander-stage-20260922/desktop-commander');
|
|
13
|
+
const expectedReferenceHashes={
|
|
14
|
+
'dist/server.js':'dfc303bb7dd2e8d01debac50fc43bb8644d00089bffdf2887e149497cff8b305',
|
|
15
|
+
'dist/tools/schemas.js':'f342345e34e48769dabaefe1b932f09d41e2e207b422f7794a15ee50ffa8f140'
|
|
16
|
+
};
|
|
17
|
+
for(const [relative,expected] of Object.entries(expectedReferenceHashes)){
|
|
18
|
+
const actual=createHash('sha256').update(await readFile(path.join(referenceRoot,relative))).digest('hex');
|
|
19
|
+
if(actual!==expected)throw new Error(`reference byte pin mismatch: ${relative}`);
|
|
20
|
+
}
|
|
21
|
+
const commanderFs=await import(pathToFileURL(path.join(referenceRoot,'dist','tools','filesystem.js')).href);
|
|
13
22
|
const { readFileFromDisk, readMultipleFiles, listDirectory, getFileInfo }=commanderFs;
|
|
14
23
|
const sha=b=>createHash('sha256').update(b).digest('hex');
|
|
15
24
|
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-commander-bench-'));
|
|
@@ -37,5 +46,5 @@ rows.push(await t('Commander get_file_info',()=>getFileInfo(commanderFile)));
|
|
|
37
46
|
rows.push(await t('DEADBYTE get_file_info',()=>db.stat({rootId:'bench',relativePath:'a.txt',hash:true})));
|
|
38
47
|
rows.push(await t('Commander read_multiple_files',()=>readMultipleFiles([path.join(commanderDir,'a.txt'),path.join(commanderDir,'b.txt')])));
|
|
39
48
|
rows.push(await t('DEADBYTE read_multiple_files', async()=>{ await db.read({rootId:'bench',relativePath:'a.txt',offset:0,maxBytes:4096}); await db.read({rootId:'bench',relativePath:'b.txt',offset:0,maxBytes:4096}); }));
|
|
40
|
-
console.log(JSON.stringify({schema:'deadbyte.commander-real-performance-benchmark.v1',reference:'@wonderwhy-er/desktop-commander
|
|
49
|
+
console.log(JSON.stringify({schema:'deadbyte.commander-real-performance-benchmark.v1',reference:'@wonderwhy-er/desktop-commander@0.2.51',reference_hashes:expectedReferenceHashes,iterations:N,rows},null,2));
|
|
41
50
|
await rm(root,{recursive:true,force:true});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
$ErrorActionPreference = 'Stop'
|
|
2
|
+
Set-StrictMode -Version 3.0
|
|
3
|
+
|
|
4
|
+
function Invoke-GateStep {
|
|
5
|
+
param([Parameter(Mandatory)][string]$Name,[Parameter(Mandatory)][string[]]$NpmArgs)
|
|
6
|
+
Write-Host "==== $Name ===="
|
|
7
|
+
& npm.cmd @NpmArgs
|
|
8
|
+
if ($LASTEXITCODE -ne 0) { throw "DEADBYTE 0.15.25 / R48 gate failed at $Name with exit code $LASTEXITCODE" }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
Invoke-GateStep 'containment-build' @('run','containment:build')
|
|
12
|
+
Invoke-GateStep 'process-policy-refresh' @('run','process:policy:refresh')
|
|
13
|
+
Invoke-GateStep 'desktop-policy-refresh' @('run','desktop:policy:refresh')
|
|
14
|
+
Invoke-GateStep 'hardware-build' @('run','hardware:build')
|
|
15
|
+
|
|
16
|
+
$hostPolicy = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\controller\deadbyte-host-policy.json'))
|
|
17
|
+
Remove-Item -LiteralPath $hostPolicy -Force -ErrorAction SilentlyContinue
|
|
18
|
+
if (Test-Path -LiteralPath $hostPolicy) { throw 'could not establish hermetic Host policy absence' }
|
|
19
|
+
|
|
20
|
+
Invoke-GateStep 'r48-release-focused' @('run','r48:release:test')
|
|
21
|
+
Invoke-GateStep 'r48-catalog' @('run','r48:catalog:test')
|
|
22
|
+
Invoke-GateStep 'commander-hardening' @('run','r47:hardening:test')
|
|
23
|
+
Invoke-GateStep 'full-regression' @('run','r48:full:test')
|
|
24
|
+
Invoke-GateStep 'mcp-smoke' @('run','mcp:smoke')
|
|
25
|
+
Invoke-GateStep 'mcp-agent-smoke' @('run','mcp:agent:smoke')
|
|
26
|
+
Invoke-GateStep 'mcp-coding-smoke' @('run','mcp:coding:smoke')
|
|
27
|
+
Invoke-GateStep 'mcp-machine-fs-smoke' @('run','mcp:machine-fs:smoke')
|
|
28
|
+
Invoke-GateStep 'mcp-observation-smoke' @('run','mcp:observation:smoke')
|
|
29
|
+
Invoke-GateStep 'mcp-process-smoke' @('run','mcp:process:smoke')
|
|
30
|
+
Invoke-GateStep 'mcp-autonomous-smoke' @('run','mcp:autonomous:smoke')
|
|
31
|
+
Invoke-GateStep 'tunnel-stdio-compat-smoke' @('run','mcp:tunnel-compat:smoke')
|
|
32
|
+
Invoke-GateStep 'containment-smoke' @('run','containment:smoke')
|
|
33
|
+
Invoke-GateStep 'contained-smoke' @('run','contained:smoke')
|
|
34
|
+
Invoke-GateStep 'mcp-contained-smoke' @('run','mcp:contained:smoke')
|
|
35
|
+
Invoke-GateStep 'host-policy-refresh' @('run','host:policy:init')
|
|
36
|
+
Invoke-GateStep 'manifest-write' @('run','manifest:write')
|
|
37
|
+
Invoke-GateStep 'manifest-verify' @('run','manifest:verify')
|
|
38
|
+
Invoke-GateStep 'doctor' @('run','doctor')
|
|
39
|
+
Invoke-GateStep 'mcp-host-smoke' @('run','mcp:host:smoke')
|
|
40
|
+
Invoke-GateStep 'authority-smoke' @('run','authority:smoke')
|
|
41
|
+
Invoke-GateStep 'manifest-final-verify' @('run','manifest:verify')
|
|
42
|
+
|
|
43
|
+
& node.exe (Join-Path $PSScriptRoot 'windows-gate-evidence-r48.mjs')
|
|
44
|
+
if ($LASTEXITCODE -ne 0) { throw "R48 evidence writer failed with exit code $LASTEXITCODE" }
|
|
45
|
+
Write-Host '==== DEADBYTE 0.15.25 / R48 WINDOWS GATE: PASS ===='
|
|
@@ -10,7 +10,15 @@ Set-StrictMode -Version 3.0
|
|
|
10
10
|
$ErrorActionPreference = 'Stop'
|
|
11
11
|
|
|
12
12
|
function Get-Sha256Lower([string]$Path) {
|
|
13
|
-
|
|
13
|
+
$stream = [System.IO.File]::OpenRead($Path)
|
|
14
|
+
try {
|
|
15
|
+
$sha = [System.Security.Cryptography.SHA256]::Create()
|
|
16
|
+
try {
|
|
17
|
+
([System.BitConverter]::ToString($sha.ComputeHash($stream))).Replace('-', '').ToLowerInvariant()
|
|
18
|
+
}
|
|
19
|
+
finally { $sha.Dispose() }
|
|
20
|
+
}
|
|
21
|
+
finally { $stream.Dispose() }
|
|
14
22
|
}
|
|
15
23
|
|
|
16
24
|
if ([string]::IsNullOrWhiteSpace($CoreRoot)) {
|
|
@@ -105,7 +105,8 @@ async function main() {
|
|
|
105
105
|
|
|
106
106
|
console.log('[3/7] Packing artifact with npm pack...');
|
|
107
107
|
const packed = parseJson(runNpm(['pack', '--json'], { timeout: 180000 }));
|
|
108
|
-
const
|
|
108
|
+
const packRecord = Array.isArray(packed) ? packed[0] : packed?.[pkg.name];
|
|
109
|
+
const filename = packRecord?.filename;
|
|
109
110
|
if (!filename) throw new Error('npm pack did not return a tarball filename');
|
|
110
111
|
const tarball = path.join(root, filename);
|
|
111
112
|
let releaseSucceeded = false;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {createHash} from 'node:crypto';
|
|
2
|
+
import {mkdir,open,readFile} from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import {fileURLToPath} from 'node:url';
|
|
6
|
+
import {doctor} from '../src/doctor-r45.mjs';
|
|
7
|
+
|
|
8
|
+
const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
|
|
9
|
+
const stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
|
|
10
|
+
const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
11
|
+
const insist=(ok,message)=>{if(!ok)throw new Error(message);};
|
|
12
|
+
const pkg=JSON.parse(await readFile(path.join(root,'package.json'),'utf8'));
|
|
13
|
+
const release=JSON.parse(await readFile(path.join(root,'RELEASE.json'),'utf8'));
|
|
14
|
+
const active=JSON.parse(await readFile(path.join(stateRoot,'active-release.json'),'utf8'));
|
|
15
|
+
const manifestSha256=sha256(await readFile(path.join(root,'MANIFEST.SHA256')));
|
|
16
|
+
insist(pkg.version==='0.15.25'&&release.version===pkg.version&&release.train==='R48','R48 candidate identity mismatch');
|
|
17
|
+
insist(active.release_id===release.parent.release_id&&active.manifest_sha256===release.parent.manifest_sha256&&active.generation_manifest_sha256===release.parent.generation_manifest_sha256,'active runtime is not the exact sealed R48 parent');
|
|
18
|
+
const health=await doctor();
|
|
19
|
+
insist(health.status==='ok'&&health.package_version===pkg.version&&health.manifest?.verified===true,'R48 doctor evidence invalid');
|
|
20
|
+
insist(health.policy?.host_v2_ready===true,'R48 Host policy v2 is not ready');
|
|
21
|
+
const evidence={schema:'deadbyte.r48-windows-gate.v1',status:'passed',exit_code:0,version:pkg.version,train:'R48',completed_at_utc:new Date().toISOString(),manifest_sha256:manifestSha256,parent:release.parent,active_predecessor:{release_id:active.release_id,version:active.version,manifest_sha256:active.manifest_sha256,generation_manifest_sha256:active.generation_manifest_sha256},doctor:{verified_entries:health.manifest.verified_entries,host_v2_ready:true},gates:{full_regression:true,commander_hardening:true,exact_catalog:true,stdio_smokes:true,manifest_final_verify:true}};
|
|
22
|
+
const bytes=Buffer.from(`${JSON.stringify(evidence,null,2)}\n`);
|
|
23
|
+
const dir=path.join(stateRoot,'evidence','release'); await mkdir(dir,{recursive:true});
|
|
24
|
+
const target=path.join(dir,`r48-windows-gate-${manifestSha256.slice(0,16)}.json`);
|
|
25
|
+
try{const handle=await open(target,'wx',0o600);try{await handle.writeFile(bytes);await handle.sync();}finally{await handle.close();}}
|
|
26
|
+
catch(error){if(error?.code!=='EEXIST')throw error;const prior=JSON.parse(await readFile(target,'utf8'));insist(prior.schema===evidence.schema&&prior.status==='passed'&&prior.version===evidence.version&&prior.manifest_sha256===manifestSha256&&prior.parent?.release_id===release.parent.release_id,'conflicting R48 gate evidence identity');}
|
|
27
|
+
console.log(JSON.stringify({status:'passed',path:target,sha256:sha256(bytes),manifest_sha256:manifestSha256}));
|
package/src/atomic-mutation.mjs
CHANGED
|
@@ -74,11 +74,19 @@ export async function atomicMutation({
|
|
|
74
74
|
throw error;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
const after = await readState(target);
|
|
78
77
|
const expectedAfter = sha256Bytes(bytes);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
try {
|
|
79
|
+
const after = await readState(target);
|
|
80
|
+
if (!after.exists || after.sha256 !== expectedAfter) throw new Error('atomic mutation post-commit hash mismatch');
|
|
81
|
+
if (verifyPostcondition) await verifyPostcondition({ before, after, expected_after_sha256: expectedAfter });
|
|
82
|
+
return { before, after, expected_after_sha256: expectedAfter };
|
|
83
|
+
} catch (error) {
|
|
84
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
85
|
+
failure.outcome = 'FAILED_AFTER_UNKNOWN_EFFECT';
|
|
86
|
+
failure.side_effect_state = 'unknown';
|
|
87
|
+
failure.expected_after_sha256 = expectedAfter;
|
|
88
|
+
throw failure;
|
|
89
|
+
}
|
|
82
90
|
} finally {
|
|
83
91
|
await releaseLock();
|
|
84
92
|
}
|
package/src/coding-mcp-tools.mjs
CHANGED
|
@@ -46,7 +46,21 @@ const taskStep = z.union([
|
|
|
46
46
|
]);
|
|
47
47
|
|
|
48
48
|
function result(schema, value, isError = false) { return codingStructuredResult(schema, value, isError); }
|
|
49
|
-
function fail(schema, error) {
|
|
49
|
+
function fail(schema, error) {
|
|
50
|
+
const base = { status: 'failed', error: String(error) };
|
|
51
|
+
if (error?.outcome === 'FAILED_AFTER_UNKNOWN_EFFECT'
|
|
52
|
+
&& error?.side_effect_state === 'unknown'
|
|
53
|
+
&& HEX64.test(error?.expected_after_sha256 ?? '')) {
|
|
54
|
+
const enriched = {
|
|
55
|
+
...base,
|
|
56
|
+
outcome: error.outcome,
|
|
57
|
+
side_effect_state: error.side_effect_state,
|
|
58
|
+
expected_after_sha256: error.expected_after_sha256
|
|
59
|
+
};
|
|
60
|
+
if (schema.safeParse(enriched).success) return result(schema, enriched, true);
|
|
61
|
+
}
|
|
62
|
+
return result(schema, base, true);
|
|
63
|
+
}
|
|
50
64
|
export function registerCodingTools(server, { codingRuntime, taskRuntime, semanticRuntime }) {
|
|
51
65
|
if (!codingRuntime || !taskRuntime || !semanticRuntime) throw new Error('coding tool registration requires runtimes');
|
|
52
66
|
|
|
@@ -168,7 +168,9 @@ export const codingSymbolReplaceOutputSchema = statusContract({
|
|
|
168
168
|
shape: { root_id: z.string().min(1).max(64).optional(), path: z.string().min(1).max(512).optional(), previous_sha256: hex64.optional(),
|
|
169
169
|
sha256: hex64.optional(), bytes: z.number().int().min(0).optional(), replacements: z.literal(1).optional(),
|
|
170
170
|
receipt_id: receiptId.optional(), receipt_sha256: hex64.optional(), request_sha256: hex64.optional(),
|
|
171
|
-
symbol_id: hex64.optional(), symbol_name: z.string().min(1).max(256).optional(), engine: z.string().min(1).max(64).optional()
|
|
171
|
+
symbol_id: hex64.optional(), symbol_name: z.string().min(1).max(256).optional(), engine: z.string().min(1).max(64).optional(),
|
|
172
|
+
outcome: z.literal('FAILED_AFTER_UNKNOWN_EFFECT').optional(), side_effect_state: z.literal('unknown').optional(),
|
|
173
|
+
expected_after_sha256: hex64.optional() },
|
|
172
174
|
successStatuses: ['committed'], successRequired: ['root_id','path','previous_sha256','sha256','bytes','replacements','receipt_id','receipt_sha256','request_sha256','symbol_id','symbol_name','engine']
|
|
173
175
|
});
|
|
174
176
|
|
|
@@ -189,14 +191,18 @@ export const codingFileWriteOutputSchema = statusContract({
|
|
|
189
191
|
statuses: ['committed', 'failed'],
|
|
190
192
|
shape: { root_id: z.string().min(1).max(64).optional(), path: z.string().min(1).max(512).optional(), previous_sha256: hex64.nullable().optional(),
|
|
191
193
|
sha256: hex64.optional(), bytes: z.number().int().min(0).optional(), created: z.boolean().optional(), receipt_id: receiptId.optional(),
|
|
192
|
-
receipt_sha256: hex64.optional(), request_sha256: hex64.optional()
|
|
194
|
+
receipt_sha256: hex64.optional(), request_sha256: hex64.optional(),
|
|
195
|
+
outcome: z.literal('FAILED_AFTER_UNKNOWN_EFFECT').optional(), side_effect_state: z.literal('unknown').optional(),
|
|
196
|
+
expected_after_sha256: hex64.optional() },
|
|
193
197
|
successStatuses: ['committed'], successRequired: ['root_id', 'path', 'previous_sha256', 'sha256', 'bytes', 'created', 'receipt_id', 'receipt_sha256', 'request_sha256']
|
|
194
198
|
});
|
|
195
199
|
export const codingTextReplaceOutputSchema = statusContract({
|
|
196
200
|
statuses: ['committed', 'failed'],
|
|
197
201
|
shape: { root_id: z.string().min(1).max(64).optional(), path: z.string().min(1).max(512).optional(), previous_sha256: hex64.optional(),
|
|
198
202
|
sha256: hex64.optional(), bytes: z.number().int().min(0).optional(), replacements: z.number().int().min(1).max(64).optional(),
|
|
199
|
-
receipt_id: receiptId.optional(), receipt_sha256: hex64.optional(), request_sha256: hex64.optional()
|
|
203
|
+
receipt_id: receiptId.optional(), receipt_sha256: hex64.optional(), request_sha256: hex64.optional(),
|
|
204
|
+
outcome: z.literal('FAILED_AFTER_UNKNOWN_EFFECT').optional(), side_effect_state: z.literal('unknown').optional(),
|
|
205
|
+
expected_after_sha256: hex64.optional() },
|
|
200
206
|
successStatuses: ['committed'], successRequired: ['root_id', 'path', 'previous_sha256', 'sha256', 'bytes', 'replacements', 'receipt_id', 'receipt_sha256', 'request_sha256']
|
|
201
207
|
});
|
|
202
208
|
export const codingCommandRunOutputSchema = statusContract({
|
|
@@ -140,7 +140,7 @@ function prettyXml(xml){
|
|
|
140
140
|
}
|
|
141
141
|
return lines.join('\n');
|
|
142
142
|
}
|
|
143
|
-
function docxOutline(xml){
|
|
143
|
+
function docxOutline(xml,files){
|
|
144
144
|
const body=xml.match(/<w:body\b[^>]*>([\s\S]*?)<\/w:body>/)?.[1];
|
|
145
145
|
if(body===undefined) return '[No w:body found in document.xml]';
|
|
146
146
|
const elements=[...body.matchAll(/<w:(p|tbl)\b[\s\S]*?<\/w:\1>/g)].map(m=>({kind:m[1],xml:m[0]}));
|
|
@@ -152,6 +152,8 @@ function docxOutline(xml){
|
|
|
152
152
|
let line=`[${i}] w:p`;
|
|
153
153
|
if(style) line+=` style="${xmlDecode(style)}"`;
|
|
154
154
|
const value=wordText(item.xml);
|
|
155
|
+
const hasDrawing=item.xml.includes('<w:drawing')||item.xml.includes('<mc:AlternateContent');
|
|
156
|
+
if(hasDrawing) line+=value?' (+ drawing/image)':` [drawing/image, ${(Buffer.byteLength(item.xml)/1024).toFixed(1)}KB]`;
|
|
155
157
|
if(value) line+='\n '+value;
|
|
156
158
|
lines.push(line);
|
|
157
159
|
}else{
|
|
@@ -163,7 +165,15 @@ function docxOutline(xml){
|
|
|
163
165
|
}
|
|
164
166
|
}
|
|
165
167
|
}
|
|
166
|
-
|
|
168
|
+
const headersFooters=[];
|
|
169
|
+
for(const [name,bytes] of [...files.entries()].sort(([a],[b])=>a.localeCompare(b))){
|
|
170
|
+
if(!/^word\/(?:header|footer)[^/]*\.xml$/i.test(name)) continue;
|
|
171
|
+
try{
|
|
172
|
+
const value=wordText(decodeUtf8(bytes,name));
|
|
173
|
+
headersFooters.push(`${name.slice('word/'.length)}: ${value?`"${value.length>100?value.slice(0,100)+'...':value}"`:'(no text content)'}`);
|
|
174
|
+
}catch{/* malformed optional part is omitted; document.xml remains authoritative */}
|
|
175
|
+
}
|
|
176
|
+
return lines.join('\n')+(headersFooters.length?`\n\nHeaders/Footers:\n${headersFooters.join('\n')}`:'');
|
|
167
177
|
}
|
|
168
178
|
|
|
169
179
|
export function readDocxBytes(input,{path:displayPath='document.docx',offset=0,length=1000}={}){
|
|
@@ -180,11 +190,34 @@ export function readDocxBytes(input,{path:displayPath='document.docx',offset=0,l
|
|
|
180
190
|
return {content:`[DOCX XML: lines ${start}-${Math.max(start,start+sliced.length-1)} of ${totalLines} (${remaining} remaining)]\n${sliced.join('\n')}`,
|
|
181
191
|
mimeType:'application/xml',metadata:{isDocx:true,lineCount:totalLines},outline:false};
|
|
182
192
|
}
|
|
183
|
-
const outline=docxOutline(xml);
|
|
193
|
+
const outline=docxOutline(xml,files);
|
|
184
194
|
const rawSizeKB=(Buffer.byteLength(xml)/1024).toFixed(1);
|
|
185
195
|
return {content:`${outline}\n\nRaw XML: ${totalLines} lines, ${rawSizeKB}KB.`,mimeType:'text/plain',metadata:{isDocx:true,lineCount:totalLines},outline:true};
|
|
186
196
|
}
|
|
187
197
|
|
|
198
|
+
export function searchDocxTextBytes(input,{pattern,ignoreCase=true,maxResults=500}={}){
|
|
199
|
+
if(typeof pattern!=='string'||pattern.length<1||pattern.length>4096) throw new Error('DOCX search pattern invalid');
|
|
200
|
+
if(!Number.isInteger(maxResults)||maxResults<1||maxResults>5000) throw new Error('DOCX search maxResults invalid');
|
|
201
|
+
const files=unzipOffice(input),needle=ignoreCase?pattern.toLowerCase():pattern,results=[];
|
|
202
|
+
const names=[...files.keys()].filter(name=>name==='word/document.xml'||/^word\/(?:header|footer)[^/]*\.xml$/i.test(name)).sort((a,b)=>{
|
|
203
|
+
if(a==='word/document.xml')return -1;if(b==='word/document.xml')return 1;return a.localeCompare(b);
|
|
204
|
+
});
|
|
205
|
+
for(const name of names){
|
|
206
|
+
let xml;
|
|
207
|
+
try{xml=decodeUtf8(files.get(name),name);}catch{continue;}
|
|
208
|
+
let line=0;
|
|
209
|
+
for(const match of xml.matchAll(/<w:t(?:\s[^>]*)?>([^<]*)<\/w:t>/g)){
|
|
210
|
+
const value=xmlDecode(match[1]??'');if(!value.trim())continue;line++;
|
|
211
|
+
const haystack=ignoreCase?value.toLowerCase():value,index=haystack.indexOf(needle);
|
|
212
|
+
if(index<0)continue;
|
|
213
|
+
const start=Math.max(0,index-50),end=Math.min(value.length,index+needle.length+50);
|
|
214
|
+
results.push({part:name,line,match:`${start>0?'...':''}${value.slice(start,end)}${end<value.length?'...':''}`});
|
|
215
|
+
if(results.length>=maxResults)return results;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return results;
|
|
219
|
+
}
|
|
220
|
+
|
|
188
221
|
|
|
189
222
|
export function createDocxBytes(content){
|
|
190
223
|
if(typeof content!=='string') throw new Error('DOCX content must be string');
|
|
@@ -110,6 +110,37 @@ export function parseDeadbytePdf(input){
|
|
|
110
110
|
const MAX_GENERIC_OBJECTS=10000;
|
|
111
111
|
const MAX_GENERIC_STREAM_BYTES=16*1024*1024;
|
|
112
112
|
|
|
113
|
+
function classicIncrementalObjectOffsets(text){
|
|
114
|
+
const starts=[...text.matchAll(/startxref\s+(\d+)\s+%%EOF/g)];
|
|
115
|
+
if(starts.length<2)return new Map();
|
|
116
|
+
const offsets=new Map(),visited=new Set();
|
|
117
|
+
let cursor=Number(starts.at(-1)[1]);
|
|
118
|
+
while(Number.isSafeInteger(cursor)&&cursor>=0&&!visited.has(cursor)){
|
|
119
|
+
visited.add(cursor);
|
|
120
|
+
if(text.slice(cursor,cursor+4)!=='xref')return new Map();
|
|
121
|
+
const trailerAt=text.indexOf('trailer',cursor+4);
|
|
122
|
+
if(trailerAt<0)return new Map();
|
|
123
|
+
const lines=text.slice(cursor+4,trailerAt).trim().split(/\r?\n/);
|
|
124
|
+
for(let i=0;i<lines.length;){
|
|
125
|
+
const header=lines[i++].trim().match(/^(\d+)\s+(\d+)$/);if(!header)return new Map();
|
|
126
|
+
const first=Number(header[1]),count=Number(header[2]);
|
|
127
|
+
if(!Number.isSafeInteger(first)||!Number.isSafeInteger(count)||count<0||count>MAX_GENERIC_OBJECTS)return new Map();
|
|
128
|
+
for(let n=0;n<count;n++){
|
|
129
|
+
const entry=lines[i++]?.trim().match(/^(\d{10})\s+(\d{5})\s+([nf])$/);if(!entry)return new Map();
|
|
130
|
+
if(entry[3]==='n'){
|
|
131
|
+
const key=`${first+n} ${Number(entry[2])}`;
|
|
132
|
+
const list=offsets.get(key)??[];list.push(Number(entry[1]));offsets.set(key,list);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const trailerEnd=text.indexOf('startxref',trailerAt+7);if(trailerEnd<0)return new Map();
|
|
137
|
+
const prev=text.slice(trailerAt+7,trailerEnd).match(/\/Prev\s+(\d+)/)?.[1];
|
|
138
|
+
if(prev===undefined)break;
|
|
139
|
+
const next=Number(prev);if(!Number.isSafeInteger(next)||next>=cursor)return new Map();cursor=next;
|
|
140
|
+
}
|
|
141
|
+
return offsets;
|
|
142
|
+
}
|
|
143
|
+
|
|
113
144
|
function decodePdfTextBytes(bytes){
|
|
114
145
|
if(bytes.length>=2&&bytes[0]===0xfe&&bytes[1]===0xff){
|
|
115
146
|
let out='';
|
|
@@ -186,6 +217,7 @@ function parseGenericPdfObjects(bytes){
|
|
|
186
217
|
const text=bytes.toString('latin1');
|
|
187
218
|
if(!text.startsWith('%PDF-')) throw new Error('invalid PDF header');
|
|
188
219
|
if(/\/Encrypt\b/.test(text)) throw new Error('encrypted PDF is unsupported');
|
|
220
|
+
const incrementalOffsets=classicIncrementalObjectOffsets(text);
|
|
189
221
|
const objects=new Map(),header=/(\d+)\s+(\d+)\s+obj\b/g;
|
|
190
222
|
let match,count=0;
|
|
191
223
|
while((match=header.exec(text))){
|
|
@@ -193,7 +225,10 @@ function parseGenericPdfObjects(bytes){
|
|
|
193
225
|
const end=text.indexOf('endobj',header.lastIndex);
|
|
194
226
|
if(end<0) throw new Error(`PDF object ${match[1]} ${match[2]} missing endobj`);
|
|
195
227
|
const key=`${match[1]} ${match[2]}`;
|
|
196
|
-
if(objects.has(key))
|
|
228
|
+
if(objects.has(key)){
|
|
229
|
+
const revisions=incrementalOffsets.get(key)??[];
|
|
230
|
+
if(!revisions.includes(objects.get(key).offset)||!revisions.includes(match.index)) throw new Error(`PDF duplicate object ${key}`);
|
|
231
|
+
}
|
|
197
232
|
objects.set(key,{id:Number(match[1]),generation:Number(match[2]),body:text.slice(header.lastIndex,end),offset:match.index});
|
|
198
233
|
header.lastIndex=end+6;
|
|
199
234
|
}
|