agent-inspect 6.20.0 → 6.21.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +1 -1
  3. package/docs/TRACE-CONTRACTS.md +39 -0
  4. package/package.json +1 -1
  5. package/packages/cli/dist/{chunk-QPRJUVLH.mjs → chunk-CRUHIL5J.mjs} +16 -2
  6. package/packages/cli/dist/chunk-CRUHIL5J.mjs.map +1 -0
  7. package/packages/cli/dist/index.cjs +22 -3
  8. package/packages/cli/dist/index.cjs.map +1 -1
  9. package/packages/cli/dist/index.mjs +4 -4
  10. package/packages/cli/dist/index.mjs.map +1 -1
  11. package/packages/cli/dist/{src-6W5ZR3I7.mjs → src-MNJFYW3W.mjs} +3 -3
  12. package/packages/cli/dist/{src-6W5ZR3I7.mjs.map → src-MNJFYW3W.mjs.map} +1 -1
  13. package/packages/core/dist/advanced.cjs +3 -0
  14. package/packages/core/dist/advanced.cjs.map +1 -1
  15. package/packages/core/dist/advanced.d.cts +6 -156
  16. package/packages/core/dist/advanced.d.ts +6 -156
  17. package/packages/core/dist/advanced.mjs +6 -60
  18. package/packages/core/dist/advanced.mjs.map +1 -1
  19. package/packages/core/dist/checks.cjs +558 -25
  20. package/packages/core/dist/checks.cjs.map +1 -1
  21. package/packages/core/dist/checks.d.cts +81 -3
  22. package/packages/core/dist/checks.d.ts +81 -3
  23. package/packages/core/dist/checks.mjs +2 -2
  24. package/packages/core/dist/{chunk-G47KQB42.mjs → chunk-43SHIPZM.mjs} +556 -29
  25. package/packages/core/dist/chunk-43SHIPZM.mjs.map +1 -0
  26. package/packages/core/dist/{chunk-3XS5O4JI.mjs → chunk-R4SSYU6D.mjs} +3 -3
  27. package/packages/core/dist/chunk-R4SSYU6D.mjs.map +1 -0
  28. package/packages/core/dist/{chunk-5YFJJFXU.mjs → chunk-WGDSM37E.mjs} +3 -3
  29. package/packages/core/dist/{chunk-5YFJJFXU.mjs.map → chunk-WGDSM37E.mjs.map} +1 -1
  30. package/packages/core/dist/{context-C5ye_xcS.d.cts → context-DOOT9GZ-.d.cts} +1 -1
  31. package/packages/core/dist/{context-CNsNSgE2.d.ts → context-DOX2-RjH.d.ts} +1 -1
  32. package/packages/core/dist/diff.d.cts +1 -1
  33. package/packages/core/dist/diff.d.ts +1 -1
  34. package/packages/core/dist/exporters.d.cts +1 -1
  35. package/packages/core/dist/exporters.d.ts +1 -1
  36. package/packages/core/dist/exporters.mjs +1 -1
  37. package/packages/core/dist/{index-rVpX5PPh.d.cts → index-BG-ZBESI.d.cts} +153 -1
  38. package/packages/core/dist/{index-Bg9ZdsdS.d.ts → index-xevIlqdd.d.ts} +153 -1
  39. package/packages/core/dist/index.d.cts +3 -3
  40. package/packages/core/dist/index.d.ts +3 -3
  41. package/packages/core/dist/index.mjs +3 -3
  42. package/packages/core/dist/persisted.d.cts +1 -1
  43. package/packages/core/dist/persisted.d.ts +1 -1
  44. package/packages/core/dist/{types--ainI31J.d.ts → types-Bx3mEZ04.d.ts} +1 -1
  45. package/packages/core/dist/{types-UnrNaTo2.d.cts → types-VzKihKQo.d.cts} +1 -1
  46. package/packages/cli/dist/chunk-QPRJUVLH.mjs.map +0 -1
  47. package/packages/core/dist/chunk-3XS5O4JI.mjs.map +0 -1
  48. package/packages/core/dist/chunk-G47KQB42.mjs.map +0 -1
@@ -64,6 +64,17 @@ function formatProgrammaticDiagnostic(code, detail) {
64
64
  }
65
65
 
66
66
  // packages/core/src/outcomes/types.ts
67
+ var OBSERVED_OUTCOME_METHODS = [
68
+ "dom",
69
+ "accessibility",
70
+ "snapshot",
71
+ "network",
72
+ "storage",
73
+ "filesystem",
74
+ "database",
75
+ "queue",
76
+ "custom"
77
+ ];
67
78
  var OUTCOME_ATTRIBUTE_STATUS_KEY = "outcomeStatus";
68
79
  var OUTCOME_ATTRIBUTE_EXPECTATION_KEY = "expectation";
69
80
  var OUTCOME_ATTRIBUTE_METHOD_KEY = "method";
@@ -910,6 +921,248 @@ function buildTraceFacts(input) {
910
921
  };
911
922
  }
912
923
 
924
+ // packages/core/src/sessions/metadata.ts
925
+ function isNonEmptyString(value) {
926
+ return typeof value === "string" && value.trim() !== "";
927
+ }
928
+ function finitePositiveInt(value) {
929
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
930
+ return void 0;
931
+ }
932
+ return Math.trunc(value);
933
+ }
934
+ function extractSessionWorkflowMetadata(record) {
935
+ if (!record) return void 0;
936
+ const out = {};
937
+ let found = false;
938
+ const assignString = (key, value) => {
939
+ if (isNonEmptyString(value)) {
940
+ out[key] = value.trim();
941
+ found = true;
942
+ }
943
+ };
944
+ assignString("sessionId", record.sessionId);
945
+ assignString("conversationId", record.conversationId);
946
+ assignString("groupId", record.groupId);
947
+ assignString("parentGroupId", record.parentGroupId);
948
+ assignString("retryOf", record.retryOf);
949
+ assignString("retryReason", record.retryReason);
950
+ assignString("handoffFrom", record.handoffFrom);
951
+ assignString("handoffTo", record.handoffTo);
952
+ assignString("subAgentId", record.subAgentId);
953
+ assignString("subAgentName", record.subAgentName);
954
+ assignString("jobId", record.jobId);
955
+ assignString("queueName", record.queueName);
956
+ assignString("workflowName", record.workflowName);
957
+ assignString("workflowStep", record.workflowStep);
958
+ assignString("toolCallId", record.toolCallId);
959
+ assignString("mcpToolCallId", record.mcpToolCallId);
960
+ assignString("linkedStepId", record.linkedStepId);
961
+ assignString("correlationId", record.correlationId);
962
+ assignString("requestId", record.requestId);
963
+ assignString("decisionId", record.decisionId);
964
+ const attempt = finitePositiveInt(record.attempt);
965
+ if (attempt !== void 0) {
966
+ out.attempt = attempt;
967
+ found = true;
968
+ }
969
+ return found ? out : void 0;
970
+ }
971
+
972
+ // packages/core/src/checks/contract-scope.ts
973
+ function diagnostic(code, message, ruleId) {
974
+ return {
975
+ code,
976
+ message,
977
+ severity: "error",
978
+ ...ruleId !== void 0 ? { ruleId } : {}
979
+ };
980
+ }
981
+ function isNonEmptyString2(value) {
982
+ return typeof value === "string" && value.trim() !== "";
983
+ }
984
+ function scopeEntries(scope) {
985
+ const out = [];
986
+ if (isNonEmptyString2(scope.runId)) out.push(["runId", scope.runId.trim()]);
987
+ if (isNonEmptyString2(scope.subAgentId)) out.push(["subAgentId", scope.subAgentId.trim()]);
988
+ if (isNonEmptyString2(scope.groupId)) out.push(["groupId", scope.groupId.trim()]);
989
+ if (isNonEmptyString2(scope.workflowStep)) {
990
+ out.push(["workflowStep", scope.workflowStep.trim()]);
991
+ }
992
+ if (isNonEmptyString2(scope.rootEventId)) {
993
+ out.push(["rootEventId", scope.rootEventId.trim()]);
994
+ }
995
+ return out;
996
+ }
997
+ function mergeWorkflowMeta(into, from) {
998
+ if (!from) return;
999
+ for (const [key, value] of Object.entries(from)) {
1000
+ if (value === void 0) continue;
1001
+ if (into[key] === void 0) {
1002
+ into[key] = value;
1003
+ }
1004
+ }
1005
+ }
1006
+ function workflowMetadataForRun(events, runId) {
1007
+ const preferred = {};
1008
+ const fallback = {};
1009
+ for (const event of events) {
1010
+ if (event.runId !== runId) continue;
1011
+ const attrs = event.attributes;
1012
+ if (!attrs || typeof attrs !== "object") continue;
1013
+ const direct = extractSessionWorkflowMetadata(attrs);
1014
+ const nested = attrs.metadata && typeof attrs.metadata === "object" ? extractSessionWorkflowMetadata(attrs.metadata) : void 0;
1015
+ const bag = {};
1016
+ mergeWorkflowMeta(bag, direct);
1017
+ mergeWorkflowMeta(bag, nested);
1018
+ if (event.kind === "RUN") {
1019
+ mergeWorkflowMeta(preferred, bag);
1020
+ } else {
1021
+ mergeWorkflowMeta(fallback, bag);
1022
+ }
1023
+ }
1024
+ mergeWorkflowMeta(preferred, fallback);
1025
+ return preferred;
1026
+ }
1027
+ function flattenNodes(nodes) {
1028
+ return nodes.flatMap((node) => [node, ...flattenNodes(node.children)]);
1029
+ }
1030
+ function collectSubtreeEventIds(run, rootEventId) {
1031
+ const nodes = flattenNodes(run.children);
1032
+ const byId = new Map(nodes.map((node) => [node.event.eventId, node]));
1033
+ const root = byId.get(rootEventId);
1034
+ if (!root) return void 0;
1035
+ const ids = /* @__PURE__ */ new Set();
1036
+ const visit = (node) => {
1037
+ ids.add(node.event.eventId);
1038
+ for (const child of node.children) visit(child);
1039
+ };
1040
+ visit(root);
1041
+ return ids;
1042
+ }
1043
+ function filterTree(nodes, keep) {
1044
+ const out = [];
1045
+ for (const node of nodes) {
1046
+ if (!keep.has(node.event.eventId)) continue;
1047
+ out.push({
1048
+ ...node,
1049
+ children: filterTree(node.children, keep)
1050
+ });
1051
+ }
1052
+ return out;
1053
+ }
1054
+ function projectInput(input, run, eventIds) {
1055
+ const runEvents = input.read.events.filter((event) => event.runId === run.runId);
1056
+ const projectedEvents = eventIds === void 0 ? runEvents : runEvents.filter((event) => eventIds.has(event.eventId) || event.kind === "RUN");
1057
+ const projectedChildren = eventIds === void 0 ? run.children : filterTree(run.children, eventIds);
1058
+ const projectedRun = {
1059
+ ...run,
1060
+ children: projectedChildren,
1061
+ metadata: {
1062
+ ...run.metadata,
1063
+ totalEvents: flattenNodes(projectedChildren).length
1064
+ }
1065
+ };
1066
+ return {
1067
+ evidenceEventCount: projectedEvents.length,
1068
+ input: {
1069
+ ...input,
1070
+ selectedRun: projectedRun,
1071
+ read: {
1072
+ ...input.read,
1073
+ runs: [projectedRun],
1074
+ events: projectedEvents
1075
+ }
1076
+ }
1077
+ };
1078
+ }
1079
+ function resolveTraceContractScope(input, scope) {
1080
+ if (scope === void 0) {
1081
+ return { diagnostics: [] };
1082
+ }
1083
+ const entries = scopeEntries(scope);
1084
+ if (entries.length === 0) {
1085
+ return {
1086
+ diagnostics: [
1087
+ diagnostic(
1088
+ "AI_CHECK_INVALID_CONFIG",
1089
+ "TraceContract scope requires at least one explicit selector (runId, subAgentId, groupId, workflowStep, or rootEventId).",
1090
+ "contract.scope.empty"
1091
+ )
1092
+ ]
1093
+ };
1094
+ }
1095
+ const matchedSelectors = Object.fromEntries(entries);
1096
+ const actorKeys = entries.filter(([key]) => key !== "rootEventId");
1097
+ const rootEventId = matchedSelectors.rootEventId;
1098
+ let candidates = [...input.read.runs];
1099
+ if (matchedSelectors.runId) {
1100
+ candidates = candidates.filter((run2) => run2.runId === matchedSelectors.runId);
1101
+ }
1102
+ for (const [key, value] of actorKeys) {
1103
+ if (key === "runId") continue;
1104
+ candidates = candidates.filter((run2) => {
1105
+ const meta = workflowMetadataForRun(input.read.events, run2.runId);
1106
+ const actual = meta[key];
1107
+ return actual === value;
1108
+ });
1109
+ }
1110
+ if (rootEventId && actorKeys.length === 0) {
1111
+ candidates = candidates.filter(
1112
+ (run2) => flattenNodes(run2.children).some((node) => node.event.eventId === rootEventId)
1113
+ );
1114
+ }
1115
+ if (candidates.length === 0) {
1116
+ return {
1117
+ diagnostics: [
1118
+ diagnostic(
1119
+ "AI_CHECK_RUN_SELECTION_REQUIRED",
1120
+ `TraceContract scope matched zero actors for ${JSON.stringify(matchedSelectors)}. Missing actor metadata does not fall back to the whole session.`,
1121
+ "contract.scope.zero-match"
1122
+ )
1123
+ ]
1124
+ };
1125
+ }
1126
+ if (candidates.length > 1) {
1127
+ return {
1128
+ diagnostics: [
1129
+ diagnostic(
1130
+ "AI_CHECK_RUN_SELECTION_REQUIRED",
1131
+ `TraceContract scope matched ${candidates.length} actors for ${JSON.stringify(matchedSelectors)} (runIds: ${candidates.map((run2) => run2.runId).sort().join(", ")}). Singular actor selectors must resolve to exactly one run.`,
1132
+ "contract.scope.ambiguous"
1133
+ )
1134
+ ]
1135
+ };
1136
+ }
1137
+ const run = candidates[0];
1138
+ let eventIds;
1139
+ if (rootEventId) {
1140
+ eventIds = collectSubtreeEventIds(run, rootEventId);
1141
+ if (eventIds === void 0) {
1142
+ return {
1143
+ diagnostics: [
1144
+ diagnostic(
1145
+ "AI_CHECK_INVALID_ARGUMENTS",
1146
+ `TraceContract scope rootEventId ${rootEventId} was not found in run ${run.runId}.`,
1147
+ "contract.scope.root-missing"
1148
+ )
1149
+ ]
1150
+ };
1151
+ }
1152
+ }
1153
+ const projected = projectInput(input, run, eventIds);
1154
+ return {
1155
+ resolved: {
1156
+ runId: run.runId,
1157
+ ...rootEventId !== void 0 ? { rootEventId } : {},
1158
+ matchedSelectors,
1159
+ evidenceEventCount: projected.evidenceEventCount,
1160
+ input: projected.input
1161
+ },
1162
+ diagnostics: []
1163
+ };
1164
+ }
1165
+
913
1166
  // packages/core/src/checks/contract.ts
914
1167
  function contractFailFinding(ruleId, message, evidence, expected, actual) {
915
1168
  return {
@@ -933,12 +1186,141 @@ function cloneBody(body) {
933
1186
  ...body.run ? { run: { ...body.run } } : {},
934
1187
  ...body.tools ? { tools: { ...body.tools } } : {},
935
1188
  ...body.llm ? { llm: { ...body.llm } } : {},
936
- ...body.observations ? { observations: { ...body.observations } } : {}
1189
+ ...body.observations ? {
1190
+ observations: {
1191
+ ...body.observations,
1192
+ ...body.observations.requireProvenance ? { requireProvenance: { ...body.observations.requireProvenance } } : {}
1193
+ }
1194
+ } : {}
937
1195
  };
938
1196
  }
939
1197
  function bodyHasRules(body) {
940
1198
  return body.run !== void 0 || body.tools !== void 0 || body.llm !== void 0 || body.observations !== void 0;
941
1199
  }
1200
+ var MAX_EVIDENCE_EVENT_IDS = 16;
1201
+ var METHOD_VOCABULARY = new Set(OBSERVED_OUTCOME_METHODS);
1202
+ function parseEvidenceEventIds(evidence) {
1203
+ if (evidence === void 0 || evidence === null) {
1204
+ return { status: "missing" };
1205
+ }
1206
+ if (typeof evidence === "string") {
1207
+ const trimmed = evidence.trim();
1208
+ if (trimmed === "") {
1209
+ return { status: "invalid", reason: "empty evidence string" };
1210
+ }
1211
+ return { status: "ok", eventIds: [trimmed] };
1212
+ }
1213
+ if (typeof evidence !== "object" || Array.isArray(evidence)) {
1214
+ return {
1215
+ status: "invalid",
1216
+ reason: "evidence must be a string event id or { eventId } / { eventIds }"
1217
+ };
1218
+ }
1219
+ const record = evidence;
1220
+ if (typeof record.eventId === "string") {
1221
+ const trimmed = record.eventId.trim();
1222
+ if (trimmed === "") {
1223
+ return { status: "invalid", reason: "empty evidence.eventId" };
1224
+ }
1225
+ return { status: "ok", eventIds: [trimmed] };
1226
+ }
1227
+ if (Array.isArray(record.eventIds)) {
1228
+ if (record.eventIds.length === 0) {
1229
+ return { status: "invalid", reason: "empty evidence.eventIds" };
1230
+ }
1231
+ if (record.eventIds.length > MAX_EVIDENCE_EVENT_IDS) {
1232
+ return {
1233
+ status: "invalid",
1234
+ reason: `evidence.eventIds exceeds bound of ${MAX_EVIDENCE_EVENT_IDS}`
1235
+ };
1236
+ }
1237
+ const eventIds = [];
1238
+ for (const item of record.eventIds) {
1239
+ if (typeof item !== "string" || item.trim() === "") {
1240
+ return { status: "invalid", reason: "evidence.eventIds contains a non-string id" };
1241
+ }
1242
+ eventIds.push(item.trim());
1243
+ }
1244
+ return { status: "ok", eventIds };
1245
+ }
1246
+ return {
1247
+ status: "invalid",
1248
+ reason: "evidence must include eventId or eventIds"
1249
+ };
1250
+ }
1251
+ function provenanceFindingsForOutcome(outcome, requireProvenance, knownEventIds) {
1252
+ const findings = [];
1253
+ const evidenceBase = [
1254
+ {
1255
+ runId: outcome.runId,
1256
+ eventId: outcome.outcomeId,
1257
+ kind: "OUTCOME",
1258
+ name: outcome.name,
1259
+ path: `outcome.${outcome.name}`
1260
+ }
1261
+ ];
1262
+ if (requireProvenance.method) {
1263
+ if (outcome.method === void 0 || outcome.method.trim() === "") {
1264
+ findings.push(
1265
+ contractFailFinding(
1266
+ "contract.observation.provenance.method",
1267
+ `Observation ${outcome.name} is missing a structural method.`,
1268
+ evidenceBase,
1269
+ { method: true },
1270
+ { name: outcome.name, method: outcome.method ?? null }
1271
+ )
1272
+ );
1273
+ } else if (!METHOD_VOCABULARY.has(outcome.method)) {
1274
+ findings.push(
1275
+ contractFailFinding(
1276
+ "contract.observation.provenance.method",
1277
+ `Observation ${outcome.name} method ${outcome.method} is outside the bounded ObservedOutcomeMethod vocabulary.`,
1278
+ evidenceBase,
1279
+ { methodVocabulary: [...OBSERVED_OUTCOME_METHODS] },
1280
+ { name: outcome.name, method: outcome.method }
1281
+ )
1282
+ );
1283
+ }
1284
+ }
1285
+ if (requireProvenance.evidence || requireProvenance.sameRunEventReference) {
1286
+ const parsed = parseEvidenceEventIds(outcome.evidence);
1287
+ if (parsed.status === "missing") {
1288
+ findings.push(
1289
+ contractFailFinding(
1290
+ "contract.observation.provenance.evidence",
1291
+ `Observation ${outcome.name} is missing bounded evidence references.`,
1292
+ evidenceBase,
1293
+ { evidence: true },
1294
+ { name: outcome.name, evidence: null }
1295
+ )
1296
+ );
1297
+ } else if (parsed.status === "invalid") {
1298
+ findings.push(
1299
+ contractFailFinding(
1300
+ "contract.observation.provenance.evidence",
1301
+ `Observation ${outcome.name} evidence is not a bounded event reference (${parsed.reason}).`,
1302
+ evidenceBase,
1303
+ { evidence: true },
1304
+ { name: outcome.name, reason: parsed.reason }
1305
+ )
1306
+ );
1307
+ } else if (requireProvenance.sameRunEventReference) {
1308
+ const missing = parsed.eventIds.filter((eventId) => !knownEventIds.has(eventId));
1309
+ if (missing.length > 0) {
1310
+ findings.push(
1311
+ contractFailFinding(
1312
+ "contract.observation.provenance.same-run",
1313
+ `Observation ${outcome.name} references event ids missing from the same run: ${missing.join(", ")}.`,
1314
+ evidenceBase,
1315
+ { sameRunEventReference: true, eventIds: parsed.eventIds },
1316
+ { name: outcome.name, missing }
1317
+ )
1318
+ );
1319
+ }
1320
+ }
1321
+ }
1322
+ return findings;
1323
+ }
942
1324
  function contractToRules(contract) {
943
1325
  const rules = [];
944
1326
  const allowedStatuses = contract.run?.allowedStatuses ?? [];
@@ -1086,9 +1468,71 @@ function contractToRules(contract) {
1086
1468
  })
1087
1469
  );
1088
1470
  }
1471
+ const requireProvenance = contract.observations.requireProvenance;
1472
+ if (requireProvenance && (requireProvenance.method || requireProvenance.evidence || requireProvenance.sameRunEventReference)) {
1473
+ rules.push({
1474
+ id: "contract.observation.provenance",
1475
+ category: "run",
1476
+ defaultSeverity: "error",
1477
+ evaluate(context) {
1478
+ const outcomes = extractOutcomesFromPersistedEvents(context.events);
1479
+ const knownEventIds = new Set(context.events.map((event) => event.eventId));
1480
+ const targets = required.length > 0 ? outcomes.filter((outcome) => required.includes(outcome.name)) : outcomes;
1481
+ return targets.flatMap(
1482
+ (outcome) => provenanceFindingsForOutcome(outcome, requireProvenance, knownEventIds)
1483
+ );
1484
+ }
1485
+ });
1486
+ }
1089
1487
  }
1090
1488
  return rules;
1091
1489
  }
1490
+ function validateScopeShape(scope) {
1491
+ if (scope === void 0) return [];
1492
+ const hasSelector = typeof scope.runId === "string" && scope.runId.trim() !== "" || typeof scope.subAgentId === "string" && scope.subAgentId.trim() !== "" || typeof scope.groupId === "string" && scope.groupId.trim() !== "" || typeof scope.workflowStep === "string" && scope.workflowStep.trim() !== "" || typeof scope.rootEventId === "string" && scope.rootEventId.trim() !== "";
1493
+ if (!hasSelector) {
1494
+ return [
1495
+ {
1496
+ code: "contract.scope.empty",
1497
+ severity: "error",
1498
+ message: "scope requires at least one explicit selector (runId, subAgentId, groupId, workflowStep, or rootEventId).",
1499
+ path: "scope"
1500
+ }
1501
+ ];
1502
+ }
1503
+ return [];
1504
+ }
1505
+ function validateProvenanceShape(observations) {
1506
+ const diagnostics = [];
1507
+ const provenance = observations?.requireProvenance;
1508
+ if (!provenance) return diagnostics;
1509
+ const enabled = provenance.method === true || provenance.evidence === true || provenance.sameRunEventReference === true;
1510
+ if (!enabled) {
1511
+ diagnostics.push({
1512
+ code: "contract.observation.provenance.empty",
1513
+ severity: "warning",
1514
+ message: "observations.requireProvenance is set but no flags are enabled.",
1515
+ path: "observations.requireProvenance"
1516
+ });
1517
+ }
1518
+ if (provenance.sameRunEventReference === true && provenance.evidence !== true) {
1519
+ diagnostics.push({
1520
+ code: "contract.observation.provenance.same-run-without-evidence",
1521
+ severity: "info",
1522
+ message: "sameRunEventReference implies evidence checking; set requireProvenance.evidence: true for clarity.",
1523
+ path: "observations.requireProvenance.sameRunEventReference"
1524
+ });
1525
+ }
1526
+ if (enabled && (observations?.required?.length ?? 0) === 0) {
1527
+ diagnostics.push({
1528
+ code: "contract.observation.provenance.without-required",
1529
+ severity: "warning",
1530
+ message: "requireProvenance without observations.required applies to every observed outcome in scope.",
1531
+ path: "observations.requireProvenance"
1532
+ });
1533
+ }
1534
+ return diagnostics;
1535
+ }
1092
1536
  function validateAlternativesShape(alternatives) {
1093
1537
  const diagnostics = [];
1094
1538
  if (alternatives === void 0) return diagnostics;
@@ -1195,9 +1639,11 @@ function evaluateBody(input, body, options = {}) {
1195
1639
  });
1196
1640
  }
1197
1641
  function defineTraceContract(input) {
1198
- const shapeErrors = validateAlternativesShape(input.alternatives).filter(
1199
- (item) => item.severity === "error"
1200
- );
1642
+ const shapeErrors = [
1643
+ ...validateAlternativesShape(input.alternatives),
1644
+ ...validateScopeShape(input.scope),
1645
+ ...validateProvenanceShape(input.observations)
1646
+ ].filter((item) => item.severity === "error");
1201
1647
  if (shapeErrors.length > 0) {
1202
1648
  throw new TypeError(shapeErrors.map((item) => item.message).join(" "));
1203
1649
  }
@@ -1209,15 +1655,25 @@ function defineTraceContract(input) {
1209
1655
  contract: cloneBody(branch.contract)
1210
1656
  }))
1211
1657
  } : void 0;
1658
+ const scope = input.scope ? {
1659
+ ...input.scope.runId !== void 0 ? { runId: input.scope.runId } : {},
1660
+ ...input.scope.subAgentId !== void 0 ? { subAgentId: input.scope.subAgentId } : {},
1661
+ ...input.scope.groupId !== void 0 ? { groupId: input.scope.groupId } : {},
1662
+ ...input.scope.workflowStep !== void 0 ? { workflowStep: input.scope.workflowStep } : {},
1663
+ ...input.scope.rootEventId !== void 0 ? { rootEventId: input.scope.rootEventId } : {}
1664
+ } : void 0;
1212
1665
  return {
1213
1666
  ...body,
1667
+ ...scope ? { scope } : {},
1214
1668
  ...alternatives ? { alternatives } : {}
1215
1669
  };
1216
1670
  }
1217
1671
  function evaluateTraceContract(input, contract, options = {}) {
1218
- const shapeErrors = validateAlternativesShape(contract.alternatives).filter(
1219
- (item) => item.severity === "error"
1220
- );
1672
+ const shapeErrors = [
1673
+ ...validateAlternativesShape(contract.alternatives),
1674
+ ...validateScopeShape(contract.scope),
1675
+ ...validateProvenanceShape(contract.observations)
1676
+ ].filter((item) => item.severity === "error");
1221
1677
  if (shapeErrors.length > 0) {
1222
1678
  return {
1223
1679
  ok: false,
@@ -1241,13 +1697,58 @@ function evaluateTraceContract(input, contract, options = {}) {
1241
1697
  ruleExecutions: []
1242
1698
  };
1243
1699
  }
1244
- const base = evaluateBody(input, contract, options);
1700
+ const scoped = resolveTraceContractScope(input, contract.scope);
1701
+ if (scoped.diagnostics.length > 0) {
1702
+ return {
1703
+ ok: false,
1704
+ status: "error",
1705
+ format: input.read.format,
1706
+ ...options.runId !== void 0 ? { runId: options.runId } : {},
1707
+ ...scoped.resolved?.runId !== void 0 ? { runId: scoped.resolved.runId } : {},
1708
+ summary: {
1709
+ passed: 0,
1710
+ failed: 0,
1711
+ warnings: 0,
1712
+ errors: scoped.diagnostics.length,
1713
+ rulesEvaluated: 0
1714
+ },
1715
+ findings: [],
1716
+ diagnostics: scoped.diagnostics,
1717
+ ruleExecutions: []
1718
+ };
1719
+ }
1720
+ const effectiveInput = scoped.resolved?.input ?? input;
1721
+ const effectiveRunId = scoped.resolved?.runId ?? options.runId;
1722
+ const base = evaluateBody(effectiveInput, contract, {
1723
+ ...effectiveRunId !== void 0 ? { runId: effectiveRunId } : {}
1724
+ });
1725
+ const scopeFinding = scoped.resolved ? {
1726
+ ruleId: "contract.scope.selected",
1727
+ severity: "info",
1728
+ status: "pass",
1729
+ message: `Selected actor ${scoped.resolved.runId} with ${scoped.resolved.evidenceEventCount} evidence event(s).`,
1730
+ expected: scoped.resolved.matchedSelectors,
1731
+ actual: {
1732
+ runId: scoped.resolved.runId,
1733
+ evidenceEventCount: scoped.resolved.evidenceEventCount,
1734
+ ...scoped.resolved.rootEventId !== void 0 ? { rootEventId: scoped.resolved.rootEventId } : {}
1735
+ },
1736
+ evidence: [
1737
+ {
1738
+ runId: scoped.resolved.runId,
1739
+ kind: "RUN",
1740
+ path: "scope"
1741
+ }
1742
+ ]
1743
+ } : void 0;
1245
1744
  const branches = contract.alternatives?.anyOf ?? [];
1246
1745
  if (branches.length === 0) {
1247
- return base;
1746
+ return scopeFinding ? mergeContractResults(effectiveInput, [base], [scopeFinding]) : base;
1248
1747
  }
1249
1748
  const branchEvaluations = branches.map((branch) => {
1250
- const result = evaluateBody(input, branch.contract, options);
1749
+ const result = evaluateBody(effectiveInput, branch.contract, {
1750
+ ...effectiveRunId !== void 0 ? { runId: effectiveRunId } : {}
1751
+ });
1251
1752
  return { branch, result };
1252
1753
  });
1253
1754
  const satisfied = branchEvaluations.filter((item) => item.result.ok && item.result.status === "pass").map((item) => item.branch.id);
@@ -1267,7 +1768,11 @@ function evaluateTraceContract(input, contract, options = {}) {
1267
1768
  },
1268
1769
  evidence: []
1269
1770
  };
1270
- return mergeContractResults(input, [base], [passFinding]);
1771
+ return mergeContractResults(
1772
+ effectiveInput,
1773
+ [base],
1774
+ [...scopeFinding ? [scopeFinding] : [], passFinding]
1775
+ );
1271
1776
  }
1272
1777
  const noneFinding = {
1273
1778
  ruleId: "contract.alternatives.none-satisfied",
@@ -1294,13 +1799,21 @@ function evaluateTraceContract(input, contract, options = {}) {
1294
1799
  ruleId: `contract.alternatives.${item.branch.id}.${finding.ruleId}`
1295
1800
  }))
1296
1801
  );
1297
- return mergeContractResults(input, [base], [noneFinding, ...prefixedBranchFindings]);
1802
+ return mergeContractResults(
1803
+ effectiveInput,
1804
+ [base],
1805
+ [...scopeFinding ? [scopeFinding] : [], noneFinding, ...prefixedBranchFindings]
1806
+ );
1298
1807
  }
1299
1808
  function evaluateTraceContractRead(read, contract, options = {}) {
1300
1809
  return evaluateTraceContract({ read }, contract, options);
1301
1810
  }
1302
1811
  function lintTraceContract(contract) {
1303
- const diagnostics = validateAlternativesShape(contract.alternatives);
1812
+ const diagnostics = [
1813
+ ...validateAlternativesShape(contract.alternatives),
1814
+ ...validateScopeShape(contract.scope),
1815
+ ...validateProvenanceShape(contract.observations)
1816
+ ];
1304
1817
  const branches = contract.alternatives?.anyOf ?? [];
1305
1818
  if (branches.length === 1) {
1306
1819
  diagnostics.push({
@@ -1335,6 +1848,12 @@ function lintTraceContract(contract) {
1335
1848
  }
1336
1849
  function explainTraceContract(contract) {
1337
1850
  const lines = [];
1851
+ if (contract.scope) {
1852
+ const parts = Object.entries(contract.scope).filter(([, value]) => typeof value === "string" && value.trim() !== "").map(([key, value]) => `${key}=${value}`);
1853
+ if (parts.length > 0) {
1854
+ lines.push(`Scope: select actor by [${parts.join(", ")}] before evaluation.`);
1855
+ }
1856
+ }
1338
1857
  if (contract.run?.requireCompleted !== false) {
1339
1858
  lines.push("Base: run must complete (no running events).");
1340
1859
  }
@@ -1386,6 +1905,18 @@ function explainTraceContract(contract) {
1386
1905
  if (contract.observations?.failOn?.length) {
1387
1906
  lines.push(`Base: fail on observations [${contract.observations.failOn.join(", ")}].`);
1388
1907
  }
1908
+ if (contract.observations?.requireProvenance) {
1909
+ const flags = [
1910
+ contract.observations.requireProvenance.method ? "method" : void 0,
1911
+ contract.observations.requireProvenance.evidence ? "evidence" : void 0,
1912
+ contract.observations.requireProvenance.sameRunEventReference ? "sameRunEventReference" : void 0
1913
+ ].filter((item) => item !== void 0);
1914
+ if (flags.length > 0) {
1915
+ lines.push(
1916
+ `Base: require structural observation provenance [${flags.join(", ")}] (not semantic truth).`
1917
+ );
1918
+ }
1919
+ }
1389
1920
  const branches = contract.alternatives?.anyOf ?? [];
1390
1921
  if (branches.length > 0) {
1391
1922
  lines.push(
@@ -1486,7 +2017,7 @@ var DEFAULT_SECRET_PATTERNS = [
1486
2017
  function compareStrings(a, b) {
1487
2018
  return (a ?? "").localeCompare(b ?? "");
1488
2019
  }
1489
- function diagnostic(code, message, ruleId) {
2020
+ function diagnostic2(code, message, ruleId) {
1490
2021
  return {
1491
2022
  code,
1492
2023
  message,
@@ -1519,14 +2050,14 @@ function errorResult(input, diagnostics, selectedRun, ruleExecutions = []) {
1519
2050
  ruleExecutions: [...ruleExecutions]
1520
2051
  };
1521
2052
  }
1522
- function flattenNodes(nodes) {
1523
- return nodes.flatMap((node) => [node, ...flattenNodes(node.children)]);
2053
+ function flattenNodes2(nodes) {
2054
+ return nodes.flatMap((node) => [node, ...flattenNodes2(node.children)]);
1524
2055
  }
1525
2056
  function buildFacts(input, selectedRun) {
1526
2057
  const scopedRuns = selectedRun ? [selectedRun] : input.read.runs;
1527
2058
  const scopedRunIds = new Set(scopedRuns.map((run) => run.runId));
1528
2059
  const scopedEvents = selectedRun === void 0 ? input.read.events : input.read.events.filter((event) => scopedRunIds.has(event.runId));
1529
- const nodes = flattenNodes(scopedRuns.flatMap((run) => run.children));
2060
+ const nodes = flattenNodes2(scopedRuns.flatMap((run) => run.children));
1530
2061
  const nodesByEventId = /* @__PURE__ */ new Map();
1531
2062
  const childrenByParentId = /* @__PURE__ */ new Map();
1532
2063
  for (const node of nodes) {
@@ -1558,7 +2089,7 @@ function resolveSelectedRun(input, runId) {
1558
2089
  if (runId && input.selectedRun.runId !== runId) {
1559
2090
  return {
1560
2091
  diagnostics: [
1561
- diagnostic(
2092
+ diagnostic2(
1562
2093
  "AI_CHECK_INVALID_ARGUMENTS",
1563
2094
  `Selected run ${input.selectedRun.runId} does not match requested run ${runId}.`
1564
2095
  )
@@ -1572,7 +2103,7 @@ function resolveSelectedRun(input, runId) {
1572
2103
  if (!run) {
1573
2104
  return {
1574
2105
  diagnostics: [
1575
- diagnostic("AI_CHECK_RUN_SELECTION_REQUIRED", `Run not found: ${runId}.`)
2106
+ diagnostic2("AI_CHECK_RUN_SELECTION_REQUIRED", `Run not found: ${runId}.`)
1576
2107
  ]
1577
2108
  };
1578
2109
  }
@@ -1584,7 +2115,7 @@ function resolveSelectedRun(input, runId) {
1584
2115
  if (input.read.runs.length === 0) {
1585
2116
  return {
1586
2117
  diagnostics: [
1587
- diagnostic(
2118
+ diagnostic2(
1588
2119
  "AI_CHECK_RUN_SELECTION_REQUIRED",
1589
2120
  formatProgrammaticDiagnostic(
1590
2121
  "AI_TRACE_CONTRACT_RUN_SELECTION_REQUIRED",
@@ -1596,7 +2127,7 @@ function resolveSelectedRun(input, runId) {
1596
2127
  }
1597
2128
  return {
1598
2129
  diagnostics: [
1599
- diagnostic(
2130
+ diagnostic2(
1600
2131
  "AI_CHECK_RUN_SELECTION_REQUIRED",
1601
2132
  formatProgrammaticDiagnostic("AI_TRACE_CONTRACT_RUN_SELECTION_REQUIRED")
1602
2133
  )
@@ -1609,7 +2140,7 @@ function selectRules(rules, selectedIds) {
1609
2140
  for (const rule of rules) {
1610
2141
  if (byId.has(rule.id)) {
1611
2142
  diagnostics.push(
1612
- diagnostic("AI_CHECK_INVALID_CONFIG", `Duplicate trace check rule id: ${rule.id}.`, rule.id)
2143
+ diagnostic2("AI_CHECK_INVALID_CONFIG", `Duplicate trace check rule id: ${rule.id}.`, rule.id)
1613
2144
  );
1614
2145
  continue;
1615
2146
  }
@@ -1620,7 +2151,7 @@ function selectRules(rules, selectedIds) {
1620
2151
  for (const id of selected) {
1621
2152
  if (!byId.has(id)) {
1622
2153
  diagnostics.push(
1623
- diagnostic("AI_CHECK_INVALID_CONFIG", `Unknown trace check rule id: ${id}.`, id)
2154
+ diagnostic2("AI_CHECK_INVALID_CONFIG", `Unknown trace check rule id: ${id}.`, id)
1624
2155
  );
1625
2156
  }
1626
2157
  }
@@ -3307,7 +3838,7 @@ function runTraceChecks(input, options = {}) {
3307
3838
  return errorResult(
3308
3839
  input,
3309
3840
  [
3310
- diagnostic(
3841
+ diagnostic2(
3311
3842
  "AI_CHECK_NO_RULES_EVALUATED",
3312
3843
  "No trace check rules were evaluated. Configure at least one rule, contract, or CLI check option."
3313
3844
  )
@@ -3338,7 +3869,7 @@ function runTraceChecks(input, options = {}) {
3338
3869
  } catch (error) {
3339
3870
  const message = error instanceof Error ? error.message : String(error);
3340
3871
  diagnostics.push(
3341
- diagnostic("AI_CHECK_INTERNAL_ERROR", `Rule ${rule.id} failed: ${message}`, rule.id)
3872
+ diagnostic2("AI_CHECK_INTERNAL_ERROR", `Rule ${rule.id} failed: ${message}`, rule.id)
3342
3873
  );
3343
3874
  ruleExecutions.push({
3344
3875
  ruleId: rule.id,
@@ -3404,7 +3935,9 @@ exports.formatProgrammaticDiagnostic = formatProgrammaticDiagnostic;
3404
3935
  exports.lintTraceContract = lintTraceContract;
3405
3936
  exports.projectLogicalEvents = projectLogicalEvents;
3406
3937
  exports.resolveCanonicalToolName = resolveCanonicalToolName;
3938
+ exports.resolveTraceContractScope = resolveTraceContractScope;
3407
3939
  exports.runTraceChecks = runTraceChecks;
3408
3940
  exports.summarizeSemanticParity = summarizeSemanticParity;
3941
+ exports.workflowMetadataForRun = workflowMetadataForRun;
3409
3942
  //# sourceMappingURL=checks.cjs.map
3410
3943
  //# sourceMappingURL=checks.cjs.map