@deksden-com/dd-flow-cli 0.9.0-beta.144 → 0.9.0-beta.149
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 +130 -0
- package/README.md +13 -8
- package/dist/build-info.json +7 -7
- package/dist/cli/command-inputs.js +13 -3
- package/dist/cli/command-metadata.js +51 -16
- package/dist/cli/diagnostic-invocation.js +46 -0
- package/dist/cli/diagnostic.js +120 -0
- package/dist/cli/help.js +67 -1
- package/dist/cli/hook-ingress.js +27 -3
- package/dist/cli/memory-flow-admission.js +9 -3
- package/dist/cli/memory-init-invocation.js +10 -3
- package/dist/cli/memory-init.js +77 -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 +79 -40
- package/dist/cli/setup.js +2 -1
- package/dist/cli.js +18 -0
- package/dist/domain/diagnostic-aspects.js +103 -0
- package/dist/domain/diagnostic-flows.js +8 -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/agy-native-inspection.mjs +216 -0
- package/dist/harness-runtime/lib/agy-native_pb.js +117 -0
- package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +84 -10
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +366 -52
- package/dist/harness-runtime/lib/dd-agy.mjs +17 -3
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +27 -13
- package/dist/harness-runtime/lib/dd-codex.mjs +4 -0
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +4 -3
- package/dist/harness-runtime/lib/dd-droid.mjs +71 -29
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +73 -16
- package/dist/harness-runtime/lib/dd-grok.mjs +6 -3
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +66 -20
- package/dist/harness-runtime/lib/dd-opencode.mjs +30 -3
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +7 -6
- 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/driver-recovery.mjs +9 -8
- 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.d.mts +1 -1
- package/dist/harness-runtime/lib/managed-daemon.mjs +24 -9
- package/dist/harness-runtime/lib/native-children.d.mts +8 -1
- package/dist/harness-runtime/lib/native-children.mjs +114 -8
- package/dist/harness-runtime/lib/native-hook-command.mjs +4 -4
- package/dist/harness-runtime/lib/native-request-proof.mjs +1 -1
- package/dist/harness-runtime/lib/observation-clock.d.mts +2 -2
- package/dist/harness-runtime/lib/observation-clock.mjs +7 -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 +5 -3
- package/dist/harness-runtime/lib/provider-overload-policy.mjs +20 -7
- package/dist/harness-runtime/lib/runner-events.mjs +3 -1
- package/dist/harness-runtime/lib/setup-progress.mjs +8 -6
- package/dist/harness-runtime/lib/tool-observations.mjs +1 -1
- package/dist/schemas/diagnostic-stage-result.schema.json +354 -0
- package/dist/schemas/diagnostic-work-result.schema.json +296 -0
- package/dist/schemas/execution-bundle.schema.json +7 -1
- 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/project-flow-pack-manifest.schema.json +1 -1
- 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/check-environment.js +21 -0
- package/dist/services/check-runner-evidence.js +267 -15
- package/dist/services/cli-operation-classifier.js +1 -1
- package/dist/services/cli-upgrade-allowlist.js +1 -1
- package/dist/services/code-checks.js +34 -19
- package/dist/services/compatibility-preflight.js +5 -2
- package/dist/services/controller-execution.js +10 -4
- package/dist/services/controller-fanout.js +34 -10
- package/dist/services/controller-memory-task.js +45 -0
- package/dist/services/controller-memory-worker.js +49 -0
- package/dist/services/dashboard.js +2 -2
- package/dist/services/descriptor-run-settlement.js +5 -3
- package/dist/services/descriptor-run-start.js +46 -20
- package/dist/services/descriptor-stage-coordinator.js +66 -13
- package/dist/services/descriptor-stage-settlement.js +2 -1
- package/dist/services/descriptor-stage-start.js +40 -4
- package/dist/services/diagnostic-evidence.js +73 -0
- package/dist/services/diagnostic-report-publication.js +72 -0
- package/dist/services/diagnostic-report.js +67 -0
- package/dist/services/diagnostic-run-bundle.js +74 -0
- package/dist/services/diagnostic-snapshot.js +159 -0
- package/dist/services/diagnostic-stage-handlers.js +108 -0
- package/dist/services/diagnostic-stage-results.js +132 -0
- package/dist/services/diagnostic-start.js +84 -0
- package/dist/services/diagnostic-work-batch.js +34 -0
- package/dist/services/diagnostic-work-collection.js +97 -0
- package/dist/services/diagnostic-work-registration.js +101 -0
- package/dist/services/diagnostic-work-results.js +93 -0
- package/dist/services/engines.js +64 -14
- package/dist/services/eval-snapshots.js +186 -119
- package/dist/services/execution-bundles.js +19 -7
- package/dist/services/external-work-launch.js +5 -3
- package/dist/services/flow-catalog.js +13 -4
- package/dist/services/frozen-engine.js +148 -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 +4 -13
- package/dist/services/hybrid-digest.js +14 -0
- package/dist/services/installed-flow-source.js +47 -4
- package/dist/services/lifecycle-invocations.js +179 -38
- 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 +44 -2
- package/dist/services/memory-init-controller-start.js +27 -8
- package/dist/services/memory-init-git-policy.js +50 -21
- package/dist/services/memory-init-lint-capability.js +24 -6
- package/dist/services/memory-init-native-capacity.js +17 -4
- package/dist/services/memory-init-preflight.js +21 -17
- 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-init-worktrunk-capability.js +3 -7
- package/dist/services/memory-layer.js +71 -6
- 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-tool-probe.js +25 -0
- 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 +92 -27
- 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/native-daemon-history.js +2 -1
- 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 +112 -46
- package/dist/services/recovery-snapshot-database.js +32 -4
- package/dist/services/release-cache.js +8 -0
- 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-control.js +2 -1
- package/dist/services/run-controller-adapter.js +2 -1
- package/dist/services/run-controller-process.js +4 -7
- package/dist/services/run-controller-recovery.js +8 -2
- package/dist/services/run-controller.js +215 -94
- package/dist/services/run-fork.js +1 -1
- 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/runs.js +6 -1
- package/dist/services/runtime-scope-resume.js +12 -6
- package/dist/services/runtime-scope-stop.js +3 -2
- package/dist/services/runtime-scope-worker.js +46 -19
- package/dist/services/runtime-service.js +2 -1
- package/dist/services/sessions.js +3 -24
- package/dist/services/setup.js +63 -13
- package/dist/services/stage-handlers.js +22 -7
- package/dist/services/stage-pause.js +111 -44
- package/dist/services/version-status.js +1 -1
- package/dist/services/vnext-code.js +2 -2
- package/dist/services/vnext-fanout.js +51 -9
- package/dist/services/vnext-plan-review.js +3 -5
- package/dist/services/work-registry.js +117 -39
- package/dist/services/workspace-bootstrap.js +2 -1
- 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 +13 -6
|
@@ -12,19 +12,8 @@ const hybridContractRegistry = new Set(hybridContractNames);
|
|
|
12
12
|
const record = (input) => input;
|
|
13
13
|
const list = (input) => input;
|
|
14
14
|
const string = (input) => input;
|
|
15
|
-
|
|
16
|
-
export
|
|
17
|
-
const canonical = (value) => {
|
|
18
|
-
if (Array.isArray(value))
|
|
19
|
-
return value.map(canonical);
|
|
20
|
-
if (value !== null && typeof value === "object") {
|
|
21
|
-
return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b, "en"))
|
|
22
|
-
.map(([key, child]) => [key, canonical(child)]));
|
|
23
|
-
}
|
|
24
|
-
return value;
|
|
25
|
-
};
|
|
26
|
-
return createHash("sha256").update(JSON.stringify(canonical(input))).digest("hex");
|
|
27
|
-
}
|
|
15
|
+
import { hybridDigest } from "./hybrid-digest.js";
|
|
16
|
+
export { hybridDigest };
|
|
28
17
|
/** E1 standalone validation. Runtime ingress must explicitly integrate it in E2/E3. */
|
|
29
18
|
export function validateHybridContract(name, input, context = {}) {
|
|
30
19
|
if (name === "flow-descriptor") {
|
|
@@ -260,6 +249,8 @@ export function validateHybridContract(name, input, context = {}) {
|
|
|
260
249
|
fail("foreign_bundle_canon", "/canon");
|
|
261
250
|
if (hybridDigest(data.project_pack) !== hybridDigest(context.bundleProvenance.project_pack))
|
|
262
251
|
fail("foreign_bundle_project_pack", "/project_pack");
|
|
252
|
+
if ((data.flow_id === "mb-audit" || data.flow_id === "project-review") !== (data.project_pack === null))
|
|
253
|
+
fail("invalid_bundle_project_pack_role", "/project_pack");
|
|
263
254
|
const memoryFlow = data.flow_id === "mb-init" || data.flow_id === "mb-upgrade";
|
|
264
255
|
if (memoryFlow && context.bootstrapIdentity?.projectRoot !== context.bundleProjectRoot)
|
|
265
256
|
fail("foreign_bootstrap_identity", "/bootstrap_receipts");
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
/** Deterministic JSON content identity, not a signature or secret-derived hash. */
|
|
3
|
+
export function hybridDigest(input) {
|
|
4
|
+
const canonical = (value) => {
|
|
5
|
+
if (Array.isArray(value))
|
|
6
|
+
return value.map(canonical);
|
|
7
|
+
if (value !== null && typeof value === "object") {
|
|
8
|
+
return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b, "en"))
|
|
9
|
+
.map(([key, child]) => [key, canonical(child)]));
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
};
|
|
13
|
+
return createHash("sha256").update(JSON.stringify(canonical(input))).digest("hex");
|
|
14
|
+
}
|
|
@@ -6,7 +6,9 @@ import { readSetupStatus } from "./setup.js";
|
|
|
6
6
|
import { readBoundedReleaseFile } from "./release-source.js";
|
|
7
7
|
import { hybridDigest, validateHybridShape } from "./hybrid-contracts.js";
|
|
8
8
|
import { admitBundledFlow } from "./stage-handlers.js";
|
|
9
|
+
import { isDiagnosticFlow } from "../domain/diagnostic-flows.js";
|
|
9
10
|
const limit = 1024 * 1024;
|
|
11
|
+
const flowIds = ["mb-init", "mb-upgrade", "mb-audit", "project-review"];
|
|
10
12
|
const hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
11
13
|
function refuse(reason) {
|
|
12
14
|
throw new AppError("installed_flow_source_invalid", "Installed flow source is not the selected verified canon", 1, { reason, phase: "prepare", effect: "no_effect" });
|
|
@@ -50,7 +52,7 @@ export class PreparedInstalledFlowSource {
|
|
|
50
52
|
}
|
|
51
53
|
static fromInstalledRelease(home, expectedInstallationSha256, flowId) {
|
|
52
54
|
try {
|
|
53
|
-
if (!path.isAbsolute(home) || !/^[a-f0-9]{64}$/.test(expectedInstallationSha256) || flowId
|
|
55
|
+
if (!path.isAbsolute(home) || !/^[a-f0-9]{64}$/.test(expectedInstallationSha256) || !flowIds.includes(flowId))
|
|
54
56
|
refuse("invalid_selection");
|
|
55
57
|
const selected = readSetupStatus(home);
|
|
56
58
|
if (!selected.installation || selected.installation_readiness !== "installed")
|
|
@@ -95,6 +97,26 @@ export class PreparedInstalledFlowSource {
|
|
|
95
97
|
folded.set(reference.path.toLowerCase(), reference.path);
|
|
96
98
|
blobs.set(reference.path, Buffer.from(content));
|
|
97
99
|
}
|
|
100
|
+
if (isDiagnosticFlow(flowId)) {
|
|
101
|
+
const support = ["mbb/index.md", "mbb/memory-bank-structure.md", "mbb/frontmatter-standards.md", "mbb/cross-references.md", "mbb/named-deferrals-guide.md",
|
|
102
|
+
...fs.readdirSync(path.join(memoryRoot, "mbb/aspects")).filter(name => name.endsWith(".md")).map(name => `mbb/aspects/${name}`),
|
|
103
|
+
"dd-flow/mb-sdlc/review/aspects.md", "dd-flow/mb-sdlc/review/critics/universal.md", "dd-flow/mb-sdlc/review/critics/architectural-harmonization.md",
|
|
104
|
+
...fs.readdirSync(path.join(flowRoot, "mb-audit/aspects")).filter(name => name.endsWith(".md")).map(name => `dd-flow/mb-audit/aspects/${name}`)];
|
|
105
|
+
for (const logical of support) {
|
|
106
|
+
const content = readBoundedReleaseFile(containedFile(memoryRoot, logical), limit), name = `support/${logical}`;
|
|
107
|
+
if (!content.length || (total += content.length) > 32 * limit || folded.has(name.toLowerCase()))
|
|
108
|
+
refuse("invalid_support_inventory");
|
|
109
|
+
new TextDecoder("utf-8", { fatal: true }).decode(content);
|
|
110
|
+
folded.set(name.toLowerCase(), name);
|
|
111
|
+
blobs.set(name, Buffer.from(content));
|
|
112
|
+
}
|
|
113
|
+
const workSchema = "schemas/diagnostic-work-result.schema.json";
|
|
114
|
+
const content = readBoundedReleaseFile(containedFile(flowRoot, workSchema), limit);
|
|
115
|
+
if (!content.length || (total += content.length) > 32 * limit || folded.has(workSchema.toLowerCase()))
|
|
116
|
+
refuse("invalid_support_inventory");
|
|
117
|
+
JSON.parse(content.toString("utf8"));
|
|
118
|
+
blobs.set(workSchema, Buffer.from(content));
|
|
119
|
+
}
|
|
98
120
|
// Verify compressed/expanded archive identity again after collecting bytes.
|
|
99
121
|
// A coherent descriptor+prompt edit cannot become a new trusted selection.
|
|
100
122
|
const final = readSetupStatus(home);
|
|
@@ -120,6 +142,18 @@ export class PreparedInstalledFlowSource {
|
|
|
120
142
|
* or admit its engine/storage; those remain the bootstrap owner's duties.
|
|
121
143
|
*/
|
|
122
144
|
static restoreRetained(input, expectedSha256) {
|
|
145
|
+
const result = this.restoreRetainedData(input, expectedSha256);
|
|
146
|
+
try {
|
|
147
|
+
result.resolveRegisteredFlow();
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return refuse("invalid_retained_source");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/** Data recovery before storage/engine admission. This cannot register a
|
|
155
|
+
* handler; productive restoration still uses restoreRetained above. */
|
|
156
|
+
static restoreRetainedData(input, expectedSha256) {
|
|
123
157
|
try {
|
|
124
158
|
if (!input || typeof input !== "object" || Array.isArray(input) || typeof expectedSha256 !== "string"
|
|
125
159
|
|| !/^[a-f0-9]{64}$/.test(expectedSha256))
|
|
@@ -132,7 +166,7 @@ export class PreparedInstalledFlowSource {
|
|
|
132
166
|
|| typeof value.canon.archive_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(value.canon.archive_sha256)
|
|
133
167
|
|| typeof value.canon.version !== "string" || !/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/.test(value.canon.version)
|
|
134
168
|
|| !value.descriptor || Object.keys(value.descriptor).sort().join() !== "path,sha256"
|
|
135
|
-
|| value.descriptor.path
|
|
169
|
+
|| !flowIds.some(id => value.descriptor.path === `${id}/flow-descriptor.json`) || typeof value.descriptor.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(value.descriptor.sha256)
|
|
136
170
|
|| !Array.isArray(value.blobs) || value.blobs.length < 1 || value.blobs.length > 129)
|
|
137
171
|
refuse("invalid_retained_source");
|
|
138
172
|
const blobs = new Map(), folded = new Set();
|
|
@@ -154,10 +188,19 @@ export class PreparedInstalledFlowSource {
|
|
|
154
188
|
const descriptor = blobs.get(value.descriptor.path);
|
|
155
189
|
if (!descriptor || hash(descriptor) !== value.descriptor.sha256)
|
|
156
190
|
refuse("retained_source_changed");
|
|
157
|
-
const
|
|
191
|
+
const flowId = value.descriptor.path.slice(0, -"/flow-descriptor.json".length);
|
|
192
|
+
const declared = JSON.parse(descriptor.toString("utf8"));
|
|
193
|
+
if (validateHybridShape("flow-descriptor", declared).length || declared.id !== flowId || declared.origin !== "canon")
|
|
194
|
+
refuse("invalid_retained_source");
|
|
195
|
+
for (const stage of declared.stages)
|
|
196
|
+
for (const kind of ["instruction", "result_schema"]) {
|
|
197
|
+
const reference = stage[kind], content = blobs.get(reference.path);
|
|
198
|
+
if (!content || hash(content) !== reference.sha256)
|
|
199
|
+
refuse("retained_source_changed");
|
|
200
|
+
}
|
|
201
|
+
const result = new PreparedInstalledFlowSource(null, value.installation_sha256, flowId, { ...value.descriptor }, { ...value.canon }, blobs);
|
|
158
202
|
if (result.sha256 !== expectedSha256)
|
|
159
203
|
refuse("retained_source_changed");
|
|
160
|
-
result.resolveRegisteredFlow();
|
|
161
204
|
return result;
|
|
162
205
|
}
|
|
163
206
|
catch (error) {
|
|
@@ -8,16 +8,15 @@ import { AppError, errorRecord } from "../shared/errors.js";
|
|
|
8
8
|
import { shortEntityReference } from "../shared/entity-references.js";
|
|
9
9
|
import { resolveWorkReference } from "../storage/work-references.js";
|
|
10
10
|
import { commandOption, commandPosition, continuationCommand, lifecycleCommandFromPayload, parseLifecycleCommand, shellSuffixOffset } from "./lifecycle-command.js";
|
|
11
|
-
import { requireProjectByRoot } from "./
|
|
12
|
-
import { assertRunMutationAllowed, assertStageSettlementAllowed, assertWorkSettlementAllowed, recoveryGuard, recoveryPacketReceipt } from "./run-recovery.js";
|
|
11
|
+
import { requireProjectByRoot } from "./project-registry.js";
|
|
12
|
+
import { assertRunMutationAllowed, assertStageSettlementAllowed, assertWorkSettlementAllowed, recoveryGuard, recoveryPacketReceipt } from "./run-recovery-guard.js";
|
|
13
13
|
import { readNativeDaemonState, requireManagedDaemonBinding } from "./managed-daemon-binding.js";
|
|
14
14
|
import { managedProcessStatus, processIsAlive, processStartedAt } from "./managed-processes.js";
|
|
15
15
|
import { appendEvent } from "./run-controller-state.js";
|
|
16
|
-
import { recoverCommittedWorkStart } from "./work-registry.js";
|
|
17
16
|
import { observeCommandHook } from "./hooks.js";
|
|
18
17
|
import { runtimeHarness } from "./execution-policy.js";
|
|
19
18
|
import { normalizedPathAlias, resolveContextualPaths } from "./command-context.js";
|
|
20
|
-
import { isRegisteredRepair } from "./repair-
|
|
19
|
+
import { isRegisteredRepair } from "./repair-receipt.js";
|
|
21
20
|
import { stageAttempt } from "./stage-work-graph.js";
|
|
22
21
|
import { contextualPathOptionsForLifecycle } from "../cli/command-inputs.js";
|
|
23
22
|
import { canonicalPath } from "../storage/paths.js";
|
|
@@ -25,6 +24,9 @@ import { runtimeCommand, shellArgument } from "./runtime-command.js";
|
|
|
25
24
|
// Process-local custody: an executing SQL row or ambient variable alone cannot
|
|
26
25
|
// recreate admission after a CLI restart, or in another resource database.
|
|
27
26
|
const currentAdmissions = new WeakMap();
|
|
27
|
+
// A same-stage finish continuation is not authority until its predecessor's
|
|
28
|
+
// outcome commits. Process death before that commit leaves no issued successor.
|
|
29
|
+
const stagedContinuations = new WeakMap();
|
|
28
30
|
export function currentLifecycleAdmission(context, id) {
|
|
29
31
|
const owned = currentAdmissions.get(context.db)?.get(id);
|
|
30
32
|
if (!owned || context.env.DD_FLOW_CURRENT_INVOCATION !== id || owned.pid !== process.pid
|
|
@@ -380,8 +382,9 @@ export function assertLifecycleOutcomes(context, scope) {
|
|
|
380
382
|
AND NOT EXISTS (SELECT 1 FROM lifecycle_invocations newer WHERE newer.scope_json = a.scope_json AND newer.fingerprint = a.fingerprint AND newer.rowid > a.rowid)
|
|
381
383
|
ORDER BY a.rowid`, [scope.projectRoot, scope.runId, scope.generation]);
|
|
382
384
|
for (const row of rows) {
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
+
const outcome = row.outcome_json ? JSON.parse(row.outcome_json) : null;
|
|
386
|
+
const error = outcome?.error;
|
|
387
|
+
if (typeof error?.code === "string" && !committedSuccessor(context, row, outcome) && !isCorrectableLifecycleRejection(error.code, error.details) && !isRegisteredRepair(context, error.details?.continuation, { projectId: project.id, runId: scope.runId }))
|
|
385
388
|
throw new AppError(error.code, typeof error.message === "string" ? error.message : "Mandatory lifecycle failed", 1, { ...(error.details ?? {}), invocation_id: row.id });
|
|
386
389
|
}
|
|
387
390
|
}
|
|
@@ -490,14 +493,27 @@ export function observeLifecycleCommand(context, command, nativeEvent) {
|
|
|
490
493
|
}
|
|
491
494
|
}
|
|
492
495
|
const registeredContinuation = error && registeredLifecycleContinuation(context, error, anchor.project_id, anchor.run_id);
|
|
493
|
-
const
|
|
494
|
-
&& typeof error.details.rejection_signature === "string"
|
|
496
|
+
const retainedRejections = error && typeof error.details.retry_command === "string"
|
|
495
497
|
&& context.db.get("SELECT 1 FROM sqlite_master WHERE name = 'lifecycle_invocations'")
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
AND json_extract(a.scope_json,'$.
|
|
499
|
-
AND json_extract(a.scope_json,'$.generation') = ? AND json_extract(a.outcome_json,'$.error.
|
|
500
|
-
AND json_extract(a.outcome_json,'$.error.details.
|
|
498
|
+
? context.db.all(`SELECT a.* FROM lifecycle_invocations a JOIN hook_events h ON h.event_key = a.event_key
|
|
499
|
+
WHERE h.id = ? AND h.project_id = ? AND a.status = 'settled'
|
|
500
|
+
AND json_extract(a.scope_json,'$.runId') = ?
|
|
501
|
+
AND json_extract(a.scope_json,'$.generation') = ? AND json_extract(a.outcome_json,'$.error.code') = ?
|
|
502
|
+
AND json_extract(a.outcome_json,'$.error.details.retry_command') = ?`, [anchor.hook_event_id, anchor.project_id, anchor.run_id, anchor.generation, error.code, error.details.retry_command]) : [];
|
|
503
|
+
const projectRoot = context.db.get("SELECT root FROM projects WHERE id = ?", [anchor.project_id])?.root;
|
|
504
|
+
const ownedRejections = retainedRejections.filter(row => {
|
|
505
|
+
try {
|
|
506
|
+
return projectRoot !== undefined && canonicalPath(JSON.parse(row.scope_json).projectRoot, false) === projectRoot;
|
|
507
|
+
}
|
|
508
|
+
catch {
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
const retainedRejection = ownedRejections.length === 1 ? ownedRejections[0] : undefined;
|
|
513
|
+
// A committed exact successor is authority; reclassifying its sanitized
|
|
514
|
+
// error would discard the original validated check capability again.
|
|
515
|
+
const retainedSuccessor = retainedRejection && (committedSuccessor(context, retainedRejection, JSON.parse(retainedRejection.outcome_json ?? "null"))
|
|
516
|
+
?? (error && currentIssuedCommand(context, retainedRejection.scope_json, String(error.details.retry_command))));
|
|
501
517
|
const hookOnlyArgumentDefect = error && !invocationId && !context.env.DD_FLOW_INVOCATION_SCOPE
|
|
502
518
|
&& !retainedSuccessor
|
|
503
519
|
&& ["missing_required_option", "missing_option_value", "empty_option_value", "invalid_number", "invalid_choice", "duplicate_option"].includes(String(error.details.reason));
|
|
@@ -536,7 +552,7 @@ export function observeLifecycleCommand(context, command, nativeEvent) {
|
|
|
536
552
|
}
|
|
537
553
|
}
|
|
538
554
|
const correctable = error && !hookOnlyArgumentDefect && error.details.recoverable !== false && error.details.effect !== "unknown"
|
|
539
|
-
&& (registeredContinuation || error.details.effect === "no_effect" && isCorrectableLifecycleRejection(error.code, error.details)
|
|
555
|
+
&& (registeredContinuation || retainedSuccessor || error.details.effect === "no_effect" && isCorrectableLifecycleRejection(error.code, error.details)
|
|
540
556
|
&& typeof error.details.retry_command === "string");
|
|
541
557
|
const correlationKey = parsed.kind === "standalone" ? fingerprint(parsed.invocation)
|
|
542
558
|
: JSON.stringify([auxiliary.operation, auxiliary.operation === "stage_block" ? auxiliary.args.positional[2] ?? null : auxiliary.args.options.get("run")?.[0] ?? null, auxiliary.args.options.get("stage")?.[0] ?? null]);
|
|
@@ -646,18 +662,60 @@ export function lifecycleRetryCommands(context, stage) {
|
|
|
646
662
|
.flatMap((row) => {
|
|
647
663
|
try {
|
|
648
664
|
const outcome = row.outcome_json ? JSON.parse(row.outcome_json) : null;
|
|
649
|
-
const retry = outcome?.error?.details?.retry_command;
|
|
665
|
+
const retry = outcome?.error?.details?.retry_command ?? outcome?.result?.next?.finish_command;
|
|
650
666
|
const parsed = invocation(row.command);
|
|
651
667
|
if (typeof retry !== "string" || parsed.operation !== "stage_finish" || commandOption(parsed, "stage") !== stage)
|
|
652
668
|
return [];
|
|
653
|
-
const
|
|
654
|
-
|
|
669
|
+
const successor = outcome?.successor_invocation_id
|
|
670
|
+
? context.db.get("SELECT * FROM lifecycle_invocations WHERE id = ? AND scope_json = ?", [outcome.successor_invocation_id, row.scope_json])
|
|
671
|
+
: currentIssuedCommand(context, row.scope_json, retry);
|
|
672
|
+
return successor?.status === "issued" && successor.fingerprint === scopedCommandFingerprint(context, row.scope_json, retry)
|
|
673
|
+
&& (successor.deadline === null || successor.deadline > Date.now()) ? [publicInvocationCommand(context, successor, successor.command)] : [];
|
|
655
674
|
}
|
|
656
675
|
catch {
|
|
657
676
|
return [];
|
|
658
677
|
}
|
|
659
678
|
}))];
|
|
660
679
|
}
|
|
680
|
+
function currentIssuedCommand(context, scope, command) {
|
|
681
|
+
const rows = context.db.all("SELECT * FROM lifecycle_invocations WHERE scope_json = ? AND fingerprint = ? AND status = 'issued'", [scope, scopedCommandFingerprint(context, scope, command)])
|
|
682
|
+
.filter(row => row.deadline === null || row.deadline > Date.now());
|
|
683
|
+
if (rows.length > 1)
|
|
684
|
+
throw new AppError("invocation_ambiguous", "Multiple current lifecycle successors match this command", 1);
|
|
685
|
+
return rows[0] ?? null;
|
|
686
|
+
}
|
|
687
|
+
function scopedCommandFingerprint(context, scope, command) {
|
|
688
|
+
return fingerprint(invocation(canonicalManagedCommand(context, command, JSON.parse(scope))));
|
|
689
|
+
}
|
|
690
|
+
function committedSuccessor(context, prior, outcome) {
|
|
691
|
+
if (typeof outcome?.successor_invocation_id !== "string" || outcome.successor_invocation_id === prior.id)
|
|
692
|
+
return null;
|
|
693
|
+
const command = outcome.error?.details?.retry_command ?? outcome.result?.next?.finish_command;
|
|
694
|
+
if (typeof command !== "string")
|
|
695
|
+
return null;
|
|
696
|
+
const next = context.db.get("SELECT * FROM lifecycle_invocations WHERE id = ? AND scope_json = ?", [outcome.successor_invocation_id, prior.scope_json]);
|
|
697
|
+
return next && next.status !== "expired" && (next.status !== "issued" || next.deadline === null || next.deadline > Date.now())
|
|
698
|
+
&& next.fingerprint === scopedCommandFingerprint(context, prior.scope_json, command) ? next : null;
|
|
699
|
+
}
|
|
700
|
+
function invocationRecoverySnapshot(context, scope) {
|
|
701
|
+
return JSON.stringify(context.db.get(`SELECT g.generation,g.status,g.recovery_id FROM run_recovery_guards g
|
|
702
|
+
JOIN projects p ON p.id=g.project_id WHERE p.root=? AND g.run_id=? ORDER BY g.generation DESC LIMIT 1`, [scope.projectRoot, scope.runId]) ?? null);
|
|
703
|
+
}
|
|
704
|
+
/** Resolve authority, never reconstruct it from an old prompt or issue on read. */
|
|
705
|
+
export function currentStageFinishCommand(context, stage) {
|
|
706
|
+
const scope = context.env.DD_FLOW_INVOCATION_SCOPE;
|
|
707
|
+
if (!scope || !context.db.get("SELECT 1 FROM sqlite_master WHERE name='lifecycle_invocations'"))
|
|
708
|
+
return null;
|
|
709
|
+
const rows = context.db.all("SELECT * FROM lifecycle_invocations WHERE scope_json = ? AND status = 'issued'", [scope])
|
|
710
|
+
.filter(row => {
|
|
711
|
+
const parsed = invocation(row.command);
|
|
712
|
+
return parsed.operation === "stage_finish" && commandOption(parsed, "stage") === stage
|
|
713
|
+
&& (row.deadline === null || row.deadline > Date.now());
|
|
714
|
+
});
|
|
715
|
+
if (rows.length > 1)
|
|
716
|
+
throw new AppError("invocation_ambiguous", "Stage has multiple current finish authorities", 1, { stage });
|
|
717
|
+
return rows[0] ? publicInvocationCommand(context, rows[0], rows[0].command) : null;
|
|
718
|
+
}
|
|
661
719
|
export function managedInvocationContext(context, input) {
|
|
662
720
|
const env = { ...context.env, DD_FLOW_CURRENT_INVOCATION: undefined, DD_FLOW_INVOCATION_SCOPE: undefined };
|
|
663
721
|
if (!["zcode-acp", "codex-desktop", "grok-acp", "antigravity-cli", "droid-cli", "opencode-server"].includes(input.harness))
|
|
@@ -713,6 +771,21 @@ export function managedLifecycleCommand(context, command, options = {}) {
|
|
|
713
771
|
return publicInvocationCommand(context, retained, command);
|
|
714
772
|
}
|
|
715
773
|
storage(context);
|
|
774
|
+
const predecessorId = context.env.DD_FLOW_CURRENT_INVOCATION;
|
|
775
|
+
const predecessor = predecessorId ? load(context, predecessorId) : null;
|
|
776
|
+
if (predecessor?.status === "executing" && predecessor.scope_json === configured
|
|
777
|
+
&& parsed.operation === "stage_finish" && invocation(predecessor.command).operation === "stage_finish"
|
|
778
|
+
&& commandOption(parsed, "stage") === commandOption(invocation(predecessor.command), "stage")) {
|
|
779
|
+
let staged = stagedContinuations.get(context.db);
|
|
780
|
+
if (!staged)
|
|
781
|
+
stagedContinuations.set(context.db, staged = new Map());
|
|
782
|
+
const prior = staged.get(predecessor.id);
|
|
783
|
+
if (prior && prior.fingerprint !== fingerprint(parsed))
|
|
784
|
+
throw new AppError("invocation_continuation_conflict", "One finish may publish only one exact successor", 1);
|
|
785
|
+
const next = prior ?? prepareIssuedInvocation(command, scope);
|
|
786
|
+
staged.set(predecessor.id, next);
|
|
787
|
+
return publicInvocationCommand(context, next, command);
|
|
788
|
+
}
|
|
716
789
|
return context.db.writeTransaction(() => {
|
|
717
790
|
const prior = options.freshWorkAttempt ? undefined : context.db.get("SELECT * FROM lifecycle_invocations WHERE scope_json = ? AND fingerprint = ? ORDER BY rowid DESC LIMIT 1", [JSON.stringify(scope), fingerprint(parsed)]);
|
|
718
791
|
if (prior && prior.id !== context.env.DD_FLOW_CURRENT_INVOCATION) {
|
|
@@ -793,17 +866,35 @@ export function settleLifecycleRejection(context, id, error) {
|
|
|
793
866
|
// Only this settlement may publish a successor; inherited help is not authority.
|
|
794
867
|
delete error.details.retry_command;
|
|
795
868
|
delete error.details.retry_instruction;
|
|
796
|
-
|
|
869
|
+
// A producer may have rendered help before throwing. Only the decision below
|
|
870
|
+
// may publish replacement authority for a rejected operation.
|
|
871
|
+
stagedContinuations.get(context.db)?.delete(id);
|
|
872
|
+
// Validate process-local receipt capabilities BEFORE diagnostic cloning.
|
|
873
|
+
// Only this local preparation crosses into the SQL commit below; public
|
|
874
|
+
// category fields/serialized receipts cannot recreate the decision.
|
|
875
|
+
const prepared = {
|
|
876
|
+
invocation: JSON.stringify(load(context, id)),
|
|
877
|
+
recovery: invocationRecoverySnapshot(context, JSON.parse(load(context, id).scope_json)),
|
|
878
|
+
retryable: isRetryableLifecycleRejection(error.code, error.details),
|
|
879
|
+
receipts: ["work_checks_failed", "code_gate_failed", "code_review_gate_failed", "merge_gate_failed"].includes(error.code) && Array.isArray(error.details.failures)
|
|
880
|
+
? error.details.failures.map(receipt => ({ receipt, row: context.db.get("SELECT * FROM check_receipts WHERE id = ?", [receipt.id]) })) : []
|
|
881
|
+
};
|
|
882
|
+
if (prepared.receipts.some(({ receipt, row }) => !row || row.status !== receipt.status || row.verification_epoch !== receipt.verification_epoch || row.command !== receipt.command || row.input_hash !== receipt.input_hash))
|
|
883
|
+
prepared.retryable = false;
|
|
884
|
+
const serialized = errorRecord(error);
|
|
885
|
+
const details = { ...serialized.details };
|
|
797
886
|
try {
|
|
798
887
|
context.db.writeTransaction(() => {
|
|
799
888
|
const prior = load(context, id);
|
|
800
|
-
if (prior.status !== "executing")
|
|
801
|
-
|
|
889
|
+
if (prior.status !== "executing" || JSON.stringify(prior) !== prepared.invocation
|
|
890
|
+
|| invocationRecoverySnapshot(context, JSON.parse(prior.scope_json)) !== prepared.recovery
|
|
891
|
+
|| prepared.receipts.some(({ receipt, row }) => JSON.stringify(context.db.get("SELECT * FROM check_receipts WHERE id = ?", [receipt.id])) !== JSON.stringify(row)))
|
|
892
|
+
throw new AppError("invocation_settlement_conflict", "Attempt or bound check receipts changed before settlement", 1, { invocation_id: id });
|
|
802
893
|
const storageRetry = error.code === "storage_write_failed" && error.details.effect === "no_effect"
|
|
803
894
|
&& !context.db.get("SELECT 1 FROM lifecycle_invocations WHERE scope_json = ? AND fingerprint = ? AND json_extract(outcome_json, '$.error.details.storage_retry') = 1 LIMIT 1", [prior.scope_json, prior.fingerprint]);
|
|
804
|
-
if (
|
|
805
|
-
const failures =
|
|
806
|
-
const retry = retryLifecycleInvocationCommand(context, id, failures
|
|
895
|
+
if (prepared.retryable || storageRetry) {
|
|
896
|
+
const failures = prepared.receipts.map(value => value.receipt);
|
|
897
|
+
const retry = retryLifecycleInvocationCommand(context, id, failures[0]?.id);
|
|
807
898
|
details.retry_command = retry;
|
|
808
899
|
details.effect = "no_effect";
|
|
809
900
|
details.recoverable = true;
|
|
@@ -825,7 +916,9 @@ export function settleLifecycleRejection(context, id, error) {
|
|
|
825
916
|
else {
|
|
826
917
|
details.recoverable = false;
|
|
827
918
|
}
|
|
828
|
-
|
|
919
|
+
const successor = stagedContinuations.get(context.db)?.get(id)
|
|
920
|
+
?? (typeof details.retry_command === "string" ? currentIssuedCommand(context, prior.scope_json, details.retry_command) : null);
|
|
921
|
+
settleLifecycleInvocation(context, id, { error: { ...serialized, details }, ...(successor ? { successor_invocation_id: successor.id } : {}) });
|
|
829
922
|
});
|
|
830
923
|
}
|
|
831
924
|
catch (persistence) {
|
|
@@ -964,15 +1057,22 @@ export function prepareLifecycleIssuance(command, value) {
|
|
|
964
1057
|
return { command, scope, parsed };
|
|
965
1058
|
}
|
|
966
1059
|
export function issueLifecycleInvocation(context, command, scope) {
|
|
967
|
-
const
|
|
1060
|
+
const row = prepareIssuedInvocation(command, scope);
|
|
968
1061
|
storage(context);
|
|
1062
|
+
insertIssuedInvocation(context, row);
|
|
1063
|
+
return { id: row.id, command: row.command };
|
|
1064
|
+
}
|
|
1065
|
+
function prepareIssuedInvocation(command, scope) {
|
|
1066
|
+
const { parsed } = prepareLifecycleIssuance(command, scope);
|
|
969
1067
|
const id = crypto.randomUUID();
|
|
970
1068
|
const prepared = commandWithInvocationId(command, id);
|
|
971
1069
|
// Validate the actual inserted argv, including quoted heredoc boundaries.
|
|
972
|
-
const row = { id, command: prepared, scope_json: JSON.stringify(scope), fingerprint: fingerprint(parsed) };
|
|
1070
|
+
const row = { id, command: prepared, scope_json: JSON.stringify(scope), fingerprint: fingerprint(parsed), status: "issued", event_key: null, identity_json: null, deadline: null, outcome_json: null };
|
|
973
1071
|
checkCommand(row, prepared);
|
|
974
|
-
|
|
975
|
-
|
|
1072
|
+
return row;
|
|
1073
|
+
}
|
|
1074
|
+
function insertIssuedInvocation(context, row) {
|
|
1075
|
+
context.db.run("INSERT INTO lifecycle_invocations (id, scope_json, command, fingerprint, status, created_at, updated_at) VALUES (?, ?, ?, ?, 'issued', ?, ?)", [row.id, row.scope_json, row.command, row.fingerprint, context.now(), context.now()]);
|
|
976
1076
|
}
|
|
977
1077
|
/** Prepare outside a write transaction; the Work retry CAS owns issuance. */
|
|
978
1078
|
export function prepareWorkStartAuthority(context, command, previousId) {
|
|
@@ -1930,9 +2030,40 @@ export async function awaitLifecycleInvocation(context, input) {
|
|
|
1930
2030
|
return { outcome: JSON.parse(row.outcome_json), replay: true };
|
|
1931
2031
|
if (row.status === "executing") {
|
|
1932
2032
|
const scope = JSON.parse(row.scope_json);
|
|
2033
|
+
const parsed = invocation(row.command);
|
|
2034
|
+
if (parsed.operation === "stage_resume" && scope.runId) {
|
|
2035
|
+
input.assertCurrent(scope);
|
|
2036
|
+
const { recoverCommittedStageResume } = await import("./stage-pause.js");
|
|
2037
|
+
const result = recoverCommittedStageResume(context, { id: row.id, projectRoot: scope.projectRoot, runId: scope.runId, generation: scope.generation, stage: commandOption(parsed, "stage") ?? "", workId: commandOption(parsed, "work") ?? "" });
|
|
2038
|
+
if (result) {
|
|
2039
|
+
const project = requireProjectByRoot(context, scope.projectRoot);
|
|
2040
|
+
const { publishStagePacket } = await import("./stage-report-renderer.js");
|
|
2041
|
+
publishStagePacket(context, project.id, scope.runId, commandOption(parsed, "stage") ?? "", String(result.resume_source_path));
|
|
2042
|
+
const { preparePromptDelivery } = await import("./prompt-delivery.js");
|
|
2043
|
+
const delivered = preparePromptDelivery(result);
|
|
2044
|
+
const previousScope = context.env.DD_FLOW_INVOCATION_SCOPE;
|
|
2045
|
+
context.env.DD_FLOW_INVOCATION_SCOPE = row.scope_json;
|
|
2046
|
+
try {
|
|
2047
|
+
bindLifecyclePublishedPacket(context, String(result.worker_prompt_markdown));
|
|
2048
|
+
}
|
|
2049
|
+
finally {
|
|
2050
|
+
if (previousScope === undefined)
|
|
2051
|
+
delete context.env.DD_FLOW_INVOCATION_SCOPE;
|
|
2052
|
+
else
|
|
2053
|
+
context.env.DD_FLOW_INVOCATION_SCOPE = previousScope;
|
|
2054
|
+
}
|
|
2055
|
+
const event = row.event_key ? context.db.get("SELECT id FROM hook_events WHERE project_id = ? AND event_key = ?", [project.id, row.event_key]) : null;
|
|
2056
|
+
if (event)
|
|
2057
|
+
observeLifecycleCommand(context, row.command, { project_id: project.id, id: event.id })({ result: delivered });
|
|
2058
|
+
const outcome = { result: delivered, effect: "committed" };
|
|
2059
|
+
settleLifecycleInvocation(context, row.id, outcome);
|
|
2060
|
+
return { outcome, replay: true };
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
1933
2063
|
if (invocation(row.command).operation === "work_start" && row.event_key) {
|
|
1934
2064
|
input.assertCurrent(scope);
|
|
1935
|
-
const
|
|
2065
|
+
const { recoverCommittedWorkStart } = await import("./work-registry.js");
|
|
2066
|
+
const result = recoverCommittedWorkStart(context, row.id, row.event_key, scope.generation, undefined, input.progress);
|
|
1936
2067
|
if (result) {
|
|
1937
2068
|
const outcome = { result, effect: "committed" };
|
|
1938
2069
|
settleLifecycleInvocation(context, row.id, outcome);
|
|
@@ -1994,14 +2125,24 @@ export async function awaitLifecycleInvocation(context, input) {
|
|
|
1994
2125
|
}
|
|
1995
2126
|
}
|
|
1996
2127
|
export function settleLifecycleInvocation(context, id, outcome) {
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
if (encoded === undefined)
|
|
2000
|
-
throw new AppError("invocation_outcome_invalid", "Invocation outcome must be JSON", 1);
|
|
2001
|
-
const changed = context.db.run("UPDATE lifecycle_invocations SET status = 'settled', outcome_json = ?, updated_at = ? WHERE id = ? AND status = 'executing'", [encoded, context.now(), id]);
|
|
2002
|
-
if (changed.changes !== 1) {
|
|
2128
|
+
const successor = stagedContinuations.get(context.db)?.get(id);
|
|
2129
|
+
context.db.writeTransaction(() => {
|
|
2003
2130
|
const row = load(context, id);
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2131
|
+
const retained = successor && outcome && typeof outcome === "object" ? { ...outcome, successor_invocation_id: successor.id } : outcome;
|
|
2132
|
+
const encoded = JSON.stringify(retained);
|
|
2133
|
+
if (encoded === undefined)
|
|
2134
|
+
throw new AppError("invocation_outcome_invalid", "Invocation outcome must be JSON", 1);
|
|
2135
|
+
if (row.status === "settled") {
|
|
2136
|
+
if (row.outcome_json !== encoded)
|
|
2137
|
+
throw new AppError("invocation_settlement_conflict", "Attempt cannot be settled with a different outcome", 1, { invocation_id: id });
|
|
2138
|
+
return;
|
|
2139
|
+
}
|
|
2140
|
+
if (row.status !== "executing")
|
|
2141
|
+
throw new AppError("invocation_settlement_conflict", "Attempt is no longer executing", 1, { invocation_id: id });
|
|
2142
|
+
if (successor)
|
|
2143
|
+
insertIssuedInvocation(context, successor);
|
|
2144
|
+
if (context.db.run("UPDATE lifecycle_invocations SET status = 'settled', outcome_json = ?, updated_at = ? WHERE id = ? AND status = 'executing'", [encoded, context.now(), id]).changes !== 1)
|
|
2145
|
+
throw new AppError("invocation_settlement_conflict", "Lifecycle outcome changed before commit", 1, { invocation_id: id });
|
|
2146
|
+
});
|
|
2147
|
+
currentAdmissions.get(context.db)?.delete(id);
|
|
2007
2148
|
}
|
|
@@ -35,15 +35,16 @@ export class PreparedAcceptedMemoryComposition {
|
|
|
35
35
|
}
|
|
36
36
|
static prepare(context, input) {
|
|
37
37
|
const { scope, bundle } = input;
|
|
38
|
-
if (!(bundle instanceof RetainedExecutionBundle) || bundle.descriptor.descriptor.id
|
|
38
|
+
if (!(bundle instanceof RetainedExecutionBundle) || !["mb-init", "mb-upgrade"].includes(bundle.descriptor.descriptor.id))
|
|
39
39
|
refuse("registered_memory_bundle_required");
|
|
40
|
+
const stage = bundle.descriptor.descriptor.id === "mb-init" ? "compose" : "upgrade";
|
|
40
41
|
const run = assertPreparedRunBundleSqlAuthority(context, scope, bundle), root = readDescriptorRootWork(context, scope, bundle);
|
|
41
|
-
const registration = selectMemoryCompositionRegistration(context, { ...stageGraphScope(context, scope.projectId, scope.runId,
|
|
42
|
+
const registration = selectMemoryCompositionRegistration(context, { ...stageGraphScope(context, scope.projectId, scope.runId, stage), stage, parent_work_id: root.workId });
|
|
42
43
|
const blueprint = PreparedMemoryResultBinding.prepareRegisteredComposition(context, { ...scope, flow: bundle.descriptor, execution: bundle.execution, bundle, acceptedReplay: true,
|
|
43
44
|
registrationScope: registration.scope });
|
|
44
45
|
const outputs = PreparedMemoryCompositionAcceptance.prepare(context, { scope: registration.scope, flow: bundle.descriptor, engine: bundle.engine(), binding: blueprint.binding() });
|
|
45
|
-
const composed = readAcceptedStageData(context, { ...scope, stage
|
|
46
|
-
const binding = PreparedMemoryResultBinding.prepare(context, { ...scope, stage
|
|
46
|
+
const composed = readAcceptedStageData(context, { ...scope, stage }), runRoot = run.run_root ?? run.run_dir;
|
|
47
|
+
const binding = PreparedMemoryResultBinding.prepare(context, { ...scope, stage, flow: bundle.descriptor, execution: bundle.execution, bundle, acceptedReplay: true,
|
|
47
48
|
sources: captureMemoryCompositionSources(context, bundle, runRoot, composed.bytes, outputs), historicalEvidence: outputs.historicalEvidence() });
|
|
48
49
|
if (!binding.acceptedBytes()?.equals(composed.bytes))
|
|
49
50
|
refuse("accepted_composition_changed");
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { assertMemorySqlCustody } from "./memory-custody-snapshot.js";
|
|
1
2
|
import { AppError } from "../shared/errors.js";
|
|
2
3
|
import { hybridDigest } from "./hybrid-contracts.js";
|
|
3
4
|
import { PreparedRegisteredMemoryCorrections } from "./memory-repair-registration.js";
|
|
4
5
|
import { PreparedRetainedSecondMemoryRepairs } from "./memory-retained-second-repairs.js";
|
|
5
6
|
import { PreparedSettledMemoryRepair } from "./memory-repair-history.js";
|
|
6
7
|
import { PreparedMemoryRepairWorkspace } from "./memory-repair-workspace.js";
|
|
8
|
+
import { isCompletedMemoryRepairClaim } from "./memory-repair-work-results.js";
|
|
7
9
|
import { reviewSourcePaths } from "./review-copy.js";
|
|
8
10
|
import { readAcceptedStageData } from "./stage-report-renderer.js";
|
|
9
11
|
const token = Symbol("completed-memory-correction-wave");
|
|
@@ -41,8 +43,7 @@ export function memoryCorrectionAffectedUnits(units, repairs, composition = [])
|
|
|
41
43
|
const affected = new Set(), changed = new Set();
|
|
42
44
|
for (const repair of repairs) {
|
|
43
45
|
if (!repair || !Array.isArray(repair.unit_ids) || !repair.unit_ids.length || new Set(repair.unit_ids).size !== repair.unit_ids.length
|
|
44
|
-
|| repair.unit_ids.some((id) => !applicable.has(id)) || repair.result
|
|
45
|
-
|| !Array.isArray(repair.result.changes) || !repair.result.changes.length)
|
|
46
|
+
|| repair.unit_ids.some((id) => !applicable.has(id)) || !isCompletedMemoryRepairClaim(repair.result))
|
|
46
47
|
refuse("invalid_settled_repair_projection");
|
|
47
48
|
for (const id of repair.unit_ids)
|
|
48
49
|
affected.add(id);
|
|
@@ -138,6 +139,7 @@ const nonMemory = (entry) => entry.path !== ".memory-bank" && !entry.path.starts
|
|
|
138
139
|
* review. Reconstruction starts at independently retained first-wave state;
|
|
139
140
|
* neither current files nor a second writer's claimed before-view are anchors. */
|
|
140
141
|
export class PreparedHistoricalSecondMemoryCorrectionWave {
|
|
142
|
+
#custodyKey = {};
|
|
141
143
|
static isPrepared(value) {
|
|
142
144
|
return typeof value === "object" && value !== null && #registration in value;
|
|
143
145
|
}
|
|
@@ -167,11 +169,11 @@ export class PreparedHistoricalSecondMemoryCorrectionWave {
|
|
|
167
169
|
for (const history of histories) {
|
|
168
170
|
if (!prefixes.has(hybridDigest(history.before().entries())))
|
|
169
171
|
refuse("unexplained_historical_input_view");
|
|
170
|
-
baseline = baseline.composeSettledDelta(history.before(), history.after());
|
|
172
|
+
baseline = baseline.composeSettledDelta(history.before(), history.after(), history.result().outcome);
|
|
171
173
|
prefixes.add(hybridDigest(baseline.entries()));
|
|
172
174
|
}
|
|
173
175
|
const batch = registration.batch(), receipt = registration.receipt(), first = registration.initialInputView();
|
|
174
|
-
const compose = readAcceptedStageData(context, { ...input.scope, stage: "compose" });
|
|
176
|
+
const compose = readAcceptedStageData(context, { ...input.scope, stage: input.bundle.descriptor.descriptor.id === "mb-upgrade" ? "upgrade" : "compose" });
|
|
175
177
|
const affected = memoryCorrectionAffectedUnits(memoryCorrectionCurrentUnits(first.unit_inventory, first.assignments), histories.map(history => ({ result: history.result(),
|
|
176
178
|
unit_ids: batch.works.find(item => receipt.work_ids[item.key] === history.workId).payload.memory_repair.unit_ids })), JSON.parse(compose.bytes.toString("utf8")).details.works);
|
|
177
179
|
const prepared = new PreparedHistoricalSecondMemoryCorrectionWave(historicalToken, registration, selected, histories, baseline, affected);
|
|
@@ -189,10 +191,13 @@ export class PreparedHistoricalSecondMemoryCorrectionWave {
|
|
|
189
191
|
unit_ids: batch.works.find(item => receipt.work_ids[item.key] === history.workId).payload.memory_repair.unit_ids }));
|
|
190
192
|
}
|
|
191
193
|
assertCustody(context) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
194
|
+
const registration = this.#registration;
|
|
195
|
+
assertMemorySqlCustody(context, this.#custodyKey, () => {
|
|
196
|
+
if (hybridDigest(selection(context, registration)) !== hybridDigest(this.#selected))
|
|
197
|
+
refuse("historical_second_graph_changed");
|
|
198
|
+
for (const history of this.#histories)
|
|
199
|
+
history.assertCustody(context);
|
|
200
|
+
});
|
|
196
201
|
}
|
|
197
202
|
revalidate(context) {
|
|
198
203
|
this.#registration.revalidate(context);
|
|
@@ -207,6 +212,7 @@ export class PreparedHistoricalSecondMemoryCorrectionWave {
|
|
|
207
212
|
* from today's filesystem. This owner neither allocates reviewers nor marks
|
|
208
213
|
* repair findings resolved; independent re-review must consume it separately. */
|
|
209
214
|
export class PreparedCompletedMemoryCorrectionWave {
|
|
215
|
+
#custodyKey = {};
|
|
210
216
|
static isPrepared(value) {
|
|
211
217
|
return typeof value === "object" && value !== null && #selected in value;
|
|
212
218
|
}
|
|
@@ -252,7 +258,7 @@ export class PreparedCompletedMemoryCorrectionWave {
|
|
|
252
258
|
for (const history of histories) {
|
|
253
259
|
if (!known.has(hybridDigest(history.before().entries())))
|
|
254
260
|
refuse("unexplained_historical_input_view");
|
|
255
|
-
baseline = baseline.composeSettledDelta(history.before(), history.after());
|
|
261
|
+
baseline = baseline.composeSettledDelta(history.before(), history.after(), history.result().outcome);
|
|
256
262
|
known.add(hybridDigest(baseline.entries()));
|
|
257
263
|
}
|
|
258
264
|
const view = PreparedRetainedSecondMemoryRepairs.isPrepared(registration) ? registration.initialInputView()
|
|
@@ -272,7 +278,7 @@ export class PreparedCompletedMemoryCorrectionWave {
|
|
|
272
278
|
if (histories.some(history => hybridDigest(history.workspaceManifest().entries().filter(nonMemory)) !== sourceSha256))
|
|
273
279
|
refuse("non_memory_source_changed");
|
|
274
280
|
const batch = registration.batch(), receipt = registration.receipt();
|
|
275
|
-
const compose = readAcceptedStageData(context, { projectId: receipt.scope.project_id, runId: receipt.run_id, stage: "compose" });
|
|
281
|
+
const compose = readAcceptedStageData(context, { projectId: receipt.scope.project_id, runId: receipt.run_id, stage: bundle.descriptor.descriptor.id === "mb-upgrade" ? "upgrade" : "compose" });
|
|
276
282
|
const dependencies = JSON.parse(compose.bytes.toString("utf8")).details.works;
|
|
277
283
|
const currentUnits = PreparedRetainedSecondMemoryRepairs.isPrepared(registration)
|
|
278
284
|
? memoryCorrectionCurrentUnits(registration.initialInputView().unit_inventory, registration.initialInputView().assignments)
|
|
@@ -292,11 +298,13 @@ export class PreparedCompletedMemoryCorrectionWave {
|
|
|
292
298
|
repairResults() { return this.#histories.map(history => ({ work_id: history.workId, result: history.result(), reference: history.reference(), authority_sha256: history.sha256 })); }
|
|
293
299
|
/** Only SQL custody is inspected inside the future review-allocation writer. */
|
|
294
300
|
assertCustody(context) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
history
|
|
301
|
+
assertMemorySqlCustody(context, this.#custodyKey, () => {
|
|
302
|
+
const selected = this.#selected;
|
|
303
|
+
if (hybridDigest(selection(context, this.#registration)) !== hybridDigest(selected))
|
|
304
|
+
refuse("completed_wave_custody_changed");
|
|
305
|
+
for (const history of this.#histories)
|
|
306
|
+
history.assertCustody(context);
|
|
307
|
+
});
|
|
300
308
|
}
|
|
301
309
|
/** Fresh allocation fence, before adding successor reviewers. General
|
|
302
310
|
* custody deliberately stays valid when those admitted children exist. */
|