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
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import {createHash} from 'node:crypto';
|
|
2
|
+
import {readFile,readdir} from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import {createR67ShardPlan,reconcileR67Shards,r67RegressionTreeHash,r67ShardTimeoutMs} from './r67-regression-shards.mjs';
|
|
5
|
+
|
|
6
|
+
const sha=b=>createHash('sha256').update(b).digest('hex');
|
|
7
|
+
const hex=x=>typeof x==='string'&&/^[a-f0-9]{64}$/.test(x);
|
|
8
|
+
const equal=(a,b)=>JSON.stringify(a)===JSON.stringify(b);
|
|
9
|
+
const idPattern=/^[a-z0-9][a-z0-9._-]{5,63}$/;
|
|
10
|
+
async function jsonAt(file){
|
|
11
|
+
try{return {value:JSON.parse(await readFile(file,'utf8'))};}
|
|
12
|
+
catch(e){return {error:e.code==='ENOENT'?'MISSING':'INVALID_OR_UNREADABLE'};}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Independent evaluator. No processes executed, no mutations. Test can inject a
|
|
17
|
+
* disposable root; production CLI passes the actual candidate + evidence root.
|
|
18
|
+
* Result hashes are integrity checks, NOT signatures.
|
|
19
|
+
*/
|
|
20
|
+
export async function reconcileR67Evidence({root,evidenceRoot,operationId,sourceHashOptions}){
|
|
21
|
+
if(!idPattern.test(operationId??'')||typeof root!=='string'||!root||typeof evidenceRoot!=='string'||!evidenceRoot)
|
|
22
|
+
throw new Error('bounded root, evidence directory and operation identity required');
|
|
23
|
+
const testNames=(await readdir(path.join(root,'test'))).filter(n=>n.endsWith('.test.mjs')).map(n=>'test/'+n).sort();
|
|
24
|
+
const plan=createR67ShardPlan(testNames,32);
|
|
25
|
+
const hashes=[];
|
|
26
|
+
for(const name of testNames)hashes.push([name,sha(await readFile(path.join(root,name)))]);
|
|
27
|
+
const testTreeSha=sha(Buffer.from(JSON.stringify(hashes)));
|
|
28
|
+
const tree=await r67RegressionTreeHash(root,sourceHashOptions);
|
|
29
|
+
const verified=[],faults=[],stale=[],unknown=[],unclassified=[],classified=[];
|
|
30
|
+
for(const shard of plan.shards){
|
|
31
|
+
const dir=path.join(evidenceRoot,operationId+'-shard-'+String(shard.index).padStart(2,'0'));
|
|
32
|
+
const inFile=await jsonAt(path.join(dir,'intent.json'));
|
|
33
|
+
if(inFile.error==='MISSING')continue;
|
|
34
|
+
if(inFile.error){faults.push({index:shard.index,reason:'INTENT_'+inFile.error});continue;}
|
|
35
|
+
const outFile=await jsonAt(path.join(dir,'result.json'));
|
|
36
|
+
if(outFile.error==='MISSING'){unknown.push(shard.index);continue;}
|
|
37
|
+
if(outFile.error){faults.push({index:shard.index,reason:'RESULT_'+outFile.error});continue;}
|
|
38
|
+
const intent=inFile.value,result=outFile.value;
|
|
39
|
+
if(!result||typeof result!=='object'||Array.isArray(result)||!intent||typeof intent!=='object'||Array.isArray(intent)){
|
|
40
|
+
faults.push({index:shard.index,reason:'NOT_OBJECT'});continue;
|
|
41
|
+
}
|
|
42
|
+
const {receipt_sha256,...receiptBody}=result;
|
|
43
|
+
const v1=result.schema==='deadbyte.r67-shard-result.v1';
|
|
44
|
+
if((!v1&&result.schema!=='deadbyte.r67-shard-result.v2')||
|
|
45
|
+
intent.schema!=='deadbyte.r67-shard-intent.v1'||
|
|
46
|
+
intent.operation!==operationId||result.operation_id!==operationId||
|
|
47
|
+
intent.shard_index!==shard.index||result.shard_index!==shard.index||
|
|
48
|
+
!hex(receipt_sha256)||receipt_sha256!==sha(Buffer.from(JSON.stringify(receiptBody)))||
|
|
49
|
+
!hex(intent.plan_sha256)||!hex(result.plan_sha256)||
|
|
50
|
+
!hex(intent.files_sha256)||!hex(result.files_sha256)||
|
|
51
|
+
!hex(intent.test_tree_sha256)||!hex(result.test_tree_sha256)||
|
|
52
|
+
!Array.isArray(intent.files)||intent.files_sha256!==sha(Buffer.from(JSON.stringify(intent.files)))||
|
|
53
|
+
intent.plan_sha256!==result.plan_sha256||
|
|
54
|
+
intent.files_sha256!==result.files_sha256||
|
|
55
|
+
intent.test_tree_sha256!==result.test_tree_sha256||
|
|
56
|
+
(intent.source_tree_sha256!==undefined&&intent.source_tree_sha256!==result.source_tree_sha256)||
|
|
57
|
+
(intent.source_file_count!==undefined&&intent.source_file_count!==result.source_file_count)){
|
|
58
|
+
faults.push({index:shard.index,reason:'RESULT_DIGEST_OR_IDENTITY_INVALID'});continue;
|
|
59
|
+
}
|
|
60
|
+
if(v1){stale.push(shard.index);continue;}
|
|
61
|
+
if(!hex(result.source_tree_sha256)||!hex(intent.source_tree_sha256)||
|
|
62
|
+
!Number.isSafeInteger(result.source_file_count)||!Number.isSafeInteger(intent.source_file_count)||
|
|
63
|
+
!Number.isSafeInteger(result.configured_timeout_ms)||result.configured_timeout_ms<1000||
|
|
64
|
+
!hex(result.stdout_sha256)||!hex(result.stderr_sha256)||
|
|
65
|
+
typeof result.timed_out!=='boolean'||!Number.isFinite(result.elapsed_ms)||result.elapsed_ms<0||
|
|
66
|
+
!(result.failure_reason===null||(typeof result.failure_reason==='string'&&result.failure_reason.length>0))||
|
|
67
|
+
!['PASS','FAILED','OUTCOME_UNKNOWN'].includes(result.status)||
|
|
68
|
+
(result.status==='PASS'&&(result.timed_out||result.failure_reason!==null||result.exit_code!==0))){
|
|
69
|
+
faults.push({index:shard.index,reason:'V2_CONTRACT_INVALID'});continue;
|
|
70
|
+
}
|
|
71
|
+
if(result.nonpass_cases!==undefined){
|
|
72
|
+
const cases=result.nonpass_cases;
|
|
73
|
+
const valid=Array.isArray(cases)&&cases.length<=10000&&
|
|
74
|
+
cases.every(c=>c&&typeof c==='object'&&!Array.isArray(c)&&
|
|
75
|
+
['SKIP','TODO'].includes(c.kind)&&typeof c.name==='string'&&
|
|
76
|
+
c.name.length>=1&&c.name.length<=256&&!/[\x00-\x1f\x7f-\x9f]/u.test(c.name)&&
|
|
77
|
+
(c.reason===null||(typeof c.reason==='string'&&c.reason.length>=1&&c.reason.length<=256&&!/[\x00-\x1f\x7f-\x9f]/u.test(c.reason))))&&
|
|
78
|
+
cases.filter(c=>c.kind==='SKIP').length===result.skipped&&
|
|
79
|
+
cases.filter(c=>c.kind==='TODO').length===result.todo&&
|
|
80
|
+
result.nonpass_fully_classified===cases.every(c=>c.reason!==null);
|
|
81
|
+
if(!valid){faults.push({index:shard.index,reason:'NONPASS_LEDGER_INVALID'});continue;}
|
|
82
|
+
}
|
|
83
|
+
if(intent.test_tree_sha256!==testTreeSha||result.source_tree_sha256!==tree.sha256||
|
|
84
|
+
result.source_file_count!==tree.file_count||
|
|
85
|
+
result.configured_timeout_ms!==r67ShardTimeoutMs(shard)||
|
|
86
|
+
intent.plan_sha256!==plan.plan_sha256||intent.files_sha256!==shard.files_sha256){
|
|
87
|
+
stale.push(shard.index);continue;
|
|
88
|
+
}
|
|
89
|
+
// New failure identity extension applies only to current-source receipts; pre-extension
|
|
90
|
+
// historical v2 receipts stay STALE_SOURCE rather than a fabricated tampering charge.
|
|
91
|
+
if(result.failure_cases!==undefined||result.failure_cases_complete!==undefined||result.failed>0){
|
|
92
|
+
const cases=result.failure_cases;
|
|
93
|
+
const valid=Array.isArray(cases)&&cases.length<=128&&
|
|
94
|
+
typeof result.failure_cases_complete==='boolean'&&
|
|
95
|
+
cases.every(c=>c&&typeof c==='object'&&!Array.isArray(c)&&
|
|
96
|
+
Object.keys(c).sort().join('|')==='name|name_sha256'&&
|
|
97
|
+
typeof c.name==='string'&&c.name.length>0&&c.name.length<=256&&
|
|
98
|
+
!/[\x00-\x1f\x7f-\x9f]/u.test(c.name)&&hex(c.name_sha256)&&
|
|
99
|
+
(c.name==='[redacted]'||sha(Buffer.from(c.name,'utf8'))===c.name_sha256))&&
|
|
100
|
+
(result.failure_cases_complete?cases.length===result.failed:cases.length<=result.failed);
|
|
101
|
+
if(!valid){faults.push({index:shard.index,reason:'FAILED_CASE_LEDGER_INVALID'});continue;}
|
|
102
|
+
}
|
|
103
|
+
verified.push(result);
|
|
104
|
+
if(result.status==='OUTCOME_UNKNOWN')unknown.push(shard.index);
|
|
105
|
+
if(result.skipped>0||result.todo>0||result.cancelled>0){
|
|
106
|
+
const caseEvidence={index:shard.index,skipped:result.skipped,todo:result.todo,cancelled:result.cancelled,...(Array.isArray(result.nonpass_cases)?{cases:result.nonpass_cases}:{}),...(typeof result.nonpass_fully_classified==='boolean'?{fully_classified:result.nonpass_fully_classified}:{})};
|
|
107
|
+
if(result.cancelled===0&&result.todo===0&&result.nonpass_fully_classified===true&&
|
|
108
|
+
Array.isArray(result.nonpass_cases)&&result.nonpass_cases.length===result.skipped&&
|
|
109
|
+
result.nonpass_cases.every(c=>c.kind==='SKIP'&&typeof c.reason==='string'&&c.reason.trim().length>0))classified.push(caseEvidence);
|
|
110
|
+
else unclassified.push(caseEvidence);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const verdict=reconcileR67Shards(plan,verified);
|
|
114
|
+
const status=faults.length?'FAILED_INTEGRITY':
|
|
115
|
+
stale.length?'STALE_SOURCE':
|
|
116
|
+
unknown.length?'OUTCOME_UNKNOWN':
|
|
117
|
+
verdict.status!=='PASS'?verdict.status:
|
|
118
|
+
unclassified.length?'BLOCKED_NONPASS_CLASSIFICATION':'PASS';
|
|
119
|
+
return {
|
|
120
|
+
schema:'deadbyte.r67-persistent-regression-reconciliation.v2',
|
|
121
|
+
operation_id:operationId,status,plan_sha256:plan.plan_sha256,
|
|
122
|
+
test_tree_sha256:testTreeSha,source_tree_sha256:tree.sha256,
|
|
123
|
+
source_file_count:tree.file_count,test_file_count:plan.file_count,
|
|
124
|
+
verified_receipts:verified.length,integrity_faults:faults,
|
|
125
|
+
stale_indices:stale,unknown_indices:unknown,unclassified_nonpass:unclassified,classified_nonpass:classified,
|
|
126
|
+
verdict,production_mutation_performed:false
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {createHash} from 'node:crypto';
|
|
2
|
+
import {codingSessionStatus} from './coding-plane.mjs';
|
|
3
|
+
|
|
4
|
+
const HEX64=/^[0-9a-f]{64}$/;
|
|
5
|
+
const hash=s=>createHash('sha256').update(s,'utf8').digest('hex');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A server-owned callback, never a model-supplied permission.
|
|
9
|
+
* Opt-in is separate from the coding standing grant; every request and its
|
|
10
|
+
* completion re-reads the live epoch and checks revocation.
|
|
11
|
+
*/
|
|
12
|
+
export function createR67NativeObservationAuthority({
|
|
13
|
+
getCodingPolicy,
|
|
14
|
+
enabled=false,
|
|
15
|
+
sessionStatus=codingSessionStatus,
|
|
16
|
+
rootId='projects'
|
|
17
|
+
}={}){
|
|
18
|
+
if(typeof getCodingPolicy!=='function'||typeof sessionStatus!=='function')throw new TypeError('trusted runtime authority providers required');
|
|
19
|
+
if(typeof enabled!=='boolean')throw new TypeError('native observer opt-in must be boolean');
|
|
20
|
+
return async ({operation,args}={})=>{
|
|
21
|
+
if(!enabled)throw new Error('R67_NATIVE_OBSERVATION_DISABLED');
|
|
22
|
+
if(!['process','identity','map'].includes(operation)||!Array.isArray(args)||args[0]!==operation)
|
|
23
|
+
throw new Error('R67_NATIVE_OBSERVATION_SCOPE_DENIED');
|
|
24
|
+
if(operation==='map'&&args[1]!=='self')throw new Error('R67_NATIVE_OBSERVATION_OTHER_PID_DENIED');
|
|
25
|
+
if(operation==='identity'&&args[1]!=='self')throw new Error('R67_NATIVE_OBSERVATION_OTHER_PID_DENIED');
|
|
26
|
+
const policy=getCodingPolicy();
|
|
27
|
+
if(!policy||!HEX64.test(policy.policy_sha256??'')||!policy.roots?.[rootId]?.read)
|
|
28
|
+
throw new Error('R67_NATIVE_OBSERVATION_POLICY_MISSING');
|
|
29
|
+
const lease=await sessionStatus(policy);
|
|
30
|
+
const epoch=lease?.authority_epoch;
|
|
31
|
+
if(!lease?.armed||lease.standing!==true||lease.reason!=='standing-grant'||
|
|
32
|
+
!Array.isArray(lease.root_ids)||!lease.root_ids.includes(rootId)||
|
|
33
|
+
!HEX64.test(lease.instance_nonce??'')||
|
|
34
|
+
epoch?.trusted!==true||!Number.isSafeInteger(epoch.seq)||epoch.seq<1||
|
|
35
|
+
!HEX64.test(epoch.event_sha256??''))
|
|
36
|
+
throw new Error('R67_NATIVE_OBSERVATION_AUTHORITY_REVOKED');
|
|
37
|
+
const grantId='coding.r67.'+hash(lease.instance_nonce+':'+epoch.event_sha256).slice(0,32);
|
|
38
|
+
return Object.freeze({
|
|
39
|
+
grant_id:grantId,
|
|
40
|
+
policy_sha256:policy.policy_sha256,
|
|
41
|
+
scope:'native-observe-readonly',
|
|
42
|
+
authority_generation:epoch.seq
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {createHash,createPublicKey,sign as edSign} from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
export const R67_OBSERVATION_DOMAIN=Buffer.from('DEADBYTE-R67-OBSERVATION-V1\0','utf8');
|
|
4
|
+
const isHex64=s=>typeof s==='string'&&/^[a-f0-9]{64}$/.test(s);
|
|
5
|
+
const sha=b=>createHash('sha256').update(b).digest('hex');
|
|
6
|
+
const canonical=value=>JSON.stringify(
|
|
7
|
+
Array.isArray(value)?value.map(v=>JSON.parse(canonical(v))):
|
|
8
|
+
value!==null&&typeof value==='object'?
|
|
9
|
+
Object.fromEntries(Object.keys(value).sort().map(k=>[k,JSON.parse(canonical(value[k]))])):
|
|
10
|
+
value
|
|
11
|
+
);
|
|
12
|
+
export const hashR67Result=result=>sha(Buffer.from(canonical(result),'utf8'));
|
|
13
|
+
|
|
14
|
+
export function signR67Observation({privateKey,publicKey,operation,requestSha256,executableSha256,result,observedAtUtc=new Date().toISOString()}){
|
|
15
|
+
if(!privateKey||!publicKey)throw new Error('R67 receipt signer must be supplied by trusted runtime; no implicit key generation');
|
|
16
|
+
if(!['process','identity','map','pe'].includes(operation)||!isHex64(requestSha256)||!isHex64(executableSha256))
|
|
17
|
+
throw new Error('R67 observation operation and 256-bit bindings required');
|
|
18
|
+
if(!result||result.schema!=='deadbyte.native-inspect.r67.v1'||!['ok','failed'].includes(result.status))
|
|
19
|
+
throw new Error('R67 result contract invalid');
|
|
20
|
+
if(result.status==='ok'&&(result.operation!==operation||!isHex64(result.data_sha256)||!result.data||
|
|
21
|
+
result.data_sha256!==sha(Buffer.from(JSON.stringify(result.data),'utf8'))))throw new Error('R67 result data digest invalid');
|
|
22
|
+
const timestamp=new Date(observedAtUtc);
|
|
23
|
+
if(!Number.isFinite(timestamp.getTime())||timestamp.toISOString()!==observedAtUtc)throw new Error('R67 observation timestamp invalid');
|
|
24
|
+
const key=publicKey?.type==='public'?publicKey:createPublicKey(publicKey);
|
|
25
|
+
const derived=createPublicKey(privateKey);
|
|
26
|
+
const spki=Buffer.from(key.export({type:'spki',format:'der'}));
|
|
27
|
+
const derivedSpki=Buffer.from(derived.export({type:'spki',format:'der'}));
|
|
28
|
+
if(key.asymmetricKeyType!=='ed25519'||!spki.equals(derivedSpki))throw new Error('R67 signer public/private identity mismatch');
|
|
29
|
+
const body={
|
|
30
|
+
schema:'deadbyte.native-observation-receipt.r67.v1',
|
|
31
|
+
observed_at_utc:observedAtUtc,
|
|
32
|
+
operation,
|
|
33
|
+
request_sha256:requestSha256,
|
|
34
|
+
executable_sha256:executableSha256,
|
|
35
|
+
result_status:result.status,
|
|
36
|
+
result_sha256:hashR67Result(result),
|
|
37
|
+
signer_key_id:sha(spki)
|
|
38
|
+
};
|
|
39
|
+
const receipt_sha256=sha(Buffer.from(canonical(body),'utf8'));
|
|
40
|
+
const signed={...body,receipt_sha256};
|
|
41
|
+
const signature=edSign(null,Buffer.concat([R67_OBSERVATION_DOMAIN,Buffer.from(canonical(signed),'utf8')]),privateKey);
|
|
42
|
+
return {...signed,attestation:{algorithm:'ed25519',key_id:body.signer_key_id,signature_base64:signature.toString('base64')}};
|
|
43
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import {createHash} from 'node:crypto';
|
|
2
|
+
import {lstat,readdir,readFile} from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
const sha=value=>createHash('sha256').update(Buffer.from(JSON.stringify(value),'utf8')).digest('hex');
|
|
5
|
+
|
|
6
|
+
export function createR67ShardPlan(filenames,shardCount=32){
|
|
7
|
+
if(!Array.isArray(filenames)||!Number.isInteger(shardCount)||shardCount<1||shardCount>64)throw new Error('invalid shard plan');
|
|
8
|
+
if(!filenames.length||filenames.length<shardCount)throw new Error('insufficient regression files');
|
|
9
|
+
const sorted=[...filenames].sort();
|
|
10
|
+
if(sorted.some((n,i)=>typeof n!=='string'||!/^test\/[a-z0-9._-]+\.test\.mjs$/.test(n)||(i>0&&n===sorted[i-1])))
|
|
11
|
+
throw new Error('invalid or duplicate test filename');
|
|
12
|
+
// Measured on Windows: 53.2s controller lifecycle and 110.4s remote lifecycle.
|
|
13
|
+
// Assign both to standalone tail shards; smaller shard counts reserve at least one normal shard.
|
|
14
|
+
const measured=['test/r58-controller-live-local.test.mjs','test/remote-session-cli.test.mjs'];
|
|
15
|
+
const present=measured.filter(name=>sorted.includes(name));
|
|
16
|
+
const isolated=present.slice(-Math.min(present.length,shardCount-1));
|
|
17
|
+
const isolatedSet=new Set(isolated);
|
|
18
|
+
const normal=sorted.filter(name=>!isolatedSet.has(name));
|
|
19
|
+
const normalShardCount=shardCount-isolated.length;
|
|
20
|
+
const shards=Array.from({length:shardCount},(_,index)=>({index,files:[]}));
|
|
21
|
+
normal.forEach((name,index)=>shards[index%normalShardCount].files.push(name));
|
|
22
|
+
isolated.forEach((name,index)=>shards[normalShardCount+index].files.push(name));
|
|
23
|
+
const plan_sha256=sha({schema:'deadbyte.r67-shard-plan.v1',shardCount,files:sorted,
|
|
24
|
+
assignment:'measured-slow-suites-isolation-v2',isolated});
|
|
25
|
+
return {schema:'deadbyte.r67-shard-plan.v1',plan_sha256,file_count:sorted.length,
|
|
26
|
+
shards:shards.map(s=>({...s,files_sha256:sha(s.files)}))};
|
|
27
|
+
}
|
|
28
|
+
export function r67ShardTimeoutMs(shard){
|
|
29
|
+
if(!shard||!Array.isArray(shard.files))throw new Error('invalid shard');
|
|
30
|
+
if(shard.files.length===1&&shard.files[0]==='test/remote-session-cli.test.mjs')return 165000;
|
|
31
|
+
if(shard.files.length===1&&shard.files[0]==='test/r58-controller-live-local.test.mjs')return 120000;
|
|
32
|
+
return 58000;
|
|
33
|
+
}
|
|
34
|
+
export function reconcileR67Shards(plan,receipts){
|
|
35
|
+
if(!plan||plan.schema!=='deadbyte.r67-shard-plan.v1'||!Array.isArray(plan.shards)||!Array.isArray(receipts))throw new Error('invalid shard proof input');
|
|
36
|
+
const seen=new Map();
|
|
37
|
+
for(const r of receipts){
|
|
38
|
+
if(!Number.isInteger(r?.shard_index)||r.shard_index<0||r.shard_index>=plan.shards.length)throw new Error('out-of-plan shard');
|
|
39
|
+
if(seen.has(r.shard_index))throw new Error('duplicate shard receipt');
|
|
40
|
+
seen.set(r.shard_index,r);
|
|
41
|
+
}
|
|
42
|
+
const missing=plan.shards.filter(s=>!seen.has(s.index)).map(s=>s.index);
|
|
43
|
+
let tampered=false,failed=false,unknown=false,passed=0,failureCount=0,testCount=0;
|
|
44
|
+
for(const shard of plan.shards){
|
|
45
|
+
const r=seen.get(shard.index);if(!r)continue;
|
|
46
|
+
const missingCounts=r.status==='OUTCOME_UNKNOWN'&&r.test_count===null&&r.passed===null&&r.failed===null;
|
|
47
|
+
const validCounts=Number.isInteger(r.test_count)&&r.test_count>=0&&
|
|
48
|
+
Number.isInteger(r.passed)&&r.passed>=0&&Number.isInteger(r.failed)&&r.failed>=0&&
|
|
49
|
+
r.passed+r.failed<=r.test_count;
|
|
50
|
+
if(r.plan_sha256!==plan.plan_sha256||r.files_sha256!==shard.files_sha256||
|
|
51
|
+
!['PASS','FAILED','OUTCOME_UNKNOWN'].includes(r.status)||!(missingCounts||validCounts)){tampered=true;continue;}
|
|
52
|
+
if(r.schema==='deadbyte.r67-shard-result.v2'){
|
|
53
|
+
const unknownWithoutCounts=r.status==='OUTCOME_UNKNOWN'&&missingCounts&&r.skipped===null&&r.cancelled===null&&r.todo===null;
|
|
54
|
+
const complete=validCounts&&Number.isInteger(r.skipped)&&r.skipped>=0&&
|
|
55
|
+
Number.isInteger(r.cancelled)&&r.cancelled===0&&Number.isInteger(r.todo)&&r.todo===0&&
|
|
56
|
+
r.passed+r.failed+r.skipped+r.cancelled+r.todo===r.test_count;
|
|
57
|
+
if(!(unknownWithoutCounts||complete)){tampered=true;continue;}
|
|
58
|
+
}
|
|
59
|
+
if(r.status==='PASS'&&(r.exit_code!==0||r.failed!==0||r.test_count<shard.files.length))tampered=true;
|
|
60
|
+
if(r.status==='FAILED'||r.exit_code!==0&&r.status!=='OUTCOME_UNKNOWN'||r.failed>0)failed=true;
|
|
61
|
+
if(r.status==='OUTCOME_UNKNOWN')unknown=true;
|
|
62
|
+
passed+=r.passed;failureCount+=r.failed;testCount+=r.test_count;
|
|
63
|
+
}
|
|
64
|
+
const status=tampered||failed?'FAILED':missing.length?'BLOCKED':unknown?'OUTCOME_UNKNOWN':'PASS';
|
|
65
|
+
return {schema:'deadbyte.r67-shard-reconciliation.v1',status,plan_sha256:plan.plan_sha256,
|
|
66
|
+
file_count:plan.file_count,shards_expected:plan.shards.length,shards_observed:seen.size,
|
|
67
|
+
missing,test_count:testCount,passed,failed:failureCount,integrity_failed:tampered};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Bind regression evidence to executable SOURCE bytes, not just test filenames. */
|
|
71
|
+
export async function r67RegressionTreeHash(root,{roots=['src','test','scripts','controller','bridge','native','docs'],
|
|
72
|
+
topLevel=['package.json','package-lock.json'],extra=['bin/deadbyte-native-inspect-r67.exe']}={}){
|
|
73
|
+
if(typeof root!=='string'||!root)throw new Error('regression source root required');
|
|
74
|
+
const seeds=[...roots,...topLevel,...extra];
|
|
75
|
+
if(!seeds.length||seeds.some(n=>typeof n!=='string'||!n||n.includes('\\')||n.split('/').some(part=>!part||part==='.'||part==='..')))
|
|
76
|
+
throw new Error('invalid regression source scope');
|
|
77
|
+
const entries=[];
|
|
78
|
+
let total=0;
|
|
79
|
+
const seen=new Set();
|
|
80
|
+
async function visit(rel){
|
|
81
|
+
if(seen.has(rel))throw new Error('duplicate regression source path');
|
|
82
|
+
seen.add(rel);
|
|
83
|
+
const file=path.join(root,...rel.split('/'));
|
|
84
|
+
const info=await lstat(file);
|
|
85
|
+
if(info.isSymbolicLink())throw new Error('symlink forbidden in regression source tree: '+rel);
|
|
86
|
+
if(info.isDirectory()){
|
|
87
|
+
const children=(await readdir(file)).sort();
|
|
88
|
+
for(const child of children)await visit(rel+'/'+child);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if(!info.isFile()||info.size>8*1024*1024)throw new Error('non-file/oversized regression source: '+rel);
|
|
92
|
+
if(entries.length>=5000||total+info.size>128*1024*1024)throw new Error('regression source budget exceeded');
|
|
93
|
+
const bytes=await readFile(file);
|
|
94
|
+
if(bytes.length!==info.size)throw new Error('regression source changed during read: '+rel);
|
|
95
|
+
total+=bytes.length;
|
|
96
|
+
entries.push([rel,bytes.length,createHash('sha256').update(bytes).digest('hex')]);
|
|
97
|
+
}
|
|
98
|
+
for(const seed of seeds)await visit(seed);
|
|
99
|
+
entries.sort((a,b)=>a[0].localeCompare(b[0],'en'));
|
|
100
|
+
return {schema:'deadbyte.r67-source-tree.v1',
|
|
101
|
+
sha256:sha({schema:'deadbyte.r67-source-tree.v1',files:entries}),
|
|
102
|
+
file_count:entries.length,bytes_total:total};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Parse complete Node TAP totals without treating missing or cancelled output as success. */
|
|
106
|
+
export function parseR67TapSummary(output){
|
|
107
|
+
if(typeof output!=='string')return null;
|
|
108
|
+
const counts=Object.create(null);
|
|
109
|
+
const keys=['tests','pass','fail','cancelled','skipped','todo'];
|
|
110
|
+
for(const raw of output.split(/\r?\n/)){
|
|
111
|
+
const line=raw.trim();
|
|
112
|
+
const match=/^(?:#|ℹ)\s+(tests|pass|fail|cancelled|skipped|todo)\s+(\d+)$/u.exec(line);
|
|
113
|
+
if(!match)continue;
|
|
114
|
+
if(Object.hasOwn(counts,match[1]))return null;
|
|
115
|
+
const n=Number(match[2]);
|
|
116
|
+
if(!Number.isSafeInteger(n))return null;
|
|
117
|
+
counts[match[1]]=n;
|
|
118
|
+
}
|
|
119
|
+
if(keys.some(k=>!Number.isSafeInteger(counts[k])))return null;
|
|
120
|
+
if(counts.tests!==counts.pass+counts.fail+counts.skipped+counts.cancelled+counts.todo)return null;
|
|
121
|
+
if(counts.cancelled!==0||counts.todo!==0)return null;
|
|
122
|
+
return Object.fromEntries(keys.map(k=>[k,counts[k]]));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function parseR67NonpassCases(output){
|
|
126
|
+
if(typeof output!=='string'||output.length>16*1024*1024)throw new Error('bounded TAP text required');
|
|
127
|
+
const keys=['tests','pass','fail','cancelled','skipped','todo'];
|
|
128
|
+
const totals=Object.create(null);
|
|
129
|
+
for(const raw of output.split(/\r?\n/)){
|
|
130
|
+
const line=raw.trim();
|
|
131
|
+
const m=/^(?:#|ℹ)\s+(tests|pass|fail|cancelled|skipped|todo)\s+(\d+)$/u.exec(line);
|
|
132
|
+
if(!m)continue;
|
|
133
|
+
if(Object.hasOwn(totals,m[1]))return null;
|
|
134
|
+
totals[m[1]]=Number(m[2]);
|
|
135
|
+
}
|
|
136
|
+
if(keys.some(k=>!Number.isSafeInteger(totals[k])))return null;
|
|
137
|
+
if(totals.tests!==totals.pass+totals.fail+totals.cancelled+totals.skipped+totals.todo)return null;
|
|
138
|
+
const summary=totals;
|
|
139
|
+
const cases=[];
|
|
140
|
+
const RX=/^\s*(?:ok|not ok)\s+\d+\s+-\s+(.+?)\s+#\s+(SKIP|TODO)(?:\s+(.*\S))?\s*$/u;
|
|
141
|
+
for(const line of output.split(/\r?\n/)){
|
|
142
|
+
const match=RX.exec(line);
|
|
143
|
+
if(!match)continue;
|
|
144
|
+
const name=match[1].trim(),reason=match[3]?.trim()||null;
|
|
145
|
+
cases.push({kind:match[2],name,reason});
|
|
146
|
+
if(cases.length>10000)throw new Error('case ledger budget exceeded');
|
|
147
|
+
}
|
|
148
|
+
const expected=summary.skipped+summary.todo;
|
|
149
|
+
const actual=cases.filter(c=>c.kind==='SKIP').length+cases.filter(c=>c.kind==='TODO').length;
|
|
150
|
+
const unclassified=cases.filter(c=>!c.name||!c.reason).length+Math.max(0,expected-actual);
|
|
151
|
+
const countersMatch=summary.skipped===cases.filter(c=>c.kind==='SKIP').length&&
|
|
152
|
+
summary.todo===cases.filter(c=>c.kind==='TODO').length;
|
|
153
|
+
return {cases,expected_nonpass:expected,fully_classified:countersMatch&&unclassified===0,
|
|
154
|
+
unclassified_count:unclassified,counters_match:countersMatch};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
/** Diagnostic-only FAILED TAP case identities. Never export stack traces or raw assertion messages. */
|
|
159
|
+
export function parseR67FailedCases(output){
|
|
160
|
+
if(typeof output!=='string'||output.length>16*1024*1024)throw new Error('bounded TAP text required');
|
|
161
|
+
const tap=parseR67TapSummary(output);
|
|
162
|
+
if(!tap)return {cases:[],count:0,complete:false,expected:null};
|
|
163
|
+
const cases=[];
|
|
164
|
+
for(const line of output.split(/\r?\n/)){
|
|
165
|
+
const match=/^\s*not ok\s+\d+\s+-\s+(.+?)\s*(?:#.*)?$/u.exec(line);
|
|
166
|
+
if(!match)continue;
|
|
167
|
+
const name=match[1].trim();
|
|
168
|
+
if(!name)continue;
|
|
169
|
+
const sensitive=/sk-(?:proj|test|live|[a-z]{10})-|bearer\s+|authorization|password\s*[:=]|(?:[A-Z]:\\Users\\|\/home\/)[^\s]+/i.test(name);
|
|
170
|
+
const printable=!/[\x00-\x1f\x7f-\x9f]/u.test(name)&&name.length<=256;
|
|
171
|
+
cases.push({name:sensitive||!printable?'[redacted]':name,
|
|
172
|
+
name_sha256:createHash('sha256').update(Buffer.from(name,'utf8')).digest('hex')});
|
|
173
|
+
if(cases.length>128)throw new Error('failed-case ledger budget exceeded');
|
|
174
|
+
}
|
|
175
|
+
return {cases,count:cases.length,expected:tap.fail,complete:tap.fail===cases.length};
|
|
176
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {createHash,createPublicKey,verify as verifySignature} from 'node:crypto';
|
|
2
|
+
import {lstat,realpath} from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import {canonicalJson} from './canonical-json.mjs';
|
|
5
|
+
|
|
6
|
+
const SUBJECT_SCHEMA='deadbyte.project-authority-subject.r70.v1';
|
|
7
|
+
const APPROVAL_SCHEMA='deadbyte.project-owner-approval.r70.v1';
|
|
8
|
+
const SCOPE='coding_autonomous_native_diff';
|
|
9
|
+
const DOMAIN=Buffer.from('DEADBYTE-R70-PROJECT-OWNER-APPROVAL\0','utf8');
|
|
10
|
+
const SHA=/^[0-9a-f]{64}$/;
|
|
11
|
+
const ID=/^[A-Za-z0-9._-]{1,64}$/;
|
|
12
|
+
const SUBJECT_FIELDS=['schema','scope','root_id','project_root','coding_policy_sha256',
|
|
13
|
+
'autonomous_policy_sha256','reviewed_artifact_sha256','issued_at_utc','expires_at_utc'];
|
|
14
|
+
const APPROVAL_FIELDS=['schema','subject_sha256','owner_key_id','subject','signature_base64'];
|
|
15
|
+
const EXPECTED_FIELDS=['rootId','projectRoot','codingPolicySha256','autonomousPolicySha256','reviewedArtifactSha256'];
|
|
16
|
+
const hash=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
17
|
+
const samePath=(a,b)=>process.platform==='win32'?a.toLowerCase()===b.toLowerCase():a===b;
|
|
18
|
+
|
|
19
|
+
function exact(value,fields,label){
|
|
20
|
+
if(!value||typeof value!=='object'||Array.isArray(value))throw new Error(`${label} invalid`);
|
|
21
|
+
const keys=Object.keys(value);
|
|
22
|
+
for(const key of keys)if(!fields.includes(key))throw new Error(`${label} unknown field '${key}'`);
|
|
23
|
+
for(const key of fields)if(!Object.hasOwn(value,key))throw new Error(`${label} missing field '${key}'`);
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
function digest(value,label){if(typeof value!=='string'||!SHA.test(value))throw new Error(`${label} invalid SHA-256`);return value;}
|
|
27
|
+
function instant(value,label){
|
|
28
|
+
if(typeof value!=='string'||!/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/.test(value))throw new Error(`${label} invalid UTC`);
|
|
29
|
+
const parsed=Date.parse(value);
|
|
30
|
+
if(!Number.isFinite(parsed)||new Date(parsed).toISOString()!==value)throw new Error(`${label} invalid UTC`);
|
|
31
|
+
return parsed;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function verifyProjectOwnerApproval({approval,ownerPublicKey,expected,now=new Date()}={}){
|
|
35
|
+
exact(expected,EXPECTED_FIELDS,'owner approval expected subject');
|
|
36
|
+
exact(approval,APPROVAL_FIELDS,'owner approval');
|
|
37
|
+
exact(approval.subject,SUBJECT_FIELDS,'owner approval subject');
|
|
38
|
+
const subject=approval.subject;
|
|
39
|
+
if(approval.schema!==APPROVAL_SCHEMA||subject.schema!==SUBJECT_SCHEMA||subject.scope!==SCOPE)throw new Error('owner approval scope/schema mismatch');
|
|
40
|
+
if(typeof subject.root_id!=='string'||!ID.test(subject.root_id)||subject.root_id!==expected.rootId)throw new Error('owner approval root identity mismatch');
|
|
41
|
+
if(typeof subject.project_root!=='string'||!path.isAbsolute(subject.project_root)||
|
|
42
|
+
typeof expected.projectRoot!=='string'||!path.isAbsolute(expected.projectRoot))throw new Error('owner approval root path invalid');
|
|
43
|
+
let real;
|
|
44
|
+
try{
|
|
45
|
+
const entry=await lstat(expected.projectRoot);
|
|
46
|
+
if(!entry.isDirectory()||entry.isSymbolicLink())throw new Error('not a real directory');
|
|
47
|
+
real=await realpath(expected.projectRoot);
|
|
48
|
+
}catch{throw new Error('owner approval project root is not a real directory');}
|
|
49
|
+
if(!samePath(path.resolve(real),path.resolve(expected.projectRoot))||
|
|
50
|
+
!samePath(path.resolve(real),path.resolve(subject.project_root)))throw new Error('owner approval real root mismatch');
|
|
51
|
+
if(digest(subject.coding_policy_sha256,'coding policy')!==digest(expected.codingPolicySha256,'expected coding policy')||
|
|
52
|
+
digest(subject.autonomous_policy_sha256,'autonomous policy')!==digest(expected.autonomousPolicySha256,'expected autonomous policy')){
|
|
53
|
+
throw new Error('owner approval policy digest mismatch');
|
|
54
|
+
}
|
|
55
|
+
if(digest(subject.reviewed_artifact_sha256,'reviewed artifact')!==digest(expected.reviewedArtifactSha256,'expected reviewed artifact')){
|
|
56
|
+
throw new Error('owner approval reviewed artifact mismatch');
|
|
57
|
+
}
|
|
58
|
+
const issued=instant(subject.issued_at_utc,'owner approval issued time');
|
|
59
|
+
const expires=instant(subject.expires_at_utc,'owner approval expiry');
|
|
60
|
+
const observed=now instanceof Date?now.getTime():NaN;
|
|
61
|
+
if(!Number.isFinite(observed)||expires<=issued||expires-issued>3_600_000)throw new Error('owner approval lifetime invalid');
|
|
62
|
+
if(observed<issued||observed>=expires)throw new Error('owner approval expired or not yet valid');
|
|
63
|
+
const subjectSha=hash(Buffer.from(canonicalJson(subject),'utf8'));
|
|
64
|
+
if(digest(approval.subject_sha256,'owner approval subject')!==subjectSha)throw new Error('owner approval subject hash mismatch');
|
|
65
|
+
let key;
|
|
66
|
+
try{key=ownerPublicKey?.type==='public'?ownerPublicKey:createPublicKey(ownerPublicKey);}catch{throw new Error('owner approval public key invalid');}
|
|
67
|
+
if(key.asymmetricKeyType!=='ed25519')throw new Error('owner approval public key must be Ed25519');
|
|
68
|
+
const keyId=hash(key.export({type:'spki',format:'der'}));
|
|
69
|
+
if(digest(approval.owner_key_id,'owner approval key id')!==keyId)throw new Error('owner approval key identity mismatch');
|
|
70
|
+
if(typeof approval.signature_base64!=='string'||!/^[A-Za-z0-9+/]{86}==$/.test(approval.signature_base64))throw new Error('owner approval signature encoding invalid');
|
|
71
|
+
const signature=Buffer.from(approval.signature_base64,'base64');
|
|
72
|
+
const signed={schema:APPROVAL_SCHEMA,subject_sha256:subjectSha,owner_key_id:keyId};
|
|
73
|
+
if(signature.length!==64||!verifySignature(null,Buffer.concat([DOMAIN,Buffer.from(canonicalJson(signed),'utf8')]),key,signature)){
|
|
74
|
+
throw new Error('owner approval signature invalid');
|
|
75
|
+
}
|
|
76
|
+
return Object.freeze({approved:true,subject_sha256:subjectSha,owner_key_id:keyId,
|
|
77
|
+
project_root:real,root_id:subject.root_id,scope:SCOPE,expires_at_utc:subject.expires_at_utc});
|
|
78
|
+
}
|