deadbyte-mcp 0.15.44 → 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.
Files changed (157) hide show
  1. package/MANIFEST.SHA256 +69 -131
  2. package/PROVENANCE.json +257 -243
  3. package/README.txt +18 -18
  4. package/RELEASE.json +348 -334
  5. package/SBOM.cdx.json +2 -2
  6. package/TEST-MATRIX.json +503 -491
  7. package/bridge/package.json +1 -1
  8. package/controller/deadbyte-controller.ps1 +1857 -1769
  9. package/controller/deadbyte-desktop-policy.json +6 -6
  10. package/controller/deadbyte-process-policy.json +4 -4
  11. package/docs/R68-COMPAT-HOST-EXEC-CANDIDATE.md +63 -0
  12. package/docs/R69-LIVE-OUTCOME-R70-POLICY-TRANSFER.md +136 -0
  13. package/docs/R70-CODING-DIGEST-SUPERVISOR-CHECKPOINT-20261013.md +42 -0
  14. package/docs/R70-FINITE-GRANT-OWNER-BOUNDARY-20261013.md +80 -0
  15. package/docs/R70-OWNER-BOOTSTRAP-CHECKPOINT-20261013.md +60 -0
  16. package/docs/R70-REFERENCE-MECHANISMS.json +95 -0
  17. package/docs/R70-TRUST-GUARD-EXPIRY-CHECKPOINT-20261013.md +47 -0
  18. package/package.json +2 -2
  19. package/scripts/benchmark-l0-r71.mjs +76 -0
  20. package/scripts/gate-windows-r71.ps1 +64 -0
  21. package/scripts/npm-release-r48.mjs +2 -0
  22. package/scripts/npm-release-readiness-r71.mjs +21 -0
  23. package/scripts/verify-r70-full-regression.ps1 +72 -0
  24. package/src/authority-continuity-r65.mjs +28 -8
  25. package/src/autonomous-mcp-tools.mjs +1 -1
  26. package/src/autonomous-output-contracts.mjs +4 -1
  27. package/src/autonomous-policy.mjs +54 -7
  28. package/src/capability-ledger.mjs +1 -1
  29. package/src/capability-resolver.mjs +2 -1
  30. package/src/commander-compat-runtime.mjs +30 -2
  31. package/src/deadbyte-cli.mjs +8 -6
  32. package/src/native-diff-v2.mjs +18 -5
  33. package/src/native-diff.mjs +21 -5
  34. package/src/r70-owner-approval.mjs +78 -0
  35. package/src/remote-live-log.mjs +48 -14
  36. package/src/remote-result-journal.mjs +1 -1
  37. package/src/runtime-update.mjs +2051 -1891
  38. package/src/tool-audit.mjs +2 -0
  39. package/src/version.mjs +1 -1
  40. package/test/autonomous-local-supervisor.test.mjs +2 -1
  41. package/test/autonomous-output-contract.test.mjs +17 -1
  42. package/test/autonomous-supervisor-integration.test.mjs +2 -1
  43. package/test/capability-resolver.test.mjs +17 -0
  44. package/test/codex-autonomous-r44.test.mjs +1 -1
  45. package/test/commander-compat-runtime.test.mjs +42 -0
  46. package/test/controller-policy-preflight-r59.test.mjs +1 -0
  47. package/test/native-diff-topology-r36.test.mjs +35 -2
  48. package/test/native-diff.test.mjs +83 -3
  49. package/test/r59-tool-surface-compat.test.mjs +9 -5
  50. package/test/r65-authority-runtime-epoch.test.mjs +9 -1
  51. package/test/r67-isolated-slot-rollback-restore.test.mjs +2 -1
  52. package/test/r67-npm-publish-gate.test.mjs +4 -2
  53. package/test/r67-release-contract.test.mjs +8 -7
  54. package/test/r69-live-outcome.test.mjs +137 -0
  55. package/test/r70-autonomous-grant-expiry.test.mjs +89 -0
  56. package/test/r70-autonomous-policy-bootstrap.test.mjs +166 -0
  57. package/test/r70-controller-autonomous-root.test.mjs +104 -0
  58. package/test/r70-controller-grant-subset.test.mjs +28 -0
  59. package/test/r70-controller-owner-issue-boundary.test.mjs +80 -0
  60. package/test/r70-controller-policy-snapshot.test.mjs +66 -0
  61. package/test/r70-owner-approval.test.mjs +67 -0
  62. package/test/r71-full-verifier-artifact.test.mjs +28 -0
  63. package/test/r71-isolated-slot-rollback-restore.test.mjs +62 -0
  64. package/test/r71-native-diff-mcp-wire.test.mjs +75 -0
  65. package/test/r71-publish-fence.test.mjs +38 -0
  66. package/test/r71-release-identity.test.mjs +31 -0
  67. package/test/remote-result-journal.test.mjs +12 -0
  68. package/test/remote-session-cli.test.mjs +2 -1
  69. package/test/standing-grants.test.mjs +2 -2
  70. package/test/tool-audit.test.mjs +14 -0
  71. package/bin/appcontainer-stage.obj +0 -0
  72. package/bin/bootstrap-advapi32.obj +0 -0
  73. package/bin/bootstrap-exitcode.obj +0 -0
  74. package/bin/bootstrap-kernel32.obj +0 -0
  75. package/bin/child-control-probe.obj +0 -0
  76. package/bin/child-control-stage.obj +0 -0
  77. package/bin/contained-reverse-worker.obj +0 -0
  78. package/bin/contained-transform-worker.obj +0 -0
  79. package/bin/containment-probe.obj +0 -0
  80. package/bin/deadbyte-contain.obj +0 -0
  81. package/bin/deadbyte-desktop-input-r40.obj +0 -0
  82. package/bin/deadbyte-desktop-observer-r40.obj +0 -0
  83. package/bin/deadbyte-desktop-uia-r40.obj +0 -0
  84. package/bin/deadbyte-exec.obj +0 -0
  85. package/bin/deadbyte-hardware-observer-r45.obj +0 -0
  86. package/bin/deadbyte-native-inspect-r67.obj +0 -0
  87. package/bin/deadbyte-process-host.obj +0 -0
  88. package/bin/deadbyte-tunnel-host.obj +0 -0
  89. package/conpty-input-observer.obj +0 -0
  90. package/miniconpty.obj +0 -0
  91. package/proof/CONTAINMENT-BUILD.txt +0 -163
  92. package/proof/MCP-SMOKE-STATUS.txt +0 -9
  93. package/proof/NODE-SYNTAX.txt +0 -4
  94. package/proof/PROOF.txt +0 -6
  95. package/proof/R34-TASK8-SUPERVISOR-CHECKPOINT.json +0 -29
  96. package/proof/R34-TASK9-MCP-SURFACE-CHECKPOINT.json +0 -28
  97. package/proof/SECRET-SCAN.txt +0 -4
  98. package/proof/SOURCE-GATES.txt +0 -16
  99. package/proof/STATUS.txt +0 -11
  100. package/proof/TARGET-V0313-CONTAINMENT-PASS.sha256 +0 -1
  101. package/proof/TARGET-V0313-CONTAINMENT-PASS.txt +0 -215
  102. package/proof/TARGET-V040-CONTAINED-IPC-FAIL.sha256 +0 -1
  103. package/proof/TARGET-V040-CONTAINED-IPC-FAIL.txt +0 -299
  104. package/proof/TARGET-V041-CONTAINED-CREATEPROCESS-203.sha256 +0 -1
  105. package/proof/TARGET-V041-CONTAINED-CREATEPROCESS-203.txt +0 -311
  106. package/proof/TARGET-V042-CONTAINED-CREATEPROCESS-203.sha256 +0 -1
  107. package/proof/TARGET-V042-CONTAINED-CREATEPROCESS-203.txt +0 -313
  108. package/proof/TARGET-V043-CONTAINED-CREATEPROCESS-203.sha256 +0 -1
  109. package/proof/TARGET-V043-CONTAINED-CREATEPROCESS-203.txt +0 -308
  110. package/proof/TARGET-V044-CONTAINED-PASS.sha256 +0 -1
  111. package/proof/TARGET-V044-CONTAINED-PASS.txt +0 -432
  112. package/proof/TARGET-V045-DUAL-OPERATION-PASS.sha256 +0 -1
  113. package/proof/TARGET-V045-DUAL-OPERATION-PASS.txt +0 -481
  114. package/proof/TARGET-V046-FIXED-EXECUTABLE-REGISTRY-PASS.sha256 +0 -1
  115. package/proof/TARGET-V046-FIXED-EXECUTABLE-REGISTRY-PASS.txt +0 -610
  116. package/proof/TARGET-V047-RUNTIME-MANIFEST-PASS.sha256 +0 -1
  117. package/proof/TARGET-V047-RUNTIME-MANIFEST-PASS.txt +0 -642
  118. package/proof/TARGET-V050-BATCH-GATE-PASS.sha256 +0 -1
  119. package/proof/TARGET-V050-BATCH-GATE-PASS.txt +0 -713
  120. package/proof/TESTS.txt +0 -686
  121. package/proof/TRUST-EPHEMERAL.txt +0 -4
  122. package/proof/V03-FREEZE.txt +0 -27
  123. package/proof/V041-IPC-HOTFIX.txt +0 -16
  124. package/proof/V042-ENV-HOTFIX.txt +0 -15
  125. package/proof/V043-DRIVE-CWD-HOTFIX.txt +0 -14
  126. package/proof/V044-LOCALAPPDATA-HOTFIX.txt +0 -18
  127. package/proof/V045-OPERATION-REGISTRY.txt +0 -7
  128. package/proof/V046-WORKER-REGISTRY.txt +0 -12
  129. package/proof/V047-WORKER-MANIFEST.txt +0 -18
  130. package/proof/V050-BATCH-TRUST-CLOSURE.txt +0 -21
  131. package/proof/V060-ACTIVE-NETWORK-AUTHORITY.txt +0 -29
  132. package/proof/V070-NODE-SYNTAX.txt +0 -36
  133. package/proof/V070-NODE-TESTS.txt +0 -752
  134. package/proof/V070-PACK-DRY-RUN.txt +0 -126
  135. package/proof/V070-SOURCE-GATE.txt +0 -30
  136. package/proof/V070-STATIC-SCAN.txt +0 -4
  137. package/proof/V070-WINDOWS-GATE-PENDING.txt +0 -25
  138. package/proof/V071-NODE-SYNTAX.txt +0 -1
  139. package/proof/V071-NODE-TESTS.txt +0 -796
  140. package/proof/V071-PACK-DRY-RUN.json +0 -656
  141. package/proof/V071-SOURCE-GATE.txt +0 -16
  142. package/proof/V071-WINDOWS-GATE-PENDING.txt +0 -24
  143. package/proof/V073-NODE-SYNTAX.txt +0 -4
  144. package/proof/V073-SOURCE-GATE.txt +0 -21
  145. package/proof/V073-STATIC-SCAN.txt +0 -10
  146. package/proof/V073-WINDOWS-GATE-PENDING.txt +0 -5
  147. package/proof/VERIFY.txt +0 -7
  148. package/proof/WINDOWS-CONTAINED-STATUS.txt +0 -5
  149. package/proof/WINDOWS-CONTAINMENT-STATUS.txt +0 -3
  150. package/proof/commander-closure-20260927/CONTINUATION.json +0 -43
  151. package/proof/commander-closure-20260927/RESULT.json +0 -30
  152. package/proof/commander-closure-20260927/SEARCH-CONTINUATION.json +0 -30
  153. package/proof/commander-closure-20260927/XLS-REFERENCE-PROBE.json +0 -13
  154. package/proof/commander-closure-20260928/DIFFERENTIAL-CHECKPOINT.json +0 -122
  155. package/proof/r59-full-regression-current.json +0 -13
  156. package/proof/r59-full-regression-current.stderr.log +0 -0
  157. package/proof/r59-full-regression-current.stdout.log +0 -1268
@@ -16,7 +16,7 @@ import { exportOfflineDiagnostics } from './r67-diagnostic-export.mjs';
16
16
  import { verifyOfflineDiagnosticsZip } from './r67-diagnostics-verify.mjs';
17
17
  import { parseActivePointer } from './release-slot.mjs';
18
18
  import { printDoctor } from './doctor-r45.mjs';
19
- import { createLiveTimelineFilter, createLiveTimelineTracker, createLiveTimelineWriter, formatCompactAuditEvent, formatRemoteLifecycle, formatRemoteToolCall, formatRuntimeDiagnosticLine, selectLiveLogNames } from './remote-live-log.mjs';
19
+ import { classifyRemoteAuditOutcome, createLiveTimelineFilter, createLiveTimelineTracker, createLiveTimelineWriter, formatCompactAuditEvent, formatRemoteLifecycle, formatRemoteToolCall, formatRuntimeDiagnosticLine, selectLiveLogNames } from './remote-live-log.mjs';
20
20
  import { prepareRemoteConsole, remoteDisplayText } from './remote-console.mjs';
21
21
  import { ensureRuntimeCoherence } from './runtime-coherence.mjs';
22
22
  import { windowsPowerShellEnvironment } from './runtime-update.mjs';
@@ -270,13 +270,15 @@ function auditTimelineRecord(file,line,auditEvent=null){
270
270
  if(event.event_type==='tool.request') return {event_type:'tool.call',request_id:event.request_id,tool:event.tool,
271
271
  observed_at_utc:observed,arguments:event.args??{},payload_sha256};
272
272
  if(event.event_type==='tool.result'){
273
- const unknown=event.outcome?.side_effect_state==='unknown';
273
+ const classified=classifyRemoteAuditOutcome(event);
274
+ const unknown=classified==='unknown';
274
275
  const recoveryReason=unknown&&typeof event.outcome?.recovery?.reason==='string'?event.outcome.recovery.reason:null;
275
276
  return {event_type:'tool.result',request_id:event.request_id,tool:event.tool,
276
- status:event.outcome?.is_error===true?'error':'ok',observed_at_utc:observed,payload_sha256,
277
+ status:classified==='pending'?'pending':classified==='ok'?'ok':'error',observed_at_utc:observed,payload_sha256,
277
278
  ...(Number.isFinite(Number(event.outcome?.duration_ms))?{duration_ms:Number(event.outcome.duration_ms)}:{}),
278
279
  ...(unknown?{side_effect_state:'unknown'}:{}),
279
- ...(typeof event.outcome?.reason==='string'&&event.outcome.reason?{reason:event.outcome.reason}:(recoveryReason?{reason:recoveryReason}:{}))};
280
+ ...(typeof event.outcome?.reason==='string'&&event.outcome.reason?{reason:event.outcome.reason}:(recoveryReason?{reason:recoveryReason}:
281
+ (classified==='pending'&&typeof event.outcome?.status==='string'?{reason:event.outcome.status}:{})))};
280
282
  }
281
283
  return null;
282
284
  }
@@ -296,9 +298,9 @@ async function replayRemoteResults(root,cursor,{displayMode='unicode'}={}){
296
298
  continue;
297
299
  }
298
300
  const unknown=event.side_effect_state==='unknown';
299
- const marker=unknown?'OUTCOME_UNKNOWN':event.status==='error'?(displayMode==='ascii'?'ERR':'✗'):(displayMode==='ascii'?'OK':'✓');
301
+ const marker=unknown?'OUTCOME_UNKNOWN':event.status==='pending'?'PENDING':event.status==='error'?(displayMode==='ascii'?'ERR':'✗'):(displayMode==='ascii'?'OK':'✓');
300
302
  const duration=Number.isFinite(Number(event.duration_ms))?` ${event.duration_ms<1000?Math.round(event.duration_ms)+'ms':(event.duration_ms/1000).toFixed(2)+'s'}`:'';
301
- const reason=(event.status==='error'||unknown)&&typeof event.reason==='string'&&event.reason?` — ${event.reason}`:'';
303
+ const reason=(event.status==='error'||event.status==='pending'||unknown)&&typeof event.reason==='string'&&event.reason?` — ${event.reason}`:'';
302
304
  process.stdout.write(remoteDisplayText(`[REPLAY] #${event.seq} ${event.tool}${duration} ${marker}${reason}\n`,displayMode));
303
305
  }
304
306
  next=page.next_cursor;
@@ -4,7 +4,7 @@ import path from 'node:path';
4
4
  import { canonicalJson } from './canonical-json.mjs';
5
5
  import { validatePortableRelativePath } from './path-policy.mjs';
6
6
  import { loadPrivateSigningKey, loadPublicVerifyKey } from './trust.mjs';
7
- import { assertAutonomousArmed, recheckAutonomousAuthority } from './autonomous-policy.mjs';
7
+ import { assertAutonomousArmed, autonomousSessionStatus, recheckAutonomousAuthority } from './autonomous-policy.mjs';
8
8
 
9
9
  export const NATIVE_DIFF_V2_SCHEMA='deadbyte.native-diff.v2';
10
10
  export const NATIVE_DIFF_V2_PREIMAGE_SCHEMA='deadbyte.native-diff-preimage.v2';
@@ -107,9 +107,16 @@ export function createNativeDiffV2Runtime({policy,codingRuntime,machineFsRuntime
107
107
  if(!machineFsRuntime?.state||!machineFsRuntime?.removeEmptyDir) throw new Error('native diff V2 requires machine fs topology runtime');
108
108
  if(!signingKeyPath||!verifyKeyPath) throw new Error('native diff V2 requires trust keys');
109
109
 
110
- async function materializeOperations(rootId,rawOperations){
111
- if(!policy.root_ids.includes(rootId)) throw new Error(`native diff root '${rootId}' is not allowed`);
112
- await assertAutonomousArmed(policy,rootId);
110
+ async function materializeOperations(rootId,rawOperations,{codingOnly=false}={}){
111
+ if(codingOnly){
112
+ const lease=await codingRuntime.sessionStatus();
113
+ if(!lease.armed) throw new Error(`coding plane is disarmed: ${lease.reason}`);
114
+ if(!lease.root_ids.includes(rootId)||codingRuntime.policy.roots[rootId]?.read!==true)
115
+ throw new Error(`native diff preview root '${rootId}' is not allowed by Coding lease`);
116
+ } else {
117
+ if(!policy.root_ids.includes(rootId)) throw new Error(`native diff root '${rootId}' is not allowed`);
118
+ await assertAutonomousArmed(policy,rootId);
119
+ }
113
120
  const {normalized,payloadBytes}=normalizeRawOperations(rawOperations,policy);
114
121
  const operations=[];
115
122
  for(const op of normalized){
@@ -168,7 +175,13 @@ export function createNativeDiffV2Runtime({policy,codingRuntime,machineFsRuntime
168
175
  async function preview({rootId,operations:rawOperations,title='',applicationScope=null}){
169
176
  if(typeof title!=='string'||title.length>160) throw new Error('native diff title must be <=160 characters');
170
177
  if(applicationScope!==null&&!SHA256_RE.test(applicationScope)) throw new Error('native diff application_scope must be SHA-256 or null');
171
- const {operations,payloadBytes}=await materializeOperations(rootId,rawOperations);
178
+ const autonomous=policy.root_ids.includes(rootId)?await autonomousSessionStatus(policy):null;
179
+ const codingOnly=autonomous?.armed!==true||!autonomous.root_ids?.includes(rootId);
180
+ const {operations,payloadBytes}=await materializeOperations(rootId,rawOperations,{codingOnly});
181
+ if(codingOnly) return {status:'preview_only',schema:NATIVE_DIFF_V2_SCHEMA,root_id:rootId,
182
+ operation_count:operations.length,payload_bytes:payloadBytes,
183
+ paths:operations.map(op=>op.path??op.source_path),
184
+ operations:operations.map(({content,old_string,new_string,...op})=>op)};
172
185
  const identity={schema:NATIVE_DIFF_V2_SCHEMA,policy_sha256:policy.policy_sha256,coding_policy_sha256:policy.coding_policy_sha256,root_id:rootId,title,operations,...(applicationScope===null?{}:{application_scope:applicationScope})};
173
186
  const diffId=sha256Object(identity); const ctx=diffContext(policy,diffId); const signing=await loadPrivateSigningKey(signingKeyPath);
174
187
  const envelope=signEnvelope(signing,DIFF_DOMAIN,{...identity,diff_id:diffId,created_at_utc:new Date().toISOString()});
@@ -4,7 +4,7 @@ import path from 'node:path';
4
4
  import { canonicalJson } from './canonical-json.mjs';
5
5
  import { validatePortableRelativePath } from './path-policy.mjs';
6
6
  import { loadPrivateSigningKey, loadPublicVerifyKey } from './trust.mjs';
7
- import { assertAutonomousArmed, recheckAutonomousAuthority } from './autonomous-policy.mjs';
7
+ import { assertAutonomousArmed, autonomousSessionStatus, recheckAutonomousAuthority } from './autonomous-policy.mjs';
8
8
  import { createNativeDiffV2Runtime, NATIVE_DIFF_V2_SCHEMA } from './native-diff-v2.mjs';
9
9
 
10
10
  export const NATIVE_DIFF_SCHEMA = 'deadbyte.native-diff.v1';
@@ -102,9 +102,19 @@ function createNativeDiffRuntimeV1({ policy, codingRuntime, signingKeyPath, veri
102
102
  if (!policy || !codingRuntime?.policy) throw new Error('native diff runtime requires autonomous and coding policy');
103
103
  if (!signingKeyPath || !verifyKeyPath) throw new Error('native diff runtime requires trust keys');
104
104
 
105
- async function materializeOperations(rootId, rawOperations) {
106
- if (!policy.root_ids.includes(rootId)) throw new Error(`native diff root '${rootId}' is not allowed`);
107
- await assertAutonomousArmed(policy, rootId);
105
+ async function materializeOperations(rootId, rawOperations, {codingOnly=false} = {}) {
106
+ if (codingOnly) {
107
+ // Preview authorization delegates ONLY to the existing, locally armed
108
+ // Coding read lease. No Autonomous grant, signer, or target mutation.
109
+ const lease=await codingRuntime.sessionStatus();
110
+ if (!lease.armed) throw new Error(`coding plane is disarmed: ${lease.reason}`);
111
+ if (!lease.root_ids.includes(rootId) || codingRuntime.policy.roots[rootId]?.read !== true) {
112
+ throw new Error(`native diff preview root '${rootId}' is not allowed by Coding lease`);
113
+ }
114
+ } else {
115
+ if (!policy.root_ids.includes(rootId)) throw new Error(`native diff root '${rootId}' is not allowed`);
116
+ await assertAutonomousArmed(policy, rootId);
117
+ }
108
118
  const { normalized, payloadBytes } = normalizeRawOperations(rawOperations, policy);
109
119
  const operations = [];
110
120
  for (const op of normalized) {
@@ -132,7 +142,13 @@ function createNativeDiffRuntimeV1({ policy, codingRuntime, signingKeyPath, veri
132
142
  async function preview({ rootId, operations:rawOperations, title = '', applicationScope = null }) {
133
143
  if (typeof title !== 'string' || title.length > 160) throw new Error('native diff title must be <=160 characters');
134
144
  if (applicationScope !== null && !SHA256_RE.test(applicationScope)) throw new Error('native diff application_scope must be SHA-256 or null');
135
- const { operations, payloadBytes } = await materializeOperations(rootId, rawOperations);
145
+ const autonomous=policy.root_ids.includes(rootId)?await autonomousSessionStatus(policy):null;
146
+ const codingOnly=autonomous?.armed!==true||!autonomous.root_ids?.includes(rootId);
147
+ const { operations, payloadBytes } = await materializeOperations(rootId, rawOperations, {codingOnly});
148
+ if (codingOnly) return {status:'preview_only',schema:NATIVE_DIFF_SCHEMA,root_id:rootId,
149
+ operation_count:operations.length,payload_bytes:payloadBytes,
150
+ paths:operations.map(op=>op.path),
151
+ operations:operations.map(({content,old_string,new_string,...op})=>op)};
136
152
  const identity = { schema:NATIVE_DIFF_SCHEMA, policy_sha256:policy.policy_sha256,
137
153
  coding_policy_sha256:policy.coding_policy_sha256, root_id:rootId, title, operations,
138
154
  ...(applicationScope === null ? {} : { application_scope:applicationScope }) };
@@ -0,0 +1,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
+ }
@@ -183,6 +183,7 @@ export function formatRemoteToolCall(event,{clock=()=>Date.now(),displayMode='un
183
183
  function formatDuration(ms){
184
184
  const value=Number(ms);
185
185
  if(!Number.isFinite(value)||value<0) return '';
186
+ if(value<1) return value===0?'0ms':`${Number(value.toPrecision(3))}ms`;
186
187
  if(value<1000) return `${Math.round(value)}ms`;
187
188
  return `${(value/1000).toFixed(2)}s`;
188
189
  }
@@ -191,7 +192,8 @@ export function formatCompactAuditEvent(event){
191
192
  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
193
  if(event.event_type==='tool.request') return `▶ RUN ${event.tool}${summarizeArgs(sanitizeAuditArgs(event.args??{}))}`;
193
194
  if(event.event_type==='tool.result'){
194
- const marker=event.outcome?.is_error===true?'❌ ERR':'✅ OK';
195
+ const outcome=outcomeClass(event);
196
+ const marker=outcome==='unknown'?'⚠ UNKNOWN':outcome==='error'?'❌ ERR':outcome==='pending'?'⏳ PENDING':'✅ OK';
195
197
  const duration=formatDuration(event.outcome?.duration_ms);
196
198
  return `${marker} ${event.tool}${duration?` ${duration}`:''}`;
197
199
  }
@@ -251,9 +253,16 @@ function traceText(meta,displayMode='unicode'){
251
253
  (meta.parent_span_id?' parent='+shortTraceValue(meta.parent_span_id,displayMode):'');
252
254
  }
253
255
  function outcomeClass(event){
254
- if(event?.outcome?.side_effect_state==='unknown') return 'unknown';
255
- return event?.outcome?.is_error===true?'error':'ok';
256
- }
256
+ const outcome=event?.outcome;
257
+ if(outcome?.side_effect_state==='unknown') return 'unknown';
258
+ const status=outcome?.status;
259
+ if(status==='timeout'||status==='timed_out') return 'unknown';
260
+ if(outcome?.is_error===true||
261
+ ['failed','denied','rejected','blocked','observed_failure','cancelled','canceled','unavailable','unsupported'].includes(status)) return 'error';
262
+ if(['pending','running','input_required','paused','processing','accepted'].includes(status)) return 'pending';
263
+ return 'ok';
264
+ }
265
+ export function classifyRemoteAuditOutcome(event){return outcomeClass(event);}
257
266
  function parseFilterExpression(expression){
258
267
  if(expression===null||expression===undefined||expression==='') return Object.freeze({});
259
268
  if(typeof expression!=='string'||expression.length>512) throw new Error('timeline filter invalid');
@@ -264,7 +273,7 @@ function parseFilterExpression(expression){
264
273
  const key=part.slice(0,at).trim();const value=part.slice(at+1).trim();
265
274
  if(!['tool','category','status','trace'].includes(key)) throw new Error(`timeline filter key '${key}' unsupported`);
266
275
  if(Object.hasOwn(out,key)) throw new Error(`timeline filter key '${key}' repeated`);
267
- if(key==='status'&&!['ok','error','unknown'].includes(value)) throw new Error('timeline filter status invalid');
276
+ if(key==='status'&&!['ok','error','unknown','pending'].includes(value)) throw new Error('timeline filter status invalid');
268
277
  if(key==='category'&&!Object.hasOwn(TOOL_ICONS,value)) throw new Error('timeline filter category invalid');
269
278
  out[key]=value;
270
279
  }
@@ -287,7 +296,7 @@ export function createLiveTimelineFilter(expression=''){
287
296
  if(!requestId||!rawTool) return false;
288
297
  if(event.event_type==='tool.request'){
289
298
  const args=event.args??{};const tool=displayToolName(rawTool,args);const trace=traceIdentity(event,args);
290
- const meta={tool,category:categoryForTool(tool),trace_id:trace.trace_id};
299
+ const meta={tool,raw_tool:rawTool,category:categoryForTool(tool),trace_id:trace.trace_id};
291
300
  if(pending.has(requestId))pending.delete(requestId);
292
301
  if(pending.size>=MAX_FILTER_IDENTITIES){pending.delete(pending.keys().next().value);evicted++;}
293
302
  pending.set(requestId,meta);
@@ -296,7 +305,8 @@ export function createLiveTimelineFilter(expression=''){
296
305
  if(event.event_type==='tool.result'){
297
306
  const meta=pending.get(requestId)??{tool:rawTool,category:categoryForTool(rawTool),trace_id:traceIdentity(event).trace_id};
298
307
  pending.delete(requestId);
299
- return baseMatch(meta)&&(!spec.status||spec.status===outcomeClass(event));
308
+ const status=meta.raw_tool&&meta.raw_tool!==rawTool?'unknown':outcomeClass(event);
309
+ return baseMatch(meta)&&(!spec.status||spec.status===status);
300
310
  }
301
311
  return false;
302
312
  },
@@ -312,10 +322,20 @@ export function createLiveTimelineWriter({stream=process.stdout,displayMode='uni
312
322
  function flush(){
313
323
  if(active){stream.write('\n');active=null;}
314
324
  }
325
+ // Inspect terminal width for every emission: resize must not erase a wrapped prior line.
326
+ // Unknown Unicode cell width falls back to append-only output, never truncation.
327
+ function fitsOneRow(line){
328
+ const columns=stream.columns;
329
+ return /^[\x20-\x7e]*$/.test(line)&&(!Number.isInteger(columns)||line.length<columns);
330
+ }
315
331
  function write(line,{event_type=null,request_id=null}={}){
316
332
  if(typeof line!=='string'||!line) return;
317
333
  const tty=stream.isTTY===true&&rewrite;
318
- if(!tty){stream.write(line+'\n');return;}
334
+ if(!tty||!fitsOneRow(line)){
335
+ if(active) flush();
336
+ stream.write(line+'\n');
337
+ return;
338
+ }
319
339
  if(active&&event_type==='tool.result'&&request_id&&request_id===active.request_id){
320
340
  stream.write('\r\x1b[2K'+line+'\n');active=null;return;
321
341
  }
@@ -340,7 +360,7 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
340
360
  const MAX_PRESENTATION_IDENTITIES=4096;
341
361
  const toolCounts=new Map();
342
362
  const durationSamples=[];
343
- let resultCount=0,okCount=0,errorCount=0,unknownCount=0,mutationCount=0,totalDurationMs=0,slowest=null,burst=null,foldedHeartbeatLines=0,peakInFlight=0;
363
+ let resultCount=0,okCount=0,errorCount=0,unknownCount=0,pendingCount=0,mutationCount=0,totalDurationMs=0,durationObservedCount=0,slowest=null,burst=null,foldedHeartbeatLines=0,peakInFlight=0;
344
364
  let lastCapability=null,suppressedCapability=0,suppressedCapabilityReported=false;
345
365
  let lastSourceSequence=0,pendingOverflow=0;
346
366
 
@@ -352,8 +372,10 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
352
372
  }
353
373
  function traceSuffix(meta){return trace&&meta?' '+traceText(meta,displayMode):'';}
354
374
  function resultLine(event,span,sequence){
355
- const error=event.outcome?.is_error===true;
356
- const unknown=event.outcome?.side_effect_state==='unknown';
375
+ const outcome=outcomeClass(event);
376
+ const error=outcome==='error';
377
+ const unknown=outcome==='unknown';
378
+ const pendingResult=outcome==='pending';
357
379
  const displayTool=span?.display_tool||event.tool;
358
380
  const rawDuration=Number(event.outcome?.duration_ms);
359
381
  const duration=Number.isFinite(rawDuration)
@@ -371,6 +393,7 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
371
393
  ?shortValue(event.outcome.receipt_sha256s[0],8):'';
372
394
  const marker=unknown?'FAILED_AFTER_UNKNOWN_EFFECT':
373
395
  error?(displayMode==='ascii'?'ERR':'✗'):
396
+ pendingResult?'PENDING':
374
397
  ['write','edit','run'].includes(category)?(displayMode==='ascii'?'SUCCEEDED':'✅ SUCCEEDED'):
375
398
  (displayMode==='ascii'?'OK':'✓');
376
399
  const rawStatus=safeAtom(event.outcome?.status);
@@ -402,11 +425,12 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
402
425
  const countKey=toolCounts.has(displayTool)||toolCounts.size<256?displayTool:'[other]';
403
426
  toolCounts.set(countKey,(toolCounts.get(countKey)??0)+1);
404
427
  const outcome=outcomeClass(event);
405
- if(outcome==='unknown') unknownCount++;else if(outcome==='error') errorCount++;else okCount++;
428
+ if(outcome==='unknown') unknownCount++;else if(outcome==='error') errorCount++;else if(outcome==='pending') pendingCount++;else okCount++;
406
429
  if(['write','edit','run'].includes(categoryForTool(displayTool))) mutationCount++;
407
430
  const duration=Number(event.outcome?.duration_ms);
408
431
  if(Number.isFinite(duration)&&duration>=0){
409
432
  totalDurationMs+=duration;
433
+ durationObservedCount++;
410
434
  if(durationSamples.length<2048) durationSamples.push(duration);
411
435
  if(!slowest||duration>slowest.duration_ms) slowest={tool:displayTool,duration_ms:duration};
412
436
  }
@@ -444,6 +468,15 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
444
468
  pending.delete(requestId);
445
469
  completed.add(requestId);
446
470
  if(completed.size>MAX_PRESENTATION_IDENTITIES)completed.delete(completed.values().next().value);
471
+ if(span&&span.tool!==tool){
472
+ const mismatchEvent={...event,outcome:{status:'correlation_mismatch',is_error:true,
473
+ side_effect_state:'unknown',duration_ms:event.outcome?.duration_ms,
474
+ reason:'CORRELATION_MISMATCH: terminal tool differs from START; inspect signed audit before retry'}};
475
+ addStats(mismatchEvent,span);
476
+ const prior=flushBurst();
477
+ const line=resultLine(mismatchEvent,span,sequence);
478
+ return prior?[prior,line]:line;
479
+ }
447
480
  addStats(event,span);
448
481
  const error=event.outcome?.is_error===true;
449
482
  const nowMs=eventMs(event,clock);
@@ -502,10 +535,11 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
502
535
  const sorted=[...durationSamples].sort((a,b)=>a-b);
503
536
  const p50=sorted.length?(sorted.length%2?sorted[(sorted.length-1)/2]:(sorted[sorted.length/2-1]+sorted[sorted.length/2])/2):null;
504
537
  const p95=sorted.length?sorted[Math.max(0,Math.ceil(sorted.length*0.95)-1)]:null;
538
+ const p99=sorted.length?sorted[Math.max(0,Math.ceil(sorted.length*0.99)-1)]:null;
505
539
  return remoteDisplayText((displayMode==='ascii'?'[SUMMARY]':'📊 [SUMMARY]')+' '+resultCount+' calls | '+okCount+' ok | '+
506
- errorCount+' error'+(errorCount===1?'':'s')+' | '+unknownCount+' unknown | '+mutationCount+' mutation'+(mutationCount===1?'':'s')+
540
+ errorCount+' error'+(errorCount===1?'':'s')+' | '+unknownCount+' unknown'+(pendingCount?' | '+pendingCount+' pending':'')+' | '+mutationCount+' mutation'+(mutationCount===1?'':'s')+
507
541
  ' | tool time '+(formatDuration(totalDurationMs)||'0ms')+
508
- (p50!==null?' | p50 '+formatDuration(p50)+' | p95 '+formatDuration(p95):'')+
542
+ (p50!==null?' | p50 '+formatDuration(p50)+' | p95 '+formatDuration(p95)+' | p99 '+formatDuration(p99)+' | sample '+durationSamples.length+'/'+durationObservedCount:'')+
509
543
  (slowest?' | slowest '+slowest.tool+' '+formatDuration(slowest.duration_ms):'')+
510
544
  ' | peak in-flight '+peakInFlight+
511
545
  (foldedHeartbeatLines?' | folded '+foldedHeartbeatLines+' heartbeat lines':'')+
@@ -5,7 +5,7 @@ import { sanitizeAuditArgs } from './tool-audit.mjs';
5
5
  export const REMOTE_RESULT_JOURNAL_SCHEMA='deadbyte.remote-result-journal.v1';
6
6
  const SESSION=/^[0-9a-f]{24}$/;
7
7
  const HASH=/^[0-9a-f]{64}$/;
8
- const STATUS=new Set(['ok','error']);
8
+ const STATUS=new Set(['ok','error','pending']);
9
9
  const SIDE_EFFECT_STATES=new Set(['unknown']);
10
10
  const EVENT_TYPES=new Set(['tool.call','tool.result']);
11
11