@brunobracaioli/aibrain 0.2.0-beta.1 → 0.3.0-beta.13

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.
@@ -0,0 +1,1336 @@
1
+ #!/usr/bin/env node
2
+ // @ts-check
3
+
4
+ import { createHash, randomUUID } from 'node:crypto';
5
+ import { chmod, lstat, mkdir, open, readFile, readdir, rename, rm, stat } from 'node:fs/promises';
6
+ import { constants } from 'node:fs';
7
+ import { dirname, isAbsolute, join, resolve } from 'node:path';
8
+ import { claudeObservationConfigBinding, readConfig } from './state.mjs';
9
+ import { selectContextShadow } from './mcp.mjs';
10
+
11
+ export const MAX_STDIN_BYTES = 64 * 1024;
12
+ export const MAX_RECORDS = 64;
13
+ export const MAX_ITEMS = 16;
14
+ export const MAX_LOG_BYTES = 1024 * 1024;
15
+ export const MAX_LOG_FILES = 3;
16
+ const LOCK_WAIT_MS = 250;
17
+ const LOCK_STALE_MS = 30_000;
18
+ const ADMISSION_MS = 60 * 60 * 1000;
19
+ export const ADAPTER_VERSION = '0.3.0-beta.13';
20
+ const MAX_DIAGNOSTIC_COUNT = 10_000_000;
21
+ const MAX_DIAGNOSTIC_BYTES = 128 * 1024 * 1024;
22
+ const CHECKPOINT_BACKENDS = new Set(['native_store', 'private_file']);
23
+ const PROVIDER_RESPONSE_FORMATS = new Set(['structured', 'text', 'both', 'missing']);
24
+ const DIAGNOSTIC_REASONS = new Set([
25
+ 'all_recent',
26
+ 'goal_missing',
27
+ 'compact_priority',
28
+ 'usage_unavailable',
29
+ 'policy_unsupported',
30
+ 'no_eligible_tools',
31
+ 'unsupported_result',
32
+ 'disabled',
33
+ 'excluded',
34
+ 'provider_not_attempted',
35
+ // Kept for records written by beta2/beta3 clients.
36
+ 'unsupported_shape',
37
+ // Completion-proof outcomes are kept private and are never treated as
38
+ // provider or billing telemetry reasons.
39
+ 'proof_expired',
40
+ 'proof_mismatch',
41
+ 'native_fallback_unconfirmed',
42
+ 'native_summary_proof_expired',
43
+ 'native_summary_host_view_mismatch',
44
+ 'native_summary_host_view_ambiguous',
45
+ 'native_summary_host_view_unavailable',
46
+ 'native_summary_note_read_failed',
47
+ 'native_summary_attachment_uncertain',
48
+ 'plan_session_mismatch',
49
+ 'plan_revision_mismatch',
50
+ 'plan_projection_mismatch',
51
+ 'plan_options_mismatch',
52
+ 'plan_expired',
53
+ 'plan_parse_envelope_invalid',
54
+ 'plan_parse_metadata_invalid',
55
+ 'plan_parse_decisions_invalid',
56
+ 'plan_parse_fitting_invalid',
57
+ 'plan_parse_stats_invalid',
58
+ 'plan_parse_response_too_large',
59
+ 'plan_parse_failed',
60
+ 'session_changed_during_planning',
61
+ 'snapshot_changed_during_planning',
62
+ 'historical_notes_changed_during_planning',
63
+ ]);
64
+ const ADMISSION_DETAILS = new Set(['state_budget_exceeded', 'shared_admission_denied']);
65
+ const ADMISSION_REASONS = new Set([
66
+ 'request_replayed',
67
+ 'account_request_limit',
68
+ 'account_token_limit',
69
+ 'global_request_limit',
70
+ 'global_token_limit',
71
+ 'account_in_flight',
72
+ 'database_unavailable',
73
+ ]);
74
+ const CHECKPOINT_PHASES = new Set([
75
+ 'read_existing',
76
+ 'list_owned',
77
+ 'read_owned',
78
+ 'prune',
79
+ 'write_candidate',
80
+ 'verify_candidate',
81
+ 'write_index',
82
+ 'verify_index',
83
+ 'rollback',
84
+ ]);
85
+ const CHECKPOINT_ERRORS = new Set([
86
+ 'checkpoint_keys_failed',
87
+ 'checkpoint_read_failed',
88
+ 'checkpoint_candidate_invalid',
89
+ 'checkpoint_exceeds_retention',
90
+ 'checkpoint_prune_failed',
91
+ 'checkpoint_write_failed',
92
+ 'checkpoint_readback_mismatch',
93
+ 'checkpoint_index_failed',
94
+ 'checkpoint_index_mismatch',
95
+ 'checkpoint_rollback_failed',
96
+ 'checkpoint_helper_invalid',
97
+ 'checkpoint_helper_failed',
98
+ 'checkpoint_helper_ack_mismatch',
99
+ 'checkpoint_helper_input_too_large',
100
+ 'checkpoint_binding_mismatch',
101
+ ]);
102
+ const COMPACT_ERROR_CODES = new Set([
103
+ 'state_budget_exceeded',
104
+ 'shared_admission_exceeded',
105
+ 'provider_budget_exceeded',
106
+ 'provider_unavailable',
107
+ 'provider_auth_denied',
108
+ 'invalid_provider_request',
109
+ 'provider_response_invalid',
110
+ 'insufficient_reduction',
111
+ 'no_eligible_items',
112
+ 'measurement_limit_exceeded',
113
+ 'projection_anchor_overflow',
114
+ 'projection_message_limit',
115
+ 'projection_budget_exceeded',
116
+ ]);
117
+
118
+ /** @typedef {{id: string, kind: 'tool_result'|'reference', chars: number, protected: boolean, description?: string}} ObservationItem */
119
+ /** @typedef {{keep_threshold: number, preserve_recent_items: number, max_state_tokens: number, max_request_tokens: number}} ObservationOptions */
120
+ /** @typedef {{schema_version: 1, action: 'diagnostics', session_key: string, instance_id: string, records: Record<string, unknown>[]}} DiagnosticsInput */
121
+ /** @typedef {{schema_version: 1, action: 'preview', session_key: string, instance_id: string, operation_id: string, client_version: string, observed_at: string, revision: string, goal: string, items: ObservationItem[], options: ObservationOptions}} PreviewInput */
122
+ /** @typedef {DiagnosticsInput | PreviewInput} ObservationInput */
123
+ /** @typedef {{endpoint: string, token: string, claudeObservation: {shadowEnabled: boolean, diagnosticsEnabled: boolean, disclosedAt: string|null}}} ObservationConfig */
124
+
125
+ const DIAGNOSTIC_STAGES = new Set([
126
+ 'entry_guard',
127
+ 'snapshot',
128
+ 'checkpoint',
129
+ 'projection',
130
+ 'budget',
131
+ 'provider_request',
132
+ 'provider_response',
133
+ 'plan_validation',
134
+ 'candidate_preparation',
135
+ 'fallback',
136
+ 'compact_entry',
137
+ 'compact_exit',
138
+ 'native_summary_proof',
139
+ 'native_error',
140
+ ]);
141
+ const DIAGNOSTIC_CATEGORIES = new Set([
142
+ 'observed',
143
+ 'provider_not_attempted',
144
+ 'request_attempted',
145
+ 'response_obtained',
146
+ 'provider_decision',
147
+ 'native_error',
148
+ 'unsupported',
149
+ 'timeout',
150
+ 'rate_limited',
151
+ 'invalid_response',
152
+ 'skipped',
153
+ 'logger_failure',
154
+ 'auth_denied',
155
+ 'permission_denied',
156
+ 'tool_unavailable',
157
+ ]);
158
+ const MODES = new Set(['shadow', 'compact']);
159
+ const HEX64 = /^[a-f0-9]{64}$/u;
160
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
161
+ const SAFE_IDENTIFIER = /^[A-Za-z0-9._:-]{1,128}$/u;
162
+ const VERSION = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?$/u;
163
+ const MAX_VERSION_LENGTH = 64;
164
+ const ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u;
165
+ const SAFE_ERROR_CODES = new Set([
166
+ 'invalid_arguments',
167
+ 'state_dir_not_absolute',
168
+ 'config_digest_invalid',
169
+ 'state_path_invalid',
170
+ 'state_owner_mismatch',
171
+ 'state_permissions_invalid',
172
+ 'diagnostics_symlink_rejected',
173
+ 'diagnostics_path_missing',
174
+ 'diagnostics_path_invalid',
175
+ 'diagnostics_owner_mismatch',
176
+ 'diagnostics_file_invalid',
177
+ 'diagnostics_file_permissions_invalid',
178
+ 'diagnostics_file_size_invalid',
179
+ 'diagnostics_lock_symlink_rejected',
180
+ 'diagnostics_lock_permissions_invalid',
181
+ 'diagnostics_lock_timeout',
182
+ 'diagnostics_record_too_large',
183
+ 'invalid_input',
184
+ 'input_too_large',
185
+ 'observation_disabled',
186
+ 'observation_not_disclosed',
187
+ 'state_changed',
188
+ 'admission_invalid',
189
+ 'admission_denied',
190
+ 'mcp_deadline_invalid',
191
+ 'mcp_auth_denied',
192
+ 'mcp_request_timeout',
193
+ 'mcp_network_error',
194
+ 'mcp_response_malformed',
195
+ 'mcp_response_invalid_shape',
196
+ 'mcp_response_too_large',
197
+ 'mcp_response_invalid_utf8',
198
+ 'mcp_response_id_mismatch',
199
+ 'mcp_session_id_invalid',
200
+ 'mcp_session_id_changed',
201
+ 'mcp_initialize_denied',
202
+ 'mcp_shadow_request_invalid',
203
+ 'mcp_shadow_rejected',
204
+ 'mcp_tool_unavailable',
205
+ 'mcp_feature_disabled',
206
+ 'mcp_paid_plan_required',
207
+ 'provider_error',
208
+ 'helper_failed',
209
+ ]);
210
+
211
+ /** @param {unknown} value @returns {value is Record<string, unknown>} */
212
+ function object(value) {
213
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
214
+ }
215
+
216
+ /** @param {unknown} value @param {number} max */
217
+ function noControlText(value, max) {
218
+ return (
219
+ typeof value === 'string' && value.length <= max && !/[\u0000-\u001f\u007f-\u009f]/u.test(value)
220
+ );
221
+ }
222
+
223
+ /** @param {unknown} value @returns {string} */
224
+ function canonical(value) {
225
+ if (value === null || typeof value !== 'object') return JSON.stringify(value);
226
+ if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`;
227
+ const objectValue = /** @type {Record<string, unknown>} */ (value);
228
+ return `{${Object.keys(objectValue)
229
+ .sort()
230
+ .map((key) => `${JSON.stringify(key)}:${canonical(objectValue[key])}`)
231
+ .join(',')}}`;
232
+ }
233
+
234
+ /** @param {unknown} value */
235
+ function hash(value) {
236
+ return createHash('sha256').update(canonical(value), 'utf8').digest('hex');
237
+ }
238
+
239
+ /** @param {string} value */
240
+ function pseudonym(value) {
241
+ return hash({ value }).slice(0, 32);
242
+ }
243
+
244
+ /** @param {unknown} value */
245
+ function validVersion(value) {
246
+ return typeof value === 'string' && value.length <= MAX_VERSION_LENGTH && VERSION.test(value);
247
+ }
248
+
249
+ /** @param {unknown} error */
250
+ function safeProviderErrorCode(error) {
251
+ return error instanceof Error && SAFE_ERROR_CODES.has(error.message)
252
+ ? error.message
253
+ : 'provider_error';
254
+ }
255
+
256
+ /** @param {string} code */
257
+ function providerFailureCategory(code) {
258
+ if (code === 'mcp_request_timeout') return 'timeout';
259
+ if (code === 'mcp_auth_denied' || code === 'mcp_paid_plan_required') return 'auth_denied';
260
+ if (code === 'mcp_tool_unavailable') return 'tool_unavailable';
261
+ if (code === 'mcp_feature_disabled') return 'unsupported';
262
+ if (code === 'mcp_shadow_rejected' || code === 'mcp_response_malformed')
263
+ return 'invalid_response';
264
+ return 'native_error';
265
+ }
266
+
267
+ /** @param {string[]} argv */
268
+ function parseArgs(argv) {
269
+ if (argv.length !== 4 || argv[0] !== '--state-dir' || argv[2] !== '--config-digest')
270
+ throw new Error('invalid_arguments');
271
+ if (!isAbsolute(argv[1])) throw new Error('state_dir_not_absolute');
272
+ if (!HEX64.test(argv[3])) throw new Error('config_digest_invalid');
273
+ return { stateDir: resolve(argv[1]), configDigest: argv[3] };
274
+ }
275
+
276
+ /** @param {string} stateDir */
277
+ async function assertOwnedDir(stateDir) {
278
+ const root = resolve(stateDir);
279
+ await assertNoSymlinkAncestors(root, false);
280
+ const entry = await lstat(root);
281
+ if (!entry.isDirectory() || entry.isSymbolicLink()) throw new Error('state_path_invalid');
282
+ if (typeof process.getuid === 'function' && entry.uid !== process.getuid())
283
+ throw new Error('state_owner_mismatch');
284
+ if ((entry.mode & 0o077) !== 0) throw new Error('state_permissions_invalid');
285
+ return root;
286
+ }
287
+
288
+ /** @param {string} target @param {boolean} allowMissing */
289
+ async function assertNoSymlinkAncestors(target, allowMissing) {
290
+ const absolute = resolve(target);
291
+ const parts = absolute.split('/').filter(Boolean);
292
+ let current = absolute.startsWith('/') ? '/' : '';
293
+ for (const part of parts) {
294
+ current = current === '/' ? `/${part}` : join(current, part);
295
+ try {
296
+ const entry = await lstat(current);
297
+ if (entry.isSymbolicLink()) throw new Error('diagnostics_symlink_rejected');
298
+ } catch (error) {
299
+ if (!(error instanceof Error) || !('code' in error) || error.code !== 'ENOENT') throw error;
300
+ if (!allowMissing) throw new Error('diagnostics_path_missing');
301
+ break;
302
+ }
303
+ }
304
+ }
305
+
306
+ /** @param {string} path @param {boolean} [create] */
307
+ async function ownedDirectory(path, create = true) {
308
+ const target = resolve(path);
309
+ await assertNoSymlinkAncestors(target, create);
310
+ if (create) await mkdir(target, { recursive: true, mode: 0o700 });
311
+ await assertNoSymlinkAncestors(target, false);
312
+ const entry = await lstat(target);
313
+ if (!entry.isDirectory() || entry.isSymbolicLink()) throw new Error('diagnostics_path_invalid');
314
+ if (typeof process.getuid === 'function' && entry.uid !== process.getuid())
315
+ throw new Error('diagnostics_owner_mismatch');
316
+ await chmod(target, 0o700);
317
+ return target;
318
+ }
319
+
320
+ /** @param {string} path */
321
+ async function ownedFile(path) {
322
+ const entry = await lstat(path);
323
+ if (!entry.isFile() || entry.isSymbolicLink()) throw new Error('diagnostics_file_invalid');
324
+ if (typeof process.getuid === 'function' && entry.uid !== process.getuid())
325
+ throw new Error('diagnostics_owner_mismatch');
326
+ if ((entry.mode & 0o077) !== 0) throw new Error('diagnostics_file_permissions_invalid');
327
+ if (entry.size > MAX_LOG_BYTES) throw new Error('diagnostics_file_size_invalid');
328
+ return entry;
329
+ }
330
+
331
+ /** @param {string} path @param {string} payload */
332
+ async function writeOwnedJson(path, payload) {
333
+ await assertNoSymlinkAncestors(dirname(path), false);
334
+ const temporary = `${path}.tmp-${process.pid}-${randomUUID()}`;
335
+ const handle = await open(
336
+ temporary,
337
+ constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW,
338
+ 0o600,
339
+ );
340
+ try {
341
+ await handle.write(payload, undefined, 'utf8');
342
+ } finally {
343
+ await handle.close();
344
+ }
345
+ await chmod(temporary, 0o600);
346
+ try {
347
+ const existing = await lstat(path);
348
+ if (existing.isSymbolicLink()) throw new Error('diagnostics_file_invalid');
349
+ if (!existing.isFile()) throw new Error('diagnostics_file_invalid');
350
+ } catch (error) {
351
+ if (!(error instanceof Error) || !('code' in error) || error.code !== 'ENOENT') {
352
+ await rm(temporary, { force: true }).catch(() => undefined);
353
+ throw error;
354
+ }
355
+ }
356
+ try {
357
+ await rename(temporary, path);
358
+ } catch (error) {
359
+ await rm(temporary, { force: true }).catch(() => undefined);
360
+ throw error;
361
+ }
362
+ }
363
+
364
+ /** @param {Record<string, unknown>} record */
365
+ function isSafeLogRecord(record) {
366
+ const keys = new Set([
367
+ 'schema_version',
368
+ 'operation_id',
369
+ 'stage',
370
+ 'category',
371
+ 'mode',
372
+ 'adapter_version',
373
+ 'client_version',
374
+ 'observed_at',
375
+ 'session_key',
376
+ 'instance_id',
377
+ 'item_id',
378
+ 'duration_ms',
379
+ 'item_count',
380
+ 'captured_count',
381
+ 'excluded_count',
382
+ 'unsupported_count',
383
+ 'checkpoint_phase',
384
+ 'checkpoint_error',
385
+ 'compact_error_code',
386
+ 'hook_version',
387
+ 'checkpoint_backend',
388
+ 'projection_total',
389
+ 'selected_count',
390
+ 'deferred_count',
391
+ 'admission_detail',
392
+ 'admission_reason',
393
+ 'request_id',
394
+ 'correlation_id',
395
+ 'retry_after_ms',
396
+ 'proof_expected_count',
397
+ 'proof_actual_count',
398
+ 'proof_match',
399
+ 'proof_match_offset',
400
+ 'proof_view_source',
401
+ 'proof_api_view_match',
402
+ 'proof_api_count',
403
+ 'proof_delivery_source',
404
+ 'proof_delivery_match_count',
405
+ 'byte_count',
406
+ 'char_count',
407
+ 'rpc_status',
408
+ 'provider_response_format',
409
+ 'would_keep',
410
+ 'applied',
411
+ 'provider_confirmed',
412
+ 'chars',
413
+ 'outcome',
414
+ 'reason',
415
+ ]);
416
+ if (
417
+ record.schema_version !== 1 ||
418
+ Object.keys(record).some((key) => !keys.has(key)) ||
419
+ !UUID.test(String(record.operation_id)) ||
420
+ !DIAGNOSTIC_STAGES.has(String(record.stage)) ||
421
+ !DIAGNOSTIC_CATEGORIES.has(String(record.category)) ||
422
+ !MODES.has(String(record.mode)) ||
423
+ !validVersion(record.adapter_version) ||
424
+ !validVersion(record.client_version) ||
425
+ !ISO.test(String(record.observed_at)) ||
426
+ !Number.isFinite(Date.parse(String(record.observed_at))) ||
427
+ (record.session_key !== undefined && !/^[a-f0-9]{64}$/u.test(String(record.session_key))) ||
428
+ (record.instance_id !== undefined && !/^[a-f0-9]{32}$/u.test(String(record.instance_id))) ||
429
+ (record.item_id !== undefined && !/^[a-f0-9]{32}$/u.test(String(record.item_id))) ||
430
+ (record.applied !== undefined && record.applied !== false) ||
431
+ (record.outcome !== undefined &&
432
+ !['protected', 'would_keep', 'would_omit_result'].includes(String(record.outcome))) ||
433
+ (record.reason !== undefined && !DIAGNOSTIC_REASONS.has(String(record.reason))) ||
434
+ (record.checkpoint_phase !== undefined &&
435
+ (typeof record.checkpoint_phase !== 'string' ||
436
+ !CHECKPOINT_PHASES.has(record.checkpoint_phase))) ||
437
+ (record.checkpoint_error !== undefined &&
438
+ (typeof record.checkpoint_error !== 'string' ||
439
+ !CHECKPOINT_ERRORS.has(record.checkpoint_error))) ||
440
+ (record.compact_error_code !== undefined &&
441
+ (typeof record.compact_error_code !== 'string' ||
442
+ !COMPACT_ERROR_CODES.has(record.compact_error_code))) ||
443
+ (record.admission_detail !== undefined &&
444
+ (typeof record.admission_detail !== 'string' ||
445
+ !ADMISSION_DETAILS.has(record.admission_detail))) ||
446
+ (record.admission_reason !== undefined &&
447
+ (typeof record.admission_reason !== 'string' ||
448
+ !ADMISSION_REASONS.has(record.admission_reason))) ||
449
+ (record.request_id !== undefined && !UUID.test(String(record.request_id))) ||
450
+ (record.correlation_id !== undefined &&
451
+ (typeof record.correlation_id !== 'string' ||
452
+ !SAFE_IDENTIFIER.test(record.correlation_id))) ||
453
+ (record.retry_after_ms !== undefined &&
454
+ (typeof record.retry_after_ms !== 'number' ||
455
+ !Number.isSafeInteger(record.retry_after_ms) ||
456
+ record.retry_after_ms < 0 ||
457
+ record.retry_after_ms > 3_600_000)) ||
458
+ (record.proof_match !== undefined &&
459
+ (typeof record.proof_match !== 'string' ||
460
+ !['exact', 'active_suffix', 'mismatch', 'ambiguous'].includes(record.proof_match))) ||
461
+ (record.proof_view_source !== undefined &&
462
+ (typeof record.proof_view_source !== 'string' ||
463
+ !['default', 'api'].includes(record.proof_view_source))) ||
464
+ (record.proof_api_view_match !== undefined &&
465
+ (typeof record.proof_api_view_match !== 'string' ||
466
+ !['exact', 'mismatch', 'unavailable', 'not_checked'].includes(
467
+ record.proof_api_view_match,
468
+ ))) ||
469
+ (record.proof_delivery_source !== undefined &&
470
+ (typeof record.proof_delivery_source !== 'string' ||
471
+ !['atomic_result', 'legacy_append'].includes(record.proof_delivery_source))) ||
472
+ (record.hook_version !== undefined && !validVersion(record.hook_version)) ||
473
+ (record.checkpoint_backend !== undefined &&
474
+ (typeof record.checkpoint_backend !== 'string' ||
475
+ !CHECKPOINT_BACKENDS.has(record.checkpoint_backend))) ||
476
+ (record.provider_response_format !== undefined &&
477
+ !PROVIDER_RESPONSE_FORMATS.has(String(record.provider_response_format)))
478
+ )
479
+ return false;
480
+ for (const key of [
481
+ 'duration_ms',
482
+ 'item_count',
483
+ 'captured_count',
484
+ 'excluded_count',
485
+ 'unsupported_count',
486
+ 'byte_count',
487
+ 'char_count',
488
+ 'rpc_status',
489
+ 'chars',
490
+ 'projection_total',
491
+ 'selected_count',
492
+ 'deferred_count',
493
+ 'proof_expected_count',
494
+ 'proof_actual_count',
495
+ 'proof_match_offset',
496
+ 'proof_api_count',
497
+ 'proof_delivery_match_count',
498
+ ]) {
499
+ const value = record[key];
500
+ const lowerBound = key === 'rpc_status' ? -100_000 : 0;
501
+ if (
502
+ value !== undefined &&
503
+ (typeof value !== 'number' ||
504
+ !Number.isSafeInteger(value) ||
505
+ value < lowerBound ||
506
+ value > (key === 'byte_count' ? MAX_DIAGNOSTIC_BYTES : MAX_DIAGNOSTIC_COUNT))
507
+ )
508
+ return false;
509
+ }
510
+ for (const key of ['would_keep', 'provider_confirmed']) {
511
+ if (record[key] !== undefined && typeof record[key] !== 'boolean') return false;
512
+ }
513
+ return true;
514
+ }
515
+
516
+ /** @param {string} lockPath @param {() => Promise<unknown>} operation */
517
+ async function withLock(lockPath, operation) {
518
+ const started = Date.now();
519
+ let acquired = false;
520
+ while (!acquired) {
521
+ try {
522
+ await mkdir(lockPath, { mode: 0o700 });
523
+ acquired = true;
524
+ try {
525
+ await writeOwnedJson(
526
+ join(lockPath, 'owner.json'),
527
+ `${JSON.stringify({ pid: process.pid, created_at: Date.now() })}\n`,
528
+ );
529
+ } catch (error) {
530
+ await rm(lockPath, { recursive: true, force: true }).catch(() => undefined);
531
+ throw error;
532
+ }
533
+ } catch (error) {
534
+ if (!(error instanceof Error) || !('code' in error) || error.code !== 'EEXIST') throw error;
535
+ const lockEntry = await lstat(lockPath).catch((lockError) => {
536
+ if (lockError instanceof Error && 'code' in lockError && lockError.code === 'ENOENT')
537
+ return null;
538
+ throw lockError;
539
+ });
540
+ if (lockEntry?.isSymbolicLink()) throw new Error('diagnostics_lock_symlink_rejected');
541
+ if (
542
+ lockEntry &&
543
+ ((typeof process.getuid === 'function' && lockEntry.uid !== process.getuid()) ||
544
+ (lockEntry.mode & 0o077) !== 0)
545
+ )
546
+ throw new Error('diagnostics_lock_permissions_invalid');
547
+ let stale = false;
548
+ if (lockEntry?.isDirectory()) {
549
+ try {
550
+ const ownerPath = join(lockPath, 'owner.json');
551
+ await ownedFile(ownerPath);
552
+ const owner = JSON.parse(await readFile(ownerPath, 'utf8'));
553
+ let ownerAlive = false;
554
+ if (Number.isSafeInteger(owner.pid) && owner.pid > 0) {
555
+ try {
556
+ process.kill(owner.pid, 0);
557
+ ownerAlive = true;
558
+ } catch (processError) {
559
+ if (
560
+ !(processError instanceof Error) ||
561
+ !('code' in processError) ||
562
+ processError.code !== 'ESRCH'
563
+ )
564
+ ownerAlive = true;
565
+ }
566
+ }
567
+ stale =
568
+ !ownerAlive &&
569
+ Number.isSafeInteger(owner.created_at) &&
570
+ Date.now() - owner.created_at > LOCK_STALE_MS;
571
+ } catch (ownerError) {
572
+ if (ownerError instanceof Error && 'code' in ownerError && ownerError.code === 'ENOENT') {
573
+ try {
574
+ const metadata = await stat(lockPath);
575
+ stale = Date.now() - metadata.mtimeMs > LOCK_STALE_MS;
576
+ } catch (metadataError) {
577
+ if (
578
+ !(metadataError instanceof Error) ||
579
+ !('code' in metadataError) ||
580
+ metadataError.code !== 'ENOENT'
581
+ )
582
+ throw metadataError;
583
+ continue;
584
+ }
585
+ } else {
586
+ throw ownerError;
587
+ }
588
+ }
589
+ }
590
+ if (stale) {
591
+ await rm(lockPath, { recursive: true, force: true });
592
+ continue;
593
+ }
594
+ if (Date.now() - started >= LOCK_WAIT_MS) throw new Error('diagnostics_lock_timeout');
595
+ await new Promise((resolvePromise) => setTimeout(resolvePromise, 10));
596
+ }
597
+ }
598
+ try {
599
+ return await operation();
600
+ } finally {
601
+ await rm(lockPath, { recursive: true, force: true }).catch(() => undefined);
602
+ }
603
+ }
604
+
605
+ /** @param {string} root @param {string} file @param {unknown[]} records */
606
+ async function appendRecords(root, file, records) {
607
+ if (records.length === 0) return 0;
608
+ const directory = await ownedDirectory(join(root, 'diagnostics', 'claude'));
609
+ const lock = join(directory, '.lock');
610
+ return withLock(lock, async () => {
611
+ const files = Array.from({ length: MAX_LOG_FILES }, (_, index) =>
612
+ join(directory, `${file}.${index}.jsonl`),
613
+ );
614
+ let current = files[0];
615
+ for (const candidate of files) {
616
+ try {
617
+ const entry = await ownedFile(candidate);
618
+ if (entry.size < MAX_LOG_BYTES) {
619
+ current = candidate;
620
+ break;
621
+ }
622
+ } catch (error) {
623
+ if (!(error instanceof Error) || !('code' in error) || error.code !== 'ENOENT') throw error;
624
+ current = candidate;
625
+ break;
626
+ }
627
+ }
628
+ let payload = records.map((record) => `${JSON.stringify(record)}\n`).join('');
629
+ if (Buffer.byteLength(payload, 'utf8') > MAX_LOG_BYTES)
630
+ throw new Error('diagnostics_record_too_large');
631
+ let entry;
632
+ try {
633
+ entry = await ownedFile(current);
634
+ } catch (error) {
635
+ if (!(error instanceof Error) || !('code' in error) || error.code !== 'ENOENT') throw error;
636
+ entry = null;
637
+ }
638
+ if (entry && entry.size + Buffer.byteLength(payload, 'utf8') > MAX_LOG_BYTES) {
639
+ await rotateFiles(files);
640
+ current = files[0];
641
+ }
642
+ const handle = await open(
643
+ current,
644
+ constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | constants.O_NOFOLLOW,
645
+ 0o600,
646
+ );
647
+ try {
648
+ await handle.write(payload, undefined, 'utf8');
649
+ } finally {
650
+ await handle.close();
651
+ }
652
+ await chmod(current, 0o600);
653
+ return records.length;
654
+ });
655
+ }
656
+
657
+ /** @param {string[]} files */
658
+ async function rotateFiles(files) {
659
+ for (let index = files.length - 1; index > 0; index -= 1) {
660
+ await rename(files[index - 1], files[index]).catch((error) => {
661
+ if (!(error instanceof Error) || !('code' in error) || error.code !== 'ENOENT') throw error;
662
+ });
663
+ }
664
+ await rm(files[0], { force: true });
665
+ }
666
+
667
+ /** @param {unknown} input @returns {ObservationInput} */
668
+ function validateInput(input) {
669
+ if (
670
+ !object(input) ||
671
+ input.schema_version !== 1 ||
672
+ !['diagnostics', 'preview'].includes(String(input.action))
673
+ )
674
+ throw new Error('invalid_input');
675
+ const topLevelKeys =
676
+ input.action === 'diagnostics'
677
+ ? new Set(['schema_version', 'action', 'session_key', 'instance_id', 'records'])
678
+ : new Set([
679
+ 'schema_version',
680
+ 'action',
681
+ 'session_key',
682
+ 'instance_id',
683
+ 'operation_id',
684
+ 'client_version',
685
+ 'observed_at',
686
+ 'revision',
687
+ 'goal',
688
+ 'items',
689
+ 'options',
690
+ ]);
691
+ if (Object.keys(input).some((key) => !topLevelKeys.has(key))) throw new Error('invalid_input');
692
+ if (!HEX64.test(String(input.session_key)) || !UUID.test(String(input.instance_id)))
693
+ throw new Error('invalid_input');
694
+ if (input.action === 'diagnostics') {
695
+ const diagnosticsInput = /** @type {Record<string, unknown>} */ (input);
696
+ if (!Array.isArray(diagnosticsInput.records) || diagnosticsInput.records.length > MAX_RECORDS)
697
+ throw new Error('invalid_input');
698
+ return /** @type {DiagnosticsInput} */ (input);
699
+ }
700
+ const previewInput = /** @type {Record<string, unknown>} */ (input);
701
+ if (
702
+ !UUID.test(String(previewInput.operation_id)) ||
703
+ !validVersion(previewInput.client_version) ||
704
+ !ISO.test(String(previewInput.observed_at)) ||
705
+ !Number.isFinite(Date.parse(String(previewInput.observed_at))) ||
706
+ !HEX64.test(String(previewInput.revision)) ||
707
+ !noControlText(previewInput.goal, 4000) ||
708
+ !Array.isArray(previewInput.items) ||
709
+ previewInput.items.length === 0 ||
710
+ previewInput.items.length > MAX_ITEMS ||
711
+ !object(previewInput.options)
712
+ )
713
+ throw new Error('invalid_input');
714
+ const items = /** @type {ObservationItem[]} */ (previewInput.items);
715
+ for (const item of items) {
716
+ if (
717
+ !object(item) ||
718
+ !noControlText(item.id, 128) ||
719
+ !['tool_result', 'reference'].includes(item.kind) ||
720
+ !Number.isSafeInteger(item.chars) ||
721
+ item.chars < 0 ||
722
+ item.chars > 10_000_000 ||
723
+ typeof item.protected !== 'boolean' ||
724
+ Object.keys(item).some(
725
+ (key) => !['id', 'kind', 'chars', 'protected', 'description'].includes(key),
726
+ ) ||
727
+ (item.description !== undefined && !noControlText(item.description, 256))
728
+ )
729
+ throw new Error('invalid_input');
730
+ }
731
+ const options = /** @type {ObservationOptions} */ (previewInput.options);
732
+ if (
733
+ Object.keys(options).some(
734
+ (key) =>
735
+ ![
736
+ 'keep_threshold',
737
+ 'preserve_recent_items',
738
+ 'max_state_tokens',
739
+ 'max_request_tokens',
740
+ ].includes(key),
741
+ )
742
+ )
743
+ throw new Error('invalid_input');
744
+ if (
745
+ typeof options.keep_threshold !== 'number' ||
746
+ !Number.isFinite(options.keep_threshold) ||
747
+ options.keep_threshold < 0 ||
748
+ options.keep_threshold > 1 ||
749
+ !Number.isSafeInteger(options.preserve_recent_items) ||
750
+ options.preserve_recent_items < 1 ||
751
+ options.preserve_recent_items > 100 ||
752
+ !Number.isSafeInteger(options.max_state_tokens) ||
753
+ options.max_state_tokens < 1000 ||
754
+ options.max_state_tokens > 25000 ||
755
+ !Number.isSafeInteger(options.max_request_tokens) ||
756
+ options.max_request_tokens <= options.max_state_tokens + 256 ||
757
+ options.max_request_tokens > 30000
758
+ )
759
+ throw new Error('invalid_input');
760
+ return /** @type {PreviewInput} */ (input);
761
+ }
762
+
763
+ /** @param {Record<string, unknown>} record @returns {Record<string, unknown>} */
764
+ function diagnosticRecord(record) {
765
+ const allowedKeys = new Set([
766
+ 'operation_id',
767
+ 'stage',
768
+ 'category',
769
+ 'mode',
770
+ 'client_version',
771
+ 'observed_at',
772
+ 'duration_ms',
773
+ 'item_count',
774
+ 'captured_count',
775
+ 'excluded_count',
776
+ 'unsupported_count',
777
+ 'checkpoint_phase',
778
+ 'checkpoint_error',
779
+ 'compact_error_code',
780
+ 'hook_version',
781
+ 'checkpoint_backend',
782
+ 'projection_total',
783
+ 'selected_count',
784
+ 'deferred_count',
785
+ 'admission_detail',
786
+ 'admission_reason',
787
+ 'request_id',
788
+ 'correlation_id',
789
+ 'retry_after_ms',
790
+ 'proof_expected_count',
791
+ 'proof_actual_count',
792
+ 'proof_match',
793
+ 'proof_match_offset',
794
+ 'proof_view_source',
795
+ 'proof_api_view_match',
796
+ 'proof_api_count',
797
+ 'proof_delivery_source',
798
+ 'proof_delivery_match_count',
799
+ 'byte_count',
800
+ 'rpc_status',
801
+ 'provider_response_format',
802
+ 'reason',
803
+ ]);
804
+ if (
805
+ Object.keys(record).some((key) => !allowedKeys.has(key)) ||
806
+ !UUID.test(String(record.operation_id)) ||
807
+ !DIAGNOSTIC_STAGES.has(String(record.stage)) ||
808
+ !DIAGNOSTIC_CATEGORIES.has(String(record.category)) ||
809
+ !MODES.has(String(record.mode)) ||
810
+ !validVersion(record.client_version) ||
811
+ !ISO.test(String(record.observed_at)) ||
812
+ !Number.isFinite(Date.parse(String(record.observed_at))) ||
813
+ (record.proof_match !== undefined &&
814
+ (typeof record.proof_match !== 'string' ||
815
+ !['exact', 'active_suffix', 'mismatch', 'ambiguous'].includes(record.proof_match))) ||
816
+ (record.proof_view_source !== undefined &&
817
+ (typeof record.proof_view_source !== 'string' ||
818
+ !['default', 'api'].includes(record.proof_view_source))) ||
819
+ (record.proof_api_view_match !== undefined &&
820
+ (typeof record.proof_api_view_match !== 'string' ||
821
+ !['exact', 'mismatch', 'unavailable', 'not_checked'].includes(
822
+ record.proof_api_view_match,
823
+ ))) ||
824
+ (record.proof_delivery_source !== undefined &&
825
+ (typeof record.proof_delivery_source !== 'string' ||
826
+ !['atomic_result', 'legacy_append'].includes(record.proof_delivery_source))) ||
827
+ (record.provider_response_format !== undefined &&
828
+ !PROVIDER_RESPONSE_FORMATS.has(String(record.provider_response_format)))
829
+ )
830
+ throw new Error('invalid_input');
831
+ /** @type {Record<string, unknown>} */
832
+ const output = {
833
+ schema_version: 1,
834
+ operation_id: String(record.operation_id),
835
+ stage: String(record.stage),
836
+ category: String(record.category),
837
+ mode: String(record.mode),
838
+ adapter_version: ADAPTER_VERSION,
839
+ client_version: String(record.client_version),
840
+ observed_at: String(record.observed_at),
841
+ };
842
+ if (record.reason !== undefined) {
843
+ if (!noControlText(record.reason, 64) || !DIAGNOSTIC_REASONS.has(String(record.reason)))
844
+ throw new Error('invalid_input');
845
+ output.reason = String(record.reason);
846
+ }
847
+ if (record.checkpoint_phase !== undefined) {
848
+ if (
849
+ typeof record.checkpoint_phase !== 'string' ||
850
+ !CHECKPOINT_PHASES.has(record.checkpoint_phase)
851
+ )
852
+ throw new Error('invalid_input');
853
+ output.checkpoint_phase = record.checkpoint_phase;
854
+ }
855
+ if (record.checkpoint_error !== undefined) {
856
+ if (
857
+ typeof record.checkpoint_error !== 'string' ||
858
+ !CHECKPOINT_ERRORS.has(record.checkpoint_error)
859
+ )
860
+ throw new Error('invalid_input');
861
+ output.checkpoint_error = record.checkpoint_error;
862
+ }
863
+ if (record.compact_error_code !== undefined) {
864
+ if (
865
+ typeof record.compact_error_code !== 'string' ||
866
+ !COMPACT_ERROR_CODES.has(record.compact_error_code)
867
+ )
868
+ throw new Error('invalid_input');
869
+ output.compact_error_code = record.compact_error_code;
870
+ }
871
+ if (record.admission_detail !== undefined) {
872
+ if (
873
+ typeof record.admission_detail !== 'string' ||
874
+ !ADMISSION_DETAILS.has(record.admission_detail)
875
+ )
876
+ throw new Error('invalid_input');
877
+ output.admission_detail = record.admission_detail;
878
+ }
879
+ if (record.admission_reason !== undefined) {
880
+ if (
881
+ typeof record.admission_reason !== 'string' ||
882
+ !ADMISSION_REASONS.has(record.admission_reason)
883
+ )
884
+ throw new Error('invalid_input');
885
+ output.admission_reason = record.admission_reason;
886
+ }
887
+ if (record.request_id !== undefined) {
888
+ if (typeof record.request_id !== 'string' || !UUID.test(record.request_id))
889
+ throw new Error('invalid_input');
890
+ output.request_id = record.request_id;
891
+ }
892
+ if (record.correlation_id !== undefined) {
893
+ if (typeof record.correlation_id !== 'string' || !SAFE_IDENTIFIER.test(record.correlation_id))
894
+ throw new Error('invalid_input');
895
+ output.correlation_id = record.correlation_id;
896
+ }
897
+ if (record.retry_after_ms !== undefined) {
898
+ if (
899
+ typeof record.retry_after_ms !== 'number' ||
900
+ !Number.isSafeInteger(record.retry_after_ms) ||
901
+ record.retry_after_ms < 0 ||
902
+ record.retry_after_ms > 3_600_000
903
+ )
904
+ throw new Error('invalid_input');
905
+ output.retry_after_ms = record.retry_after_ms;
906
+ }
907
+ for (const key of ['proof_expected_count', 'proof_actual_count', 'proof_match_offset']) {
908
+ const value = record[key];
909
+ if (
910
+ value !== undefined &&
911
+ (typeof value !== 'number' ||
912
+ !Number.isSafeInteger(value) ||
913
+ value < 0 ||
914
+ value > MAX_DIAGNOSTIC_COUNT)
915
+ )
916
+ throw new Error('invalid_input');
917
+ if (value !== undefined) output[key] = value;
918
+ }
919
+ if (record.proof_match !== undefined) output.proof_match = String(record.proof_match);
920
+ if (record.proof_view_source !== undefined)
921
+ output.proof_view_source = String(record.proof_view_source);
922
+ if (record.proof_api_view_match !== undefined)
923
+ output.proof_api_view_match = String(record.proof_api_view_match);
924
+ if (record.proof_delivery_source !== undefined)
925
+ output.proof_delivery_source = String(record.proof_delivery_source);
926
+ if (record.hook_version !== undefined) {
927
+ if (!validVersion(record.hook_version)) throw new Error('invalid_input');
928
+ output.hook_version = record.hook_version;
929
+ }
930
+ if (record.checkpoint_backend !== undefined) {
931
+ if (
932
+ typeof record.checkpoint_backend !== 'string' ||
933
+ !CHECKPOINT_BACKENDS.has(record.checkpoint_backend)
934
+ )
935
+ throw new Error('invalid_input');
936
+ output.checkpoint_backend = record.checkpoint_backend;
937
+ }
938
+ if (record.provider_response_format !== undefined) {
939
+ if (
940
+ typeof record.provider_response_format !== 'string' ||
941
+ !PROVIDER_RESPONSE_FORMATS.has(record.provider_response_format)
942
+ )
943
+ throw new Error('invalid_input');
944
+ output.provider_response_format = record.provider_response_format;
945
+ }
946
+ for (const key of [
947
+ 'duration_ms',
948
+ 'item_count',
949
+ 'captured_count',
950
+ 'excluded_count',
951
+ 'unsupported_count',
952
+ 'byte_count',
953
+ 'rpc_status',
954
+ 'projection_total',
955
+ 'selected_count',
956
+ 'deferred_count',
957
+ 'proof_expected_count',
958
+ 'proof_actual_count',
959
+ 'proof_match_offset',
960
+ 'proof_api_count',
961
+ 'proof_delivery_match_count',
962
+ ]) {
963
+ const numeric = record[key];
964
+ if (numeric !== undefined) {
965
+ const lowerBound = key === 'rpc_status' ? -100_000 : 0;
966
+ if (typeof numeric !== 'number' || !Number.isSafeInteger(numeric) || numeric < lowerBound)
967
+ throw new Error('invalid_input');
968
+ if (numeric <= (key === 'byte_count' ? MAX_DIAGNOSTIC_BYTES : MAX_DIAGNOSTIC_COUNT))
969
+ output[key] = numeric;
970
+ }
971
+ }
972
+ return output;
973
+ }
974
+
975
+ /** @param {string} root @param {string} digest @returns {Promise<ObservationConfig>} */
976
+ async function currentConfig(root, digest) {
977
+ const config = await readConfig(root);
978
+ if (!config || !config.claudeObservation) throw new Error('observation_disabled');
979
+ const observation = config.claudeObservation;
980
+ if (observation.disclosedAt === null) throw new Error('observation_not_disclosed');
981
+ if (claudeObservationConfigBinding(config) !== digest) throw new Error('state_changed');
982
+ return /** @type {ObservationConfig} */ (config);
983
+ }
984
+
985
+ /** @param {DiagnosticsInput} input @param {ObservationConfig} config @param {string} root */
986
+ async function diagnostics(input, config, root) {
987
+ if (config.claudeObservation?.diagnosticsEnabled !== true)
988
+ throw new Error('observation_disabled');
989
+ const records = input.records.map((record) => {
990
+ if (!object(record)) throw new Error('invalid_input');
991
+ return {
992
+ ...diagnosticRecord(record),
993
+ session_key: String(input.session_key),
994
+ instance_id: pseudonym(String(input.instance_id)),
995
+ };
996
+ });
997
+ try {
998
+ const written = await appendRecords(root, 'events', records);
999
+ return { schema_version: 1, accepted: true, action: 'diagnostics', written };
1000
+ } catch {
1001
+ return { schema_version: 1, accepted: false, error_code: 'logger_failure' };
1002
+ }
1003
+ }
1004
+
1005
+ /** @param {PreviewInput} input @param {ObservationConfig} config @param {string} root */
1006
+ async function preview(input, config, root) {
1007
+ if (config.claudeObservation?.shadowEnabled !== true) throw new Error('observation_disabled');
1008
+ const recentStart = Math.max(0, input.items.length - input.options.preserve_recent_items);
1009
+ const items = input.items.map((item, index) => ({
1010
+ ...item,
1011
+ protected: item.protected || item.kind === 'reference' || index >= recentStart,
1012
+ }));
1013
+ const eligible = items.filter((item) => item.protected !== true);
1014
+ if (eligible.length === 0)
1015
+ return {
1016
+ schema_version: 1,
1017
+ accepted: true,
1018
+ action: 'preview',
1019
+ applied: false,
1020
+ skipped: 'all_recent',
1021
+ decisions: [],
1022
+ };
1023
+ const directory = await ownedDirectory(join(root, 'diagnostics', 'claude'));
1024
+ const lock = join(directory, '.admission.lock');
1025
+ return withLock(lock, async () => {
1026
+ const admissionPath = join(directory, 'preview-admission.json');
1027
+ let last = 0;
1028
+ try {
1029
+ const entry = await ownedFile(admissionPath);
1030
+ if (entry.size > 4096) throw new Error('admission_invalid');
1031
+ const data = JSON.parse(await readFile(admissionPath, 'utf8'));
1032
+ if (
1033
+ data.binding ===
1034
+ hash({
1035
+ endpoint: config.endpoint,
1036
+ token: config.token,
1037
+ disclosedAt: config.claudeObservation.disclosedAt,
1038
+ }) &&
1039
+ Number.isSafeInteger(data.attempted_at)
1040
+ )
1041
+ last = data.attempted_at;
1042
+ } catch (error) {
1043
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') last = 0;
1044
+ else throw error;
1045
+ }
1046
+ if (Date.now() - last < ADMISSION_MS) throw new Error('admission_denied');
1047
+ await writeOwnedJson(
1048
+ admissionPath,
1049
+ `${JSON.stringify({ binding: hash({ endpoint: config.endpoint, token: config.token, disclosedAt: config.claudeObservation.disclosedAt }), attempted_at: Date.now() })}\n`,
1050
+ );
1051
+ await chmod(admissionPath, 0o600);
1052
+ const projection = {
1053
+ goal: input.goal,
1054
+ items: items.map((item) => ({
1055
+ id: String(item.id),
1056
+ kind: item.kind,
1057
+ text: item.description ?? `${item.kind} (${item.chars} chars)`,
1058
+ local_chars: item.chars,
1059
+ protected: item.protected,
1060
+ pin_reasons: item.protected ? ['recent'] : [],
1061
+ })),
1062
+ };
1063
+ const request = {
1064
+ schema_version: 2,
1065
+ client: 'claude_code',
1066
+ client_version: String(input.client_version),
1067
+ session_ref: `claude-shadow-${String(input.session_key).slice(0, 32)}`,
1068
+ revision: String(input.revision),
1069
+ observed_at: String(input.observed_at),
1070
+ request_id: String(input.operation_id),
1071
+ policy_version: 'context-recovery-v2',
1072
+ projection,
1073
+ options: input.options,
1074
+ };
1075
+ const commonLog = {
1076
+ schema_version: 1,
1077
+ operation_id: input.operation_id,
1078
+ mode: 'shadow',
1079
+ adapter_version: ADAPTER_VERSION,
1080
+ client_version: input.client_version,
1081
+ observed_at: input.observed_at,
1082
+ session_key: String(input.session_key),
1083
+ instance_id: pseudonym(String(input.instance_id)),
1084
+ item_count: items.length,
1085
+ char_count: items.reduce((total, item) => total + item.chars, 0),
1086
+ applied: false,
1087
+ };
1088
+ await appendRecords(root, 'events', [
1089
+ {
1090
+ ...commonLog,
1091
+ stage: 'provider_request',
1092
+ category: 'request_attempted',
1093
+ },
1094
+ ]).catch(() => undefined);
1095
+ let response;
1096
+ try {
1097
+ response = await selectContextShadow(config.endpoint, config.token, request, 15_000);
1098
+ } catch (error) {
1099
+ const code = safeProviderErrorCode(error);
1100
+ await appendRecords(root, 'events', [
1101
+ {
1102
+ ...commonLog,
1103
+ stage: 'provider_response',
1104
+ category: providerFailureCategory(code),
1105
+ },
1106
+ ]).catch(() => undefined);
1107
+ return {
1108
+ schema_version: 1,
1109
+ accepted: false,
1110
+ action: 'preview',
1111
+ applied: false,
1112
+ error_code: code,
1113
+ };
1114
+ }
1115
+ const expectedProjectionHash = hash(projection);
1116
+ const expectedOptionsHash = hash(input.options);
1117
+ const responseDecisions = Array.isArray(response.decisions)
1118
+ ? /** @type {Record<string, unknown>[]} */ (response.decisions)
1119
+ : [];
1120
+ const createdAt = Date.parse(String(response.created_at));
1121
+ const expiresAt = Date.parse(String(response.expires_at));
1122
+ const responseNow = Date.now();
1123
+ const responseStats = object(response.stats)
1124
+ ? /** @type {Record<string, unknown>} */ (response.stats)
1125
+ : {};
1126
+ /** @param {unknown} value */
1127
+ const count = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
1128
+ /** @param {unknown} value */
1129
+ const nullableCount = (value) => value === null || count(value);
1130
+ const statsValid =
1131
+ count(responseStats.selected_count) &&
1132
+ count(responseStats.protected_count) &&
1133
+ count(responseStats.eligible_count) &&
1134
+ count(responseStats.batch_count) &&
1135
+ typeof responseStats.batch_count === 'number' &&
1136
+ responseStats.batch_count <= 4 &&
1137
+ nullableCount(responseStats.provider_input_tokens) &&
1138
+ nullableCount(responseStats.provider_output_tokens);
1139
+ const decisionsValid = responseDecisions.every((decision, index) => {
1140
+ if (!object(decision)) return false;
1141
+ const candidate = /** @type {Record<string, unknown>} */ (decision);
1142
+ const protectedItem = items[index]?.protected === true;
1143
+ const probability = candidate.probability;
1144
+ if (
1145
+ !noControlText(candidate.id, 128) ||
1146
+ typeof candidate.keep !== 'boolean' ||
1147
+ !['protected', 'relevant', 'below_threshold'].includes(String(candidate.reason))
1148
+ )
1149
+ return false;
1150
+ if (protectedItem)
1151
+ return candidate.keep === true && candidate.reason === 'protected' && probability === null;
1152
+ if (
1153
+ candidate.reason === 'protected' ||
1154
+ typeof probability !== 'number' ||
1155
+ !Number.isFinite(probability) ||
1156
+ probability < 0 ||
1157
+ probability > 1
1158
+ )
1159
+ return false;
1160
+ return candidate.keep === probability >= input.options.keep_threshold;
1161
+ });
1162
+ if (
1163
+ response.schema_version !== 2 ||
1164
+ !['planned', 'noop'].includes(String(response.status)) ||
1165
+ response.applied !== false ||
1166
+ response.persisted !== false ||
1167
+ response.request_id !== request.request_id ||
1168
+ response.session_ref !== request.session_ref ||
1169
+ response.revision !== request.revision ||
1170
+ response.projection_hash !== expectedProjectionHash ||
1171
+ response.options_hash !== expectedOptionsHash ||
1172
+ response.policy_version !== request.policy_version ||
1173
+ !Number.isFinite(createdAt) ||
1174
+ !Number.isFinite(expiresAt) ||
1175
+ createdAt > responseNow + 60_000 ||
1176
+ createdAt > expiresAt ||
1177
+ expiresAt <= responseNow ||
1178
+ expiresAt > createdAt + 60_000 ||
1179
+ (response.provider_model !== null &&
1180
+ (typeof response.provider_model !== 'string' || response.provider_model.length > 128)) ||
1181
+ !statsValid ||
1182
+ responseDecisions.length !== items.length ||
1183
+ !decisionsValid
1184
+ ) {
1185
+ await appendRecords(root, 'events', [
1186
+ { ...commonLog, stage: 'provider_response', category: 'invalid_response' },
1187
+ ]).catch(() => undefined);
1188
+ return {
1189
+ schema_version: 1,
1190
+ accepted: false,
1191
+ action: 'preview',
1192
+ applied: false,
1193
+ error_code: 'invalid_response',
1194
+ };
1195
+ }
1196
+ const batchCount = responseStats.batch_count;
1197
+ const providerConfirmed =
1198
+ typeof response.provider_model === 'string' &&
1199
+ response.provider_model.length > 0 &&
1200
+ typeof batchCount === 'number' &&
1201
+ Number.isSafeInteger(batchCount) &&
1202
+ batchCount > 0;
1203
+ const orderedIds = responseDecisions.map((decision) => String(decision.id));
1204
+ if (
1205
+ orderedIds.some((id, index) => id !== items[index]?.id) ||
1206
+ Number(responseStats.protected_count) !== items.filter((item) => item.protected).length ||
1207
+ Number(responseStats.eligible_count) !== items.filter((item) => !item.protected).length ||
1208
+ Number(responseStats.selected_count) !==
1209
+ responseDecisions.filter((decision) => decision.keep === true).length ||
1210
+ (response.status === 'planned') !==
1211
+ responseDecisions.some(
1212
+ (decision, index) => items[index]?.protected !== true && decision.keep === false,
1213
+ )
1214
+ ) {
1215
+ await appendRecords(root, 'events', [
1216
+ { ...commonLog, stage: 'provider_response', category: 'invalid_response' },
1217
+ ]).catch(() => undefined);
1218
+ return {
1219
+ schema_version: 1,
1220
+ accepted: false,
1221
+ action: 'preview',
1222
+ applied: false,
1223
+ error_code: 'invalid_response',
1224
+ };
1225
+ }
1226
+ await appendRecords(root, 'events', [
1227
+ {
1228
+ ...commonLog,
1229
+ stage: 'provider_response',
1230
+ category: providerConfirmed ? 'provider_decision' : 'response_obtained',
1231
+ provider_confirmed: providerConfirmed,
1232
+ },
1233
+ ]).catch(() => undefined);
1234
+ const decisions = responseDecisions.map((decision) => ({
1235
+ id: pseudonym(String(decision.id)),
1236
+ outcome:
1237
+ decision.reason === 'protected'
1238
+ ? 'protected'
1239
+ : decision.keep === true
1240
+ ? 'would_keep'
1241
+ : 'would_omit_result',
1242
+ }));
1243
+ await appendRecords(
1244
+ root,
1245
+ 'events',
1246
+ items.map((item, index) => ({
1247
+ schema_version: 1,
1248
+ operation_id: input.operation_id,
1249
+ mode: 'shadow',
1250
+ stage: 'provider_response',
1251
+ category: providerConfirmed ? 'provider_decision' : 'response_obtained',
1252
+ adapter_version: ADAPTER_VERSION,
1253
+ client_version: input.client_version,
1254
+ observed_at: input.observed_at,
1255
+ session_key: String(input.session_key),
1256
+ instance_id: pseudonym(String(input.instance_id)),
1257
+ item_id: pseudonym(String(item.id)),
1258
+ provider_confirmed: providerConfirmed,
1259
+ outcome: decisions[index].outcome,
1260
+ would_keep: decisions[index].outcome === 'would_keep',
1261
+ applied: false,
1262
+ chars: item.chars,
1263
+ })),
1264
+ );
1265
+ return { schema_version: 1, accepted: true, action: 'preview', applied: false, decisions };
1266
+ });
1267
+ }
1268
+
1269
+ /** @param {string[]} argv @param {string} inputText */
1270
+ export async function executeObservation(argv, inputText) {
1271
+ const { stateDir, configDigest } = parseArgs(argv);
1272
+ await assertOwnedDir(stateDir);
1273
+ if (Buffer.byteLength(inputText, 'utf8') > MAX_STDIN_BYTES) throw new Error('input_too_large');
1274
+ const input = validateInput(JSON.parse(inputText));
1275
+ const config = await currentConfig(stateDir, configDigest);
1276
+ return input.action === 'diagnostics'
1277
+ ? diagnostics(input, config, stateDir)
1278
+ : preview(input, config, stateDir);
1279
+ }
1280
+
1281
+ /** @param {string} stateDir @param {number} tail */
1282
+ export async function readDiagnostics(stateDir, tail = 20) {
1283
+ const root = await assertOwnedDir(stateDir);
1284
+ let directory;
1285
+ try {
1286
+ directory = await ownedDirectory(join(root, 'diagnostics', 'claude'), false);
1287
+ } catch (error) {
1288
+ if (
1289
+ (error instanceof Error && 'code' in error && error.code === 'ENOENT') ||
1290
+ (error instanceof Error && error.message === 'diagnostics_path_missing')
1291
+ )
1292
+ return [];
1293
+ throw error;
1294
+ }
1295
+ const files = (await readdir(directory))
1296
+ .filter((file) => /^events\.[012]\.jsonl$/u.test(file))
1297
+ .sort((left, right) => Number(right[7]) - Number(left[7]));
1298
+ const records = [];
1299
+ for (const file of files) {
1300
+ const path = join(directory, file);
1301
+ await ownedFile(path);
1302
+ const lines = (await readFile(path, 'utf8')).split('\n').filter(Boolean);
1303
+ for (const line of lines) {
1304
+ try {
1305
+ const record = JSON.parse(line);
1306
+ if (object(record) && isSafeLogRecord(record)) records.push(record);
1307
+ } catch {
1308
+ // Ignore only malformed owned lines; never surface their contents.
1309
+ }
1310
+ }
1311
+ }
1312
+ return records.slice(-Math.max(1, Math.min(100, tail)));
1313
+ }
1314
+
1315
+ async function main() {
1316
+ let input = '';
1317
+ for await (const chunk of process.stdin) {
1318
+ input += String(chunk);
1319
+ if (Buffer.byteLength(input, 'utf8') > MAX_STDIN_BYTES) throw new Error('input_too_large');
1320
+ }
1321
+ const result = await executeObservation(process.argv.slice(2), input);
1322
+ process.stdout.write(`${JSON.stringify(result)}\n`);
1323
+ }
1324
+
1325
+ if (process.argv[1] && resolve(process.argv[1]) === resolve(new URL(import.meta.url).pathname)) {
1326
+ main().catch((error) => {
1327
+ const code =
1328
+ error instanceof Error && SAFE_ERROR_CODES.has(error.message)
1329
+ ? error.message
1330
+ : 'helper_failed';
1331
+ process.stdout.write(
1332
+ `${JSON.stringify({ schema_version: 1, accepted: false, error_code: code })}\n`,
1333
+ );
1334
+ process.exitCode = 1;
1335
+ });
1336
+ }