@skill-harness/adapters 0.15.0 → 0.17.0
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/dist/blind-intervention.d.ts +6 -0
- package/dist/blind-intervention.js +9 -2
- package/dist/evidence-archive.d.ts +8 -0
- package/dist/evidence-archive.js +21 -17
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/learning-journal.d.ts +2 -2
- package/dist/learning-journal.js +1 -1
- package/dist/learning-trust-setup.d.ts +64 -0
- package/dist/learning-trust-setup.js +55 -0
- package/dist/learning-workspace.d.ts +1084 -0
- package/dist/learning-workspace.js +705 -0
- package/dist/session-observation.d.ts +50 -0
- package/dist/session-observation.js +186 -0
- package/dist/session-retention.d.ts +48 -0
- package/dist/session-retention.js +121 -0
- package/dist/trust-lifecycle.d.ts +28 -4
- package/dist/trust-lifecycle.js +14 -5
- package/package.json +2 -2
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
type Row = Record<string, any>;
|
|
2
|
+
export interface SessionTimelineEntry {
|
|
3
|
+
line: number;
|
|
4
|
+
timestamp: string | null;
|
|
5
|
+
kind: string;
|
|
6
|
+
excerpt: string;
|
|
7
|
+
}
|
|
8
|
+
export interface SessionObservation {
|
|
9
|
+
version: 'pi-session-observation-v1';
|
|
10
|
+
sessionId: string;
|
|
11
|
+
cwd: string;
|
|
12
|
+
timeline: SessionTimelineEntry[];
|
|
13
|
+
toolCalls: Record<string, number>;
|
|
14
|
+
userMessages: number;
|
|
15
|
+
assistantMessages: number;
|
|
16
|
+
modelRecords: {
|
|
17
|
+
provider: string;
|
|
18
|
+
model: string;
|
|
19
|
+
messages: number;
|
|
20
|
+
}[];
|
|
21
|
+
usage: {
|
|
22
|
+
fields: Record<string, {
|
|
23
|
+
sum: number;
|
|
24
|
+
reportedMessages: number;
|
|
25
|
+
}>;
|
|
26
|
+
assistantMessages: number;
|
|
27
|
+
scope: 'this-transcript-only';
|
|
28
|
+
};
|
|
29
|
+
lastActivity: string | null;
|
|
30
|
+
lastAssistantStop: string | null;
|
|
31
|
+
gaps: string[];
|
|
32
|
+
}
|
|
33
|
+
export declare function sessionText(bytes: Uint8Array): string;
|
|
34
|
+
export declare function sessionRows(bytes: Uint8Array): Row[];
|
|
35
|
+
/** Chronological visible history, including abandoned branches. Never interprets prose as approval. */
|
|
36
|
+
export declare function observePiSession(bytes: Uint8Array): SessionObservation;
|
|
37
|
+
export interface SessionAttempt {
|
|
38
|
+
executionId: string;
|
|
39
|
+
parentExecutionId: string | null;
|
|
40
|
+
logicalChildId: string;
|
|
41
|
+
agent: string | null;
|
|
42
|
+
state: string;
|
|
43
|
+
pane: string | null;
|
|
44
|
+
startedAt: string | null;
|
|
45
|
+
endedAt: string | null;
|
|
46
|
+
runningSeconds: number | null;
|
|
47
|
+
}
|
|
48
|
+
export declare function observeSessionLedger(bytes: Uint8Array): SessionAttempt[];
|
|
49
|
+
export declare function sessionScope(observation: SessionObservation, sourceSha256: string): string;
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { learningHash } from './learning-journal.js';
|
|
2
|
+
import { normalizePiDaddyLedgerV3 } from './trajectory.js';
|
|
3
|
+
export function sessionText(bytes) {
|
|
4
|
+
if (bytes.byteLength > 8 * 1024 * 1024)
|
|
5
|
+
throw Error('session source exceeds 8 MiB');
|
|
6
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
7
|
+
}
|
|
8
|
+
export function sessionRows(bytes) {
|
|
9
|
+
const text = sessionText(bytes);
|
|
10
|
+
if (!text.endsWith('\n'))
|
|
11
|
+
throw Error('incomplete session/ledger line; retry after the writer settles');
|
|
12
|
+
const lines = text.slice(0, -1).split('\n');
|
|
13
|
+
if (lines.length > 4096)
|
|
14
|
+
throw Error('session source exceeds 4096 records');
|
|
15
|
+
return lines.map((line, i) => {
|
|
16
|
+
let row;
|
|
17
|
+
try {
|
|
18
|
+
row = JSON.parse(line);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
throw Error('invalid JSON record at line ' + (i + 1));
|
|
22
|
+
}
|
|
23
|
+
if (!row || typeof row !== 'object' || Array.isArray(row))
|
|
24
|
+
throw Error('object record required at line ' + (i + 1));
|
|
25
|
+
return row;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function value(v, max = 512) {
|
|
29
|
+
if (typeof v !== 'string' || !v.trim() || v.length > max)
|
|
30
|
+
throw Error('bounded session identifier required');
|
|
31
|
+
return v;
|
|
32
|
+
}
|
|
33
|
+
const stamp = (v) => typeof v === 'string' && Number.isFinite(Date.parse(v)) ? v : null;
|
|
34
|
+
const excerpt = (v) => v.length > 512 ? v.slice(0, 512) + ' … [excerpt; inspect retained source for complete text]' : v;
|
|
35
|
+
/** Chronological visible history, including abandoned branches. Never interprets prose as approval. */
|
|
36
|
+
export function observePiSession(bytes) {
|
|
37
|
+
const rows = sessionRows(bytes), header = rows[0];
|
|
38
|
+
if (header?.type !== 'session' || header.version !== 3)
|
|
39
|
+
throw Error('Pi session format version 3 required');
|
|
40
|
+
const sessionId = value(header.id), cwd = value(header.cwd, 4096), ids = new Set();
|
|
41
|
+
const timeline = [], tools = new Map(), models = new Map();
|
|
42
|
+
const fields = {};
|
|
43
|
+
let userMessages = 0, assistantMessages = 0, lastAssistantStop = null, lastActivity = null, opaque = 0;
|
|
44
|
+
const add = (i, row, kind, text) => timeline.push({ line: i + 1, timestamp: stamp(row.timestamp), kind, excerpt: excerpt(text) });
|
|
45
|
+
rows.forEach((row, i) => {
|
|
46
|
+
if (i && row.type === 'session')
|
|
47
|
+
throw Error('multiple session headers');
|
|
48
|
+
if (row.id !== undefined) {
|
|
49
|
+
const id = value(row.id);
|
|
50
|
+
if (ids.has(id))
|
|
51
|
+
throw Error('duplicate session record id');
|
|
52
|
+
ids.add(id);
|
|
53
|
+
}
|
|
54
|
+
lastActivity = stamp(row.timestamp) ?? lastActivity;
|
|
55
|
+
if (row.type === 'model_change') {
|
|
56
|
+
add(i, row, 'model-setting', [row.provider, row.modelId].filter(x => typeof x === 'string').join('/'));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (row.type === 'thinking_level_change') {
|
|
60
|
+
add(i, row, 'thinking-setting', String(row.thinkingLevel ?? 'unknown'));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (row.type !== 'message')
|
|
64
|
+
return;
|
|
65
|
+
const m = row.message;
|
|
66
|
+
if (!m || typeof m !== 'object' || Array.isArray(m))
|
|
67
|
+
throw Error('invalid session message');
|
|
68
|
+
const content = typeof m.content === 'string' ? [{ type: 'text', text: m.content }] : m.content;
|
|
69
|
+
if (!Array.isArray(content))
|
|
70
|
+
throw Error('invalid session message content');
|
|
71
|
+
const text = content.filter(c => c?.type === 'text' && typeof c.text === 'string').map(c => c.text).join('\n');
|
|
72
|
+
if (m.role === 'user') {
|
|
73
|
+
userMessages++;
|
|
74
|
+
add(i, row, 'user-message', text);
|
|
75
|
+
}
|
|
76
|
+
else if (m.role === 'assistant') {
|
|
77
|
+
assistantMessages++;
|
|
78
|
+
lastAssistantStop = typeof m.stopReason === 'string' ? m.stopReason : null;
|
|
79
|
+
const provider = typeof m.provider === 'string' ? m.provider : 'unknown', model = typeof m.model === 'string' ? m.model : 'unknown';
|
|
80
|
+
const key = JSON.stringify([provider, model]), record = models.get(key) ?? { provider, model, messages: 0 };
|
|
81
|
+
record.messages++;
|
|
82
|
+
models.set(key, record);
|
|
83
|
+
if (text)
|
|
84
|
+
add(i, row, 'assistant-message', text);
|
|
85
|
+
for (const field of ['input', 'output', 'cacheRead', 'cacheWrite', 'totalTokens']) {
|
|
86
|
+
const n = m.usage?.[field];
|
|
87
|
+
if (n === undefined)
|
|
88
|
+
continue;
|
|
89
|
+
if (!Number.isSafeInteger(n) || n < 0)
|
|
90
|
+
throw Error('invalid provider-reported usage');
|
|
91
|
+
const f = fields[field] ?? { sum: 0, reportedMessages: 0 };
|
|
92
|
+
f.sum += n;
|
|
93
|
+
f.reportedMessages++;
|
|
94
|
+
if (!Number.isSafeInteger(f.sum))
|
|
95
|
+
throw Error('usage sum overflow');
|
|
96
|
+
fields[field] = f;
|
|
97
|
+
}
|
|
98
|
+
for (const c of content)
|
|
99
|
+
if (c?.type === 'toolCall') {
|
|
100
|
+
const name = value(c.name, 128);
|
|
101
|
+
tools.set(name, (tools.get(name) ?? 0) + 1);
|
|
102
|
+
add(i, row, 'tool-call', name);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else if (m.role === 'toolResult')
|
|
106
|
+
add(i, row, 'tool-result', String(m.toolName ?? 'unknown') + (m.isError === true ? ' — tool reported error' : ' — result recorded; success not inferred'));
|
|
107
|
+
else
|
|
108
|
+
opaque++;
|
|
109
|
+
});
|
|
110
|
+
const gaps = [
|
|
111
|
+
'Retrospective chronological transcript; branch selection and live continuity are not established.',
|
|
112
|
+
'Messages and tool results are evidence to inspect, not independently verified decisions, tests or acceptance.',
|
|
113
|
+
'Only explicit inputs are captured. Source revision, initial workspace and external user feedback may be missing.',
|
|
114
|
+
'Usage is provider-reported repeated-request usage, not unique context, billing or complete parent/child accounting.',
|
|
115
|
+
];
|
|
116
|
+
if (opaque)
|
|
117
|
+
gaps.push(opaque + ' message(s) with other roles omitted from the timeline; retained source is complete.');
|
|
118
|
+
if (!assistantMessages || Object.values(fields).some(f => f.reportedMessages < assistantMessages) || Object.keys(fields).length < 5)
|
|
119
|
+
gaps.push('Usage fields are missing; sums cover only reported messages.');
|
|
120
|
+
if (lastAssistantStop !== 'stop')
|
|
121
|
+
gaps.push('No final assistant stop observed; this snapshot may represent unfinished work.');
|
|
122
|
+
return { version: 'pi-session-observation-v1', sessionId, cwd, timeline, toolCalls: Object.fromEntries(tools), userMessages, assistantMessages, modelRecords: [...models.values()], usage: { fields, assistantMessages, scope: 'this-transcript-only' }, lastActivity, lastAssistantStop, gaps };
|
|
123
|
+
}
|
|
124
|
+
export function observeSessionLedger(bytes) {
|
|
125
|
+
const rows = sessionRows(bytes);
|
|
126
|
+
normalizePiDaddyLedgerV3(sessionText(bytes));
|
|
127
|
+
const attempts = new Map(), decisions = new Set(), lifecycles = new Set(), deadlines = new Map(), displayed = new Set();
|
|
128
|
+
for (const row of rows) {
|
|
129
|
+
if (!['capability_decision', 'child_lifecycle', 'workspace_lease'].includes(row.event) || String(row.childId).startsWith('check:'))
|
|
130
|
+
continue;
|
|
131
|
+
const id = value(row.executionId), old = attempts.get(id);
|
|
132
|
+
if (old && (old.parentExecutionId !== row.parentExecutionId || old.logicalChildId !== row.childId))
|
|
133
|
+
throw Error('execution identity changed');
|
|
134
|
+
const a = old ?? { executionId: id, parentExecutionId: row.parentExecutionId, logicalChildId: row.childId, agent: null, state: 'unknown', pane: null, startedAt: null, endedAt: null, runningSeconds: null };
|
|
135
|
+
if (row.event === 'workspace_lease') {
|
|
136
|
+
attempts.set(id, a);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
displayed.add(id);
|
|
140
|
+
if (row.event === 'capability_decision') {
|
|
141
|
+
if (decisions.has(id))
|
|
142
|
+
throw Error('duplicate capability decision for one execution');
|
|
143
|
+
decisions.add(id);
|
|
144
|
+
a.agent = row.agentType;
|
|
145
|
+
a.state = row.blocked ? 'refused' : a.state === 'unknown' ? 'authorised' : a.state;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
if (a.endedAt)
|
|
149
|
+
throw Error('lifecycle after terminal execution');
|
|
150
|
+
if (lifecycles.has(id) && row.state === 'starting')
|
|
151
|
+
throw Error('duplicate starting lifecycle event');
|
|
152
|
+
if (deadlines.has(id) && stamp(row.deadlineAt) && deadlines.get(id) !== row.deadlineAt)
|
|
153
|
+
throw Error('lifecycle deadline changed');
|
|
154
|
+
if (stamp(row.deadlineAt))
|
|
155
|
+
deadlines.set(id, row.deadlineAt);
|
|
156
|
+
lifecycles.add(id);
|
|
157
|
+
a.state = row.state;
|
|
158
|
+
a.pane = row.herdrPaneId ?? a.pane;
|
|
159
|
+
if (row.state === 'running')
|
|
160
|
+
a.startedAt = a.startedAt ?? row.ts;
|
|
161
|
+
if (['completed', 'failed'].includes(row.state))
|
|
162
|
+
a.endedAt = row.ts;
|
|
163
|
+
}
|
|
164
|
+
if (a.startedAt && a.endedAt) {
|
|
165
|
+
a.runningSeconds = (Date.parse(a.endedAt) - Date.parse(a.startedAt)) / 1000;
|
|
166
|
+
if (a.runningSeconds < 0)
|
|
167
|
+
throw Error('negative execution duration');
|
|
168
|
+
}
|
|
169
|
+
attempts.set(id, a);
|
|
170
|
+
}
|
|
171
|
+
for (const a of attempts.values()) {
|
|
172
|
+
const seen = new Set();
|
|
173
|
+
let cursor = a;
|
|
174
|
+
while (cursor) {
|
|
175
|
+
if (seen.has(cursor.executionId))
|
|
176
|
+
throw Error('execution parent cycle');
|
|
177
|
+
seen.add(cursor.executionId);
|
|
178
|
+
cursor = cursor.parentExecutionId ? attempts.get(cursor.parentExecutionId) : undefined;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return [...attempts.values()].filter(a => displayed.has(a.executionId));
|
|
182
|
+
}
|
|
183
|
+
export function sessionScope(observation, sourceSha256) {
|
|
184
|
+
return learningHash({ kind: 'retrospective-pi-session', sessionId: observation.sessionId, sourceSha256 });
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=session-observation.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { observeSessionLedger, type SessionObservation } from './session-observation.js';
|
|
2
|
+
export interface SessionSourceSelection {
|
|
3
|
+
kind: 'parent' | 'child' | 'ledger' | 'feedback' | 'artifact';
|
|
4
|
+
path: string;
|
|
5
|
+
executionId?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface Source {
|
|
8
|
+
kind: SessionSourceSelection['kind'];
|
|
9
|
+
path: string;
|
|
10
|
+
sha256: string;
|
|
11
|
+
bytes: number;
|
|
12
|
+
executionId: string | null;
|
|
13
|
+
}
|
|
14
|
+
export interface SessionImportPreview {
|
|
15
|
+
version: 'session-import-preview-v1';
|
|
16
|
+
digest: string;
|
|
17
|
+
sources: Source[];
|
|
18
|
+
parent: SessionObservation;
|
|
19
|
+
children: {
|
|
20
|
+
source: number;
|
|
21
|
+
executionId: string | null;
|
|
22
|
+
session: SessionObservation;
|
|
23
|
+
}[];
|
|
24
|
+
attempts: ReturnType<typeof observeSessionLedger>;
|
|
25
|
+
gaps: string[];
|
|
26
|
+
acceptance: 'not-assessed';
|
|
27
|
+
}
|
|
28
|
+
/** Explicit local selection only. Does not scan session directories, follow links, or contact workers. */
|
|
29
|
+
export declare function prepareSessionImport(selections: SessionSourceSelection[]): SessionImportPreview;
|
|
30
|
+
export declare function retainSessionImport(root: string, preview: SessionImportPreview, expectedDigest: string): string;
|
|
31
|
+
export declare function readRetainedSession(root: string, manifestId: string): {
|
|
32
|
+
acceptance: "not-assessed";
|
|
33
|
+
parent: SessionObservation;
|
|
34
|
+
children: {
|
|
35
|
+
source: number;
|
|
36
|
+
executionId: string | null;
|
|
37
|
+
session: SessionObservation;
|
|
38
|
+
}[];
|
|
39
|
+
attempts: import("./session-observation.js").SessionAttempt[];
|
|
40
|
+
gaps: string[];
|
|
41
|
+
version: "retrospective-session-v1";
|
|
42
|
+
manifestId: string;
|
|
43
|
+
selectionDigest: string;
|
|
44
|
+
sources: (Source & {
|
|
45
|
+
manifestId: string;
|
|
46
|
+
})[];
|
|
47
|
+
};
|
|
48
|
+
export declare function readRetainedSessionSource(root: string, manifestId: string, index: number): Buffer;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { learningFile, learningHash, learningCopy } from './learning-journal.js';
|
|
4
|
+
import { retainArchiveSource, readArchiveSource } from './evidence-archive.js';
|
|
5
|
+
import { observePiSession, observeSessionLedger, sessionText } from './session-observation.js';
|
|
6
|
+
const captured = new WeakMap();
|
|
7
|
+
const sha = (bytes) => createHash('sha256').update(bytes).digest('hex');
|
|
8
|
+
function checkedSelections(input) {
|
|
9
|
+
if (!Array.isArray(input) || input.length < 1 || input.length > 16)
|
|
10
|
+
throw Error('select 1–16 explicit session sources');
|
|
11
|
+
const seen = new Set(), executions = new Set();
|
|
12
|
+
let parents = 0, ledgers = 0;
|
|
13
|
+
for (const s of input) {
|
|
14
|
+
if (!s || typeof s !== 'object' || Object.keys(s).some(k => !['kind', 'path', 'executionId'].includes(k)))
|
|
15
|
+
throw Error('closed source selection required');
|
|
16
|
+
if (!['parent', 'child', 'ledger', 'feedback', 'artifact'].includes(s.kind) || typeof s.path !== 'string' || resolve(s.path) !== s.path)
|
|
17
|
+
throw Error('explicit absolute source path required');
|
|
18
|
+
if (seen.has(s.path))
|
|
19
|
+
throw Error('duplicate session source');
|
|
20
|
+
seen.add(s.path);
|
|
21
|
+
if (s.kind === 'parent')
|
|
22
|
+
parents++;
|
|
23
|
+
if (s.kind === 'ledger')
|
|
24
|
+
ledgers++;
|
|
25
|
+
if (s.executionId !== undefined) {
|
|
26
|
+
if (s.kind !== 'child' || !/^exec:[0-9a-f-]{36}$/i.test(s.executionId) || executions.has(s.executionId))
|
|
27
|
+
throw Error('unique child execution id required');
|
|
28
|
+
executions.add(s.executionId);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (parents !== 1 || ledgers > 1)
|
|
32
|
+
throw Error('exactly one parent and at most one ledger required');
|
|
33
|
+
}
|
|
34
|
+
function projectSession(selections, buffers) {
|
|
35
|
+
const parent = observePiSession(buffers[selections.findIndex(s => s.kind === 'parent')]);
|
|
36
|
+
const children = selections.flatMap((s, i) => s.kind === 'child' ? [{ source: i, executionId: s.executionId ?? null, session: observePiSession(buffers[i]) }] : []);
|
|
37
|
+
if (new Set([parent.sessionId, ...children.map(c => c.session.sessionId)]).size !== children.length + 1)
|
|
38
|
+
throw Error('duplicate parent/child session id');
|
|
39
|
+
const ledger = selections.findIndex(s => s.kind === 'ledger'), attempts = ledger < 0 ? [] : observeSessionLedger(buffers[ledger]);
|
|
40
|
+
for (const child of children)
|
|
41
|
+
if (child.executionId && !attempts.some(a => a.executionId === child.executionId))
|
|
42
|
+
throw Error('selected child execution absent from ledger');
|
|
43
|
+
const gaps = [...parent.gaps, 'No quality, acceptance, calibrated learning or adoption is inferred from this import.'];
|
|
44
|
+
for (const child of children)
|
|
45
|
+
gaps.push(...child.session.gaps.map(g => 'Child source ' + (child.source + 1) + ' (' + child.session.sessionId + '): ' + g));
|
|
46
|
+
if (ledger < 0)
|
|
47
|
+
gaps.push('No delegation ledger selected.');
|
|
48
|
+
else
|
|
49
|
+
gaps.push('Ledger/session association is operator-selected, not authenticated; ledger may include other sessions.');
|
|
50
|
+
if (attempts.length > children.filter(c => c.executionId).length)
|
|
51
|
+
gaps.push('Child transcript coverage incomplete; no combined token total is claimed.');
|
|
52
|
+
if (children.length)
|
|
53
|
+
gaps.push('Child execution mappings are explicit operator declarations, not proof of parentage. Each transcript has separate usage.');
|
|
54
|
+
if (!selections.some(s => s.kind === 'feedback'))
|
|
55
|
+
gaps.push('External feedback not supplied; user messages remain available for review.');
|
|
56
|
+
if (!selections.some(s => s.kind === 'artifact'))
|
|
57
|
+
gaps.push('No final artifact or source snapshot supplied.');
|
|
58
|
+
return { parent, children, attempts, gaps };
|
|
59
|
+
}
|
|
60
|
+
/** Explicit local selection only. Does not scan session directories, follow links, or contact workers. */
|
|
61
|
+
export function prepareSessionImport(selections) {
|
|
62
|
+
checkedSelections(selections);
|
|
63
|
+
const buffers = selections.map(s => learningFile(s.path, 8 * 1024 * 1024));
|
|
64
|
+
if (buffers.reduce((n, b) => n + b.length, 0) > 32 * 1024 * 1024)
|
|
65
|
+
throw Error('session selection exceeds 32 MiB');
|
|
66
|
+
const sources = selections.map((s, i) => ({ ...s, executionId: s.executionId ?? null, sha256: sha(buffers[i]), bytes: buffers[i].length }));
|
|
67
|
+
const { parent, children, attempts, gaps } = projectSession(selections, buffers);
|
|
68
|
+
const digest = learningHash({ version: 'session-selection-v1', sources });
|
|
69
|
+
const preview = { version: 'session-import-preview-v1', digest, sources, parent, children, attempts, gaps, acceptance: 'not-assessed' };
|
|
70
|
+
captured.set(preview, { preview: learningCopy(preview), buffers });
|
|
71
|
+
return preview;
|
|
72
|
+
}
|
|
73
|
+
export function retainSessionImport(root, preview, expectedDigest) {
|
|
74
|
+
const c = captured.get(preview);
|
|
75
|
+
if (!c || learningHash(c.preview) !== learningHash(preview) || expectedDigest !== c.preview.digest)
|
|
76
|
+
throw Error('exact preview digest required; changed or forged session import');
|
|
77
|
+
for (const s of c.preview.sources)
|
|
78
|
+
if (sha(learningFile(s.path, 8 * 1024 * 1024)) !== s.sha256)
|
|
79
|
+
throw Error('session source changed since preview; prepare again');
|
|
80
|
+
const sources = c.preview.sources.map((s, i) => {
|
|
81
|
+
const parser = s.kind === 'parent' || s.kind === 'child' ? 'pi-session-jsonl' : s.kind === 'ledger' ? 'pi-daddy-session-ledger' : 'session-evidence';
|
|
82
|
+
const r = retainArchiveSource(root, { sourceId: 'session-' + s.sha256, parser: { id: parser, version: '1' }, retention: 'exact', bytes: c.buffers[i] });
|
|
83
|
+
return { ...s, manifestId: r.manifestId };
|
|
84
|
+
});
|
|
85
|
+
const record = { version: 'retrospective-session-v1', selectionDigest: preview.digest, sources };
|
|
86
|
+
return retainArchiveSource(root, { sourceId: 'retrospective-' + preview.digest, parser: { id: 'retrospective-session', version: '1' }, retention: 'exact', bytes: Buffer.from(JSON.stringify(record)) }).manifestId;
|
|
87
|
+
}
|
|
88
|
+
export function readRetainedSession(root, manifestId) {
|
|
89
|
+
const selected = readArchiveSource(root, manifestId);
|
|
90
|
+
if (selected.status !== 'available' || selected.reference.retention !== 'exact' || selected.reference.parser.id !== 'retrospective-session' || selected.reference.parser.version !== '1')
|
|
91
|
+
throw Error('retained session unavailable or unsupported');
|
|
92
|
+
const record = JSON.parse(sessionText(selected.bytes));
|
|
93
|
+
if (!record || record.version !== 'retrospective-session-v1' || Object.keys(record).sort().join() !== 'selectionDigest,sources,version' || !Array.isArray(record.sources))
|
|
94
|
+
throw Error('invalid retained session');
|
|
95
|
+
checkedSelections(record.sources.map(s => ({ kind: s.kind, path: s.path, ...(s.executionId === null ? {} : { executionId: s.executionId }) })));
|
|
96
|
+
const sources = record.sources.map(s => {
|
|
97
|
+
if (Object.keys(s).sort().join() !== 'bytes,executionId,kind,manifestId,path,sha256')
|
|
98
|
+
throw Error('invalid retained session source');
|
|
99
|
+
const r = readArchiveSource(root, s.manifestId);
|
|
100
|
+
if (r.status !== 'available' || r.reference.retention !== 'exact' || r.reference.sha256 !== s.sha256 || r.bytes.length !== s.bytes)
|
|
101
|
+
throw Error('retained session source missing or changed');
|
|
102
|
+
return { selection: s, bytes: r.bytes };
|
|
103
|
+
});
|
|
104
|
+
if (sources.reduce((n, s) => n + s.bytes.length, 0) > 32 * 1024 * 1024)
|
|
105
|
+
throw Error('session selection exceeds 32 MiB');
|
|
106
|
+
const plain = record.sources.map(({ manifestId, ...s }) => s);
|
|
107
|
+
if (learningHash({ version: 'session-selection-v1', sources: plain }) !== record.selectionDigest)
|
|
108
|
+
throw Error('retained session selection mismatch');
|
|
109
|
+
const projection = projectSession(record.sources.map(s => ({ kind: s.kind, path: s.path, ...(s.executionId === null ? {} : { executionId: s.executionId }) })), sources.map(s => s.bytes));
|
|
110
|
+
return { version: record.version, manifestId, selectionDigest: record.selectionDigest, sources: record.sources, ...projection, acceptance: 'not-assessed' };
|
|
111
|
+
}
|
|
112
|
+
export function readRetainedSessionSource(root, manifestId, index) {
|
|
113
|
+
const session = readRetainedSession(root, manifestId), source = session.sources[index];
|
|
114
|
+
if (!Number.isSafeInteger(index) || !source)
|
|
115
|
+
throw Error('select a listed session source');
|
|
116
|
+
const result = readArchiveSource(root, source.manifestId);
|
|
117
|
+
if (result.status !== 'available')
|
|
118
|
+
throw Error('session source unavailable');
|
|
119
|
+
return result.bytes;
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=session-retention.js.map
|
|
@@ -51,6 +51,18 @@ export declare function createTrustLifecycle(directory: string, input: TrustLife
|
|
|
51
51
|
tip: string;
|
|
52
52
|
grantExpansion: false;
|
|
53
53
|
};
|
|
54
|
+
configuration: () => TrustLifecycleInput;
|
|
55
|
+
previewExposure: (now: number) => {
|
|
56
|
+
mode: "silent" | "ask" | "retire";
|
|
57
|
+
reason: string;
|
|
58
|
+
advisoryOnly: true;
|
|
59
|
+
} | {
|
|
60
|
+
mode: "retire";
|
|
61
|
+
reason: string;
|
|
62
|
+
} | {
|
|
63
|
+
mode: "silent";
|
|
64
|
+
reason: string;
|
|
65
|
+
};
|
|
54
66
|
history: () => import("./learning-journal.js").LearningEvent[];
|
|
55
67
|
previewCaseOutcome: (predictionId: string, reference: LearningCaseReference) => {
|
|
56
68
|
outcome: TrustReferenceOutcome;
|
|
@@ -58,10 +70,10 @@ export declare function createTrustLifecycle(directory: string, input: TrustLife
|
|
|
58
70
|
digest: string;
|
|
59
71
|
tip: string;
|
|
60
72
|
};
|
|
61
|
-
linkCaseOutcome(predictionId: string, reference: LearningCaseReference, authorizedDigests: readonly string[]): void;
|
|
73
|
+
linkCaseOutcome(predictionId: string, reference: LearningCaseReference, authorizedDigests: readonly string[], referenceAuthor?: string): void;
|
|
62
74
|
predict(p: CalibrationPrediction): void;
|
|
63
75
|
correct(prior: string, p: CalibrationPrediction, reason: string): void;
|
|
64
|
-
outcome(value: TrustReferenceOutcome, authorizedReferenceDigests: readonly string[]): void;
|
|
76
|
+
outcome(value: TrustReferenceOutcome, authorizedReferenceDigests: readonly string[], referenceAuthor?: string): void;
|
|
65
77
|
expose(id: string, now: number): {
|
|
66
78
|
replayed: boolean;
|
|
67
79
|
mode: "ask" | "silent" | "retire";
|
|
@@ -104,6 +116,18 @@ export declare function openTrustLifecycle(directory: string): {
|
|
|
104
116
|
tip: string;
|
|
105
117
|
grantExpansion: false;
|
|
106
118
|
};
|
|
119
|
+
configuration: () => TrustLifecycleInput;
|
|
120
|
+
previewExposure: (now: number) => {
|
|
121
|
+
mode: "silent" | "ask" | "retire";
|
|
122
|
+
reason: string;
|
|
123
|
+
advisoryOnly: true;
|
|
124
|
+
} | {
|
|
125
|
+
mode: "retire";
|
|
126
|
+
reason: string;
|
|
127
|
+
} | {
|
|
128
|
+
mode: "silent";
|
|
129
|
+
reason: string;
|
|
130
|
+
};
|
|
107
131
|
history: () => import("./learning-journal.js").LearningEvent[];
|
|
108
132
|
previewCaseOutcome: (predictionId: string, reference: LearningCaseReference) => {
|
|
109
133
|
outcome: TrustReferenceOutcome;
|
|
@@ -111,10 +135,10 @@ export declare function openTrustLifecycle(directory: string): {
|
|
|
111
135
|
digest: string;
|
|
112
136
|
tip: string;
|
|
113
137
|
};
|
|
114
|
-
linkCaseOutcome(predictionId: string, reference: LearningCaseReference, authorizedDigests: readonly string[]): void;
|
|
138
|
+
linkCaseOutcome(predictionId: string, reference: LearningCaseReference, authorizedDigests: readonly string[], referenceAuthor?: string): void;
|
|
115
139
|
predict(p: CalibrationPrediction): void;
|
|
116
140
|
correct(prior: string, p: CalibrationPrediction, reason: string): void;
|
|
117
|
-
outcome(value: TrustReferenceOutcome, authorizedReferenceDigests: readonly string[]): void;
|
|
141
|
+
outcome(value: TrustReferenceOutcome, authorizedReferenceDigests: readonly string[], referenceAuthor?: string): void;
|
|
118
142
|
expose(id: string, now: number): {
|
|
119
143
|
replayed: boolean;
|
|
120
144
|
mode: "ask" | "silent" | "retire";
|
package/dist/trust-lifecycle.js
CHANGED
|
@@ -136,8 +136,15 @@ export function openTrustLifecycle(directory) {
|
|
|
136
136
|
const outcome = { kind: 'prediction', targetId: p.id, value: resolved.current.disposition === 'confirmed_defect', evidenceManifestId: ref.manifestId, referenceId: ref.decisionId };
|
|
137
137
|
return { outcome, caseReference: ref, digest: learningHash({ outcome, caseReference: ref }), tip: s.tip };
|
|
138
138
|
};
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
const previewExposure = (now) => {
|
|
140
|
+
const view = inspect(now), policy = input.exposure ? { ...input.exposure, attentionRemaining: Math.max(0, input.exposure.attentionRemaining - view.attentionUsed) } : null;
|
|
141
|
+
const report = view.calibration.reports.find(r => policy && r.kind === policy.kind && r.split === policy.split);
|
|
142
|
+
return view.retired ? { mode: 'retire', reason: 'previously-retired' } : report ? recommendExposure(report, policy, now) : { mode: 'silent', reason: policy ? 'insufficient-independent-evidence' : 'policy-unavailable' };
|
|
143
|
+
};
|
|
144
|
+
return { inspect, configuration: () => learningCopy(input), previewExposure, history: () => journal.read(), previewCaseOutcome,
|
|
145
|
+
linkCaseOutcome(predictionId, reference, authorizedDigests, referenceAuthor) {
|
|
146
|
+
if (referenceAuthor !== undefined && !text(referenceAuthor))
|
|
147
|
+
throw Error('explicit reference author required');
|
|
141
148
|
const preview = previewCaseOutcome(predictionId, reference);
|
|
142
149
|
if (!authorizedDigests.includes(preview.digest))
|
|
143
150
|
throw Error('independent exact reference authority required');
|
|
@@ -150,7 +157,7 @@ export function openTrustLifecycle(directory) {
|
|
|
150
157
|
throw Error('reference identity conflict');
|
|
151
158
|
return;
|
|
152
159
|
}
|
|
153
|
-
journal.append(s.tip, { type: 'outcome', outcome: preview.outcome, caseReference: preview.caseReference, authorityDigest: preview.digest });
|
|
160
|
+
journal.append(s.tip, { type: 'outcome', outcome: preview.outcome, caseReference: preview.caseReference, authorityDigest: preview.digest, ...(referenceAuthor === undefined ? {} : { referenceAuthor }) });
|
|
154
161
|
},
|
|
155
162
|
predict(p) { const safe = prediction(p), s = replay(); if (s.allIds.has(safe.id)) {
|
|
156
163
|
if (learningHash(s.predictions.get(safe.id) ?? null) !== learningHash(safe))
|
|
@@ -159,7 +166,9 @@ export function openTrustLifecycle(directory) {
|
|
|
159
166
|
} journal.append(s.tip, { type: 'prediction', prediction: safe }); },
|
|
160
167
|
correct(prior, p, reason) { const safe = prediction(p), s = replay(), old = s.predictions.get(prior); if (!old || old.incidentId !== safe.incidentId || s.allIds.has(safe.id) || !text(reason))
|
|
161
168
|
throw Error('stale or invalid prediction correction'); journal.append(s.tip, { type: 'correction', prior, prediction: safe, reason }); },
|
|
162
|
-
outcome(value, authorizedReferenceDigests) {
|
|
169
|
+
outcome(value, authorizedReferenceDigests, referenceAuthor) {
|
|
170
|
+
if (referenceAuthor !== undefined && !text(referenceAuthor))
|
|
171
|
+
throw Error('explicit reference author required');
|
|
163
172
|
const r = learningCopy(value);
|
|
164
173
|
if (Object.keys(r).sort().join() !== 'evidenceManifestId,kind,referenceId,targetId,value' || !['prediction', 'unflagged'].includes(r.kind) || !text(r.targetId) || !text(r.referenceId) || typeof r.value !== 'boolean')
|
|
165
174
|
throw Error('invalid reference outcome');
|
|
@@ -176,7 +185,7 @@ export function openTrustLifecycle(directory) {
|
|
|
176
185
|
throw Error('reference identity conflict');
|
|
177
186
|
return;
|
|
178
187
|
}
|
|
179
|
-
journal.append(s.tip, { type: 'outcome', outcome: r, authorityDigest: digest });
|
|
188
|
+
journal.append(s.tip, { type: 'outcome', outcome: r, authorityDigest: digest, ...(referenceAuthor === undefined ? {} : { referenceAuthor }) });
|
|
180
189
|
},
|
|
181
190
|
expose(id, now) {
|
|
182
191
|
if (!text(id))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skill-harness/adapters",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "skill-harness harness adapters — pi runner + claude-code judge routing (internal API)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"prepack": "node ../../scripts/release-pack.mjs --guard"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@skill-harness/core": "0.
|
|
43
|
+
"@skill-harness/core": "0.17.0",
|
|
44
44
|
"typebox": "1.3.4"
|
|
45
45
|
}
|
|
46
46
|
}
|