@deksden-com/dd-flow-cli 0.9.0-beta.144 → 0.9.0-beta.145
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/README.md +13 -8
- package/dist/build-info.json +7 -7
- package/dist/cli/command-inputs.js +9 -3
- package/dist/cli/command-metadata.js +33 -14
- package/dist/cli/help.js +32 -1
- package/dist/cli/hook-ingress.js +27 -3
- package/dist/cli/memory-flow-admission.js +7 -3
- package/dist/cli/memory-init-invocation.js +10 -3
- package/dist/cli/memory-init.js +74 -68
- package/dist/cli/memory-storage-admission.js +3 -2
- package/dist/cli/memory-storage-qualification.js +105 -0
- package/dist/cli/memory-storage-transition.js +123 -0
- package/dist/cli/memory-upgrade.js +189 -0
- package/dist/cli/native-hook-ingress.js +14 -4
- package/dist/cli/native-hook-preparation.js +55 -0
- package/dist/cli/response-file.js +2 -1
- package/dist/cli/run-cli.js +70 -32
- package/dist/cli/setup.js +2 -1
- package/dist/cli.js +12 -0
- package/dist/domain/stage-handler-identities.js +8 -0
- package/dist/harness-runtime/bin/dd-agy.mjs +3 -3
- package/dist/harness-runtime/bin/dd-grok.mjs +3 -3
- package/dist/harness-runtime/bin/dd-zcode.mjs +5 -5
- package/dist/harness-runtime/lib/agy-error-boundary.mjs +58 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +62 -20
- package/dist/harness-runtime/lib/dd-agy.mjs +17 -3
- package/dist/harness-runtime/lib/dd-droid.mjs +1 -1
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +70 -13
- package/dist/harness-runtime/lib/dd-grok.mjs +6 -3
- package/dist/harness-runtime/lib/dd-zcode.mjs +44 -14
- package/dist/harness-runtime/lib/delegation-instructions.mjs +1 -1
- package/dist/harness-runtime/lib/entity-references.d.mts +2 -0
- package/dist/harness-runtime/lib/entity-references.mjs +9 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +13 -6
- package/dist/harness-runtime/lib/native-hook-command.mjs +4 -4
- package/dist/harness-runtime/lib/opencode-plugin.mjs +3 -3
- package/dist/harness-runtime/lib/operation-errors.mjs +1 -1
- package/dist/harness-runtime/lib/provider-overload-policy.d.mts +4 -2
- package/dist/harness-runtime/lib/provider-overload-policy.mjs +19 -6
- package/dist/harness-runtime/lib/runner-events.mjs +3 -1
- package/dist/harness-runtime/lib/setup-progress.mjs +8 -6
- package/dist/schemas/mb-upgrade-stage-result.schema.json +863 -0
- package/dist/schemas/memory-repair-work-result.schema.json +9 -2
- package/dist/schemas/release-impact.schema.json +3 -3
- package/dist/schemas/stage-start-response.schema.json +4 -0
- package/dist/services/accepted-intent.js +13 -1
- package/dist/services/canon.js +43 -7
- package/dist/services/cli-operation-classifier.js +1 -1
- package/dist/services/cli-upgrade-allowlist.js +1 -1
- package/dist/services/code-checks.js +11 -4
- package/dist/services/compatibility-preflight.js +5 -2
- package/dist/services/controller-fanout.js +21 -4
- package/dist/services/controller-memory-task.js +45 -0
- package/dist/services/controller-memory-worker.js +48 -0
- package/dist/services/dashboard.js +2 -2
- package/dist/services/descriptor-run-start.js +26 -19
- package/dist/services/descriptor-stage-coordinator.js +30 -10
- package/dist/services/descriptor-stage-start.js +40 -4
- package/dist/services/engines.js +64 -14
- package/dist/services/eval-snapshots.js +104 -93
- package/dist/services/external-work-launch.js +5 -3
- package/dist/services/flow-catalog.js +8 -4
- package/dist/services/frozen-engine.js +113 -11
- package/dist/services/hook-memory-routing.js +188 -0
- package/dist/services/hook-recovery-guard.js +14 -0
- package/dist/services/hook-session-observation.js +24 -0
- package/dist/services/hooks.js +27 -8
- package/dist/services/hybrid-contracts.js +2 -13
- package/dist/services/hybrid-digest.js +14 -0
- package/dist/services/installed-flow-source.js +25 -4
- package/dist/services/lifecycle-invocations.js +33 -5
- package/dist/services/memory-accepted-composition.js +5 -4
- package/dist/services/memory-completed-correction-wave.js +23 -15
- package/dist/services/memory-compose-handler.js +163 -21
- package/dist/services/memory-composition-acceptance.js +186 -10
- package/dist/services/memory-composition-graph.js +42 -11
- package/dist/services/memory-composition-registration.js +23 -8
- package/dist/services/memory-composition-work-custody.js +14 -9
- package/dist/services/memory-composition-work-results.js +15 -3
- package/dist/services/memory-corrected-review-batch.js +10 -3
- package/dist/services/memory-corrected-review-findings.js +4 -2
- package/dist/services/memory-corrected-review-inputs.js +10 -5
- package/dist/services/memory-corrected-review-receipt.js +17 -9
- package/dist/services/memory-corrected-review-registration.js +3 -2
- package/dist/services/memory-correction-check-receipt.js +20 -4
- package/dist/services/memory-custody-snapshot.js +36 -0
- package/dist/services/memory-finish-evidence.js +44 -22
- package/dist/services/memory-finish-handler.js +37 -23
- package/dist/services/memory-finish-integration.js +218 -48
- package/dist/services/memory-finish-readiness.js +66 -19
- package/dist/services/memory-git-integration.js +87 -18
- package/dist/services/memory-historical-corrected-review.js +21 -15
- package/dist/services/memory-historical-original-review.js +22 -9
- package/dist/services/memory-historical-repair-registration.js +14 -8
- package/dist/services/memory-historical-second-review.js +30 -11
- package/dist/services/memory-init-contexts.js +28 -2
- package/dist/services/memory-init-controller-start.js +19 -8
- package/dist/services/memory-init-git-policy.js +50 -21
- package/dist/services/memory-init-lint-capability.js +21 -3
- package/dist/services/memory-init-native-capacity.js +13 -0
- package/dist/services/memory-init-preflight.js +11 -1
- package/dist/services/memory-init-request-candidate.js +73 -17
- package/dist/services/memory-init-request-input.js +23 -7
- package/dist/services/memory-init-run-bundle.js +49 -12
- package/dist/services/memory-init-storage.js +17 -7
- package/dist/services/memory-layer.js +68 -4
- package/dist/services/memory-lint-config.js +111 -2
- package/dist/services/memory-native-review-copy.js +5 -67
- package/dist/services/memory-normal-flow-entry.js +12 -0
- package/dist/services/memory-ordinary-defaults.js +22 -5
- package/dist/services/memory-preflight-handler.js +15 -0
- package/dist/services/memory-repair-current-view.js +1 -1
- package/dist/services/memory-repair-document-delta.js +70 -33
- package/dist/services/memory-repair-history.js +12 -7
- package/dist/services/memory-repair-physical-binding.js +1 -119
- package/dist/services/memory-repair-publication-plan.js +5 -3
- package/dist/services/memory-repair-publication-source.js +1 -1
- package/dist/services/memory-repair-publication.js +3 -2
- package/dist/services/memory-repair-registration.js +2 -1
- package/dist/services/memory-repair-work-baseline.js +4 -2
- package/dist/services/memory-repair-work-completion.js +32 -12
- package/dist/services/memory-repair-work-results.js +21 -2
- package/dist/services/memory-repair-workspace.js +11 -2
- package/dist/services/memory-repair-writer-copy.js +2 -1
- package/dist/services/memory-result-binding.js +78 -12
- package/dist/services/memory-retained-review-authority.js +17 -8
- package/dist/services/memory-retained-second-baseline.js +1 -1
- package/dist/services/memory-retained-second-repairs.js +12 -6
- package/dist/services/memory-review-correction-plan.js +47 -12
- package/dist/services/memory-review-handler.js +17 -17
- package/dist/services/memory-review-inputs.js +55 -9
- package/dist/services/memory-review-limits.js +10 -0
- package/dist/services/memory-review-receipt.js +2 -2
- package/dist/services/memory-review-registration.js +13 -4
- package/dist/services/memory-review-work-custody.js +3 -1
- package/dist/services/memory-review-work-input.js +26 -7
- package/dist/services/memory-review-work-results.js +26 -4
- package/dist/services/memory-second-corrected-review-inputs.js +5 -3
- package/dist/services/memory-second-correction-plan.js +2 -1
- package/dist/services/memory-second-repair-launch.js +4 -4
- package/dist/services/memory-second-repair-source.js +1 -1
- package/dist/services/memory-stage-dispatch.js +38 -1
- package/dist/services/memory-stage-results.js +113 -22
- package/dist/services/memory-upgrade-analysis-handler.js +243 -0
- package/dist/services/memory-upgrade-bootstrap-readiness.js +251 -0
- package/dist/services/memory-upgrade-evidence.js +100 -0
- package/dist/services/memory-upgrade-inventory.js +442 -0
- package/dist/services/memory-upgrade-publication-custody.js +73 -0
- package/dist/services/memory-upgrade-publication.js +413 -0
- package/dist/services/memory-upgrade-selection.js +430 -0
- package/dist/services/memory-upgrade-start.js +60 -0
- package/dist/services/memory-upgrade-storage-readiness.js +340 -0
- package/dist/services/memory-verification-acceptance.js +38 -17
- package/dist/services/memory-verification-checks.js +77 -23
- package/dist/services/memory-verification-receipt.js +6 -5
- package/dist/services/memory-verify-handler.js +15 -13
- package/dist/services/memory-work-result-json.js +12 -0
- package/dist/services/memory-work-result-preparation.js +5 -4
- package/dist/services/merge-server.js +4 -2
- package/dist/services/migrations.js +127 -98
- package/dist/services/physical-startup.js +45 -0
- package/dist/services/project-registry.js +97 -0
- package/dist/services/project-summary.js +1 -1
- package/dist/services/projects.js +3 -93
- package/dist/services/prompt-delivery.js +71 -0
- package/dist/services/provider-capacity.js +69 -8
- package/dist/services/recovery-snapshot-database.js +32 -4
- package/dist/services/repair-intents.js +1 -12
- package/dist/services/repair-receipt.js +13 -0
- package/dist/services/run-control-worker.js +35 -18
- package/dist/services/run-controller-process.js +2 -6
- package/dist/services/run-controller-recovery.js +6 -1
- package/dist/services/run-controller.js +131 -80
- package/dist/services/run-recovery-guard.js +30 -0
- package/dist/services/run-recovery.js +11 -44
- package/dist/services/run-start-custody.js +17 -5
- package/dist/services/runtime-scope-worker.js +46 -19
- package/dist/services/sessions.js +3 -24
- package/dist/services/setup.js +63 -13
- package/dist/services/stage-handlers.js +9 -7
- package/dist/services/stage-pause.js +108 -44
- package/dist/services/version-status.js +1 -1
- package/dist/services/vnext-fanout.js +21 -9
- package/dist/services/vnext-plan-review.js +3 -5
- package/dist/services/work-registry.js +80 -33
- package/dist/services/workspace-files.js +1 -1
- package/dist/shared/entity-references.js +1 -8
- package/dist/storage/data-preservation.js +65 -0
- package/dist/storage/database.js +53 -19
- package/dist/storage/import-guard.js +84 -27
- package/dist/storage/memory-upgrade-schema-bootstrap.js +38 -0
- package/dist/storage/paired-writer-migration.js +424 -0
- package/dist/storage/writer-contract.js +11 -4
- package/dist/storage/writer-migration.js +313 -52
- package/dist/storage/writer-restore.js +217 -0
- package/package.json +8 -4
|
@@ -137,15 +137,16 @@ function accepted(context, scope) {
|
|
|
137
137
|
return refuse("invalid_receipt_json");
|
|
138
138
|
}
|
|
139
139
|
if (!receipt || typeof receipt !== "object" || Array.isArray(receipt) || hybridDigest({ ...receipt, checks: undefined }) !== hybridDigest({ schema_id: "dd-flow/memory-verification-acceptance@1", run_id: scope.runId, stage: "verify", project_id: scope.projectId,
|
|
140
|
-
attempt: selected.attempt, input_sha256: selected.input_sha256, result_sha256: data.result.sha256 }) ||
|
|
140
|
+
attempt: selected.attempt, input_sha256: selected.input_sha256, result_sha256: data.result.sha256 }) || !["dd-flow/mb-init-verify-result@1", "dd-flow/mb-upgrade-stage-result@1"].includes(data.data_schema_id ?? "")
|
|
141
141
|
|| data.input_sha256 !== selected.input_sha256)
|
|
142
142
|
refuse("accepted_receipt_changed");
|
|
143
143
|
const value = JSON.parse(data.bytes.toString("utf8"));
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
const authoring = data.data_schema_id === "dd-flow/mb-upgrade-stage-result@1" ? "upgrade" : "compose";
|
|
145
|
+
const compose = readAcceptedStageData(context, { ...scope, stage: authoring }), composed = JSON.parse(compose.bytes.toString("utf8"));
|
|
146
|
+
if (compose.data_schema_id !== (authoring === "upgrade" ? data.data_schema_id : "dd-flow/mb-init-compose-result@1")
|
|
147
|
+
|| composed.schema_id !== compose.data_schema_id || composed.stage !== authoring || composed.run_id !== scope.runId
|
|
147
148
|
|| composed.outcome !== "accepted" || composed.input_sha256 !== compose.input_sha256
|
|
148
|
-
|| hybridDigest(value.predecessors) !== hybridDigest([{ stage:
|
|
149
|
+
|| hybridDigest(value.predecessors) !== hybridDigest([{ stage: authoring, result: compose.result, input_sha256: compose.input_sha256 }]))
|
|
149
150
|
refuse("accepted_predecessor_changed");
|
|
150
151
|
if (value.stage !== "verify" || value.run_id !== scope.runId || !["accepted", "needs_repair"].includes(value.outcome) || value.input_sha256 !== selected.input_sha256
|
|
151
152
|
|| !Array.isArray(receipt.checks) || receipt.checks.some(reference => !reference || typeof reference !== "object" || Array.isArray(reference)
|
|
@@ -24,14 +24,14 @@ function refuse(reason) {
|
|
|
24
24
|
*/
|
|
25
25
|
export function runMemoryVerificationChecks(context, input) {
|
|
26
26
|
const bundle = readFlowRunExecutionBundle(context, input);
|
|
27
|
-
if (!bundle || bundle.descriptor.descriptor.id
|
|
27
|
+
if (!bundle || !["mb-init", "mb-upgrade"].includes(bundle.descriptor.descriptor.id) || bundle.descriptor.successor(bundle.descriptor.descriptor.id === "mb-upgrade" ? "upgrade" : "compose", "accepted") !== "verify"
|
|
28
28
|
|| bundle.descriptor.successor("verify", "accepted") !== "review")
|
|
29
29
|
refuse("registered_memory_bundle_required");
|
|
30
30
|
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot)), scope = { projectId: project.id, runId: input.runId };
|
|
31
31
|
const run = assertPreparedRunBundleSqlAuthority(context, scope, bundle), index = JSON.parse(run.index_json);
|
|
32
32
|
if (index.current_stage !== "verify" || index.stage_runs.find((stage) => stage.stage === "verify")?.status !== "running")
|
|
33
33
|
refuse("active_verification_required");
|
|
34
|
-
return PreparedMemoryVerificationChecks.prepare(context, { ...scope, execution: bundle.execution, flow: bundle.descriptor }).execute(context);
|
|
34
|
+
return PreparedMemoryVerificationChecks.prepare(context, { ...scope, execution: bundle.execution, flow: bundle.descriptor, bundle }).execute(context);
|
|
35
35
|
}
|
|
36
36
|
/** Productive entry implementation, not registered until the complete six-stage
|
|
37
37
|
* memory group is ready. Checks and semantic assessment acceptance belong to
|
|
@@ -41,22 +41,23 @@ export function startMemoryVerification(context, input) {
|
|
|
41
41
|
if (!(input.bytes instanceof Uint8Array) || input.bytes.length > 1024 * 1024)
|
|
42
42
|
refuse("invalid_input_bytes");
|
|
43
43
|
const bundle = readFlowRunExecutionBundle(context, input);
|
|
44
|
-
if (!bundle || bundle.descriptor.descriptor.id
|
|
44
|
+
if (!bundle || !["mb-init", "mb-upgrade"].includes(bundle.descriptor.descriptor.id) || bundle.descriptor.successor(bundle.descriptor.descriptor.id === "mb-upgrade" ? "upgrade" : "compose", "accepted") !== "verify"
|
|
45
45
|
|| bundle.descriptor.successor("verify", "accepted") !== "review")
|
|
46
46
|
refuse("registered_memory_bundle_required");
|
|
47
47
|
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot)), scope = { projectId: project.id, runId: input.runId, stage: "verify" };
|
|
48
48
|
const run = assertPreparedRunBundleSqlAuthority(context, scope, bundle), index = JSON.parse(run.index_json);
|
|
49
49
|
if (index.stage_runs.find((stage) => stage.stage === "verify")?.status === "done")
|
|
50
50
|
refuse("use_accepted_finish_replay");
|
|
51
|
-
const
|
|
52
|
-
const
|
|
51
|
+
const upgrade = bundle.descriptor.descriptor.id === "mb-upgrade", authoring = upgrade ? "upgrade" : "compose";
|
|
52
|
+
const preservation = upgrade ? PreparedAcceptedMemoryComposition.prepare(context, { scope, bundle }) : PreparedMemoryBaselinePreservation.prepare(context, bundle.execution);
|
|
53
|
+
const inherited = context.db.readSnapshot(() => ({ compose: readAcceptedStageData(context, { ...scope, stage: authoring }), preflight: readAcceptedStageData(context, { ...scope, stage: "preflight" }) }));
|
|
53
54
|
const compose = JSON.parse(inherited.compose.bytes.toString("utf8")), preflight = JSON.parse(inherited.preflight.bytes.toString("utf8"));
|
|
54
|
-
if (compose.schema_id !== "dd-flow/mb-init-compose-result@1" || compose.run_id !== input.runId || compose.outcome !== "accepted"
|
|
55
|
-
|| preflight.schema_id !== "dd-flow/mb-init-preflight-result@1" || preflight.run_id !== input.runId || preflight.outcome !== "accepted")
|
|
55
|
+
if (compose.schema_id !== (upgrade ? "dd-flow/mb-upgrade-stage-result@1" : "dd-flow/mb-init-compose-result@1") || compose.run_id !== input.runId || compose.outcome !== "accepted"
|
|
56
|
+
|| preflight.schema_id !== (upgrade ? compose.schema_id : "dd-flow/mb-init-preflight-result@1") || preflight.run_id !== input.runId || preflight.outcome !== "accepted")
|
|
56
57
|
refuse("accepted_composition_and_baseline_required");
|
|
57
58
|
const fence = (writer) => {
|
|
58
59
|
preservation.assertCustody(writer);
|
|
59
|
-
if (readAcceptedStageData(writer, { ...scope, stage:
|
|
60
|
+
if (readAcceptedStageData(writer, { ...scope, stage: authoring }).authority_sha256 !== inherited.compose.authority_sha256
|
|
60
61
|
|| readAcceptedStageData(writer, { ...scope, stage: "preflight" }).authority_sha256 !== inherited.preflight.authority_sha256)
|
|
61
62
|
refuse("inherited_custody_changed");
|
|
62
63
|
};
|
|
@@ -75,7 +76,7 @@ export function finishMemoryVerification(context, input) {
|
|
|
75
76
|
if (!(input.bytes instanceof Uint8Array) || !input.bytes.length || input.bytes.length > 1024 * 1024)
|
|
76
77
|
refuse("invalid_result_bytes");
|
|
77
78
|
const bundle = readFlowRunExecutionBundle(context, input);
|
|
78
|
-
if (!bundle || bundle.descriptor.descriptor.id
|
|
79
|
+
if (!bundle || !["mb-init", "mb-upgrade"].includes(bundle.descriptor.descriptor.id) || bundle.descriptor.successor(bundle.descriptor.descriptor.id === "mb-upgrade" ? "upgrade" : "compose", "accepted") !== "verify"
|
|
79
80
|
|| bundle.descriptor.successor("verify", "accepted") !== "review")
|
|
80
81
|
refuse("registered_memory_bundle_required");
|
|
81
82
|
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot)), scope = { projectId: project.id, runId: input.runId };
|
|
@@ -99,11 +100,11 @@ export function finishMemoryVerification(context, input) {
|
|
|
99
100
|
}
|
|
100
101
|
const composition = PreparedAcceptedMemoryComposition.prepare(context, { scope, bundle }), compose = composition.result();
|
|
101
102
|
const composedUnits = compose.value().units;
|
|
102
|
-
const preservation = PreparedMemoryBaselinePreservation.prepare(context, bundle.execution);
|
|
103
|
-
const policy = PreparedMemoryVerificationChecks.prepare(context, { ...scope, execution: bundle.execution, flow: bundle.descriptor });
|
|
103
|
+
const preservation = bundle.descriptor.descriptor.id === "mb-upgrade" ? composition : PreparedMemoryBaselinePreservation.prepare(context, bundle.execution);
|
|
104
|
+
const policy = PreparedMemoryVerificationChecks.prepare(context, { ...scope, execution: bundle.execution, flow: bundle.descriptor, bundle, composition });
|
|
104
105
|
const checks = policy.evidence(context, "diagnostic");
|
|
105
106
|
const binding = PreparedMemoryResultBinding.prepare(context, { ...scope, stage: "verify", flow: bundle.descriptor, execution: bundle.execution, bundle,
|
|
106
|
-
sources: composition.captureSources(context, input.bytes, checks), checks });
|
|
107
|
+
sources: composition.captureSources(context, input.bytes, checks), checks, historicalEvidence: composition.historicalEvidence() });
|
|
107
108
|
let proposal;
|
|
108
109
|
try {
|
|
109
110
|
proposal = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(input.bytes));
|
|
@@ -114,7 +115,8 @@ export function finishMemoryVerification(context, input) {
|
|
|
114
115
|
}
|
|
115
116
|
const references = checks.sourceReferences().map(({ path, sha256 }) => ({ path, sha256 }));
|
|
116
117
|
const result = PreparedMemoryStageResult.prepare(Buffer.from(JSON.stringify(projectMemoryVerificationChecks(projectMemoryVerificationArtifacts(proposal, composedUnits, composition.outputReferences(), createHash("sha256").update(input.bytes).digest("hex")), references, bundle.descriptor.stage("verify").directory, createHash("sha256").update(input.bytes).digest("hex")))), bundle.descriptor, binding.binding());
|
|
117
|
-
const acceptance = PreparedMemoryVerificationAcceptance.prepare({ scope, result, compose, checks, composition, roots: bundle.execution.snapshot().scope
|
|
118
|
+
const acceptance = PreparedMemoryVerificationAcceptance.prepare({ scope, result, compose, checks, composition, roots: bundle.execution.snapshot().scope,
|
|
119
|
+
proposalSha256: createHash("sha256").update(input.bytes).digest("hex") });
|
|
118
120
|
const receipt = PreparedMemoryVerificationReceipt.prepare(context, { scope, result, checks, acceptance });
|
|
119
121
|
const settlement = PreparedDescriptorStageSettlement.prepare(context, { ...input, stage: "verify", bytes: result.bytes() });
|
|
120
122
|
composition.revalidate(context);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { AppError } from "../shared/errors.js";
|
|
3
|
+
/** Syntax only: the caller still owns bounds, assignment and evidence checks. */
|
|
4
|
+
export function parseMemoryWorkResultProposal(bytes, code) {
|
|
5
|
+
try {
|
|
6
|
+
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
throw new AppError(code, "Memory result proposal is not valid UTF-8 JSON. Correct only this proposal using its assigned packet; do not rerun settled work or change retained inputs", 2, { reason: "invalid_result_json", phase: "prepare", effect: "no_effect", recoverable: true,
|
|
10
|
+
result_sha256: createHash("sha256").update(bytes).digest("hex") });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -10,7 +10,7 @@ import { restoreMemoryRepairPublicationFromStage, prepareMemorySecondRepairPubli
|
|
|
10
10
|
* from worker JSON, mutable schemas, caller profile or a supplied request key.
|
|
11
11
|
* Preparation is read-only and occurs before the settlement writer.
|
|
12
12
|
*/
|
|
13
|
-
export function prepareRegisteredMemoryWorkResult(context, work, result) {
|
|
13
|
+
export function prepareRegisteredMemoryWorkResult(context, work, result, progress) {
|
|
14
14
|
if (!["dd-flow/memory-composition-work-result@1", "dd-flow/memory-review-work-result@1", "dd-flow/memory-repair-work-result@1"].includes(work.result_schema ?? ""))
|
|
15
15
|
return null;
|
|
16
16
|
const bytes = Buffer.from(result, "utf8");
|
|
@@ -44,15 +44,16 @@ export function prepareRegisteredMemoryWorkResult(context, work, result) {
|
|
|
44
44
|
return PreparedPublishedMemoryRepairWorkResult.prepare(context, publication);
|
|
45
45
|
}
|
|
46
46
|
if (work.result_schema === "dd-flow/memory-review-work-result@1") {
|
|
47
|
-
return PreparedRegisteredMemoryReviewWorkResult.prepare(context, { workId: work.work_id, bytes });
|
|
47
|
+
return PreparedRegisteredMemoryReviewWorkResult.prepare(context, { workId: work.work_id, bytes, ...(progress ? { progress } : {}) });
|
|
48
48
|
}
|
|
49
49
|
const run = context.db.get("SELECT project_root FROM runs WHERE project_id=? AND id=?", [work.project_id, work.run_id]);
|
|
50
50
|
const bundle = run && readFlowRunExecutionBundle(context, { projectRoot: run.project_root, runId: work.run_id });
|
|
51
|
-
if (!bundle || bundle.descriptor.descriptor.id
|
|
51
|
+
if (!bundle || !["mb-init", "mb-upgrade"].includes(bundle.descriptor.descriptor.id))
|
|
52
52
|
throw new AppError("memory_work_result_preparation_required", "Composition finish requires its retained memory flow", 1, { effect: "no_effect" });
|
|
53
|
+
const stage = bundle.descriptor.descriptor.id === "mb-init" ? "compose" : "upgrade";
|
|
53
54
|
const custody = readWorkBatchCustody(context, work.work_id);
|
|
54
55
|
const registration = selectMemoryCompositionRegistration(context, {
|
|
55
|
-
project_id: work.project_id, run_id: work.run_id, stage
|
|
56
|
+
project_id: work.project_id, run_id: work.run_id, stage, attempt: custody.scope.attempt,
|
|
56
57
|
cycle: custody.scope.cycle, parent_work_id: custody.scope.parent_work_id
|
|
57
58
|
});
|
|
58
59
|
return PreparedRegisteredMemoryCompositionWorkResult.prepareFromStageCustody(context, bytes, bundle.engine(), {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { managedInvocationContext, managedLifecycleCommand } from "./lifecycle-invocations.js";
|
|
2
2
|
import { stageLifecycleInstruction } from "../harness-runtime/lib/delegation-instructions.mjs";
|
|
3
|
-
import { continueProviderCapacity } from "./provider-capacity.js";
|
|
3
|
+
import { continueProviderCapacity, ownedProviderBoundary } from "./provider-capacity.js";
|
|
4
4
|
import { errorRecord } from "./run-controller-state.js";
|
|
5
5
|
import crypto from "node:crypto";
|
|
6
6
|
import fs from "node:fs";
|
|
@@ -185,7 +185,9 @@ async function dispatch(context, input) {
|
|
|
185
185
|
if (!["created", "running", "paused", "completed"].includes(context.db.get("SELECT status FROM works WHERE work_id = ?", [input.request.executor_work_id])?.status ?? ""))
|
|
186
186
|
throw new AppError("capacity_continuation_blocked", "MERGE Work has failed or lost its binding", 1);
|
|
187
187
|
},
|
|
188
|
-
decision: () => {
|
|
188
|
+
decision: (boundary) => {
|
|
189
|
+
if (boundary && !ownedProviderBoundary(context, { projectId: input.request.project_id, runId: input.request.run_id, stage: "merge", sessionId, generation, workId: input.request.executor_work_id, scope: "stage" }, boundary))
|
|
190
|
+
return "reconciliation_blocked";
|
|
189
191
|
const status = context.db.get("SELECT status FROM merge_requests WHERE merge_request_id = ?", [input.request.merge_request_id])?.status;
|
|
190
192
|
const workStatus = context.db.get("SELECT status FROM works WHERE work_id = ?", [input.request.executor_work_id])?.status;
|
|
191
193
|
return status === "completed" ? "completed" : status === "action_required" || workStatus === "paused" ? "paused" : "continue";
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import crypto from "node:crypto";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import semver from "semver";
|
|
4
6
|
import { AppError } from "../shared/errors.js";
|
|
5
7
|
import { requireProjectByRoot } from "./projects.js";
|
|
6
8
|
import { getProjectVersionStatus, resolveStatusProjectRoot } from "./version-status.js";
|
|
@@ -12,11 +14,16 @@ import { classifyCliOperation } from "./cli-operation-classifier.js";
|
|
|
12
14
|
import { compatibilityContexts, selectEngine } from "./engines.js";
|
|
13
15
|
import { getCliBuildInfo } from "./build-info.js";
|
|
14
16
|
import { prepareJsonFile } from "../cli/input-preparation.js";
|
|
17
|
+
import { validateSchema } from "./schema-validation.js";
|
|
18
|
+
import { PreparedMemoryInitRuntimeStorage } from "./memory-init-storage.js";
|
|
19
|
+
import { PreparedMemoryInitResourceStorage } from "./memory-init-resource-storage.js";
|
|
20
|
+
import { AdmittedFrozenEngine } from "./frozen-engine.js";
|
|
21
|
+
import { resourceHome } from "./managed-processes.js";
|
|
15
22
|
export function assessMigrationImpact(context, input) {
|
|
16
23
|
const root = resolveStatusProjectRoot({ requestedRoot: input.projectRoot, cwd: process.cwd() });
|
|
17
24
|
if (!root.root)
|
|
18
25
|
throw new AppError("not_found", `Project root does not exist: ${input.projectRoot}`, 1);
|
|
19
|
-
const canon =
|
|
26
|
+
const canon = upgradeCanon(context);
|
|
20
27
|
if (!canon.ok || !canon.canon)
|
|
21
28
|
throw new AppError("canon_unavailable", "Cannot resolve canonical Memory Bank for upgrade impact assessment", 1, { blockers: canon.blockers, bootstrap: canon.bootstrap });
|
|
22
29
|
const status = getProjectVersionStatus({ projectRoot: root.root, rootSource: root.root_source, canon: { root: canon.canon.root, metadata: canon.canon.metadata } });
|
|
@@ -24,53 +31,65 @@ export function assessMigrationImpact(context, input) {
|
|
|
24
31
|
const to = input.targetVersion ?? canon.canon.version;
|
|
25
32
|
const forced = input.mode === "full_migration";
|
|
26
33
|
const impacts = readImpacts(path.join(canon.canon.memorybank_root, "release-impact"));
|
|
27
|
-
const
|
|
34
|
+
const resolved = from && to ? adjacentMigrationChain(from, to, impacts) : null;
|
|
35
|
+
const chain = resolved?.status === "ok"
|
|
36
|
+
? resolved.steps.map(step => impacts.find(item => item.from_version === step.from && item.to_version === step.to))
|
|
37
|
+
: [];
|
|
28
38
|
const domains = new Set();
|
|
29
39
|
const blockers = [];
|
|
30
40
|
let mode = "patch";
|
|
31
41
|
if (!from || !to)
|
|
32
42
|
blockers.push("project or canonical Memory Bank version is unavailable");
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
break;
|
|
39
|
-
}
|
|
40
|
-
chain.push(impact);
|
|
43
|
+
if (to !== canon.canon.version)
|
|
44
|
+
blockers.push("requested target version does not match the selected canon");
|
|
45
|
+
if (resolved?.status === "unsupported")
|
|
46
|
+
blockers.push(resolved.reason ?? "release impact chain is unsupported");
|
|
47
|
+
for (const impact of chain) {
|
|
41
48
|
for (const domain of impact.domains)
|
|
42
49
|
domains.add(domain);
|
|
43
50
|
mode = stricterMode(mode, impact.minimum_upgrade_mode);
|
|
44
51
|
if (impact.path_migration || impact.runtime_migration)
|
|
45
52
|
mode = "full_migration";
|
|
46
|
-
current = impact.to_version;
|
|
47
53
|
}
|
|
48
|
-
if (
|
|
49
|
-
blockers.push(`release impact chain does not reach ${to}`);
|
|
50
|
-
if (status?.flow_pack.status !== "present" && from !== to)
|
|
54
|
+
if (status?.flow_pack.status !== "present")
|
|
51
55
|
blockers.push("project flow-pack provenance is not present");
|
|
56
|
+
else {
|
|
57
|
+
try {
|
|
58
|
+
const file = path.resolve(root.root, status.flow_pack.manifest_path);
|
|
59
|
+
const prepared = prepareJsonFile(file, "Project flow-pack provenance");
|
|
60
|
+
validateSchema({ schemaName: "project-flow-pack-manifest", file, data: prepared.value,
|
|
61
|
+
schemaDir: fileURLToPath(new URL("../schemas/", import.meta.url)) });
|
|
62
|
+
if (!/^[a-f0-9]{40}$/.test(status.flow_pack.source_commit ?? "")
|
|
63
|
+
|| status.flow_pack.canon_version_at_source_commit !== from)
|
|
64
|
+
blockers.push("project flow-pack provenance does not match its declared current version and commit");
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (!(error instanceof AppError) || !["schema_validation", "invalid_json", "validation"].includes(error.code)
|
|
68
|
+
&& !error.code.startsWith("input_file_"))
|
|
69
|
+
throw error;
|
|
70
|
+
blockers.push("project flow-pack provenance is invalid");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
52
73
|
if (blockers.length > 0)
|
|
53
74
|
mode = "full_migration";
|
|
54
75
|
if (forced)
|
|
55
76
|
mode = "full_migration";
|
|
56
77
|
return { ok: true, schema_id: "dd-flow/mb-upgrade-impact-assessment@1", project_root: root.root, source_version: from, target_version: to, mode, forced_full_migration: forced, chain, domains: [...domains].sort(), blockers, required_checks: [...new Set(chain.flatMap((item) => item.required_checks))], next_action: mode === "full_migration" ? "run full mb-upgrade contour" : "run selected mb-upgrade contour" };
|
|
57
78
|
}
|
|
58
|
-
function readImpacts(dir) {
|
|
79
|
+
export function readImpacts(dir) {
|
|
59
80
|
if (!fs.existsSync(dir))
|
|
60
81
|
return [];
|
|
61
|
-
return fs.readdirSync(dir).filter(
|
|
82
|
+
return fs.readdirSync(dir).filter(file => file.endsWith(".json")).sort().map(file => {
|
|
83
|
+
const source = path.join(dir, file);
|
|
62
84
|
try {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (!["patch", "targeted_minor", "full_migration"].includes(String(value.minimum_upgrade_mode)))
|
|
69
|
-
return [];
|
|
70
|
-
return [{ from_version: value.from_version, to_version: value.to_version, minimum_upgrade_mode: value.minimum_upgrade_mode, domains: value.domains.filter((item) => typeof item === "string"), path_migration: value.path_migration === true, runtime_migration: value.runtime_migration === true, required_checks: value.required_checks.filter((item) => typeof item === "string") }];
|
|
85
|
+
const prepared = prepareJsonFile(source, "Canonical release impact");
|
|
86
|
+
validateSchema({ schemaName: "release-impact", file: prepared.file, data: prepared.value,
|
|
87
|
+
schemaDir: fileURLToPath(new URL("../schemas/", import.meta.url)) });
|
|
88
|
+
return { ...prepared.value, source_reference: { path: prepared.file,
|
|
89
|
+
sha256: crypto.createHash("sha256").update(prepared.text).digest("hex") } };
|
|
71
90
|
}
|
|
72
|
-
catch {
|
|
73
|
-
|
|
91
|
+
catch (error) {
|
|
92
|
+
throw new AppError("release_impact_invalid", "Canonical release impact is invalid; it cannot authorize a lightweight upgrade", 1, { file: source, effect: "no_effect", cause: error instanceof AppError ? error.code : "unreadable_release_impact" });
|
|
74
93
|
}
|
|
75
94
|
});
|
|
76
95
|
}
|
|
@@ -267,7 +286,7 @@ function applyPreparedMigration(context, prepared, writeDb) {
|
|
|
267
286
|
}
|
|
268
287
|
return { ok: true, schema_id: "dd-flow/mb-upgrade-migration-apply@1", status: "applied", changed: true, backup: verifiedBackup, recovery: result };
|
|
269
288
|
}
|
|
270
|
-
export function assertUpgradeRunStorageReady(context, projectRoot) {
|
|
289
|
+
export function assertUpgradeRunStorageReady(context, projectRoot, engine) {
|
|
271
290
|
const project = context.db.get("SELECT id FROM projects WHERE root = ?", [resolveProjectRoot(projectRoot)]);
|
|
272
291
|
if (!project)
|
|
273
292
|
throw new AppError("migration_not_applied", "mb-upgrade RUN requires a registered project with explicit migration apply", 1);
|
|
@@ -277,12 +296,57 @@ export function assertUpgradeRunStorageReady(context, projectRoot) {
|
|
|
277
296
|
if (!source || !target)
|
|
278
297
|
throw new AppError("migration_not_applied", "mb-upgrade RUN requires source and target Memory Bank versions", 1);
|
|
279
298
|
const applied = context.db.get("SELECT value_json FROM runtime_config WHERE key = ?", [applyKey(project.id, source, target)]);
|
|
280
|
-
if (!applied)
|
|
299
|
+
if (!applied || engine !== undefined) {
|
|
300
|
+
const assessment = assessMigrationImpact(context, { projectRoot });
|
|
301
|
+
const blockers = assessment.blockers;
|
|
302
|
+
const chain = assessment.chain;
|
|
303
|
+
if (!blockers.length && (engine !== undefined || chain.every(step => !step.runtime_migration))) {
|
|
304
|
+
const canon = upgradeCanon(context).canon;
|
|
305
|
+
const contract = prepareJsonFile(path.join(canon.flow_root, "flow-contract.json"), "Target flow contract");
|
|
306
|
+
let selectedEngineStorage;
|
|
307
|
+
if (engine !== undefined) {
|
|
308
|
+
if (!(engine instanceof AdmittedFrozenEngine))
|
|
309
|
+
throw new AppError("upgrade_storage_incompatible", "Upgrade requires an admitted selected engine", 1, { effect: "no_effect" });
|
|
310
|
+
const compatibility = prepareJsonFile(path.join(canon.flow_root, "compatibility.json"), "Target engine compatibility");
|
|
311
|
+
validateSchema({ schemaName: "compatibility", file: compatibility.file, data: compatibility.value,
|
|
312
|
+
schemaDir: fileURLToPath(new URL("../schemas/", import.meta.url)) });
|
|
313
|
+
const manifest = engine.manifest();
|
|
314
|
+
if (compatibility.value.memory_bank_version !== target || compatibility.value.dd_flow_cli.flow_contract !== contract.value.id
|
|
315
|
+
|| manifest.package_name !== compatibility.value.dd_flow_cli.package_name || !semver.valid(compatibility.value.dd_flow_cli.min_version)
|
|
316
|
+
|| semver.lt(manifest.package_version, compatibility.value.dd_flow_cli.min_version)
|
|
317
|
+
|| manifest.package_name !== compatibility.value.engine.package_name
|
|
318
|
+
|| !semver.satisfies(manifest.package_version, compatibility.value.engine.version_range, { includePrerelease: true }))
|
|
319
|
+
throw new AppError("upgrade_storage_incompatible", "Selected engine is incompatible with target canon", 1, { effect: "no_effect" });
|
|
320
|
+
selectedEngineStorage = engine.admitMemoryUpgradeStorage(context.ddFlowHome, resourceHome(context));
|
|
321
|
+
}
|
|
322
|
+
// Reuse the executing CLI's physical compatibility checks, not version text
|
|
323
|
+
// or a synthetic apply marker. This does not authorize storage migration.
|
|
324
|
+
const runOwner = fs.lstatSync(context.db.path);
|
|
325
|
+
if (!runOwner.isFile() || runOwner.nlink !== 1)
|
|
326
|
+
throw new AppError("upgrade_storage_incompatible", "Upgrade requires a regular single-linked runtime store", 1, { effect: "no_effect" });
|
|
327
|
+
const run = PreparedMemoryInitRuntimeStorage.prepareUpgrade(context);
|
|
328
|
+
const resource = PreparedMemoryInitResourceStorage.prepare(context);
|
|
329
|
+
const currentOwner = fs.lstatSync(context.db.path);
|
|
330
|
+
if (currentOwner.dev !== runOwner.dev || currentOwner.ino !== runOwner.ino)
|
|
331
|
+
throw new AppError("upgrade_storage_changed", "Runtime store changed during upgrade admission", 1, { effect: "no_effect" });
|
|
332
|
+
const manifest = prepareJsonFile(path.resolve(projectRoot, status.flow_pack.manifest_path), "Current flow-pack provenance");
|
|
333
|
+
return { schema_id: "dd-flow/mb-upgrade-storage-readiness@1", route: "verified-no-op",
|
|
334
|
+
source_version: source, target_version: target, chain,
|
|
335
|
+
current_provenance: { path: manifest.file, sha256: crypto.createHash("sha256").update(manifest.text).digest("hex") },
|
|
336
|
+
target: { root: canon.root, commit: canon.commit, contract_sha256: crypto.createHash("sha256").update(contract.text).digest("hex") },
|
|
337
|
+
...(selectedEngineStorage ? { selected_engine_storage: selectedEngineStorage } : {}),
|
|
338
|
+
stores: [{ ...run.facts(), store_dev: runOwner.dev, store_ino: runOwner.ino }, resource.facts()].map(store => ({ store_kind: store.store_kind,
|
|
339
|
+
store_dev: store.store_dev, store_ino: store.store_ino,
|
|
340
|
+
store_path: store.store_path, schema_sha256: store.schema_sha256,
|
|
341
|
+
executing_writer_version: store.executing_writer_version })) };
|
|
342
|
+
}
|
|
281
343
|
throw new AppError("migration_not_applied", "mb-upgrade RUN requires explicit migration apply before RUN creation", 1, {
|
|
282
344
|
source_version: source,
|
|
283
345
|
target_version: target,
|
|
346
|
+
blockers,
|
|
284
347
|
next_action: "run migration plan, verify and explicit migration apply first"
|
|
285
348
|
});
|
|
349
|
+
}
|
|
286
350
|
const value = recordValue(parseJsonValue(applied.value_json));
|
|
287
351
|
if (!value || !["applied", "verified_noop"].includes(stringValue(value.status) ?? "")) {
|
|
288
352
|
throw new AppError("migration_not_applied", "mb-upgrade RUN has no successful explicit migration apply evidence", 1, {
|
|
@@ -290,6 +354,8 @@ export function assertUpgradeRunStorageReady(context, projectRoot) {
|
|
|
290
354
|
target_version: target
|
|
291
355
|
});
|
|
292
356
|
}
|
|
357
|
+
return { schema_id: "dd-flow/mb-upgrade-storage-readiness@1", route: "existing-apply",
|
|
358
|
+
source_version: source, target_version: target, apply: value };
|
|
293
359
|
}
|
|
294
360
|
export function verifyMigrationReport(_context, input) {
|
|
295
361
|
const filePath = path.resolve(input.file);
|
|
@@ -452,61 +518,45 @@ function hasActiveState(active) {
|
|
|
452
518
|
function applyKey(projectId, source, target) {
|
|
453
519
|
return `mb-upgrade.apply.${projectId}.${source}.${target}`;
|
|
454
520
|
}
|
|
455
|
-
function adjacentMigrationChain(source, target, impacts = []) {
|
|
456
|
-
if (source
|
|
457
|
-
return { status: "ok", steps: [] };
|
|
458
|
-
const sourceParts = parseSemver(source);
|
|
459
|
-
const targetParts = parseSemver(target);
|
|
460
|
-
if (!sourceParts || !targetParts) {
|
|
521
|
+
export function adjacentMigrationChain(source, target, impacts = []) {
|
|
522
|
+
if (semver.valid(source) !== source || semver.valid(target) !== target) {
|
|
461
523
|
return { status: "unsupported", reason: "source or target version is not semver", steps: [] };
|
|
462
524
|
}
|
|
463
|
-
if (
|
|
464
|
-
const steps = [];
|
|
465
|
-
const visited = new Set();
|
|
466
|
-
let current = source;
|
|
467
|
-
while (current !== target) {
|
|
468
|
-
if (visited.has(current))
|
|
469
|
-
return { status: "unsupported", reason: "release impact chain contains a cycle", steps: [] };
|
|
470
|
-
visited.add(current);
|
|
471
|
-
const impact = impacts.find((item) => item.from_version === current);
|
|
472
|
-
if (!impact)
|
|
473
|
-
return { status: "unsupported", reason: `missing adjacent release impact from ${current}`, steps: [] };
|
|
474
|
-
steps.push({
|
|
475
|
-
id: `${impact.from_version}-to-${impact.to_version}`,
|
|
476
|
-
from: impact.from_version,
|
|
477
|
-
to: impact.to_version,
|
|
478
|
-
status: "planned",
|
|
479
|
-
minimum_upgrade_mode: impact.minimum_upgrade_mode,
|
|
480
|
-
domains: impact.domains,
|
|
481
|
-
path_migration: impact.path_migration,
|
|
482
|
-
runtime_migration: impact.runtime_migration,
|
|
483
|
-
required_checks: impact.required_checks
|
|
484
|
-
});
|
|
485
|
-
current = impact.to_version;
|
|
486
|
-
const next = parseSemver(current);
|
|
487
|
-
if (!next || compareParts(next, targetParts) > 0) {
|
|
488
|
-
return { status: "unsupported", reason: `release impact chain overshoots target ${target}`, steps: [] };
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
return { status: "ok", steps };
|
|
492
|
-
}
|
|
493
|
-
if (sourceParts.major !== targetParts.major) {
|
|
494
|
-
return { status: "unsupported", reason: "major version migration requires canonical release-impact units", steps: [] };
|
|
495
|
-
}
|
|
496
|
-
if (targetParts.minor < sourceParts.minor || (targetParts.minor === sourceParts.minor && targetParts.patch < sourceParts.patch)) {
|
|
525
|
+
if (semver.gt(source, target))
|
|
497
526
|
return { status: "unsupported", reason: "downgrade migrations are not supported", steps: [] };
|
|
498
|
-
|
|
527
|
+
if (source === target)
|
|
528
|
+
return { status: "ok", steps: [] };
|
|
499
529
|
const steps = [];
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
530
|
+
const visited = new Set();
|
|
531
|
+
let current = source;
|
|
532
|
+
while (current !== target) {
|
|
533
|
+
if (visited.has(current))
|
|
534
|
+
return { status: "unsupported", reason: "release impact chain contains a cycle", steps: [] };
|
|
535
|
+
visited.add(current);
|
|
536
|
+
const matches = impacts.filter(item => item.from_version === current);
|
|
537
|
+
if (matches.length > 1)
|
|
538
|
+
return { status: "unsupported", reason: `ambiguous adjacent release impacts from ${current}`, steps: [] };
|
|
539
|
+
const impact = matches[0];
|
|
540
|
+
if (!impact)
|
|
541
|
+
return { status: "unsupported", reason: `missing adjacent release impact from ${current}`, steps: [] };
|
|
542
|
+
if (semver.valid(impact.to_version) !== impact.to_version || !semver.gt(impact.to_version, current)) {
|
|
543
|
+
return { status: "unsupported", reason: "release impact chain must advance through valid versions", steps: [] };
|
|
544
|
+
}
|
|
545
|
+
if (semver.gt(impact.to_version, target))
|
|
546
|
+
return { status: "unsupported", reason: `release impact chain overshoots target ${target}`, steps: [] };
|
|
503
547
|
steps.push({
|
|
504
|
-
id: `${
|
|
505
|
-
from:
|
|
506
|
-
to:
|
|
507
|
-
status: "planned"
|
|
548
|
+
id: `${impact.from_version}-to-${impact.to_version}`,
|
|
549
|
+
from: impact.from_version,
|
|
550
|
+
to: impact.to_version,
|
|
551
|
+
status: "planned",
|
|
552
|
+
minimum_upgrade_mode: impact.minimum_upgrade_mode,
|
|
553
|
+
domains: impact.domains,
|
|
554
|
+
path_migration: impact.path_migration,
|
|
555
|
+
runtime_migration: impact.runtime_migration,
|
|
556
|
+
required_checks: impact.required_checks,
|
|
557
|
+
...(impact.source_reference ? { source_reference: impact.source_reference } : {})
|
|
508
558
|
});
|
|
509
|
-
current =
|
|
559
|
+
current = impact.to_version;
|
|
510
560
|
}
|
|
511
561
|
return { status: "ok", steps };
|
|
512
562
|
}
|
|
@@ -556,24 +606,3 @@ function recordValue(value) {
|
|
|
556
606
|
function stringValue(value) {
|
|
557
607
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
558
608
|
}
|
|
559
|
-
function parseSemver(value) {
|
|
560
|
-
const match = value.match(/^([0-9]+)\.([0-9]+)\.([0-9]+)/);
|
|
561
|
-
if (!match?.[1] || !match[2] || !match[3])
|
|
562
|
-
return null;
|
|
563
|
-
return { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]) };
|
|
564
|
-
}
|
|
565
|
-
function compareParts(left, right) {
|
|
566
|
-
if (left.major !== right.major)
|
|
567
|
-
return left.major - right.major;
|
|
568
|
-
if (left.minor !== right.minor)
|
|
569
|
-
return left.minor - right.minor;
|
|
570
|
-
return left.patch - right.patch;
|
|
571
|
-
}
|
|
572
|
-
function nextAdjacent(current, target) {
|
|
573
|
-
if (current.minor < target.minor)
|
|
574
|
-
return { major: current.major, minor: current.minor + 1, patch: 0 };
|
|
575
|
-
return { major: current.major, minor: current.minor, patch: Math.min(current.patch + 1, target.patch) };
|
|
576
|
-
}
|
|
577
|
-
function formatParts(parts) {
|
|
578
|
-
return `${parts.major}.${parts.minor}.${parts.patch}`;
|
|
579
|
-
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
2
|
+
import { AppError } from "../shared/errors.js";
|
|
3
|
+
import { createRenewalState, maintenanceRetryable, renewalDelay, renewalFailure, RENEWAL_POLICY } from "../harness-runtime/lib/lease-renewal.mjs";
|
|
4
|
+
import { processStartedAt } from "./managed-processes.js";
|
|
5
|
+
/** Birth and confirmation share one startup horizon. Retrying observations
|
|
6
|
+
* neither launches another child nor renews an unconfirmed resource lease. */
|
|
7
|
+
export function physicalStartup(databaseDeadlineAt) {
|
|
8
|
+
const deadline = performance.now() + RENEWAL_POLICY.startupMs;
|
|
9
|
+
const episode = createRenewalState();
|
|
10
|
+
const unavailable = () => new AppError("process_ownership_unknown", "Managed physical identity observation is unavailable", 1, { phase: "process_confirmation", effect: "no_effect", native_dispatch_started: false, reason: "physical_identity_unavailable" });
|
|
11
|
+
async function observe(operation, assertCurrent, leaseExpiresAt) {
|
|
12
|
+
let last;
|
|
13
|
+
const remaining = () => Math.max(0, Math.min(deadline - performance.now(), databaseDeadlineAt === undefined ? Infinity : databaseDeadlineAt - Date.now(), leaseExpiresAt === undefined ? Infinity : Date.parse(leaseExpiresAt) - Date.now() - RENEWAL_POLICY.safetyMs));
|
|
14
|
+
for (;;) {
|
|
15
|
+
assertCurrent();
|
|
16
|
+
if (!(remaining() > 0))
|
|
17
|
+
throw last ?? unavailable();
|
|
18
|
+
try {
|
|
19
|
+
const result = operation();
|
|
20
|
+
assertCurrent();
|
|
21
|
+
if (!(remaining() > 0))
|
|
22
|
+
throw new AppError("process_startup_expired", "Physical observation completed after its original admission horizon", 1, { phase: "process_confirmation", effect: "unknown", native_dispatch_started: false });
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
// A stale owner/cancellation wins over a transient observation error.
|
|
27
|
+
assertCurrent();
|
|
28
|
+
const details = error instanceof AppError ? error.details : null;
|
|
29
|
+
if (!(error instanceof AppError) || error.code !== "process_ownership_unknown" || details?.reason !== "physical_identity_unavailable" || !maintenanceRetryable(error))
|
|
30
|
+
throw error;
|
|
31
|
+
last = error;
|
|
32
|
+
renewalFailure(episode);
|
|
33
|
+
if (!(remaining() > 0))
|
|
34
|
+
throw error;
|
|
35
|
+
await delay(Math.min(renewalDelay(episode), remaining()));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return { observe, birth: (assertCurrent) => observe(() => {
|
|
40
|
+
const started = processStartedAt(process.pid);
|
|
41
|
+
if (!started)
|
|
42
|
+
throw unavailable();
|
|
43
|
+
return { pid: process.pid, pid_started_at: started };
|
|
44
|
+
}, assertCurrent) };
|
|
45
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { AppError } from "../shared/errors.js";
|
|
4
|
+
import { projectRuntimeRoot, resolveProjectRoot } from "../storage/paths.js";
|
|
5
|
+
import { formatFullId, parseFullEntityId, slugFromRoot } from "../domain/entity-ids.js";
|
|
6
|
+
import { appendAudit } from "./audit.js";
|
|
7
|
+
/** Resolve filesystem identity before entering a composite SQL writer. */
|
|
8
|
+
export class PreparedProjectRegistration {
|
|
9
|
+
#root;
|
|
10
|
+
constructor(root) { this.#root = root; Object.freeze(this); }
|
|
11
|
+
static prepare(input) {
|
|
12
|
+
return new PreparedProjectRegistration(resolveProjectRoot(input.root));
|
|
13
|
+
}
|
|
14
|
+
root() { return this.#root; }
|
|
15
|
+
}
|
|
16
|
+
export function registerProject(context, input) {
|
|
17
|
+
return registerPreparedProject(context, PreparedProjectRegistration.prepare(input));
|
|
18
|
+
}
|
|
19
|
+
/** Metadata-only registration; the composite caller owns its SQL transaction. */
|
|
20
|
+
export function registerPreparedProject(context, prepared) {
|
|
21
|
+
if (!(prepared instanceof PreparedProjectRegistration))
|
|
22
|
+
throw new AppError("validation", "Project registration must be prepared", 2);
|
|
23
|
+
const root = prepared.root();
|
|
24
|
+
const existing = context.db.get("SELECT * FROM projects WHERE root = ?", [root]);
|
|
25
|
+
const now = context.now();
|
|
26
|
+
const project = existing ?? newProjectRecord(context, root, now);
|
|
27
|
+
if (existing) {
|
|
28
|
+
context.db.run("UPDATE projects SET status = 'active', updated_at = ? WHERE id = ?", [now, existing.id]);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
context.db.run("INSERT INTO projects (id, short_id, slug, root, status, state_root, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [
|
|
32
|
+
project.id,
|
|
33
|
+
project.short_id,
|
|
34
|
+
project.slug,
|
|
35
|
+
project.root,
|
|
36
|
+
project.status,
|
|
37
|
+
project.state_root,
|
|
38
|
+
project.created_at,
|
|
39
|
+
project.updated_at
|
|
40
|
+
]);
|
|
41
|
+
appendAudit(context, {
|
|
42
|
+
projectId: project.id,
|
|
43
|
+
eventType: "project.registered",
|
|
44
|
+
payload: { project_id: project.id, root }
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return { ok: true, project: { ...project, status: "active", updated_at: now }, db_path: context.db.path };
|
|
48
|
+
}
|
|
49
|
+
export function normalizeStoredRoot(root) {
|
|
50
|
+
const absolute = path.resolve(root);
|
|
51
|
+
if (fs.existsSync(absolute)) {
|
|
52
|
+
return fs.realpathSync(absolute);
|
|
53
|
+
}
|
|
54
|
+
const parent = path.dirname(absolute);
|
|
55
|
+
if (fs.existsSync(parent)) {
|
|
56
|
+
return path.join(fs.realpathSync(parent), path.basename(absolute));
|
|
57
|
+
}
|
|
58
|
+
return absolute;
|
|
59
|
+
}
|
|
60
|
+
export function findProjectByRoot(context, root) {
|
|
61
|
+
return context.db.get("SELECT * FROM projects WHERE root = ?", [root]);
|
|
62
|
+
}
|
|
63
|
+
export function requireProjectByRoot(context, root) {
|
|
64
|
+
// Callers may receive a path through a native adapter or shell. Resolve
|
|
65
|
+
// aliases such as macOS `/var` -> `/private/var` before comparing with the
|
|
66
|
+
// canonical root persisted in the project registry.
|
|
67
|
+
const canonical = normalizeStoredRoot(root);
|
|
68
|
+
const project = findProjectByRoot(context, canonical);
|
|
69
|
+
if (!project) {
|
|
70
|
+
throw new AppError("not_found", `Project is not registered: ${canonical}`, 1);
|
|
71
|
+
}
|
|
72
|
+
return project;
|
|
73
|
+
}
|
|
74
|
+
export function newProjectRecord(context, root, now) {
|
|
75
|
+
const slug = slugFromRoot(root);
|
|
76
|
+
const id = nextFullProjectId(context, slug);
|
|
77
|
+
const { shortId } = parseFullEntityId(id);
|
|
78
|
+
return {
|
|
79
|
+
id,
|
|
80
|
+
short_id: shortId,
|
|
81
|
+
slug,
|
|
82
|
+
root,
|
|
83
|
+
status: "active",
|
|
84
|
+
state_root: projectRuntimeRoot(context.ddFlowHome, id),
|
|
85
|
+
created_at: now,
|
|
86
|
+
updated_at: now
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export function nextFullProjectId(context, slug) {
|
|
90
|
+
const rows = context.db.all("SELECT id FROM projects WHERE id LIKE 'PRJ-%'");
|
|
91
|
+
const used = rows
|
|
92
|
+
.map((row) => /^PRJ-(\d{3})-/.exec(row.id)?.[1])
|
|
93
|
+
.filter((value) => Boolean(value))
|
|
94
|
+
.map((value) => Number(value));
|
|
95
|
+
const next = Math.max(0, ...used) + 1;
|
|
96
|
+
return formatFullId("PRJ", next, slug);
|
|
97
|
+
}
|