@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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
2
3
|
import { spawnSync } from "node:child_process";
|
|
3
4
|
import { AppError } from "../shared/errors.js";
|
|
4
5
|
import { hybridDigest } from "./hybrid-contracts.js";
|
|
@@ -15,6 +16,51 @@ function validScope(scope) {
|
|
|
15
16
|
|| Object.keys(scope).sort().join() !== "projectRoot,requestId,workspaceRoot")
|
|
16
17
|
refuse("invalid_request_scope");
|
|
17
18
|
}
|
|
19
|
+
/** Shared read-only policy observation for init and upgrade. The caller owns
|
|
20
|
+
* retained input custody; these facts grant neither dispatch nor push. */
|
|
21
|
+
export function observeMemoryGitPolicySelection(environment, input) {
|
|
22
|
+
validScope(input.scope);
|
|
23
|
+
const { scope, selection } = input;
|
|
24
|
+
if (!selection || Object.keys(selection).sort().join() !== "integrationBranch,route"
|
|
25
|
+
|| !["integration_branch_direct", "feature_worktree"].includes(selection.route)
|
|
26
|
+
|| typeof selection.integrationBranch !== "string" || !selection.integrationBranch || selection.integrationBranch.startsWith("-") || selection.integrationBranch.length > 1024
|
|
27
|
+
|| [...selection.integrationBranch].some(character => character.charCodeAt(0) <= 32 || character.charCodeAt(0) === 127))
|
|
28
|
+
refuse("explicit_policy_required");
|
|
29
|
+
if (typeof input.gitExecutable !== "string" || !path.isAbsolute(input.gitExecutable))
|
|
30
|
+
refuse("explicit_git_required");
|
|
31
|
+
const env = Object.fromEntries(Object.entries(environment).filter(([key]) => !key.startsWith("GIT_")));
|
|
32
|
+
const git = (directory, args, optional = false) => {
|
|
33
|
+
const result = spawnSync(input.gitExecutable, ["-c", "core.fsmonitor=false", "-C", directory, ...args], { env: { ...env, GIT_OPTIONAL_LOCKS: "0" }, encoding: "utf8", timeout: 5000, maxBuffer: 64 * 1024, shell: false, windowsHide: true });
|
|
34
|
+
if (result.error || result.signal || !optional && result.status !== 0)
|
|
35
|
+
refuse("git_policy_observation_failed");
|
|
36
|
+
return result.status === 0 ? result.stdout.trim() : null;
|
|
37
|
+
};
|
|
38
|
+
const valid = spawnSync(input.gitExecutable, ["check-ref-format", `refs/heads/${selection.integrationBranch}`], { env: { ...env, GIT_OPTIONAL_LOCKS: "0" }, timeout: 5000, maxBuffer: 64 * 1024, shell: false, windowsHide: true });
|
|
39
|
+
if (valid.error || valid.signal || valid.status !== 0)
|
|
40
|
+
refuse("invalid_integration_branch");
|
|
41
|
+
const common = (directory) => {
|
|
42
|
+
if (git(directory, ["rev-parse", "--is-bare-repository"]) !== "false"
|
|
43
|
+
|| fs.realpathSync(git(directory, ["rev-parse", "--show-toplevel"])) !== directory)
|
|
44
|
+
refuse("not_repository_root");
|
|
45
|
+
return fs.realpathSync(path.resolve(directory, git(directory, ["rev-parse", "--git-common-dir"])));
|
|
46
|
+
};
|
|
47
|
+
const repository = common(scope.projectRoot);
|
|
48
|
+
if (common(scope.workspaceRoot) !== repository)
|
|
49
|
+
refuse("foreign_workspace_repository");
|
|
50
|
+
const branch = git(scope.workspaceRoot, ["symbolic-ref", "--short", "HEAD"]);
|
|
51
|
+
if (!branch)
|
|
52
|
+
refuse("workspace_branch_unavailable");
|
|
53
|
+
if (selection.route === "integration_branch_direct"
|
|
54
|
+
? scope.projectRoot !== scope.workspaceRoot || branch !== selection.integrationBranch
|
|
55
|
+
: scope.projectRoot === scope.workspaceRoot || branch === selection.integrationBranch)
|
|
56
|
+
refuse("route_workspace_mismatch");
|
|
57
|
+
if (selection.route === "feature_worktree"
|
|
58
|
+
&& !/^[a-f0-9]{40,64}$/.test(git(scope.projectRoot, ["rev-parse", "--verify", `refs/heads/${selection.integrationBranch}^{commit}`], true) ?? ""))
|
|
59
|
+
refuse("integration_branch_unavailable");
|
|
60
|
+
const head = git(scope.workspaceRoot, ["rev-parse", "--verify", "HEAD"], true);
|
|
61
|
+
return { repository, workspace_branch: branch, initial_head: head, source: "explicit_request",
|
|
62
|
+
route: selection.route, integration_branch: selection.integrationBranch, push_authorized: false };
|
|
63
|
+
}
|
|
18
64
|
/** SQL preservation, not fresh policy inference, Git execution or permission. */
|
|
19
65
|
export function readMemoryInitGitPolicy(context, scope) {
|
|
20
66
|
validScope(scope);
|
|
@@ -75,32 +121,15 @@ export class PreparedMemoryInitGitPolicy {
|
|
|
75
121
|
static prepareFresh(environment, input) {
|
|
76
122
|
if (!(input.target instanceof PreparedMemoryInitTarget))
|
|
77
123
|
refuse("prepared_target_required");
|
|
78
|
-
const selection = input.selection;
|
|
79
|
-
if (!selection || Object.keys(selection).sort().join() !== "integrationBranch,route"
|
|
80
|
-
|| !["integration_branch_direct", "feature_worktree"].includes(selection.route)
|
|
81
|
-
|| typeof selection.integrationBranch !== "string" || !selection.integrationBranch || selection.integrationBranch.startsWith("-") || selection.integrationBranch.length > 1024
|
|
82
|
-
|| [...selection.integrationBranch].some(character => character.charCodeAt(0) <= 32 || character.charCodeAt(0) === 127))
|
|
83
|
-
refuse("explicit_policy_required");
|
|
84
124
|
input.target.revalidate();
|
|
85
125
|
const facts = input.target.facts(), scope = { requestId: facts.request_id, projectRoot: facts.project_root, workspaceRoot: facts.workspace_root };
|
|
86
126
|
if (!facts.tool_probes)
|
|
87
127
|
refuse("fresh_target_required");
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
refuse("invalid_integration_branch");
|
|
92
|
-
if (selection.route === "integration_branch_direct"
|
|
93
|
-
? scope.projectRoot !== scope.workspaceRoot || facts.branch !== selection.integrationBranch
|
|
94
|
-
: scope.projectRoot === scope.workspaceRoot || facts.branch === selection.integrationBranch)
|
|
95
|
-
refuse("route_workspace_mismatch");
|
|
96
|
-
if (selection.route === "feature_worktree") {
|
|
97
|
-
const branch = spawnSync(facts.tools.git, ["-C", scope.projectRoot, "rev-parse", "--verify", `refs/heads/${selection.integrationBranch}^{commit}`], { env: { ...env, GIT_OPTIONAL_LOCKS: "0" }, encoding: "utf8", timeout: 5000, maxBuffer: 64 * 1024, shell: false, windowsHide: true });
|
|
98
|
-
if (branch.error || branch.signal || branch.status !== 0 || !/^[a-f0-9]{40,64}$/.test(branch.stdout.trim()))
|
|
99
|
-
refuse("integration_branch_unavailable");
|
|
100
|
-
}
|
|
128
|
+
const policy = observeMemoryGitPolicySelection(environment, { scope, gitExecutable: facts.tools.git, selection: input.selection });
|
|
129
|
+
if (policy.repository !== facts.repository || policy.workspace_branch !== facts.branch || policy.initial_head !== facts.head)
|
|
130
|
+
refuse("target_changed_after_preparation");
|
|
101
131
|
const receipt = { schema_id: "dd-flow/memory-init-git-policy@1", scope, target_sha256: input.target.sha256,
|
|
102
|
-
|
|
103
|
-
route: selection.route, integration_branch: selection.integrationBranch, push_authorized: false };
|
|
132
|
+
...policy };
|
|
104
133
|
return new PreparedMemoryInitGitPolicy(token, input.target, receipt);
|
|
105
134
|
}
|
|
106
135
|
value() { return structuredClone(this.#receipt); }
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { spawnSync } from "node:child_process";
|
|
5
4
|
import { createHash } from "node:crypto";
|
|
6
5
|
import { AppError } from "../shared/errors.js";
|
|
7
6
|
import { hybridDigest } from "./hybrid-contracts.js";
|
|
8
7
|
import { PreparedMemoryInitTarget } from "./memory-init-preflight.js";
|
|
9
8
|
import { readBoundedReleaseFile } from "./release-source.js";
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
import { runMemoryToolProbe } from "./memory-tool-probe.js";
|
|
10
|
+
function refuse(reason, diagnostics) {
|
|
11
|
+
throw new AppError("memory_init_lint_capability_failed", "Selected mb-lint cannot validate Memory Bank documents", 1, { reason, effect: "no_effect", next_action: "repair_prerequisite_then_retry", ...(diagnostics ? { tool_probe: diagnostics } : {}) });
|
|
12
12
|
}
|
|
13
13
|
const hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
14
14
|
const logical = ".memory-bank/index.md";
|
|
@@ -54,6 +54,12 @@ export class PreparedMemoryInitLintCapability {
|
|
|
54
54
|
const selected = target.facts(), executable = selected.tools["mb-lint"], probe = selected.tool_probes?.["mb-lint"];
|
|
55
55
|
if (!probe)
|
|
56
56
|
refuse("missing_selected_tool_probe");
|
|
57
|
+
return PreparedMemoryInitLintCapability.prepareExecutable(executable, probe, env);
|
|
58
|
+
}
|
|
59
|
+
static prepareExecutable(executable, probe, env, options = {}) {
|
|
60
|
+
if (!path.isAbsolute(executable) || !probe || !/^[a-f0-9]{64}$/.test(probe.executable_sha256)
|
|
61
|
+
|| !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(probe.version))
|
|
62
|
+
refuse("invalid_selected_tool");
|
|
57
63
|
const checksum = () => {
|
|
58
64
|
try {
|
|
59
65
|
return hash(readBoundedReleaseFile(executable, 64 * 1024 * 1024));
|
|
@@ -97,11 +103,11 @@ export class PreparedMemoryInitLintCapability {
|
|
|
97
103
|
}
|
|
98
104
|
};
|
|
99
105
|
const probeEnv = Object.fromEntries(Object.entries(env).filter(([key]) => !key.startsWith("GIT_")));
|
|
100
|
-
const run = (status, rule) => {
|
|
106
|
+
const run = (status, rule, config) => {
|
|
101
107
|
ownDirectories();
|
|
102
|
-
const result =
|
|
108
|
+
const { result, diagnostics } = runMemoryToolProbe(executable, ["--root", scratch, "--files", logical, "--format", "json", ...(config ? ["--config", config] : [])], { cwd: scratch, env: { ...probeEnv, GIT_OPTIONAL_LOCKS: "0" } });
|
|
103
109
|
if (result.error || result.signal || result.status !== status)
|
|
104
|
-
refuse("lint_probe_execution_failed");
|
|
110
|
+
refuse(config ? "temporary_configuration_not_supported" : "lint_probe_execution_failed", { ...diagnostics, phase: "lint_probe", expected_status: status });
|
|
105
111
|
ownDirectories();
|
|
106
112
|
const actual = fs.lstatSync(file);
|
|
107
113
|
if (!actual.isFile() || actual.nlink !== 1 || !same(actual, fileOwner)
|
|
@@ -136,6 +142,15 @@ export class PreparedMemoryInitLintCapability {
|
|
|
136
142
|
const fields = run(1, "frontmatter/required-fields");
|
|
137
143
|
writeProbe(`${validDocument}\n[Missing target](missing-probe-target.md)\n`);
|
|
138
144
|
const links = run(1, "links/markdown-target-exists");
|
|
145
|
+
if (options.temporaryConfiguration) {
|
|
146
|
+
// Upgrade's active-document profile needs an external config with exact
|
|
147
|
+
// finding exclusions; a version response or basic JSON lint is insufficient.
|
|
148
|
+
const config = path.join(scratch, "upgrade-lint.json");
|
|
149
|
+
fs.writeFileSync(config, JSON.stringify({ ignoreFindings: [{
|
|
150
|
+
ruleId: "links/markdown-target-exists", file: logical, target: "missing-probe-target.md"
|
|
151
|
+
}] }), { flag: "wx", mode: 0o600 });
|
|
152
|
+
run(0, undefined, config);
|
|
153
|
+
}
|
|
139
154
|
if (checksum() !== probe.executable_sha256)
|
|
140
155
|
refuse("selected_executable_changed");
|
|
141
156
|
prepared = new PreparedMemoryInitLintCapability({ schema_id: "dd-flow/memory-init-lint-capability@1",
|
|
@@ -170,6 +185,9 @@ export class PreparedMemoryInitLintCapability {
|
|
|
170
185
|
if (!probe || selected.tools["mb-lint"] !== this.#facts.executable
|
|
171
186
|
|| probe.version !== this.#facts.version || probe.executable_sha256 !== this.#facts.executable_sha256)
|
|
172
187
|
refuse("lint_capability_changed");
|
|
188
|
+
this.revalidateExecutable();
|
|
189
|
+
}
|
|
190
|
+
revalidateExecutable() {
|
|
173
191
|
// This instance already passed the functional probes. Publication checks
|
|
174
192
|
// preserve its selected executable, not repeat qualification after effects.
|
|
175
193
|
let checksum;
|
|
@@ -7,11 +7,12 @@ import { hybridDigest } from "./hybrid-contracts.js";
|
|
|
7
7
|
import { resolveHarnessCommand, harnessRuntimeArguments } from "./harness-config.js";
|
|
8
8
|
import { runHarnessAdapter, adapterSessionId } from "./harness-adapter.js";
|
|
9
9
|
import { renderCapacityInstructions } from "../harness-runtime/lib/delegation-instructions.mjs";
|
|
10
|
-
import { normalizeNativeChildren } from "../harness-runtime/lib/native-children.mjs";
|
|
10
|
+
import { normalizeNativeChildren, nativeShutdownConfirmed } from "../harness-runtime/lib/native-children.mjs";
|
|
11
11
|
import { readBoundedReleaseFile } from "./release-source.js";
|
|
12
12
|
import { acquireInstallationLock } from "./installation-lock.js";
|
|
13
13
|
import { inspectDaemonOperation } from "../harness-runtime/lib/daemon-operations.mjs";
|
|
14
14
|
import { nativeCommandFingerprint } from "../harness-runtime/lib/native-request-proof.mjs";
|
|
15
|
+
import { validateMemoryInitStartupScope } from "./memory-init-startup-custody.js";
|
|
15
16
|
import { continueProviderCapacity } from "./provider-capacity.js";
|
|
16
17
|
import { CAPACITY_POLICY, MAX_OVERLOAD_RETRIES, refusalObservation, terminalProviderOverload } from "../harness-runtime/lib/provider-overload-policy.mjs";
|
|
17
18
|
const sha = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
@@ -114,7 +115,7 @@ export async function produceMemoryInitNativeCapacity(input) {
|
|
|
114
115
|
|| hybridDigest(proof.children) !== hybridDigest(children) || proof.capacity !== children.length
|
|
115
116
|
|| children.length < input.maximum || children.length > input.maximum || new Set(children.map(child => child.session_id)).size !== children.length
|
|
116
117
|
|| children.some(child => child.session_id === rootSessionId || child.parent_session_id !== rootSessionId || child.provenance === "parent_mismatch")
|
|
117
|
-
|| cleanup?.stopped !== true || cleanup.settled !== true || cleanup.shutdown_contract !== "dd-flow/daemon-shutdown@
|
|
118
|
+
|| cleanup?.stopped !== true || cleanup.settled !== true || !nativeShutdownConfirmed(cleanup) || cleanup.shutdown_contract !== "dd-flow/daemon-shutdown@2"
|
|
118
119
|
|| typeof start?.daemon_id !== "string" || cleanup.daemon_id !== start.daemon_id
|
|
119
120
|
|| hybridDigest(cleanup) !== hybridDigest(read(path.join(root, "daemon-stop.json"))))
|
|
120
121
|
refuse("probe_not_qualified", "partial");
|
|
@@ -127,6 +128,8 @@ export async function produceMemoryInitNativeCapacity(input) {
|
|
|
127
128
|
};
|
|
128
129
|
if (result)
|
|
129
130
|
return await publish(result);
|
|
131
|
+
if (input.restoreOnly)
|
|
132
|
+
refuse("completed_probe_required", "unknown");
|
|
130
133
|
// An independent bounded technical workspace cannot modify the target.
|
|
131
134
|
const workspace = path.join(root, "project"), stateDir = path.join(root, "daemon");
|
|
132
135
|
fs.mkdirSync(workspace, { recursive: true, mode: 0o700 });
|
|
@@ -277,8 +280,8 @@ export async function produceMemoryInitNativeCapacity(input) {
|
|
|
277
280
|
}
|
|
278
281
|
}
|
|
279
282
|
const children = rootSessionId && inspected ? normalizeNativeChildren(inspected, rootSessionId) : [];
|
|
280
|
-
const qualified = !failure && cleanup?.stopped === true && cleanup.settled === true
|
|
281
|
-
&& cleanup.shutdown_contract === "dd-flow/daemon-shutdown@
|
|
283
|
+
const qualified = !failure && cleanup?.stopped === true && cleanup.settled === true && nativeShutdownConfirmed(cleanup)
|
|
284
|
+
&& cleanup.shutdown_contract === "dd-flow/daemon-shutdown@2" && typeof cleanup.daemon_id === "string"
|
|
282
285
|
&& children.length === input.maximum && children.length <= 15
|
|
283
286
|
&& children.every(child => child.parent_session_id === rootSessionId && child.session_id !== rootSessionId && child.provenance !== "parent_mismatch");
|
|
284
287
|
const proof = { schema_id: "dd-flow/memory-native-capacity@1", input: identity, root_session_id: rootSessionId,
|
|
@@ -294,3 +297,13 @@ export async function produceMemoryInitNativeCapacity(input) {
|
|
|
294
297
|
lease.release();
|
|
295
298
|
}
|
|
296
299
|
}
|
|
300
|
+
/** Recover exact completed probe bytes for a retained raw request whose
|
|
301
|
+
* resolved selection was not published. Never starts or repairs a probe. */
|
|
302
|
+
export async function restoreCompletedMemoryNativeCapacity(input) {
|
|
303
|
+
validateMemoryInitStartupScope(input.scope);
|
|
304
|
+
const prior = read(path.join(input.home, "memory-init-native-probes", hybridDigest(input.scope), "input.json"));
|
|
305
|
+
if (!prior || hybridDigest(prior.scope) !== hybridDigest(input.scope))
|
|
306
|
+
refuse("original_probe_required", "unknown");
|
|
307
|
+
return produceMemoryInitNativeCapacity({ ...input, profile: prior.profile, maximum: Number(prior.maximum),
|
|
308
|
+
nativeConfigHome: String(prior.native_config_home), requestSha256: String(prior.request_sha256), allowLiveProbe: true, restoreOnly: true });
|
|
309
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { spawnSync } from "node:child_process";
|
|
4
3
|
import { createHash } from "node:crypto";
|
|
5
4
|
import { AppError } from "../shared/errors.js";
|
|
6
5
|
import { hybridDigest } from "./hybrid-contracts.js";
|
|
7
6
|
import { readBoundedReleaseFile } from "./release-source.js";
|
|
8
7
|
import { defaultMemoryLintConfiguration, validateMemoryLintConfig } from "./memory-lint-config.js";
|
|
8
|
+
import { runMemoryToolProbe } from "./memory-tool-probe.js";
|
|
9
9
|
function refuse(reason, diagnostics) {
|
|
10
10
|
throw new AppError("memory_init_preflight_failed", "Memory init prerequisites are not admitted", 1, { reason, phase: "prepare", effect: "no_effect", next_action: reason === "existing_memory_bank" ? "audit_or_upgrade" : "repair_prerequisite_then_retry", ...(diagnostics ? { tool_probe: diagnostics } : {}) });
|
|
11
11
|
}
|
|
@@ -55,21 +55,15 @@ function noMemoryBank(directory) {
|
|
|
55
55
|
function probeTools(tools, env) {
|
|
56
56
|
const facts = Object.create(null);
|
|
57
57
|
const probeEnv = Object.fromEntries(Object.entries(env).filter(([key]) => !key.startsWith("GIT_")));
|
|
58
|
-
for (const name of
|
|
59
|
-
const diagnostics = { name, executable: tools[name], args: ["--version"],
|
|
58
|
+
for (const name of Object.keys(tools)) {
|
|
59
|
+
const diagnostics = { name, executable: tools[name], args: ["--version"], phase: "executable_read" };
|
|
60
60
|
try {
|
|
61
61
|
const digest = () => createHash("sha256").update(readBoundedReleaseFile(tools[name], 64 * 1024 * 1024)).digest("hex");
|
|
62
62
|
const executableSha256 = digest();
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
Object.assign(diagnostics, { phase: "version_probe"
|
|
67
|
-
error_code: result.error?.code ?? null,
|
|
68
|
-
timed_out: result.error?.code === "ETIMEDOUT",
|
|
69
|
-
stdout_bytes: result.stdout?.length ?? null, stderr_bytes: result.stderr?.length ?? null,
|
|
70
|
-
// Tool output can contain environment secrets; retain bounded custody
|
|
71
|
-
// hashes rather than echoing uncontrolled stderr into public receipts.
|
|
72
|
-
stderr_sha256: result.stderr ? createHash("sha256").update(result.stderr).digest("hex") : null });
|
|
63
|
+
const { result, diagnostics: observed } = runMemoryToolProbe(tools[name], ["--version"], {
|
|
64
|
+
cwd: path.dirname(tools[name]), env: probeEnv, maxBuffer: 64 * 1024
|
|
65
|
+
});
|
|
66
|
+
Object.assign(diagnostics, observed, { name, phase: "version_probe" });
|
|
73
67
|
if (result.error || result.signal || result.status !== 0 || !result.stdout || !result.stderr)
|
|
74
68
|
refuse(`tool_probe_failed:${name}`, diagnostics);
|
|
75
69
|
const output = new TextDecoder("utf-8", { fatal: true }).decode(result.stdout);
|
|
@@ -90,6 +84,16 @@ function probeTools(tools, env) {
|
|
|
90
84
|
}
|
|
91
85
|
return facts;
|
|
92
86
|
}
|
|
87
|
+
/** Shared deterministic executable selection; no project initialization or
|
|
88
|
+
* tool installation. Upgrade freezes this before content synchronization. */
|
|
89
|
+
export function inspectMemoryLintExecutable(env) {
|
|
90
|
+
const selected = executable("mb-lint", env);
|
|
91
|
+
return { executable: selected, probe: probeTools({ "mb-lint": selected }, env)["mb-lint"] };
|
|
92
|
+
}
|
|
93
|
+
export function inspectMemoryGitExecutable(env) {
|
|
94
|
+
const selected = executable("git", env);
|
|
95
|
+
return { executable: selected, probe: probeTools({ git: selected }, env).git };
|
|
96
|
+
}
|
|
93
97
|
function inspect(input, env, ownedPublication = false, qualifyTools = true, observe, lintReservation, sourceTools) {
|
|
94
98
|
if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,95}$/.test(input.requestId))
|
|
95
99
|
refuse("invalid_request_id");
|
|
@@ -145,12 +149,12 @@ function inspect(input, env, ownedPublication = false, qualifyTools = true, obse
|
|
|
145
149
|
const toolProbes = qualifyTools ? probeTools(tools, env) : undefined;
|
|
146
150
|
const gitEnv = Object.fromEntries(Object.entries(env).filter(([key]) => !key.startsWith("GIT_")));
|
|
147
151
|
const git = (directory, args, optional = false) => {
|
|
148
|
-
const result =
|
|
149
|
-
env: { ...gitEnv, GIT_OPTIONAL_LOCKS: "0" },
|
|
152
|
+
const { result, diagnostics } = runMemoryToolProbe(tools.git, ["-c", "core.fsmonitor=false", "-c", "core.untrackedCache=false", "-C", directory, ...args], {
|
|
153
|
+
cwd: directory, env: { ...gitEnv, GIT_OPTIONAL_LOCKS: "0" }, maxBuffer: 1024 * 1024,
|
|
150
154
|
});
|
|
151
155
|
if (result.error || result.signal || result.status !== 0 && !optional)
|
|
152
|
-
refuse("git_observation_failed");
|
|
153
|
-
return result.status === 0 ? result.stdout : null;
|
|
156
|
+
refuse("git_observation_failed", { ...diagnostics, phase: "git_observation" });
|
|
157
|
+
return result.status === 0 ? result.stdout.toString("utf8") : null;
|
|
154
158
|
};
|
|
155
159
|
for (const directory of new Set([project, workspace])) {
|
|
156
160
|
if (git(directory, ["rev-parse", "--is-bare-repository"])?.trim() !== "false"
|
|
@@ -7,15 +7,21 @@ import { assertImportAdmission } from "../storage/import-guard.js";
|
|
|
7
7
|
import { hybridDigest } from "./hybrid-contracts.js";
|
|
8
8
|
import { readBoundedReleaseFile } from "./release-source.js";
|
|
9
9
|
import { validateMemoryInitObjective, validateMemoryInitStartupScope } from "./memory-init-startup-custody.js";
|
|
10
|
-
import { prepareMemoryInitRequestInput } from "./memory-init-request-input.js";
|
|
10
|
+
import { prepareMemoryInitRequestInput, prepareMemoryUpgradeRequestInput } from "./memory-init-request-input.js";
|
|
11
11
|
const preparedCandidates = new WeakMap();
|
|
12
12
|
/** Only a genuinely retained candidate can supply fresh startup input. Copied
|
|
13
13
|
* objects and caller-authored snapshots never acquire candidate authority. */
|
|
14
14
|
export function captureMemoryInitRequestCandidate(candidate, home, scope, objective) {
|
|
15
|
-
const
|
|
16
|
-
if (!
|
|
15
|
+
const witness = candidate && typeof candidate === "object" ? preparedCandidates.get(candidate) : undefined;
|
|
16
|
+
if (!witness || witness.operation !== "memory-init")
|
|
17
17
|
refuse("unprepared_request_candidate");
|
|
18
|
-
return capture(home, scope, objective);
|
|
18
|
+
return witness.capture(home, scope, objective);
|
|
19
|
+
}
|
|
20
|
+
export function captureMemoryUpgradeRequestCandidate(candidate, home, scope, objective) {
|
|
21
|
+
const witness = candidate && typeof candidate === "object" ? preparedCandidates.get(candidate) : undefined;
|
|
22
|
+
if (!witness || witness.operation !== "memory-upgrade")
|
|
23
|
+
refuse("unprepared_request_candidate");
|
|
24
|
+
return witness.capture(home, scope, objective);
|
|
19
25
|
}
|
|
20
26
|
const sha = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
21
27
|
function refuse(reason) {
|
|
@@ -47,10 +53,42 @@ function syncDirectory(file) {
|
|
|
47
53
|
fs.closeSync(fd);
|
|
48
54
|
}
|
|
49
55
|
}
|
|
56
|
+
const candidateKey = (scope, operation) => hybridDigest(operation === "memory-upgrade"
|
|
57
|
+
? { projectRoot: scope.projectRoot, requestId: scope.requestId } : scope);
|
|
50
58
|
/** Read-only preparation and exact byte capture. A candidate is not startup,
|
|
51
59
|
* storage, engine or dispatch authority. Retain only after prerequisite checks;
|
|
52
60
|
* no missing candidate component is reconstructed from ambient selections. */
|
|
53
61
|
export function prepareMemoryInitRequestCandidate(home, scope, selection, intake) {
|
|
62
|
+
return prepareMemoryRequestCandidate(home, scope, selection, intake, "memory-init");
|
|
63
|
+
}
|
|
64
|
+
export function prepareMemoryUpgradeRequestCandidate(home, scope, selection, intake) {
|
|
65
|
+
return prepareMemoryRequestCandidate(home, scope, selection, intake, "memory-upgrade");
|
|
66
|
+
}
|
|
67
|
+
/** Exact original input recovery before an incompatible SQLite store is opened.
|
|
68
|
+
* No profile, installed release, model, provider or missing component is chosen. */
|
|
69
|
+
export function readMemoryUpgradeRequestCandidate(home, scope) {
|
|
70
|
+
validateMemoryInitStartupScope(scope);
|
|
71
|
+
directory(home);
|
|
72
|
+
const namespace = path.join(home, "memory-upgrade-request-candidates"), key = candidateKey(scope, "memory-upgrade");
|
|
73
|
+
if (!exists(namespace))
|
|
74
|
+
return null;
|
|
75
|
+
directory(namespace);
|
|
76
|
+
const root = path.join(namespace, key), marker = path.join(namespace, `${key}.json`);
|
|
77
|
+
if (!exists(root) && !exists(marker))
|
|
78
|
+
return null;
|
|
79
|
+
if (!exists(root) || !exists(marker))
|
|
80
|
+
refuse("candidate_custody_missing");
|
|
81
|
+
directory(root);
|
|
82
|
+
const selection = readBoundedReleaseFile(path.join(root, "selection.json"), 256 * 1024);
|
|
83
|
+
const intake = readBoundedReleaseFile(path.join(root, "intake.txt"), 64 * 1024);
|
|
84
|
+
const candidate = prepareMemoryUpgradeRequestCandidate(home, scope, selection, intake);
|
|
85
|
+
// Existing-candidate retain verifies only; it cannot fill partial publication.
|
|
86
|
+
if (candidate.retain().created)
|
|
87
|
+
refuse("candidate_custody_missing");
|
|
88
|
+
return { candidate, objective: candidate.objective,
|
|
89
|
+
input: captureMemoryUpgradeRequestCandidate(candidate, home, scope, candidate.objective) };
|
|
90
|
+
}
|
|
91
|
+
function prepareMemoryRequestCandidate(home, scope, selection, intake, operation) {
|
|
54
92
|
validateMemoryInitStartupScope(scope);
|
|
55
93
|
scope = structuredClone(scope);
|
|
56
94
|
const selectionBytes = Buffer.from(selection), intakeBytes = Buffer.from(intake);
|
|
@@ -64,17 +102,21 @@ export function prepareMemoryInitRequestCandidate(home, scope, selection, intake
|
|
|
64
102
|
return refuse("invalid_intake_utf8");
|
|
65
103
|
}
|
|
66
104
|
validateMemoryInitObjective(objective);
|
|
105
|
+
// Original-input validity is not installed-target or storage admission.
|
|
106
|
+
if (operation === "memory-upgrade")
|
|
107
|
+
prepareMemoryUpgradeRequestInput(scope, selectionBytes, intakeBytes, objective);
|
|
67
108
|
const homeOwner = directory(home);
|
|
68
109
|
if ([scope.projectRoot, scope.workspaceRoot].some(root => {
|
|
69
110
|
const relative = path.relative(root, home);
|
|
70
111
|
return relative === "" || !path.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path.sep}`);
|
|
71
112
|
}))
|
|
72
113
|
refuse("candidate_home_inside_project");
|
|
73
|
-
|
|
74
|
-
|
|
114
|
+
if (operation === "memory-init")
|
|
115
|
+
assertImportAdmission(home);
|
|
116
|
+
const namespace = path.join(home, `${operation}-request-candidates`), key = candidateKey(scope, operation);
|
|
75
117
|
const root = path.join(namespace, key), marker = path.join(namespace, `${key}.json`);
|
|
76
118
|
const inputFile = path.join(root, "intake.txt"), selectionFile = path.join(root, "selection.json");
|
|
77
|
-
const facts = { schema_id:
|
|
119
|
+
const facts = { schema_id: `dd-flow/${operation}-request-candidate@1`, home, home_owner: homeOwner, scope,
|
|
78
120
|
selection_sha256: sha(selectionBytes), intake_sha256: sha(intakeBytes) };
|
|
79
121
|
const verify = () => {
|
|
80
122
|
if (hybridDigest(directory(home)) !== hybridDigest(homeOwner))
|
|
@@ -84,7 +126,16 @@ export function prepareMemoryInitRequestCandidate(home, scope, selection, intake
|
|
|
84
126
|
refuse("candidate_custody_missing");
|
|
85
127
|
const expected = { ...facts, owner: directory(root) };
|
|
86
128
|
const bytes = readBoundedReleaseFile(marker, 16 * 1024);
|
|
87
|
-
|
|
129
|
+
let observed;
|
|
130
|
+
try {
|
|
131
|
+
observed = JSON.parse(bytes.toString("utf8"));
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return refuse("candidate_request_changed");
|
|
135
|
+
}
|
|
136
|
+
// Metadata key order is not request identity. Original intake/selection
|
|
137
|
+
// remain byte-bound below; a reader must not rewrite their marker.
|
|
138
|
+
if (hybridDigest(observed) !== hybridDigest(expected))
|
|
88
139
|
refuse("candidate_request_changed");
|
|
89
140
|
if (!exists(inputFile) || !exists(selectionFile))
|
|
90
141
|
refuse("candidate_input_missing");
|
|
@@ -95,6 +146,10 @@ export function prepareMemoryInitRequestCandidate(home, scope, selection, intake
|
|
|
95
146
|
if (exists(namespace))
|
|
96
147
|
directory(namespace);
|
|
97
148
|
const found = exists(root) || exists(marker);
|
|
149
|
+
// Migration reconciliation must be able to read its original upgrade input
|
|
150
|
+
// while ordinary writers are fenced. It gains no permit or fresh write.
|
|
151
|
+
if (!found && operation === "memory-upgrade")
|
|
152
|
+
assertImportAdmission(home);
|
|
98
153
|
if (found)
|
|
99
154
|
verify();
|
|
100
155
|
let retained = false;
|
|
@@ -102,7 +157,7 @@ export function prepareMemoryInitRequestCandidate(home, scope, selection, intake
|
|
|
102
157
|
const candidate = {
|
|
103
158
|
objective,
|
|
104
159
|
retain: () => {
|
|
105
|
-
if (found) {
|
|
160
|
+
if (found || operation === "memory-upgrade" && retained) {
|
|
106
161
|
verify();
|
|
107
162
|
return complete(false);
|
|
108
163
|
}
|
|
@@ -145,13 +200,14 @@ export function prepareMemoryInitRequestCandidate(home, scope, selection, intake
|
|
|
145
200
|
}
|
|
146
201
|
}
|
|
147
202
|
};
|
|
148
|
-
preparedCandidates.set(candidate, (expectedHome, expectedScope, expectedObjective) => {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
203
|
+
preparedCandidates.set(candidate, { operation, capture: (expectedHome, expectedScope, expectedObjective) => {
|
|
204
|
+
if (!retained)
|
|
205
|
+
refuse("request_candidate_not_retained");
|
|
206
|
+
if (home !== expectedHome || hybridDigest(scope) !== hybridDigest(expectedScope) || objective !== expectedObjective)
|
|
207
|
+
refuse("foreign_request_candidate");
|
|
208
|
+
verify();
|
|
209
|
+
return operation === "memory-upgrade" ? prepareMemoryUpgradeRequestInput(scope, selectionBytes, intakeBytes, objective)
|
|
210
|
+
: prepareMemoryInitRequestInput(scope, selectionBytes, intakeBytes, objective);
|
|
211
|
+
} });
|
|
156
212
|
return Object.freeze(candidate);
|
|
157
213
|
}
|
|
@@ -26,9 +26,9 @@ function text(bytes) {
|
|
|
26
26
|
}
|
|
27
27
|
/** Data-only decoding for SQL custody/recovery. No Home, files, engine registry,
|
|
28
28
|
* installed canon, current profiles or provider is consulted. */
|
|
29
|
-
|
|
29
|
+
function readMemoryRequestInput(value, scope, objective, operation) {
|
|
30
30
|
if (!exact(value, ["schema_id", "scope", "intake_base64", "intake_sha256", "selection_base64", "selection_sha256"])
|
|
31
|
-
|| !object(value) || value.schema_id !==
|
|
31
|
+
|| !object(value) || value.schema_id !== `dd-flow/${operation}-request-input@1`
|
|
32
32
|
|| hybridDigest(value.scope) !== hybridDigest(scope))
|
|
33
33
|
refuse("invalid_input_scope");
|
|
34
34
|
const intake = decode(value.intake_base64, value.intake_sha256, 64 * 1024);
|
|
@@ -44,8 +44,10 @@ export function readMemoryInitRequestInput(value, scope, objective) {
|
|
|
44
44
|
throw error;
|
|
45
45
|
return refuse("invalid_selection_json");
|
|
46
46
|
}
|
|
47
|
-
if (!exact(selection, ["schema_id", "scope", "execution", "git_policy", "objective", "engine_checksum", "receipts"
|
|
48
|
-
|
|
47
|
+
if (!exact(selection, ["schema_id", "scope", "execution", "git_policy", "objective", "engine_checksum", "receipts",
|
|
48
|
+
...(operation === "memory-upgrade" ? ["target_installation_sha256"] : [])])
|
|
49
|
+
|| !object(selection) || selection.schema_id !== `dd-flow/${operation}-request-selection@1`
|
|
50
|
+
|| operation === "memory-upgrade" && !digest(selection.target_installation_sha256)
|
|
49
51
|
|| hybridDigest(selection.scope) !== hybridDigest(scope) || selection.objective !== objective
|
|
50
52
|
|| !object(selection.execution) || !exact(selection.git_policy, ["route", "integrationBranch"])
|
|
51
53
|
|| !object(selection.git_policy) || !["integration_branch_direct", "feature_worktree"].includes(String(selection.git_policy.route))
|
|
@@ -55,12 +57,26 @@ export function readMemoryInitRequestInput(value, scope, objective) {
|
|
|
55
57
|
refuse("invalid_original_selection");
|
|
56
58
|
return { intake, selectionBytes, selection: selection };
|
|
57
59
|
}
|
|
58
|
-
|
|
60
|
+
function prepareMemoryRequestInput(scope, selection, intake, objective, operation) {
|
|
59
61
|
if (!selection.byteLength || selection.byteLength > 256 * 1024 || !intake.byteLength || intake.byteLength > 64 * 1024)
|
|
60
62
|
refuse("unbounded_original_input");
|
|
61
|
-
const value = { schema_id:
|
|
63
|
+
const value = { schema_id: `dd-flow/${operation}-request-input@1`, scope: structuredClone(scope),
|
|
62
64
|
intake_base64: Buffer.from(intake).toString("base64"), intake_sha256: sha(intake),
|
|
63
65
|
selection_base64: Buffer.from(selection).toString("base64"), selection_sha256: sha(selection) };
|
|
64
|
-
|
|
66
|
+
readMemoryRequestInput(value, scope, objective, operation);
|
|
65
67
|
return value;
|
|
66
68
|
}
|
|
69
|
+
export function readMemoryInitRequestInput(value, scope, objective) {
|
|
70
|
+
const read = readMemoryRequestInput(value, scope, objective, "memory-init");
|
|
71
|
+
return { ...read, selection: read.selection };
|
|
72
|
+
}
|
|
73
|
+
export function readMemoryUpgradeRequestInput(value, scope, objective) {
|
|
74
|
+
const read = readMemoryRequestInput(value, scope, objective, "memory-upgrade");
|
|
75
|
+
return { ...read, selection: read.selection };
|
|
76
|
+
}
|
|
77
|
+
export function prepareMemoryInitRequestInput(scope, selection, intake, objective) {
|
|
78
|
+
return prepareMemoryRequestInput(scope, selection, intake, objective, "memory-init");
|
|
79
|
+
}
|
|
80
|
+
export function prepareMemoryUpgradeRequestInput(scope, selection, intake, objective) {
|
|
81
|
+
return prepareMemoryRequestInput(scope, selection, intake, objective, "memory-upgrade");
|
|
82
|
+
}
|
|
@@ -9,6 +9,9 @@ import { readMemoryInitRunAllocation } from "./memory-init-run-allocation.js";
|
|
|
9
9
|
import { readMemoryInitProjectBootstrap } from "./memory-init-project-bootstrap.js";
|
|
10
10
|
import { bundledHandlerContext } from "./stage-handlers.js";
|
|
11
11
|
import { hybridDigest } from "./hybrid-contracts.js";
|
|
12
|
+
import { readMemoryUpgradeBootstrapReadiness } from "./memory-upgrade-bootstrap-readiness.js";
|
|
13
|
+
import { readUpgradeStorageReadiness } from "./memory-upgrade-storage-readiness.js";
|
|
14
|
+
import { nextDescriptorRunId } from "./descriptor-run-start.js";
|
|
12
15
|
const bytes = (value) => Buffer.from(JSON.stringify(value));
|
|
13
16
|
const hash = (value) => createHash("sha256").update(value).digest("hex");
|
|
14
17
|
function refuse(reason) {
|
|
@@ -29,6 +32,48 @@ export function prepareMemoryInitRunBundle(context, input) {
|
|
|
29
32
|
if (!ready || !allocation || !bootstrap || allocation.project_id !== ready.projectId
|
|
30
33
|
|| ready.authority.binding.execution_sha256 !== input.execution.sha256)
|
|
31
34
|
refuse("bootstrap_selection_changed");
|
|
35
|
+
input.source.revalidate();
|
|
36
|
+
const flow = input.source.resolveRegisteredFlow(), canon = input.source.canonProvenance();
|
|
37
|
+
const release = JSON.parse(Buffer.from(ready.authority.binding.installation_manifest_base64, "base64").toString("utf8"));
|
|
38
|
+
if (input.source.installationSha256() !== ready.authority.binding.installation_manifest_sha256
|
|
39
|
+
|| flow.sha256 !== snapshot.settings.descriptor_sha256 || snapshot.settings.flow_id !== "mb-init"
|
|
40
|
+
|| canon.version !== release.canon.version || canon.commit !== release.canon.commit
|
|
41
|
+
|| canon.archive_sha256 !== release.canon.archive.sha256
|
|
42
|
+
|| !input.engine.manifestBytes().equals(Buffer.from(ready.authority.binding.engine_manifest_base64, "base64")))
|
|
43
|
+
refuse("frozen_source_changed");
|
|
44
|
+
return prepareMemoryRunBundle(input, ready, { runId: allocation.run_id, boundAt: bootstrap.receipt.created_at,
|
|
45
|
+
flowId: "mb-init", projectPack: { sha256: ready.authority.binding.pack_sha256, source_commit: canon.commit } });
|
|
46
|
+
}
|
|
47
|
+
/** Upgrade executes the target canon but starts with the original project pack.
|
|
48
|
+
* It must not claim that publication has already happened at RUN allocation. */
|
|
49
|
+
export function prepareMemoryUpgradeRunBundle(context, input) {
|
|
50
|
+
if (!(input.source instanceof PreparedInstalledFlowSource) || !(input.execution instanceof FrozenGenericExecution)
|
|
51
|
+
|| !(input.engine instanceof AdmittedFrozenEngine))
|
|
52
|
+
refuse("unprepared_selection");
|
|
53
|
+
const snapshot = input.execution.snapshot(), scope = { requestId: snapshot.scope.requestId,
|
|
54
|
+
projectRoot: snapshot.scope.projectRoot, workspaceRoot: snapshot.scope.workspace };
|
|
55
|
+
const ready = readMemoryUpgradeBootstrapReadiness(context, scope), storage = readUpgradeStorageReadiness(context, scope);
|
|
56
|
+
if (!ready || !storage?.selection || !ready.hasFlowCapability || !ready.hasGitPolicy || !ready.hasLintCapability
|
|
57
|
+
|| !ready.hasPreservationBaseline || ready.authority.binding.execution_sha256 !== input.execution.sha256
|
|
58
|
+
|| ready.authority.binding.selection_sha256 !== storage.selection.sha256)
|
|
59
|
+
refuse("bootstrap_selection_changed");
|
|
60
|
+
const p = storage.selection.payload, canon = input.source.canonProvenance();
|
|
61
|
+
input.source.revalidate();
|
|
62
|
+
if (input.source.sha256 !== p.source.sha256 || input.source.installationSha256() !== p.release.manifest_sha256
|
|
63
|
+
|| snapshot.settings.flow_id !== "mb-upgrade" || input.source.resolveRegisteredFlow().sha256 !== snapshot.settings.descriptor_sha256
|
|
64
|
+
|| canon.version !== storage.selection.release.canon.version || canon.commit !== storage.selection.release.canon.commit
|
|
65
|
+
|| canon.archive_sha256 !== storage.selection.release.canon.archive.sha256
|
|
66
|
+
|| !input.engine.manifestBytes().equals(Buffer.from(ready.authority.binding.engine_manifest_base64, "base64")))
|
|
67
|
+
refuse("frozen_source_changed");
|
|
68
|
+
return prepareMemoryRunBundle(input, ready, {
|
|
69
|
+
runId: nextDescriptorRunId(context, { projectRoot: scope.projectRoot, requestId: scope.requestId, slug: "mb-upgrade" }),
|
|
70
|
+
boundAt: ready.createdAt, flowId: "mb-upgrade",
|
|
71
|
+
projectPack: { sha256: ready.authority.receipt.project_current.pack_sha256, source_commit: p.current.source_commit }
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function prepareMemoryRunBundle(input, ready, selected) {
|
|
75
|
+
const snapshot = input.execution.snapshot(), scope = { requestId: snapshot.scope.requestId,
|
|
76
|
+
projectRoot: snapshot.scope.projectRoot, workspaceRoot: snapshot.scope.workspace };
|
|
32
77
|
const { bootstrapIdentity, receipts: bootstrapReceipts } = ready.admission;
|
|
33
78
|
if (!bootstrapIdentity || !bootstrapReceipts)
|
|
34
79
|
refuse("bootstrap_admission_missing");
|
|
@@ -43,22 +88,14 @@ export function prepareMemoryInitRunBundle(context, input) {
|
|
|
43
88
|
refuse("execution_receipt_collision");
|
|
44
89
|
receipts.set(reference.sha256, structuredClone(fact));
|
|
45
90
|
}
|
|
46
|
-
input.source.revalidate();
|
|
47
91
|
const flow = input.source.resolveRegisteredFlow(), canon = input.source.canonProvenance();
|
|
48
|
-
const
|
|
49
|
-
if (input.source.installationSha256() !== ready.authority.binding.installation_manifest_sha256
|
|
50
|
-
|| flow.sha256 !== snapshot.settings.descriptor_sha256 || snapshot.settings.flow_id !== "mb-init"
|
|
51
|
-
|| canon.version !== release.canon.version || canon.commit !== release.canon.commit
|
|
52
|
-
|| canon.archive_sha256 !== release.canon.archive.sha256
|
|
53
|
-
|| !input.engine.manifestBytes().equals(Buffer.from(ready.authority.binding.engine_manifest_base64, "base64")))
|
|
54
|
-
refuse("frozen_source_changed");
|
|
55
|
-
const manifest = input.engine.manifest(), runId = allocation.run_id;
|
|
92
|
+
const manifest = input.engine.manifest(), runId = selected.runId;
|
|
56
93
|
if (typeof manifest.integrity.checksum !== "string")
|
|
57
94
|
refuse("missing_engine_checksum");
|
|
58
95
|
// The accepted bootstrap timestamp makes reconstruction stable after a lost
|
|
59
96
|
// reply. Current time is not a new selector for this request's engine binding.
|
|
60
97
|
const binding = { schema_id: "dd-flow/run-engine-binding@1", run_id: runId, project_root: scope.projectRoot,
|
|
61
|
-
bound_at:
|
|
98
|
+
bound_at: selected.boundAt, source: "run_creation", reason: selected.flowId === "mb-init" ? "Request-bound memory init bootstrap" : "Request-bound memory upgrade bootstrap",
|
|
62
99
|
engine: { package_name: manifest.package_name, package_version: manifest.package_version, engine_version: manifest.engine_version,
|
|
63
100
|
integrity_checksum: manifest.integrity.checksum, snapshot_root: manifest.snapshot_root }, probe: { status: "not_required", command: null } };
|
|
64
101
|
const blobs = input.source.blobs();
|
|
@@ -84,8 +121,8 @@ export function prepareMemoryInitRunBundle(context, input) {
|
|
|
84
121
|
for (const evidence of ready.authority.evidence)
|
|
85
122
|
put(evidence.path, Buffer.from(evidence.base64, "base64"));
|
|
86
123
|
const receipt = put("receipts/bootstrap.json", bytes(ready.authority.receipt));
|
|
87
|
-
const provenance = { canon, project_pack:
|
|
88
|
-
const bundle = { schema_id: "dd-flow/execution-bundle@1", run_id: runId, flow_id:
|
|
124
|
+
const provenance = { canon, project_pack: selected.projectPack };
|
|
125
|
+
const bundle = { schema_id: "dd-flow/execution-bundle@1", run_id: runId, flow_id: selected.flowId,
|
|
89
126
|
descriptor: input.source.descriptorReference(), execution_settings: execution, profile_settings: profile,
|
|
90
127
|
engine_binding: engineBinding, engine_manifest: engineManifest, bootstrap_receipts: [receipt], ...provenance,
|
|
91
128
|
inventory: [...blobs].map(([logical, content]) => ({ path: logical, sha256: hash(content) })) };
|