engineering-behavior-observatory 0.2.1

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 (178) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +151 -0
  3. package/contracts/codex-app-server-0.150.1/manifest.json +25 -0
  4. package/contracts/codex-app-server-0.150.1/schema/InitializeParams.json +84 -0
  5. package/contracts/codex-app-server-0.150.1/schema/JSONRPCMessage.json +137 -0
  6. package/contracts/codex-app-server-0.150.1/schema/v2/ThreadReadParams.json +17 -0
  7. package/contracts/codex-app-server-0.150.1/schema/v2/ThreadTokenUsageUpdatedNotification.json +82 -0
  8. package/contracts/codex-app-server-0.150.1/schema/v2/TurnInterruptParams.json +17 -0
  9. package/contracts/codex-app-server-0.150.1/types/AskForApproval.ts +5 -0
  10. package/contracts/codex-app-server-0.150.1/types/ClientNotification.ts +5 -0
  11. package/contracts/codex-app-server-0.150.1/types/SandboxMode.ts +5 -0
  12. package/contracts/codex-app-server-0.150.1/types/ThreadReadParams.ts +9 -0
  13. package/contracts/codex-app-server-0.150.1/types/TokenUsageBreakdown.ts +5 -0
  14. package/contracts/codex-app-server-0.150.1/types/TurnInterruptParams.ts +5 -0
  15. package/contracts/codex-app-server-0.153.4/manifest.json +27 -0
  16. package/contracts/codex-app-server-0.153.4/schema/JSONRPCMessage.json +137 -0
  17. package/contracts/codex-app-server-0.153.4/schema/v1/InitializeParams.json +84 -0
  18. package/contracts/codex-app-server-0.153.4/schema/v2/ThreadReadParams.json +17 -0
  19. package/contracts/codex-app-server-0.153.4/schema/v2/ThreadStartParams.json +515 -0
  20. package/contracts/codex-app-server-0.153.4/schema/v2/ThreadTokenUsageUpdatedNotification.json +82 -0
  21. package/contracts/codex-app-server-0.153.4/schema/v2/TurnInterruptParams.json +17 -0
  22. package/contracts/codex-app-server-0.153.4/schema/v2/TurnStartParams.json +921 -0
  23. package/contracts/codex-app-server-0.153.4/types/AskForApproval.ts +5 -0
  24. package/contracts/codex-app-server-0.153.4/types/ClientNotification.ts +5 -0
  25. package/contracts/codex-app-server-0.153.4/types/SandboxMode.ts +5 -0
  26. package/contracts/codex-app-server-0.153.4/types/ThreadReadParams.ts +12 -0
  27. package/contracts/codex-app-server-0.153.4/types/TokenUsageBreakdown.ts +5 -0
  28. package/contracts/codex-app-server-0.153.4/types/TurnInterruptParams.ts +5 -0
  29. package/contracts/openhands-agent-server-v1.44.1.json +65 -0
  30. package/contracts/openhands-agent-server-v1.46.0.json +46 -0
  31. package/dist/contracts/codex-app-server-0.153.4/types/AskForApproval.d.ts +9 -0
  32. package/dist/contracts/codex-app-server-0.153.4/types/AskForApproval.js +2 -0
  33. package/dist/contracts/codex-app-server-0.153.4/types/ClientNotification.d.ts +3 -0
  34. package/dist/contracts/codex-app-server-0.153.4/types/ClientNotification.js +2 -0
  35. package/dist/contracts/codex-app-server-0.153.4/types/SandboxMode.d.ts +1 -0
  36. package/dist/contracts/codex-app-server-0.153.4/types/SandboxMode.js +2 -0
  37. package/dist/contracts/codex-app-server-0.153.4/types/ThreadReadParams.d.ts +10 -0
  38. package/dist/contracts/codex-app-server-0.153.4/types/ThreadReadParams.js +2 -0
  39. package/dist/contracts/codex-app-server-0.153.4/types/TokenUsageBreakdown.d.ts +8 -0
  40. package/dist/contracts/codex-app-server-0.153.4/types/TokenUsageBreakdown.js +2 -0
  41. package/dist/contracts/codex-app-server-0.153.4/types/TurnInterruptParams.d.ts +4 -0
  42. package/dist/contracts/codex-app-server-0.153.4/types/TurnInterruptParams.js +2 -0
  43. package/dist/src/agent-sdk-normalizer.d.ts +22 -0
  44. package/dist/src/agent-sdk-normalizer.js +995 -0
  45. package/dist/src/agent-sdk-run.d.ts +31 -0
  46. package/dist/src/agent-sdk-run.js +242 -0
  47. package/dist/src/agent-sdk-runner.d.ts +96 -0
  48. package/dist/src/agent-sdk-runner.js +407 -0
  49. package/dist/src/agent-sdk.d.ts +294 -0
  50. package/dist/src/agent-sdk.js +644 -0
  51. package/dist/src/aggregation.d.ts +183 -0
  52. package/dist/src/aggregation.js +513 -0
  53. package/dist/src/artifacts.d.ts +36 -0
  54. package/dist/src/artifacts.js +1372 -0
  55. package/dist/src/atlas-grafana.d.ts +377 -0
  56. package/dist/src/atlas-grafana.js +47 -0
  57. package/dist/src/atlas-html.d.ts +2 -0
  58. package/dist/src/atlas-html.js +34 -0
  59. package/dist/src/atlas.d.ts +130 -0
  60. package/dist/src/atlas.js +235 -0
  61. package/dist/src/behavior-assertions.d.ts +95 -0
  62. package/dist/src/behavior-assertions.js +154 -0
  63. package/dist/src/cli.d.ts +5 -0
  64. package/dist/src/cli.js +957 -0
  65. package/dist/src/codex-judge.d.ts +44 -0
  66. package/dist/src/codex-judge.js +214 -0
  67. package/dist/src/codex-run.d.ts +114 -0
  68. package/dist/src/codex-run.js +519 -0
  69. package/dist/src/codex.d.ts +187 -0
  70. package/dist/src/codex.js +1028 -0
  71. package/dist/src/contracts.d.ts +155 -0
  72. package/dist/src/contracts.js +866 -0
  73. package/dist/src/corpus.d.ts +67 -0
  74. package/dist/src/corpus.js +540 -0
  75. package/dist/src/cursor-sdk-runner.d.ts +81 -0
  76. package/dist/src/cursor-sdk-runner.js +362 -0
  77. package/dist/src/cursor-sdk.d.ts +81 -0
  78. package/dist/src/cursor-sdk.js +1078 -0
  79. package/dist/src/deepseek-adapter.d.ts +152 -0
  80. package/dist/src/deepseek-adapter.js +777 -0
  81. package/dist/src/exports.d.ts +69 -0
  82. package/dist/src/exports.js +966 -0
  83. package/dist/src/human-calibration.d.ts +167 -0
  84. package/dist/src/human-calibration.js +618 -0
  85. package/dist/src/index.d.ts +71 -0
  86. package/dist/src/index.js +38 -0
  87. package/dist/src/lifecycle.d.ts +196 -0
  88. package/dist/src/lifecycle.js +1889 -0
  89. package/dist/src/normalization-integrity.d.ts +128 -0
  90. package/dist/src/normalization-integrity.js +429 -0
  91. package/dist/src/openhands-run.d.ts +36 -0
  92. package/dist/src/openhands-run.js +415 -0
  93. package/dist/src/openhands.d.ts +130 -0
  94. package/dist/src/openhands.js +859 -0
  95. package/dist/src/pi.d.ts +203 -0
  96. package/dist/src/pi.js +1345 -0
  97. package/dist/src/process-protocol.d.ts +245 -0
  98. package/dist/src/process-protocol.js +1261 -0
  99. package/dist/src/retained-evidence.d.ts +12 -0
  100. package/dist/src/retained-evidence.js +201 -0
  101. package/dist/src/run-bundles.d.ts +193 -0
  102. package/dist/src/run-bundles.js +993 -0
  103. package/dist/src/scheduler.d.ts +109 -0
  104. package/dist/src/scheduler.js +1080 -0
  105. package/dist/src/semantic-judge.d.ts +194 -0
  106. package/dist/src/semantic-judge.js +875 -0
  107. package/dist/src/structural-observations.d.ts +124 -0
  108. package/dist/src/structural-observations.js +671 -0
  109. package/dist/src/task-packets.d.ts +115 -0
  110. package/dist/src/task-packets.js +683 -0
  111. package/dist/src/uniform-events.d.ts +120 -0
  112. package/dist/src/uniform-events.js +158 -0
  113. package/dist/src/verifiers.d.ts +116 -0
  114. package/dist/src/verifiers.js +819 -0
  115. package/dist/src/workspaces.d.ts +56 -0
  116. package/dist/src/workspaces.js +1314 -0
  117. package/docs/README.md +60 -0
  118. package/docs/development/README.md +71 -0
  119. package/docs/development/documentation-sync.md +58 -0
  120. package/docs/development/extension-contracts.md +307 -0
  121. package/docs/evaluation/README.md +48 -0
  122. package/docs/evaluation/aggregation.md +112 -0
  123. package/docs/evaluation/behavior-assertions.md +45 -0
  124. package/docs/evaluation/human-calibration.md +170 -0
  125. package/docs/evaluation/normalization-integrity.md +80 -0
  126. package/docs/evaluation/semantic-judge.md +188 -0
  127. package/docs/evaluation/structural-observations.md +84 -0
  128. package/docs/evaluation/uniform-events.md +131 -0
  129. package/docs/guides/atlas.md +144 -0
  130. package/docs/guides/evidence-and-sharing.md +100 -0
  131. package/docs/guides/operator-guide.md +424 -0
  132. package/docs/guides/quickstart.md +152 -0
  133. package/docs/guides/telemetry.md +66 -0
  134. package/docs/harnesses/README.md +31 -0
  135. package/docs/harnesses/claude-agent-sdk.md +61 -0
  136. package/docs/harnesses/codex-harness.md +174 -0
  137. package/docs/harnesses/cursor-sdk.md +149 -0
  138. package/docs/harnesses/deepseek-harness.md +134 -0
  139. package/docs/harnesses/openhands-agent-server.md +153 -0
  140. package/docs/harnesses/pi-sdk.md +66 -0
  141. package/docs/reference/README.md +19 -0
  142. package/docs/reference/agent-sdk-operational-runner.md +118 -0
  143. package/docs/reference/cli.md +114 -0
  144. package/docs/reference/contracts.md +222 -0
  145. package/docs/reference/run-bundle-contract.md +354 -0
  146. package/docs/reference/run-lifecycle.md +54 -0
  147. package/examples/cursor-sdk/README.md +11 -0
  148. package/examples/cursor-sdk/capture-profile.json +1 -0
  149. package/examples/cursor-sdk/harness.json +1 -0
  150. package/examples/cursor-sdk/model.json +1 -0
  151. package/examples/cursor-sdk/native-limits.json +1 -0
  152. package/examples/cursor-sdk/native-tool-policy.json +1 -0
  153. package/ontology/behavior-categories.v1.json +46 -0
  154. package/package.json +66 -0
  155. package/release/0.1.0/KNOWN_LIMITATIONS.md +35 -0
  156. package/release/0.1.0/README.md +46 -0
  157. package/release/0.1.0/reproducibility.json +61 -0
  158. package/release/0.2.0/KNOWN_LIMITATIONS.md +40 -0
  159. package/release/0.2.0/README.md +42 -0
  160. package/release/0.2.0/reproducibility.json +73 -0
  161. package/release/0.2.1/KNOWN_LIMITATIONS.md +18 -0
  162. package/release/0.2.1/README.md +63 -0
  163. package/release/0.2.1/reproducibility.json +73 -0
  164. package/release/README.md +13 -0
  165. package/schemas/aggregation.v1.json +293 -0
  166. package/schemas/behavior-assertions.v1.json +220 -0
  167. package/schemas/deepseek-runtime-composition.v1.schema.json +122 -0
  168. package/schemas/experiment.v1.schema.json +188 -0
  169. package/schemas/human-calibration.v1.json +336 -0
  170. package/schemas/normalization-integrity.v1.json +302 -0
  171. package/schemas/run-bundles/v1.json +1040 -0
  172. package/schemas/run-queue.v1.schema.json +206 -0
  173. package/schemas/semantic-judge.v1.json +380 -0
  174. package/schemas/structural-observations.v1.json +131 -0
  175. package/schemas/task-packet-freeze.v1.schema.json +106 -0
  176. package/schemas/task-packet.v1.schema.json +234 -0
  177. package/schemas/uniform-events/v1.json +289 -0
  178. package/scripts/atlas-grafana.sh +18 -0
@@ -0,0 +1,995 @@
1
+ import { createHash } from "node:crypto";
2
+ import { resolve } from "node:path";
3
+ import { HOOK_EVENTS } from "@anthropic-ai/claude-agent-sdk";
4
+ import { assertNoDuplicateJsonKeys, digestMetadata, readVerifiedArtifact, validateArtifact, } from "./artifacts.js";
5
+ import { qualifyRunBundle, } from "./run-bundles.js";
6
+ import { readBoundedFile } from "./scheduler.js";
7
+ import { assertAdapterContract, } from "./uniform-events.js";
8
+ export const CLAUDE_AGENT_SDK_NORMALIZATION_ADAPTER_ID = "claude-agent-sdk/v1";
9
+ export const CLAUDE_AGENT_SDK_HARNESS = "claude-agent-sdk";
10
+ export const CLAUDE_AGENT_SDK_NORMALIZATION_ADAPTER_VERSION = "1.0.0";
11
+ const MAX_MANIFEST_BYTES = 16 * 1024 * 1024;
12
+ const MAX_EVIDENCE_BYTES = 64 * 1024 * 1024;
13
+ const HOOK_EVENT_SET = new Set(HOOK_EVENTS);
14
+ const SESSION_NATIVE_TYPES = [
15
+ "assistant",
16
+ "auth_status",
17
+ "conversation_reset",
18
+ "prompt_suggestion",
19
+ "rate_limit_event",
20
+ "result",
21
+ "stream_event",
22
+ "system",
23
+ "tool_progress",
24
+ "tool_use_summary",
25
+ "user",
26
+ ];
27
+ const ADAPTER_NATIVE_TYPES = [
28
+ ...SESSION_NATIVE_TYPES,
29
+ ...HOOK_EVENTS,
30
+ "agent-sdk-telemetry",
31
+ "workspace-outcome",
32
+ "verifier-result",
33
+ "assessment-mode",
34
+ "terminal-record",
35
+ ];
36
+ const BASE_CAPABILITY_PROFILE = {
37
+ schemaVersion: "ebo.adapter-capability-profile/v1",
38
+ adapterId: CLAUDE_AGENT_SDK_NORMALIZATION_ADAPTER_ID,
39
+ harness: CLAUDE_AGENT_SDK_HARNESS,
40
+ nativeTypes: ADAPTER_NATIVE_TYPES,
41
+ families: {
42
+ message: { status: "available" },
43
+ "model-request": {
44
+ status: "unsupported",
45
+ detail: "Retained Agent SDK evidence does not expose inference-request lifecycle records.",
46
+ },
47
+ tool: { status: "available" },
48
+ context: { status: "available" },
49
+ permission: { status: "available" },
50
+ delegation: { status: "available" },
51
+ artifact: { status: "available" },
52
+ validation: { status: "available" },
53
+ runtime: { status: "available" },
54
+ outcome: { status: "available" },
55
+ },
56
+ evidence: {
57
+ nativeOrder: { status: "partial", detail: "Session and hook streams retain independent source-local sequences." },
58
+ nativeTime: { status: "partial", detail: "Origin timestamps are used when emitted; capture clocks are not substituted." },
59
+ parentage: { status: "partial", detail: "Relations require stable tool-use, task, agent, or workspace identifiers." },
60
+ content: { status: "partial", detail: "Raw content remains in native evidence and is represented only by references." },
61
+ },
62
+ };
63
+ export const claudeAgentSdkNormalizationAdapter = {
64
+ id: CLAUDE_AGENT_SDK_NORMALIZATION_ADAPTER_ID,
65
+ harness: CLAUDE_AGENT_SDK_HARNESS,
66
+ capabilityProfile: BASE_CAPABILITY_PROFILE,
67
+ async normalize(input) {
68
+ assertQualifiedInput(input);
69
+ const drafts = [];
70
+ const unmapped = [];
71
+ for (const captured of input.records) {
72
+ const mapped = mapRecord(input, captured);
73
+ drafts.push(...mapped);
74
+ if (mapped.length === 0) {
75
+ unmapped.push({
76
+ reference: captured.reference,
77
+ reason: unmappedReason(captured.record),
78
+ });
79
+ }
80
+ }
81
+ const capabilityProfile = capabilityProfileFor(input.records);
82
+ assertValidCapabilityProfile(capabilityProfile);
83
+ return {
84
+ events: resolveRelations(drafts),
85
+ unmapped,
86
+ capabilityProfile,
87
+ };
88
+ },
89
+ };
90
+ /** Read, structurally qualify, normalize, and reference-check one retained Agent SDK run bundle. */
91
+ export async function normalizeClaudeAgentSdkRunBundle(bundleRoot) {
92
+ const input = await readQualifiedClaudeAgentSdkCapture(bundleRoot);
93
+ const resolver = createAgentSdkNativeEvidenceResolver(input);
94
+ const adapter = {
95
+ capture: {
96
+ id: CLAUDE_AGENT_SDK_NORMALIZATION_ADAPTER_ID,
97
+ harness: CLAUDE_AGENT_SDK_HARNESS,
98
+ capture: async () => input,
99
+ },
100
+ normalization: claudeAgentSdkNormalizationAdapter,
101
+ };
102
+ return await assertAdapterContract(adapter, null, resolver);
103
+ }
104
+ export async function readQualifiedClaudeAgentSdkCapture(bundleRoot) {
105
+ const manifest = readManifest(resolve(bundleRoot));
106
+ if (!manifest.run.runtime.some(({ source, name }) => source === "anthropic" && ["agent-sdk", CLAUDE_AGENT_SDK_HARNESS].includes(name))) {
107
+ throw new Error("Run bundle is not an Agent SDK capture.");
108
+ }
109
+ const capture = await readQualifiedRunCapture(bundleRoot);
110
+ assertQualifiedInput(capture);
111
+ return capture;
112
+ }
113
+ /** Verified retained artifact envelopes; native session documents remain source-specific. */
114
+ export async function readQualifiedRunCapture(bundleRoot) {
115
+ const root = resolve(bundleRoot);
116
+ const manifest = readManifest(root);
117
+ await assertPersistedStructuralQualification(root, manifest);
118
+ const qualification = await qualifyRunBundle(root);
119
+ if (!qualification.semanticAnalysisUsable
120
+ || !["qualified", "qualified-with-gaps"].includes(qualification.status)) {
121
+ const reasons = qualification.reasons.map(({ code }) => code).join(", ") || "unknown qualification failure";
122
+ throw new Error(`Agent SDK normalization requires capture-qualified evidence: ${reasons}.`);
123
+ }
124
+ const records = [];
125
+ for (const [evidenceIndex, descriptor] of manifest.evidence.entries()) {
126
+ if (descriptor.sanitizedFrom !== undefined || descriptor.kind === "export-manifest")
127
+ continue;
128
+ if (descriptor.kind === "workspace" || descriptor.kind === "diagnostic") {
129
+ records.push({
130
+ reference: { artifactId: "manifest", recordLocator: `#/evidence/${evidenceIndex}` },
131
+ record: { kind: descriptor.kind, document: structuredClone(descriptor) },
132
+ });
133
+ continue;
134
+ }
135
+ const bytes = await readVerifiedArtifact(root, descriptor.relativePath, digestFrom(descriptor.digest), MAX_EVIDENCE_BYTES);
136
+ if ((descriptor.kind === "session" || descriptor.kind === "hook")
137
+ && descriptor.mediaType === "application/x-ndjson") {
138
+ for (const { line, document } of parseJsonl(bytes)) {
139
+ records.push({
140
+ reference: { artifactId: descriptor.id, recordLocator: `line:${line}` },
141
+ record: { kind: descriptor.kind, document },
142
+ });
143
+ }
144
+ }
145
+ else if ((descriptor.kind === "session" || descriptor.kind === "hook")
146
+ && descriptor.mediaType === "application/json") {
147
+ records.push({
148
+ reference: { artifactId: descriptor.id, recordLocator: "#" },
149
+ record: { kind: descriptor.kind, document: parseJson(bytes) },
150
+ });
151
+ }
152
+ else if (["telemetry", "verifier", "capture-report"].includes(descriptor.kind)) {
153
+ records.push({
154
+ reference: { artifactId: descriptor.id, recordLocator: "#" },
155
+ record: { kind: descriptor.kind, document: parseJson(bytes) },
156
+ });
157
+ }
158
+ }
159
+ if (manifest.run.assessmentMode !== undefined) {
160
+ records.push({
161
+ reference: { artifactId: "manifest", recordLocator: "#/run/assessmentMode" },
162
+ record: { kind: "assessment-mode", document: manifest.run.assessmentMode },
163
+ });
164
+ }
165
+ records.push({
166
+ reference: { artifactId: "manifest", recordLocator: "#/terminal" },
167
+ record: {
168
+ kind: "manifest",
169
+ document: structuredClone(manifest.terminal),
170
+ },
171
+ });
172
+ const input = {
173
+ runId: manifest.run.id,
174
+ attemptId: manifest.attempt.id,
175
+ qualification: qualification.status === "qualified" ? "qualified" : "qualified-with-gaps",
176
+ records,
177
+ };
178
+ return input;
179
+ }
180
+ export function createAgentSdkNativeEvidenceResolver(input) {
181
+ const resolved = (digest) => ({
182
+ runId: input.runId,
183
+ attemptId: input.attemptId,
184
+ digest,
185
+ });
186
+ const records = new Map(input.records.map((captured) => [referenceKey(captured.reference), {
187
+ captured,
188
+ resolution: resolved(`sha256:${digestMetadata(captured.record).value}`),
189
+ }]));
190
+ const manifestArtifacts = new Map(input.records.flatMap(({ record, reference }) => {
191
+ if (record.kind !== "workspace" && record.kind !== "diagnostic")
192
+ return [];
193
+ if (reference.artifactId !== "manifest" || !/^#\/evidence\/(0|[1-9][0-9]*)$/u.test(reference.recordLocator))
194
+ return [];
195
+ const descriptor = asRecord(record.document);
196
+ const id = text(descriptor?.id);
197
+ const digest = text(descriptor?.digest);
198
+ return id === undefined || digest === undefined || !/^sha256:[a-f0-9]{64}$/u.test(digest)
199
+ ? []
200
+ : [[referenceKey({ artifactId: id, recordLocator: "#" }), resolved(digest)]];
201
+ }));
202
+ return {
203
+ resolve(reference) {
204
+ const exact = records.get(referenceKey(reference))?.resolution ?? manifestArtifacts.get(referenceKey(reference));
205
+ if (exact !== undefined)
206
+ return exact;
207
+ const derived = splitDerivedLocator(reference.recordLocator);
208
+ if (derived === undefined)
209
+ return false;
210
+ const record = records.get(referenceKey({ artifactId: reference.artifactId, recordLocator: derived.base }));
211
+ return record !== undefined && resolvesJsonPointer(record.captured.record.document, derived.pointer)
212
+ ? record.resolution
213
+ : false;
214
+ },
215
+ };
216
+ }
217
+ function mapRecord(input, captured) {
218
+ switch (captured.record.kind) {
219
+ case "session": return mapSessionRecord(input, captured);
220
+ case "hook": return mapHookRecord(input, captured);
221
+ case "telemetry": return mapTelemetryRecord(input, captured);
222
+ case "workspace": return mapWorkspaceRecord(input, captured);
223
+ case "verifier": return mapVerifierRecord(input, captured);
224
+ case "assessment-mode": return mapAssessmentModeRecord(input, captured);
225
+ case "manifest": return mapManifestRecord(input, captured);
226
+ case "diagnostic":
227
+ case "capture-report": return [];
228
+ }
229
+ }
230
+ function mapSessionRecord(input, captured) {
231
+ const wrapper = asRecord(captured.record.document);
232
+ const nativeType = text(wrapper?.nativeType);
233
+ const message = asRecord(wrapper?.message);
234
+ if (wrapper === undefined || message === undefined || nativeType === undefined)
235
+ return [];
236
+ const drafts = [];
237
+ const subtype = text(wrapper.nativeSubtype) ?? text(message.subtype);
238
+ const sessionId = text(wrapper.sessionId) ?? text(message.session_id);
239
+ const nativeOrder = sourceOrder(wrapper, orderDomain("session", captured.reference.artifactId));
240
+ const nativeTime = originTime(message.timestamp, "Agent SDK message has no originating timestamp");
241
+ const common = { input, captured, nativeType, nativeOrder, nativeTime, sessionId };
242
+ const base = sessionEventShape(nativeType, subtype, message);
243
+ if (base !== undefined) {
244
+ const stableToolId = text(message.tool_use_id);
245
+ const stableTaskId = text(message.task_id);
246
+ drafts.push(draftEvent({
247
+ ...common,
248
+ discriminator: "record",
249
+ family: base.family,
250
+ phase: base.phase,
251
+ actor: base.actor,
252
+ scope: base.scope ?? (sessionId === undefined ? { kind: "attempt", id: input.attemptId } : { kind: "session", id: sessionId }),
253
+ attributes: sessionAttributes(nativeType, subtype, message),
254
+ content: sessionContent(captured.reference, nativeType, subtype, message),
255
+ relationKeys: [
256
+ ...(stableToolId === undefined ? [] : [`tool:${stableToolId}`]),
257
+ ...(stableTaskId === undefined ? [] : [`task:${stableTaskId}`]),
258
+ ],
259
+ anchors: nativeType === "system" && subtype === "task_started" && stableTaskId !== undefined
260
+ ? [{ key: `task:${stableTaskId}`, rank: 1 }]
261
+ : [],
262
+ parentKey: parentToolKey(message),
263
+ }));
264
+ }
265
+ if (nativeType === "result")
266
+ drafts.push(...mapResultResources(common, message));
267
+ if (nativeType === "assistant" || nativeType === "user") {
268
+ drafts.push(...mapToolBlocks(common, message));
269
+ }
270
+ return drafts;
271
+ }
272
+ function mapResultResources(common, message) {
273
+ const modelUsage = asRecord(message.modelUsage);
274
+ if (modelUsage === undefined && typeof message.total_cost_usd !== "number")
275
+ return [];
276
+ return [draftEvent({
277
+ ...common,
278
+ discriminator: "resources",
279
+ family: "runtime",
280
+ phase: "after",
281
+ actor: { kind: "harness" },
282
+ scope: common.sessionId === undefined
283
+ ? { kind: "attempt", id: common.input.attemptId }
284
+ : { kind: "session", id: common.sessionId },
285
+ attributes: compactAttributes({
286
+ inputTokens: summedModelUsage(modelUsage, "inputTokens"),
287
+ outputTokens: summedModelUsage(modelUsage, "outputTokens"),
288
+ cacheReadInputTokens: summedModelUsage(modelUsage, "cacheReadInputTokens"),
289
+ cacheCreationInputTokens: summedModelUsage(modelUsage, "cacheCreationInputTokens"),
290
+ totalCostUsd: scalar(message.total_cost_usd),
291
+ resourceSemantics: "cumulative-final",
292
+ }),
293
+ content: { status: "unknown", reason: "Native resource details remain in the Agent SDK result record" },
294
+ })];
295
+ }
296
+ function summedModelUsage(modelUsage, field) {
297
+ const records = Object.values(modelUsage ?? {}).map(asRecord);
298
+ const values = records.map((record) => record?.[field]);
299
+ return records.length > 0 && values.every((value) => typeof value === "number" && Number.isFinite(value) && value >= 0)
300
+ ? values.reduce((total, value) => total + value, 0)
301
+ : undefined;
302
+ }
303
+ function sessionEventShape(nativeType, subtype, message) {
304
+ switch (nativeType) {
305
+ case "assistant": return { family: "message", phase: "instant", actor: { kind: "model" } };
306
+ case "user": return { family: "message", phase: "instant", actor: { kind: message.isSynthetic === true ? "harness" : "user" } };
307
+ case "result": return { family: "outcome", phase: "after", actor: { kind: "harness" } };
308
+ case "stream_event": return { family: "message", phase: "during", actor: { kind: "model" } };
309
+ case "tool_progress": return { family: "tool", phase: "during", actor: { kind: "tool", ...(text(message.tool_name) === undefined ? {} : { id: text(message.tool_name) }) } };
310
+ case "tool_use_summary": return { family: "tool", phase: "after", actor: { kind: "harness" } };
311
+ case "auth_status":
312
+ case "rate_limit_event": return { family: "runtime", phase: "instant", actor: { kind: "harness" } };
313
+ case "conversation_reset": return { family: "context", phase: "after", actor: { kind: "harness" } };
314
+ case "prompt_suggestion": return { family: "message", phase: "after", actor: { kind: "harness" } };
315
+ case "system": return systemEventShape(subtype);
316
+ default: return undefined;
317
+ }
318
+ }
319
+ function systemEventShape(subtype) {
320
+ if (subtype === "compact_boundary")
321
+ return { family: "context", phase: "after", actor: { kind: "harness" } };
322
+ if (["task_started", "task_progress", "task_updated", "task_notification", "worker_shutting_down"].includes(subtype ?? "")) {
323
+ return { family: "delegation", phase: subtype === "task_started" ? "before" : "during", actor: { kind: "agent" } };
324
+ }
325
+ if (subtype === "files_persisted")
326
+ return { family: "artifact", phase: "after", actor: { kind: "harness" } };
327
+ if (subtype === "permission_denied")
328
+ return { family: "permission", phase: "after", actor: { kind: "harness" } };
329
+ if ([
330
+ "init", "status", "hook_started", "hook_progress", "hook_response", "background_tasks_changed",
331
+ "thinking_tokens", "session_state_changed", "commands_changed", "notification", "informational",
332
+ "local_command_output", "memory_recall", "elicitation_complete",
333
+ ].includes(subtype ?? ""))
334
+ return { family: "runtime", phase: "instant", actor: { kind: "harness" } };
335
+ return undefined;
336
+ }
337
+ function mapToolBlocks(common, message) {
338
+ const payload = asRecord(message.message);
339
+ const content = Array.isArray(payload?.content) ? payload.content : [];
340
+ return content.flatMap((value, index) => {
341
+ const block = asRecord(value);
342
+ if (block?.type === "tool_use") {
343
+ const toolUseId = text(block.id);
344
+ const toolName = text(block.name);
345
+ if (toolUseId === undefined || toolName === undefined)
346
+ return [];
347
+ return [draftEvent({
348
+ ...common,
349
+ discriminator: `tool-use:${index}`,
350
+ family: "tool",
351
+ phase: "before",
352
+ actor: { kind: "model" },
353
+ scope: { kind: "operation", id: toolUseId },
354
+ attributes: {
355
+ toolName,
356
+ toolUseId,
357
+ ...(block.input === undefined ? {} : { inputDigest: digestAttribute(block.input) }),
358
+ },
359
+ content: knownContent(contentReference(common.captured.reference, `/message/message/content/${index}`, "tool-input")),
360
+ anchors: [{ key: `tool:${toolUseId}`, rank: 0 }],
361
+ })];
362
+ }
363
+ if (block?.type === "tool_result") {
364
+ const toolUseId = text(block.tool_use_id);
365
+ if (toolUseId === undefined)
366
+ return [];
367
+ return [draftEvent({
368
+ ...common,
369
+ discriminator: `tool-result:${index}`,
370
+ family: "tool",
371
+ phase: "after",
372
+ actor: { kind: "tool" },
373
+ scope: { kind: "operation", id: toolUseId },
374
+ attributes: { toolUseId, ...(typeof block.is_error === "boolean" ? { isError: block.is_error } : {}) },
375
+ content: knownContent(contentReference(common.captured.reference, `/message/message/content/${index}`, "tool-result")),
376
+ relationKeys: [`tool:${toolUseId}`],
377
+ })];
378
+ }
379
+ return [];
380
+ });
381
+ }
382
+ function mapHookRecord(input, captured) {
383
+ const wrapper = asRecord(captured.record.document);
384
+ const hook = text(wrapper?.hook);
385
+ const payload = asRecord(wrapper?.nativePayload);
386
+ if (wrapper === undefined || payload === undefined || hook === undefined || !HOOK_EVENT_SET.has(hook))
387
+ return [];
388
+ const shape = hookEventShape(hook);
389
+ if (shape === undefined)
390
+ return [];
391
+ const toolUseId = text(wrapper.toolUseId) ?? text(payload.tool_use_id);
392
+ const agentId = text(wrapper.agentId) ?? text(payload.agent_id);
393
+ const taskId = text(payload.task_id);
394
+ const stable = toolUseId === undefined ? taskId === undefined ? agentId === undefined ? undefined : `agent:${agentId}` : `task:${taskId}` : `tool:${toolUseId}`;
395
+ const anchorRank = hook === "PreToolUse" ? 1
396
+ : hook === "SubagentStart" || hook === "TaskCreated" ? 0
397
+ : undefined;
398
+ return [draftEvent({
399
+ input,
400
+ captured,
401
+ discriminator: "record",
402
+ nativeType: hook,
403
+ nativeOrder: sourceOrder(wrapper, orderDomain("hooks", captured.reference.artifactId)),
404
+ nativeTime: { status: "unknown", reason: "Hook callback record has no native occurrence timestamp" },
405
+ family: shape.family,
406
+ phase: shape.phase,
407
+ actor: hookActor(shape.family, hook, payload),
408
+ scope: hookScope(shape.family, input.attemptId, wrapper, payload, toolUseId, agentId, taskId),
409
+ attributes: hookAttributes(hook, payload, toolUseId, agentId, taskId),
410
+ content: hookContent(captured.reference, hook, payload),
411
+ anchors: stable === undefined || anchorRank === undefined ? [] : [{ key: stable, rank: anchorRank }],
412
+ relationKeys: stable === undefined ? [] : [stable],
413
+ })];
414
+ }
415
+ function hookEventShape(hook) {
416
+ if (["PreToolUse"].includes(hook))
417
+ return { family: "tool", phase: "before" };
418
+ if (["PostToolUse", "PostToolUseFailure", "PostToolBatch"].includes(hook))
419
+ return { family: "tool", phase: "after" };
420
+ if (["UserPromptSubmit", "UserPromptExpansion"].includes(hook))
421
+ return { family: "message", phase: "before" };
422
+ if (hook === "MessageDisplay")
423
+ return { family: "message", phase: "after" };
424
+ if (hook === "PreCompact")
425
+ return { family: "context", phase: "before" };
426
+ if (["PostCompact", "InstructionsLoaded"].includes(hook))
427
+ return { family: "context", phase: "after" };
428
+ if (hook === "PreModelSwitch")
429
+ return { family: "runtime", phase: "before" };
430
+ if (hook === "PostModelSwitch")
431
+ return { family: "runtime", phase: "after" };
432
+ if (["PermissionRequest", "Elicitation"].includes(hook))
433
+ return { family: "permission", phase: "before" };
434
+ if (["PermissionDenied", "ElicitationResult"].includes(hook))
435
+ return { family: "permission", phase: "after" };
436
+ if (["SubagentStart", "TaskCreated"].includes(hook))
437
+ return { family: "delegation", phase: "before" };
438
+ if (["SubagentStop", "TaskCompleted"].includes(hook))
439
+ return { family: "delegation", phase: "after" };
440
+ if (hook === "TeammateIdle")
441
+ return { family: "delegation", phase: "instant" };
442
+ if (["WorktreeCreate"].includes(hook))
443
+ return { family: "artifact", phase: "before" };
444
+ if (["WorktreeRemove"].includes(hook))
445
+ return { family: "artifact", phase: "after" };
446
+ if (["FileChanged", "DirectoryAdded"].includes(hook))
447
+ return { family: "artifact", phase: "instant" };
448
+ if (["SessionStart", "Setup"].includes(hook))
449
+ return { family: "runtime", phase: "before" };
450
+ if (["SessionEnd", "Stop", "StopFailure"].includes(hook))
451
+ return { family: "runtime", phase: "after" };
452
+ if (["Notification", "ConfigChange", "CwdChanged"].includes(hook))
453
+ return { family: "runtime", phase: "instant" };
454
+ return undefined;
455
+ }
456
+ function mapTelemetryRecord(input, captured) {
457
+ const document = asRecord(captured.record.document);
458
+ if (document?.schemaVersion !== "ebo.agent-sdk-telemetry/v1")
459
+ return [];
460
+ const telemetry = asRecord(document.telemetry);
461
+ const receipt = asRecord(telemetry?.receipt);
462
+ const usage = asRecord(document.usage);
463
+ return [draftEvent({
464
+ input,
465
+ captured,
466
+ discriminator: "record",
467
+ nativeType: "agent-sdk-telemetry",
468
+ nativeOrder: { status: "unknown", reason: "Telemetry artifact has no cross-signal native order" },
469
+ nativeTime: { status: "unknown", reason: "Telemetry summary has no native occurrence timestamp" },
470
+ family: "runtime",
471
+ phase: "during",
472
+ actor: { kind: "system", id: "otlp" },
473
+ scope: { kind: "attempt", id: input.attemptId },
474
+ attributes: compactAttributes({
475
+ receiptStatus: scalar(receipt?.status),
476
+ receivedSignals: scalarList(receipt?.signals),
477
+ totalCostUsd: scalar(usage?.totalCostUsd),
478
+ numTurns: scalar(usage?.numTurns),
479
+ }),
480
+ content: { status: "unknown", reason: "Telemetry payload remains in native evidence" },
481
+ })];
482
+ }
483
+ function mapWorkspaceRecord(input, captured) {
484
+ const descriptor = asRecord(captured.record.document);
485
+ if (descriptor?.kind !== "workspace" || captured.reference.artifactId !== "manifest"
486
+ || !/^#\/evidence\/(0|[1-9][0-9]*)$/u.test(captured.reference.recordLocator))
487
+ return [];
488
+ const mediaType = text(descriptor.mediaType);
489
+ return [draftEvent({
490
+ input,
491
+ captured,
492
+ discriminator: "record",
493
+ nativeType: "workspace-outcome",
494
+ nativeOrder: { status: "unknown", reason: "Workspace artifact has no native event order" },
495
+ nativeTime: { status: "unknown", reason: "Workspace artifact has no native occurrence timestamp" },
496
+ family: "artifact",
497
+ phase: "after",
498
+ actor: { kind: "system", id: "ebo-workspace" },
499
+ scope: { kind: "workspace", id: descriptor.id },
500
+ attributes: compactAttributes({ mediaType: descriptor.mediaType, fingerprint: descriptor.fingerprint }),
501
+ content: knownContent({
502
+ nativeReference: { artifactId: descriptor.id, recordLocator: "#" },
503
+ ...(mediaType === undefined ? {} : { mediaType }),
504
+ role: "workspace-outcome",
505
+ }),
506
+ anchors: [{ key: `artifact:${descriptor.id}`, rank: 0 }],
507
+ })];
508
+ }
509
+ function mapVerifierRecord(input, captured) {
510
+ const document = asRecord(captured.record.document);
511
+ if (!isVerifierRecord(captured) || document === undefined)
512
+ return [];
513
+ return [draftEvent({
514
+ input,
515
+ captured,
516
+ discriminator: "record",
517
+ nativeType: "verifier-result",
518
+ nativeOrder: { status: "unknown", reason: "Verifier artifact has no native event order" },
519
+ nativeTime: { status: "unknown", reason: "Verifier result has no native occurrence timestamp" },
520
+ family: "validation",
521
+ phase: "after",
522
+ actor: { kind: "system", id: "ebo-verifier" },
523
+ scope: { kind: "attempt", id: input.attemptId },
524
+ attributes: compactAttributes({
525
+ status: scalar(document.status),
526
+ exitCode: scalar(document.exitCode),
527
+ assertionCount: Array.isArray(document.assertions) ? document.assertions.length : undefined,
528
+ }),
529
+ content: Array.isArray(document.assertions)
530
+ ? knownContent(contentReference(captured.reference, "/assertions", "assertions"))
531
+ : { status: "unknown", reason: "Verifier assertions were not retained" },
532
+ })];
533
+ }
534
+ function mapManifestRecord(input, captured) {
535
+ const terminal = asRecord(captured.record.document);
536
+ if (terminal === undefined || typeof terminal.state !== "string"
537
+ || captured.reference.artifactId !== "manifest" || captured.reference.recordLocator !== "#/terminal")
538
+ return [];
539
+ const workspaceId = text(terminal.workspaceArtifactId);
540
+ return [draftEvent({
541
+ input,
542
+ captured,
543
+ discriminator: "record",
544
+ nativeType: "terminal-record",
545
+ nativeOrder: { status: "unknown", reason: "Run manifest has no native event order" },
546
+ nativeTime: { status: "unknown", reason: "Terminal record has no native occurrence timestamp" },
547
+ family: "outcome",
548
+ phase: "after",
549
+ actor: { kind: "system", id: "ebo" },
550
+ scope: { kind: "attempt", id: input.attemptId },
551
+ attributes: compactAttributes({
552
+ state: scalar(terminal.state),
553
+ failureClass: scalar(terminal.failureClass),
554
+ stopReason: scalar(terminal.stopReason),
555
+ }),
556
+ content: workspaceId === undefined
557
+ ? { status: "unknown", reason: "Terminal record does not identify a workspace artifact" }
558
+ : knownContent({ nativeReference: { artifactId: workspaceId, recordLocator: "#" }, role: "final-workspace" }),
559
+ relationKeys: workspaceId === undefined ? [] : [`artifact:${workspaceId}`],
560
+ })];
561
+ }
562
+ function mapAssessmentModeRecord(input, captured) {
563
+ const assessmentMode = assessmentModeOf(captured);
564
+ if (assessmentMode === undefined)
565
+ return [];
566
+ return [draftEvent({
567
+ input,
568
+ captured,
569
+ discriminator: "record",
570
+ nativeType: "assessment-mode",
571
+ nativeOrder: { status: "unknown", reason: "Run assessment mode has no native event order" },
572
+ nativeTime: { status: "unknown", reason: "Run assessment mode has no native occurrence timestamp" },
573
+ family: "outcome",
574
+ phase: "instant",
575
+ actor: { kind: "system", id: "ebo" },
576
+ scope: { kind: "attempt", id: input.attemptId },
577
+ attributes: { assessmentMode },
578
+ content: { status: "known", value: [] },
579
+ })];
580
+ }
581
+ function draftEvent(input) {
582
+ return {
583
+ event: {
584
+ schemaVersion: "ebo.uniform-event/v1",
585
+ id: stableEventId(input.input, input.captured.reference, input.discriminator),
586
+ runId: input.input.runId,
587
+ attemptId: input.input.attemptId,
588
+ source: {
589
+ harness: CLAUDE_AGENT_SDK_HARNESS,
590
+ nativeType: input.nativeType,
591
+ nativeReference: input.captured.reference,
592
+ },
593
+ nativeOrder: input.nativeOrder,
594
+ nativeTime: input.nativeTime,
595
+ actor: input.actor,
596
+ family: input.family,
597
+ phase: input.phase,
598
+ scope: input.scope,
599
+ relations: { parent: { status: "unknown", reason: "Native record does not establish an emitted parent" }, known: [] },
600
+ attributes: input.attributes,
601
+ content: input.content,
602
+ },
603
+ anchors: input.anchors ?? [],
604
+ relationKeys: input.relationKeys ?? [],
605
+ ...(input.parentKey === undefined ? {} : { parentKey: input.parentKey }),
606
+ };
607
+ }
608
+ function resolveRelations(drafts) {
609
+ const candidates = new Map();
610
+ for (const draft of drafts) {
611
+ for (const anchor of draft.anchors) {
612
+ const entries = candidates.get(anchor.key) ?? [];
613
+ entries.push({ id: draft.event.id, rank: anchor.rank });
614
+ candidates.set(anchor.key, entries);
615
+ }
616
+ }
617
+ const anchors = new Map();
618
+ for (const [key, entries] of candidates) {
619
+ const minimum = Math.min(...entries.map(({ rank }) => rank));
620
+ const preferred = entries.filter(({ rank }) => rank === minimum);
621
+ if (preferred.length === 1)
622
+ anchors.set(key, preferred[0].id);
623
+ }
624
+ return drafts.map((draft) => {
625
+ const known = [...new Set(draft.relationKeys.map((key) => anchors.get(key)))]
626
+ .filter((id) => id !== undefined && id !== draft.event.id)
627
+ .map((eventId) => ({ kind: "correlates-with", eventId }));
628
+ const parent = draft.parentKey === undefined ? draft.event.relations.parent
629
+ : anchors.has(draft.parentKey)
630
+ ? { status: "known", value: anchors.get(draft.parentKey) }
631
+ : { status: "unknown", reason: "Stable parent tool ID has no uniquely mapped tool-call event" };
632
+ return { ...draft.event, relations: { parent, known } };
633
+ });
634
+ }
635
+ function capabilityProfileFor(records) {
636
+ const retainedCapabilities = records.flatMap(({ record }) => {
637
+ if (record.kind !== "capture-report")
638
+ return [];
639
+ const agentSdk = asRecord(asRecord(record.document)?.agentSdk);
640
+ const capabilities = asRecord(agentSdk?.capabilities);
641
+ return capabilities === undefined ? [] : [capabilities];
642
+ })[0];
643
+ const sdkVersion = text(retainedCapabilities?.sdkVersion);
644
+ const detailedBetaConfigured = records.some(({ record }) => {
645
+ if (record.kind !== "telemetry")
646
+ return false;
647
+ const telemetry = asRecord(asRecord(record.document)?.telemetry);
648
+ return asRecord(telemetry?.hookSpans)?.status === "enabled";
649
+ });
650
+ return {
651
+ ...structuredClone(BASE_CAPABILITY_PROFILE),
652
+ families: {
653
+ ...structuredClone(BASE_CAPABILITY_PROFILE.families),
654
+ "model-request": {
655
+ status: "unsupported",
656
+ detail: sdkVersion === undefined
657
+ ? "Retained Agent SDK evidence does not establish inference-request lifecycle coverage."
658
+ : `Retained Agent SDK ${sdkVersion} exposes model-switch callbacks, not inference-request lifecycle records.`,
659
+ },
660
+ },
661
+ evidence: {
662
+ ...structuredClone(BASE_CAPABILITY_PROFILE.evidence),
663
+ nativeTime: {
664
+ status: "partial",
665
+ detail: detailedBetaConfigured
666
+ ? "Origin timestamps are used when emitted; detailed-beta hook spans are configured but are not substituted for native semantic events."
667
+ : "Origin timestamps are used when emitted; detailed-beta hook span timing is unavailable and semantic hook history is unchanged.",
668
+ },
669
+ },
670
+ };
671
+ }
672
+ function assertQualifiedInput(input) {
673
+ if (!["qualified", "qualified-with-gaps"].includes(input.qualification)) {
674
+ throw new Error("Agent SDK normalization requires capture-qualified evidence.");
675
+ }
676
+ if (text(input.runId) === undefined || text(input.attemptId) === undefined || !Array.isArray(input.records)) {
677
+ throw new Error("Agent SDK normalization input identity is invalid.");
678
+ }
679
+ const kinds = new Set(input.records.map(({ record }) => record.kind));
680
+ for (const required of ["session", "hook", "workspace", "manifest"]) {
681
+ if (!kinds.has(required))
682
+ throw new Error(`Capture-qualified Agent SDK input is missing required ${required} evidence.`);
683
+ }
684
+ const assessmentMode = input.records.map(assessmentModeOf).find((value) => value !== undefined);
685
+ if (assessmentMode !== "observational" && !input.records.some(isVerifierRecord)) {
686
+ throw new Error("Capture-qualified verified input is missing required verifier evidence.");
687
+ }
688
+ }
689
+ function assessmentModeOf(captured) {
690
+ return captured.record.kind === "assessment-mode"
691
+ && captured.reference.artifactId === "manifest"
692
+ && captured.reference.recordLocator === "#/run/assessmentMode"
693
+ && (captured.record.document === "observational" || captured.record.document === "verified")
694
+ ? captured.record.document
695
+ : undefined;
696
+ }
697
+ function isVerifierRecord(captured) {
698
+ return captured.record.kind === "verifier"
699
+ && captured.reference.recordLocator === "#"
700
+ && asRecord(captured.record.document)?.schemaVersion === "verifier-result/v1";
701
+ }
702
+ function assertValidCapabilityProfile(profile) {
703
+ const errors = validateArtifact("Agent SDK adapter capability profile", profile);
704
+ if (errors.length > 0)
705
+ throw new Error(errors.map(({ field, message }) => `${field}: ${message}`).join("\n"));
706
+ }
707
+ function readManifest(root) {
708
+ const bytes = readBoundedFile(resolve(root, "manifest.json"), "Run manifest", undefined, MAX_MANIFEST_BYTES);
709
+ const textValue = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
710
+ assertNoDuplicateJsonKeys(textValue);
711
+ const document = JSON.parse(textValue);
712
+ const errors = validateArtifact("manifest.json", document);
713
+ if (errors.length > 0)
714
+ throw new Error(errors.map(({ field, message }) => `${field}: ${message}`).join("\n"));
715
+ return document;
716
+ }
717
+ async function assertPersistedStructuralQualification(root, manifest) {
718
+ const descriptor = manifest.evidence.find(({ kind, sanitizedFrom }) => kind === "capture-report" && sanitizedFrom === undefined);
719
+ if (descriptor === undefined)
720
+ return;
721
+ const report = asRecord(parseJson(await readVerifiedArtifact(root, descriptor.relativePath, digestFrom(descriptor.digest), MAX_EVIDENCE_BYTES)));
722
+ const structural = asRecord(report?.structuralQualification);
723
+ if (structural?.status === "unqualified" || structural?.semanticAnalysisUsable === false) {
724
+ throw new Error("Agent SDK normalization rejects the persisted unqualified structural capture report.");
725
+ }
726
+ }
727
+ function parseJsonl(bytes) {
728
+ const textValue = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
729
+ return textValue.split(/\r?\n/u).map((value, index) => ({ value, line: index + 1 }))
730
+ .filter(({ value }) => value.trim() !== "")
731
+ .map(({ value, line }) => {
732
+ assertNoDuplicateJsonKeys(value);
733
+ return { line, document: JSON.parse(value) };
734
+ });
735
+ }
736
+ function parseJson(bytes) {
737
+ const textValue = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
738
+ assertNoDuplicateJsonKeys(textValue);
739
+ return JSON.parse(textValue);
740
+ }
741
+ function digestFrom(value) {
742
+ return { algorithm: "sha256", value: value.replace(/^sha256:/u, "") };
743
+ }
744
+ function sourceOrder(document, domain) {
745
+ const sequence = document.sequence;
746
+ return Number.isSafeInteger(sequence) && Number(sequence) >= 0
747
+ ? { status: "known", value: Number(sequence), domain }
748
+ : { status: "unknown", reason: `${domain} record has no valid source-local sequence` };
749
+ }
750
+ function orderDomain(kind, artifactId) {
751
+ const readable = `${kind}:${artifactId}`;
752
+ return [...readable].length <= 256
753
+ ? readable
754
+ : `${kind}:sha256:${createHash("sha256").update(artifactId).digest("hex")}`;
755
+ }
756
+ function originTime(value, reason) {
757
+ if (typeof value === "string" && isRfc3339DateTime(value))
758
+ return { status: "known", value };
759
+ return { status: "unknown", reason };
760
+ }
761
+ function isRfc3339DateTime(value) {
762
+ const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|[+-](\d{2}):(\d{2}))$/iu.exec(value);
763
+ if (match === null)
764
+ return false;
765
+ const [, yearText, monthText, dayText, hourText, minuteText, secondText, offsetHourText, offsetMinuteText] = match;
766
+ const year = Number(yearText);
767
+ const month = Number(monthText);
768
+ const day = Number(dayText);
769
+ const hour = Number(hourText);
770
+ const minute = Number(minuteText);
771
+ const second = Number(secondText);
772
+ const offsetHour = offsetHourText === undefined ? 0 : Number(offsetHourText);
773
+ const offsetMinute = offsetMinuteText === undefined ? 0 : Number(offsetMinuteText);
774
+ const leapYear = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
775
+ const monthDays = [31, leapYear ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
776
+ return month >= 1 && month <= 12
777
+ && day >= 1 && day <= monthDays[month - 1]
778
+ && hour <= 23 && minute <= 59 && second <= 59
779
+ && offsetHour <= 23 && offsetMinute <= 59;
780
+ }
781
+ function sessionAttributes(nativeType, subtype, message) {
782
+ const rateLimit = asRecord(message.rate_limit_info);
783
+ return compactAttributes({
784
+ subtype,
785
+ messageId: scalar(message.uuid),
786
+ model: scalar(asRecord(message.message)?.model),
787
+ isSynthetic: scalar(message.isSynthetic),
788
+ isError: scalar(message.is_error),
789
+ stopReason: scalar(message.stop_reason),
790
+ numTurns: scalar(message.num_turns),
791
+ toolName: scalar(message.tool_name),
792
+ elapsedSeconds: scalar(message.elapsed_time_seconds),
793
+ durationMs: scalar(message.duration_ms),
794
+ apiDurationMs: scalar(message.duration_api_ms),
795
+ unprojectedToolBlockCount: unprojectedToolBlockCount(message),
796
+ resourceSemantics: nativeType === "result" && typeof message.duration_ms === "number" ? "cumulative-final" : undefined,
797
+ rateLimitStatus: nativeType === "rate_limit_event" ? scalar(rateLimit?.status) : undefined,
798
+ rateLimitType: nativeType === "rate_limit_event" ? scalar(rateLimit?.rateLimitType) : undefined,
799
+ utilization: nativeType === "rate_limit_event" ? scalar(rateLimit?.utilization) : undefined,
800
+ });
801
+ }
802
+ function unprojectedToolBlockCount(message) {
803
+ const content = asRecord(message.message)?.content;
804
+ if (!Array.isArray(content))
805
+ return undefined;
806
+ const count = content.filter((value) => {
807
+ const block = asRecord(value);
808
+ return block?.type === "tool_use" && (text(block.id) === undefined || text(block.name) === undefined)
809
+ || block?.type === "tool_result" && text(block.tool_use_id) === undefined;
810
+ }).length;
811
+ return count === 0 ? undefined : count;
812
+ }
813
+ function sessionContent(reference, nativeType, subtype, message) {
814
+ const inner = asRecord(message.message);
815
+ if ((nativeType === "assistant" || nativeType === "user") && inner?.content !== undefined) {
816
+ return knownContent(contentReference(reference, "/message/message/content", `${nativeType}-message`));
817
+ }
818
+ if (nativeType === "result" && (message.result !== undefined || message.errors !== undefined)) {
819
+ return knownContent(contentReference(reference, message.result !== undefined ? "/message/result" : "/message/errors", "result"));
820
+ }
821
+ if (nativeType === "prompt_suggestion" && message.suggestion !== undefined) {
822
+ return knownContent(contentReference(reference, "/message/suggestion", "suggestion"));
823
+ }
824
+ if (nativeType === "system" && ["local_command_output", "informational"].includes(subtype ?? "") && message.content !== undefined) {
825
+ return knownContent(contentReference(reference, "/message/content", "system-message"));
826
+ }
827
+ return { status: "unknown", reason: "Native message content remains in the source record" };
828
+ }
829
+ function hookActor(family, hook, payload) {
830
+ if (family === "tool")
831
+ return { kind: "tool", ...(text(payload.tool_name) === undefined ? {} : { id: text(payload.tool_name) }) };
832
+ if (family === "delegation")
833
+ return { kind: "agent", ...(text(payload.agent_id) === undefined ? {} : { id: text(payload.agent_id) }) };
834
+ if (hook === "UserPromptSubmit" || hook === "UserPromptExpansion")
835
+ return { kind: "user" };
836
+ if (hook === "MessageDisplay")
837
+ return { kind: "model" };
838
+ if (family === "artifact")
839
+ return { kind: "agent" };
840
+ return { kind: "harness" };
841
+ }
842
+ function hookScope(family, attemptId, wrapper, payload, toolUseId, agentId, taskId) {
843
+ if ((family === "tool" || family === "permission") && toolUseId !== undefined)
844
+ return { kind: "operation", id: toolUseId };
845
+ if (family === "delegation" && (taskId ?? agentId) !== undefined)
846
+ return { kind: "operation", id: taskId ?? agentId };
847
+ if (family === "artifact")
848
+ return { kind: "workspace" };
849
+ const turnId = text(payload.turn_id);
850
+ if (family === "message" && turnId !== undefined)
851
+ return { kind: "turn", id: turnId };
852
+ const sessionId = text(wrapper.sessionId) ?? text(payload.session_id);
853
+ return sessionId === undefined ? { kind: "attempt", id: attemptId } : { kind: "session", id: sessionId };
854
+ }
855
+ function hookAttributes(hook, payload, toolUseId, agentId, taskId) {
856
+ return compactAttributes({
857
+ hook,
858
+ mutation: hook === "FileChanged" ? true : undefined,
859
+ toolUseId,
860
+ toolName: scalar(payload.tool_name),
861
+ inputDigest: hook === "PreToolUse" && payload.tool_input !== undefined
862
+ ? digestAttribute(payload.tool_input)
863
+ : undefined,
864
+ agentId,
865
+ agentType: scalar(payload.agent_type),
866
+ taskId,
867
+ trigger: scalar(payload.trigger),
868
+ source: scalar(payload.source),
869
+ fromModel: scalar(payload.from_model),
870
+ toModel: scalar(payload.to_model),
871
+ requestedModel: scalar(payload.requested_model),
872
+ decision: hook === "PermissionDenied" ? "denied" : scalar(payload.action),
873
+ event: scalar(payload.event),
874
+ notificationType: scalar(payload.notification_type),
875
+ mcpServerName: scalar(payload.mcp_server_name),
876
+ });
877
+ }
878
+ function hookContent(reference, hook, payload) {
879
+ const fields = {
880
+ UserPromptSubmit: { field: "prompt", role: "user-prompt" },
881
+ UserPromptExpansion: { field: "prompt", role: "expanded-prompt" },
882
+ MessageDisplay: { field: "delta", role: "display-message" },
883
+ Notification: { field: "message", role: "notification" },
884
+ PreCompact: { field: "custom_instructions", role: "compaction-instructions" },
885
+ PostCompact: { field: "compact_summary", role: "compaction-summary" },
886
+ Elicitation: { field: "message", role: "elicitation" },
887
+ PreToolUse: { field: "tool_input", role: "tool-input" },
888
+ PostToolUse: { field: "tool_response", role: "tool-result" },
889
+ PostToolUseFailure: { field: "error", role: "tool-error" },
890
+ PostToolBatch: { field: "tool_calls", role: "tool-batch" },
891
+ };
892
+ const selected = fields[hook];
893
+ return selected !== undefined && payload[selected.field] !== undefined
894
+ ? knownContent(contentReference(reference, `/nativePayload/${selected.field}`, selected.role))
895
+ : { status: "unknown", reason: "Hook payload content remains in the source record" };
896
+ }
897
+ function parentToolKey(message) {
898
+ const parent = text(message.parent_tool_use_id);
899
+ return parent === undefined ? undefined : `tool:${parent}`;
900
+ }
901
+ function stableEventId(input, reference, discriminator) {
902
+ const digest = createHash("sha256").update(JSON.stringify([
903
+ input.runId,
904
+ input.attemptId,
905
+ reference.artifactId,
906
+ reference.recordLocator,
907
+ discriminator,
908
+ ])).digest("hex");
909
+ return `agent-sdk-${digest.slice(0, 32)}`;
910
+ }
911
+ function contentReference(reference, pointer, role) {
912
+ return {
913
+ nativeReference: {
914
+ artifactId: reference.artifactId,
915
+ recordLocator: reference.recordLocator === "#"
916
+ ? `#${pointer}`
917
+ : `${reference.recordLocator}#${pointer}`,
918
+ },
919
+ role,
920
+ };
921
+ }
922
+ function knownContent(...content) {
923
+ return { status: "known", value: content };
924
+ }
925
+ function compactAttributes(values) {
926
+ return Object.fromEntries(Object.entries(values).filter((entry) => entry[1] !== undefined && validAttributeValue(entry[1])));
927
+ }
928
+ function scalar(value) {
929
+ return validAttributeScalar(value) ? value : undefined;
930
+ }
931
+ function scalarList(value) {
932
+ return Array.isArray(value) && value.length <= 16 && value.every(validAttributeScalar)
933
+ ? value
934
+ : undefined;
935
+ }
936
+ function digestAttribute(value) {
937
+ return `sha256:${digestMetadata(value).value}`;
938
+ }
939
+ function validAttributeValue(value) {
940
+ return Array.isArray(value) ? value.length <= 16 && value.every(validAttributeScalar) : validAttributeScalar(value);
941
+ }
942
+ function validAttributeScalar(value) {
943
+ return value === null || typeof value === "boolean"
944
+ || typeof value === "number" && Number.isFinite(value)
945
+ || typeof value === "string" && [...value].length <= 512;
946
+ }
947
+ function asRecord(value) {
948
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
949
+ }
950
+ function text(value) {
951
+ return typeof value === "string" && value.trim() !== "" && [...value].length <= 256 ? value : undefined;
952
+ }
953
+ function unmappedReason(record) {
954
+ if (record.kind === "capture-report")
955
+ return "Capture qualification metadata informs the capability report and is not a semantic event";
956
+ if (record.kind === "diagnostic")
957
+ return "Diagnostic sidecar remains native outcome evidence";
958
+ if (record.kind === "session")
959
+ return "Unrecognized Agent SDK message type remains in the native session artifact";
960
+ if (record.kind === "hook")
961
+ return "Unrecognized Agent SDK hook record remains in the native hook artifact";
962
+ return `Unrecognized ${record.kind} record remains in native evidence`;
963
+ }
964
+ function referenceKey(reference) {
965
+ return JSON.stringify([reference.artifactId, reference.recordLocator]);
966
+ }
967
+ function splitDerivedLocator(locator) {
968
+ if (locator.startsWith("#/"))
969
+ return { base: "#", pointer: locator.slice(1) };
970
+ const marker = locator.indexOf("#/");
971
+ return marker === -1 ? undefined : { base: locator.slice(0, marker), pointer: locator.slice(marker + 1) };
972
+ }
973
+ function resolvesJsonPointer(document, pointer) {
974
+ if (pointer === "")
975
+ return true;
976
+ if (!pointer.startsWith("/"))
977
+ return false;
978
+ let current = document;
979
+ for (const encoded of pointer.slice(1).split("/")) {
980
+ const segment = encoded.replace(/~1/gu, "/").replace(/~0/gu, "~");
981
+ if (Array.isArray(current)) {
982
+ if (!/^(0|[1-9][0-9]*)$/u.test(segment) || Number(segment) >= current.length)
983
+ return false;
984
+ current = current.at(Number(segment));
985
+ }
986
+ else {
987
+ const record = asRecord(current);
988
+ const property = record === undefined ? undefined : Object.getOwnPropertyDescriptor(record, segment);
989
+ if (property === undefined || !("value" in property))
990
+ return false;
991
+ current = property.value;
992
+ }
993
+ }
994
+ return true;
995
+ }