ai-capability-framework 1.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/README.md +38 -30
- package/ROADMAP.md +14 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +32 -1
- package/dist/discovery/bindings.d.ts +7 -0
- package/dist/discovery/bindings.d.ts.map +1 -0
- package/dist/discovery/bindings.js +24 -0
- package/dist/discovery/budget.d.ts +17 -0
- package/dist/discovery/budget.d.ts.map +1 -0
- package/dist/discovery/budget.js +77 -0
- package/dist/discovery/configuration.d.ts +10 -0
- package/dist/discovery/configuration.d.ts.map +1 -0
- package/dist/discovery/configuration.js +63 -0
- package/dist/discovery/corpus.d.ts +8 -0
- package/dist/discovery/corpus.d.ts.map +1 -0
- package/dist/discovery/corpus.js +64 -0
- package/dist/discovery/diagnostics.d.ts +15 -0
- package/dist/discovery/diagnostics.d.ts.map +1 -0
- package/dist/discovery/diagnostics.js +11 -0
- package/dist/discovery/discoverer.d.ts +23 -0
- package/dist/discovery/discoverer.d.ts.map +1 -0
- package/dist/discovery/discoverer.js +261 -0
- package/dist/discovery/embedding-configuration.d.ts +12 -0
- package/dist/discovery/embedding-configuration.d.ts.map +1 -0
- package/dist/discovery/embedding-configuration.js +81 -0
- package/dist/discovery/embedding-types.d.ts +75 -0
- package/dist/discovery/embedding-types.d.ts.map +1 -0
- package/dist/discovery/embedding-types.js +1 -0
- package/dist/discovery/embeddings.d.ts +38 -0
- package/dist/discovery/embeddings.d.ts.map +1 -0
- package/dist/discovery/embeddings.js +71 -0
- package/dist/discovery/evaluation-validation.d.ts +8 -0
- package/dist/discovery/evaluation-validation.d.ts.map +1 -0
- package/dist/discovery/evaluation-validation.js +19 -0
- package/dist/discovery/evaluation.d.ts +84 -0
- package/dist/discovery/evaluation.d.ts.map +1 -0
- package/dist/discovery/evaluation.js +310 -0
- package/dist/discovery/export.d.ts +14 -0
- package/dist/discovery/export.d.ts.map +1 -0
- package/dist/discovery/export.js +81 -0
- package/dist/discovery/facade.d.ts +82 -0
- package/dist/discovery/facade.d.ts.map +1 -0
- package/dist/discovery/facade.js +266 -0
- package/dist/discovery/fingerprint.d.ts +71 -0
- package/dist/discovery/fingerprint.d.ts.map +1 -0
- package/dist/discovery/fingerprint.js +67 -0
- package/dist/discovery/index-builder.d.ts +3 -0
- package/dist/discovery/index-builder.d.ts.map +1 -0
- package/dist/discovery/index-builder.js +129 -0
- package/dist/discovery/index-validation.d.ts +12 -0
- package/dist/discovery/index-validation.d.ts.map +1 -0
- package/dist/discovery/index-validation.js +117 -0
- package/dist/discovery/index.d.ts +32 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +18 -0
- package/dist/discovery/json-snapshot.d.ts +3 -0
- package/dist/discovery/json-snapshot.d.ts.map +1 -0
- package/dist/discovery/json-snapshot.js +11 -0
- package/dist/discovery/lexical.d.ts +12 -0
- package/dist/discovery/lexical.d.ts.map +1 -0
- package/dist/discovery/lexical.js +26 -0
- package/dist/discovery/metadata.d.ts +4 -0
- package/dist/discovery/metadata.d.ts.map +1 -0
- package/dist/discovery/metadata.js +33 -0
- package/dist/discovery/ranking.d.ts +7 -0
- package/dist/discovery/ranking.d.ts.map +1 -0
- package/dist/discovery/ranking.js +68 -0
- package/dist/discovery/replay.d.ts +50 -0
- package/dist/discovery/replay.d.ts.map +1 -0
- package/dist/discovery/replay.js +175 -0
- package/dist/discovery/retrieval-policy.d.ts +5 -0
- package/dist/discovery/retrieval-policy.d.ts.map +1 -0
- package/dist/discovery/retrieval-policy.js +10 -0
- package/dist/discovery/router.d.ts +39 -0
- package/dist/discovery/router.d.ts.map +1 -0
- package/dist/discovery/router.js +98 -0
- package/dist/discovery/selection-attempt.d.ts +20 -0
- package/dist/discovery/selection-attempt.d.ts.map +1 -0
- package/dist/discovery/selection-attempt.js +39 -0
- package/dist/discovery/selection-cards.d.ts +12 -0
- package/dist/discovery/selection-cards.d.ts.map +1 -0
- package/dist/discovery/selection-cards.js +79 -0
- package/dist/discovery/selection-configuration.d.ts +18 -0
- package/dist/discovery/selection-configuration.d.ts.map +1 -0
- package/dist/discovery/selection-configuration.js +71 -0
- package/dist/discovery/selection-response.d.ts +13 -0
- package/dist/discovery/selection-response.d.ts.map +1 -0
- package/dist/discovery/selection-response.js +47 -0
- package/dist/discovery/selection-types.d.ts +18 -0
- package/dist/discovery/selection-types.d.ts.map +1 -0
- package/dist/discovery/selection-types.js +1 -0
- package/dist/discovery/selection.d.ts +6 -0
- package/dist/discovery/selection.d.ts.map +1 -0
- package/dist/discovery/selection.js +145 -0
- package/dist/discovery/types.d.ts +291 -0
- package/dist/discovery/types.d.ts.map +1 -0
- package/dist/discovery/types.js +1 -0
- package/dist/discovery/usage.d.ts +2 -0
- package/dist/discovery/usage.d.ts.map +1 -0
- package/dist/discovery/usage.js +1 -0
- package/dist/discovery/vectors.d.ts +5 -0
- package/dist/discovery/vectors.d.ts.map +1 -0
- package/dist/discovery/vectors.js +43 -0
- package/dist/discovery/versions.d.ts +37 -0
- package/dist/discovery/versions.d.ts.map +1 -0
- package/dist/discovery/versions.js +36 -0
- package/dist/evals-live/live-eval-runner.d.ts +1 -0
- package/dist/evals-live/live-eval-runner.d.ts.map +1 -1
- package/dist/evals-live/live-eval-runner.js +12 -1
- package/dist/evals-live/types.d.ts +3 -0
- package/dist/evals-live/types.d.ts.map +1 -1
- package/dist/generated/manifest-types.d.ts +3655 -0
- package/dist/generated/manifest-types.d.ts.map +1 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +8 -0
- package/dist/mcp-server/server.d.ts.map +1 -1
- package/dist/mcp-server/server.js +11 -2
- package/dist/mcp-server/types.d.ts +4 -0
- package/dist/mcp-server/types.d.ts.map +1 -1
- package/dist/observability/redaction.d.ts.map +1 -1
- package/dist/observability/redaction.js +6 -2
- package/dist/observability/types.d.ts +1 -1
- package/dist/observability/types.d.ts.map +1 -1
- package/dist/openai/index.d.ts +2 -0
- package/dist/openai/index.d.ts.map +1 -1
- package/dist/openai/index.js +1 -0
- package/dist/openai/responses-runner.d.ts +1 -1
- package/dist/openai/responses-runner.d.ts.map +1 -1
- package/dist/openai/responses-runner.js +134 -7
- package/dist/openai/selection-client.d.ts +28 -0
- package/dist/openai/selection-client.d.ts.map +1 -0
- package/dist/openai/selection-client.js +95 -0
- package/dist/openai/tool-loop.js +1 -1
- package/dist/openai/types.d.ts +19 -2
- package/dist/openai/types.d.ts.map +1 -1
- package/dist/replay/discovery-snapshot.d.ts +10 -0
- package/dist/replay/discovery-snapshot.d.ts.map +1 -0
- package/dist/replay/discovery-snapshot.js +17 -0
- package/dist/replay/recorder.d.ts.map +1 -1
- package/dist/replay/recorder.js +4 -0
- package/dist/replay/redaction.js +5 -1
- package/dist/replay/replayer.d.ts.map +1 -1
- package/dist/replay/replayer.js +14 -1
- package/dist/replay/types.d.ts +3 -0
- package/dist/replay/types.d.ts.map +1 -1
- package/dist/replay/validation.d.ts.map +1 -1
- package/dist/replay/validation.js +6 -0
- package/dist/runtime/bounded-json.d.ts +4 -0
- package/dist/runtime/bounded-json.d.ts.map +1 -0
- package/dist/runtime/bounded-json.js +74 -0
- package/dist/runtime/eligibility.d.ts +4 -0
- package/dist/runtime/eligibility.d.ts.map +1 -0
- package/dist/runtime/eligibility.js +128 -0
- package/dist/runtime/index.d.ts +5 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/provider-attempt.d.ts +11 -0
- package/dist/runtime/provider-attempt.d.ts.map +1 -0
- package/dist/runtime/provider-attempt.js +221 -0
- package/dist/runtime/provider-run.d.ts +10 -0
- package/dist/runtime/provider-run.d.ts.map +1 -0
- package/dist/runtime/provider-run.js +123 -0
- package/dist/runtime/provider-stop.d.ts +9 -0
- package/dist/runtime/provider-stop.d.ts.map +1 -0
- package/dist/runtime/provider-stop.js +28 -0
- package/dist/runtime/provider-types.d.ts +189 -0
- package/dist/runtime/provider-types.d.ts.map +1 -0
- package/dist/runtime/provider-types.js +1 -0
- package/dist/runtime/provider-usage.d.ts +9 -0
- package/dist/runtime/provider-usage.d.ts.map +1 -0
- package/dist/runtime/provider-usage.js +69 -0
- package/dist/runtime/router.d.ts.map +1 -1
- package/dist/runtime/router.js +3 -118
- package/dist/runtime/types.d.ts +9 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/validator.d.ts +3 -0
- package/dist/validator.d.ts.map +1 -1
- package/dist/validator.js +32 -0
- package/docs/api/public-api-policy.md +10 -0
- package/docs/api/stable-workflow.md +58 -0
- package/docs/api.md +7 -0
- package/docs/capability-manifests.md +24 -0
- package/docs/development.md +22 -0
- package/docs/discovery.md +186 -0
- package/docs/evals/discovery-live.md +199 -0
- package/docs/evals/discovery-qualification.md +158 -0
- package/docs/evals/overview.md +9 -0
- package/docs/getting-started/installation.md +11 -12
- package/docs/getting-started/openai-quickstart.md +16 -5
- package/docs/getting-started/provider-neutral-quickstart.md +247 -88
- package/docs/getting-started/quickstart.md +8 -30
- package/docs/index.md +10 -0
- package/docs/release.md +29 -5
- package/docs/runtime.md +27 -0
- package/docs/start-here.md +23 -134
- package/examples/12-capability-discovery-selection/README.md +346 -0
- package/examples/12-capability-discovery-selection/customer-cases.mjs +52 -0
- package/examples/12-capability-discovery-selection/customer-harness.mjs +91 -0
- package/examples/12-capability-discovery-selection/customer-request-example.mjs +320 -0
- package/examples/12-capability-discovery-selection/discovery-example.mjs +175 -0
- package/examples/12-capability-discovery-selection/discovery-fixtures/three-domains.suite.json +7887 -0
- package/examples/12-capability-discovery-selection/discovery-fixtures/three-domains.v2.suite.json +8167 -0
- package/examples/12-capability-discovery-selection/discovery-fixtures/three-domains.v3.suite.json +8342 -0
- package/examples/12-capability-discovery-selection/evaluation/application.mjs +305 -0
- package/examples/12-capability-discovery-selection/evaluation/campaign.mjs +248 -0
- package/examples/12-capability-discovery-selection/evaluation/clients.mjs +433 -0
- package/examples/12-capability-discovery-selection/evaluation/continuation.mjs +208 -0
- package/examples/12-capability-discovery-selection/evaluation/durable-budget.mjs +420 -0
- package/examples/12-capability-discovery-selection/evaluation/freeze.mjs +9 -0
- package/examples/12-capability-discovery-selection/evaluation/harness.mjs +618 -0
- package/examples/12-capability-discovery-selection/evaluation/mock-host.mjs +260 -0
- package/examples/12-capability-discovery-selection/evaluation/preflight.mjs +115 -0
- package/examples/12-capability-discovery-selection/evaluation/qualification-workflows.mjs +87 -0
- package/examples/12-capability-discovery-selection/evaluation/regrade.mjs +281 -0
- package/examples/12-capability-discovery-selection/evaluation/report.mjs +517 -0
- package/examples/12-capability-discovery-selection/evaluation/runner.mjs +613 -0
- package/examples/12-capability-discovery-selection/evaluation/saved-campaign.mjs +385 -0
- package/examples/12-capability-discovery-selection/evaluation/wording.mjs +115 -0
- package/examples/12-capability-discovery-selection/evaluation/workflow-application.mjs +378 -0
- package/examples/12-capability-discovery-selection/evaluation/workflow-cases.mjs +90 -0
- package/examples/12-capability-discovery-selection/evaluation-example.mjs +154 -0
- package/examples/12-capability-discovery-selection/hybrid-example.mjs +174 -0
- package/examples/12-capability-discovery-selection/mock-embeddings.mjs +101 -0
- package/examples/12-capability-discovery-selection/mock-selection.mjs +78 -0
- package/examples/12-capability-discovery-selection/pipeline-example.mjs +496 -0
- package/examples/12-capability-discovery-selection/retrieval-cases.mjs +31 -0
- package/examples/12-capability-discovery-selection/run-campaign-mock.mjs +40 -0
- package/examples/12-capability-discovery-selection/run-customer-mock.mjs +15 -0
- package/examples/12-capability-discovery-selection/run-evaluation-live.mjs +10 -0
- package/examples/12-capability-discovery-selection/run-evaluation-mock.mjs +3 -0
- package/examples/12-capability-discovery-selection/run-evaluation-regrade.mjs +71 -0
- package/examples/12-capability-discovery-selection/run-hybrid-mock.mjs +3 -0
- package/examples/12-capability-discovery-selection/run-mock.mjs +3 -0
- package/examples/12-capability-discovery-selection/run-pipeline-mock.mjs +2 -0
- package/examples/12-capability-discovery-selection/run-selector-mock.mjs +3 -0
- package/examples/12-capability-discovery-selection/selection-example.mjs +88 -0
- package/package.json +17 -4
- package/schemas/discovery/embedding-index.schema.json +71 -0
- package/schemas/discovery/eval-observations.schema.json +409 -0
- package/schemas/discovery/eval-suite.schema.json +375 -0
- package/schemas/discovery/metadata.schema.json +30 -0
- package/schemas/discovery/selection-response.schema.json +47 -0
- package/schemas/replay/discovery-snapshot.schema.json +335 -0
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { toOpenAIResponsesToolName } from "ai-capability-framework";
|
|
3
|
+
import {
|
|
4
|
+
createAicfProviderRun,
|
|
5
|
+
DefaultContextBuilder
|
|
6
|
+
} from "ai-capability-framework/runtime";
|
|
7
|
+
import {
|
|
8
|
+
createCapabilityDiscovery,
|
|
9
|
+
createInMemoryDiscoveryBudget,
|
|
10
|
+
buildCapabilityEmbeddingIndex,
|
|
11
|
+
buildCapabilitySearchCorpus,
|
|
12
|
+
createDiscoverySnapshot,
|
|
13
|
+
replayDiscoverySnapshot
|
|
14
|
+
} from "ai-capability-framework/discovery";
|
|
15
|
+
import {
|
|
16
|
+
createOpenAISelectionClient,
|
|
17
|
+
MockOpenAIResponsesClient,
|
|
18
|
+
mockTextResponse,
|
|
19
|
+
mockFunctionCallResponse,
|
|
20
|
+
runOpenAIResponses
|
|
21
|
+
} from "ai-capability-framework/openai";
|
|
22
|
+
import { createCustomerHarness } from "./customer-harness.mjs";
|
|
23
|
+
import {
|
|
24
|
+
plannedTasks,
|
|
25
|
+
taskExpectations,
|
|
26
|
+
validatePlannedTasks
|
|
27
|
+
} from "./customer-cases.mjs";
|
|
28
|
+
import {
|
|
29
|
+
DeterministicMockEmbeddingClient,
|
|
30
|
+
embeddingIndexLimits
|
|
31
|
+
} from "./mock-embeddings.mjs";
|
|
32
|
+
|
|
33
|
+
const configuration = {
|
|
34
|
+
provider: "openai",
|
|
35
|
+
model: "mock-model",
|
|
36
|
+
configurationId: "example.pipeline.responses.v1"
|
|
37
|
+
};
|
|
38
|
+
const normalizedUsage = {
|
|
39
|
+
inputTokens: 20,
|
|
40
|
+
outputTokens: 5,
|
|
41
|
+
totalTokens: 25,
|
|
42
|
+
cachedInputTokens: 0,
|
|
43
|
+
reasoningTokens: 0,
|
|
44
|
+
estimatedCost: 0
|
|
45
|
+
};
|
|
46
|
+
function reported(response) {
|
|
47
|
+
return {
|
|
48
|
+
...response,
|
|
49
|
+
status: "completed",
|
|
50
|
+
usage: {
|
|
51
|
+
input_tokens: 20,
|
|
52
|
+
output_tokens: 5,
|
|
53
|
+
total_tokens: 25,
|
|
54
|
+
input_tokens_details: { cached_tokens: 0 },
|
|
55
|
+
output_tokens_details: { reasoning_tokens: 0 }
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function runFor(harness, maxProviderCalls = 20) {
|
|
60
|
+
const budget = createInMemoryDiscoveryBudget({
|
|
61
|
+
runId: harness.runtimeContext.runId,
|
|
62
|
+
maxProviderCalls,
|
|
63
|
+
maxInputTokens: 80_000,
|
|
64
|
+
maxOutputTokens: 20_000
|
|
65
|
+
});
|
|
66
|
+
return createAicfProviderRun({
|
|
67
|
+
runId: harness.runtimeContext.runId,
|
|
68
|
+
budget,
|
|
69
|
+
deadlineAt: Date.now() + 30_000
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// An application explicitly invokes this administrative operation, separately from serving.
|
|
73
|
+
async function buildMockIndex(harness) {
|
|
74
|
+
const client = new DeterministicMockEmbeddingClient();
|
|
75
|
+
const budget = createInMemoryDiscoveryBudget({
|
|
76
|
+
runId: "run_index_example",
|
|
77
|
+
maxProviderCalls: 1,
|
|
78
|
+
maxInputTokens: 8000
|
|
79
|
+
});
|
|
80
|
+
const execution = {
|
|
81
|
+
budgetPolicy: budget.policy,
|
|
82
|
+
limits: {
|
|
83
|
+
maxInputTokens: 8000,
|
|
84
|
+
maxCharactersPerText: 16_384,
|
|
85
|
+
maxCharactersPerCall: 65_536
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const built = await buildCapabilityEmbeddingIndex({
|
|
89
|
+
corpus: buildCapabilitySearchCorpus(harness.registry),
|
|
90
|
+
client,
|
|
91
|
+
execution,
|
|
92
|
+
limits: embeddingIndexLimits,
|
|
93
|
+
batchSize: 10,
|
|
94
|
+
maxProviderCalls: 1,
|
|
95
|
+
budget: budget.ledger,
|
|
96
|
+
runId: "run_index_example",
|
|
97
|
+
requestId: "index_build_example",
|
|
98
|
+
deadlineAt: Date.now() + 30_000
|
|
99
|
+
});
|
|
100
|
+
assert.equal(built.status, "built");
|
|
101
|
+
return { client, index: built.index, usage: built.usage };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Four explicit integrations; all responses and vectors are deterministic contract fixtures. */
|
|
105
|
+
export async function runPipelineCase({ hybrid = false, dedicated = false } = {}) {
|
|
106
|
+
const harness = await createCustomerHarness(),
|
|
107
|
+
providerRun = runFor(harness);
|
|
108
|
+
const administrative = hybrid ? await buildMockIndex(harness) : undefined;
|
|
109
|
+
const retrieval = {
|
|
110
|
+
policyVersion: "example.pipeline.v1",
|
|
111
|
+
...(hybrid
|
|
112
|
+
? {
|
|
113
|
+
mode: "hybrid",
|
|
114
|
+
weights: { semantic: 0.75, lexical: 0.25 },
|
|
115
|
+
failurePolicy: "unavailable"
|
|
116
|
+
}
|
|
117
|
+
: { mode: "lexical" })
|
|
118
|
+
};
|
|
119
|
+
const discoveryOptions = {
|
|
120
|
+
registry: harness.registry,
|
|
121
|
+
...retrieval,
|
|
122
|
+
...(hybrid
|
|
123
|
+
? {
|
|
124
|
+
embedding: {
|
|
125
|
+
client: administrative.client,
|
|
126
|
+
index: administrative.index,
|
|
127
|
+
maxInputTokens: 4000
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
: {})
|
|
131
|
+
};
|
|
132
|
+
const planning = await providerRun.attempt({
|
|
133
|
+
requestId: "request_plan",
|
|
134
|
+
stage: "planning",
|
|
135
|
+
configuration,
|
|
136
|
+
mock: true,
|
|
137
|
+
maxInputTokens: 4000,
|
|
138
|
+
maxOutputTokens: 1000,
|
|
139
|
+
invoke: async () => ({
|
|
140
|
+
output: plannedTasks,
|
|
141
|
+
providerAttempts: 1,
|
|
142
|
+
usage: normalizedUsage
|
|
143
|
+
}),
|
|
144
|
+
validate: (response) => ({ value: validatePlannedTasks(response.output) })
|
|
145
|
+
});
|
|
146
|
+
assert.equal(planning.code, undefined);
|
|
147
|
+
const completed = new Map(),
|
|
148
|
+
taskResults = [],
|
|
149
|
+
snapshots = [];
|
|
150
|
+
let ordinaryTurns = 0,
|
|
151
|
+
selectorAttempts = 0;
|
|
152
|
+
for (const task of planning.value) {
|
|
153
|
+
assert.ok(task.dependsOn.every((id) => completed.has(id)));
|
|
154
|
+
const prior = completed.get("read-ticket");
|
|
155
|
+
const runtimeContext = {
|
|
156
|
+
...harness.runtimeContext,
|
|
157
|
+
requestId: "request_" + task.id
|
|
158
|
+
};
|
|
159
|
+
const contextBuilder = new DefaultContextBuilder({
|
|
160
|
+
hostItems: prior
|
|
161
|
+
? [
|
|
162
|
+
{
|
|
163
|
+
id: "authorized-ticket",
|
|
164
|
+
kind: "entity",
|
|
165
|
+
title: "Authorized ticket result",
|
|
166
|
+
data: prior.data,
|
|
167
|
+
trusted: true,
|
|
168
|
+
visibleToModel: true
|
|
169
|
+
}
|
|
170
|
+
]
|
|
171
|
+
: []
|
|
172
|
+
});
|
|
173
|
+
const discovery = createCapabilityDiscovery({
|
|
174
|
+
...discoveryOptions,
|
|
175
|
+
contextBuilder
|
|
176
|
+
});
|
|
177
|
+
const [result] = await discovery.search({
|
|
178
|
+
tasks: [{ id: task.id, text: task.text }],
|
|
179
|
+
runtimeContext,
|
|
180
|
+
providerRun
|
|
181
|
+
});
|
|
182
|
+
assert.equal(result.status, "matched");
|
|
183
|
+
const expectations = taskExpectations[task.id],
|
|
184
|
+
isRead = task.id === "read-ticket";
|
|
185
|
+
const capabilityId = isRead ? "support.ticket.get" : "support.refund.prepare_case";
|
|
186
|
+
assert.ok(
|
|
187
|
+
result.candidates.some((candidate) =>
|
|
188
|
+
expectations.compatibleIds.includes(candidate.capabilityId)
|
|
189
|
+
)
|
|
190
|
+
);
|
|
191
|
+
assert.ok(
|
|
192
|
+
result.candidates.every(
|
|
193
|
+
(candidate) => !expectations.forbiddenIds.includes(candidate.capabilityId)
|
|
194
|
+
)
|
|
195
|
+
);
|
|
196
|
+
let batch;
|
|
197
|
+
if (dedicated) {
|
|
198
|
+
const transport = new MockOpenAIResponsesClient([
|
|
199
|
+
reported(
|
|
200
|
+
mockTextResponse(
|
|
201
|
+
JSON.stringify({
|
|
202
|
+
schema_version: "1.0",
|
|
203
|
+
decisions: [
|
|
204
|
+
{
|
|
205
|
+
query_id: task.id,
|
|
206
|
+
decision: "select",
|
|
207
|
+
capability_id: capabilityId,
|
|
208
|
+
reason_code: "supported_candidate"
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
})
|
|
212
|
+
)
|
|
213
|
+
)
|
|
214
|
+
]);
|
|
215
|
+
const client = createOpenAISelectionClient({
|
|
216
|
+
client: transport,
|
|
217
|
+
configuration,
|
|
218
|
+
mock: true,
|
|
219
|
+
inputTokenPreflight: () => 120
|
|
220
|
+
});
|
|
221
|
+
batch = await discovery.select({
|
|
222
|
+
results: [result],
|
|
223
|
+
client,
|
|
224
|
+
maxInputTokens: 4000,
|
|
225
|
+
maxOutputTokens: 1000,
|
|
226
|
+
estimateCost: () => 0
|
|
227
|
+
});
|
|
228
|
+
assert.equal(batch.outcomes[0].status, "selected");
|
|
229
|
+
selectorAttempts += transport.requests.length;
|
|
230
|
+
}
|
|
231
|
+
const router = batch
|
|
232
|
+
? discovery.routerFor(batch, task.id)
|
|
233
|
+
: discovery.routerFor(result);
|
|
234
|
+
const client = new MockOpenAIResponsesClient([
|
|
235
|
+
reported(
|
|
236
|
+
mockFunctionCallResponse({
|
|
237
|
+
name: toOpenAIResponsesToolName(capabilityId),
|
|
238
|
+
callId: "call_" + task.id,
|
|
239
|
+
args: isRead
|
|
240
|
+
? { ticket_id: "TCK-100" }
|
|
241
|
+
: {
|
|
242
|
+
ticket_id: prior.data.ticket.ticket_id,
|
|
243
|
+
order_id: prior.data.ticket.order_id,
|
|
244
|
+
reason_code: "customer_request",
|
|
245
|
+
requested_amount: null
|
|
246
|
+
}
|
|
247
|
+
})
|
|
248
|
+
),
|
|
249
|
+
reported(
|
|
250
|
+
mockTextResponse(
|
|
251
|
+
isRead ? "Ticket read." : "Refund preparation requires approval."
|
|
252
|
+
)
|
|
253
|
+
)
|
|
254
|
+
]);
|
|
255
|
+
const loop = await runOpenAIResponses({
|
|
256
|
+
...harness,
|
|
257
|
+
runtimeContext,
|
|
258
|
+
contextBuilder,
|
|
259
|
+
userInput: { text: task.text },
|
|
260
|
+
router,
|
|
261
|
+
client,
|
|
262
|
+
model: "mock-model",
|
|
263
|
+
providerExecution: {
|
|
264
|
+
configuration,
|
|
265
|
+
mock: true,
|
|
266
|
+
maxInputTokens: 4000,
|
|
267
|
+
maxOutputTokens: 1000,
|
|
268
|
+
inputTokenPreflight: () => 120,
|
|
269
|
+
estimateCost: () => 0
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
assert.equal(loop.status, "completed");
|
|
273
|
+
assert.ok(
|
|
274
|
+
loop.toolCalls.every((call) =>
|
|
275
|
+
expectations.compatibleIds.includes(call.capabilityId)
|
|
276
|
+
)
|
|
277
|
+
);
|
|
278
|
+
assert.ok(
|
|
279
|
+
client.requests.every((request) =>
|
|
280
|
+
request.tools.every(
|
|
281
|
+
(tool) =>
|
|
282
|
+
tool.name !== toOpenAIResponsesToolName("support.refund.commit_case")
|
|
283
|
+
)
|
|
284
|
+
)
|
|
285
|
+
);
|
|
286
|
+
completed.set(task.id, loop.toolResults[0]);
|
|
287
|
+
ordinaryTurns += client.requests.length;
|
|
288
|
+
taskResults.push({
|
|
289
|
+
taskId: task.id,
|
|
290
|
+
candidateIds: result.candidates.map((candidate) => candidate.capabilityId),
|
|
291
|
+
chosenId: loop.toolCalls[0].capabilityId,
|
|
292
|
+
toolStatus: loop.toolResults[0].status,
|
|
293
|
+
approvalRequired:
|
|
294
|
+
loop.toolResults[0].action?.approvalRequired ??
|
|
295
|
+
loop.toolResults[0].status === "approval_required"
|
|
296
|
+
});
|
|
297
|
+
const builtContext = await contextBuilder.build({
|
|
298
|
+
baseContext: runtimeContext,
|
|
299
|
+
registry: harness.registry,
|
|
300
|
+
userInput: { text: task.text }
|
|
301
|
+
});
|
|
302
|
+
const routeRequest = {
|
|
303
|
+
registry: harness.registry,
|
|
304
|
+
builtContext,
|
|
305
|
+
userInput: { text: task.text }
|
|
306
|
+
};
|
|
307
|
+
const snapshot = createDiscoverySnapshot({
|
|
308
|
+
routeRequest,
|
|
309
|
+
configuration: retrieval,
|
|
310
|
+
queries: [{ id: task.id, fragments: [{ key: "task", text: task.text }] }],
|
|
311
|
+
discovery: [result],
|
|
312
|
+
selection: batch
|
|
313
|
+
});
|
|
314
|
+
assert.equal(
|
|
315
|
+
replayDiscoverySnapshot(snapshot, { mode: "snapshot_validation", routeRequest })
|
|
316
|
+
.status,
|
|
317
|
+
"passed"
|
|
318
|
+
);
|
|
319
|
+
assert.equal(
|
|
320
|
+
replayDiscoverySnapshot(snapshot, { mode: "ranking", routeRequest }).status,
|
|
321
|
+
"refused"
|
|
322
|
+
);
|
|
323
|
+
snapshots.push(snapshot.snapshotFingerprint);
|
|
324
|
+
}
|
|
325
|
+
assert.deepEqual(harness.executedOperations, ["read", "prepare"]);
|
|
326
|
+
assert.equal(harness.readCalls.length, 1);
|
|
327
|
+
assert.equal(harness.prepareCalls.length, 1);
|
|
328
|
+
assert.equal(
|
|
329
|
+
harness.prepareCalls[0].order_id,
|
|
330
|
+
completed.get("read-ticket").data.ticket.order_id
|
|
331
|
+
);
|
|
332
|
+
assert.ok(completed.get("prepare-refund").action?.preparedActionId);
|
|
333
|
+
const receipt = providerRun.snapshot();
|
|
334
|
+
const queryEmbeddings = hybrid
|
|
335
|
+
? administrative.client.calls.length - administrative.usage.totals.providerAttempts
|
|
336
|
+
: 0;
|
|
337
|
+
assert.equal(
|
|
338
|
+
receipt.usage.totals.providerAttempts,
|
|
339
|
+
1 + ordinaryTurns + selectorAttempts + queryEmbeddings
|
|
340
|
+
);
|
|
341
|
+
assert.equal(ordinaryTurns, 4);
|
|
342
|
+
assert.equal(selectorAttempts, dedicated ? 2 : 0);
|
|
343
|
+
assert.equal(queryEmbeddings, hybrid ? 2 : 0);
|
|
344
|
+
return {
|
|
345
|
+
retrieval: hybrid ? "hybrid" : "lexical",
|
|
346
|
+
selection: dedicated ? "dedicated" : "native",
|
|
347
|
+
mock: true,
|
|
348
|
+
planningTurns: 1,
|
|
349
|
+
ordinaryTurns,
|
|
350
|
+
selectorAttempts,
|
|
351
|
+
queryEmbeddings,
|
|
352
|
+
administrativeIndexUsage: administrative?.usage ?? null,
|
|
353
|
+
tasks: taskResults,
|
|
354
|
+
snapshotFingerprints: snapshots,
|
|
355
|
+
usage: receipt.usage,
|
|
356
|
+
traceEvents: receipt.events.length,
|
|
357
|
+
customerResponse:
|
|
358
|
+
"Ticket TCK-100 was read. A refund case for order ORD-100 is prepared and requires approval; no refund was committed."
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export async function runPipelineFailureCases() {
|
|
363
|
+
const harness = await createCustomerHarness(),
|
|
364
|
+
providerRun = runFor(harness);
|
|
365
|
+
const discovery = createCapabilityDiscovery({ registry: harness.registry });
|
|
366
|
+
const [none] = await discovery.search({
|
|
367
|
+
tasks: [{ id: "none", text: "quasar" }],
|
|
368
|
+
runtimeContext: harness.runtimeContext,
|
|
369
|
+
providerRun
|
|
370
|
+
});
|
|
371
|
+
assert.equal(none.status, "no_match");
|
|
372
|
+
const [matched] = await discovery.search({
|
|
373
|
+
tasks: [{ id: "one", text: "ticket" }],
|
|
374
|
+
runtimeContext: harness.runtimeContext,
|
|
375
|
+
providerRun
|
|
376
|
+
});
|
|
377
|
+
const transport = new MockOpenAIResponsesClient([
|
|
378
|
+
reported(
|
|
379
|
+
mockTextResponse(
|
|
380
|
+
JSON.stringify({
|
|
381
|
+
schema_version: "1.0",
|
|
382
|
+
decisions: [
|
|
383
|
+
{
|
|
384
|
+
query_id: "one",
|
|
385
|
+
decision: "abstain",
|
|
386
|
+
capability_id: null,
|
|
387
|
+
reason_code: "insufficient_context"
|
|
388
|
+
}
|
|
389
|
+
]
|
|
390
|
+
})
|
|
391
|
+
)
|
|
392
|
+
)
|
|
393
|
+
]);
|
|
394
|
+
const batch = await discovery.select({
|
|
395
|
+
results: [matched],
|
|
396
|
+
client: createOpenAISelectionClient({
|
|
397
|
+
client: transport,
|
|
398
|
+
configuration,
|
|
399
|
+
mock: true,
|
|
400
|
+
inputTokenPreflight: () => 120
|
|
401
|
+
}),
|
|
402
|
+
maxInputTokens: 4000,
|
|
403
|
+
maxOutputTokens: 1000
|
|
404
|
+
});
|
|
405
|
+
assert.equal(batch.outcomes[0].status, "abstained");
|
|
406
|
+
const bound = discovery.routerFor(matched);
|
|
407
|
+
harness.runtimeContext.subject.permissions = [];
|
|
408
|
+
const builtContext = await new DefaultContextBuilder().build({
|
|
409
|
+
baseContext: harness.runtimeContext,
|
|
410
|
+
registry: harness.registry,
|
|
411
|
+
userInput: { text: "ticket" }
|
|
412
|
+
});
|
|
413
|
+
assert.equal(
|
|
414
|
+
bound.route({
|
|
415
|
+
registry: harness.registry,
|
|
416
|
+
builtContext,
|
|
417
|
+
userInput: { text: "ticket" }
|
|
418
|
+
}).items.length,
|
|
419
|
+
0
|
|
420
|
+
);
|
|
421
|
+
const fresh = await createCustomerHarness(),
|
|
422
|
+
administrative = await buildMockIndex(fresh),
|
|
423
|
+
invalidRun = runFor(fresh);
|
|
424
|
+
const before = administrative.client.calls.length;
|
|
425
|
+
const invalid = createCapabilityDiscovery({
|
|
426
|
+
registry: fresh.registry,
|
|
427
|
+
mode: "hybrid",
|
|
428
|
+
embedding: {
|
|
429
|
+
client: administrative.client,
|
|
430
|
+
index: { ...administrative.index, indexFingerprint: "sha256:" + "0".repeat(64) },
|
|
431
|
+
maxInputTokens: 4000
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
const [rejected] = await invalid.search({
|
|
435
|
+
tasks: [{ id: "bad", text: "ticket" }],
|
|
436
|
+
runtimeContext: fresh.runtimeContext,
|
|
437
|
+
providerRun: invalidRun
|
|
438
|
+
});
|
|
439
|
+
assert.equal(rejected.status, "unavailable");
|
|
440
|
+
assert.equal(administrative.client.calls.length, before);
|
|
441
|
+
const sourceApi = createCapabilityDiscovery({ registry: fresh.registry });
|
|
442
|
+
const [sourceResult] = await sourceApi.search({
|
|
443
|
+
tasks: [{ id: "source", text: "ticket" }],
|
|
444
|
+
runtimeContext: fresh.runtimeContext
|
|
445
|
+
});
|
|
446
|
+
fresh.registry.capabilities[0].manifest.summary += " Updated guidance.";
|
|
447
|
+
const sourceContext = await new DefaultContextBuilder().build({
|
|
448
|
+
baseContext: fresh.runtimeContext,
|
|
449
|
+
registry: fresh.registry,
|
|
450
|
+
userInput: { text: "ticket" }
|
|
451
|
+
});
|
|
452
|
+
assert.equal(
|
|
453
|
+
sourceApi.routerFor(sourceResult).route({
|
|
454
|
+
registry: fresh.registry,
|
|
455
|
+
builtContext: sourceContext,
|
|
456
|
+
userInput: { text: "ticket" }
|
|
457
|
+
}).items.length,
|
|
458
|
+
0
|
|
459
|
+
);
|
|
460
|
+
const exhausted = runFor(fresh, 1);
|
|
461
|
+
const attempt = {
|
|
462
|
+
requestId: "budget",
|
|
463
|
+
stage: "planning",
|
|
464
|
+
configuration,
|
|
465
|
+
mock: true,
|
|
466
|
+
maxInputTokens: 100,
|
|
467
|
+
maxOutputTokens: 20,
|
|
468
|
+
invoke: async () => ({ providerAttempts: 1, usage: normalizedUsage }),
|
|
469
|
+
validate: () => ({ value: true })
|
|
470
|
+
};
|
|
471
|
+
await exhausted.attempt(attempt);
|
|
472
|
+
assert.equal((await exhausted.attempt(attempt)).code, "budget_denied");
|
|
473
|
+
return {
|
|
474
|
+
clarification: "Please clarify the task you want to perform.",
|
|
475
|
+
noMatch: none.status,
|
|
476
|
+
abstention: batch.outcomes[0].status,
|
|
477
|
+
permissionRevocation: "no_tools",
|
|
478
|
+
sourceMutation: "no_tools",
|
|
479
|
+
invalidIndex: rejected.status,
|
|
480
|
+
invalidIndexQueryCalls: administrative.client.calls.length - before,
|
|
481
|
+
budgetDeniedAfterAttempts: exhausted.snapshot().usage.totals.providerAttempts,
|
|
482
|
+
realProviderCalls: 0
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
export async function runPipelineExample() {
|
|
486
|
+
const cases = [];
|
|
487
|
+
for (const hybrid of [false, true])
|
|
488
|
+
for (const dedicated of [false, true])
|
|
489
|
+
cases.push(await runPipelineCase({ hybrid, dedicated }));
|
|
490
|
+
return {
|
|
491
|
+
cases,
|
|
492
|
+
failures: await runPipelineFailureCases(),
|
|
493
|
+
realProviderCalls: 0,
|
|
494
|
+
qualityQualified: false
|
|
495
|
+
};
|
|
496
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Executable retrieval fixtures reuse the support manifests without adding manifest files.
|
|
2
|
+
export const retrievalCases = [
|
|
3
|
+
{
|
|
4
|
+
id: "ticket_lookup",
|
|
5
|
+
fragments: [{ key: "request", text: "Read ticket" }],
|
|
6
|
+
compatibleIds: ["support.ticket.get"],
|
|
7
|
+
forbiddenIds: ["support.refund.commit_case", "support.refund.prepare_case"]
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
id: "refund_review",
|
|
11
|
+
fragments: [{ key: "request", text: "Prepare refund" }],
|
|
12
|
+
compatibleIds: ["support.refund.prepare_case"],
|
|
13
|
+
forbiddenIds: ["support.refund.commit_case", "support.ticket.get"]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: "ticket_lookup_again",
|
|
17
|
+
fragments: [{ key: "request", text: "ticket" }],
|
|
18
|
+
compatibleIds: ["support.ticket.get"],
|
|
19
|
+
forbiddenIds: ["support.refund.commit_case"]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "no_matching_terms",
|
|
23
|
+
fragments: [{ key: "request", text: "quasar" }],
|
|
24
|
+
compatibleIds: [],
|
|
25
|
+
forbiddenIds: [
|
|
26
|
+
"support.refund.commit_case",
|
|
27
|
+
"support.refund.prepare_case",
|
|
28
|
+
"support.ticket.get"
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
];
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { writeFileSync } from "node:fs";
|
|
4
|
+
import { runCampaign } from "./evaluation/runner.mjs";
|
|
5
|
+
import { createMockHost } from "./evaluation/mock-host.mjs";
|
|
6
|
+
|
|
7
|
+
const directory = path.resolve(".aicf/discovery-evaluation", `mock-${randomUUID()}`);
|
|
8
|
+
const result = await runCampaign({ directory, mockFactory: createMockHost });
|
|
9
|
+
writeFileSync(
|
|
10
|
+
path.join(directory, "report.json"),
|
|
11
|
+
JSON.stringify(result, null, 2) + "\n"
|
|
12
|
+
);
|
|
13
|
+
if (
|
|
14
|
+
!result.report?.complete ||
|
|
15
|
+
result.report.ledger.pendingReservations ||
|
|
16
|
+
result.report.semanticQualityQualified ||
|
|
17
|
+
Object.values(result.report.modes).some((mode) => !mode.workflow.qualified)
|
|
18
|
+
)
|
|
19
|
+
throw new Error("Mock campaign integration did not satisfy its offline contract.");
|
|
20
|
+
console.log(
|
|
21
|
+
JSON.stringify(
|
|
22
|
+
{
|
|
23
|
+
status: result.status,
|
|
24
|
+
liveProviderCalls: 0,
|
|
25
|
+
campaignFingerprint: result.campaignFingerprint,
|
|
26
|
+
indexFingerprint: result.indexFingerprint,
|
|
27
|
+
ledger: result.report.ledger,
|
|
28
|
+
workflows: Object.fromEntries(
|
|
29
|
+
Object.entries(result.report.modes).map(([mode, value]) => [
|
|
30
|
+
mode,
|
|
31
|
+
value.workflow
|
|
32
|
+
])
|
|
33
|
+
),
|
|
34
|
+
evidenceDirectory: directory,
|
|
35
|
+
semanticQualityQualified: false
|
|
36
|
+
},
|
|
37
|
+
null,
|
|
38
|
+
2
|
|
39
|
+
)
|
|
40
|
+
);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
runCustomerRequestExample,
|
|
3
|
+
runSimpleHybridExample
|
|
4
|
+
} from "./customer-request-example.mjs";
|
|
5
|
+
|
|
6
|
+
console.log(
|
|
7
|
+
JSON.stringify(
|
|
8
|
+
{
|
|
9
|
+
customerFlow: await runCustomerRequestExample(),
|
|
10
|
+
hybrid: await runSimpleHybridExample()
|
|
11
|
+
},
|
|
12
|
+
null,
|
|
13
|
+
2
|
|
14
|
+
)
|
|
15
|
+
);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { campaignCli } from "./evaluation/runner.mjs";
|
|
2
|
+
|
|
3
|
+
try {
|
|
4
|
+
console.log(JSON.stringify(await campaignCli(process.argv.slice(2)), null, 2));
|
|
5
|
+
} catch {
|
|
6
|
+
console.error(
|
|
7
|
+
"Evaluation could not run. Check the explicit configuration, offline gate, campaign lock and fingerprinted evidence; no automatic retry was scheduled."
|
|
8
|
+
);
|
|
9
|
+
process.exitCode = 1;
|
|
10
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { regradeSavedCampaign } from "./evaluation/regrade.mjs";
|
|
6
|
+
|
|
7
|
+
export function writeOfflineRegrade(inputPath, outputPath) {
|
|
8
|
+
const input = fs.realpathSync(inputPath),
|
|
9
|
+
output = path.resolve(outputPath);
|
|
10
|
+
const parent = fs.realpathSync(path.dirname(output));
|
|
11
|
+
if (path.dirname(output) !== parent)
|
|
12
|
+
throw new Error("Output parent must be a real directory.");
|
|
13
|
+
execFileSync("git", ["check-ignore", "--quiet", "--", output], { stdio: "ignore" });
|
|
14
|
+
const { value, evidence } = regradeSavedCampaign(input);
|
|
15
|
+
const inside = (target, directory) => {
|
|
16
|
+
const relative = path.relative(directory, target);
|
|
17
|
+
return (
|
|
18
|
+
relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative))
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
if (
|
|
22
|
+
inside(output, evidence.directory) ||
|
|
23
|
+
inside(evidence.directory, output) ||
|
|
24
|
+
[evidence.reportPath, input].some((source) => inside(source, output))
|
|
25
|
+
)
|
|
26
|
+
throw new Error("Output must be separate from source evidence and input files.");
|
|
27
|
+
const serialized = JSON.stringify(value, null, 2) + "\n";
|
|
28
|
+
if (Buffer.byteLength(serialized) > 16 * 1024 * 1024)
|
|
29
|
+
throw new Error("Regrade report exceeds its bound.");
|
|
30
|
+
const name = "regrade.json",
|
|
31
|
+
target = path.join(output, name);
|
|
32
|
+
if (fs.existsSync(output)) {
|
|
33
|
+
if (
|
|
34
|
+
fs.lstatSync(output).isSymbolicLink() ||
|
|
35
|
+
fs.readdirSync(output).join() !== name ||
|
|
36
|
+
fs.readFileSync(target, "utf8") !== serialized
|
|
37
|
+
)
|
|
38
|
+
throw new Error("Conflicting output already exists.");
|
|
39
|
+
} else {
|
|
40
|
+
const temporary = fs.mkdtempSync(output + ".pending-");
|
|
41
|
+
try {
|
|
42
|
+
fs.writeFileSync(path.join(temporary, name), serialized, { flag: "wx" });
|
|
43
|
+
fs.renameSync(temporary, output);
|
|
44
|
+
} finally {
|
|
45
|
+
fs.rmSync(temporary, { recursive: true, force: true });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
reportFingerprint: value.reportFingerprint,
|
|
50
|
+
original: value.original,
|
|
51
|
+
revised: value.revised,
|
|
52
|
+
executionUsage: value.executionUsage
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (
|
|
57
|
+
process.argv[1] &&
|
|
58
|
+
path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)
|
|
59
|
+
) {
|
|
60
|
+
try {
|
|
61
|
+
const args = process.argv.slice(2);
|
|
62
|
+
if (args.length !== 4 || args[0] !== "--input" || args[2] !== "--output")
|
|
63
|
+
throw new Error("Explicit input and output required.");
|
|
64
|
+
console.log(JSON.stringify(writeOfflineRegrade(args[1], args[3]), null, 2));
|
|
65
|
+
} catch {
|
|
66
|
+
console.error(
|
|
67
|
+
"Offline regrade refused: check pinned evidence, complete workflow facts and a separate ignored output directory. No provider work was attempted."
|
|
68
|
+
);
|
|
69
|
+
process.exitCode = 1;
|
|
70
|
+
}
|
|
71
|
+
}
|