@peasant-labs/schema 0.23.0 → 0.25.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.
@@ -1,11 +1,27 @@
1
1
  export declare function sessionGraphCapabilityFixtures(): {
2
2
  readonly requiredNames: {
3
- readonly derivation: readonly ["legacy-empty", "root-null", "usage-null", "model-only", "source-ref-only", "count-only-zero", "count-only-positive", "root-only", "purpose-only-interaction", "purpose-only-delegated", "purpose-only-helper", "purpose-only-unknown", "relationship-only", "main-deeper-provenance", "folded-call-provenance", "folded-result-provenance", "earlier-only", "earlier-nested-tool-native-usage-model", "navigation-only-read"];
4
- readonly reader: readonly ["advertisement-omitted", "advertisement-empty", "advertisement-null", "advertisement-unknown-duplicate-unordered", "advertisement-future-suffix"];
5
- readonly producer: readonly ["producer-canonical-four", "producer-unsorted-four", "producer-duplicate", "producer-unknown"];
3
+ readonly derivation: readonly ["legacy-empty", "root-null", "usage-null", "model-only", "source-ref-only", "count-only-zero", "count-only-positive", "root-only", "purpose-only-interaction", "purpose-only-delegated", "purpose-only-helper", "purpose-only-unknown", "relationship-only", "main-deeper-provenance", "folded-call-provenance", "folded-result-provenance", "earlier-only", "earlier-nested-tool-native-usage-model", "navigation-only-read", "retained-accumulates-all"];
4
+ readonly reader: readonly ["advertisement-omitted", "advertisement-empty", "advertisement-null", "advertisement-unknown-duplicate-unordered", "advertisement-future-suffix", "advertisement-retained-duplicate"];
5
+ readonly producer: readonly ["producer-canonical-four", "producer-unsorted-four", "producer-duplicate", "producer-unknown", "producer-canonical-retained", "producer-unsorted-retained", "producer-duplicate-retained"];
6
6
  };
7
7
  readonly derivation: {
8
8
  readonly cases: readonly [{
9
+ readonly name: "retained-accumulates-all";
10
+ readonly classification: "must-pass";
11
+ readonly provenance: {
12
+ readonly source: "requirement";
13
+ readonly ref: "content-capability-negotiation";
14
+ };
15
+ readonly mutation: {
16
+ readonly description: "Retained evidence accumulates every existing preservation requirement";
17
+ };
18
+ readonly input: {
19
+ readonly detailJSON: "{\"id\":\"fixture-session\",\"harness\":\"pi\",\"startTime\":\"2020-01-01T00:00:00Z\",\"endTime\":\"2020-01-01T00:00:00Z\",\"durationMins\":0,\"totalTokens\":0,\"tokensIn\":0,\"tokensOut\":0,\"turnCount\":1,\"toolCallCount\":1,\"purpose\":\"interaction\",\"turns\":[{\"index\":0,\"role\":\"assistant\",\"depth\":0,\"content\":\"\",\"timestamp\":\"2020-01-01T00:00:00Z\",\"sourceEntryRef\":\"e1\",\"observedModel\":\"provider/model\",\"usage\":{\"ownerId\":\"u1\",\"sourceEntryRef\":\"e1\",\"scope\":\"assistant\",\"completeness\":\"unknown\"},\"toolCalls\":[{\"id\":\"t1\",\"name\":\"tool\",\"namespace\":\"extension\",\"arguments\":\"\",\"result\":\"\"}]}],\"nativeMetadata\":[{\"id\":\"n1\",\"kind\":\"pi.custom.data\",\"source\":{\"entryRef\":\"custom1\",\"sourceType\":\"pi.custom\"},\"customType\":\"extension\",\"data\":{}}],\"diagnostics\":{\"partial\":true},\"retainedUnknown\":[{\"sourceRef\":\"source-0\",\"recordIndex\":2,\"position\":4,\"pointer\":\"\",\"namespace\":\"record\",\"kind\":\"future\",\"payload\":\"{\\\"type\\\":\\\"future\\\"}\"}]}";
20
+ };
21
+ readonly expected: {
22
+ readonly capabilities: readonly ["detailed_usage_v1", "native_metadata_v1", "observed_model_v1", "retained_unknown_v1", "session_graph_provenance_v1", "tool_namespace_v1"];
23
+ };
24
+ }, {
9
25
  readonly name: "legacy-empty";
10
26
  readonly classification: "must-pass";
11
27
  readonly provenance: {
@@ -303,6 +319,22 @@ export declare function sessionGraphCapabilityFixtures(): {
303
319
  };
304
320
  readonly reader: {
305
321
  readonly cases: readonly [{
322
+ readonly name: "advertisement-retained-duplicate";
323
+ readonly classification: "must-pass";
324
+ readonly provenance: {
325
+ readonly source: "requirement";
326
+ readonly ref: "content-capability-negotiation";
327
+ };
328
+ readonly mutation: {
329
+ readonly description: "Exact retained token survives forward-open discovery";
330
+ };
331
+ readonly input: {
332
+ readonly json: "{\"contentCapabilities\":[\"retained_unknown_v1\",\"retained_unknown_v2\",\"retained_unknown_v1\",\"session_graph_provenance_v1\"]}";
333
+ };
334
+ readonly expected: {
335
+ readonly known: readonly ["retained_unknown_v1", "session_graph_provenance_v1"];
336
+ };
337
+ }, {
306
338
  readonly name: "advertisement-omitted";
307
339
  readonly classification: "must-pass";
308
340
  readonly provenance: {
@@ -386,6 +418,54 @@ export declare function sessionGraphCapabilityFixtures(): {
386
418
  };
387
419
  readonly producer: {
388
420
  readonly cases: readonly [{
421
+ readonly name: "producer-canonical-retained";
422
+ readonly classification: "must-pass";
423
+ readonly provenance: {
424
+ readonly source: "requirement";
425
+ readonly ref: "content-capability-negotiation";
426
+ };
427
+ readonly mutation: {
428
+ readonly description: "Emits sorted accumulated inventory";
429
+ };
430
+ readonly input: {
431
+ readonly tokens: readonly ["detailed_usage_v1", "native_metadata_v1", "observed_model_v1", "retained_unknown_v1", "session_graph_provenance_v1", "tool_namespace_v1"];
432
+ };
433
+ readonly expected: {
434
+ readonly accepted: true;
435
+ };
436
+ }, {
437
+ readonly name: "producer-unsorted-retained";
438
+ readonly classification: "must-fail";
439
+ readonly provenance: {
440
+ readonly source: "boundary";
441
+ readonly ref: "content-capability-negotiation";
442
+ };
443
+ readonly mutation: {
444
+ readonly description: "Retained token out of canonical order refuses";
445
+ };
446
+ readonly input: {
447
+ readonly tokens: readonly ["retained_unknown_v1", "observed_model_v1"];
448
+ };
449
+ readonly expected: {
450
+ readonly errorContains: "canonical lexicographic order";
451
+ };
452
+ }, {
453
+ readonly name: "producer-duplicate-retained";
454
+ readonly classification: "must-fail";
455
+ readonly provenance: {
456
+ readonly source: "boundary";
457
+ readonly ref: "content-capability-negotiation";
458
+ };
459
+ readonly mutation: {
460
+ readonly description: "Duplicate retained token refuses";
461
+ };
462
+ readonly input: {
463
+ readonly tokens: readonly ["retained_unknown_v1", "retained_unknown_v1"];
464
+ };
465
+ readonly expected: {
466
+ readonly errorContains: "duplicated";
467
+ };
468
+ }, {
389
469
  readonly name: "producer-canonical-four";
390
470
  readonly classification: "must-pass";
391
471
  readonly provenance: {
@@ -5246,6 +5246,7 @@ export declare function sessionGraphFixtures(): {
5246
5246
  readonly total_turns: 0;
5247
5247
  readonly total_duration_ms: 0;
5248
5248
  readonly total_tokens: 0;
5249
+ readonly pull_request_count: 0;
5249
5250
  };
5250
5251
  readonly models: readonly [];
5251
5252
  readonly contributors: readonly [];
@@ -5291,6 +5292,7 @@ export declare function sessionGraphFixtures(): {
5291
5292
  readonly total_turns: 0;
5292
5293
  readonly total_duration_ms: 0;
5293
5294
  readonly total_tokens: 0;
5295
+ readonly pull_request_count: 0;
5294
5296
  };
5295
5297
  readonly models: readonly [];
5296
5298
  readonly contributors: readonly [];
@@ -5345,6 +5347,7 @@ export declare function sessionGraphFixtures(): {
5345
5347
  readonly total_turns: 0;
5346
5348
  readonly total_duration_ms: 0;
5347
5349
  readonly total_tokens: 0;
5350
+ readonly pull_request_count: 0;
5348
5351
  };
5349
5352
  readonly models: readonly [];
5350
5353
  readonly contributors: readonly [];
@@ -5392,6 +5395,7 @@ export declare function sessionGraphFixtures(): {
5392
5395
  readonly total_turns: 0;
5393
5396
  readonly total_duration_ms: 0;
5394
5397
  readonly total_tokens: 0;
5398
+ readonly pull_request_count: 0;
5395
5399
  };
5396
5400
  readonly models: readonly [];
5397
5401
  readonly contributors: readonly [];
@@ -6145,6 +6149,7 @@ export declare function sessionGraphFixtures(): {
6145
6149
  readonly inputSubmissionCount: 1;
6146
6150
  readonly model: "codex";
6147
6151
  readonly syncStatus: "synced";
6152
+ readonly previouslyPushed: true;
6148
6153
  };
6149
6154
  };
6150
6155
  readonly helperGroups: readonly [{
@@ -6203,6 +6208,7 @@ export declare function sessionGraphFixtures(): {
6203
6208
  readonly inputSubmissionCount: 0;
6204
6209
  readonly model: "codex";
6205
6210
  readonly syncStatus: "synced";
6211
+ readonly previouslyPushed: true;
6206
6212
  };
6207
6213
  };
6208
6214
  readonly helperGroups: readonly [{
@@ -6472,6 +6478,10 @@ export declare function sessionGraphFixtures(): {
6472
6478
  readonly owner_username: "owner";
6473
6479
  readonly owner_avatar_url: null;
6474
6480
  readonly owner_is_discoverable: true;
6481
+ readonly pull_requests: {
6482
+ readonly count: 0;
6483
+ readonly recent: readonly [];
6484
+ };
6475
6485
  };
6476
6486
  };
6477
6487
  readonly helperGroups: readonly [{
@@ -6641,6 +6651,10 @@ export declare function sessionGraphFixtures(): {
6641
6651
  readonly owner_username: "owner";
6642
6652
  readonly owner_avatar_url: null;
6643
6653
  readonly owner_is_discoverable: true;
6654
+ readonly pull_requests: {
6655
+ readonly count: 0;
6656
+ readonly recent: readonly [];
6657
+ };
6644
6658
  readonly input_submission_count: 0;
6645
6659
  };
6646
6660
  };
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ export * from "./internal/generated/versions.gen.js";
5
5
  import { requiredContentCapabilities, KnownContentCapability } from "./internal/generated/content-capabilities.gen.js";
6
6
  import { zNativeMetadataRecord, zTurnDetail, zProjectHash, zServerMessage, zSessionDetailPayload, zSessionDetailReadPayload, zTranscriptContent } from "./internal/generated/contract/zod.gen.js";
7
7
  import { maxNativeMetadataStringBytes } from "./internal/generated/metadata-limits.gen.js";
8
+ import { wireAliasShapes } from "./internal/generated/wire-alias-shapes.gen.js";
8
9
  function assertProjectHash(value, operation) {
9
10
  if (!isProjectHash(value)) {
10
11
  const rendered = renderProjectHashInput(value);
@@ -49,6 +50,7 @@ function scanRawJson(text, policy) {
49
50
  return scanner;
50
51
  }
51
52
  export function parseSessionDetailPayloadValue(value) {
53
+ rejectWireAliases(value, "SessionDetailPayload");
52
54
  rejectExplicitNullEvidence(value);
53
55
  validateRawGraphValue(value);
54
56
  const normalized = structuredClone(value);
@@ -117,6 +119,7 @@ export function parseTranscriptContentText(text) {
117
119
  const raw = JSON.parse(text);
118
120
  if (!isRecord(raw))
119
121
  return zTranscriptContent.parse(raw);
122
+ rejectWireAliases(raw, "TranscriptContent");
120
123
  const normalizedEnvelope = { ...raw, sessionDetail: parseSessionDetailPayloadValue(raw.sessionDetail) };
121
124
  const content = zTranscriptContent.parse(normalizedEnvelope);
122
125
  if (content.sessionDetail === undefined || content.sessionDetail === null)
@@ -297,6 +300,7 @@ class RawJsonScanner {
297
300
  }
298
301
  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]); }
299
302
  function validateSessionDetail(payload) {
303
+ validateRetainedUnknown(payload);
300
304
  validateRelationships(payload);
301
305
  const state = newDetailState(payload);
302
306
  const turns = validateTurnEvidence(payload.turns ?? [], state);
@@ -308,6 +312,77 @@ function validateSessionDetail(payload) {
308
312
  if (state.nativeCount > 0 && String(payload.harness) !== "pi")
309
313
  failSemantic("Pi native metadata requires harness pi across every history partition");
310
314
  }
315
+ function validateRetainedUnknown(payload) {
316
+ if ((payload.retainedUnknown?.length ?? 0) > 0 && payload.diagnostics?.partial !== true)
317
+ failSemantic("retainedUnknown requires diagnostics.partial=true");
318
+ const sources = new Map();
319
+ for (const [index, record] of (payload.retainedUnknown ?? []).entries()) {
320
+ if (!Number.isSafeInteger(record.recordIndex) || !Number.isSafeInteger(record.position) || record.recordIndex < 0 || record.position < record.recordIndex)
321
+ failSemantic("retainedUnknown has invalid recordIndex or position");
322
+ // Scan strings as JSON as well: typed JS values can contain unpaired surrogates.
323
+ scanRawJsonText(JSON.stringify([record.sourceRef, record.namespace, record.kind, record.pointer]));
324
+ const previous = sources.get(record.sourceRef);
325
+ if (previous !== undefined && (record.position <= previous.position || record.recordIndex < previous.record))
326
+ failSemantic("retainedUnknown repeats or reverses a source position");
327
+ const pointers = previous?.record === record.recordIndex ? previous.pointers : new RetainedPointerNode();
328
+ if (!pointers.insert(record.pointer))
329
+ failSemantic("retainedUnknown duplicates or overlaps a source pointer");
330
+ try {
331
+ scanRawJsonText(record.payload, { maxDocumentBytes: 8 << 20, maxDocumentDepth: 64 });
332
+ }
333
+ catch {
334
+ // Native keys/paths are untrusted even when a producer claims redaction.
335
+ // Do not expose the scanner exception, its message, or a cause chain.
336
+ failSemantic(`retainedUnknown[${index}].payload failed JSON syntax or safety validation; provide one complete JSON value with valid Unicode, unique object keys, and the published byte/depth bounds`);
337
+ }
338
+ sources.set(record.sourceRef, { position: record.position, record: record.recordIndex, pointers });
339
+ }
340
+ }
341
+ class RetainedPointerNode {
342
+ terminal = false;
343
+ children = new Map();
344
+ insert(pointer) {
345
+ let node = this;
346
+ for (const component of pointer === "" ? [] : pointer.slice(1).split("/")) {
347
+ if (node.terminal)
348
+ return false;
349
+ let child = node.children.get(component);
350
+ if (child === undefined) {
351
+ child = new RetainedPointerNode();
352
+ node.children.set(component, child);
353
+ }
354
+ node = child;
355
+ }
356
+ if (node.terminal || node.children.size > 0)
357
+ return false;
358
+ node.terminal = true;
359
+ return true;
360
+ }
361
+ }
362
+ function rejectWireAliases(value, descriptor) {
363
+ const shape = typeof descriptor === "string" ? wireAliasShapes[descriptor] : descriptor;
364
+ if (shape === undefined)
365
+ return;
366
+ if (shape.items !== undefined && Array.isArray(value)) {
367
+ for (const item of value)
368
+ rejectWireAliases(item, shape.items);
369
+ }
370
+ if (shape.fields === undefined || !isRecord(value))
371
+ return;
372
+ const fields = shape.fields;
373
+ // All canonical contract keys are ASCII. Go's Unicode simple-fold aliases
374
+ // additionally include long s and Kelvin sign; lowercasing handles the latter.
375
+ const fold = (key) => key.replaceAll("ſ", "s").toLowerCase();
376
+ const canonical = new Map(Object.keys(fields).map(key => [fold(key), key]));
377
+ for (const key of Object.keys(value)) {
378
+ const name = canonical.get(fold(key));
379
+ if (name !== undefined && key !== name)
380
+ failSemantic(`key ${key} aliases canonical field ${name}; use only exact published field names`);
381
+ const child = fields[key];
382
+ if (child !== undefined)
383
+ rejectWireAliases(value[key], child);
384
+ }
385
+ }
311
386
  function parseSessionDetailReadPayloadValue(value) {
312
387
  rejectExplicitNullEvidence(value);
313
388
  if (!isRecord(value))
@@ -12,6 +12,7 @@ export declare const KnownContentCapability: Readonly<{
12
12
  readonly DetailedUsageV1: "detailed_usage_v1";
13
13
  readonly NativeMetadataV1: "native_metadata_v1";
14
14
  readonly ObservedModelV1: "observed_model_v1";
15
+ readonly RetainedUnknownV1: "retained_unknown_v1";
15
16
  readonly SessionGraphProvenanceV1: "session_graph_provenance_v1";
16
17
  readonly ToolNamespaceV1: "tool_namespace_v1";
17
18
  }>;
@@ -11,10 +11,11 @@ export const KnownContentCapability = Object.freeze({
11
11
  DetailedUsageV1: "detailed_usage_v1",
12
12
  NativeMetadataV1: "native_metadata_v1",
13
13
  ObservedModelV1: "observed_model_v1",
14
+ RetainedUnknownV1: "retained_unknown_v1",
14
15
  SessionGraphProvenanceV1: "session_graph_provenance_v1",
15
16
  ToolNamespaceV1: "tool_namespace_v1",
16
17
  });
17
- export const AllContentCapabilities = Object.freeze([KnownContentCapability.DetailedUsageV1, KnownContentCapability.NativeMetadataV1, KnownContentCapability.ObservedModelV1, KnownContentCapability.SessionGraphProvenanceV1, KnownContentCapability.ToolNamespaceV1]);
18
+ export const AllContentCapabilities = Object.freeze([KnownContentCapability.DetailedUsageV1, KnownContentCapability.NativeMetadataV1, KnownContentCapability.ObservedModelV1, KnownContentCapability.RetainedUnknownV1, KnownContentCapability.SessionGraphProvenanceV1, KnownContentCapability.ToolNamespaceV1]);
18
19
  export function isContentCapability(value) {
19
20
  return typeof value === "string" && AllContentCapabilities.includes(value);
20
21
  }
@@ -55,6 +56,8 @@ export function requiredContentCapabilities(detail) {
55
56
  found.add(KnownContentCapability.SessionGraphProvenanceV1);
56
57
  if ((detail.nativeMetadata?.length ?? 0) > 0)
57
58
  found.add(KnownContentCapability.NativeMetadataV1);
59
+ if ((detail.retainedUnknown?.length ?? 0) > 0 || detail.diagnostics !== undefined)
60
+ found.add(KnownContentCapability.RetainedUnknownV1);
58
61
  visitTurns(detail.turns, found);
59
62
  for (const section of detail.earlierHistory ?? []) {
60
63
  if ((section.nativeMetadata?.length ?? 0) > 0)