@skill-harness/adapters 0.10.0 → 0.12.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/README.md +2 -1
- package/dist/closed-schema.d.ts +6 -0
- package/dist/closed-schema.js +70 -14
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/pi-daddy-ledger-v2.d.ts +4 -4
- package/dist/pi-daddy-ledger-v2.js +9 -7
- package/dist/pi-daddy-ledger-v3.d.ts +683 -0
- package/dist/pi-daddy-ledger-v3.js +961 -0
- package/dist/pi-json.d.ts +12 -0
- package/dist/pi-json.js +6 -2
- package/dist/pi.js +171 -30
- package/dist/prompt-capture-extension.d.ts +10 -0
- package/dist/prompt-capture-extension.js +26 -0
- package/dist/prompt-provenance.d.ts +22 -0
- package/dist/prompt-provenance.js +146 -0
- package/dist/trajectory.d.ts +15 -9
- package/dist/trajectory.js +375 -55
- package/package.json +3 -3
package/dist/trajectory.js
CHANGED
|
@@ -2,8 +2,9 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { readFileSync, readdirSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { TRAJECTORY_EVENT_VERSION, deserializeTrajectoryEvents, matchesGlob, redactArgs, redactText } from "@skill-harness/core";
|
|
5
|
-
import { assertSupportedSchema, declaredPropertyNames, validateClosedSchema } from "./closed-schema.js";
|
|
5
|
+
import { assertSupportedSchema, assertSupportedSchemaV3, declaredPropertyNames, validateClosedSchema, validateClosedSchemaV3, } from "./closed-schema.js";
|
|
6
6
|
import { PI_DADDY_CONTRACT_COMMIT, PI_DADDY_LEDGER_V2_SCHEMA } from "./pi-daddy-ledger-v2.js";
|
|
7
|
+
import { PI_DADDY_LEDGER_V3_CONTRACT_COMMIT, PI_DADDY_LEDGER_V3_SCHEMA } from "./pi-daddy-ledger-v3.js";
|
|
7
8
|
/** Read and normalize declared workspace-local native ledger files. */
|
|
8
9
|
export function collectTrajectorySources(cwd, sources) {
|
|
9
10
|
const files = walkFiles(cwd);
|
|
@@ -29,15 +30,19 @@ export function collectTrajectorySources(cwd, sources) {
|
|
|
29
30
|
const normalized = source.adapter === "principal-assurance-v1"
|
|
30
31
|
? normalizePrincipalAssuranceLedger(text)
|
|
31
32
|
: source.adapter === "pi-daddy-v1"
|
|
32
|
-
?
|
|
33
|
-
:
|
|
33
|
+
? normalizePiDaddyLegacyLedger(text)
|
|
34
|
+
: source.adapter === "pi-daddy-ledger-v3"
|
|
35
|
+
? normalizePiDaddyLedgerV3(text)
|
|
36
|
+
: deserializeTrajectoryEvents(text);
|
|
34
37
|
if (!normalized)
|
|
35
38
|
throw new Error("normalized-v1 source is empty, malformed, or unsupported");
|
|
36
39
|
const times = normalized.map((event) => validTime(event.at) ? Date.parse(event.at) : null);
|
|
37
40
|
if (times.every((time) => time !== null)) {
|
|
38
41
|
const highWaterByStream = new Map();
|
|
39
42
|
for (let index = 0; index < times.length; index += 1) {
|
|
40
|
-
const stream = source.adapter === "pi-daddy-v1"
|
|
43
|
+
const stream = source.adapter === "pi-daddy-v1" || source.adapter === "pi-daddy-ledger-v3"
|
|
44
|
+
? normalizedPiDaddyStreamKey(normalized[index], index)
|
|
45
|
+
: "source";
|
|
41
46
|
const highWater = highWaterByStream.get(stream);
|
|
42
47
|
if (highWater !== undefined && times[index] < highWater && !isAllowedPiDaddyReceiptInversion(source.adapter, normalized, index)) {
|
|
43
48
|
throw new Error("native event timestamps move backwards relative to the source's recorded sequence");
|
|
@@ -137,6 +142,26 @@ export function normalizePiTraces(traces) {
|
|
|
137
142
|
}
|
|
138
143
|
return events;
|
|
139
144
|
}
|
|
145
|
+
const PRINCIPAL_ASSURANCE_SOURCES = new Set([
|
|
146
|
+
"default", "flag", "alias", "natural-language", "policy", "user", "user-downgrade",
|
|
147
|
+
]);
|
|
148
|
+
function validatePrincipalAssurance(record, line) {
|
|
149
|
+
if (record.assurance === undefined)
|
|
150
|
+
return;
|
|
151
|
+
const assurance = object(record.assurance);
|
|
152
|
+
if (!assurance || typeof assurance.source !== "string" || !PRINCIPAL_ASSURANCE_SOURCES.has(assurance.source)) {
|
|
153
|
+
throw new Error(`invalid principal assurance v1 event at line ${line}: assurance.source is not a recognized source`);
|
|
154
|
+
}
|
|
155
|
+
const scope = object(assurance.scope);
|
|
156
|
+
if (!scope || Object.keys(scope).some((key) => key !== "type" && key !== "selectors") ||
|
|
157
|
+
(scope.type !== "entire-run" && scope.type !== "selectors") || !Array.isArray(scope.selectors) ||
|
|
158
|
+
scope.selectors.some((selector) => typeof selector !== "string" || !selector) ||
|
|
159
|
+
new Set(scope.selectors).size !== scope.selectors.length ||
|
|
160
|
+
(scope.type === "entire-run" && scope.selectors.length !== 0) ||
|
|
161
|
+
(scope.type === "selectors" && scope.selectors.length === 0)) {
|
|
162
|
+
throw new Error(`invalid principal assurance v1 event at line ${line}: assurance.scope is not a closed structured scope`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
140
165
|
/** Normalize principal-pi-skills' immutable assurance event schema v1.0. */
|
|
141
166
|
export function normalizePrincipalAssuranceLedger(text) {
|
|
142
167
|
const records = parseJsonl(text, "principal assurance");
|
|
@@ -148,6 +173,7 @@ export function normalizePrincipalAssuranceLedger(text) {
|
|
|
148
173
|
if (!Number.isInteger(record.seq) || Number(record.seq) < 1 || typeof record.type !== "string" || typeof record.run_id !== "string") {
|
|
149
174
|
throw new Error(`invalid principal assurance v1 event at line ${index + 1}: seq, type, and run_id are required`);
|
|
150
175
|
}
|
|
176
|
+
validatePrincipalAssurance(record, index + 1);
|
|
151
177
|
const packet = object(record.packet);
|
|
152
178
|
const definitionDigests = object(packet?.definition_digests);
|
|
153
179
|
const definition = typeof record.definition_digest === "string"
|
|
@@ -184,10 +210,28 @@ export function normalizePrincipalAssuranceLedger(text) {
|
|
|
184
210
|
});
|
|
185
211
|
}
|
|
186
212
|
/**
|
|
187
|
-
* Normalize pi-daddy's public ledgers: unversioned 0.17 GrantRecord lines
|
|
188
|
-
* ledgerVersion 2
|
|
189
|
-
*
|
|
213
|
+
* Normalize pi-daddy's public ledgers: unversioned 0.17 GrantRecord lines,
|
|
214
|
+
* frozen ledgerVersion 2 events, and separately pinned production ledgerVersion
|
|
215
|
+
* 3 events. Explicit version dispatch precedes legacy fallback, so an unknown or
|
|
216
|
+
* malformed new record is never reinterpreted as an old grant.
|
|
190
217
|
*/
|
|
218
|
+
/** Historical selector behavior: unversioned 0.17 and frozen ledger v2 only. */
|
|
219
|
+
export function normalizePiDaddyLegacyLedger(text) {
|
|
220
|
+
const records = parseJsonl(text, "pi-daddy");
|
|
221
|
+
const explicitV3 = records.findIndex((record) => record.ledgerVersion === 3);
|
|
222
|
+
if (explicitV3 >= 0)
|
|
223
|
+
throw new Error(`pi-daddy-v1 selector does not admit ledgerVersion 3 at line ${explicitV3 + 1}; use pi-daddy-ledger-v3`);
|
|
224
|
+
return normalizePiDaddyLedger(text);
|
|
225
|
+
}
|
|
226
|
+
/** Separately versioned selector: every line must be an explicit ledgerVersion 3 event. */
|
|
227
|
+
export function normalizePiDaddyLedgerV3(text) {
|
|
228
|
+
const records = parseJsonl(text, "pi-daddy");
|
|
229
|
+
const wrong = records.findIndex((record) => record.ledgerVersion !== 3);
|
|
230
|
+
if (wrong >= 0)
|
|
231
|
+
throw new Error(`pi-daddy-ledger-v3 requires explicit ledgerVersion 3 at line ${wrong + 1}`);
|
|
232
|
+
return normalizePiDaddyLedger(text);
|
|
233
|
+
}
|
|
234
|
+
/** Programmatic mixed-version dispatcher; source declarations use explicit selectors above. */
|
|
191
235
|
export function normalizePiDaddyLedger(text) {
|
|
192
236
|
const records = parseJsonl(text, "pi-daddy");
|
|
193
237
|
validatePiDaddyTimestampOrder(records);
|
|
@@ -195,23 +239,30 @@ export function normalizePiDaddyLedger(text) {
|
|
|
195
239
|
let seq = 1;
|
|
196
240
|
records.forEach((record, index) => {
|
|
197
241
|
if (record.ledgerVersion !== undefined) {
|
|
198
|
-
if (record.ledgerVersion
|
|
199
|
-
|
|
242
|
+
if (record.ledgerVersion === 2) {
|
|
243
|
+
// The discriminator first (it names the four public variants), then the
|
|
244
|
+
// producer's own closed schema, then semantic normalization. Nothing
|
|
245
|
+
// downstream may assume a field the pinned contract has not admitted.
|
|
246
|
+
requireV2Discriminator(record, index + 1);
|
|
247
|
+
assertPinnedV2Contract(record, index + 1);
|
|
248
|
+
for (const event of normalizePiDaddyV2(record, index))
|
|
249
|
+
out.push({ ...event, seq: seq++ });
|
|
250
|
+
return;
|
|
200
251
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
252
|
+
if (record.ledgerVersion === 3) {
|
|
253
|
+
requireV3Discriminator(record, index + 1);
|
|
254
|
+
assertPinnedV3Contract(record, index + 1);
|
|
255
|
+
for (const event of normalizePiDaddyV3(record, index))
|
|
256
|
+
out.push({ ...event, seq: seq++ });
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
throw new Error(`unsupported pi-daddy ledgerVersion ${safeDiagnosticValue(record.ledgerVersion)} at line ${index + 1}; expected 2, 3, or an unversioned 0.17 GrantRecord`);
|
|
209
260
|
}
|
|
210
261
|
if (record.schema_version !== undefined) {
|
|
211
|
-
throw new Error(`pi-daddy schema_version/record_type at line ${index + 1} is not a public pi-daddy ledger format; expected ledgerVersion 2 or an unversioned 0.17 GrantRecord`);
|
|
262
|
+
throw new Error(`pi-daddy schema_version/record_type at line ${index + 1} is not a public pi-daddy ledger format; expected ledgerVersion 2, ledgerVersion 3, or an unversioned 0.17 GrantRecord`);
|
|
212
263
|
}
|
|
213
264
|
if (record.event !== undefined) {
|
|
214
|
-
throw new Error(`pi-daddy event [REDACTED invalid value] at line ${index + 1} is missing explicit ledgerVersion 2`);
|
|
265
|
+
throw new Error(`pi-daddy event [REDACTED invalid value] at line ${index + 1} is missing explicit ledgerVersion 2 or 3`);
|
|
215
266
|
}
|
|
216
267
|
for (const event of normalizeLegacyGrant(record, index))
|
|
217
268
|
out.push({ ...event, seq: seq++ });
|
|
@@ -257,6 +308,33 @@ function assertPinnedV2Contract(record, line) {
|
|
|
257
308
|
throw new Error(`invalid pi-daddy v2 ${label} at line ${line}: closed contract violation — ${first.path ? `${first.path} ` : ""}${first.message}${extra}` +
|
|
258
309
|
` [pi-daddy ${PI_DADDY_CONTRACT_COMMIT.slice(0, 12)}]`);
|
|
259
310
|
}
|
|
311
|
+
/** The five public `ledgerVersion: 3` event discriminators. */
|
|
312
|
+
const V3_EVENTS = new Set(["capability_decision", "workspace_lease", "child_lifecycle", "check_receipt", "workflow_fact"]);
|
|
313
|
+
function requireV3Discriminator(record, line) {
|
|
314
|
+
const nativeEvent = string(record.event);
|
|
315
|
+
if (!nativeEvent || !V3_EVENTS.has(nativeEvent)) {
|
|
316
|
+
throw new Error(`invalid pi-daddy v3 event at line ${line}: event discriminator is required and must be capability_decision, workspace_lease, child_lifecycle, check_receipt, or workflow_fact`);
|
|
317
|
+
}
|
|
318
|
+
return nativeEvent;
|
|
319
|
+
}
|
|
320
|
+
let pinnedV3ContractChecked = false;
|
|
321
|
+
let pinnedV3ContractFieldNames;
|
|
322
|
+
function assertPinnedV3Contract(record, line) {
|
|
323
|
+
if (!pinnedV3ContractChecked) {
|
|
324
|
+
assertSupportedSchemaV3(PI_DADDY_LEDGER_V3_SCHEMA, "pinned pi-daddy ledger v3 schema");
|
|
325
|
+
pinnedV3ContractFieldNames = declaredPropertyNames(PI_DADDY_LEDGER_V3_SCHEMA);
|
|
326
|
+
pinnedV3ContractChecked = true;
|
|
327
|
+
}
|
|
328
|
+
const violations = validateClosedSchemaV3(PI_DADDY_LEDGER_V3_SCHEMA, record, { knownFieldNames: pinnedV3ContractFieldNames });
|
|
329
|
+
if (violations.length === 0)
|
|
330
|
+
return;
|
|
331
|
+
const nativeEvent = string(record.event);
|
|
332
|
+
const label = nativeEvent && V3_EVENTS.has(nativeEvent) ? nativeEvent : "record";
|
|
333
|
+
const [first] = violations;
|
|
334
|
+
const extra = violations.length > 1 ? ` (+${violations.length - 1} more contract violation${violations.length > 2 ? "s" : ""})` : "";
|
|
335
|
+
throw new Error(`invalid pi-daddy v3 ${label} at line ${line}: closed contract violation — ${first.path ? `${first.path} ` : ""}${first.message}${extra}` +
|
|
336
|
+
` [pi-daddy ${PI_DADDY_LEDGER_V3_CONTRACT_COMMIT.slice(0, 12)}]`);
|
|
337
|
+
}
|
|
260
338
|
const V2_LEASE_OUTCOMES = new Set([
|
|
261
339
|
"acquired", "uncontended", "refused", "released", "released-unrecorded", "lost", "retained", "timeout", "recovered",
|
|
262
340
|
]);
|
|
@@ -281,23 +359,14 @@ const V2_CORRELATION_NUMERIC_FIELDS = new Set(["event_seq", "last_change_seq", "
|
|
|
281
359
|
const V2_APPROVAL_SOURCES = new Set(["prompt", "session", "persisted", "inherited"]);
|
|
282
360
|
const V2_APPROVAL_SCOPES = new Set(["once", "session", "always"]);
|
|
283
361
|
/**
|
|
284
|
-
* pi-daddy's canonical refusal vocabulary,
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
* exactly how `GRANT_ID_MALFORMED` came to be rejected as "unsupported".
|
|
362
|
+
* pi-daddy's canonical refusal vocabulary, derived directly from the immutable
|
|
363
|
+
* pinned schema. The producer's `src/refusals.ts` is vendored and digest-checked
|
|
364
|
+
* separately, while the real-builder verifier proves its production export is
|
|
365
|
+
* set-equal to this schema-derived runtime set. There is no second list here to
|
|
366
|
+
* forget when pi-daddy adds a contract-valid code.
|
|
290
367
|
*/
|
|
291
|
-
export const V2_REFUSAL_CODES = new Set(
|
|
292
|
-
|
|
293
|
-
"GATED_UNAPPROVED", "APPROVAL_EXPIRED", "APPROVAL_SCOPE_MISMATCH", "APPROVAL_FLOW_FAILED",
|
|
294
|
-
"DEPTH_EXCEEDED", "FANOUT_EXCEEDED", "EXECUTOR_UNAVAILABLE", "CHILD_TIMED_OUT", "CHILD_CANCELLED",
|
|
295
|
-
"CHILD_EXIT_NONZERO", "TASK_MISSING", "UNKNOWN_DEFINITION", "CEILING_PATTERNS_UNRESOLVED",
|
|
296
|
-
"NARROWING_VIOLATED", "DEFINITION_UNREADABLE", "CORRELATION_TOO_LARGE", "CORRELATION_INVALID",
|
|
297
|
-
"LEDGER_WRITE_FAILED", "FANOUT_FAILED", "WORKSPACE_NOT_REGISTERED", "WORKSPACE_WRITE_CONFLICT",
|
|
298
|
-
"WORKSPACE_LEASE_STALE", "CHECK_NOT_CONFIGURED", "CHECK_CONFIGURATION_INVALID",
|
|
299
|
-
"CHECK_IDENTITY_UNAVAILABLE", "CHECK_IDENTITY_MISMATCH",
|
|
300
|
-
]);
|
|
368
|
+
export const V2_REFUSAL_CODES = new Set(PI_DADDY_LEDGER_V2_SCHEMA.$defs.refusalCode.enum);
|
|
369
|
+
export const V3_REFUSAL_CODES = new Set(PI_DADDY_LEDGER_V3_SCHEMA.$defs.refusalCode.enum);
|
|
301
370
|
/**
|
|
302
371
|
* Every vocabulary the adapter restates from the pinned contract, paired with the
|
|
303
372
|
* place in the schema it must equal.
|
|
@@ -340,6 +409,11 @@ const V2_CORRELATION_MAX_SCOPE_BYTES = 4 * 1024;
|
|
|
340
409
|
function piDaddyStreamKey(record, index) {
|
|
341
410
|
if (record.ledgerVersion === undefined)
|
|
342
411
|
return JSON.stringify(["legacy", string(record.childId) ?? `missing-child:${index}`]);
|
|
412
|
+
if (record.ledgerVersion === 3) {
|
|
413
|
+
return record.event === "workflow_fact"
|
|
414
|
+
? JSON.stringify(["v3-fact", string(record.factId) ?? `missing-fact:${index}`])
|
|
415
|
+
: JSON.stringify(["v3-execution", string(record.executionId) ?? `missing-execution:${index}`]);
|
|
416
|
+
}
|
|
343
417
|
const correlation = object(record.correlation);
|
|
344
418
|
return JSON.stringify([
|
|
345
419
|
string(correlation?.run_id) ?? `missing-run:${index}`,
|
|
@@ -351,6 +425,11 @@ function piDaddyStreamKey(record, index) {
|
|
|
351
425
|
function normalizedPiDaddyStreamKey(event, index) {
|
|
352
426
|
if (event.source === "pi-daddy-0.17")
|
|
353
427
|
return JSON.stringify(["legacy", event.child_id ?? `missing-child:${index}`]);
|
|
428
|
+
if (event.source === "pi-daddy-v3") {
|
|
429
|
+
return event.workflow_fact_id
|
|
430
|
+
? JSON.stringify(["v3-fact", event.workflow_fact_id])
|
|
431
|
+
: JSON.stringify(["v3-execution", event.execution_id ?? `missing-execution:${index}`]);
|
|
432
|
+
}
|
|
354
433
|
const correlation = object(event.attributes?.correlation);
|
|
355
434
|
return JSON.stringify([
|
|
356
435
|
event.run_id ?? `missing-run:${index}`,
|
|
@@ -368,9 +447,9 @@ function sameRawCorrelationIdentity(left, right) {
|
|
|
368
447
|
function validatePiDaddyTimestampOrder(records) {
|
|
369
448
|
const highWaterByChild = new Map();
|
|
370
449
|
records.forEach((record, index) => {
|
|
371
|
-
const
|
|
450
|
+
const supportedVersion = record.ledgerVersion === 2 || record.ledgerVersion === 3;
|
|
372
451
|
const legacy = record.ledgerVersion === undefined && record.schema_version === undefined && record.event === undefined;
|
|
373
|
-
if (!
|
|
452
|
+
if (!supportedVersion && !legacy)
|
|
374
453
|
return;
|
|
375
454
|
const at = string(record.ts);
|
|
376
455
|
if (!validTime(at))
|
|
@@ -387,17 +466,17 @@ function validatePiDaddyTimestampOrder(records) {
|
|
|
387
466
|
function isRawPiDaddyReceiptInversion(records, index, receiptTime) {
|
|
388
467
|
const receipt = records[index];
|
|
389
468
|
const release = records[index - 1];
|
|
390
|
-
if (receipt?.ledgerVersion !== 2 || receipt.event !== "check_receipt" || release?.ledgerVersion !==
|
|
469
|
+
if ((receipt?.ledgerVersion !== 2 && receipt?.ledgerVersion !== 3) || receipt.event !== "check_receipt" || release?.ledgerVersion !== receipt.ledgerVersion || release.event !== "workspace_lease")
|
|
391
470
|
return false;
|
|
392
471
|
if (receipt.childId !== release.childId || receipt.workspaceId !== release.workspaceId || !sameRawCorrelationIdentity(receipt, release) || !V2_RECEIPT_RELEASE_OUTCOMES.has(string(release.outcome) ?? ""))
|
|
393
472
|
return false;
|
|
394
|
-
const previousLease = records.slice(0, index - 1).reverse().find((record) => record.ledgerVersion ===
|
|
473
|
+
const previousLease = records.slice(0, index - 1).reverse().find((record) => record.ledgerVersion === receipt.ledgerVersion && record.event === "workspace_lease" && record.childId === receipt.childId &&
|
|
395
474
|
record.workspaceId === receipt.workspaceId && sameRawCorrelationIdentity(receipt, record));
|
|
396
475
|
return Boolean(previousLease && V2_RECEIPT_PRIOR_LEASE_OUTCOMES.has(string(previousLease.outcome) ?? "") &&
|
|
397
476
|
validTime(string(previousLease.ts)) && Date.parse(string(previousLease.ts)) <= receiptTime);
|
|
398
477
|
}
|
|
399
478
|
function isAllowedPiDaddyReceiptInversion(adapter, events, index) {
|
|
400
|
-
if (adapter !== "pi-daddy-v1")
|
|
479
|
+
if (adapter !== "pi-daddy-v1" && adapter !== "pi-daddy-ledger-v3")
|
|
401
480
|
return false;
|
|
402
481
|
const receipt = events[index];
|
|
403
482
|
const release = events[index - 1];
|
|
@@ -662,6 +741,246 @@ function normalizePiDaddyV2(record, index) {
|
|
|
662
741
|
}),
|
|
663
742
|
})];
|
|
664
743
|
}
|
|
744
|
+
function normalizePiDaddyV3(record, index) {
|
|
745
|
+
const line = index + 1;
|
|
746
|
+
const nativeEvent = requireV3Discriminator(record, line);
|
|
747
|
+
const at = requireV3String(record, "ts", nativeEvent, line);
|
|
748
|
+
const correlation = object(record.correlation) ?? {};
|
|
749
|
+
const correlationDigests = anyDefined({
|
|
750
|
+
correlation_plan: string(correlation.plan_digest),
|
|
751
|
+
correlation_task: string(correlation.task_digest),
|
|
752
|
+
correlation_definition: string(correlation.definition_digest),
|
|
753
|
+
correlation_base: string(correlation.base_sha),
|
|
754
|
+
correlation_head: string(correlation.head_sha),
|
|
755
|
+
correlation_tree: string(correlation.tree_sha),
|
|
756
|
+
});
|
|
757
|
+
const correlationAttributes = safeAttributes({
|
|
758
|
+
ledger_version: 3,
|
|
759
|
+
native_event: nativeEvent,
|
|
760
|
+
correlation: Object.keys(correlation).length ? sanitizeAttributes(correlation) : undefined,
|
|
761
|
+
event_seq: finiteNumber(correlation.event_seq),
|
|
762
|
+
last_change_seq: finiteNumber(correlation.last_change_seq),
|
|
763
|
+
last_authority_seq: finiteNumber(correlation.last_authority_seq),
|
|
764
|
+
check_receipt_id: string(correlation.check_receipt_id),
|
|
765
|
+
assurance: string(correlation.assurance),
|
|
766
|
+
assurance_effective: string(correlation.assurance_effective),
|
|
767
|
+
policy_label: string(correlation.policy_label),
|
|
768
|
+
assurance_source: string(correlation.assurance_source),
|
|
769
|
+
assurance_scope: correlation.assurance_scope,
|
|
770
|
+
activated_at: string(correlation.activated_at),
|
|
771
|
+
});
|
|
772
|
+
if (nativeEvent === "workflow_fact") {
|
|
773
|
+
return [cleanEvent({
|
|
774
|
+
event_version: TRAJECTORY_EVENT_VERSION,
|
|
775
|
+
type: "workflow_fact",
|
|
776
|
+
source: "pi-daddy-v3",
|
|
777
|
+
at,
|
|
778
|
+
run_id: string(correlation.run_id),
|
|
779
|
+
task_id: string(correlation.task_id),
|
|
780
|
+
workspace_id: string(correlation.workspace_id),
|
|
781
|
+
context_id: string(correlation.context_id),
|
|
782
|
+
phase: string(correlation.phase),
|
|
783
|
+
workflow_fact_id: requireV3String(record, "factId", nativeEvent, line),
|
|
784
|
+
digests: correlationDigests,
|
|
785
|
+
attributes: safeAttributes({
|
|
786
|
+
...correlationAttributes,
|
|
787
|
+
source: string(record.source),
|
|
788
|
+
provenance: string(record.provenance),
|
|
789
|
+
fact_kind: string(record.kind),
|
|
790
|
+
fact_subject: string(record.subject),
|
|
791
|
+
fact_state: string(record.state),
|
|
792
|
+
}),
|
|
793
|
+
})];
|
|
794
|
+
}
|
|
795
|
+
const executionId = requireV3String(record, "executionId", nativeEvent, line);
|
|
796
|
+
const parentExecutionId = record.parentExecutionId === null ? null : requireV3String(record, "parentExecutionId", nativeEvent, line);
|
|
797
|
+
if (parentExecutionId === executionId)
|
|
798
|
+
throw new Error(`invalid pi-daddy v3 ${nativeEvent} at line ${line}: an execution cannot be its own parent`);
|
|
799
|
+
const childId = requireV3String(record, "childId", nativeEvent, line);
|
|
800
|
+
const carriesTopWorkspace = nativeEvent === "workspace_lease" || nativeEvent === "check_receipt";
|
|
801
|
+
const topWorkspace = carriesTopWorkspace ? string(record.workspaceId) : undefined;
|
|
802
|
+
const correlationWorkspace = string(correlation.workspace_id);
|
|
803
|
+
if (topWorkspace && correlationWorkspace && topWorkspace !== correlationWorkspace) {
|
|
804
|
+
throw new Error(`invalid pi-daddy v3 ${nativeEvent} at line ${line}: workspaceId disagrees with correlation.workspace_id`);
|
|
805
|
+
}
|
|
806
|
+
const common = {
|
|
807
|
+
event_version: TRAJECTORY_EVENT_VERSION,
|
|
808
|
+
source: "pi-daddy-v3",
|
|
809
|
+
at,
|
|
810
|
+
run_id: string(correlation.run_id),
|
|
811
|
+
task_id: string(correlation.task_id),
|
|
812
|
+
workspace_id: topWorkspace,
|
|
813
|
+
context_id: string(correlation.context_id),
|
|
814
|
+
child_id: childId,
|
|
815
|
+
execution_id: executionId,
|
|
816
|
+
parent_execution_id: parentExecutionId,
|
|
817
|
+
phase: string(correlation.phase),
|
|
818
|
+
digests: correlationDigests,
|
|
819
|
+
};
|
|
820
|
+
if (nativeEvent === "capability_decision") {
|
|
821
|
+
const requested = record.requested;
|
|
822
|
+
const parentGrant = record.parentGrant;
|
|
823
|
+
const effective = record.effective;
|
|
824
|
+
const denied = record.denied;
|
|
825
|
+
const clipped = record.clipped;
|
|
826
|
+
const gated = record.gatedBlocked;
|
|
827
|
+
const approved = record.approved;
|
|
828
|
+
validateCapabilityPartition(requested, effective, denied, clipped, gated, approved, Boolean(record.blocked), line, 3);
|
|
829
|
+
const approvalSources = object(record.approvalSources);
|
|
830
|
+
const approvalScopes = object(record.approvalScopes);
|
|
831
|
+
const approvalExpiresAt = object(record.approvalExpiresAt);
|
|
832
|
+
const approvalUses = object(record.approvalUses);
|
|
833
|
+
if (approvalUses && Object.values(approvalUses).some((use) => !object(use) || !Number.isInteger(use.max) || !Number.isInteger(use.remaining) || use.max < 0 || use.remaining < 0 || use.remaining > use.max))
|
|
834
|
+
throw new Error(`invalid pi-daddy v3 capability_decision at line ${line}: approvalUses requires remaining <= max integer bounds`);
|
|
835
|
+
validateApprovalEvidence(approved ?? [], string(record.approvalSource), approvalSources, approvalScopes, approvalExpiresAt, approvalUses, line, 3);
|
|
836
|
+
const refusal = structuredRefusal(record.refusal, nativeEvent, line, 3);
|
|
837
|
+
if (!record.blocked && refusal)
|
|
838
|
+
throw new Error(`invalid pi-daddy v3 capability_decision at line ${line}: an allowed decision cannot carry a refusal`);
|
|
839
|
+
const definition = object(record.definitionDigest);
|
|
840
|
+
const taskDigest = requireV3String(record, "taskDigest", nativeEvent, line);
|
|
841
|
+
const trustedDefinition = string(definition?.sha256);
|
|
842
|
+
const normalizedRequested = [...new Set(requested)];
|
|
843
|
+
const attributes = safeAttributes({
|
|
844
|
+
...correlationAttributes,
|
|
845
|
+
depth: record.depth,
|
|
846
|
+
agent_type: string(record.agentType),
|
|
847
|
+
executor: string(record.executor),
|
|
848
|
+
task_from: string(record.taskFrom),
|
|
849
|
+
parent_grant: parentGrant,
|
|
850
|
+
denied,
|
|
851
|
+
clipped,
|
|
852
|
+
gated_blocked: gated,
|
|
853
|
+
blocked: record.blocked,
|
|
854
|
+
reason: string(record.reason),
|
|
855
|
+
approved,
|
|
856
|
+
approval_source: string(record.approvalSource),
|
|
857
|
+
approval_sources: approvalSources,
|
|
858
|
+
approval_scope: string(record.approvalScope),
|
|
859
|
+
approval_scopes: approvalScopes,
|
|
860
|
+
approval_expires_at: approvalExpiresAt,
|
|
861
|
+
approval_uses: approvalUses,
|
|
862
|
+
human_denied: record.humanDenied,
|
|
863
|
+
gate_outcome: string(record.gateOutcome),
|
|
864
|
+
definition_name: string(definition?.name),
|
|
865
|
+
definition_source: string(definition?.source),
|
|
866
|
+
structured_refusal: refusal,
|
|
867
|
+
});
|
|
868
|
+
const base = {
|
|
869
|
+
...common,
|
|
870
|
+
parent_id: requireV3String(record, "parentId", nativeEvent, line),
|
|
871
|
+
task_from_execution_id: string(record.taskFromExecutionId),
|
|
872
|
+
requested_capabilities: normalizedRequested,
|
|
873
|
+
effective_capabilities: effective,
|
|
874
|
+
digests: anyDefined({ ...correlationDigests, task: taskDigest, definition: trustedDefinition }),
|
|
875
|
+
attributes,
|
|
876
|
+
};
|
|
877
|
+
const refusalCode = string(refusal?.code);
|
|
878
|
+
const events = normalizedRequested.map((capability) => ({ ...base, type: "capability_requested", capability }));
|
|
879
|
+
for (const capability of approved ?? []) {
|
|
880
|
+
events.push(cleanEvent({
|
|
881
|
+
...base,
|
|
882
|
+
type: "approval_used",
|
|
883
|
+
capability,
|
|
884
|
+
approval: cleanObject({
|
|
885
|
+
capability,
|
|
886
|
+
subject: approvalSubject(record.agentType),
|
|
887
|
+
source: string(approvalSources?.[capability]) ?? string(record.approvalSource),
|
|
888
|
+
scope: string(approvalScopes?.[capability]) ?? string(record.approvalScope),
|
|
889
|
+
expires_at: string(approvalExpiresAt?.[capability]),
|
|
890
|
+
used_at: at,
|
|
891
|
+
}),
|
|
892
|
+
attributes: safeAttributes({ ...attributes, approval_uses: object(approvalUses?.[capability]) }),
|
|
893
|
+
}));
|
|
894
|
+
}
|
|
895
|
+
const approvedSet = new Set(approved ?? []);
|
|
896
|
+
if (!record.blocked)
|
|
897
|
+
events.push(...effective.map((capability) => ({ ...base, type: "capability_granted", capability })));
|
|
898
|
+
events.push(...[...new Set([...denied, ...gated.filter((capability) => !approvedSet.has(capability))])].map((capability) => ({
|
|
899
|
+
...base,
|
|
900
|
+
type: "capability_refused",
|
|
901
|
+
capability,
|
|
902
|
+
refusal_code: denied.includes(capability) ? "CAPABILITY_ESCALATION" : refusalCode,
|
|
903
|
+
})));
|
|
904
|
+
events.push(cleanEvent({ ...base, type: record.blocked ? "child_spawn_refused" : "capability_decision", refusal_code: refusalCode }));
|
|
905
|
+
return events;
|
|
906
|
+
}
|
|
907
|
+
if (nativeEvent === "workspace_lease") {
|
|
908
|
+
const workspaceId = requireV3String(record, "workspaceId", nativeEvent, line);
|
|
909
|
+
const access = requireV3String(record, "access", nativeEvent, line);
|
|
910
|
+
const outcome = requireV3String(record, "outcome", nativeEvent, line);
|
|
911
|
+
const refusal = structuredRefusal(record.refusal, nativeEvent, line, 3);
|
|
912
|
+
const type = access === "read"
|
|
913
|
+
? `workspace_read_${outcome.replaceAll("-", "_")}`
|
|
914
|
+
: outcome === "refused" && refusal?.code === "WORKSPACE_WRITE_CONFLICT"
|
|
915
|
+
? "writer_lease_conflict"
|
|
916
|
+
: `writer_lease_${outcome.replaceAll("-", "_")}`;
|
|
917
|
+
return [cleanEvent({
|
|
918
|
+
...common,
|
|
919
|
+
workspace_id: workspaceId,
|
|
920
|
+
type,
|
|
921
|
+
refusal_code: string(refusal?.code),
|
|
922
|
+
attributes: safeAttributes({
|
|
923
|
+
...correlationAttributes,
|
|
924
|
+
root: string(record.root),
|
|
925
|
+
access,
|
|
926
|
+
outcome,
|
|
927
|
+
recovered: record.recovered,
|
|
928
|
+
release_reason: string(record.releaseReason),
|
|
929
|
+
structured_refusal: refusal,
|
|
930
|
+
}),
|
|
931
|
+
})];
|
|
932
|
+
}
|
|
933
|
+
if (nativeEvent === "child_lifecycle") {
|
|
934
|
+
const state = requireV3String(record, "state", nativeEvent, line);
|
|
935
|
+
const type = state === "starting" ? "child_started" : state === "running" ? "child_running" : state === "completed" ? "child_completed" : "child_failed";
|
|
936
|
+
return [cleanEvent({
|
|
937
|
+
...common,
|
|
938
|
+
type,
|
|
939
|
+
deadline_at: string(record.deadlineAt),
|
|
940
|
+
exit_code: Number.isInteger(record.exitCode) ? Number(record.exitCode) : undefined,
|
|
941
|
+
attributes: safeAttributes({
|
|
942
|
+
...correlationAttributes,
|
|
943
|
+
state,
|
|
944
|
+
executor: string(record.executor),
|
|
945
|
+
exit_code: record.exitCode,
|
|
946
|
+
signal: record.signal,
|
|
947
|
+
timed_out: record.timedOut,
|
|
948
|
+
aborted: record.aborted,
|
|
949
|
+
truncated: record.truncated,
|
|
950
|
+
reason: string(record.reason),
|
|
951
|
+
deadline_at: string(record.deadlineAt),
|
|
952
|
+
herdr_pane_id: string(record.herdrPaneId),
|
|
953
|
+
herdr_agent_name: string(record.herdrAgentName),
|
|
954
|
+
}),
|
|
955
|
+
})];
|
|
956
|
+
}
|
|
957
|
+
const workspaceId = requireV3String(record, "workspaceId", nativeEvent, line);
|
|
958
|
+
const receiptId = requireV3String(record, "receiptId", nativeEvent, line);
|
|
959
|
+
const treeSha = requireV3String(record, "treeSha", nativeEvent, line);
|
|
960
|
+
if (!/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/.test(treeSha)) {
|
|
961
|
+
throw new Error(`invalid pi-daddy v3 check_receipt at line ${line}: treeSha must be a git object id for normalized tree evidence`);
|
|
962
|
+
}
|
|
963
|
+
return [cleanEvent({
|
|
964
|
+
...common,
|
|
965
|
+
workspace_id: workspaceId,
|
|
966
|
+
type: "check_receipt_recorded",
|
|
967
|
+
digests: anyDefined({ ...correlationDigests, tree: treeSha }),
|
|
968
|
+
attributes: safeAttributes({
|
|
969
|
+
...correlationAttributes,
|
|
970
|
+
receipt_id: receiptId,
|
|
971
|
+
check_id: string(record.checkId),
|
|
972
|
+
check_receipt_id: string(correlation.check_receipt_id),
|
|
973
|
+
}),
|
|
974
|
+
})];
|
|
975
|
+
}
|
|
976
|
+
function requireV3String(record, field, event, line) {
|
|
977
|
+
const value = string(record[field]);
|
|
978
|
+
if (!value)
|
|
979
|
+
throw new Error(`invalid pi-daddy v3 ${event} at line ${line}: ${field} is required`);
|
|
980
|
+
if (redactText(value) !== value)
|
|
981
|
+
throw new Error(`invalid pi-daddy v3 ${event} at line ${line}: ${field} contains a sensitive value`);
|
|
982
|
+
return value;
|
|
983
|
+
}
|
|
665
984
|
function requireV2Correlation(record, event, line) {
|
|
666
985
|
const correlation = object(record.correlation);
|
|
667
986
|
if (!correlation) {
|
|
@@ -795,35 +1114,35 @@ function optionalV2ApprovalUses(value, event, line) {
|
|
|
795
1114
|
}
|
|
796
1115
|
return output;
|
|
797
1116
|
}
|
|
798
|
-
function validateCapabilityPartition(requested, effective, denied, clipped, gated, approved, blocked, line) {
|
|
1117
|
+
function validateCapabilityPartition(requested, effective, denied, clipped, gated, approved, blocked, line, version = 2) {
|
|
799
1118
|
const groups = [effective, denied, clipped, gated];
|
|
800
1119
|
if (groups.some((values) => new Set(values).size !== values.length)) {
|
|
801
|
-
throw new Error(`invalid pi-daddy
|
|
1120
|
+
throw new Error(`invalid pi-daddy v${version} capability_decision at line ${line}: result capability arrays must not contain duplicates`);
|
|
802
1121
|
}
|
|
803
1122
|
const requestedSet = new Set(requested);
|
|
804
1123
|
if (groups.some((values) => values.some((capability) => !requestedSet.has(capability)))) {
|
|
805
|
-
throw new Error(`invalid pi-daddy
|
|
1124
|
+
throw new Error(`invalid pi-daddy v${version} capability_decision at line ${line}: effective, denied, clipped, and gatedBlocked must partition requested`);
|
|
806
1125
|
}
|
|
807
1126
|
const flattened = groups.flat();
|
|
808
1127
|
if (new Set(flattened).size !== flattened.length) {
|
|
809
|
-
throw new Error(`invalid pi-daddy
|
|
1128
|
+
throw new Error(`invalid pi-daddy v${version} capability_decision at line ${line}: effective, denied, clipped, and gatedBlocked must be disjoint subsets of requested`);
|
|
810
1129
|
}
|
|
811
1130
|
if ((approved ?? []).some((capability) => !requestedSet.has(capability) || (blocked ? !effective.includes(capability) && !gated.includes(capability) : !effective.includes(capability)))) {
|
|
812
|
-
throw new Error(`invalid pi-daddy
|
|
1131
|
+
throw new Error(`invalid pi-daddy v${version} capability_decision at line ${line}: approved capabilities must be requested and reflected in the resolved decision`);
|
|
813
1132
|
}
|
|
814
1133
|
}
|
|
815
|
-
function validateApprovalEvidence(approved, scalarSource, sources, scopes, expiries, uses, line) {
|
|
1134
|
+
function validateApprovalEvidence(approved, scalarSource, sources, scopes, expiries, uses, line, version = 2) {
|
|
816
1135
|
const approvedSet = new Set(approved);
|
|
817
1136
|
for (const [field, map] of [["approvalSources", sources], ["approvalScopes", scopes], ["approvalExpiresAt", expiries], ["approvalUses", uses]]) {
|
|
818
1137
|
if (map && Object.keys(map).some((capability) => !approvedSet.has(capability))) {
|
|
819
|
-
throw new Error(`invalid pi-daddy
|
|
1138
|
+
throw new Error(`invalid pi-daddy v${version} capability_decision at line ${line}: ${field} keys must be approved capabilities`);
|
|
820
1139
|
}
|
|
821
1140
|
}
|
|
822
1141
|
if (approved.some((capability) => !sources?.[capability] && !scalarSource)) {
|
|
823
|
-
throw new Error(`invalid pi-daddy
|
|
1142
|
+
throw new Error(`invalid pi-daddy v${version} capability_decision at line ${line}: each approved capability requires an approval source`);
|
|
824
1143
|
}
|
|
825
1144
|
if (approved.length === 0 && (scalarSource || sources || scopes || expiries || uses)) {
|
|
826
|
-
throw new Error(`invalid pi-daddy
|
|
1145
|
+
throw new Error(`invalid pi-daddy v${version} capability_decision at line ${line}: approval evidence requires approved capabilities`);
|
|
827
1146
|
}
|
|
828
1147
|
}
|
|
829
1148
|
function optionalV2Boolean(record, field, event, line) {
|
|
@@ -838,23 +1157,24 @@ function approvalSubject(value) {
|
|
|
838
1157
|
const agentType = string(value);
|
|
839
1158
|
return agentType === undefined || agentType === "delegate" ? "<delegate>" : agentType;
|
|
840
1159
|
}
|
|
841
|
-
function structuredRefusal(value, event, line) {
|
|
1160
|
+
function structuredRefusal(value, event, line, version = 2) {
|
|
842
1161
|
if (value === undefined)
|
|
843
1162
|
return undefined;
|
|
844
1163
|
const parsed = object(value);
|
|
845
1164
|
const code = string(parsed?.code);
|
|
846
1165
|
if (!parsed || !code || !string(parsed.message)) {
|
|
847
|
-
throw new Error(`invalid pi-daddy
|
|
1166
|
+
throw new Error(`invalid pi-daddy v${version} ${event} at line ${line}: refusal requires code and message`);
|
|
848
1167
|
}
|
|
849
|
-
|
|
850
|
-
|
|
1168
|
+
const refusalCodes = version === 3 ? V3_REFUSAL_CODES : V2_REFUSAL_CODES;
|
|
1169
|
+
if (!refusalCodes.has(code)) {
|
|
1170
|
+
throw new Error(`invalid pi-daddy v${version} ${event} at line ${line}: refusal has unsupported code ${safeDiagnosticValue(code)}`);
|
|
851
1171
|
}
|
|
852
1172
|
const unknown = Object.keys(parsed).filter((key) => !V2_REFUSAL_FIELDS.has(key));
|
|
853
1173
|
if (unknown.length > 0)
|
|
854
|
-
throw new Error(`invalid pi-daddy
|
|
1174
|
+
throw new Error(`invalid pi-daddy v${version} ${event} at line ${line}: refusal carries unsupported fields`);
|
|
855
1175
|
const details = parsed.details === undefined ? undefined : object(parsed.details);
|
|
856
1176
|
if (parsed.details !== undefined && (!details || Object.values(details).some((entry) => !V2_REFUSAL_DETAIL_TYPES.has(entry === null ? "null" : typeof entry)))) {
|
|
857
|
-
throw new Error(`invalid pi-daddy
|
|
1177
|
+
throw new Error(`invalid pi-daddy v${version} ${event} at line ${line}: refusal.details must contain scalar values`);
|
|
858
1178
|
}
|
|
859
1179
|
return parsed;
|
|
860
1180
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skill-harness/adapters",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "skill-harness harness adapters — pi runner + claude-code judge routing (internal API)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"node": ">=20"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
|
-
"prepack": "
|
|
40
|
+
"prepack": "node ../../scripts/release-pack.mjs --guard"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@skill-harness/core": "0.
|
|
43
|
+
"@skill-harness/core": "0.12.0"
|
|
44
44
|
}
|
|
45
45
|
}
|