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.
- package/LICENSE +202 -0
- package/README.md +151 -0
- package/contracts/codex-app-server-0.150.1/manifest.json +25 -0
- package/contracts/codex-app-server-0.150.1/schema/InitializeParams.json +84 -0
- package/contracts/codex-app-server-0.150.1/schema/JSONRPCMessage.json +137 -0
- package/contracts/codex-app-server-0.150.1/schema/v2/ThreadReadParams.json +17 -0
- package/contracts/codex-app-server-0.150.1/schema/v2/ThreadTokenUsageUpdatedNotification.json +82 -0
- package/contracts/codex-app-server-0.150.1/schema/v2/TurnInterruptParams.json +17 -0
- package/contracts/codex-app-server-0.150.1/types/AskForApproval.ts +5 -0
- package/contracts/codex-app-server-0.150.1/types/ClientNotification.ts +5 -0
- package/contracts/codex-app-server-0.150.1/types/SandboxMode.ts +5 -0
- package/contracts/codex-app-server-0.150.1/types/ThreadReadParams.ts +9 -0
- package/contracts/codex-app-server-0.150.1/types/TokenUsageBreakdown.ts +5 -0
- package/contracts/codex-app-server-0.150.1/types/TurnInterruptParams.ts +5 -0
- package/contracts/codex-app-server-0.153.4/manifest.json +27 -0
- package/contracts/codex-app-server-0.153.4/schema/JSONRPCMessage.json +137 -0
- package/contracts/codex-app-server-0.153.4/schema/v1/InitializeParams.json +84 -0
- package/contracts/codex-app-server-0.153.4/schema/v2/ThreadReadParams.json +17 -0
- package/contracts/codex-app-server-0.153.4/schema/v2/ThreadStartParams.json +515 -0
- package/contracts/codex-app-server-0.153.4/schema/v2/ThreadTokenUsageUpdatedNotification.json +82 -0
- package/contracts/codex-app-server-0.153.4/schema/v2/TurnInterruptParams.json +17 -0
- package/contracts/codex-app-server-0.153.4/schema/v2/TurnStartParams.json +921 -0
- package/contracts/codex-app-server-0.153.4/types/AskForApproval.ts +5 -0
- package/contracts/codex-app-server-0.153.4/types/ClientNotification.ts +5 -0
- package/contracts/codex-app-server-0.153.4/types/SandboxMode.ts +5 -0
- package/contracts/codex-app-server-0.153.4/types/ThreadReadParams.ts +12 -0
- package/contracts/codex-app-server-0.153.4/types/TokenUsageBreakdown.ts +5 -0
- package/contracts/codex-app-server-0.153.4/types/TurnInterruptParams.ts +5 -0
- package/contracts/openhands-agent-server-v1.44.1.json +65 -0
- package/contracts/openhands-agent-server-v1.46.0.json +46 -0
- package/dist/contracts/codex-app-server-0.153.4/types/AskForApproval.d.ts +9 -0
- package/dist/contracts/codex-app-server-0.153.4/types/AskForApproval.js +2 -0
- package/dist/contracts/codex-app-server-0.153.4/types/ClientNotification.d.ts +3 -0
- package/dist/contracts/codex-app-server-0.153.4/types/ClientNotification.js +2 -0
- package/dist/contracts/codex-app-server-0.153.4/types/SandboxMode.d.ts +1 -0
- package/dist/contracts/codex-app-server-0.153.4/types/SandboxMode.js +2 -0
- package/dist/contracts/codex-app-server-0.153.4/types/ThreadReadParams.d.ts +10 -0
- package/dist/contracts/codex-app-server-0.153.4/types/ThreadReadParams.js +2 -0
- package/dist/contracts/codex-app-server-0.153.4/types/TokenUsageBreakdown.d.ts +8 -0
- package/dist/contracts/codex-app-server-0.153.4/types/TokenUsageBreakdown.js +2 -0
- package/dist/contracts/codex-app-server-0.153.4/types/TurnInterruptParams.d.ts +4 -0
- package/dist/contracts/codex-app-server-0.153.4/types/TurnInterruptParams.js +2 -0
- package/dist/src/agent-sdk-normalizer.d.ts +22 -0
- package/dist/src/agent-sdk-normalizer.js +995 -0
- package/dist/src/agent-sdk-run.d.ts +31 -0
- package/dist/src/agent-sdk-run.js +242 -0
- package/dist/src/agent-sdk-runner.d.ts +96 -0
- package/dist/src/agent-sdk-runner.js +407 -0
- package/dist/src/agent-sdk.d.ts +294 -0
- package/dist/src/agent-sdk.js +644 -0
- package/dist/src/aggregation.d.ts +183 -0
- package/dist/src/aggregation.js +513 -0
- package/dist/src/artifacts.d.ts +36 -0
- package/dist/src/artifacts.js +1372 -0
- package/dist/src/atlas-grafana.d.ts +377 -0
- package/dist/src/atlas-grafana.js +47 -0
- package/dist/src/atlas-html.d.ts +2 -0
- package/dist/src/atlas-html.js +34 -0
- package/dist/src/atlas.d.ts +130 -0
- package/dist/src/atlas.js +235 -0
- package/dist/src/behavior-assertions.d.ts +95 -0
- package/dist/src/behavior-assertions.js +154 -0
- package/dist/src/cli.d.ts +5 -0
- package/dist/src/cli.js +957 -0
- package/dist/src/codex-judge.d.ts +44 -0
- package/dist/src/codex-judge.js +214 -0
- package/dist/src/codex-run.d.ts +114 -0
- package/dist/src/codex-run.js +519 -0
- package/dist/src/codex.d.ts +187 -0
- package/dist/src/codex.js +1028 -0
- package/dist/src/contracts.d.ts +155 -0
- package/dist/src/contracts.js +866 -0
- package/dist/src/corpus.d.ts +67 -0
- package/dist/src/corpus.js +540 -0
- package/dist/src/cursor-sdk-runner.d.ts +81 -0
- package/dist/src/cursor-sdk-runner.js +362 -0
- package/dist/src/cursor-sdk.d.ts +81 -0
- package/dist/src/cursor-sdk.js +1078 -0
- package/dist/src/deepseek-adapter.d.ts +152 -0
- package/dist/src/deepseek-adapter.js +777 -0
- package/dist/src/exports.d.ts +69 -0
- package/dist/src/exports.js +966 -0
- package/dist/src/human-calibration.d.ts +167 -0
- package/dist/src/human-calibration.js +618 -0
- package/dist/src/index.d.ts +71 -0
- package/dist/src/index.js +38 -0
- package/dist/src/lifecycle.d.ts +196 -0
- package/dist/src/lifecycle.js +1889 -0
- package/dist/src/normalization-integrity.d.ts +128 -0
- package/dist/src/normalization-integrity.js +429 -0
- package/dist/src/openhands-run.d.ts +36 -0
- package/dist/src/openhands-run.js +415 -0
- package/dist/src/openhands.d.ts +130 -0
- package/dist/src/openhands.js +859 -0
- package/dist/src/pi.d.ts +203 -0
- package/dist/src/pi.js +1345 -0
- package/dist/src/process-protocol.d.ts +245 -0
- package/dist/src/process-protocol.js +1261 -0
- package/dist/src/retained-evidence.d.ts +12 -0
- package/dist/src/retained-evidence.js +201 -0
- package/dist/src/run-bundles.d.ts +193 -0
- package/dist/src/run-bundles.js +993 -0
- package/dist/src/scheduler.d.ts +109 -0
- package/dist/src/scheduler.js +1080 -0
- package/dist/src/semantic-judge.d.ts +194 -0
- package/dist/src/semantic-judge.js +875 -0
- package/dist/src/structural-observations.d.ts +124 -0
- package/dist/src/structural-observations.js +671 -0
- package/dist/src/task-packets.d.ts +115 -0
- package/dist/src/task-packets.js +683 -0
- package/dist/src/uniform-events.d.ts +120 -0
- package/dist/src/uniform-events.js +158 -0
- package/dist/src/verifiers.d.ts +116 -0
- package/dist/src/verifiers.js +819 -0
- package/dist/src/workspaces.d.ts +56 -0
- package/dist/src/workspaces.js +1314 -0
- package/docs/README.md +60 -0
- package/docs/development/README.md +71 -0
- package/docs/development/documentation-sync.md +58 -0
- package/docs/development/extension-contracts.md +307 -0
- package/docs/evaluation/README.md +48 -0
- package/docs/evaluation/aggregation.md +112 -0
- package/docs/evaluation/behavior-assertions.md +45 -0
- package/docs/evaluation/human-calibration.md +170 -0
- package/docs/evaluation/normalization-integrity.md +80 -0
- package/docs/evaluation/semantic-judge.md +188 -0
- package/docs/evaluation/structural-observations.md +84 -0
- package/docs/evaluation/uniform-events.md +131 -0
- package/docs/guides/atlas.md +144 -0
- package/docs/guides/evidence-and-sharing.md +100 -0
- package/docs/guides/operator-guide.md +424 -0
- package/docs/guides/quickstart.md +152 -0
- package/docs/guides/telemetry.md +66 -0
- package/docs/harnesses/README.md +31 -0
- package/docs/harnesses/claude-agent-sdk.md +61 -0
- package/docs/harnesses/codex-harness.md +174 -0
- package/docs/harnesses/cursor-sdk.md +149 -0
- package/docs/harnesses/deepseek-harness.md +134 -0
- package/docs/harnesses/openhands-agent-server.md +153 -0
- package/docs/harnesses/pi-sdk.md +66 -0
- package/docs/reference/README.md +19 -0
- package/docs/reference/agent-sdk-operational-runner.md +118 -0
- package/docs/reference/cli.md +114 -0
- package/docs/reference/contracts.md +222 -0
- package/docs/reference/run-bundle-contract.md +354 -0
- package/docs/reference/run-lifecycle.md +54 -0
- package/examples/cursor-sdk/README.md +11 -0
- package/examples/cursor-sdk/capture-profile.json +1 -0
- package/examples/cursor-sdk/harness.json +1 -0
- package/examples/cursor-sdk/model.json +1 -0
- package/examples/cursor-sdk/native-limits.json +1 -0
- package/examples/cursor-sdk/native-tool-policy.json +1 -0
- package/ontology/behavior-categories.v1.json +46 -0
- package/package.json +66 -0
- package/release/0.1.0/KNOWN_LIMITATIONS.md +35 -0
- package/release/0.1.0/README.md +46 -0
- package/release/0.1.0/reproducibility.json +61 -0
- package/release/0.2.0/KNOWN_LIMITATIONS.md +40 -0
- package/release/0.2.0/README.md +42 -0
- package/release/0.2.0/reproducibility.json +73 -0
- package/release/0.2.1/KNOWN_LIMITATIONS.md +18 -0
- package/release/0.2.1/README.md +63 -0
- package/release/0.2.1/reproducibility.json +73 -0
- package/release/README.md +13 -0
- package/schemas/aggregation.v1.json +293 -0
- package/schemas/behavior-assertions.v1.json +220 -0
- package/schemas/deepseek-runtime-composition.v1.schema.json +122 -0
- package/schemas/experiment.v1.schema.json +188 -0
- package/schemas/human-calibration.v1.json +336 -0
- package/schemas/normalization-integrity.v1.json +302 -0
- package/schemas/run-bundles/v1.json +1040 -0
- package/schemas/run-queue.v1.schema.json +206 -0
- package/schemas/semantic-judge.v1.json +380 -0
- package/schemas/structural-observations.v1.json +131 -0
- package/schemas/task-packet-freeze.v1.schema.json +106 -0
- package/schemas/task-packet.v1.schema.json +234 -0
- package/schemas/uniform-events/v1.json +289 -0
- package/scripts/atlas-grafana.sh +18 -0
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { createAgentSdkBehaviorEvidence } from "./behavior-assertions.js";
|
|
3
|
+
import { createRetainedBehaviorEvidence } from "./retained-evidence.js";
|
|
4
|
+
import {} from "./agent-sdk-normalizer.js";
|
|
5
|
+
import { canonicalizeMetadata, digestMetadata, validateArtifact } from "./artifacts.js";
|
|
6
|
+
import {} from "./normalization-integrity.js";
|
|
7
|
+
export const STRUCTURAL_EXTRACTOR_VERSION = "1.0.0";
|
|
8
|
+
export { CLAUDE_AGENT_SDK_NORMALIZATION_ADAPTER_VERSION } from "./agent-sdk-normalizer.js";
|
|
9
|
+
export const STRUCTURAL_EXTRACTOR_REGISTRY = [
|
|
10
|
+
registry("model-request-count", ["family:model-request"], "Distinct source-native inference requests observed in the attempt."),
|
|
11
|
+
registry("tool-operation-count", ["family:tool"], "Distinct logical tool operations, deduplicated by source-native operation identity."),
|
|
12
|
+
registry("tool-native-record-count", ["family:tool"], "Native records carrying mapped or explicitly unprojectable tool evidence."),
|
|
13
|
+
registry("unidentified-tool-native-record-count", ["family:tool"], "Native tool-family records without a resolvable source-native operation identity."),
|
|
14
|
+
registry("tool-error-count", ["family:tool"], "Distinct logical tool operations with an explicit native failure result."),
|
|
15
|
+
registry("repeated-tool-operation-count", ["family:tool"], "Distinct logical tool operations beyond the first with the same explicit tool identity and input digest."),
|
|
16
|
+
registry("failure-followed-by-same-tool-count", ["family:tool", "evidence:nativeOrder"], "Failed logical tool operations followed by a distinct operation of the same tool in the same native-order domain."),
|
|
17
|
+
registry("failure-followed-by-alternate-tool-count", ["family:tool", "evidence:nativeOrder"], "Failed logical tool operations followed by a distinct operation of another tool in the same native-order domain."),
|
|
18
|
+
registry("validation-after-last-mutation", ["family:artifact", "family:validation", "evidence:nativeOrder"], "Whether an explicitly identified validation occurs after the last explicitly identified mutation in one native-order domain."),
|
|
19
|
+
registry("compaction-boundary-record-count", ["family:context"], "Native records that explicitly identify a compaction boundary."),
|
|
20
|
+
registry("input-token-count", ["family:runtime"], "Native-reported input-token total without combining cumulative snapshots or telemetry sources."),
|
|
21
|
+
registry("output-token-count", ["family:runtime"], "Native-reported output-token total without combining cumulative snapshots or telemetry sources."),
|
|
22
|
+
registry("cache-read-input-token-count", ["family:runtime"], "Native-reported cache-read input-token total."),
|
|
23
|
+
registry("cache-creation-input-token-count", ["family:runtime"], "Native-reported cache-creation input-token total."),
|
|
24
|
+
registry("total-token-count", ["family:runtime"], "Native-reported total-token total; component categories are not added to reconstruct it."),
|
|
25
|
+
registry("total-cost-usd", ["family:runtime"], "Native-reported attempt cost in US dollars; subscription utilization is not inferred."),
|
|
26
|
+
registry("attempt-latency-ms", ["family:outcome"], "Native-reported end-to-end attempt duration in milliseconds."),
|
|
27
|
+
];
|
|
28
|
+
export async function createAgentSdkStructuralObservationSet(bundleRoot) {
|
|
29
|
+
const { capture, dataset, resolver, coverage } = await createAgentSdkBehaviorEvidence(bundleRoot);
|
|
30
|
+
const result = createStructuralObservationSet(dataset, coverage, capture);
|
|
31
|
+
await validateStructuralObservationSet(result, resolver);
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
export async function createRetainedStructuralObservationSet(bundleRoot) {
|
|
35
|
+
const { outcomeCapture, dataset, resolver, coverage } = await createRetainedBehaviorEvidence(bundleRoot);
|
|
36
|
+
const result = createStructuralObservationSet(dataset, coverage, outcomeCapture);
|
|
37
|
+
await validateStructuralObservationSet(result, resolver);
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
export function createStructuralObservationSet(dataset, coverage, nativeCapture) {
|
|
41
|
+
if (coverage.runId !== dataset.runId || coverage.attemptId !== dataset.attemptId
|
|
42
|
+
|| canonicalizeMetadata(coverage.adapter) !== canonicalizeMetadata(dataset.adapter)) {
|
|
43
|
+
throw new Error("Structural observation coverage does not match the normalized dataset.");
|
|
44
|
+
}
|
|
45
|
+
const assessmentMode = importAssessmentMode(dataset, nativeCapture);
|
|
46
|
+
const observations = [
|
|
47
|
+
...importOutcomes(dataset, nativeCapture, assessmentMode),
|
|
48
|
+
...extractStructuralFacts(dataset),
|
|
49
|
+
];
|
|
50
|
+
return {
|
|
51
|
+
schemaVersion: "ebo.structural-observation-set/v1",
|
|
52
|
+
runId: dataset.runId,
|
|
53
|
+
attemptId: dataset.attemptId,
|
|
54
|
+
assessmentMode,
|
|
55
|
+
extractorVersion: STRUCTURAL_EXTRACTOR_VERSION,
|
|
56
|
+
normalization: {
|
|
57
|
+
datasetDigest: `sha256:${digestMetadata(dataset).value}`,
|
|
58
|
+
adapter: structuredClone(dataset.adapter),
|
|
59
|
+
capabilityProfile: structuredClone(dataset.capabilityProfile),
|
|
60
|
+
coverage: structuredClone(coverage),
|
|
61
|
+
},
|
|
62
|
+
observations,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export async function validateStructuralObservationSet(report, resolver) {
|
|
66
|
+
const errors = validateArtifact("structural observations", report);
|
|
67
|
+
if (errors.length > 0)
|
|
68
|
+
throw new Error(errors.map(({ field, message }) => `${field}: ${message}`).join("\n"));
|
|
69
|
+
const ids = new Set();
|
|
70
|
+
for (const observation of report.observations) {
|
|
71
|
+
if (ids.has(observation.id))
|
|
72
|
+
throw new Error(`Duplicate structural observation ID "${observation.id}".`);
|
|
73
|
+
ids.add(observation.id);
|
|
74
|
+
if (observation.runId !== report.runId || observation.attemptId !== report.attemptId) {
|
|
75
|
+
throw new Error(`Structural observation "${observation.id}" belongs to another run or attempt.`);
|
|
76
|
+
}
|
|
77
|
+
if (observation.denominator.id !== report.attemptId) {
|
|
78
|
+
throw new Error(`Structural observation "${observation.id}" has the wrong attempt denominator.`);
|
|
79
|
+
}
|
|
80
|
+
if (observation.sourceRecordCount !== observation.citations.length) {
|
|
81
|
+
throw new Error(`Structural observation "${observation.id}" source-record count does not match its citations.`);
|
|
82
|
+
}
|
|
83
|
+
for (const citation of observation.citations) {
|
|
84
|
+
const resolution = await resolver.resolve(citation);
|
|
85
|
+
if (typeof resolution !== "object" || resolution.runId !== report.runId || resolution.attemptId !== report.attemptId) {
|
|
86
|
+
throw new Error(`Structural observation "${observation.id}" has an unresolved native citation.`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function extractStructuralFacts(dataset) {
|
|
92
|
+
const operations = toolOperations(dataset.events);
|
|
93
|
+
const unprojectedToolEvidence = dataset.events.filter((event) => typeof event.attributes.unprojectedToolBlockCount === "number" && event.attributes.unprojectedToolBlockCount > 0);
|
|
94
|
+
const ambiguousTools = uniqueEvents([
|
|
95
|
+
...dataset.events.filter((event) => event.family === "tool" && operationId(event, dataset.events) === undefined),
|
|
96
|
+
...ambiguousActorScopedToolEvents(dataset.events),
|
|
97
|
+
...unprojectedToolEvidence,
|
|
98
|
+
]);
|
|
99
|
+
const ambiguousToolFailures = ambiguousTools.filter(explicitToolFailure);
|
|
100
|
+
const toolEvidence = uniqueEvents([...dataset.events.filter(({ family }) => family === "tool"), ...unprojectedToolEvidence]);
|
|
101
|
+
const toolCitations = citations(toolEvidence);
|
|
102
|
+
const unprojectedToolReason = unprojectedToolEvidence.length === 0 ? undefined
|
|
103
|
+
: `${unprojectedToolEvidence.length} native record(s) contain unprojectable tool blocks.`;
|
|
104
|
+
const modelEvents = dataset.events.filter(({ family }) => family === "model-request");
|
|
105
|
+
const requests = logicalRequestCount(modelEvents);
|
|
106
|
+
const failed = operations.filter(({ failed }) => failed);
|
|
107
|
+
const ambiguousToolStarts = ambiguousTools.filter(isToolStart);
|
|
108
|
+
const repeated = repeatedOperations(operations, ambiguousToolStarts);
|
|
109
|
+
const followed = followedOperations(operations, ambiguousToolStarts);
|
|
110
|
+
const compactions = dataset.events.filter(isCompactionBoundary);
|
|
111
|
+
return [
|
|
112
|
+
countOrUnavailable(dataset, registration("model-request-count"), requests.count, citations(modelEvents), requests.reason, "requests", modelEvents),
|
|
113
|
+
countOrUnavailable(dataset, registration("tool-operation-count"), operations.length, toolCitations, unprojectedToolReason, "identified-logical-tool-operations", toolEvidence),
|
|
114
|
+
countOrUnavailable(dataset, registration("tool-native-record-count"), toolCitations.length, toolCitations, undefined, "native-records", toolEvidence),
|
|
115
|
+
countOrUnavailable(dataset, registration("unidentified-tool-native-record-count"), citations(ambiguousTools).length, citations(ambiguousTools), undefined, "native-records", ambiguousTools),
|
|
116
|
+
countOrUnavailable(dataset, registration("tool-error-count"), failed.length, citations(failed.flatMap(({ events }) => events)), unprojectedToolReason
|
|
117
|
+
?? (ambiguousToolFailures.length > 0 ? "Tool failure totals are unavailable because a failure record has ambiguous operation identity." : undefined), "logical-tool-operations", failed.flatMap(({ events }) => events)),
|
|
118
|
+
countOrUnavailable(dataset, registration("repeated-tool-operation-count"), repeated.count, repeated.citations, unprojectedToolReason ?? repeated.reason, "logical-tool-operations", repeated.events),
|
|
119
|
+
countOrUnavailable(dataset, registration("failure-followed-by-same-tool-count"), followed.same, followed.citations, unprojectedToolReason ?? followed.reason, "failed-logical-tool-operations", followed.events),
|
|
120
|
+
countOrUnavailable(dataset, registration("failure-followed-by-alternate-tool-count"), followed.alternate, followed.citations, unprojectedToolReason ?? followed.reason, "failed-logical-tool-operations", followed.events),
|
|
121
|
+
validationAfterMutation(dataset),
|
|
122
|
+
countOrUnavailable(dataset, registration("compaction-boundary-record-count"), compactions.length, citations(compactions), undefined, "native-records", compactions),
|
|
123
|
+
resourceObservation(dataset, "input-token-count", ["inputTokens"], "tokens"),
|
|
124
|
+
resourceObservation(dataset, "output-token-count", ["outputTokens"], "tokens"),
|
|
125
|
+
resourceObservation(dataset, "cache-read-input-token-count", ["cacheReadInputTokens", "cachedInputTokens"], "tokens"),
|
|
126
|
+
resourceObservation(dataset, "cache-creation-input-token-count", ["cacheCreationInputTokens", "cacheWriteInputTokens"], "tokens"),
|
|
127
|
+
resourceObservation(dataset, "total-token-count", ["totalTokens"], "tokens"),
|
|
128
|
+
resourceObservation(dataset, "total-cost-usd", ["totalCostUsd"], "usd"),
|
|
129
|
+
resourceObservation(dataset, "attempt-latency-ms", ["durationMs"], "milliseconds"),
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
function importOutcomes(dataset, capture, assessmentMode) {
|
|
133
|
+
const terminal = dataset.events.find((event) => event.family === "outcome" && event.source.nativeType === "terminal-record");
|
|
134
|
+
const retainedTerminal = capture?.records.find(({ record }) => record.kind === "manifest");
|
|
135
|
+
const retainedState = asRecord(retainedTerminal?.record.document);
|
|
136
|
+
const retainedWorkspace = capture?.records.find(({ record }) => record.kind === "workspace"
|
|
137
|
+
&& asRecord(record.document)?.id === retainedState?.workspaceArtifactId);
|
|
138
|
+
const terminalWorkspaceIds = new Set(terminal?.content.status === "known"
|
|
139
|
+
? terminal.content.value.flatMap(({ nativeReference, role }) => role === "final-workspace" ? [nativeReference.artifactId] : [])
|
|
140
|
+
: []);
|
|
141
|
+
const workspace = dataset.events.filter((event) => event.family === "artifact"
|
|
142
|
+
&& event.scope.kind === "workspace" && typeof event.scope.id === "string" && terminalWorkspaceIds.has(event.scope.id)
|
|
143
|
+
&& event.content.status === "known" && event.content.value.some(({ nativeReference, role }) => role === "workspace-outcome" && terminalWorkspaceIds.has(nativeReference.artifactId)));
|
|
144
|
+
const captureReports = capture?.records.filter(({ record }) => record.kind === "capture-report") ?? [];
|
|
145
|
+
const captureReferences = captureReports.length > 0
|
|
146
|
+
? captureReports.map(({ reference }) => reference)
|
|
147
|
+
: capture?.records.map(({ reference }) => reference) ?? [];
|
|
148
|
+
const assessmentEvents = dataset.events.filter((event) => event.source.nativeType === "assessment-mode");
|
|
149
|
+
const assessmentReferences = capture?.records.filter(({ record }) => record.kind === "assessment-mode").map(({ reference }) => reference) ?? [];
|
|
150
|
+
const verifierReferences = capture?.records.filter(({ record }) => record.kind === "verifier").map(({ reference }) => reference) ?? [];
|
|
151
|
+
const base = [
|
|
152
|
+
assessmentMode === "unknown"
|
|
153
|
+
? unavailable(dataset, outcomeRegistry("assessment-mode", "Assessment mode retained by the run manifest."), "Assessment mode evidence is missing.", [])
|
|
154
|
+
: directObservation(dataset, "assessment-mode", "Assessment mode retained by the run manifest.", assessmentMode, "mode", assessmentEvents, assessmentEvents.length === 0 ? assessmentReferences.length > 0 ? assessmentReferences : verifierReferences : []),
|
|
155
|
+
terminal === undefined || typeof terminal.attributes.state !== "string"
|
|
156
|
+
? retainedTerminal !== undefined && typeof retainedState?.state === "string"
|
|
157
|
+
? directObservation(dataset, "terminal-state", "Terminal state retained by the run manifest.", retainedState.state, "state", [], [retainedTerminal.reference])
|
|
158
|
+
: unavailable(dataset, outcomeRegistry("terminal-state", "Terminal state retained by the run manifest."), "Terminal outcome evidence is missing.", [])
|
|
159
|
+
: directObservation(dataset, "terminal-state", "Terminal state retained by the run manifest.", String(terminal.attributes.state), "state", [terminal]),
|
|
160
|
+
capture === undefined
|
|
161
|
+
? unavailable(dataset, outcomeRegistry("capture-qualification", "Structural capture qualification used to admit normalization."), "Capture qualification evidence was not supplied.", [])
|
|
162
|
+
: directObservation(dataset, "capture-qualification", "Structural capture qualification used to admit normalization.", capture.qualification, "qualification", [], captureReferences),
|
|
163
|
+
workspace.length === 0 && retainedWorkspace !== undefined && retainedTerminal !== undefined
|
|
164
|
+
? directObservation(dataset, "workspace-outcome-count", "Retained final workspace outcomes referenced by the attempt.", 1, "workspace-outcomes", [], [retainedWorkspace.reference, retainedTerminal.reference])
|
|
165
|
+
: workspace.length === 0
|
|
166
|
+
? unavailable(dataset, outcomeRegistry("workspace-outcome-count", "Retained final workspace outcomes referenced by the attempt."), terminalWorkspaceIds.size === 0 ? "Terminal outcome does not reference a final workspace artifact." : "Terminal-referenced workspace outcome evidence is missing.", terminal === undefined ? [] : citations([terminal]), terminal === undefined ? [] : [terminal])
|
|
167
|
+
: directObservation(dataset, "workspace-outcome-count", "Retained final workspace outcomes referenced by the attempt.", new Set(workspace.map(({ scope }) => scope.id)).size, "workspace-outcomes", [...workspace, ...(terminal === undefined ? [] : [terminal])]),
|
|
168
|
+
];
|
|
169
|
+
if (assessmentMode !== "verified" || capture === undefined)
|
|
170
|
+
return base;
|
|
171
|
+
const assertions = capture.records.flatMap(({ record, reference }) => {
|
|
172
|
+
if (record.kind !== "verifier")
|
|
173
|
+
return [];
|
|
174
|
+
const document = asRecord(record.document);
|
|
175
|
+
return Array.isArray(document?.assertions) ? document.assertions.flatMap((candidate, index) => {
|
|
176
|
+
const assertion = asRecord(candidate);
|
|
177
|
+
return typeof assertion?.id === "string" && typeof assertion.status === "string"
|
|
178
|
+
? [{ id: assertion.id, status: assertion.status, reference: derivedReference(reference, `/assertions/${index}`) }]
|
|
179
|
+
: [];
|
|
180
|
+
}) : [];
|
|
181
|
+
});
|
|
182
|
+
return [...base, ...assertions.map(({ id, status, reference }) => directObservation(dataset, `verifier-assertion-${stableSuffix(id)}`, "Verifier assertion outcome identified by the cited native record.", status, "assertion-status", [], [reference]))];
|
|
183
|
+
}
|
|
184
|
+
function toolOperations(events) {
|
|
185
|
+
const groups = new Map();
|
|
186
|
+
for (const event of events.filter(({ family }) => family === "tool")) {
|
|
187
|
+
const id = operationId(event, events);
|
|
188
|
+
if (id === undefined)
|
|
189
|
+
continue;
|
|
190
|
+
const grouped = groups.get(id) ?? [];
|
|
191
|
+
grouped.push(event);
|
|
192
|
+
groups.set(id, grouped);
|
|
193
|
+
}
|
|
194
|
+
return [...groups].sort(([left], [right]) => left.localeCompare(right))
|
|
195
|
+
.flatMap(([id, grouped]) => groupToolEvents(id, grouped, events).groups.map(([scopedId, scoped]) => operation(scopedId, scoped)));
|
|
196
|
+
}
|
|
197
|
+
function operation(id, grouped) {
|
|
198
|
+
const toolNames = new Set(grouped.flatMap((event) => scalarString(event.attributes.toolName) ?? []));
|
|
199
|
+
const inputDigests = new Set(grouped.flatMap((event) => scalarString(event.attributes.inputDigest) ?? []));
|
|
200
|
+
return {
|
|
201
|
+
id,
|
|
202
|
+
events: grouped,
|
|
203
|
+
...(toolNames.size === 1 ? { toolName: [...toolNames][0] } : {}),
|
|
204
|
+
...(inputDigests.size === 1 ? { inputDigest: [...inputDigests][0] } : {}),
|
|
205
|
+
failed: grouped.some(explicitToolFailure),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function actorScope(event) {
|
|
209
|
+
return explicitAgentScope(event) ?? directSessionScope(event);
|
|
210
|
+
}
|
|
211
|
+
function explicitAgentScope(event) {
|
|
212
|
+
const id = scalarString(event.attributes.agentId) ?? (event.actor.kind === "agent" ? event.actor.id : undefined);
|
|
213
|
+
return id === undefined ? undefined : `agent:${id}`;
|
|
214
|
+
}
|
|
215
|
+
function directSessionScope(event) {
|
|
216
|
+
const retained = scalarString(event.attributes.sessionId);
|
|
217
|
+
if (retained !== undefined)
|
|
218
|
+
return `session:${retained}`;
|
|
219
|
+
const scopeId = scalarString(event.scope.id);
|
|
220
|
+
return (event.scope.kind === "session" || event.scope.kind === "turn") && scopeId !== undefined
|
|
221
|
+
? `${event.scope.kind}:${scopeId}` : undefined;
|
|
222
|
+
}
|
|
223
|
+
function sessionScope(event, allEvents) {
|
|
224
|
+
const direct = directSessionScope(event);
|
|
225
|
+
if (direct !== undefined)
|
|
226
|
+
return direct;
|
|
227
|
+
const reference = canonicalizeMetadata(event.source.nativeReference);
|
|
228
|
+
const sibling = allEvents.find((candidate) => candidate.id !== event.id
|
|
229
|
+
&& canonicalizeMetadata(candidate.source.nativeReference) === reference && directSessionScope(candidate) !== undefined);
|
|
230
|
+
if (sibling !== undefined)
|
|
231
|
+
return directSessionScope(sibling);
|
|
232
|
+
for (const relation of event.relations.known) {
|
|
233
|
+
const related = allEvents.find(({ id }) => id === relation.eventId);
|
|
234
|
+
if (related === undefined)
|
|
235
|
+
continue;
|
|
236
|
+
const relatedDirect = directSessionScope(related);
|
|
237
|
+
if (relatedDirect !== undefined)
|
|
238
|
+
return relatedDirect;
|
|
239
|
+
const relatedReference = canonicalizeMetadata(related.source.nativeReference);
|
|
240
|
+
const relatedSibling = allEvents.find((candidate) => candidate.id !== related.id
|
|
241
|
+
&& canonicalizeMetadata(candidate.source.nativeReference) === relatedReference && directSessionScope(candidate) !== undefined);
|
|
242
|
+
if (relatedSibling !== undefined)
|
|
243
|
+
return directSessionScope(relatedSibling);
|
|
244
|
+
}
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
function groupToolEvents(id, events, allEvents) {
|
|
248
|
+
const sessions = new Set(events.flatMap((event) => sessionScope(event, allEvents) ?? []));
|
|
249
|
+
if (sessions.size === 0)
|
|
250
|
+
return splitToolEventsByAgent(id, events);
|
|
251
|
+
const bySession = new Map([...sessions].sort().map((session) => [session, []]));
|
|
252
|
+
const agentSessions = new Map();
|
|
253
|
+
for (const event of events) {
|
|
254
|
+
const agent = explicitAgentScope(event);
|
|
255
|
+
const session = sessionScope(event, allEvents);
|
|
256
|
+
if (agent === undefined || session === undefined)
|
|
257
|
+
continue;
|
|
258
|
+
const linked = agentSessions.get(agent) ?? new Set();
|
|
259
|
+
linked.add(session);
|
|
260
|
+
agentSessions.set(agent, linked);
|
|
261
|
+
}
|
|
262
|
+
const ambiguous = [];
|
|
263
|
+
for (const event of events) {
|
|
264
|
+
const session = sessionScope(event, allEvents);
|
|
265
|
+
if (session !== undefined)
|
|
266
|
+
bySession.get(session).push(event);
|
|
267
|
+
else if (sessions.size === 1)
|
|
268
|
+
bySession.values().next().value.push(event);
|
|
269
|
+
else {
|
|
270
|
+
const linked = agentSessions.get(explicitAgentScope(event) ?? "");
|
|
271
|
+
if (linked?.size === 1)
|
|
272
|
+
bySession.get([...linked][0]).push(event);
|
|
273
|
+
else
|
|
274
|
+
ambiguous.push(event);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const groups = [];
|
|
278
|
+
for (const [session, grouped] of bySession) {
|
|
279
|
+
const split = splitToolEventsByAgent(`${session}:${id}`, grouped);
|
|
280
|
+
groups.push(...split.groups);
|
|
281
|
+
ambiguous.push(...split.ambiguous);
|
|
282
|
+
}
|
|
283
|
+
return { groups, ambiguous };
|
|
284
|
+
}
|
|
285
|
+
function splitToolEventsByAgent(id, events) {
|
|
286
|
+
const agents = new Set(events.flatMap((event) => explicitAgentScope(event) ?? []));
|
|
287
|
+
if (agents.size <= 1)
|
|
288
|
+
return { groups: [[id, [...events]]], ambiguous: [] };
|
|
289
|
+
return {
|
|
290
|
+
groups: [...agents].sort().map((agent) => [`${agent}:${id}`, events.filter((event) => explicitAgentScope(event) === agent)]),
|
|
291
|
+
ambiguous: events.filter((event) => explicitAgentScope(event) === undefined),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
function ambiguousActorScopedToolEvents(events) {
|
|
295
|
+
const groups = new Map();
|
|
296
|
+
for (const event of events.filter(({ family }) => family === "tool")) {
|
|
297
|
+
const id = operationId(event, events);
|
|
298
|
+
if (id === undefined)
|
|
299
|
+
continue;
|
|
300
|
+
const grouped = groups.get(id) ?? [];
|
|
301
|
+
grouped.push(event);
|
|
302
|
+
groups.set(id, grouped);
|
|
303
|
+
}
|
|
304
|
+
return [...groups.entries()].flatMap(([id, grouped]) => groupToolEvents(id, grouped, events).ambiguous);
|
|
305
|
+
}
|
|
306
|
+
function uniqueEvents(events) {
|
|
307
|
+
return [...new Map(events.map((event) => [event.id, event])).values()];
|
|
308
|
+
}
|
|
309
|
+
function repeatedOperations(operations, ambiguousStarts) {
|
|
310
|
+
if (ambiguousStarts.length > 0) {
|
|
311
|
+
const events = [...operations.flatMap(({ events }) => events), ...ambiguousStarts];
|
|
312
|
+
return { count: 0, citations: citations(events), events, reason: "Identity-less tool starts make repeated-operation identity ambiguous." };
|
|
313
|
+
}
|
|
314
|
+
if (operations.length === 0)
|
|
315
|
+
return { count: 0, citations: [], events: [] };
|
|
316
|
+
if (operations.some(({ toolName, inputDigest }) => toolName === undefined || inputDigest === undefined)) {
|
|
317
|
+
const events = operations.flatMap(({ events }) => events);
|
|
318
|
+
return { count: 0, citations: citations(events), events, reason: "Repetition requires an explicit tool identity and native-input digest for every logical operation." };
|
|
319
|
+
}
|
|
320
|
+
const seen = new Map();
|
|
321
|
+
let count = 0;
|
|
322
|
+
const records = [];
|
|
323
|
+
for (const operation of operations) {
|
|
324
|
+
const signature = JSON.stringify([operation.toolName, operation.inputDigest]);
|
|
325
|
+
const first = seen.get(signature);
|
|
326
|
+
if (first !== undefined) {
|
|
327
|
+
count += 1;
|
|
328
|
+
records.push(...first.events, ...operation.events);
|
|
329
|
+
}
|
|
330
|
+
else
|
|
331
|
+
seen.set(signature, operation);
|
|
332
|
+
}
|
|
333
|
+
return { count, citations: citations(records), events: records };
|
|
334
|
+
}
|
|
335
|
+
function followedOperations(operations, ambiguousStarts) {
|
|
336
|
+
const failed = operations.filter(({ failed }) => failed);
|
|
337
|
+
if (failed.length === 0)
|
|
338
|
+
return { same: 0, alternate: 0, citations: [], events: [] };
|
|
339
|
+
if (ambiguousStarts.some(({ nativeOrder }) => nativeOrder.status !== "known")) {
|
|
340
|
+
const events = [...failed.flatMap(({ events }) => events), ...ambiguousStarts];
|
|
341
|
+
return {
|
|
342
|
+
same: 0,
|
|
343
|
+
alternate: 0,
|
|
344
|
+
citations: citations(events),
|
|
345
|
+
events,
|
|
346
|
+
reason: "An identity-less tool start has unknown native order relative to a failed operation.",
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
const ordered = operations.map((operation) => ({ operation, starts: orderedEvents(operation.events, false), failures: orderedEvents(operation.events, true) }));
|
|
350
|
+
if (ordered.some(({ operation, starts }) => starts.length === 0 || operation.toolName === undefined)
|
|
351
|
+
|| ordered.filter(({ operation }) => operation.failed).some(({ failures }) => failures.length === 0)) {
|
|
352
|
+
const events = operations.flatMap(({ events }) => events);
|
|
353
|
+
return {
|
|
354
|
+
same: 0,
|
|
355
|
+
alternate: 0,
|
|
356
|
+
citations: citations(events),
|
|
357
|
+
events,
|
|
358
|
+
reason: "Failure-followed-by-operation requires explicit tool identities and source-local order for starts and failures.",
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
let same = 0;
|
|
362
|
+
let alternate = 0;
|
|
363
|
+
const records = [];
|
|
364
|
+
for (const current of ordered.filter(({ operation }) => operation.failed)) {
|
|
365
|
+
records.push(...current.operation.events);
|
|
366
|
+
const nextOperations = [];
|
|
367
|
+
for (const failure of current.failures) {
|
|
368
|
+
const candidates = ordered.flatMap(({ operation, starts }) => starts.map((start) => ({ operation, start })))
|
|
369
|
+
.filter(({ operation, start }) => operation.id !== current.operation.id
|
|
370
|
+
&& start.domain === failure.domain && start.value > failure.value);
|
|
371
|
+
const nextOrder = candidates.length === 0 ? Number.POSITIVE_INFINITY : Math.min(...candidates.map(({ start }) => start.value));
|
|
372
|
+
const intervening = ambiguousStarts.filter(({ nativeOrder }) => nativeOrder.status === "known"
|
|
373
|
+
&& nativeOrder.domain === failure.domain && nativeOrder.value > failure.value && nativeOrder.value <= nextOrder);
|
|
374
|
+
if (intervening.length > 0) {
|
|
375
|
+
const next = candidates.filter(({ start }) => start.value === nextOrder).flatMap(({ operation }) => operation.events);
|
|
376
|
+
const events = [...current.operation.events, ...intervening, ...next];
|
|
377
|
+
return {
|
|
378
|
+
same: 0,
|
|
379
|
+
alternate: 0,
|
|
380
|
+
citations: citations(events),
|
|
381
|
+
events,
|
|
382
|
+
reason: "An identity-less tool start can be the next operation after a failure.",
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
if (candidates.length === 0)
|
|
386
|
+
continue;
|
|
387
|
+
const tied = candidates.filter(({ start }) => start.value === nextOrder);
|
|
388
|
+
const tiedClassifications = new Set(tied.map(({ operation }) => operation.toolName === current.operation.toolName ? "same" : "alternate"));
|
|
389
|
+
if (tiedClassifications.size > 1) {
|
|
390
|
+
const events = [current.operation, ...tied.map(({ operation }) => operation)].flatMap(({ events }) => events);
|
|
391
|
+
return {
|
|
392
|
+
same: 0,
|
|
393
|
+
alternate: 0,
|
|
394
|
+
citations: citations(events),
|
|
395
|
+
events,
|
|
396
|
+
reason: "Tied next operations in one native-order domain disagree on tool classification.",
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
nextOperations.push(...tied.map(({ operation }) => operation));
|
|
400
|
+
}
|
|
401
|
+
const classifications = new Set(nextOperations.map((operation) => operation.toolName === current.operation.toolName ? "same" : "alternate"));
|
|
402
|
+
if (classifications.size > 1) {
|
|
403
|
+
const events = [current.operation, ...nextOperations].flatMap(({ events }) => events);
|
|
404
|
+
return {
|
|
405
|
+
same: 0,
|
|
406
|
+
alternate: 0,
|
|
407
|
+
citations: citations(events),
|
|
408
|
+
events,
|
|
409
|
+
reason: "Independent native-order domains disagree on the next tool operation classification.",
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
const classification = [...classifications][0];
|
|
413
|
+
if (classification === "same")
|
|
414
|
+
same += 1;
|
|
415
|
+
if (classification === "alternate")
|
|
416
|
+
alternate += 1;
|
|
417
|
+
if (classification !== undefined)
|
|
418
|
+
records.push(...nextOperations.flatMap(({ events }) => events));
|
|
419
|
+
}
|
|
420
|
+
return { same, alternate, citations: citations(records), events: records };
|
|
421
|
+
}
|
|
422
|
+
function isToolStart(event) {
|
|
423
|
+
return event.family === "tool" && (event.phase === "before" || event.phase === "instant");
|
|
424
|
+
}
|
|
425
|
+
function validationAfterMutation(dataset) {
|
|
426
|
+
const registrationValue = registration("validation-after-last-mutation");
|
|
427
|
+
const mutations = dataset.events.filter((event) => event.family === "artifact" && event.attributes.mutation === true);
|
|
428
|
+
const validations = dataset.events.filter(({ family }) => family === "validation");
|
|
429
|
+
const records = [...mutations, ...validations];
|
|
430
|
+
const capabilityReason = unavailableCapability(dataset, registrationValue, true);
|
|
431
|
+
if (capabilityReason !== undefined)
|
|
432
|
+
return unavailable(dataset, registrationValue, capabilityReason, citations(records), records);
|
|
433
|
+
if (mutations.length === 0)
|
|
434
|
+
return unavailable(dataset, registrationValue, "No normalized event explicitly identifies a mutation.", citations(records), records);
|
|
435
|
+
const ordered = records.flatMap((event) => event.nativeOrder.status === "known"
|
|
436
|
+
? [{ event, value: event.nativeOrder.value, domain: event.nativeOrder.domain }] : []);
|
|
437
|
+
if (ordered.length !== records.length || new Set(ordered.map(({ domain }) => domain)).size !== 1) {
|
|
438
|
+
return unavailable(dataset, registrationValue, "Mutation and validation ordering is unknown or spans unrelated native-order domains.", citations(records), records);
|
|
439
|
+
}
|
|
440
|
+
const lastMutation = Math.max(...ordered.filter(({ event }) => event.family === "artifact").map(({ value }) => value));
|
|
441
|
+
const value = ordered.some(({ event, value: order }) => event.family === "validation" && order > lastMutation);
|
|
442
|
+
return known(dataset, registrationValue, value, "boolean", citations(records), records);
|
|
443
|
+
}
|
|
444
|
+
function resourceObservation(dataset, id, fields, unit) {
|
|
445
|
+
const resourceEvents = dataset.events.filter((event) => typeof event.attributes.resourceSemantics === "string");
|
|
446
|
+
const candidates = resourceEvents.flatMap((event) => {
|
|
447
|
+
if (typeof event.attributes.resourceSemantics !== "string")
|
|
448
|
+
return [];
|
|
449
|
+
const aliases = fields.flatMap((field) => typeof event.attributes[field] === "number"
|
|
450
|
+
? [{ field, value: event.attributes[field] }] : []);
|
|
451
|
+
return aliases.length === 0 ? [] : [{
|
|
452
|
+
event,
|
|
453
|
+
field: aliases.map(({ field }) => field).join("/"),
|
|
454
|
+
value: aliases[0].value,
|
|
455
|
+
semantics: event.attributes.resourceSemantics,
|
|
456
|
+
aliasConflict: new Set(aliases.map(({ value }) => value)).size > 1,
|
|
457
|
+
}];
|
|
458
|
+
});
|
|
459
|
+
const field = fields.join("/");
|
|
460
|
+
const registrationValue = registration(id);
|
|
461
|
+
const capabilityReason = unavailableCapability(dataset, registrationValue, resourceEvents.length === 0);
|
|
462
|
+
if (capabilityReason !== undefined)
|
|
463
|
+
return unavailable(dataset, registrationValue, capabilityReason, citations(candidates.map(({ event }) => event)), candidates.map(({ event }) => event));
|
|
464
|
+
if (candidates.length === 0)
|
|
465
|
+
return unavailable(dataset, registrationValue, `No supported native ${field} record is available.`, []);
|
|
466
|
+
const finals = candidates.filter(({ semantics }) => semantics === "cumulative-final");
|
|
467
|
+
if (finals.length > 0) {
|
|
468
|
+
const invalid = invalidResourceReason(finals, field, unit);
|
|
469
|
+
if (invalid !== undefined)
|
|
470
|
+
return unavailable(dataset, registrationValue, invalid, citations(finals.map(({ event }) => event)), finals.map(({ event }) => event));
|
|
471
|
+
const values = new Set(finals.map(({ value }) => value));
|
|
472
|
+
return values.size === 1
|
|
473
|
+
? known(dataset, registrationValue, finals[0].value, unit, citations(finals.map(({ event }) => event)), finals.map(({ event }) => event))
|
|
474
|
+
: unavailable(dataset, registrationValue, `Conflicting cumulative-final ${field} records are available.`, citations(finals.map(({ event }) => event)), finals.map(({ event }) => event));
|
|
475
|
+
}
|
|
476
|
+
if (candidates.every(({ semantics }) => semantics === "increment")) {
|
|
477
|
+
const invalid = invalidResourceReason(candidates, field, unit);
|
|
478
|
+
return invalid === undefined
|
|
479
|
+
? known(dataset, registrationValue, candidates.reduce((sum, { value }) => sum + value, 0), unit, citations(candidates.map(({ event }) => event)), candidates.map(({ event }) => event))
|
|
480
|
+
: unavailable(dataset, registrationValue, invalid, citations(candidates.map(({ event }) => event)), candidates.map(({ event }) => event));
|
|
481
|
+
}
|
|
482
|
+
if (candidates.every(({ semantics }) => semantics === "cumulative-snapshot")) {
|
|
483
|
+
const snapshots = resourceEvents.filter(({ attributes }) => attributes.resourceSemantics === "cumulative-snapshot");
|
|
484
|
+
return cumulativeResourceObservation(dataset, registrationValue, candidates, snapshots, field, unit);
|
|
485
|
+
}
|
|
486
|
+
return unavailable(dataset, registrationValue, `${field} records have overlapping or unordered usage semantics.`, citations(candidates.map(({ event }) => event)), candidates.map(({ event }) => event));
|
|
487
|
+
}
|
|
488
|
+
function cumulativeResourceObservation(dataset, extractor, candidates, semanticEvents, field, unit) {
|
|
489
|
+
const ordered = semanticEvents.flatMap((event) => event.nativeOrder.status === "known"
|
|
490
|
+
? [{ event, order: event.nativeOrder.value, domain: event.nativeOrder.domain }] : []);
|
|
491
|
+
if (semanticEvents.length > 1 && (ordered.length !== semanticEvents.length || new Set(ordered.map(({ domain }) => domain)).size !== 1)) {
|
|
492
|
+
return unavailable(dataset, extractor, `${field} cumulative snapshot records have unknown or unrelated native order.`, citations(semanticEvents), semanticEvents);
|
|
493
|
+
}
|
|
494
|
+
const latestEvents = semanticEvents.length === 1
|
|
495
|
+
? semanticEvents
|
|
496
|
+
: ordered.filter(({ order }) => order === Math.max(...ordered.map(({ order: value }) => value))).map(({ event }) => event);
|
|
497
|
+
const latestIds = new Set(latestEvents.map(({ id }) => id));
|
|
498
|
+
const latest = candidates.filter(({ event }) => latestIds.has(event.id));
|
|
499
|
+
if (latest.length === 0) {
|
|
500
|
+
return unavailable(dataset, extractor, `Latest cumulative snapshot omits ${field}.`, citations(latestEvents), latestEvents);
|
|
501
|
+
}
|
|
502
|
+
const invalid = invalidResourceReason(latest, field, unit);
|
|
503
|
+
if (invalid !== undefined)
|
|
504
|
+
return unavailable(dataset, extractor, invalid, citations(latest.map(({ event }) => event)), latest.map(({ event }) => event));
|
|
505
|
+
const values = new Set(latest.map(({ value }) => value));
|
|
506
|
+
return values.size === 1
|
|
507
|
+
? known(dataset, extractor, latest[0].value, unit, citations(latest.map(({ event }) => event)), latest.map(({ event }) => event))
|
|
508
|
+
: unavailable(dataset, extractor, `Conflicting latest cumulative ${field} snapshots are available.`, citations(latest.map(({ event }) => event)), latest.map(({ event }) => event));
|
|
509
|
+
}
|
|
510
|
+
function invalidResourceReason(candidates, field, unit) {
|
|
511
|
+
if (candidates.some(({ aliasConflict }) => aliasConflict))
|
|
512
|
+
return `${field} aliases conflict within one native record.`;
|
|
513
|
+
return candidates.some(({ value }) => !Number.isFinite(value) || value < 0 || unit === "tokens" && !Number.isSafeInteger(value))
|
|
514
|
+
? `${field} contains a negative or non-integral native value.` : undefined;
|
|
515
|
+
}
|
|
516
|
+
function countOrUnavailable(dataset, extractor, count, evidence, reason, unit = "requests", sourceEvents = []) {
|
|
517
|
+
if (reason !== undefined)
|
|
518
|
+
return unavailable(dataset, extractor, reason, evidence, sourceEvents);
|
|
519
|
+
const unavailableReason = unavailableCapability(dataset, extractor, count === 0);
|
|
520
|
+
if (unavailableReason !== undefined)
|
|
521
|
+
return unavailable(dataset, extractor, unavailableReason, evidence, sourceEvents);
|
|
522
|
+
return known(dataset, extractor, count, unit, evidence, sourceEvents);
|
|
523
|
+
}
|
|
524
|
+
function directObservation(dataset, id, definition, value, unit, events, extraCitations = []) {
|
|
525
|
+
return known(dataset, outcomeRegistry(id, definition), value, unit, citations(events, extraCitations), events);
|
|
526
|
+
}
|
|
527
|
+
function known(dataset, extractor, value, unit, evidence, sourceEvents = []) {
|
|
528
|
+
return observation(dataset, extractor, { status: "known", value, unit }, evidence, sourceEvents);
|
|
529
|
+
}
|
|
530
|
+
function unavailable(dataset, extractor, reason, evidence, sourceEvents = []) {
|
|
531
|
+
return observation(dataset, extractor, { status: "unavailable", reason }, evidence, sourceEvents);
|
|
532
|
+
}
|
|
533
|
+
function observation(dataset, extractor, value, evidence, sourceEvents) {
|
|
534
|
+
const unique = uniqueReferences(evidence);
|
|
535
|
+
return {
|
|
536
|
+
schemaVersion: "ebo.structural-observation/v1",
|
|
537
|
+
id: `${dataset.attemptId}:${extractor.id}`,
|
|
538
|
+
runId: dataset.runId,
|
|
539
|
+
attemptId: dataset.attemptId,
|
|
540
|
+
extractor: { id: extractor.id, version: STRUCTURAL_EXTRACTOR_VERSION, requiredCapabilities: extractor.requiredCapabilities },
|
|
541
|
+
definition: extractor.definition,
|
|
542
|
+
denominator: { scope: "attempt", id: dataset.attemptId, value: 1, unit: "attempt" },
|
|
543
|
+
value,
|
|
544
|
+
sourceEventIds: [...new Set(sourceEvents.map(({ id }) => id))].sort(),
|
|
545
|
+
sourceRecordCount: unique.length,
|
|
546
|
+
citations: unique,
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
function operationId(event, events) {
|
|
550
|
+
const direct = [event.attributes.toolUseId, event.attributes.toolCallId, event.attributes.actionId, event.attributes.callId, event.attributes.itemId]
|
|
551
|
+
.map(scalarString).find((value) => value !== undefined)
|
|
552
|
+
?? (event.scope.kind === "operation" ? event.scope.id : undefined);
|
|
553
|
+
if (direct !== undefined)
|
|
554
|
+
return direct;
|
|
555
|
+
for (const relation of event.relations.known) {
|
|
556
|
+
// ponytail: linear lookup keeps the extractor stateless; index IDs if million-record profiles require it.
|
|
557
|
+
const related = events.find(({ id }) => id === relation.eventId);
|
|
558
|
+
const relatedId = related === undefined ? undefined : operationIdWithoutRelations(related);
|
|
559
|
+
if (relatedId !== undefined)
|
|
560
|
+
return relatedId;
|
|
561
|
+
}
|
|
562
|
+
return undefined;
|
|
563
|
+
}
|
|
564
|
+
function operationIdWithoutRelations(event) {
|
|
565
|
+
return [event.attributes.toolUseId, event.attributes.toolCallId, event.attributes.actionId, event.attributes.callId, event.attributes.itemId]
|
|
566
|
+
.map(scalarString).find((value) => value !== undefined)
|
|
567
|
+
?? (event.scope.kind === "operation" ? event.scope.id : undefined);
|
|
568
|
+
}
|
|
569
|
+
function requestId(event) {
|
|
570
|
+
return [event.attributes.requestId, event.attributes.callId, event.attributes.llmResponseId]
|
|
571
|
+
.map(scalarString).find((value) => value !== undefined)
|
|
572
|
+
?? (event.scope.kind === "operation" ? event.scope.id : undefined);
|
|
573
|
+
}
|
|
574
|
+
function logicalRequestCount(events) {
|
|
575
|
+
const groups = new Map();
|
|
576
|
+
const unidentified = events.filter((event) => {
|
|
577
|
+
const id = requestId(event);
|
|
578
|
+
if (id === undefined)
|
|
579
|
+
return true;
|
|
580
|
+
const grouped = groups.get(id) ?? [];
|
|
581
|
+
grouped.push(event);
|
|
582
|
+
groups.set(id, grouped);
|
|
583
|
+
return false;
|
|
584
|
+
});
|
|
585
|
+
let count = 0;
|
|
586
|
+
let ambiguousScopes = 0;
|
|
587
|
+
for (const grouped of groups.values()) {
|
|
588
|
+
const scopes = new Set(grouped.flatMap((event) => actorScope(event) ?? []));
|
|
589
|
+
count += Math.max(1, scopes.size);
|
|
590
|
+
if (scopes.size > 1)
|
|
591
|
+
ambiguousScopes += grouped.filter((event) => actorScope(event) === undefined).length;
|
|
592
|
+
}
|
|
593
|
+
const ambiguities = [
|
|
594
|
+
unidentified.length > 0 ? `${unidentified.length} record(s) lack a source-native request identity` : undefined,
|
|
595
|
+
ambiguousScopes > 0 ? `${ambiguousScopes} record(s) lack a native scope while the same request ID occurs in multiple scopes` : undefined,
|
|
596
|
+
].filter((reason) => reason !== undefined);
|
|
597
|
+
return { count, ...(ambiguities.length === 0 ? {} : { reason: `Model-request count is unavailable because ${ambiguities.join(" and ")}.` }) };
|
|
598
|
+
}
|
|
599
|
+
function explicitToolFailure(event) {
|
|
600
|
+
return event.attributes.isError === true || event.attributes.errorScope === "agent-tool"
|
|
601
|
+
|| event.attributes.hook === "PostToolUseFailure" || event.attributes.status === "failed"
|
|
602
|
+
|| event.attributes.status === "error" || event.attributes.status === "declined";
|
|
603
|
+
}
|
|
604
|
+
function orderedEvents(events, failures) {
|
|
605
|
+
return events.filter((event) => failures ? explicitToolFailure(event) : event.phase === "before" || event.phase === "instant")
|
|
606
|
+
.flatMap((event) => event.nativeOrder.status === "known"
|
|
607
|
+
? [{ value: event.nativeOrder.value, domain: event.nativeOrder.domain }] : []);
|
|
608
|
+
}
|
|
609
|
+
function isCompactionBoundary(event) {
|
|
610
|
+
if (event.family !== "context")
|
|
611
|
+
return false;
|
|
612
|
+
const subtype = scalarString(event.attributes.subtype);
|
|
613
|
+
const hook = scalarString(event.attributes.hook);
|
|
614
|
+
const method = scalarString(event.attributes.method);
|
|
615
|
+
const eventType = scalarString(event.attributes.eventType);
|
|
616
|
+
return subtype === "compact_boundary" || hook === "PreCompact" || hook === "PostCompact"
|
|
617
|
+
|| method === "thread/compacted" || eventType?.startsWith("compaction/") === true
|
|
618
|
+
|| event.source.nativeType === "Condensation" || event.source.nativeType === "CondensationSummaryEvent";
|
|
619
|
+
}
|
|
620
|
+
function importAssessmentMode(dataset, capture) {
|
|
621
|
+
const record = capture?.records.find(({ record }) => record.kind === "assessment-mode")?.record.document;
|
|
622
|
+
if (record === "observational" || record === "verified")
|
|
623
|
+
return record;
|
|
624
|
+
if (capture?.records.some(({ record }) => record.kind === "verifier") === true)
|
|
625
|
+
return "verified";
|
|
626
|
+
const value = dataset.events.find((event) => event.source.nativeType === "assessment-mode")?.attributes.assessmentMode;
|
|
627
|
+
return value === "observational" || value === "verified" ? value : "unknown";
|
|
628
|
+
}
|
|
629
|
+
function unavailableCapability(dataset, extractor, zero) {
|
|
630
|
+
for (const requirement of extractor.requiredCapabilities) {
|
|
631
|
+
const capability = requirement === "evidence:nativeOrder"
|
|
632
|
+
? dataset.capabilityProfile.evidence.nativeOrder
|
|
633
|
+
: dataset.capabilityProfile.families[requirement.slice("family:".length)];
|
|
634
|
+
if (capability.status === "unsupported")
|
|
635
|
+
return capability.detail ?? `Required capability ${requirement} is unsupported.`;
|
|
636
|
+
if (zero && capability.status === "partial")
|
|
637
|
+
return capability.detail ?? `Required capability ${requirement} is partial, so an observed zero is not supported.`;
|
|
638
|
+
}
|
|
639
|
+
return undefined;
|
|
640
|
+
}
|
|
641
|
+
function citations(events, extra = []) {
|
|
642
|
+
return uniqueReferences([...events.map(({ source }) => source.nativeReference), ...extra]);
|
|
643
|
+
}
|
|
644
|
+
function uniqueReferences(references) {
|
|
645
|
+
return [...new Map(references.map((reference) => [canonicalizeMetadata(reference), structuredClone(reference)])).values()]
|
|
646
|
+
.sort((left, right) => canonicalizeMetadata(left).localeCompare(canonicalizeMetadata(right)));
|
|
647
|
+
}
|
|
648
|
+
function derivedReference(reference, pointer) {
|
|
649
|
+
return { artifactId: reference.artifactId, recordLocator: reference.recordLocator === "#" ? `#${pointer}` : `${reference.recordLocator}#${pointer}` };
|
|
650
|
+
}
|
|
651
|
+
function scalarString(value) {
|
|
652
|
+
return typeof value === "string" && value !== "" ? value : undefined;
|
|
653
|
+
}
|
|
654
|
+
function stableSuffix(value) {
|
|
655
|
+
return createHash("sha256").update(value).digest("hex").slice(0, 16);
|
|
656
|
+
}
|
|
657
|
+
function registry(id, requiredCapabilities, definition) {
|
|
658
|
+
return { id, requiredCapabilities, definition };
|
|
659
|
+
}
|
|
660
|
+
function registration(id) {
|
|
661
|
+
const found = STRUCTURAL_EXTRACTOR_REGISTRY.find((candidate) => candidate.id === id);
|
|
662
|
+
if (found === undefined)
|
|
663
|
+
throw new Error(`Unknown structural extractor "${id}".`);
|
|
664
|
+
return found;
|
|
665
|
+
}
|
|
666
|
+
function outcomeRegistry(id, definition) {
|
|
667
|
+
return registry(`outcome-${id}`, [], definition);
|
|
668
|
+
}
|
|
669
|
+
function asRecord(value) {
|
|
670
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
671
|
+
}
|