@skill-harness/adapters 0.12.0 → 0.13.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/archive-access.d.ts +58 -0
- package/dist/archive-access.js +61 -0
- package/dist/archive-checkpoint.d.ts +47 -0
- package/dist/archive-checkpoint.js +116 -0
- package/dist/archive-facts.d.ts +19 -0
- package/dist/archive-facts.js +20 -0
- package/dist/archive-observer.d.ts +21 -0
- package/dist/archive-observer.js +79 -0
- package/dist/archive-policy.d.ts +107 -0
- package/dist/archive-policy.js +135 -0
- package/dist/archive-read-capability.d.ts +49 -0
- package/dist/archive-read-capability.js +45 -0
- package/dist/archive-retention-policy.d.ts +52 -0
- package/dist/archive-retention-policy.js +49 -0
- package/dist/archived-work.d.ts +44 -0
- package/dist/archived-work.js +59 -0
- package/dist/blind-intervention.d.ts +29 -0
- package/dist/blind-intervention.js +211 -0
- package/dist/codex-diagnostic-model.d.ts +14 -0
- package/dist/codex-diagnostic-model.js +39 -0
- package/dist/codex-host-observer.d.ts +111 -0
- package/dist/codex-host-observer.js +309 -0
- package/dist/codex-https.d.ts +7 -0
- package/dist/codex-https.js +221 -0
- package/dist/codex-model-profile.d.ts +38 -0
- package/dist/codex-model-profile.js +54 -0
- package/dist/codex-producer-ipc.d.ts +55 -0
- package/dist/codex-producer-ipc.js +67 -0
- package/dist/codex-sdk-transport.d.ts +50 -0
- package/dist/codex-sdk-transport.js +294 -0
- package/dist/codex-subscription.d.ts +114 -0
- package/dist/codex-subscription.js +268 -0
- package/dist/evidence-archive.d.ts +57 -0
- package/dist/evidence-archive.js +221 -0
- package/dist/execution-projection-schema.d.ts +10 -0
- package/dist/execution-projection-schema.js +30 -0
- package/dist/execution-retention-archive.d.ts +69 -0
- package/dist/execution-retention-archive.js +154 -0
- package/dist/generated/retention-v2-contract.d.ts +72 -0
- package/dist/generated/retention-v2-contract.js +105 -0
- package/dist/generated/retention-v2-json.d.ts +24 -0
- package/dist/generated/retention-v2-json.js +268 -0
- package/dist/generated/retention-v2-native-json.d.ts +2 -0
- package/dist/generated/retention-v2-native-json.js +33 -0
- package/dist/generated/retention-v2-native.d.ts +58 -0
- package/dist/generated/retention-v2-native.js +155 -0
- package/dist/generated/retention-v2-pin.d.ts +2 -0
- package/dist/generated/retention-v2-pin.js +4 -0
- package/dist/generated/retention-v2-types.d.ts +49 -0
- package/dist/generated/retention-v2-types.js +2 -0
- package/dist/generated/retention-v2-work-types.d.ts +260 -0
- package/dist/generated/retention-v2-work-types.js +13 -0
- package/dist/generated/work-v4/execution-id.d.ts +5 -0
- package/dist/generated/work-v4/execution-id.js +14 -0
- package/dist/generated/work-v4/json.d.ts +24 -0
- package/dist/generated/work-v4/json.js +268 -0
- package/dist/generated/work-v4/occurrences.d.ts +226 -0
- package/dist/generated/work-v4/occurrences.js +116 -0
- package/dist/generated/work-v4/pin.d.ts +1 -0
- package/dist/generated/work-v4/pin.js +3 -0
- package/dist/generated/work-v4/projection.d.ts +294 -0
- package/dist/generated/work-v4/projection.js +238 -0
- package/dist/generated/work-v4/reader.d.ts +24 -0
- package/dist/generated/work-v4/reader.js +109 -0
- package/dist/generated/work-v4/snapshot.d.ts +28 -0
- package/dist/generated/work-v4/snapshot.js +268 -0
- package/dist/generated/work-v4/types.d.ts +260 -0
- package/dist/generated/work-v4/types.js +13 -0
- package/dist/generated/work-v4/validation.d.ts +13 -0
- package/dist/generated/work-v4/validation.js +227 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +22 -0
- package/dist/installed-review-session.d.ts +28 -0
- package/dist/installed-review-session.js +166 -0
- package/dist/intervention-run.d.ts +403 -0
- package/dist/intervention-run.js +169 -0
- package/dist/learning-case.d.ts +12 -0
- package/dist/learning-case.js +15 -0
- package/dist/learning-journal.d.ts +18 -0
- package/dist/learning-journal.js +256 -0
- package/dist/principal-payload-port.d.ts +25 -0
- package/dist/principal-payload-port.js +40 -0
- package/dist/producer-product.d.ts +70 -0
- package/dist/producer-product.js +186 -0
- package/dist/producer-review.d.ts +53 -0
- package/dist/producer-review.js +207 -0
- package/dist/reviewed-archive-export.d.ts +42 -0
- package/dist/reviewed-archive-export.js +78 -0
- package/dist/trust-lifecycle.d.ts +128 -0
- package/dist/trust-lifecycle.js +200 -0
- package/dist/weekly-investigation.d.ts +237 -0
- package/dist/weekly-investigation.js +330 -0
- package/dist/work-candidates.d.ts +29 -0
- package/dist/work-candidates.js +78 -0
- package/dist/work-case-archive.d.ts +25 -0
- package/dist/work-case-archive.js +63 -0
- package/dist/work-case-review.d.ts +45 -0
- package/dist/work-case-review.js +220 -0
- package/dist/work-signal-cases.d.ts +15 -0
- package/dist/work-signal-cases.js +54 -0
- package/dist/work-signal-observation.d.ts +23 -0
- package/dist/work-signal-observation.js +89 -0
- package/package.json +3 -2
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { constants, closeSync, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { appendWorkCaseDecision } from "@skill-harness/core";
|
|
5
|
+
import { readArchiveSource } from "./evidence-archive.js";
|
|
6
|
+
import { readWorkCandidate } from "./work-case-archive.js";
|
|
7
|
+
import { readWorkSignalBatch, readWorkSignalCase } from "./work-signal-cases.js";
|
|
8
|
+
const SHA = /^[a-f0-9]{64}$/;
|
|
9
|
+
const missing = (error) => error?.code === "ENOENT";
|
|
10
|
+
function assertDirectory(path) {
|
|
11
|
+
const stat = lstatSync(path);
|
|
12
|
+
if (!stat.isDirectory() || stat.isSymbolicLink() || (stat.mode & 0o077) || (process.getuid && stat.uid !== process.getuid()))
|
|
13
|
+
throw new Error("private case directory required");
|
|
14
|
+
}
|
|
15
|
+
function syncDirectory(path) {
|
|
16
|
+
const fd = openSync(path, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
17
|
+
try {
|
|
18
|
+
fsyncSync(fd);
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
closeSync(fd);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function privateDirectory(path) {
|
|
25
|
+
let created = false;
|
|
26
|
+
try {
|
|
27
|
+
mkdirSync(path, { mode: 0o700 });
|
|
28
|
+
created = true;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (error.code !== "EEXIST")
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
assertDirectory(path);
|
|
35
|
+
return created;
|
|
36
|
+
}
|
|
37
|
+
function bytes(path, limit) {
|
|
38
|
+
const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
39
|
+
try {
|
|
40
|
+
const stat = fstatSync(fd);
|
|
41
|
+
if (!stat.isFile() || stat.nlink !== 1 || stat.size > limit || (stat.mode & 0o077))
|
|
42
|
+
throw new Error("invalid case history file");
|
|
43
|
+
const out = Buffer.alloc(limit + 1);
|
|
44
|
+
let used = 0;
|
|
45
|
+
while (used <= limit) {
|
|
46
|
+
const n = readSync(fd, out, used, out.length - used, used);
|
|
47
|
+
if (!n)
|
|
48
|
+
break;
|
|
49
|
+
used += n;
|
|
50
|
+
}
|
|
51
|
+
if (used > limit)
|
|
52
|
+
throw new Error("case history bound exceeded");
|
|
53
|
+
return out.subarray(0, used);
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
closeSync(fd);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function historyAt(directory, caseId, brandNew = false) {
|
|
60
|
+
let raw;
|
|
61
|
+
try {
|
|
62
|
+
raw = bytes(join(directory, "history.jsonl"), 1024 * 1024);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (missing(error) && brandNew)
|
|
66
|
+
return [];
|
|
67
|
+
if (missing(error))
|
|
68
|
+
throw new Error("case history missing; explicit recovery required");
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
if (!raw.length || raw.at(-1) !== 10)
|
|
72
|
+
throw new Error("case history incomplete; explicit recovery required");
|
|
73
|
+
const records = raw.toString("utf8").trimEnd().split("\n").map(line => JSON.parse(line));
|
|
74
|
+
let valid = [];
|
|
75
|
+
for (const record of records) {
|
|
76
|
+
if (record.caseId !== caseId || record.decision_schema !== 1)
|
|
77
|
+
throw new Error("case history binding mismatch");
|
|
78
|
+
const { id, decision_schema: _schema, ...input } = record;
|
|
79
|
+
valid = appendWorkCaseDecision(valid, input);
|
|
80
|
+
if (!valid.some(r => r.id === id))
|
|
81
|
+
throw new Error("case history identity mismatch");
|
|
82
|
+
}
|
|
83
|
+
return valid;
|
|
84
|
+
}
|
|
85
|
+
function assertAuthor(author) {
|
|
86
|
+
if (typeof author !== "string" || !author || author.length > 512 || /[\u0000-\u001f\u007f]/.test(author))
|
|
87
|
+
throw new Error("explicit operator author required");
|
|
88
|
+
}
|
|
89
|
+
/** Operator-selected batch/author capability. Not user authentication, worker authority or scenario promotion. */
|
|
90
|
+
export function createWorkCaseReviewer(root, batchId, author) {
|
|
91
|
+
assertAuthor(author);
|
|
92
|
+
const source = readArchiveSource(root, batchId);
|
|
93
|
+
if (source.status !== "available" || source.reference.parser.id !== "work-candidate-batch" || source.reference.parser.version !== "1" || source.reference.retention !== "exact")
|
|
94
|
+
throw new Error("case batch missing or invalid");
|
|
95
|
+
const batch = JSON.parse(source.bytes.toString("utf8"));
|
|
96
|
+
if (batch.version !== "work-candidate-batch-v1" || batch.visibility !== "silent" || batch.promotion !== "not-authorized"
|
|
97
|
+
|| !Array.isArray(batch.candidateIds) || batch.candidateIds.length > 4096 || batch.candidateIds.some((id) => typeof id !== "string" || !SHA.test(id)))
|
|
98
|
+
throw new Error("invalid selected case batch");
|
|
99
|
+
return createSelectedCaseReviewer(root, author, batch.candidateIds, id => readWorkCandidate(root, id));
|
|
100
|
+
}
|
|
101
|
+
/** Explicit new signal contract; the existing v2 entry point still refuses these batches. */
|
|
102
|
+
export function createWorkSignalReviewer(root, batchId, author) {
|
|
103
|
+
assertAuthor(author);
|
|
104
|
+
const batch = readWorkSignalBatch(root, batchId);
|
|
105
|
+
const reviewer = createSelectedCaseReviewer(root, author, batch.candidateIds, id => {
|
|
106
|
+
const selected = readWorkSignalCase(root, id);
|
|
107
|
+
if (selected.observationId !== batch.observationId)
|
|
108
|
+
throw new Error("work signal case outside frozen observation");
|
|
109
|
+
return selected.candidate;
|
|
110
|
+
});
|
|
111
|
+
return { ...reviewer, list(offset = 0, limit = 5) {
|
|
112
|
+
const current = readWorkSignalBatch(root, batchId);
|
|
113
|
+
return { ...reviewer.list(offset, limit), observationId: current.observationId, issues: current.issues };
|
|
114
|
+
} };
|
|
115
|
+
}
|
|
116
|
+
function createSelectedCaseReviewer(root, author, ids, readCandidate) {
|
|
117
|
+
const allowed = new Set(ids);
|
|
118
|
+
const selected = (id) => { if (!allowed.has(id))
|
|
119
|
+
throw new Error("case outside selected batch"); return readCandidate(id); };
|
|
120
|
+
const getHistory = (caseManifestId) => {
|
|
121
|
+
const candidate = selected(caseManifestId), directory = join(root, "case-decisions", candidate.id);
|
|
122
|
+
try {
|
|
123
|
+
assertDirectory(join(root, "case-decisions"));
|
|
124
|
+
assertDirectory(directory);
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
if (missing(error))
|
|
128
|
+
return [];
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
return historyAt(directory, candidate.id);
|
|
132
|
+
};
|
|
133
|
+
return {
|
|
134
|
+
history: getHistory,
|
|
135
|
+
list(offset = 0, limit = 5) {
|
|
136
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(limit) || limit < 1 || limit > 5)
|
|
137
|
+
throw new Error("bounded case page required");
|
|
138
|
+
const ids = [...allowed].sort();
|
|
139
|
+
return { total: ids.length, offset, items: ids.slice(offset, offset + limit).map(caseManifestId => {
|
|
140
|
+
const candidate = selected(caseManifestId), current = getHistory(caseManifestId).at(-1) ?? null;
|
|
141
|
+
return { caseManifestId, candidate, priorDecisionId: current?.id ?? null, disposition: current?.disposition ?? "unresolved" };
|
|
142
|
+
}) };
|
|
143
|
+
},
|
|
144
|
+
decide(request) {
|
|
145
|
+
if (!request || Object.keys(request).sort().join() !== "caseManifestId,disposition,note,priorDecisionId"
|
|
146
|
+
|| !["confirmed_defect", "expected_behavior", "exemplar", "uncertain", "skip"].includes(request.disposition)
|
|
147
|
+
|| typeof request.note !== "string" || request.note.length > 4000
|
|
148
|
+
|| !(request.priorDecisionId === null || (typeof request.priorDecisionId === "string" && SHA.test(request.priorDecisionId))))
|
|
149
|
+
throw new Error("invalid case review request");
|
|
150
|
+
const candidate = selected(request.caseManifestId);
|
|
151
|
+
const parent = join(root, "case-decisions");
|
|
152
|
+
privateDirectory(parent);
|
|
153
|
+
const directory = join(parent, candidate.id);
|
|
154
|
+
const lockPath = join(parent, candidate.id + ".lock"), token = randomUUID();
|
|
155
|
+
const lock = openSync(lockPath, constants.O_RDWR | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
|
|
156
|
+
const identity = fstatSync(lock);
|
|
157
|
+
let primary;
|
|
158
|
+
let result;
|
|
159
|
+
try {
|
|
160
|
+
writeFileSync(lock, token);
|
|
161
|
+
fsyncSync(lock);
|
|
162
|
+
// Hold the case lock BEFORE first directory creation; concurrent first reviewers cannot poison initialization.
|
|
163
|
+
const brandNew = privateDirectory(directory);
|
|
164
|
+
const before = historyAt(directory, candidate.id, brandNew);
|
|
165
|
+
const after = appendWorkCaseDecision(before, { caseId: candidate.id, priorDecisionId: request.priorDecisionId,
|
|
166
|
+
disposition: request.disposition, author, evidence: [request.caseManifestId], note: request.note });
|
|
167
|
+
if (after.length > before.length) {
|
|
168
|
+
const path = join(directory, "history.jsonl");
|
|
169
|
+
const fd = openSync(path, constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | constants.O_NOFOLLOW | constants.O_NONBLOCK, 0o600);
|
|
170
|
+
try {
|
|
171
|
+
const stat = fstatSync(fd);
|
|
172
|
+
if (!stat.isFile() || stat.nlink !== 1 || (stat.mode & 0o077))
|
|
173
|
+
throw new Error("invalid case history destination");
|
|
174
|
+
const line = Buffer.from(JSON.stringify(after.at(-1)) + "\n");
|
|
175
|
+
if (stat.size + line.length > 1024 * 1024)
|
|
176
|
+
throw new Error("case history bound exceeded");
|
|
177
|
+
let offset = 0;
|
|
178
|
+
while (offset < line.length) {
|
|
179
|
+
const n = writeSync(fd, line, offset, line.length - offset);
|
|
180
|
+
if (!n)
|
|
181
|
+
throw new Error("case history write stalled");
|
|
182
|
+
offset += n;
|
|
183
|
+
}
|
|
184
|
+
fsyncSync(fd);
|
|
185
|
+
}
|
|
186
|
+
finally {
|
|
187
|
+
closeSync(fd);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
syncDirectory(directory);
|
|
191
|
+
syncDirectory(parent);
|
|
192
|
+
syncDirectory(root);
|
|
193
|
+
result = { current: after.at(-1), replayed: after.length === before.length };
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
primary = error;
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
try {
|
|
200
|
+
const present = lstatSync(lockPath);
|
|
201
|
+
if (present.dev !== identity.dev || present.ino !== identity.ino || bytes(lockPath, 128).toString() !== token)
|
|
202
|
+
throw new Error("case writer lock ownership lost");
|
|
203
|
+
unlinkSync(lockPath);
|
|
204
|
+
syncDirectory(parent);
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
if (!primary)
|
|
208
|
+
primary = error;
|
|
209
|
+
}
|
|
210
|
+
finally {
|
|
211
|
+
closeSync(lock);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (primary)
|
|
215
|
+
throw primary;
|
|
216
|
+
return result;
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=work-case-review.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Case records point to frozen inputs, not cached verdicts or caller-authored authority. */
|
|
2
|
+
export declare function captureWorkSignalCases(root: string, observationId: string): {
|
|
3
|
+
batchId: string;
|
|
4
|
+
observationId: string;
|
|
5
|
+
candidateIds: string[];
|
|
6
|
+
};
|
|
7
|
+
export declare function readWorkSignalCase(root: string, manifestId: string): {
|
|
8
|
+
observationId: string;
|
|
9
|
+
candidate: import("@skill-harness/core").WorkCaptureCaseV2 | import("@skill-harness/core").WorkCaptureCaseV3;
|
|
10
|
+
};
|
|
11
|
+
export declare function readWorkSignalBatch(root: string, manifestId: string): {
|
|
12
|
+
observationId: string;
|
|
13
|
+
candidateIds: string[];
|
|
14
|
+
issues: string[];
|
|
15
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { readArchiveSource, retainArchiveSource } from "./evidence-archive.js";
|
|
2
|
+
import { readWorkSignalObservation } from "./work-signal-observation.js";
|
|
3
|
+
const SHA = /^[a-f0-9]{64}$/;
|
|
4
|
+
const hash = (v) => typeof v === "string" && SHA.test(v);
|
|
5
|
+
function read(root, id, parser) {
|
|
6
|
+
const source = readArchiveSource(root, id);
|
|
7
|
+
if (source.status !== "available" || source.reference.retention !== "exact" || source.reference.parser.id !== parser || source.reference.parser.version !== "1"
|
|
8
|
+
|| source.bytes.length > 128 * 1024)
|
|
9
|
+
throw new Error("work signal case evidence unavailable or unsupported");
|
|
10
|
+
let value;
|
|
11
|
+
try {
|
|
12
|
+
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(source.bytes));
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
throw new Error("invalid work signal case JSON");
|
|
16
|
+
}
|
|
17
|
+
return { value, sourceId: source.reference.sourceId };
|
|
18
|
+
}
|
|
19
|
+
function closed(v, keys) {
|
|
20
|
+
if (!v || typeof v !== "object" || Array.isArray(v) || Object.keys(v).sort().join() !== [...keys].sort().join())
|
|
21
|
+
throw new Error("invalid closed work signal case record");
|
|
22
|
+
}
|
|
23
|
+
function retain(root, sourceId, parser, value) {
|
|
24
|
+
return retainArchiveSource(root, { sourceId, parser: { id: parser, version: "1" }, retention: "exact", bytes: Buffer.from(JSON.stringify(value)) }).manifestId;
|
|
25
|
+
}
|
|
26
|
+
/** Case records point to frozen inputs, not cached verdicts or caller-authored authority. */
|
|
27
|
+
export function captureWorkSignalCases(root, observationId) {
|
|
28
|
+
const observation = readWorkSignalObservation(root, observationId);
|
|
29
|
+
const candidateIds = observation.detection.cases.map(candidate => retain(root, `work-signal-case-${candidate.id}`, "work-signal-case", { version: "work-signal-case-v1", observationId, caseId: candidate.id }));
|
|
30
|
+
const batchId = retain(root, `work-signal-batch-${observationId}`, "work-signal-batch", { version: "work-signal-batch-v1", observationId, candidateIds, visibility: "silent", promotion: "not-authorized" });
|
|
31
|
+
return { batchId, observationId, candidateIds };
|
|
32
|
+
}
|
|
33
|
+
export function readWorkSignalCase(root, manifestId) {
|
|
34
|
+
const { value, sourceId } = read(root, manifestId, "work-signal-case");
|
|
35
|
+
closed(value, ["version", "observationId", "caseId"]);
|
|
36
|
+
if (value.version !== "work-signal-case-v1" || !hash(value.observationId) || !hash(value.caseId) || sourceId !== `work-signal-case-${value.caseId}`)
|
|
37
|
+
throw new Error("work signal case binding mismatch");
|
|
38
|
+
const observation = readWorkSignalObservation(root, value.observationId);
|
|
39
|
+
const candidate = observation.detection.cases.find(c => c.id === value.caseId);
|
|
40
|
+
if (!candidate)
|
|
41
|
+
throw new Error("case is not nominated by frozen work signal inputs");
|
|
42
|
+
return { observationId: value.observationId, candidate };
|
|
43
|
+
}
|
|
44
|
+
export function readWorkSignalBatch(root, manifestId) {
|
|
45
|
+
const { value, sourceId } = read(root, manifestId, "work-signal-batch");
|
|
46
|
+
closed(value, ["version", "observationId", "candidateIds", "visibility", "promotion"]);
|
|
47
|
+
if (value.version !== "work-signal-batch-v1" || !hash(value.observationId) || sourceId !== `work-signal-batch-${value.observationId}`
|
|
48
|
+
|| value.visibility !== "silent" || value.promotion !== "not-authorized" || !Array.isArray(value.candidateIds)
|
|
49
|
+
|| value.candidateIds.length > 1024 || !value.candidateIds.every(hash) || new Set(value.candidateIds).size !== value.candidateIds.length)
|
|
50
|
+
throw new Error("invalid work signal batch binding");
|
|
51
|
+
const observation = readWorkSignalObservation(root, value.observationId);
|
|
52
|
+
return { observationId: value.observationId, candidateIds: value.candidateIds, issues: observation.detection.issues };
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=work-signal-cases.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type WorkSignalFacts, type WorkSignalSnapshot } from "@skill-harness/core";
|
|
2
|
+
interface WorkSignalInput {
|
|
3
|
+
observation_schema: "work-signal-observation-v1";
|
|
4
|
+
snapshot: WorkSignalSnapshot;
|
|
5
|
+
facts: WorkSignalFacts;
|
|
6
|
+
}
|
|
7
|
+
/** Explicit private host-fact retention, not automatic discovery, authenticated authority or case promotion. */
|
|
8
|
+
export declare function retainWorkSignalObservation(root: string, snapshot: WorkSignalSnapshot, facts: WorkSignalFacts): {
|
|
9
|
+
manifestId: string;
|
|
10
|
+
inputSha256: string;
|
|
11
|
+
};
|
|
12
|
+
/** Re-derive, never trust a cached nomination. Missing/corrupt inputs fail rather than becoming empty work. */
|
|
13
|
+
export declare function readWorkSignalObservation(root: string, manifestId: string): {
|
|
14
|
+
manifestId: string;
|
|
15
|
+
inputSha256: string;
|
|
16
|
+
input: WorkSignalInput;
|
|
17
|
+
detection: {
|
|
18
|
+
version: "work-signals-v1";
|
|
19
|
+
cases: (import("@skill-harness/core").WorkCaptureCaseV2 | import("@skill-harness/core").WorkCaptureCaseV3)[];
|
|
20
|
+
issues: string[];
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { detectAdditionalWorkCases } from "@skill-harness/core";
|
|
2
|
+
import { readArchiveSource, retainArchiveSource } from "./evidence-archive.js";
|
|
3
|
+
const LIMIT = 1024 * 1024;
|
|
4
|
+
function closed(value, keys) {
|
|
5
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value)))
|
|
6
|
+
throw new Error("closed work signal object required");
|
|
7
|
+
const descriptors = Object.getOwnPropertyDescriptors(value), names = Reflect.ownKeys(value);
|
|
8
|
+
if (names.length !== keys.length || names.some(k => typeof k !== "string" || !keys.includes(k) || !descriptors[k].enumerable || !Object.hasOwn(descriptors[k], "value")))
|
|
9
|
+
throw new Error("closed work signal fields required");
|
|
10
|
+
}
|
|
11
|
+
function array(value) {
|
|
12
|
+
if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype || value.length > 256
|
|
13
|
+
|| Reflect.ownKeys(value).length !== value.length + 1)
|
|
14
|
+
throw new Error("bounded dense work signal array required");
|
|
15
|
+
for (let i = 0; i < value.length; i++) {
|
|
16
|
+
const d = Object.getOwnPropertyDescriptor(value, String(i));
|
|
17
|
+
if (!d || !d.enumerable || !Object.hasOwn(d, "value"))
|
|
18
|
+
throw new Error("dense work signal data array required");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// Only called after closed primitive/shape validation. Arrays retain observation order.
|
|
22
|
+
function canonical(value) {
|
|
23
|
+
if (Array.isArray(value))
|
|
24
|
+
return `[${value.map(canonical).join(",")}]`;
|
|
25
|
+
if (value !== null && typeof value === "object")
|
|
26
|
+
return `{${Object.keys(value).sort().map(k => `${JSON.stringify(k)}:${canonical(value[k])}`).join(",")}}`;
|
|
27
|
+
return JSON.stringify(value);
|
|
28
|
+
}
|
|
29
|
+
function validate(value) {
|
|
30
|
+
closed(value, ["observation_schema", "snapshot", "facts"]);
|
|
31
|
+
if (value.observation_schema !== "work-signal-observation-v1")
|
|
32
|
+
throw new Error("unsupported work signal observation");
|
|
33
|
+
closed(value.snapshot, ["snapshotDigest", "scopeValid", "obligations"]);
|
|
34
|
+
closed(value.facts, ["scopeDigest", "version", "population", "expectedWaits", "checkpoints", "violations", "priorAccepted"]);
|
|
35
|
+
const s = value.snapshot, f = value.facts;
|
|
36
|
+
if (typeof s.scopeValid !== "boolean")
|
|
37
|
+
throw new Error("invalid work signal scope state");
|
|
38
|
+
array(s.obligations);
|
|
39
|
+
array(f.expectedWaits);
|
|
40
|
+
array(f.checkpoints);
|
|
41
|
+
array(f.violations);
|
|
42
|
+
array(f.priorAccepted);
|
|
43
|
+
const seen = new Set();
|
|
44
|
+
for (const o of s.obligations) {
|
|
45
|
+
closed(o, ["id", "digest", "intentDigest", "policyDigest", "artifactDigest", "acceptance", "coverage"]);
|
|
46
|
+
if (seen.has(o.digest))
|
|
47
|
+
throw new Error("duplicate work signal obligation identity");
|
|
48
|
+
seen.add(o.digest);
|
|
49
|
+
}
|
|
50
|
+
for (const c of f.checkpoints)
|
|
51
|
+
closed(c, ["obligationDigest", "deadlineMs", "observedAt", "status", "evidence"]);
|
|
52
|
+
for (const v of f.violations)
|
|
53
|
+
closed(v, ["obligationDigest", "status", "evidence"]);
|
|
54
|
+
for (const p of f.priorAccepted)
|
|
55
|
+
closed(p, ["obligationDigest", "intentDigest", "policyDigest", "artifactDigest", "acceptanceEvidence"]);
|
|
56
|
+
const input = value;
|
|
57
|
+
// Shared rules validate scalar types, exact scope, bounded timestamps, enums and evidence identities.
|
|
58
|
+
detectAdditionalWorkCases(input.snapshot, input.facts);
|
|
59
|
+
return input;
|
|
60
|
+
}
|
|
61
|
+
/** Explicit private host-fact retention, not automatic discovery, authenticated authority or case promotion. */
|
|
62
|
+
export function retainWorkSignalObservation(root, snapshot, facts) {
|
|
63
|
+
const input = validate({ observation_schema: "work-signal-observation-v1", snapshot, facts });
|
|
64
|
+
const bytes = Buffer.from(canonical(input));
|
|
65
|
+
if (bytes.length > LIMIT)
|
|
66
|
+
throw new Error("work signal observation exceeds byte bound");
|
|
67
|
+
const stored = retainArchiveSource(root, { sourceId: `work-signals-${snapshot.snapshotDigest}`, parser: { id: "work-signal-observation", version: "1" }, retention: "exact", bytes });
|
|
68
|
+
return { manifestId: stored.manifestId, inputSha256: stored.reference.sha256 };
|
|
69
|
+
}
|
|
70
|
+
/** Re-derive, never trust a cached nomination. Missing/corrupt inputs fail rather than becoming empty work. */
|
|
71
|
+
export function readWorkSignalObservation(root, manifestId) {
|
|
72
|
+
const stored = readArchiveSource(root, manifestId);
|
|
73
|
+
if (stored.status !== "available" || stored.reference.retention !== "exact" || stored.reference.parser.id !== "work-signal-observation"
|
|
74
|
+
|| stored.reference.parser.version !== "1" || stored.bytes.length > LIMIT)
|
|
75
|
+
throw new Error("work signal observation unavailable or unsupported");
|
|
76
|
+
let text, decoded;
|
|
77
|
+
try {
|
|
78
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(stored.bytes);
|
|
79
|
+
decoded = JSON.parse(text);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
throw new Error("invalid work signal observation JSON");
|
|
83
|
+
} // No raw byte excerpts in diagnostics.
|
|
84
|
+
const input = validate(decoded);
|
|
85
|
+
if (canonical(input) !== text || stored.reference.sourceId !== `work-signals-${input.snapshot.snapshotDigest}`)
|
|
86
|
+
throw new Error("work signal observation binding mismatch");
|
|
87
|
+
return { manifestId, inputSha256: stored.reference.sha256, input, detection: detectAdditionalWorkCases(input.snapshot, input.facts) };
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=work-signal-observation.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skill-harness/adapters",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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,6 +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.13.0",
|
|
44
|
+
"typebox": "1.3.4"
|
|
44
45
|
}
|
|
45
46
|
}
|