@skill-harness/adapters 0.14.0 → 0.16.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/archived-work.d.ts +1 -0
- package/dist/archived-work.js +16 -2
- 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 +5 -0
- package/dist/index.js +5 -0
- package/dist/learning-journal.d.ts +2 -2
- package/dist/learning-journal.js +1 -1
- package/dist/learning-lifecycle.d.ts +28 -0
- package/dist/learning-lifecycle.js +66 -0
- package/dist/learning-trust-setup.d.ts +64 -0
- package/dist/learning-trust-setup.js +55 -0
- package/dist/learning-workspace.d.ts +930 -0
- package/dist/learning-workspace.js +663 -0
- package/dist/trust-lifecycle.d.ts +28 -4
- package/dist/trust-lifecycle.js +14 -5
- package/package.json +2 -2
package/dist/archived-work.d.ts
CHANGED
|
@@ -36,6 +36,7 @@ export declare function captureArchivedWorkSignals(root: string, manifestId: str
|
|
|
36
36
|
workSha256: string;
|
|
37
37
|
producerCommit: string;
|
|
38
38
|
projectionManifestId: string;
|
|
39
|
+
runtimeFactsManifestId: string;
|
|
39
40
|
observationId: string;
|
|
40
41
|
caseBatchId: string;
|
|
41
42
|
candidateIds: string[];
|
package/dist/archived-work.js
CHANGED
|
@@ -36,7 +36,7 @@ export function captureArchivedWorkSignals(root, manifestId, context, suppliedFa
|
|
|
36
36
|
const read = readArchivedWork(root, manifestId, context);
|
|
37
37
|
if (read.state !== "available" || read.projection.errors.length || read.projection.scopeState !== "valid" || !read.projection.selectedSnapshot || !read.projection.runtime)
|
|
38
38
|
throw new Error("work source/selection incomplete; cannot capture signals");
|
|
39
|
-
const work = read.projection, snapshotDigest = read.projection.selectedSnapshot.digest;
|
|
39
|
+
const work = read.projection, runtime = read.projection.runtime, snapshotDigest = read.projection.selectedSnapshot.digest;
|
|
40
40
|
const snapshot = { snapshotDigest, scopeValid: true,
|
|
41
41
|
obligations: work.obligations.map(o => ({ id: o.binding.obligation.id, digest: o.binding.obligation.digest,
|
|
42
42
|
intentDigest: o.binding.intent.digest, policyDigest: o.binding.policy.digest, artifactDigest: o.binding.artifact?.digest ?? null,
|
|
@@ -49,6 +49,20 @@ export function captureArchivedWorkSignals(root, manifestId, context, suppliedFa
|
|
|
49
49
|
// prior acceptance or authority. Coverage candidates still follow from the projected obligation.
|
|
50
50
|
const facts = suppliedFacts ?? { scopeDigest: snapshotDigest, version: "observed-work-v1", population: "retained-work",
|
|
51
51
|
expectedWaits: [], checkpoints: [], violations: [], priorAccepted: [] };
|
|
52
|
+
const runtimeFacts = {
|
|
53
|
+
version: "observed-work-runtime-v1", snapshotDigest, sourceManifestId: manifestId,
|
|
54
|
+
attempts: runtime.attempts.map(attempt => {
|
|
55
|
+
const eventDigests = runtime.occurrences.filter(occurrence => occurrence.payload.executionId === attempt.executionId &&
|
|
56
|
+
occurrence.payload.provenance === "observed" && ["completed", "failed", "cancelled"].includes(occurrence.payload.state))
|
|
57
|
+
.map(occurrence => occurrence.event.digest).sort();
|
|
58
|
+
return { executionId: attempt.executionId, state: attempt.state, resolution: attempt.resolution,
|
|
59
|
+
completionEvidence: { state: eventDigests.length ? "available" : "unavailable", eventDigests } };
|
|
60
|
+
}).sort((a, b) => a.executionId < b.executionId ? -1 : a.executionId > b.executionId ? 1 : 0),
|
|
61
|
+
obligations: snapshot.obligations.map(obligation => ({ obligationDigest: obligation.digest, acceptance: obligation.acceptance, coverage: obligation.coverage }))
|
|
62
|
+
.sort((a, b) => a.obligationDigest < b.obligationDigest ? -1 : a.obligationDigest > b.obligationDigest ? 1 : 0),
|
|
63
|
+
unavailable: ["checkpoint-evidence", "expected-wait-evidence", "prior-acceptance-history"],
|
|
64
|
+
};
|
|
65
|
+
const retainedRuntimeFacts = retainArchiveSource(root, { sourceId: `observed-runtime-${read.sourceSha256}`, parser: { id: "observed-work-runtime-facts", version: "1" }, retention: "exact", bytes: Buffer.from(JSON.stringify(runtimeFacts)) });
|
|
52
66
|
// All four digests here are REVISION identities, not artifact content digests.
|
|
53
67
|
// Duplicate obligation bindings cannot be collapsed by this input profile; its validator refuses them.
|
|
54
68
|
const observation = retainWorkSignalObservation(root, snapshot, facts);
|
|
@@ -56,7 +70,7 @@ export function captureArchivedWorkSignals(root, manifestId, context, suppliedFa
|
|
|
56
70
|
const cases = captureWorkSignalCases(root, observation.manifestId);
|
|
57
71
|
const linkage = { version: "archived-work-signals-v1", bindingProfile: "work-v4-revision-digests-v1", workManifestId: manifestId,
|
|
58
72
|
workSha256: read.sourceSha256, producerCommit: read.producerCommit, projectionManifestId: projection.manifestId,
|
|
59
|
-
observationId: observation.manifestId, caseBatchId: cases.batchId, candidateIds: cases.candidateIds,
|
|
73
|
+
runtimeFactsManifestId: retainedRuntimeFacts.manifestId, observationId: observation.manifestId, caseBatchId: cases.batchId, candidateIds: cases.candidateIds,
|
|
60
74
|
authorityBasis: "independently-supplied-host-context", acceptance: "not-assessed" };
|
|
61
75
|
const stored = retainArchiveSource(root, { sourceId: `work-signal-link-${read.sourceSha256}`, parser: { id: "archived-work-signals", version: "1" }, retention: "exact", bytes: Buffer.from(JSON.stringify(linkage)) });
|
|
62
76
|
return { ...linkage, linkageManifestId: stored.manifestId };
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { type BlindQualityChoice, type InterventionManifest, type InterventionEvidence, type InterventionQualification } from "@skill-harness/core";
|
|
2
2
|
/** Explicit private retention. Host role declarations are NOT authentication. No provider is called. */
|
|
3
3
|
export declare function retainBlindIntervention(root: string, manifest: InterventionManifest, evidence: InterventionEvidence[], qualification: InterventionQualification, author: string): string;
|
|
4
|
+
/** Opaque linkage metadata only; never exposes roles, arm identities or cost before choice. */
|
|
5
|
+
export declare function readBlindInterventionBinding(root: string, id: string, author: string): Readonly<{
|
|
6
|
+
experimentDigest: string;
|
|
7
|
+
investigationDigest: string;
|
|
8
|
+
}>;
|
|
4
9
|
/** One immutable quality choice per comparison. Reopening cannot reset the reveal gate. */
|
|
5
10
|
export declare function openBlindIntervention(root: string, id: string, author: string): Readonly<{
|
|
6
11
|
view: () => {
|
|
@@ -12,6 +17,7 @@ export declare function openBlindIntervention(root: string, id: string, author:
|
|
|
12
17
|
limitations: string[];
|
|
13
18
|
};
|
|
14
19
|
quality(): BlindQualityChoice | null;
|
|
20
|
+
previewChoice(input: BlindQualityChoice): BlindQualityChoice;
|
|
15
21
|
readArtifact: (label: string, hash: string) => Buffer<ArrayBuffer>;
|
|
16
22
|
choose(input: BlindQualityChoice): BlindQualityChoice;
|
|
17
23
|
reveal(): {
|
|
@@ -90,7 +90,13 @@ function load(root, id, author) {
|
|
|
90
90
|
throw new Error("blind artifact unavailable");
|
|
91
91
|
artifacts.set(ref.hash, artifact.bytes);
|
|
92
92
|
}
|
|
93
|
-
|
|
93
|
+
const assessment = assessIntervention(bundle.manifest, bundle.evidence, { ...bundle.qualification, artifacts });
|
|
94
|
+
return { blind: createBlindComparison(bundle.manifest, assessment, bundle.seed), manifest: bundle.manifest };
|
|
95
|
+
}
|
|
96
|
+
/** Opaque linkage metadata only; never exposes roles, arm identities or cost before choice. */
|
|
97
|
+
export function readBlindInterventionBinding(root, id, author) {
|
|
98
|
+
const { manifest } = load(root, id, author);
|
|
99
|
+
return Object.freeze({ experimentDigest: manifest.id, investigationDigest: manifest.investigationSha256 });
|
|
94
100
|
}
|
|
95
101
|
function directory(path) {
|
|
96
102
|
const s = lstatSync(path);
|
|
@@ -159,10 +165,11 @@ function readChoice(root, id) {
|
|
|
159
165
|
export function openBlindIntervention(root, id, author) {
|
|
160
166
|
if (!SHA.test(id))
|
|
161
167
|
throw new Error("invalid blind comparison identity");
|
|
162
|
-
const current = () => load(root, id, author);
|
|
168
|
+
const current = () => load(root, id, author).blind;
|
|
163
169
|
return Object.freeze({
|
|
164
170
|
view: () => current().view(),
|
|
165
171
|
quality() { const blind = current(), choice = readChoice(root, id); return choice ? blind.choose(choice) : null; },
|
|
172
|
+
previewChoice(input) { return current().choose(input); },
|
|
166
173
|
readArtifact: (label, hash) => current().readArtifact(label, hash),
|
|
167
174
|
choose(input) {
|
|
168
175
|
const blind = current(), choice = blind.choose(input);
|
|
@@ -36,6 +36,14 @@ export declare function retainArchiveSource(root: string, input: ArchiveSourceIn
|
|
|
36
36
|
manifestId: string;
|
|
37
37
|
reference: ArchiveSourceReference;
|
|
38
38
|
};
|
|
39
|
+
export type ArchiveReferenceRead = {
|
|
40
|
+
status: 'available';
|
|
41
|
+
reference: ArchiveSourceReference;
|
|
42
|
+
} | Exclude<ArchiveRead, {
|
|
43
|
+
status: 'available';
|
|
44
|
+
}>;
|
|
45
|
+
/** Metadata only for explicit archive inventory. Does not claim content availability. */
|
|
46
|
+
export declare function readArchiveSourceReference(root: string, manifestId: string): ArchiveReferenceRead;
|
|
39
47
|
export declare function readArchiveSource(root: string, manifestId: string, maxBytes?: number): ArchiveRead;
|
|
40
48
|
export interface ArchivedJsonl {
|
|
41
49
|
status: "complete" | "partial" | "error";
|
package/dist/evidence-archive.js
CHANGED
|
@@ -149,27 +149,31 @@ export function retainArchiveSource(root, input) {
|
|
|
149
149
|
syncDirectory(resolve(root));
|
|
150
150
|
return { manifestId, reference };
|
|
151
151
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
return { status: "error", reason: "invalid read bound" };
|
|
152
|
+
/** Metadata only for explicit archive inventory. Does not claim content availability. */
|
|
153
|
+
export function readArchiveSourceReference(root, manifestId) {
|
|
155
154
|
if (!HASH.test(manifestId))
|
|
156
|
-
return { status:
|
|
155
|
+
return { status: 'error', reason: 'invalid manifest identity' };
|
|
157
156
|
try {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
directory(join(root, "manifests"), false);
|
|
162
|
-
manifest = readVerified(join(root, "manifests", manifestId), manifestId, 8192);
|
|
163
|
-
}
|
|
164
|
-
catch (error) {
|
|
165
|
-
if (absent(error))
|
|
166
|
-
return { status: "missing", reason: "manifest" };
|
|
167
|
-
throw error;
|
|
168
|
-
}
|
|
169
|
-
const text = manifest.toString("utf8");
|
|
157
|
+
directory(root, false);
|
|
158
|
+
directory(join(root, 'manifests'), false);
|
|
159
|
+
const text = readVerified(join(root, 'manifests', manifestId), manifestId, 8192).toString('utf8');
|
|
170
160
|
const reference = JSON.parse(text);
|
|
171
161
|
if (!validReference(reference) || JSON.stringify(reference) !== text)
|
|
172
|
-
fail(
|
|
162
|
+
fail('invalid manifest');
|
|
163
|
+
return { status: 'available', reference };
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
return absent(error) ? { status: 'missing', reason: 'manifest' } : { status: 'error', reason: 'invalid or inaccessible retained evidence' };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
export function readArchiveSource(root, manifestId, maxBytes = LIMIT) {
|
|
170
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0 || maxBytes > LIMIT)
|
|
171
|
+
return { status: "error", reason: "invalid read bound" };
|
|
172
|
+
try {
|
|
173
|
+
const manifest = readArchiveSourceReference(root, manifestId);
|
|
174
|
+
if (manifest.status !== 'available')
|
|
175
|
+
return manifest;
|
|
176
|
+
const { reference } = manifest;
|
|
173
177
|
if (reference.retention === "reference-only")
|
|
174
178
|
return { status: "missing", reason: "not-retained" };
|
|
175
179
|
if (reference.bytes > maxBytes)
|
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,11 @@ export * from "./work-case-review.js";
|
|
|
18
18
|
export * from "./archive-read-capability.js";
|
|
19
19
|
export * from "./archived-work.js";
|
|
20
20
|
export * from "./blind-intervention.js";
|
|
21
|
+
export * from "./learning-lifecycle.js";
|
|
22
|
+
export * from "./learning-workspace.js";
|
|
23
|
+
export * from "./learning-trust-setup.js";
|
|
24
|
+
export { learningHash, learningFile } from "./learning-journal.js";
|
|
25
|
+
export * from "./learning-case.js";
|
|
21
26
|
export * from "./archive-observer.js";
|
|
22
27
|
export * from "./weekly-investigation.js";
|
|
23
28
|
export * from "./intervention-run.js";
|
package/dist/index.js
CHANGED
|
@@ -30,6 +30,11 @@ export * from "./work-case-review.js";
|
|
|
30
30
|
export * from "./archive-read-capability.js";
|
|
31
31
|
export * from "./archived-work.js";
|
|
32
32
|
export * from "./blind-intervention.js";
|
|
33
|
+
export * from "./learning-lifecycle.js";
|
|
34
|
+
export * from "./learning-workspace.js";
|
|
35
|
+
export * from "./learning-trust-setup.js";
|
|
36
|
+
export { learningHash, learningFile } from "./learning-journal.js";
|
|
37
|
+
export * from "./learning-case.js";
|
|
33
38
|
export * from "./archive-observer.js";
|
|
34
39
|
export * from "./weekly-investigation.js";
|
|
35
40
|
export * from "./intervention-run.js";
|
|
@@ -14,5 +14,5 @@ export declare function learningJournal(path: string, initial?: Record<string, u
|
|
|
14
14
|
append(prior: string, value: Record<string, unknown>): LearningEvent;
|
|
15
15
|
};
|
|
16
16
|
/** One authoritative store per weekly key/policy in this archive; a renamed directory cannot refill it. */
|
|
17
|
-
export declare function registerLearningStore(root: string, kind: 'weekly' | 'trust' | 'intervention' | 'access', key: string, target: string, binding: string): void;
|
|
18
|
-
export declare function verifyLearningStore(root: string, kind: 'weekly' | 'trust' | 'intervention' | 'access', key: string, target: string, binding: string): void;
|
|
17
|
+
export declare function registerLearningStore(root: string, kind: 'weekly' | 'trust' | 'intervention' | 'access' | 'workspace', key: string, target: string, binding: string): void;
|
|
18
|
+
export declare function verifyLearningStore(root: string, kind: 'weekly' | 'trust' | 'intervention' | 'access' | 'workspace', key: string, target: string, binding: string): void;
|
package/dist/learning-journal.js
CHANGED
|
@@ -214,7 +214,7 @@ export function learningJournal(path, initial) {
|
|
|
214
214
|
}
|
|
215
215
|
/** One authoritative store per weekly key/policy in this archive; a renamed directory cannot refill it. */
|
|
216
216
|
export function registerLearningStore(root, kind, key, target, binding) {
|
|
217
|
-
if (!/^[a-f0-9]{64}$/.test(key) || !isAbsolute(target) || !['weekly', 'trust', 'intervention', 'access'].includes(kind))
|
|
217
|
+
if (!/^[a-f0-9]{64}$/.test(key) || !isAbsolute(target) || !['weekly', 'trust', 'intervention', 'access', 'workspace'].includes(kind))
|
|
218
218
|
throw Error('invalid learning registration');
|
|
219
219
|
directory(root);
|
|
220
220
|
directory(dirname(target));
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface LearningLifecycleInput {
|
|
2
|
+
caseManifestId: string;
|
|
3
|
+
hypothesisManifestId: string;
|
|
4
|
+
comparisonManifestId: string;
|
|
5
|
+
choiceManifestId: string | null;
|
|
6
|
+
adoptionManifestId: string | null;
|
|
7
|
+
rollbackManifestId: string | null;
|
|
8
|
+
outcomeManifestIds: string[];
|
|
9
|
+
predecessorManifestId: string | null;
|
|
10
|
+
}
|
|
11
|
+
export type LearningLifecycleState = 'awaiting-human-choice' | 'decision-recorded' | 'adopted-awaiting-outcome' | 'rolled-back' | 'outcome-observed';
|
|
12
|
+
export interface LearningLifecycleView {
|
|
13
|
+
version: 'retained-learning-lifecycle-v1';
|
|
14
|
+
state: LearningLifecycleState;
|
|
15
|
+
predecessorManifestId: string | null;
|
|
16
|
+
links: {
|
|
17
|
+
case: string;
|
|
18
|
+
hypothesis: string;
|
|
19
|
+
comparison: string;
|
|
20
|
+
choice: string | null;
|
|
21
|
+
adoption: string | null;
|
|
22
|
+
rollback: string | null;
|
|
23
|
+
outcomes: string[];
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** Retains navigation only. Linked artifacts retain their own semantics and authority. */
|
|
27
|
+
export declare function retainLearningLifecycle(root: string, input: LearningLifecycleInput): string;
|
|
28
|
+
export declare function readLearningLifecycle(root: string, manifestId: string): LearningLifecycleView;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { readArchiveSource, retainArchiveSource } from './evidence-archive.js';
|
|
2
|
+
const SHA = /^[a-f0-9]{64}$/;
|
|
3
|
+
const closed = (value, names) => !!value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).sort().join() === names.sort().join();
|
|
4
|
+
function available(root, id, parser) {
|
|
5
|
+
if (!SHA.test(id))
|
|
6
|
+
throw Error('invalid retained learning link');
|
|
7
|
+
const source = readArchiveSource(root, id);
|
|
8
|
+
if (source.status !== 'available' || source.reference.retention !== 'exact' || parser && source.reference.parser.id !== parser)
|
|
9
|
+
throw Error('retained learning link unavailable');
|
|
10
|
+
return source;
|
|
11
|
+
}
|
|
12
|
+
function validate(root, input, depth = 0) {
|
|
13
|
+
if (depth > 64)
|
|
14
|
+
throw Error('learning lifecycle predecessor bound exceeded');
|
|
15
|
+
if (!closed(input, ['caseManifestId', 'hypothesisManifestId', 'comparisonManifestId', 'choiceManifestId', 'adoptionManifestId', 'rollbackManifestId', 'outcomeManifestIds', 'predecessorManifestId']) || !Array.isArray(input.outcomeManifestIds) || input.outcomeManifestIds.length > 64 || new Set(input.outcomeManifestIds).size !== input.outcomeManifestIds.length)
|
|
16
|
+
throw Error('invalid bounded learning lifecycle');
|
|
17
|
+
for (const id of [input.caseManifestId, input.hypothesisManifestId, input.comparisonManifestId])
|
|
18
|
+
available(root, id);
|
|
19
|
+
for (const id of [input.choiceManifestId, input.adoptionManifestId, input.rollbackManifestId, ...input.outcomeManifestIds])
|
|
20
|
+
if (id !== null)
|
|
21
|
+
available(root, id);
|
|
22
|
+
if (input.adoptionManifestId && !input.choiceManifestId)
|
|
23
|
+
throw Error('quality choice required before adoption');
|
|
24
|
+
if (input.rollbackManifestId && !input.adoptionManifestId)
|
|
25
|
+
throw Error('adoption required before rollback');
|
|
26
|
+
if (input.outcomeManifestIds.length && !input.adoptionManifestId)
|
|
27
|
+
throw Error('adoption required before outcomes');
|
|
28
|
+
if (input.predecessorManifestId) {
|
|
29
|
+
const prior = read(root, input.predecessorManifestId, depth + 1);
|
|
30
|
+
if (prior.links.case !== input.caseManifestId || prior.links.hypothesis !== input.hypothesisManifestId || prior.links.comparison !== input.comparisonManifestId)
|
|
31
|
+
throw Error('learning lifecycle identity changed');
|
|
32
|
+
for (const key of ['choice', 'adoption', 'rollback'])
|
|
33
|
+
if (prior.links[key] !== null && prior.links[key] !== ({ choice: input.choiceManifestId, adoption: input.adoptionManifestId, rollback: input.rollbackManifestId })[key])
|
|
34
|
+
throw Error('learning lifecycle cannot regress or replace decisions');
|
|
35
|
+
if (prior.links.outcomes.some((id, index) => input.outcomeManifestIds[index] !== id))
|
|
36
|
+
throw Error('learning lifecycle cannot regress or replace outcomes');
|
|
37
|
+
}
|
|
38
|
+
const state = input.outcomeManifestIds.length ? 'outcome-observed' : input.rollbackManifestId ? 'rolled-back' : input.adoptionManifestId ? 'adopted-awaiting-outcome' : input.choiceManifestId ? 'decision-recorded' : 'awaiting-human-choice';
|
|
39
|
+
return { version: 'retained-learning-lifecycle-v1', state, predecessorManifestId: input.predecessorManifestId, links: { case: input.caseManifestId, hypothesis: input.hypothesisManifestId, comparison: input.comparisonManifestId, choice: input.choiceManifestId, adoption: input.adoptionManifestId, rollback: input.rollbackManifestId, outcomes: [...input.outcomeManifestIds] } };
|
|
40
|
+
}
|
|
41
|
+
/** Retains navigation only. Linked artifacts retain their own semantics and authority. */
|
|
42
|
+
export function retainLearningLifecycle(root, input) {
|
|
43
|
+
const view = validate(root, input);
|
|
44
|
+
return retainArchiveSource(root, { sourceId: `learning-${view.links.comparison.slice(0, 24)}`, parser: { id: 'learning-lifecycle', version: '1' }, retention: 'exact', bytes: Buffer.from(JSON.stringify(view)) }).manifestId;
|
|
45
|
+
}
|
|
46
|
+
function read(root, manifestId, depth) {
|
|
47
|
+
if (depth > 64)
|
|
48
|
+
throw Error('learning lifecycle predecessor bound exceeded');
|
|
49
|
+
const source = available(root, manifestId, 'learning-lifecycle');
|
|
50
|
+
let value;
|
|
51
|
+
try {
|
|
52
|
+
value = JSON.parse(source.bytes.toString('utf8'));
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
throw Error('invalid retained learning lifecycle');
|
|
56
|
+
}
|
|
57
|
+
if (!closed(value, ['version', 'state', 'predecessorManifestId', 'links']) || value.version !== 'retained-learning-lifecycle-v1' || !closed(value.links, ['case', 'hypothesis', 'comparison', 'choice', 'adoption', 'rollback', 'outcomes']))
|
|
58
|
+
throw Error('invalid retained learning lifecycle');
|
|
59
|
+
const links = value.links;
|
|
60
|
+
const rebuilt = validate(root, { caseManifestId: links.case, hypothesisManifestId: links.hypothesis, comparisonManifestId: links.comparison, choiceManifestId: links.choice, adoptionManifestId: links.adoption, rollbackManifestId: links.rollback, outcomeManifestIds: links.outcomes, predecessorManifestId: value.predecessorManifestId }, depth);
|
|
61
|
+
if (rebuilt.state !== value.state || JSON.stringify(rebuilt) !== source.bytes.toString('utf8'))
|
|
62
|
+
throw Error('retained learning lifecycle changed');
|
|
63
|
+
return Object.freeze({ ...rebuilt, links: Object.freeze({ ...rebuilt.links, outcomes: Object.freeze(rebuilt.links.outcomes) }) });
|
|
64
|
+
}
|
|
65
|
+
export function readLearningLifecycle(root, manifestId) { return read(root, manifestId, 0); }
|
|
66
|
+
//# sourceMappingURL=learning-lifecycle.js.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { CalibrationPrediction, CalibrationSplit, ExposurePolicy } from '@skill-harness/core';
|
|
2
|
+
import type { LearningCaseReference } from './learning-case.js';
|
|
3
|
+
import { type TrustLifecycleInput } from './trust-lifecycle.js';
|
|
4
|
+
export interface LearningTrustSetup {
|
|
5
|
+
caseName: string;
|
|
6
|
+
detectorId: string;
|
|
7
|
+
split: CalibrationSplit;
|
|
8
|
+
maxUnflagged: number;
|
|
9
|
+
unflagged: {
|
|
10
|
+
incidentId: string;
|
|
11
|
+
manifestId: string;
|
|
12
|
+
split: CalibrationSplit;
|
|
13
|
+
}[];
|
|
14
|
+
exposure: Omit<ExposurePolicy, 'component' | 'kind' | 'split'> | null;
|
|
15
|
+
}
|
|
16
|
+
/** Forms construct a frozen cohort from actual selected cases. Repeated observations of the
|
|
17
|
+
* same target are ONE incident; a detector nomination never becomes its own reference label. */
|
|
18
|
+
export declare function previewLearningTrust(directory: string, raw: LearningTrustSetup): {
|
|
19
|
+
input: TrustLifecycleInput;
|
|
20
|
+
policyDigest: string;
|
|
21
|
+
predictions: CalibrationPrediction[];
|
|
22
|
+
nominationCount: number;
|
|
23
|
+
independentIncidentCount: number;
|
|
24
|
+
caseManifestIds: string[];
|
|
25
|
+
labelsCreated: 0;
|
|
26
|
+
excludedOtherDetectors: number;
|
|
27
|
+
};
|
|
28
|
+
export declare function configureLearningTrust(directory: string, options: LearningTrustSetup, authorizedPolicyDigests: readonly string[]): {
|
|
29
|
+
policyId: string;
|
|
30
|
+
component: import("@skill-harness/core").CalibrationComponent;
|
|
31
|
+
calibration: {
|
|
32
|
+
version: "factory-calibration-v1";
|
|
33
|
+
reports: import("@skill-harness/core").CalibrationReport[];
|
|
34
|
+
excludedTuning: number;
|
|
35
|
+
referenceBasis: "independently-supplied-host-context";
|
|
36
|
+
advisoryOnly: true;
|
|
37
|
+
};
|
|
38
|
+
sampledIncidentIds: string[];
|
|
39
|
+
unflagged: {
|
|
40
|
+
sampled: number;
|
|
41
|
+
resolved: number;
|
|
42
|
+
misses: number;
|
|
43
|
+
unresolved: number;
|
|
44
|
+
conflicted: number;
|
|
45
|
+
};
|
|
46
|
+
attentionUsed: number;
|
|
47
|
+
retired: boolean;
|
|
48
|
+
exposures: {
|
|
49
|
+
mode: "ask" | "silent" | "retire";
|
|
50
|
+
reason: string;
|
|
51
|
+
id: string;
|
|
52
|
+
}[];
|
|
53
|
+
tip: string;
|
|
54
|
+
grantExpansion: false;
|
|
55
|
+
};
|
|
56
|
+
/** Selected case reference, not a retyped verdict. Separate explicit reference authority is
|
|
57
|
+
* still required by linkCaseOutcome; this only supplies the exact preview to the operator. */
|
|
58
|
+
export declare function previewLearningCaseLabel(directory: string, reference: LearningCaseReference): {
|
|
59
|
+
outcome: import("./trust-lifecycle.js").TrustReferenceOutcome;
|
|
60
|
+
caseReference: LearningCaseReference;
|
|
61
|
+
digest: string;
|
|
62
|
+
tip: string;
|
|
63
|
+
predictionId: string;
|
|
64
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { learningCopy, learningHash } from './learning-journal.js';
|
|
2
|
+
import { openLearningWorkspace } from './learning-workspace.js';
|
|
3
|
+
import { trustPolicyDigest } from './trust-lifecycle.js';
|
|
4
|
+
/** Forms construct a frozen cohort from actual selected cases. Repeated observations of the
|
|
5
|
+
* same target are ONE incident; a detector nomination never becomes its own reference label. */
|
|
6
|
+
export function previewLearningTrust(directory, raw) {
|
|
7
|
+
const options = learningCopy(raw), workspace = openLearningWorkspace(directory), config = workspace.configuration();
|
|
8
|
+
if (Object.keys(options).sort().join() !== 'caseName,detectorId,exposure,maxUnflagged,split,unflagged' || !['calibration', 'heldout', 'tuning'].includes(options.split) || !Array.isArray(options.unflagged) || options.unflagged.length > 1024)
|
|
9
|
+
throw Error('closed trust setup required');
|
|
10
|
+
const page = workspace.cases(options.caseName), items = [...page.items];
|
|
11
|
+
for (let offset = 5; offset < page.total; offset += 5)
|
|
12
|
+
items.push(...workspace.cases(options.caseName, offset).items);
|
|
13
|
+
const chosen = items.filter(c => c.candidate.detector.id === options.detectorId);
|
|
14
|
+
if (!chosen.length)
|
|
15
|
+
throw Error('selected detector has no retained cases');
|
|
16
|
+
const component = { kind: 'detector', ...chosen[0].candidate.detector };
|
|
17
|
+
if (chosen.some(c => learningHash(c.candidate.detector) !== learningHash(chosen[0].candidate.detector)))
|
|
18
|
+
throw Error('detector versions/populations must be calibrated separately');
|
|
19
|
+
const binding = workspace.inspect(0).cases.find(b => b.name === options.caseName);
|
|
20
|
+
if (!binding || binding.state !== 'ready')
|
|
21
|
+
throw Error('case binding unavailable');
|
|
22
|
+
const incidents = new Map();
|
|
23
|
+
for (const c of chosen) {
|
|
24
|
+
const id = learningHash({ target: c.candidate.target, population: component.population });
|
|
25
|
+
if (!incidents.has(id))
|
|
26
|
+
incidents.set(id, c);
|
|
27
|
+
}
|
|
28
|
+
const cohort = [...[...incidents].map(([incidentId, c]) => ({ incidentId, manifestId: c.caseManifestId, flagged: true, split: options.split })), ...options.unflagged.map(c => ({ ...c, flagged: false }))];
|
|
29
|
+
const input = { archiveRoot: config.archiveRoot, component, seed: learningHash({ scope: config.scopeDigest, component, cohort }), maxUnflagged: options.maxUnflagged, cohort,
|
|
30
|
+
exposure: options.exposure ? { ...options.exposure, component, kind: 'positive', split: options.split === 'tuning' ? 'calibration' : options.split } : null };
|
|
31
|
+
const predictions = [...incidents].map(([incidentId]) => ({ id: learningHash({ incidentId, component, split: options.split }), incidentId, component, kind: 'positive', split: options.split }));
|
|
32
|
+
return { input, policyDigest: trustPolicyDigest(input), predictions, nominationCount: chosen.length, independentIncidentCount: incidents.size,
|
|
33
|
+
caseManifestIds: [...incidents.values()].map(c => c.caseManifestId), labelsCreated: 0, excludedOtherDetectors: items.length - chosen.length };
|
|
34
|
+
}
|
|
35
|
+
export function configureLearningTrust(directory, options, authorizedPolicyDigests) {
|
|
36
|
+
const preview = previewLearningTrust(directory, options), workspace = openLearningWorkspace(directory);
|
|
37
|
+
workspace.configureTrust(preview.input, authorizedPolicyDigests);
|
|
38
|
+
for (const p of preview.predictions)
|
|
39
|
+
workspace.trust().predict(p);
|
|
40
|
+
return workspace.trust().inspect(0);
|
|
41
|
+
}
|
|
42
|
+
/** Selected case reference, not a retyped verdict. Separate explicit reference authority is
|
|
43
|
+
* still required by linkCaseOutcome; this only supplies the exact preview to the operator. */
|
|
44
|
+
export function previewLearningCaseLabel(directory, reference) {
|
|
45
|
+
const trust = openLearningWorkspace(directory).trust(), config = trust.configuration();
|
|
46
|
+
const row = config.cohort.find(c => c.flagged && c.manifestId === reference.manifestId);
|
|
47
|
+
if (!row)
|
|
48
|
+
throw Error('case outside frozen calibration cohort');
|
|
49
|
+
const predictions = trust.history().filter(e => e.value.type === 'prediction' || e.value.type === 'correction').map(e => e.value.prediction);
|
|
50
|
+
const p = predictions.filter(p => p.incidentId === row.incidentId).at(-1);
|
|
51
|
+
if (!p)
|
|
52
|
+
throw Error('prediction missing');
|
|
53
|
+
return { predictionId: p.id, ...trust.previewCaseOutcome(p.id, reference) };
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=learning-trust-setup.js.map
|