@flowdesk/opencode-plugin 0.1.1 → 0.1.3
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 +21 -0
- package/README.md +86 -0
- package/dist/command-handlers.d.ts +5 -1
- package/dist/command-handlers.d.ts.map +1 -1
- package/dist/command-handlers.js +74 -1
- package/dist/command-handlers.js.map +1 -1
- package/dist/lane-heartbeat-writer.d.ts +43 -0
- package/dist/lane-heartbeat-writer.d.ts.map +1 -0
- package/dist/lane-heartbeat-writer.js +133 -0
- package/dist/lane-heartbeat-writer.js.map +1 -0
- package/dist/local-adapter.d.ts +8 -1
- package/dist/local-adapter.d.ts.map +1 -1
- package/dist/local-adapter.js +62 -5
- package/dist/local-adapter.js.map +1 -1
- package/dist/managed-dispatch-adapter.d.ts +506 -1
- package/dist/managed-dispatch-adapter.d.ts.map +1 -1
- package/dist/managed-dispatch-adapter.js +2624 -38
- package/dist/managed-dispatch-adapter.js.map +1 -1
- package/dist/provider-usage-live-tool.d.ts +59 -0
- package/dist/provider-usage-live-tool.d.ts.map +1 -0
- package/dist/provider-usage-live-tool.js +259 -0
- package/dist/provider-usage-live-tool.js.map +1 -0
- package/dist/quick-fallback-run.d.ts +54 -0
- package/dist/quick-fallback-run.d.ts.map +1 -0
- package/dist/quick-fallback-run.js +230 -0
- package/dist/quick-fallback-run.js.map +1 -0
- package/dist/quick-reviewer-run.d.ts +61 -0
- package/dist/quick-reviewer-run.d.ts.map +1 -0
- package/dist/quick-reviewer-run.js +397 -0
- package/dist/quick-reviewer-run.js.map +1 -0
- package/dist/runtime-reviewer-execution-bridge.d.ts +43 -0
- package/dist/runtime-reviewer-execution-bridge.d.ts.map +1 -0
- package/dist/runtime-reviewer-execution-bridge.js +313 -0
- package/dist/runtime-reviewer-execution-bridge.js.map +1 -0
- package/dist/server.d.ts +104 -6
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1990 -104
- package/dist/server.js.map +1 -1
- package/dist/status-live-tool.d.ts +55 -0
- package/dist/status-live-tool.d.ts.map +1 -0
- package/dist/status-live-tool.js +215 -0
- package/dist/status-live-tool.js.map +1 -0
- package/package.json +21 -4
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { dirname, resolve, sep } from "node:path";
|
|
4
|
+
import { applyFlowDeskSessionEvidenceWriteIntentsV1, evaluateFlowDeskDispatchAttemptDurablePrecallV1, evaluateManagedDispatchBetaGuardBoundaryV1, planFlowDeskFallbackRegateV1, prepareFlowDeskDispatchIdempotencyReservationV1, prepareFlowDeskDispatchIdempotencyStateUpdateV1, prepareFlowDeskSessionEvidenceWriteIntentV1, promoteFlowDeskExternalWriteAuthorityV1, promoteFlowDeskFallbackReselectionRegateV1, promoteFlowDeskManagedDispatchBetaAuthorityV1, promoteFlowDeskReviewerTypedVerdictsV1, recordFlowDeskExactModelAvailabilityCacheProviderAcquisitionResultV1, reloadFlowDeskSessionEvidenceV1, validateFlowDeskExactModelAvailabilityCacheAcquisitionPlanV1, validateFlowDeskFallbackRegatePlanV1, validateFlowDeskPermissionAskDecisionV1, validateFlowDeskPromptNoReplyDecisionV1, validateFlowDeskRuntimeLaneLaunchPlanV1, validateFlowDeskSessionAbortDecisionV1, validateNoForbiddenRawPayloads, validateTopTierReviewVerdictV1, } from "@flowdesk/core";
|
|
2
5
|
export const flowdeskManagedDispatchBetaAdapterProfile = "managed_dispatch_beta_real_opencode_dispatch_adapter";
|
|
3
6
|
function disabledAuthority() {
|
|
4
7
|
return {
|
|
@@ -8,7 +11,1890 @@ function disabledAuthority() {
|
|
|
8
11
|
actualLaneLaunch: false,
|
|
9
12
|
fallbackAuthority: false,
|
|
10
13
|
toolAuthority: false,
|
|
11
|
-
hardCancelOrNoReplyAuthority: false
|
|
14
|
+
hardCancelOrNoReplyAuthority: false,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function disabledFallbackAuthority() {
|
|
18
|
+
return { ...disabledAuthority(), automaticFallbackAuthorized: false };
|
|
19
|
+
}
|
|
20
|
+
function managedFallbackRegateAuthority(prepared) {
|
|
21
|
+
return {
|
|
22
|
+
...disabledAuthority(),
|
|
23
|
+
freshRegatePlanPrepared: prepared,
|
|
24
|
+
automaticFallbackAuthorized: false,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function controlledExternalWriteAuthority(authorized) {
|
|
28
|
+
return {
|
|
29
|
+
...disabledAuthority(),
|
|
30
|
+
controlledExternalWriteAuthorized: authorized,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function exactModelProviderAcquisitionAuthority(recorded) {
|
|
34
|
+
return {
|
|
35
|
+
...disabledAuthority(),
|
|
36
|
+
exactModelProviderAcquisitionRecorded: recorded,
|
|
37
|
+
reviewerLaunchAuthorized: false,
|
|
38
|
+
dispatchAuthorityEnabled: false,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function runtimeLaneLaunchAuthority(authorized) {
|
|
42
|
+
return {
|
|
43
|
+
...disabledAuthority(),
|
|
44
|
+
providerCall: authorized,
|
|
45
|
+
runtimeExecution: authorized,
|
|
46
|
+
actualLaneLaunch: authorized,
|
|
47
|
+
runtimeLaneLaunchAuthorized: authorized,
|
|
48
|
+
defaultRelease1ServerBehaviorUnchanged: true,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function blockedRuntimeLaneLaunch(redactedBlockReason, plan) {
|
|
52
|
+
return {
|
|
53
|
+
adapterProfile: "injected_sdk_runtime_lane_launch_adapter",
|
|
54
|
+
status: "blocked_before_lane_launch",
|
|
55
|
+
createAttempted: false,
|
|
56
|
+
promptAttempted: false,
|
|
57
|
+
...(plan?.workflow_id === undefined ? {} : { workflowId: plan.workflow_id }),
|
|
58
|
+
...(plan?.attempt_id === undefined ? {} : { attemptId: plan.attempt_id }),
|
|
59
|
+
...(plan?.lane_id === undefined ? {} : { laneId: plan.lane_id }),
|
|
60
|
+
...(plan?.parent_session_ref === undefined
|
|
61
|
+
? {}
|
|
62
|
+
: { parentSessionRef: plan.parent_session_ref }),
|
|
63
|
+
redactedBlockReason,
|
|
64
|
+
safeNextActions: ["/flowdesk-status", "/flowdesk-export-debug"],
|
|
65
|
+
authority: runtimeLaneLaunchAuthority(false),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function runtimeLaneLaunchLifecycleAuthority(persisted) {
|
|
69
|
+
return {
|
|
70
|
+
...disabledAuthority(),
|
|
71
|
+
runtimeLaneLifecyclePersisted: persisted,
|
|
72
|
+
defaultRelease1ServerBehaviorUnchanged: true,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function blockRuntimeLaneLaunchLifecycle(input) {
|
|
76
|
+
return {
|
|
77
|
+
adapterProfile: "runtime_lane_launch_lifecycle_materializer",
|
|
78
|
+
status: "blocked_before_lane_lifecycle",
|
|
79
|
+
writeAttempted: false,
|
|
80
|
+
evidenceReloaded: input.evidenceReloaded ?? false,
|
|
81
|
+
...(input.plan?.workflow_id === undefined
|
|
82
|
+
? {}
|
|
83
|
+
: { workflowId: input.plan.workflow_id }),
|
|
84
|
+
...(input.plan?.attempt_id === undefined
|
|
85
|
+
? {}
|
|
86
|
+
: { attemptId: input.plan.attempt_id }),
|
|
87
|
+
...(input.plan?.lane_id === undefined ? {} : { laneId: input.plan.lane_id }),
|
|
88
|
+
...(input.evidenceId === undefined ? {} : { evidenceId: input.evidenceId }),
|
|
89
|
+
redactedBlockReason: input.reason,
|
|
90
|
+
safeNextActions: ["/flowdesk-status", "/flowdesk-export-debug"],
|
|
91
|
+
authority: runtimeLaneLaunchLifecycleAuthority(false),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function blockedExactModelProviderAcquisition(reason, input) {
|
|
95
|
+
return {
|
|
96
|
+
adapterProfile: "exact_model_provider_acquisition_live_test_adapter",
|
|
97
|
+
status: "blocked_before_provider_acquisition",
|
|
98
|
+
providerCallAttempted: false,
|
|
99
|
+
writeAttempted: false,
|
|
100
|
+
evidenceReloaded: false,
|
|
101
|
+
workflowId: input?.workflowId,
|
|
102
|
+
evidenceId: input?.evidenceId,
|
|
103
|
+
resultId: input?.resultId,
|
|
104
|
+
providerQualifiedModelId: input?.providerQualifiedModelId,
|
|
105
|
+
redactedBlockReason: reason,
|
|
106
|
+
safeNextActions: ["/flowdesk-status"],
|
|
107
|
+
authority: exactModelProviderAcquisitionAuthority(false),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function opencodeMetadataCallParameters(input) {
|
|
111
|
+
const parameters = {
|
|
112
|
+
...(typeof input.directory === "string" && input.directory.trim().length > 0
|
|
113
|
+
? { directory: input.directory }
|
|
114
|
+
: {}),
|
|
115
|
+
...(typeof input.workspace === "string" && input.workspace.trim().length > 0
|
|
116
|
+
? { workspace: input.workspace }
|
|
117
|
+
: {}),
|
|
118
|
+
};
|
|
119
|
+
return Object.keys(parameters).length > 0 ? parameters : undefined;
|
|
120
|
+
}
|
|
121
|
+
function opencodeProvidersFromConfig(value) {
|
|
122
|
+
const record = asRecord(responseData(value));
|
|
123
|
+
return Array.isArray(record?.providers) ? record.providers : undefined;
|
|
124
|
+
}
|
|
125
|
+
function opencodeProvidersFromList(value) {
|
|
126
|
+
const record = asRecord(responseData(value));
|
|
127
|
+
return Array.isArray(record?.all) ? record.all : undefined;
|
|
128
|
+
}
|
|
129
|
+
function opencodeConnectedProviderIds(value) {
|
|
130
|
+
const record = asRecord(responseData(value));
|
|
131
|
+
if (!Array.isArray(record?.connected))
|
|
132
|
+
return undefined;
|
|
133
|
+
return record.connected.every((item) => typeof item === "string")
|
|
134
|
+
? record.connected
|
|
135
|
+
: undefined;
|
|
136
|
+
}
|
|
137
|
+
function opencodeProviderAuthMethodTypes(value, providerID) {
|
|
138
|
+
const methods = asRecord(responseData(value))?.[providerID];
|
|
139
|
+
if (!Array.isArray(methods) || methods.length === 0)
|
|
140
|
+
return undefined;
|
|
141
|
+
const types = methods.map((method) => asRecord(method)?.type);
|
|
142
|
+
return types.every((type) => type === "api" || type === "oauth")
|
|
143
|
+
? types
|
|
144
|
+
: undefined;
|
|
145
|
+
}
|
|
146
|
+
function opencodeProviderById(providers, providerID) {
|
|
147
|
+
return providers
|
|
148
|
+
.map(asRecord)
|
|
149
|
+
.find((provider) => provider?.id === providerID);
|
|
150
|
+
}
|
|
151
|
+
function opencodeProviderHasModel(provider, modelID) {
|
|
152
|
+
const models = asRecord(provider?.models);
|
|
153
|
+
return models !== undefined && asRecord(models[modelID]) !== undefined;
|
|
154
|
+
}
|
|
155
|
+
function unavailableMetadataResult(labels) {
|
|
156
|
+
return {
|
|
157
|
+
outcome: "blocked",
|
|
158
|
+
blocked_labels: labels,
|
|
159
|
+
provider_call_confirmed: false,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const flowdeskExactModelProviderAcquisitionSentinelPromptV1 = "FlowDesk exact-model provider acquisition sentinel. Return a short acknowledgement only.";
|
|
163
|
+
function providerAcquisitionPromptOptions(input) {
|
|
164
|
+
const sessionId = input.sessionId?.trim() || input.request.live_test_run_ref;
|
|
165
|
+
return {
|
|
166
|
+
path: { id: sessionId },
|
|
167
|
+
...(input.directory === undefined
|
|
168
|
+
? {}
|
|
169
|
+
: { query: { directory: input.directory } }),
|
|
170
|
+
body: {
|
|
171
|
+
model: input.model,
|
|
172
|
+
agent: input.agent?.trim() || "general",
|
|
173
|
+
parts: [{ type: "text", text: flowdeskExactModelProviderAcquisitionSentinelPromptV1 }],
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function providerAcquisitionDuplicateLabels(input) {
|
|
178
|
+
const reloaded = reloadFlowDeskSessionEvidenceV1({
|
|
179
|
+
workflowId: input.request.workflowId,
|
|
180
|
+
rootDir: input.rootDir,
|
|
181
|
+
});
|
|
182
|
+
if (!reloaded.ok || reloaded.blocked.length > 0) {
|
|
183
|
+
return {
|
|
184
|
+
ok: false,
|
|
185
|
+
reason: "Exact-model provider acquisition live-test requires reloadable durable evidence before any provider call.",
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
for (const entry of reloaded.entries) {
|
|
189
|
+
if (entry.evidenceClass !==
|
|
190
|
+
"exact_model_availability_cache_provider_acquisition_result") {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const record = entry.record;
|
|
194
|
+
if (record.live_test_run_ref === input.request.liveTestRunRef ||
|
|
195
|
+
record.idempotency_ref === input.request.idempotencyRef) {
|
|
196
|
+
return {
|
|
197
|
+
ok: false,
|
|
198
|
+
reason: "Exact-model provider acquisition live-test duplicate idempotency evidence blocks before any provider call.",
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return { ok: true };
|
|
203
|
+
}
|
|
204
|
+
export function createFlowDeskOpenCodeMetadataProviderAcquisitionClientV1(options) {
|
|
205
|
+
const client = asRecord(options.client);
|
|
206
|
+
if (client === undefined)
|
|
207
|
+
return undefined;
|
|
208
|
+
return {
|
|
209
|
+
async checkExactModelAvailability(request) {
|
|
210
|
+
const model = parseProviderQualifiedModelId(request.provider_qualified_model_id);
|
|
211
|
+
const providerID = opencodeRuntimeProviderIDForFlowDeskProviderFamily(request.provider_family);
|
|
212
|
+
if (model === undefined ||
|
|
213
|
+
providerID === undefined ||
|
|
214
|
+
model.providerID !== request.provider_family) {
|
|
215
|
+
return unavailableMetadataResult([
|
|
216
|
+
"opencode_provider_mapping_missing",
|
|
217
|
+
]);
|
|
218
|
+
}
|
|
219
|
+
if (typeof client.config?.providers !== "function" ||
|
|
220
|
+
typeof client.provider?.list !== "function" ||
|
|
221
|
+
typeof client.provider?.auth !== "function") {
|
|
222
|
+
return unavailableMetadataResult([
|
|
223
|
+
"opencode_metadata_surface_missing",
|
|
224
|
+
]);
|
|
225
|
+
}
|
|
226
|
+
const callParameters = opencodeMetadataCallParameters(options);
|
|
227
|
+
let configProvidersResponse;
|
|
228
|
+
let providerListResponse;
|
|
229
|
+
let providerAuthResponse;
|
|
230
|
+
try {
|
|
231
|
+
[configProvidersResponse, providerListResponse, providerAuthResponse] = await Promise.all([
|
|
232
|
+
client.config.providers.call(client.config, callParameters),
|
|
233
|
+
client.provider.list.call(client.provider, callParameters),
|
|
234
|
+
client.provider.auth.call(client.provider, callParameters),
|
|
235
|
+
]);
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
return unavailableMetadataResult([
|
|
239
|
+
"opencode_metadata_query_failed",
|
|
240
|
+
]);
|
|
241
|
+
}
|
|
242
|
+
const configProviders = opencodeProvidersFromConfig(configProvidersResponse);
|
|
243
|
+
const listedProviders = opencodeProvidersFromList(providerListResponse);
|
|
244
|
+
const connectedProviderIds = opencodeConnectedProviderIds(providerListResponse);
|
|
245
|
+
const authMethodTypes = opencodeProviderAuthMethodTypes(providerAuthResponse, providerID);
|
|
246
|
+
if (configProviders === undefined)
|
|
247
|
+
return unavailableMetadataResult([
|
|
248
|
+
"opencode_config_providers_missing",
|
|
249
|
+
]);
|
|
250
|
+
if (listedProviders === undefined)
|
|
251
|
+
return unavailableMetadataResult([
|
|
252
|
+
"opencode_provider_list_missing",
|
|
253
|
+
]);
|
|
254
|
+
if (connectedProviderIds === undefined || authMethodTypes === undefined)
|
|
255
|
+
return unavailableMetadataResult([
|
|
256
|
+
"opencode_provider_auth_missing",
|
|
257
|
+
]);
|
|
258
|
+
const configProvider = opencodeProviderById(configProviders, providerID);
|
|
259
|
+
const listedProvider = opencodeProviderById(listedProviders, providerID);
|
|
260
|
+
if (configProvider === undefined || listedProvider === undefined)
|
|
261
|
+
return unavailableMetadataResult([
|
|
262
|
+
"opencode_provider_metadata_missing",
|
|
263
|
+
]);
|
|
264
|
+
if (!opencodeProviderHasModel(configProvider, model.modelID) ||
|
|
265
|
+
!opencodeProviderHasModel(listedProvider, model.modelID)) {
|
|
266
|
+
return unavailableMetadataResult([
|
|
267
|
+
"opencode_provider_model_missing",
|
|
268
|
+
]);
|
|
269
|
+
}
|
|
270
|
+
if (!connectedProviderIds.includes(providerID))
|
|
271
|
+
return unavailableMetadataResult([
|
|
272
|
+
"opencode_provider_auth_missing",
|
|
273
|
+
]);
|
|
274
|
+
const sanitizedFacts = {
|
|
275
|
+
providerID,
|
|
276
|
+
modelID: model.modelID,
|
|
277
|
+
authMethodTypes,
|
|
278
|
+
};
|
|
279
|
+
const sanitized = validateNoForbiddenRawPayloads(sanitizedFacts, "opencode_metadata_provider_acquisition_facts");
|
|
280
|
+
if (!sanitized.ok)
|
|
281
|
+
return unavailableMetadataResult([
|
|
282
|
+
"opencode_provider_metadata_not_sanitized",
|
|
283
|
+
]);
|
|
284
|
+
return {
|
|
285
|
+
outcome: "available",
|
|
286
|
+
sanitized_provider_result_ref: refFrom("provider-result", `${providerID}-${model.modelID}-metadata`),
|
|
287
|
+
availability_ref: refFrom("availability", `${providerID}-${model.modelID}-metadata`),
|
|
288
|
+
highest_tier_eligible: true,
|
|
289
|
+
provider_call_confirmed: false,
|
|
290
|
+
};
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
export function createFlowDeskOpenCodePromptBackedProviderAcquisitionClientV1(options) {
|
|
295
|
+
const client = asRecord(options.client);
|
|
296
|
+
const metadataClient = createFlowDeskOpenCodeMetadataProviderAcquisitionClientV1(options);
|
|
297
|
+
if (client === undefined || metadataClient === undefined)
|
|
298
|
+
return undefined;
|
|
299
|
+
return {
|
|
300
|
+
async checkExactModelAvailability(request) {
|
|
301
|
+
const metadataResult = await metadataClient.checkExactModelAvailability(request);
|
|
302
|
+
if (metadataResult.outcome !== "available") {
|
|
303
|
+
return { ...metadataResult, provider_call_confirmed: false };
|
|
304
|
+
}
|
|
305
|
+
if (options.allowProviderCall !== true) {
|
|
306
|
+
return unavailableMetadataResult([
|
|
307
|
+
"opencode_sdk_provider_call_not_allowed",
|
|
308
|
+
]);
|
|
309
|
+
}
|
|
310
|
+
if (!options.allowedProviderQualifiedModelIds.includes(request.provider_qualified_model_id)) {
|
|
311
|
+
return unavailableMetadataResult([
|
|
312
|
+
"opencode_sdk_provider_model_not_allowed",
|
|
313
|
+
]);
|
|
314
|
+
}
|
|
315
|
+
const parsedModel = parseProviderQualifiedModelId(request.provider_qualified_model_id);
|
|
316
|
+
const runtimeModel = parsedModel === undefined || parsedModel.providerID !== request.provider_family
|
|
317
|
+
? undefined
|
|
318
|
+
: opencodeRuntimeModelForFlowDeskModel(parsedModel);
|
|
319
|
+
if (runtimeModel === undefined) {
|
|
320
|
+
return unavailableMetadataResult([
|
|
321
|
+
"opencode_provider_mapping_missing",
|
|
322
|
+
]);
|
|
323
|
+
}
|
|
324
|
+
const prompt = typeof client.session?.promptAsync === "function"
|
|
325
|
+
? client.session.promptAsync
|
|
326
|
+
: client.session?.prompt;
|
|
327
|
+
if (prompt === undefined) {
|
|
328
|
+
return unavailableMetadataResult([
|
|
329
|
+
"opencode_sdk_surface_missing",
|
|
330
|
+
]);
|
|
331
|
+
}
|
|
332
|
+
const promptOptions = providerAcquisitionPromptOptions({
|
|
333
|
+
request,
|
|
334
|
+
model: runtimeModel,
|
|
335
|
+
...(typeof options.sessionId === "string" ? { sessionId: options.sessionId } : {}),
|
|
336
|
+
...(typeof options.agent === "string" ? { agent: options.agent } : {}),
|
|
337
|
+
...(typeof options.directory === "string" ? { directory: options.directory } : {}),
|
|
338
|
+
});
|
|
339
|
+
try {
|
|
340
|
+
await prompt.call(client.session, promptOptions);
|
|
341
|
+
}
|
|
342
|
+
catch {
|
|
343
|
+
return {
|
|
344
|
+
outcome: "blocked",
|
|
345
|
+
blocked_labels: ["opencode_sdk_provider_call_failed"],
|
|
346
|
+
provider_call_confirmed: true,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
outcome: "available",
|
|
351
|
+
sanitized_provider_result_ref: refFrom("provider-result", `${runtimeModel.providerID}-${runtimeModel.modelID}-sdk-sentinel`),
|
|
352
|
+
availability_ref: refFrom("availability", `${runtimeModel.providerID}-${runtimeModel.modelID}-sdk-sentinel`),
|
|
353
|
+
highest_tier_eligible: metadataResult.highest_tier_eligible === true,
|
|
354
|
+
provider_call_confirmed: true,
|
|
355
|
+
};
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
export async function runFlowDeskExactModelProviderAcquisitionLiveTestV1(input) {
|
|
360
|
+
if (typeof input.rootDir !== "string" || input.rootDir.trim().length === 0)
|
|
361
|
+
return blockedExactModelProviderAcquisition("Exact-model provider acquisition live-test requires a durable state root before any provider call.", input.request);
|
|
362
|
+
const planValidation = validateFlowDeskExactModelAvailabilityCacheAcquisitionPlanV1(input.request.acquisitionPlan);
|
|
363
|
+
if (!planValidation.ok || input.request.acquisitionPlan.state !== "acquisition_planned") {
|
|
364
|
+
return blockedExactModelProviderAcquisition("Exact-model provider acquisition live-test requires valid acquisition_planned evidence before any provider call.", input.request);
|
|
365
|
+
}
|
|
366
|
+
const duplicateCheck = providerAcquisitionDuplicateLabels(input);
|
|
367
|
+
if (!duplicateCheck.ok) {
|
|
368
|
+
return blockedExactModelProviderAcquisition(duplicateCheck.reason, input.request);
|
|
369
|
+
}
|
|
370
|
+
let clientResult;
|
|
371
|
+
let providerCallAttempted = false;
|
|
372
|
+
try {
|
|
373
|
+
clientResult = await input.client.checkExactModelAvailability({
|
|
374
|
+
provider_family: input.request.providerFamily,
|
|
375
|
+
provider_identity_ref: input.request.providerIdentityRef,
|
|
376
|
+
provider_qualified_model_id: input.request.providerQualifiedModelId,
|
|
377
|
+
model_family: input.request.modelFamily,
|
|
378
|
+
active_profile_ref: input.request.activeProfileRef,
|
|
379
|
+
auth_account_boundary_ref: input.request.authAccountBoundaryRef,
|
|
380
|
+
live_test_run_ref: input.request.liveTestRunRef,
|
|
381
|
+
redaction_proof_ref: input.request.redactionProofRef,
|
|
382
|
+
});
|
|
383
|
+
providerCallAttempted = clientResult.provider_call_confirmed !== false;
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
providerCallAttempted = true;
|
|
387
|
+
clientResult = {
|
|
388
|
+
outcome: "blocked",
|
|
389
|
+
blocked_labels: ["provider_acquisition_client_error"],
|
|
390
|
+
provider_call_confirmed: true,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
const rawCheck = validateNoForbiddenRawPayloads(clientResult, "provider_acquisition_client_result");
|
|
394
|
+
const blockedLabels = [
|
|
395
|
+
...(clientResult.blocked_labels ?? []),
|
|
396
|
+
...(rawCheck.ok ? [] : ["provider_acquisition_result_not_sanitized"]),
|
|
397
|
+
];
|
|
398
|
+
const result = recordFlowDeskExactModelAvailabilityCacheProviderAcquisitionResultV1({
|
|
399
|
+
acquisitionPlan: input.request.acquisitionPlan,
|
|
400
|
+
resultId: input.request.resultId,
|
|
401
|
+
localDate: input.request.localDate,
|
|
402
|
+
activeProfileRef: input.request.activeProfileRef,
|
|
403
|
+
opencodeVersionRef: input.request.opencodeVersionRef,
|
|
404
|
+
flowdeskPackageVersionRef: input.request.flowdeskPackageVersionRef,
|
|
405
|
+
registryHash: input.request.registryHash,
|
|
406
|
+
policyPackHash: input.request.policyPackHash,
|
|
407
|
+
authAccountBoundaryRef: input.request.authAccountBoundaryRef,
|
|
408
|
+
providerFamily: input.request.providerFamily,
|
|
409
|
+
providerIdentityRef: input.request.providerIdentityRef,
|
|
410
|
+
providerQualifiedModelId: input.request.providerQualifiedModelId,
|
|
411
|
+
modelFamily: input.request.modelFamily,
|
|
412
|
+
availabilityRef: clientResult.availability_ref ?? input.request.availabilityRef,
|
|
413
|
+
preCallAuditRef: input.request.preCallAuditRef,
|
|
414
|
+
idempotencyRef: input.request.idempotencyRef,
|
|
415
|
+
liveTestRunRef: input.request.liveTestRunRef,
|
|
416
|
+
redactionProofRef: input.request.redactionProofRef,
|
|
417
|
+
sanitizedProviderResultRef: clientResult.sanitized_provider_result_ref,
|
|
418
|
+
observedAt: input.request.observedAt,
|
|
419
|
+
outcome: rawCheck.ok ? clientResult.outcome : "blocked",
|
|
420
|
+
highestTierEligible: clientResult.highest_tier_eligible,
|
|
421
|
+
blockedLabels,
|
|
422
|
+
providerCall: providerCallAttempted,
|
|
423
|
+
});
|
|
424
|
+
const prepared = prepareFlowDeskSessionEvidenceWriteIntentV1({
|
|
425
|
+
workflowId: input.request.workflowId,
|
|
426
|
+
evidenceId: input.request.evidenceId,
|
|
427
|
+
record: result,
|
|
428
|
+
});
|
|
429
|
+
if (!prepared.ok || prepared.writeIntent === undefined) {
|
|
430
|
+
return {
|
|
431
|
+
adapterProfile: "exact_model_provider_acquisition_live_test_adapter",
|
|
432
|
+
status: "provider_acquisition_blocked",
|
|
433
|
+
providerCallAttempted,
|
|
434
|
+
writeAttempted: false,
|
|
435
|
+
evidenceReloaded: false,
|
|
436
|
+
workflowId: input.request.workflowId,
|
|
437
|
+
evidenceId: input.request.evidenceId,
|
|
438
|
+
resultId: input.request.resultId,
|
|
439
|
+
providerQualifiedModelId: input.request.providerQualifiedModelId,
|
|
440
|
+
redactedBlockReason: "Provider acquisition result could not be prepared as durable session evidence.",
|
|
441
|
+
result,
|
|
442
|
+
safeNextActions: ["/flowdesk-status"],
|
|
443
|
+
authority: exactModelProviderAcquisitionAuthority(false),
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
const applied = applyFlowDeskSessionEvidenceWriteIntentsV1(input.rootDir, [prepared.writeIntent]);
|
|
447
|
+
const reloaded = applied.ok
|
|
448
|
+
? reloadFlowDeskSessionEvidenceV1({ workflowId: input.request.workflowId, rootDir: input.rootDir })
|
|
449
|
+
: undefined;
|
|
450
|
+
const evidenceReloaded = reloaded?.entries.some((entry) => entry.evidenceClass === "exact_model_availability_cache_provider_acquisition_result" && entry.evidenceId === input.request.evidenceId) === true;
|
|
451
|
+
const recorded = applied.ok && evidenceReloaded && result.state === "availability_acquired" && result.ok;
|
|
452
|
+
return {
|
|
453
|
+
adapterProfile: "exact_model_provider_acquisition_live_test_adapter",
|
|
454
|
+
status: recorded ? "provider_acquisition_recorded" : "provider_acquisition_blocked",
|
|
455
|
+
providerCallAttempted,
|
|
456
|
+
writeAttempted: true,
|
|
457
|
+
evidenceReloaded,
|
|
458
|
+
workflowId: input.request.workflowId,
|
|
459
|
+
evidenceId: input.request.evidenceId,
|
|
460
|
+
resultId: input.request.resultId,
|
|
461
|
+
providerQualifiedModelId: input.request.providerQualifiedModelId,
|
|
462
|
+
...(recorded ? {} : { redactedBlockReason: "Provider acquisition live-test did not produce reloadable availability evidence." }),
|
|
463
|
+
result,
|
|
464
|
+
safeNextActions: ["/flowdesk-status"],
|
|
465
|
+
authority: exactModelProviderAcquisitionAuthority(recorded),
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
function reviewerTypedVerdictAuthority(accepted) {
|
|
469
|
+
return { ...disabledAuthority(), typedReviewerVerdictsAccepted: accepted };
|
|
470
|
+
}
|
|
471
|
+
function durableReviewerVerdictAuthority(accepted) {
|
|
472
|
+
return {
|
|
473
|
+
...disabledAuthority(),
|
|
474
|
+
typedReviewerVerdictsAccepted: accepted,
|
|
475
|
+
durableReviewerVerdictEvidenceLinked: accepted,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
function observedReviewerVerdictEvidenceAuthority(persisted) {
|
|
479
|
+
return {
|
|
480
|
+
...disabledAuthority(),
|
|
481
|
+
typedReviewerVerdictPersisted: persisted,
|
|
482
|
+
typedReviewerVerdictsAccepted: false,
|
|
483
|
+
durableReviewerVerdictEvidenceLinked: false,
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
function controlledConformanceDocWriteAuthority(recorded) {
|
|
487
|
+
return {
|
|
488
|
+
...disabledAuthority(),
|
|
489
|
+
controlledExternalWriteAuthorized: recorded,
|
|
490
|
+
localConformanceDocWriteRecorded: recorded,
|
|
491
|
+
remoteWriteAttempted: false,
|
|
492
|
+
githubWriteAttempted: false,
|
|
493
|
+
connectorWriteAttempted: false,
|
|
494
|
+
storageWriteAttempted: false,
|
|
495
|
+
databaseWriteAttempted: false,
|
|
496
|
+
urlWriteAttempted: false,
|
|
497
|
+
rawPathWriteAttempted: false,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
function controlledRedactedAuditExportWriteAuthority(recorded) {
|
|
501
|
+
return {
|
|
502
|
+
...disabledAuthority(),
|
|
503
|
+
controlledExternalWriteAuthorized: recorded,
|
|
504
|
+
localRedactedAuditExportWriteRecorded: recorded,
|
|
505
|
+
remoteWriteAttempted: false,
|
|
506
|
+
githubWriteAttempted: false,
|
|
507
|
+
connectorWriteAttempted: false,
|
|
508
|
+
storageWriteAttempted: false,
|
|
509
|
+
databaseWriteAttempted: false,
|
|
510
|
+
urlWriteAttempted: false,
|
|
511
|
+
rawPathWriteAttempted: false,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
function permissionAskControlAuthority(authorized) {
|
|
515
|
+
return {
|
|
516
|
+
...disabledAuthority(),
|
|
517
|
+
permissionAskStatusControlAuthorized: authorized,
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
function sessionAbortControlAuthority(authorized) {
|
|
521
|
+
return { ...disabledAuthority(), sessionAbortAuthorized: authorized };
|
|
522
|
+
}
|
|
523
|
+
function promptNoReplyControlAuthority(authorized) {
|
|
524
|
+
return {
|
|
525
|
+
...disabledAuthority(),
|
|
526
|
+
providerCall: authorized,
|
|
527
|
+
runtimeExecution: authorized,
|
|
528
|
+
promptNoReplyAuthorized: authorized,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
function blockControlledConformanceDocWrite(input) {
|
|
532
|
+
return {
|
|
533
|
+
adapterProfile: "controlled_conformance_doc_local_writer",
|
|
534
|
+
status: "blocked_before_local_write",
|
|
535
|
+
writeAttempted: false,
|
|
536
|
+
workflowId: input.request?.workflow_id,
|
|
537
|
+
attemptId: input.request?.attempt_id,
|
|
538
|
+
targetKind: input.request?.target_kind === "release_conformance_doc"
|
|
539
|
+
? "release_conformance_doc"
|
|
540
|
+
: undefined,
|
|
541
|
+
targetRef: input.request?.target_ref,
|
|
542
|
+
ledgerEvidenceReloaded: false,
|
|
543
|
+
redactedBlockReason: input.reason,
|
|
544
|
+
safeNextActions: ["/flowdesk-status"],
|
|
545
|
+
authority: controlledConformanceDocWriteAuthority(false),
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
function blockControlledRedactedAuditExportWrite(input) {
|
|
549
|
+
return {
|
|
550
|
+
adapterProfile: "controlled_redacted_audit_export_local_writer",
|
|
551
|
+
status: "blocked_before_local_write",
|
|
552
|
+
writeAttempted: false,
|
|
553
|
+
workflowId: input.request?.workflow_id,
|
|
554
|
+
attemptId: input.request?.attempt_id,
|
|
555
|
+
targetKind: input.request?.target_kind === "redacted_audit_export"
|
|
556
|
+
? "redacted_audit_export"
|
|
557
|
+
: undefined,
|
|
558
|
+
targetRef: input.request?.target_ref,
|
|
559
|
+
ledgerEvidenceReloaded: false,
|
|
560
|
+
redactedBlockReason: input.reason,
|
|
561
|
+
safeNextActions: ["/flowdesk-status"],
|
|
562
|
+
authority: controlledRedactedAuditExportWriteAuthority(false),
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
function blockObservedReviewerVerdictEvidence(input) {
|
|
566
|
+
return {
|
|
567
|
+
adapterProfile: "observed_reviewer_verdict_evidence_materializer",
|
|
568
|
+
status: "blocked_before_verdict_evidence",
|
|
569
|
+
writeAttempted: false,
|
|
570
|
+
workflowId: input.observation.workflowId,
|
|
571
|
+
sessionRef: input.observation.sessionRef,
|
|
572
|
+
lanePlanRef: input.observation.lanePlanRef,
|
|
573
|
+
bindingRef: input.observation.bindingRef,
|
|
574
|
+
perspective: input.observation.perspective,
|
|
575
|
+
verdictId: input.observation.verdictId,
|
|
576
|
+
evidenceId: input.evidenceId,
|
|
577
|
+
evidenceReloaded: input.evidenceReloaded ?? false,
|
|
578
|
+
redactedBlockReason: input.reason,
|
|
579
|
+
safeNextActions: ["/flowdesk-status"],
|
|
580
|
+
authority: observedReviewerVerdictEvidenceAuthority(false),
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
export function applyFlowDeskPermissionAskControlV1(input) {
|
|
584
|
+
const validation = validateFlowDeskPermissionAskDecisionV1(input.decision);
|
|
585
|
+
if (!validation.ok) {
|
|
586
|
+
return {
|
|
587
|
+
adapterProfile: "permission_ask_control_adapter",
|
|
588
|
+
status: "blocked_before_permission_status",
|
|
589
|
+
permissionStatusApplied: false,
|
|
590
|
+
workflowId: input.decision.workflow_id,
|
|
591
|
+
attemptId: input.decision.attempt_id,
|
|
592
|
+
redactedBlockReason: validation.errors.join(",") || "invalid permission decision",
|
|
593
|
+
authority: permissionAskControlAuthority(false),
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
input.output.status = input.decision.status;
|
|
597
|
+
return {
|
|
598
|
+
adapterProfile: "permission_ask_control_adapter",
|
|
599
|
+
status: "permission_status_applied",
|
|
600
|
+
permissionStatusApplied: true,
|
|
601
|
+
permissionStatus: input.decision.status,
|
|
602
|
+
workflowId: input.decision.workflow_id,
|
|
603
|
+
attemptId: input.decision.attempt_id,
|
|
604
|
+
authority: permissionAskControlAuthority(true),
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
export async function abortFlowDeskSessionWithDecisionV1(input) {
|
|
608
|
+
const validation = validateFlowDeskSessionAbortDecisionV1(input.decision);
|
|
609
|
+
if (!validation.ok) {
|
|
610
|
+
return {
|
|
611
|
+
adapterProfile: "session_abort_control_adapter",
|
|
612
|
+
status: "blocked_before_session_abort",
|
|
613
|
+
abortAttempted: false,
|
|
614
|
+
workflowId: input.decision.workflow_id,
|
|
615
|
+
attemptId: input.decision.attempt_id,
|
|
616
|
+
sessionRef: input.decision.session_ref,
|
|
617
|
+
redactedBlockReason: validation.errors.join(",") || "invalid abort decision",
|
|
618
|
+
authority: sessionAbortControlAuthority(false),
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
const abort = input.client.session.abort;
|
|
622
|
+
if (abort === undefined) {
|
|
623
|
+
return {
|
|
624
|
+
adapterProfile: "session_abort_control_adapter",
|
|
625
|
+
status: "blocked_before_session_abort",
|
|
626
|
+
abortAttempted: false,
|
|
627
|
+
workflowId: input.decision.workflow_id,
|
|
628
|
+
attemptId: input.decision.attempt_id,
|
|
629
|
+
sessionRef: input.decision.session_ref,
|
|
630
|
+
redactedBlockReason: "Injected OpenCode client is missing session.abort.",
|
|
631
|
+
authority: sessionAbortControlAuthority(false),
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
const response = await abort.call(input.client.session, {
|
|
635
|
+
path: { id: input.decision.session_ref },
|
|
636
|
+
...(input.directory === undefined
|
|
637
|
+
? {}
|
|
638
|
+
: { query: { directory: input.directory } }),
|
|
639
|
+
});
|
|
640
|
+
return {
|
|
641
|
+
adapterProfile: "session_abort_control_adapter",
|
|
642
|
+
status: "session_abort_sent",
|
|
643
|
+
abortAttempted: true,
|
|
644
|
+
workflowId: input.decision.workflow_id,
|
|
645
|
+
attemptId: input.decision.attempt_id,
|
|
646
|
+
sessionRef: input.decision.session_ref,
|
|
647
|
+
response,
|
|
648
|
+
authority: sessionAbortControlAuthority(true),
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
export async function dispatchFlowDeskPromptNoReplyWithDecisionV1(input) {
|
|
652
|
+
const validation = validateFlowDeskPromptNoReplyDecisionV1(input.decision);
|
|
653
|
+
if (!validation.ok) {
|
|
654
|
+
return {
|
|
655
|
+
adapterProfile: "prompt_no_reply_control_adapter",
|
|
656
|
+
status: "blocked_before_no_reply_prompt",
|
|
657
|
+
promptAttempted: false,
|
|
658
|
+
workflowId: input.decision.workflow_id,
|
|
659
|
+
attemptId: input.decision.attempt_id,
|
|
660
|
+
sessionRef: input.decision.session_ref,
|
|
661
|
+
redactedBlockReason: validation.errors.join(",") || "invalid no-reply decision",
|
|
662
|
+
authority: promptNoReplyControlAuthority(false),
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
if (input.request.sessionId !== input.decision.session_ref) {
|
|
666
|
+
return {
|
|
667
|
+
adapterProfile: "prompt_no_reply_control_adapter",
|
|
668
|
+
status: "blocked_before_no_reply_prompt",
|
|
669
|
+
promptAttempted: false,
|
|
670
|
+
workflowId: input.decision.workflow_id,
|
|
671
|
+
attemptId: input.decision.attempt_id,
|
|
672
|
+
sessionRef: input.decision.session_ref,
|
|
673
|
+
redactedBlockReason: "No-reply decision session_ref must match request sessionId.",
|
|
674
|
+
authority: promptNoReplyControlAuthority(false),
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
if (refFrom("agent", input.request.agent) !== input.decision.agent_ref) {
|
|
678
|
+
return {
|
|
679
|
+
adapterProfile: "prompt_no_reply_control_adapter",
|
|
680
|
+
status: "blocked_before_no_reply_prompt",
|
|
681
|
+
promptAttempted: false,
|
|
682
|
+
workflowId: input.decision.workflow_id,
|
|
683
|
+
attemptId: input.decision.attempt_id,
|
|
684
|
+
sessionRef: input.decision.session_ref,
|
|
685
|
+
redactedBlockReason: "No-reply decision agent_ref must match request agent.",
|
|
686
|
+
authority: promptNoReplyControlAuthority(false),
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
if (input.request.provider_qualified_model_id !==
|
|
690
|
+
input.decision.provider_qualified_model_id) {
|
|
691
|
+
return {
|
|
692
|
+
adapterProfile: "prompt_no_reply_control_adapter",
|
|
693
|
+
status: "blocked_before_no_reply_prompt",
|
|
694
|
+
promptAttempted: false,
|
|
695
|
+
workflowId: input.decision.workflow_id,
|
|
696
|
+
attemptId: input.decision.attempt_id,
|
|
697
|
+
sessionRef: input.decision.session_ref,
|
|
698
|
+
redactedBlockReason: "No-reply decision provider_qualified_model_id must match request model.",
|
|
699
|
+
authority: promptNoReplyControlAuthority(false),
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
const text = promptTextFrom(input.request);
|
|
703
|
+
const model = parseProviderQualifiedModelId(input.request.provider_qualified_model_id);
|
|
704
|
+
const runtimeModel = model === undefined
|
|
705
|
+
? undefined
|
|
706
|
+
: opencodeRuntimeModelForFlowDeskModel(model);
|
|
707
|
+
if (text === undefined ||
|
|
708
|
+
runtimeModel === undefined ||
|
|
709
|
+
input.request.agent.trim().length === 0) {
|
|
710
|
+
return {
|
|
711
|
+
adapterProfile: "prompt_no_reply_control_adapter",
|
|
712
|
+
status: "blocked_before_no_reply_prompt",
|
|
713
|
+
promptAttempted: false,
|
|
714
|
+
workflowId: input.decision.workflow_id,
|
|
715
|
+
attemptId: input.decision.attempt_id,
|
|
716
|
+
sessionRef: input.decision.session_ref,
|
|
717
|
+
redactedBlockReason: "No-reply prompt request is missing agent, model, or bounded text.",
|
|
718
|
+
authority: promptNoReplyControlAuthority(false),
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
const dispatchMethod = input.request.dispatchMethod ?? "prompt";
|
|
722
|
+
const dispatch = input.client.session[dispatchMethod];
|
|
723
|
+
if (dispatch === undefined) {
|
|
724
|
+
return {
|
|
725
|
+
adapterProfile: "prompt_no_reply_control_adapter",
|
|
726
|
+
status: "blocked_before_no_reply_prompt",
|
|
727
|
+
promptAttempted: false,
|
|
728
|
+
workflowId: input.decision.workflow_id,
|
|
729
|
+
attemptId: input.decision.attempt_id,
|
|
730
|
+
sessionRef: input.decision.session_ref,
|
|
731
|
+
redactedBlockReason: "Injected OpenCode client is missing the requested prompt method.",
|
|
732
|
+
authority: promptNoReplyControlAuthority(false),
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
const options = dispatchOptions(input.request, runtimeModel, text);
|
|
736
|
+
const response = await dispatch.call(input.client.session, {
|
|
737
|
+
...options,
|
|
738
|
+
body: { ...options.body, noReply: true },
|
|
739
|
+
});
|
|
740
|
+
return {
|
|
741
|
+
adapterProfile: "prompt_no_reply_control_adapter",
|
|
742
|
+
status: "no_reply_prompt_sent",
|
|
743
|
+
promptAttempted: true,
|
|
744
|
+
workflowId: input.decision.workflow_id,
|
|
745
|
+
attemptId: input.decision.attempt_id,
|
|
746
|
+
sessionRef: input.decision.session_ref,
|
|
747
|
+
agent: input.request.agent,
|
|
748
|
+
model: runtimeModel,
|
|
749
|
+
response,
|
|
750
|
+
authority: promptNoReplyControlAuthority(true),
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
function sha256Ref(content) {
|
|
754
|
+
return `sha256-${createHash("sha256").update(content, "utf8").digest("hex")}`;
|
|
755
|
+
}
|
|
756
|
+
function safeJoinUnderRoot(rootDir, relativePath) {
|
|
757
|
+
const root = resolve(rootDir);
|
|
758
|
+
const target = resolve(root, relativePath);
|
|
759
|
+
const rootPrefix = root.endsWith(sep) ? root : `${root}${sep}`;
|
|
760
|
+
if (target !== root && !target.startsWith(rootPrefix))
|
|
761
|
+
throw new Error("controlled write target escapes root directory");
|
|
762
|
+
return target;
|
|
763
|
+
}
|
|
764
|
+
function ensureNoSymlinkedDirectory(rootDir, relativePath) {
|
|
765
|
+
const root = resolve(rootDir);
|
|
766
|
+
if (existsSync(root) && lstatSync(root).isSymbolicLink())
|
|
767
|
+
throw new Error("controlled write root must not be a symlink");
|
|
768
|
+
const parts = dirname(relativePath)
|
|
769
|
+
.split("/")
|
|
770
|
+
.filter((part) => part.length > 0);
|
|
771
|
+
let current = root;
|
|
772
|
+
for (const part of parts) {
|
|
773
|
+
current = resolve(current, part);
|
|
774
|
+
if (existsSync(current) && lstatSync(current).isSymbolicLink())
|
|
775
|
+
throw new Error("controlled write directory must not be a symlink");
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
function controlledDocPathFor(targetRef) {
|
|
779
|
+
return `docs/conformance/${targetRef}.md`;
|
|
780
|
+
}
|
|
781
|
+
function controlledRedactedAuditExportPathFor(workflowId, targetRef) {
|
|
782
|
+
return `.flowdesk/sessions/${workflowId}/redacted-audit/${targetRef}.json`;
|
|
783
|
+
}
|
|
784
|
+
function validateControlledDocMarkdown(value) {
|
|
785
|
+
if (typeof value !== "string" || value.trim().length === 0)
|
|
786
|
+
return ["documentMarkdown must be a non-empty string"];
|
|
787
|
+
if (value.length > 200_000)
|
|
788
|
+
return ["documentMarkdown exceeds controlled conformance doc limit"];
|
|
789
|
+
return [];
|
|
790
|
+
}
|
|
791
|
+
function validateRedactedAuditExportJson(value) {
|
|
792
|
+
if (typeof value !== "string" || value.trim().length === 0)
|
|
793
|
+
return ["exportJson must be a non-empty string"];
|
|
794
|
+
if (value.length > 200_000)
|
|
795
|
+
return ["exportJson exceeds controlled redacted audit export limit"];
|
|
796
|
+
let parsed;
|
|
797
|
+
try {
|
|
798
|
+
parsed = JSON.parse(value);
|
|
799
|
+
}
|
|
800
|
+
catch {
|
|
801
|
+
return ["exportJson must be parseable JSON"];
|
|
802
|
+
}
|
|
803
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
804
|
+
return ["exportJson must be a JSON object"];
|
|
805
|
+
const redaction = validateNoForbiddenRawPayloads(parsed, "redacted_audit_export");
|
|
806
|
+
return redaction.ok ? [] : redaction.errors;
|
|
807
|
+
}
|
|
808
|
+
export function materializeFlowDeskControlledConformanceDocLocalWriteV1(input) {
|
|
809
|
+
const errors = [];
|
|
810
|
+
if (typeof input.rootDir !== "string" || input.rootDir.trim().length === 0)
|
|
811
|
+
errors.push("rootDir is required");
|
|
812
|
+
errors.push(...validateControlledDocMarkdown(input.documentMarkdown));
|
|
813
|
+
if (input.readiness.status !== "write_ready")
|
|
814
|
+
errors.push("controlled external write readiness must be write_ready");
|
|
815
|
+
if (input.readiness.authority.controlledExternalWriteAuthorized !== true)
|
|
816
|
+
errors.push("controlled external write readiness must authorize the controlled target");
|
|
817
|
+
if (input.request.target_kind !== "release_conformance_doc")
|
|
818
|
+
errors.push("local writer only supports release_conformance_doc targets");
|
|
819
|
+
const recheckedReadiness = prepareFlowDeskControlledExternalWriteAdapterV1({
|
|
820
|
+
request: input.request,
|
|
821
|
+
consumedApproval: input.consumedApproval,
|
|
822
|
+
});
|
|
823
|
+
if (recheckedReadiness.status !== "write_ready")
|
|
824
|
+
errors.push(recheckedReadiness.redactedBlockReason ??
|
|
825
|
+
"controlled external write readiness recheck failed");
|
|
826
|
+
if (input.readiness.workflowId !== input.request.workflow_id ||
|
|
827
|
+
input.readiness.attemptId !== input.request.attempt_id ||
|
|
828
|
+
input.readiness.targetKind !== input.request.target_kind ||
|
|
829
|
+
input.readiness.targetRef !== input.request.target_ref)
|
|
830
|
+
errors.push("readiness result does not match request target");
|
|
831
|
+
if (input.consumedApproval.consumed_at === undefined)
|
|
832
|
+
errors.push("external_write approval must be consumed before local write");
|
|
833
|
+
if (input.consumedApproval.consumption_audit_ref === undefined)
|
|
834
|
+
errors.push("external_write approval must include consumption audit ref");
|
|
835
|
+
const artifactSha256Ref = sha256Ref(input.documentMarkdown);
|
|
836
|
+
if (input.request.content_hash_ref !== artifactSha256Ref)
|
|
837
|
+
errors.push("request content_hash_ref must match document sha256");
|
|
838
|
+
const materializedAt = input.materializedAt ?? new Date().toISOString();
|
|
839
|
+
if (!Number.isFinite(Date.parse(materializedAt)))
|
|
840
|
+
errors.push("materializedAt must be a parseable timestamp");
|
|
841
|
+
if (errors.length > 0)
|
|
842
|
+
return blockControlledConformanceDocWrite({
|
|
843
|
+
request: input.request,
|
|
844
|
+
reason: errors.join(", "),
|
|
845
|
+
});
|
|
846
|
+
const documentPath = controlledDocPathFor(input.request.target_ref);
|
|
847
|
+
const ledgerRecord = {
|
|
848
|
+
schema_version: "flowdesk.controlled_conformance_doc_write.v1",
|
|
849
|
+
ledger_entry_id: input.ledgerEntryId,
|
|
850
|
+
request_id: input.request.request_id,
|
|
851
|
+
workflow_id: input.request.workflow_id,
|
|
852
|
+
attempt_id: input.request.attempt_id,
|
|
853
|
+
target_kind: "release_conformance_doc",
|
|
854
|
+
target_ref: input.request.target_ref,
|
|
855
|
+
approval_id: input.consumedApproval.approval_id,
|
|
856
|
+
actor_ref: input.consumedApproval.actor_ref,
|
|
857
|
+
profile_ref: input.consumedApproval.profile_ref,
|
|
858
|
+
evidence_bundle_hash: input.consumedApproval.evidence_bundle_hash,
|
|
859
|
+
guard_decision_ref: input.consumedApproval.guard_decision_ref,
|
|
860
|
+
issuance_audit_ref: input.consumedApproval.issuance_audit_ref,
|
|
861
|
+
consumption_audit_ref: input.consumedApproval
|
|
862
|
+
.consumption_audit_ref,
|
|
863
|
+
redaction_policy_ref: input.request.redaction_policy_ref,
|
|
864
|
+
content_hash_ref: input.request.content_hash_ref,
|
|
865
|
+
pre_write_audit_ref: input.request.pre_write_audit_ref,
|
|
866
|
+
dry_run_ref: input.request.dry_run_ref,
|
|
867
|
+
artifact_ref: `artifact-${input.request.target_ref}`,
|
|
868
|
+
artifact_path: documentPath,
|
|
869
|
+
artifact_sha256_ref: artifactSha256Ref,
|
|
870
|
+
materialized_at: materializedAt,
|
|
871
|
+
local_only: true,
|
|
872
|
+
writeAttempted: true,
|
|
873
|
+
remoteWriteAttempted: false,
|
|
874
|
+
githubWriteAttempted: false,
|
|
875
|
+
connectorWriteAttempted: false,
|
|
876
|
+
storageWriteAttempted: false,
|
|
877
|
+
databaseWriteAttempted: false,
|
|
878
|
+
urlWriteAttempted: false,
|
|
879
|
+
rawPathWriteAttempted: false,
|
|
880
|
+
dispatch_authority_enabled: false,
|
|
881
|
+
realOpenCodeDispatch: false,
|
|
882
|
+
providerCall: false,
|
|
883
|
+
actualLaneLaunch: false,
|
|
884
|
+
runtimeExecution: false,
|
|
885
|
+
fallbackAuthority: false,
|
|
886
|
+
toolAuthority: false,
|
|
887
|
+
hardCancelOrNoReplyAuthority: false,
|
|
888
|
+
};
|
|
889
|
+
const preparedLedger = prepareFlowDeskSessionEvidenceWriteIntentV1({
|
|
890
|
+
workflowId: input.request.workflow_id,
|
|
891
|
+
evidenceId: input.ledgerEntryId,
|
|
892
|
+
record: ledgerRecord,
|
|
893
|
+
});
|
|
894
|
+
if (!preparedLedger.ok || preparedLedger.writeIntent === undefined)
|
|
895
|
+
return blockControlledConformanceDocWrite({
|
|
896
|
+
request: input.request,
|
|
897
|
+
reason: preparedLedger.errors.join(", ") || "ledger write intent invalid",
|
|
898
|
+
});
|
|
899
|
+
const preWriteReload = reloadFlowDeskSessionEvidenceV1({
|
|
900
|
+
workflowId: input.request.workflow_id,
|
|
901
|
+
rootDir: input.rootDir,
|
|
902
|
+
});
|
|
903
|
+
if (!preWriteReload.ok || preWriteReload.blocked.length > 0)
|
|
904
|
+
return blockControlledConformanceDocWrite({
|
|
905
|
+
request: input.request,
|
|
906
|
+
reason: "controlled conformance doc pre-write evidence reload failed",
|
|
907
|
+
});
|
|
908
|
+
let documentRenamed = false;
|
|
909
|
+
let ledgerRenamed = false;
|
|
910
|
+
try {
|
|
911
|
+
ensureNoSymlinkedDirectory(input.rootDir, documentPath);
|
|
912
|
+
ensureNoSymlinkedDirectory(input.rootDir, preparedLedger.writeIntent.path);
|
|
913
|
+
const documentTarget = safeJoinUnderRoot(input.rootDir, documentPath);
|
|
914
|
+
const documentTemp = safeJoinUnderRoot(input.rootDir, `docs/conformance/.${input.request.target_ref}.tmp-controlled-conformance-doc-write`);
|
|
915
|
+
const ledgerTarget = safeJoinUnderRoot(input.rootDir, preparedLedger.writeIntent.path);
|
|
916
|
+
const ledgerTemp = safeJoinUnderRoot(input.rootDir, preparedLedger.writeIntent.tempPath);
|
|
917
|
+
if (existsSync(documentTarget) || existsSync(ledgerTarget))
|
|
918
|
+
return blockControlledConformanceDocWrite({
|
|
919
|
+
request: input.request,
|
|
920
|
+
reason: "controlled conformance doc or ledger target already exists",
|
|
921
|
+
});
|
|
922
|
+
if (dirname(documentTarget) !== dirname(documentTemp))
|
|
923
|
+
return blockControlledConformanceDocWrite({
|
|
924
|
+
request: input.request,
|
|
925
|
+
reason: "controlled conformance doc temp path must stay beside target",
|
|
926
|
+
});
|
|
927
|
+
if (dirname(ledgerTarget) !== dirname(ledgerTemp))
|
|
928
|
+
return blockControlledConformanceDocWrite({
|
|
929
|
+
request: input.request,
|
|
930
|
+
reason: "controlled conformance doc ledger temp path must stay beside target",
|
|
931
|
+
});
|
|
932
|
+
mkdirSync(dirname(documentTarget), { recursive: true });
|
|
933
|
+
mkdirSync(dirname(ledgerTarget), { recursive: true });
|
|
934
|
+
writeFileSync(documentTemp, input.documentMarkdown, "utf8");
|
|
935
|
+
writeFileSync(ledgerTemp, JSON.stringify(ledgerRecord), "utf8");
|
|
936
|
+
renameSync(documentTemp, documentTarget);
|
|
937
|
+
documentRenamed = true;
|
|
938
|
+
renameSync(ledgerTemp, ledgerTarget);
|
|
939
|
+
ledgerRenamed = true;
|
|
940
|
+
const writtenHash = sha256Ref(readFileSync(documentTarget, "utf8"));
|
|
941
|
+
if (writtenHash !== artifactSha256Ref) {
|
|
942
|
+
try {
|
|
943
|
+
rmSync(ledgerTarget, { force: true });
|
|
944
|
+
rmSync(documentTarget, { force: true });
|
|
945
|
+
}
|
|
946
|
+
catch {
|
|
947
|
+
// Best-effort cleanup only; result remains blocked.
|
|
948
|
+
}
|
|
949
|
+
finally {
|
|
950
|
+
documentRenamed = false;
|
|
951
|
+
ledgerRenamed = false;
|
|
952
|
+
}
|
|
953
|
+
return blockControlledConformanceDocWrite({
|
|
954
|
+
request: input.request,
|
|
955
|
+
reason: "controlled conformance doc hash verification failed",
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
const reloaded = reloadFlowDeskSessionEvidenceV1({
|
|
959
|
+
workflowId: input.request.workflow_id,
|
|
960
|
+
rootDir: input.rootDir,
|
|
961
|
+
});
|
|
962
|
+
const ledgerReloaded = reloaded.ok &&
|
|
963
|
+
reloaded.blocked.length === 0 &&
|
|
964
|
+
reloaded.entries.some((entry) => entry.evidenceClass === "controlled_conformance_doc_write" &&
|
|
965
|
+
entry.evidenceId === input.ledgerEntryId &&
|
|
966
|
+
entry.record.artifact_sha256_ref === artifactSha256Ref);
|
|
967
|
+
if (!ledgerReloaded)
|
|
968
|
+
try {
|
|
969
|
+
rmSync(ledgerTarget, { force: true });
|
|
970
|
+
rmSync(documentTarget, { force: true });
|
|
971
|
+
}
|
|
972
|
+
catch {
|
|
973
|
+
// Best-effort cleanup only; result remains blocked.
|
|
974
|
+
}
|
|
975
|
+
finally {
|
|
976
|
+
documentRenamed = false;
|
|
977
|
+
ledgerRenamed = false;
|
|
978
|
+
}
|
|
979
|
+
if (!ledgerReloaded)
|
|
980
|
+
return blockControlledConformanceDocWrite({
|
|
981
|
+
request: input.request,
|
|
982
|
+
reason: "controlled conformance doc ledger reload failed",
|
|
983
|
+
});
|
|
984
|
+
return {
|
|
985
|
+
adapterProfile: "controlled_conformance_doc_local_writer",
|
|
986
|
+
status: "write_recorded",
|
|
987
|
+
workflowId: input.request.workflow_id,
|
|
988
|
+
attemptId: input.request.attempt_id,
|
|
989
|
+
targetKind: "release_conformance_doc",
|
|
990
|
+
targetRef: input.request.target_ref,
|
|
991
|
+
writeAttempted: true,
|
|
992
|
+
documentPath,
|
|
993
|
+
ledgerEntryId: input.ledgerEntryId,
|
|
994
|
+
ledgerEvidenceReloaded: true,
|
|
995
|
+
artifactSha256Ref,
|
|
996
|
+
safeNextActions: ["/flowdesk-status", "/flowdesk-export-debug"],
|
|
997
|
+
authority: controlledConformanceDocWriteAuthority(true),
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
catch (error) {
|
|
1001
|
+
try {
|
|
1002
|
+
if (ledgerRenamed) {
|
|
1003
|
+
const ledgerTarget = safeJoinUnderRoot(input.rootDir, preparedLedger.writeIntent.path);
|
|
1004
|
+
rmSync(ledgerTarget, { force: true });
|
|
1005
|
+
}
|
|
1006
|
+
if (documentRenamed) {
|
|
1007
|
+
const documentTarget = safeJoinUnderRoot(input.rootDir, documentPath);
|
|
1008
|
+
rmSync(documentTarget, { force: true });
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
catch {
|
|
1012
|
+
// Best-effort cleanup only; result remains blocked.
|
|
1013
|
+
}
|
|
1014
|
+
return blockControlledConformanceDocWrite({
|
|
1015
|
+
request: input.request,
|
|
1016
|
+
reason: error instanceof Error
|
|
1017
|
+
? error.message
|
|
1018
|
+
: "controlled conformance doc local write failed",
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
export function materializeFlowDeskControlledRedactedAuditExportLocalWriteV1(input) {
|
|
1023
|
+
const errors = [];
|
|
1024
|
+
if (typeof input.rootDir !== "string" || input.rootDir.trim().length === 0)
|
|
1025
|
+
errors.push("rootDir is required");
|
|
1026
|
+
errors.push(...validateRedactedAuditExportJson(input.exportJson));
|
|
1027
|
+
if (input.readiness.status !== "write_ready")
|
|
1028
|
+
errors.push("controlled external write readiness must be write_ready");
|
|
1029
|
+
if (input.readiness.authority.controlledExternalWriteAuthorized !== true)
|
|
1030
|
+
errors.push("controlled external write readiness must authorize the controlled target");
|
|
1031
|
+
if (input.request.target_kind !== "redacted_audit_export")
|
|
1032
|
+
errors.push("local writer only supports redacted_audit_export targets");
|
|
1033
|
+
const recheckedReadiness = prepareFlowDeskControlledExternalWriteAdapterV1({
|
|
1034
|
+
request: input.request,
|
|
1035
|
+
consumedApproval: input.consumedApproval,
|
|
1036
|
+
});
|
|
1037
|
+
if (recheckedReadiness.status !== "write_ready")
|
|
1038
|
+
errors.push(recheckedReadiness.redactedBlockReason ??
|
|
1039
|
+
"controlled external write readiness recheck failed");
|
|
1040
|
+
if (input.readiness.workflowId !== input.request.workflow_id ||
|
|
1041
|
+
input.readiness.attemptId !== input.request.attempt_id ||
|
|
1042
|
+
input.readiness.targetKind !== input.request.target_kind ||
|
|
1043
|
+
input.readiness.targetRef !== input.request.target_ref)
|
|
1044
|
+
errors.push("readiness result does not match request target");
|
|
1045
|
+
if (input.consumedApproval.consumed_at === undefined)
|
|
1046
|
+
errors.push("external_write approval must be consumed before local write");
|
|
1047
|
+
if (input.consumedApproval.consumption_audit_ref === undefined)
|
|
1048
|
+
errors.push("external_write approval must include consumption audit ref");
|
|
1049
|
+
const artifactSha256Ref = sha256Ref(input.exportJson);
|
|
1050
|
+
if (input.request.content_hash_ref !== artifactSha256Ref)
|
|
1051
|
+
errors.push("request content_hash_ref must match redacted audit export sha256");
|
|
1052
|
+
const materializedAt = input.materializedAt ?? new Date().toISOString();
|
|
1053
|
+
if (!Number.isFinite(Date.parse(materializedAt)))
|
|
1054
|
+
errors.push("materializedAt must be a parseable timestamp");
|
|
1055
|
+
if (errors.length > 0)
|
|
1056
|
+
return blockControlledRedactedAuditExportWrite({
|
|
1057
|
+
request: input.request,
|
|
1058
|
+
reason: errors.join(", "),
|
|
1059
|
+
});
|
|
1060
|
+
const exportPath = controlledRedactedAuditExportPathFor(input.request.workflow_id, input.request.target_ref);
|
|
1061
|
+
const ledgerRecord = {
|
|
1062
|
+
schema_version: "flowdesk.controlled_redacted_audit_export_write.v1",
|
|
1063
|
+
ledger_entry_id: input.ledgerEntryId,
|
|
1064
|
+
request_id: input.request.request_id,
|
|
1065
|
+
workflow_id: input.request.workflow_id,
|
|
1066
|
+
attempt_id: input.request.attempt_id,
|
|
1067
|
+
target_kind: "redacted_audit_export",
|
|
1068
|
+
target_ref: input.request.target_ref,
|
|
1069
|
+
approval_id: input.consumedApproval.approval_id,
|
|
1070
|
+
actor_ref: input.consumedApproval.actor_ref,
|
|
1071
|
+
profile_ref: input.consumedApproval.profile_ref,
|
|
1072
|
+
evidence_bundle_hash: input.consumedApproval.evidence_bundle_hash,
|
|
1073
|
+
guard_decision_ref: input.consumedApproval.guard_decision_ref,
|
|
1074
|
+
issuance_audit_ref: input.consumedApproval.issuance_audit_ref,
|
|
1075
|
+
consumption_audit_ref: input.consumedApproval
|
|
1076
|
+
.consumption_audit_ref,
|
|
1077
|
+
redaction_policy_ref: input.request.redaction_policy_ref,
|
|
1078
|
+
content_hash_ref: input.request.content_hash_ref,
|
|
1079
|
+
pre_write_audit_ref: input.request.pre_write_audit_ref,
|
|
1080
|
+
dry_run_ref: input.request.dry_run_ref,
|
|
1081
|
+
artifact_ref: `artifact-${input.request.target_ref}`,
|
|
1082
|
+
artifact_path: exportPath,
|
|
1083
|
+
artifact_sha256_ref: artifactSha256Ref,
|
|
1084
|
+
materialized_at: materializedAt,
|
|
1085
|
+
local_only: true,
|
|
1086
|
+
redacted: true,
|
|
1087
|
+
writeAttempted: true,
|
|
1088
|
+
remoteWriteAttempted: false,
|
|
1089
|
+
githubWriteAttempted: false,
|
|
1090
|
+
connectorWriteAttempted: false,
|
|
1091
|
+
storageWriteAttempted: false,
|
|
1092
|
+
databaseWriteAttempted: false,
|
|
1093
|
+
urlWriteAttempted: false,
|
|
1094
|
+
rawPathWriteAttempted: false,
|
|
1095
|
+
dispatch_authority_enabled: false,
|
|
1096
|
+
realOpenCodeDispatch: false,
|
|
1097
|
+
providerCall: false,
|
|
1098
|
+
actualLaneLaunch: false,
|
|
1099
|
+
runtimeExecution: false,
|
|
1100
|
+
fallbackAuthority: false,
|
|
1101
|
+
toolAuthority: false,
|
|
1102
|
+
hardCancelOrNoReplyAuthority: false,
|
|
1103
|
+
};
|
|
1104
|
+
const preparedLedger = prepareFlowDeskSessionEvidenceWriteIntentV1({
|
|
1105
|
+
workflowId: input.request.workflow_id,
|
|
1106
|
+
evidenceId: input.ledgerEntryId,
|
|
1107
|
+
record: ledgerRecord,
|
|
1108
|
+
});
|
|
1109
|
+
if (!preparedLedger.ok || preparedLedger.writeIntent === undefined)
|
|
1110
|
+
return blockControlledRedactedAuditExportWrite({
|
|
1111
|
+
request: input.request,
|
|
1112
|
+
reason: preparedLedger.errors.join(", ") || "ledger write intent invalid",
|
|
1113
|
+
});
|
|
1114
|
+
const preWriteReload = reloadFlowDeskSessionEvidenceV1({
|
|
1115
|
+
workflowId: input.request.workflow_id,
|
|
1116
|
+
rootDir: input.rootDir,
|
|
1117
|
+
});
|
|
1118
|
+
if (!preWriteReload.ok || preWriteReload.blocked.length > 0)
|
|
1119
|
+
return blockControlledRedactedAuditExportWrite({
|
|
1120
|
+
request: input.request,
|
|
1121
|
+
reason: "controlled redacted audit export pre-write evidence reload failed",
|
|
1122
|
+
});
|
|
1123
|
+
let exportRenamed = false;
|
|
1124
|
+
let ledgerRenamed = false;
|
|
1125
|
+
try {
|
|
1126
|
+
ensureNoSymlinkedDirectory(input.rootDir, exportPath);
|
|
1127
|
+
ensureNoSymlinkedDirectory(input.rootDir, preparedLedger.writeIntent.path);
|
|
1128
|
+
const exportTarget = safeJoinUnderRoot(input.rootDir, exportPath);
|
|
1129
|
+
const exportTemp = safeJoinUnderRoot(input.rootDir, `.flowdesk/sessions/${input.request.workflow_id}/redacted-audit/.${input.request.target_ref}.tmp-controlled-redacted-audit-export-write`);
|
|
1130
|
+
const ledgerTarget = safeJoinUnderRoot(input.rootDir, preparedLedger.writeIntent.path);
|
|
1131
|
+
const ledgerTemp = safeJoinUnderRoot(input.rootDir, preparedLedger.writeIntent.tempPath);
|
|
1132
|
+
if (existsSync(exportTarget) || existsSync(ledgerTarget))
|
|
1133
|
+
return blockControlledRedactedAuditExportWrite({
|
|
1134
|
+
request: input.request,
|
|
1135
|
+
reason: "controlled redacted audit export or ledger target already exists",
|
|
1136
|
+
});
|
|
1137
|
+
if (dirname(exportTarget) !== dirname(exportTemp))
|
|
1138
|
+
return blockControlledRedactedAuditExportWrite({
|
|
1139
|
+
request: input.request,
|
|
1140
|
+
reason: "controlled redacted audit export temp path must stay beside target",
|
|
1141
|
+
});
|
|
1142
|
+
if (dirname(ledgerTarget) !== dirname(ledgerTemp))
|
|
1143
|
+
return blockControlledRedactedAuditExportWrite({
|
|
1144
|
+
request: input.request,
|
|
1145
|
+
reason: "controlled redacted audit export ledger temp path must stay beside target",
|
|
1146
|
+
});
|
|
1147
|
+
mkdirSync(dirname(exportTarget), { recursive: true });
|
|
1148
|
+
mkdirSync(dirname(ledgerTarget), { recursive: true });
|
|
1149
|
+
writeFileSync(exportTemp, input.exportJson, "utf8");
|
|
1150
|
+
writeFileSync(ledgerTemp, JSON.stringify(ledgerRecord), "utf8");
|
|
1151
|
+
renameSync(exportTemp, exportTarget);
|
|
1152
|
+
exportRenamed = true;
|
|
1153
|
+
renameSync(ledgerTemp, ledgerTarget);
|
|
1154
|
+
ledgerRenamed = true;
|
|
1155
|
+
const writtenHash = sha256Ref(readFileSync(exportTarget, "utf8"));
|
|
1156
|
+
if (writtenHash !== artifactSha256Ref) {
|
|
1157
|
+
try {
|
|
1158
|
+
rmSync(ledgerTarget, { force: true });
|
|
1159
|
+
rmSync(exportTarget, { force: true });
|
|
1160
|
+
}
|
|
1161
|
+
catch {
|
|
1162
|
+
// Best-effort cleanup only; result remains blocked.
|
|
1163
|
+
}
|
|
1164
|
+
finally {
|
|
1165
|
+
exportRenamed = false;
|
|
1166
|
+
ledgerRenamed = false;
|
|
1167
|
+
}
|
|
1168
|
+
return blockControlledRedactedAuditExportWrite({
|
|
1169
|
+
request: input.request,
|
|
1170
|
+
reason: "controlled redacted audit export hash verification failed",
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
const reloaded = reloadFlowDeskSessionEvidenceV1({
|
|
1174
|
+
workflowId: input.request.workflow_id,
|
|
1175
|
+
rootDir: input.rootDir,
|
|
1176
|
+
});
|
|
1177
|
+
const ledgerReloaded = reloaded.ok &&
|
|
1178
|
+
reloaded.blocked.length === 0 &&
|
|
1179
|
+
reloaded.entries.some((entry) => entry.evidenceClass === "controlled_redacted_audit_export_write" &&
|
|
1180
|
+
entry.evidenceId === input.ledgerEntryId &&
|
|
1181
|
+
entry.record.artifact_sha256_ref === artifactSha256Ref);
|
|
1182
|
+
if (!ledgerReloaded)
|
|
1183
|
+
try {
|
|
1184
|
+
rmSync(ledgerTarget, { force: true });
|
|
1185
|
+
rmSync(exportTarget, { force: true });
|
|
1186
|
+
}
|
|
1187
|
+
catch {
|
|
1188
|
+
// Best-effort cleanup only; result remains blocked.
|
|
1189
|
+
}
|
|
1190
|
+
finally {
|
|
1191
|
+
exportRenamed = false;
|
|
1192
|
+
ledgerRenamed = false;
|
|
1193
|
+
}
|
|
1194
|
+
if (!ledgerReloaded)
|
|
1195
|
+
return blockControlledRedactedAuditExportWrite({
|
|
1196
|
+
request: input.request,
|
|
1197
|
+
reason: "controlled redacted audit export ledger reload failed",
|
|
1198
|
+
});
|
|
1199
|
+
return {
|
|
1200
|
+
adapterProfile: "controlled_redacted_audit_export_local_writer",
|
|
1201
|
+
status: "write_recorded",
|
|
1202
|
+
workflowId: input.request.workflow_id,
|
|
1203
|
+
attemptId: input.request.attempt_id,
|
|
1204
|
+
targetKind: "redacted_audit_export",
|
|
1205
|
+
targetRef: input.request.target_ref,
|
|
1206
|
+
writeAttempted: true,
|
|
1207
|
+
exportPath,
|
|
1208
|
+
ledgerEntryId: input.ledgerEntryId,
|
|
1209
|
+
ledgerEvidenceReloaded: true,
|
|
1210
|
+
artifactSha256Ref,
|
|
1211
|
+
safeNextActions: ["/flowdesk-status", "/flowdesk-export-debug"],
|
|
1212
|
+
authority: controlledRedactedAuditExportWriteAuthority(true),
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
catch (error) {
|
|
1216
|
+
try {
|
|
1217
|
+
if (ledgerRenamed) {
|
|
1218
|
+
const ledgerTarget = safeJoinUnderRoot(input.rootDir, preparedLedger.writeIntent.path);
|
|
1219
|
+
rmSync(ledgerTarget, { force: true });
|
|
1220
|
+
}
|
|
1221
|
+
if (exportRenamed) {
|
|
1222
|
+
const exportTarget = safeJoinUnderRoot(input.rootDir, exportPath);
|
|
1223
|
+
rmSync(exportTarget, { force: true });
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
catch {
|
|
1227
|
+
// Best-effort cleanup only; result remains blocked.
|
|
1228
|
+
}
|
|
1229
|
+
return blockControlledRedactedAuditExportWrite({
|
|
1230
|
+
request: input.request,
|
|
1231
|
+
reason: error instanceof Error
|
|
1232
|
+
? error.message
|
|
1233
|
+
: "controlled redacted audit export local write failed",
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
export function prepareFlowDeskFallbackReselectionRegateAdapterV1(input) {
|
|
1238
|
+
const promotion = promoteFlowDeskFallbackReselectionRegateV1(input);
|
|
1239
|
+
if (!promotion.ok ||
|
|
1240
|
+
promotion.fallback_reselection_regate_authority_enabled !== true) {
|
|
1241
|
+
return {
|
|
1242
|
+
adapterProfile: "fallback_reselection_regate_adapter",
|
|
1243
|
+
status: "blocked_before_regate",
|
|
1244
|
+
dispatchAttempted: false,
|
|
1245
|
+
workflowId: input.decision.workflow_id,
|
|
1246
|
+
parentAttemptId: input.decision.parent_attempt_id,
|
|
1247
|
+
newAttemptId: input.decision.new_attempt_id,
|
|
1248
|
+
redactedBlockReason: promotion.errors.join(",") || "fallback reselection regate blocked",
|
|
1249
|
+
safeNextActions: ["/flowdesk-status"],
|
|
1250
|
+
authority: disabledFallbackAuthority(),
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
return {
|
|
1254
|
+
adapterProfile: "fallback_reselection_regate_adapter",
|
|
1255
|
+
status: "regate_required",
|
|
1256
|
+
dispatchAttempted: false,
|
|
1257
|
+
workflowId: input.decision.workflow_id,
|
|
1258
|
+
parentAttemptId: input.decision.parent_attempt_id,
|
|
1259
|
+
newAttemptId: input.decision.new_attempt_id,
|
|
1260
|
+
fromProviderQualifiedModelId: input.decision.from_provider_qualified_model_id,
|
|
1261
|
+
toProviderQualifiedModelId: input.decision.to_provider_qualified_model_id,
|
|
1262
|
+
safeNextActions: ["/flowdesk-status", "/flowdesk-run"],
|
|
1263
|
+
authority: disabledFallbackAuthority(),
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
export function orchestrateFlowDeskManagedFallbackRegateV1(input) {
|
|
1267
|
+
const plan = planFlowDeskFallbackRegateV1(input);
|
|
1268
|
+
if (!plan.ok || plan.state !== "full_regate_required") {
|
|
1269
|
+
return {
|
|
1270
|
+
adapterProfile: "managed_fallback_regate_orchestrator",
|
|
1271
|
+
status: "blocked_before_regate_plan",
|
|
1272
|
+
dispatchAttempted: false,
|
|
1273
|
+
providerSwitchAttempted: false,
|
|
1274
|
+
sdkCallAttempted: false,
|
|
1275
|
+
workflowId: input.decision.workflow_id,
|
|
1276
|
+
parentAttemptId: input.decision.parent_attempt_id,
|
|
1277
|
+
newAttemptId: input.decision.new_attempt_id,
|
|
1278
|
+
fromProviderQualifiedModelId: input.decision.from_provider_qualified_model_id,
|
|
1279
|
+
toProviderQualifiedModelId: input.decision.to_provider_qualified_model_id,
|
|
1280
|
+
regatePlan: plan,
|
|
1281
|
+
redactedBlockReason: plan.errors.join(",") || "fallback regate plan blocked",
|
|
1282
|
+
safeNextActions: ["/flowdesk-status"],
|
|
1283
|
+
authority: managedFallbackRegateAuthority(false),
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
return {
|
|
1287
|
+
adapterProfile: "managed_fallback_regate_orchestrator",
|
|
1288
|
+
status: "regate_plan_ready",
|
|
1289
|
+
dispatchAttempted: false,
|
|
1290
|
+
providerSwitchAttempted: false,
|
|
1291
|
+
sdkCallAttempted: false,
|
|
1292
|
+
workflowId: plan.workflow_id,
|
|
1293
|
+
parentAttemptId: plan.parent_attempt_id,
|
|
1294
|
+
newAttemptId: plan.new_attempt_id,
|
|
1295
|
+
fromProviderQualifiedModelId: plan.from_provider_qualified_model_id,
|
|
1296
|
+
toProviderQualifiedModelId: plan.to_provider_qualified_model_id,
|
|
1297
|
+
regatePlan: plan,
|
|
1298
|
+
safeNextActions: ["/flowdesk-status", "/flowdesk-run"],
|
|
1299
|
+
authority: managedFallbackRegateAuthority(true),
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
export function materializeFlowDeskManagedFallbackRegatePlanEvidenceV1(input) {
|
|
1303
|
+
const blockedAuthority = {
|
|
1304
|
+
realOpenCodeDispatch: false,
|
|
1305
|
+
providerCall: false,
|
|
1306
|
+
runtimeExecution: false,
|
|
1307
|
+
actualLaneLaunch: false,
|
|
1308
|
+
fallbackAuthority: false,
|
|
1309
|
+
automaticFallbackAuthorized: false,
|
|
1310
|
+
regatePlanPersisted: false,
|
|
1311
|
+
toolAuthority: false,
|
|
1312
|
+
hardCancelOrNoReplyAuthority: false,
|
|
1313
|
+
};
|
|
1314
|
+
if (typeof input.rootDir !== "string" || input.rootDir.trim().length === 0)
|
|
1315
|
+
return {
|
|
1316
|
+
adapterProfile: "managed_fallback_regate_plan_evidence_materializer",
|
|
1317
|
+
status: "blocked_before_regate_plan_evidence",
|
|
1318
|
+
writeAttempted: false,
|
|
1319
|
+
evidenceReloaded: false,
|
|
1320
|
+
workflowId: input.regatePlan?.workflow_id,
|
|
1321
|
+
evidenceId: input.evidenceId,
|
|
1322
|
+
redactedBlockReason: "rootDir is required",
|
|
1323
|
+
safeNextActions: ["/flowdesk-status"],
|
|
1324
|
+
authority: blockedAuthority,
|
|
1325
|
+
};
|
|
1326
|
+
const planValidation = validateFlowDeskFallbackRegatePlanV1(input.regatePlan);
|
|
1327
|
+
if (!planValidation.ok)
|
|
1328
|
+
return {
|
|
1329
|
+
adapterProfile: "managed_fallback_regate_plan_evidence_materializer",
|
|
1330
|
+
status: "blocked_before_regate_plan_evidence",
|
|
1331
|
+
writeAttempted: false,
|
|
1332
|
+
evidenceReloaded: false,
|
|
1333
|
+
workflowId: input.regatePlan?.workflow_id,
|
|
1334
|
+
evidenceId: input.evidenceId,
|
|
1335
|
+
redactedBlockReason: planValidation.errors.join(", ") ||
|
|
1336
|
+
"fallback regate plan invalid",
|
|
1337
|
+
safeNextActions: ["/flowdesk-status"],
|
|
1338
|
+
authority: blockedAuthority,
|
|
1339
|
+
};
|
|
1340
|
+
if (input.regatePlan.state !== "full_regate_required" ||
|
|
1341
|
+
input.regatePlan.ok !== true)
|
|
1342
|
+
return {
|
|
1343
|
+
adapterProfile: "managed_fallback_regate_plan_evidence_materializer",
|
|
1344
|
+
status: "blocked_before_regate_plan_evidence",
|
|
1345
|
+
writeAttempted: false,
|
|
1346
|
+
evidenceReloaded: false,
|
|
1347
|
+
workflowId: input.regatePlan?.workflow_id,
|
|
1348
|
+
evidenceId: input.evidenceId,
|
|
1349
|
+
redactedBlockReason: "fallback regate plan must be full_regate_required",
|
|
1350
|
+
safeNextActions: ["/flowdesk-status"],
|
|
1351
|
+
authority: blockedAuthority,
|
|
1352
|
+
};
|
|
1353
|
+
const workflowId = input.regatePlan.workflow_id;
|
|
1354
|
+
if (typeof workflowId !== "string" || workflowId.trim().length === 0)
|
|
1355
|
+
return {
|
|
1356
|
+
adapterProfile: "managed_fallback_regate_plan_evidence_materializer",
|
|
1357
|
+
status: "blocked_before_regate_plan_evidence",
|
|
1358
|
+
writeAttempted: false,
|
|
1359
|
+
evidenceReloaded: false,
|
|
1360
|
+
evidenceId: input.evidenceId,
|
|
1361
|
+
redactedBlockReason: "fallback regate plan workflow_id is required",
|
|
1362
|
+
safeNextActions: ["/flowdesk-status"],
|
|
1363
|
+
authority: blockedAuthority,
|
|
1364
|
+
};
|
|
1365
|
+
const preWriteReload = reloadFlowDeskSessionEvidenceV1({
|
|
1366
|
+
workflowId,
|
|
1367
|
+
rootDir: input.rootDir,
|
|
1368
|
+
});
|
|
1369
|
+
if (!preWriteReload.ok || preWriteReload.blocked.length > 0)
|
|
1370
|
+
return {
|
|
1371
|
+
adapterProfile: "managed_fallback_regate_plan_evidence_materializer",
|
|
1372
|
+
status: "blocked_before_regate_plan_evidence",
|
|
1373
|
+
writeAttempted: false,
|
|
1374
|
+
evidenceReloaded: false,
|
|
1375
|
+
workflowId,
|
|
1376
|
+
evidenceId: input.evidenceId,
|
|
1377
|
+
redactedBlockReason: "fallback regate plan pre-write reload failed",
|
|
1378
|
+
safeNextActions: ["/flowdesk-status"],
|
|
1379
|
+
authority: blockedAuthority,
|
|
1380
|
+
};
|
|
1381
|
+
if (preWriteReload.entries.some((entry) => entry.evidenceClass === "fallback_regate_plan" &&
|
|
1382
|
+
entry.evidenceId === input.evidenceId))
|
|
1383
|
+
return {
|
|
1384
|
+
adapterProfile: "managed_fallback_regate_plan_evidence_materializer",
|
|
1385
|
+
status: "blocked_before_regate_plan_evidence",
|
|
1386
|
+
writeAttempted: false,
|
|
1387
|
+
evidenceReloaded: true,
|
|
1388
|
+
workflowId,
|
|
1389
|
+
evidenceId: input.evidenceId,
|
|
1390
|
+
redactedBlockReason: "fallback regate plan evidence already exists",
|
|
1391
|
+
safeNextActions: ["/flowdesk-status"],
|
|
1392
|
+
authority: blockedAuthority,
|
|
1393
|
+
};
|
|
1394
|
+
const prepared = prepareFlowDeskSessionEvidenceWriteIntentV1({
|
|
1395
|
+
workflowId,
|
|
1396
|
+
evidenceId: input.evidenceId,
|
|
1397
|
+
record: input.regatePlan,
|
|
1398
|
+
});
|
|
1399
|
+
if (!prepared.ok || prepared.writeIntent === undefined)
|
|
1400
|
+
return {
|
|
1401
|
+
adapterProfile: "managed_fallback_regate_plan_evidence_materializer",
|
|
1402
|
+
status: "blocked_before_regate_plan_evidence",
|
|
1403
|
+
writeAttempted: false,
|
|
1404
|
+
evidenceReloaded: true,
|
|
1405
|
+
workflowId,
|
|
1406
|
+
evidenceId: input.evidenceId,
|
|
1407
|
+
redactedBlockReason: prepared.errors.join(", ") ||
|
|
1408
|
+
"fallback regate plan evidence intent invalid",
|
|
1409
|
+
safeNextActions: ["/flowdesk-status"],
|
|
1410
|
+
authority: blockedAuthority,
|
|
1411
|
+
};
|
|
1412
|
+
const applied = applyFlowDeskSessionEvidenceWriteIntentsV1(input.rootDir, [
|
|
1413
|
+
prepared.writeIntent,
|
|
1414
|
+
]);
|
|
1415
|
+
if (!applied.ok)
|
|
1416
|
+
return {
|
|
1417
|
+
adapterProfile: "managed_fallback_regate_plan_evidence_materializer",
|
|
1418
|
+
status: "blocked_before_regate_plan_evidence",
|
|
1419
|
+
writeAttempted: false,
|
|
1420
|
+
evidenceReloaded: true,
|
|
1421
|
+
workflowId,
|
|
1422
|
+
evidenceId: input.evidenceId,
|
|
1423
|
+
redactedBlockReason: applied.errors.join(", ") ||
|
|
1424
|
+
"fallback regate plan evidence write failed",
|
|
1425
|
+
safeNextActions: ["/flowdesk-status"],
|
|
1426
|
+
authority: blockedAuthority,
|
|
1427
|
+
};
|
|
1428
|
+
const postWriteReload = reloadFlowDeskSessionEvidenceV1({
|
|
1429
|
+
workflowId,
|
|
1430
|
+
rootDir: input.rootDir,
|
|
1431
|
+
});
|
|
1432
|
+
const persisted = postWriteReload.ok &&
|
|
1433
|
+
postWriteReload.entries.some((entry) => entry.evidenceClass === "fallback_regate_plan" &&
|
|
1434
|
+
entry.evidenceId === input.evidenceId &&
|
|
1435
|
+
entry.record.state === "full_regate_required");
|
|
1436
|
+
if (!persisted)
|
|
1437
|
+
return {
|
|
1438
|
+
adapterProfile: "managed_fallback_regate_plan_evidence_materializer",
|
|
1439
|
+
status: "blocked_before_regate_plan_evidence",
|
|
1440
|
+
writeAttempted: true,
|
|
1441
|
+
evidenceReloaded: false,
|
|
1442
|
+
workflowId,
|
|
1443
|
+
evidenceId: input.evidenceId,
|
|
1444
|
+
redactedBlockReason: "fallback regate plan evidence reload verification failed",
|
|
1445
|
+
safeNextActions: ["/flowdesk-status"],
|
|
1446
|
+
authority: blockedAuthority,
|
|
1447
|
+
};
|
|
1448
|
+
return {
|
|
1449
|
+
adapterProfile: "managed_fallback_regate_plan_evidence_materializer",
|
|
1450
|
+
status: "regate_plan_evidence_recorded",
|
|
1451
|
+
writeAttempted: true,
|
|
1452
|
+
evidenceReloaded: true,
|
|
1453
|
+
workflowId,
|
|
1454
|
+
evidenceId: input.evidenceId,
|
|
1455
|
+
safeNextActions: ["/flowdesk-status", "/flowdesk-run"],
|
|
1456
|
+
authority: {
|
|
1457
|
+
...blockedAuthority,
|
|
1458
|
+
regatePlanPersisted: true,
|
|
1459
|
+
},
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
export function prepareFlowDeskControlledExternalWriteAdapterV1(input) {
|
|
1463
|
+
const promotion = promoteFlowDeskExternalWriteAuthorityV1(input);
|
|
1464
|
+
if (!promotion.ok || promotion.external_write_authority_enabled !== true) {
|
|
1465
|
+
return {
|
|
1466
|
+
adapterProfile: "controlled_external_write_adapter",
|
|
1467
|
+
status: "blocked_before_write",
|
|
1468
|
+
writeAttempted: false,
|
|
1469
|
+
workflowId: input.request.workflow_id,
|
|
1470
|
+
attemptId: input.request.attempt_id,
|
|
1471
|
+
redactedBlockReason: promotion.errors.join(",") || "controlled external write blocked",
|
|
1472
|
+
safeNextActions: ["/flowdesk-status"],
|
|
1473
|
+
authority: controlledExternalWriteAuthority(false),
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
return {
|
|
1477
|
+
adapterProfile: "controlled_external_write_adapter",
|
|
1478
|
+
status: "write_ready",
|
|
1479
|
+
writeAttempted: false,
|
|
1480
|
+
workflowId: input.request.workflow_id,
|
|
1481
|
+
attemptId: input.request.attempt_id,
|
|
1482
|
+
targetKind: input.request.target_kind,
|
|
1483
|
+
targetRef: input.request.target_ref,
|
|
1484
|
+
safeNextActions: ["/flowdesk-status", "/flowdesk-export-debug"],
|
|
1485
|
+
authority: controlledExternalWriteAuthority(true),
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
export function prepareFlowDeskReviewerTypedVerdictAcceptanceAdapterV1(input) {
|
|
1489
|
+
const promotion = promoteFlowDeskReviewerTypedVerdictsV1(input);
|
|
1490
|
+
if (!promotion.ok ||
|
|
1491
|
+
promotion.typed_reviewer_verdict_acceptance_enabled !== true) {
|
|
1492
|
+
return {
|
|
1493
|
+
adapterProfile: "reviewer_typed_verdict_acceptance_adapter",
|
|
1494
|
+
status: "blocked_before_acceptance",
|
|
1495
|
+
workflowId: input.workflowId,
|
|
1496
|
+
attemptId: input.attemptId,
|
|
1497
|
+
acceptedVerdictIds: [],
|
|
1498
|
+
acceptedPerspectives: [],
|
|
1499
|
+
redactedBlockReason: promotion.errors.join(",") || "reviewer verdict acceptance blocked",
|
|
1500
|
+
safeNextActions: ["/flowdesk-status"],
|
|
1501
|
+
authority: reviewerTypedVerdictAuthority(false),
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
return {
|
|
1505
|
+
adapterProfile: "reviewer_typed_verdict_acceptance_adapter",
|
|
1506
|
+
status: "verdicts_accepted",
|
|
1507
|
+
workflowId: input.workflowId,
|
|
1508
|
+
attemptId: input.attemptId,
|
|
1509
|
+
acceptedVerdictIds: promotion.accepted_verdict_ids ?? [],
|
|
1510
|
+
acceptedPerspectives: promotion.accepted_perspectives ?? [],
|
|
1511
|
+
safeNextActions: ["/flowdesk-status", "/flowdesk-run"],
|
|
1512
|
+
authority: reviewerTypedVerdictAuthority(true),
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
function durableReviewerVerdictIds(reloadedEvidence) {
|
|
1516
|
+
return new Set(reloadedEvidence.entries
|
|
1517
|
+
.filter((entry) => entry.evidenceClass === "reviewer_verdict")
|
|
1518
|
+
.map((entry) => entry.record.verdict_id)
|
|
1519
|
+
.filter((value) => typeof value === "string"));
|
|
1520
|
+
}
|
|
1521
|
+
function durableCompleteLifecycleRefs(input) {
|
|
1522
|
+
const verdictIds = new Set(input.verdictIds);
|
|
1523
|
+
const refs = new Map();
|
|
1524
|
+
for (const entry of input.reloadedEvidence.entries) {
|
|
1525
|
+
if (entry.evidenceClass !== "lane_lifecycle")
|
|
1526
|
+
continue;
|
|
1527
|
+
const record = entry.record;
|
|
1528
|
+
if (record.workflow_id === input.workflowId &&
|
|
1529
|
+
record.attempt_id === input.attemptId &&
|
|
1530
|
+
record.state === "complete" &&
|
|
1531
|
+
typeof record.verdict_ref === "string" &&
|
|
1532
|
+
verdictIds.has(record.verdict_ref)) {
|
|
1533
|
+
refs.set(record.verdict_ref, entry.evidenceId);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
return refs;
|
|
1537
|
+
}
|
|
1538
|
+
export function prepareFlowDeskDurableReviewerVerdictLinkageAdapterV1(input) {
|
|
1539
|
+
const acceptance = prepareFlowDeskReviewerTypedVerdictAcceptanceAdapterV1({
|
|
1540
|
+
workflowId: input.workflowId,
|
|
1541
|
+
attemptId: input.attemptId,
|
|
1542
|
+
verdicts: input.verdicts,
|
|
1543
|
+
consumedApproval: input.consumedApproval,
|
|
1544
|
+
});
|
|
1545
|
+
if (acceptance.status !== "verdicts_accepted") {
|
|
1546
|
+
return {
|
|
1547
|
+
adapterProfile: "durable_reviewer_verdict_linkage_adapter",
|
|
1548
|
+
status: "blocked_before_durable_acceptance",
|
|
1549
|
+
workflowId: input.workflowId,
|
|
1550
|
+
attemptId: input.attemptId,
|
|
1551
|
+
linkedVerdictIds: [],
|
|
1552
|
+
linkedLifecycleRefs: [],
|
|
1553
|
+
redactedBlockReason: acceptance.redactedBlockReason ?? "reviewer verdict acceptance blocked",
|
|
1554
|
+
safeNextActions: ["/flowdesk-status"],
|
|
1555
|
+
authority: durableReviewerVerdictAuthority(false),
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
if (!input.reloadedEvidence.ok || input.reloadedEvidence.blocked.length > 0) {
|
|
1559
|
+
return {
|
|
1560
|
+
adapterProfile: "durable_reviewer_verdict_linkage_adapter",
|
|
1561
|
+
status: "blocked_before_durable_acceptance",
|
|
1562
|
+
workflowId: input.workflowId,
|
|
1563
|
+
attemptId: input.attemptId,
|
|
1564
|
+
linkedVerdictIds: [],
|
|
1565
|
+
linkedLifecycleRefs: [],
|
|
1566
|
+
redactedBlockReason: "reviewer durable evidence reload is blocked",
|
|
1567
|
+
safeNextActions: ["/flowdesk-status"],
|
|
1568
|
+
authority: durableReviewerVerdictAuthority(false),
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
const requiredVerdictIds = acceptance.acceptedVerdictIds;
|
|
1572
|
+
const verdictEvidence = durableReviewerVerdictIds(input.reloadedEvidence);
|
|
1573
|
+
const lifecycleRefs = durableCompleteLifecycleRefs({
|
|
1574
|
+
reloadedEvidence: input.reloadedEvidence,
|
|
1575
|
+
workflowId: input.workflowId,
|
|
1576
|
+
attemptId: input.attemptId,
|
|
1577
|
+
verdictIds: requiredVerdictIds,
|
|
1578
|
+
});
|
|
1579
|
+
const missingVerdicts = requiredVerdictIds.filter((verdictId) => !verdictEvidence.has(verdictId));
|
|
1580
|
+
const missingLifecycle = requiredVerdictIds.filter((verdictId) => !lifecycleRefs.has(verdictId));
|
|
1581
|
+
if (missingVerdicts.length > 0 || missingLifecycle.length > 0) {
|
|
1582
|
+
return {
|
|
1583
|
+
adapterProfile: "durable_reviewer_verdict_linkage_adapter",
|
|
1584
|
+
status: "blocked_before_durable_acceptance",
|
|
1585
|
+
workflowId: input.workflowId,
|
|
1586
|
+
attemptId: input.attemptId,
|
|
1587
|
+
linkedVerdictIds: requiredVerdictIds.filter((verdictId) => verdictEvidence.has(verdictId)),
|
|
1588
|
+
linkedLifecycleRefs: [...lifecycleRefs.values()],
|
|
1589
|
+
redactedBlockReason: [
|
|
1590
|
+
missingVerdicts.length > 0
|
|
1591
|
+
? "missing durable reviewer verdict evidence"
|
|
1592
|
+
: undefined,
|
|
1593
|
+
missingLifecycle.length > 0
|
|
1594
|
+
? "missing complete lane lifecycle evidence"
|
|
1595
|
+
: undefined,
|
|
1596
|
+
]
|
|
1597
|
+
.filter((reason) => reason !== undefined)
|
|
1598
|
+
.join(", "),
|
|
1599
|
+
safeNextActions: ["/flowdesk-status"],
|
|
1600
|
+
authority: durableReviewerVerdictAuthority(false),
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
return {
|
|
1604
|
+
adapterProfile: "durable_reviewer_verdict_linkage_adapter",
|
|
1605
|
+
status: "durable_verdicts_accepted",
|
|
1606
|
+
workflowId: input.workflowId,
|
|
1607
|
+
attemptId: input.attemptId,
|
|
1608
|
+
linkedVerdictIds: requiredVerdictIds,
|
|
1609
|
+
linkedLifecycleRefs: [...lifecycleRefs.values()],
|
|
1610
|
+
safeNextActions: ["/flowdesk-status", "/flowdesk-run"],
|
|
1611
|
+
authority: durableReviewerVerdictAuthority(true),
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
export function materializeFlowDeskObservedReviewerVerdictEvidenceV1(input) {
|
|
1615
|
+
const evidenceId = input.evidenceId ?? input.observation.verdictId;
|
|
1616
|
+
if (typeof input.rootDir !== "string" || input.rootDir.trim().length === 0)
|
|
1617
|
+
return blockObservedReviewerVerdictEvidence({
|
|
1618
|
+
observation: input.observation,
|
|
1619
|
+
evidenceId,
|
|
1620
|
+
reason: "rootDir is required",
|
|
1621
|
+
});
|
|
1622
|
+
if (input.observation.status !== "verdict_observed" ||
|
|
1623
|
+
input.observation.verdict === undefined)
|
|
1624
|
+
return blockObservedReviewerVerdictEvidence({
|
|
1625
|
+
observation: input.observation,
|
|
1626
|
+
evidenceId,
|
|
1627
|
+
reason: "reviewer verdict observation must be verdict_observed",
|
|
1628
|
+
});
|
|
1629
|
+
if (evidenceId === undefined)
|
|
1630
|
+
return blockObservedReviewerVerdictEvidence({
|
|
1631
|
+
observation: input.observation,
|
|
1632
|
+
reason: "reviewer verdict evidence id is required",
|
|
1633
|
+
});
|
|
1634
|
+
const validation = validateTopTierReviewVerdictV1(input.observation.verdict);
|
|
1635
|
+
if (!validation.ok)
|
|
1636
|
+
return blockObservedReviewerVerdictEvidence({
|
|
1637
|
+
observation: input.observation,
|
|
1638
|
+
evidenceId,
|
|
1639
|
+
reason: validation.errors.join(", ") || "reviewer verdict is invalid",
|
|
1640
|
+
});
|
|
1641
|
+
const preWriteReload = reloadFlowDeskSessionEvidenceV1({
|
|
1642
|
+
workflowId: input.observation.workflowId,
|
|
1643
|
+
rootDir: input.rootDir,
|
|
1644
|
+
});
|
|
1645
|
+
if (!preWriteReload.ok || preWriteReload.blocked.length > 0)
|
|
1646
|
+
return blockObservedReviewerVerdictEvidence({
|
|
1647
|
+
observation: input.observation,
|
|
1648
|
+
evidenceId,
|
|
1649
|
+
reason: "reviewer verdict pre-write evidence reload failed",
|
|
1650
|
+
evidenceReloaded: false,
|
|
1651
|
+
});
|
|
1652
|
+
if (preWriteReload.entries.some((entry) => entry.evidenceClass === "reviewer_verdict" &&
|
|
1653
|
+
entry.evidenceId === evidenceId))
|
|
1654
|
+
return blockObservedReviewerVerdictEvidence({
|
|
1655
|
+
observation: input.observation,
|
|
1656
|
+
evidenceId,
|
|
1657
|
+
reason: "reviewer verdict evidence already exists",
|
|
1658
|
+
evidenceReloaded: true,
|
|
1659
|
+
});
|
|
1660
|
+
const prepared = prepareFlowDeskSessionEvidenceWriteIntentV1({
|
|
1661
|
+
workflowId: input.observation.workflowId,
|
|
1662
|
+
evidenceId,
|
|
1663
|
+
record: input.observation.verdict,
|
|
1664
|
+
});
|
|
1665
|
+
if (!prepared.ok || prepared.writeIntent === undefined)
|
|
1666
|
+
return blockObservedReviewerVerdictEvidence({
|
|
1667
|
+
observation: input.observation,
|
|
1668
|
+
evidenceId,
|
|
1669
|
+
reason: prepared.errors.join(", ") ||
|
|
1670
|
+
"reviewer verdict evidence intent invalid",
|
|
1671
|
+
evidenceReloaded: true,
|
|
1672
|
+
});
|
|
1673
|
+
const applied = applyFlowDeskSessionEvidenceWriteIntentsV1(input.rootDir, [
|
|
1674
|
+
prepared.writeIntent,
|
|
1675
|
+
]);
|
|
1676
|
+
if (!applied.ok)
|
|
1677
|
+
return blockObservedReviewerVerdictEvidence({
|
|
1678
|
+
observation: input.observation,
|
|
1679
|
+
evidenceId,
|
|
1680
|
+
reason: applied.errors.join(", ") || "reviewer verdict evidence write failed",
|
|
1681
|
+
evidenceReloaded: true,
|
|
1682
|
+
});
|
|
1683
|
+
const postWriteReload = reloadFlowDeskSessionEvidenceV1({
|
|
1684
|
+
workflowId: input.observation.workflowId,
|
|
1685
|
+
rootDir: input.rootDir,
|
|
1686
|
+
});
|
|
1687
|
+
const persisted = postWriteReload.ok &&
|
|
1688
|
+
postWriteReload.entries.some((entry) => entry.evidenceClass === "reviewer_verdict" &&
|
|
1689
|
+
entry.evidenceId === evidenceId &&
|
|
1690
|
+
entry.record.verdict_id === input.observation.verdictId);
|
|
1691
|
+
if (!persisted)
|
|
1692
|
+
return blockObservedReviewerVerdictEvidence({
|
|
1693
|
+
observation: input.observation,
|
|
1694
|
+
evidenceId,
|
|
1695
|
+
reason: "reviewer verdict evidence reload verification failed",
|
|
1696
|
+
evidenceReloaded: false,
|
|
1697
|
+
});
|
|
1698
|
+
return {
|
|
1699
|
+
adapterProfile: "observed_reviewer_verdict_evidence_materializer",
|
|
1700
|
+
status: "verdict_evidence_recorded",
|
|
1701
|
+
writeAttempted: true,
|
|
1702
|
+
workflowId: input.observation.workflowId,
|
|
1703
|
+
sessionRef: input.observation.sessionRef,
|
|
1704
|
+
lanePlanRef: input.observation.lanePlanRef,
|
|
1705
|
+
bindingRef: input.observation.bindingRef,
|
|
1706
|
+
perspective: input.observation.perspective,
|
|
1707
|
+
verdictId: input.observation.verdictId,
|
|
1708
|
+
evidenceId,
|
|
1709
|
+
evidenceReloaded: true,
|
|
1710
|
+
safeNextActions: ["/flowdesk-status"],
|
|
1711
|
+
authority: observedReviewerVerdictEvidenceAuthority(true),
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
function idempotencySnapshotFrom(entry) {
|
|
1715
|
+
if (entry?.evidenceClass !== "dispatch_idempotency")
|
|
1716
|
+
return undefined;
|
|
1717
|
+
return entry.record;
|
|
1718
|
+
}
|
|
1719
|
+
function existingIdempotencySnapshot(reloadedEvidence) {
|
|
1720
|
+
const snapshots = reloadedEvidence.entries
|
|
1721
|
+
.map(idempotencySnapshotFrom)
|
|
1722
|
+
.filter((snapshot) => snapshot !== undefined);
|
|
1723
|
+
const latest = snapshots[snapshots.length - 1];
|
|
1724
|
+
if (latest === undefined)
|
|
1725
|
+
return undefined;
|
|
1726
|
+
const entriesByKey = new Map();
|
|
1727
|
+
for (const snapshot of snapshots) {
|
|
1728
|
+
for (const entry of snapshot.entries)
|
|
1729
|
+
entriesByKey.set(entry.idempotency_key, entry);
|
|
1730
|
+
}
|
|
1731
|
+
return { ...latest, entries: [...entriesByKey.values()] };
|
|
1732
|
+
}
|
|
1733
|
+
function currentIdempotencySnapshot(rootDir, workflowId) {
|
|
1734
|
+
const reloaded = reloadFlowDeskSessionEvidenceV1({ workflowId, rootDir });
|
|
1735
|
+
if (!reloaded.ok || reloaded.blocked.length > 0)
|
|
1736
|
+
return { ok: false, redactedFailureReason: "reservation reload failed" };
|
|
1737
|
+
return { ok: true, snapshot: existingIdempotencySnapshot(reloaded) };
|
|
1738
|
+
}
|
|
1739
|
+
function reservationKey(manifest) {
|
|
1740
|
+
return `${manifest.workflow_id}:${manifest.attempt_id}:${manifest.idempotency_key}`;
|
|
1741
|
+
}
|
|
1742
|
+
function snapshotRefFor(manifest, suffix) {
|
|
1743
|
+
return `idempotency-${manifest.attempt_id}-${suffix}`
|
|
1744
|
+
.replaceAll(/[^A-Za-z0-9_.:-]/g, "-")
|
|
1745
|
+
.slice(0, 96);
|
|
1746
|
+
}
|
|
1747
|
+
function hasMatchingEntry(snapshot, manifest, state) {
|
|
1748
|
+
return snapshot.entries.some((entry) => entry.attempt_id === manifest.attempt_id &&
|
|
1749
|
+
entry.idempotency_key === manifest.idempotency_key &&
|
|
1750
|
+
(state === undefined || entry.state === state));
|
|
1751
|
+
}
|
|
1752
|
+
function materializeSnapshot(input) {
|
|
1753
|
+
const prepared = prepareFlowDeskSessionEvidenceWriteIntentV1({
|
|
1754
|
+
workflowId: input.manifest.workflow_id,
|
|
1755
|
+
evidenceId: input.evidenceId,
|
|
1756
|
+
record: input.snapshot,
|
|
1757
|
+
});
|
|
1758
|
+
if (!prepared.ok || prepared.writeIntent === undefined) {
|
|
1759
|
+
return {
|
|
1760
|
+
ok: false,
|
|
1761
|
+
reservationEvidenceReloaded: false,
|
|
1762
|
+
redactedFailureReason: "reservation write intent invalid",
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
const applied = applyFlowDeskSessionEvidenceWriteIntentsV1(input.rootDir, [
|
|
1766
|
+
prepared.writeIntent,
|
|
1767
|
+
]);
|
|
1768
|
+
if (!applied.ok) {
|
|
1769
|
+
return {
|
|
1770
|
+
ok: false,
|
|
1771
|
+
reservationEvidenceReloaded: false,
|
|
1772
|
+
redactedFailureReason: "reservation materialization failed",
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
const reloaded = reloadFlowDeskSessionEvidenceV1({
|
|
1776
|
+
workflowId: input.manifest.workflow_id,
|
|
1777
|
+
rootDir: input.rootDir,
|
|
1778
|
+
});
|
|
1779
|
+
if (!reloaded.ok || reloaded.blocked.length > 0) {
|
|
1780
|
+
return {
|
|
1781
|
+
ok: false,
|
|
1782
|
+
reservationEvidenceReloaded: false,
|
|
1783
|
+
redactedFailureReason: "reservation reload failed",
|
|
1784
|
+
};
|
|
1785
|
+
}
|
|
1786
|
+
const reloadedSnapshot = idempotencySnapshotFrom(reloaded.entries.find((entry) => entry.evidenceClass === "dispatch_idempotency" &&
|
|
1787
|
+
entry.evidenceId === input.evidenceId));
|
|
1788
|
+
if (reloadedSnapshot === undefined ||
|
|
1789
|
+
!hasMatchingEntry(reloadedSnapshot, input.manifest, input.expectedState)) {
|
|
1790
|
+
return {
|
|
1791
|
+
ok: false,
|
|
1792
|
+
reservationEvidenceReloaded: false,
|
|
1793
|
+
redactedFailureReason: "reservation evidence missing after reload",
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
return {
|
|
1797
|
+
ok: true,
|
|
1798
|
+
reservationEvidenceReloaded: true,
|
|
1799
|
+
snapshot: reloadedSnapshot,
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
export function createFlowDeskManagedDispatchBetaDurableReservationStoreV1(options) {
|
|
1803
|
+
const now = options.now ?? (() => new Date());
|
|
1804
|
+
const reservedSnapshots = new Map();
|
|
1805
|
+
return {
|
|
1806
|
+
reserve(input) {
|
|
1807
|
+
const recordedAt = now().toISOString();
|
|
1808
|
+
const evidenceId = snapshotRefFor(input.manifest, "reserved");
|
|
1809
|
+
const current = currentIdempotencySnapshot(options.rootDir, input.manifest.workflow_id);
|
|
1810
|
+
if (!current.ok) {
|
|
1811
|
+
return {
|
|
1812
|
+
ok: false,
|
|
1813
|
+
reservationEvidenceReloaded: false,
|
|
1814
|
+
redactedFailureReason: current.redactedFailureReason,
|
|
1815
|
+
};
|
|
1816
|
+
}
|
|
1817
|
+
const reservation = prepareFlowDeskDispatchIdempotencyReservationV1({
|
|
1818
|
+
workflowId: input.manifest.workflow_id,
|
|
1819
|
+
attemptId: input.manifest.attempt_id,
|
|
1820
|
+
idempotencyKey: input.manifest.idempotency_key,
|
|
1821
|
+
snapshotRef: evidenceId,
|
|
1822
|
+
reservedAt: recordedAt,
|
|
1823
|
+
existingSnapshot: current.snapshot ??
|
|
1824
|
+
existingIdempotencySnapshot(input.reloadedEvidence),
|
|
1825
|
+
});
|
|
1826
|
+
if (!reservation.reservation_prepared ||
|
|
1827
|
+
reservation.snapshot === undefined) {
|
|
1828
|
+
return {
|
|
1829
|
+
ok: false,
|
|
1830
|
+
reservationEvidenceReloaded: false,
|
|
1831
|
+
redactedFailureReason: "reservation preparation blocked",
|
|
1832
|
+
};
|
|
1833
|
+
}
|
|
1834
|
+
const materialized = materializeSnapshot({
|
|
1835
|
+
rootDir: options.rootDir,
|
|
1836
|
+
manifest: input.manifest,
|
|
1837
|
+
evidenceId,
|
|
1838
|
+
snapshot: reservation.snapshot,
|
|
1839
|
+
expectedState: "reserved",
|
|
1840
|
+
});
|
|
1841
|
+
if (materialized.ok && materialized.snapshot !== undefined)
|
|
1842
|
+
reservedSnapshots.set(reservationKey(input.manifest), materialized.snapshot);
|
|
1843
|
+
return {
|
|
1844
|
+
ok: materialized.ok,
|
|
1845
|
+
reservationEvidenceReloaded: materialized.reservationEvidenceReloaded,
|
|
1846
|
+
...(materialized.redactedFailureReason === undefined
|
|
1847
|
+
? {}
|
|
1848
|
+
: { redactedFailureReason: materialized.redactedFailureReason }),
|
|
1849
|
+
};
|
|
1850
|
+
},
|
|
1851
|
+
recordDispatchFailure(input) {
|
|
1852
|
+
const recordedAt = now().toISOString();
|
|
1853
|
+
const evidenceId = snapshotRefFor(input.manifest, "dispatch-failed");
|
|
1854
|
+
const current = currentIdempotencySnapshot(options.rootDir, input.manifest.workflow_id);
|
|
1855
|
+
if (!current.ok) {
|
|
1856
|
+
return {
|
|
1857
|
+
ok: false,
|
|
1858
|
+
reservationEvidenceReloaded: false,
|
|
1859
|
+
redactedFailureReason: current.redactedFailureReason,
|
|
1860
|
+
};
|
|
1861
|
+
}
|
|
1862
|
+
const stateUpdate = prepareFlowDeskDispatchIdempotencyStateUpdateV1({
|
|
1863
|
+
workflowId: input.manifest.workflow_id,
|
|
1864
|
+
attemptId: input.manifest.attempt_id,
|
|
1865
|
+
idempotencyKey: input.manifest.idempotency_key,
|
|
1866
|
+
snapshotRef: evidenceId,
|
|
1867
|
+
recordedAt,
|
|
1868
|
+
nextState: "dispatch_failed",
|
|
1869
|
+
existingSnapshot: reservedSnapshots.get(reservationKey(input.manifest)) ??
|
|
1870
|
+
current.snapshot ??
|
|
1871
|
+
existingIdempotencySnapshot(input.reloadedEvidence),
|
|
1872
|
+
});
|
|
1873
|
+
if (!stateUpdate.state_update_prepared ||
|
|
1874
|
+
stateUpdate.snapshot === undefined) {
|
|
1875
|
+
return {
|
|
1876
|
+
ok: false,
|
|
1877
|
+
reservationEvidenceReloaded: false,
|
|
1878
|
+
redactedFailureReason: "failure state preparation blocked",
|
|
1879
|
+
};
|
|
1880
|
+
}
|
|
1881
|
+
const materialized = materializeSnapshot({
|
|
1882
|
+
rootDir: options.rootDir,
|
|
1883
|
+
manifest: input.manifest,
|
|
1884
|
+
evidenceId,
|
|
1885
|
+
snapshot: stateUpdate.snapshot,
|
|
1886
|
+
expectedState: "dispatch_failed",
|
|
1887
|
+
});
|
|
1888
|
+
if (materialized.ok && materialized.snapshot !== undefined)
|
|
1889
|
+
reservedSnapshots.set(reservationKey(input.manifest), materialized.snapshot);
|
|
1890
|
+
return {
|
|
1891
|
+
ok: materialized.ok,
|
|
1892
|
+
reservationEvidenceReloaded: materialized.reservationEvidenceReloaded,
|
|
1893
|
+
...(materialized.redactedFailureReason === undefined
|
|
1894
|
+
? {}
|
|
1895
|
+
: { redactedFailureReason: materialized.redactedFailureReason }),
|
|
1896
|
+
};
|
|
1897
|
+
},
|
|
12
1898
|
};
|
|
13
1899
|
}
|
|
14
1900
|
function enabledDispatchAuthority() {
|
|
@@ -16,15 +1902,19 @@ function enabledDispatchAuthority() {
|
|
|
16
1902
|
...disabledAuthority(),
|
|
17
1903
|
realOpenCodeDispatch: true,
|
|
18
1904
|
providerCall: true,
|
|
19
|
-
runtimeExecution: true
|
|
1905
|
+
runtimeExecution: true,
|
|
20
1906
|
};
|
|
21
1907
|
}
|
|
22
1908
|
function verificationFor(input) {
|
|
23
1909
|
return {
|
|
24
1910
|
ambiguityQuarantined: input.ambiguityQuarantined === true,
|
|
25
|
-
...(input.configuredVerificationRef === undefined
|
|
26
|
-
|
|
27
|
-
|
|
1911
|
+
...(input.configuredVerificationRef === undefined
|
|
1912
|
+
? {}
|
|
1913
|
+
: { configuredVerificationRef: input.configuredVerificationRef }),
|
|
1914
|
+
...(input.preDispatchAuditRef === undefined
|
|
1915
|
+
? {}
|
|
1916
|
+
: { preDispatchAuditRef: input.preDispatchAuditRef }),
|
|
1917
|
+
defaultRelease1ServerBehaviorUnchanged: true,
|
|
28
1918
|
};
|
|
29
1919
|
}
|
|
30
1920
|
function blocked(input, guardDecision, redactedBlockReason = guardDecision.redacted_reason) {
|
|
@@ -35,7 +1925,7 @@ function blocked(input, guardDecision, redactedBlockReason = guardDecision.redac
|
|
|
35
1925
|
guardDecision,
|
|
36
1926
|
redactedBlockReason,
|
|
37
1927
|
authority: disabledAuthority(),
|
|
38
|
-
verification: verificationFor(input)
|
|
1928
|
+
verification: verificationFor(input),
|
|
39
1929
|
};
|
|
40
1930
|
}
|
|
41
1931
|
function parseProviderQualifiedModelId(value) {
|
|
@@ -48,17 +1938,51 @@ function parseProviderQualifiedModelId(value) {
|
|
|
48
1938
|
return undefined;
|
|
49
1939
|
return { providerID, modelID };
|
|
50
1940
|
}
|
|
1941
|
+
function opencodeRuntimeProviderIDForFlowDeskProviderFamily(providerFamily) {
|
|
1942
|
+
switch (providerFamily) {
|
|
1943
|
+
case "claude":
|
|
1944
|
+
return "anthropic";
|
|
1945
|
+
case "gemini":
|
|
1946
|
+
return "google";
|
|
1947
|
+
case "openai":
|
|
1948
|
+
return "openai";
|
|
1949
|
+
default:
|
|
1950
|
+
return undefined;
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
function opencodeRuntimeModelForFlowDeskModel(model) {
|
|
1954
|
+
const providerID = opencodeRuntimeProviderIDForFlowDeskProviderFamily(model.providerID);
|
|
1955
|
+
return providerID === undefined
|
|
1956
|
+
? undefined
|
|
1957
|
+
: { providerID, modelID: model.modelID };
|
|
1958
|
+
}
|
|
51
1959
|
function refFrom(label, value) {
|
|
52
|
-
const safe = value
|
|
1960
|
+
const safe = value
|
|
1961
|
+
.replaceAll(/[^A-Za-z0-9_.:-]/g, "-")
|
|
1962
|
+
.replaceAll(/-+/g, "-")
|
|
1963
|
+
.slice(0, 96);
|
|
53
1964
|
return `${label}-${safe.length > 0 ? safe : "unknown"}`;
|
|
54
1965
|
}
|
|
55
1966
|
function asRecord(value) {
|
|
56
|
-
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
1967
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
1968
|
+
? value
|
|
1969
|
+
: undefined;
|
|
57
1970
|
}
|
|
58
1971
|
function responseData(value) {
|
|
59
1972
|
const record = asRecord(value);
|
|
60
1973
|
return record !== undefined && "data" in record ? record.data : value;
|
|
61
1974
|
}
|
|
1975
|
+
function isSdkErrorResponse(value) {
|
|
1976
|
+
const record = asRecord(value);
|
|
1977
|
+
const data = asRecord(responseData(value));
|
|
1978
|
+
return record?.error !== undefined || data?.error !== undefined;
|
|
1979
|
+
}
|
|
1980
|
+
async function callSdkWithLegacyFallback(method, thisArg, currentOptions, legacyOptions) {
|
|
1981
|
+
const current = await method.call(thisArg, currentOptions);
|
|
1982
|
+
if (!isSdkErrorResponse(current))
|
|
1983
|
+
return current;
|
|
1984
|
+
return method.call(thisArg, legacyOptions);
|
|
1985
|
+
}
|
|
62
1986
|
function arrayData(value) {
|
|
63
1987
|
const data = responseData(value);
|
|
64
1988
|
if (Array.isArray(data))
|
|
@@ -71,10 +1995,33 @@ function modelRef(value) {
|
|
|
71
1995
|
if (record === undefined)
|
|
72
1996
|
return undefined;
|
|
73
1997
|
const providerID = typeof record.providerID === "string" ? record.providerID : undefined;
|
|
74
|
-
const modelID = typeof record.modelID === "string"
|
|
75
|
-
|
|
1998
|
+
const modelID = typeof record.modelID === "string"
|
|
1999
|
+
? record.modelID
|
|
2000
|
+
: typeof record.id === "string"
|
|
2001
|
+
? record.id
|
|
2002
|
+
: undefined;
|
|
2003
|
+
return providerID !== undefined && modelID !== undefined
|
|
2004
|
+
? refFrom("model", `${providerID}-${modelID}`)
|
|
2005
|
+
: undefined;
|
|
2006
|
+
}
|
|
2007
|
+
function sessionIdFromResponse(value) {
|
|
2008
|
+
const data = responseData(value);
|
|
2009
|
+
const record = asRecord(data);
|
|
2010
|
+
return typeof record?.id === "string" && record.id.trim().length > 0
|
|
2011
|
+
? record.id
|
|
2012
|
+
: undefined;
|
|
2013
|
+
}
|
|
2014
|
+
function agentIdFromRef(value) {
|
|
2015
|
+
if (value === undefined || !value.startsWith("agent-"))
|
|
2016
|
+
return undefined;
|
|
2017
|
+
const agent = value.slice("agent-".length).trim();
|
|
2018
|
+
return agent.length > 0 ? agent : undefined;
|
|
76
2019
|
}
|
|
77
2020
|
function firstMessageRef(value) {
|
|
2021
|
+
const direct = asRecord(responseData(value));
|
|
2022
|
+
const directInfo = asRecord(direct?.info) ?? direct;
|
|
2023
|
+
if (typeof directInfo?.id === "string")
|
|
2024
|
+
return refFrom("message", directInfo.id);
|
|
78
2025
|
const messages = arrayData(value);
|
|
79
2026
|
for (const message of messages) {
|
|
80
2027
|
const record = asRecord(message);
|
|
@@ -84,6 +2031,133 @@ function firstMessageRef(value) {
|
|
|
84
2031
|
}
|
|
85
2032
|
return undefined;
|
|
86
2033
|
}
|
|
2034
|
+
function lifecycleMessageRefFrom(value) {
|
|
2035
|
+
if (value === undefined)
|
|
2036
|
+
return undefined;
|
|
2037
|
+
if (value.startsWith("msg-"))
|
|
2038
|
+
return value;
|
|
2039
|
+
if (value.startsWith("message-"))
|
|
2040
|
+
return `msg-${value.slice("message-".length)}`;
|
|
2041
|
+
return value;
|
|
2042
|
+
}
|
|
2043
|
+
function messageTextCandidates(value) {
|
|
2044
|
+
return arrayData(value).flatMap((message) => {
|
|
2045
|
+
const record = asRecord(message);
|
|
2046
|
+
const parts = Array.isArray(record?.parts) ? record.parts : [];
|
|
2047
|
+
return parts
|
|
2048
|
+
.flatMap((part) => {
|
|
2049
|
+
const partRecord = asRecord(part);
|
|
2050
|
+
const text = typeof partRecord?.text === "string"
|
|
2051
|
+
? partRecord.text
|
|
2052
|
+
: typeof partRecord?.content === "string"
|
|
2053
|
+
? partRecord.content
|
|
2054
|
+
: undefined;
|
|
2055
|
+
return text === undefined || text.length > 20_000 ? [] : [text.trim()];
|
|
2056
|
+
})
|
|
2057
|
+
.filter((text) => text.startsWith("{") && text.endsWith("}"));
|
|
2058
|
+
});
|
|
2059
|
+
}
|
|
2060
|
+
function parseJsonCandidate(text) {
|
|
2061
|
+
try {
|
|
2062
|
+
return JSON.parse(text);
|
|
2063
|
+
}
|
|
2064
|
+
catch {
|
|
2065
|
+
return undefined;
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
function topTierVerdictCandidates(messagesResponse) {
|
|
2069
|
+
const direct = arrayData(messagesResponse).filter((message) => asRecord(message)?.schema_version ===
|
|
2070
|
+
"flowdesk.top_tier_review_verdict.v1");
|
|
2071
|
+
const parsed = messageTextCandidates(messagesResponse)
|
|
2072
|
+
.map(parseJsonCandidate)
|
|
2073
|
+
.filter((candidate) => candidate !== undefined);
|
|
2074
|
+
return [...direct, ...parsed];
|
|
2075
|
+
}
|
|
2076
|
+
function verdictMatchesRequest(verdict, request) {
|
|
2077
|
+
return [
|
|
2078
|
+
verdict.workflow_id === request.workflowId
|
|
2079
|
+
? undefined
|
|
2080
|
+
: "verdict workflow_id mismatch",
|
|
2081
|
+
verdict.lane_plan_ref === request.lanePlanRef
|
|
2082
|
+
? undefined
|
|
2083
|
+
: "verdict lane_plan_ref mismatch",
|
|
2084
|
+
verdict.binding_ref === request.bindingRef
|
|
2085
|
+
? undefined
|
|
2086
|
+
: "verdict binding_ref mismatch",
|
|
2087
|
+
verdict.perspective === request.perspective
|
|
2088
|
+
? undefined
|
|
2089
|
+
: "verdict perspective mismatch",
|
|
2090
|
+
].filter((error) => error !== undefined);
|
|
2091
|
+
}
|
|
2092
|
+
export async function observeInjectedSdkReviewerVerdictV1(input) {
|
|
2093
|
+
const base = {
|
|
2094
|
+
adapterProfile: "injected_sdk_reviewer_verdict_observation",
|
|
2095
|
+
sessionRef: refFrom("session", input.request.sessionId),
|
|
2096
|
+
workflowId: input.request.workflowId,
|
|
2097
|
+
lanePlanRef: input.request.lanePlanRef,
|
|
2098
|
+
bindingRef: input.request.bindingRef,
|
|
2099
|
+
perspective: input.request.perspective,
|
|
2100
|
+
authority: disabledAuthority(),
|
|
2101
|
+
};
|
|
2102
|
+
const messages = input.client.session.messages;
|
|
2103
|
+
if (messages === undefined) {
|
|
2104
|
+
return {
|
|
2105
|
+
...base,
|
|
2106
|
+
status: "observation_unavailable",
|
|
2107
|
+
observationAttempted: false,
|
|
2108
|
+
redactedErrors: ["session_messages_api_missing"],
|
|
2109
|
+
};
|
|
2110
|
+
}
|
|
2111
|
+
try {
|
|
2112
|
+
const messagesResponse = await callSdkWithLegacyFallback(messages, input.client.session, {
|
|
2113
|
+
sessionID: input.request.sessionId,
|
|
2114
|
+
...(input.request.directory === undefined
|
|
2115
|
+
? {}
|
|
2116
|
+
: { directory: input.request.directory }),
|
|
2117
|
+
}, {
|
|
2118
|
+
path: { id: input.request.sessionId },
|
|
2119
|
+
...(input.request.directory === undefined
|
|
2120
|
+
? {}
|
|
2121
|
+
: { query: { directory: input.request.directory } }),
|
|
2122
|
+
});
|
|
2123
|
+
const errors = [];
|
|
2124
|
+
for (const candidate of topTierVerdictCandidates(messagesResponse)) {
|
|
2125
|
+
const validation = validateTopTierReviewVerdictV1(candidate);
|
|
2126
|
+
if (!validation.ok) {
|
|
2127
|
+
errors.push(...validation.errors.slice(0, 5));
|
|
2128
|
+
continue;
|
|
2129
|
+
}
|
|
2130
|
+
const verdict = candidate;
|
|
2131
|
+
const matchErrors = verdictMatchesRequest(verdict, input.request);
|
|
2132
|
+
if (matchErrors.length > 0) {
|
|
2133
|
+
errors.push(...matchErrors);
|
|
2134
|
+
continue;
|
|
2135
|
+
}
|
|
2136
|
+
return {
|
|
2137
|
+
...base,
|
|
2138
|
+
status: "verdict_observed",
|
|
2139
|
+
observationAttempted: true,
|
|
2140
|
+
verdictId: verdict.verdict_id,
|
|
2141
|
+
verdict,
|
|
2142
|
+
redactedErrors: [],
|
|
2143
|
+
};
|
|
2144
|
+
}
|
|
2145
|
+
return {
|
|
2146
|
+
...base,
|
|
2147
|
+
status: errors.length > 0 ? "invalid_verdict" : "missing_verdict",
|
|
2148
|
+
observationAttempted: true,
|
|
2149
|
+
redactedErrors: [...new Set(errors)],
|
|
2150
|
+
};
|
|
2151
|
+
}
|
|
2152
|
+
catch {
|
|
2153
|
+
return {
|
|
2154
|
+
...base,
|
|
2155
|
+
status: "observation_failed",
|
|
2156
|
+
observationAttempted: true,
|
|
2157
|
+
redactedErrors: ["session_messages_read_failed"],
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
87
2161
|
export async function observeInjectedSdkLaneV1(input) {
|
|
88
2162
|
const base = {
|
|
89
2163
|
adapterProfile: "injected_sdk_lane_observation_probe",
|
|
@@ -91,7 +2165,7 @@ export async function observeInjectedSdkLaneV1(input) {
|
|
|
91
2165
|
laneId: input.request.laneId,
|
|
92
2166
|
requestedAgentRef: refFrom("agent", input.request.requestedAgent),
|
|
93
2167
|
requestedModelRef: refFrom("model", input.request.requestedProviderQualifiedModelId),
|
|
94
|
-
authority: disabledAuthority()
|
|
2168
|
+
authority: disabledAuthority(),
|
|
95
2169
|
};
|
|
96
2170
|
const children = input.client.session.children;
|
|
97
2171
|
if (children === undefined) {
|
|
@@ -99,24 +2173,45 @@ export async function observeInjectedSdkLaneV1(input) {
|
|
|
99
2173
|
...base,
|
|
100
2174
|
status: "observation_unavailable",
|
|
101
2175
|
observationAttempted: false,
|
|
102
|
-
missingLabels: ["session_children_api_missing"]
|
|
2176
|
+
missingLabels: ["session_children_api_missing"],
|
|
103
2177
|
};
|
|
104
2178
|
}
|
|
105
2179
|
try {
|
|
106
|
-
const childrenResponse = await children
|
|
2180
|
+
const childrenResponse = await callSdkWithLegacyFallback(children, input.client.session, {
|
|
2181
|
+
sessionID: input.request.parentSessionId,
|
|
2182
|
+
...(input.request.directory === undefined
|
|
2183
|
+
? {}
|
|
2184
|
+
: { directory: input.request.directory }),
|
|
2185
|
+
}, {
|
|
107
2186
|
path: { id: input.request.parentSessionId },
|
|
108
|
-
...(input.request.directory === undefined
|
|
2187
|
+
...(input.request.directory === undefined
|
|
2188
|
+
? {}
|
|
2189
|
+
: { query: { directory: input.request.directory } }),
|
|
109
2190
|
});
|
|
110
|
-
const childRecord = arrayData(childrenResponse)
|
|
2191
|
+
const childRecord = arrayData(childrenResponse)
|
|
2192
|
+
.map(asRecord)
|
|
2193
|
+
.find((record) => record !== undefined);
|
|
111
2194
|
const childSessionId = typeof childRecord?.id === "string" ? childRecord.id : undefined;
|
|
112
|
-
const childSessionRef = childSessionId === undefined
|
|
113
|
-
|
|
2195
|
+
const childSessionRef = childSessionId === undefined
|
|
2196
|
+
? undefined
|
|
2197
|
+
: refFrom("child-session", childSessionId);
|
|
2198
|
+
const observedAgentRef = typeof childRecord?.agent === "string"
|
|
2199
|
+
? refFrom("agent", childRecord.agent)
|
|
2200
|
+
: undefined;
|
|
114
2201
|
const observedModelRef = modelRef(childRecord?.model);
|
|
115
2202
|
let messageRef;
|
|
116
|
-
if (childSessionId !== undefined &&
|
|
117
|
-
|
|
2203
|
+
if (childSessionId !== undefined &&
|
|
2204
|
+
input.client.session.messages !== undefined) {
|
|
2205
|
+
const messagesResponse = await callSdkWithLegacyFallback(input.client.session.messages, input.client.session, {
|
|
2206
|
+
sessionID: childSessionId,
|
|
2207
|
+
...(input.request.directory === undefined
|
|
2208
|
+
? {}
|
|
2209
|
+
: { directory: input.request.directory }),
|
|
2210
|
+
}, {
|
|
118
2211
|
path: { id: childSessionId },
|
|
119
|
-
...(input.request.directory === undefined
|
|
2212
|
+
...(input.request.directory === undefined
|
|
2213
|
+
? {}
|
|
2214
|
+
: { query: { directory: input.request.directory } }),
|
|
120
2215
|
});
|
|
121
2216
|
messageRef = firstMessageRef(messagesResponse);
|
|
122
2217
|
}
|
|
@@ -134,7 +2229,7 @@ export async function observeInjectedSdkLaneV1(input) {
|
|
|
134
2229
|
...(messageRef === undefined ? {} : { messageRef }),
|
|
135
2230
|
...(observedAgentRef === undefined ? {} : { observedAgentRef }),
|
|
136
2231
|
...(observedModelRef === undefined ? {} : { observedModelRef }),
|
|
137
|
-
missingLabels
|
|
2232
|
+
missingLabels,
|
|
138
2233
|
};
|
|
139
2234
|
}
|
|
140
2235
|
catch {
|
|
@@ -142,10 +2237,436 @@ export async function observeInjectedSdkLaneV1(input) {
|
|
|
142
2237
|
...base,
|
|
143
2238
|
status: "observation_failed",
|
|
144
2239
|
observationAttempted: true,
|
|
145
|
-
missingLabels: ["session_observation_failed"]
|
|
2240
|
+
missingLabels: ["session_observation_failed"],
|
|
146
2241
|
};
|
|
147
2242
|
}
|
|
148
2243
|
}
|
|
2244
|
+
export async function launchFlowDeskInjectedSdkRuntimeLaneFromPlanV1(input) {
|
|
2245
|
+
const plan = input.launchPlan;
|
|
2246
|
+
const planValidation = validateFlowDeskRuntimeLaneLaunchPlanV1(plan);
|
|
2247
|
+
if (!planValidation.ok)
|
|
2248
|
+
return blockedRuntimeLaneLaunch(`Runtime lane launch plan invalid: ${planValidation.errors.join(",") || "unknown"}.`, plan);
|
|
2249
|
+
if (plan.state !== "launch_ready")
|
|
2250
|
+
return blockedRuntimeLaneLaunch(`Runtime lane launch plan is not ready: ${plan.blocked_labels.join(",") || "blocked"}.`, plan);
|
|
2251
|
+
if (input.request.allowActualLaneLaunch !== true)
|
|
2252
|
+
return blockedRuntimeLaneLaunch("Explicit actual runtime lane launch opt-in is required.", plan);
|
|
2253
|
+
const parentSessionRef = refFrom("ses", input.request.parentSessionId);
|
|
2254
|
+
if (plan.parent_session_ref !== parentSessionRef)
|
|
2255
|
+
return blockedRuntimeLaneLaunch("Runtime lane launch parent session does not match the launch plan binding.", plan);
|
|
2256
|
+
const agent = agentIdFromRef(plan.agent_ref);
|
|
2257
|
+
const model = plan.provider_qualified_model_id === undefined
|
|
2258
|
+
? undefined
|
|
2259
|
+
: parseProviderQualifiedModelId(plan.provider_qualified_model_id);
|
|
2260
|
+
const runtimeModel = model === undefined
|
|
2261
|
+
? undefined
|
|
2262
|
+
: opencodeRuntimeModelForFlowDeskModel(model);
|
|
2263
|
+
const text = input.request.promptText.trim();
|
|
2264
|
+
if (agent === undefined || runtimeModel === undefined || text.length === 0)
|
|
2265
|
+
return blockedRuntimeLaneLaunch("Runtime lane launch is missing an agent, runtime model, or bounded prompt text.", plan);
|
|
2266
|
+
if (text.length > 20_000)
|
|
2267
|
+
return blockedRuntimeLaneLaunch("Runtime lane launch prompt text exceeds the bounded prompt limit.", plan);
|
|
2268
|
+
const create = input.client.session.create;
|
|
2269
|
+
if (create === undefined)
|
|
2270
|
+
return blockedRuntimeLaneLaunch("Injected OpenCode client is missing session.create for child lane launch.", plan);
|
|
2271
|
+
const dispatchMethod = input.request.dispatchMethod ?? "promptAsync";
|
|
2272
|
+
const dispatch = input.client.session[dispatchMethod];
|
|
2273
|
+
if (dispatch === undefined)
|
|
2274
|
+
return blockedRuntimeLaneLaunch("Injected OpenCode client is missing the requested session prompt method.", plan);
|
|
2275
|
+
let childSessionId;
|
|
2276
|
+
try {
|
|
2277
|
+
childSessionId = sessionIdFromResponse(await callSdkWithLegacyFallback(create, input.client.session, {
|
|
2278
|
+
parentID: input.request.parentSessionId,
|
|
2279
|
+
...(input.request.title === undefined
|
|
2280
|
+
? {}
|
|
2281
|
+
: { title: input.request.title.slice(0, 120) }),
|
|
2282
|
+
}, {
|
|
2283
|
+
body: {
|
|
2284
|
+
parentID: input.request.parentSessionId,
|
|
2285
|
+
...(input.request.title === undefined
|
|
2286
|
+
? {}
|
|
2287
|
+
: { title: input.request.title.slice(0, 120) }),
|
|
2288
|
+
},
|
|
2289
|
+
}));
|
|
2290
|
+
}
|
|
2291
|
+
catch {
|
|
2292
|
+
return {
|
|
2293
|
+
adapterProfile: "injected_sdk_runtime_lane_launch_adapter",
|
|
2294
|
+
status: "lane_launch_failed",
|
|
2295
|
+
createAttempted: true,
|
|
2296
|
+
promptAttempted: false,
|
|
2297
|
+
workflowId: plan.workflow_id,
|
|
2298
|
+
attemptId: plan.attempt_id,
|
|
2299
|
+
laneId: plan.lane_id,
|
|
2300
|
+
parentSessionRef: plan.parent_session_ref,
|
|
2301
|
+
redactedErrorCategory: "runtime",
|
|
2302
|
+
safeNextActions: ["/flowdesk-status", "/flowdesk-export-debug"],
|
|
2303
|
+
authority: runtimeLaneLaunchAuthority(false),
|
|
2304
|
+
};
|
|
2305
|
+
}
|
|
2306
|
+
if (childSessionId === undefined)
|
|
2307
|
+
return {
|
|
2308
|
+
adapterProfile: "injected_sdk_runtime_lane_launch_adapter",
|
|
2309
|
+
status: "lane_launch_failed",
|
|
2310
|
+
createAttempted: true,
|
|
2311
|
+
promptAttempted: false,
|
|
2312
|
+
workflowId: plan.workflow_id,
|
|
2313
|
+
attemptId: plan.attempt_id,
|
|
2314
|
+
laneId: plan.lane_id,
|
|
2315
|
+
parentSessionRef: plan.parent_session_ref,
|
|
2316
|
+
redactedErrorCategory: "runtime",
|
|
2317
|
+
safeNextActions: ["/flowdesk-status", "/flowdesk-export-debug"],
|
|
2318
|
+
authority: runtimeLaneLaunchAuthority(false),
|
|
2319
|
+
};
|
|
2320
|
+
let response;
|
|
2321
|
+
try {
|
|
2322
|
+
response = await callSdkWithLegacyFallback(dispatch, input.client.session, {
|
|
2323
|
+
sessionID: childSessionId,
|
|
2324
|
+
...(input.request.directory === undefined
|
|
2325
|
+
? {}
|
|
2326
|
+
: { directory: input.request.directory }),
|
|
2327
|
+
model: runtimeModel,
|
|
2328
|
+
agent,
|
|
2329
|
+
parts: [{ type: "text", text }],
|
|
2330
|
+
}, {
|
|
2331
|
+
path: { id: childSessionId },
|
|
2332
|
+
...(input.request.directory === undefined
|
|
2333
|
+
? {}
|
|
2334
|
+
: { query: { directory: input.request.directory } }),
|
|
2335
|
+
body: {
|
|
2336
|
+
model: runtimeModel,
|
|
2337
|
+
agent,
|
|
2338
|
+
parts: [{ type: "text", text }],
|
|
2339
|
+
},
|
|
2340
|
+
});
|
|
2341
|
+
if (isSdkErrorResponse(response))
|
|
2342
|
+
throw new Error("sdk prompt failed");
|
|
2343
|
+
}
|
|
2344
|
+
catch {
|
|
2345
|
+
return {
|
|
2346
|
+
adapterProfile: "injected_sdk_runtime_lane_launch_adapter",
|
|
2347
|
+
status: "lane_launch_failed",
|
|
2348
|
+
createAttempted: true,
|
|
2349
|
+
promptAttempted: true,
|
|
2350
|
+
workflowId: plan.workflow_id,
|
|
2351
|
+
attemptId: plan.attempt_id,
|
|
2352
|
+
laneId: plan.lane_id,
|
|
2353
|
+
parentSessionRef: plan.parent_session_ref,
|
|
2354
|
+
childSessionRef: refFrom("ses", childSessionId),
|
|
2355
|
+
agent,
|
|
2356
|
+
model: runtimeModel,
|
|
2357
|
+
dispatchMethod,
|
|
2358
|
+
redactedErrorCategory: "provider_api",
|
|
2359
|
+
safeNextActions: ["/flowdesk-status", "/flowdesk-export-debug"],
|
|
2360
|
+
authority: runtimeLaneLaunchAuthority(false),
|
|
2361
|
+
};
|
|
2362
|
+
}
|
|
2363
|
+
return {
|
|
2364
|
+
adapterProfile: "injected_sdk_runtime_lane_launch_adapter",
|
|
2365
|
+
status: "lane_launch_started",
|
|
2366
|
+
createAttempted: true,
|
|
2367
|
+
promptAttempted: true,
|
|
2368
|
+
workflowId: plan.workflow_id,
|
|
2369
|
+
attemptId: plan.attempt_id,
|
|
2370
|
+
laneId: plan.lane_id,
|
|
2371
|
+
parentSessionRef: plan.parent_session_ref,
|
|
2372
|
+
childSessionRef: refFrom("ses", childSessionId),
|
|
2373
|
+
...(firstMessageRef(response) === undefined
|
|
2374
|
+
? {}
|
|
2375
|
+
: { messageRef: firstMessageRef(response) }),
|
|
2376
|
+
agent,
|
|
2377
|
+
model: runtimeModel,
|
|
2378
|
+
dispatchMethod,
|
|
2379
|
+
safeNextActions: ["/flowdesk-status"],
|
|
2380
|
+
authority: runtimeLaneLaunchAuthority(true),
|
|
2381
|
+
};
|
|
2382
|
+
}
|
|
2383
|
+
export function materializeFlowDeskRuntimeLaneLaunchLifecycleEvidenceV1(input) {
|
|
2384
|
+
if (typeof input.rootDir !== "string" || input.rootDir.trim().length === 0)
|
|
2385
|
+
return blockRuntimeLaneLaunchLifecycle({
|
|
2386
|
+
plan: input.launchPlan,
|
|
2387
|
+
evidenceId: input.evidenceId,
|
|
2388
|
+
reason: "rootDir is required",
|
|
2389
|
+
});
|
|
2390
|
+
const planValidation = validateFlowDeskRuntimeLaneLaunchPlanV1(input.launchPlan);
|
|
2391
|
+
if (!planValidation.ok || input.launchPlan.state !== "launch_ready")
|
|
2392
|
+
return blockRuntimeLaneLaunchLifecycle({
|
|
2393
|
+
plan: input.launchPlan,
|
|
2394
|
+
evidenceId: input.evidenceId,
|
|
2395
|
+
reason: planValidation.errors.join(", ") ||
|
|
2396
|
+
"runtime lane launch plan must be launch_ready",
|
|
2397
|
+
});
|
|
2398
|
+
if (input.launchResult.status !== "lane_launch_started" &&
|
|
2399
|
+
input.launchResult.status !== "lane_launch_failed")
|
|
2400
|
+
return blockRuntimeLaneLaunchLifecycle({
|
|
2401
|
+
plan: input.launchPlan,
|
|
2402
|
+
evidenceId: input.evidenceId,
|
|
2403
|
+
reason: "runtime lane launch result must be started or failed",
|
|
2404
|
+
});
|
|
2405
|
+
if (input.launchResult.workflowId !== input.launchPlan.workflow_id ||
|
|
2406
|
+
input.launchResult.attemptId !== input.launchPlan.attempt_id ||
|
|
2407
|
+
input.launchResult.laneId !== input.launchPlan.lane_id ||
|
|
2408
|
+
input.launchResult.parentSessionRef !== input.launchPlan.parent_session_ref)
|
|
2409
|
+
return blockRuntimeLaneLaunchLifecycle({
|
|
2410
|
+
plan: input.launchPlan,
|
|
2411
|
+
evidenceId: input.evidenceId,
|
|
2412
|
+
reason: "runtime lane launch result does not match launch plan binding",
|
|
2413
|
+
});
|
|
2414
|
+
const state = input.launchResult.status === "lane_launch_started"
|
|
2415
|
+
? "running"
|
|
2416
|
+
: "invocation_failed";
|
|
2417
|
+
const record = {
|
|
2418
|
+
schema_version: "flowdesk.lane_lifecycle_record.v1",
|
|
2419
|
+
lane_id: input.launchPlan.lane_id ?? "lane-missing",
|
|
2420
|
+
workflow_id: input.launchPlan.workflow_id ?? "workflow-missing",
|
|
2421
|
+
attempt_id: input.launchPlan.attempt_id ?? "attempt-missing",
|
|
2422
|
+
parent_session_ref: input.launchPlan.parent_session_ref ?? "ses-missing",
|
|
2423
|
+
...(input.launchResult.childSessionRef === undefined
|
|
2424
|
+
? {}
|
|
2425
|
+
: { child_session_ref: input.launchResult.childSessionRef }),
|
|
2426
|
+
...(lifecycleMessageRefFrom(input.launchResult.messageRef) === undefined
|
|
2427
|
+
? {}
|
|
2428
|
+
: { message_ref: lifecycleMessageRefFrom(input.launchResult.messageRef) }),
|
|
2429
|
+
agent_ref: input.launchPlan.agent_ref ?? "agent-missing",
|
|
2430
|
+
provider_qualified_model_id: input.launchPlan.provider_qualified_model_id ?? "claude/missing",
|
|
2431
|
+
state,
|
|
2432
|
+
timeout_ms: input.timeoutMs ?? 0,
|
|
2433
|
+
orphan_max_age_ms: input.orphanMaxAgeMs ?? 0,
|
|
2434
|
+
retry_count: input.retryCount ?? 0,
|
|
2435
|
+
created_at: input.observedAt,
|
|
2436
|
+
updated_at: input.observedAt,
|
|
2437
|
+
dispatch_authority_enabled: false,
|
|
2438
|
+
providerCall: false,
|
|
2439
|
+
actualLaneLaunch: false,
|
|
2440
|
+
runtimeExecution: false,
|
|
2441
|
+
};
|
|
2442
|
+
const preWriteReload = reloadFlowDeskSessionEvidenceV1({
|
|
2443
|
+
workflowId: record.workflow_id,
|
|
2444
|
+
rootDir: input.rootDir,
|
|
2445
|
+
});
|
|
2446
|
+
if (!preWriteReload.ok || preWriteReload.blocked.length > 0)
|
|
2447
|
+
return blockRuntimeLaneLaunchLifecycle({
|
|
2448
|
+
plan: input.launchPlan,
|
|
2449
|
+
evidenceId: input.evidenceId,
|
|
2450
|
+
reason: "lane lifecycle pre-write evidence reload failed",
|
|
2451
|
+
evidenceReloaded: false,
|
|
2452
|
+
});
|
|
2453
|
+
if (preWriteReload.entries.some((entry) => entry.evidenceClass === "lane_lifecycle" &&
|
|
2454
|
+
entry.evidenceId === input.evidenceId))
|
|
2455
|
+
return blockRuntimeLaneLaunchLifecycle({
|
|
2456
|
+
plan: input.launchPlan,
|
|
2457
|
+
evidenceId: input.evidenceId,
|
|
2458
|
+
reason: "lane lifecycle evidence already exists",
|
|
2459
|
+
evidenceReloaded: true,
|
|
2460
|
+
});
|
|
2461
|
+
const prepared = prepareFlowDeskSessionEvidenceWriteIntentV1({
|
|
2462
|
+
workflowId: record.workflow_id,
|
|
2463
|
+
evidenceId: input.evidenceId,
|
|
2464
|
+
record: record,
|
|
2465
|
+
});
|
|
2466
|
+
if (!prepared.ok || prepared.writeIntent === undefined)
|
|
2467
|
+
return blockRuntimeLaneLaunchLifecycle({
|
|
2468
|
+
plan: input.launchPlan,
|
|
2469
|
+
evidenceId: input.evidenceId,
|
|
2470
|
+
reason: prepared.errors.join(", ") || "lane lifecycle evidence intent invalid",
|
|
2471
|
+
evidenceReloaded: true,
|
|
2472
|
+
});
|
|
2473
|
+
const applied = applyFlowDeskSessionEvidenceWriteIntentsV1(input.rootDir, [
|
|
2474
|
+
prepared.writeIntent,
|
|
2475
|
+
]);
|
|
2476
|
+
if (!applied.ok)
|
|
2477
|
+
return blockRuntimeLaneLaunchLifecycle({
|
|
2478
|
+
plan: input.launchPlan,
|
|
2479
|
+
evidenceId: input.evidenceId,
|
|
2480
|
+
reason: applied.errors.join(", ") || "lane lifecycle evidence write failed",
|
|
2481
|
+
evidenceReloaded: true,
|
|
2482
|
+
});
|
|
2483
|
+
const postWriteReload = reloadFlowDeskSessionEvidenceV1({
|
|
2484
|
+
workflowId: record.workflow_id,
|
|
2485
|
+
rootDir: input.rootDir,
|
|
2486
|
+
});
|
|
2487
|
+
const persisted = postWriteReload.ok &&
|
|
2488
|
+
postWriteReload.entries.some((entry) => entry.evidenceClass === "lane_lifecycle" &&
|
|
2489
|
+
entry.evidenceId === input.evidenceId &&
|
|
2490
|
+
entry.record.lane_id === record.lane_id &&
|
|
2491
|
+
entry.record.state === record.state);
|
|
2492
|
+
if (!persisted)
|
|
2493
|
+
return blockRuntimeLaneLaunchLifecycle({
|
|
2494
|
+
plan: input.launchPlan,
|
|
2495
|
+
evidenceId: input.evidenceId,
|
|
2496
|
+
reason: "lane lifecycle evidence reload verification failed",
|
|
2497
|
+
evidenceReloaded: false,
|
|
2498
|
+
});
|
|
2499
|
+
return {
|
|
2500
|
+
adapterProfile: "runtime_lane_launch_lifecycle_materializer",
|
|
2501
|
+
status: "lane_lifecycle_recorded",
|
|
2502
|
+
writeAttempted: true,
|
|
2503
|
+
evidenceReloaded: true,
|
|
2504
|
+
workflowId: record.workflow_id,
|
|
2505
|
+
attemptId: record.attempt_id,
|
|
2506
|
+
laneId: record.lane_id,
|
|
2507
|
+
evidenceId: input.evidenceId,
|
|
2508
|
+
lifecycleState: record.state,
|
|
2509
|
+
safeNextActions: ["/flowdesk-status"],
|
|
2510
|
+
authority: runtimeLaneLaunchLifecycleAuthority(true),
|
|
2511
|
+
};
|
|
2512
|
+
}
|
|
2513
|
+
export function materializeFlowDeskRuntimeLaneCompleteLifecycleEvidenceV1(input) {
|
|
2514
|
+
if (typeof input.rootDir !== "string" || input.rootDir.trim().length === 0)
|
|
2515
|
+
return blockRuntimeLaneLaunchLifecycle({
|
|
2516
|
+
plan: input.launchPlan,
|
|
2517
|
+
evidenceId: input.evidenceId,
|
|
2518
|
+
reason: "rootDir is required",
|
|
2519
|
+
});
|
|
2520
|
+
const planValidation = validateFlowDeskRuntimeLaneLaunchPlanV1(input.launchPlan);
|
|
2521
|
+
if (!planValidation.ok || input.launchPlan.state !== "launch_ready")
|
|
2522
|
+
return blockRuntimeLaneLaunchLifecycle({
|
|
2523
|
+
plan: input.launchPlan,
|
|
2524
|
+
evidenceId: input.evidenceId,
|
|
2525
|
+
reason: planValidation.errors.join(", ") ||
|
|
2526
|
+
"runtime lane launch plan must be launch_ready",
|
|
2527
|
+
});
|
|
2528
|
+
if (input.launchResult.status !== "lane_launch_started")
|
|
2529
|
+
return blockRuntimeLaneLaunchLifecycle({
|
|
2530
|
+
plan: input.launchPlan,
|
|
2531
|
+
evidenceId: input.evidenceId,
|
|
2532
|
+
reason: "runtime lane launch result must be lane_launch_started",
|
|
2533
|
+
});
|
|
2534
|
+
if (input.launchResult.workflowId !== input.launchPlan.workflow_id ||
|
|
2535
|
+
input.launchResult.attemptId !== input.launchPlan.attempt_id ||
|
|
2536
|
+
input.launchResult.laneId !== input.launchPlan.lane_id ||
|
|
2537
|
+
input.launchResult.parentSessionRef !== input.launchPlan.parent_session_ref)
|
|
2538
|
+
return blockRuntimeLaneLaunchLifecycle({
|
|
2539
|
+
plan: input.launchPlan,
|
|
2540
|
+
evidenceId: input.evidenceId,
|
|
2541
|
+
reason: "runtime lane launch result does not match launch plan binding",
|
|
2542
|
+
});
|
|
2543
|
+
if (input.verdictObservation.status !== "verdict_observed" ||
|
|
2544
|
+
input.verdictObservation.verdict === undefined ||
|
|
2545
|
+
input.verdictObservation.verdictId === undefined)
|
|
2546
|
+
return blockRuntimeLaneLaunchLifecycle({
|
|
2547
|
+
plan: input.launchPlan,
|
|
2548
|
+
evidenceId: input.evidenceId,
|
|
2549
|
+
reason: "reviewer verdict observation must be verdict_observed",
|
|
2550
|
+
});
|
|
2551
|
+
if (input.verdictObservation.workflowId !== input.launchPlan.workflow_id)
|
|
2552
|
+
return blockRuntimeLaneLaunchLifecycle({
|
|
2553
|
+
plan: input.launchPlan,
|
|
2554
|
+
evidenceId: input.evidenceId,
|
|
2555
|
+
reason: "reviewer verdict observation workflow does not match launch plan",
|
|
2556
|
+
});
|
|
2557
|
+
const verdictValidation = validateTopTierReviewVerdictV1(input.verdictObservation.verdict);
|
|
2558
|
+
if (!verdictValidation.ok)
|
|
2559
|
+
return blockRuntimeLaneLaunchLifecycle({
|
|
2560
|
+
plan: input.launchPlan,
|
|
2561
|
+
evidenceId: input.evidenceId,
|
|
2562
|
+
reason: verdictValidation.errors.join(", ") || "reviewer verdict is invalid",
|
|
2563
|
+
});
|
|
2564
|
+
const childSessionRef = input.launchResult.childSessionRef;
|
|
2565
|
+
const messageRef = lifecycleMessageRefFrom(input.launchResult.messageRef);
|
|
2566
|
+
if (childSessionRef === undefined || messageRef === undefined)
|
|
2567
|
+
return blockRuntimeLaneLaunchLifecycle({
|
|
2568
|
+
plan: input.launchPlan,
|
|
2569
|
+
evidenceId: input.evidenceId,
|
|
2570
|
+
reason: "complete lane lifecycle requires child and message refs",
|
|
2571
|
+
});
|
|
2572
|
+
const record = {
|
|
2573
|
+
schema_version: "flowdesk.lane_lifecycle_record.v1",
|
|
2574
|
+
lane_id: input.launchPlan.lane_id ?? "lane-missing",
|
|
2575
|
+
workflow_id: input.launchPlan.workflow_id ?? "workflow-missing",
|
|
2576
|
+
attempt_id: input.launchPlan.attempt_id ?? "attempt-missing",
|
|
2577
|
+
parent_session_ref: input.launchPlan.parent_session_ref ?? "ses-missing",
|
|
2578
|
+
child_session_ref: childSessionRef,
|
|
2579
|
+
message_ref: messageRef,
|
|
2580
|
+
agent_ref: input.launchPlan.agent_ref ?? "agent-missing",
|
|
2581
|
+
provider_qualified_model_id: input.launchPlan.provider_qualified_model_id ?? "claude/missing",
|
|
2582
|
+
state: "complete",
|
|
2583
|
+
verdict_ref: input.verdictObservation.verdictId,
|
|
2584
|
+
output_ref: input.outputRef,
|
|
2585
|
+
runtime_echo_ref: input.runtimeEchoRef,
|
|
2586
|
+
telemetry_ref: input.telemetryRef,
|
|
2587
|
+
timeout_ms: input.timeoutMs ?? 0,
|
|
2588
|
+
orphan_max_age_ms: input.orphanMaxAgeMs ?? 0,
|
|
2589
|
+
retry_count: input.retryCount ?? 0,
|
|
2590
|
+
created_at: input.observedAt,
|
|
2591
|
+
updated_at: input.observedAt,
|
|
2592
|
+
dispatch_authority_enabled: false,
|
|
2593
|
+
providerCall: false,
|
|
2594
|
+
actualLaneLaunch: false,
|
|
2595
|
+
runtimeExecution: false,
|
|
2596
|
+
};
|
|
2597
|
+
const preWriteReload = reloadFlowDeskSessionEvidenceV1({
|
|
2598
|
+
workflowId: record.workflow_id,
|
|
2599
|
+
rootDir: input.rootDir,
|
|
2600
|
+
});
|
|
2601
|
+
if (!preWriteReload.ok || preWriteReload.blocked.length > 0)
|
|
2602
|
+
return blockRuntimeLaneLaunchLifecycle({
|
|
2603
|
+
plan: input.launchPlan,
|
|
2604
|
+
evidenceId: input.evidenceId,
|
|
2605
|
+
reason: "complete lane lifecycle pre-write evidence reload failed",
|
|
2606
|
+
evidenceReloaded: false,
|
|
2607
|
+
});
|
|
2608
|
+
if (preWriteReload.entries.some((entry) => entry.evidenceClass === "lane_lifecycle" &&
|
|
2609
|
+
entry.evidenceId === input.evidenceId))
|
|
2610
|
+
return blockRuntimeLaneLaunchLifecycle({
|
|
2611
|
+
plan: input.launchPlan,
|
|
2612
|
+
evidenceId: input.evidenceId,
|
|
2613
|
+
reason: "lane lifecycle evidence already exists",
|
|
2614
|
+
evidenceReloaded: true,
|
|
2615
|
+
});
|
|
2616
|
+
const prepared = prepareFlowDeskSessionEvidenceWriteIntentV1({
|
|
2617
|
+
workflowId: record.workflow_id,
|
|
2618
|
+
evidenceId: input.evidenceId,
|
|
2619
|
+
record: record,
|
|
2620
|
+
});
|
|
2621
|
+
if (!prepared.ok || prepared.writeIntent === undefined)
|
|
2622
|
+
return blockRuntimeLaneLaunchLifecycle({
|
|
2623
|
+
plan: input.launchPlan,
|
|
2624
|
+
evidenceId: input.evidenceId,
|
|
2625
|
+
reason: prepared.errors.join(", ") ||
|
|
2626
|
+
"complete lane lifecycle evidence intent invalid",
|
|
2627
|
+
evidenceReloaded: true,
|
|
2628
|
+
});
|
|
2629
|
+
const applied = applyFlowDeskSessionEvidenceWriteIntentsV1(input.rootDir, [
|
|
2630
|
+
prepared.writeIntent,
|
|
2631
|
+
]);
|
|
2632
|
+
if (!applied.ok)
|
|
2633
|
+
return blockRuntimeLaneLaunchLifecycle({
|
|
2634
|
+
plan: input.launchPlan,
|
|
2635
|
+
evidenceId: input.evidenceId,
|
|
2636
|
+
reason: applied.errors.join(", ") ||
|
|
2637
|
+
"complete lane lifecycle evidence write failed",
|
|
2638
|
+
evidenceReloaded: true,
|
|
2639
|
+
});
|
|
2640
|
+
const postWriteReload = reloadFlowDeskSessionEvidenceV1({
|
|
2641
|
+
workflowId: record.workflow_id,
|
|
2642
|
+
rootDir: input.rootDir,
|
|
2643
|
+
});
|
|
2644
|
+
const persisted = postWriteReload.ok &&
|
|
2645
|
+
postWriteReload.entries.some((entry) => entry.evidenceClass === "lane_lifecycle" &&
|
|
2646
|
+
entry.evidenceId === input.evidenceId &&
|
|
2647
|
+
entry.record.state === "complete" &&
|
|
2648
|
+
entry.record.verdict_ref === input.verdictObservation.verdictId);
|
|
2649
|
+
if (!persisted)
|
|
2650
|
+
return blockRuntimeLaneLaunchLifecycle({
|
|
2651
|
+
plan: input.launchPlan,
|
|
2652
|
+
evidenceId: input.evidenceId,
|
|
2653
|
+
reason: "complete lane lifecycle evidence reload verification failed",
|
|
2654
|
+
evidenceReloaded: false,
|
|
2655
|
+
});
|
|
2656
|
+
return {
|
|
2657
|
+
adapterProfile: "runtime_lane_launch_lifecycle_materializer",
|
|
2658
|
+
status: "lane_lifecycle_recorded",
|
|
2659
|
+
writeAttempted: true,
|
|
2660
|
+
evidenceReloaded: true,
|
|
2661
|
+
workflowId: record.workflow_id,
|
|
2662
|
+
attemptId: record.attempt_id,
|
|
2663
|
+
laneId: record.lane_id,
|
|
2664
|
+
evidenceId: input.evidenceId,
|
|
2665
|
+
lifecycleState: "complete",
|
|
2666
|
+
safeNextActions: ["/flowdesk-status"],
|
|
2667
|
+
authority: runtimeLaneLaunchLifecycleAuthority(true),
|
|
2668
|
+
};
|
|
2669
|
+
}
|
|
149
2670
|
function promptTextFrom(request) {
|
|
150
2671
|
const text = request.promptText?.trim() ?? request.promptSummary?.trim() ?? "";
|
|
151
2672
|
return text.length > 0 ? text.slice(0, 20_000) : undefined;
|
|
@@ -153,12 +2674,14 @@ function promptTextFrom(request) {
|
|
|
153
2674
|
function dispatchOptions(request, model, text) {
|
|
154
2675
|
return {
|
|
155
2676
|
path: { id: request.sessionId },
|
|
156
|
-
...(request.directory === undefined
|
|
2677
|
+
...(request.directory === undefined
|
|
2678
|
+
? {}
|
|
2679
|
+
: { query: { directory: request.directory } }),
|
|
157
2680
|
body: {
|
|
158
2681
|
model,
|
|
159
2682
|
agent: request.agent,
|
|
160
|
-
parts: [{ type: "text", text }]
|
|
161
|
-
}
|
|
2683
|
+
parts: [{ type: "text", text }],
|
|
2684
|
+
},
|
|
162
2685
|
};
|
|
163
2686
|
}
|
|
164
2687
|
export async function dispatchManagedDispatchBetaPromptV1(input) {
|
|
@@ -166,27 +2689,82 @@ export async function dispatchManagedDispatchBetaPromptV1(input) {
|
|
|
166
2689
|
if (guardDecision.status !== "eligible")
|
|
167
2690
|
return blocked(input.boundaryInput, guardDecision);
|
|
168
2691
|
const approvedProviderQualifiedModelId = input.boundaryInput.guardApproval?.provider_qualified_model_id;
|
|
169
|
-
if (approvedProviderQualifiedModelId === undefined ||
|
|
2692
|
+
if (approvedProviderQualifiedModelId === undefined ||
|
|
2693
|
+
input.request.provider_qualified_model_id !==
|
|
2694
|
+
approvedProviderQualifiedModelId) {
|
|
170
2695
|
return blocked(input.boundaryInput, guardDecision, "Dispatch request model must exactly match Guard-approved provider-qualified model.");
|
|
171
2696
|
}
|
|
172
2697
|
const model = parseProviderQualifiedModelId(approvedProviderQualifiedModelId);
|
|
173
|
-
if (model === undefined ||
|
|
2698
|
+
if (model === undefined ||
|
|
2699
|
+
model.providerID !== input.boundaryInput.guardApproval?.provider_family) {
|
|
174
2700
|
return blocked(input.boundaryInput, guardDecision, "Guard-approved provider-qualified model is invalid or provider-mismatched.");
|
|
175
2701
|
}
|
|
2702
|
+
const runtimeModel = opencodeRuntimeModelForFlowDeskModel(model);
|
|
2703
|
+
if (runtimeModel === undefined) {
|
|
2704
|
+
return blocked(input.boundaryInput, guardDecision, "Guard-approved provider family is not mapped to an OpenCode runtime provider.");
|
|
2705
|
+
}
|
|
176
2706
|
const text = promptTextFrom(input.request);
|
|
177
|
-
if (input.request.sessionId.trim().length === 0 ||
|
|
2707
|
+
if (input.request.sessionId.trim().length === 0 ||
|
|
2708
|
+
input.request.agent.trim().length === 0 ||
|
|
2709
|
+
text === undefined) {
|
|
178
2710
|
return blocked(input.boundaryInput, guardDecision, "Dispatch request is missing session, agent, or bounded prompt text.");
|
|
179
2711
|
}
|
|
2712
|
+
if (input.dispatchManifest === undefined ||
|
|
2713
|
+
input.reloadedEvidence === undefined) {
|
|
2714
|
+
return blocked(input.boundaryInput, guardDecision, "Dispatch attempt manifest and durable evidence reload are required before SDK call.");
|
|
2715
|
+
}
|
|
2716
|
+
const precall = evaluateFlowDeskDispatchAttemptDurablePrecallV1({
|
|
2717
|
+
manifest: input.dispatchManifest,
|
|
2718
|
+
reloadedEvidence: input.reloadedEvidence,
|
|
2719
|
+
});
|
|
2720
|
+
if (!precall.sdk_call_permitted) {
|
|
2721
|
+
return blocked(input.boundaryInput, guardDecision, `Dispatch pre-call gate blocked: ${precall.blocked_labels.join(",") || precall.errors.join(",") || "unknown"}.`);
|
|
2722
|
+
}
|
|
2723
|
+
const consumedApproval = input.reloadedEvidence.entries.find((entry) => entry.evidenceClass === "production_approval_source" &&
|
|
2724
|
+
entry.record.approval_id === input.dispatchManifest?.approval_ref)?.record;
|
|
2725
|
+
if (consumedApproval === undefined) {
|
|
2726
|
+
return blocked(input.boundaryInput, guardDecision, "Durable dispatch pre-call gate did not expose a reloaded consumed approval source.");
|
|
2727
|
+
}
|
|
2728
|
+
const { durable_provenance_required: _durableProvenanceRequired, reloaded_approval_source_ref: _reloadedApprovalSourceRef, reloaded_pre_dispatch_audit_ref: _reloadedPreDispatchAuditRef, reloaded_idempotency_snapshot_ref: _reloadedIdempotencySnapshotRef, ...promotionPrecall } = precall;
|
|
2729
|
+
if (input.boundaryInput.preDispatchAuditRef === undefined ||
|
|
2730
|
+
input.boundaryInput.runtimeEchoEvidence?.conformance_ref === undefined) {
|
|
2731
|
+
return blocked(input.boundaryInput, guardDecision, "Managed-dispatch promotion requires matching audit and conformance refs before SDK call.");
|
|
2732
|
+
}
|
|
2733
|
+
const promotion = promoteFlowDeskManagedDispatchBetaAuthorityV1({
|
|
2734
|
+
guardDecision,
|
|
2735
|
+
precallEvaluation: promotionPrecall,
|
|
2736
|
+
consumedApproval,
|
|
2737
|
+
auditRef: input.boundaryInput.preDispatchAuditRef,
|
|
2738
|
+
conformanceRef: input.boundaryInput.runtimeEchoEvidence.conformance_ref,
|
|
2739
|
+
});
|
|
2740
|
+
if (!promotion.ok ||
|
|
2741
|
+
promotion.managed_dispatch_beta_authority_enabled !== true) {
|
|
2742
|
+
return blocked(input.boundaryInput, guardDecision, `Managed-dispatch promotion blocked: ${promotion.errors.join(",") || "unknown"}.`);
|
|
2743
|
+
}
|
|
180
2744
|
const dispatchMethod = input.request.dispatchMethod ?? "promptAsync";
|
|
181
2745
|
const dispatch = input.client.session[dispatchMethod];
|
|
182
2746
|
if (dispatch === undefined)
|
|
183
2747
|
return blocked(input.boundaryInput, guardDecision, "Injected OpenCode client is missing the requested session prompt method.");
|
|
184
|
-
|
|
2748
|
+
if (input.reservationStore === undefined) {
|
|
2749
|
+
return blocked(input.boundaryInput, guardDecision, "Dispatch idempotency reservation materialization is required before SDK call.");
|
|
2750
|
+
}
|
|
2751
|
+
const reservation = await input.reservationStore.reserve({
|
|
2752
|
+
manifest: input.dispatchManifest,
|
|
2753
|
+
reloadedEvidence: input.reloadedEvidence,
|
|
2754
|
+
});
|
|
2755
|
+
if (!reservation.ok || reservation.reservationEvidenceReloaded !== true) {
|
|
2756
|
+
return blocked(input.boundaryInput, guardDecision, `Dispatch idempotency reservation materialization blocked: ${reservation.redactedFailureReason ?? "reload not proven"}.`);
|
|
2757
|
+
}
|
|
2758
|
+
const options = dispatchOptions(input.request, runtimeModel, text);
|
|
185
2759
|
let response;
|
|
186
2760
|
try {
|
|
187
2761
|
response = await dispatch.call(input.client.session, options);
|
|
188
2762
|
}
|
|
189
2763
|
catch {
|
|
2764
|
+
const failureRecord = await input.reservationStore.recordDispatchFailure({
|
|
2765
|
+
manifest: input.dispatchManifest,
|
|
2766
|
+
reloadedEvidence: input.reloadedEvidence,
|
|
2767
|
+
});
|
|
190
2768
|
return {
|
|
191
2769
|
adapterProfile: flowdeskManagedDispatchBetaAdapterProfile,
|
|
192
2770
|
status: "dispatch_failed",
|
|
@@ -195,26 +2773,34 @@ export async function dispatchManagedDispatchBetaPromptV1(input) {
|
|
|
195
2773
|
guardDecision,
|
|
196
2774
|
sessionId: input.request.sessionId,
|
|
197
2775
|
agent: input.request.agent,
|
|
198
|
-
model,
|
|
199
|
-
...(input.request.directory === undefined
|
|
200
|
-
|
|
2776
|
+
model: runtimeModel,
|
|
2777
|
+
...(input.request.directory === undefined
|
|
2778
|
+
? {}
|
|
2779
|
+
: { directory: input.request.directory }),
|
|
2780
|
+
redactedErrorCategory: failureRecord.ok && failureRecord.reservationEvidenceReloaded
|
|
2781
|
+
? "provider_api"
|
|
2782
|
+
: "runtime",
|
|
201
2783
|
authority: { ...enabledDispatchAuthority(), runtimeExecution: false },
|
|
202
|
-
verification: verificationFor(input.boundaryInput)
|
|
2784
|
+
verification: verificationFor(input.boundaryInput),
|
|
203
2785
|
};
|
|
204
2786
|
}
|
|
205
2787
|
return {
|
|
206
2788
|
adapterProfile: flowdeskManagedDispatchBetaAdapterProfile,
|
|
207
|
-
status: dispatchMethod === "promptAsync"
|
|
2789
|
+
status: dispatchMethod === "promptAsync"
|
|
2790
|
+
? "dispatch_accepted"
|
|
2791
|
+
: "dispatch_completed",
|
|
208
2792
|
dispatchAttempted: true,
|
|
209
2793
|
dispatchMethod,
|
|
210
2794
|
guardDecision,
|
|
211
2795
|
sessionId: input.request.sessionId,
|
|
212
2796
|
agent: input.request.agent,
|
|
213
|
-
model,
|
|
214
|
-
...(input.request.directory === undefined
|
|
2797
|
+
model: runtimeModel,
|
|
2798
|
+
...(input.request.directory === undefined
|
|
2799
|
+
? {}
|
|
2800
|
+
: { directory: input.request.directory }),
|
|
215
2801
|
...(response === undefined ? {} : { response }),
|
|
216
2802
|
authority: enabledDispatchAuthority(),
|
|
217
|
-
verification: verificationFor(input.boundaryInput)
|
|
2803
|
+
verification: verificationFor(input.boundaryInput),
|
|
218
2804
|
};
|
|
219
2805
|
}
|
|
220
2806
|
//# sourceMappingURL=managed-dispatch-adapter.js.map
|