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
@@ -1,6 +1,61 @@
1
- import { extractOutcomesFromPersistedEvents, outcomesMatchingStatus } from './chunk-3XS5O4JI.mjs';
1
+ import { OBSERVED_OUTCOME_METHODS, extractOutcomesFromPersistedEvents, outcomesMatchingStatus } from './chunk-R4SSYU6D.mjs';
2
2
  import { formatProgrammaticDiagnostic } from './chunk-FWK3Q3OJ.mjs';
3
3
 
4
+ // packages/core/src/sessions/metadata.ts
5
+ function isNonEmptyString(value) {
6
+ return typeof value === "string" && value.trim() !== "";
7
+ }
8
+ function finitePositiveInt(value) {
9
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
10
+ return void 0;
11
+ }
12
+ return Math.trunc(value);
13
+ }
14
+ function extractSessionWorkflowMetadata(record) {
15
+ if (!record) return void 0;
16
+ const out = {};
17
+ let found = false;
18
+ const assignString = (key, value) => {
19
+ if (isNonEmptyString(value)) {
20
+ out[key] = value.trim();
21
+ found = true;
22
+ }
23
+ };
24
+ assignString("sessionId", record.sessionId);
25
+ assignString("conversationId", record.conversationId);
26
+ assignString("groupId", record.groupId);
27
+ assignString("parentGroupId", record.parentGroupId);
28
+ assignString("retryOf", record.retryOf);
29
+ assignString("retryReason", record.retryReason);
30
+ assignString("handoffFrom", record.handoffFrom);
31
+ assignString("handoffTo", record.handoffTo);
32
+ assignString("subAgentId", record.subAgentId);
33
+ assignString("subAgentName", record.subAgentName);
34
+ assignString("jobId", record.jobId);
35
+ assignString("queueName", record.queueName);
36
+ assignString("workflowName", record.workflowName);
37
+ assignString("workflowStep", record.workflowStep);
38
+ assignString("toolCallId", record.toolCallId);
39
+ assignString("mcpToolCallId", record.mcpToolCallId);
40
+ assignString("linkedStepId", record.linkedStepId);
41
+ assignString("correlationId", record.correlationId);
42
+ assignString("requestId", record.requestId);
43
+ assignString("decisionId", record.decisionId);
44
+ const attempt = finitePositiveInt(record.attempt);
45
+ if (attempt !== void 0) {
46
+ out.attempt = attempt;
47
+ found = true;
48
+ }
49
+ return found ? out : void 0;
50
+ }
51
+ function sessionKeyForRun(meta, options) {
52
+ if (meta?.sessionId) return meta.sessionId;
53
+ if (options?.correlateByGroupId && meta?.groupId) {
54
+ return `group:${meta.groupId}`;
55
+ }
56
+ return void 0;
57
+ }
58
+
4
59
  // packages/core/src/checks/logical-events.ts
5
60
  function isRecord(value) {
6
61
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -722,6 +777,200 @@ function buildTraceFacts(input) {
722
777
  };
723
778
  }
724
779
 
780
+ // packages/core/src/checks/contract-scope.ts
781
+ function diagnostic(code, message, ruleId) {
782
+ return {
783
+ code,
784
+ message,
785
+ severity: "error",
786
+ ...ruleId !== void 0 ? { ruleId } : {}
787
+ };
788
+ }
789
+ function isNonEmptyString2(value) {
790
+ return typeof value === "string" && value.trim() !== "";
791
+ }
792
+ function scopeEntries(scope) {
793
+ const out = [];
794
+ if (isNonEmptyString2(scope.runId)) out.push(["runId", scope.runId.trim()]);
795
+ if (isNonEmptyString2(scope.subAgentId)) out.push(["subAgentId", scope.subAgentId.trim()]);
796
+ if (isNonEmptyString2(scope.groupId)) out.push(["groupId", scope.groupId.trim()]);
797
+ if (isNonEmptyString2(scope.workflowStep)) {
798
+ out.push(["workflowStep", scope.workflowStep.trim()]);
799
+ }
800
+ if (isNonEmptyString2(scope.rootEventId)) {
801
+ out.push(["rootEventId", scope.rootEventId.trim()]);
802
+ }
803
+ return out;
804
+ }
805
+ function mergeWorkflowMeta(into, from) {
806
+ if (!from) return;
807
+ for (const [key, value] of Object.entries(from)) {
808
+ if (value === void 0) continue;
809
+ if (into[key] === void 0) {
810
+ into[key] = value;
811
+ }
812
+ }
813
+ }
814
+ function workflowMetadataForRun(events, runId) {
815
+ const preferred = {};
816
+ const fallback = {};
817
+ for (const event of events) {
818
+ if (event.runId !== runId) continue;
819
+ const attrs = event.attributes;
820
+ if (!attrs || typeof attrs !== "object") continue;
821
+ const direct = extractSessionWorkflowMetadata(attrs);
822
+ const nested = attrs.metadata && typeof attrs.metadata === "object" ? extractSessionWorkflowMetadata(attrs.metadata) : void 0;
823
+ const bag = {};
824
+ mergeWorkflowMeta(bag, direct);
825
+ mergeWorkflowMeta(bag, nested);
826
+ if (event.kind === "RUN") {
827
+ mergeWorkflowMeta(preferred, bag);
828
+ } else {
829
+ mergeWorkflowMeta(fallback, bag);
830
+ }
831
+ }
832
+ mergeWorkflowMeta(preferred, fallback);
833
+ return preferred;
834
+ }
835
+ function flattenNodes(nodes) {
836
+ return nodes.flatMap((node) => [node, ...flattenNodes(node.children)]);
837
+ }
838
+ function collectSubtreeEventIds(run, rootEventId) {
839
+ const nodes = flattenNodes(run.children);
840
+ const byId = new Map(nodes.map((node) => [node.event.eventId, node]));
841
+ const root = byId.get(rootEventId);
842
+ if (!root) return void 0;
843
+ const ids = /* @__PURE__ */ new Set();
844
+ const visit = (node) => {
845
+ ids.add(node.event.eventId);
846
+ for (const child of node.children) visit(child);
847
+ };
848
+ visit(root);
849
+ return ids;
850
+ }
851
+ function filterTree(nodes, keep) {
852
+ const out = [];
853
+ for (const node of nodes) {
854
+ if (!keep.has(node.event.eventId)) continue;
855
+ out.push({
856
+ ...node,
857
+ children: filterTree(node.children, keep)
858
+ });
859
+ }
860
+ return out;
861
+ }
862
+ function projectInput(input, run, eventIds) {
863
+ const runEvents = input.read.events.filter((event) => event.runId === run.runId);
864
+ const projectedEvents = eventIds === void 0 ? runEvents : runEvents.filter((event) => eventIds.has(event.eventId) || event.kind === "RUN");
865
+ const projectedChildren = eventIds === void 0 ? run.children : filterTree(run.children, eventIds);
866
+ const projectedRun = {
867
+ ...run,
868
+ children: projectedChildren,
869
+ metadata: {
870
+ ...run.metadata,
871
+ totalEvents: flattenNodes(projectedChildren).length
872
+ }
873
+ };
874
+ return {
875
+ evidenceEventCount: projectedEvents.length,
876
+ input: {
877
+ ...input,
878
+ selectedRun: projectedRun,
879
+ read: {
880
+ ...input.read,
881
+ runs: [projectedRun],
882
+ events: projectedEvents
883
+ }
884
+ }
885
+ };
886
+ }
887
+ function resolveTraceContractScope(input, scope) {
888
+ if (scope === void 0) {
889
+ return { diagnostics: [] };
890
+ }
891
+ const entries = scopeEntries(scope);
892
+ if (entries.length === 0) {
893
+ return {
894
+ diagnostics: [
895
+ diagnostic(
896
+ "AI_CHECK_INVALID_CONFIG",
897
+ "TraceContract scope requires at least one explicit selector (runId, subAgentId, groupId, workflowStep, or rootEventId).",
898
+ "contract.scope.empty"
899
+ )
900
+ ]
901
+ };
902
+ }
903
+ const matchedSelectors = Object.fromEntries(entries);
904
+ const actorKeys = entries.filter(([key]) => key !== "rootEventId");
905
+ const rootEventId = matchedSelectors.rootEventId;
906
+ let candidates = [...input.read.runs];
907
+ if (matchedSelectors.runId) {
908
+ candidates = candidates.filter((run2) => run2.runId === matchedSelectors.runId);
909
+ }
910
+ for (const [key, value] of actorKeys) {
911
+ if (key === "runId") continue;
912
+ candidates = candidates.filter((run2) => {
913
+ const meta = workflowMetadataForRun(input.read.events, run2.runId);
914
+ const actual = meta[key];
915
+ return actual === value;
916
+ });
917
+ }
918
+ if (rootEventId && actorKeys.length === 0) {
919
+ candidates = candidates.filter(
920
+ (run2) => flattenNodes(run2.children).some((node) => node.event.eventId === rootEventId)
921
+ );
922
+ }
923
+ if (candidates.length === 0) {
924
+ return {
925
+ diagnostics: [
926
+ diagnostic(
927
+ "AI_CHECK_RUN_SELECTION_REQUIRED",
928
+ `TraceContract scope matched zero actors for ${JSON.stringify(matchedSelectors)}. Missing actor metadata does not fall back to the whole session.`,
929
+ "contract.scope.zero-match"
930
+ )
931
+ ]
932
+ };
933
+ }
934
+ if (candidates.length > 1) {
935
+ return {
936
+ diagnostics: [
937
+ diagnostic(
938
+ "AI_CHECK_RUN_SELECTION_REQUIRED",
939
+ `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.`,
940
+ "contract.scope.ambiguous"
941
+ )
942
+ ]
943
+ };
944
+ }
945
+ const run = candidates[0];
946
+ let eventIds;
947
+ if (rootEventId) {
948
+ eventIds = collectSubtreeEventIds(run, rootEventId);
949
+ if (eventIds === void 0) {
950
+ return {
951
+ diagnostics: [
952
+ diagnostic(
953
+ "AI_CHECK_INVALID_ARGUMENTS",
954
+ `TraceContract scope rootEventId ${rootEventId} was not found in run ${run.runId}.`,
955
+ "contract.scope.root-missing"
956
+ )
957
+ ]
958
+ };
959
+ }
960
+ }
961
+ const projected = projectInput(input, run, eventIds);
962
+ return {
963
+ resolved: {
964
+ runId: run.runId,
965
+ ...rootEventId !== void 0 ? { rootEventId } : {},
966
+ matchedSelectors,
967
+ evidenceEventCount: projected.evidenceEventCount,
968
+ input: projected.input
969
+ },
970
+ diagnostics: []
971
+ };
972
+ }
973
+
725
974
  // packages/core/src/safety/sensitive-key.ts
726
975
  function keyHasExplicitSeparator(value) {
727
976
  return /[_\-.]/.test(value);
@@ -878,7 +1127,7 @@ var DEFAULT_SECRET_PATTERNS = [
878
1127
  function compareStrings(a, b) {
879
1128
  return (a ?? "").localeCompare(b ?? "");
880
1129
  }
881
- function diagnostic(code, message, ruleId) {
1130
+ function diagnostic2(code, message, ruleId) {
882
1131
  return {
883
1132
  code,
884
1133
  message,
@@ -911,14 +1160,14 @@ function errorResult(input, diagnostics, selectedRun, ruleExecutions = []) {
911
1160
  ruleExecutions: [...ruleExecutions]
912
1161
  };
913
1162
  }
914
- function flattenNodes(nodes) {
915
- return nodes.flatMap((node) => [node, ...flattenNodes(node.children)]);
1163
+ function flattenNodes2(nodes) {
1164
+ return nodes.flatMap((node) => [node, ...flattenNodes2(node.children)]);
916
1165
  }
917
1166
  function buildFacts(input, selectedRun) {
918
1167
  const scopedRuns = selectedRun ? [selectedRun] : input.read.runs;
919
1168
  const scopedRunIds = new Set(scopedRuns.map((run) => run.runId));
920
1169
  const scopedEvents = selectedRun === void 0 ? input.read.events : input.read.events.filter((event) => scopedRunIds.has(event.runId));
921
- const nodes = flattenNodes(scopedRuns.flatMap((run) => run.children));
1170
+ const nodes = flattenNodes2(scopedRuns.flatMap((run) => run.children));
922
1171
  const nodesByEventId = /* @__PURE__ */ new Map();
923
1172
  const childrenByParentId = /* @__PURE__ */ new Map();
924
1173
  for (const node of nodes) {
@@ -950,7 +1199,7 @@ function resolveSelectedRun(input, runId) {
950
1199
  if (runId && input.selectedRun.runId !== runId) {
951
1200
  return {
952
1201
  diagnostics: [
953
- diagnostic(
1202
+ diagnostic2(
954
1203
  "AI_CHECK_INVALID_ARGUMENTS",
955
1204
  `Selected run ${input.selectedRun.runId} does not match requested run ${runId}.`
956
1205
  )
@@ -964,7 +1213,7 @@ function resolveSelectedRun(input, runId) {
964
1213
  if (!run) {
965
1214
  return {
966
1215
  diagnostics: [
967
- diagnostic("AI_CHECK_RUN_SELECTION_REQUIRED", `Run not found: ${runId}.`)
1216
+ diagnostic2("AI_CHECK_RUN_SELECTION_REQUIRED", `Run not found: ${runId}.`)
968
1217
  ]
969
1218
  };
970
1219
  }
@@ -976,7 +1225,7 @@ function resolveSelectedRun(input, runId) {
976
1225
  if (input.read.runs.length === 0) {
977
1226
  return {
978
1227
  diagnostics: [
979
- diagnostic(
1228
+ diagnostic2(
980
1229
  "AI_CHECK_RUN_SELECTION_REQUIRED",
981
1230
  formatProgrammaticDiagnostic(
982
1231
  "AI_TRACE_CONTRACT_RUN_SELECTION_REQUIRED",
@@ -988,7 +1237,7 @@ function resolveSelectedRun(input, runId) {
988
1237
  }
989
1238
  return {
990
1239
  diagnostics: [
991
- diagnostic(
1240
+ diagnostic2(
992
1241
  "AI_CHECK_RUN_SELECTION_REQUIRED",
993
1242
  formatProgrammaticDiagnostic("AI_TRACE_CONTRACT_RUN_SELECTION_REQUIRED")
994
1243
  )
@@ -1001,7 +1250,7 @@ function selectRules(rules, selectedIds) {
1001
1250
  for (const rule of rules) {
1002
1251
  if (byId.has(rule.id)) {
1003
1252
  diagnostics.push(
1004
- diagnostic("AI_CHECK_INVALID_CONFIG", `Duplicate trace check rule id: ${rule.id}.`, rule.id)
1253
+ diagnostic2("AI_CHECK_INVALID_CONFIG", `Duplicate trace check rule id: ${rule.id}.`, rule.id)
1005
1254
  );
1006
1255
  continue;
1007
1256
  }
@@ -1012,7 +1261,7 @@ function selectRules(rules, selectedIds) {
1012
1261
  for (const id of selected) {
1013
1262
  if (!byId.has(id)) {
1014
1263
  diagnostics.push(
1015
- diagnostic("AI_CHECK_INVALID_CONFIG", `Unknown trace check rule id: ${id}.`, id)
1264
+ diagnostic2("AI_CHECK_INVALID_CONFIG", `Unknown trace check rule id: ${id}.`, id)
1016
1265
  );
1017
1266
  }
1018
1267
  }
@@ -2699,7 +2948,7 @@ function runTraceChecks(input, options = {}) {
2699
2948
  return errorResult(
2700
2949
  input,
2701
2950
  [
2702
- diagnostic(
2951
+ diagnostic2(
2703
2952
  "AI_CHECK_NO_RULES_EVALUATED",
2704
2953
  "No trace check rules were evaluated. Configure at least one rule, contract, or CLI check option."
2705
2954
  )
@@ -2730,7 +2979,7 @@ function runTraceChecks(input, options = {}) {
2730
2979
  } catch (error) {
2731
2980
  const message = error instanceof Error ? error.message : String(error);
2732
2981
  diagnostics.push(
2733
- diagnostic("AI_CHECK_INTERNAL_ERROR", `Rule ${rule.id} failed: ${message}`, rule.id)
2982
+ diagnostic2("AI_CHECK_INTERNAL_ERROR", `Rule ${rule.id} failed: ${message}`, rule.id)
2734
2983
  );
2735
2984
  ruleExecutions.push({
2736
2985
  ruleId: rule.id,
@@ -2783,12 +3032,141 @@ function cloneBody(body) {
2783
3032
  ...body.run ? { run: { ...body.run } } : {},
2784
3033
  ...body.tools ? { tools: { ...body.tools } } : {},
2785
3034
  ...body.llm ? { llm: { ...body.llm } } : {},
2786
- ...body.observations ? { observations: { ...body.observations } } : {}
3035
+ ...body.observations ? {
3036
+ observations: {
3037
+ ...body.observations,
3038
+ ...body.observations.requireProvenance ? { requireProvenance: { ...body.observations.requireProvenance } } : {}
3039
+ }
3040
+ } : {}
2787
3041
  };
2788
3042
  }
2789
3043
  function bodyHasRules(body) {
2790
3044
  return body.run !== void 0 || body.tools !== void 0 || body.llm !== void 0 || body.observations !== void 0;
2791
3045
  }
3046
+ var MAX_EVIDENCE_EVENT_IDS = 16;
3047
+ var METHOD_VOCABULARY = new Set(OBSERVED_OUTCOME_METHODS);
3048
+ function parseEvidenceEventIds(evidence) {
3049
+ if (evidence === void 0 || evidence === null) {
3050
+ return { status: "missing" };
3051
+ }
3052
+ if (typeof evidence === "string") {
3053
+ const trimmed = evidence.trim();
3054
+ if (trimmed === "") {
3055
+ return { status: "invalid", reason: "empty evidence string" };
3056
+ }
3057
+ return { status: "ok", eventIds: [trimmed] };
3058
+ }
3059
+ if (typeof evidence !== "object" || Array.isArray(evidence)) {
3060
+ return {
3061
+ status: "invalid",
3062
+ reason: "evidence must be a string event id or { eventId } / { eventIds }"
3063
+ };
3064
+ }
3065
+ const record = evidence;
3066
+ if (typeof record.eventId === "string") {
3067
+ const trimmed = record.eventId.trim();
3068
+ if (trimmed === "") {
3069
+ return { status: "invalid", reason: "empty evidence.eventId" };
3070
+ }
3071
+ return { status: "ok", eventIds: [trimmed] };
3072
+ }
3073
+ if (Array.isArray(record.eventIds)) {
3074
+ if (record.eventIds.length === 0) {
3075
+ return { status: "invalid", reason: "empty evidence.eventIds" };
3076
+ }
3077
+ if (record.eventIds.length > MAX_EVIDENCE_EVENT_IDS) {
3078
+ return {
3079
+ status: "invalid",
3080
+ reason: `evidence.eventIds exceeds bound of ${MAX_EVIDENCE_EVENT_IDS}`
3081
+ };
3082
+ }
3083
+ const eventIds = [];
3084
+ for (const item of record.eventIds) {
3085
+ if (typeof item !== "string" || item.trim() === "") {
3086
+ return { status: "invalid", reason: "evidence.eventIds contains a non-string id" };
3087
+ }
3088
+ eventIds.push(item.trim());
3089
+ }
3090
+ return { status: "ok", eventIds };
3091
+ }
3092
+ return {
3093
+ status: "invalid",
3094
+ reason: "evidence must include eventId or eventIds"
3095
+ };
3096
+ }
3097
+ function provenanceFindingsForOutcome(outcome, requireProvenance, knownEventIds) {
3098
+ const findings = [];
3099
+ const evidenceBase = [
3100
+ {
3101
+ runId: outcome.runId,
3102
+ eventId: outcome.outcomeId,
3103
+ kind: "OUTCOME",
3104
+ name: outcome.name,
3105
+ path: `outcome.${outcome.name}`
3106
+ }
3107
+ ];
3108
+ if (requireProvenance.method) {
3109
+ if (outcome.method === void 0 || outcome.method.trim() === "") {
3110
+ findings.push(
3111
+ contractFailFinding(
3112
+ "contract.observation.provenance.method",
3113
+ `Observation ${outcome.name} is missing a structural method.`,
3114
+ evidenceBase,
3115
+ { method: true },
3116
+ { name: outcome.name, method: outcome.method ?? null }
3117
+ )
3118
+ );
3119
+ } else if (!METHOD_VOCABULARY.has(outcome.method)) {
3120
+ findings.push(
3121
+ contractFailFinding(
3122
+ "contract.observation.provenance.method",
3123
+ `Observation ${outcome.name} method ${outcome.method} is outside the bounded ObservedOutcomeMethod vocabulary.`,
3124
+ evidenceBase,
3125
+ { methodVocabulary: [...OBSERVED_OUTCOME_METHODS] },
3126
+ { name: outcome.name, method: outcome.method }
3127
+ )
3128
+ );
3129
+ }
3130
+ }
3131
+ if (requireProvenance.evidence || requireProvenance.sameRunEventReference) {
3132
+ const parsed = parseEvidenceEventIds(outcome.evidence);
3133
+ if (parsed.status === "missing") {
3134
+ findings.push(
3135
+ contractFailFinding(
3136
+ "contract.observation.provenance.evidence",
3137
+ `Observation ${outcome.name} is missing bounded evidence references.`,
3138
+ evidenceBase,
3139
+ { evidence: true },
3140
+ { name: outcome.name, evidence: null }
3141
+ )
3142
+ );
3143
+ } else if (parsed.status === "invalid") {
3144
+ findings.push(
3145
+ contractFailFinding(
3146
+ "contract.observation.provenance.evidence",
3147
+ `Observation ${outcome.name} evidence is not a bounded event reference (${parsed.reason}).`,
3148
+ evidenceBase,
3149
+ { evidence: true },
3150
+ { name: outcome.name, reason: parsed.reason }
3151
+ )
3152
+ );
3153
+ } else if (requireProvenance.sameRunEventReference) {
3154
+ const missing = parsed.eventIds.filter((eventId) => !knownEventIds.has(eventId));
3155
+ if (missing.length > 0) {
3156
+ findings.push(
3157
+ contractFailFinding(
3158
+ "contract.observation.provenance.same-run",
3159
+ `Observation ${outcome.name} references event ids missing from the same run: ${missing.join(", ")}.`,
3160
+ evidenceBase,
3161
+ { sameRunEventReference: true, eventIds: parsed.eventIds },
3162
+ { name: outcome.name, missing }
3163
+ )
3164
+ );
3165
+ }
3166
+ }
3167
+ }
3168
+ return findings;
3169
+ }
2792
3170
  function contractToRules(contract) {
2793
3171
  const rules = [];
2794
3172
  const allowedStatuses = contract.run?.allowedStatuses ?? [];
@@ -2936,9 +3314,71 @@ function contractToRules(contract) {
2936
3314
  })
2937
3315
  );
2938
3316
  }
3317
+ const requireProvenance = contract.observations.requireProvenance;
3318
+ if (requireProvenance && (requireProvenance.method || requireProvenance.evidence || requireProvenance.sameRunEventReference)) {
3319
+ rules.push({
3320
+ id: "contract.observation.provenance",
3321
+ category: "run",
3322
+ defaultSeverity: "error",
3323
+ evaluate(context) {
3324
+ const outcomes = extractOutcomesFromPersistedEvents(context.events);
3325
+ const knownEventIds = new Set(context.events.map((event) => event.eventId));
3326
+ const targets = required.length > 0 ? outcomes.filter((outcome) => required.includes(outcome.name)) : outcomes;
3327
+ return targets.flatMap(
3328
+ (outcome) => provenanceFindingsForOutcome(outcome, requireProvenance, knownEventIds)
3329
+ );
3330
+ }
3331
+ });
3332
+ }
2939
3333
  }
2940
3334
  return rules;
2941
3335
  }
3336
+ function validateScopeShape(scope) {
3337
+ if (scope === void 0) return [];
3338
+ 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() !== "";
3339
+ if (!hasSelector) {
3340
+ return [
3341
+ {
3342
+ code: "contract.scope.empty",
3343
+ severity: "error",
3344
+ message: "scope requires at least one explicit selector (runId, subAgentId, groupId, workflowStep, or rootEventId).",
3345
+ path: "scope"
3346
+ }
3347
+ ];
3348
+ }
3349
+ return [];
3350
+ }
3351
+ function validateProvenanceShape(observations) {
3352
+ const diagnostics = [];
3353
+ const provenance = observations?.requireProvenance;
3354
+ if (!provenance) return diagnostics;
3355
+ const enabled = provenance.method === true || provenance.evidence === true || provenance.sameRunEventReference === true;
3356
+ if (!enabled) {
3357
+ diagnostics.push({
3358
+ code: "contract.observation.provenance.empty",
3359
+ severity: "warning",
3360
+ message: "observations.requireProvenance is set but no flags are enabled.",
3361
+ path: "observations.requireProvenance"
3362
+ });
3363
+ }
3364
+ if (provenance.sameRunEventReference === true && provenance.evidence !== true) {
3365
+ diagnostics.push({
3366
+ code: "contract.observation.provenance.same-run-without-evidence",
3367
+ severity: "info",
3368
+ message: "sameRunEventReference implies evidence checking; set requireProvenance.evidence: true for clarity.",
3369
+ path: "observations.requireProvenance.sameRunEventReference"
3370
+ });
3371
+ }
3372
+ if (enabled && (observations?.required?.length ?? 0) === 0) {
3373
+ diagnostics.push({
3374
+ code: "contract.observation.provenance.without-required",
3375
+ severity: "warning",
3376
+ message: "requireProvenance without observations.required applies to every observed outcome in scope.",
3377
+ path: "observations.requireProvenance"
3378
+ });
3379
+ }
3380
+ return diagnostics;
3381
+ }
2942
3382
  function validateAlternativesShape(alternatives) {
2943
3383
  const diagnostics = [];
2944
3384
  if (alternatives === void 0) return diagnostics;
@@ -3045,9 +3485,11 @@ function evaluateBody(input, body, options = {}) {
3045
3485
  });
3046
3486
  }
3047
3487
  function defineTraceContract(input) {
3048
- const shapeErrors = validateAlternativesShape(input.alternatives).filter(
3049
- (item) => item.severity === "error"
3050
- );
3488
+ const shapeErrors = [
3489
+ ...validateAlternativesShape(input.alternatives),
3490
+ ...validateScopeShape(input.scope),
3491
+ ...validateProvenanceShape(input.observations)
3492
+ ].filter((item) => item.severity === "error");
3051
3493
  if (shapeErrors.length > 0) {
3052
3494
  throw new TypeError(shapeErrors.map((item) => item.message).join(" "));
3053
3495
  }
@@ -3059,15 +3501,25 @@ function defineTraceContract(input) {
3059
3501
  contract: cloneBody(branch.contract)
3060
3502
  }))
3061
3503
  } : void 0;
3504
+ const scope = input.scope ? {
3505
+ ...input.scope.runId !== void 0 ? { runId: input.scope.runId } : {},
3506
+ ...input.scope.subAgentId !== void 0 ? { subAgentId: input.scope.subAgentId } : {},
3507
+ ...input.scope.groupId !== void 0 ? { groupId: input.scope.groupId } : {},
3508
+ ...input.scope.workflowStep !== void 0 ? { workflowStep: input.scope.workflowStep } : {},
3509
+ ...input.scope.rootEventId !== void 0 ? { rootEventId: input.scope.rootEventId } : {}
3510
+ } : void 0;
3062
3511
  return {
3063
3512
  ...body,
3513
+ ...scope ? { scope } : {},
3064
3514
  ...alternatives ? { alternatives } : {}
3065
3515
  };
3066
3516
  }
3067
3517
  function evaluateTraceContract(input, contract, options = {}) {
3068
- const shapeErrors = validateAlternativesShape(contract.alternatives).filter(
3069
- (item) => item.severity === "error"
3070
- );
3518
+ const shapeErrors = [
3519
+ ...validateAlternativesShape(contract.alternatives),
3520
+ ...validateScopeShape(contract.scope),
3521
+ ...validateProvenanceShape(contract.observations)
3522
+ ].filter((item) => item.severity === "error");
3071
3523
  if (shapeErrors.length > 0) {
3072
3524
  return {
3073
3525
  ok: false,
@@ -3091,13 +3543,58 @@ function evaluateTraceContract(input, contract, options = {}) {
3091
3543
  ruleExecutions: []
3092
3544
  };
3093
3545
  }
3094
- const base = evaluateBody(input, contract, options);
3546
+ const scoped = resolveTraceContractScope(input, contract.scope);
3547
+ if (scoped.diagnostics.length > 0) {
3548
+ return {
3549
+ ok: false,
3550
+ status: "error",
3551
+ format: input.read.format,
3552
+ ...options.runId !== void 0 ? { runId: options.runId } : {},
3553
+ ...scoped.resolved?.runId !== void 0 ? { runId: scoped.resolved.runId } : {},
3554
+ summary: {
3555
+ passed: 0,
3556
+ failed: 0,
3557
+ warnings: 0,
3558
+ errors: scoped.diagnostics.length,
3559
+ rulesEvaluated: 0
3560
+ },
3561
+ findings: [],
3562
+ diagnostics: scoped.diagnostics,
3563
+ ruleExecutions: []
3564
+ };
3565
+ }
3566
+ const effectiveInput = scoped.resolved?.input ?? input;
3567
+ const effectiveRunId = scoped.resolved?.runId ?? options.runId;
3568
+ const base = evaluateBody(effectiveInput, contract, {
3569
+ ...effectiveRunId !== void 0 ? { runId: effectiveRunId } : {}
3570
+ });
3571
+ const scopeFinding = scoped.resolved ? {
3572
+ ruleId: "contract.scope.selected",
3573
+ severity: "info",
3574
+ status: "pass",
3575
+ message: `Selected actor ${scoped.resolved.runId} with ${scoped.resolved.evidenceEventCount} evidence event(s).`,
3576
+ expected: scoped.resolved.matchedSelectors,
3577
+ actual: {
3578
+ runId: scoped.resolved.runId,
3579
+ evidenceEventCount: scoped.resolved.evidenceEventCount,
3580
+ ...scoped.resolved.rootEventId !== void 0 ? { rootEventId: scoped.resolved.rootEventId } : {}
3581
+ },
3582
+ evidence: [
3583
+ {
3584
+ runId: scoped.resolved.runId,
3585
+ kind: "RUN",
3586
+ path: "scope"
3587
+ }
3588
+ ]
3589
+ } : void 0;
3095
3590
  const branches = contract.alternatives?.anyOf ?? [];
3096
3591
  if (branches.length === 0) {
3097
- return base;
3592
+ return scopeFinding ? mergeContractResults(effectiveInput, [base], [scopeFinding]) : base;
3098
3593
  }
3099
3594
  const branchEvaluations = branches.map((branch) => {
3100
- const result = evaluateBody(input, branch.contract, options);
3595
+ const result = evaluateBody(effectiveInput, branch.contract, {
3596
+ ...effectiveRunId !== void 0 ? { runId: effectiveRunId } : {}
3597
+ });
3101
3598
  return { branch, result };
3102
3599
  });
3103
3600
  const satisfied = branchEvaluations.filter((item) => item.result.ok && item.result.status === "pass").map((item) => item.branch.id);
@@ -3117,7 +3614,11 @@ function evaluateTraceContract(input, contract, options = {}) {
3117
3614
  },
3118
3615
  evidence: []
3119
3616
  };
3120
- return mergeContractResults(input, [base], [passFinding]);
3617
+ return mergeContractResults(
3618
+ effectiveInput,
3619
+ [base],
3620
+ [...scopeFinding ? [scopeFinding] : [], passFinding]
3621
+ );
3121
3622
  }
3122
3623
  const noneFinding = {
3123
3624
  ruleId: "contract.alternatives.none-satisfied",
@@ -3144,13 +3645,21 @@ function evaluateTraceContract(input, contract, options = {}) {
3144
3645
  ruleId: `contract.alternatives.${item.branch.id}.${finding.ruleId}`
3145
3646
  }))
3146
3647
  );
3147
- return mergeContractResults(input, [base], [noneFinding, ...prefixedBranchFindings]);
3648
+ return mergeContractResults(
3649
+ effectiveInput,
3650
+ [base],
3651
+ [...scopeFinding ? [scopeFinding] : [], noneFinding, ...prefixedBranchFindings]
3652
+ );
3148
3653
  }
3149
3654
  function evaluateTraceContractRead(read, contract, options = {}) {
3150
3655
  return evaluateTraceContract({ read }, contract, options);
3151
3656
  }
3152
3657
  function lintTraceContract(contract) {
3153
- const diagnostics = validateAlternativesShape(contract.alternatives);
3658
+ const diagnostics = [
3659
+ ...validateAlternativesShape(contract.alternatives),
3660
+ ...validateScopeShape(contract.scope),
3661
+ ...validateProvenanceShape(contract.observations)
3662
+ ];
3154
3663
  const branches = contract.alternatives?.anyOf ?? [];
3155
3664
  if (branches.length === 1) {
3156
3665
  diagnostics.push({
@@ -3185,6 +3694,12 @@ function lintTraceContract(contract) {
3185
3694
  }
3186
3695
  function explainTraceContract(contract) {
3187
3696
  const lines = [];
3697
+ if (contract.scope) {
3698
+ const parts = Object.entries(contract.scope).filter(([, value]) => typeof value === "string" && value.trim() !== "").map(([key, value]) => `${key}=${value}`);
3699
+ if (parts.length > 0) {
3700
+ lines.push(`Scope: select actor by [${parts.join(", ")}] before evaluation.`);
3701
+ }
3702
+ }
3188
3703
  if (contract.run?.requireCompleted !== false) {
3189
3704
  lines.push("Base: run must complete (no running events).");
3190
3705
  }
@@ -3236,6 +3751,18 @@ function explainTraceContract(contract) {
3236
3751
  if (contract.observations?.failOn?.length) {
3237
3752
  lines.push(`Base: fail on observations [${contract.observations.failOn.join(", ")}].`);
3238
3753
  }
3754
+ if (contract.observations?.requireProvenance) {
3755
+ const flags = [
3756
+ contract.observations.requireProvenance.method ? "method" : void 0,
3757
+ contract.observations.requireProvenance.evidence ? "evidence" : void 0,
3758
+ contract.observations.requireProvenance.sameRunEventReference ? "sameRunEventReference" : void 0
3759
+ ].filter((item) => item !== void 0);
3760
+ if (flags.length > 0) {
3761
+ lines.push(
3762
+ `Base: require structural observation provenance [${flags.join(", ")}] (not semantic truth).`
3763
+ );
3764
+ }
3765
+ }
3239
3766
  const branches = contract.alternatives?.anyOf ?? [];
3240
3767
  if (branches.length > 0) {
3241
3768
  lines.push(
@@ -3252,6 +3779,6 @@ function explainTraceContract(contract) {
3252
3779
  return lines;
3253
3780
  }
3254
3781
 
3255
- export { buildTraceFacts, createBaselineRegressionRule, createDecisionRule, createGuardrailRule, createLlmUsageRule, createMaxStepDurationRule, createObservedOutcomeRule, createRequireCompletedRule, createRetrievalRule, createRunDepthRule, createRunDurationRule, createRunEventCountRule, createRunStatusRule, createSafetyOversizedAttributeRule, createSafetyRawContentRule, createSafetyRedactionRule, createSafetySecretPatternRule, createStallDetectionRule, createStructureCycleRule, createStructureIncompleteRule, createStructureOrphanRule, createStructureParallelWidthRule, createStructureRelationshipRule, createToolFailureRule, createToolOrderingRule, createToolUsageRule, defineTraceContract, deriveFailureFacts, evaluateTraceContract, evaluateTraceContractRead, explainTraceContract, lintTraceContract, projectLogicalEvents, resolveCanonicalToolName, runTraceChecks, summarizeSemanticParity };
3256
- //# sourceMappingURL=chunk-G47KQB42.mjs.map
3257
- //# sourceMappingURL=chunk-G47KQB42.mjs.map
3782
+ export { buildTraceFacts, createBaselineRegressionRule, createDecisionRule, createGuardrailRule, createLlmUsageRule, createMaxStepDurationRule, createObservedOutcomeRule, createRequireCompletedRule, createRetrievalRule, createRunDepthRule, createRunDurationRule, createRunEventCountRule, createRunStatusRule, createSafetyOversizedAttributeRule, createSafetyRawContentRule, createSafetyRedactionRule, createSafetySecretPatternRule, createStallDetectionRule, createStructureCycleRule, createStructureIncompleteRule, createStructureOrphanRule, createStructureParallelWidthRule, createStructureRelationshipRule, createToolFailureRule, createToolOrderingRule, createToolUsageRule, defineTraceContract, deriveFailureFacts, evaluateTraceContract, evaluateTraceContractRead, explainTraceContract, extractSessionWorkflowMetadata, lintTraceContract, projectLogicalEvents, resolveCanonicalToolName, resolveTraceContractScope, runTraceChecks, sessionKeyForRun, summarizeSemanticParity, workflowMetadataForRun };
3783
+ //# sourceMappingURL=chunk-43SHIPZM.mjs.map
3784
+ //# sourceMappingURL=chunk-43SHIPZM.mjs.map