@tomflow/proflow-platform-cli 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 (144) hide show
  1. package/README.md +37 -0
  2. package/conformance.json +1 -0
  3. package/dist/deployment/adapter.d.ts +57 -0
  4. package/dist/deployment/adapter.js +25 -0
  5. package/dist/deployment/descriptor.d.ts +39 -0
  6. package/dist/deployment/descriptor.js +41 -0
  7. package/dist/src/apply/apply.d.ts +24 -0
  8. package/dist/src/apply/apply.js +271 -0
  9. package/dist/src/apply/cleanup.d.ts +12 -0
  10. package/dist/src/apply/cleanup.js +37 -0
  11. package/dist/src/apply/current.d.ts +4 -0
  12. package/dist/src/apply/current.js +73 -0
  13. package/dist/src/apply/driver.d.ts +23 -0
  14. package/dist/src/apply/driver.js +154 -0
  15. package/dist/src/apply/execute.d.ts +36 -0
  16. package/dist/src/apply/execute.js +124 -0
  17. package/dist/src/apply/index.d.ts +8 -0
  18. package/dist/src/apply/index.js +4 -0
  19. package/dist/src/apply/reality.d.ts +20 -0
  20. package/dist/src/apply/reality.js +93 -0
  21. package/dist/src/binding/production-bindings.d.ts +45 -0
  22. package/dist/src/binding/production-bindings.js +102 -0
  23. package/dist/src/cli.d.ts +12 -0
  24. package/dist/src/cli.js +712 -0
  25. package/dist/src/contracts.d.ts +113 -0
  26. package/dist/src/contracts.js +1 -0
  27. package/dist/src/discovery/catalog.d.ts +10 -0
  28. package/dist/src/discovery/catalog.js +76 -0
  29. package/dist/src/discovery/discover.d.ts +19 -0
  30. package/dist/src/discovery/discover.js +124 -0
  31. package/dist/src/discovery/index.d.ts +5 -0
  32. package/dist/src/discovery/index.js +4 -0
  33. package/dist/src/discovery/installed.d.ts +21 -0
  34. package/dist/src/discovery/installed.js +130 -0
  35. package/dist/src/discovery/workspace.d.ts +10 -0
  36. package/dist/src/discovery/workspace.js +155 -0
  37. package/dist/src/docs/docs.d.ts +47 -0
  38. package/dist/src/docs/docs.js +129 -0
  39. package/dist/src/docs/index.d.ts +1 -0
  40. package/dist/src/docs/index.js +1 -0
  41. package/dist/src/doctor/doctor.d.ts +41 -0
  42. package/dist/src/doctor/doctor.js +80 -0
  43. package/dist/src/doctor/index.d.ts +2 -0
  44. package/dist/src/doctor/index.js +1 -0
  45. package/dist/src/errors.d.ts +6 -0
  46. package/dist/src/errors.js +35 -0
  47. package/dist/src/graph/graph.d.ts +23 -0
  48. package/dist/src/graph/graph.js +132 -0
  49. package/dist/src/graph/index.d.ts +2 -0
  50. package/dist/src/graph/index.js +1 -0
  51. package/dist/src/index.d.ts +28 -0
  52. package/dist/src/index.js +18 -0
  53. package/dist/src/install/bootstrap.d.ts +10 -0
  54. package/dist/src/install/bootstrap.js +79 -0
  55. package/dist/src/install/environment.d.ts +29 -0
  56. package/dist/src/install/environment.js +226 -0
  57. package/dist/src/install/index.d.ts +6 -0
  58. package/dist/src/install/index.js +3 -0
  59. package/dist/src/install/install.d.ts +19 -0
  60. package/dist/src/install/install.js +131 -0
  61. package/dist/src/install/package-manager.d.ts +11 -0
  62. package/dist/src/install/package-manager.js +58 -0
  63. package/dist/src/lifecycle/dispatch.d.ts +47 -0
  64. package/dist/src/lifecycle/dispatch.js +142 -0
  65. package/dist/src/lifecycle/index.d.ts +3 -0
  66. package/dist/src/lifecycle/index.js +2 -0
  67. package/dist/src/lifecycle/service-process.d.ts +11 -0
  68. package/dist/src/lifecycle/service-process.js +326 -0
  69. package/dist/src/manifest/index.d.ts +2 -0
  70. package/dist/src/manifest/index.js +1 -0
  71. package/dist/src/manifest/manifest.d.ts +61 -0
  72. package/dist/src/manifest/manifest.js +160 -0
  73. package/dist/src/modules.d.ts +11 -0
  74. package/dist/src/modules.js +55 -0
  75. package/dist/src/observer/deployment-summary.d.ts +31 -0
  76. package/dist/src/observer/deployment-summary.js +34 -0
  77. package/dist/src/paths.d.ts +21 -0
  78. package/dist/src/paths.js +56 -0
  79. package/dist/src/persistence/atomic.d.ts +2 -0
  80. package/dist/src/persistence/atomic.js +16 -0
  81. package/dist/src/persistence/config.d.ts +12 -0
  82. package/dist/src/persistence/config.js +43 -0
  83. package/dist/src/persistence/generated.d.ts +2 -0
  84. package/dist/src/persistence/generated.js +4 -0
  85. package/dist/src/persistence/guards.d.ts +7 -0
  86. package/dist/src/persistence/guards.js +120 -0
  87. package/dist/src/persistence/index.d.ts +6 -0
  88. package/dist/src/persistence/index.js +5 -0
  89. package/dist/src/persistence/plans.d.ts +5 -0
  90. package/dist/src/persistence/plans.js +34 -0
  91. package/dist/src/persistence/state.d.ts +7 -0
  92. package/dist/src/persistence/state.js +34 -0
  93. package/dist/src/persistence/verification.d.ts +5 -0
  94. package/dist/src/persistence/verification.js +47 -0
  95. package/dist/src/planner/assemble.d.ts +16 -0
  96. package/dist/src/planner/assemble.js +107 -0
  97. package/dist/src/planner/check.d.ts +20 -0
  98. package/dist/src/planner/check.js +96 -0
  99. package/dist/src/planner/fingerprint.d.ts +12 -0
  100. package/dist/src/planner/fingerprint.js +164 -0
  101. package/dist/src/planner/index.d.ts +13 -0
  102. package/dist/src/planner/index.js +7 -0
  103. package/dist/src/planner/plan.d.ts +14 -0
  104. package/dist/src/planner/plan.js +156 -0
  105. package/dist/src/planner/repair.d.ts +28 -0
  106. package/dist/src/planner/repair.js +131 -0
  107. package/dist/src/planner/stale.d.ts +7 -0
  108. package/dist/src/planner/stale.js +23 -0
  109. package/dist/src/planner/steps.d.ts +37 -0
  110. package/dist/src/planner/steps.js +165 -0
  111. package/dist/src/planner/target-catalog.d.ts +13 -0
  112. package/dist/src/planner/target-catalog.js +48 -0
  113. package/dist/src/planner/upgrade.d.ts +18 -0
  114. package/dist/src/planner/upgrade.js +105 -0
  115. package/dist/src/preflight/config.d.ts +10 -0
  116. package/dist/src/preflight/config.js +45 -0
  117. package/dist/src/preflight/findings.d.ts +26 -0
  118. package/dist/src/preflight/findings.js +1 -0
  119. package/dist/src/preflight/index.d.ts +7 -0
  120. package/dist/src/preflight/index.js +3 -0
  121. package/dist/src/preflight/preflight.d.ts +8 -0
  122. package/dist/src/preflight/preflight.js +174 -0
  123. package/dist/src/preflight/requirements.d.ts +11 -0
  124. package/dist/src/preflight/requirements.js +224 -0
  125. package/dist/src/ready/index.d.ts +2 -0
  126. package/dist/src/ready/index.js +1 -0
  127. package/dist/src/ready/ready.d.ts +40 -0
  128. package/dist/src/ready/ready.js +201 -0
  129. package/dist/src/registry/index.d.ts +2 -0
  130. package/dist/src/registry/index.js +1 -0
  131. package/dist/src/registry/npm-registry.d.ts +51 -0
  132. package/dist/src/registry/npm-registry.js +341 -0
  133. package/dist/src/security/index.d.ts +3 -0
  134. package/dist/src/security/index.js +2 -0
  135. package/dist/src/security/lock.d.ts +9 -0
  136. package/dist/src/security/lock.js +89 -0
  137. package/dist/src/security/redact.d.ts +6 -0
  138. package/dist/src/security/redact.js +48 -0
  139. package/dist/src/verification/index.d.ts +2 -0
  140. package/dist/src/verification/index.js +1 -0
  141. package/dist/src/verification/verify.d.ts +33 -0
  142. package/dist/src/verification/verify.js +114 -0
  143. package/package.json +49 -0
  144. package/proflow.module.json +45 -0
package/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # platform-cli
2
+
3
+ Domain Owner: deployment-governance
4
+ Module Kind: cli
5
+ Service: none
6
+ Process: none
7
+ Business Fact Owner: none
8
+
9
+ ## Purpose
10
+
11
+ The single platform-level deterministic Deployment planner/executor. It understands only the Module Contract and public lifecycle/verification contracts; it does not import other domains' business internals.
12
+
13
+ For the v1 Custom GPT carrier it may plan/verify Web-only setup, role-scoped Action auth/schema, File Bridge/Code Interpreter/Web Search requirements, `x-openai-isConsequential:false` + Always Allow target configuration, Chrome/runtime prerequisites and current verification freshness. It never creates/guesses Task `workerRef/c-id`, never uses exact ChatGPT model id as READY truth, and never accepts System Observer assessment as Deployment READY truth.
14
+
15
+ ## Commands
16
+
17
+ ```
18
+ platform search [package]
19
+ platform modules [module|package]
20
+ platform docs [module|package] [documentId]
21
+ platform preflight [module]
22
+ platform preflight --intent install
23
+ platform install [package]
24
+ platform uninstall <module|package>
25
+ platform upgrade [module|package]
26
+ platform plan --intent install|configure|upgrade|uninstall|repair [options]
27
+ platform apply <planRef>
28
+ platform start [module]
29
+ platform stop [module]
30
+ platform restart [module]
31
+ platform status [module]
32
+ platform verify [module]
33
+ platform doctor [module]
34
+ platform manifest [module]
35
+ ```
36
+
37
+ `install` / `upgrade` / `uninstall` are AI-friendly high-level commands that reuse the same frozen Plan -> Apply engine. `search` reads the npm Registry installable world; `modules` reads the current Workspace managed world; `docs` mechanically aggregates package-owned Descriptor, npm `bin/exports`, and declared documentation without maintaining a second package-knowledge catalog. Package mutation is real Workspace package-manager mutation and is never satisfied by the historical no-op driver seam.
@@ -0,0 +1 @@
1
+ { "contract": "proflow.conformance.v1", "levels": ["C1", "C2", "C3"] }
@@ -0,0 +1,57 @@
1
+ export declare const behaviorAdapter: {
2
+ describe: () => {
3
+ result: {
4
+ contract: "deployment.result.v1";
5
+ ok: boolean;
6
+ status: "SUCCEEDED";
7
+ moduleRef: "platform-cli";
8
+ moduleVersion: "0.1.0";
9
+ data?: {} | null;
10
+ };
11
+ observedEffects: never[];
12
+ };
13
+ preflight: () => {
14
+ result: {
15
+ contract: "deployment.result.v1";
16
+ ok: boolean;
17
+ status: "SUCCEEDED";
18
+ moduleRef: "platform-cli";
19
+ moduleVersion: "0.1.0";
20
+ data?: {} | null;
21
+ };
22
+ observedEffects: never[];
23
+ };
24
+ status: () => {
25
+ result: {
26
+ contract: "deployment.result.v1";
27
+ ok: boolean;
28
+ status: "SUCCEEDED";
29
+ moduleRef: "platform-cli";
30
+ moduleVersion: "0.1.0";
31
+ data?: {} | null;
32
+ };
33
+ observedEffects: never[];
34
+ };
35
+ verify: () => {
36
+ result: {
37
+ contract: "deployment.result.v1";
38
+ ok: boolean;
39
+ status: "SUCCEEDED";
40
+ moduleRef: "platform-cli";
41
+ moduleVersion: "0.1.0";
42
+ data?: {} | null;
43
+ };
44
+ observedEffects: never[];
45
+ };
46
+ doctor: () => {
47
+ result: {
48
+ contract: "deployment.result.v1";
49
+ ok: boolean;
50
+ status: "SUCCEEDED";
51
+ moduleRef: "platform-cli";
52
+ moduleVersion: "0.1.0";
53
+ data?: {} | null;
54
+ };
55
+ observedEffects: never[];
56
+ };
57
+ };
@@ -0,0 +1,25 @@
1
+ import { descriptor } from "./descriptor.js";
2
+ const success = (data) => ({
3
+ contract: "deployment.result.v1",
4
+ ok: true,
5
+ status: "SUCCEEDED",
6
+ moduleRef: descriptor.moduleRef,
7
+ moduleVersion: descriptor.moduleVersion,
8
+ ...(data === undefined ? {} : { data }),
9
+ });
10
+ export const behaviorAdapter = {
11
+ describe: () => ({
12
+ result: success({ role: "deployment-governance", kind: descriptor.kind }),
13
+ observedEffects: [],
14
+ }),
15
+ preflight: () => ({ result: success(), observedEffects: [] }),
16
+ status: () => ({
17
+ result: success({ runnable: true }),
18
+ observedEffects: [],
19
+ }),
20
+ verify: () => ({
21
+ result: success(),
22
+ observedEffects: [],
23
+ }),
24
+ doctor: () => ({ result: success(), observedEffects: [] }),
25
+ };
@@ -0,0 +1,39 @@
1
+ export declare const descriptor: {
2
+ readonly contract: "module";
3
+ readonly contractVersion: "1.0.0";
4
+ readonly moduleRef: "platform-cli";
5
+ readonly packageName: "@tomflow/proflow-platform-cli";
6
+ readonly moduleVersion: "0.1.0";
7
+ readonly kind: "cli";
8
+ readonly templateVersion: "1.0.0";
9
+ readonly platformCompatibility: ">=1.0.0 <2.0.0";
10
+ readonly installClass: "core";
11
+ readonly identity: {
12
+ readonly domain: "deployment-governance";
13
+ readonly summary: "Deterministic platform-level deployment discovery, planning, lifecycle and verification CLI.";
14
+ };
15
+ readonly provides: readonly [];
16
+ readonly requires: readonly [];
17
+ readonly requirements: readonly [{
18
+ readonly kind: "runtime";
19
+ readonly runtime: "node";
20
+ readonly versionRange: ">=24.19.0";
21
+ }];
22
+ readonly configSlots: readonly [];
23
+ readonly lifecycle: {
24
+ readonly supported: readonly ["describe", "preflight", "status", "verify", "doctor"];
25
+ };
26
+ readonly verification: {
27
+ readonly checks: readonly [{
28
+ readonly id: "cli-surface";
29
+ readonly description: "Deterministic command surface parses and dispatches";
30
+ readonly lifecycle: "verify";
31
+ }];
32
+ };
33
+ readonly effects: readonly [];
34
+ readonly documentation: readonly [{
35
+ readonly id: "overview";
36
+ readonly path: "./README.md";
37
+ readonly description: "Platform CLI package overview and command surface";
38
+ }];
39
+ };
@@ -0,0 +1,41 @@
1
+ export const descriptor = {
2
+ contract: "module",
3
+ contractVersion: "1.0.0",
4
+ moduleRef: "platform-cli",
5
+ packageName: "@tomflow/proflow-platform-cli",
6
+ moduleVersion: "0.1.0",
7
+ kind: "cli",
8
+ templateVersion: "1.0.0",
9
+ platformCompatibility: ">=1.0.0 <2.0.0",
10
+ installClass: "core",
11
+ identity: {
12
+ domain: "deployment-governance",
13
+ summary: "Deterministic platform-level deployment discovery, planning, lifecycle and verification CLI.",
14
+ },
15
+ provides: [],
16
+ requires: [],
17
+ requirements: [
18
+ { kind: "runtime", runtime: "node", versionRange: ">=24.19.0" },
19
+ ],
20
+ configSlots: [],
21
+ lifecycle: {
22
+ supported: ["describe", "preflight", "status", "verify", "doctor"],
23
+ },
24
+ verification: {
25
+ checks: [
26
+ {
27
+ id: "cli-surface",
28
+ description: "Deterministic command surface parses and dispatches",
29
+ lifecycle: "verify",
30
+ },
31
+ ],
32
+ },
33
+ effects: [],
34
+ documentation: [
35
+ {
36
+ id: "overview",
37
+ path: "./README.md",
38
+ description: "Platform CLI package overview and command surface",
39
+ },
40
+ ],
41
+ };
@@ -0,0 +1,24 @@
1
+ import type { ApplyResult } from "../contracts.ts";
2
+ import type { ModuleCatalog } from "../modules.ts";
3
+ import { type WorkspacePaths } from "../paths.ts";
4
+ import { type PlanInput } from "../planner/index.ts";
5
+ import type { PackageManagerDriver } from "./driver.ts";
6
+ import type { RealityObserver } from "./reality.ts";
7
+ export interface ApplyContext {
8
+ paths: WorkspacePaths;
9
+ planRef: string;
10
+ catalog: ModuleCatalog;
11
+ /** Current stable assumptions the plan must still match (staleness gate). */
12
+ current: PlanInput;
13
+ driver: PackageManagerDriver;
14
+ observer?: RealityObserver;
15
+ }
16
+ /**
17
+ * Applies a frozen deployment plan deterministically. Every step is re-checked
18
+ * against current reality before any action: satisfied steps SKIP, human steps
19
+ * and ACTION_REQUIRED mutations persist a structured pendingAction and STOP with
20
+ * ACTION_REQUIRED, a BLOCKED/FAILED mutation STOPs with the matching outcome,
21
+ * and only a fully applied plan is recorded in `lastAppliedPlans`. The exclusive
22
+ * workspace lock is released in all paths.
23
+ */
24
+ export declare function applyPlan(context: ApplyContext): Promise<ApplyResult>;
@@ -0,0 +1,271 @@
1
+ import { PlatformError } from "../errors.js";
2
+ import { ensureLayout } from "../paths.js";
3
+ import { emptyDeploymentState, loadDeploymentState, loadPlan, saveDeploymentState, } from "../persistence/index.js";
4
+ import { checkPlanStale, evaluateStepCheck, } from "../planner/index.js";
5
+ import { acquireWorkspaceLock } from "../security/index.js";
6
+ import { executeStep } from "./execute.js";
7
+ import { createRealityObserver } from "./reality.js";
8
+ function nowIso() {
9
+ return new Date().toISOString();
10
+ }
11
+ function failureMessage(error) {
12
+ return error instanceof Error ? error.message : String(error);
13
+ }
14
+ /**
15
+ * Applies a frozen deployment plan deterministically. Every step is re-checked
16
+ * against current reality before any action: satisfied steps SKIP, human steps
17
+ * and ACTION_REQUIRED mutations persist a structured pendingAction and STOP with
18
+ * ACTION_REQUIRED, a BLOCKED/FAILED mutation STOPs with the matching outcome,
19
+ * and only a fully applied plan is recorded in `lastAppliedPlans`. The exclusive
20
+ * workspace lock is released in all paths.
21
+ */
22
+ export async function applyPlan(context) {
23
+ const driver = context.driver;
24
+ const observer = context.observer ??
25
+ createRealityObserver({
26
+ paths: context.paths,
27
+ catalog: context.catalog,
28
+ driver,
29
+ });
30
+ await ensureLayout(context.paths);
31
+ const lock = await acquireWorkspaceLock(context.paths, context.planRef);
32
+ try {
33
+ const plan = await loadPlan(context.paths, context.planRef);
34
+ if (plan === undefined) {
35
+ throw new PlatformError("PLAN_NOT_FOUND", `plan ${context.planRef} not found`);
36
+ }
37
+ const staleness = checkPlanStale(plan, context.current);
38
+ if (staleness.stale) {
39
+ return {
40
+ planRef: plan.planRef,
41
+ outcome: "BLOCKED",
42
+ stepResults: [],
43
+ completedAt: nowIso(),
44
+ };
45
+ }
46
+ const state = (await loadDeploymentState(context.paths)) ?? emptyDeploymentState();
47
+ const stepResults = [];
48
+ for (const step of plan.steps) {
49
+ const reality = await observer.observe(step, plan);
50
+ if (reality === undefined) {
51
+ // UNKNOWN: current reality could not be observed. Stop without
52
+ // replaying any lifecycle/external effect, then doctor/repair.
53
+ stepResults.push({
54
+ stepRef: step.stepRef,
55
+ moduleRef: step.moduleRef,
56
+ status: "FAILED",
57
+ message: `blocked: current reality for ${step.moduleRef} could not be observed`,
58
+ });
59
+ state.updatedAt = nowIso();
60
+ await saveDeploymentState(context.paths, state);
61
+ return {
62
+ planRef: plan.planRef,
63
+ outcome: "BLOCKED",
64
+ stepResults,
65
+ completedAt: nowIso(),
66
+ };
67
+ }
68
+ const check = evaluateStepCheck(step, plan, reality);
69
+ if (check.status === "SATISFIED") {
70
+ // A previously-pending action for this step can no longer block a
71
+ // resume: the step is satisfied in current reality, so clear it.
72
+ state.pendingActions = state.pendingActions.filter((pending) => !(pending.planRef === plan.planRef &&
73
+ pending.stepRef === step.stepRef));
74
+ stepResults.push({
75
+ stepRef: step.stepRef,
76
+ moduleRef: step.moduleRef,
77
+ status: "SKIP",
78
+ message: check.reason,
79
+ });
80
+ continue;
81
+ }
82
+ if (step.kind === "human") {
83
+ const completedAt = nowIso();
84
+ state.pendingActions.push({
85
+ planRef: plan.planRef,
86
+ stepRef: step.stepRef,
87
+ moduleRef: step.moduleRef,
88
+ action: step.expectedEffect,
89
+ createdAt: completedAt,
90
+ });
91
+ state.updatedAt = completedAt;
92
+ await saveDeploymentState(context.paths, state);
93
+ stepResults.push({
94
+ stepRef: step.stepRef,
95
+ moduleRef: step.moduleRef,
96
+ status: "ACTION_REQUIRED",
97
+ message: check.reason,
98
+ });
99
+ return {
100
+ planRef: plan.planRef,
101
+ outcome: "ACTION_REQUIRED",
102
+ stepResults,
103
+ completedAt,
104
+ };
105
+ }
106
+ let outcome;
107
+ try {
108
+ outcome = await executeStep({ paths: context.paths, catalog: context.catalog, driver }, step, plan);
109
+ }
110
+ catch (error) {
111
+ stepResults.push({
112
+ stepRef: step.stepRef,
113
+ moduleRef: step.moduleRef,
114
+ status: "FAILED",
115
+ message: failureMessage(error),
116
+ });
117
+ state.updatedAt = nowIso();
118
+ await saveDeploymentState(context.paths, state);
119
+ return {
120
+ planRef: plan.planRef,
121
+ outcome: "FAILED",
122
+ stepResults,
123
+ completedAt: nowIso(),
124
+ };
125
+ }
126
+ switch (outcome.kind) {
127
+ case "SUCCEEDED": {
128
+ // Postcondition re-check: only a genuinely successful mutation is
129
+ // confirmed; an effect that cannot be confirmed stops the apply
130
+ // rather than blindly repeating it.
131
+ const postReality = await observer.observe(step, plan);
132
+ if (postReality === undefined) {
133
+ stepResults.push({
134
+ stepRef: step.stepRef,
135
+ moduleRef: step.moduleRef,
136
+ status: "FAILED",
137
+ message: `blocked: postcondition reality for ${step.moduleRef} could not be observed`,
138
+ });
139
+ state.updatedAt = nowIso();
140
+ await saveDeploymentState(context.paths, state);
141
+ return {
142
+ planRef: plan.planRef,
143
+ outcome: "BLOCKED",
144
+ stepResults,
145
+ completedAt: nowIso(),
146
+ };
147
+ }
148
+ const post = evaluateStepCheck(step, plan, postReality);
149
+ if (post.status !== "SATISFIED") {
150
+ stepResults.push({
151
+ stepRef: step.stepRef,
152
+ moduleRef: step.moduleRef,
153
+ status: "FAILED",
154
+ message: `postcondition not satisfied after execution: ${post.reason}`,
155
+ });
156
+ state.updatedAt = nowIso();
157
+ await saveDeploymentState(context.paths, state);
158
+ return {
159
+ planRef: plan.planRef,
160
+ outcome: "FAILED",
161
+ stepResults,
162
+ completedAt: nowIso(),
163
+ };
164
+ }
165
+ stepResults.push({
166
+ stepRef: step.stepRef,
167
+ moduleRef: step.moduleRef,
168
+ status: "EXECUTED",
169
+ message: post.reason,
170
+ });
171
+ break;
172
+ }
173
+ case "ACTION_REQUIRED": {
174
+ const completedAt = nowIso();
175
+ state.pendingActions.push({
176
+ planRef: plan.planRef,
177
+ stepRef: step.stepRef,
178
+ moduleRef: step.moduleRef,
179
+ action: outcome.actionRequired.action,
180
+ description: outcome.actionRequired.description,
181
+ createdAt: completedAt,
182
+ });
183
+ state.updatedAt = completedAt;
184
+ await saveDeploymentState(context.paths, state);
185
+ stepResults.push({
186
+ stepRef: step.stepRef,
187
+ moduleRef: step.moduleRef,
188
+ status: "ACTION_REQUIRED",
189
+ message: outcome.actionRequired.action,
190
+ });
191
+ return {
192
+ planRef: plan.planRef,
193
+ outcome: "ACTION_REQUIRED",
194
+ stepResults,
195
+ completedAt,
196
+ };
197
+ }
198
+ case "BLOCKED": {
199
+ stepResults.push({
200
+ stepRef: step.stepRef,
201
+ moduleRef: step.moduleRef,
202
+ status: "FAILED",
203
+ message: `blocked: ${outcome.reason}`,
204
+ });
205
+ state.updatedAt = nowIso();
206
+ await saveDeploymentState(context.paths, state);
207
+ return {
208
+ planRef: plan.planRef,
209
+ outcome: "BLOCKED",
210
+ stepResults,
211
+ completedAt: nowIso(),
212
+ };
213
+ }
214
+ case "FAILED": {
215
+ stepResults.push({
216
+ stepRef: step.stepRef,
217
+ moduleRef: step.moduleRef,
218
+ status: "FAILED",
219
+ message: outcome.reason,
220
+ });
221
+ state.updatedAt = nowIso();
222
+ await saveDeploymentState(context.paths, state);
223
+ return {
224
+ planRef: plan.planRef,
225
+ outcome: "FAILED",
226
+ stepResults,
227
+ completedAt: nowIso(),
228
+ };
229
+ }
230
+ }
231
+ }
232
+ const completedAt = nowIso();
233
+ if (plan.intent === "uninstall") {
234
+ const removed = new Set(plan.resolvedModules.map((module) => module.moduleRef));
235
+ state.selectedModules = state.selectedModules.filter((module) => !removed.has(module.moduleRef));
236
+ }
237
+ else if ((plan.intent === "install" || plan.intent === "upgrade") &&
238
+ plan.resolvedModules.every((module) => module.source.type === "registry")) {
239
+ const byRef = new Map(state.selectedModules.map((module) => [module.moduleRef, module]));
240
+ for (const module of plan.resolvedModules) {
241
+ byRef.set(module.moduleRef, {
242
+ moduleRef: module.moduleRef,
243
+ moduleVersion: module.moduleVersion,
244
+ });
245
+ }
246
+ state.selectedModules = [...byRef.values()].sort((left, right) => left.moduleRef.localeCompare(right.moduleRef));
247
+ }
248
+ else {
249
+ state.selectedModules = plan.resolvedModules.map((module) => ({
250
+ moduleRef: module.moduleRef,
251
+ moduleVersion: module.moduleVersion,
252
+ }));
253
+ }
254
+ state.lastAppliedPlans.push({
255
+ planRef: plan.planRef,
256
+ intent: plan.intent,
257
+ appliedAt: completedAt,
258
+ });
259
+ state.updatedAt = completedAt;
260
+ await saveDeploymentState(context.paths, state);
261
+ return {
262
+ planRef: plan.planRef,
263
+ outcome: "COMPLETE",
264
+ stepResults,
265
+ completedAt,
266
+ };
267
+ }
268
+ finally {
269
+ await lock.release();
270
+ }
271
+ }
@@ -0,0 +1,12 @@
1
+ import type { DeploymentEffect } from "@tomflow/proflow-module-contract";
2
+ /**
3
+ * Removes only package-declared ephemeral filesystem effects under the
4
+ * Workspace-owned `.proflow` state tree. Persistent and explicit-purge effects
5
+ * are never deleted by ordinary uninstall. Paths outside `.proflow` are left to
6
+ * the package-owned uninstall adapter so a descriptor can never turn the
7
+ * platform package manager into an arbitrary recursive-delete primitive.
8
+ */
9
+ export declare function cleanupRemovableFilesystemEffects(input: {
10
+ workspaceRoot: string;
11
+ effects: readonly DeploymentEffect[];
12
+ }): Promise<string[]>;
@@ -0,0 +1,37 @@
1
+ import { rm } from "node:fs/promises";
2
+ import { isAbsolute, relative, resolve } from "node:path";
3
+ import { PlatformError } from "../errors.js";
4
+ /**
5
+ * Removes only package-declared ephemeral filesystem effects under the
6
+ * Workspace-owned `.proflow` state tree. Persistent and explicit-purge effects
7
+ * are never deleted by ordinary uninstall. Paths outside `.proflow` are left to
8
+ * the package-owned uninstall adapter so a descriptor can never turn the
9
+ * platform package manager into an arbitrary recursive-delete primitive.
10
+ */
11
+ export async function cleanupRemovableFilesystemEffects(input) {
12
+ const workspaceRoot = resolve(input.workspaceRoot);
13
+ const proflowRoot = resolve(workspaceRoot, ".proflow");
14
+ const removed = [];
15
+ for (const effect of input.effects) {
16
+ if (effect.kind !== "filesystem" ||
17
+ effect.retention !== "remove" ||
18
+ effect.path === undefined) {
19
+ continue;
20
+ }
21
+ if (isAbsolute(effect.path)) {
22
+ throw new PlatformError("UNINSTALL_FAILED", `removable filesystem effect must use a Workspace-relative .proflow path: ${effect.path}`);
23
+ }
24
+ const target = resolve(workspaceRoot, effect.path);
25
+ const withinProflow = relative(proflowRoot, target);
26
+ if (target === proflowRoot ||
27
+ withinProflow === "" ||
28
+ withinProflow === ".." ||
29
+ withinProflow.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) ||
30
+ isAbsolute(withinProflow)) {
31
+ throw new PlatformError("UNINSTALL_FAILED", `refusing unsafe removable filesystem effect outside a .proflow child path: ${effect.path}`);
32
+ }
33
+ await rm(target, { recursive: true, force: true });
34
+ removed.push(effect.path);
35
+ }
36
+ return removed;
37
+ }
@@ -0,0 +1,4 @@
1
+ import type { DeploymentPlan } from "../contracts.ts";
2
+ import type { ModuleCatalog } from "../modules.ts";
3
+ import type { PlanInput } from "../planner/plan.ts";
4
+ export declare function rebuildCurrentAssumptions(catalog: ModuleCatalog, plan: DeploymentPlan): Promise<PlanInput>;
@@ -0,0 +1,73 @@
1
+ import { parseModuleDescriptor, } from "@tomflow/proflow-module-contract";
2
+ import { discoverModules } from "../discovery/index.js";
3
+ import { doctorModules } from "../doctor/index.js";
4
+ import { diagnoseRepair } from "../planner/repair.js";
5
+ function moduleSourceOf(module) {
6
+ if (module.source.type === "registry") {
7
+ throw new Error(`registry bootstrap target ${module.packageName} has no local descriptor`);
8
+ }
9
+ const source = {
10
+ type: module.source.type,
11
+ packageName: module.packageName,
12
+ };
13
+ if (module.source.path !== undefined)
14
+ source.path = module.source.path;
15
+ return source;
16
+ }
17
+ async function loadDescriptors(catalog, modules) {
18
+ const descriptors = [];
19
+ for (const module of modules) {
20
+ const raw = await catalog.loadDescriptor(moduleSourceOf(module));
21
+ descriptors.push(parseModuleDescriptor(raw));
22
+ }
23
+ return descriptors;
24
+ }
25
+ // Rebuilds a plan's stable current assumptions from the CURRENT module catalog
26
+ // and current reality. The old plan's resolvedModules are never reused: modules
27
+ // are re-discovered and re-parsed so descriptor drift reaches the staleness
28
+ // gate. Upgrade additionally re-discovers current descriptors and reuses the
29
+ // plan's frozen target descriptors; repair re-runs doctor for fresh facts.
30
+ export async function rebuildCurrentAssumptions(catalog, plan) {
31
+ if ((plan.intent === "install" || plan.intent === "upgrade") &&
32
+ plan.resolvedModules.every((module) => module.source.type === "registry")) {
33
+ return {
34
+ intent: plan.intent,
35
+ modules: plan.resolvedModules,
36
+ targets: plan.moduleTargets,
37
+ };
38
+ }
39
+ const discovered = await discoverModules({ catalog });
40
+ const byRef = new Map(discovered.map((module) => [module.moduleRef, module]));
41
+ const modules = [];
42
+ for (const target of plan.moduleTargets) {
43
+ const module = byRef.get(target.moduleRef);
44
+ if (module !== undefined)
45
+ modules.push(module);
46
+ }
47
+ if (plan.intent === "upgrade") {
48
+ const currentDescriptors = await loadDescriptors(catalog, modules);
49
+ if (plan.targetDescriptors === undefined)
50
+ return { intent: plan.intent, modules, targets: plan.moduleTargets };
51
+ return {
52
+ intent: plan.intent,
53
+ modules,
54
+ targets: plan.moduleTargets,
55
+ currentDescriptors,
56
+ targetDescriptors: plan.targetDescriptors,
57
+ };
58
+ }
59
+ if (plan.intent === "repair") {
60
+ const diagnosis = diagnoseRepair(await doctorModules(catalog, modules));
61
+ return {
62
+ intent: plan.intent,
63
+ modules,
64
+ targets: plan.moduleTargets,
65
+ facts: diagnosis.facts,
66
+ };
67
+ }
68
+ return {
69
+ intent: plan.intent,
70
+ modules,
71
+ targets: plan.moduleTargets,
72
+ };
73
+ }
@@ -0,0 +1,23 @@
1
+ import type { ResolvedModule } from "../contracts.ts";
2
+ import { type PackageCommandRunner } from "../install/package-manager.ts";
3
+ /**
4
+ * Package-level installation seam. Apply never invokes an arbitrary shell: a
5
+ * package step is satisfied or mutated only through this injected driver. The
6
+ * production driver executes a fixed package-manager binary with argv arrays.
7
+ */
8
+ export interface PackageManagerDriver {
9
+ observeInstalledVersion(module: ResolvedModule): Promise<string | undefined>;
10
+ install(module: ResolvedModule): Promise<void>;
11
+ upgrade(module: ResolvedModule): Promise<void>;
12
+ remove(module: ResolvedModule): Promise<void>;
13
+ }
14
+ /**
15
+ * Monorepo/test seam retained for source-resident planning. Production CLI must
16
+ * inject createWorkspacePackageManagerDriver(); it must never rely on this
17
+ * no-op driver to claim a product Workspace package mutation succeeded.
18
+ */
19
+ export declare function workspaceResidentDriver(): PackageManagerDriver;
20
+ export declare function createWorkspacePackageManagerDriver(options: {
21
+ workspaceRoot: string;
22
+ runner?: PackageCommandRunner;
23
+ }): PackageManagerDriver;