@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.
Files changed (121) hide show
  1. package/dist/agent-profiles.d.ts +38 -0
  2. package/dist/agent-profiles.d.ts.map +1 -0
  3. package/dist/agent-profiles.js +138 -0
  4. package/dist/agent-profiles.js.map +1 -0
  5. package/dist/audit.d.ts +11 -0
  6. package/dist/audit.d.ts.map +1 -0
  7. package/dist/audit.js +65 -0
  8. package/dist/audit.js.map +1 -0
  9. package/dist/bootstrap-foundation.d.ts +83 -0
  10. package/dist/bootstrap-foundation.d.ts.map +1 -0
  11. package/dist/bootstrap-foundation.js +773 -0
  12. package/dist/bootstrap-foundation.js.map +1 -0
  13. package/dist/chat-routing.d.ts +15 -0
  14. package/dist/chat-routing.d.ts.map +1 -0
  15. package/dist/chat-routing.js +172 -0
  16. package/dist/chat-routing.js.map +1 -0
  17. package/dist/command-manifest.d.ts +39 -0
  18. package/dist/command-manifest.d.ts.map +1 -0
  19. package/dist/command-manifest.js +240 -0
  20. package/dist/command-manifest.js.map +1 -0
  21. package/dist/config-policy.d.ts +31 -0
  22. package/dist/config-policy.d.ts.map +1 -0
  23. package/dist/config-policy.js +341 -0
  24. package/dist/config-policy.js.map +1 -0
  25. package/dist/fake-runtime.d.ts +68 -0
  26. package/dist/fake-runtime.d.ts.map +1 -0
  27. package/dist/fake-runtime.js +285 -0
  28. package/dist/fake-runtime.js.map +1 -0
  29. package/dist/guard-boundary.d.ts +53 -0
  30. package/dist/guard-boundary.d.ts.map +1 -0
  31. package/dist/guard-boundary.js +220 -0
  32. package/dist/guard-boundary.js.map +1 -0
  33. package/dist/guarded-dry-run.d.ts +46 -0
  34. package/dist/guarded-dry-run.d.ts.map +1 -0
  35. package/dist/guarded-dry-run.js +111 -0
  36. package/dist/guarded-dry-run.js.map +1 -0
  37. package/dist/hook-harness.d.ts +8 -0
  38. package/dist/hook-harness.d.ts.map +1 -0
  39. package/dist/hook-harness.js +130 -0
  40. package/dist/hook-harness.js.map +1 -0
  41. package/dist/index.d.ts +41 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +51 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/lane-observability.d.ts +22 -0
  46. package/dist/lane-observability.d.ts.map +1 -0
  47. package/dist/lane-observability.js +138 -0
  48. package/dist/lane-observability.js.map +1 -0
  49. package/dist/plan.d.ts +37 -0
  50. package/dist/plan.d.ts.map +1 -0
  51. package/dist/plan.js +137 -0
  52. package/dist/plan.js.map +1 -0
  53. package/dist/production-enablement.d.ts +57 -0
  54. package/dist/production-enablement.d.ts.map +1 -0
  55. package/dist/production-enablement.js +259 -0
  56. package/dist/production-enablement.js.map +1 -0
  57. package/dist/provider-failures.d.ts +19 -0
  58. package/dist/provider-failures.d.ts.map +1 -0
  59. package/dist/provider-failures.js +116 -0
  60. package/dist/provider-failures.js.map +1 -0
  61. package/dist/provider-usage-collector.d.ts +60 -0
  62. package/dist/provider-usage-collector.d.ts.map +1 -0
  63. package/dist/provider-usage-collector.js +502 -0
  64. package/dist/provider-usage-collector.js.map +1 -0
  65. package/dist/redaction.d.ts +5 -0
  66. package/dist/redaction.d.ts.map +1 -0
  67. package/dist/redaction.js +11 -0
  68. package/dist/redaction.js.map +1 -0
  69. package/dist/release1-contracts.d.ts +1203 -0
  70. package/dist/release1-contracts.d.ts.map +1 -0
  71. package/dist/release1-contracts.js +256 -0
  72. package/dist/release1-contracts.js.map +1 -0
  73. package/dist/retry.d.ts +21 -0
  74. package/dist/retry.d.ts.map +1 -0
  75. package/dist/retry.js +107 -0
  76. package/dist/retry.js.map +1 -0
  77. package/dist/reviewer-lane-conformance.d.ts +35 -0
  78. package/dist/reviewer-lane-conformance.d.ts.map +1 -0
  79. package/dist/reviewer-lane-conformance.js +124 -0
  80. package/dist/reviewer-lane-conformance.js.map +1 -0
  81. package/dist/schema-artifacts.d.ts +19 -0
  82. package/dist/schema-artifacts.d.ts.map +1 -0
  83. package/dist/schema-artifacts.js +182 -0
  84. package/dist/schema-artifacts.js.map +1 -0
  85. package/dist/schema-registry.d.ts +32 -0
  86. package/dist/schema-registry.d.ts.map +1 -0
  87. package/dist/schema-registry.js +163 -0
  88. package/dist/schema-registry.js.map +1 -0
  89. package/dist/session-evidence.d.ts +71 -0
  90. package/dist/session-evidence.d.ts.map +1 -0
  91. package/dist/session-evidence.js +336 -0
  92. package/dist/session-evidence.js.map +1 -0
  93. package/dist/state-paths.d.ts +20 -0
  94. package/dist/state-paths.d.ts.map +1 -0
  95. package/dist/state-paths.js +72 -0
  96. package/dist/state-paths.js.map +1 -0
  97. package/dist/state-store.d.ts +75 -0
  98. package/dist/state-store.d.ts.map +1 -0
  99. package/dist/state-store.js +567 -0
  100. package/dist/state-store.js.map +1 -0
  101. package/dist/status.d.ts +37 -0
  102. package/dist/status.d.ts.map +1 -0
  103. package/dist/status.js +304 -0
  104. package/dist/status.js.map +1 -0
  105. package/dist/tool-contract-fixtures.d.ts +134 -0
  106. package/dist/tool-contract-fixtures.d.ts.map +1 -0
  107. package/dist/tool-contract-fixtures.js +389 -0
  108. package/dist/tool-contract-fixtures.js.map +1 -0
  109. package/dist/top-tier-reviewer-lane-probe.d.ts +10 -0
  110. package/dist/top-tier-reviewer-lane-probe.d.ts.map +1 -0
  111. package/dist/top-tier-reviewer-lane-probe.js +76 -0
  112. package/dist/top-tier-reviewer-lane-probe.js.map +1 -0
  113. package/dist/usage-health.d.ts +44 -0
  114. package/dist/usage-health.d.ts.map +1 -0
  115. package/dist/usage-health.js +105 -0
  116. package/dist/usage-health.js.map +1 -0
  117. package/dist/validators.d.ts +142 -0
  118. package/dist/validators.d.ts.map +1 -0
  119. package/dist/validators.js +1690 -0
  120. package/dist/validators.js.map +1 -0
  121. package/package.json +37 -0
@@ -0,0 +1,773 @@
1
+ import { mkdirSync, renameSync, writeFileSync } from "node:fs";
2
+ import { dirname, resolve, sep } from "node:path";
3
+ import { BOOTSTRAP_FAILURE_CLASSES, BOOTSTRAP_MUTATION_STATUSES, BOOTSTRAP_PHASES, DISABLED_MODES, DOCTOR_FAILURE_CATEGORIES } from "./release1-contracts.js";
4
+ import { validateFlowDeskRelativeStatePath } from "./state-paths.js";
5
+ import { invalid, valid, validateNoForbiddenRawPayloads, validateOpaqueId, validateOpaqueRef, validateSchemaArtifactValue } from "./validators.js";
6
+ const BOOTSTRAP_SAFE_NEXT_ACTIONS = ["/flowdesk-doctor", "/flowdesk-status", "/flowdesk-export-debug", "continue_chat", "ask_clarification"];
7
+ const DOCTOR_SAFE_NEXT_ACTIONS = ["/flowdesk-doctor", "/flowdesk-status", "/flowdesk-export-debug"];
8
+ const REQUIRED_DISABLED_BOOTSTRAP_MODES = ["real_dispatch", "managed_fallback", "lane_launch", "hard_chat_blocking"];
9
+ const CHAT_DISABLED_MODES = ["chat_routed"];
10
+ const DOCTOR_CATEGORY_OUTCOMES = {
11
+ dispatch_blocking: {
12
+ category: "dispatch_blocking",
13
+ disabled_modes: [...REQUIRED_DISABLED_BOOTSTRAP_MODES],
14
+ safe_next_actions: [...DOCTOR_SAFE_NEXT_ACTIONS],
15
+ managed_dispatch_allowed: false,
16
+ privileged_automation_allowed: false,
17
+ dispatch_authorized: false,
18
+ fallback_authorized: false,
19
+ guard_bypassed: false
20
+ },
21
+ chat_mode_disable: {
22
+ category: "chat_mode_disable",
23
+ disabled_modes: [...REQUIRED_DISABLED_BOOTSTRAP_MODES, ...CHAT_DISABLED_MODES],
24
+ safe_next_actions: [...DOCTOR_SAFE_NEXT_ACTIONS],
25
+ managed_dispatch_allowed: false,
26
+ privileged_automation_allowed: false,
27
+ dispatch_authorized: false,
28
+ fallback_authorized: false,
29
+ guard_bypassed: false
30
+ },
31
+ degraded_mode_warning: {
32
+ category: "degraded_mode_warning",
33
+ disabled_modes: [...REQUIRED_DISABLED_BOOTSTRAP_MODES],
34
+ safe_next_actions: ["/flowdesk-doctor", "/flowdesk-status"],
35
+ managed_dispatch_allowed: false,
36
+ privileged_automation_allowed: false,
37
+ dispatch_authorized: false,
38
+ fallback_authorized: false,
39
+ guard_bypassed: false
40
+ },
41
+ informational: {
42
+ category: "informational",
43
+ disabled_modes: [...REQUIRED_DISABLED_BOOTSTRAP_MODES],
44
+ safe_next_actions: ["/flowdesk-status"],
45
+ managed_dispatch_allowed: false,
46
+ privileged_automation_allowed: false,
47
+ dispatch_authorized: false,
48
+ fallback_authorized: false,
49
+ guard_bypassed: false
50
+ }
51
+ };
52
+ const MUTATING_BOOTSTRAP_PHASES = ["profile_mutation", "omo_cleanup", "command_generation", "config_scaffold"];
53
+ const BOOTSTRAP_REPORT_REF_FIELDS = ["backup_manifest_ref", "profile_mutation_ref", "omo_cleanup_ref", "command_generation_ref", "config_scaffold_ref", "rollback_plan_ref", "rollback_result_ref", "doctor_handoff_ref", "doctor_report_ref"];
54
+ const disabledDurableBootstrapAuthority = {
55
+ bootstrapAuthority: "redacted_bootstrap_artifact",
56
+ productionRegistrationEligible: false,
57
+ dispatchApprovalEligible: false,
58
+ realOpenCodeDispatch: false,
59
+ actualLaneLaunch: false,
60
+ providerCall: false,
61
+ runtimeExecution: false
62
+ };
63
+ function isRecord(value) {
64
+ return typeof value === "object" && value !== null && !Array.isArray(value);
65
+ }
66
+ function isEnumValue(value, allowed) {
67
+ return typeof value === "string" && allowed.includes(value);
68
+ }
69
+ function combine(results) {
70
+ const errors = results.flatMap((result) => result.errors);
71
+ return errors.length === 0 ? valid() : invalid(...errors);
72
+ }
73
+ function validateTimestamp(value, label) {
74
+ return typeof value === "string" && Number.isFinite(Date.parse(value)) ? valid() : invalid(`${label} must be a timestamp`);
75
+ }
76
+ function validateHash(value, label) {
77
+ if (typeof value !== "string" || value.length < 3 || value.length > 128)
78
+ return invalid(`${label} must be a bounded hash`);
79
+ if (!/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/.test(value))
80
+ return invalid(`${label} is not schema-safe`);
81
+ return validateNoForbiddenRawPayloads(value, label);
82
+ }
83
+ function validateRefArray(value, label, maxItems = 20) {
84
+ if (!Array.isArray(value))
85
+ return invalid(`${label} must be an array`);
86
+ const errors = [];
87
+ if (value.length > maxItems)
88
+ errors.push(`${label} exceeds max items ${maxItems}`);
89
+ value.forEach((item, index) => {
90
+ errors.push(...validateOpaqueRef(item, `${label}[${index}]`).errors);
91
+ });
92
+ return errors.length === 0 ? valid() : invalid(...errors);
93
+ }
94
+ function validateBootstrapSafeNextActions(value, label = "safe_next_actions") {
95
+ if (!Array.isArray(value))
96
+ return invalid(`${label} must be an array`);
97
+ const errors = [];
98
+ if (value.length > 4)
99
+ errors.push(`${label} exceeds bootstrap max items 4`);
100
+ value.forEach((item, index) => {
101
+ if (!isEnumValue(item, BOOTSTRAP_SAFE_NEXT_ACTIONS))
102
+ errors.push(`${label}[${index}] is not a bootstrap-safe next action`);
103
+ });
104
+ return errors.length === 0 ? valid() : invalid(...errors);
105
+ }
106
+ export function getDoctorFailureCategoryOutcomeV1(category) {
107
+ return { ...DOCTOR_CATEGORY_OUTCOMES[category], disabled_modes: [...DOCTOR_CATEGORY_OUTCOMES[category].disabled_modes], safe_next_actions: [...DOCTOR_CATEGORY_OUTCOMES[category].safe_next_actions] };
108
+ }
109
+ export function buildDoctorSectionResultV1(input) {
110
+ const outcome = getDoctorFailureCategoryOutcomeV1(input.category);
111
+ return {
112
+ schema_version: "flowdesk.doctor_section_result.v1",
113
+ ...input,
114
+ safe_next_actions: [...(input.safe_next_actions ?? outcome.safe_next_actions)]
115
+ };
116
+ }
117
+ function validateDoctorSafeNextActions(value, label = "safe_next_actions") {
118
+ if (!Array.isArray(value))
119
+ return invalid(`${label} must be an array`);
120
+ const errors = [];
121
+ if (value.length > 3)
122
+ errors.push(`${label} exceeds doctor max items 3`);
123
+ value.forEach((item, index) => {
124
+ if (!isEnumValue(item, DOCTOR_SAFE_NEXT_ACTIONS))
125
+ errors.push(`${label}[${index}] is not a doctor diagnostic or status action`);
126
+ });
127
+ return errors.length === 0 ? valid() : invalid(...errors);
128
+ }
129
+ function validateBootstrapStatus(value, label = "status") {
130
+ return isEnumValue(value, BOOTSTRAP_MUTATION_STATUSES) ? valid() : invalid(`${label} is invalid`);
131
+ }
132
+ function validateCount(value, label) {
133
+ return typeof value === "number" && Number.isInteger(value) && Number.isFinite(value) && value >= 0 ? valid() : invalid(`${label} is invalid`);
134
+ }
135
+ function validateFreshCheck(value, label) {
136
+ if (!isRecord(value))
137
+ return invalid(`${label} must be an object`);
138
+ return combine([
139
+ isEnumValue(value.check, ["usage", "provider_health", "policy", "runtime_capability", "checkpoint", "audit"]) ? valid() : invalid(`${label}.check is invalid`),
140
+ typeof value.required === "boolean" ? valid() : invalid(`${label}.required must be boolean`),
141
+ value.ref === undefined ? valid() : validateOpaqueRef(value.ref, `${label}.ref`),
142
+ validateNoForbiddenRawPayloads(value, label)
143
+ ]);
144
+ }
145
+ function validateFreshCheckArray(value, label, maxItems = 20) {
146
+ if (!Array.isArray(value))
147
+ return invalid(`${label} must be an array`);
148
+ const errors = [];
149
+ if (value.length > maxItems)
150
+ errors.push(`${label} exceeds max items ${maxItems}`);
151
+ value.forEach((item, index) => {
152
+ errors.push(...validateFreshCheck(item, `${label}[${index}]`).errors);
153
+ });
154
+ return errors.length === 0 ? valid() : invalid(...errors);
155
+ }
156
+ function validateDisabledModeContainment(value) {
157
+ if (!Array.isArray(value))
158
+ return invalid("disabled_modes must be an array");
159
+ const modes = new Set(value);
160
+ const errors = [];
161
+ value.forEach((mode, index) => {
162
+ if (!isEnumValue(mode, DISABLED_MODES))
163
+ errors.push(`disabled_modes[${index}] is invalid`);
164
+ });
165
+ for (const mode of REQUIRED_DISABLED_BOOTSTRAP_MODES) {
166
+ if (!modes.has(mode))
167
+ errors.push(`bootstrap report must keep ${mode} disabled`);
168
+ }
169
+ return errors.length === 0 ? valid() : invalid(...errors);
170
+ }
171
+ function validateBootstrapReportRefs(value) {
172
+ return combine(BOOTSTRAP_REPORT_REF_FIELDS.map((field) => (value[field] === undefined ? valid() : validateOpaqueRef(value[field], field))));
173
+ }
174
+ function appendResultErrors(errors, label, result) {
175
+ if (!result.ok)
176
+ errors.push(...result.errors.map((error) => `${label}: ${error}`));
177
+ }
178
+ function validateTypedConfirmationBinding(input) {
179
+ const confirmation = input.typedConfirmation;
180
+ if (confirmation === undefined)
181
+ return invalid("typed confirmation binding is required for bootstrap mutation evidence");
182
+ if (!isRecord(confirmation))
183
+ return invalid("typed confirmation binding must be an object");
184
+ const errors = [];
185
+ appendResultErrors(errors, "typed_confirmation.confirmation_ref", validateOpaqueRef(confirmation.confirmation_ref, "confirmation_ref"));
186
+ appendResultErrors(errors, "typed_confirmation.target_profile_ref", validateOpaqueRef(confirmation.target_profile_ref, "target_profile_ref"));
187
+ appendResultErrors(errors, "typed_confirmation.install_plan_ref", validateOpaqueRef(confirmation.install_plan_ref, "install_plan_ref"));
188
+ appendResultErrors(errors, "typed_confirmation.backup_manifest_ref", validateOpaqueRef(confirmation.backup_manifest_ref, "backup_manifest_ref"));
189
+ appendResultErrors(errors, "typed_confirmation.rollback_plan_ref", validateOpaqueRef(confirmation.rollback_plan_ref, "rollback_plan_ref"));
190
+ appendResultErrors(errors, "typed_confirmation.expires_at", validateTimestamp(confirmation.expires_at, "expires_at"));
191
+ if (confirmation.actor_class !== "user")
192
+ errors.push("typed confirmation actor_class must be user");
193
+ if (confirmation.consumed_by !== undefined && !isEnumValue(confirmation.consumed_by, ["profile_mutation", "rollback", "doctor_handoff"]))
194
+ errors.push("typed confirmation consumed_by is invalid");
195
+ if (confirmation.consumed_ref !== undefined)
196
+ appendResultErrors(errors, "typed_confirmation.consumed_ref", validateOpaqueRef(confirmation.consumed_ref, "consumed_ref"));
197
+ if (input.installPlan.confirmation_ref !== confirmation.confirmation_ref)
198
+ errors.push("typed confirmation must match install plan confirmation_ref");
199
+ if (input.installPlan.install_plan_id !== confirmation.install_plan_ref)
200
+ errors.push("typed confirmation must bind the install plan");
201
+ if (input.installPlan.target_profile_ref !== confirmation.target_profile_ref)
202
+ errors.push("typed confirmation must bind the target profile");
203
+ if (input.installPlan.rollback_plan_ref !== confirmation.rollback_plan_ref)
204
+ errors.push("typed confirmation must bind the rollback plan");
205
+ if (input.backupManifest === undefined)
206
+ errors.push("typed confirmation requires a backup manifest before mutation");
207
+ else if (!isRecord(input.backupManifest))
208
+ errors.push("typed confirmation backup manifest evidence must be an object");
209
+ else if (input.backupManifest.backup_manifest_id !== confirmation.backup_manifest_ref)
210
+ errors.push("typed confirmation must bind the backup manifest");
211
+ if (input.rollbackPlan !== undefined && !isRecord(input.rollbackPlan))
212
+ errors.push("typed confirmation rollback plan evidence must be an object");
213
+ else if (isRecord(input.rollbackPlan) && input.rollbackPlan.rollback_plan_id !== confirmation.rollback_plan_ref)
214
+ errors.push("typed confirmation must match rollback plan evidence");
215
+ return errors.length === 0 ? valid() : invalid(...errors);
216
+ }
217
+ function validateDoctorPassed(report) {
218
+ return isRecord(report) && Array.isArray(report.category_results) && report.category_results.every((result) => isRecord(result) && result.category !== "dispatch_blocking");
219
+ }
220
+ export function validateDoctorSectionResultV1(value) {
221
+ if (!isRecord(value))
222
+ return invalid("doctor section result must be an object");
223
+ return combine([
224
+ validateSchemaArtifactValue("flowdesk.doctor_section_result.v1", value),
225
+ validateOpaqueId(value.run_id, "run_id"),
226
+ isEnumValue(value.section, ["migration_cleanup", "opencode_plugin_compatibility", "provider_usage_readiness", "policy_project_safety"]) ? valid() : invalid("doctor section is invalid"),
227
+ isEnumValue(value.category, DOCTOR_FAILURE_CATEGORIES) ? valid() : invalid("doctor category is invalid"),
228
+ typeof value.summary === "string" && value.summary.length > 0 && value.summary.length <= 500 ? validateNoForbiddenRawPayloads(value.summary, "summary") : invalid("summary is invalid"),
229
+ validateDoctorSafeNextActions(value.safe_next_actions),
230
+ validateRefArray(value.refs, "refs"),
231
+ validateHash(value.redaction_version, "redaction_version")
232
+ ]);
233
+ }
234
+ function validateDoctorReportCategoryContainment(value) {
235
+ const results = Array.isArray(value.category_results) ? value.category_results.filter(isRecord) : [];
236
+ const categories = new Set(results.map((result) => result.category));
237
+ const disabledModes = new Set(Array.isArray(value.disabled_modes) ? value.disabled_modes : []);
238
+ const errors = [];
239
+ if (categories.has("chat_mode_disable") && !disabledModes.has("chat_routed"))
240
+ errors.push("chat_mode_disable doctor reports must disable chat_routed mode");
241
+ if (!categories.has("chat_mode_disable") && disabledModes.has("chat_routed"))
242
+ errors.push("chat_routed mode may be disabled only for chat_mode_disable doctor results");
243
+ if (categories.has("dispatch_blocking")) {
244
+ for (const mode of REQUIRED_DISABLED_BOOTSTRAP_MODES) {
245
+ if (!disabledModes.has(mode))
246
+ errors.push(`dispatch_blocking doctor reports must disable ${mode}`);
247
+ }
248
+ }
249
+ const allowedModes = new Set([...REQUIRED_DISABLED_BOOTSTRAP_MODES, ...(categories.has("chat_mode_disable") ? CHAT_DISABLED_MODES : [])]);
250
+ for (const mode of disabledModes) {
251
+ if (isEnumValue(mode, DISABLED_MODES) && !allowedModes.has(mode))
252
+ errors.push(`doctor category report must not disable unrelated mode ${String(mode)}`);
253
+ }
254
+ return errors.length === 0 ? valid() : invalid(...errors);
255
+ }
256
+ export function validateBootstrapInstallPlanV1(value) {
257
+ if (!isRecord(value))
258
+ return invalid("bootstrap install plan must be an object");
259
+ const phases = Array.isArray(value.planned_phases) ? value.planned_phases : [];
260
+ const requiresMutationConfirmation = phases.some((phase) => MUTATING_BOOTSTRAP_PHASES.includes(phase));
261
+ return combine([
262
+ validateSchemaArtifactValue("flowdesk.bootstrap_install_plan.v1", value),
263
+ validateOpaqueId(value.install_plan_id, "install_plan_id"),
264
+ validateTimestamp(value.created_at, "created_at"),
265
+ validateOpaqueRef(value.target_profile_ref, "target_profile_ref"),
266
+ value.release_mode === "release1" ? valid() : invalid("bootstrap install plan release_mode must be release1"),
267
+ Array.isArray(value.planned_phases) ? combine(value.planned_phases.map((phase, index) => (isEnumValue(phase, BOOTSTRAP_PHASES) ? valid() : invalid(`planned_phases[${index}] is invalid`)))) : invalid("planned_phases must be an array"),
268
+ typeof value.requires_typed_confirmation === "boolean" ? valid() : invalid("requires_typed_confirmation must be boolean"),
269
+ requiresMutationConfirmation && value.requires_typed_confirmation !== true ? invalid("mutating bootstrap phases require typed confirmation") : valid(),
270
+ requiresMutationConfirmation && value.confirmation_ref === undefined ? invalid("mutating bootstrap phases require confirmation_ref") : valid(),
271
+ value.confirmation_ref === undefined ? valid() : validateOpaqueRef(value.confirmation_ref, "confirmation_ref"),
272
+ validateOpaqueRef(value.package_ref, "package_ref"),
273
+ validateOpaqueRef(value.rollback_plan_ref, "rollback_plan_ref"),
274
+ validateBootstrapSafeNextActions(value.safe_next_actions)
275
+ ]);
276
+ }
277
+ export function validateBootstrapBackupManifestV1(value) {
278
+ if (!isRecord(value))
279
+ return invalid("bootstrap backup manifest must be an object");
280
+ return combine([
281
+ validateSchemaArtifactValue("flowdesk.bootstrap_backup_manifest.v1", value),
282
+ validateOpaqueId(value.backup_manifest_id, "backup_manifest_id"),
283
+ validateTimestamp(value.created_at, "created_at"),
284
+ validateOpaqueRef(value.target_profile_ref, "target_profile_ref"),
285
+ validateOpaqueRef(value.backup_ref, "backup_ref"),
286
+ validateHash(value.backup_hash, "backup_hash"),
287
+ validateOpaqueRef(value.source_config_ref, "source_config_ref"),
288
+ isEnumValue(value.credential_preservation_check, ["passed", "blocked", "not_applicable"]) ? valid() : invalid("credential_preservation_check is invalid"),
289
+ typeof value.restore_eligible === "boolean" ? valid() : invalid("restore_eligible must be boolean"),
290
+ value.credential_preservation_check === "blocked" && value.restore_eligible === true ? invalid("blocked credential preservation cannot be restore eligible") : valid(),
291
+ validateOpaqueRef(value.audit_ref, "audit_ref")
292
+ ]);
293
+ }
294
+ export function validateProfileMutationSummaryV1(value) {
295
+ if (!isRecord(value))
296
+ return invalid("profile mutation summary must be an object");
297
+ return combine([
298
+ validateSchemaArtifactValue("flowdesk.profile_mutation_summary.v1", value),
299
+ validateOpaqueId(value.mutation_id, "mutation_id"),
300
+ validateOpaqueRef(value.target_profile_ref, "target_profile_ref"),
301
+ validateBootstrapStatus(value.status),
302
+ validateRefArray(value.changed_entry_refs, "changed_entry_refs"),
303
+ validateRefArray(value.skipped_entry_refs, "skipped_entry_refs"),
304
+ isEnumValue(value.provider_auth_preserved, ["passed", "blocked", "unknown"]) ? valid() : invalid("provider_auth_preserved is invalid"),
305
+ value.unrelated_profile_mutation === false ? valid() : invalid("unrelated_profile_mutation must remain false"),
306
+ validateOpaqueRef(value.backup_manifest_ref, "backup_manifest_ref"),
307
+ validateOpaqueRef(value.audit_ref, "audit_ref")
308
+ ]);
309
+ }
310
+ export function validateOmoCleanupSummaryV1(value) {
311
+ if (!isRecord(value))
312
+ return invalid("cleanup summary must be an object");
313
+ return combine([
314
+ validateSchemaArtifactValue("flowdesk.omo_cleanup_summary.v1", value),
315
+ validateOpaqueId(value.cleanup_id, "cleanup_id"),
316
+ validateOpaqueRef(value.target_profile_ref, "target_profile_ref"),
317
+ validateBootstrapStatus(value.status),
318
+ validateCount(value.removed_ref_count, "removed_ref_count"),
319
+ validateCount(value.retained_ref_count, "retained_ref_count"),
320
+ validateCount(value.blocked_ref_count, "blocked_ref_count"),
321
+ typeof value.omitted_legacy_runtime_imports === "boolean" ? valid() : invalid("omitted_legacy_runtime_imports must be boolean"),
322
+ isEnumValue(value.provider_auth_preserved, ["passed", "blocked", "unknown"]) ? valid() : invalid("provider_auth_preserved is invalid"),
323
+ validateOpaqueRef(value.backup_manifest_ref, "backup_manifest_ref"),
324
+ validateOpaqueRef(value.audit_ref, "audit_ref")
325
+ ]);
326
+ }
327
+ export function validateCommandGenerationSummaryV1(value) {
328
+ if (!isRecord(value))
329
+ return invalid("command generation summary must be an object");
330
+ return combine([
331
+ validateSchemaArtifactValue("flowdesk.command_generation_summary.v1", value),
332
+ validateOpaqueId(value.generation_id, "generation_id"),
333
+ validateOpaqueRef(value.target_profile_ref, "target_profile_ref"),
334
+ validateBootstrapStatus(value.status),
335
+ validateRefArray(value.command_refs, "command_refs"),
336
+ validateHash(value.template_hash, "template_hash"),
337
+ isEnumValue(value.static_template_validation, ["passed", "blocked"]) ? valid() : invalid("static_template_validation is invalid"),
338
+ value.static_template_validation === "passed" && value.status === "failed" ? invalid("failed command generation cannot have passed template validation") : valid(),
339
+ value.alias_conformance_ref === undefined ? valid() : validateOpaqueRef(value.alias_conformance_ref, "alias_conformance_ref"),
340
+ validateOpaqueRef(value.rollback_ref, "rollback_ref")
341
+ ]);
342
+ }
343
+ export function validateConfigScaffoldSummaryV1(value) {
344
+ if (!isRecord(value))
345
+ return invalid("config scaffold summary must be an object");
346
+ return combine([
347
+ validateSchemaArtifactValue("flowdesk.config_scaffold_summary.v1", value),
348
+ validateOpaqueId(value.scaffold_id, "scaffold_id"),
349
+ validateBootstrapStatus(value.status),
350
+ validateOpaqueRef(value.config_ref, "config_ref"),
351
+ validateHash(value.config_hash, "config_hash"),
352
+ validateRefArray(value.policy_pack_refs, "policy_pack_refs"),
353
+ Array.isArray(value.policy_pack_hashes) ? combine(value.policy_pack_hashes.map((hash, index) => validateHash(hash, `policy_pack_hashes[${index}]`))) : invalid("policy_pack_hashes must be an array"),
354
+ validateOpaqueRef(value.audit_ref, "audit_ref")
355
+ ]);
356
+ }
357
+ export function validateBootstrapRollbackPlanV1(value) {
358
+ if (!isRecord(value))
359
+ return invalid("bootstrap rollback plan must be an object");
360
+ return combine([
361
+ validateSchemaArtifactValue("flowdesk.bootstrap_rollback_plan.v1", value),
362
+ validateOpaqueId(value.rollback_plan_id, "rollback_plan_id"),
363
+ validateOpaqueId(value.install_plan_id, "install_plan_id"),
364
+ validateOpaqueRef(value.target_profile_ref, "target_profile_ref"),
365
+ validateOpaqueRef(value.backup_manifest_ref, "backup_manifest_ref"),
366
+ validateRefArray(value.reversible_phase_refs, "reversible_phase_refs"),
367
+ validateRefArray(value.non_reversible_summary_refs, "non_reversible_summary_refs"),
368
+ validateFreshCheckArray(value.restore_preconditions, "restore_preconditions"),
369
+ validateBootstrapSafeNextActions(value.safe_next_actions)
370
+ ]);
371
+ }
372
+ export function validateBootstrapRollbackResultV1(value) {
373
+ if (!isRecord(value))
374
+ return invalid("bootstrap rollback result must be an object");
375
+ return combine([
376
+ validateSchemaArtifactValue("flowdesk.bootstrap_rollback_result.v1", value),
377
+ validateOpaqueId(value.rollback_result_id, "rollback_result_id"),
378
+ validateOpaqueId(value.rollback_plan_id, "rollback_plan_id"),
379
+ validateTimestamp(value.completed_at, "completed_at"),
380
+ isEnumValue(value.status, ["restored", "partial", "blocked", "failed"]) ? valid() : invalid("status is invalid"),
381
+ validateCount(value.restored_ref_count, "restored_ref_count"),
382
+ validateCount(value.skipped_ref_count, "skipped_ref_count"),
383
+ validateCount(value.warning_count, "warning_count"),
384
+ validateRefArray(value.audit_refs, "audit_refs"),
385
+ validateBootstrapSafeNextActions(value.safe_next_actions)
386
+ ]);
387
+ }
388
+ export function validateBootstrapReportV1(value) {
389
+ if (!isRecord(value))
390
+ return invalid("bootstrap report must be an object");
391
+ return combine([
392
+ validateSchemaArtifactValue("flowdesk.bootstrap_report.v1", value),
393
+ validateOpaqueId(value.report_id, "report_id"),
394
+ validateOpaqueId(value.install_plan_id, "install_plan_id"),
395
+ validateOpaqueRef(value.target_profile_ref, "target_profile_ref"),
396
+ validateTimestamp(value.started_at, "started_at"),
397
+ value.completed_at === undefined ? valid() : validateTimestamp(value.completed_at, "completed_at"),
398
+ isEnumValue(value.final_phase, BOOTSTRAP_PHASES) ? valid() : invalid("final_phase is invalid"),
399
+ isEnumValue(value.status, ["complete", "failed", "rolled_back", "partial"]) ? valid() : invalid("status is invalid"),
400
+ value.failure_class === undefined ? valid() : isEnumValue(value.failure_class, BOOTSTRAP_FAILURE_CLASSES) ? valid() : invalid("failure_class is invalid"),
401
+ validateBootstrapReportRefs(value),
402
+ validateDisabledModeContainment(value.disabled_modes),
403
+ validateBootstrapSafeNextActions(value.safe_next_actions),
404
+ validateRefArray(value.audit_refs, "audit_refs")
405
+ ]);
406
+ }
407
+ export function validateDoctorReportV1(value) {
408
+ if (!isRecord(value))
409
+ return invalid("doctor report must be an object");
410
+ return combine([
411
+ validateSchemaArtifactValue("flowdesk.doctor_report.v1", value),
412
+ validateOpaqueId(value.run_id, "run_id"),
413
+ validateTimestamp(value.checked_at, "checked_at"),
414
+ isEnumValue(value.profile, ["production", "development", "test"]) ? valid() : invalid("profile is invalid"),
415
+ Array.isArray(value.category_results) ? combine(value.category_results.map((result) => validateDoctorSectionResultV1(result))) : invalid("category_results must be an array"),
416
+ validateDisabledModeContainment(value.disabled_modes),
417
+ validateDoctorReportCategoryContainment(value),
418
+ validateOpaqueRef(value.compatibility_ref, "compatibility_ref"),
419
+ validateDoctorSafeNextActions(value.safe_next_actions)
420
+ ]);
421
+ }
422
+ export function validateDoctorHandoffV1(value) {
423
+ if (!isRecord(value))
424
+ return invalid("doctor handoff must be an object");
425
+ return combine([
426
+ validateSchemaArtifactValue("flowdesk.doctor_handoff.v1", value),
427
+ validateOpaqueId(value.handoff_id, "handoff_id"),
428
+ validateTimestamp(value.created_at, "created_at"),
429
+ validateOpaqueRef(value.install_plan_ref, "install_plan_ref"),
430
+ validateOpaqueRef(value.bootstrap_report_ref, "bootstrap_report_ref"),
431
+ value.config_ref === undefined ? valid() : validateOpaqueRef(value.config_ref, "config_ref"),
432
+ value.compatibility_ref === undefined ? valid() : validateOpaqueRef(value.compatibility_ref, "compatibility_ref"),
433
+ validateOpaqueRef(value.doctor_request_ref, "doctor_request_ref"),
434
+ validateBootstrapSafeNextActions(value.safe_next_actions)
435
+ ]);
436
+ }
437
+ export function validateBootstrapFailureEvidenceV1(value) {
438
+ if (!isRecord(value))
439
+ return invalid("bootstrap failure evidence must be an object");
440
+ const input = value;
441
+ const errors = [];
442
+ if (input.installPlan === undefined)
443
+ return invalid("bootstrap failure evidence requires installPlan");
444
+ if (!isRecord(input.installPlan))
445
+ return invalid("bootstrap failure evidence installPlan must be an object");
446
+ appendResultErrors(errors, "installPlan", validateBootstrapInstallPlanV1(input.installPlan));
447
+ if (input.backupManifest !== undefined)
448
+ appendResultErrors(errors, "backupManifest", validateBootstrapBackupManifestV1(input.backupManifest));
449
+ if (input.profileMutation !== undefined)
450
+ appendResultErrors(errors, "profileMutation", validateProfileMutationSummaryV1(input.profileMutation));
451
+ if (input.omoCleanup !== undefined)
452
+ appendResultErrors(errors, "omoCleanup", validateOmoCleanupSummaryV1(input.omoCleanup));
453
+ if (input.commandGeneration !== undefined)
454
+ appendResultErrors(errors, "commandGeneration", validateCommandGenerationSummaryV1(input.commandGeneration));
455
+ if (input.configScaffold !== undefined)
456
+ appendResultErrors(errors, "configScaffold", validateConfigScaffoldSummaryV1(input.configScaffold));
457
+ if (input.rollbackPlan !== undefined)
458
+ appendResultErrors(errors, "rollbackPlan", validateBootstrapRollbackPlanV1(input.rollbackPlan));
459
+ if (input.rollbackResult !== undefined)
460
+ appendResultErrors(errors, "rollbackResult", validateBootstrapRollbackResultV1(input.rollbackResult));
461
+ if (input.bootstrapReport !== undefined)
462
+ appendResultErrors(errors, "bootstrapReport", validateBootstrapReportV1(input.bootstrapReport));
463
+ if (input.doctorHandoff !== undefined)
464
+ appendResultErrors(errors, "doctorHandoff", validateDoctorHandoffV1(input.doctorHandoff));
465
+ if (input.doctorReport !== undefined)
466
+ appendResultErrors(errors, "doctorReport", validateDoctorReportV1(input.doctorReport));
467
+ const backupManifest = isRecord(input.backupManifest) ? input.backupManifest : undefined;
468
+ const profileMutation = isRecord(input.profileMutation) ? input.profileMutation : undefined;
469
+ const omoCleanup = isRecord(input.omoCleanup) ? input.omoCleanup : undefined;
470
+ const commandGeneration = isRecord(input.commandGeneration) ? input.commandGeneration : undefined;
471
+ const rollbackPlan = isRecord(input.rollbackPlan) ? input.rollbackPlan : undefined;
472
+ const rollbackResult = isRecord(input.rollbackResult) ? input.rollbackResult : undefined;
473
+ const bootstrapReport = isRecord(input.bootstrapReport) ? input.bootstrapReport : undefined;
474
+ const doctorHandoff = isRecord(input.doctorHandoff) ? input.doctorHandoff : undefined;
475
+ const mutationArtifacts = [input.profileMutation, input.omoCleanup, input.commandGeneration, input.configScaffold].filter((artifact) => artifact !== undefined);
476
+ if (mutationArtifacts.length > 0) {
477
+ if (input.backupManifest === undefined)
478
+ errors.push("bootstrap mutation evidence requires backup-first manifest");
479
+ appendResultErrors(errors, "typedConfirmation", validateTypedConfirmationBinding(input));
480
+ }
481
+ const targetProfileRef = input.installPlan.target_profile_ref;
482
+ if (backupManifest !== undefined && backupManifest.target_profile_ref !== targetProfileRef)
483
+ errors.push("backup manifest target profile must match install plan");
484
+ for (const [label, artifact] of [
485
+ ["profileMutation", profileMutation],
486
+ ["omoCleanup", omoCleanup],
487
+ ["commandGeneration", commandGeneration],
488
+ ["rollbackPlan", rollbackPlan],
489
+ ["bootstrapReport", bootstrapReport]
490
+ ]) {
491
+ if (artifact !== undefined && "target_profile_ref" in artifact && artifact.target_profile_ref !== targetProfileRef)
492
+ errors.push(`${label} target profile must match install plan`);
493
+ }
494
+ const backupManifestId = backupManifest?.backup_manifest_id;
495
+ if (profileMutation !== undefined && profileMutation.backup_manifest_ref !== backupManifestId)
496
+ errors.push("profile mutation must reference the backup manifest");
497
+ if (omoCleanup !== undefined && omoCleanup.backup_manifest_ref !== backupManifestId)
498
+ errors.push("OMO cleanup must reference the backup manifest");
499
+ if (rollbackPlan !== undefined) {
500
+ if (input.backupManifest === undefined)
501
+ errors.push("rollback plan requires backup manifest evidence");
502
+ else if (rollbackPlan.backup_manifest_ref !== backupManifestId)
503
+ errors.push("rollback plan must reference the backup manifest");
504
+ if (rollbackPlan.install_plan_id !== input.installPlan.install_plan_id)
505
+ errors.push("rollback plan must reference the install plan");
506
+ }
507
+ if (input.rollbackResult !== undefined && input.rollbackPlan === undefined)
508
+ errors.push("rollback result requires rollback plan evidence");
509
+ if (profileMutation?.status === "applied" && profileMutation.provider_auth_preserved !== "passed")
510
+ errors.push("applied profile mutation must prove provider auth preservation");
511
+ if (omoCleanup?.status === "applied" && omoCleanup.provider_auth_preserved !== "passed")
512
+ errors.push("applied OMO cleanup must prove provider auth preservation");
513
+ if (rollbackResult !== undefined && ["partial", "blocked", "failed"].includes(String(rollbackResult.status))) {
514
+ if (rollbackResult.warning_count === 0 && rollbackResult.skipped_ref_count === 0)
515
+ errors.push("partial or blocked rollback requires warning or skipped counts");
516
+ if (!Array.isArray(rollbackResult.safe_next_actions) || !rollbackResult.safe_next_actions.some((action) => action === "/flowdesk-doctor" || action === "/flowdesk-export-debug"))
517
+ errors.push("partial or blocked rollback must expose doctor or debug export guidance");
518
+ }
519
+ if (bootstrapReport !== undefined) {
520
+ if (bootstrapReport.backup_manifest_ref !== undefined && bootstrapReport.backup_manifest_ref !== backupManifestId)
521
+ errors.push("bootstrap report backup ref must match backup manifest");
522
+ if (bootstrapReport.rollback_plan_ref !== undefined && bootstrapReport.rollback_plan_ref !== rollbackPlan?.rollback_plan_id)
523
+ errors.push("bootstrap report rollback plan ref must match rollback plan");
524
+ if (bootstrapReport.rollback_result_ref !== undefined && bootstrapReport.rollback_result_ref !== rollbackResult?.rollback_result_id)
525
+ errors.push("bootstrap report rollback result ref must match rollback result");
526
+ if (bootstrapReport.status === "complete") {
527
+ if (input.doctorHandoff === undefined)
528
+ errors.push("complete bootstrap report requires doctor handoff evidence");
529
+ if (bootstrapReport.doctor_handoff_ref === undefined)
530
+ errors.push("complete bootstrap report requires doctor_handoff_ref");
531
+ }
532
+ }
533
+ if (doctorHandoff !== undefined) {
534
+ if (doctorHandoff.install_plan_ref !== input.installPlan.install_plan_id)
535
+ errors.push("doctor handoff must bind install plan");
536
+ if (bootstrapReport !== undefined && doctorHandoff.bootstrap_report_ref !== bootstrapReport.report_id)
537
+ errors.push("doctor handoff must bind bootstrap report");
538
+ }
539
+ if (validateDoctorPassed(input.doctorReport) && input.bootstrapAuthorityRequestedAfterDoctorPass === true)
540
+ errors.push("bootstrap authority is closed after a passing doctor report");
541
+ return errors.length === 0 ? valid() : invalid(...errors);
542
+ }
543
+ export function validateFlowDeskBootstrapArtifactV1(value) {
544
+ if (!isRecord(value) || typeof value.schema_version !== "string")
545
+ return invalid("bootstrap artifact schema_version is required");
546
+ switch (value.schema_version) {
547
+ case "flowdesk.bootstrap_install_plan.v1":
548
+ return validateBootstrapInstallPlanV1(value);
549
+ case "flowdesk.bootstrap_backup_manifest.v1":
550
+ return validateBootstrapBackupManifestV1(value);
551
+ case "flowdesk.profile_mutation_summary.v1":
552
+ return validateProfileMutationSummaryV1(value);
553
+ case "flowdesk.omo_cleanup_summary.v1":
554
+ return validateOmoCleanupSummaryV1(value);
555
+ case "flowdesk.command_generation_summary.v1":
556
+ return validateCommandGenerationSummaryV1(value);
557
+ case "flowdesk.config_scaffold_summary.v1":
558
+ return validateConfigScaffoldSummaryV1(value);
559
+ case "flowdesk.bootstrap_rollback_plan.v1":
560
+ return validateBootstrapRollbackPlanV1(value);
561
+ case "flowdesk.bootstrap_rollback_result.v1":
562
+ return validateBootstrapRollbackResultV1(value);
563
+ case "flowdesk.bootstrap_report.v1":
564
+ return validateBootstrapReportV1(value);
565
+ case "flowdesk.doctor_handoff.v1":
566
+ return validateDoctorHandoffV1(value);
567
+ case "flowdesk.doctor_report.v1":
568
+ return validateDoctorReportV1(value);
569
+ default:
570
+ return invalid("unsupported bootstrap artifact schema_version");
571
+ }
572
+ }
573
+ export function assertFlowDeskBootstrapArtifactV1(value) {
574
+ const result = validateFlowDeskBootstrapArtifactV1(value);
575
+ if (!result.ok)
576
+ throw new Error(result.errors.join("; "));
577
+ }
578
+ function bootstrapArtifactId(record) {
579
+ switch (record.schema_version) {
580
+ case "flowdesk.bootstrap_install_plan.v1":
581
+ return record.install_plan_id;
582
+ case "flowdesk.bootstrap_backup_manifest.v1":
583
+ return record.backup_manifest_id;
584
+ case "flowdesk.profile_mutation_summary.v1":
585
+ return record.mutation_id;
586
+ case "flowdesk.omo_cleanup_summary.v1":
587
+ return record.cleanup_id;
588
+ case "flowdesk.command_generation_summary.v1":
589
+ return record.generation_id;
590
+ case "flowdesk.config_scaffold_summary.v1":
591
+ return record.scaffold_id;
592
+ case "flowdesk.bootstrap_rollback_plan.v1":
593
+ return record.rollback_plan_id;
594
+ case "flowdesk.bootstrap_rollback_result.v1":
595
+ return record.rollback_result_id;
596
+ case "flowdesk.bootstrap_report.v1":
597
+ return record.report_id;
598
+ case "flowdesk.doctor_handoff.v1":
599
+ return record.handoff_id;
600
+ case "flowdesk.doctor_report.v1":
601
+ return record.run_id;
602
+ }
603
+ }
604
+ function bootstrapArtifactPath(installPlanId, schemaId, artifactId) {
605
+ const schemaSlug = schemaId.replace(/^flowdesk\./, "").replace(/\.v1$/, "").replace(/_/g, "-");
606
+ return `.flowdesk/bootstrap/${installPlanId}/${schemaSlug}/${artifactId}.json`;
607
+ }
608
+ function bootstrapRecordInstallPlanId(record) {
609
+ if ("install_plan_id" in record)
610
+ return record.install_plan_id;
611
+ if ("install_plan_ref" in record)
612
+ return record.install_plan_ref;
613
+ return undefined;
614
+ }
615
+ function canonicalPathForIntent(record, path) {
616
+ const prefix = ".flowdesk/bootstrap/";
617
+ if (!path.startsWith(prefix))
618
+ return invalid("bootstrap write intent path must stay under .flowdesk/bootstrap");
619
+ const segments = path.slice(prefix.length).split("/");
620
+ if (segments.length !== 3)
621
+ return invalid("bootstrap write intent path must use canonical bootstrap artifact layout");
622
+ const [installPlanId] = segments;
623
+ const installPlanResult = validateOpaqueId(installPlanId, "bootstrap write intent install_plan_id");
624
+ if (!installPlanResult.ok)
625
+ return installPlanResult;
626
+ const recordInstallPlanId = bootstrapRecordInstallPlanId(record);
627
+ if (recordInstallPlanId !== undefined && installPlanId !== recordInstallPlanId)
628
+ return invalid("bootstrap write intent install_plan_id must match record");
629
+ const expectedPath = bootstrapArtifactPath(installPlanId, record.schema_version, bootstrapArtifactId(record));
630
+ return path === expectedPath ? valid() : invalid("bootstrap write intent path must match record schema and artifact id");
631
+ }
632
+ function cloneRecord(record) {
633
+ return JSON.parse(JSON.stringify(record));
634
+ }
635
+ export function prepareRedactedBootstrapArtifactWriteIntent(installPlanId, record) {
636
+ const installPlanResult = validateOpaqueId(installPlanId, "install_plan_id");
637
+ if (!installPlanResult.ok)
638
+ return installPlanResult;
639
+ const recordResult = validateFlowDeskBootstrapArtifactV1(record);
640
+ if (!recordResult.ok)
641
+ return recordResult;
642
+ const artifactId = bootstrapArtifactId(record);
643
+ const artifactIdResult = validateOpaqueId(artifactId, "artifact_id");
644
+ if (!artifactIdResult.ok)
645
+ return artifactIdResult;
646
+ const path = bootstrapArtifactPath(installPlanId, record.schema_version, artifactId);
647
+ const pathResult = validateFlowDeskRelativeStatePath(path);
648
+ if (!pathResult.ok)
649
+ return pathResult;
650
+ const tempPath = `${path}.tmp-${record.schema_version.replace(/[^A-Za-z0-9_.-]/g, "-")}`;
651
+ const tempPathResult = validateFlowDeskRelativeStatePath(tempPath, "temp path");
652
+ if (!tempPathResult.ok)
653
+ return tempPathResult;
654
+ const cloned = cloneRecord(record);
655
+ return {
656
+ ok: true,
657
+ errors: [],
658
+ record: cloned,
659
+ writeIntent: {
660
+ operation: "write_json",
661
+ path,
662
+ schemaId: record.schema_version,
663
+ authority: "redacted_bootstrap_artifact",
664
+ record: cloned,
665
+ serialization: "json",
666
+ fsSafety: "validated_relative_flowdesk_path_only",
667
+ atomicity: {
668
+ strategy: "temp_then_rename_intent",
669
+ tempPath
670
+ }
671
+ }
672
+ };
673
+ }
674
+ export function validateRedactedBootstrapArtifactWriteIntent(intent) {
675
+ if (!isRecord(intent))
676
+ return invalid("bootstrap write intent must be an object");
677
+ const errors = [];
678
+ const unknown = Object.keys(intent).filter((key) => !["operation", "path", "schemaId", "authority", "record", "serialization", "fsSafety", "atomicity"].includes(key));
679
+ if (unknown.length > 0)
680
+ errors.push(`bootstrap write intent unknown properties: ${unknown.join(",")}`);
681
+ if (intent.operation !== "write_json")
682
+ errors.push("bootstrap write intent operation is invalid");
683
+ if (intent.authority !== "redacted_bootstrap_artifact")
684
+ errors.push("bootstrap write intent authority is invalid");
685
+ if (intent.serialization !== "json")
686
+ errors.push("bootstrap write intent serialization is invalid");
687
+ if (intent.fsSafety !== "validated_relative_flowdesk_path_only")
688
+ errors.push("bootstrap write intent path safety is invalid");
689
+ if (typeof intent.schemaId !== "string")
690
+ errors.push("bootstrap write intent schemaId is required");
691
+ const pathResult = validateFlowDeskRelativeStatePath(intent.path, "bootstrap write intent path");
692
+ if (!pathResult.ok)
693
+ errors.push(...pathResult.errors);
694
+ const tempPathResult = validateFlowDeskRelativeStatePath(isRecord(intent.atomicity) ? intent.atomicity.tempPath : undefined, "bootstrap write intent temp path");
695
+ if (!tempPathResult.ok)
696
+ errors.push(...tempPathResult.errors);
697
+ if (isRecord(intent.atomicity)) {
698
+ const unknownAtomicity = Object.keys(intent.atomicity).filter((key) => !["strategy", "tempPath"].includes(key));
699
+ if (unknownAtomicity.length > 0)
700
+ errors.push(`bootstrap write intent atomicity unknown properties: ${unknownAtomicity.join(",")}`);
701
+ }
702
+ if (!isRecord(intent.atomicity) || intent.atomicity.strategy !== "temp_then_rename_intent")
703
+ errors.push("bootstrap write intent atomicity is invalid");
704
+ if (typeof intent.path === "string" && isRecord(intent.atomicity) && typeof intent.atomicity.tempPath === "string" && !intent.atomicity.tempPath.startsWith(`${intent.path}.tmp-`))
705
+ errors.push("bootstrap write intent temp path must derive from target path");
706
+ if (!isRecord(intent.record))
707
+ errors.push("bootstrap write intent record is required");
708
+ if (typeof intent.schemaId === "string" && isRecord(intent.record) && intent.record.schema_version !== intent.schemaId)
709
+ errors.push("bootstrap write intent schemaId must match record");
710
+ if (errors.length > 0)
711
+ return invalid(...errors);
712
+ const recordResult = validateFlowDeskBootstrapArtifactV1(intent.record);
713
+ if (!recordResult.ok)
714
+ return recordResult;
715
+ return canonicalPathForIntent(intent.record, intent.path);
716
+ }
717
+ export function applyBootstrapWriteIntentsToInMemoryState(intents, initial) {
718
+ const state = new Map(initial ?? []);
719
+ for (const intent of intents) {
720
+ const result = validateRedactedBootstrapArtifactWriteIntent(intent);
721
+ if (!result.ok)
722
+ throw new Error(result.errors.join("; "));
723
+ state.set(intent.path, JSON.stringify(intent.record));
724
+ }
725
+ return state;
726
+ }
727
+ function resolveBootstrapTarget(rootDir, relativePath) {
728
+ const root = resolve(rootDir);
729
+ const target = resolve(root, relativePath);
730
+ const temp = resolve(root, `${relativePath}.tmp-${Date.now().toString(36)}`);
731
+ const rootPrefix = root.endsWith(sep) ? root : `${root}${sep}`;
732
+ if (target !== root && !target.startsWith(rootPrefix))
733
+ throw new Error("bootstrap target escapes rootDir");
734
+ if (temp !== root && !temp.startsWith(rootPrefix))
735
+ throw new Error("bootstrap temp target escapes rootDir");
736
+ return { root, target, temp };
737
+ }
738
+ export function applyBootstrapWriteIntentsToDurableState(rootDir, intents) {
739
+ if (typeof rootDir !== "string" || rootDir.trim().length === 0)
740
+ return { ...invalid("rootDir is required"), ...disabledDurableBootstrapAuthority };
741
+ const writtenPaths = [];
742
+ try {
743
+ const root = resolve(rootDir);
744
+ const prepared = intents.map((intent) => {
745
+ const validation = validateRedactedBootstrapArtifactWriteIntent(intent);
746
+ if (!validation.ok)
747
+ return { validation, intent };
748
+ return { validation, intent, resolved: resolveBootstrapTarget(root, intent.path) };
749
+ });
750
+ const errors = prepared.flatMap((entry) => entry.validation.ok ? [] : entry.validation.errors);
751
+ if (errors.length > 0)
752
+ return { ...invalid(...errors), writtenPaths, ...disabledDurableBootstrapAuthority };
753
+ for (const { intent, resolved } of prepared) {
754
+ if (resolved === undefined)
755
+ throw new Error("bootstrap durable write prevalidation failed");
756
+ mkdirSync(dirname(resolved.target), { recursive: true });
757
+ writeFileSync(resolved.temp, JSON.stringify(intent.record), "utf8");
758
+ renameSync(resolved.temp, resolved.target);
759
+ writtenPaths.push(intent.path);
760
+ }
761
+ return { ...valid(), rootDir: root, writtenPaths, ...disabledDurableBootstrapAuthority };
762
+ }
763
+ catch (error) {
764
+ return { ...invalid(error instanceof Error ? error.message : "bootstrap durable write failed"), writtenPaths, ...disabledDurableBootstrapAuthority };
765
+ }
766
+ }
767
+ export function redactedBootstrapRef(ref) {
768
+ const result = validateOpaqueRef(ref, "bootstrap ref");
769
+ if (!result.ok)
770
+ throw new Error(result.errors.join("; "));
771
+ return ref;
772
+ }
773
+ //# sourceMappingURL=bootstrap-foundation.js.map