deadbyte-mcp 0.15.43 → 0.15.45
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 +160 -161
- package/PROVENANCE.json +257 -232
- package/README.txt +18 -18
- package/RELEASE.json +348 -320
- package/SBOM.cdx.json +14 -2
- package/TEST-MATRIX.json +503 -460
- package/bin/deadbyte-native-inspect-r67.exe +0 -0
- package/bridge/package.json +1 -1
- package/controller/deadbyte-controller.ps1 +1857 -1769
- package/controller/deadbyte-desktop-policy.json +6 -6
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R67-ALPHA-REFERENCE-DIFFERENTIAL.md +42 -0
- package/docs/R67-ALPHA-STATIC-COMPARISON.json +205 -0
- package/docs/R67-CANDIDATE-PARENT.json +10 -0
- package/docs/R67-EVIDENCE-RECONCILE-CLOSEOUT-20261011.md +64 -0
- package/docs/R67-FULL-REGRESSION-UNKNOWN.json +27 -0
- package/docs/R67-NATIVE-INSPECT-BINARY.json +11 -0
- package/docs/R67-NPM-EXDEV-PRESERVATION-20261011.md +39 -0
- package/docs/R67-OBSERVATION-TRUTH.md +82 -0
- package/docs/R67-REFERENCE-GAP-MATRIX.json +57 -0
- package/docs/R67-REGRESSION-CLOSURE-20261011.md +24 -0
- package/docs/R68-COMPAT-HOST-EXEC-CANDIDATE.md +63 -0
- package/docs/R69-LIVE-OUTCOME-R70-POLICY-TRANSFER.md +136 -0
- package/docs/R70-CODING-DIGEST-SUPERVISOR-CHECKPOINT-20261013.md +42 -0
- package/docs/R70-FINITE-GRANT-OWNER-BOUNDARY-20261013.md +80 -0
- package/docs/R70-OWNER-BOOTSTRAP-CHECKPOINT-20261013.md +60 -0
- package/docs/R70-REFERENCE-MECHANISMS.json +95 -0
- package/docs/R70-TRUST-GUARD-EXPIRY-CHECKPOINT-20261013.md +47 -0
- package/native/deadbyte-native-inspect-r67.cpp +252 -0
- package/package.json +6 -3
- package/scripts/benchmark-l0-r71.mjs +76 -0
- package/scripts/build-native-inspector-r67.ps1 +28 -0
- package/scripts/gate-windows-r67.ps1 +56 -0
- package/scripts/gate-windows-r71.ps1 +64 -0
- package/scripts/npm-preserve-artifact-r67.mjs +99 -0
- package/scripts/npm-release-r48.mjs +12 -2
- package/scripts/npm-release-readiness-r67.mjs +26 -0
- package/scripts/npm-release-readiness-r71.mjs +21 -0
- package/scripts/r67-acquire-alpha-reference.mjs +95 -0
- package/scripts/r67-audit-nonpass-source.mjs +39 -0
- package/scripts/r67-compare-desktop-alpha.mjs +48 -0
- package/scripts/r67-diagnose-failed-shard.mjs +23 -0
- package/scripts/r67-durable-batch.mjs +42 -0
- package/scripts/r67-focused-gate.mjs +58 -0
- package/scripts/r67-full-regression.mjs +28 -0
- package/scripts/r67-independent-observation-verify.mjs +36 -0
- package/scripts/r67-independent-runtime-observation-verify.mjs +63 -0
- package/scripts/r67-nonpass-adjudicate.mjs +20 -0
- package/scripts/r67-publish-boundary-audit.mjs +32 -0
- package/scripts/r67-regression-reconcile.mjs +10 -0
- package/scripts/r67-regression-shard-run.mjs +70 -0
- package/scripts/r67-run-shard-batch.mjs +18 -0
- package/scripts/r67-stage-candidate.mjs +30 -0
- package/scripts/r67-verify-parent-ping.mjs +22 -0
- package/scripts/release-manifest.mjs +1 -0
- package/scripts/verify-r70-full-regression.ps1 +72 -0
- package/src/activation-continuity-engine-r65.mjs +2 -1
- package/src/authority-continuity-r65.mjs +28 -8
- package/src/autonomous-mcp-tools.mjs +1 -1
- package/src/autonomous-output-contracts.mjs +4 -1
- package/src/autonomous-policy.mjs +54 -7
- package/src/autonomous-runtime.mjs +1 -1
- package/src/capability-ledger.mjs +1 -1
- package/src/capability-resolver.mjs +2 -1
- package/src/commander-compat-runtime.mjs +30 -2
- package/src/deadbyte-cli.mjs +41 -7
- package/src/machine-fs-smoke-client.mjs +13 -5
- package/src/machine-identity.mjs +59 -2
- package/src/machine-mcp-tools.mjs +8 -0
- package/src/mcp-server.mjs +11 -2
- package/src/native-diff-v2.mjs +18 -5
- package/src/native-diff.mjs +21 -5
- package/src/native-inspector-r67.mjs +66 -0
- package/src/r67-diagnostic-export.mjs +118 -0
- package/src/r67-diagnostics-verify.mjs +82 -0
- package/src/r67-evidence-reconcile.mjs +128 -0
- package/src/r67-native-observation-authority.mjs +45 -0
- package/src/r67-observation-receipt.mjs +43 -0
- package/src/r67-regression-shards.mjs +176 -0
- package/src/r70-owner-approval.mjs +78 -0
- package/src/remote-live-log.mjs +62 -24
- package/src/remote-result-journal.mjs +1 -1
- package/src/runtime-update.mjs +2051 -1891
- package/src/tool-audit.mjs +3 -1
- package/src/version.mjs +1 -1
- package/test/agent-runtime.test.mjs +1 -1
- package/test/autonomous-local-supervisor.test.mjs +2 -1
- package/test/autonomous-loop.test.mjs +11 -6
- package/test/autonomous-output-contract.test.mjs +17 -1
- package/test/autonomous-runtime.test.mjs +1 -1
- package/test/autonomous-supervisor-integration.test.mjs +2 -1
- package/test/capability-resolver.test.mjs +17 -0
- package/test/codex-autonomous-r44.test.mjs +1 -1
- package/test/coding-plane.test.mjs +1 -1
- package/test/commander-compat-runtime.test.mjs +42 -0
- package/test/controller-policy-preflight-r59.test.mjs +1 -0
- package/test/full-max-catalog-r46.test.mjs +1 -1
- package/test/native-diff-topology-r36.test.mjs +35 -2
- package/test/native-diff.test.mjs +83 -3
- package/test/output-schema.test.mjs +1 -1
- package/test/r51-live-log-polling.test.mjs +12 -0
- package/test/r53-release-contract.test.mjs +1 -1
- package/test/r54-release-contract.test.mjs +1 -1
- package/test/r55-release-contract.test.mjs +1 -1
- package/test/r56-release-contract.test.mjs +1 -1
- package/test/r57-release-contract.test.mjs +1 -1
- package/test/r58-bundled-bridge.test.mjs +1 -1
- package/test/r58-release-contract.test.mjs +2 -2
- package/test/r58-windows-gate.test.mjs +1 -1
- package/test/r59-tool-surface-compat.test.mjs +9 -5
- package/test/r60-release-contract.test.mjs +3 -3
- package/test/r61-release-contract.test.mjs +1 -1
- package/test/r62-release-contract.test.mjs +1 -1
- package/test/r63-release-contract.test.mjs +1 -1
- package/test/r64-release-contract.test.mjs +1 -1
- package/test/r65-authority-runtime-epoch.test.mjs +9 -1
- package/test/r65-epoch-native-activation-integration.test.mjs +4 -4
- package/test/r65-release-contract.test.mjs +1 -1
- package/test/r66-release-contract.test.mjs +2 -1
- package/test/r67-cli-integration.test.mjs +41 -0
- package/test/r67-diagnostics-cli.test.mjs +60 -0
- package/test/r67-diagnostics-verify.test.mjs +49 -0
- package/test/r67-diagnostics-zip-oracle.test.mjs +38 -0
- package/test/r67-diagnostics.test.mjs +60 -0
- package/test/r67-evidence-reconcile.test.mjs +186 -0
- package/test/r67-isolated-slot-rollback-restore.test.mjs +63 -0
- package/test/r67-live-log-secret-canary.test.mjs +44 -0
- package/test/r67-machine-fs-smoke-epoch.test.mjs +22 -0
- package/test/r67-native-inspector.test.mjs +131 -0
- package/test/r67-native-observation-authority.test.mjs +49 -0
- package/test/r67-npm-cross-volume.test.mjs +74 -0
- package/test/r67-npm-publish-gate.test.mjs +40 -0
- package/test/r67-regression-shards.test.mjs +184 -0
- package/test/r67-release-contract.test.mjs +45 -0
- package/test/r67-runtime-observation-binding.test.mjs +86 -0
- package/test/r67-server-observation-wiring.test.mjs +12 -0
- package/test/r67-signed-receipt.test.mjs +66 -0
- package/test/r67-skip-rationale.test.mjs +31 -0
- package/test/r67-tool-annotations.test.mjs +21 -0
- package/test/r67-windows-gate-argv.test.mjs +13 -0
- package/test/r69-live-outcome.test.mjs +137 -0
- package/test/r70-autonomous-grant-expiry.test.mjs +89 -0
- package/test/r70-autonomous-policy-bootstrap.test.mjs +166 -0
- package/test/r70-controller-autonomous-root.test.mjs +104 -0
- package/test/r70-controller-grant-subset.test.mjs +28 -0
- package/test/r70-controller-owner-issue-boundary.test.mjs +80 -0
- package/test/r70-controller-policy-snapshot.test.mjs +66 -0
- package/test/r70-owner-approval.test.mjs +67 -0
- package/test/r71-full-verifier-artifact.test.mjs +28 -0
- package/test/r71-isolated-slot-rollback-restore.test.mjs +62 -0
- package/test/r71-native-diff-mcp-wire.test.mjs +75 -0
- package/test/r71-publish-fence.test.mjs +38 -0
- package/test/r71-release-identity.test.mjs +31 -0
- package/test/release-version.test.mjs +1 -1
- package/test/remote-live-log-r36.test.mjs +10 -0
- package/test/remote-live-log-r37.test.mjs +29 -0
- package/test/remote-result-journal.test.mjs +12 -0
- package/test/remote-session-cli.test.mjs +2 -1
- package/test/semantic-multilang.test.mjs +1 -1
- package/test/standing-grants.test.mjs +2 -2
- package/test/tool-audit.test.mjs +14 -0
- package/bin/appcontainer-stage.obj +0 -0
- package/bin/bootstrap-advapi32.obj +0 -0
- package/bin/bootstrap-exitcode.obj +0 -0
- package/bin/bootstrap-kernel32.obj +0 -0
- package/bin/child-control-probe.obj +0 -0
- package/bin/child-control-stage.obj +0 -0
- package/bin/contained-reverse-worker.obj +0 -0
- package/bin/contained-transform-worker.obj +0 -0
- package/bin/containment-probe.obj +0 -0
- package/bin/deadbyte-contain.obj +0 -0
- package/bin/deadbyte-desktop-input-r40.obj +0 -0
- package/bin/deadbyte-desktop-observer-r40.obj +0 -0
- package/bin/deadbyte-desktop-uia-r40.obj +0 -0
- package/bin/deadbyte-exec.obj +0 -0
- package/bin/deadbyte-hardware-observer-r45.obj +0 -0
- package/bin/deadbyte-process-host.obj +0 -0
- package/bin/deadbyte-tunnel-host.obj +0 -0
- package/conpty-input-observer.obj +0 -0
- package/miniconpty.obj +0 -0
- package/proof/CONTAINMENT-BUILD.txt +0 -163
- package/proof/MCP-SMOKE-STATUS.txt +0 -9
- package/proof/NODE-SYNTAX.txt +0 -4
- package/proof/PROOF.txt +0 -6
- package/proof/R34-TASK8-SUPERVISOR-CHECKPOINT.json +0 -29
- package/proof/R34-TASK9-MCP-SURFACE-CHECKPOINT.json +0 -28
- package/proof/SECRET-SCAN.txt +0 -4
- package/proof/SOURCE-GATES.txt +0 -16
- package/proof/STATUS.txt +0 -11
- package/proof/TARGET-V0313-CONTAINMENT-PASS.sha256 +0 -1
- package/proof/TARGET-V0313-CONTAINMENT-PASS.txt +0 -215
- package/proof/TARGET-V040-CONTAINED-IPC-FAIL.sha256 +0 -1
- package/proof/TARGET-V040-CONTAINED-IPC-FAIL.txt +0 -299
- package/proof/TARGET-V041-CONTAINED-CREATEPROCESS-203.sha256 +0 -1
- package/proof/TARGET-V041-CONTAINED-CREATEPROCESS-203.txt +0 -311
- package/proof/TARGET-V042-CONTAINED-CREATEPROCESS-203.sha256 +0 -1
- package/proof/TARGET-V042-CONTAINED-CREATEPROCESS-203.txt +0 -313
- package/proof/TARGET-V043-CONTAINED-CREATEPROCESS-203.sha256 +0 -1
- package/proof/TARGET-V043-CONTAINED-CREATEPROCESS-203.txt +0 -308
- package/proof/TARGET-V044-CONTAINED-PASS.sha256 +0 -1
- package/proof/TARGET-V044-CONTAINED-PASS.txt +0 -432
- package/proof/TARGET-V045-DUAL-OPERATION-PASS.sha256 +0 -1
- package/proof/TARGET-V045-DUAL-OPERATION-PASS.txt +0 -481
- package/proof/TARGET-V046-FIXED-EXECUTABLE-REGISTRY-PASS.sha256 +0 -1
- package/proof/TARGET-V046-FIXED-EXECUTABLE-REGISTRY-PASS.txt +0 -610
- package/proof/TARGET-V047-RUNTIME-MANIFEST-PASS.sha256 +0 -1
- package/proof/TARGET-V047-RUNTIME-MANIFEST-PASS.txt +0 -642
- package/proof/TARGET-V050-BATCH-GATE-PASS.sha256 +0 -1
- package/proof/TARGET-V050-BATCH-GATE-PASS.txt +0 -713
- package/proof/TESTS.txt +0 -686
- package/proof/TRUST-EPHEMERAL.txt +0 -4
- package/proof/V03-FREEZE.txt +0 -27
- package/proof/V041-IPC-HOTFIX.txt +0 -16
- package/proof/V042-ENV-HOTFIX.txt +0 -15
- package/proof/V043-DRIVE-CWD-HOTFIX.txt +0 -14
- package/proof/V044-LOCALAPPDATA-HOTFIX.txt +0 -18
- package/proof/V045-OPERATION-REGISTRY.txt +0 -7
- package/proof/V046-WORKER-REGISTRY.txt +0 -12
- package/proof/V047-WORKER-MANIFEST.txt +0 -18
- package/proof/V050-BATCH-TRUST-CLOSURE.txt +0 -21
- package/proof/V060-ACTIVE-NETWORK-AUTHORITY.txt +0 -29
- package/proof/V070-NODE-SYNTAX.txt +0 -36
- package/proof/V070-NODE-TESTS.txt +0 -752
- package/proof/V070-PACK-DRY-RUN.txt +0 -126
- package/proof/V070-SOURCE-GATE.txt +0 -30
- package/proof/V070-STATIC-SCAN.txt +0 -4
- package/proof/V070-WINDOWS-GATE-PENDING.txt +0 -25
- package/proof/V071-NODE-SYNTAX.txt +0 -1
- package/proof/V071-NODE-TESTS.txt +0 -796
- package/proof/V071-PACK-DRY-RUN.json +0 -656
- package/proof/V071-SOURCE-GATE.txt +0 -16
- package/proof/V071-WINDOWS-GATE-PENDING.txt +0 -24
- package/proof/V073-NODE-SYNTAX.txt +0 -4
- package/proof/V073-SOURCE-GATE.txt +0 -21
- package/proof/V073-STATIC-SCAN.txt +0 -10
- package/proof/V073-WINDOWS-GATE-PENDING.txt +0 -5
- package/proof/VERIFY.txt +0 -7
- package/proof/WINDOWS-CONTAINED-STATUS.txt +0 -5
- package/proof/WINDOWS-CONTAINMENT-STATUS.txt +0 -3
- package/proof/commander-closure-20260927/CONTINUATION.json +0 -43
- package/proof/commander-closure-20260927/RESULT.json +0 -30
- package/proof/commander-closure-20260927/SEARCH-CONTINUATION.json +0 -30
- package/proof/commander-closure-20260927/XLS-REFERENCE-PROBE.json +0 -13
- package/proof/commander-closure-20260928/DIFFERENTIAL-CHECKPOINT.json +0 -122
- package/proof/r59-full-regression-current.json +0 -13
- package/proof/r59-full-regression-current.stderr.log +0 -0
- package/proof/r59-full-regression-current.stdout.log +0 -1268
|
@@ -6,6 +6,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
6
6
|
import { Client } from '@modelcontextprotocol/client';
|
|
7
7
|
import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
|
|
8
8
|
import { CODING_GRANT_SCHEMA, loadCodingPolicy } from './coding-plane.mjs';
|
|
9
|
+
import { createAuthorityEpochStore } from './authority-continuity-r65.mjs';
|
|
9
10
|
import { verifyMachineFsReceiptFile } from './machine-fs.mjs';
|
|
10
11
|
import { generateTrustKeypair } from './trust.mjs';
|
|
11
12
|
import { assertExactCatalog, expectedFullCatalogForEnv } from './runtime-catalog-expectation.mjs';
|
|
@@ -23,14 +24,16 @@ const structured = (result, label) => {
|
|
|
23
24
|
const scratch = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-machine-fs-smoke-'));
|
|
24
25
|
const coreRoot = path.join(scratch, 'core');
|
|
25
26
|
const trustRoot = path.join(scratch, 'trust');
|
|
27
|
+
const runtimeRoot = path.join(scratch, 'runtime');
|
|
26
28
|
await mkdir(coreRoot, { recursive: true });
|
|
27
29
|
await mkdir(trustRoot, { recursive: true });
|
|
30
|
+
await mkdir(runtimeRoot, { recursive: true });
|
|
28
31
|
const keys = await generateTrustKeypair({
|
|
29
|
-
privateKeyPath: path.join(trustRoot, 'private.pem'),
|
|
30
|
-
publicKeyPath: path.join(trustRoot, 'public.pem')
|
|
32
|
+
privateKeyPath: path.join(trustRoot, 'ed25519-private.pem'),
|
|
33
|
+
publicKeyPath: path.join(trustRoot, 'ed25519-public.pem')
|
|
31
34
|
});
|
|
32
35
|
const policyPath = path.join(scratch, 'coding-policy.json');
|
|
33
|
-
const grantPath = path.join(
|
|
36
|
+
const grantPath = path.join(runtimeRoot, 'coding-grant.json');
|
|
34
37
|
const evidenceDir = path.join(scratch, 'evidence');
|
|
35
38
|
const rawPolicy = {
|
|
36
39
|
schema: 'deadbyte.coding-policy.v1', enabled: true,
|
|
@@ -41,11 +44,16 @@ const rawPolicy = {
|
|
|
41
44
|
const policyBytes = Buffer.from(`${JSON.stringify(rawPolicy, null, 2)}\n`, 'utf8');
|
|
42
45
|
await writeFile(policyPath, policyBytes, { flag: 'wx' });
|
|
43
46
|
const policy = await loadCodingPolicy(policyPath);
|
|
44
|
-
|
|
47
|
+
const grantBytes = Buffer.from(`${JSON.stringify({
|
|
45
48
|
schema: CODING_GRANT_SCHEMA, policy_sha256: policy.policy_sha256,
|
|
46
49
|
instance_nonce: randomBytes(32).toString('hex'), root_ids: ['core'],
|
|
47
50
|
granted_at_utc: new Date().toISOString()
|
|
48
|
-
}, null, 2)}\n`,
|
|
51
|
+
}, null, 2)}\n`, 'utf8');
|
|
52
|
+
await writeFile(grantPath, grantBytes, { flag: 'wx' });
|
|
53
|
+
const grant = JSON.parse(grantBytes.toString('utf8'));
|
|
54
|
+
await createAuthorityEpochStore({ runtimeDir:runtimeRoot, policyBytes }).append({
|
|
55
|
+
plane:'coding', action:'grant', grantBytes, grantInstanceNonce:grant.instance_nonce, updateId:null
|
|
56
|
+
});
|
|
49
57
|
|
|
50
58
|
const client = new Client({ name: 'deadbyte-machine-fs-smoke', version: '0.8.0' },
|
|
51
59
|
{ versionNegotiation: { mode: { pin: '2026-07-28' } } });
|
package/src/machine-identity.mjs
CHANGED
|
@@ -11,11 +11,13 @@ import { lstat, mkdir, open, readFile, rename, rm, writeFile } from 'node:fs/pro
|
|
|
11
11
|
import os from 'node:os';
|
|
12
12
|
import path from 'node:path';
|
|
13
13
|
import { canonicalJson } from './canonical-json.mjs';
|
|
14
|
+
import {nativeInspectArgs,runNativeInspect} from './native-inspector-r67.mjs';
|
|
14
15
|
import { parseActivePointer } from './release-slot.mjs';
|
|
15
16
|
|
|
16
17
|
export const MACHINE_IDENTITY_SCHEMA = 'deadbyte.machine-identity.v1';
|
|
17
18
|
export const MACHINE_PING_SCHEMA = 'deadbyte.machine-ping.v1';
|
|
18
19
|
const MACHINE_PING_DOMAIN = Buffer.from('DEADBYTE-MACHINE-PING-V1\0', 'utf8');
|
|
20
|
+
export const R67_MACHINE_OBSERVATION_DOMAIN=Buffer.from('DEADBYTE-R67-MACHINE-OBSERVATION-V1\0','utf8');
|
|
19
21
|
const HEX64 = /^[0-9a-f]{64}$/;
|
|
20
22
|
|
|
21
23
|
const sha256 = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
@@ -182,7 +184,8 @@ function statusBody(identity, pointer, onlineEpoch) {
|
|
|
182
184
|
|
|
183
185
|
export async function createMachineRuntime({
|
|
184
186
|
stateRoot = path.join(os.homedir(), '.deadbyte-mcp'),
|
|
185
|
-
activePointerPath = path.join(stateRoot, 'active-release.json')
|
|
187
|
+
activePointerPath = path.join(stateRoot, 'active-release.json'),
|
|
188
|
+
assertObservationAuthority = null
|
|
186
189
|
} = {}) {
|
|
187
190
|
await initializeMachineIdentity({ stateRoot });
|
|
188
191
|
let identity = await loadIdentity(stateRoot);
|
|
@@ -238,7 +241,61 @@ export async function createMachineRuntime({
|
|
|
238
241
|
identity_generation: identity.state.identity_generation, online_epoch: onlineEpoch };
|
|
239
242
|
}
|
|
240
243
|
|
|
241
|
-
|
|
244
|
+
|
|
245
|
+
// A non-public server-owned signing path: observer + machine identity stay in one process.
|
|
246
|
+
// The MCP bridge must never expose this as a caller-supplied signing oracle.
|
|
247
|
+
async function observeNative({args}){
|
|
248
|
+
if(typeof assertObservationAuthority!=='function')throw new Error('R67 observation authority callback required');
|
|
249
|
+
if(!Array.isArray(args))throw new Error('R67 observation args required');
|
|
250
|
+
const nativeArgs=nativeInspectArgs(args);
|
|
251
|
+
const operation=nativeArgs[0];
|
|
252
|
+
const before=await status();
|
|
253
|
+
const checkGrant=grant=>{
|
|
254
|
+
if(!grant||!/^[-a-zA-Z0-9._:]{1,128}$/.test(grant.grant_id??'')||
|
|
255
|
+
!HEX64.test(grant.policy_sha256??'')||grant.scope!=='native-observe-readonly'||
|
|
256
|
+
!Number.isSafeInteger(grant.authority_generation)||grant.authority_generation<1)
|
|
257
|
+
throw new Error('R67 authority grant invalid');
|
|
258
|
+
return {grant_id:grant.grant_id,policy_sha256:grant.policy_sha256,
|
|
259
|
+
scope:grant.scope,authority_generation:grant.authority_generation};
|
|
260
|
+
};
|
|
261
|
+
const first=checkGrant(await assertObservationAuthority({phase:'pre',operation,args:nativeArgs,machine:before}));
|
|
262
|
+
const result=await runNativeInspect(args);
|
|
263
|
+
const after=await status();
|
|
264
|
+
const second=checkGrant(await assertObservationAuthority({phase:'post',operation,args:nativeArgs,machine:after}));
|
|
265
|
+
if(canonicalJson(first)!==canonicalJson(second))throw new Error('R67 authority lease changed during observation');
|
|
266
|
+
for(const field of ['machine_id','identity_generation','online_epoch','release_id','release_manifest_sha256','generation_manifest_sha256'])
|
|
267
|
+
if(before[field]!==after[field])throw new Error('R67 machine or release generation changed during observation');
|
|
268
|
+
const pinned=JSON.parse(await readFile(new URL('../docs/R67-NATIVE-INSPECT-BINARY.json',import.meta.url),'utf8'));
|
|
269
|
+
if(pinned.schema!=='deadbyte.r67-native-inspector-binary.v1'||!HEX64.test(pinned.sha256??''))
|
|
270
|
+
throw new Error('R67 observer executable pin invalid');
|
|
271
|
+
// Trusted signer is loaded from the machine identity store by this runtime, never provided by a client.
|
|
272
|
+
identity=await loadIdentity(stateRoot);
|
|
273
|
+
if(identity.machine_id!==before.machine_id||identity.state.identity_generation!==before.identity_generation)
|
|
274
|
+
throw new Error('R67 identity revoked before signing');
|
|
275
|
+
const receiptBody={
|
|
276
|
+
schema:'deadbyte.native-observation-runtime-r67.v1',
|
|
277
|
+
observed_at_utc:new Date().toISOString(),
|
|
278
|
+
machine_id:before.machine_id,
|
|
279
|
+
identity_generation:before.identity_generation,
|
|
280
|
+
online_epoch:before.online_epoch,
|
|
281
|
+
release_id:before.release_id,
|
|
282
|
+
release_manifest_sha256:before.release_manifest_sha256,
|
|
283
|
+
generation_manifest_sha256:before.generation_manifest_sha256,
|
|
284
|
+
authority:first,
|
|
285
|
+
operation,
|
|
286
|
+
request_sha256:sha256(Buffer.from(JSON.stringify(nativeArgs),'utf8')),
|
|
287
|
+
executable_sha256:pinned.sha256,
|
|
288
|
+
result_status:result.status,
|
|
289
|
+
result_sha256:sha256(Buffer.from(canonicalJson(result),'utf8'))
|
|
290
|
+
};
|
|
291
|
+
const signature=cryptoSign(null,Buffer.concat([R67_MACHINE_OBSERVATION_DOMAIN,
|
|
292
|
+
Buffer.from(canonicalJson(receiptBody),'utf8')]),identity.privateKey);
|
|
293
|
+
return {result,receipt:{...receiptBody,attestation:{
|
|
294
|
+
algorithm:'ed25519',key_id:identity.machine_id,signature_base64:signature.toString('base64')
|
|
295
|
+
}}};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return Object.freeze({ list, status, ping, revoke, observeNative });
|
|
242
299
|
}
|
|
243
300
|
|
|
244
301
|
export async function verifyMachinePingPublic({ ping, publicKeySpkiBase64 }) {
|
|
@@ -7,6 +7,10 @@ import {
|
|
|
7
7
|
machineStructuredResult
|
|
8
8
|
} from './machine-output-contracts.mjs';
|
|
9
9
|
|
|
10
|
+
const READ_HINTS = Object.freeze({ readOnlyHint:true, destructiveHint:false, idempotentHint:true, openWorldHint:false });
|
|
11
|
+
const SIGNED_PING_HINTS = Object.freeze({ readOnlyHint:true, destructiveHint:false, idempotentHint:false, openWorldHint:false });
|
|
12
|
+
const REVOKE_HINTS = Object.freeze({ readOnlyHint:false, destructiveHint:true, idempotentHint:false, openWorldHint:false });
|
|
13
|
+
|
|
10
14
|
function ok(schema, value) { return machineStructuredResult(schema, value, false); }
|
|
11
15
|
function fail(schema, status, error) { return machineStructuredResult(schema, { status, error: String(error) }, true); }
|
|
12
16
|
|
|
@@ -16,21 +20,25 @@ export function registerMachineTools(server, { runtime, assertRevokeAuthority })
|
|
|
16
20
|
|
|
17
21
|
server.registerTool('machine_list', {
|
|
18
22
|
description: 'List DEADBYTE machine identities visible to this runtime. Identity is public-key based; hostname is informational.',
|
|
23
|
+
annotations: READ_HINTS,
|
|
19
24
|
inputSchema: z.strictObject({}), outputSchema: machineListOutputSchema
|
|
20
25
|
}, async () => { try { return ok(machineListOutputSchema, await runtime.list()); } catch (error) { return fail(machineListOutputSchema, 'failed', error); } });
|
|
21
26
|
|
|
22
27
|
server.registerTool('machine_status', {
|
|
23
28
|
description: 'Read this machine identity, online epoch, active release/generation binding, and capability set.',
|
|
29
|
+
annotations: READ_HINTS,
|
|
24
30
|
inputSchema: z.strictObject({}), outputSchema: machineStatusOutputSchema
|
|
25
31
|
}, async () => { try { return ok(machineStatusOutputSchema, await runtime.status()); } catch (error) { return fail(machineStatusOutputSchema, 'failed', error); } });
|
|
26
32
|
|
|
27
33
|
server.registerTool('machine_ping', {
|
|
28
34
|
description: 'Return an Ed25519-signed nonce echo bound to machine identity, online epoch, release, and generation.',
|
|
35
|
+
annotations: SIGNED_PING_HINTS,
|
|
29
36
|
inputSchema: z.strictObject({ nonce: z.string().min(1).max(256) }), outputSchema: machinePingOutputSchema
|
|
30
37
|
}, async ({ nonce }) => { try { return ok(machinePingOutputSchema, await runtime.ping({ nonce })); } catch (error) { return fail(machinePingOutputSchema, 'failed', error); } });
|
|
31
38
|
|
|
32
39
|
server.registerTool('machine_revoke', {
|
|
33
40
|
description: 'Invalidate the current machine online epoch/generation. Requires independently armed local host authority and never expands authority.',
|
|
41
|
+
annotations: REVOKE_HINTS,
|
|
34
42
|
inputSchema: z.strictObject({ reason: z.string().min(1).max(256) }), outputSchema: machineRevokeOutputSchema
|
|
35
43
|
}, async ({ reason }) => {
|
|
36
44
|
try {
|
package/src/mcp-server.mjs
CHANGED
|
@@ -20,6 +20,7 @@ import { recoverInterruptedSlotUpdates } from './runtime-update.mjs';
|
|
|
20
20
|
import { decodeUtf8Strict } from './text-policy.mjs';
|
|
21
21
|
import { assertHostArmed, hostExec, hostExecInline, hostExecNative, hostFileRead, hostFileWrite, loadHostPolicy } from './host-plane.mjs';
|
|
22
22
|
import { createMachineRuntime } from './machine-identity.mjs';
|
|
23
|
+
import { createR67NativeObservationAuthority } from './r67-native-observation-authority.mjs';
|
|
23
24
|
import { registerMachineTools } from './machine-mcp-tools.mjs';
|
|
24
25
|
import { createAgentRuntime } from './agent-runtime.mjs';
|
|
25
26
|
import { registerAgentTools } from './agent-mcp-tools.mjs';
|
|
@@ -107,10 +108,19 @@ if (!privilegedPolicyExplicit) {
|
|
|
107
108
|
}
|
|
108
109
|
planeRegistry.define('host',{configured:Boolean(privilegedPolicyPath),required:false});
|
|
109
110
|
const hostPolicy=await planeRegistry.initialize('host',async()=>loadHostPolicy(privilegedPolicyPath));
|
|
111
|
+
// The machine runtime is initialized before Coding. Authority is resolved lazily
|
|
112
|
+
// from the trusted Coding plane at the moment of each observation and rechecked
|
|
113
|
+
// after the native read, never from a client-supplied permission object.
|
|
114
|
+
let codingPolicy=null;
|
|
115
|
+
const nativeObserveMode=process.env.DEADBYTE_R67_NATIVE_OBSERVE ?? '0';
|
|
116
|
+
if(!['0','1'].includes(nativeObserveMode))throw new Error('DEADBYTE_R67_NATIVE_OBSERVE must be 0 or 1');
|
|
117
|
+
const assertNativeObservationAuthority=createR67NativeObservationAuthority({
|
|
118
|
+
getCodingPolicy:()=>codingPolicy,enabled:nativeObserveMode==='1'
|
|
119
|
+
});
|
|
110
120
|
const machineRuntimeMode = process.env.DEADBYTE_MACHINE_RUNTIME ?? '0';
|
|
111
121
|
if (!['0', '1'].includes(machineRuntimeMode)) throw new Error('DEADBYTE_MACHINE_RUNTIME must be 0 or 1');
|
|
112
122
|
planeRegistry.define('machine',{configured:machineRuntimeMode==='1',required:true});
|
|
113
|
-
const machineRuntime=await planeRegistry.initialize('machine',async()=>createMachineRuntime({stateRoot:runtimeStateRoot}));
|
|
123
|
+
const machineRuntime=await planeRegistry.initialize('machine',async()=>createMachineRuntime({stateRoot:runtimeStateRoot,assertObservationAuthority:assertNativeObservationAuthority}));
|
|
114
124
|
const agentRuntimeMode = process.env.DEADBYTE_AGENT_RUNTIME ?? '0';
|
|
115
125
|
if (!['0', '1'].includes(agentRuntimeMode)) throw new Error('DEADBYTE_AGENT_RUNTIME must be 0 or 1');
|
|
116
126
|
planeRegistry.define('agent',{configured:agentRuntimeMode==='1',required:false});
|
|
@@ -142,7 +152,6 @@ if (codingRuntimeMode === '1' && !codingPolicyPath) {
|
|
|
142
152
|
catch (error) { if (error?.code !== 'ENOENT') throw error; }
|
|
143
153
|
}
|
|
144
154
|
planeRegistry.define('coding',{configured:codingRuntimeMode==='1',required:false});
|
|
145
|
-
let codingPolicy=null;
|
|
146
155
|
const codingRuntime=await planeRegistry.initialize('coding',async()=>{
|
|
147
156
|
if(!codingPolicyPath) throw new Error('DEADBYTE_CODING_RUNTIME=1 requires a coding policy');
|
|
148
157
|
codingPolicy=await loadCodingPolicy(codingPolicyPath);
|
package/src/native-diff-v2.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { canonicalJson } from './canonical-json.mjs';
|
|
5
5
|
import { validatePortableRelativePath } from './path-policy.mjs';
|
|
6
6
|
import { loadPrivateSigningKey, loadPublicVerifyKey } from './trust.mjs';
|
|
7
|
-
import { assertAutonomousArmed, recheckAutonomousAuthority } from './autonomous-policy.mjs';
|
|
7
|
+
import { assertAutonomousArmed, autonomousSessionStatus, recheckAutonomousAuthority } from './autonomous-policy.mjs';
|
|
8
8
|
|
|
9
9
|
export const NATIVE_DIFF_V2_SCHEMA='deadbyte.native-diff.v2';
|
|
10
10
|
export const NATIVE_DIFF_V2_PREIMAGE_SCHEMA='deadbyte.native-diff-preimage.v2';
|
|
@@ -107,9 +107,16 @@ export function createNativeDiffV2Runtime({policy,codingRuntime,machineFsRuntime
|
|
|
107
107
|
if(!machineFsRuntime?.state||!machineFsRuntime?.removeEmptyDir) throw new Error('native diff V2 requires machine fs topology runtime');
|
|
108
108
|
if(!signingKeyPath||!verifyKeyPath) throw new Error('native diff V2 requires trust keys');
|
|
109
109
|
|
|
110
|
-
async function materializeOperations(rootId,rawOperations){
|
|
111
|
-
if(
|
|
112
|
-
|
|
110
|
+
async function materializeOperations(rootId,rawOperations,{codingOnly=false}={}){
|
|
111
|
+
if(codingOnly){
|
|
112
|
+
const lease=await codingRuntime.sessionStatus();
|
|
113
|
+
if(!lease.armed) throw new Error(`coding plane is disarmed: ${lease.reason}`);
|
|
114
|
+
if(!lease.root_ids.includes(rootId)||codingRuntime.policy.roots[rootId]?.read!==true)
|
|
115
|
+
throw new Error(`native diff preview root '${rootId}' is not allowed by Coding lease`);
|
|
116
|
+
} else {
|
|
117
|
+
if(!policy.root_ids.includes(rootId)) throw new Error(`native diff root '${rootId}' is not allowed`);
|
|
118
|
+
await assertAutonomousArmed(policy,rootId);
|
|
119
|
+
}
|
|
113
120
|
const {normalized,payloadBytes}=normalizeRawOperations(rawOperations,policy);
|
|
114
121
|
const operations=[];
|
|
115
122
|
for(const op of normalized){
|
|
@@ -168,7 +175,13 @@ export function createNativeDiffV2Runtime({policy,codingRuntime,machineFsRuntime
|
|
|
168
175
|
async function preview({rootId,operations:rawOperations,title='',applicationScope=null}){
|
|
169
176
|
if(typeof title!=='string'||title.length>160) throw new Error('native diff title must be <=160 characters');
|
|
170
177
|
if(applicationScope!==null&&!SHA256_RE.test(applicationScope)) throw new Error('native diff application_scope must be SHA-256 or null');
|
|
171
|
-
const
|
|
178
|
+
const autonomous=policy.root_ids.includes(rootId)?await autonomousSessionStatus(policy):null;
|
|
179
|
+
const codingOnly=autonomous?.armed!==true||!autonomous.root_ids?.includes(rootId);
|
|
180
|
+
const {operations,payloadBytes}=await materializeOperations(rootId,rawOperations,{codingOnly});
|
|
181
|
+
if(codingOnly) return {status:'preview_only',schema:NATIVE_DIFF_V2_SCHEMA,root_id:rootId,
|
|
182
|
+
operation_count:operations.length,payload_bytes:payloadBytes,
|
|
183
|
+
paths:operations.map(op=>op.path??op.source_path),
|
|
184
|
+
operations:operations.map(({content,old_string,new_string,...op})=>op)};
|
|
172
185
|
const identity={schema:NATIVE_DIFF_V2_SCHEMA,policy_sha256:policy.policy_sha256,coding_policy_sha256:policy.coding_policy_sha256,root_id:rootId,title,operations,...(applicationScope===null?{}:{application_scope:applicationScope})};
|
|
173
186
|
const diffId=sha256Object(identity); const ctx=diffContext(policy,diffId); const signing=await loadPrivateSigningKey(signingKeyPath);
|
|
174
187
|
const envelope=signEnvelope(signing,DIFF_DOMAIN,{...identity,diff_id:diffId,created_at_utc:new Date().toISOString()});
|
package/src/native-diff.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { canonicalJson } from './canonical-json.mjs';
|
|
5
5
|
import { validatePortableRelativePath } from './path-policy.mjs';
|
|
6
6
|
import { loadPrivateSigningKey, loadPublicVerifyKey } from './trust.mjs';
|
|
7
|
-
import { assertAutonomousArmed, recheckAutonomousAuthority } from './autonomous-policy.mjs';
|
|
7
|
+
import { assertAutonomousArmed, autonomousSessionStatus, recheckAutonomousAuthority } from './autonomous-policy.mjs';
|
|
8
8
|
import { createNativeDiffV2Runtime, NATIVE_DIFF_V2_SCHEMA } from './native-diff-v2.mjs';
|
|
9
9
|
|
|
10
10
|
export const NATIVE_DIFF_SCHEMA = 'deadbyte.native-diff.v1';
|
|
@@ -102,9 +102,19 @@ function createNativeDiffRuntimeV1({ policy, codingRuntime, signingKeyPath, veri
|
|
|
102
102
|
if (!policy || !codingRuntime?.policy) throw new Error('native diff runtime requires autonomous and coding policy');
|
|
103
103
|
if (!signingKeyPath || !verifyKeyPath) throw new Error('native diff runtime requires trust keys');
|
|
104
104
|
|
|
105
|
-
async function materializeOperations(rootId, rawOperations) {
|
|
106
|
-
if (
|
|
107
|
-
|
|
105
|
+
async function materializeOperations(rootId, rawOperations, {codingOnly=false} = {}) {
|
|
106
|
+
if (codingOnly) {
|
|
107
|
+
// Preview authorization delegates ONLY to the existing, locally armed
|
|
108
|
+
// Coding read lease. No Autonomous grant, signer, or target mutation.
|
|
109
|
+
const lease=await codingRuntime.sessionStatus();
|
|
110
|
+
if (!lease.armed) throw new Error(`coding plane is disarmed: ${lease.reason}`);
|
|
111
|
+
if (!lease.root_ids.includes(rootId) || codingRuntime.policy.roots[rootId]?.read !== true) {
|
|
112
|
+
throw new Error(`native diff preview root '${rootId}' is not allowed by Coding lease`);
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
if (!policy.root_ids.includes(rootId)) throw new Error(`native diff root '${rootId}' is not allowed`);
|
|
116
|
+
await assertAutonomousArmed(policy, rootId);
|
|
117
|
+
}
|
|
108
118
|
const { normalized, payloadBytes } = normalizeRawOperations(rawOperations, policy);
|
|
109
119
|
const operations = [];
|
|
110
120
|
for (const op of normalized) {
|
|
@@ -132,7 +142,13 @@ function createNativeDiffRuntimeV1({ policy, codingRuntime, signingKeyPath, veri
|
|
|
132
142
|
async function preview({ rootId, operations:rawOperations, title = '', applicationScope = null }) {
|
|
133
143
|
if (typeof title !== 'string' || title.length > 160) throw new Error('native diff title must be <=160 characters');
|
|
134
144
|
if (applicationScope !== null && !SHA256_RE.test(applicationScope)) throw new Error('native diff application_scope must be SHA-256 or null');
|
|
135
|
-
const
|
|
145
|
+
const autonomous=policy.root_ids.includes(rootId)?await autonomousSessionStatus(policy):null;
|
|
146
|
+
const codingOnly=autonomous?.armed!==true||!autonomous.root_ids?.includes(rootId);
|
|
147
|
+
const { operations, payloadBytes } = await materializeOperations(rootId, rawOperations, {codingOnly});
|
|
148
|
+
if (codingOnly) return {status:'preview_only',schema:NATIVE_DIFF_SCHEMA,root_id:rootId,
|
|
149
|
+
operation_count:operations.length,payload_bytes:payloadBytes,
|
|
150
|
+
paths:operations.map(op=>op.path),
|
|
151
|
+
operations:operations.map(({content,old_string,new_string,...op})=>op)};
|
|
136
152
|
const identity = { schema:NATIVE_DIFF_SCHEMA, policy_sha256:policy.policy_sha256,
|
|
137
153
|
coding_policy_sha256:policy.coding_policy_sha256, root_id:rootId, title, operations,
|
|
138
154
|
...(applicationScope === null ? {} : { application_scope:applicationScope }) };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {spawnSync} from 'node:child_process';
|
|
2
|
+
import {createHash} from 'node:crypto';
|
|
3
|
+
import {lstat,readFile} from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import {fileURLToPath} from 'node:url';
|
|
6
|
+
import {signR67Observation} from './r67-observation-receipt.mjs';
|
|
7
|
+
|
|
8
|
+
const root=path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
9
|
+
const exe=path.join(root,'bin','deadbyte-native-inspect-r67.exe');
|
|
10
|
+
const metaFile=path.join(root,'docs','R67-NATIVE-INSPECT-BINARY.json');
|
|
11
|
+
const hash=b=>createHash('sha256').update(b).digest('hex');
|
|
12
|
+
const validLimit=(s,min,max)=>{if(!/^[1-9][0-9]*$/.test(s))throw new Error('invalid positive limit');const n=Number(s);if(!Number.isSafeInteger(n)||n<min||n>max)throw new Error('limit out of range');return String(n);};
|
|
13
|
+
export function nativeInspectArgs(rest){
|
|
14
|
+
const [kind,...args]=rest;
|
|
15
|
+
if(kind==='process'){if(args.length>1)throw new Error('inspect process [1..256]');return ['process',...(args.length?[validLimit(args[0],1,256)]:[])];}
|
|
16
|
+
if(kind==='identity'){
|
|
17
|
+
if(args.length!==1)throw new Error('inspect identity <PID>');
|
|
18
|
+
return ['identity',validLimit(args[0],1,4294967295)];
|
|
19
|
+
}
|
|
20
|
+
if(kind==='memory'){
|
|
21
|
+
if(args[0]==='self'){
|
|
22
|
+
if(args.length>2)throw new Error('inspect memory self [limit]');
|
|
23
|
+
return ['map','self',...(args.length===2?[validLimit(args[1],1,512)]:[])];
|
|
24
|
+
}
|
|
25
|
+
if(args.length<2||args.length>3)throw new Error('inspect memory PID <creation_filetime> [limit]');
|
|
26
|
+
const pid=validLimit(args[0],1,4294967295);
|
|
27
|
+
const token=args[1];
|
|
28
|
+
if(!/^[1-9][0-9]*$/.test(token)||BigInt(token)>18446744073709551615n)throw new Error('invalid process creation FILETIME');
|
|
29
|
+
return ['map',pid,token,...(args.length===3?[validLimit(args[2],1,512)]:[])];
|
|
30
|
+
}
|
|
31
|
+
if(kind==='pe'){
|
|
32
|
+
if(args.length!==1||!args[0]||args[0].includes('\0'))throw new Error('inspect pe <file>');
|
|
33
|
+
return ['pe',path.resolve(args[0])];
|
|
34
|
+
}
|
|
35
|
+
throw new Error('inspect expects process, memory, or pe');
|
|
36
|
+
}
|
|
37
|
+
export async function runNativeInspect(rest,{trustedSigner=null}={}){
|
|
38
|
+
if(process.platform!=='win32')throw new Error('native inspector requires win32');
|
|
39
|
+
const args=nativeInspectArgs(rest);
|
|
40
|
+
const meta=JSON.parse(await readFile(metaFile,'utf8'));
|
|
41
|
+
if(meta?.schema!=='deadbyte.r67-native-inspector-binary.v1'||meta.exe!=='bin/deadbyte-native-inspect-r67.exe'||!/^[a-f0-9]{64}$/.test(meta.sha256))throw new Error('R67 native executable authority invalid');
|
|
42
|
+
const info=await lstat(exe);
|
|
43
|
+
if(!info.isFile()||info.isSymbolicLink())throw new Error('R67 executable is not a regular non-symlink');
|
|
44
|
+
const before=hash(await readFile(exe));
|
|
45
|
+
if(before!==meta.sha256)throw new Error('R67 executable hash pin mismatch');
|
|
46
|
+
const result=spawnSync(exe,args,{encoding:'utf8',windowsHide:true,shell:false,timeout:15000,maxBuffer:2097152});
|
|
47
|
+
const after=hash(await readFile(exe));
|
|
48
|
+
if(after!==before)throw new Error('R67 executable drift after execution; outcome uncertain');
|
|
49
|
+
if(result.error)throw new Error('native inspector execution failed: '+result.error.message);
|
|
50
|
+
const stdout=String(result.stdout||'');
|
|
51
|
+
if(Buffer.byteLength(stdout,'utf8')>2097152)throw new Error('native inspector output exceeded limit');
|
|
52
|
+
let parsed;try{parsed=JSON.parse(stdout);}catch{throw new Error('native inspector output invalid JSON');}
|
|
53
|
+
if(parsed?.schema!=='deadbyte.native-inspect.r67.v1')throw new Error('native inspector output schema mismatch');
|
|
54
|
+
if(parsed.status==='ok'){
|
|
55
|
+
if(result.status!==0||parsed.operation!==args[0]||!parsed.data||parsed.data_sha256!==hash(Buffer.from(JSON.stringify(parsed.data),'utf8')))throw new Error('native inspector result digest mismatch');
|
|
56
|
+
}else if(parsed.status!=='failed'||result.status!==2||typeof parsed.error!=='string')throw new Error('native inspector failure contract mismatch');
|
|
57
|
+
if(trustedSigner!==null){
|
|
58
|
+
if(!trustedSigner||!trustedSigner.privateKey||!trustedSigner.publicKey)throw new Error('R67 signed observation requires an explicit trusted signer');
|
|
59
|
+
const receipt=signR67Observation({
|
|
60
|
+
...trustedSigner,operation:args[0],requestSha256:hash(Buffer.from(JSON.stringify(args),'utf8')),
|
|
61
|
+
executableSha256:before,result:parsed
|
|
62
|
+
});
|
|
63
|
+
return {result:parsed,receipt};
|
|
64
|
+
}
|
|
65
|
+
return parsed;
|
|
66
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import {createHash} from 'node:crypto';
|
|
2
|
+
import {lstat,readFile,readdir,open} from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
const hash=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
6
|
+
const allowedLog=/^controller-(?:start|restart)\.(?:stdout|stderr)\.log$/;
|
|
7
|
+
const releasePattern=/^v[0-9]+\.[0-9]+\.[0-9]+-[a-f0-9]{16}$/;
|
|
8
|
+
const hex64=/^[a-f0-9]{64}$/;
|
|
9
|
+
const events=['reconnect','connected','disconnect','timeout','cancel','rollback','restore','revoked','ready','start','restart','failed','success'];
|
|
10
|
+
const EVENT_RX=new RegExp('\\b('+events.join('|')+')\\b','i');
|
|
11
|
+
const timestampRx=/^(\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z)\b/;
|
|
12
|
+
|
|
13
|
+
export function redactDiagnosticLine(text){
|
|
14
|
+
if(typeof text!=='string'||text.length>8192)return null;
|
|
15
|
+
const level=/\b(ERROR|WARN|INFO|DEBUG)\b/i.exec(text)?.[1]?.toUpperCase();
|
|
16
|
+
const event=EVENT_RX.exec(text)?.[1]?.toLowerCase();
|
|
17
|
+
if(!level||!event)return null;
|
|
18
|
+
const result={};
|
|
19
|
+
const stamp=timestampRx.exec(text)?.[1];
|
|
20
|
+
if(stamp&&!Number.isNaN(Date.parse(stamp)))result.timestamp=stamp;
|
|
21
|
+
result.level=level;result.event=event;
|
|
22
|
+
const error=/\bwin32=(\d{1,5})\b/i.exec(text)?.[1];
|
|
23
|
+
if(error!==undefined&&Number(error)<=65535)result.win32_error=Number(error);
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
function crc32(data){
|
|
27
|
+
let crc=0xffffffff;
|
|
28
|
+
for(const value of data){
|
|
29
|
+
crc^=value;
|
|
30
|
+
for(let i=0;i<8;i++)crc=(crc>>>1)^((crc&1)?0xedb88320:0);
|
|
31
|
+
}
|
|
32
|
+
return (crc^0xffffffff)>>>0;
|
|
33
|
+
}
|
|
34
|
+
function zipStore(entries){
|
|
35
|
+
const locals=[],centrals=[];
|
|
36
|
+
let offset=0;
|
|
37
|
+
for(const [name,contents] of entries){
|
|
38
|
+
if(!/^[a-z0-9.-]+\.json$/.test(name))throw new Error('noncanonical archive entry');
|
|
39
|
+
const nameBytes=Buffer.from(name),data=Buffer.from(contents,'utf8'),crc=crc32(data);
|
|
40
|
+
if(data.length>1024*1024)throw new Error('archive payload too large');
|
|
41
|
+
const local=Buffer.alloc(30);
|
|
42
|
+
local.writeUInt32LE(0x04034b50,0);
|
|
43
|
+
local.writeUInt16LE(20,4);
|
|
44
|
+
local.writeUInt32LE(crc,14);
|
|
45
|
+
local.writeUInt32LE(data.length,18);local.writeUInt32LE(data.length,22);
|
|
46
|
+
local.writeUInt16LE(nameBytes.length,26);
|
|
47
|
+
locals.push(local,nameBytes,data);
|
|
48
|
+
const central=Buffer.alloc(46);
|
|
49
|
+
central.writeUInt32LE(0x02014b50,0);
|
|
50
|
+
central.writeUInt16LE(20,4);central.writeUInt16LE(20,6);
|
|
51
|
+
central.writeUInt32LE(crc,16);
|
|
52
|
+
central.writeUInt32LE(data.length,20);central.writeUInt32LE(data.length,24);
|
|
53
|
+
central.writeUInt16LE(nameBytes.length,28);
|
|
54
|
+
central.writeUInt32LE(offset,42);
|
|
55
|
+
centrals.push(central,nameBytes);
|
|
56
|
+
offset+=local.length+nameBytes.length+data.length;
|
|
57
|
+
}
|
|
58
|
+
const directory=Buffer.concat(centrals);
|
|
59
|
+
const end=Buffer.alloc(22);
|
|
60
|
+
end.writeUInt32LE(0x06054b50,0);
|
|
61
|
+
end.writeUInt16LE(entries.length,8);end.writeUInt16LE(entries.length,10);
|
|
62
|
+
end.writeUInt32LE(directory.length,12);end.writeUInt32LE(offset,16);
|
|
63
|
+
return Buffer.concat([...locals,directory,end]);
|
|
64
|
+
}
|
|
65
|
+
export function makeOfflineDiagnosticBundle({releaseId,manifestSha256,logs=[]}={}){
|
|
66
|
+
if(!releasePattern.test(releaseId??'')||!hex64.test(manifestSha256??''))throw new Error('trusted release identity invalid');
|
|
67
|
+
if(!Array.isArray(logs)||logs.length>8)throw new Error('log source budget exceeded');
|
|
68
|
+
const redacted=[];
|
|
69
|
+
const sources=new Set();
|
|
70
|
+
for(const source of logs){
|
|
71
|
+
if(!source||!allowedLog.test(source.source)||typeof source.content!=='string'||Buffer.byteLength(source.content)>65536||sources.has(source.source))
|
|
72
|
+
throw new Error('unsupported diagnostic source');
|
|
73
|
+
sources.add(source.source);
|
|
74
|
+
const lines=source.content.split(/\r?\n/).slice(0,512);
|
|
75
|
+
for(const line of lines){
|
|
76
|
+
if(redacted.length===256)break;
|
|
77
|
+
const record=redactDiagnosticLine(line);
|
|
78
|
+
if(record)redacted.push({source:source.source,...record});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const summary={schema:'deadbyte.r67-offline-diagnostics.v1',release_id:releaseId,release_manifest_sha256:manifestSha256,
|
|
82
|
+
network_upload:'NEVER',redaction:'FIXED_FIELDS_ONLY',source_count:sources.size,redacted_event_count:redacted.length,
|
|
83
|
+
raw_log_lines_exported:0,private_memory_exported:false};
|
|
84
|
+
const zip=zipStore([
|
|
85
|
+
['diagnostics.json',JSON.stringify(summary,null,2)+'\n'],
|
|
86
|
+
['redacted-events.json',JSON.stringify(redacted,null,2)+'\n']
|
|
87
|
+
]);
|
|
88
|
+
return {zip,summary,archive_sha256:hash(zip)};
|
|
89
|
+
}
|
|
90
|
+
async function regularBounded(file,maxBytes){
|
|
91
|
+
const info=await lstat(file);
|
|
92
|
+
if(!info.isFile()||info.isSymbolicLink()||info.size>maxBytes)throw new Error('invalid or oversized diagnostic source');
|
|
93
|
+
const b=await readFile(file);
|
|
94
|
+
if(b.length>maxBytes)throw new Error('source size changed');
|
|
95
|
+
return b.toString('utf8');
|
|
96
|
+
}
|
|
97
|
+
export async function exportOfflineDiagnostics({stateRoot,outputPath,upload=false}={}){
|
|
98
|
+
if(upload!==false)throw new Error('network upload disabled unconditionally');
|
|
99
|
+
if(typeof outputPath!=='string'||!outputPath||/^[a-z]+:\/\//i.test(outputPath))throw new Error('explicit local outputPath required');
|
|
100
|
+
if(typeof stateRoot!=='string'||!stateRoot)throw new Error('local stateRoot required');
|
|
101
|
+
const root=path.resolve(stateRoot);
|
|
102
|
+
const pointer=JSON.parse(await regularBounded(path.join(root,'active-release.json'),131072));
|
|
103
|
+
const logDir=path.join(root,'runtime','logs');
|
|
104
|
+
let names=[];
|
|
105
|
+
try{names=(await readdir(logDir)).filter(n=>allowedLog.test(n)).sort().slice(0,8);}catch(error){if(error.code!=='ENOENT')throw error;}
|
|
106
|
+
const logs=[];
|
|
107
|
+
for(const name of names){
|
|
108
|
+
const file=path.join(logDir,name);
|
|
109
|
+
try{logs.push({source:name,content:await regularBounded(file,65536)});}
|
|
110
|
+
catch(error){if(error.code!=='ENOENT')throw error;}
|
|
111
|
+
}
|
|
112
|
+
const bundle=makeOfflineDiagnosticBundle({releaseId:pointer.release_id,manifestSha256:pointer.manifest_sha256,logs});
|
|
113
|
+
const target=path.resolve(outputPath);
|
|
114
|
+
const h=await open(target,'wx',0o600);
|
|
115
|
+
try{await h.writeFile(bundle.zip);await h.sync();}finally{await h.close();}
|
|
116
|
+
return {status:'written_local_only',uploaded:false,archive_sha256:bundle.archive_sha256,bytes:bundle.zip.length,
|
|
117
|
+
source_count:bundle.summary.source_count,redacted_event_count:bundle.summary.redacted_event_count};
|
|
118
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {createHash} from 'node:crypto';
|
|
2
|
+
const sha=b=>createHash('sha256').update(b).digest('hex');
|
|
3
|
+
const MAX=4*1024*1024;
|
|
4
|
+
function crc32(bytes){
|
|
5
|
+
let crc=0xffffffff;
|
|
6
|
+
for(const value of bytes){crc^=value;for(let i=0;i<8;i++)crc=(crc>>>1)^((crc&1)?0xedb88320:0);}
|
|
7
|
+
return (crc^0xffffffff)>>>0;
|
|
8
|
+
}
|
|
9
|
+
function fail(what){throw new Error('ZIP integrity: '+what);}
|
|
10
|
+
function read16(b,o){if(o<0||o+2>b.length)fail('boundary');return b.readUInt16LE(o);}
|
|
11
|
+
function read32(b,o){if(o<0||o+4>b.length)fail('boundary');return b.readUInt32LE(o);}
|
|
12
|
+
export function verifyOfflineDiagnosticsZip(input){
|
|
13
|
+
if(!Buffer.isBuffer(input))throw new Error('ZIP input must be Buffer');
|
|
14
|
+
const b=input;
|
|
15
|
+
if(b.length<22||b.length>MAX)throw new Error('ZIP too large or truncated size');
|
|
16
|
+
const end=b.length-22;
|
|
17
|
+
if(read32(b,end)!==0x06054b50)fail('missing terminal EOCD or trailing data');
|
|
18
|
+
if(read16(b,end+4)||read16(b,end+6)||read16(b,end+20))fail('multidisk/comments forbidden');
|
|
19
|
+
const count=read16(b,end+10);
|
|
20
|
+
if(count!==2||read16(b,end+8)!==2)fail('exactly two entries required');
|
|
21
|
+
const centralSize=read32(b,end+12),centralOffset=read32(b,end+16);
|
|
22
|
+
if(centralOffset+centralSize!==end)fail('central directory boundary invalid');
|
|
23
|
+
const expected=['diagnostics.json','redacted-events.json'];
|
|
24
|
+
let position=centralOffset;
|
|
25
|
+
let localEnd=0;
|
|
26
|
+
const files={};
|
|
27
|
+
for(let i=0;i<2;i++){
|
|
28
|
+
if(read32(b,position)!==0x02014b50)fail('bad central file header');
|
|
29
|
+
if(read16(b,position+8)||read16(b,position+10))fail('unsafe ZIP flags or compressed method');
|
|
30
|
+
const crc=read32(b,position+16),stored=read32(b,position+20),raw=read32(b,position+24);
|
|
31
|
+
const n=read16(b,position+28),extra=read16(b,position+30),comment=read16(b,position+32);
|
|
32
|
+
const off=read32(b,position+42);
|
|
33
|
+
if(stored!==raw||stored>1048576||extra||comment||read16(b,position+34))fail('unsupported entry');
|
|
34
|
+
if(position+46+n>centralOffset+centralSize)fail('central filename boundary');
|
|
35
|
+
const name=b.toString('utf8',position+46,position+46+n);
|
|
36
|
+
if(name!==expected[i])fail('unexpected entry name/order');
|
|
37
|
+
if(read32(b,off)!==0x04034b50||read16(b,off+6)||read16(b,off+8))fail('local header unsafe flags');
|
|
38
|
+
if(read32(b,off+14)!==crc||read32(b,off+18)!==stored||read32(b,off+22)!==raw)fail('local versus central mismatch');
|
|
39
|
+
const ln=read16(b,off+26),le=read16(b,off+28);
|
|
40
|
+
if(ln!==n||le!==0||b.toString('utf8',off+30,off+30+ln)!==name)fail('local filename mismatch');
|
|
41
|
+
const start=off+30+n,endByte=start+stored;
|
|
42
|
+
if(endByte>centralOffset||off!==localEnd)fail('local file bounds or gap');
|
|
43
|
+
const data=b.subarray(start,endByte);
|
|
44
|
+
if(crc32(data)!==crc)fail('CRC mismatch');
|
|
45
|
+
files[name]=data;
|
|
46
|
+
localEnd=endByte;
|
|
47
|
+
position+=46+n;
|
|
48
|
+
}
|
|
49
|
+
if(position!==centralOffset+centralSize||localEnd!==centralOffset)fail('file/directory overlap or padding');
|
|
50
|
+
let summary,events;
|
|
51
|
+
try{
|
|
52
|
+
summary=JSON.parse(files['diagnostics.json'].toString('utf8'));
|
|
53
|
+
events=JSON.parse(files['redacted-events.json'].toString('utf8'));
|
|
54
|
+
}catch{fail('invalid JSON');}
|
|
55
|
+
if(!summary||typeof summary!=='object'||Array.isArray(summary)||
|
|
56
|
+
summary.schema!=='deadbyte.r67-offline-diagnostics.v1'||
|
|
57
|
+
!/^v\d+\.\d+\.\d+-[a-f0-9]{16}$/.test(summary.release_id)||
|
|
58
|
+
!/^[a-f0-9]{64}$/.test(summary.release_manifest_sha256)||
|
|
59
|
+
summary.redaction!=='FIXED_FIELDS_ONLY'||summary.network_upload!=='NEVER'||
|
|
60
|
+
summary.raw_log_lines_exported!==0||summary.private_memory_exported!==false||
|
|
61
|
+
!Number.isInteger(summary.source_count)||summary.source_count<0||summary.source_count>8||
|
|
62
|
+
!Number.isInteger(summary.redacted_event_count)||summary.redacted_event_count<0||summary.redacted_event_count>256)
|
|
63
|
+
fail('unsafe diagnostic summary');
|
|
64
|
+
const allowedSummary=['schema','release_id','release_manifest_sha256','network_upload','redaction','source_count','redacted_event_count','raw_log_lines_exported','private_memory_exported'];
|
|
65
|
+
if(Object.keys(summary).sort().join('|')!==allowedSummary.sort().join('|'))fail('unexpected summary fields');
|
|
66
|
+
if(!Array.isArray(events)||events.length!==summary.redacted_event_count)fail('event count mismatch');
|
|
67
|
+
const sources=new Set();
|
|
68
|
+
const validLevels=new Set(['ERROR','WARN','INFO','DEBUG']);
|
|
69
|
+
const validEvents=new Set(['reconnect','connected','disconnect','timeout','cancel','rollback','restore','revoked','ready','start','restart','failed','success']);
|
|
70
|
+
for(const event of events){
|
|
71
|
+
if(!event||typeof event!=='object'||Array.isArray(event))fail('malformed event');
|
|
72
|
+
const keys=Object.keys(event).sort();
|
|
73
|
+
if(keys.some(x=>!['source','timestamp','level','event','win32_error'].includes(x)))fail('unexpected event fields');
|
|
74
|
+
if(!/^controller-(?:start|restart)\.(?:stdout|stderr)\.log$/.test(event.source)||
|
|
75
|
+
!validLevels.has(event.level)||!validEvents.has(event.event))fail('non-allowlisted event value');
|
|
76
|
+
if(event.timestamp!==undefined&&(!/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/.test(event.timestamp)||Number.isNaN(Date.parse(event.timestamp))))fail('bad event time');
|
|
77
|
+
if(event.win32_error!==undefined&&(!Number.isInteger(event.win32_error)||event.win32_error<0||event.win32_error>65535))fail('bad Win32 code');
|
|
78
|
+
sources.add(event.source);
|
|
79
|
+
}
|
|
80
|
+
if(sources.size>summary.source_count)fail('event sources exceed declared sources');
|
|
81
|
+
return {status:'VERIFIED_LOCAL_STRUCTURE',uploaded:false,archive_sha256:sha(b),entry_count:2,summary};
|
|
82
|
+
}
|