@peasant-labs/schema 0.17.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 +227 -39
- package/dist/internal/generated/content-capabilities.gen.d.ts +7 -0
- package/dist/internal/generated/content-capabilities.gen.js +48 -2
- package/dist/internal/generated/contract/zod.gen.d.ts +9558 -1328
- package/dist/internal/generated/contract/zod.gen.js +597 -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/metadata-limits.gen.d.ts +1 -0
- package/dist/internal/generated/metadata-limits.gen.js +2 -0
- 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,9 @@ 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";
|
|
7
|
+
import { maxNativeMetadataStringBytes } from "./internal/generated/metadata-limits.gen.js";
|
|
6
8
|
function assertProjectHash(value, operation) {
|
|
7
9
|
if (!isProjectHash(value)) {
|
|
8
10
|
const rendered = renderProjectHashInput(value);
|
|
@@ -48,20 +50,75 @@ function scanRawJson(text, policy) {
|
|
|
48
50
|
}
|
|
49
51
|
export function parseSessionDetailPayloadValue(value) {
|
|
50
52
|
rejectExplicitNullEvidence(value);
|
|
51
|
-
|
|
53
|
+
validateRawGraphValue(value);
|
|
54
|
+
const normalized = structuredClone(value);
|
|
55
|
+
normalizeEmptyOptionalPublicRefs(normalized);
|
|
56
|
+
const payload = zSessionDetailPayload.parse(normalized);
|
|
52
57
|
validateSessionDetail(payload);
|
|
53
58
|
return payload;
|
|
54
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
|
+
}
|
|
55
98
|
export function parseSessionDetailPayloadText(text) {
|
|
56
99
|
scanRawJsonText(text, { maxDocumentBytes: 8 << 20, maxDocumentDepth: 64, opaqueMetadataPointers: ["/nativeMetadata/*/data"] });
|
|
57
100
|
return parseSessionDetailPayloadValue(JSON.parse(text));
|
|
58
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
|
+
}
|
|
59
115
|
export function parseTranscriptContentText(text) {
|
|
60
116
|
scanRawJsonText(text, { maxDocumentBytes: 8 << 20, maxDocumentDepth: 64, opaqueMetadataPointers: ["/sessionDetail/nativeMetadata/*/data"] });
|
|
61
117
|
const raw = JSON.parse(text);
|
|
62
|
-
if (isRecord(raw))
|
|
63
|
-
|
|
64
|
-
const
|
|
118
|
+
if (!isRecord(raw))
|
|
119
|
+
return zTranscriptContent.parse(raw);
|
|
120
|
+
const normalizedEnvelope = { ...raw, sessionDetail: parseSessionDetailPayloadValue(raw.sessionDetail) };
|
|
121
|
+
const content = zTranscriptContent.parse(normalizedEnvelope);
|
|
65
122
|
if (content.sessionDetail === undefined || content.sessionDetail === null)
|
|
66
123
|
failSemantic("sessionDetail is required");
|
|
67
124
|
parseSessionDetailPayloadValue(content.sessionDetail);
|
|
@@ -75,13 +132,10 @@ export function parseServerMessageRaw(text) {
|
|
|
75
132
|
scanRawJsonText(text, { maxDocumentBytes: 8 << 20, maxDocumentDepth: 64, opaqueMetadataPointers: ["/data/nativeMetadata/*/data"] });
|
|
76
133
|
}
|
|
77
134
|
const value = JSON.parse(text);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
parseSessionDetailPayloadValue(message.data);
|
|
83
|
-
}
|
|
84
|
-
return message;
|
|
135
|
+
const normalized = isRecord(value) && value.type === "session_detail"
|
|
136
|
+
? { ...value, data: parseSessionDetailReadPayloadValue(value.data) }
|
|
137
|
+
: value;
|
|
138
|
+
return zServerMessage.parse(normalized);
|
|
85
139
|
}
|
|
86
140
|
class RawJsonScanner {
|
|
87
141
|
text;
|
|
@@ -124,8 +178,8 @@ class RawJsonScanner {
|
|
|
124
178
|
const value = this.string();
|
|
125
179
|
if (path === "/type")
|
|
126
180
|
this.rootMessageType = value;
|
|
127
|
-
if (opaque && new TextEncoder().encode(value).length >
|
|
128
|
-
this.fail(
|
|
181
|
+
if (opaque && new TextEncoder().encode(value).length > maxNativeMetadataStringBytes)
|
|
182
|
+
this.fail(`metadata string exceeds ${maxNativeMetadataStringBytes} UTF-8 bytes`);
|
|
129
183
|
return;
|
|
130
184
|
}
|
|
131
185
|
if (c === "t")
|
|
@@ -243,29 +297,75 @@ class RawJsonScanner {
|
|
|
243
297
|
}
|
|
244
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]); }
|
|
245
299
|
function validateSessionDetail(payload) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
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");
|
|
250
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 }; }
|
|
251
352
|
// Producers can validate metadata before attaching it to a harness envelope.
|
|
252
353
|
// The full detail parsers reuse this same record and target validation.
|
|
253
354
|
export function parseNativeMetadataRecordsValue(value, targets = []) {
|
|
254
355
|
rejectExplicitNullEvidence({ nativeMetadata: value, turns: targets });
|
|
255
356
|
const turns = zTurnDetail.array().parse(targets);
|
|
256
357
|
const records = zNativeMetadataRecord.array().parse(value);
|
|
257
|
-
|
|
358
|
+
const state = newDetailState({ turns });
|
|
359
|
+
validateMetadataRecords(records, validateTurnEvidence(turns, state), state);
|
|
258
360
|
return records;
|
|
259
361
|
}
|
|
260
362
|
export function parseNativeMetadataRecordsText(text, targets = []) {
|
|
261
363
|
scanRawJsonText(text, { maxDocumentBytes: 8 << 20, maxDocumentDepth: 64, opaqueMetadataPointers: ["/*/data"] });
|
|
262
364
|
return parseNativeMetadataRecordsValue(JSON.parse(text), targets);
|
|
263
365
|
}
|
|
264
|
-
function validateTurnEvidence(targets) {
|
|
265
|
-
const owners =
|
|
266
|
-
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;
|
|
267
368
|
const turnIndexes = new Set();
|
|
268
|
-
const toolIds = new Set();
|
|
269
369
|
const turns = new Map();
|
|
270
370
|
for (const turn of targets) {
|
|
271
371
|
if (turn.observedModel !== undefined && (turn.role !== "assistant" || !validUnicode(turn.observedModel)))
|
|
@@ -274,8 +374,15 @@ function validateTurnEvidence(targets) {
|
|
|
274
374
|
failSemantic("duplicate turn index makes metadata attachment ambiguous");
|
|
275
375
|
turnIndexes.add(turn.index);
|
|
276
376
|
turns.set(turn.index, turn);
|
|
277
|
-
if (turn.sourceEntryRef !== undefined && !validOptionalRef(turn.sourceEntryRef))
|
|
278
|
-
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");
|
|
279
386
|
if (turn.usage != null) {
|
|
280
387
|
if (!((turn.role === "assistant" && turn.usage.scope === "assistant") || (turn.role === "system" && turn.usage.scope === "summary")))
|
|
281
388
|
failSemantic("turn role and usage scope disagree");
|
|
@@ -284,16 +391,24 @@ function validateTurnEvidence(targets) {
|
|
|
284
391
|
validateUsage(turn.usage, owners, sources);
|
|
285
392
|
}
|
|
286
393
|
for (const tool of turn.toolCalls ?? []) {
|
|
394
|
+
if (tool.namespace !== undefined && !validUnicode(tool.namespace))
|
|
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);
|
|
287
400
|
if (!validOptionalRef(tool.id))
|
|
288
401
|
failSemantic("tool id reference is invalid or exceeds 96 bytes");
|
|
289
402
|
if (tool.id && toolIds.has(tool.id))
|
|
290
403
|
failSemantic("duplicate tool call id makes metadata attachment ambiguous");
|
|
291
404
|
if (tool.id)
|
|
292
405
|
toolIds.add(tool.id);
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
+
}
|
|
297
412
|
if (tool.usage != null) {
|
|
298
413
|
if (tool.usage.scope !== "tool" || !tool.resultEntryRef || tool.usage.sourceEntryRef !== tool.resultEntryRef)
|
|
299
414
|
failSemantic("tool usage attribution disagrees");
|
|
@@ -301,23 +416,29 @@ function validateTurnEvidence(targets) {
|
|
|
301
416
|
}
|
|
302
417
|
}
|
|
303
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");
|
|
304
422
|
return turns;
|
|
305
423
|
}
|
|
306
|
-
function validateMetadataRecords(records, turns) {
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
if (records.length > 256)
|
|
424
|
+
function validateMetadataRecords(records, turns, state) {
|
|
425
|
+
const blockRefs = state.blockRefs;
|
|
426
|
+
if (state.nativeCount + records.length > 256)
|
|
310
427
|
failSemantic("native metadata record count exceeds 256");
|
|
428
|
+
state.nativeCount += records.length;
|
|
311
429
|
for (const metadata of records) {
|
|
312
|
-
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)))
|
|
313
431
|
failSemantic("metadata reference is invalid or duplicated");
|
|
314
|
-
|
|
432
|
+
state.nativeIds.add(metadata.id);
|
|
433
|
+
state.nativeSources.add(metadata.source.entryRef);
|
|
434
|
+
if (metadata.attachment === undefined)
|
|
435
|
+
blockRefs.add(metadata.source.entryRef);
|
|
315
436
|
if (metadata.customType !== undefined && (!validUnicode(metadata.customType) || new TextEncoder().encode(metadata.customType).length > 128))
|
|
316
437
|
failSemantic("customType is invalid Unicode or exceeds 128 bytes");
|
|
317
438
|
validateMetadataValue(metadata.data, 1);
|
|
318
439
|
const encoded = new TextEncoder().encode(JSON.stringify(metadata.data)).length;
|
|
319
|
-
|
|
320
|
-
if (encoded > 65536 ||
|
|
440
|
+
state.nativeBytes += encoded;
|
|
441
|
+
if (encoded > 65536 || state.nativeBytes > 1048576)
|
|
321
442
|
failSemantic("metadata data exceeds its byte budget");
|
|
322
443
|
const attachment = metadata.attachment;
|
|
323
444
|
const target = attachment?.turnIndex == null ? undefined : turns.get(attachment.turnIndex);
|
|
@@ -350,6 +471,13 @@ function validateMetadataRecords(records, turns) {
|
|
|
350
471
|
}
|
|
351
472
|
}
|
|
352
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
|
+
}
|
|
353
481
|
function validateUsage(usage, owners, sources) {
|
|
354
482
|
if (!validRef(usage.ownerId) || !validRef(usage.sourceEntryRef) || owners.has(usage.ownerId) || sources.has(usage.sourceEntryRef))
|
|
355
483
|
failSemantic("usage owner/source reference is invalid or duplicated");
|
|
@@ -396,8 +524,8 @@ function validUnicode(value) {
|
|
|
396
524
|
function validateMetadataValue(value, depth) {
|
|
397
525
|
if (depth > 32)
|
|
398
526
|
failSemantic("metadata data exceeds depth 32");
|
|
399
|
-
if (typeof value === "string" && (!validUnicode(value) || new TextEncoder().encode(value).length >
|
|
400
|
-
failSemantic(
|
|
527
|
+
if (typeof value === "string" && (!validUnicode(value) || new TextEncoder().encode(value).length > maxNativeMetadataStringBytes))
|
|
528
|
+
failSemantic(`metadata string is invalid Unicode or exceeds ${maxNativeMetadataStringBytes} UTF-8 bytes`);
|
|
401
529
|
if (typeof value === "number" && (!Number.isFinite(value) || (Number.isInteger(value) && !Number.isSafeInteger(value))))
|
|
402
530
|
failSemantic("metadata number is outside the finite JS-safe domain");
|
|
403
531
|
if (value !== null && !["string", "number", "boolean", "object"].includes(typeof value))
|
|
@@ -425,10 +553,19 @@ function isRecord(value) { return typeof value === "object" && value !== null &&
|
|
|
425
553
|
function rejectExplicitNullEvidence(value) {
|
|
426
554
|
if (!isRecord(value))
|
|
427
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");
|
|
428
560
|
for (const turn of Array.isArray(value.turns) ? value.turns : []) {
|
|
429
561
|
if (!isRecord(turn))
|
|
430
562
|
continue;
|
|
563
|
+
if (turn.provenance === null)
|
|
564
|
+
failSemantic("turn provenance is explicitly null");
|
|
431
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");
|
|
432
569
|
for (const usage of [turn.usage, ...tools.map(tool => isRecord(tool) ? tool.usage : undefined)]) {
|
|
433
570
|
if (!isRecord(usage) || !isRecord(usage.tokens))
|
|
434
571
|
continue;
|
|
@@ -437,6 +574,9 @@ function rejectExplicitNullEvidence(value) {
|
|
|
437
574
|
failSemantic(`token ${name} is explicitly null`);
|
|
438
575
|
}
|
|
439
576
|
}
|
|
577
|
+
for (const section of Array.isArray(value.earlierHistory) ? value.earlierHistory : [])
|
|
578
|
+
if (isRecord(section))
|
|
579
|
+
rejectExplicitNullEvidence({ turns: section.turns, nativeMetadata: section.nativeMetadata });
|
|
440
580
|
for (const record of Array.isArray(value.nativeMetadata) ? value.nativeMetadata : []) {
|
|
441
581
|
if (!isRecord(record))
|
|
442
582
|
continue;
|
|
@@ -455,4 +595,52 @@ function rejectExplicitNullEvidence(value) {
|
|
|
455
595
|
failSemantic("metadata source.messageRole is forbidden for this kind");
|
|
456
596
|
}
|
|
457
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
|
+
}
|
|
458
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,7 +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";
|
|
16
|
+
readonly ToolNamespaceV1: "tool_namespace_v1";
|
|
14
17
|
}>;
|
|
15
18
|
export type KnownContentCapability = (typeof KnownContentCapability)[keyof typeof KnownContentCapability];
|
|
16
19
|
export declare const AllContentCapabilities: readonly KnownContentCapability[];
|
|
17
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,8 +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
|
+
ToolNamespaceV1: "tool_namespace_v1",
|
|
15
16
|
});
|
|
16
|
-
export const AllContentCapabilities = Object.freeze([KnownContentCapability.DetailedUsageV1, KnownContentCapability.NativeMetadataV1, KnownContentCapability.ObservedModelV1]);
|
|
17
|
+
export const AllContentCapabilities = Object.freeze([KnownContentCapability.DetailedUsageV1, KnownContentCapability.NativeMetadataV1, KnownContentCapability.ObservedModelV1, KnownContentCapability.SessionGraphProvenanceV1, KnownContentCapability.ToolNamespaceV1]);
|
|
17
18
|
export function isContentCapability(value) {
|
|
18
19
|
return typeof value === "string" && AllContentCapabilities.includes(value);
|
|
19
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
|
+
}
|