@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.
@@ -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";
@@ -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
- return { inspect, history: () => journal.read(), previewCaseOutcome,
140
- linkCaseOutcome(predictionId, reference, authorizedDigests) {
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.15.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.15.0",
43
+ "@skill-harness/core": "0.17.0",
44
44
  "typebox": "1.3.4"
45
45
  }
46
46
  }