@tomflow/proflow-platform-cli 0.1.14 → 0.1.16

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 (142) hide show
  1. package/README.md +48 -43
  2. package/dist/deployment/adapter.d.ts +5 -5
  3. package/dist/deployment/adapter.js +2 -1
  4. package/dist/deployment/descriptor.d.ts +2 -3
  5. package/dist/deployment/descriptor.js +2 -3
  6. package/dist/src/binding/production-bindings.d.ts +17 -15
  7. package/dist/src/binding/production-bindings.js +18 -59
  8. package/dist/src/cli.d.ts +7 -9
  9. package/dist/src/cli.js +327 -1316
  10. package/dist/src/contracts.d.ts +4 -97
  11. package/dist/src/discovery/discover.js +0 -1
  12. package/dist/src/docs/aggregate.d.ts +18 -0
  13. package/dist/src/docs/aggregate.js +44 -0
  14. package/dist/src/docs/docs.d.ts +0 -31
  15. package/dist/src/docs/docs.js +0 -61
  16. package/dist/src/docs/index.d.ts +2 -1
  17. package/dist/src/docs/index.js +1 -1
  18. package/dist/src/errors.d.ts +1 -1
  19. package/dist/src/errors.js +0 -18
  20. package/dist/src/graph/graph.d.ts +1 -4
  21. package/dist/src/graph/graph.js +1 -13
  22. package/dist/src/graph/index.d.ts +2 -2
  23. package/dist/src/graph/index.js +1 -1
  24. package/dist/src/index.d.ts +4 -24
  25. package/dist/src/index.js +2 -16
  26. package/dist/src/install/package-manager.d.ts +21 -0
  27. package/dist/src/install/package-manager.js +153 -0
  28. package/dist/src/lifecycle/dispatch.d.ts +0 -31
  29. package/dist/src/lifecycle/dispatch.js +0 -125
  30. package/dist/src/lifecycle/index.d.ts +4 -3
  31. package/dist/src/lifecycle/index.js +2 -2
  32. package/dist/src/lifecycle/thin.d.ts +11 -0
  33. package/dist/src/lifecycle/thin.js +68 -0
  34. package/dist/src/paths.d.ts +0 -14
  35. package/dist/src/paths.js +0 -39
  36. package/dist/src/persistence/config.d.ts +0 -6
  37. package/dist/src/persistence/config.js +0 -14
  38. package/dist/src/persistence/guards.d.ts +0 -6
  39. package/dist/src/persistence/guards.js +0 -114
  40. package/dist/src/persistence/index.d.ts +4 -6
  41. package/dist/src/persistence/index.js +2 -5
  42. package/dist/src/persistence/workspace-metadata.d.ts +7 -0
  43. package/dist/src/persistence/workspace-metadata.js +39 -0
  44. package/dist/src/registry/index.d.ts +1 -1
  45. package/dist/src/registry/index.js +1 -1
  46. package/dist/src/registry/npm-registry.d.ts +0 -14
  47. package/dist/src/registry/npm-registry.js +1 -132
  48. package/package.json +5 -9
  49. package/proflow.module.json +2 -3
  50. package/CHANGELOG.md +0 -85
  51. package/dist/src/apply/apply.d.ts +0 -26
  52. package/dist/src/apply/apply.js +0 -314
  53. package/dist/src/apply/cleanup.d.ts +0 -12
  54. package/dist/src/apply/cleanup.js +0 -37
  55. package/dist/src/apply/current.d.ts +0 -5
  56. package/dist/src/apply/current.js +0 -95
  57. package/dist/src/apply/driver.d.ts +0 -28
  58. package/dist/src/apply/driver.js +0 -239
  59. package/dist/src/apply/execute.d.ts +0 -36
  60. package/dist/src/apply/execute.js +0 -152
  61. package/dist/src/apply/index.d.ts +0 -8
  62. package/dist/src/apply/index.js +0 -4
  63. package/dist/src/apply/reality.d.ts +0 -20
  64. package/dist/src/apply/reality.js +0 -105
  65. package/dist/src/binding/global-binding.d.ts +0 -69
  66. package/dist/src/binding/global-binding.js +0 -349
  67. package/dist/src/doctor/doctor.d.ts +0 -41
  68. package/dist/src/doctor/doctor.js +0 -80
  69. package/dist/src/doctor/index.d.ts +0 -2
  70. package/dist/src/doctor/index.js +0 -1
  71. package/dist/src/install/bootstrap.d.ts +0 -10
  72. package/dist/src/install/bootstrap.js +0 -79
  73. package/dist/src/install/environment.d.ts +0 -30
  74. package/dist/src/install/environment.js +0 -230
  75. package/dist/src/install/index.d.ts +0 -6
  76. package/dist/src/install/index.js +0 -3
  77. package/dist/src/install/install.d.ts +0 -19
  78. package/dist/src/install/install.js +0 -172
  79. package/dist/src/lifecycle/service-process.d.ts +0 -11
  80. package/dist/src/lifecycle/service-process.js +0 -356
  81. package/dist/src/manifest/index.d.ts +0 -2
  82. package/dist/src/manifest/index.js +0 -1
  83. package/dist/src/manifest/manifest.d.ts +0 -61
  84. package/dist/src/manifest/manifest.js +0 -167
  85. package/dist/src/observer/deployment-summary.d.ts +0 -31
  86. package/dist/src/observer/deployment-summary.js +0 -34
  87. package/dist/src/persistence/atomic.d.ts +0 -2
  88. package/dist/src/persistence/atomic.js +0 -16
  89. package/dist/src/persistence/effective-config.d.ts +0 -3
  90. package/dist/src/persistence/effective-config.js +0 -20
  91. package/dist/src/persistence/generated.d.ts +0 -2
  92. package/dist/src/persistence/generated.js +0 -4
  93. package/dist/src/persistence/plans.d.ts +0 -5
  94. package/dist/src/persistence/plans.js +0 -34
  95. package/dist/src/persistence/state.d.ts +0 -7
  96. package/dist/src/persistence/state.js +0 -34
  97. package/dist/src/persistence/verification.d.ts +0 -5
  98. package/dist/src/persistence/verification.js +0 -47
  99. package/dist/src/planner/assemble.d.ts +0 -16
  100. package/dist/src/planner/assemble.js +0 -107
  101. package/dist/src/planner/check.d.ts +0 -20
  102. package/dist/src/planner/check.js +0 -107
  103. package/dist/src/planner/fingerprint.d.ts +0 -12
  104. package/dist/src/planner/fingerprint.js +0 -164
  105. package/dist/src/planner/index.d.ts +0 -13
  106. package/dist/src/planner/index.js +0 -7
  107. package/dist/src/planner/plan.d.ts +0 -15
  108. package/dist/src/planner/plan.js +0 -144
  109. package/dist/src/planner/repair.d.ts +0 -28
  110. package/dist/src/planner/repair.js +0 -131
  111. package/dist/src/planner/stale.d.ts +0 -7
  112. package/dist/src/planner/stale.js +0 -23
  113. package/dist/src/planner/steps.d.ts +0 -37
  114. package/dist/src/planner/steps.js +0 -165
  115. package/dist/src/planner/target-catalog.d.ts +0 -13
  116. package/dist/src/planner/target-catalog.js +0 -48
  117. package/dist/src/planner/upgrade.d.ts +0 -18
  118. package/dist/src/planner/upgrade.js +0 -114
  119. package/dist/src/preflight/config.d.ts +0 -11
  120. package/dist/src/preflight/config.js +0 -88
  121. package/dist/src/preflight/findings.d.ts +0 -26
  122. package/dist/src/preflight/findings.js +0 -1
  123. package/dist/src/preflight/index.d.ts +0 -7
  124. package/dist/src/preflight/index.js +0 -3
  125. package/dist/src/preflight/preflight.d.ts +0 -10
  126. package/dist/src/preflight/preflight.js +0 -190
  127. package/dist/src/preflight/requirements.d.ts +0 -11
  128. package/dist/src/preflight/requirements.js +0 -231
  129. package/dist/src/ready/index.d.ts +0 -2
  130. package/dist/src/ready/index.js +0 -1
  131. package/dist/src/ready/ready.d.ts +0 -40
  132. package/dist/src/ready/ready.js +0 -201
  133. package/dist/src/security/index.d.ts +0 -3
  134. package/dist/src/security/index.js +0 -2
  135. package/dist/src/security/lock.d.ts +0 -9
  136. package/dist/src/security/lock.js +0 -127
  137. package/dist/src/security/redact.d.ts +0 -6
  138. package/dist/src/security/redact.js +0 -48
  139. package/dist/src/verification/index.d.ts +0 -2
  140. package/dist/src/verification/index.js +0 -1
  141. package/dist/src/verification/verify.d.ts +0 -33
  142. package/dist/src/verification/verify.js +0 -114
@@ -1,231 +0,0 @@
1
- import { accessSync, constants } from "node:fs";
2
- import { connect } from "node:net";
3
- import { join } from "node:path";
4
- import { versionSatisfies } from "../modules.js";
5
- function compareRef(a, b) {
6
- return a < b ? -1 : a > b ? 1 : 0;
7
- }
8
- export async function probeAllRequirements(modules, humanVerifiedModuleRefs = new Set()) {
9
- const sorted = [...modules].sort((a, b) => compareRef(a.moduleRef, b.moduleRef));
10
- const results = [];
11
- for (const module of sorted) {
12
- for (const requirement of module.requirements) {
13
- results.push(await probeRequirement(module.moduleRef, requirement, modules, humanVerifiedModuleRefs));
14
- }
15
- }
16
- return results;
17
- }
18
- export async function probeRequirement(moduleRef, requirement, modules, humanVerifiedModuleRefs = new Set()) {
19
- switch (requirement.kind) {
20
- case "runtime":
21
- return probeRuntime(moduleRef, requirement);
22
- case "executable":
23
- return probeExecutable(moduleRef, requirement);
24
- case "filesystem":
25
- return probeFilesystem(moduleRef, requirement);
26
- case "port":
27
- return probePort(moduleRef, requirement);
28
- case "network":
29
- return probeNetwork(moduleRef, requirement);
30
- case "module-contract":
31
- return probeModuleContract(moduleRef, requirement, modules);
32
- case "human":
33
- return humanVerifiedModuleRefs.has(moduleRef)
34
- ? {
35
- moduleRef,
36
- requirement,
37
- status: "PASS",
38
- message: `human prerequisite verified for ${moduleRef}`,
39
- }
40
- : {
41
- moduleRef,
42
- requirement,
43
- status: "ACTION_REQUIRED",
44
- message: requirement.action,
45
- };
46
- }
47
- }
48
- function probeRuntime(moduleRef, requirement) {
49
- if (requirement.runtime !== "node") {
50
- return {
51
- moduleRef,
52
- requirement,
53
- status: "PASS",
54
- message: `${requirement.runtime} runtime availability is not probed by platform-cli`,
55
- };
56
- }
57
- const version = process.version.startsWith("v")
58
- ? process.version.slice(1)
59
- : process.version;
60
- const satisfied = versionSatisfies(version, requirement.versionRange);
61
- return satisfied
62
- ? {
63
- moduleRef,
64
- requirement,
65
- status: "PASS",
66
- message: `node ${version} satisfies ${requirement.versionRange}`,
67
- }
68
- : {
69
- moduleRef,
70
- requirement,
71
- status: "FAIL",
72
- message: `node ${version} does not satisfy ${requirement.versionRange}`,
73
- };
74
- }
75
- function probeExecutable(moduleRef, requirement) {
76
- const found = findOnPath(requirement.command);
77
- return found
78
- ? {
79
- moduleRef,
80
- requirement,
81
- status: "PASS",
82
- message: `executable ${requirement.command} found on PATH`,
83
- }
84
- : {
85
- moduleRef,
86
- requirement,
87
- status: "FAIL",
88
- message: `executable ${requirement.command} not found on PATH`,
89
- };
90
- }
91
- // Bounded by construction: a synchronous scan of the finite PATH directory
92
- // list. It spawns no subprocess, so it cannot hang.
93
- function findOnPath(command) {
94
- const path = process.env.PATH ?? "";
95
- const directories = path.split(":").filter((entry) => entry !== "");
96
- for (const directory of directories) {
97
- const candidate = join(directory, command);
98
- try {
99
- accessSync(candidate, constants.X_OK);
100
- return true;
101
- }
102
- catch {
103
- // keep searching
104
- }
105
- }
106
- return false;
107
- }
108
- function probeFilesystem(moduleRef, requirement) {
109
- const mode = requirement.access === "read"
110
- ? constants.R_OK
111
- : requirement.access === "write"
112
- ? constants.W_OK
113
- : constants.R_OK | constants.W_OK;
114
- try {
115
- accessSync(requirement.path, mode);
116
- return {
117
- moduleRef,
118
- requirement,
119
- status: "PASS",
120
- message: `path ${requirement.path} is ${requirement.access} accessible`,
121
- };
122
- }
123
- catch {
124
- return {
125
- moduleRef,
126
- requirement,
127
- status: "FAIL",
128
- message: `path ${requirement.path} is not ${requirement.access} accessible`,
129
- };
130
- }
131
- }
132
- async function probePort(moduleRef, requirement) {
133
- if (requirement.protocol === "udp") {
134
- return {
135
- moduleRef,
136
- requirement,
137
- status: "PASS",
138
- message: `udp port ${requirement.port} is not probed by platform-cli`,
139
- };
140
- }
141
- return new Promise((resolve) => {
142
- let settled = false;
143
- const socket = connect({ host: "127.0.0.1", port: requirement.port });
144
- const finish = (result) => {
145
- if (settled)
146
- return;
147
- settled = true;
148
- socket.destroy();
149
- resolve(result);
150
- };
151
- const timer = setTimeout(() => {
152
- finish({
153
- moduleRef,
154
- requirement,
155
- status: "FAIL",
156
- message: `port ${requirement.port} probe timed out`,
157
- });
158
- }, 2_000);
159
- socket.once("connect", () => {
160
- clearTimeout(timer);
161
- finish({
162
- moduleRef,
163
- requirement,
164
- status: "FAIL",
165
- message: `port ${requirement.port} is already in use`,
166
- });
167
- });
168
- socket.once("error", () => {
169
- clearTimeout(timer);
170
- finish({
171
- moduleRef,
172
- requirement,
173
- status: "PASS",
174
- message: `port ${requirement.port} is available`,
175
- });
176
- });
177
- });
178
- }
179
- async function probeNetwork(moduleRef, requirement) {
180
- try {
181
- const response = await fetch(requirement.url, {
182
- method: "GET",
183
- signal: AbortSignal.timeout(3_000),
184
- });
185
- return {
186
- moduleRef,
187
- requirement,
188
- status: "PASS",
189
- message: `network ${requirement.url} reachable (HTTP ${response.status})`,
190
- };
191
- }
192
- catch {
193
- return {
194
- moduleRef,
195
- requirement,
196
- status: "FAIL",
197
- message: `network ${requirement.url} unreachable`,
198
- };
199
- }
200
- }
201
- function probeModuleContract(moduleRef, requirement, modules) {
202
- const providers = modules.filter((module) => module.provides.some((provide) => provide.contractRef === requirement.contractRef));
203
- const matching = providers.filter((module) => module.provides.some((provide) => provide.contractRef === requirement.contractRef &&
204
- versionSatisfies(provide.version, requirement.versionRange)));
205
- if (matching.length > 0) {
206
- const refs = matching
207
- .map((module) => module.moduleRef)
208
- .sort(compareRef)
209
- .join(", ");
210
- return {
211
- moduleRef,
212
- requirement,
213
- status: "PASS",
214
- message: `module-contract ${requirement.contractRef} satisfied by ${refs}`,
215
- };
216
- }
217
- if (providers.length > 0) {
218
- return {
219
- moduleRef,
220
- requirement,
221
- status: "FAIL",
222
- message: `module-contract ${requirement.contractRef} providers incompatible with ${requirement.versionRange}`,
223
- };
224
- }
225
- return {
226
- moduleRef,
227
- requirement,
228
- status: "FAIL",
229
- message: `module-contract ${requirement.contractRef} has no provider`,
230
- };
231
- }
@@ -1,2 +0,0 @@
1
- export type { BlockingAction, PlatformReadyInput, PlatformReadyResult, ReadyFinding, ReadyFindingCode, ReadyFindingSeverity, ResourceReality, } from "./ready.ts";
2
- export { assessPlatformReady } from "./ready.ts";
@@ -1 +0,0 @@
1
- export { assessPlatformReady } from "./ready.js";
@@ -1,40 +0,0 @@
1
- import type { PlatformState, ResolvedModule, VerificationRecord } from "../contracts.ts";
2
- import type { LifecycleRunResult } from "../lifecycle/index.ts";
3
- export type ReadyFindingSeverity = "blocking" | "degraded";
4
- export type ReadyFindingCode = "DEPENDENCY_UNRESOLVED" | "DEPENDENCY_INCOMPATIBLE" | "DEPENDENCY_CYCLE" | "MODULE_REF_UNRESOLVED" | "CONFIG_MISSING" | "RUNTIME_STATUS_UNAVAILABLE" | "RUNTIME_NOT_READY" | "RUNTIME_ACTION_REQUIRED" | "RUNTIME_CHECK_FAIL" | "RUNTIME_CHECK_WARN" | "VERIFICATION_MISSING" | "VERIFICATION_STALE" | "VERIFICATION_FAIL" | "BLOCKING_ACTION";
5
- export interface ReadyFinding {
6
- severity: ReadyFindingSeverity;
7
- code: ReadyFindingCode;
8
- moduleRef?: string;
9
- message: string;
10
- }
11
- export interface BlockingAction {
12
- moduleRef?: string;
13
- action: string;
14
- description?: string;
15
- }
16
- export interface ResourceReality {
17
- moduleRef: string;
18
- resourceIdentity?: string;
19
- resourceVersion?: string;
20
- }
21
- export interface PlatformReadyInput {
22
- modules: readonly ResolvedModule[];
23
- status: readonly LifecycleRunResult[];
24
- verification: readonly VerificationRecord[];
25
- config?: Record<string, Record<string, string>>;
26
- blockingActions?: readonly BlockingAction[];
27
- resources?: readonly ResourceReality[];
28
- }
29
- export interface PlatformReadyResult {
30
- state: PlatformState;
31
- findings: ReadyFinding[];
32
- }
33
- /**
34
- * Aggregates current reality into a single PlatformState. READY is derived only
35
- * from live runtime status, current-version verification records, resolved
36
- * dependencies, required config, and the absence of blocking human actions.
37
- * Persisted PASS history, a bare SUCCEEDED operation, or a schema-valid but
38
- * non-success structured result can never fabricate READY on their own.
39
- */
40
- export declare function assessPlatformReady(input: PlatformReadyInput): PlatformReadyResult;
@@ -1,201 +0,0 @@
1
- import { PlatformError } from "../errors.js";
2
- import { buildDependencyGraph, ModuleRefUnresolvedError, } from "../graph/index.js";
3
- import { checkConfigReadiness } from "../preflight/index.js";
4
- function compareRef(a, b) {
5
- return a < b ? -1 : a > b ? 1 : 0;
6
- }
7
- function toDependencyCode(code) {
8
- switch (code) {
9
- case "DEPENDENCY_UNRESOLVED":
10
- case "DEPENDENCY_INCOMPATIBLE":
11
- case "DEPENDENCY_CYCLE":
12
- return code;
13
- default:
14
- return undefined;
15
- }
16
- }
17
- function isHumanAction(code) {
18
- return code === "RUNTIME_ACTION_REQUIRED" || code === "BLOCKING_ACTION";
19
- }
20
- function deriveState(findings) {
21
- let hardFailure = false;
22
- let humanAction = false;
23
- let degraded = false;
24
- for (const finding of findings) {
25
- if (finding.severity === "degraded") {
26
- degraded = true;
27
- continue;
28
- }
29
- if (isHumanAction(finding.code))
30
- humanAction = true;
31
- else
32
- hardFailure = true;
33
- }
34
- if (hardFailure)
35
- return "NOT_READY";
36
- if (humanAction)
37
- return "ACTION_REQUIRED";
38
- if (degraded)
39
- return "DEGRADED";
40
- return "READY";
41
- }
42
- /**
43
- * Aggregates current reality into a single PlatformState. READY is derived only
44
- * from live runtime status, current-version verification records, resolved
45
- * dependencies, required config, and the absence of blocking human actions.
46
- * Persisted PASS history, a bare SUCCEEDED operation, or a schema-valid but
47
- * non-success structured result can never fabricate READY on their own.
48
- */
49
- export function assessPlatformReady(input) {
50
- const findings = [];
51
- const modules = [...input.modules].sort((a, b) => compareRef(a.moduleRef, b.moduleRef));
52
- // logical dependencies, cross-module compatibility, and moduleRef bindings
53
- try {
54
- buildDependencyGraph(modules, input.config === undefined ? {} : { config: input.config });
55
- }
56
- catch (error) {
57
- if (error instanceof ModuleRefUnresolvedError) {
58
- findings.push({
59
- severity: "blocking",
60
- code: "MODULE_REF_UNRESOLVED",
61
- moduleRef: error.from,
62
- message: error.message,
63
- });
64
- }
65
- else if (error instanceof PlatformError) {
66
- const code = toDependencyCode(error.code);
67
- if (code === undefined)
68
- throw error;
69
- findings.push({ severity: "blocking", code, message: error.message });
70
- }
71
- else {
72
- throw error;
73
- }
74
- }
75
- // required config ready
76
- for (const finding of checkConfigReadiness(modules, input.config)) {
77
- findings.push({
78
- severity: "blocking",
79
- code: "CONFIG_MISSING",
80
- ...(finding.moduleRef !== undefined
81
- ? { moduleRef: finding.moduleRef }
82
- : {}),
83
- message: finding.message,
84
- });
85
- }
86
- // required runtimes current READY (live status, never persisted)
87
- const statusByRef = new Map(input.status.map((run) => [run.moduleRef, run]));
88
- for (const module of modules) {
89
- if (!module.lifecycle.includes("status"))
90
- continue;
91
- const run = statusByRef.get(module.moduleRef);
92
- if (run === undefined ||
93
- run.status !== "EXECUTED" ||
94
- run.result === undefined) {
95
- findings.push({
96
- severity: "blocking",
97
- code: "RUNTIME_STATUS_UNAVAILABLE",
98
- moduleRef: module.moduleRef,
99
- message: `no live status observed for ${module.moduleRef}`,
100
- });
101
- continue;
102
- }
103
- const result = run.result;
104
- if (result.status === "ACTION_REQUIRED") {
105
- const action = result.actionRequired;
106
- findings.push({
107
- severity: "blocking",
108
- code: "RUNTIME_ACTION_REQUIRED",
109
- moduleRef: module.moduleRef,
110
- message: action !== undefined
111
- ? `${module.moduleRef} requires action "${action.action}"`
112
- : `${module.moduleRef} requires an unspecified human action`,
113
- });
114
- }
115
- else if (result.status !== "SUCCEEDED") {
116
- findings.push({
117
- severity: "blocking",
118
- code: "RUNTIME_NOT_READY",
119
- moduleRef: module.moduleRef,
120
- message: `runtime ${module.moduleRef} is ${result.status.toLowerCase()}`,
121
- });
122
- }
123
- for (const runtimeCheck of result.checks ?? []) {
124
- if (runtimeCheck.status === "FAIL") {
125
- findings.push({
126
- severity: "blocking",
127
- code: "RUNTIME_CHECK_FAIL",
128
- moduleRef: module.moduleRef,
129
- message: `runtime check ${runtimeCheck.id} failed for ${module.moduleRef}`,
130
- });
131
- }
132
- else if (runtimeCheck.status === "WARN") {
133
- findings.push({
134
- severity: "degraded",
135
- code: "RUNTIME_CHECK_WARN",
136
- moduleRef: module.moduleRef,
137
- message: `runtime check ${runtimeCheck.id} warns for ${module.moduleRef}`,
138
- });
139
- }
140
- }
141
- }
142
- // current-version verification PASS against current resource reality
143
- const recordsByRef = new Map();
144
- for (const record of input.verification) {
145
- const list = recordsByRef.get(record.moduleRef) ?? [];
146
- list.push(record);
147
- recordsByRef.set(record.moduleRef, list);
148
- }
149
- const resourcesByRef = new Map((input.resources ?? []).map((resource) => [resource.moduleRef, resource]));
150
- for (const module of modules) {
151
- if (!module.lifecycle.includes("verify"))
152
- continue;
153
- const records = recordsByRef.get(module.moduleRef) ?? [];
154
- const reality = resourcesByRef.get(module.moduleRef);
155
- const current = records.filter((record) => {
156
- if (record.moduleVersion !== module.moduleVersion)
157
- return false;
158
- if (reality === undefined)
159
- return true;
160
- if (reality.resourceIdentity !== undefined &&
161
- record.resourceIdentity !== reality.resourceIdentity) {
162
- return false;
163
- }
164
- if (reality.resourceVersion !== undefined &&
165
- record.resourceVersion !== reality.resourceVersion) {
166
- return false;
167
- }
168
- return true;
169
- });
170
- const latestCurrent = current[current.length - 1];
171
- if (latestCurrent === undefined) {
172
- const code = records.length === 0 ? "VERIFICATION_MISSING" : "VERIFICATION_STALE";
173
- findings.push({
174
- severity: "blocking",
175
- code,
176
- moduleRef: module.moduleRef,
177
- message: `no current-version verification record for ${module.moduleRef}@${module.moduleVersion}`,
178
- });
179
- }
180
- else if (latestCurrent.result !== "PASS") {
181
- findings.push({
182
- severity: "blocking",
183
- code: "VERIFICATION_FAIL",
184
- moduleRef: module.moduleRef,
185
- message: `current-version verification FAIL for ${module.moduleRef}@${module.moduleVersion}`,
186
- });
187
- }
188
- }
189
- // blocking ACTION_REQUIRED count must be zero
190
- for (const action of input.blockingActions ?? []) {
191
- findings.push({
192
- severity: "blocking",
193
- code: "BLOCKING_ACTION",
194
- ...(action.moduleRef !== undefined
195
- ? { moduleRef: action.moduleRef }
196
- : {}),
197
- message: action.description ?? action.action,
198
- });
199
- }
200
- return { state: deriveState(findings), findings };
201
- }
@@ -1,3 +0,0 @@
1
- export type { WorkspaceLockHandle } from "./lock.ts";
2
- export { acquireWorkspaceLock, readWorkspaceLock, workspaceLockPath, } from "./lock.ts";
3
- export { redactDeep, redactPlanSecrets, redactSecretValues, SECRET_REDACTED, } from "./redact.ts";
@@ -1,2 +0,0 @@
1
- export { acquireWorkspaceLock, readWorkspaceLock, workspaceLockPath, } from "./lock.js";
2
- export { redactDeep, redactPlanSecrets, redactSecretValues, SECRET_REDACTED, } from "./redact.js";
@@ -1,9 +0,0 @@
1
- import type { LockRecord } from "../contracts.ts";
2
- import type { WorkspacePaths } from "../paths.ts";
3
- export interface WorkspaceLockHandle {
4
- record: LockRecord;
5
- release(): Promise<void>;
6
- }
7
- export declare function workspaceLockPath(paths: WorkspacePaths): string;
8
- export declare function readWorkspaceLock(paths: WorkspacePaths): Promise<LockRecord | undefined>;
9
- export declare function acquireWorkspaceLock(paths: WorkspacePaths, planRef: string): Promise<WorkspaceLockHandle>;
@@ -1,127 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { mkdir, open, readFile, realpath, unlink, } from "node:fs/promises";
3
- import { join, resolve } from "node:path";
4
- import { PlatformError } from "../errors.js";
5
- import { isLockRecord } from "../persistence/guards.js";
6
- export function workspaceLockPath(paths) {
7
- return join(paths.runtime, "apply.lock");
8
- }
9
- function errorHasCode(error, code) {
10
- return (typeof error === "object" &&
11
- error !== null &&
12
- "code" in error &&
13
- error.code === code);
14
- }
15
- async function computeFingerprint(root) {
16
- let canonical = root;
17
- try {
18
- canonical = await realpath(root);
19
- }
20
- catch {
21
- canonical = resolve(root);
22
- }
23
- return createHash("sha256").update(canonical).digest("hex").slice(0, 16);
24
- }
25
- export async function readWorkspaceLock(paths) {
26
- let raw;
27
- try {
28
- raw = await readFile(workspaceLockPath(paths), "utf8");
29
- }
30
- catch {
31
- return undefined;
32
- }
33
- let parsed;
34
- try {
35
- parsed = JSON.parse(raw);
36
- }
37
- catch {
38
- return undefined;
39
- }
40
- return isLockRecord(parsed) ? parsed : undefined;
41
- }
42
- function lockedError(existing) {
43
- const details = existing === undefined
44
- ? "existing lock is unreadable"
45
- : `pid=${existing.pid} planRef=${existing.planRef} since=${existing.createdAt} fingerprint=${existing.workspaceFingerprint}`;
46
- return new PlatformError("WORKSPACE_LOCKED", `workspace apply already in progress (${details})`);
47
- }
48
- function isProvablyDeadPid(pid) {
49
- try {
50
- process.kill(pid, 0);
51
- return false;
52
- }
53
- catch (error) {
54
- return errorHasCode(error, "ESRCH");
55
- }
56
- }
57
- async function reclaimProvablyStaleLock(paths, expectedFingerprint) {
58
- const existing = await readWorkspaceLock(paths);
59
- if (existing === undefined ||
60
- existing.workspaceFingerprint !== expectedFingerprint ||
61
- !isProvablyDeadPid(existing.pid)) {
62
- return false;
63
- }
64
- try {
65
- await unlink(workspaceLockPath(paths));
66
- return true;
67
- }
68
- catch (error) {
69
- if (errorHasCode(error, "ENOENT"))
70
- return true;
71
- throw error;
72
- }
73
- }
74
- // v1 single-process exclusive lock: O_EXCL create, no distributed coordination,
75
- // and no reclaim of a lock that cannot be proven stale.
76
- export async function acquireWorkspaceLock(paths, planRef) {
77
- const file = workspaceLockPath(paths);
78
- await mkdir(paths.runtime, { recursive: true });
79
- const record = {
80
- pid: process.pid,
81
- createdAt: new Date().toISOString(),
82
- planRef,
83
- workspaceFingerprint: await computeFingerprint(paths.root),
84
- };
85
- let handle;
86
- try {
87
- handle = await open(file, "wx", 0o644);
88
- }
89
- catch (error) {
90
- if (!errorHasCode(error, "EEXIST"))
91
- throw error;
92
- if (await reclaimProvablyStaleLock(paths, record.workspaceFingerprint)) {
93
- try {
94
- handle = await open(file, "wx", 0o644);
95
- }
96
- catch (retryError) {
97
- if (errorHasCode(retryError, "EEXIST")) {
98
- throw lockedError(await readWorkspaceLock(paths));
99
- }
100
- throw retryError;
101
- }
102
- }
103
- else {
104
- throw lockedError(await readWorkspaceLock(paths));
105
- }
106
- }
107
- try {
108
- await handle.writeFile(`${JSON.stringify(record, null, 2)}\n`, {
109
- encoding: "utf8",
110
- });
111
- }
112
- finally {
113
- await handle.close();
114
- }
115
- return {
116
- record,
117
- async release() {
118
- try {
119
- await unlink(file);
120
- }
121
- catch (error) {
122
- if (!errorHasCode(error, "ENOENT"))
123
- throw error;
124
- }
125
- },
126
- };
127
- }
@@ -1,6 +0,0 @@
1
- import type { DeploymentPlan } from "../contracts.ts";
2
- export declare const SECRET_REDACTED = "<redacted>";
3
- export declare function isValidSecretRef(value: string): boolean;
4
- export declare function redactSecretValues(values: Record<string, string>, _secretRefs: readonly string[]): Record<string, string>;
5
- export declare function redactDeep(value: unknown, rawSecrets: readonly string[]): unknown;
6
- export declare function redactPlanSecrets(plan: DeploymentPlan): DeploymentPlan;
@@ -1,48 +0,0 @@
1
- export const SECRET_REDACTED = "<redacted>";
2
- const SECRET_REF_PATTERN = /^(?:secret:\/\/[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)*|credential-ref:[A-Za-z0-9._-]+)$/;
3
- export function isValidSecretRef(value) {
4
- return SECRET_REF_PATTERN.test(value);
5
- }
6
- // secretRef slot values are opaque reference identities (e.g.
7
- // `secret://model-provider/default`, `credential-ref:local-platform`), NOT raw
8
- // tokens/passwords. They are safe to persist and display verbatim, so this
9
- // function preserves them instead of substituting a redaction marker. Raw
10
- // secret leak scanning is `redactDeep`'s job, not this function's.
11
- export function redactSecretValues(values, _secretRefs) {
12
- return { ...values };
13
- }
14
- function redactString(value, secrets) {
15
- let out = value;
16
- for (const secret of secrets) {
17
- if (secret.length === 0)
18
- continue;
19
- out = out.split(secret).join(SECRET_REDACTED);
20
- }
21
- return out;
22
- }
23
- // Deep-structural redaction: returns a clone with every occurrence of a raw
24
- // secret value replaced, in any nested string position. This is the leak-scan
25
- // layer for genuine raw secret values (test sentinels or future non-secretRef
26
- // sensitive values). secretRef references are NOT raw secrets and must never be
27
- // passed here as raw secrets.
28
- export function redactDeep(value, rawSecrets) {
29
- const secrets = rawSecrets.filter((secret) => secret.length > 0);
30
- if (typeof value === "string")
31
- return redactString(value, secrets);
32
- if (Array.isArray(value))
33
- return value.map((item) => redactDeep(item, secrets));
34
- if (typeof value === "object" && value !== null) {
35
- const out = {};
36
- for (const [key, item] of Object.entries(value)) {
37
- out[key] = redactDeep(item, secrets);
38
- }
39
- return out;
40
- }
41
- return value;
42
- }
43
- // Plans are public DTOs whose secretRef config values are opaque reference
44
- // identities, not raw secrets. This returns a non-mutating clone that preserves
45
- // secretRef references verbatim (no `<redacted>` substitution).
46
- export function redactPlanSecrets(plan) {
47
- return structuredClone(plan);
48
- }
@@ -1,2 +0,0 @@
1
- export type { VerifyResult } from "./verify.ts";
2
- export { configFingerprint, verificationRefOf, verifyModule, verifyModules, } from "./verify.ts";
@@ -1 +0,0 @@
1
- export { configFingerprint, verificationRefOf, verifyModule, verifyModules, } from "./verify.js";