@flowdesk/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-profiles.d.ts +38 -0
- package/dist/agent-profiles.d.ts.map +1 -0
- package/dist/agent-profiles.js +138 -0
- package/dist/agent-profiles.js.map +1 -0
- package/dist/audit.d.ts +11 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +65 -0
- package/dist/audit.js.map +1 -0
- package/dist/bootstrap-foundation.d.ts +83 -0
- package/dist/bootstrap-foundation.d.ts.map +1 -0
- package/dist/bootstrap-foundation.js +773 -0
- package/dist/bootstrap-foundation.js.map +1 -0
- package/dist/chat-routing.d.ts +15 -0
- package/dist/chat-routing.d.ts.map +1 -0
- package/dist/chat-routing.js +172 -0
- package/dist/chat-routing.js.map +1 -0
- package/dist/command-manifest.d.ts +39 -0
- package/dist/command-manifest.d.ts.map +1 -0
- package/dist/command-manifest.js +240 -0
- package/dist/command-manifest.js.map +1 -0
- package/dist/config-policy.d.ts +31 -0
- package/dist/config-policy.d.ts.map +1 -0
- package/dist/config-policy.js +341 -0
- package/dist/config-policy.js.map +1 -0
- package/dist/fake-runtime.d.ts +68 -0
- package/dist/fake-runtime.d.ts.map +1 -0
- package/dist/fake-runtime.js +285 -0
- package/dist/fake-runtime.js.map +1 -0
- package/dist/guard-boundary.d.ts +53 -0
- package/dist/guard-boundary.d.ts.map +1 -0
- package/dist/guard-boundary.js +220 -0
- package/dist/guard-boundary.js.map +1 -0
- package/dist/guarded-dry-run.d.ts +46 -0
- package/dist/guarded-dry-run.d.ts.map +1 -0
- package/dist/guarded-dry-run.js +111 -0
- package/dist/guarded-dry-run.js.map +1 -0
- package/dist/hook-harness.d.ts +8 -0
- package/dist/hook-harness.d.ts.map +1 -0
- package/dist/hook-harness.js +130 -0
- package/dist/hook-harness.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/lane-observability.d.ts +22 -0
- package/dist/lane-observability.d.ts.map +1 -0
- package/dist/lane-observability.js +138 -0
- package/dist/lane-observability.js.map +1 -0
- package/dist/plan.d.ts +37 -0
- package/dist/plan.d.ts.map +1 -0
- package/dist/plan.js +137 -0
- package/dist/plan.js.map +1 -0
- package/dist/production-enablement.d.ts +57 -0
- package/dist/production-enablement.d.ts.map +1 -0
- package/dist/production-enablement.js +259 -0
- package/dist/production-enablement.js.map +1 -0
- package/dist/provider-failures.d.ts +19 -0
- package/dist/provider-failures.d.ts.map +1 -0
- package/dist/provider-failures.js +116 -0
- package/dist/provider-failures.js.map +1 -0
- package/dist/provider-usage-collector.d.ts +60 -0
- package/dist/provider-usage-collector.d.ts.map +1 -0
- package/dist/provider-usage-collector.js +502 -0
- package/dist/provider-usage-collector.js.map +1 -0
- package/dist/redaction.d.ts +5 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/redaction.js +11 -0
- package/dist/redaction.js.map +1 -0
- package/dist/release1-contracts.d.ts +1203 -0
- package/dist/release1-contracts.d.ts.map +1 -0
- package/dist/release1-contracts.js +256 -0
- package/dist/release1-contracts.js.map +1 -0
- package/dist/retry.d.ts +21 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +107 -0
- package/dist/retry.js.map +1 -0
- package/dist/reviewer-lane-conformance.d.ts +35 -0
- package/dist/reviewer-lane-conformance.d.ts.map +1 -0
- package/dist/reviewer-lane-conformance.js +124 -0
- package/dist/reviewer-lane-conformance.js.map +1 -0
- package/dist/schema-artifacts.d.ts +19 -0
- package/dist/schema-artifacts.d.ts.map +1 -0
- package/dist/schema-artifacts.js +182 -0
- package/dist/schema-artifacts.js.map +1 -0
- package/dist/schema-registry.d.ts +32 -0
- package/dist/schema-registry.d.ts.map +1 -0
- package/dist/schema-registry.js +163 -0
- package/dist/schema-registry.js.map +1 -0
- package/dist/session-evidence.d.ts +71 -0
- package/dist/session-evidence.d.ts.map +1 -0
- package/dist/session-evidence.js +336 -0
- package/dist/session-evidence.js.map +1 -0
- package/dist/state-paths.d.ts +20 -0
- package/dist/state-paths.d.ts.map +1 -0
- package/dist/state-paths.js +72 -0
- package/dist/state-paths.js.map +1 -0
- package/dist/state-store.d.ts +75 -0
- package/dist/state-store.d.ts.map +1 -0
- package/dist/state-store.js +567 -0
- package/dist/state-store.js.map +1 -0
- package/dist/status.d.ts +37 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +304 -0
- package/dist/status.js.map +1 -0
- package/dist/tool-contract-fixtures.d.ts +134 -0
- package/dist/tool-contract-fixtures.d.ts.map +1 -0
- package/dist/tool-contract-fixtures.js +389 -0
- package/dist/tool-contract-fixtures.js.map +1 -0
- package/dist/top-tier-reviewer-lane-probe.d.ts +10 -0
- package/dist/top-tier-reviewer-lane-probe.d.ts.map +1 -0
- package/dist/top-tier-reviewer-lane-probe.js +76 -0
- package/dist/top-tier-reviewer-lane-probe.js.map +1 -0
- package/dist/usage-health.d.ts +44 -0
- package/dist/usage-health.d.ts.map +1 -0
- package/dist/usage-health.js +105 -0
- package/dist/usage-health.js.map +1 -0
- package/dist/validators.d.ts +142 -0
- package/dist/validators.d.ts.map +1 -0
- package/dist/validators.js +1690 -0
- package/dist/validators.js.map +1 -0
- package/package.json +37 -0
|
@@ -0,0 +1,1690 @@
|
|
|
1
|
+
import { ARTIFACT_DISPOSITIONS, ATTEMPT_STATES, DEBUG_SECTIONS, DISABLED_MODES, DOCTOR_FAILURE_CATEGORIES, FORBIDDEN_RAW_PAYLOAD_MARKERS, HOOK_HARNESS_ATTEMPT_KINDS, HOOK_HARNESS_CAPABILITIES, HOOK_HARNESS_DECISIONS, HOOK_HARNESS_MODES, INPUT_MODES, LANE_FAILURE_CLASSES, LANE_INVOCATION_REF_KINDS, LANE_VERDICT_STATUSES, LOCK_RECOVERY_STATES, NON_DISPATCH_PERMISSION_CLASSES, PERSISTED_LANE_STATES, PROVIDER_FAILURE_CLASSES, PROVIDER_FAMILIES, REDACTED_ERROR_CATEGORIES, RELEASE_1_RUN_MODES, RESUME_MODES, SAFE_NEXT_ACTIONS, TOOL_STATUSES, USAGE_UNCERTAINTY_FLAGS, WORKFLOW_STATES } from "./release1-contracts.js";
|
|
2
|
+
import { getRelease1SchemaArtifact } from "./schema-artifacts.js";
|
|
3
|
+
function validateConcreteProviderQualifiedModelId(value, label = "provider_qualified_model_id") {
|
|
4
|
+
const result = validateProviderQualifiedModelId(value);
|
|
5
|
+
if (!result.ok)
|
|
6
|
+
return result;
|
|
7
|
+
const model = value.split("/")[1] ?? "";
|
|
8
|
+
return /(^|[-_.:])(latest|default|auto)($|[-_.:])/i.test(model) ? invalid(`${label} must be a concrete non-alias model id`) : valid();
|
|
9
|
+
}
|
|
10
|
+
export function valid() {
|
|
11
|
+
return { ok: true, errors: [] };
|
|
12
|
+
}
|
|
13
|
+
export function invalid(...errors) {
|
|
14
|
+
return { ok: false, errors };
|
|
15
|
+
}
|
|
16
|
+
function combine(results) {
|
|
17
|
+
const errors = results.flatMap((result) => result.errors);
|
|
18
|
+
return errors.length === 0 ? valid() : invalid(...errors);
|
|
19
|
+
}
|
|
20
|
+
function isRecord(value) {
|
|
21
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22
|
+
}
|
|
23
|
+
function isEnumValue(value, allowed) {
|
|
24
|
+
return typeof value === "string" && allowed.includes(value);
|
|
25
|
+
}
|
|
26
|
+
function validateTimestamp(value, label) {
|
|
27
|
+
if (typeof value !== "string" || value.length === 0)
|
|
28
|
+
return invalid(`${label} is required`);
|
|
29
|
+
return Number.isFinite(Date.parse(value)) ? valid() : invalid(`${label} must be a parseable timestamp`);
|
|
30
|
+
}
|
|
31
|
+
function rejectUnknownProperties(value, allowed) {
|
|
32
|
+
const unknown = Object.keys(value).filter((key) => !allowed.includes(key));
|
|
33
|
+
return unknown.length === 0 ? valid() : invalid(`unknown properties: ${unknown.join(",")}`);
|
|
34
|
+
}
|
|
35
|
+
function requireFields(value, fields) {
|
|
36
|
+
const missing = fields.filter((field) => !(field in value));
|
|
37
|
+
return missing.length === 0 ? valid() : invalid(`missing required fields: ${missing.join(",")}`);
|
|
38
|
+
}
|
|
39
|
+
function validateSchemaArtifactPropertyValue(fieldName, value, property) {
|
|
40
|
+
if (value === undefined)
|
|
41
|
+
return valid();
|
|
42
|
+
if (property.enum === undefined && property.maxLength === undefined && property.maxItems === undefined)
|
|
43
|
+
return valid();
|
|
44
|
+
const errors = [];
|
|
45
|
+
if (property.type === "string") {
|
|
46
|
+
if (typeof value !== "string")
|
|
47
|
+
errors.push(`${fieldName} must be a string`);
|
|
48
|
+
else {
|
|
49
|
+
if (property.maxLength !== undefined && value.length > property.maxLength)
|
|
50
|
+
errors.push(`${fieldName} exceeds max length ${property.maxLength}`);
|
|
51
|
+
if (property.enum !== undefined && !property.enum.includes(value))
|
|
52
|
+
errors.push(`${fieldName} is not allowed`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else if (property.type === "number") {
|
|
56
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
57
|
+
errors.push(`${fieldName} must be a finite number`);
|
|
58
|
+
}
|
|
59
|
+
else if (property.type === "boolean") {
|
|
60
|
+
if (typeof value !== "boolean")
|
|
61
|
+
errors.push(`${fieldName} must be a boolean`);
|
|
62
|
+
}
|
|
63
|
+
else if (property.type === "array") {
|
|
64
|
+
if (!Array.isArray(value))
|
|
65
|
+
errors.push(`${fieldName} must be an array`);
|
|
66
|
+
else if (property.maxItems !== undefined && value.length > property.maxItems)
|
|
67
|
+
errors.push(`${fieldName} exceeds max items ${property.maxItems}`);
|
|
68
|
+
}
|
|
69
|
+
else if (property.type === "object") {
|
|
70
|
+
if (!isRecord(value))
|
|
71
|
+
errors.push(`${fieldName} must be an object`);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
errors.push(`${fieldName} has unsupported schema artifact type`);
|
|
75
|
+
}
|
|
76
|
+
return errors.length === 0 ? valid() : invalid(...errors);
|
|
77
|
+
}
|
|
78
|
+
function validateStringArray(value, label, allowed, maxItems) {
|
|
79
|
+
if (!Array.isArray(value))
|
|
80
|
+
return invalid(`${label} must be an array`);
|
|
81
|
+
const errors = [];
|
|
82
|
+
if (maxItems !== undefined && value.length > maxItems)
|
|
83
|
+
errors.push(`${label} exceeds max items ${maxItems}`);
|
|
84
|
+
value.forEach((item, index) => {
|
|
85
|
+
if (typeof item !== "string")
|
|
86
|
+
errors.push(`${label}[${index}] must be a string`);
|
|
87
|
+
if (allowed !== undefined && !isEnumValue(item, allowed))
|
|
88
|
+
errors.push(`${label}[${index}] is not allowed`);
|
|
89
|
+
errors.push(...validateNoForbiddenRawPayloads(item, `${label}[${index}]`).errors);
|
|
90
|
+
});
|
|
91
|
+
return errors.length === 0 ? valid() : invalid(...errors);
|
|
92
|
+
}
|
|
93
|
+
function validateOpaqueRefArray(value, label, maxItems) {
|
|
94
|
+
if (!Array.isArray(value))
|
|
95
|
+
return invalid(`${label} must be an array`);
|
|
96
|
+
const errors = [];
|
|
97
|
+
if (maxItems !== undefined && value.length > maxItems)
|
|
98
|
+
errors.push(`${label} exceeds max items ${maxItems}`);
|
|
99
|
+
value.forEach((item, index) => {
|
|
100
|
+
errors.push(...validateOpaqueRef(item, `${label}[${index}]`).errors);
|
|
101
|
+
});
|
|
102
|
+
return errors.length === 0 ? valid() : invalid(...errors);
|
|
103
|
+
}
|
|
104
|
+
export function validateOpaqueId(value, label = "opaque id") {
|
|
105
|
+
if (typeof value !== "string")
|
|
106
|
+
return invalid(`${label} must be a string`);
|
|
107
|
+
if (value.length < 3 || value.length > 128)
|
|
108
|
+
return invalid(`${label} length is outside 3..128`);
|
|
109
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/.test(value))
|
|
110
|
+
return invalid(`${label} is not schema-safe`);
|
|
111
|
+
if (/[/\s]/.test(value) || value.includes(".."))
|
|
112
|
+
return invalid(`${label} must not contain paths, spaces, or traversal`);
|
|
113
|
+
return validateNoForbiddenRawPayloads(value, label);
|
|
114
|
+
}
|
|
115
|
+
export function validateOpaqueRef(value, label = "opaque ref") {
|
|
116
|
+
return validateOpaqueId(value, label);
|
|
117
|
+
}
|
|
118
|
+
export function validateProviderFamily(value) {
|
|
119
|
+
return isEnumValue(value, PROVIDER_FAMILIES) ? valid() : invalid("provider_family is missing or malformed");
|
|
120
|
+
}
|
|
121
|
+
export function validateProviderQualifiedModelId(value) {
|
|
122
|
+
if (typeof value !== "string" || value.length > 128)
|
|
123
|
+
return invalid("model id must be a bounded string");
|
|
124
|
+
const parts = value.split("/");
|
|
125
|
+
if (parts.length !== 2 || !parts[0] || !parts[1])
|
|
126
|
+
return invalid("model id must be provider-qualified as provider/model");
|
|
127
|
+
const [family, model] = parts;
|
|
128
|
+
if (!isEnumValue(family, PROVIDER_FAMILIES) || family === "unknown" || family === "all")
|
|
129
|
+
return invalid("model id provider family is not dispatchable");
|
|
130
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/.test(model))
|
|
131
|
+
return invalid("model id is not schema-safe");
|
|
132
|
+
return validateNoForbiddenRawPayloads(value, "model id");
|
|
133
|
+
}
|
|
134
|
+
export function validateNoForbiddenRawPayloads(value, label = "payload") {
|
|
135
|
+
const errors = [];
|
|
136
|
+
const schemaSafeKeysWithForbiddenTerms = new Set(["credential_preservation_check", "credential_scope_ref", "runtime_echo_mode", "runtime_echo_validation", "runtime_echo_ref"]);
|
|
137
|
+
const visit = (current, path) => {
|
|
138
|
+
if (typeof current === "string") {
|
|
139
|
+
const lower = current.toLowerCase();
|
|
140
|
+
if (/\b(prompt|system prompt|developer message|transcript|stack trace|runtime echo|provider payload|provider response|tool args|tool result|shell output|raw log|raw config)\b/.test(lower)) {
|
|
141
|
+
errors.push(`${path} contains prompt-like or raw payload marker`);
|
|
142
|
+
}
|
|
143
|
+
if (/([A-Za-z]:[\\/]|\\\\|\/(Users|home|etc|var|private|tmp|usr|opt|bin|sbin|Volumes|Applications|Library|System|dev|proc|root|mnt|srv|run)(?:\/|$|[\s"'`])|(^|[\s"'`])\/(?!flowdesk(?:-[A-Za-z0-9-]+)?\b)[A-Za-z0-9._-]+(?:\/|$|[\s"'`])|\.\.\/|\.\.\\|\.git\/|src\/|packages\/|~\/)/.test(current)) {
|
|
144
|
+
errors.push(`${path} contains raw path marker`);
|
|
145
|
+
}
|
|
146
|
+
if (/\b(sk-[A-Za-z0-9]|api[_-]?key|bearer\s+[A-Za-z0-9]|token[:=]|credential|secret)\b/i.test(current)) {
|
|
147
|
+
errors.push(`${path} contains credential-shaped marker`);
|
|
148
|
+
}
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (Array.isArray(current)) {
|
|
152
|
+
current.forEach((item, index) => {
|
|
153
|
+
visit(item, `${path}[${index}]`);
|
|
154
|
+
});
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (isRecord(current)) {
|
|
158
|
+
for (const [key, nested] of Object.entries(current)) {
|
|
159
|
+
const normalizedKey = key.toLowerCase();
|
|
160
|
+
if (!schemaSafeKeysWithForbiddenTerms.has(normalizedKey) && FORBIDDEN_RAW_PAYLOAD_MARKERS.some((marker) => normalizedKey.includes(marker)))
|
|
161
|
+
errors.push(`${path}.${key} is a forbidden raw payload field`);
|
|
162
|
+
visit(nested, `${path}.${key}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
visit(value, label);
|
|
167
|
+
return errors.length === 0 ? valid() : invalid(...errors);
|
|
168
|
+
}
|
|
169
|
+
function validateNonNegativeInteger(value, label) {
|
|
170
|
+
return typeof value === "number" && Number.isInteger(value) && Number.isFinite(value) && value >= 0 ? valid() : invalid(`${label} is invalid`);
|
|
171
|
+
}
|
|
172
|
+
export function validateSchemaArtifactValue(schemaId, value) {
|
|
173
|
+
if (!isRecord(value))
|
|
174
|
+
return invalid(`${schemaId} value must be an object`);
|
|
175
|
+
const artifact = getRelease1SchemaArtifact(schemaId);
|
|
176
|
+
if (artifact === undefined)
|
|
177
|
+
return invalid(`unknown schema artifact: ${schemaId}`);
|
|
178
|
+
const propertyResults = Object.entries(value).map(([fieldName, fieldValue]) => {
|
|
179
|
+
const property = artifact.properties[fieldName];
|
|
180
|
+
return property === undefined ? valid() : validateSchemaArtifactPropertyValue(fieldName, fieldValue, property);
|
|
181
|
+
});
|
|
182
|
+
return combine([
|
|
183
|
+
rejectUnknownProperties(value, Object.keys(artifact.properties)),
|
|
184
|
+
requireFields(value, artifact.required),
|
|
185
|
+
value.schema_version === undefined || value.schema_version === schemaId ? valid() : invalid("schema_version mismatch"),
|
|
186
|
+
...propertyResults,
|
|
187
|
+
validateNoForbiddenRawPayloads(value)
|
|
188
|
+
]);
|
|
189
|
+
}
|
|
190
|
+
function validateBlockerSummaryV1(value, label = "blocker") {
|
|
191
|
+
if (!isRecord(value))
|
|
192
|
+
return invalid(`${label} must be an object`);
|
|
193
|
+
return combine([
|
|
194
|
+
rejectUnknownProperties(value, ["category", "summary", "safe_remediation", "refs"]),
|
|
195
|
+
requireFields(value, ["category", "summary", "safe_remediation", "refs"]),
|
|
196
|
+
isEnumValue(value.category, REDACTED_ERROR_CATEGORIES) ? valid() : invalid(`${label}.category is invalid`),
|
|
197
|
+
typeof value.summary === "string" && value.summary.length > 0 && value.summary.length <= 500 ? validateNoForbiddenRawPayloads(value.summary, `${label}.summary`) : invalid(`${label}.summary is required`),
|
|
198
|
+
typeof value.safe_remediation === "string" && value.safe_remediation.length > 0 && value.safe_remediation.length <= 500 ? validateNoForbiddenRawPayloads(value.safe_remediation, `${label}.safe_remediation`) : invalid(`${label}.safe_remediation is required`),
|
|
199
|
+
validateStringArray(value.refs, `${label}.refs`, undefined, 20),
|
|
200
|
+
validateNoForbiddenRawPayloads(value, label)
|
|
201
|
+
]);
|
|
202
|
+
}
|
|
203
|
+
function validateGuardCheckV1(value, label = "guard_check") {
|
|
204
|
+
if (!isRecord(value))
|
|
205
|
+
return invalid(`${label} must be an object`);
|
|
206
|
+
return combine([
|
|
207
|
+
rejectUnknownProperties(value, ["check", "result", "ref"]),
|
|
208
|
+
requireFields(value, ["check", "result"]),
|
|
209
|
+
isEnumValue(value.check, ["policy", "usage", "provider_health", "runtime_compatibility", "audit", "redaction", "approval", "conformance"]) ? valid() : invalid(`${label}.check is invalid`),
|
|
210
|
+
isEnumValue(value.result, ["pass", "fail", "unknown"]) ? valid() : invalid(`${label}.result is invalid`),
|
|
211
|
+
value.ref === undefined ? valid() : validateOpaqueRef(value.ref, `${label}.ref`),
|
|
212
|
+
validateNoForbiddenRawPayloads(value, label)
|
|
213
|
+
]);
|
|
214
|
+
}
|
|
215
|
+
function validateGuardPrecheckSummaryV1(value) {
|
|
216
|
+
if (!isRecord(value))
|
|
217
|
+
return invalid("guard_precheck must be an object");
|
|
218
|
+
return combine([
|
|
219
|
+
rejectUnknownProperties(value, ["result", "required_checks", "refs"]),
|
|
220
|
+
requireFields(value, ["result", "required_checks", "refs"]),
|
|
221
|
+
isEnumValue(value.result, ["eligible", "blocked", "requires_approval"]) ? valid() : invalid("guard_precheck.result is invalid"),
|
|
222
|
+
Array.isArray(value.required_checks) ? combine(value.required_checks.map((check, index) => validateGuardCheckV1(check, `guard_precheck.required_checks[${index}]`))) : invalid("guard_precheck.required_checks must be an array"),
|
|
223
|
+
validateStringArray(value.refs, "guard_precheck.refs", undefined, 20),
|
|
224
|
+
validateNoForbiddenRawPayloads(value, "guard_precheck")
|
|
225
|
+
]);
|
|
226
|
+
}
|
|
227
|
+
export function validateProviderHealthSummaryV1(value) {
|
|
228
|
+
if (!isRecord(value))
|
|
229
|
+
return invalid("provider health summary must be an object");
|
|
230
|
+
const remediation = typeof value.safe_remediation === "string" ? value.safe_remediation : "";
|
|
231
|
+
return combine([
|
|
232
|
+
rejectUnknownProperties(value, ["provider_family", "model_family", "availability_state", "failure_class", "dispatchability", "safe_remediation", "snapshot_ref"]),
|
|
233
|
+
requireFields(value, ["provider_family", "availability_state", "failure_class", "dispatchability", "safe_remediation"]),
|
|
234
|
+
validateProviderFamily(value.provider_family),
|
|
235
|
+
value.model_family === undefined ? valid() : typeof value.model_family === "string" && value.model_family.length <= 128 ? validateNoForbiddenRawPayloads(value.model_family, "model_family") : invalid("model_family must be a bounded string"),
|
|
236
|
+
isEnumValue(value.availability_state, ["healthy", "degraded", "unavailable", "unknown"]) ? valid() : invalid("availability_state is invalid"),
|
|
237
|
+
isEnumValue(value.failure_class, PROVIDER_FAILURE_CLASSES) ? valid() : invalid("failure_class is invalid"),
|
|
238
|
+
isEnumValue(value.dispatchability, ["dispatchable", "diagnostic_only", "non_dispatchable"]) ? valid() : invalid("dispatchability is invalid"),
|
|
239
|
+
["degraded", "unavailable", "unknown"].includes(String(value.availability_state)) && value.dispatchability === "dispatchable" ? invalid("degraded, unavailable, or unknown provider health must not be dispatchable") : valid(),
|
|
240
|
+
value.failure_class !== "none" && value.dispatchability === "dispatchable" ? invalid("provider health failure classes must not be dispatchable") : valid(),
|
|
241
|
+
/\b(?:fallback|reselect|reselection)\b/i.test(remediation) ? invalid("provider health remediation must not suggest fallback or reselection") : valid(),
|
|
242
|
+
typeof value.safe_remediation === "string" && value.safe_remediation.length > 0 && value.safe_remediation.length <= 500 ? validateNoForbiddenRawPayloads(value.safe_remediation, "safe_remediation") : invalid("safe_remediation is required"),
|
|
243
|
+
value.snapshot_ref === undefined ? valid() : validateOpaqueRef(value.snapshot_ref, "snapshot_ref"),
|
|
244
|
+
validateNoForbiddenRawPayloads(value, "provider_health_summary")
|
|
245
|
+
]);
|
|
246
|
+
}
|
|
247
|
+
export function assertProviderHealthSummaryV1(value) {
|
|
248
|
+
const result = validateProviderHealthSummaryV1(value);
|
|
249
|
+
if (!result.ok)
|
|
250
|
+
throw new Error(result.errors.join("; "));
|
|
251
|
+
}
|
|
252
|
+
function validateRequestEnvelope(value, schemaVersion) {
|
|
253
|
+
return combine([
|
|
254
|
+
validateSchemaArtifactValue(schemaVersion, value),
|
|
255
|
+
validateOpaqueId(value.request_id, "request_id"),
|
|
256
|
+
isEnumValue(value.input_mode, INPUT_MODES) ? valid() : invalid("input_mode is invalid"),
|
|
257
|
+
value.workflow_id === undefined ? valid() : validateOpaqueId(value.workflow_id, "workflow_id"),
|
|
258
|
+
value.session_ref === undefined ? valid() : validateOpaqueRef(value.session_ref, "session_ref"),
|
|
259
|
+
value.redacted_intake_ref === undefined ? valid() : validateOpaqueRef(value.redacted_intake_ref, "redacted_intake_ref"),
|
|
260
|
+
value.user_approval_ref === undefined ? valid() : validateOpaqueRef(value.user_approval_ref, "user_approval_ref")
|
|
261
|
+
]);
|
|
262
|
+
}
|
|
263
|
+
function validateNoUnsupportedRetryAuthority(value, label) {
|
|
264
|
+
if (typeof value !== "string")
|
|
265
|
+
return invalid(`${label} must be a string`);
|
|
266
|
+
if (value.length === 0 || value.length > 500)
|
|
267
|
+
return invalid(`${label} is required and must be bounded`);
|
|
268
|
+
if (/\b(?:real[\s_-]*(?:opencode[\s_-]*)?dispatch|actual[\s_-]*lane[\s_-]*launch|provider[\s_-]*(?:call|request|api|payload|response)|fallback|reselect|reselection|automatic[\s_-]*(?:fallback|model)|hard[\s_-]*(?:chat|cancel|stop)|no[\s_-]*reply|noReply|cancel:\s*true|stop:\s*true|raw[\s_-]*(?:prompt|payload|log)|system prompt|developer message|transcript)\b/i.test(value)) {
|
|
269
|
+
return invalid(`${label} contains unsupported retry authority or raw payload marker`);
|
|
270
|
+
}
|
|
271
|
+
return validateNoForbiddenRawPayloads(value, label);
|
|
272
|
+
}
|
|
273
|
+
export function validateResponseEnvelopeV1(value, schemaVersion) {
|
|
274
|
+
if (!isRecord(value))
|
|
275
|
+
return invalid("response envelope must be an object");
|
|
276
|
+
const expectedSchema = schemaVersion ?? (typeof value.schema_version === "string" ? value.schema_version : "flowdesk.tool.response.v1");
|
|
277
|
+
return combine([
|
|
278
|
+
validateSchemaArtifactValue(expectedSchema, value),
|
|
279
|
+
typeof value.ok === "boolean" ? valid() : invalid("ok must be boolean"),
|
|
280
|
+
isEnumValue(value.status, TOOL_STATUSES) ? valid() : invalid("status is invalid"),
|
|
281
|
+
validateStringArray(value.safe_next_actions, "safe_next_actions", SAFE_NEXT_ACTIONS, 8),
|
|
282
|
+
typeof value.user_message === "string" && value.user_message.length <= 500 ? valid() : invalid("user_message is missing or too long"),
|
|
283
|
+
value.workflow_id === undefined ? valid() : validateOpaqueId(value.workflow_id, "workflow_id"),
|
|
284
|
+
value.audit_ref === undefined ? valid() : validateOpaqueRef(value.audit_ref, "audit_ref"),
|
|
285
|
+
value.debug_ref === undefined ? valid() : validateOpaqueRef(value.debug_ref, "debug_ref"),
|
|
286
|
+
value.lane_refs === undefined ? valid() : validateStringArray(value.lane_refs, "lane_refs", undefined, 20),
|
|
287
|
+
value.error === undefined ? valid() : validateRedactedErrorV1(value.error)
|
|
288
|
+
]);
|
|
289
|
+
}
|
|
290
|
+
export function validateRedactedErrorV1(value) {
|
|
291
|
+
if (!isRecord(value))
|
|
292
|
+
return invalid("error must be an object");
|
|
293
|
+
return combine([
|
|
294
|
+
validateSchemaArtifactValue("flowdesk.redacted_error.v1", value),
|
|
295
|
+
value.code === undefined ? valid() : validateOpaqueId(value.code, "error.code"),
|
|
296
|
+
isEnumValue(value.category, REDACTED_ERROR_CATEGORIES) ? valid() : invalid("error.category is invalid"),
|
|
297
|
+
typeof value.safe_remediation === "string" && value.safe_remediation.length <= 500 ? valid() : invalid("error.safe_remediation is missing or too long")
|
|
298
|
+
]);
|
|
299
|
+
}
|
|
300
|
+
export function validateChatIntakeRequestV1(value) {
|
|
301
|
+
if (!isRecord(value))
|
|
302
|
+
return invalid("chat intake request must be an object");
|
|
303
|
+
return combine([
|
|
304
|
+
validateRequestEnvelope(value, "flowdesk.chat_intake.request.v1"),
|
|
305
|
+
typeof value.intake_summary === "string" && value.intake_summary.length > 0 && value.intake_summary.length <= 500 ? validateNoForbiddenRawPayloads(value.intake_summary, "intake_summary") : invalid("intake_summary is required"),
|
|
306
|
+
isEnumValue(value.source_surface, ["chat.message", "command.execute.before", "manual_command", "test_fixture"]) ? valid() : invalid("source_surface is invalid"),
|
|
307
|
+
/\b(?:real[\s_-]*(?:opencode[\s_-]*)?dispatch|realOpenCodeDispatch|actual[\s_-]*lane[\s_-]*launch|actualLaneLaunch|providerCall|provider[\s_-]*(?:payload|response|call|request|api)|fallbackAuthority|automaticFallbackOrReselection|hardCancelOrNoReply|noReply|no[\s_-]*reply|hard[\s_-]*(?:cancel|stop)|cancel:\s*true|stop:\s*true|prevent-default|raw[\s_-]*prompt|system prompt|developer message|transcript)\b/i.test(String(value.intake_summary ?? "")) ? invalid("chat intake summary contains unsupported authority or raw payload marker") : valid()
|
|
308
|
+
]);
|
|
309
|
+
}
|
|
310
|
+
export function assertChatIntakeRequestV1(value) {
|
|
311
|
+
const result = validateChatIntakeRequestV1(value);
|
|
312
|
+
if (!result.ok)
|
|
313
|
+
throw new Error(result.errors.join("; "));
|
|
314
|
+
}
|
|
315
|
+
export function validateChatIntakeResponseV1(value) {
|
|
316
|
+
if (!isRecord(value))
|
|
317
|
+
return invalid("chat intake response must be an object");
|
|
318
|
+
const safeActions = Array.isArray(value.safe_next_actions) ? value.safe_next_actions : [];
|
|
319
|
+
return combine([
|
|
320
|
+
validateResponseEnvelopeV1(value, "flowdesk.chat_intake.response.v1"),
|
|
321
|
+
isEnumValue(value.classification, ["fast_chat", "managed_plan", "clarify", "blocked"]) ? valid() : invalid("classification is invalid"),
|
|
322
|
+
validateOpaqueRef(value.redacted_intake_ref, "redacted_intake_ref"),
|
|
323
|
+
isEnumValue(value.route_decision, ["continue_chat", "show_plan", "ask_clarification", "block", "use_command_fallback"]) ? valid() : invalid("route_decision is invalid"),
|
|
324
|
+
value.route_decision === "continue_chat" && safeActions.some((action) => action !== "continue_chat") ? invalid("continue_chat route must not imply managed FlowDesk authority") : valid(),
|
|
325
|
+
value.classification === "blocked" && safeActions.includes("/flowdesk-run") ? invalid("blocked chat intake must not suggest run") : valid(),
|
|
326
|
+
validateNoForbiddenRawPayloads(value, "chat_intake_response")
|
|
327
|
+
]);
|
|
328
|
+
}
|
|
329
|
+
export function assertChatIntakeResponseV1(value) {
|
|
330
|
+
const result = validateChatIntakeResponseV1(value);
|
|
331
|
+
if (!result.ok)
|
|
332
|
+
throw new Error(result.errors.join("; "));
|
|
333
|
+
}
|
|
334
|
+
export function validateHookHarnessRequestV1(value) {
|
|
335
|
+
if (!isRecord(value))
|
|
336
|
+
return invalid("hook harness request must be an object");
|
|
337
|
+
return combine([
|
|
338
|
+
validateSchemaArtifactValue("flowdesk.hook_harness.request.v1", value),
|
|
339
|
+
validateOpaqueId(value.request_id, "request_id"),
|
|
340
|
+
isEnumValue(value.hook_harness_mode, HOOK_HARNESS_MODES) ? valid() : invalid("hook_harness_mode is invalid"),
|
|
341
|
+
isEnumValue(value.attempt_kind, HOOK_HARNESS_ATTEMPT_KINDS) ? valid() : invalid("attempt_kind is invalid"),
|
|
342
|
+
isEnumValue(value.requested_capability, HOOK_HARNESS_CAPABILITIES) ? valid() : invalid("requested_capability is invalid"),
|
|
343
|
+
validateOpaqueRef(value.redacted_attempt_ref, "redacted_attempt_ref"),
|
|
344
|
+
typeof value.attempt_summary === "string" && value.attempt_summary.length > 0 && value.attempt_summary.length <= 500 ? validateNoForbiddenRawPayloads(value.attempt_summary, "attempt_summary") : invalid("attempt_summary is required"),
|
|
345
|
+
value.chat_intake_mode === undefined || isEnumValue(value.chat_intake_mode, ["steering", "observe_only", "off"]) ? valid() : invalid("chat_intake_mode is invalid"),
|
|
346
|
+
value.conformance_ref === undefined ? valid() : validateOpaqueRef(value.conformance_ref, "conformance_ref"),
|
|
347
|
+
validateNoForbiddenRawPayloads(value, "hook_harness_request")
|
|
348
|
+
]);
|
|
349
|
+
}
|
|
350
|
+
export function assertHookHarnessRequestV1(value) {
|
|
351
|
+
const result = validateHookHarnessRequestV1(value);
|
|
352
|
+
if (!result.ok)
|
|
353
|
+
throw new Error(result.errors.join("; "));
|
|
354
|
+
}
|
|
355
|
+
export function validateHookHarnessResponseV1(value) {
|
|
356
|
+
if (!isRecord(value))
|
|
357
|
+
return invalid("hook harness response must be an object");
|
|
358
|
+
const actions = Array.isArray(value.safe_next_actions) ? value.safe_next_actions : [];
|
|
359
|
+
const safeManualActions = ["/flowdesk-doctor", "/flowdesk-status", "/flowdesk-export-debug"];
|
|
360
|
+
const usesOnlySafeManualActions = actions.every((action) => typeof action === "string" && safeManualActions.includes(action));
|
|
361
|
+
return combine([
|
|
362
|
+
validateSchemaArtifactValue("flowdesk.hook_harness.response.v1", value),
|
|
363
|
+
validateOpaqueId(value.request_id, "request_id"),
|
|
364
|
+
isEnumValue(value.hook_harness_mode, HOOK_HARNESS_MODES) ? valid() : invalid("hook_harness_mode is invalid"),
|
|
365
|
+
isEnumValue(value.decision, HOOK_HARNESS_DECISIONS) ? valid() : invalid("decision is invalid"),
|
|
366
|
+
Array.isArray(value.diagnostic_observations) ? validateStringArray(value.diagnostic_observations, "diagnostic_observations", undefined, 8) : invalid("diagnostic_observations must be an array"),
|
|
367
|
+
validateStringArray(value.safe_next_actions, "safe_next_actions", SAFE_NEXT_ACTIONS, 8),
|
|
368
|
+
typeof value.user_message === "string" && value.user_message.length > 0 && value.user_message.length <= 500 ? validateNoForbiddenRawPayloads(value.user_message, "user_message") : invalid("user_message is required"),
|
|
369
|
+
typeof value.managed_automation_enabled === "boolean" ? valid() : invalid("managed_automation_enabled must be boolean"),
|
|
370
|
+
value.privileged_automation_enabled === false ? valid() : invalid("privileged_automation_enabled must be false"),
|
|
371
|
+
value.dispatch_authorized === false ? valid() : invalid("dispatch_authorized must be false"),
|
|
372
|
+
value.guard_bypassed === false ? valid() : invalid("guard_bypassed must be false"),
|
|
373
|
+
value.fallback_authorized === false ? valid() : invalid("fallback_authorized must be false"),
|
|
374
|
+
value.hard_chat_authority === false ? valid() : invalid("hard_chat_authority must be false"),
|
|
375
|
+
typeof value.mutation_applied === "boolean" ? valid() : invalid("mutation_applied must be boolean"),
|
|
376
|
+
typeof value.denial_applied === "boolean" ? valid() : invalid("denial_applied must be boolean"),
|
|
377
|
+
validateOpaqueRef(value.redacted_attempt_ref, "redacted_attempt_ref"),
|
|
378
|
+
value.audit_ref === undefined ? valid() : validateOpaqueRef(value.audit_ref, "audit_ref"),
|
|
379
|
+
value.hook_harness_mode === "observe" && (value.mutation_applied !== false || value.denial_applied !== false || value.managed_automation_enabled !== false) ? invalid("observe mode must not mutate, deny, or enable managed automation") : valid(),
|
|
380
|
+
value.hook_harness_mode === "off" && (value.mutation_applied !== false || value.denial_applied !== false || value.managed_automation_enabled !== false) ? invalid("off mode must leave only safe manual fallback") : valid(),
|
|
381
|
+
value.hook_harness_mode === "observe" && !usesOnlySafeManualActions ? invalid("observe mode must only suggest safe manual diagnostics") : valid(),
|
|
382
|
+
value.hook_harness_mode === "off" && !usesOnlySafeManualActions ? invalid("off mode must only suggest safe manual fallback") : valid(),
|
|
383
|
+
value.decision === "deny" && actions.includes("/flowdesk-run") ? invalid("denied hook harness response must not suggest run") : valid(),
|
|
384
|
+
validateNoForbiddenRawPayloads(value, "hook_harness_response")
|
|
385
|
+
]);
|
|
386
|
+
}
|
|
387
|
+
const doctorDiagnosticActions = ["/flowdesk-doctor", "/flowdesk-status", "/flowdesk-export-debug"];
|
|
388
|
+
function validateDoctorDiagnosticActions(value, label = "safe_next_actions") {
|
|
389
|
+
return validateStringArray(value, label, doctorDiagnosticActions, 3);
|
|
390
|
+
}
|
|
391
|
+
function validateDoctorSectionResultLikeV1(value, label = "doctor_results") {
|
|
392
|
+
if (!isRecord(value))
|
|
393
|
+
return invalid(`${label} item must be an object`);
|
|
394
|
+
return combine([
|
|
395
|
+
validateSchemaArtifactValue("flowdesk.doctor_section_result.v1", value),
|
|
396
|
+
validateOpaqueId(value.run_id, `${label}.run_id`),
|
|
397
|
+
isEnumValue(value.section, ["migration_cleanup", "opencode_plugin_compatibility", "provider_usage_readiness", "policy_project_safety"]) ? valid() : invalid(`${label}.section is invalid`),
|
|
398
|
+
isEnumValue(value.category, DOCTOR_FAILURE_CATEGORIES) ? valid() : invalid(`${label}.category is invalid`),
|
|
399
|
+
typeof value.summary === "string" && value.summary.length > 0 && value.summary.length <= 500 ? validateNoForbiddenRawPayloads(value.summary, `${label}.summary`) : invalid(`${label}.summary is invalid`),
|
|
400
|
+
validateDoctorDiagnosticActions(value.safe_next_actions, `${label}.safe_next_actions`),
|
|
401
|
+
validateStringArray(value.refs, `${label}.refs`, undefined, 20),
|
|
402
|
+
validateOpaqueId(value.redaction_version, `${label}.redaction_version`),
|
|
403
|
+
validateNoForbiddenRawPayloads(value, label)
|
|
404
|
+
]);
|
|
405
|
+
}
|
|
406
|
+
export function validateDoctorRequestV1(value) {
|
|
407
|
+
if (!isRecord(value))
|
|
408
|
+
return invalid("doctor request must be an object");
|
|
409
|
+
return combine([
|
|
410
|
+
validateRequestEnvelope(value, "flowdesk.doctor.request.v1"),
|
|
411
|
+
isEnumValue(value.check_scope, ["install", "runtime", "policy", "usage", "provider_health", "conformance", "all"]) ? valid() : invalid("check_scope is invalid"),
|
|
412
|
+
isEnumValue(value.profile, ["production", "development", "test"]) ? valid() : invalid("profile is invalid"),
|
|
413
|
+
value.persist_report === undefined || typeof value.persist_report === "boolean" ? valid() : invalid("persist_report must be boolean")
|
|
414
|
+
]);
|
|
415
|
+
}
|
|
416
|
+
export function assertDoctorRequestV1(value) {
|
|
417
|
+
const result = validateDoctorRequestV1(value);
|
|
418
|
+
if (!result.ok)
|
|
419
|
+
throw new Error(result.errors.join("; "));
|
|
420
|
+
}
|
|
421
|
+
export function validateDoctorResponseV1(value) {
|
|
422
|
+
if (!isRecord(value))
|
|
423
|
+
return invalid("doctor response must be an object");
|
|
424
|
+
const doctorResults = Array.isArray(value.doctor_results) ? value.doctor_results : [];
|
|
425
|
+
const categories = new Set(doctorResults.filter(isRecord).map((result) => result.category));
|
|
426
|
+
const disabledModes = new Set(Array.isArray(value.disabled_modes) ? value.disabled_modes : []);
|
|
427
|
+
const allowedDisabledModes = new Set(["real_dispatch", "managed_fallback", "lane_launch", "hard_chat_blocking", ...(categories.has("chat_mode_disable") ? ["chat_routed"] : [])]);
|
|
428
|
+
const release1BaseDisabledModes = ["real_dispatch", "managed_fallback", "lane_launch", "hard_chat_blocking"];
|
|
429
|
+
return combine([
|
|
430
|
+
validateResponseEnvelopeV1(value, "flowdesk.doctor.response.v1"),
|
|
431
|
+
Array.isArray(value.doctor_results) ? combine(value.doctor_results.map((result, index) => validateDoctorSectionResultLikeV1(result, `doctor_results[${index}]`))) : invalid("doctor_results must be an array"),
|
|
432
|
+
validateProviderHealthSummaryV1(value.provider_health_summary),
|
|
433
|
+
validateOpaqueRef(value.compatibility_ref, "compatibility_ref"),
|
|
434
|
+
validateStringArray(value.disabled_modes, "disabled_modes", DISABLED_MODES, 8),
|
|
435
|
+
validateDoctorDiagnosticActions(value.safe_next_actions),
|
|
436
|
+
categories.has("chat_mode_disable") && !disabledModes.has("chat_routed") ? invalid("chat_mode_disable doctor responses must disable chat_routed mode") : valid(),
|
|
437
|
+
!categories.has("chat_mode_disable") && disabledModes.has("chat_routed") ? invalid("chat_routed mode may be disabled only for chat_mode_disable doctor results") : valid(),
|
|
438
|
+
release1BaseDisabledModes.some((mode) => !disabledModes.has(mode)) ? invalid("doctor responses must keep Release 1 managed dispatch and privileged automation modes disabled") : valid(),
|
|
439
|
+
[...disabledModes].some((mode) => typeof mode === "string" && DISABLED_MODES.includes(mode) && !allowedDisabledModes.has(mode)) ? invalid("doctor responses must not disable unrelated modes for category mapping") : valid(),
|
|
440
|
+
validateNoForbiddenRawPayloads(value, "doctor_response")
|
|
441
|
+
]);
|
|
442
|
+
}
|
|
443
|
+
export function assertDoctorResponseV1(value) {
|
|
444
|
+
const result = validateDoctorResponseV1(value);
|
|
445
|
+
if (!result.ok)
|
|
446
|
+
throw new Error(result.errors.join("; "));
|
|
447
|
+
}
|
|
448
|
+
export function assertHookHarnessResponseV1(value) {
|
|
449
|
+
const result = validateHookHarnessResponseV1(value);
|
|
450
|
+
if (!result.ok)
|
|
451
|
+
throw new Error(result.errors.join("; "));
|
|
452
|
+
}
|
|
453
|
+
export function validatePlanRequestV1(value) {
|
|
454
|
+
if (!isRecord(value))
|
|
455
|
+
return invalid("plan request must be an object");
|
|
456
|
+
return combine([
|
|
457
|
+
validateRequestEnvelope(value, "flowdesk.plan.request.v1"),
|
|
458
|
+
typeof value.goal_summary === "string" && value.goal_summary.length > 0 && value.goal_summary.length <= 500 ? validateNoForbiddenRawPayloads(value.goal_summary, "goal_summary") : invalid("goal_summary is required"),
|
|
459
|
+
typeof value.scope_summary === "string" && value.scope_summary.length > 0 && value.scope_summary.length <= 500 ? validateNoForbiddenRawPayloads(value.scope_summary, "scope_summary") : invalid("scope_summary is required"),
|
|
460
|
+
typeof value.risk_hint === "string" && value.risk_hint.length > 0 && value.risk_hint.length <= 500 ? validateNoForbiddenRawPayloads(value.risk_hint, "risk_hint") : invalid("risk_hint is required"),
|
|
461
|
+
value.existing_plan_revision_id === undefined ? valid() : validateOpaqueId(value.existing_plan_revision_id, "existing_plan_revision_id")
|
|
462
|
+
]);
|
|
463
|
+
}
|
|
464
|
+
export function assertPlanRequestV1(value) {
|
|
465
|
+
const result = validatePlanRequestV1(value);
|
|
466
|
+
if (!result.ok)
|
|
467
|
+
throw new Error(result.errors.join("; "));
|
|
468
|
+
}
|
|
469
|
+
export function validatePlanResponseV1(value) {
|
|
470
|
+
if (!isRecord(value))
|
|
471
|
+
return invalid("plan response must be an object");
|
|
472
|
+
return combine([
|
|
473
|
+
validateResponseEnvelopeV1(value, "flowdesk.plan.response.v1"),
|
|
474
|
+
validateOpaqueId(value.plan_revision_id, "plan_revision_id"),
|
|
475
|
+
typeof value.delegated_authoring_summary === "string" && value.delegated_authoring_summary.length > 0 && value.delegated_authoring_summary.length <= 500 ? validateNoForbiddenRawPayloads(value.delegated_authoring_summary, "delegated_authoring_summary") : invalid("delegated_authoring_summary is required"),
|
|
476
|
+
validateStringArray(value.required_approvals, "required_approvals", undefined, 20),
|
|
477
|
+
validateGuardPrecheckSummaryV1(value.guard_precheck)
|
|
478
|
+
]);
|
|
479
|
+
}
|
|
480
|
+
export function assertPlanResponseV1(value) {
|
|
481
|
+
const result = validatePlanResponseV1(value);
|
|
482
|
+
if (!result.ok)
|
|
483
|
+
throw new Error(result.errors.join("; "));
|
|
484
|
+
}
|
|
485
|
+
export function validateRunRequestV1(value) {
|
|
486
|
+
if (!isRecord(value))
|
|
487
|
+
return invalid("run request must be an object");
|
|
488
|
+
return combine([
|
|
489
|
+
validateRequestEnvelope(value, "flowdesk.run.request.v1"),
|
|
490
|
+
value.run_mode === "guarded-dry-run" || value.run_mode === "fake-runtime" ? valid() : invalid("run_mode is invalid for Release 1"),
|
|
491
|
+
validateOpaqueId(value.plan_revision_id, "plan_revision_id"),
|
|
492
|
+
value.step_id === undefined ? valid() : validateOpaqueId(value.step_id, "step_id")
|
|
493
|
+
]);
|
|
494
|
+
}
|
|
495
|
+
export function assertRunRequestV1(value) {
|
|
496
|
+
const result = validateRunRequestV1(value);
|
|
497
|
+
if (!result.ok)
|
|
498
|
+
throw new Error(result.errors.join("; "));
|
|
499
|
+
}
|
|
500
|
+
export function validateRunResponseV1(value) {
|
|
501
|
+
if (!isRecord(value))
|
|
502
|
+
return invalid("run response must be an object");
|
|
503
|
+
return combine([
|
|
504
|
+
validateResponseEnvelopeV1(value, "flowdesk.run.response.v1"),
|
|
505
|
+
validateOpaqueRef(value.run_result_ref, "run_result_ref"),
|
|
506
|
+
validateOpaqueRef(value.verification_summary_ref, "verification_summary_ref"),
|
|
507
|
+
["none", "quarantined", "promoted", "discarded"].includes(String(value.artifact_disposition)) ? valid() : invalid("artifact_disposition is invalid")
|
|
508
|
+
]);
|
|
509
|
+
}
|
|
510
|
+
export function assertRunResponseV1(value) {
|
|
511
|
+
const result = validateRunResponseV1(value);
|
|
512
|
+
if (!result.ok)
|
|
513
|
+
throw new Error(result.errors.join("; "));
|
|
514
|
+
}
|
|
515
|
+
export function validateStatusRequestV1(value) {
|
|
516
|
+
if (!isRecord(value))
|
|
517
|
+
return invalid("status request must be an object");
|
|
518
|
+
return combine([
|
|
519
|
+
validateRequestEnvelope(value, "flowdesk.status.request.v1"),
|
|
520
|
+
value.detail_level === undefined || isEnumValue(value.detail_level, ["summary", "diagnostic", "debug_refs", "lane_refs"]) ? valid() : invalid("detail_level is invalid")
|
|
521
|
+
]);
|
|
522
|
+
}
|
|
523
|
+
export function assertStatusRequestV1(value) {
|
|
524
|
+
const result = validateStatusRequestV1(value);
|
|
525
|
+
if (!result.ok)
|
|
526
|
+
throw new Error(result.errors.join("; "));
|
|
527
|
+
}
|
|
528
|
+
export function validateRetryRequestV1(value) {
|
|
529
|
+
if (!isRecord(value))
|
|
530
|
+
return invalid("retry request must be an object");
|
|
531
|
+
return combine([
|
|
532
|
+
validateRequestEnvelope(value, "flowdesk.retry.request.v1"),
|
|
533
|
+
validateOpaqueId(value.attempt_id, "attempt_id"),
|
|
534
|
+
validateNoUnsupportedRetryAuthority(value.retry_reason, "retry_reason"),
|
|
535
|
+
value.new_binding_hint === undefined ? valid() : validateNoUnsupportedRetryAuthority(value.new_binding_hint, "new_binding_hint")
|
|
536
|
+
]);
|
|
537
|
+
}
|
|
538
|
+
export function assertRetryRequestV1(value) {
|
|
539
|
+
const result = validateRetryRequestV1(value);
|
|
540
|
+
if (!result.ok)
|
|
541
|
+
throw new Error(result.errors.join("; "));
|
|
542
|
+
}
|
|
543
|
+
export function validateRetryResponseV1(value) {
|
|
544
|
+
if (!isRecord(value))
|
|
545
|
+
return invalid("retry response must be an object");
|
|
546
|
+
const actions = Array.isArray(value.safe_next_actions) ? value.safe_next_actions : [];
|
|
547
|
+
const allowedRetryActions = ["/flowdesk-doctor", "/flowdesk-usage", "/flowdesk-status", "/flowdesk-retry", "/flowdesk-export-debug"];
|
|
548
|
+
return combine([
|
|
549
|
+
validateResponseEnvelopeV1(value, "flowdesk.retry.response.v1"),
|
|
550
|
+
validateOpaqueId(value.new_attempt_id, "new_attempt_id"),
|
|
551
|
+
Array.isArray(value.required_guard_checks) ? combine(value.required_guard_checks.map((check, index) => validateGuardCheckV1(check, `required_guard_checks[${index}]`))) : invalid("required_guard_checks must be an array"),
|
|
552
|
+
isEnumValue(value.retry_state, ["planned", "blocked", "diagnostic_only"]) ? valid() : invalid("retry_state is invalid"),
|
|
553
|
+
actions.some((action) => typeof action === "string" && !allowedRetryActions.includes(action)) ? invalid("retry responses must only suggest diagnostic, usage, status, retry-planning, or debug actions") : valid(),
|
|
554
|
+
value.retry_state !== "planned" && value.status !== "blocked" && value.status !== "diagnostic_only" && value.status !== "degraded" ? invalid("blocked retry planning must remain diagnostic or blocked") : valid(),
|
|
555
|
+
validateNoForbiddenRawPayloads(value, "retry_response")
|
|
556
|
+
]);
|
|
557
|
+
}
|
|
558
|
+
export function assertRetryResponseV1(value) {
|
|
559
|
+
const result = validateRetryResponseV1(value);
|
|
560
|
+
if (!result.ok)
|
|
561
|
+
throw new Error(result.errors.join("; "));
|
|
562
|
+
}
|
|
563
|
+
export function validateStatusLaneSummaryV1(value) {
|
|
564
|
+
if (!isRecord(value))
|
|
565
|
+
return invalid("status lane summary must be an object");
|
|
566
|
+
return combine([
|
|
567
|
+
rejectUnknownProperties(value, ["lane_id", "task_ref", "lane_class", "state", "failure_class", "safe_next_action", "refs", "invocation_ref_kind", "retry_count", "verdict_status", "workflow_id", "plan_revision_id", "attempt_id", "created_at", "started_at", "updated_at", "completed_at", "event_refs", "audit_refs", "observability_ref", "log_ref", "debug_ref"]),
|
|
568
|
+
requireFields(value, ["lane_id", "workflow_id", "plan_revision_id", "task_ref", "lane_class", "state", "created_at", "updated_at", "safe_next_action", "refs", "event_refs", "audit_refs"]),
|
|
569
|
+
validateOpaqueId(value.lane_id, "lane_id"),
|
|
570
|
+
validateOpaqueId(value.workflow_id, "workflow_id"),
|
|
571
|
+
validateOpaqueId(value.plan_revision_id, "plan_revision_id"),
|
|
572
|
+
value.attempt_id === undefined ? valid() : validateOpaqueId(value.attempt_id, "attempt_id"),
|
|
573
|
+
validateOpaqueRef(value.task_ref, "task_ref"),
|
|
574
|
+
isEnumValue(value.lane_class, ["planning_draft", "planning_refine", "planning_review", "research", "documentation", "verification", "diagnostics", "other"]) ? valid() : invalid("lane_class is invalid"),
|
|
575
|
+
isEnumValue(value.state, PERSISTED_LANE_STATES) ? valid() : invalid("lane summary state is invalid or future-gated"),
|
|
576
|
+
value.state === "hard_cancel_proven" ? invalid("Release 1 status lane summaries must not claim hard cancel is proven") : valid(),
|
|
577
|
+
value.failure_class === undefined || isEnumValue(value.failure_class, LANE_FAILURE_CLASSES) ? valid() : invalid("failure_class is invalid"),
|
|
578
|
+
value.invocation_ref_kind === undefined || isEnumValue(value.invocation_ref_kind, LANE_INVOCATION_REF_KINDS) ? valid() : invalid("invocation_ref_kind is invalid"),
|
|
579
|
+
value.retry_count === undefined || (typeof value.retry_count === "number" && Number.isInteger(value.retry_count) && value.retry_count >= 0 && value.retry_count <= 2) ? valid() : invalid("retry_count is invalid"),
|
|
580
|
+
value.verdict_status === undefined || isEnumValue(value.verdict_status, LANE_VERDICT_STATUSES) ? valid() : invalid("verdict_status is invalid"),
|
|
581
|
+
isEnumValue(value.safe_next_action, SAFE_NEXT_ACTIONS) ? valid() : invalid("safe_next_action is invalid"),
|
|
582
|
+
validateStringArray(value.refs, "refs", undefined, 20),
|
|
583
|
+
validateStringArray(value.event_refs, "event_refs", undefined, 20),
|
|
584
|
+
validateStringArray(value.audit_refs, "audit_refs", undefined, 20),
|
|
585
|
+
validateTimestamp(value.created_at, "created_at"),
|
|
586
|
+
value.started_at === undefined ? valid() : validateTimestamp(value.started_at, "started_at"),
|
|
587
|
+
validateTimestamp(value.updated_at, "updated_at"),
|
|
588
|
+
value.completed_at === undefined ? valid() : validateTimestamp(value.completed_at, "completed_at"),
|
|
589
|
+
value.log_ref === undefined ? valid() : validateOpaqueRef(value.log_ref, "log_ref"),
|
|
590
|
+
value.observability_ref === undefined ? valid() : validateOpaqueRef(value.observability_ref, "observability_ref"),
|
|
591
|
+
value.debug_ref === undefined ? valid() : validateOpaqueRef(value.debug_ref, "debug_ref"),
|
|
592
|
+
validateNoForbiddenRawPayloads(value, "status_lane_summary")
|
|
593
|
+
]);
|
|
594
|
+
}
|
|
595
|
+
export function assertStatusLaneSummaryV1(value) {
|
|
596
|
+
const result = validateStatusLaneSummaryV1(value);
|
|
597
|
+
if (!result.ok)
|
|
598
|
+
throw new Error(result.errors.join("; "));
|
|
599
|
+
}
|
|
600
|
+
export function validateStatusResponseV1(value) {
|
|
601
|
+
if (!isRecord(value))
|
|
602
|
+
return invalid("status response must be an object");
|
|
603
|
+
const safeActions = Array.isArray(value.safe_next_actions) ? value.safe_next_actions : [];
|
|
604
|
+
return combine([
|
|
605
|
+
validateResponseEnvelopeV1(value, "flowdesk.status.response.v1"),
|
|
606
|
+
isEnumValue(value.workflow_state, WORKFLOW_STATES) ? valid() : invalid("workflow_state is invalid"),
|
|
607
|
+
value.current_step_id === undefined ? valid() : validateOpaqueId(value.current_step_id, "current_step_id"),
|
|
608
|
+
Array.isArray(value.lane_summaries) ? combine(value.lane_summaries.map((summary) => validateStatusLaneSummaryV1(summary))) : invalid("lane_summaries must be an array"),
|
|
609
|
+
validateProviderHealthSummaryV1(value.provider_health_summary),
|
|
610
|
+
value.blocker === undefined ? valid() : validateBlockerSummaryV1(value.blocker),
|
|
611
|
+
value.checkpoint_id === undefined ? valid() : validateOpaqueId(value.checkpoint_id, "checkpoint_id"),
|
|
612
|
+
value.ok === false && safeActions.some((action) => action === "/flowdesk-resume" || action === "/flowdesk-retry") ? invalid("failed status responses must not suggest resume or retry") : valid(),
|
|
613
|
+
validateNoForbiddenRawPayloads(value, "status_response")
|
|
614
|
+
]);
|
|
615
|
+
}
|
|
616
|
+
export function assertStatusResponseV1(value) {
|
|
617
|
+
const result = validateStatusResponseV1(value);
|
|
618
|
+
if (!result.ok)
|
|
619
|
+
throw new Error(result.errors.join("; "));
|
|
620
|
+
}
|
|
621
|
+
export function validateStatusSummaryArtifactV1(value) {
|
|
622
|
+
if (!isRecord(value))
|
|
623
|
+
return invalid("status summary artifact must be an object");
|
|
624
|
+
return combine([
|
|
625
|
+
validateSchemaArtifactValue("flowdesk.status_summary.v1", value),
|
|
626
|
+
validateOpaqueId(value.workflow_id, "workflow_id"),
|
|
627
|
+
isEnumValue(value.state, WORKFLOW_STATES) ? valid() : invalid("state is invalid"),
|
|
628
|
+
value.current_step_id === undefined ? valid() : validateOpaqueId(value.current_step_id, "current_step_id"),
|
|
629
|
+
value.blocker_summary === undefined ? valid() : validateBlockerSummaryV1(value.blocker_summary, "blocker_summary"),
|
|
630
|
+
validateStringArray(value.lane_summary_refs, "lane_summary_refs", undefined, 20),
|
|
631
|
+
value.usage_summary_ref === undefined ? valid() : validateOpaqueRef(value.usage_summary_ref, "usage_summary_ref"),
|
|
632
|
+
value.provider_health_summary_ref === undefined ? valid() : validateOpaqueRef(value.provider_health_summary_ref, "provider_health_summary_ref"),
|
|
633
|
+
value.checkpoint_id === undefined ? valid() : validateOpaqueId(value.checkpoint_id, "checkpoint_id"),
|
|
634
|
+
validateStringArray(value.safe_next_actions, "safe_next_actions", SAFE_NEXT_ACTIONS, 8),
|
|
635
|
+
validateStringArray(value.audit_refs, "audit_refs", undefined, 20),
|
|
636
|
+
value.debug_ref === undefined ? valid() : validateOpaqueRef(value.debug_ref, "debug_ref")
|
|
637
|
+
]);
|
|
638
|
+
}
|
|
639
|
+
export function assertStatusSummaryArtifactV1(value) {
|
|
640
|
+
const result = validateStatusSummaryArtifactV1(value);
|
|
641
|
+
if (!result.ok)
|
|
642
|
+
throw new Error(result.errors.join("; "));
|
|
643
|
+
}
|
|
644
|
+
export function validateWorkflowStepV1(value, label = "workflow step") {
|
|
645
|
+
if (!isRecord(value))
|
|
646
|
+
return invalid(`${label} must be an object`);
|
|
647
|
+
return combine([
|
|
648
|
+
rejectUnknownProperties(value, ["step_id", "title", "state", "lane_class", "requires_guard", "required_fresh_checks"]),
|
|
649
|
+
requireFields(value, ["step_id", "title", "state", "requires_guard", "required_fresh_checks"]),
|
|
650
|
+
validateOpaqueId(value.step_id, "step_id"),
|
|
651
|
+
typeof value.title === "string" && value.title.length > 0 && value.title.length <= 160 ? validateNoForbiddenRawPayloads(value.title, "title") : invalid("title is invalid"),
|
|
652
|
+
isEnumValue(value.state, WORKFLOW_STATES) ? valid() : invalid("step state is invalid"),
|
|
653
|
+
value.lane_class === undefined || isEnumValue(value.lane_class, ["planning_draft", "planning_refine", "planning_review", "research", "documentation", "verification", "diagnostics", "other"]) ? valid() : invalid("lane_class is invalid"),
|
|
654
|
+
typeof value.requires_guard === "boolean" ? valid() : invalid("requires_guard must be boolean"),
|
|
655
|
+
Array.isArray(value.required_fresh_checks) ? combine(value.required_fresh_checks.map((check, index) => validateFreshCheckV1(check, `required_fresh_checks[${index}]`))) : invalid("required_fresh_checks must be an array"),
|
|
656
|
+
validateNoForbiddenRawPayloads(value, "workflow_step")
|
|
657
|
+
]);
|
|
658
|
+
}
|
|
659
|
+
export function validateWorkflowTaxonomyV1(value) {
|
|
660
|
+
if (!isRecord(value))
|
|
661
|
+
return invalid("taxonomy must be an object");
|
|
662
|
+
return combine([
|
|
663
|
+
rejectUnknownProperties(value, ["primary_category", "difficulty_drivers", "coupling_scope", "algorithmic_hardness", "architecture_hardness", "migration_state_hardness", "domain_uncertainty", "verification_hardness", "operational_risk", "policy_professional_boundary"]),
|
|
664
|
+
requireFields(value, ["primary_category", "difficulty_drivers", "coupling_scope", "algorithmic_hardness", "architecture_hardness", "migration_state_hardness", "domain_uncertainty", "verification_hardness", "operational_risk", "policy_professional_boundary"]),
|
|
665
|
+
isEnumValue(value.primary_category, ["coding", "debugging", "refactor", "test", "documentation", "research", "planning", "review", "security", "data", "ops", "design", "specialist_reference"]) ? valid() : invalid("taxonomy.primary_category is invalid"),
|
|
666
|
+
validateStringArray(value.difficulty_drivers, "taxonomy.difficulty_drivers", undefined, 8),
|
|
667
|
+
isEnumValue(value.coupling_scope, ["single_file", "few_files", "module", "cross_module", "repo_wide", "multi_repo", "external_system"]) ? valid() : invalid("taxonomy.coupling_scope is invalid"),
|
|
668
|
+
isEnumValue(value.algorithmic_hardness, ["none", "low", "moderate", "high", "research_grade"]) ? valid() : invalid("taxonomy.algorithmic_hardness is invalid"),
|
|
669
|
+
isEnumValue(value.architecture_hardness, ["none", "low", "moderate", "high", "system_boundary"]) ? valid() : invalid("taxonomy.architecture_hardness is invalid"),
|
|
670
|
+
isEnumValue(value.migration_state_hardness, ["none", "low", "moderate", "high", "irreversible"]) ? valid() : invalid("taxonomy.migration_state_hardness is invalid"),
|
|
671
|
+
isEnumValue(value.domain_uncertainty, ["none", "low", "moderate", "high", "expert_review_required"]) ? valid() : invalid("taxonomy.domain_uncertainty is invalid"),
|
|
672
|
+
isEnumValue(value.verification_hardness, ["none", "low", "moderate", "high", "external_lab"]) ? valid() : invalid("taxonomy.verification_hardness is invalid"),
|
|
673
|
+
isEnumValue(value.operational_risk, ["none", "low", "moderate", "high", "critical"]) ? valid() : invalid("taxonomy.operational_risk is invalid"),
|
|
674
|
+
isEnumValue(value.policy_professional_boundary, ["ordinary", "sensitive", "restricted", "specialist_reference_only", "professional_human_required"]) ? valid() : invalid("taxonomy.policy_professional_boundary is invalid"),
|
|
675
|
+
validateNoForbiddenRawPayloads(value, "taxonomy")
|
|
676
|
+
]);
|
|
677
|
+
}
|
|
678
|
+
export function assertWorkflowTaxonomyV1(value) {
|
|
679
|
+
const result = validateWorkflowTaxonomyV1(value);
|
|
680
|
+
if (!result.ok)
|
|
681
|
+
throw new Error(result.errors.join("; "));
|
|
682
|
+
}
|
|
683
|
+
export function assertWorkflowStepV1(value) {
|
|
684
|
+
const result = validateWorkflowStepV1(value);
|
|
685
|
+
if (!result.ok)
|
|
686
|
+
throw new Error(result.errors.join("; "));
|
|
687
|
+
}
|
|
688
|
+
export function validateWorkflowPlanV1(value) {
|
|
689
|
+
if (!isRecord(value))
|
|
690
|
+
return invalid("workflow plan must be an object");
|
|
691
|
+
return combine([
|
|
692
|
+
validateSchemaArtifactValue("flowdesk.workflow_plan.v1", value),
|
|
693
|
+
validateOpaqueId(value.plan_revision_id, "plan_revision_id"),
|
|
694
|
+
validateOpaqueId(value.workflow_id, "workflow_id"),
|
|
695
|
+
validateTimestamp(value.created_at, "created_at"),
|
|
696
|
+
validateWorkflowTaxonomyV1(value.taxonomy),
|
|
697
|
+
Array.isArray(value.steps) && value.steps.length > 0 ? combine(value.steps.map((step, index) => validateWorkflowStepV1(step, `steps[${index}]`))) : invalid("steps must be a non-empty array"),
|
|
698
|
+
Array.isArray(value.required_approvals) ? combine(value.required_approvals.map((check, index) => validateGuardCheckV1(check, `required_approvals[${index}]`))) : invalid("required_approvals must be an array"),
|
|
699
|
+
typeof value.verification_summary === "string" && value.verification_summary.length > 0 && value.verification_summary.length <= 500 ? validateNoForbiddenRawPayloads(value.verification_summary, "verification_summary") : invalid("verification_summary is invalid")
|
|
700
|
+
]);
|
|
701
|
+
}
|
|
702
|
+
export function assertWorkflowPlanV1(value) {
|
|
703
|
+
const result = validateWorkflowPlanV1(value);
|
|
704
|
+
if (!result.ok)
|
|
705
|
+
throw new Error(result.errors.join("; "));
|
|
706
|
+
}
|
|
707
|
+
export function validatePlanSummaryArtifactV1(value) {
|
|
708
|
+
if (!isRecord(value))
|
|
709
|
+
return invalid("plan summary artifact must be an object");
|
|
710
|
+
return combine([
|
|
711
|
+
validateSchemaArtifactValue("flowdesk.plan_summary.v1", value),
|
|
712
|
+
validateOpaqueId(value.plan_revision_id, "plan_revision_id"),
|
|
713
|
+
validateOpaqueId(value.workflow_id, "workflow_id"),
|
|
714
|
+
validateTimestamp(value.created_at, "created_at"),
|
|
715
|
+
typeof value.goal_summary === "string" && value.goal_summary.length > 0 && value.goal_summary.length <= 500 ? validateNoForbiddenRawPayloads(value.goal_summary, "goal_summary") : invalid("goal_summary is invalid"),
|
|
716
|
+
typeof value.scope_summary === "string" && value.scope_summary.length > 0 && value.scope_summary.length <= 500 ? validateNoForbiddenRawPayloads(value.scope_summary, "scope_summary") : invalid("scope_summary is invalid"),
|
|
717
|
+
isEnumValue(value.risk_tier, ["low", "medium", "high", "blocked"]) ? valid() : invalid("risk_tier is invalid"),
|
|
718
|
+
Array.isArray(value.required_approvals) ? combine(value.required_approvals.map((check, index) => validateGuardCheckV1(check, `required_approvals[${index}]`))) : invalid("required_approvals must be an array"),
|
|
719
|
+
validateStringArray(value.step_summary_refs, "step_summary_refs", undefined, 20),
|
|
720
|
+
typeof value.verification_summary === "string" && value.verification_summary.length > 0 && value.verification_summary.length <= 500 ? validateNoForbiddenRawPayloads(value.verification_summary, "verification_summary") : invalid("verification_summary is invalid")
|
|
721
|
+
]);
|
|
722
|
+
}
|
|
723
|
+
export function assertPlanSummaryArtifactV1(value) {
|
|
724
|
+
const result = validatePlanSummaryArtifactV1(value);
|
|
725
|
+
if (!result.ok)
|
|
726
|
+
throw new Error(result.errors.join("; "));
|
|
727
|
+
}
|
|
728
|
+
export function validateLaneSummaryArtifactV1(value) {
|
|
729
|
+
if (!isRecord(value))
|
|
730
|
+
return invalid("lane summary artifact must be an object");
|
|
731
|
+
const { schema_version: _schemaVersion, ...summary } = value;
|
|
732
|
+
return combine([
|
|
733
|
+
validateSchemaArtifactValue("flowdesk.lane_summary.v1", value),
|
|
734
|
+
validateStatusLaneSummaryV1(summary),
|
|
735
|
+
value.schema_version === "flowdesk.lane_summary.v1" ? valid() : invalid("lane summary schema_version is invalid")
|
|
736
|
+
]);
|
|
737
|
+
}
|
|
738
|
+
export function assertLaneSummaryArtifactV1(value) {
|
|
739
|
+
const result = validateLaneSummaryArtifactV1(value);
|
|
740
|
+
if (!result.ok)
|
|
741
|
+
throw new Error(result.errors.join("; "));
|
|
742
|
+
}
|
|
743
|
+
export function validateAbortResponseV1(value) {
|
|
744
|
+
if (!isRecord(value))
|
|
745
|
+
return invalid("abort response must be an object");
|
|
746
|
+
return combine([
|
|
747
|
+
validateResponseEnvelopeV1(value, "flowdesk.abort.response.v1"),
|
|
748
|
+
["cancel_requested", "cancel_observed", "cancel_failed"].includes(String(value.cancellation_state)) ? valid() : invalid("cancellation_state is invalid for Release 1 abort response"),
|
|
749
|
+
validateStringArray(value.remaining_safe_actions, "remaining_safe_actions", SAFE_NEXT_ACTIONS, 8)
|
|
750
|
+
]);
|
|
751
|
+
}
|
|
752
|
+
export function assertAbortResponseV1(value) {
|
|
753
|
+
const result = validateAbortResponseV1(value);
|
|
754
|
+
if (!result.ok)
|
|
755
|
+
throw new Error(result.errors.join("; "));
|
|
756
|
+
}
|
|
757
|
+
export function validateUsageRequestV1(value) {
|
|
758
|
+
if (!isRecord(value))
|
|
759
|
+
return invalid("usage request must be an object");
|
|
760
|
+
return combine([
|
|
761
|
+
validateRequestEnvelope(value, "flowdesk.usage.request.v1"),
|
|
762
|
+
validateProviderFamily(value.provider_family),
|
|
763
|
+
typeof value.refresh === "boolean" ? valid() : invalid("refresh must be boolean")
|
|
764
|
+
]);
|
|
765
|
+
}
|
|
766
|
+
export function assertUsageRequestV1(value) {
|
|
767
|
+
const result = validateUsageRequestV1(value);
|
|
768
|
+
if (!result.ok)
|
|
769
|
+
throw new Error(result.errors.join("; "));
|
|
770
|
+
}
|
|
771
|
+
export function validateUsageResponseV1(value) {
|
|
772
|
+
if (!isRecord(value))
|
|
773
|
+
return invalid("usage response must be an object");
|
|
774
|
+
const uncertainty = Array.isArray(value.uncertainty_flags) ? value.uncertainty_flags : [];
|
|
775
|
+
const failClosedFlags = ["unknown", "stale", "refused", "shared_limit_suspected", "fallback_derived", "model_generated"];
|
|
776
|
+
return combine([
|
|
777
|
+
validateResponseEnvelopeV1(value, "flowdesk.usage.response.v1"),
|
|
778
|
+
validateOpaqueRef(value.usage_snapshot_ref, "usage_snapshot_ref"),
|
|
779
|
+
value.provider_health_snapshot_ref === undefined ? valid() : validateOpaqueRef(value.provider_health_snapshot_ref, "provider_health_snapshot_ref"),
|
|
780
|
+
isEnumValue(value.freshness, ["fresh", "stale", "unknown"]) ? valid() : invalid("freshness is invalid"),
|
|
781
|
+
isEnumValue(value.dispatchability, ["dispatchable", "diagnostic_only", "non_dispatchable"]) ? valid() : invalid("dispatchability is invalid"),
|
|
782
|
+
validateStringArray(value.uncertainty_flags, "uncertainty_flags", USAGE_UNCERTAINTY_FLAGS),
|
|
783
|
+
value.freshness !== "fresh" && value.dispatchability === "dispatchable" ? invalid("non-fresh usage response must not be dispatchable") : valid(),
|
|
784
|
+
uncertainty.some((flag) => failClosedFlags.includes(String(flag))) && value.dispatchability !== "non_dispatchable" ? invalid("unsafe usage response uncertainty must be non_dispatchable") : valid()
|
|
785
|
+
]);
|
|
786
|
+
}
|
|
787
|
+
export function assertUsageResponseV1(value) {
|
|
788
|
+
const result = validateUsageResponseV1(value);
|
|
789
|
+
if (!result.ok)
|
|
790
|
+
throw new Error(result.errors.join("; "));
|
|
791
|
+
}
|
|
792
|
+
export function validateUsageSnapshotV1(value) {
|
|
793
|
+
if (!isRecord(value))
|
|
794
|
+
return invalid("usage snapshot must be an object");
|
|
795
|
+
const uncertainty = Array.isArray(value.uncertainty_flags) ? value.uncertainty_flags : [];
|
|
796
|
+
const failClosedFlags = ["unknown", "stale", "refused", "shared_limit_suspected", "fallback_derived", "model_generated"];
|
|
797
|
+
return combine([
|
|
798
|
+
validateSchemaArtifactValue("flowdesk.usage_snapshot.v1", value),
|
|
799
|
+
validateOpaqueId(value.snapshot_id, "snapshot_id"),
|
|
800
|
+
validateProviderFamily(value.provider_family),
|
|
801
|
+
typeof value.model_family === "string" && value.model_family.length > 0 && value.model_family.length <= 128 ? validateNoForbiddenRawPayloads(value.model_family, "model_family") : invalid("model_family is required"),
|
|
802
|
+
isEnumValue(value.freshness, ["fresh", "stale", "unknown"]) ? valid() : invalid("freshness is invalid"),
|
|
803
|
+
typeof value.freshness_ttl === "number" && value.freshness_ttl >= 0 ? valid() : invalid("freshness_ttl is invalid"),
|
|
804
|
+
typeof value.reset_time === "string" && value.reset_time.length > 0 ? validateNoForbiddenRawPayloads(value.reset_time, "reset_time") : invalid("reset_time is required"),
|
|
805
|
+
typeof value.reset_bucket === "string" && value.reset_bucket.length > 0 ? validateNoForbiddenRawPayloads(value.reset_bucket, "reset_bucket") : invalid("reset_bucket is required"),
|
|
806
|
+
isEnumValue(value.dispatchability, ["dispatchable", "diagnostic_only", "non_dispatchable"]) ? valid() : invalid("dispatchability is invalid"),
|
|
807
|
+
validateStringArray(value.uncertainty_flags, "uncertainty_flags", USAGE_UNCERTAINTY_FLAGS),
|
|
808
|
+
uncertainty.some((flag) => failClosedFlags.includes(String(flag))) && value.dispatchability !== "non_dispatchable" ? invalid("unsafe usage uncertainty must be non_dispatchable") : valid(),
|
|
809
|
+
value.freshness !== "fresh" && value.dispatchability === "dispatchable" ? invalid("non-fresh usage must not be dispatchable") : valid(),
|
|
810
|
+
validateOpaqueRef(value.source_ref, "source_ref")
|
|
811
|
+
]);
|
|
812
|
+
}
|
|
813
|
+
export function assertUsageSnapshotV1(value) {
|
|
814
|
+
const result = validateUsageSnapshotV1(value);
|
|
815
|
+
if (!result.ok)
|
|
816
|
+
throw new Error(result.errors.join("; "));
|
|
817
|
+
}
|
|
818
|
+
export function validateProviderHealthSnapshotV1(value) {
|
|
819
|
+
if (!isRecord(value))
|
|
820
|
+
return invalid("provider health snapshot must be an object");
|
|
821
|
+
const remediation = typeof value.safe_remediation === "string" ? value.safe_remediation : "";
|
|
822
|
+
return combine([
|
|
823
|
+
validateSchemaArtifactValue("flowdesk.provider_health_snapshot.v1", value),
|
|
824
|
+
validateOpaqueId(value.snapshot_id, "snapshot_id"),
|
|
825
|
+
validateProviderFamily(value.provider_family),
|
|
826
|
+
value.model_family === undefined ? valid() : typeof value.model_family === "string" && value.model_family.length <= 128 ? validateNoForbiddenRawPayloads(value.model_family, "model_family") : invalid("model_family must be a bounded string"),
|
|
827
|
+
typeof value.observed_at === "string" && value.observed_at.length > 0 ? valid() : invalid("observed_at is required"),
|
|
828
|
+
isEnumValue(value.freshness, ["fresh", "stale", "unknown"]) ? valid() : invalid("freshness is invalid"),
|
|
829
|
+
typeof value.freshness_ttl === "number" && value.freshness_ttl >= 0 ? valid() : invalid("freshness_ttl is invalid"),
|
|
830
|
+
isEnumValue(value.source_surface, ["opencode_config", "plugin_event", "doctor_probe", "usage_collector", "provider_smoke_test", "manual_report", "unknown"]) ? valid() : invalid("source_surface is invalid"),
|
|
831
|
+
isEnumValue(value.availability_state, ["healthy", "degraded", "unavailable", "unknown"]) ? valid() : invalid("availability_state is invalid"),
|
|
832
|
+
isEnumValue(value.failure_class, PROVIDER_FAILURE_CLASSES) ? valid() : invalid("failure_class is invalid"),
|
|
833
|
+
isEnumValue(value.dispatchability, ["dispatchable", "diagnostic_only", "non_dispatchable"]) ? valid() : invalid("dispatchability is invalid"),
|
|
834
|
+
value.freshness !== "fresh" && value.dispatchability === "dispatchable" ? invalid("non-fresh provider health must not be dispatchable") : valid(),
|
|
835
|
+
["degraded", "unavailable", "unknown"].includes(String(value.availability_state)) && value.dispatchability === "dispatchable" ? invalid("degraded, unavailable, or unknown provider health must not be dispatchable") : valid(),
|
|
836
|
+
value.failure_class !== "none" && value.dispatchability === "dispatchable" ? invalid("provider failure classes must not be dispatchable") : valid(),
|
|
837
|
+
validateOpaqueRef(value.source_ref, "source_ref"),
|
|
838
|
+
/\b(?:fallback|reselect|reselection)\b/i.test(remediation) ? invalid("provider health remediation must not suggest fallback or reselection") : valid(),
|
|
839
|
+
typeof value.safe_remediation === "string" && value.safe_remediation.length > 0 && value.safe_remediation.length <= 500 ? validateNoForbiddenRawPayloads(value.safe_remediation, "safe_remediation") : invalid("safe_remediation is required")
|
|
840
|
+
]);
|
|
841
|
+
}
|
|
842
|
+
export function assertProviderHealthSnapshotV1(value) {
|
|
843
|
+
const result = validateProviderHealthSnapshotV1(value);
|
|
844
|
+
if (!result.ok)
|
|
845
|
+
throw new Error(result.errors.join("; "));
|
|
846
|
+
}
|
|
847
|
+
export function validateGuardRequestV1(value) {
|
|
848
|
+
if (!isRecord(value))
|
|
849
|
+
return invalid("guard request must be an object");
|
|
850
|
+
const needsDispatchEvidence = value.requested_operation === "real-opencode-dispatch";
|
|
851
|
+
return combine([
|
|
852
|
+
validateSchemaArtifactValue("flowdesk.guard_request.v1", value),
|
|
853
|
+
validateOpaqueId(value.guard_request_id, "guard_request_id"),
|
|
854
|
+
validateOpaqueId(value.workflow_id, "workflow_id"),
|
|
855
|
+
isEnumValue(value.requested_operation, ["guarded-dry-run", "fake-runtime", "command-steering", "real-opencode-dispatch", "non-dispatch-permission"]) ? valid() : invalid("requested_operation is invalid"),
|
|
856
|
+
needsDispatchEvidence && value.usage_snapshot_ref === undefined ? invalid("real dispatch requires usage_snapshot_ref") : valid(),
|
|
857
|
+
needsDispatchEvidence && value.provider_health_snapshot_ref === undefined ? invalid("real dispatch requires provider_health_snapshot_ref") : valid(),
|
|
858
|
+
value.non_dispatch_permission_class === undefined ? valid() : isEnumValue(value.non_dispatch_permission_class, NON_DISPATCH_PERMISSION_CLASSES) ? valid() : invalid("non_dispatch_permission_class is invalid")
|
|
859
|
+
]);
|
|
860
|
+
}
|
|
861
|
+
export function assertGuardRequestV1(value) {
|
|
862
|
+
const result = validateGuardRequestV1(value);
|
|
863
|
+
if (!result.ok)
|
|
864
|
+
throw new Error(result.errors.join("; "));
|
|
865
|
+
}
|
|
866
|
+
export function validateGuardApprovedDispatchV1(value) {
|
|
867
|
+
if (!isRecord(value))
|
|
868
|
+
return invalid("guard dispatch approval must be an object");
|
|
869
|
+
const family = value.provider_family;
|
|
870
|
+
const modelProvider = typeof value.provider_qualified_model_id === "string" ? value.provider_qualified_model_id.split("/")[0] : undefined;
|
|
871
|
+
return combine([
|
|
872
|
+
validateSchemaArtifactValue("flowdesk.guard_approved_dispatch.v1", value),
|
|
873
|
+
validateOpaqueId(value.guard_decision_id, "guard_decision_id"),
|
|
874
|
+
validateOpaqueId(value.workflow_id, "workflow_id"),
|
|
875
|
+
validateOpaqueId(value.step_id, "step_id"),
|
|
876
|
+
validateOpaqueId(value.attempt_id, "attempt_id"),
|
|
877
|
+
validateProviderFamily(family),
|
|
878
|
+
family === "unknown" || family === "all" ? invalid("dispatch provider_family must be concrete") : valid(),
|
|
879
|
+
validateProviderQualifiedModelId(value.provider_qualified_model_id),
|
|
880
|
+
typeof family === "string" && modelProvider !== undefined && modelProvider !== family ? invalid("provider_qualified_model_id provider must match provider_family") : valid(),
|
|
881
|
+
validateOpaqueRef(value.usage_snapshot_ref, "usage_snapshot_ref"),
|
|
882
|
+
validateOpaqueRef(value.provider_health_snapshot_ref, "provider_health_snapshot_ref"),
|
|
883
|
+
validateOpaqueRef(value.runtime_capability_ref, "runtime_capability_ref"),
|
|
884
|
+
validateOpaqueRef(value.pre_dispatch_audit_ref, "pre_dispatch_audit_ref"),
|
|
885
|
+
typeof value.expires_at === "string" && value.expires_at.length > 0 ? valid() : invalid("expires_at is required")
|
|
886
|
+
]);
|
|
887
|
+
}
|
|
888
|
+
export function assertGuardApprovedDispatchV1(value) {
|
|
889
|
+
const result = validateGuardApprovedDispatchV1(value);
|
|
890
|
+
if (!result.ok)
|
|
891
|
+
throw new Error(result.errors.join("; "));
|
|
892
|
+
}
|
|
893
|
+
function validateFutureTimestamp(value, label, now) {
|
|
894
|
+
const timestampResult = validateTimestamp(value, label);
|
|
895
|
+
if (!timestampResult.ok || now === undefined)
|
|
896
|
+
return timestampResult;
|
|
897
|
+
const parsed = Date.parse(String(value));
|
|
898
|
+
return Number.isFinite(parsed) && parsed > now ? valid() : invalid(`${label} is expired`);
|
|
899
|
+
}
|
|
900
|
+
function validateProviderAndModelBinding(value, label, options) {
|
|
901
|
+
const family = value.provider_family;
|
|
902
|
+
const modelProvider = typeof value.provider_qualified_model_id === "string" ? value.provider_qualified_model_id.split("/")[0] : undefined;
|
|
903
|
+
return combine([
|
|
904
|
+
validateProviderFamily(family),
|
|
905
|
+
family === "unknown" || family === "all" ? invalid(`${label}.provider_family must be concrete`) : valid(),
|
|
906
|
+
validateProviderQualifiedModelId(value.provider_qualified_model_id),
|
|
907
|
+
typeof family === "string" && modelProvider !== undefined && modelProvider !== family ? invalid(`${label}.provider_qualified_model_id provider mismatch`) : valid(),
|
|
908
|
+
options?.expectedProviderFamily !== undefined && family !== options.expectedProviderFamily ? invalid(`${label}.provider_family mismatch`) : valid(),
|
|
909
|
+
options?.expectedProviderQualifiedModelId !== undefined && value.provider_qualified_model_id !== options.expectedProviderQualifiedModelId ? invalid(`${label}.provider_qualified_model_id mismatch`) : valid()
|
|
910
|
+
]);
|
|
911
|
+
}
|
|
912
|
+
export function validateManagedDispatchBetaPolicyV1(value, options) {
|
|
913
|
+
if (!isRecord(value))
|
|
914
|
+
return invalid("managed dispatch beta policy must be an object");
|
|
915
|
+
return combine([
|
|
916
|
+
rejectUnknownProperties(value, ["release_mode", "policy_mode", "config_hash", "policy_pack_hashes", "fallback_reselection_mode", "hard_chat_authority", "require_quarantine_on_ambiguity", "audit_ref"]),
|
|
917
|
+
requireFields(value, ["release_mode", "policy_mode", "config_hash", "policy_pack_hashes", "fallback_reselection_mode", "hard_chat_authority", "require_quarantine_on_ambiguity", "audit_ref"]),
|
|
918
|
+
value.release_mode === "managed_dispatch_beta" ? valid() : invalid("managed dispatch beta policy release_mode is invalid"),
|
|
919
|
+
value.policy_mode === "managed_dispatch_beta" ? valid() : invalid("managed dispatch beta policy mode is invalid"),
|
|
920
|
+
validateOpaqueRef(value.config_hash, "config_hash"),
|
|
921
|
+
options?.expectedConfigHash !== undefined && value.config_hash !== options.expectedConfigHash ? invalid("config_hash mismatch") : valid(),
|
|
922
|
+
validateOpaqueRefArray(value.policy_pack_hashes, "policy_pack_hashes"),
|
|
923
|
+
options?.expectedPolicyPackHashes !== undefined && JSON.stringify(value.policy_pack_hashes) !== JSON.stringify([...options.expectedPolicyPackHashes]) ? invalid("policy_pack_hashes mismatch") : valid(),
|
|
924
|
+
value.fallback_reselection_mode === "disabled" ? valid() : invalid("managed dispatch beta fallback must be disabled"),
|
|
925
|
+
value.hard_chat_authority === "disabled" ? valid() : invalid("managed dispatch beta hard chat authority must be disabled"),
|
|
926
|
+
value.require_quarantine_on_ambiguity === true ? valid() : invalid("managed dispatch beta must quarantine on ambiguity"),
|
|
927
|
+
validateOpaqueRef(value.audit_ref, "audit_ref")
|
|
928
|
+
]);
|
|
929
|
+
}
|
|
930
|
+
export function validateManagedDispatchBetaApprovalFreshnessV1(value, options) {
|
|
931
|
+
const approvalResult = validateGuardApprovedDispatchV1(value);
|
|
932
|
+
if (!approvalResult.ok || !isRecord(value))
|
|
933
|
+
return approvalResult;
|
|
934
|
+
return combine([
|
|
935
|
+
validateFutureTimestamp(value.expires_at, "guard dispatch approval expires_at", options?.now),
|
|
936
|
+
options?.expectedWorkflowId !== undefined && value.workflow_id !== options.expectedWorkflowId ? invalid("guard approval workflow_id mismatch") : valid(),
|
|
937
|
+
options?.expectedStepId !== undefined && value.step_id !== options.expectedStepId ? invalid("guard approval step_id mismatch") : valid(),
|
|
938
|
+
options?.expectedAttemptId !== undefined && value.attempt_id !== options.expectedAttemptId ? invalid("guard approval attempt_id mismatch") : valid(),
|
|
939
|
+
validateProviderAndModelBinding(value, "guard approval", options),
|
|
940
|
+
options?.expectedUsageSnapshotRef !== undefined && value.usage_snapshot_ref !== options.expectedUsageSnapshotRef ? invalid("guard approval usage_snapshot_ref mismatch") : valid(),
|
|
941
|
+
options?.expectedProviderHealthSnapshotRef !== undefined && value.provider_health_snapshot_ref !== options.expectedProviderHealthSnapshotRef ? invalid("guard approval provider_health_snapshot_ref mismatch") : valid(),
|
|
942
|
+
options?.expectedRuntimeCapabilityRef !== undefined && value.runtime_capability_ref !== options.expectedRuntimeCapabilityRef ? invalid("guard approval runtime_capability_ref mismatch") : valid(),
|
|
943
|
+
options?.expectedPreDispatchAuditRef !== undefined && value.pre_dispatch_audit_ref !== options.expectedPreDispatchAuditRef ? invalid("guard approval pre_dispatch_audit_ref mismatch") : valid()
|
|
944
|
+
]);
|
|
945
|
+
}
|
|
946
|
+
export function validateManagedDispatchBetaBindingEvidenceV1(value, options) {
|
|
947
|
+
if (!isRecord(value))
|
|
948
|
+
return invalid("managed dispatch beta binding evidence must be an object");
|
|
949
|
+
return combine([
|
|
950
|
+
rejectUnknownProperties(value, ["schema_version", "binding_ref", "workflow_id", "step_id", "attempt_id", "provider_family", "provider_qualified_model_id", "source", "trusted", "created_at", "expires_at"]),
|
|
951
|
+
requireFields(value, ["schema_version", "binding_ref", "workflow_id", "step_id", "attempt_id", "provider_family", "provider_qualified_model_id", "source", "trusted", "created_at", "expires_at"]),
|
|
952
|
+
value.schema_version === "flowdesk.managed_dispatch_beta.binding_evidence.v1" ? valid() : invalid("binding evidence schema_version is invalid"),
|
|
953
|
+
validateOpaqueRef(value.binding_ref, "binding_ref"),
|
|
954
|
+
validateOpaqueId(value.workflow_id, "workflow_id"),
|
|
955
|
+
validateOpaqueId(value.step_id, "step_id"),
|
|
956
|
+
validateOpaqueId(value.attempt_id, "attempt_id"),
|
|
957
|
+
options?.expectedWorkflowId !== undefined && value.workflow_id !== options.expectedWorkflowId ? invalid("binding evidence workflow_id mismatch") : valid(),
|
|
958
|
+
options?.expectedStepId !== undefined && value.step_id !== options.expectedStepId ? invalid("binding evidence step_id mismatch") : valid(),
|
|
959
|
+
options?.expectedAttemptId !== undefined && value.attempt_id !== options.expectedAttemptId ? invalid("binding evidence attempt_id mismatch") : valid(),
|
|
960
|
+
validateProviderAndModelBinding(value, "binding evidence", options),
|
|
961
|
+
value.source === "guard_approved_dispatch" ? valid() : invalid("binding evidence source is untrusted"),
|
|
962
|
+
value.trusted === true ? valid() : invalid("binding evidence must be trusted"),
|
|
963
|
+
validateTimestamp(value.created_at, "binding evidence created_at"),
|
|
964
|
+
validateFutureTimestamp(value.expires_at, "binding evidence expires_at", options?.now)
|
|
965
|
+
]);
|
|
966
|
+
}
|
|
967
|
+
export function validateManagedDispatchBetaUsageEvidenceV1(value, options) {
|
|
968
|
+
const result = validateUsageSnapshotV1(value);
|
|
969
|
+
if (!result.ok || !isRecord(value))
|
|
970
|
+
return result;
|
|
971
|
+
const uncertainty = Array.isArray(value.uncertainty_flags) ? value.uncertainty_flags : [];
|
|
972
|
+
const ttlMinutes = typeof value.freshness_ttl === "number" ? value.freshness_ttl : Number.NaN;
|
|
973
|
+
return combine([
|
|
974
|
+
value.freshness === "fresh" ? valid() : invalid("managed dispatch beta usage must be fresh"),
|
|
975
|
+
ttlMinutes > 0 ? valid() : invalid("managed dispatch beta usage freshness_ttl must be positive"),
|
|
976
|
+
value.reset_time !== "unknown" ? valid() : invalid("managed dispatch beta usage requires provider-native reset_time evidence"),
|
|
977
|
+
value.reset_bucket !== "unknown" ? valid() : invalid("managed dispatch beta usage requires provider-native reset_bucket evidence"),
|
|
978
|
+
value.dispatchability === "dispatchable" ? valid() : invalid("managed dispatch beta usage must be dispatchable"),
|
|
979
|
+
uncertainty.length === 0 ? valid() : invalid("managed dispatch beta usage uncertainty is ambiguous"),
|
|
980
|
+
options?.expectedProviderFamily !== undefined && value.provider_family !== options.expectedProviderFamily ? invalid("usage provider_family mismatch") : valid(),
|
|
981
|
+
options?.expectedModelFamily !== undefined && value.model_family !== options.expectedModelFamily ? invalid("usage model_family mismatch") : valid()
|
|
982
|
+
]);
|
|
983
|
+
}
|
|
984
|
+
export function validateManagedDispatchBetaUsageAuthorityEvidenceV1(value, usage, options) {
|
|
985
|
+
if (!isRecord(value))
|
|
986
|
+
return invalid("managed dispatch beta usage authority evidence must be an object");
|
|
987
|
+
const authority = value;
|
|
988
|
+
const authorityProviderFamily = value.provider_family;
|
|
989
|
+
return combine([
|
|
990
|
+
rejectUnknownProperties(value, ["schema_version", "authority_ref", "usage_snapshot_ref", "provider_family", "provider_qualified_model_id", "model_family", "source_kind", "source_version_ref", "auth_profile_ref", "auth_evidence_ref", "credential_scope_ref", "account_boundary_ref", "quota_evidence_ref", "usage_acquired", "reset_time", "reset_bucket", "source_ref", "conformance_ref", "redacted_evidence_refs", "trusted", "observed_at", "expires_at"]),
|
|
991
|
+
requireFields(value, ["schema_version", "authority_ref", "usage_snapshot_ref", "provider_family", "provider_qualified_model_id", "model_family", "source_kind", "source_version_ref", "auth_profile_ref", "auth_evidence_ref", "credential_scope_ref", "account_boundary_ref", "quota_evidence_ref", "usage_acquired", "reset_time", "reset_bucket", "source_ref", "conformance_ref", "redacted_evidence_refs", "trusted", "observed_at", "expires_at"]),
|
|
992
|
+
value.schema_version === "flowdesk.managed_dispatch_beta.usage_authority_evidence.v1" ? valid() : invalid("usage authority evidence schema_version is invalid"),
|
|
993
|
+
validateOpaqueRef(value.authority_ref, "authority_ref"),
|
|
994
|
+
validateOpaqueRef(value.usage_snapshot_ref, "usage_snapshot_ref"),
|
|
995
|
+
value.usage_snapshot_ref === usage.snapshot_id ? valid() : invalid("usage authority usage_snapshot_ref mismatch"),
|
|
996
|
+
validateProviderFamily(authorityProviderFamily),
|
|
997
|
+
authorityProviderFamily === "unknown" || authorityProviderFamily === "all" ? invalid("usage authority provider_family must be concrete") : valid(),
|
|
998
|
+
options?.expectedProviderFamily !== undefined && authorityProviderFamily !== options.expectedProviderFamily ? invalid("usage authority provider_family mismatch") : valid(),
|
|
999
|
+
authorityProviderFamily === usage.provider_family ? valid() : invalid("usage authority usage provider_family mismatch"),
|
|
1000
|
+
validateConcreteProviderQualifiedModelId(value.provider_qualified_model_id, "usage authority provider_qualified_model_id"),
|
|
1001
|
+
options?.expectedProviderQualifiedModelId !== undefined && value.provider_qualified_model_id !== options.expectedProviderQualifiedModelId ? invalid("usage authority provider_qualified_model_id mismatch") : valid(),
|
|
1002
|
+
typeof authority.model_family === "string" && authority.model_family.length > 0 && authority.model_family.length <= 120 ? validateNoForbiddenRawPayloads(authority.model_family, "usage authority model_family") : invalid("usage authority model_family is required"),
|
|
1003
|
+
authority.model_family === usage.model_family ? valid() : invalid("usage authority model_family mismatch"),
|
|
1004
|
+
options?.expectedModelFamily !== undefined && authority.model_family !== options.expectedModelFamily ? invalid("usage authority expected model_family mismatch") : valid(),
|
|
1005
|
+
isEnumValue(value.source_kind, ["provider_native", "dex_conductor", "openusage"]) ? valid() : invalid("usage authority source_kind is invalid"),
|
|
1006
|
+
validateOpaqueRef(value.source_version_ref, "source_version_ref"),
|
|
1007
|
+
validateOpaqueRef(value.auth_profile_ref, "auth_profile_ref"),
|
|
1008
|
+
options?.expectedAuthProfileRef !== undefined && value.auth_profile_ref !== options.expectedAuthProfileRef ? invalid("usage authority auth_profile_ref mismatch") : valid(),
|
|
1009
|
+
validateOpaqueRef(value.auth_evidence_ref, "auth_evidence_ref"),
|
|
1010
|
+
validateOpaqueRef(value.credential_scope_ref, "credential_scope_ref"),
|
|
1011
|
+
options?.expectedCredentialScopeRef !== undefined && value.credential_scope_ref !== options.expectedCredentialScopeRef ? invalid("usage authority credential_scope_ref mismatch") : valid(),
|
|
1012
|
+
validateOpaqueRef(value.account_boundary_ref, "account_boundary_ref"),
|
|
1013
|
+
options?.expectedAccountBoundaryRef !== undefined && value.account_boundary_ref !== options.expectedAccountBoundaryRef ? invalid("usage authority account_boundary_ref mismatch") : valid(),
|
|
1014
|
+
validateOpaqueRef(value.quota_evidence_ref, "quota_evidence_ref"),
|
|
1015
|
+
value.usage_acquired === true ? valid() : invalid("usage authority requires actual usage acquisition"),
|
|
1016
|
+
validateTimestamp(value.reset_time, "usage authority reset_time"),
|
|
1017
|
+
value.reset_time === usage.reset_time ? valid() : invalid("usage authority reset_time mismatch"),
|
|
1018
|
+
typeof authority.reset_bucket === "string" && authority.reset_bucket.length > 0 && authority.reset_bucket !== "unknown" ? validateNoForbiddenRawPayloads(authority.reset_bucket, "usage authority reset_bucket") : invalid("usage authority reset_bucket is required"),
|
|
1019
|
+
authority.reset_bucket === usage.reset_bucket ? valid() : invalid("usage authority reset_bucket mismatch"),
|
|
1020
|
+
validateOpaqueRef(value.source_ref, "source_ref"),
|
|
1021
|
+
value.source_ref === usage.source_ref ? valid() : invalid("usage authority source_ref mismatch"),
|
|
1022
|
+
validateOpaqueRef(value.conformance_ref, "usage authority conformance_ref"),
|
|
1023
|
+
validateOpaqueRefArray(value.redacted_evidence_refs, "usage authority redacted_evidence_refs", 20),
|
|
1024
|
+
Array.isArray(value.redacted_evidence_refs) && value.redacted_evidence_refs.length > 0 ? valid() : invalid("usage authority redacted_evidence_refs are required"),
|
|
1025
|
+
value.trusted === true ? valid() : invalid("usage authority evidence must be trusted"),
|
|
1026
|
+
validateTimestamp(value.observed_at, "usage authority observed_at"),
|
|
1027
|
+
validateFutureTimestamp(value.expires_at, "usage authority expires_at", options?.now)
|
|
1028
|
+
]);
|
|
1029
|
+
}
|
|
1030
|
+
export function validateManagedDispatchBetaProviderHealthEvidenceV1(value, options) {
|
|
1031
|
+
const result = validateProviderHealthSnapshotV1(value);
|
|
1032
|
+
if (!result.ok || !isRecord(value))
|
|
1033
|
+
return result;
|
|
1034
|
+
const observedAt = typeof value.observed_at === "string" ? Date.parse(value.observed_at) : Number.NaN;
|
|
1035
|
+
const ttlMinutes = typeof value.freshness_ttl === "number" ? value.freshness_ttl : Number.NaN;
|
|
1036
|
+
const freshUntil = observedAt + ttlMinutes * 60_000;
|
|
1037
|
+
return combine([
|
|
1038
|
+
value.freshness === "fresh" ? valid() : invalid("managed dispatch beta provider health must be fresh"),
|
|
1039
|
+
ttlMinutes > 0 ? valid() : invalid("managed dispatch beta provider health freshness_ttl must be positive"),
|
|
1040
|
+
options?.now !== undefined && (!Number.isFinite(freshUntil) || freshUntil <= options.now) ? invalid("managed dispatch beta provider health is stale") : valid(),
|
|
1041
|
+
value.availability_state === "healthy" ? valid() : invalid("managed dispatch beta provider health must be healthy"),
|
|
1042
|
+
value.failure_class === "none" ? valid() : invalid("managed dispatch beta provider health failure class must be none"),
|
|
1043
|
+
value.dispatchability === "dispatchable" ? valid() : invalid("managed dispatch beta provider health must be dispatchable"),
|
|
1044
|
+
value.source_surface === "provider_smoke_test" || value.source_surface === "usage_collector" || value.source_surface === "plugin_event" ? valid() : invalid("managed dispatch beta provider health requires OpenCode-native or provider-native evidence"),
|
|
1045
|
+
options?.expectedProviderFamily !== undefined && value.provider_family !== options.expectedProviderFamily ? invalid("provider health provider_family mismatch") : valid()
|
|
1046
|
+
]);
|
|
1047
|
+
}
|
|
1048
|
+
export function validateManagedDispatchBetaRuntimeEchoEvidenceV1(value, conformanceMetadata, options) {
|
|
1049
|
+
if (!isRecord(value))
|
|
1050
|
+
return invalid("managed dispatch beta runtime echo evidence must be an object");
|
|
1051
|
+
const conformanceResult = validateConformanceRuntimeMetadataV1(conformanceMetadata);
|
|
1052
|
+
const conformanceRecord = isRecord(conformanceMetadata) ? conformanceMetadata : {};
|
|
1053
|
+
return combine([
|
|
1054
|
+
rejectUnknownProperties(value, ["schema_version", "runtime_echo_ref", "workflow_id", "step_id", "attempt_id", "provider_family", "provider_qualified_model_id", "runtime_capability_ref", "conformance_ref", "runtime_echo_mode", "trusted", "observed_at", "expires_at"]),
|
|
1055
|
+
requireFields(value, ["schema_version", "runtime_echo_ref", "workflow_id", "step_id", "attempt_id", "provider_family", "provider_qualified_model_id", "runtime_capability_ref", "conformance_ref", "runtime_echo_mode", "trusted", "observed_at", "expires_at"]),
|
|
1056
|
+
value.schema_version === "flowdesk.managed_dispatch_beta.runtime_echo_evidence.v1" ? valid() : invalid("runtime echo evidence schema_version is invalid"),
|
|
1057
|
+
validateOpaqueRef(value.runtime_echo_ref, "runtime_echo_ref"),
|
|
1058
|
+
validateOpaqueId(value.workflow_id, "workflow_id"),
|
|
1059
|
+
validateOpaqueId(value.step_id, "step_id"),
|
|
1060
|
+
validateOpaqueId(value.attempt_id, "attempt_id"),
|
|
1061
|
+
options?.expectedWorkflowId !== undefined && value.workflow_id !== options.expectedWorkflowId ? invalid("runtime echo evidence workflow_id mismatch") : valid(),
|
|
1062
|
+
options?.expectedStepId !== undefined && value.step_id !== options.expectedStepId ? invalid("runtime echo evidence step_id mismatch") : valid(),
|
|
1063
|
+
options?.expectedAttemptId !== undefined && value.attempt_id !== options.expectedAttemptId ? invalid("runtime echo evidence attempt_id mismatch") : valid(),
|
|
1064
|
+
validateProviderAndModelBinding(value, "runtime echo evidence", options),
|
|
1065
|
+
validateOpaqueRef(value.runtime_capability_ref, "runtime_capability_ref"),
|
|
1066
|
+
options?.expectedRuntimeCapabilityRef !== undefined && value.runtime_capability_ref !== options.expectedRuntimeCapabilityRef ? invalid("runtime echo evidence runtime_capability_ref mismatch") : valid(),
|
|
1067
|
+
validateOpaqueRef(value.conformance_ref, "conformance_ref"),
|
|
1068
|
+
options?.expectedConformanceRef !== undefined && value.conformance_ref !== options.expectedConformanceRef ? invalid("runtime echo evidence conformance_ref mismatch") : valid(),
|
|
1069
|
+
value.runtime_echo_mode === "trusted" ? valid() : invalid("runtime echo evidence must be trusted"),
|
|
1070
|
+
value.trusted === true ? valid() : invalid("runtime echo evidence must be trusted"),
|
|
1071
|
+
validateTimestamp(value.observed_at, "runtime echo evidence observed_at"),
|
|
1072
|
+
validateFutureTimestamp(value.expires_at, "runtime echo evidence expires_at", options?.now),
|
|
1073
|
+
conformanceResult,
|
|
1074
|
+
conformanceRecord.dispatch_mode === "real-opencode-dispatch" ? valid() : invalid("conformance dispatch_mode is not managed-dispatch ready"),
|
|
1075
|
+
conformanceRecord.runtime_echo_mode === "trusted" ? valid() : invalid("conformance runtime echo is not trusted"),
|
|
1076
|
+
conformanceRecord.event_telemetry_mode === "sufficient" ? valid() : invalid("conformance telemetry is insufficient"),
|
|
1077
|
+
conformanceRecord.provider_health_mode === "dispatch_gate_ready" ? valid() : invalid("conformance provider health is not dispatch-gate ready"),
|
|
1078
|
+
conformanceRecord.fallback_reselection_mode === "disabled" ? valid() : invalid("conformance fallback reselection must be disabled"),
|
|
1079
|
+
conformanceRecord.hook_harness_mode === "enforce" ? valid() : invalid("conformance hook harness must enforce")
|
|
1080
|
+
]);
|
|
1081
|
+
}
|
|
1082
|
+
export function validateManagedDispatchBetaTelemetryCorrelationV1(value, options) {
|
|
1083
|
+
if (!isRecord(value))
|
|
1084
|
+
return invalid("managed dispatch beta telemetry correlation must be an object");
|
|
1085
|
+
return combine([
|
|
1086
|
+
rejectUnknownProperties(value, ["schema_version", "telemetry_ref", "workflow_id", "step_id", "attempt_id", "event_telemetry_mode", "correlation_count", "ambiguous", "source_refs"]),
|
|
1087
|
+
requireFields(value, ["schema_version", "telemetry_ref", "workflow_id", "step_id", "attempt_id", "event_telemetry_mode", "correlation_count", "ambiguous", "source_refs"]),
|
|
1088
|
+
value.schema_version === "flowdesk.managed_dispatch_beta.telemetry_correlation.v1" ? valid() : invalid("telemetry correlation schema_version is invalid"),
|
|
1089
|
+
validateOpaqueRef(value.telemetry_ref, "telemetry_ref"),
|
|
1090
|
+
validateOpaqueId(value.workflow_id, "workflow_id"),
|
|
1091
|
+
validateOpaqueId(value.step_id, "step_id"),
|
|
1092
|
+
validateOpaqueId(value.attempt_id, "attempt_id"),
|
|
1093
|
+
options?.expectedWorkflowId !== undefined && value.workflow_id !== options.expectedWorkflowId ? invalid("telemetry correlation workflow_id mismatch") : valid(),
|
|
1094
|
+
options?.expectedStepId !== undefined && value.step_id !== options.expectedStepId ? invalid("telemetry correlation step_id mismatch") : valid(),
|
|
1095
|
+
options?.expectedAttemptId !== undefined && value.attempt_id !== options.expectedAttemptId ? invalid("telemetry correlation attempt_id mismatch") : valid(),
|
|
1096
|
+
value.event_telemetry_mode === "sufficient" ? valid() : invalid("telemetry correlation is insufficient"),
|
|
1097
|
+
validateNonNegativeInteger(value.correlation_count, "telemetry correlation_count"),
|
|
1098
|
+
typeof value.correlation_count === "number" && value.correlation_count >= 2 ? valid() : invalid("telemetry correlation_count is insufficient"),
|
|
1099
|
+
value.ambiguous === false ? valid() : invalid("telemetry correlation is ambiguous"),
|
|
1100
|
+
validateOpaqueRefArray(value.source_refs, "telemetry source_refs", 20),
|
|
1101
|
+
Array.isArray(value.source_refs) && value.source_refs.length > 0 ? valid() : invalid("telemetry source_refs are required")
|
|
1102
|
+
]);
|
|
1103
|
+
}
|
|
1104
|
+
export function validateAttemptRecordV1(value) {
|
|
1105
|
+
if (!isRecord(value))
|
|
1106
|
+
return invalid("attempt record must be an object");
|
|
1107
|
+
return combine([
|
|
1108
|
+
validateSchemaArtifactValue("flowdesk.attempt_record.v1", value),
|
|
1109
|
+
validateOpaqueId(value.attempt_id, "attempt_id"),
|
|
1110
|
+
validateOpaqueId(value.workflow_id, "workflow_id"),
|
|
1111
|
+
value.step_id === undefined ? valid() : validateOpaqueId(value.step_id, "step_id"),
|
|
1112
|
+
isEnumValue(value.run_mode, RELEASE_1_RUN_MODES) ? valid() : invalid("run_mode is invalid"),
|
|
1113
|
+
value.run_mode === "real-opencode-dispatch" ? invalid("Release 1 attempt records cannot store real dispatch run mode") : valid(),
|
|
1114
|
+
isEnumValue(value.state_at_start, WORKFLOW_STATES) ? valid() : invalid("state_at_start is invalid"),
|
|
1115
|
+
value.state_at_end === undefined || isEnumValue(value.state_at_end, WORKFLOW_STATES) ? valid() : invalid("state_at_end is invalid"),
|
|
1116
|
+
isEnumValue(value.attempt_state, ATTEMPT_STATES) ? valid() : invalid("attempt_state is invalid"),
|
|
1117
|
+
isEnumValue(value.runtime_echo_validation, ["not_applicable", "untrusted", "trusted", "failed"]) ? valid() : invalid("runtime_echo_validation is invalid"),
|
|
1118
|
+
isEnumValue(value.artifact_disposition, ARTIFACT_DISPOSITIONS) ? valid() : invalid("artifact_disposition is invalid"),
|
|
1119
|
+
value.guard_decision_ref === undefined ? valid() : validateOpaqueRef(value.guard_decision_ref, "guard_decision_ref"),
|
|
1120
|
+
value.non_dispatch_permission_ref === undefined ? valid() : validateOpaqueRef(value.non_dispatch_permission_ref, "non_dispatch_permission_ref"),
|
|
1121
|
+
value.usage_snapshot_ref === undefined ? valid() : validateOpaqueRef(value.usage_snapshot_ref, "usage_snapshot_ref"),
|
|
1122
|
+
value.provider_health_snapshot_ref === undefined ? valid() : validateOpaqueRef(value.provider_health_snapshot_ref, "provider_health_snapshot_ref"),
|
|
1123
|
+
value.runtime_capability_ref === undefined ? valid() : validateOpaqueRef(value.runtime_capability_ref, "runtime_capability_ref"),
|
|
1124
|
+
validateOpaqueRef(value.pre_run_audit_ref, "pre_run_audit_ref"),
|
|
1125
|
+
value.verification_ref === undefined ? valid() : validateOpaqueRef(value.verification_ref, "verification_ref"),
|
|
1126
|
+
value.outcome_audit_ref === undefined ? valid() : validateOpaqueRef(value.outcome_audit_ref, "outcome_audit_ref"),
|
|
1127
|
+
value.failure_category === undefined || isEnumValue(value.failure_category, REDACTED_ERROR_CATEGORIES) ? valid() : invalid("failure_category is invalid"),
|
|
1128
|
+
validateStringArray(value.safe_next_actions, "safe_next_actions", SAFE_NEXT_ACTIONS, 8)
|
|
1129
|
+
]);
|
|
1130
|
+
}
|
|
1131
|
+
export function assertAttemptRecordV1(value) {
|
|
1132
|
+
const result = validateAttemptRecordV1(value);
|
|
1133
|
+
if (!result.ok)
|
|
1134
|
+
throw new Error(result.errors.join("; "));
|
|
1135
|
+
}
|
|
1136
|
+
export function validateWorkflowActiveV1(value, expected) {
|
|
1137
|
+
if (!isRecord(value))
|
|
1138
|
+
return invalid("workflow active record must be an object");
|
|
1139
|
+
return combine([
|
|
1140
|
+
validateSchemaArtifactValue("flowdesk.workflow_active.v1", value),
|
|
1141
|
+
value.active_workflow_id === undefined ? valid() : validateOpaqueId(value.active_workflow_id, "active_workflow_id"),
|
|
1142
|
+
value.active_attempt_id === undefined ? valid() : validateOpaqueId(value.active_attempt_id, "active_attempt_id"),
|
|
1143
|
+
isEnumValue(value.state, WORKFLOW_STATES) ? valid() : invalid("state is invalid"),
|
|
1144
|
+
validateStringArray(value.audit_refs, "audit_refs"),
|
|
1145
|
+
expected?.workflowId !== undefined && value.active_workflow_id !== undefined && value.active_workflow_id !== expected.workflowId ? invalid("active workflow mismatch") : valid(),
|
|
1146
|
+
expected?.attemptId !== undefined && value.active_attempt_id !== undefined && value.active_attempt_id !== expected.attemptId ? invalid("active attempt mismatch") : valid()
|
|
1147
|
+
]);
|
|
1148
|
+
}
|
|
1149
|
+
export function assertWorkflowActiveV1(value) {
|
|
1150
|
+
const result = validateWorkflowActiveV1(value);
|
|
1151
|
+
if (!result.ok)
|
|
1152
|
+
throw new Error(result.errors.join("; "));
|
|
1153
|
+
}
|
|
1154
|
+
export function validateWorkflowRecordV1(value) {
|
|
1155
|
+
if (!isRecord(value))
|
|
1156
|
+
return invalid("workflow record must be an object");
|
|
1157
|
+
return combine([
|
|
1158
|
+
validateSchemaArtifactValue("flowdesk.workflow_record.v1", value),
|
|
1159
|
+
validateOpaqueId(value.workflow_id, "workflow_id"),
|
|
1160
|
+
value.session_ref === undefined ? valid() : validateOpaqueRef(value.session_ref, "session_ref"),
|
|
1161
|
+
isEnumValue(value.state, WORKFLOW_STATES) ? valid() : invalid("state is invalid"),
|
|
1162
|
+
value.latest_plan_revision_id === undefined ? valid() : validateOpaqueId(value.latest_plan_revision_id, "latest_plan_revision_id"),
|
|
1163
|
+
value.current_step_id === undefined ? valid() : validateOpaqueId(value.current_step_id, "current_step_id"),
|
|
1164
|
+
validateOpaqueRef(value.project_root_ref, "project_root_ref"),
|
|
1165
|
+
typeof value.config_hash === "string" && value.config_hash.length > 0 ? validateNoForbiddenRawPayloads(value.config_hash, "config_hash") : invalid("config_hash is required"),
|
|
1166
|
+
validateOpaqueId(value.policy_pack_id, "policy_pack_id"),
|
|
1167
|
+
typeof value.policy_pack_hash === "string" && value.policy_pack_hash.length > 0 ? validateNoForbiddenRawPayloads(value.policy_pack_hash, "policy_pack_hash") : invalid("policy_pack_hash is required"),
|
|
1168
|
+
value.current_attempt_id === undefined ? valid() : validateOpaqueId(value.current_attempt_id, "current_attempt_id"),
|
|
1169
|
+
value.latest_checkpoint_id === undefined ? valid() : validateOpaqueId(value.latest_checkpoint_id, "latest_checkpoint_id"),
|
|
1170
|
+
isEnumValue(value.artifact_disposition, ARTIFACT_DISPOSITIONS) ? valid() : invalid("artifact_disposition is invalid"),
|
|
1171
|
+
validateStringArray(value.attempt_refs, "attempt_refs"),
|
|
1172
|
+
validateStringArray(value.checkpoint_refs, "checkpoint_refs"),
|
|
1173
|
+
validateStringArray(value.lane_refs, "lane_refs"),
|
|
1174
|
+
validateStringArray(value.latest_lane_summary_refs, "latest_lane_summary_refs"),
|
|
1175
|
+
validateStringArray(value.audit_refs, "audit_refs"),
|
|
1176
|
+
validateStringArray(value.safe_next_actions, "safe_next_actions", SAFE_NEXT_ACTIONS, 8)
|
|
1177
|
+
]);
|
|
1178
|
+
}
|
|
1179
|
+
export function assertWorkflowRecordV1(value) {
|
|
1180
|
+
const result = validateWorkflowRecordV1(value);
|
|
1181
|
+
if (!result.ok)
|
|
1182
|
+
throw new Error(result.errors.join("; "));
|
|
1183
|
+
}
|
|
1184
|
+
export function validateCheckpointRecordV1(value) {
|
|
1185
|
+
if (!isRecord(value))
|
|
1186
|
+
return invalid("checkpoint record must be an object");
|
|
1187
|
+
return combine([
|
|
1188
|
+
validateSchemaArtifactValue("flowdesk.checkpoint_record.v1", value),
|
|
1189
|
+
validateOpaqueId(value.checkpoint_id, "checkpoint_id"),
|
|
1190
|
+
validateOpaqueId(value.workflow_id, "workflow_id"),
|
|
1191
|
+
value.attempt_id === undefined ? valid() : validateOpaqueId(value.attempt_id, "attempt_id"),
|
|
1192
|
+
value.current_step_id === undefined ? valid() : validateOpaqueId(value.current_step_id, "current_step_id"),
|
|
1193
|
+
isEnumValue(value.resume_mode, RESUME_MODES) ? valid() : invalid("resume_mode is invalid"),
|
|
1194
|
+
isEnumValue(value.reason, ["planned_pause", "retryable_failure", "verification_failed", "blocked", "abort_requested", "status_snapshot"]) ? valid() : invalid("reason is invalid"),
|
|
1195
|
+
Array.isArray(value.audit_refs) && value.audit_refs.length > 0 ? validateStringArray(value.audit_refs, "audit_refs") : invalid("checkpoint requires durable audit_refs"),
|
|
1196
|
+
validateStringArray(value.artifact_refs, "artifact_refs"),
|
|
1197
|
+
Array.isArray(value.required_fresh_checks) ? combine(value.required_fresh_checks.map((check, index) => validateFreshCheckV1(check, `required_fresh_checks[${index}]`))) : invalid("required_fresh_checks must be an array"),
|
|
1198
|
+
validateStringArray(value.safe_next_actions, "safe_next_actions", SAFE_NEXT_ACTIONS, 8)
|
|
1199
|
+
]);
|
|
1200
|
+
}
|
|
1201
|
+
export function validateFreshCheckV1(value, label = "fresh check") {
|
|
1202
|
+
if (!isRecord(value))
|
|
1203
|
+
return invalid(`${label} must be an object`);
|
|
1204
|
+
return combine([
|
|
1205
|
+
rejectUnknownProperties(value, ["check", "required", "ref"]),
|
|
1206
|
+
requireFields(value, ["check", "required"]),
|
|
1207
|
+
isEnumValue(value.check, ["usage", "provider_health", "policy", "runtime_capability", "checkpoint", "audit"]) ? valid() : invalid(`${label}.check is invalid`),
|
|
1208
|
+
typeof value.required === "boolean" ? valid() : invalid(`${label}.required must be boolean`),
|
|
1209
|
+
value.ref === undefined ? valid() : validateOpaqueRef(value.ref, `${label}.ref`),
|
|
1210
|
+
validateNoForbiddenRawPayloads(value, label)
|
|
1211
|
+
]);
|
|
1212
|
+
}
|
|
1213
|
+
export function assertCheckpointRecordV1(value) {
|
|
1214
|
+
const result = validateCheckpointRecordV1(value);
|
|
1215
|
+
if (!result.ok)
|
|
1216
|
+
throw new Error(result.errors.join("; "));
|
|
1217
|
+
}
|
|
1218
|
+
export function validateActiveAttemptLockV1(value, now = Date.now()) {
|
|
1219
|
+
if (!isRecord(value))
|
|
1220
|
+
return invalid("active attempt lock must be an object");
|
|
1221
|
+
const expiresAt = typeof value.expires_at === "string" ? Date.parse(value.expires_at) : Number.NaN;
|
|
1222
|
+
return combine([
|
|
1223
|
+
validateSchemaArtifactValue("flowdesk.active_attempt_lock.v1", value),
|
|
1224
|
+
validateOpaqueId(value.workflow_id, "workflow_id"),
|
|
1225
|
+
validateOpaqueId(value.attempt_id, "attempt_id"),
|
|
1226
|
+
validateOpaqueRef(value.owner_ref, "owner_ref"),
|
|
1227
|
+
validateTimestamp(value.acquired_at, "acquired_at"),
|
|
1228
|
+
validateTimestamp(value.expires_at, "expires_at"),
|
|
1229
|
+
value.heartbeat_at === undefined ? valid() : validateTimestamp(value.heartbeat_at, "heartbeat_at"),
|
|
1230
|
+
isEnumValue(value.recovery_state, LOCK_RECOVERY_STATES) ? valid() : invalid("recovery_state is invalid"),
|
|
1231
|
+
value.recovery_state === "corrupt" ? invalid("corrupt active-attempt lock fails closed") : valid(),
|
|
1232
|
+
Number.isFinite(expiresAt) && expiresAt < now && value.recovery_state === "active" ? invalid("stale active lock cannot remain active") : valid(),
|
|
1233
|
+
validateOpaqueRef(value.audit_ref, "audit_ref")
|
|
1234
|
+
]);
|
|
1235
|
+
}
|
|
1236
|
+
export function assertActiveAttemptLockV1(value) {
|
|
1237
|
+
const result = validateActiveAttemptLockV1(value);
|
|
1238
|
+
if (!result.ok)
|
|
1239
|
+
throw new Error(result.errors.join("; "));
|
|
1240
|
+
}
|
|
1241
|
+
export function validateLaneRecordV1(value) {
|
|
1242
|
+
if (!isRecord(value))
|
|
1243
|
+
return invalid("lane record must be an object");
|
|
1244
|
+
return combine([
|
|
1245
|
+
validateSchemaArtifactValue("flowdesk.lane_record.v1", value),
|
|
1246
|
+
validateOpaqueId(value.lane_id, "lane_id"),
|
|
1247
|
+
validateOpaqueId(value.workflow_id, "workflow_id"),
|
|
1248
|
+
value.plan_revision_id === undefined ? valid() : validateOpaqueId(value.plan_revision_id, "plan_revision_id"),
|
|
1249
|
+
value.attempt_id === undefined ? valid() : validateOpaqueId(value.attempt_id, "attempt_id"),
|
|
1250
|
+
validateOpaqueRef(value.task_ref, "task_ref"),
|
|
1251
|
+
isEnumValue(value.lane_class, ["planning_draft", "planning_refine", "planning_review", "research", "documentation", "verification", "diagnostics", "other"]) ? valid() : invalid("lane_class is invalid"),
|
|
1252
|
+
isEnumValue(value.state, PERSISTED_LANE_STATES) ? valid() : invalid("lane record state is invalid or future-gated"),
|
|
1253
|
+
value.state === "hard_cancel_proven" ? invalid("Release 1 persisted lane records must not store hard_cancel_proven") : valid(),
|
|
1254
|
+
validateStringArray(value.refs, "refs"),
|
|
1255
|
+
validateStringArray(value.event_refs, "event_refs"),
|
|
1256
|
+
validateStringArray(value.audit_refs, "audit_refs"),
|
|
1257
|
+
value.observability_ref === undefined ? valid() : validateOpaqueRef(value.observability_ref, "observability_ref"),
|
|
1258
|
+
value.debug_ref === undefined ? valid() : validateOpaqueRef(value.debug_ref, "debug_ref"),
|
|
1259
|
+
value.invocation_ref_kind === undefined || isEnumValue(value.invocation_ref_kind, LANE_INVOCATION_REF_KINDS) ? valid() : invalid("invocation_ref_kind is invalid"),
|
|
1260
|
+
value.retry_count === undefined || (typeof value.retry_count === "number" && Number.isInteger(value.retry_count) && value.retry_count >= 0 && value.retry_count <= 2) ? valid() : invalid("retry_count is invalid"),
|
|
1261
|
+
value.verdict_status === undefined || isEnumValue(value.verdict_status, LANE_VERDICT_STATUSES) ? valid() : invalid("verdict_status is invalid"),
|
|
1262
|
+
isEnumValue(value.safe_next_action, SAFE_NEXT_ACTIONS) ? valid() : invalid("safe_next_action is invalid")
|
|
1263
|
+
]);
|
|
1264
|
+
}
|
|
1265
|
+
export function assertLaneRecordV1(value) {
|
|
1266
|
+
const result = validateLaneRecordV1(value);
|
|
1267
|
+
if (!result.ok)
|
|
1268
|
+
throw new Error(result.errors.join("; "));
|
|
1269
|
+
}
|
|
1270
|
+
export function validateAuditEventV1(value) {
|
|
1271
|
+
if (!isRecord(value))
|
|
1272
|
+
return invalid("audit event must be an object");
|
|
1273
|
+
return combine([
|
|
1274
|
+
validateSchemaArtifactValue("flowdesk.audit_event.v1", value),
|
|
1275
|
+
validateOpaqueId(value.event_id, "event_id"),
|
|
1276
|
+
typeof value.event_type === "string" && value.event_type.length > 0 && value.event_type.length <= 128 ? validateNoForbiddenRawPayloads(value.event_type, "event_type") : invalid("event_type is required"),
|
|
1277
|
+
value.workflow_id === undefined ? valid() : validateOpaqueId(value.workflow_id, "workflow_id"),
|
|
1278
|
+
value.attempt_id === undefined ? valid() : validateOpaqueId(value.attempt_id, "attempt_id"),
|
|
1279
|
+
value.step_id === undefined ? valid() : validateOpaqueId(value.step_id, "step_id"),
|
|
1280
|
+
typeof value.created_at === "string" && value.created_at.length > 0 ? valid() : invalid("created_at is required"),
|
|
1281
|
+
isEnumValue(value.actor_class, ["user", "flowdesk", "opencode", "provider", "system", "unknown"]) ? valid() : invalid("actor_class is invalid"),
|
|
1282
|
+
value.policy_ref === undefined ? valid() : validateOpaqueRef(value.policy_ref, "policy_ref"),
|
|
1283
|
+
value.decision_ref === undefined ? valid() : validateOpaqueRef(value.decision_ref, "decision_ref"),
|
|
1284
|
+
typeof value.redaction_version === "string" && value.redaction_version.length > 0 ? validateNoForbiddenRawPayloads(value.redaction_version, "redaction_version") : invalid("redaction_version is required"),
|
|
1285
|
+
typeof value.summary_label === "string" && value.summary_label.length > 0 && value.summary_label.length <= 160 ? validateNoForbiddenRawPayloads(value.summary_label, "summary_label") : invalid("summary_label is required"),
|
|
1286
|
+
validateStringArray(value.artifact_refs, "artifact_refs", undefined, 20)
|
|
1287
|
+
]);
|
|
1288
|
+
}
|
|
1289
|
+
export function assertAuditEventV1(value) {
|
|
1290
|
+
const result = validateAuditEventV1(value);
|
|
1291
|
+
if (!result.ok)
|
|
1292
|
+
throw new Error(result.errors.join("; "));
|
|
1293
|
+
}
|
|
1294
|
+
export function validateAuditRecordV1(value) {
|
|
1295
|
+
if (!isRecord(value))
|
|
1296
|
+
return invalid("audit record must be an object");
|
|
1297
|
+
return combine([
|
|
1298
|
+
validateSchemaArtifactValue("flowdesk.audit_record.v1", value),
|
|
1299
|
+
validateOpaqueRef(value.audit_ref, "audit_ref"),
|
|
1300
|
+
value.event_id === undefined ? valid() : validateOpaqueId(value.event_id, "event_id"),
|
|
1301
|
+
value.workflow_id === undefined ? valid() : validateOpaqueId(value.workflow_id, "workflow_id"),
|
|
1302
|
+
value.attempt_id === undefined ? valid() : validateOpaqueId(value.attempt_id, "attempt_id"),
|
|
1303
|
+
value.step_id === undefined ? valid() : validateOpaqueId(value.step_id, "step_id"),
|
|
1304
|
+
value.checkpoint_id === undefined ? valid() : validateOpaqueId(value.checkpoint_id, "checkpoint_id"),
|
|
1305
|
+
typeof value.event_type === "string" && value.event_type.length > 0 ? validateNoForbiddenRawPayloads(value.event_type, "event_type") : invalid("event_type is required"),
|
|
1306
|
+
typeof value.created_at === "string" && value.created_at.length > 0 ? valid() : invalid("created_at is required"),
|
|
1307
|
+
value.actor_class === undefined || isEnumValue(value.actor_class, ["user", "flowdesk", "opencode", "provider", "system", "unknown"]) ? valid() : invalid("actor_class is invalid"),
|
|
1308
|
+
typeof value.summary_label === "string" && value.summary_label.length > 0 ? validateNoForbiddenRawPayloads(value.summary_label, "summary_label") : invalid("summary_label is required"),
|
|
1309
|
+
value.policy_ref === undefined ? valid() : validateOpaqueRef(value.policy_ref, "policy_ref"),
|
|
1310
|
+
value.decision_ref === undefined ? valid() : validateOpaqueRef(value.decision_ref, "decision_ref"),
|
|
1311
|
+
validateOpaqueRefArray(value.evidence_refs, "evidence_refs", 20),
|
|
1312
|
+
validateStringArray(value.artifact_refs, "artifact_refs", undefined, 20),
|
|
1313
|
+
typeof value.redaction_version === "string" && value.redaction_version.length > 0 ? validateNoForbiddenRawPayloads(value.redaction_version, "redaction_version") : invalid("redaction_version is required")
|
|
1314
|
+
]);
|
|
1315
|
+
}
|
|
1316
|
+
export function validateAuditRefSummaryV1(value) {
|
|
1317
|
+
if (!isRecord(value))
|
|
1318
|
+
return invalid("audit ref summary must be an object");
|
|
1319
|
+
return combine([
|
|
1320
|
+
validateSchemaArtifactValue("flowdesk.audit_ref_summary.v1", value),
|
|
1321
|
+
validateOpaqueRef(value.audit_ref, "audit_ref"),
|
|
1322
|
+
value.workflow_id === undefined ? valid() : validateOpaqueId(value.workflow_id, "workflow_id"),
|
|
1323
|
+
value.attempt_id === undefined ? valid() : validateOpaqueId(value.attempt_id, "attempt_id"),
|
|
1324
|
+
typeof value.event_type === "string" && value.event_type.length > 0 && value.event_type.length <= 128 ? validateNoForbiddenRawPayloads(value.event_type, "event_type") : invalid("event_type is required"),
|
|
1325
|
+
typeof value.summary_label === "string" && value.summary_label.length > 0 && value.summary_label.length <= 160 ? validateNoForbiddenRawPayloads(value.summary_label, "summary_label") : invalid("summary_label is required"),
|
|
1326
|
+
validateTimestamp(value.created_at, "created_at"),
|
|
1327
|
+
typeof value.redaction_version === "string" && value.redaction_version.length > 0 && value.redaction_version.length <= 128 ? validateNoForbiddenRawPayloads(value.redaction_version, "redaction_version") : invalid("redaction_version is required")
|
|
1328
|
+
]);
|
|
1329
|
+
}
|
|
1330
|
+
export function assertAuditRefSummaryV1(value) {
|
|
1331
|
+
const result = validateAuditRefSummaryV1(value);
|
|
1332
|
+
if (!result.ok)
|
|
1333
|
+
throw new Error(result.errors.join("; "));
|
|
1334
|
+
}
|
|
1335
|
+
export function assertAuditRecordV1(value) {
|
|
1336
|
+
const result = validateAuditRecordV1(value);
|
|
1337
|
+
if (!result.ok)
|
|
1338
|
+
throw new Error(result.errors.join("; "));
|
|
1339
|
+
}
|
|
1340
|
+
export function validateDebugSectionSummaryV1(value) {
|
|
1341
|
+
if (!isRecord(value))
|
|
1342
|
+
return invalid("debug section summary must be an object");
|
|
1343
|
+
return combine([
|
|
1344
|
+
validateSchemaArtifactValue("flowdesk.debug_section_summary.v1", value),
|
|
1345
|
+
validateOpaqueId(value.export_id, "export_id"),
|
|
1346
|
+
isEnumValue(value.section, DEBUG_SECTIONS) ? valid() : invalid("debug section is invalid"),
|
|
1347
|
+
validateOpaqueRef(value.ref, "ref"),
|
|
1348
|
+
isEnumValue(value.redaction_status, ["passed", "partial", "blocked"]) ? valid() : invalid("redaction_status is invalid"),
|
|
1349
|
+
validateNonNegativeInteger(value.warning_count, "warning_count"),
|
|
1350
|
+
validateStringArray(value.excluded_categories, "excluded_categories", REDACTED_ERROR_CATEGORIES)
|
|
1351
|
+
]);
|
|
1352
|
+
}
|
|
1353
|
+
export function validateDebugExportManifestV1(value) {
|
|
1354
|
+
if (!isRecord(value))
|
|
1355
|
+
return invalid("debug export manifest must be an object");
|
|
1356
|
+
const partialDeletionWarningResult = value.partial_deletion_warning === undefined
|
|
1357
|
+
? valid()
|
|
1358
|
+
: typeof value.partial_deletion_warning === "string" && value.partial_deletion_warning.length > 0 && value.partial_deletion_warning.length <= 500
|
|
1359
|
+
? validateNoForbiddenRawPayloads(value.partial_deletion_warning, "partial_deletion_warning")
|
|
1360
|
+
: invalid("partial_deletion_warning must be a bounded string");
|
|
1361
|
+
return combine([
|
|
1362
|
+
validateSchemaArtifactValue("flowdesk.debug_export_manifest.v1", value),
|
|
1363
|
+
validateOpaqueId(value.export_id, "export_id"),
|
|
1364
|
+
validateOpaqueRef(value.manifest_ref, "manifest_ref"),
|
|
1365
|
+
value.workflow_id === undefined ? valid() : validateOpaqueId(value.workflow_id, "workflow_id"),
|
|
1366
|
+
value.session_ref === undefined ? valid() : validateOpaqueRef(value.session_ref, "session_ref"),
|
|
1367
|
+
Array.isArray(value.included_sections) ? combine(value.included_sections.map((section) => validateDebugSectionSummaryV1(section))) : invalid("included_sections must be an array"),
|
|
1368
|
+
validateNonNegativeInteger(value.file_count, "file_count"),
|
|
1369
|
+
validateNonNegativeInteger(value.byte_count, "byte_count"),
|
|
1370
|
+
isEnumValue(value.deletion_state, ["pending", "deleted", "partial", "retained_by_policy"]) ? valid() : invalid("deletion_state is invalid"),
|
|
1371
|
+
validateOpaqueRefArray(value.source_refs, "source_refs"),
|
|
1372
|
+
validateOpaqueRefArray(value.audit_refs, "audit_refs"),
|
|
1373
|
+
validateStringArray(value.warnings, "warnings", undefined, 20),
|
|
1374
|
+
value.deletion_proof_ref === undefined ? valid() : validateOpaqueRef(value.deletion_proof_ref, "deletion_proof_ref"),
|
|
1375
|
+
value.deletion_state === "deleted" && value.deletion_proof_ref === undefined ? invalid("deleted debug exports require deletion_proof_ref") : valid(),
|
|
1376
|
+
value.deletion_state === "partial" && value.partial_deletion_warning === undefined ? invalid("partial debug export deletion requires partial_deletion_warning") : valid(),
|
|
1377
|
+
partialDeletionWarningResult
|
|
1378
|
+
]);
|
|
1379
|
+
}
|
|
1380
|
+
export function validateConformanceRuntimeMetadataV1(value) {
|
|
1381
|
+
if (!isRecord(value))
|
|
1382
|
+
return invalid("conformance runtime metadata must be an object");
|
|
1383
|
+
return combine([
|
|
1384
|
+
validateSchemaArtifactValue("flowdesk.conformance_runtime_metadata.v1", value),
|
|
1385
|
+
typeof value.opencode_version === "string" && value.opencode_version.length > 0 && value.opencode_version.length <= 128 ? validateNoForbiddenRawPayloads(value.opencode_version, "opencode_version") : invalid("opencode_version is required"),
|
|
1386
|
+
value.opencode_commit === undefined ? valid() : typeof value.opencode_commit === "string" && value.opencode_commit.length > 0 && value.opencode_commit.length <= 128 ? validateNoForbiddenRawPayloads(value.opencode_commit, "opencode_commit") : invalid("opencode_commit is invalid"),
|
|
1387
|
+
validateTimestamp(value.checked_at, "checked_at"),
|
|
1388
|
+
value.plugin_package === "@flowdesk/opencode-plugin" ? valid() : invalid("plugin_package is invalid"),
|
|
1389
|
+
typeof value.plugin_version_or_commit === "string" && value.plugin_version_or_commit.length > 0 && value.plugin_version_or_commit.length <= 128 ? validateNoForbiddenRawPayloads(value.plugin_version_or_commit, "plugin_version_or_commit") : invalid("plugin_version_or_commit is required"),
|
|
1390
|
+
isEnumValue(value.chat_intake_mode, ["blocking", "steering", "observe_only", "off"]) ? valid() : invalid("chat_intake_mode is invalid"),
|
|
1391
|
+
isEnumValue(value.command_alias_mode, ["portable_only", "colon_alias_supported"]) ? valid() : invalid("command_alias_mode is invalid"),
|
|
1392
|
+
isEnumValue(value.dispatch_mode, ["none", "fake-runtime", "guarded-dry-run", "command-steering", "real-opencode-dispatch"]) ? valid() : invalid("dispatch_mode is invalid"),
|
|
1393
|
+
isEnumValue(value.runtime_echo_mode, ["none", "trusted", "untrusted", "request_surface_only"]) ? valid() : invalid("runtime_echo_mode is invalid"),
|
|
1394
|
+
isEnumValue(value.event_telemetry_mode, ["none", "partial", "sufficient"]) ? valid() : invalid("event_telemetry_mode is invalid"),
|
|
1395
|
+
isEnumValue(value.provider_health_mode, ["none", "diagnostic_only", "dispatch_gate_ready"]) ? valid() : invalid("provider_health_mode is invalid"),
|
|
1396
|
+
isEnumValue(value.fallback_reselection_mode, ["disabled", "diagnostic_only", "guarded_real_dispatch_ready"]) ? valid() : invalid("fallback_reselection_mode is invalid"),
|
|
1397
|
+
isEnumValue(value.diagnostics_surface_mode, ["none", "status_only", "doctor_usage_status", "doctor_usage_status_debug"]) ? valid() : invalid("diagnostics_surface_mode is invalid"),
|
|
1398
|
+
isEnumValue(value.lane_observability_mode, ["none", "command_summary", "openable_refs"]) ? valid() : invalid("lane_observability_mode is invalid"),
|
|
1399
|
+
isEnumValue(value.hook_harness_mode, HOOK_HARNESS_MODES) ? valid() : invalid("hook_harness_mode is invalid"),
|
|
1400
|
+
isEnumValue(value.tui_mode, ["ux_only", "unsupported"]) ? valid() : invalid("tui_mode is invalid"),
|
|
1401
|
+
validateStringArray(value.mode_fields, "mode_fields", undefined, 20),
|
|
1402
|
+
validateOpaqueRefArray(value.evidence_refs, "evidence_refs", 20),
|
|
1403
|
+
validateStringArray(value.disabled_modes, "disabled_modes", DISABLED_MODES, 8),
|
|
1404
|
+
validateNoForbiddenRawPayloads(value, "conformance_runtime_metadata")
|
|
1405
|
+
]);
|
|
1406
|
+
}
|
|
1407
|
+
export function assertConformanceRuntimeMetadataV1(value) {
|
|
1408
|
+
const result = validateConformanceRuntimeMetadataV1(value);
|
|
1409
|
+
if (!result.ok)
|
|
1410
|
+
throw new Error(result.errors.join("; "));
|
|
1411
|
+
}
|
|
1412
|
+
export function validateConformanceEvidenceRecordV1(value) {
|
|
1413
|
+
if (!isRecord(value))
|
|
1414
|
+
return invalid("conformance evidence record must be an object");
|
|
1415
|
+
return combine([
|
|
1416
|
+
validateSchemaArtifactValue("flowdesk.conformance_evidence_record.v1", value),
|
|
1417
|
+
validateOpaqueRef(value.evidence_ref, "evidence_ref"),
|
|
1418
|
+
validateOpaqueId(value.run_id, "run_id"),
|
|
1419
|
+
validateTimestamp(value.checked_at, "checked_at"),
|
|
1420
|
+
typeof value.evidence_area === "string" && value.evidence_area.length > 0 && value.evidence_area.length <= 128 ? validateNoForbiddenRawPayloads(value.evidence_area, "evidence_area") : invalid("evidence_area is required"),
|
|
1421
|
+
isEnumValue(value.result, ["pass", "fail", "partial", "skipped"]) ? valid() : invalid("result is invalid"),
|
|
1422
|
+
typeof value.summary_label === "string" && value.summary_label.length > 0 && value.summary_label.length <= 160 ? validateNoForbiddenRawPayloads(value.summary_label, "summary_label") : invalid("summary_label is required"),
|
|
1423
|
+
typeof value.redaction_version === "string" && value.redaction_version.length > 0 && value.redaction_version.length <= 128 ? validateNoForbiddenRawPayloads(value.redaction_version, "redaction_version") : invalid("redaction_version is required"),
|
|
1424
|
+
validateOpaqueRefArray(value.source_refs, "source_refs", 20),
|
|
1425
|
+
validateNoForbiddenRawPayloads(value, "conformance_evidence_record")
|
|
1426
|
+
]);
|
|
1427
|
+
}
|
|
1428
|
+
export function assertConformanceEvidenceRecordV1(value) {
|
|
1429
|
+
const result = validateConformanceEvidenceRecordV1(value);
|
|
1430
|
+
if (!result.ok)
|
|
1431
|
+
throw new Error(result.errors.join("; "));
|
|
1432
|
+
}
|
|
1433
|
+
export function assertDebugExportManifestV1(value) {
|
|
1434
|
+
const result = validateDebugExportManifestV1(value);
|
|
1435
|
+
if (!result.ok)
|
|
1436
|
+
throw new Error(result.errors.join("; "));
|
|
1437
|
+
}
|
|
1438
|
+
export function validateExportDebugRequestV1(value) {
|
|
1439
|
+
if (!isRecord(value))
|
|
1440
|
+
return invalid("export debug request must be an object");
|
|
1441
|
+
return combine([
|
|
1442
|
+
validateRequestEnvelope(value, "flowdesk.export_debug.request.v1"),
|
|
1443
|
+
validateStringArray(value.include_sections, "include_sections", DEBUG_SECTIONS, 7),
|
|
1444
|
+
isEnumValue(value.retention_hint, ["delete_after_export", "keep_until_default_expiry", "keep_until_policy_expiry"]) ? valid() : invalid("retention_hint is invalid")
|
|
1445
|
+
]);
|
|
1446
|
+
}
|
|
1447
|
+
export function validateExportDebugResponseV1(value) {
|
|
1448
|
+
if (!isRecord(value))
|
|
1449
|
+
return invalid("export debug response must be an object");
|
|
1450
|
+
return combine([
|
|
1451
|
+
validateResponseEnvelopeV1(value, "flowdesk.export_debug.response.v1"),
|
|
1452
|
+
validateOpaqueRef(value.export_manifest_ref, "export_manifest_ref"),
|
|
1453
|
+
Array.isArray(value.included_sections) ? combine(value.included_sections.map((section) => validateDebugSectionSummaryV1(section))) : invalid("included_sections must be an array"),
|
|
1454
|
+
validateTimestamp(value.delete_after, "delete_after")
|
|
1455
|
+
]);
|
|
1456
|
+
}
|
|
1457
|
+
export function assertExportDebugResponseV1(value) {
|
|
1458
|
+
const result = validateExportDebugResponseV1(value);
|
|
1459
|
+
if (!result.ok)
|
|
1460
|
+
throw new Error(result.errors.join("; "));
|
|
1461
|
+
}
|
|
1462
|
+
export function validateSessionRecordCannotReplaceWorkflowState(value) {
|
|
1463
|
+
if (!isRecord(value))
|
|
1464
|
+
return invalid("session record must be an object");
|
|
1465
|
+
if (typeof value.schema_version !== "string" || !value.schema_version.startsWith("flowdesk."))
|
|
1466
|
+
return invalid("session record schema_version is missing");
|
|
1467
|
+
const sessionSchemas = new Set(["flowdesk.audit_record.v1", "flowdesk.lane_record.v1", "flowdesk.debug_export_manifest.v1"]);
|
|
1468
|
+
if (!sessionSchemas.has(value.schema_version))
|
|
1469
|
+
return invalid("unknown session-side schema cannot replace workflow state");
|
|
1470
|
+
const forbiddenAuthorityKeys = ["active_workflow_id", "active_attempt_id", "latest_checkpoint_id", "current_attempt_id", "workflow_active", "checkpoint_state", "guard_approved_dispatch"];
|
|
1471
|
+
const present = forbiddenAuthorityKeys.filter((key) => key in value);
|
|
1472
|
+
if (present.length > 0)
|
|
1473
|
+
return invalid(`session record cannot replace workflow/checkpoint state: ${present.join(",")}`);
|
|
1474
|
+
if (value.schema_version === "flowdesk.audit_record.v1")
|
|
1475
|
+
return validateAuditRecordV1(value);
|
|
1476
|
+
if (value.schema_version === "flowdesk.lane_record.v1")
|
|
1477
|
+
return validateLaneRecordV1(value);
|
|
1478
|
+
return validateDebugExportManifestV1(value);
|
|
1479
|
+
}
|
|
1480
|
+
const TOP_TIER_REVIEWER_PERSPECTIVES = ["policy_security", "architecture", "verification_implementation"];
|
|
1481
|
+
const TOP_TIER_BINDING_AVAILABILITY_STATES = ["registered_available", "registered_unavailable", "registered_blocked"];
|
|
1482
|
+
const TOP_TIER_LANE_INCLUSION_STATES = ["included", "excluded", "blocked"];
|
|
1483
|
+
const TOP_TIER_REVIEW_INVENTORY_DECISIONS = ["ready", "blocked", "policy_change_required"];
|
|
1484
|
+
const TOP_TIER_REVIEW_VERDICT_LABELS = ["pass", "changes_required", "blocked", "inconclusive"];
|
|
1485
|
+
const TOP_TIER_REVIEW_FINDING_SEVERITIES = ["info", "low", "medium", "high", "critical"];
|
|
1486
|
+
const TOP_TIER_REVIEW_UNCERTAINTY_LEVELS = ["low", "medium", "high", "unknown"];
|
|
1487
|
+
const PLANNED_MODE_FIELD_LABELS = ["top_tier_multi_perspective_review_mode"];
|
|
1488
|
+
const PLANNED_MODE_FIELD_STATES = ["disabled", "planned", "conformance_ready", "release_gate_ready"];
|
|
1489
|
+
const FORBIDDEN_PLANNED_MODE_FIELD_STATES = ["enabled", "active", "dispatch_ready", "approved", "authorized"];
|
|
1490
|
+
const FORBIDDEN_REVIEWER_AUTHORITY_KEYS = [
|
|
1491
|
+
"trusted",
|
|
1492
|
+
"approve_dispatch",
|
|
1493
|
+
"guard_decision",
|
|
1494
|
+
"guard_approved_dispatch",
|
|
1495
|
+
"real_dispatch",
|
|
1496
|
+
"actual_dispatch",
|
|
1497
|
+
"managed_dispatch",
|
|
1498
|
+
"fallback_authorized",
|
|
1499
|
+
"noReply"
|
|
1500
|
+
];
|
|
1501
|
+
function rejectForbiddenAuthorityKeys(value, label) {
|
|
1502
|
+
const present = FORBIDDEN_REVIEWER_AUTHORITY_KEYS.filter((key) => key in value);
|
|
1503
|
+
return present.length === 0 ? valid() : invalid(`${label} cannot carry authority keys: ${present.join(",")}`);
|
|
1504
|
+
}
|
|
1505
|
+
export function validateTopTierReviewerBindingV1(value) {
|
|
1506
|
+
if (!isRecord(value))
|
|
1507
|
+
return invalid("top tier reviewer binding must be an object");
|
|
1508
|
+
const providerFamily = value.provider_family;
|
|
1509
|
+
return combine([
|
|
1510
|
+
validateSchemaArtifactValue("flowdesk.top_tier_reviewer_binding.v1", value),
|
|
1511
|
+
rejectForbiddenAuthorityKeys(value, "top tier reviewer binding"),
|
|
1512
|
+
validateOpaqueId(value.binding_id, "binding_id"),
|
|
1513
|
+
value.reviewer_profile_id === "reviewer" ? valid() : invalid("reviewer_profile_id must be canonical reviewer"),
|
|
1514
|
+
typeof value.binding_label === "string" && value.binding_label.length > 0 && value.binding_label.length <= 64 && /^[a-z][a-z0-9_]*$/.test(value.binding_label) ? validateNoForbiddenRawPayloads(value.binding_label, "binding_label") : invalid("binding_label must be a bounded lowercase identifier"),
|
|
1515
|
+
validateProviderFamily(providerFamily),
|
|
1516
|
+
providerFamily === "unknown" || providerFamily === "all" ? invalid("top tier reviewer binding provider_family must be concrete") : valid(),
|
|
1517
|
+
validateConcreteProviderQualifiedModelId(value.provider_qualified_model_id, "top tier reviewer binding provider_qualified_model_id"),
|
|
1518
|
+
typeof value.model_family === "string" && value.model_family.length > 0 && value.model_family.length <= 120 ? validateNoForbiddenRawPayloads(value.model_family, "model_family") : invalid("model_family is required"),
|
|
1519
|
+
value.highest_tier_eligible === true ? valid() : invalid("highest_tier_eligible must be true"),
|
|
1520
|
+
validateOpaqueRef(value.registry_entry_ref, "registry_entry_ref"),
|
|
1521
|
+
validateOpaqueRef(value.policy_pack_eligibility_ref, "policy_pack_eligibility_ref"),
|
|
1522
|
+
isEnumValue(value.availability, TOP_TIER_BINDING_AVAILABILITY_STATES) ? valid() : invalid("availability is invalid"),
|
|
1523
|
+
value.dispatch_authority_enabled === false ? valid() : invalid("dispatch_authority_enabled must be false for top tier reviewer binding"),
|
|
1524
|
+
validateTimestamp(value.observed_at, "observed_at"),
|
|
1525
|
+
validateTimestamp(value.expires_at, "expires_at"),
|
|
1526
|
+
validateNoForbiddenRawPayloads(value, "top_tier_reviewer_binding")
|
|
1527
|
+
]);
|
|
1528
|
+
}
|
|
1529
|
+
export function validateTopTierReviewerLanePlanV1(value) {
|
|
1530
|
+
if (!isRecord(value))
|
|
1531
|
+
return invalid("top tier reviewer lane plan must be an object");
|
|
1532
|
+
return combine([
|
|
1533
|
+
validateSchemaArtifactValue("flowdesk.top_tier_reviewer_lane_plan.v1", value),
|
|
1534
|
+
rejectForbiddenAuthorityKeys(value, "top tier reviewer lane plan"),
|
|
1535
|
+
validateOpaqueId(value.lane_plan_id, "lane_plan_id"),
|
|
1536
|
+
validateOpaqueRef(value.binding_ref, "binding_ref"),
|
|
1537
|
+
isEnumValue(value.perspective, TOP_TIER_REVIEWER_PERSPECTIVES) ? valid() : invalid("perspective is invalid"),
|
|
1538
|
+
isEnumValue(value.inclusion_state, TOP_TIER_LANE_INCLUSION_STATES) ? valid() : invalid("inclusion_state is invalid"),
|
|
1539
|
+
typeof value.reason_label === "string" && value.reason_label.length > 0 && value.reason_label.length <= 200 ? validateNoForbiddenRawPayloads(value.reason_label, "reason_label") : invalid("reason_label is required"),
|
|
1540
|
+
validateStringArray(value.safe_next_actions, "safe_next_actions", SAFE_NEXT_ACTIONS, 8),
|
|
1541
|
+
value.dispatch_authority_enabled === false ? valid() : invalid("dispatch_authority_enabled must be false for top tier reviewer lane plan"),
|
|
1542
|
+
validateNoForbiddenRawPayloads(value, "top_tier_reviewer_lane_plan")
|
|
1543
|
+
]);
|
|
1544
|
+
}
|
|
1545
|
+
export function validateTopTierReviewBindingInventoryV1(value) {
|
|
1546
|
+
if (!isRecord(value))
|
|
1547
|
+
return invalid("top tier review binding inventory must be an object");
|
|
1548
|
+
return combine([
|
|
1549
|
+
validateSchemaArtifactValue("flowdesk.top_tier_review_binding_inventory.v1", value),
|
|
1550
|
+
rejectForbiddenAuthorityKeys(value, "top tier review binding inventory"),
|
|
1551
|
+
validateOpaqueId(value.inventory_id, "inventory_id"),
|
|
1552
|
+
validateOpaqueId(value.workflow_id, "workflow_id"),
|
|
1553
|
+
validateOpaqueId(value.plan_revision_id, "plan_revision_id"),
|
|
1554
|
+
validateTimestamp(value.created_at, "created_at"),
|
|
1555
|
+
typeof value.redaction_version === "string" && value.redaction_version.length > 0 && value.redaction_version.length <= 128 ? validateNoForbiddenRawPayloads(value.redaction_version, "redaction_version") : invalid("redaction_version is required"),
|
|
1556
|
+
validateOpaqueRefArray(value.registered_binding_refs, "registered_binding_refs", 32),
|
|
1557
|
+
validateOpaqueRefArray(value.available_binding_refs, "available_binding_refs", 32),
|
|
1558
|
+
validateOpaqueRefArray(value.unavailable_binding_refs, "unavailable_binding_refs", 32),
|
|
1559
|
+
validateOpaqueRefArray(value.blocked_binding_refs, "blocked_binding_refs", 32),
|
|
1560
|
+
validateOpaqueRefArray(value.lane_plan_refs, "lane_plan_refs", 64),
|
|
1561
|
+
Array.isArray(value.registered_binding_refs) && value.registered_binding_refs.length > 0 ? valid() : invalid("registered_binding_refs must list at least one binding"),
|
|
1562
|
+
Array.isArray(value.lane_plan_refs) && value.lane_plan_refs.length > 0 ? valid() : invalid("lane_plan_refs must list at least one lane plan"),
|
|
1563
|
+
typeof value.max_concurrent_lane_count === "number" && Number.isInteger(value.max_concurrent_lane_count) && value.max_concurrent_lane_count > 0 && value.max_concurrent_lane_count <= 32 ? valid() : invalid("max_concurrent_lane_count must be a positive bounded integer"),
|
|
1564
|
+
typeof value.budget_cap_label === "string" && value.budget_cap_label.length > 0 && value.budget_cap_label.length <= 200 ? validateNoForbiddenRawPayloads(value.budget_cap_label, "budget_cap_label") : invalid("budget_cap_label is required"),
|
|
1565
|
+
typeof value.quota_reserve_label === "string" && value.quota_reserve_label.length > 0 && value.quota_reserve_label.length <= 200 ? validateNoForbiddenRawPayloads(value.quota_reserve_label, "quota_reserve_label") : invalid("quota_reserve_label is required"),
|
|
1566
|
+
typeof value.timeout_label === "string" && value.timeout_label.length > 0 && value.timeout_label.length <= 200 ? validateNoForbiddenRawPayloads(value.timeout_label, "timeout_label") : invalid("timeout_label is required"),
|
|
1567
|
+
typeof value.retry_budget_label === "string" && value.retry_budget_label.length > 0 && value.retry_budget_label.length <= 200 ? validateNoForbiddenRawPayloads(value.retry_budget_label, "retry_budget_label") : invalid("retry_budget_label is required"),
|
|
1568
|
+
isEnumValue(value.inventory_decision, TOP_TIER_REVIEW_INVENTORY_DECISIONS) ? valid() : invalid("inventory_decision is invalid"),
|
|
1569
|
+
validateStringArray(value.safe_next_actions, "safe_next_actions", SAFE_NEXT_ACTIONS, 8),
|
|
1570
|
+
value.dispatch_authority_enabled === false ? valid() : invalid("dispatch_authority_enabled must be false for top tier review binding inventory"),
|
|
1571
|
+
validateNoForbiddenRawPayloads(value, "top_tier_review_binding_inventory")
|
|
1572
|
+
]);
|
|
1573
|
+
}
|
|
1574
|
+
function validateTopTierReviewFindingV1(value, label) {
|
|
1575
|
+
if (!isRecord(value))
|
|
1576
|
+
return invalid(`${label} must be an object`);
|
|
1577
|
+
return combine([
|
|
1578
|
+
rejectUnknownProperties(value, ["finding_id", "severity", "category", "summary_label", "evidence_refs", "required_fix_label"]),
|
|
1579
|
+
requireFields(value, ["finding_id", "severity", "category", "summary_label", "evidence_refs", "required_fix_label"]),
|
|
1580
|
+
rejectForbiddenAuthorityKeys(value, label),
|
|
1581
|
+
validateOpaqueId(value.finding_id, `${label}.finding_id`),
|
|
1582
|
+
isEnumValue(value.severity, TOP_TIER_REVIEW_FINDING_SEVERITIES) ? valid() : invalid(`${label}.severity is invalid`),
|
|
1583
|
+
isEnumValue(value.category, REDACTED_ERROR_CATEGORIES) ? valid() : invalid(`${label}.category is invalid`),
|
|
1584
|
+
typeof value.summary_label === "string" && value.summary_label.length > 0 && value.summary_label.length <= 200 ? validateNoForbiddenRawPayloads(value.summary_label, `${label}.summary_label`) : invalid(`${label}.summary_label is required`),
|
|
1585
|
+
validateOpaqueRefArray(value.evidence_refs, `${label}.evidence_refs`, 20),
|
|
1586
|
+
typeof value.required_fix_label === "string" && value.required_fix_label.length > 0 && value.required_fix_label.length <= 200 ? validateNoForbiddenRawPayloads(value.required_fix_label, `${label}.required_fix_label`) : invalid(`${label}.required_fix_label is required`),
|
|
1587
|
+
validateNoForbiddenRawPayloads(value, label)
|
|
1588
|
+
]);
|
|
1589
|
+
}
|
|
1590
|
+
export function validateTopTierReviewVerdictV1(value) {
|
|
1591
|
+
if (!isRecord(value))
|
|
1592
|
+
return invalid("top tier review verdict must be an object");
|
|
1593
|
+
const findingsArray = Array.isArray(value.findings) ? value.findings : [];
|
|
1594
|
+
return combine([
|
|
1595
|
+
validateSchemaArtifactValue("flowdesk.top_tier_review_verdict.v1", value),
|
|
1596
|
+
rejectForbiddenAuthorityKeys(value, "top tier review verdict"),
|
|
1597
|
+
validateOpaqueId(value.verdict_id, "verdict_id"),
|
|
1598
|
+
validateOpaqueId(value.workflow_id, "workflow_id"),
|
|
1599
|
+
validateOpaqueRef(value.lane_plan_ref, "lane_plan_ref"),
|
|
1600
|
+
validateOpaqueRef(value.binding_ref, "binding_ref"),
|
|
1601
|
+
isEnumValue(value.perspective, TOP_TIER_REVIEWER_PERSPECTIVES) ? valid() : invalid("perspective is invalid"),
|
|
1602
|
+
typeof value.source === "string" && value.source.length > 0 && value.source.length <= 64 && /^[a-z][a-z0-9_]*$/.test(value.source) ? validateNoForbiddenRawPayloads(value.source, "source") : invalid("source must be a bounded lowercase reviewer binding label"),
|
|
1603
|
+
validateTimestamp(value.created_at, "created_at"),
|
|
1604
|
+
typeof value.redaction_version === "string" && value.redaction_version.length > 0 && value.redaction_version.length <= 128 ? validateNoForbiddenRawPayloads(value.redaction_version, "redaction_version") : invalid("redaction_version is required"),
|
|
1605
|
+
Array.isArray(value.findings) ? valid() : invalid("findings must be an array"),
|
|
1606
|
+
findingsArray.length <= 20 ? valid() : invalid("findings exceeds max items 20"),
|
|
1607
|
+
combine(findingsArray.map((finding, index) => validateTopTierReviewFindingV1(finding, `findings[${index}]`))),
|
|
1608
|
+
validateOpaqueRefArray(value.evidence_refs, "evidence_refs", 20),
|
|
1609
|
+
isEnumValue(value.uncertainty, TOP_TIER_REVIEW_UNCERTAINTY_LEVELS) ? valid() : invalid("uncertainty is invalid"),
|
|
1610
|
+
Array.isArray(value.required_fixes) ? valid() : invalid("required_fixes must be an array"),
|
|
1611
|
+
Array.isArray(value.required_fixes) && value.required_fixes.length <= 20 ? valid() : invalid("required_fixes exceeds max items 20"),
|
|
1612
|
+
validateStringArray(value.required_fixes, "required_fixes", undefined, 20),
|
|
1613
|
+
Array.isArray(value.required_fixes) ? combine(value.required_fixes.map((fix, index) => typeof fix === "string" && fix.length > 0 && fix.length <= 200 ? valid() : invalid(`required_fixes[${index}] must be a bounded string`))) : valid(),
|
|
1614
|
+
isEnumValue(value.verdict_label, TOP_TIER_REVIEW_VERDICT_LABELS) ? valid() : invalid("verdict_label is invalid"),
|
|
1615
|
+
validateStringArray(value.safe_next_actions, "safe_next_actions", SAFE_NEXT_ACTIONS, 8),
|
|
1616
|
+
value.dispatch_authority_enabled === false ? valid() : invalid("dispatch_authority_enabled must be false for top tier review verdict"),
|
|
1617
|
+
validateNoForbiddenRawPayloads(value, "top_tier_review_verdict")
|
|
1618
|
+
]);
|
|
1619
|
+
}
|
|
1620
|
+
export function validateFlowDeskPlannedModeFieldV1(value) {
|
|
1621
|
+
if (!isRecord(value))
|
|
1622
|
+
return invalid("planned mode field must be an object");
|
|
1623
|
+
return combine([
|
|
1624
|
+
rejectUnknownProperties(value, ["label", "state", "dispatch_authority_enabled"]),
|
|
1625
|
+
requireFields(value, ["label", "state", "dispatch_authority_enabled"]),
|
|
1626
|
+
isEnumValue(value.label, PLANNED_MODE_FIELD_LABELS) ? valid() : invalid("planned mode field label is invalid"),
|
|
1627
|
+
typeof value.state === "string" && FORBIDDEN_PLANNED_MODE_FIELD_STATES.includes(value.state) ? invalid(`planned mode field state cannot be "${value.state}" without a release gate`) : valid(),
|
|
1628
|
+
isEnumValue(value.state, PLANNED_MODE_FIELD_STATES) ? valid() : invalid("planned mode field state is invalid"),
|
|
1629
|
+
value.dispatch_authority_enabled === false ? valid() : invalid("planned mode field cannot enable dispatch authority"),
|
|
1630
|
+
validateNoForbiddenRawPayloads(value, "planned_mode_field")
|
|
1631
|
+
]);
|
|
1632
|
+
}
|
|
1633
|
+
export function validateFlowDeskPlannedModeFieldEntryStringV1(entry) {
|
|
1634
|
+
if (typeof entry !== "string" || entry.length === 0 || entry.length > 200)
|
|
1635
|
+
return invalid("planned mode field entry must be a bounded string");
|
|
1636
|
+
const parts = entry.split("=");
|
|
1637
|
+
if (parts.length !== 2)
|
|
1638
|
+
return invalid("planned mode field entry must be in label=state form");
|
|
1639
|
+
const [label, state] = parts;
|
|
1640
|
+
return validateFlowDeskPlannedModeFieldV1({ label, state, dispatch_authority_enabled: false });
|
|
1641
|
+
}
|
|
1642
|
+
const TOP_TIER_REVIEWER_LANE_PROBE_CHANNELS = ["subtask_true_command_lane", "injected_sdk_client"];
|
|
1643
|
+
const TOP_TIER_REVIEWER_LANE_PROBE_OUTCOMES = ["probe_pass", "probe_fail_closed", "probe_invalid"];
|
|
1644
|
+
export function validateTopTierReviewerLaneProbeRequestV1(value) {
|
|
1645
|
+
if (!isRecord(value))
|
|
1646
|
+
return invalid("top tier reviewer lane probe request must be an object");
|
|
1647
|
+
return combine([
|
|
1648
|
+
validateSchemaArtifactValue("flowdesk.top_tier_reviewer_lane_probe.request.v1", value),
|
|
1649
|
+
rejectForbiddenAuthorityKeys(value, "top tier reviewer lane probe request"),
|
|
1650
|
+
validateOpaqueId(value.probe_id, "probe_id"),
|
|
1651
|
+
validateOpaqueRef(value.binding_ref, "binding_ref"),
|
|
1652
|
+
validateOpaqueRef(value.lane_plan_ref, "lane_plan_ref"),
|
|
1653
|
+
isEnumValue(value.channel, TOP_TIER_REVIEWER_LANE_PROBE_CHANNELS) ? valid() : invalid("channel is invalid"),
|
|
1654
|
+
value.agent_id === "reviewer" ? valid() : invalid("agent_id must be canonical reviewer"),
|
|
1655
|
+
validateConcreteProviderQualifiedModelId(value.provider_qualified_model_id, "probe provider_qualified_model_id"),
|
|
1656
|
+
isEnumValue(value.perspective, TOP_TIER_REVIEWER_PERSPECTIVES) ? valid() : invalid("perspective is invalid"),
|
|
1657
|
+
validateOpaqueRef(value.auth_evidence_ref, "auth_evidence_ref"),
|
|
1658
|
+
validateOpaqueRef(value.usage_evidence_ref, "usage_evidence_ref"),
|
|
1659
|
+
validateOpaqueRef(value.quota_evidence_ref, "quota_evidence_ref"),
|
|
1660
|
+
validateOpaqueRef(value.provider_health_ref, "provider_health_ref"),
|
|
1661
|
+
validateOpaqueRef(value.runtime_echo_ref, "runtime_echo_ref"),
|
|
1662
|
+
validateOpaqueRef(value.telemetry_ref, "telemetry_ref"),
|
|
1663
|
+
validateOpaqueRef(value.policy_pack_eligibility_ref, "policy_pack_eligibility_ref"),
|
|
1664
|
+
typeof value.redaction_version === "string" && value.redaction_version.length > 0 && value.redaction_version.length <= 128 ? validateNoForbiddenRawPayloads(value.redaction_version, "redaction_version") : invalid("redaction_version is required"),
|
|
1665
|
+
value.fake_runtime === true ? valid() : invalid("fake_runtime must be true for probe requests"),
|
|
1666
|
+
value.dispatch_authority_enabled === false ? valid() : invalid("dispatch_authority_enabled must be false for probe requests"),
|
|
1667
|
+
validateNoForbiddenRawPayloads(value, "top_tier_reviewer_lane_probe_request")
|
|
1668
|
+
]);
|
|
1669
|
+
}
|
|
1670
|
+
export function validateTopTierReviewerLaneProbeResultV1(value) {
|
|
1671
|
+
if (!isRecord(value))
|
|
1672
|
+
return invalid("top tier reviewer lane probe result must be an object");
|
|
1673
|
+
return combine([
|
|
1674
|
+
validateSchemaArtifactValue("flowdesk.top_tier_reviewer_lane_probe.result.v1", value),
|
|
1675
|
+
rejectForbiddenAuthorityKeys(value, "top tier reviewer lane probe result"),
|
|
1676
|
+
validateOpaqueId(value.probe_id, "probe_id"),
|
|
1677
|
+
isEnumValue(value.channel, TOP_TIER_REVIEWER_LANE_PROBE_CHANNELS) ? valid() : invalid("channel is invalid"),
|
|
1678
|
+
isEnumValue(value.outcome, TOP_TIER_REVIEWER_LANE_PROBE_OUTCOMES) ? valid() : invalid("outcome is invalid"),
|
|
1679
|
+
value.failure_label === undefined || (typeof value.failure_label === "string" && value.failure_label.length > 0 && value.failure_label.length <= 200) ? valid() : invalid("failure_label must be a bounded string when present"),
|
|
1680
|
+
value.failure_label === undefined ? valid() : validateNoForbiddenRawPayloads(value.failure_label, "failure_label"),
|
|
1681
|
+
validateTimestamp(value.observed_at, "observed_at"),
|
|
1682
|
+
validateOpaqueRefArray(value.evidence_refs, "evidence_refs", 20),
|
|
1683
|
+
validateStringArray(value.safe_next_actions, "safe_next_actions", SAFE_NEXT_ACTIONS, 8),
|
|
1684
|
+
value.dispatch_authority_enabled === false ? valid() : invalid("dispatch_authority_enabled must be false for probe results"),
|
|
1685
|
+
value.provider_call_made === false ? valid() : invalid("provider_call_made must be false for probe results"),
|
|
1686
|
+
value.lane_launch_made === false ? valid() : invalid("lane_launch_made must be false for probe results"),
|
|
1687
|
+
validateNoForbiddenRawPayloads(value, "top_tier_reviewer_lane_probe_result")
|
|
1688
|
+
]);
|
|
1689
|
+
}
|
|
1690
|
+
//# sourceMappingURL=validators.js.map
|