@peasant-labs/schema 0.18.0 → 0.19.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 +4 -0
- package/dist/fixtures/session-graph-capability.d.ts +454 -0
- package/dist/fixtures/session-graph-capability.js +4 -0
- package/dist/fixtures/session-graph.d.ts +7349 -0
- package/dist/fixtures/session-graph.js +4 -0
- package/dist/fixtures.d.ts +2 -0
- package/dist/fixtures.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +220 -35
- package/dist/internal/generated/content-capabilities.gen.d.ts +6 -0
- package/dist/internal/generated/content-capabilities.gen.js +47 -2
- package/dist/internal/generated/contract/zod.gen.d.ts +9554 -1328
- package/dist/internal/generated/contract/zod.gen.js +596 -27
- package/dist/internal/generated/enums.gen.d.ts +136 -0
- package/dist/internal/generated/enums.gen.js +149 -0
- package/dist/internal/generated/local-api.d.ts +308 -6
- package/dist/internal/generated/public-contract.gen.d.ts +2 -2
- package/dist/internal/generated/public-contract.gen.js +1 -1
- package/dist/internal/generated/session-graph-capability-fixtures.gen.d.ts +454 -0
- package/dist/internal/generated/session-graph-capability-fixtures.gen.js +570 -0
- package/dist/internal/generated/session-graph-fixtures.gen.d.ts +7349 -0
- package/dist/internal/generated/session-graph-fixtures.gen.js +8052 -0
- package/dist/internal/generated/versions.gen.d.ts +4 -4
- package/dist/internal/generated/versions.gen.js +4 -4
- package/dist/internal/generated/village-api.d.ts +512 -8
- package/package.json +9 -1
package/dist/fixtures.d.ts
CHANGED
package/dist/fixtures.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export interface RawJsonPathPolicy {
|
|
|
15
15
|
export declare function scanRawJsonText(text: string, policy?: RawJsonPathPolicy): void;
|
|
16
16
|
export declare function parseSessionDetailPayloadValue(value: unknown): import("./internal/generated/contract/zod.gen.js").SessionDetailPayload;
|
|
17
17
|
export declare function parseSessionDetailPayloadText(text: string): import("./internal/generated/contract/zod.gen.js").SessionDetailPayload;
|
|
18
|
+
export declare function parseSchemaVersionAdvertisement(text: string): string[];
|
|
18
19
|
export declare function parseTranscriptContentText(text: string): import("./internal/generated/contract/zod.gen.js").TranscriptContent;
|
|
19
20
|
export declare function parseServerMessageRaw(text: string): import("./internal/generated/contract/zod.gen.js").ServerMessage;
|
|
20
21
|
type Detail = import("./internal/generated/contract/zod.gen.js").SessionDetailPayload;
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,8 @@ export * from "./internal/generated/public-contract.gen.js";
|
|
|
2
2
|
export * from "./internal/generated/enums.gen.js";
|
|
3
3
|
export * from "./internal/generated/content-capabilities.gen.js";
|
|
4
4
|
export * from "./internal/generated/versions.gen.js";
|
|
5
|
-
import {
|
|
5
|
+
import { requiredContentCapabilities, KnownContentCapability } from "./internal/generated/content-capabilities.gen.js";
|
|
6
|
+
import { zNativeMetadataRecord, zTurnDetail, zProjectHash, zServerMessage, zSessionDetailPayload, zSessionDetailReadPayload, zTranscriptContent } from "./internal/generated/contract/zod.gen.js";
|
|
6
7
|
import { maxNativeMetadataStringBytes } from "./internal/generated/metadata-limits.gen.js";
|
|
7
8
|
function assertProjectHash(value, operation) {
|
|
8
9
|
if (!isProjectHash(value)) {
|
|
@@ -49,20 +50,75 @@ function scanRawJson(text, policy) {
|
|
|
49
50
|
}
|
|
50
51
|
export function parseSessionDetailPayloadValue(value) {
|
|
51
52
|
rejectExplicitNullEvidence(value);
|
|
52
|
-
|
|
53
|
+
validateRawGraphValue(value);
|
|
54
|
+
const normalized = structuredClone(value);
|
|
55
|
+
normalizeEmptyOptionalPublicRefs(normalized);
|
|
56
|
+
const payload = zSessionDetailPayload.parse(normalized);
|
|
53
57
|
validateSessionDetail(payload);
|
|
54
58
|
return payload;
|
|
55
59
|
}
|
|
60
|
+
function normalizeEmptyOptionalPublicRefs(value) {
|
|
61
|
+
if (!isRecord(value))
|
|
62
|
+
return;
|
|
63
|
+
if (value.parentSessionId === null)
|
|
64
|
+
delete value.parentSessionId;
|
|
65
|
+
for (const relation of Array.isArray(value.relationships) ? value.relationships : []) {
|
|
66
|
+
if (isRecord(relation) && isRecord(relation.anchor)) {
|
|
67
|
+
if (relation.anchor.sourceEntryRef === "")
|
|
68
|
+
delete relation.anchor.sourceEntryRef;
|
|
69
|
+
if (relation.anchor.sourceRevisionRef === "")
|
|
70
|
+
delete relation.anchor.sourceRevisionRef;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const visitTurns = (turns) => {
|
|
74
|
+
if (!Array.isArray(turns))
|
|
75
|
+
return;
|
|
76
|
+
for (const turn of turns)
|
|
77
|
+
if (isRecord(turn)) {
|
|
78
|
+
if (turn.sourceEntryRef === "")
|
|
79
|
+
delete turn.sourceEntryRef;
|
|
80
|
+
for (const tool of Array.isArray(turn.toolCalls) ? turn.toolCalls : [])
|
|
81
|
+
if (isRecord(tool)) {
|
|
82
|
+
if (tool.callEntryRef === "")
|
|
83
|
+
delete tool.callEntryRef;
|
|
84
|
+
if (tool.resultEntryRef === "")
|
|
85
|
+
delete tool.resultEntryRef;
|
|
86
|
+
}
|
|
87
|
+
for (const provenance of [turn.provenance, ...(Array.isArray(turn.toolCalls) ? turn.toolCalls.flatMap(tool => isRecord(tool) ? [tool.callProvenance, tool.resultProvenance] : []) : [])]) {
|
|
88
|
+
if (isRecord(provenance) && provenance.submissionRef === "")
|
|
89
|
+
delete provenance.submissionRef;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
visitTurns(value.turns);
|
|
94
|
+
for (const section of Array.isArray(value.earlierHistory) ? value.earlierHistory : [])
|
|
95
|
+
if (isRecord(section))
|
|
96
|
+
visitTurns(section.turns);
|
|
97
|
+
}
|
|
56
98
|
export function parseSessionDetailPayloadText(text) {
|
|
57
99
|
scanRawJsonText(text, { maxDocumentBytes: 8 << 20, maxDocumentDepth: 64, opaqueMetadataPointers: ["/nativeMetadata/*/data"] });
|
|
58
100
|
return parseSessionDetailPayloadValue(JSON.parse(text));
|
|
59
101
|
}
|
|
102
|
+
export function parseSchemaVersionAdvertisement(text) {
|
|
103
|
+
scanRawJsonText(text, { maxDocumentBytes: 1 << 20, maxDocumentDepth: 16 });
|
|
104
|
+
const value = JSON.parse(text);
|
|
105
|
+
if (!isRecord(value))
|
|
106
|
+
throw new TypeError("Schema capability validation failed at @peasant-labs/schema parseSchemaVersionAdvertisement during discovery decoding: response is not an object; callers cannot negotiate preservation support; send a JSON object and retry.");
|
|
107
|
+
if (value.contentCapabilities === null)
|
|
108
|
+
throw new TypeError("Schema capability validation failed at @peasant-labs/schema parseSchemaVersionAdvertisement during discovery decoding: contentCapabilities is null; callers cannot distinguish omission from malformed support; omit it or send an array of strings.");
|
|
109
|
+
if (value.contentCapabilities === undefined)
|
|
110
|
+
return [];
|
|
111
|
+
if (!Array.isArray(value.contentCapabilities) || value.contentCapabilities.some((token) => typeof token !== "string"))
|
|
112
|
+
throw new TypeError("Schema capability validation failed at @peasant-labs/schema parseSchemaVersionAdvertisement during discovery decoding: contentCapabilities is not an array of strings; callers cannot negotiate support; send opaque string tokens and retry.");
|
|
113
|
+
return [...value.contentCapabilities];
|
|
114
|
+
}
|
|
60
115
|
export function parseTranscriptContentText(text) {
|
|
61
116
|
scanRawJsonText(text, { maxDocumentBytes: 8 << 20, maxDocumentDepth: 64, opaqueMetadataPointers: ["/sessionDetail/nativeMetadata/*/data"] });
|
|
62
117
|
const raw = JSON.parse(text);
|
|
63
|
-
if (isRecord(raw))
|
|
64
|
-
|
|
65
|
-
const
|
|
118
|
+
if (!isRecord(raw))
|
|
119
|
+
return zTranscriptContent.parse(raw);
|
|
120
|
+
const normalizedEnvelope = { ...raw, sessionDetail: parseSessionDetailPayloadValue(raw.sessionDetail) };
|
|
121
|
+
const content = zTranscriptContent.parse(normalizedEnvelope);
|
|
66
122
|
if (content.sessionDetail === undefined || content.sessionDetail === null)
|
|
67
123
|
failSemantic("sessionDetail is required");
|
|
68
124
|
parseSessionDetailPayloadValue(content.sessionDetail);
|
|
@@ -76,13 +132,10 @@ export function parseServerMessageRaw(text) {
|
|
|
76
132
|
scanRawJsonText(text, { maxDocumentBytes: 8 << 20, maxDocumentDepth: 64, opaqueMetadataPointers: ["/data/nativeMetadata/*/data"] });
|
|
77
133
|
}
|
|
78
134
|
const value = JSON.parse(text);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
parseSessionDetailPayloadValue(message.data);
|
|
84
|
-
}
|
|
85
|
-
return message;
|
|
135
|
+
const normalized = isRecord(value) && value.type === "session_detail"
|
|
136
|
+
? { ...value, data: parseSessionDetailReadPayloadValue(value.data) }
|
|
137
|
+
: value;
|
|
138
|
+
return zServerMessage.parse(normalized);
|
|
86
139
|
}
|
|
87
140
|
class RawJsonScanner {
|
|
88
141
|
text;
|
|
@@ -244,29 +297,75 @@ class RawJsonScanner {
|
|
|
244
297
|
}
|
|
245
298
|
function pointerMatches(pattern, path) { const expected = pattern.split("/"); const actual = path.split("/"); return expected.length === actual.length && expected.every((part, index) => part === "*" || part === actual[index]); }
|
|
246
299
|
function validateSessionDetail(payload) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
validateMetadataRecords(payload.nativeMetadata ?? [], turns);
|
|
300
|
+
validateRelationships(payload);
|
|
301
|
+
const state = newDetailState(payload);
|
|
302
|
+
const turns = validateTurnEvidence(payload.turns ?? [], state);
|
|
303
|
+
validateMetadataRecords(payload.nativeMetadata ?? [], turns, state);
|
|
304
|
+
for (const section of payload.earlierHistory ?? []) {
|
|
305
|
+
const earlierTurns = validateTurnEvidence(section.turns ?? [], state);
|
|
306
|
+
validateMetadataRecords(section.nativeMetadata ?? [], earlierTurns, state);
|
|
307
|
+
}
|
|
308
|
+
if (state.nativeCount > 0 && String(payload.harness) !== "pi")
|
|
309
|
+
failSemantic("Pi native metadata requires harness pi across every history partition");
|
|
251
310
|
}
|
|
311
|
+
function parseSessionDetailReadPayloadValue(value) {
|
|
312
|
+
rejectExplicitNullEvidence(value);
|
|
313
|
+
if (!isRecord(value))
|
|
314
|
+
failSemantic("session detail read payload is not an object");
|
|
315
|
+
const durable = { ...value };
|
|
316
|
+
delete durable.relationshipNavigation;
|
|
317
|
+
const normalized = parseSessionDetailPayloadValue(durable);
|
|
318
|
+
return zSessionDetailReadPayload.parse(Object.hasOwn(value, "relationshipNavigation")
|
|
319
|
+
? { ...normalized, relationshipNavigation: value.relationshipNavigation }
|
|
320
|
+
: normalized);
|
|
321
|
+
}
|
|
322
|
+
function validateRelationships(payload) {
|
|
323
|
+
const seen = new Set();
|
|
324
|
+
let startedBy;
|
|
325
|
+
for (const relationship of payload.relationships ?? []) {
|
|
326
|
+
if (seen.has(relationship.kind))
|
|
327
|
+
failSemantic("relationship kind occurs more than once");
|
|
328
|
+
seen.add(relationship.kind);
|
|
329
|
+
const known = relationship.targetState === "target_known" || relationship.targetState === "target_known_retained";
|
|
330
|
+
if (known !== (relationship.targetLocalId != null))
|
|
331
|
+
failSemantic("relationship target state and targetLocalId presence disagree");
|
|
332
|
+
if (relationship.targetLocalId != null && !validSessionId(relationship.targetLocalId))
|
|
333
|
+
failSemantic("relationship targetLocalId is malformed");
|
|
334
|
+
if (relationship.anchor != null) {
|
|
335
|
+
if (relationship.kind !== "context_from" || !known)
|
|
336
|
+
failSemantic("anchor is allowed only on a known context_from relationship");
|
|
337
|
+
const exact = relationship.anchor.kind !== "general_source_session";
|
|
338
|
+
const hasEntry = relationship.anchor.sourceEntryRef !== undefined, hasRevision = relationship.anchor.sourceRevisionRef !== undefined;
|
|
339
|
+
if ((!exact && (hasEntry || hasRevision)) || (exact && (!hasEntry || !hasRevision)))
|
|
340
|
+
failSemantic("anchor references disagree with anchor kind");
|
|
341
|
+
if (exact && (!validRef(relationship.anchor.sourceEntryRef) || !validRef(relationship.anchor.sourceRevisionRef)))
|
|
342
|
+
failSemantic("anchor reference is invalid");
|
|
343
|
+
}
|
|
344
|
+
if (relationship.kind === "started_by" && known)
|
|
345
|
+
startedBy = relationship.targetLocalId ?? undefined;
|
|
346
|
+
}
|
|
347
|
+
if ((payload.relationships?.length ?? 0) > 0 && payload.parentSessionId != null && payload.parentSessionId !== startedBy)
|
|
348
|
+
failSemantic("parentSessionId disagrees with durable started_by relationship");
|
|
349
|
+
}
|
|
350
|
+
function validSessionId(value) { return !value.includes("/") && !value.includes("\\") && !value.includes("..") && /^(?:[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}|agent-[a-f0-9]+|ses_[a-zA-Z0-9]+|sess_[a-zA-Z0-9]+|msg_[a-zA-Z0-9]+|[A-Z2-7]{26}|[0-9]{8}T[0-9]{6}\.[0-9]{9}Z-[A-Z2-7]{26})$/.test(value); }
|
|
351
|
+
function newDetailState(payload) { return { strictBlockIdentity: requiredContentCapabilities(payload).includes(KnownContentCapability.SessionGraphProvenanceV1), blockRefs: new Set(), owners: new Set(), sources: new Set(), toolIds: new Set(), nativeIds: new Set(), nativeSources: new Set(), nativeCount: 0, nativeBytes: 0 }; }
|
|
252
352
|
// Producers can validate metadata before attaching it to a harness envelope.
|
|
253
353
|
// The full detail parsers reuse this same record and target validation.
|
|
254
354
|
export function parseNativeMetadataRecordsValue(value, targets = []) {
|
|
255
355
|
rejectExplicitNullEvidence({ nativeMetadata: value, turns: targets });
|
|
256
356
|
const turns = zTurnDetail.array().parse(targets);
|
|
257
357
|
const records = zNativeMetadataRecord.array().parse(value);
|
|
258
|
-
|
|
358
|
+
const state = newDetailState({ turns });
|
|
359
|
+
validateMetadataRecords(records, validateTurnEvidence(turns, state), state);
|
|
259
360
|
return records;
|
|
260
361
|
}
|
|
261
362
|
export function parseNativeMetadataRecordsText(text, targets = []) {
|
|
262
363
|
scanRawJsonText(text, { maxDocumentBytes: 8 << 20, maxDocumentDepth: 64, opaqueMetadataPointers: ["/*/data"] });
|
|
263
364
|
return parseNativeMetadataRecordsValue(JSON.parse(text), targets);
|
|
264
365
|
}
|
|
265
|
-
function validateTurnEvidence(targets) {
|
|
266
|
-
const owners =
|
|
267
|
-
const sources = new Set();
|
|
366
|
+
function validateTurnEvidence(targets, state = newDetailState({ turns: targets })) {
|
|
367
|
+
const blockRefs = state.blockRefs, owners = state.owners, sources = state.sources, toolIds = state.toolIds;
|
|
268
368
|
const turnIndexes = new Set();
|
|
269
|
-
const toolIds = new Set();
|
|
270
369
|
const turns = new Map();
|
|
271
370
|
for (const turn of targets) {
|
|
272
371
|
if (turn.observedModel !== undefined && (turn.role !== "assistant" || !validUnicode(turn.observedModel)))
|
|
@@ -275,8 +374,15 @@ function validateTurnEvidence(targets) {
|
|
|
275
374
|
failSemantic("duplicate turn index makes metadata attachment ambiguous");
|
|
276
375
|
turnIndexes.add(turn.index);
|
|
277
376
|
turns.set(turn.index, turn);
|
|
278
|
-
if (turn.sourceEntryRef !== undefined && !validOptionalRef(turn.sourceEntryRef))
|
|
279
|
-
failSemantic("turn source reference is invalid or exceeds 96 bytes");
|
|
377
|
+
if (turn.sourceEntryRef !== undefined && (!validOptionalRef(turn.sourceEntryRef) || (state.strictBlockIdentity && blockRefs.has(turn.sourceEntryRef))))
|
|
378
|
+
failSemantic("turn source reference is invalid, duplicated, or exceeds 96 bytes");
|
|
379
|
+
if (turn.sourceEntryRef !== undefined)
|
|
380
|
+
blockRefs.add(turn.sourceEntryRef);
|
|
381
|
+
if (turn.provenance != null)
|
|
382
|
+
validateProvenance(turn.provenance);
|
|
383
|
+
const projected = turn.sourceEntryRef !== undefined || turn.provenance !== undefined || (turn.toolCalls ?? []).some((tool) => tool.callEntryRef !== undefined || tool.resultEntryRef !== undefined || tool.callProvenance !== undefined || tool.resultProvenance !== undefined);
|
|
384
|
+
if (projected && (((turn.depth ?? 0) === 0) !== (turn.parentIndex == null)))
|
|
385
|
+
failSemantic("turn depth and parentIndex disagree");
|
|
280
386
|
if (turn.usage != null) {
|
|
281
387
|
if (!((turn.role === "assistant" && turn.usage.scope === "assistant") || (turn.role === "system" && turn.usage.scope === "summary")))
|
|
282
388
|
failSemantic("turn role and usage scope disagree");
|
|
@@ -287,16 +393,22 @@ function validateTurnEvidence(targets) {
|
|
|
287
393
|
for (const tool of turn.toolCalls ?? []) {
|
|
288
394
|
if (tool.namespace !== undefined && !validUnicode(tool.namespace))
|
|
289
395
|
failSemantic("tool namespace contains invalid Unicode; provide a valid string or omit unrecorded namespace");
|
|
396
|
+
if (tool.callProvenance != null)
|
|
397
|
+
validateProvenance(tool.callProvenance);
|
|
398
|
+
if (tool.resultProvenance != null)
|
|
399
|
+
validateProvenance(tool.resultProvenance);
|
|
290
400
|
if (!validOptionalRef(tool.id))
|
|
291
401
|
failSemantic("tool id reference is invalid or exceeds 96 bytes");
|
|
292
402
|
if (tool.id && toolIds.has(tool.id))
|
|
293
403
|
failSemantic("duplicate tool call id makes metadata attachment ambiguous");
|
|
294
404
|
if (tool.id)
|
|
295
405
|
toolIds.add(tool.id);
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
406
|
+
for (const ref of [tool.callEntryRef, tool.resultEntryRef]) {
|
|
407
|
+
if (ref !== undefined && (!validOptionalRef(ref) || (state.strictBlockIdentity && blockRefs.has(ref))))
|
|
408
|
+
failSemantic("tool reference is invalid, duplicated, or exceeds 96 bytes");
|
|
409
|
+
if (ref !== undefined)
|
|
410
|
+
blockRefs.add(ref);
|
|
411
|
+
}
|
|
300
412
|
if (tool.usage != null) {
|
|
301
413
|
if (tool.usage.scope !== "tool" || !tool.resultEntryRef || tool.usage.sourceEntryRef !== tool.resultEntryRef)
|
|
302
414
|
failSemantic("tool usage attribution disagrees");
|
|
@@ -304,23 +416,29 @@ function validateTurnEvidence(targets) {
|
|
|
304
416
|
}
|
|
305
417
|
}
|
|
306
418
|
}
|
|
419
|
+
for (const turn of targets)
|
|
420
|
+
if (turn.parentIndex != null && !turns.has(turn.parentIndex))
|
|
421
|
+
failSemantic("parentIndex does not target a turn in the same partition");
|
|
307
422
|
return turns;
|
|
308
423
|
}
|
|
309
|
-
function validateMetadataRecords(records, turns) {
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
if (records.length > 256)
|
|
424
|
+
function validateMetadataRecords(records, turns, state) {
|
|
425
|
+
const blockRefs = state.blockRefs;
|
|
426
|
+
if (state.nativeCount + records.length > 256)
|
|
313
427
|
failSemantic("native metadata record count exceeds 256");
|
|
428
|
+
state.nativeCount += records.length;
|
|
314
429
|
for (const metadata of records) {
|
|
315
|
-
if (!validRef(metadata.id) || !validRef(metadata.source.entryRef) ||
|
|
430
|
+
if (!validRef(metadata.id) || !validRef(metadata.source.entryRef) || state.nativeIds.has(metadata.id) || state.nativeSources.has(metadata.source.entryRef) || (state.strictBlockIdentity && metadata.attachment === undefined && blockRefs.has(metadata.source.entryRef)))
|
|
316
431
|
failSemantic("metadata reference is invalid or duplicated");
|
|
317
|
-
|
|
432
|
+
state.nativeIds.add(metadata.id);
|
|
433
|
+
state.nativeSources.add(metadata.source.entryRef);
|
|
434
|
+
if (metadata.attachment === undefined)
|
|
435
|
+
blockRefs.add(metadata.source.entryRef);
|
|
318
436
|
if (metadata.customType !== undefined && (!validUnicode(metadata.customType) || new TextEncoder().encode(metadata.customType).length > 128))
|
|
319
437
|
failSemantic("customType is invalid Unicode or exceeds 128 bytes");
|
|
320
438
|
validateMetadataValue(metadata.data, 1);
|
|
321
439
|
const encoded = new TextEncoder().encode(JSON.stringify(metadata.data)).length;
|
|
322
|
-
|
|
323
|
-
if (encoded > 65536 ||
|
|
440
|
+
state.nativeBytes += encoded;
|
|
441
|
+
if (encoded > 65536 || state.nativeBytes > 1048576)
|
|
324
442
|
failSemantic("metadata data exceeds its byte budget");
|
|
325
443
|
const attachment = metadata.attachment;
|
|
326
444
|
const target = attachment?.turnIndex == null ? undefined : turns.get(attachment.turnIndex);
|
|
@@ -353,6 +471,13 @@ function validateMetadataRecords(records, turns) {
|
|
|
353
471
|
}
|
|
354
472
|
}
|
|
355
473
|
}
|
|
474
|
+
function validateProvenance(value) {
|
|
475
|
+
const sets = [[value.origin, ["submitted_input", "harness_context", "agent_output", "agent_communication", "tool_activity", "system_control", "generated_summary", "unknown"]], [value.actor, ["operator", "agent_delegate", "harness", "unknown"]], [value.delivery, ["session_admission", "guardian_review", "subagent_delivery", "inherited_context", "tool_delivery", "system_lifecycle", "unknown"]], [value.ownership, ["local", "inherited", "uncertain"]], [value.evidence, ["native_typed", "lifecycle_typed", "existing_adapter", "retained_last_good", "unknown", "conflict"]], [value.inputModality, ["none", "text", "media", "user_action", "mixed", "unknown"]]];
|
|
476
|
+
if (sets.some(([v, allowed]) => !allowed.includes(v)))
|
|
477
|
+
failSemantic("provenance dimension is missing or outside its closed set");
|
|
478
|
+
if (value.submissionRef !== undefined && value.submissionRef !== "" && !validRef(value.submissionRef))
|
|
479
|
+
failSemantic("submissionRef is invalid");
|
|
480
|
+
}
|
|
356
481
|
function validateUsage(usage, owners, sources) {
|
|
357
482
|
if (!validRef(usage.ownerId) || !validRef(usage.sourceEntryRef) || owners.has(usage.ownerId) || sources.has(usage.sourceEntryRef))
|
|
358
483
|
failSemantic("usage owner/source reference is invalid or duplicated");
|
|
@@ -428,10 +553,19 @@ function isRecord(value) { return typeof value === "object" && value !== null &&
|
|
|
428
553
|
function rejectExplicitNullEvidence(value) {
|
|
429
554
|
if (!isRecord(value))
|
|
430
555
|
return;
|
|
556
|
+
if (value.inputSubmissionCount === null)
|
|
557
|
+
failSemantic("inputSubmissionCount is explicitly null");
|
|
558
|
+
if (value.inputSubmissionCount !== undefined && (typeof value.inputSubmissionCount !== "number" || !Number.isSafeInteger(value.inputSubmissionCount) || value.inputSubmissionCount < 0))
|
|
559
|
+
failSemantic("inputSubmissionCount is outside 0..9007199254740991");
|
|
431
560
|
for (const turn of Array.isArray(value.turns) ? value.turns : []) {
|
|
432
561
|
if (!isRecord(turn))
|
|
433
562
|
continue;
|
|
563
|
+
if (turn.provenance === null)
|
|
564
|
+
failSemantic("turn provenance is explicitly null");
|
|
434
565
|
const tools = Array.isArray(turn.toolCalls) ? turn.toolCalls : [];
|
|
566
|
+
for (const tool of tools)
|
|
567
|
+
if (isRecord(tool) && (tool.callProvenance === null || tool.resultProvenance === null))
|
|
568
|
+
failSemantic("folded provenance is explicitly null");
|
|
435
569
|
for (const usage of [turn.usage, ...tools.map(tool => isRecord(tool) ? tool.usage : undefined)]) {
|
|
436
570
|
if (!isRecord(usage) || !isRecord(usage.tokens))
|
|
437
571
|
continue;
|
|
@@ -440,6 +574,9 @@ function rejectExplicitNullEvidence(value) {
|
|
|
440
574
|
failSemantic(`token ${name} is explicitly null`);
|
|
441
575
|
}
|
|
442
576
|
}
|
|
577
|
+
for (const section of Array.isArray(value.earlierHistory) ? value.earlierHistory : [])
|
|
578
|
+
if (isRecord(section))
|
|
579
|
+
rejectExplicitNullEvidence({ turns: section.turns, nativeMetadata: section.nativeMetadata });
|
|
443
580
|
for (const record of Array.isArray(value.nativeMetadata) ? value.nativeMetadata : []) {
|
|
444
581
|
if (!isRecord(record))
|
|
445
582
|
continue;
|
|
@@ -458,4 +595,52 @@ function rejectExplicitNullEvidence(value) {
|
|
|
458
595
|
failSemantic("metadata source.messageRole is forbidden for this kind");
|
|
459
596
|
}
|
|
460
597
|
}
|
|
598
|
+
const rootForbiddenGraphFields = new Set(["relationshipNavigation", "label", "explanation", "cooked", "collapsed", "url", "resolved", "detail"]);
|
|
599
|
+
const forbiddenGraphFields = new Set(["relationshipNavigation", "status", "transcriptId", "label", "explanation", "cooked", "collapsed", "url", "resolved", "detail"]);
|
|
600
|
+
function validateRawGraphValue(value) {
|
|
601
|
+
if (!isRecord(value))
|
|
602
|
+
return;
|
|
603
|
+
for (const key of Object.keys(value))
|
|
604
|
+
if (rootForbiddenGraphFields.has(key))
|
|
605
|
+
failSemantic(`durable payload contains forbidden read-only or cooked field ${key}`);
|
|
606
|
+
for (const relationship of Array.isArray(value.relationships) ? value.relationships : [])
|
|
607
|
+
if (isRecord(relationship)) {
|
|
608
|
+
for (const key of Object.keys(relationship))
|
|
609
|
+
if (!["kind", "targetState", "targetLocalId", "evidence", "anchor"].includes(key))
|
|
610
|
+
failSemantic(`relationship contains forbidden field ${key}`);
|
|
611
|
+
if (isRecord(relationship.anchor))
|
|
612
|
+
for (const key of Object.keys(relationship.anchor))
|
|
613
|
+
if (!["kind", "sourceEntryRef", "sourceRevisionRef"].includes(key))
|
|
614
|
+
failSemantic(`anchor contains forbidden field ${key}`);
|
|
615
|
+
}
|
|
616
|
+
const visitTurns = (turns) => {
|
|
617
|
+
if (!Array.isArray(turns))
|
|
618
|
+
return;
|
|
619
|
+
for (const turn of turns) {
|
|
620
|
+
if (!isRecord(turn))
|
|
621
|
+
continue;
|
|
622
|
+
for (const key of Object.keys(turn))
|
|
623
|
+
if (forbiddenGraphFields.has(key))
|
|
624
|
+
failSemantic(`turn contains forbidden read-only or cooked field ${key}`);
|
|
625
|
+
for (const tool of Array.isArray(turn.toolCalls) ? turn.toolCalls : [])
|
|
626
|
+
if (isRecord(tool))
|
|
627
|
+
for (const key of Object.keys(tool))
|
|
628
|
+
if (forbiddenGraphFields.has(key))
|
|
629
|
+
failSemantic(`tool contains forbidden read-only or cooked field ${key}`);
|
|
630
|
+
for (const provenance of [turn.provenance, ...(Array.isArray(turn.toolCalls) ? turn.toolCalls.flatMap((tool) => isRecord(tool) ? [tool.callProvenance, tool.resultProvenance] : []) : [])])
|
|
631
|
+
if (isRecord(provenance))
|
|
632
|
+
for (const key of Object.keys(provenance))
|
|
633
|
+
if (!["origin", "actor", "delivery", "ownership", "evidence", "inputModality", "submissionRef"].includes(key))
|
|
634
|
+
failSemantic(`provenance contains forbidden field ${key}`);
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
visitTurns(value.turns);
|
|
638
|
+
for (const section of Array.isArray(value.earlierHistory) ? value.earlierHistory : [])
|
|
639
|
+
if (isRecord(section)) {
|
|
640
|
+
for (const key of Object.keys(section))
|
|
641
|
+
if (!["state", "turns", "nativeMetadata"].includes(key))
|
|
642
|
+
failSemantic(`earlier history contains forbidden field ${key}`);
|
|
643
|
+
visitTurns(section.turns);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
461
646
|
function failSemantic(reason) { throw new TypeError(`Session detail validation failed at @peasant-labs/schema public parser during semantic validation: ${reason}; consumers cannot safely attribute transcript evidence; correct the payload and retry.`); }
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SessionDetailPayload } from "./contract/zod.gen.js";
|
|
1
2
|
/**
|
|
2
3
|
* KnownContentCapability is the closed inventory of content-capability tokens known to this
|
|
3
4
|
* pinned schema release. It is intentionally distinct from the OPEN discovery
|
|
@@ -11,8 +12,13 @@ export declare const KnownContentCapability: Readonly<{
|
|
|
11
12
|
readonly DetailedUsageV1: "detailed_usage_v1";
|
|
12
13
|
readonly NativeMetadataV1: "native_metadata_v1";
|
|
13
14
|
readonly ObservedModelV1: "observed_model_v1";
|
|
15
|
+
readonly SessionGraphProvenanceV1: "session_graph_provenance_v1";
|
|
14
16
|
readonly ToolNamespaceV1: "tool_namespace_v1";
|
|
15
17
|
}>;
|
|
16
18
|
export type KnownContentCapability = (typeof KnownContentCapability)[keyof typeof KnownContentCapability];
|
|
17
19
|
export declare const AllContentCapabilities: readonly KnownContentCapability[];
|
|
18
20
|
export declare function isContentCapability(value: unknown): value is KnownContentCapability;
|
|
21
|
+
export declare function knownContentCapabilities(values: readonly string[]): KnownContentCapability[];
|
|
22
|
+
export declare function missingContentCapabilities(advertised: readonly string[], required: readonly KnownContentCapability[]): KnownContentCapability[];
|
|
23
|
+
export declare function validateContentCapabilityAdvertisements(values: readonly string[]): void;
|
|
24
|
+
export declare function requiredContentCapabilities(detail: SessionDetailPayload): KnownContentCapability[];
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// Code generated from the canonical Go/OpenAPI contract. DO NOT EDIT.
|
|
2
1
|
/**
|
|
3
2
|
* KnownContentCapability is the closed inventory of content-capability tokens known to this
|
|
4
3
|
* pinned schema release. It is intentionally distinct from the OPEN discovery
|
|
@@ -12,9 +11,55 @@ export const KnownContentCapability = Object.freeze({
|
|
|
12
11
|
DetailedUsageV1: "detailed_usage_v1",
|
|
13
12
|
NativeMetadataV1: "native_metadata_v1",
|
|
14
13
|
ObservedModelV1: "observed_model_v1",
|
|
14
|
+
SessionGraphProvenanceV1: "session_graph_provenance_v1",
|
|
15
15
|
ToolNamespaceV1: "tool_namespace_v1",
|
|
16
16
|
});
|
|
17
|
-
export const AllContentCapabilities = Object.freeze([KnownContentCapability.DetailedUsageV1, KnownContentCapability.NativeMetadataV1, KnownContentCapability.ObservedModelV1, KnownContentCapability.ToolNamespaceV1]);
|
|
17
|
+
export const AllContentCapabilities = Object.freeze([KnownContentCapability.DetailedUsageV1, KnownContentCapability.NativeMetadataV1, KnownContentCapability.ObservedModelV1, KnownContentCapability.SessionGraphProvenanceV1, KnownContentCapability.ToolNamespaceV1]);
|
|
18
18
|
export function isContentCapability(value) {
|
|
19
19
|
return typeof value === "string" && AllContentCapabilities.includes(value);
|
|
20
20
|
}
|
|
21
|
+
export function knownContentCapabilities(values) {
|
|
22
|
+
return [...new Set(values.filter(isContentCapability))].sort();
|
|
23
|
+
}
|
|
24
|
+
export function missingContentCapabilities(advertised, required) {
|
|
25
|
+
const present = new Set(knownContentCapabilities(advertised));
|
|
26
|
+
return [...new Set(required)].filter((token) => !present.has(token)).sort();
|
|
27
|
+
}
|
|
28
|
+
export function validateContentCapabilityAdvertisements(values) {
|
|
29
|
+
for (let index = 0; index < values.length; index++) {
|
|
30
|
+
const token = values[index];
|
|
31
|
+
if (token === undefined || !isContentCapability(token))
|
|
32
|
+
throw new TypeError(`content capability producer validation failed at @peasant-labs/schema validateContentCapabilityAdvertisements: unknown token ${JSON.stringify(token)}; callers cannot advertise unimplemented preservation; emit only AllContentCapabilities after deployment support is proven`);
|
|
33
|
+
const previous = values[index - 1];
|
|
34
|
+
if (previous !== undefined && previous === token)
|
|
35
|
+
throw new TypeError("content capability producer validation failed at @peasant-labs/schema validateContentCapabilityAdvertisements: token is duplicated; callers cannot emit a canonical advertisement; deduplicate and sort the known inventory");
|
|
36
|
+
if (previous !== undefined && previous > token)
|
|
37
|
+
throw new TypeError("content capability producer validation failed at @peasant-labs/schema validateContentCapabilityAdvertisements: tokens are not in canonical lexicographic order; callers cannot emit a canonical advertisement; sort the unique known inventory");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function visitTurns(turns, found) {
|
|
41
|
+
for (const turn of turns ?? []) {
|
|
42
|
+
if ((turn.observedModel ?? "") !== "")
|
|
43
|
+
found.add(KnownContentCapability.ObservedModelV1);
|
|
44
|
+
if (turn.toolCalls?.some((tool) => tool.namespace !== undefined))
|
|
45
|
+
found.add(KnownContentCapability.ToolNamespaceV1);
|
|
46
|
+
if (turn.usage != null || turn.toolCalls?.some((tool) => tool.usage != null))
|
|
47
|
+
found.add(KnownContentCapability.DetailedUsageV1);
|
|
48
|
+
if (turn.provenance != null || turn.toolCalls?.some((tool) => tool.callProvenance != null || tool.resultProvenance != null))
|
|
49
|
+
found.add(KnownContentCapability.SessionGraphProvenanceV1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function requiredContentCapabilities(detail) {
|
|
53
|
+
const found = new Set();
|
|
54
|
+
if (detail.inputSubmissionCount !== undefined || detail.rootSessionId != null || (detail.purpose ?? "") !== "" || (detail.relationships?.length ?? 0) > 0 || (detail.earlierHistory?.length ?? 0) > 0)
|
|
55
|
+
found.add(KnownContentCapability.SessionGraphProvenanceV1);
|
|
56
|
+
if ((detail.nativeMetadata?.length ?? 0) > 0)
|
|
57
|
+
found.add(KnownContentCapability.NativeMetadataV1);
|
|
58
|
+
visitTurns(detail.turns, found);
|
|
59
|
+
for (const section of detail.earlierHistory ?? []) {
|
|
60
|
+
if ((section.nativeMetadata?.length ?? 0) > 0)
|
|
61
|
+
found.add(KnownContentCapability.NativeMetadataV1);
|
|
62
|
+
visitTurns(section.turns, found);
|
|
63
|
+
}
|
|
64
|
+
return [...found].sort();
|
|
65
|
+
}
|