deadbyte-mcp 0.15.42 → 0.15.44
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 +131 -68
- package/PROVENANCE.json +37 -13
- package/README.txt +18 -18
- package/RELEASE.json +40 -14
- package/SBOM.cdx.json +14 -2
- package/TEST-MATRIX.json +52 -10
- 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-native-inspect-r67.exe +0 -0
- package/bin/deadbyte-native-inspect-r67.obj +0 -0
- package/bin/deadbyte-process-host.obj +0 -0
- package/bin/deadbyte-tunnel-host.obj +0 -0
- package/bridge/package.json +1 -1
- package/controller/deadbyte-desktop-policy.json +5 -5
- 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/native/deadbyte-native-inspect-r67.cpp +252 -0
- package/package.json +7 -3
- package/proof/CONTAINMENT-BUILD.txt +6 -6
- package/scripts/build-native-inspector-r67.ps1 +28 -0
- package/scripts/gate-windows-r66-prepublish.ps1 +65 -0
- package/scripts/gate-windows-r67.ps1 +56 -0
- package/scripts/npm-preserve-artifact-r67.mjs +99 -0
- package/scripts/npm-release-r48.mjs +10 -2
- package/scripts/npm-release-readiness-r67.mjs +26 -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/src/activation-continuity-engine-r65.mjs +2 -1
- package/src/autonomous-runtime.mjs +1 -1
- package/src/deadbyte-cli.mjs +33 -1
- package/src/doctor-runtime-r58.mjs +3 -1
- 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-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/remote-live-log.mjs +14 -10
- package/src/tool-audit.mjs +1 -1
- package/src/version.mjs +1 -1
- package/test/agent-runtime.test.mjs +1 -1
- package/test/autonomous-loop.test.mjs +11 -6
- package/test/autonomous-runtime.test.mjs +1 -1
- package/test/coding-plane.test.mjs +1 -1
- package/test/full-max-catalog-r46.test.mjs +1 -1
- 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-doctor-runtime.test.mjs +18 -0
- 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 +1 -1
- 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-epoch-native-activation-integration.test.mjs +4 -4
- package/test/r65-release-contract.test.mjs +2 -1
- package/test/r66-release-contract.test.mjs +57 -0
- 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 +62 -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 +38 -0
- package/test/r67-regression-shards.test.mjs +184 -0
- package/test/r67-release-contract.test.mjs +44 -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/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/semantic-multilang.test.mjs +1 -1
|
@@ -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
|
+
}
|
|
@@ -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
|
+
}
|
package/src/remote-live-log.mjs
CHANGED
|
@@ -25,7 +25,7 @@ const LIFECYCLE_ASCII=Object.freeze({
|
|
|
25
25
|
|
|
26
26
|
function safeAtom(value){
|
|
27
27
|
if(typeof value==='string'){
|
|
28
|
-
if(value.length<1||value.length>160||/[\
|
|
28
|
+
if(value.length<1||value.length>160||/[\x00-\x1f\x7f-\x9f]/u.test(value)) return null;
|
|
29
29
|
return value;
|
|
30
30
|
}
|
|
31
31
|
if(typeof value==='number'&&Number.isFinite(value)) return String(value);
|
|
@@ -55,8 +55,10 @@ const START_VISIBLE_PATTERNS=[/(?:^|_)file_write$/,/text_replace$/,/symbol_repla
|
|
|
55
55
|
|
|
56
56
|
export function selectLiveLogNames({auditNames=[],runtimeNames=[]}={}){
|
|
57
57
|
if(!Array.isArray(auditNames)||!Array.isArray(runtimeNames)) throw new Error('live log names must be arrays');
|
|
58
|
-
const audit=auditNames.filter(name=>typeof name==='string'&&/^segment-\d{8}\.jsonl$/.test(name)).sort().slice(-2);
|
|
59
|
-
|
|
58
|
+
const audit=[...new Set(auditNames.filter(name=>typeof name==='string'&&/^segment-\d{8}\.jsonl$/.test(name)))].sort().slice(-2);
|
|
59
|
+
// Runtime filenames come from discovery, but never trust caller-supplied path components.
|
|
60
|
+
// Bound number of active readers to prevent log-file flooding from exhausting handles.
|
|
61
|
+
const runtime=[...new Set(runtimeNames.filter(name=>typeof name==='string'&&/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.log$/.test(name)))].sort().slice(-64);
|
|
60
62
|
return Object.freeze({audit:Object.freeze(audit),runtime:Object.freeze(runtime)});
|
|
61
63
|
}
|
|
62
64
|
|
|
@@ -72,7 +74,7 @@ export function truncateUtf8(value,maxBytes,{ellipsis='…'}={}){
|
|
|
72
74
|
}
|
|
73
75
|
function clipped(value,max=72){
|
|
74
76
|
if(typeof value!=='string') return '';
|
|
75
|
-
const one=value.replace(/[\
|
|
77
|
+
const one=value.replace(/[\x00-\x1f\x7f-\x9f]+/gu,' ').replace(/\s{2,}/g,' ').trim();
|
|
76
78
|
return truncateUtf8(one,max);
|
|
77
79
|
}
|
|
78
80
|
function shortValue(value,max=12){
|
|
@@ -83,7 +85,7 @@ function payloadByteCount(value){
|
|
|
83
85
|
return value&&typeof value==='object'&&value.redacted===true&&Number.isFinite(Number(value.byte_count))?Number(value.byte_count):null;
|
|
84
86
|
}
|
|
85
87
|
function safePathAtom(value){
|
|
86
|
-
if(typeof value!=='string'||value.length<1||value.length>2048||/[\
|
|
88
|
+
if(typeof value!=='string'||value.length<1||value.length>2048||/[\x00-\x1f\x7f-\x9f]/u.test(value)) return null;
|
|
87
89
|
return value;
|
|
88
90
|
}
|
|
89
91
|
function rootedPath(args,key='path',rootPaths={}){
|
|
@@ -172,7 +174,7 @@ function displayClock(ms){
|
|
|
172
174
|
|
|
173
175
|
export function formatRemoteToolCall(event,{clock=()=>Date.now(),displayMode='unicode'}={}){
|
|
174
176
|
if(!event||typeof event!=='object'||Array.isArray(event)||event.event_type!=='tool.request'||
|
|
175
|
-
typeof event.tool!=='string'
|
|
177
|
+
typeof event.tool!=='string'||!/^[A-Za-z][A-Za-z0-9_.-]{0,127}$/.test(event.tool)) return null;
|
|
176
178
|
if(typeof clock!=='function') throw new Error('remote tool-call clock must be a function');
|
|
177
179
|
const args=sanitizeAuditArgs(event.args??{});
|
|
178
180
|
return remoteDisplayText(`${eventTimestamp(event,clock)} | ${event.tool.padEnd(TOOL_COLUMN_WIDTH)} | Arguments: ${canonicalJson(args)}`,displayMode);
|
|
@@ -186,8 +188,8 @@ function formatDuration(ms){
|
|
|
186
188
|
}
|
|
187
189
|
|
|
188
190
|
export function formatCompactAuditEvent(event){
|
|
189
|
-
if(!event||typeof event!=='object'||Array.isArray(event)||typeof event.tool!=='string'
|
|
190
|
-
if(event.event_type==='tool.request') return `▶ RUN ${event.tool}${summarizeArgs(event.args)}`;
|
|
191
|
+
if(!event||typeof event!=='object'||Array.isArray(event)||typeof event.tool!=='string'||!/^[A-Za-z][A-Za-z0-9_.-]{0,127}$/.test(event.tool)) return null;
|
|
192
|
+
if(event.event_type==='tool.request') return `▶ RUN ${event.tool}${summarizeArgs(sanitizeAuditArgs(event.args??{}))}`;
|
|
191
193
|
if(event.event_type==='tool.result'){
|
|
192
194
|
const marker=event.outcome?.is_error===true?'❌ ERR':'✅ OK';
|
|
193
195
|
const duration=formatDuration(event.outcome?.duration_ms);
|
|
@@ -361,7 +363,9 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
361
363
|
const summary=span?.summary||'';
|
|
362
364
|
const rawReason=typeof event.outcome?.reason==='string'&&event.outcome.reason?event.outcome.reason:
|
|
363
365
|
(unknown&&typeof event.outcome?.recovery?.reason==='string'?event.outcome.recovery.reason:'');
|
|
364
|
-
const
|
|
366
|
+
const screenedReason=typeof rawReason==='string'?sanitizeAuditArgs(rawReason):null;
|
|
367
|
+
const sensitiveReason=typeof rawReason==='string'&&typeof screenedReason!=='string';
|
|
368
|
+
const reason=sensitiveReason?'[REDACTED_SENSITIVE_REASON]':rawReason?clipped(rawReason,180):(unknown?'terminal result unavailable; inspect state before retry':'');
|
|
365
369
|
const category=categoryForTool(displayTool);
|
|
366
370
|
const receipt=Array.isArray(event.outcome?.receipt_sha256s)&&typeof event.outcome.receipt_sha256s[0]==='string'
|
|
367
371
|
?shortValue(event.outcome.receipt_sha256s[0],8):'';
|
|
@@ -414,7 +418,7 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
414
418
|
lastSourceSequence=sourceSequence;
|
|
415
419
|
}
|
|
416
420
|
const requestId=typeof event.request_id==='string'&&event.request_id?event.request_id:null;
|
|
417
|
-
const tool=typeof event.tool==='string'
|
|
421
|
+
const tool=typeof event.tool==='string'&&/^[A-Za-z][A-Za-z0-9_.-]{0,127}$/.test(event.tool)?event.tool:null;
|
|
418
422
|
if(event.event_type==='tool.request'&&requestId&&tool){
|
|
419
423
|
if(pending.has(requestId)||completed.has(requestId)) return null;
|
|
420
424
|
const args=event.args??{};
|
package/src/tool-audit.mjs
CHANGED
|
@@ -21,7 +21,7 @@ const secretExactKeys=new Set([
|
|
|
21
21
|
]);
|
|
22
22
|
const secretSuffixes=['_password','_passwd','_secret','_token','_credential','_credentials','_cookie','_cookies','_private_key','_recovery_code','_api_key'];
|
|
23
23
|
const payloadKeys=new Set(['content','file_text','text','body','new_string','old_string','raw_input','raw_text','data_base64']);
|
|
24
|
-
const secretValuePattern=/(?:^|[^a-z])(bearer\s+\S+|(?:api[-_]?key|access[-_]?token|refresh[-_]?token|token|password|secret)\s*[=:]\s*\S
|
|
24
|
+
const secretValuePattern=/(?:^|[^a-z])(bearer\s+\S+|(?:api[-_]?key|access[-_]?token|refresh[-_]?token|token|password|secret)\s*[=:]\s*\S+|sk-(?:proj-|svcacct-|admin-)?[A-Za-z0-9_-]{12,}|gh[pousr]_[A-Za-z0-9_]{12,}|github_pat_[A-Za-z0-9_]{12,}|-----BEGIN [^-]*PRIVATE KEY-----)/i;
|
|
25
25
|
const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
26
26
|
const utf8Bytes=value=>Buffer.from(typeof value==='string'?value:canonicalJson(value),'utf8');
|
|
27
27
|
const byteCount=value=>utf8Bytes(value).length;
|
package/src/version.mjs
CHANGED
|
@@ -69,7 +69,7 @@ async function cleanup(f, client) {
|
|
|
69
69
|
await rm(f.trust, { recursive: true, force: true }).catch(() => {});
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
test
|
|
72
|
+
test('R44 historical agent catalog contract is frozen; current R47 runtime supersedes its base surface',{skip:'Historical R44 13-tool catalog deliberately superseded by R47; not applicable to current release'}, async () => {
|
|
73
73
|
const f = await fixture();
|
|
74
74
|
let client;
|
|
75
75
|
try {
|