deadbyte-mcp 0.15.37 → 0.15.38
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 +30 -20
- package/PROVENANCE.json +28 -15
- package/README.txt +12 -12
- package/RELEASE.json +26 -14
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +23 -15
- package/bridge/package.json +1 -1
- package/bridge/src/bridge.mjs +4 -4
- package/bridge/src/r40-governor.mjs +109 -19
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R61-TASK-CONTRACT.md +11 -0
- package/docs/r61-reference-evidence.md +18 -0
- package/package.json +4 -3
- package/scripts/gate-windows-r61-prepublish.ps1 +45 -0
- package/scripts/npm-artifact-identity-r61.mjs +22 -0
- package/scripts/npm-release-r48.mjs +21 -5
- package/scripts/r61-mixed-lane-benchmark.mjs +98 -0
- package/src/deadbyte-cli.mjs +9 -6
- package/src/remote-live-log.mjs +28 -5
- package/src/runtime-update.mjs +13 -1
- package/src/version.mjs +1 -1
- package/test/bridge-runtime-r59.test.mjs +5 -5
- package/test/commander-schema-differential-r48.test.mjs +1 -1
- package/test/r60-release-contract.test.mjs +4 -3
- package/test/r61-control-lane.test.mjs +154 -0
- package/test/r61-live-timeline-bounds.test.mjs +31 -0
- package/test/r61-npm-artifact-identity.test.mjs +17 -0
- package/test/r61-release-bridge-binding.test.mjs +32 -0
- package/test/r61-release-contract.test.mjs +44 -0
- package/test/remote-live-log-r37.test.mjs +23 -6
- package/test/remote-session-cli.test.mjs +1 -1
|
@@ -4,24 +4,39 @@ const DESKTOP_RAW_KEYS=new Set([
|
|
|
4
4
|
'pixels','pixel_bytes','frame_bytes','raw_bgra','raw_rgba','raw_image','screenshot_bytes','bitmap_bytes'
|
|
5
5
|
]);
|
|
6
6
|
const DEFAULT_LIMITS=Object.freeze({
|
|
7
|
+
control:Object.freeze({max_active:8,max_queue:16,queue_timeout_ms:5000}),
|
|
7
8
|
normal:Object.freeze({max_active:8,max_queue:32,queue_timeout_ms:30000}),
|
|
9
|
+
bulk:Object.freeze({max_active:8,max_queue:16,queue_timeout_ms:30000}),
|
|
8
10
|
autonomous:Object.freeze({max_active:2,max_queue:8,queue_timeout_ms:60000}),
|
|
9
11
|
desktop_observe:Object.freeze({max_active:2,max_queue:4,queue_timeout_ms:10000}),
|
|
10
12
|
desktop_action:Object.freeze({max_active:1,max_queue:2,queue_timeout_ms:5000})
|
|
11
13
|
});
|
|
14
|
+
const DEFAULT_SHARED_LIMITS=Object.freeze({max_active:9,max_non_control_active:8});
|
|
12
15
|
const DEADLINES=Object.freeze({
|
|
16
|
+
control:Object.freeze({timeout:10000,maxTotalTimeout:15000}),
|
|
13
17
|
normal:Object.freeze({timeout:120000,maxTotalTimeout:180000}),
|
|
18
|
+
bulk:Object.freeze({timeout:120000,maxTotalTimeout:180000}),
|
|
14
19
|
autonomous:Object.freeze({timeout:900000,maxTotalTimeout:1200000}),
|
|
15
20
|
desktop_observe:Object.freeze({timeout:30000,maxTotalTimeout:60000}),
|
|
16
21
|
desktop_action:Object.freeze({timeout:30000,maxTotalTimeout:45000})
|
|
17
22
|
});
|
|
18
23
|
|
|
19
24
|
function insist(condition,message){ if(!condition) throw new Error(message); }
|
|
20
|
-
function toolClass(name){
|
|
25
|
+
function toolClass(name,args={}){
|
|
21
26
|
const value=String(name??'');
|
|
22
27
|
if(value==='autonomous_goal_run') return 'autonomous';
|
|
23
|
-
if(value==='desktop_action') return 'desktop_action';
|
|
28
|
+
if(value==='desktop_action'||(value==='desktop_control'&&['action','target'].includes(args?.op))) return 'desktop_action';
|
|
24
29
|
if(value.startsWith('desktop_')) return 'desktop_observe';
|
|
30
|
+
if(value==='desktop_control') return 'desktop_observe';
|
|
31
|
+
if(['machine_ping','machine_status','machine_revoke','machine_list','capability_manifest',
|
|
32
|
+
'autonomous_session_status','coding_session_status'].includes(value))return 'control';
|
|
33
|
+
if(value==='machine_observe'&&['machine_ping','machine_status','machine_list'].includes(args?.op))return 'control';
|
|
34
|
+
if(['coding_search','coding_command_run','coding_benchmark_run','coding_task_run',
|
|
35
|
+
'process_session_start','process_session_read','process_session_action',
|
|
36
|
+
'machine_search_start','machine_search_page'].includes(value))return 'bulk';
|
|
37
|
+
if(value==='coding_inspect'&&['search','symbol_references','symbol_implementations'].includes(args?.op))return 'bulk';
|
|
38
|
+
if(value==='process_session'&&['start','read','action'].includes(args?.op))return 'bulk';
|
|
39
|
+
if(value==='machine_observe'&&['search_start','search_page','process_list'].includes(args?.op))return 'bulk';
|
|
25
40
|
return 'normal';
|
|
26
41
|
}
|
|
27
42
|
function estimateBase64Bytes(value){
|
|
@@ -57,8 +72,8 @@ function scanDesktopPayload(value,{maxPreviewBytes},path='result',seen=new Set()
|
|
|
57
72
|
seen.delete(value);
|
|
58
73
|
}
|
|
59
74
|
|
|
60
|
-
export function bridgeToolClass(name){ return toolClass(name); }
|
|
61
|
-
export function bridgeDeadlineForTool(name){ return DEADLINES[toolClass(name)]; }
|
|
75
|
+
export function bridgeToolClass(name,args){ return toolClass(name,args); }
|
|
76
|
+
export function bridgeDeadlineForTool(name,args){ return DEADLINES[toolClass(name,args)]; }
|
|
62
77
|
export function bridgeCatalogSha256(toolNames){
|
|
63
78
|
insist(Array.isArray(toolNames)&&toolNames.every(x=>typeof x==='string'),'bridge catalog names invalid');
|
|
64
79
|
const sorted=[...new Set(toolNames)].sort();
|
|
@@ -95,8 +110,12 @@ export function enforceDesktopBridgeResult(toolName,result,{maxPreviewBytes=1024
|
|
|
95
110
|
return result;
|
|
96
111
|
}
|
|
97
112
|
|
|
98
|
-
export function createBridgeGovernor({limits=
|
|
99
|
-
const
|
|
113
|
+
export function createBridgeGovernor({limits:overrides={},shared:sharedOverrides={}}={}){
|
|
114
|
+
const limits={...DEFAULT_LIMITS,...overrides};
|
|
115
|
+
const shared={...DEFAULT_SHARED_LIMITS,...sharedOverrides};
|
|
116
|
+
insist(Number.isInteger(shared.max_active)&&shared.max_active>=2&&shared.max_active<=64,'bridge shared max_active invalid');
|
|
117
|
+
insist(Number.isInteger(shared.max_non_control_active)&&shared.max_non_control_active>=1&&shared.max_non_control_active<shared.max_active,'bridge shared max_non_control_active invalid');
|
|
118
|
+
const classes=['control','normal','bulk','autonomous','desktop_observe','desktop_action'];
|
|
100
119
|
for(const name of classes){
|
|
101
120
|
const cfg=limits[name];
|
|
102
121
|
insist(cfg&&Number.isInteger(cfg.max_active)&&cfg.max_active>=1&&cfg.max_active<=64,`bridge limit ${name}.max_active invalid`);
|
|
@@ -104,6 +123,7 @@ export function createBridgeGovernor({limits=DEFAULT_LIMITS}={}){
|
|
|
104
123
|
insist(Number.isInteger(cfg.queue_timeout_ms)&&cfg.queue_timeout_ms>=100&&cfg.queue_timeout_ms<=120000,`bridge limit ${name}.queue_timeout_ms invalid`);
|
|
105
124
|
}
|
|
106
125
|
const state=new Map(classes.map(name=>[name,{active:0,queue:[],admitted:0,completed:0,rejected:0,timed_out:0,cancelled:0}]));
|
|
126
|
+
let sharedActive=0,sharedNonControlActive=0;
|
|
107
127
|
let sequence=0;
|
|
108
128
|
let closed=false;
|
|
109
129
|
const drainWaiters=[];
|
|
@@ -113,13 +133,75 @@ export function createBridgeGovernor({limits=DEFAULT_LIMITS}={}){
|
|
|
113
133
|
while(drainWaiters.length) drainWaiters.shift()();
|
|
114
134
|
}
|
|
115
135
|
|
|
116
|
-
function
|
|
136
|
+
function canAdmit(name){
|
|
117
137
|
const cfg=limits[name],s=state.get(name);
|
|
118
|
-
|
|
138
|
+
if(s.active>=cfg.max_active)return false;
|
|
139
|
+
if(sharedActive>=shared.max_active)return false;
|
|
140
|
+
if(name!=='control'&&sharedNonControlActive>=shared.max_non_control_active)return false;
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function noteAdmit(name){
|
|
145
|
+
sharedActive++;
|
|
146
|
+
if(name!=='control')sharedNonControlActive++;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function noteRelease(name){
|
|
150
|
+
sharedActive=Math.max(0,sharedActive-1);
|
|
151
|
+
if(name!=='control')sharedNonControlActive=Math.max(0,sharedNonControlActive-1);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function dispatchOne(name){
|
|
155
|
+
const s=state.get(name);
|
|
156
|
+
if(!canAdmit(name))return false;
|
|
157
|
+
|
|
158
|
+
while(s.queue.length){
|
|
119
159
|
const waiter=s.queue.shift();
|
|
120
|
-
if(waiter.done)
|
|
121
|
-
|
|
122
|
-
waiter.
|
|
160
|
+
if(waiter.done)continue;
|
|
161
|
+
|
|
162
|
+
waiter.done=true;
|
|
163
|
+
clearTimeout(waiter.timer);
|
|
164
|
+
waiter.cleanup();
|
|
165
|
+
|
|
166
|
+
s.active++;
|
|
167
|
+
noteAdmit(name);
|
|
168
|
+
s.admitted++;
|
|
169
|
+
|
|
170
|
+
waiter.resolve(
|
|
171
|
+
makeTicket(name,waiter.tool,waiter.id,waiter.queuedAt)
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function oldestAdmissibleQueuedClass(){
|
|
181
|
+
let selected=null;
|
|
182
|
+
|
|
183
|
+
for(const name of classes){
|
|
184
|
+
if(!canAdmit(name))continue;
|
|
185
|
+
|
|
186
|
+
const s=state.get(name);
|
|
187
|
+
const waiter=s.queue.find(item=>!item.done);
|
|
188
|
+
|
|
189
|
+
if(!waiter)continue;
|
|
190
|
+
|
|
191
|
+
if(selected===null||waiter.id<selected.id){
|
|
192
|
+
selected={name,id:waiter.id};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return selected?.name??null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function dispatchAvailable(){
|
|
200
|
+
while(true){
|
|
201
|
+
const name=oldestAdmissibleQueuedClass();
|
|
202
|
+
|
|
203
|
+
if(name===null)break;
|
|
204
|
+
if(!dispatchOne(name))break;
|
|
123
205
|
}
|
|
124
206
|
}
|
|
125
207
|
function makeTicket(name,tool,id,queuedAt){
|
|
@@ -130,19 +212,19 @@ export function createBridgeGovernor({limits=DEFAULT_LIMITS}={}){
|
|
|
130
212
|
if(released) return false;
|
|
131
213
|
released=true;
|
|
132
214
|
const s=state.get(name);
|
|
133
|
-
s.active=Math.max(0,s.active-1);s.completed++;
|
|
134
|
-
|
|
215
|
+
s.active=Math.max(0,s.active-1);noteRelease(name);s.completed++;
|
|
216
|
+
dispatchAvailable();
|
|
135
217
|
finishDrain();
|
|
136
218
|
return true;
|
|
137
219
|
}
|
|
138
220
|
});
|
|
139
221
|
}
|
|
140
|
-
async function acquire(tool,{signal}={}){
|
|
141
|
-
const name=toolClass(tool),cfg=limits[name],s=state.get(name),id=++sequence,queuedAt=Date.now();
|
|
222
|
+
async function acquire(tool,{signal,arguments:args}={}){
|
|
223
|
+
const name=toolClass(tool,args),cfg=limits[name],s=state.get(name),id=++sequence,queuedAt=Date.now();
|
|
142
224
|
if(closed){s.rejected++;throw new Error('bridge governor closed');}
|
|
143
225
|
if(signal?.aborted){s.cancelled++;throw signal.reason instanceof Error?signal.reason:new Error('bridge request aborted');}
|
|
144
|
-
if(
|
|
145
|
-
s.active++;s.admitted++;
|
|
226
|
+
if(canAdmit(name)){
|
|
227
|
+
s.active++;noteAdmit(name);s.admitted++;
|
|
146
228
|
return makeTicket(name,String(tool),id,queuedAt);
|
|
147
229
|
}
|
|
148
230
|
if(s.queue.length>=cfg.max_queue){s.rejected++;throw new Error(`bridge backpressure queue full for ${name}`);}
|
|
@@ -193,14 +275,22 @@ export function createBridgeGovernor({limits=DEFAULT_LIMITS}={}){
|
|
|
193
275
|
return removed;
|
|
194
276
|
}
|
|
195
277
|
function snapshot(){
|
|
196
|
-
|
|
278
|
+
const lanes=Object.fromEntries(classes.map(name=>{
|
|
197
279
|
const s=state.get(name),cfg=limits[name];
|
|
198
280
|
return [name,Object.freeze({active:s.active,queued:s.queue.filter(x=>!x.done).length,max_active:cfg.max_active,max_queue:cfg.max_queue,
|
|
199
281
|
admitted:s.admitted,completed:s.completed,rejected:s.rejected,timed_out:s.timed_out,cancelled:s.cancelled,saturated:s.active>=cfg.max_active})];
|
|
200
|
-
}))
|
|
282
|
+
}));
|
|
283
|
+
return Object.freeze({...lanes,shared:Object.freeze({
|
|
284
|
+
active:sharedActive,
|
|
285
|
+
non_control_active:sharedNonControlActive,
|
|
286
|
+
max_active:shared.max_active,
|
|
287
|
+
max_non_control_active:shared.max_non_control_active,
|
|
288
|
+
saturated:sharedActive>=shared.max_active
|
|
289
|
+
})});
|
|
201
290
|
}
|
|
202
291
|
return Object.freeze({acquire,reconcile,close,snapshot});
|
|
203
292
|
}
|
|
204
293
|
|
|
205
294
|
export const BRIDGE_R40_DEFAULT_LIMITS=DEFAULT_LIMITS;
|
|
295
|
+
export const BRIDGE_R61_SHARED_LIMITS=DEFAULT_SHARED_LIMITS;
|
|
206
296
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "deadbyte.process-policy.v1",
|
|
3
3
|
"enabled": true,
|
|
4
|
-
"grant_file": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.
|
|
5
|
-
"state_dir": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.
|
|
4
|
+
"grant_file": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.38-r61\\controller\\runtime\\process-grant.json",
|
|
5
|
+
"state_dir": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.38-r61\\controller\\runtime\\process-state",
|
|
6
6
|
"native_host": {
|
|
7
|
-
"path": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.
|
|
7
|
+
"path": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.38-r61\\bin\\deadbyte-process-host.exe",
|
|
8
8
|
"sha256": "daf9cfb371bee88cafbf5597eaa507fec504dbe0b0ddd40d0c3c2e02817f49e4"
|
|
9
9
|
},
|
|
10
10
|
"roots": {
|
|
11
11
|
"core": {
|
|
12
|
-
"path": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.
|
|
12
|
+
"path": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-0.15.38-r61",
|
|
13
13
|
"deny_paths": [
|
|
14
14
|
"node_modules",
|
|
15
15
|
"controller/runtime",
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# R61 LIVELOG and multi-workload resilience
|
|
2
|
+
|
|
3
|
+
- Source: a byte-verified copy of the non-Git R60 development tree at `D:\Projects\deadbyte-mcp\development\deadbyte-mcp-0.15.38-r61`, retaining its three intentional LIVELOG edits. The old R60 path remains a compatibility dependency of the live 0.15.37 generation and is not an R61 edit target.
|
|
4
|
+
- Sealed predecessor: `v0.15.37-1cfecd7889419da7`; release manifest `1cfecd7889419da7800814e3a2a4ffa93d48dfdb0c344a5b7b91fe8d093230aa`; generation manifest `54842021c75f01bf7ecbd5b10ab8a482ecf107765b45c46f9d39b12bab65eac4`.
|
|
5
|
+
- Target: `deadbyte-mcp@0.15.38`, R61, unchanged 43-tool compact catalog and zero new authority scopes.
|
|
6
|
+
- Mechanism decisions and license observations: [r61-reference-evidence.md](r61-reference-evidence.md). No donor source was copied.
|
|
7
|
+
- RED: eight admitted `coding_search` jobs blocked `machine_ping` at the governor boundary; 12,000 filtered LIVELOG requests grew an unbounded identity map; a historical schema differential test referenced a moved external source despite byte-identical canonical reference content; the active 0.15.37 generation pinned Bridge source/dependency hashes to the mutable R60 development path, so the first R61 edits caused read-only controller status/doctor to fail.
|
|
8
|
+
- Fix: existing Bridge governor now separates bounded control/ordinary/bulk admission; LIVELOG presentation and filter identities are bounded and audit-sequence-aware; the historical schema test reads the canonical reference corpus and retains the exact pinned SHA-256 check. The R61 updater verifies the development Bridge input but binds the generated runtime config to the manifest-verified immutable release slot. The two trusted R60 Bridge files were restored byte-for-byte from the sealed 0.15.37 slot after R61 was copied to its own path; live status returned ONLINE without restart, revoke, or slot mutation.
|
|
9
|
+
- Performance evidence: `D:\Projects\deadbyte-mcp\testing\0.15.38\r61-ab-nine-rounds.json` (SHA-256 `3f10010654f08b4347c8e22e0f7ec3c521a69872878934770a0d2c0fd5b16ef7`) and 15-round 1/8 follow-up `r61-ab-single-and-eight.json` (SHA-256 `b16143ff7cb8aeced0e685352ffebeeeb74116d648d5b3509af2c66bd1f3b8a0a`). At 32-way mixed governor load, median throughput changed 790.1 → 903.75 ops/s, control p95 31.54 → 21.82 ms, and maximum observed queue depth 23 → 14. All 108 full-matrix runs had zero errors and unknown outcomes. At 1-way follow-up, throughput 244.78 → 246.82 ops/s and total p95 46.03 → 45.93 ms. This is a direct-governor/scratch-I/O benchmark, **not** full MCP transport, signed audit, or production-load proof.
|
|
10
|
+
- R61 focused gate after identity change: 47/47 PASS, 0 skip. Isolated Bridge/Core cold start, public MCP status, Core recovery and stop: PASS in one run; not a multi-workload transport benchmark.
|
|
11
|
+
- Final full regression, Windows gate, release manifest/doctor, package boundary after final bytes, registry, activation, rollback and restore remain separate required gates. This document is not a release PASS claim.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# R61 reference-mechanism decisions
|
|
2
|
+
|
|
3
|
+
This is a source-inspection record, not authority to copy donor code. R61 uses
|
|
4
|
+
patterns reimplemented inside existing DEADBYTE components. No donor source
|
|
5
|
+
bytes or dependencies are imported.
|
|
6
|
+
|
|
7
|
+
| Reference | Inspected source | Mechanism and comparison with DEADBYTE | License/decision |
|
|
8
|
+
| --- | --- | --- | --- |
|
|
9
|
+
| Better Remote Commander | `references/better-remote-commander/packages/relay/src/device-hub.ts`, `mcp/invoke.ts`, `NOTICE` | Replaces stale sockets, correlates call/result and uses a pong watchdog. DEADBYTE already has a single guarded Bridge dispatch and bounded reconnect; test half-open detection and shared recovery, but do not add a fleet or new relay. | Root package declares MIT; NOTICE identifies derived Desktop Commander code. ADAPT watchdog tests only; KEEP current dispatch. |
|
|
10
|
+
| CUA | `references/cua/rfcs/3007-cua-driver-lifecycle-sessions.md`, `3682-typed-native-window-sdk.md`, `libs/cua-driver/rust/crates/cua-driver-core/tests/session_lifecycle.rs` | Accepted lifecycle sessions separate cleanup from authority; typed window actions distinguish snapshot target and post-action verification. DEADBYTE already has native Desktop and stale-frame controls. Add adversarial lifecycle/fresh-observation tests only if missing. RFC 2447 is superseded and not an architecture guide. | Top-level Python metadata declares MIT; component licenses must be checked before any copying. KEEP native/adapter split; ADAPT tests. |
|
|
11
|
+
| Desktop Commander | `references/desktop-commander/dist/utils/toolHistory.js`, process handlers | History caps entries, stored output bytes and file size. DEADBYTE already bounds process retained output via policy, repairs PATHEXT, supports offset reads, cancellation and bounded operation history. LIVELOG completed identity set is unbounded presentation state. | Package declares MIT. ADAPT LIVELOG retention; KEEP process runtime. |
|
|
12
|
+
| Open Remote MCP | `references/open-remote-mcp/docs/ARCHITECTURE.md`, `apps/agent/src/index.ts`, `NOTICE.md` | Relay policy narrows, never widens local device capability. DEADBYTE signed ledger and separate local planes are stronger; add a focused relay/local enforcement test, not a second permission system. | Root package has no license field in inspected metadata; no source copying. KEEP local authority; ADAPT test. |
|
|
13
|
+
| ReMCP | `references/remcp/docs/TOOL_SELECTION_EVAL.md`, `scripts/check-distribution.mjs`, `docs/DISTRIBUTION.md` | Golden positive/negative tool-selection cases and cross-distribution identity checks. DEADBYTE already has exact catalog, release manifest and npm closure; add a small golden routing test without a new runtime dependency. | Package declares MIT. ADAPT evaluation; KEEP release machinery. |
|
|
14
|
+
| Secure Host MCP | `references/secure-host-mcp/src/workspace.ts`, `src/executor.ts`, `test/workspace.test.ts` | Rejects broad workspace roots, resolves symlink parents and bounds executor time. DEADBYTE already has explicit roots, policy-pinned profiles and native containment. Probe equivalent boundary cases; do not expose generic shell or change root policy without RED evidence. | Package declares MIT. KEEP architecture; ADAPT adversarial cases if missing. |
|
|
15
|
+
| WinRemote MCP | `references/winremote-mcp/src/winremote/security.py`, `tiers.py`, `tests/test_security_hardening.py` | Capability tiers and URL/OAuth hardening. DEADBYTE authority planes are narrower and independently revocable; OCR/tier expansion would add scope. Compare URL and Windows privilege boundary tests where current interfaces overlap. | Python metadata declares MIT. KEEP planes; REJECT OCR and tier expansion. |
|
|
16
|
+
|
|
17
|
+
Acceptance remains based on DEADBYTE's observed behavior, not donor popularity.
|
|
18
|
+
Any newly found donor gap requires a focused RED and a narrow local fix.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deadbyte-mcp",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.38",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Authenticated MCP agentic runtime with signed skills, persistent model backends, identity-bound remote devices, durable job supervision, immutable release slots, and separately revocable machine authority.",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"contained:run": "node src/contained-cli.mjs workspaces/demo input.txt",
|
|
43
43
|
"contained:smoke": "node src/contained-smoke.mjs",
|
|
44
44
|
"mcp:contained:smoke": "node src/mcp-contained-smoke-client.mjs",
|
|
45
|
-
"gate:windows": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/gate-windows-
|
|
45
|
+
"gate:windows": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/gate-windows-r61-prepublish.ps1",
|
|
46
46
|
"authority:smoke": "node src/authority-smoke.mjs",
|
|
47
47
|
"mcp:remote:smoke": "node src/remote-mcp-smoke-client.mjs",
|
|
48
48
|
"mcp:host:smoke": "node src/mcp-host-smoke-client.mjs",
|
|
@@ -85,7 +85,8 @@
|
|
|
85
85
|
"r59:focused:test": "node --test --test-concurrency=1 test/capability-snapshot-r59.test.mjs test/capability-resolver.test.mjs test/runtime-plane-registry-r59.test.mjs test/optional-plane-degraded-r59.test.mjs test/bridge-runtime-r59.test.mjs test/controller-policy-preflight-r59.test.mjs test/r59-tool-surface-compat.test.mjs test/r58-controller-state-coherence.test.mjs",
|
|
86
86
|
"r59:release:test": "node --test --test-concurrency=1 test/r59-release-contract.test.mjs test/r59-tool-surface-compat.test.mjs test/bridge-runtime-r59.test.mjs test/controller-policy-preflight-r59.test.mjs",
|
|
87
87
|
"r60:focused:test": "node --test --test-concurrency=1 test/r60-authority-coherence.test.mjs test/r60-authority-enforcement.test.mjs test/r60-bridge-dispatch.test.mjs test/r60-clock-discipline.test.mjs test/r60-live-timeline.test.mjs test/r60-reconnect-errors.test.mjs test/r60-reconnect-jitter.test.mjs test/r60-remote-call-recovery.test.mjs test/r60-windows-log-decode.test.mjs test/r60-release-contract.test.mjs",
|
|
88
|
-
"r60:full:test": "node scripts/full-regression-r60.mjs"
|
|
88
|
+
"r60:full:test": "node scripts/full-regression-r60.mjs",
|
|
89
|
+
"r61:focused:test": "node --test --test-concurrency=1 test/r61-release-contract.test.mjs test/r61-release-bridge-binding.test.mjs test/r61-control-lane.test.mjs test/r61-live-timeline-bounds.test.mjs test/r61-npm-artifact-identity.test.mjs test/remote-live-log-r37.test.mjs test/bridge-runtime-r59.test.mjs test/commander-schema-differential-r48.test.mjs test/package-boundary.test.mjs test/r60-bridge-dispatch.test.mjs test/r60-authority-enforcement.test.mjs"
|
|
89
90
|
},
|
|
90
91
|
"bundleDependencies": [
|
|
91
92
|
"@modelcontextprotocol/server",
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[ValidateSet('all','preflight','focused','full','manifest','doctor')]
|
|
3
|
+
[string]$Stage='all'
|
|
4
|
+
)
|
|
5
|
+
|
|
6
|
+
$ErrorActionPreference='Stop'
|
|
7
|
+
Set-StrictMode -Version 3.0
|
|
8
|
+
$root=Split-Path $PSScriptRoot -Parent
|
|
9
|
+
Set-Location -LiteralPath $root
|
|
10
|
+
|
|
11
|
+
function Invoke-Npm([string]$Name,[string[]]$NpmArgs){
|
|
12
|
+
Write-Host "==== $Name ===="
|
|
13
|
+
& npm.cmd @NpmArgs
|
|
14
|
+
if($LASTEXITCODE -ne 0){ throw "R61 prepublish gate failed at $Name exit=$LASTEXITCODE" }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
$stateRoot=if(-not [string]::IsNullOrWhiteSpace($env:DEADBYTE_STATE_ROOT)){
|
|
18
|
+
[IO.Path]::GetFullPath($env:DEADBYTE_STATE_ROOT)
|
|
19
|
+
}else{ Join-Path $env:USERPROFILE '.deadbyte-mcp' }
|
|
20
|
+
$pointer=Join-Path $stateRoot 'active-release.json'
|
|
21
|
+
if(-not (Test-Path -LiteralPath $pointer -PathType Leaf)){ throw "R61 predecessor pointer missing: $pointer" }
|
|
22
|
+
$active=Get-Content -LiteralPath $pointer -Raw | ConvertFrom-Json
|
|
23
|
+
if($active.release_id -ne 'v0.15.37-1cfecd7889419da7'){ throw "R61 predecessor release mismatch: $($active.release_id)" }
|
|
24
|
+
if($active.manifest_sha256 -ne '1cfecd7889419da7800814e3a2a4ffa93d48dfdb0c344a5b7b91fe8d093230aa'){ throw 'R61 predecessor manifest mismatch' }
|
|
25
|
+
if($active.generation_manifest_sha256 -ne '54842021c75f01bf7ecbd5b10ab8a482ecf107765b45c46f9d39b12bab65eac4'){ throw 'R61 predecessor generation mismatch' }
|
|
26
|
+
if($Stage -eq 'preflight'){Write-Host '==== R61 WINDOWS GATE: PASS preflight ====';return}
|
|
27
|
+
|
|
28
|
+
if($Stage -eq 'all' -or $Stage -eq 'focused'){
|
|
29
|
+
Invoke-Npm 'r61-focused' @('run','r61:focused:test')
|
|
30
|
+
if($Stage -eq 'focused'){Write-Host '==== R61 WINDOWS GATE: PASS focused ====';return}
|
|
31
|
+
}
|
|
32
|
+
if($Stage -eq 'all' -or $Stage -eq 'full'){
|
|
33
|
+
Invoke-Npm 'full-regression' @('test')
|
|
34
|
+
if($Stage -eq 'full'){Write-Host '==== R61 WINDOWS GATE: PASS full ====';return}
|
|
35
|
+
}
|
|
36
|
+
if($Stage -eq 'all' -or $Stage -eq 'manifest'){
|
|
37
|
+
Invoke-Npm 'manifest-write' @('run','manifest:write')
|
|
38
|
+
Invoke-Npm 'manifest-verify' @('run','manifest:verify')
|
|
39
|
+
if($Stage -eq 'manifest'){Write-Host '==== R61 WINDOWS GATE: PASS manifest ====';return}
|
|
40
|
+
}
|
|
41
|
+
if($Stage -eq 'all' -or $Stage -eq 'doctor'){
|
|
42
|
+
Invoke-Npm 'doctor' @('run','doctor')
|
|
43
|
+
if($Stage -eq 'doctor'){Write-Host '==== R61 WINDOWS GATE: PASS doctor ====';return}
|
|
44
|
+
}
|
|
45
|
+
Write-Host '==== R61 PREPUBLISH WINDOWS GATE: PASS ===='
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {createHash} from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
const digest=(name,bytes,encoding='hex')=>createHash(name).update(bytes).digest(encoding);
|
|
4
|
+
|
|
5
|
+
export function verifyPublishedTarballIdentity({version,localBytes,registryBytes,published,tags}){
|
|
6
|
+
if(typeof version!=='string'||!/^\d+\.\d+\.\d+$/.test(version)||!Buffer.isBuffer(localBytes)||!Buffer.isBuffer(registryBytes)){
|
|
7
|
+
throw new Error('npm artifact identity inputs invalid');
|
|
8
|
+
}
|
|
9
|
+
const tarballSha256=digest('sha256',localBytes);
|
|
10
|
+
const integrity='sha512-'+digest('sha512',localBytes,'base64');
|
|
11
|
+
const shasum=digest('sha1',localBytes);
|
|
12
|
+
const dist=published?.dist??published;
|
|
13
|
+
if(published?.version!==version||dist?.integrity!==integrity||dist?.shasum!==shasum||
|
|
14
|
+
dist?.tarball!==`https://registry.npmjs.org/deadbyte-mcp/-/deadbyte-mcp-${version}.tgz`){
|
|
15
|
+
throw new Error('published registry metadata does not match the exact local tarball');
|
|
16
|
+
}
|
|
17
|
+
if(tags?.latest!==version)throw new Error(`npm latest tag mismatch: expected ${version}, got ${tags?.latest??'<missing>'}`);
|
|
18
|
+
if(digest('sha256',registryBytes)!==tarballSha256||'sha512-'+digest('sha512',registryBytes,'base64')!==integrity){
|
|
19
|
+
throw new Error('registry tarball bytes differ from the exact published local artifact');
|
|
20
|
+
}
|
|
21
|
+
return Object.freeze({tarball_sha256:tarballSha256,npm_integrity:integrity,shasum});
|
|
22
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { verifyPublishedTarballIdentity } from './npm-artifact-identity-r61.mjs';
|
|
2
3
|
import { mkdtemp, readFile, rm, unlink } from 'node:fs/promises';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import os from 'node:os';
|
|
@@ -58,6 +59,8 @@ const allowedUnmanifested = new Set([
|
|
|
58
59
|
|
|
59
60
|
async function main() {
|
|
60
61
|
const isVerifyOnly = process.argv.includes('--verify-only') || process.argv.includes('--dry-run');
|
|
62
|
+
const publishOnly = process.argv.includes('--publish-only');
|
|
63
|
+
if (isVerifyOnly && publishOnly) throw new Error('--publish-only conflicts with verify-only mode');
|
|
61
64
|
|
|
62
65
|
console.log(`[1/7] Reading package.json and checking registry publication status... ${isVerifyOnly ? '(MODE: VERIFY-ONLY / DRY-RUN)' : '(MODE: PUBLISH)'}`);
|
|
63
66
|
const pkg = JSON.parse(await readFile(path.join(root, 'package.json'), 'utf8'));
|
|
@@ -180,6 +183,8 @@ async function main() {
|
|
|
180
183
|
return;
|
|
181
184
|
}
|
|
182
185
|
|
|
186
|
+
const localBytes = await readFile(tarball);
|
|
187
|
+
|
|
183
188
|
console.log('[6/7] Publishing artifact to npm registry...');
|
|
184
189
|
publishAttempted = true;
|
|
185
190
|
let publishOut;
|
|
@@ -193,8 +198,15 @@ async function main() {
|
|
|
193
198
|
}
|
|
194
199
|
throw publishError;
|
|
195
200
|
}
|
|
196
|
-
const
|
|
201
|
+
const viewed = parseJson(runNpm(['view', `deadbyte-mcp@${version}`, 'version', 'dist.integrity', 'dist.shasum', 'dist.tarball', '--json'], { timeout: 120000 }));
|
|
202
|
+
const published = Array.isArray(viewed) ? viewed[0] : viewed;
|
|
203
|
+
const dist = published?.dist ?? published;
|
|
204
|
+
if(typeof dist?.tarball !== 'string')throw new Error('published registry tarball URL missing');
|
|
205
|
+
const registryResponse = await fetch(dist.tarball,{signal:AbortSignal.timeout(120000)});
|
|
206
|
+
if(!registryResponse.ok) throw new Error(`registry tarball fetch failed status=${registryResponse.status}`);
|
|
207
|
+
const registryBytes = Buffer.from(await registryResponse.arrayBuffer());
|
|
197
208
|
const tags = parseJson(runNpm(['view', 'deadbyte-mcp', 'dist-tags', '--json'], { timeout: 120000 }));
|
|
209
|
+
const identity=verifyPublishedTarballIdentity({version,localBytes,registryBytes,published,tags});
|
|
198
210
|
|
|
199
211
|
console.log('[7/7] Post-publish external registry smoke test (executed outside repo in temp)...');
|
|
200
212
|
const tempDir = os.tmpdir();
|
|
@@ -211,16 +223,20 @@ async function main() {
|
|
|
211
223
|
timeout: 300000,
|
|
212
224
|
});
|
|
213
225
|
|
|
214
|
-
|
|
215
|
-
|
|
226
|
+
if(!publishOnly){
|
|
227
|
+
console.log('[8/8] Activating the published release into the live immutable slot and waiting for verified handoff...');
|
|
228
|
+
run(node, [path.join(root, 'scripts', 'deploy-r48.mjs')], { cwd: root, timeout: 900000 });
|
|
229
|
+
}else console.log('[8/8] Publication verified; activation deliberately deferred to the separate production gate.');
|
|
216
230
|
|
|
217
231
|
releaseSucceeded = true;
|
|
218
232
|
console.log('\nRELEASE SUCCESSFUL:');
|
|
219
233
|
console.log(JSON.stringify({
|
|
220
|
-
status: 'published',
|
|
234
|
+
status: publishOnly?'published_verified_activation_pending':'published',
|
|
221
235
|
name: pkg.name,
|
|
222
236
|
version,
|
|
223
237
|
tarball: filename,
|
|
238
|
+
tarball_sha256: identity.tarball_sha256,
|
|
239
|
+
npm_integrity: identity.npm_integrity,
|
|
224
240
|
published,
|
|
225
241
|
published_output_tail: publishOut.slice(-2000),
|
|
226
242
|
dist_tags: tags,
|
|
@@ -231,7 +247,7 @@ async function main() {
|
|
|
231
247
|
}, null, 2));
|
|
232
248
|
|
|
233
249
|
} finally {
|
|
234
|
-
if (releaseSucceeded || !publishAttempted) {
|
|
250
|
+
if ((releaseSucceeded && !publishOnly) || !publishAttempted) {
|
|
235
251
|
await unlink(tarball).catch(() => {});
|
|
236
252
|
} else {
|
|
237
253
|
console.error('Release artifact preserved after failure: ' + tarball);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {mkdtemp,mkdir,readFile,readdir,stat,writeFile,open,rm} from 'node:fs/promises';
|
|
2
|
+
import {spawn} from 'node:child_process';
|
|
3
|
+
import {createHash} from 'node:crypto';
|
|
4
|
+
import {monitorEventLoopDelay,performance} from 'node:perf_hooks';
|
|
5
|
+
import {pathToFileURL} from 'node:url';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
|
|
9
|
+
const roots=process.argv.slice(2);
|
|
10
|
+
if(roots.length!==2||roots.some(item=>!path.isAbsolute(item)))throw new Error('two absolute governor module paths required');
|
|
11
|
+
const levels=(process.env.DEADBYTE_R61_BENCH_LEVELS??'1,2,4,8,16,32').split(',').map(Number);
|
|
12
|
+
if(!levels.length||levels.some(level=>![1,2,4,8,16,32].includes(level))||new Set(levels).size!==levels.length)throw new Error('benchmark levels invalid');
|
|
13
|
+
const ROUNDS=Number(process.env.DEADBYTE_R61_BENCH_ROUNDS??3),OPS=160;
|
|
14
|
+
if(!Number.isInteger(ROUNDS)||ROUNDS<3||ROUNDS>15)throw new Error('benchmark rounds must be 3..15');
|
|
15
|
+
const scratchRoot=process.env.DEADBYTE_R61_SCRATCH_ROOT;
|
|
16
|
+
if(!scratchRoot||!path.isAbsolute(scratchRoot))throw new Error('explicit absolute DEADBYTE_R61_SCRATCH_ROOT required');
|
|
17
|
+
const quant=(values,p)=>{const ordered=[...values].sort((a,b)=>a-b);return ordered[Math.max(0,Math.ceil(ordered.length*p)-1)]};
|
|
18
|
+
const sha=value=>createHash('sha256').update(value).digest('hex');
|
|
19
|
+
|
|
20
|
+
async function childOutput(){
|
|
21
|
+
const child=spawn(process.execPath,['-e','process.stdout.write("R61_CHILD_OK")'],{windowsHide:true,stdio:['ignore','pipe','pipe']});
|
|
22
|
+
let output='';for await(const chunk of child.stdout)output+=chunk;
|
|
23
|
+
const exit=await new Promise(resolve=>child.once('close',resolve));
|
|
24
|
+
if(exit!==0||output!=='R61_CHILD_OK')throw new Error('bounded child process oracle failed');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function runCase(modulePath,level,round){
|
|
28
|
+
const {createBridgeGovernor}=await import(pathToFileURL(modulePath).href);
|
|
29
|
+
await mkdir(scratchRoot,{recursive:true});
|
|
30
|
+
const scratch=await mkdtemp(path.join(scratchRoot,'deadbyte-r61-bench-'));
|
|
31
|
+
const fixture=path.join(scratch,'fixture'),audit=path.join(scratch,'presentation.jsonl');
|
|
32
|
+
await mkdir(fixture);
|
|
33
|
+
const sample='deadbyte bounded corpus สวัสดี\n'.repeat(512);
|
|
34
|
+
for(let i=0;i<16;i++)await writeFile(path.join(fixture,`f${i}.txt`),sample);
|
|
35
|
+
const governor=createBridgeGovernor();
|
|
36
|
+
const calls=[],control=[],counts={error:0,unknown:0},peak={rss:process.memoryUsage().rss,queue:0,active:0};
|
|
37
|
+
const delay=monitorEventLoopDelay({resolution:20});delay.enable();
|
|
38
|
+
let next=0;
|
|
39
|
+
const started=performance.now();
|
|
40
|
+
async function worker(){
|
|
41
|
+
while(next<OPS){
|
|
42
|
+
const i=next++;
|
|
43
|
+
const kind=i%16;
|
|
44
|
+
const tool=kind<3?(kind===0?'machine_ping':'machine_status'):
|
|
45
|
+
kind<5?'capability_manifest':kind<8?'machine_fs_read':kind<10?'coding_search':
|
|
46
|
+
kind<12?'machine_fs_write':kind===12?'process_session_start':kind<15?'coding_file_read':'machine_process_list';
|
|
47
|
+
const t=performance.now();let ticket;
|
|
48
|
+
try{
|
|
49
|
+
ticket=await governor.acquire(tool);
|
|
50
|
+
if(tool==='machine_ping')sha(`nonce-${round}-${i}`);
|
|
51
|
+
else if(tool==='machine_status')await stat(fixture);
|
|
52
|
+
else if(tool==='capability_manifest')sha(await readFile(roots[0]));
|
|
53
|
+
else if(tool==='coding_search'){
|
|
54
|
+
const names=await readdir(fixture);
|
|
55
|
+
for(const name of names)if((await readFile(path.join(fixture,name),'utf8')).includes('bounded'))break;
|
|
56
|
+
}else if(tool==='machine_fs_write')await writeFile(path.join(scratch,`out-${i}.txt`),sample.slice(0,128));
|
|
57
|
+
else if(tool==='process_session_start')await childOutput();
|
|
58
|
+
else if(tool==='machine_process_list')os.cpus().length;
|
|
59
|
+
else await readFile(path.join(fixture,`f${i%16}.txt`));
|
|
60
|
+
const line=JSON.stringify({kind,at:i,hash:sha(String(i))})+'\n';
|
|
61
|
+
const handle=await open(audit,'a');try{await handle.writeFile(line);await handle.sync();}finally{await handle.close();}
|
|
62
|
+
}catch(error){counts.error++;throw error;}
|
|
63
|
+
finally{
|
|
64
|
+
ticket?.release();
|
|
65
|
+
const elapsed=performance.now()-t;
|
|
66
|
+
calls.push(elapsed);if(kind<3)control.push(elapsed);
|
|
67
|
+
const state=governor.snapshot();
|
|
68
|
+
peak.rss=Math.max(peak.rss,process.memoryUsage().rss);
|
|
69
|
+
peak.queue=Math.max(peak.queue,...Object.values(state).map(value=>value.queued));
|
|
70
|
+
peak.active=Math.max(peak.active,...Object.values(state).map(value=>value.active));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
try{
|
|
75
|
+
await Promise.all(Array.from({length:level},()=>worker()));
|
|
76
|
+
await governor.close();
|
|
77
|
+
const total=performance.now()-started;
|
|
78
|
+
return {level,round,n:calls.length,throughput_ops_s:OPS*1000/total,p50_ms:quant(calls,.5),p95_ms:quant(calls,.95),
|
|
79
|
+
p99_ms:quant(calls,.99),max_ms:Math.max(...calls),control_p95_ms:quant(control,.95),
|
|
80
|
+
event_loop_p95_ms:delay.percentile(95)/1e6,peak_rss_bytes:peak.rss,peak_queue:peak.queue,
|
|
81
|
+
peak_active:peak.active,errors:counts.error,unknown:counts.unknown};
|
|
82
|
+
}finally{delay.disable();await rm(scratch,{recursive:true,force:true});}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const results=[];
|
|
86
|
+
for(const level of levels)for(let round=0;round<ROUNDS;round++){
|
|
87
|
+
const order=round%2===0?[0,1]:[1,0];
|
|
88
|
+
for(const index of order)results.push({target:index,...await runCase(roots[index],level,round)});
|
|
89
|
+
}
|
|
90
|
+
const report={schema:'deadbyte.r61.mixed-lane-benchmark.v1',node:process.version,platform:process.platform,
|
|
91
|
+
governor_sha256:await Promise.all(roots.map(async value=>sha(await readFile(value)))),levels,rounds:ROUNDS,operations_per_run:OPS,
|
|
92
|
+
limitations:['direct governor and real scratch I/O, not full MCP transport','presentation traffic is fsynced fixture data, not signed audit'],results};
|
|
93
|
+
const outputPath=process.env.DEADBYTE_R61_BENCH_OUTPUT;
|
|
94
|
+
if(outputPath){
|
|
95
|
+
if(!path.isAbsolute(outputPath))throw new Error('benchmark output must be absolute');
|
|
96
|
+
await writeFile(outputPath,JSON.stringify(report,null,2)+'\n',{flag:'wx'});
|
|
97
|
+
console.log(JSON.stringify({schema:report.schema,output_path:outputPath,result_count:results.length,governor_sha256:report.governor_sha256}));
|
|
98
|
+
}else console.log(JSON.stringify(report,null,2));
|
package/src/deadbyte-cli.mjs
CHANGED
|
@@ -226,12 +226,12 @@ function parseAuditLogLine(file,line){
|
|
|
226
226
|
if(!/^segment-\d{8}\.jsonl$/.test(path.basename(file))||!line) return null;
|
|
227
227
|
try{return JSON.parse(line);}catch{return null;}
|
|
228
228
|
}
|
|
229
|
-
function formatLogLine(file,line,{auditEvent=null,activityTracker=null,sequence=null,verbose=false,displayMode='unicode'}={}){
|
|
229
|
+
function formatLogLine(file,line,{auditEvent=null,activityTracker=null,sequence=null,sourceSequence=null,verbose=false,displayMode='unicode'}={}){
|
|
230
230
|
if(!line) return null;
|
|
231
231
|
if(/^segment-\d{8}\.jsonl$/.test(path.basename(file))){
|
|
232
232
|
const event=auditEvent??parseAuditLogLine(file,line);
|
|
233
233
|
if(!event) return null;
|
|
234
|
-
return activityTracker?activityTracker.observe(event,{sequence}):formatCompactAuditEvent(event);
|
|
234
|
+
return activityTracker?activityTracker.observe(event,{sequence,sourceSequence}):formatCompactAuditEvent(event);
|
|
235
235
|
}
|
|
236
236
|
return formatRuntimeDiagnosticLine(file,line,{verbose,displayMode});
|
|
237
237
|
}
|
|
@@ -328,7 +328,7 @@ function timelineFilterEvent(timeline){
|
|
|
328
328
|
async function tailRemoteLogs(root,signal,{journal=null,offsets=null,activityTracker=null,timelineFilter=null,timelineWriter=null,verbose=false,displayMode='unicode'}={}){
|
|
329
329
|
process.stdout.write(verbose
|
|
330
330
|
? '[LOG] Verbose live timeline. Full sanitized request detail enabled; Ctrl+C disconnects and revokes authority.\n'
|
|
331
|
-
: '[LOG] Human live timeline. Transport IN/OUT hidden; full replay/audit detail remains on disk. Ctrl+C disconnects and revokes authority.\n');
|
|
331
|
+
: '[LOG] Human live timeline. Transport IN/OUT hidden; full replay/audit detail remains on disk. START/DONE retained; heartbeat bursts folded; Ctrl+C disconnects and revokes authority.\n');
|
|
332
332
|
const activeOffsets=offsets??await snapshotRemoteLogOffsets(root);
|
|
333
333
|
const partialLines=new Map();
|
|
334
334
|
let liveFiles=[...activeOffsets.keys()];
|
|
@@ -358,7 +358,8 @@ async function tailRemoteLogs(root,signal,{journal=null,offsets=null,activityTra
|
|
|
358
358
|
: await journal.recordTerminal(timeline);
|
|
359
359
|
if(recorded?.duplicate) continue;
|
|
360
360
|
const accepted=timelineFilter&&timeline?timelineFilter.accept(timelineFilterEvent(timeline)):true;
|
|
361
|
-
const formatted=formatLogLine(file,line,{auditEvent,activityTracker,sequence:recorded?.event?.seq??null,
|
|
361
|
+
const formatted=formatLogLine(file,line,{auditEvent,activityTracker,sequence:recorded?.event?.seq??null,
|
|
362
|
+
sourceSequence:Number.isSafeInteger(auditEvent?.seq)?auditEvent.seq:null,verbose,displayMode});
|
|
362
363
|
if(!formatted||!accepted) continue;
|
|
363
364
|
const output=Array.isArray(formatted)?formatted:[formatted];
|
|
364
365
|
for(const item of output) if(item){
|
|
@@ -512,9 +513,9 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
512
513
|
filePath:remoteResultJournalPath(ctx.root,marker.result_session_id),sessionId:marker.result_session_id,maxEntries:2048
|
|
513
514
|
});
|
|
514
515
|
const rootPaths=await readLiveRootPaths(ctx.root);
|
|
515
|
-
const activityTracker=createLiveTimelineTracker({verbose,displayMode,rootPaths,trace});
|
|
516
|
+
const activityTracker=createLiveTimelineTracker({verbose,displayMode,rootPaths,trace,foldHeartbeats:!verbose});
|
|
516
517
|
const timelineFilter=filterExpression?createLiveTimelineFilter(filterExpression):null;
|
|
517
|
-
const timelineWriter=createLiveTimelineWriter({stream:process.stdout,displayMode,rewrite
|
|
518
|
+
const timelineWriter=createLiveTimelineWriter({stream:process.stdout,displayMode,rewrite:false});
|
|
518
519
|
const aborter=new AbortController();
|
|
519
520
|
const abort=()=>aborter.abort();
|
|
520
521
|
const watchStdin=!process.stdin.isTTY;
|
|
@@ -588,6 +589,8 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
588
589
|
if(logTask) await logTask.catch(()=>{});
|
|
589
590
|
timelineWriter.flush();
|
|
590
591
|
for(const line of activityTracker.reconcileInterrupted('session_end')) process.stdout.write(`${line}\n`);
|
|
592
|
+
const filterRetention=timelineFilter?.retention();
|
|
593
|
+
if(filterRetention?.evicted>0) process.stdout.write(`[WARN] LIVELOG filter evicted ${filterRetention.evicted} presentation identities; full audit remains on disk.\n`);
|
|
591
594
|
process.stdout.write(`${activityTracker.summary()}\n`);
|
|
592
595
|
process.removeListener('SIGINT',abort);process.removeListener('SIGTERM',abort);
|
|
593
596
|
if(watchStdin){process.stdin.removeListener('end',abort);process.stdin.removeListener('close',abort);process.stdin.pause();}
|