返回 CodeWhale
pet-runtime-lifecycle.test.mjs
根目录 / pet / tests / pet-runtime-lifecycle.test.mjs
1 import test from 'node:test';
2 import assert from 'node:assert/strict';
3 import { importTrace } from '../dist/core/ingest.js';
4 import { observeRuntimeRequests } from '../dist/core/codewhale.js';
5 import { compilePetTelemetry } from '../dist/core/pet-telemetry.js';
6 import { buildPyramid } from '../dist/core/signal.js';
7
8 const epoch = Date.parse('2026-09-12T00:00:00Z');
9 const stamp = ms => new Date(epoch + ms).toISOString();
10 const row = (seq, event, ms, payload, turn_id = 'turn-a') => ({ seq, event, thread_id: 'fixture', turn_id, timestamp: stamp(ms), payload });
11 const read = rows => importTrace(JSON.stringify(rows), 'fixture', { privacy: 'metadata' })[0];
12
13 test('a late Runtime failure keeps the real operation interval and tears once at its receipt time', () => {
14 const trace = read([
15 row(1, 'item.started', 0, { item: { id: 'work', kind: 'tool_call', started_at: stamp(0), status: 'running' }, tool: 'bash' }),
16 row(2, 'item.completed', 2000, { item: { id: 'work', kind: 'tool_call', started_at: stamp(0), ended_at: stamp(1000), status: 'failed', detail: 'fixture-private-result' }, tool: 'bash' }),
17 ]);
18 const item = trace.events.find(e => e.id === 'work');
19 assert.equal(item.status, 'error'); assert.equal(item.endTime, 1000);
20 assert.equal(item.attributes['whalesong.error_onset_ms'], 2000);
21 assert.equal(trace.duration, 2000); assert.doesNotMatch(JSON.stringify(trace), /fixture-private-result/);
22 const tape = compilePetTelemetry(trace.events, 2400);
23 assert.equal(tape[0].channel, 'code'); assert.equal(tape[0].errors, 0);
24 assert.equal(tape[3].observed, 0);
25 assert.equal(tape[5].channel, 'error'); assert.equal(tape[5].observed, 1);
26 assert.equal(tape.reduce((sum, b) => sum + b.errors, 0), 1);
27 const level = buildPyramid(trace.events).levels[0], offset = 3 * level.length;
28 assert.equal(level.errors[offset], 0);
29 assert.equal(level.errors[offset + Math.floor(2000 / level.binMs)], 1);
30 });
31
32 test('Runtime approval and input identities retain waiting intervals through their matching terminal receipts', () => {
33 const trace = read([
34 row(1, 'approval.required', 0, { approval_id: 'shared', description: 'fixture-private-question' }),
35 row(2, 'user_input.required', 1000, { id: 'shared', request: { questions: ['fixture-private-question'] } }),
36 row(3, 'user_input.answered', 2000, { input_id: 'shared' }, 'different-turn'),
37 row(4, 'approval.decided', 4000, { approval_id: 'shared', decision: 'deny' }),
38 row(5, 'user_input.canceled', 8000, { input_id: 'shared' }),
39 ]);
40 const approval = trace.events.find(e => e.name === 'approval.required');
41 const input = trace.events.find(e => e.name === 'user_input.required');
42 assert.equal(approval.endTime, 4000); assert.equal(input.endTime, 8000);
43 assert.equal(approval.openEnded, false); assert.equal(input.openEnded, false);
44 const tape = compilePetTelemetry(trace.events, 8800);
45 assert.ok(tape.slice(0, 20).every(b => b.waiting && b.channel === 'human'));
46 assert.equal(tape[20].waiting, false); assert.equal(tape[20].observed, 0);
47 assert.equal(tape.reduce((sum, b) => sum + b.errors, 0), 0);
48 assert.doesNotMatch(JSON.stringify(trace), /fixture-private-question/);
49 });
50
51 test('automatic consent never becomes a human wait and turn completion closes only its own requests', () => {
52 const automatic = read([
53 row(1, 'approval.required', 0, { id: 'auto' }),
54 row(2, 'approval.decided', 500, { approval_id: 'auto', decision: 'allow', auto: true }),
55 ]);
56 assert.ok(compilePetTelemetry(automatic.events, 1200).every(b => !b.waiting && !b.observed));
57 const trace = read([
58 row(1, 'user_input.required', 0, { id: 'same' }),
59 row(2, 'user_input.required', 100, { id: 'same' }, 'turn-b'),
60 row(3, 'turn.completed', 2000, { turn: { status: 'completed' } }),
61 ]);
62 const requests = trace.events.filter(e => e.name === 'user_input.required');
63 assert.equal(requests[0].openEnded, false); assert.equal(requests[0].endTime, 2000);
64 assert.equal(requests[1].openEnded, true);
65 assert.throws(() => read([row(1, 'user_input.required', 0, {})]), /identity/);
66 });
67
68 test('only a healthy live observation extends a witnessed request; static imports and open tools stay unknown', () => {
69 const trace = read([
70 row(1, 'user_input.required', 0, { id: 'wait' }),
71 row(2, 'item.started', 100, { item: { id: 'work', kind: 'tool_call', status: 'running' }, tool: 'bash' }),
72 ]);
73 const snapshot = JSON.stringify(trace);
74 assert.equal(compilePetTelemetry(trace.events, 30_000)[70].observed, 0);
75 const live = observeRuntimeRequests(trace, epoch + 30_000);
76 const tape = compilePetTelemetry(live.events, live.duration);
77 assert.equal(tape[70].waiting, true); assert.equal(tape[70].activeMs[11], 400);
78 assert.equal(tape[70].activeMs[3], 0); assert.equal(JSON.stringify(trace), snapshot);
79 assert.throws(() => observeRuntimeRequests(trace, NaN), /horizon/);
80 });
81
82 test('a fixed recorder clock survives older source origins without replaying historical failures or shifting onsets', () => {
83 const event = { schemaVersion: 1, id: 'work', traceId: 't', startTime: 0, endTime: 1000,
84 agentId: 'parent', name: 'bash', category: 'code', status: 'error', attributes: { 'whalesong.error_onset_ms': 3000 } };
85 const tape = compilePetTelemetry([event], 3600, 0, 400);
86 const shifted = { ...event, startTime: 5000, endTime: 6000, attributes: { 'whalesong.error_onset_ms': 8000 } };
87 assert.deepEqual(compilePetTelemetry([shifted], 8600, 0, 5400), tape);
88 assert.equal(tape[6].errors, 1); assert.equal(tape[0].onsets[3], 0);
89 assert.equal(compilePetTelemetry([event], 4400, 0, 4000).reduce((sum, b) => sum + b.errors, 0), 0);
90 const normalized = read([row(1, 'item.completed', 3000, { item: { id: 'done', kind: 'tool_call', started_at: stamp(1000), ended_at: stamp(2000), status: 'failed' } })]);
91 assert.equal(normalized.originTime, stamp(1000));
92 assert.equal(normalized.events[0].attributes['whalesong.error_onset_ms'], 2000);
93 const generic = importTrace(JSON.stringify([shifted]), 'event-v1', { privacy: 'metadata' })[0];
94 assert.equal(generic.events[0].attributes['whalesong.error_onset_ms'], 3000);
95 assert.throws(() => compilePetTelemetry([{ ...event, attributes: { 'whalesong.error_onset_ms': -1 } }]), /failure observation/);
96 });
97
97 lines Plain Text