deadbyte-mcp 0.15.44 → 0.15.46

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 (174) hide show
  1. package/MANIFEST.SHA256 +86 -138
  2. package/PROVENANCE.json +35 -18
  3. package/README.txt +22 -21
  4. package/RELEASE.json +369 -334
  5. package/SBOM.cdx.json +2 -2
  6. package/TEST-MATRIX.json +27 -17
  7. package/bridge/package.json +1 -1
  8. package/controller/deadbyte-controller.ps1 +1871 -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/gate-windows-r72.ps1 +76 -0
  22. package/scripts/npm-release-r48.mjs +4 -0
  23. package/scripts/npm-release-readiness-r71.mjs +21 -0
  24. package/scripts/npm-release-readiness-r72.mjs +76 -0
  25. package/scripts/verify-r70-full-regression.ps1 +72 -0
  26. package/src/authority-continuity-r65.mjs +28 -8
  27. package/src/autonomous-mcp-tools.mjs +1 -1
  28. package/src/autonomous-output-contracts.mjs +4 -1
  29. package/src/autonomous-policy.mjs +54 -7
  30. package/src/capability-ledger.mjs +1 -1
  31. package/src/capability-resolver.mjs +2 -1
  32. package/src/commander-compat-runtime.mjs +30 -2
  33. package/src/deadbyte-cli.mjs +722 -719
  34. package/src/launcher-isolation.mjs +58 -0
  35. package/src/native-diff-v2.mjs +18 -5
  36. package/src/native-diff.mjs +21 -5
  37. package/src/r70-owner-approval.mjs +78 -0
  38. package/src/remote-authority.mjs +75 -75
  39. package/src/remote-live-log.mjs +48 -14
  40. package/src/remote-mcp-machine-smoke-client.mjs +84 -84
  41. package/src/remote-result-journal.mjs +1 -1
  42. package/src/runtime-update.mjs +232 -24
  43. package/src/staging-input-isolation.mjs +79 -0
  44. package/src/tool-audit.mjs +2 -0
  45. package/src/version.mjs +1 -1
  46. package/test/autonomous-local-supervisor.test.mjs +2 -1
  47. package/test/autonomous-output-contract.test.mjs +17 -1
  48. package/test/autonomous-supervisor-integration.test.mjs +2 -1
  49. package/test/capability-resolver.test.mjs +17 -0
  50. package/test/codex-autonomous-r44.test.mjs +1 -1
  51. package/test/commander-compat-runtime.test.mjs +42 -0
  52. package/test/controller-policy-preflight-r59.test.mjs +1 -0
  53. package/test/deferred-slot-operation-behavior.test.mjs +2 -2
  54. package/test/native-diff-topology-r36.test.mjs +35 -2
  55. package/test/native-diff.test.mjs +83 -3
  56. package/test/r58-cleanup-retention.test.mjs +3 -3
  57. package/test/r58-concurrent-recovery.test.mjs +3 -3
  58. package/test/r58-stale-owner.test.mjs +1 -1
  59. package/test/r59-tool-surface-compat.test.mjs +9 -5
  60. package/test/r60-authority-coherence.test.mjs +10 -2
  61. package/test/r65-authority-runtime-epoch.test.mjs +9 -1
  62. package/test/r67-isolated-slot-rollback-restore.test.mjs +2 -1
  63. package/test/r67-npm-publish-gate.test.mjs +4 -2
  64. package/test/r67-release-contract.test.mjs +8 -7
  65. package/test/r69-live-outcome.test.mjs +137 -0
  66. package/test/r70-autonomous-grant-expiry.test.mjs +89 -0
  67. package/test/r70-autonomous-policy-bootstrap.test.mjs +166 -0
  68. package/test/r70-controller-autonomous-root.test.mjs +104 -0
  69. package/test/r70-controller-grant-subset.test.mjs +28 -0
  70. package/test/r70-controller-owner-issue-boundary.test.mjs +80 -0
  71. package/test/r70-controller-policy-snapshot.test.mjs +66 -0
  72. package/test/r70-owner-approval.test.mjs +67 -0
  73. package/test/r71-full-verifier-artifact.test.mjs +28 -0
  74. package/test/r71-isolated-slot-rollback-restore.test.mjs +62 -0
  75. package/test/r71-native-diff-mcp-wire.test.mjs +75 -0
  76. package/test/r71-publish-fence.test.mjs +38 -0
  77. package/test/r71-release-identity.test.mjs +33 -0
  78. package/test/r72-launcher-isolation.test.mjs +99 -0
  79. package/test/r72-publication-fence.test.mjs +56 -0
  80. package/test/r72-publication-output-integrity.test.mjs +110 -0
  81. package/test/r72-remote-safe-contract.test.mjs +101 -0
  82. package/test/r72-staging-input-isolation.test.mjs +87 -0
  83. package/test/r72-staging-workspace-isolation.test.mjs +114 -0
  84. package/test/remote-result-journal.test.mjs +12 -0
  85. package/test/remote-session-cli.test.mjs +112 -65
  86. package/test/standing-grants.test.mjs +2 -2
  87. package/test/tool-audit.test.mjs +14 -0
  88. package/bin/appcontainer-stage.obj +0 -0
  89. package/bin/bootstrap-advapi32.obj +0 -0
  90. package/bin/bootstrap-exitcode.obj +0 -0
  91. package/bin/bootstrap-kernel32.obj +0 -0
  92. package/bin/child-control-probe.obj +0 -0
  93. package/bin/child-control-stage.obj +0 -0
  94. package/bin/contained-reverse-worker.obj +0 -0
  95. package/bin/contained-transform-worker.obj +0 -0
  96. package/bin/containment-probe.obj +0 -0
  97. package/bin/deadbyte-contain.obj +0 -0
  98. package/bin/deadbyte-desktop-input-r40.obj +0 -0
  99. package/bin/deadbyte-desktop-observer-r40.obj +0 -0
  100. package/bin/deadbyte-desktop-uia-r40.obj +0 -0
  101. package/bin/deadbyte-exec.obj +0 -0
  102. package/bin/deadbyte-hardware-observer-r45.obj +0 -0
  103. package/bin/deadbyte-native-inspect-r67.obj +0 -0
  104. package/bin/deadbyte-process-host.obj +0 -0
  105. package/bin/deadbyte-tunnel-host.obj +0 -0
  106. package/conpty-input-observer.obj +0 -0
  107. package/miniconpty.obj +0 -0
  108. package/proof/CONTAINMENT-BUILD.txt +0 -163
  109. package/proof/MCP-SMOKE-STATUS.txt +0 -9
  110. package/proof/NODE-SYNTAX.txt +0 -4
  111. package/proof/PROOF.txt +0 -6
  112. package/proof/R34-TASK8-SUPERVISOR-CHECKPOINT.json +0 -29
  113. package/proof/R34-TASK9-MCP-SURFACE-CHECKPOINT.json +0 -28
  114. package/proof/SECRET-SCAN.txt +0 -4
  115. package/proof/SOURCE-GATES.txt +0 -16
  116. package/proof/STATUS.txt +0 -11
  117. package/proof/TARGET-V0313-CONTAINMENT-PASS.sha256 +0 -1
  118. package/proof/TARGET-V0313-CONTAINMENT-PASS.txt +0 -215
  119. package/proof/TARGET-V040-CONTAINED-IPC-FAIL.sha256 +0 -1
  120. package/proof/TARGET-V040-CONTAINED-IPC-FAIL.txt +0 -299
  121. package/proof/TARGET-V041-CONTAINED-CREATEPROCESS-203.sha256 +0 -1
  122. package/proof/TARGET-V041-CONTAINED-CREATEPROCESS-203.txt +0 -311
  123. package/proof/TARGET-V042-CONTAINED-CREATEPROCESS-203.sha256 +0 -1
  124. package/proof/TARGET-V042-CONTAINED-CREATEPROCESS-203.txt +0 -313
  125. package/proof/TARGET-V043-CONTAINED-CREATEPROCESS-203.sha256 +0 -1
  126. package/proof/TARGET-V043-CONTAINED-CREATEPROCESS-203.txt +0 -308
  127. package/proof/TARGET-V044-CONTAINED-PASS.sha256 +0 -1
  128. package/proof/TARGET-V044-CONTAINED-PASS.txt +0 -432
  129. package/proof/TARGET-V045-DUAL-OPERATION-PASS.sha256 +0 -1
  130. package/proof/TARGET-V045-DUAL-OPERATION-PASS.txt +0 -481
  131. package/proof/TARGET-V046-FIXED-EXECUTABLE-REGISTRY-PASS.sha256 +0 -1
  132. package/proof/TARGET-V046-FIXED-EXECUTABLE-REGISTRY-PASS.txt +0 -610
  133. package/proof/TARGET-V047-RUNTIME-MANIFEST-PASS.sha256 +0 -1
  134. package/proof/TARGET-V047-RUNTIME-MANIFEST-PASS.txt +0 -642
  135. package/proof/TARGET-V050-BATCH-GATE-PASS.sha256 +0 -1
  136. package/proof/TARGET-V050-BATCH-GATE-PASS.txt +0 -713
  137. package/proof/TESTS.txt +0 -686
  138. package/proof/TRUST-EPHEMERAL.txt +0 -4
  139. package/proof/V03-FREEZE.txt +0 -27
  140. package/proof/V041-IPC-HOTFIX.txt +0 -16
  141. package/proof/V042-ENV-HOTFIX.txt +0 -15
  142. package/proof/V043-DRIVE-CWD-HOTFIX.txt +0 -14
  143. package/proof/V044-LOCALAPPDATA-HOTFIX.txt +0 -18
  144. package/proof/V045-OPERATION-REGISTRY.txt +0 -7
  145. package/proof/V046-WORKER-REGISTRY.txt +0 -12
  146. package/proof/V047-WORKER-MANIFEST.txt +0 -18
  147. package/proof/V050-BATCH-TRUST-CLOSURE.txt +0 -21
  148. package/proof/V060-ACTIVE-NETWORK-AUTHORITY.txt +0 -29
  149. package/proof/V070-NODE-SYNTAX.txt +0 -36
  150. package/proof/V070-NODE-TESTS.txt +0 -752
  151. package/proof/V070-PACK-DRY-RUN.txt +0 -126
  152. package/proof/V070-SOURCE-GATE.txt +0 -30
  153. package/proof/V070-STATIC-SCAN.txt +0 -4
  154. package/proof/V070-WINDOWS-GATE-PENDING.txt +0 -25
  155. package/proof/V071-NODE-SYNTAX.txt +0 -1
  156. package/proof/V071-NODE-TESTS.txt +0 -796
  157. package/proof/V071-PACK-DRY-RUN.json +0 -656
  158. package/proof/V071-SOURCE-GATE.txt +0 -16
  159. package/proof/V071-WINDOWS-GATE-PENDING.txt +0 -24
  160. package/proof/V073-NODE-SYNTAX.txt +0 -4
  161. package/proof/V073-SOURCE-GATE.txt +0 -21
  162. package/proof/V073-STATIC-SCAN.txt +0 -10
  163. package/proof/V073-WINDOWS-GATE-PENDING.txt +0 -5
  164. package/proof/VERIFY.txt +0 -7
  165. package/proof/WINDOWS-CONTAINED-STATUS.txt +0 -5
  166. package/proof/WINDOWS-CONTAINMENT-STATUS.txt +0 -3
  167. package/proof/commander-closure-20260927/CONTINUATION.json +0 -43
  168. package/proof/commander-closure-20260927/RESULT.json +0 -30
  169. package/proof/commander-closure-20260927/SEARCH-CONTINUATION.json +0 -30
  170. package/proof/commander-closure-20260927/XLS-REFERENCE-PROBE.json +0 -13
  171. package/proof/commander-closure-20260928/DIFFERENTIAL-CHECKPOINT.json +0 -122
  172. package/proof/r59-full-regression-current.json +0 -13
  173. package/proof/r59-full-regression-current.stderr.log +0 -0
  174. package/proof/r59-full-regression-current.stdout.log +0 -1268
@@ -0,0 +1,58 @@
1
+ /** R72: state-root-owned launcher routing. No staging caller may write the user-wide Downloads launcher. */
2
+ import {lstatSync} from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+
6
+ const normalize=file=>process.platform==='win32'?path.resolve(file).toLowerCase():path.resolve(file);
7
+ function contains(parent,child){
8
+ const rel=path.relative(parent,child);
9
+ return rel===''||(!path.isAbsolute(rel)&&rel!=='..'&&!rel.startsWith(`..${path.sep}`));
10
+ }
11
+ function assertNoReparseParts(file){
12
+ let cursor=path.resolve(file);
13
+ while(true){
14
+ try{
15
+ const info=lstatSync(cursor);
16
+ if(info.isSymbolicLink())throw new Error(`staging launcher boundary is a reparse/symlink: ${cursor}`);
17
+ }catch(error){if(error?.code!=='ENOENT')throw error;}
18
+ const parent=path.dirname(cursor);
19
+ if(parent===cursor)break;
20
+ cursor=parent;
21
+ }
22
+ }
23
+ export function canonicalLauncherPaths(stateRoot,{home=os.homedir()}={}){
24
+ if(typeof stateRoot!=='string'||!path.isAbsolute(stateRoot)||stateRoot.includes('\0'))throw new Error('launcher state root must be absolute');
25
+ const state=path.resolve(stateRoot),productionState=path.resolve(home,'.deadbyte-mcp');
26
+ const production=normalize(state)===normalize(productionState);
27
+ if(!production){
28
+ if(contains(normalize(productionState),normalize(state))||contains(normalize(state),normalize(productionState)))
29
+ throw new Error('staging state overlaps production state root');
30
+ assertNoReparseParts(state);
31
+ assertNoReparseParts(path.join(state,'bin'));
32
+ assertNoReparseParts(path.join(state,'launchers'));
33
+ }
34
+ const top=production?path.join(home,'Downloads','DEADBYTE-MCP.cmd'):path.join(state,'launchers','DEADBYTE-MCP.cmd');
35
+ if(!production)assertNoReparseParts(top);
36
+ return Object.freeze({state,production,top,ps1:path.join(state,'bin','DEADBYTE-MCP.ps1'),cmd:path.join(state,'bin','DEADBYTE-MCP.cmd')});
37
+ }
38
+ export function assertCanonicalLaunchers(stateRoot,launchers){
39
+ const expected=canonicalLauncherPaths(stateRoot);
40
+ if(!launchers||typeof launchers!=='object')throw new Error('launcher paths absent');
41
+ for(const field of ['top','ps1','cmd']){
42
+ if(typeof launchers[field]!=='string'||!path.isAbsolute(launchers[field])||
43
+ normalize(launchers[field])!==normalize(expected[field]))
44
+ throw new Error(`launcher ${field} is not canonical for selected state root`);
45
+ }
46
+ return expected;
47
+ }
48
+ /** Relative to bin/ or launchers/, so the entry point always selects its own state root. */
49
+ export function renderStateBoundLauncherCmd(){
50
+ return [
51
+ '@echo off',
52
+ 'setlocal EnableExtensions DisableDelayedExpansion',
53
+ 'set "DEADBYTE_STATE_ROOT=%~dp0.."',
54
+ 'powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%DEADBYTE_STATE_ROOT%\\bin\\DEADBYTE-MCP.ps1" %*',
55
+ 'exit /b %ERRORLEVEL%',
56
+ ''
57
+ ].join('\r\n');
58
+ }
@@ -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
+ }
@@ -1,75 +1,75 @@
1
- import path from 'node:path';
2
-
3
- export const AUTHORITY_PLANES=Object.freeze(['process','coding','autonomous','desktop','host']);
4
-
5
- export function authorityVector(mode='default'){
6
- if(!['default','desktop','host','full'].includes(mode)) throw new Error('remote authority mode invalid');
7
- return Object.freeze({
8
- process:true,coding:true,autonomous:true,
9
- desktop:mode==='desktop'||mode==='full',
10
- host:mode==='host'||mode==='full'
11
- });
12
- }
13
-
14
- export function authorityRestorePlan(snapshot){
15
- const disarm=[],arm=[];
16
- for(const plane of AUTHORITY_PLANES){
17
- const entry=snapshot?.authority?.[plane];
18
- if(!entry||typeof entry.armed!=='boolean') throw new Error(`authority ${plane} observation invalid`);
19
- (entry.armed?arm:disarm).push(`${entry.armed?'arm':'disarm'}${plane}`);
20
- }
21
- return Object.freeze([...disarm,...arm]);
22
- }
23
-
24
- export function parseAuthorityObservation(raw){
25
- let value;
26
- try{value=typeof raw==='string'?JSON.parse(raw.replace(/^\uFEFF/,'')):raw;}
27
- catch{throw new Error('controller observation JSON invalid');}
28
- if(value?.schema!=='deadbyte.controller-observation.v1'||!value.authority||typeof value.authority!=='object'||Array.isArray(value.authority))
29
- throw new Error('controller observation schema invalid');
30
- const authority={};
31
- for(const plane of AUTHORITY_PLANES){
32
- const entry=value.authority[plane];
33
- if(!entry||typeof entry!=='object'||typeof entry.armed!=='boolean'||typeof entry.configured!=='boolean'||typeof entry.reason!=='string')
34
- throw new Error(`controller authority ${plane} observation invalid`);
35
- const hasEpochSeq=Object.hasOwn(entry,'epoch_seq'),hasEpochSha=Object.hasOwn(entry,'epoch_sha256');
36
- if(hasEpochSeq!==hasEpochSha)throw new Error(`controller authority ${plane} epoch observation incomplete`);
37
- let epochSeq=null,epochSha256=null;
38
- if(hasEpochSeq){
39
- if(!Number.isSafeInteger(entry.epoch_seq)||entry.epoch_seq<1||typeof entry.epoch_sha256!=='string'||!/^[0-9a-f]{64}$/.test(entry.epoch_sha256))
40
- throw new Error(`controller authority ${plane} epoch observation invalid`);
41
- epochSeq=entry.epoch_seq;epochSha256=entry.epoch_sha256;
42
- }
43
- authority[plane]=Object.freeze({configured:entry.configured,armed:entry.armed,reason:entry.reason.slice(0,128),
44
- epoch_seq:epochSeq,epoch_sha256:epochSha256});
45
- }
46
- return Object.freeze({...value,authority:Object.freeze(authority)});
47
- }
48
-
49
- export function assertAuthorityVector(snapshot,expected){
50
- for(const plane of AUTHORITY_PLANES){
51
- const entry=snapshot?.authority?.[plane];
52
- if(!entry||typeof expected?.[plane]!=='boolean') throw new Error(`authority ${plane} observation or expectation invalid`);
53
- if(entry.armed!==expected[plane]) throw new Error(`${plane} authority expected ${expected[plane]?'ARMED':'DISARMED'}; observed ${entry.armed?'ARMED':'DISARMED'} (${entry.reason})`);
54
- if(expected[plane]&&!entry.configured) throw new Error(`${plane} authority unconfigured`);
55
- }
56
- return snapshot;
57
- }
58
-
59
- export function authorityBanner(snapshot){
60
- return AUTHORITY_PLANES.map(plane=>`${plane} ${snapshot.authority[plane].armed?'ARMED':'DISARMED'}`).join(' | ');
61
- }
62
-
63
- export function assertRuntimeReadyObservation(snapshot,{coreRoot=null}={}){
64
- if(snapshot?.state!=='ONLINE') throw new Error(`runtime state not ONLINE: ${String(snapshot?.state)}`);
65
- for(const name of ['bridge','core','cloud']){
66
- if(snapshot[name]?.alive!==true||snapshot[name]?.reason!=='verified') throw new Error(`${name} runtime identity unverified`);
67
- }
68
- if(snapshot.local_ready!==true) throw new Error('local MCP readiness unverified');
69
- if(snapshot.supervisor?.running!==true) throw new Error('autonomous supervisor not running');
70
- if(coreRoot!==null){
71
- if(typeof snapshot.core_root!=='string'||path.win32.resolve(snapshot.core_root).toLowerCase()!==path.win32.resolve(coreRoot).toLowerCase())
72
- throw new Error('core root does not match active release');
73
- }
74
- return snapshot;
75
- }
1
+ import path from 'node:path';
2
+
3
+ export const AUTHORITY_PLANES=Object.freeze(['process','coding','autonomous','desktop','host']);
4
+
5
+ export function authorityVector(mode='default'){
6
+ if(!['default','operator','desktop','host','full'].includes(mode)) throw new Error('remote authority mode invalid');
7
+ return Object.freeze({
8
+ process:mode!=='default',coding:mode!=='default',autonomous:mode!=='default',
9
+ desktop:mode==='desktop'||mode==='full',
10
+ host:mode==='host'||mode==='full'
11
+ });
12
+ }
13
+
14
+ export function authorityRestorePlan(snapshot){
15
+ const disarm=[],arm=[];
16
+ for(const plane of AUTHORITY_PLANES){
17
+ const entry=snapshot?.authority?.[plane];
18
+ if(!entry||typeof entry.armed!=='boolean') throw new Error(`authority ${plane} observation invalid`);
19
+ (entry.armed?arm:disarm).push(`${entry.armed?'arm':'disarm'}${plane}`);
20
+ }
21
+ return Object.freeze([...disarm,...arm]);
22
+ }
23
+
24
+ export function parseAuthorityObservation(raw){
25
+ let value;
26
+ try{value=typeof raw==='string'?JSON.parse(raw.replace(/^\uFEFF/,'')):raw;}
27
+ catch{throw new Error('controller observation JSON invalid');}
28
+ if(value?.schema!=='deadbyte.controller-observation.v1'||!value.authority||typeof value.authority!=='object'||Array.isArray(value.authority))
29
+ throw new Error('controller observation schema invalid');
30
+ const authority={};
31
+ for(const plane of AUTHORITY_PLANES){
32
+ const entry=value.authority[plane];
33
+ if(!entry||typeof entry!=='object'||typeof entry.armed!=='boolean'||typeof entry.configured!=='boolean'||typeof entry.reason!=='string')
34
+ throw new Error(`controller authority ${plane} observation invalid`);
35
+ const hasEpochSeq=Object.hasOwn(entry,'epoch_seq'),hasEpochSha=Object.hasOwn(entry,'epoch_sha256');
36
+ if(hasEpochSeq!==hasEpochSha)throw new Error(`controller authority ${plane} epoch observation incomplete`);
37
+ let epochSeq=null,epochSha256=null;
38
+ if(hasEpochSeq){
39
+ if(!Number.isSafeInteger(entry.epoch_seq)||entry.epoch_seq<1||typeof entry.epoch_sha256!=='string'||!/^[0-9a-f]{64}$/.test(entry.epoch_sha256))
40
+ throw new Error(`controller authority ${plane} epoch observation invalid`);
41
+ epochSeq=entry.epoch_seq;epochSha256=entry.epoch_sha256;
42
+ }
43
+ authority[plane]=Object.freeze({configured:entry.configured,armed:entry.armed,reason:entry.reason.slice(0,128),
44
+ epoch_seq:epochSeq,epoch_sha256:epochSha256});
45
+ }
46
+ return Object.freeze({...value,authority:Object.freeze(authority)});
47
+ }
48
+
49
+ export function assertAuthorityVector(snapshot,expected){
50
+ for(const plane of AUTHORITY_PLANES){
51
+ const entry=snapshot?.authority?.[plane];
52
+ if(!entry||typeof expected?.[plane]!=='boolean') throw new Error(`authority ${plane} observation or expectation invalid`);
53
+ if(entry.armed!==expected[plane]) throw new Error(`${plane} authority expected ${expected[plane]?'ARMED':'DISARMED'}; observed ${entry.armed?'ARMED':'DISARMED'} (${entry.reason})`);
54
+ if(expected[plane]&&!entry.configured) throw new Error(`${plane} authority unconfigured`);
55
+ }
56
+ return snapshot;
57
+ }
58
+
59
+ export function authorityBanner(snapshot){
60
+ return AUTHORITY_PLANES.map(plane=>`${plane} ${snapshot.authority[plane].armed?'ARMED':'DISARMED'}`).join(' | ');
61
+ }
62
+
63
+ export function assertRuntimeReadyObservation(snapshot,{coreRoot=null}={}){
64
+ if(snapshot?.state!=='ONLINE') throw new Error(`runtime state not ONLINE: ${String(snapshot?.state)}`);
65
+ for(const name of ['bridge','core','cloud']){
66
+ if(snapshot[name]?.alive!==true||snapshot[name]?.reason!=='verified') throw new Error(`${name} runtime identity unverified`);
67
+ }
68
+ if(snapshot.local_ready!==true) throw new Error('local MCP readiness unverified');
69
+ if(snapshot.supervisor?.running!==true) throw new Error('autonomous supervisor not running');
70
+ if(coreRoot!==null){
71
+ if(typeof snapshot.core_root!=='string'||path.win32.resolve(snapshot.core_root).toLowerCase()!==path.win32.resolve(coreRoot).toLowerCase())
72
+ throw new Error('core root does not match active release');
73
+ }
74
+ return snapshot;
75
+ }
@@ -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':'')+