@deksden-com/dd-flow-cli 0.9.0-beta.144 → 0.9.0-beta.145
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/README.md +13 -8
- package/dist/build-info.json +7 -7
- package/dist/cli/command-inputs.js +9 -3
- package/dist/cli/command-metadata.js +33 -14
- package/dist/cli/help.js +32 -1
- package/dist/cli/hook-ingress.js +27 -3
- package/dist/cli/memory-flow-admission.js +7 -3
- package/dist/cli/memory-init-invocation.js +10 -3
- package/dist/cli/memory-init.js +74 -68
- package/dist/cli/memory-storage-admission.js +3 -2
- package/dist/cli/memory-storage-qualification.js +105 -0
- package/dist/cli/memory-storage-transition.js +123 -0
- package/dist/cli/memory-upgrade.js +189 -0
- package/dist/cli/native-hook-ingress.js +14 -4
- package/dist/cli/native-hook-preparation.js +55 -0
- package/dist/cli/response-file.js +2 -1
- package/dist/cli/run-cli.js +70 -32
- package/dist/cli/setup.js +2 -1
- package/dist/cli.js +12 -0
- package/dist/domain/stage-handler-identities.js +8 -0
- package/dist/harness-runtime/bin/dd-agy.mjs +3 -3
- package/dist/harness-runtime/bin/dd-grok.mjs +3 -3
- package/dist/harness-runtime/bin/dd-zcode.mjs +5 -5
- package/dist/harness-runtime/lib/agy-error-boundary.mjs +58 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +62 -20
- package/dist/harness-runtime/lib/dd-agy.mjs +17 -3
- package/dist/harness-runtime/lib/dd-droid.mjs +1 -1
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +70 -13
- package/dist/harness-runtime/lib/dd-grok.mjs +6 -3
- package/dist/harness-runtime/lib/dd-zcode.mjs +44 -14
- package/dist/harness-runtime/lib/delegation-instructions.mjs +1 -1
- package/dist/harness-runtime/lib/entity-references.d.mts +2 -0
- package/dist/harness-runtime/lib/entity-references.mjs +9 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +13 -6
- package/dist/harness-runtime/lib/native-hook-command.mjs +4 -4
- package/dist/harness-runtime/lib/opencode-plugin.mjs +3 -3
- package/dist/harness-runtime/lib/operation-errors.mjs +1 -1
- package/dist/harness-runtime/lib/provider-overload-policy.d.mts +4 -2
- package/dist/harness-runtime/lib/provider-overload-policy.mjs +19 -6
- package/dist/harness-runtime/lib/runner-events.mjs +3 -1
- package/dist/harness-runtime/lib/setup-progress.mjs +8 -6
- package/dist/schemas/mb-upgrade-stage-result.schema.json +863 -0
- package/dist/schemas/memory-repair-work-result.schema.json +9 -2
- package/dist/schemas/release-impact.schema.json +3 -3
- package/dist/schemas/stage-start-response.schema.json +4 -0
- package/dist/services/accepted-intent.js +13 -1
- package/dist/services/canon.js +43 -7
- package/dist/services/cli-operation-classifier.js +1 -1
- package/dist/services/cli-upgrade-allowlist.js +1 -1
- package/dist/services/code-checks.js +11 -4
- package/dist/services/compatibility-preflight.js +5 -2
- package/dist/services/controller-fanout.js +21 -4
- package/dist/services/controller-memory-task.js +45 -0
- package/dist/services/controller-memory-worker.js +48 -0
- package/dist/services/dashboard.js +2 -2
- package/dist/services/descriptor-run-start.js +26 -19
- package/dist/services/descriptor-stage-coordinator.js +30 -10
- package/dist/services/descriptor-stage-start.js +40 -4
- package/dist/services/engines.js +64 -14
- package/dist/services/eval-snapshots.js +104 -93
- package/dist/services/external-work-launch.js +5 -3
- package/dist/services/flow-catalog.js +8 -4
- package/dist/services/frozen-engine.js +113 -11
- package/dist/services/hook-memory-routing.js +188 -0
- package/dist/services/hook-recovery-guard.js +14 -0
- package/dist/services/hook-session-observation.js +24 -0
- package/dist/services/hooks.js +27 -8
- package/dist/services/hybrid-contracts.js +2 -13
- package/dist/services/hybrid-digest.js +14 -0
- package/dist/services/installed-flow-source.js +25 -4
- package/dist/services/lifecycle-invocations.js +33 -5
- package/dist/services/memory-accepted-composition.js +5 -4
- package/dist/services/memory-completed-correction-wave.js +23 -15
- package/dist/services/memory-compose-handler.js +163 -21
- package/dist/services/memory-composition-acceptance.js +186 -10
- package/dist/services/memory-composition-graph.js +42 -11
- package/dist/services/memory-composition-registration.js +23 -8
- package/dist/services/memory-composition-work-custody.js +14 -9
- package/dist/services/memory-composition-work-results.js +15 -3
- package/dist/services/memory-corrected-review-batch.js +10 -3
- package/dist/services/memory-corrected-review-findings.js +4 -2
- package/dist/services/memory-corrected-review-inputs.js +10 -5
- package/dist/services/memory-corrected-review-receipt.js +17 -9
- package/dist/services/memory-corrected-review-registration.js +3 -2
- package/dist/services/memory-correction-check-receipt.js +20 -4
- package/dist/services/memory-custody-snapshot.js +36 -0
- package/dist/services/memory-finish-evidence.js +44 -22
- package/dist/services/memory-finish-handler.js +37 -23
- package/dist/services/memory-finish-integration.js +218 -48
- package/dist/services/memory-finish-readiness.js +66 -19
- package/dist/services/memory-git-integration.js +87 -18
- package/dist/services/memory-historical-corrected-review.js +21 -15
- package/dist/services/memory-historical-original-review.js +22 -9
- package/dist/services/memory-historical-repair-registration.js +14 -8
- package/dist/services/memory-historical-second-review.js +30 -11
- package/dist/services/memory-init-contexts.js +28 -2
- package/dist/services/memory-init-controller-start.js +19 -8
- package/dist/services/memory-init-git-policy.js +50 -21
- package/dist/services/memory-init-lint-capability.js +21 -3
- package/dist/services/memory-init-native-capacity.js +13 -0
- package/dist/services/memory-init-preflight.js +11 -1
- package/dist/services/memory-init-request-candidate.js +73 -17
- package/dist/services/memory-init-request-input.js +23 -7
- package/dist/services/memory-init-run-bundle.js +49 -12
- package/dist/services/memory-init-storage.js +17 -7
- package/dist/services/memory-layer.js +68 -4
- package/dist/services/memory-lint-config.js +111 -2
- package/dist/services/memory-native-review-copy.js +5 -67
- package/dist/services/memory-normal-flow-entry.js +12 -0
- package/dist/services/memory-ordinary-defaults.js +22 -5
- package/dist/services/memory-preflight-handler.js +15 -0
- package/dist/services/memory-repair-current-view.js +1 -1
- package/dist/services/memory-repair-document-delta.js +70 -33
- package/dist/services/memory-repair-history.js +12 -7
- package/dist/services/memory-repair-physical-binding.js +1 -119
- package/dist/services/memory-repair-publication-plan.js +5 -3
- package/dist/services/memory-repair-publication-source.js +1 -1
- package/dist/services/memory-repair-publication.js +3 -2
- package/dist/services/memory-repair-registration.js +2 -1
- package/dist/services/memory-repair-work-baseline.js +4 -2
- package/dist/services/memory-repair-work-completion.js +32 -12
- package/dist/services/memory-repair-work-results.js +21 -2
- package/dist/services/memory-repair-workspace.js +11 -2
- package/dist/services/memory-repair-writer-copy.js +2 -1
- package/dist/services/memory-result-binding.js +78 -12
- package/dist/services/memory-retained-review-authority.js +17 -8
- package/dist/services/memory-retained-second-baseline.js +1 -1
- package/dist/services/memory-retained-second-repairs.js +12 -6
- package/dist/services/memory-review-correction-plan.js +47 -12
- package/dist/services/memory-review-handler.js +17 -17
- package/dist/services/memory-review-inputs.js +55 -9
- package/dist/services/memory-review-limits.js +10 -0
- package/dist/services/memory-review-receipt.js +2 -2
- package/dist/services/memory-review-registration.js +13 -4
- package/dist/services/memory-review-work-custody.js +3 -1
- package/dist/services/memory-review-work-input.js +26 -7
- package/dist/services/memory-review-work-results.js +26 -4
- package/dist/services/memory-second-corrected-review-inputs.js +5 -3
- package/dist/services/memory-second-correction-plan.js +2 -1
- package/dist/services/memory-second-repair-launch.js +4 -4
- package/dist/services/memory-second-repair-source.js +1 -1
- package/dist/services/memory-stage-dispatch.js +38 -1
- package/dist/services/memory-stage-results.js +113 -22
- package/dist/services/memory-upgrade-analysis-handler.js +243 -0
- package/dist/services/memory-upgrade-bootstrap-readiness.js +251 -0
- package/dist/services/memory-upgrade-evidence.js +100 -0
- package/dist/services/memory-upgrade-inventory.js +442 -0
- package/dist/services/memory-upgrade-publication-custody.js +73 -0
- package/dist/services/memory-upgrade-publication.js +413 -0
- package/dist/services/memory-upgrade-selection.js +430 -0
- package/dist/services/memory-upgrade-start.js +60 -0
- package/dist/services/memory-upgrade-storage-readiness.js +340 -0
- package/dist/services/memory-verification-acceptance.js +38 -17
- package/dist/services/memory-verification-checks.js +77 -23
- package/dist/services/memory-verification-receipt.js +6 -5
- package/dist/services/memory-verify-handler.js +15 -13
- package/dist/services/memory-work-result-json.js +12 -0
- package/dist/services/memory-work-result-preparation.js +5 -4
- package/dist/services/merge-server.js +4 -2
- package/dist/services/migrations.js +127 -98
- package/dist/services/physical-startup.js +45 -0
- package/dist/services/project-registry.js +97 -0
- package/dist/services/project-summary.js +1 -1
- package/dist/services/projects.js +3 -93
- package/dist/services/prompt-delivery.js +71 -0
- package/dist/services/provider-capacity.js +69 -8
- package/dist/services/recovery-snapshot-database.js +32 -4
- package/dist/services/repair-intents.js +1 -12
- package/dist/services/repair-receipt.js +13 -0
- package/dist/services/run-control-worker.js +35 -18
- package/dist/services/run-controller-process.js +2 -6
- package/dist/services/run-controller-recovery.js +6 -1
- package/dist/services/run-controller.js +131 -80
- package/dist/services/run-recovery-guard.js +30 -0
- package/dist/services/run-recovery.js +11 -44
- package/dist/services/run-start-custody.js +17 -5
- package/dist/services/runtime-scope-worker.js +46 -19
- package/dist/services/sessions.js +3 -24
- package/dist/services/setup.js +63 -13
- package/dist/services/stage-handlers.js +9 -7
- package/dist/services/stage-pause.js +108 -44
- package/dist/services/version-status.js +1 -1
- package/dist/services/vnext-fanout.js +21 -9
- package/dist/services/vnext-plan-review.js +3 -5
- package/dist/services/work-registry.js +80 -33
- package/dist/services/workspace-files.js +1 -1
- package/dist/shared/entity-references.js +1 -8
- package/dist/storage/data-preservation.js +65 -0
- package/dist/storage/database.js +53 -19
- package/dist/storage/import-guard.js +84 -27
- package/dist/storage/memory-upgrade-schema-bootstrap.js +38 -0
- package/dist/storage/paired-writer-migration.js +424 -0
- package/dist/storage/writer-contract.js +11 -4
- package/dist/storage/writer-migration.js +313 -52
- package/dist/storage/writer-restore.js +217 -0
- package/package.json +8 -4
|
@@ -5,7 +5,7 @@ import { PreparedMemoryFinishReview } from "./memory-finish-handler.js";
|
|
|
5
5
|
import { readMemoryInitBootstrapReadiness } from "./memory-init-bootstrap-readiness.js";
|
|
6
6
|
import { readMemoryInitGitPolicy } from "./memory-init-git-policy.js";
|
|
7
7
|
import { hybridDigest } from "./hybrid-contracts.js";
|
|
8
|
-
import { observeReviewedMemoryGitIntegration, reviewedMemoryGitFiles } from "./memory-git-integration.js";
|
|
8
|
+
import { observeReviewedMemoryGitIntegration, reviewedMemoryGitFiles, isReviewedMemoryGitFile, assertMemoryGitRemovalSources } from "./memory-git-integration.js";
|
|
9
9
|
import { createHash } from "node:crypto";
|
|
10
10
|
import { readBoundedReleaseFile } from "./release-source.js";
|
|
11
11
|
import { spawnSync } from "node:child_process";
|
|
@@ -17,14 +17,34 @@ import { readDescriptorStageInput } from "./descriptor-stage-coordinator.js";
|
|
|
17
17
|
import { appendAudit } from "./audit.js";
|
|
18
18
|
import fs from "node:fs";
|
|
19
19
|
import path from "node:path";
|
|
20
|
+
import { readMemoryUpgradeBootstrapReadiness } from "./memory-upgrade-bootstrap-readiness.js";
|
|
21
|
+
import { readUpgradeStorageReadiness } from "./memory-upgrade-storage-readiness.js";
|
|
22
|
+
import { readCompletedMemoryUpgradePublication } from "./memory-upgrade-publication-custody.js";
|
|
23
|
+
import { readDescriptorRootWork } from "./descriptor-root-work.js";
|
|
24
|
+
import { stageGraphScope } from "./stage-work-graph.js";
|
|
25
|
+
import { selectMemoryCompositionRegistration } from "./memory-composition-registration.js";
|
|
26
|
+
import { readAcceptedStageData } from "./stage-report-renderer.js";
|
|
27
|
+
import { localEnvironmentAllowed } from "./workspace-files.js";
|
|
28
|
+
import { observeMemoryDocumentInventory } from "./memory-repair-document-delta.js";
|
|
20
29
|
const token = Symbol("memory-finish-integration");
|
|
21
30
|
function refuse(reason) {
|
|
22
31
|
throw new AppError("memory_finish_integration_invalid", "Finish lacks policy-bound reviewed Git integration", 2, { reason, effect: "no_effect" });
|
|
23
32
|
}
|
|
24
|
-
function
|
|
33
|
+
export function readMemoryFinishGitSelection(context, scope, bundle) {
|
|
25
34
|
assertPreparedRunBundleSqlAuthority(context, scope, bundle);
|
|
26
35
|
const frozen = bundle.execution.snapshot().scope;
|
|
27
36
|
const request = { requestId: frozen.requestId, projectRoot: frozen.projectRoot, workspaceRoot: frozen.workspace };
|
|
37
|
+
if (bundle.descriptor.descriptor.id === "mb-upgrade") {
|
|
38
|
+
const readiness = readMemoryUpgradeBootstrapReadiness(context, request), storage = readUpgradeStorageReadiness(context, request);
|
|
39
|
+
const git = storage?.selection?.payload.git;
|
|
40
|
+
if (!readiness?.hasGitPolicy || !storage?.selection || !git || readiness.projectId !== scope.projectId
|
|
41
|
+
|| readiness.authority.binding.execution_sha256 !== bundle.execution.sha256
|
|
42
|
+
|| readiness.authority.binding.storage_sha256 !== storage.sha256)
|
|
43
|
+
refuse("policy_bootstrap_binding_changed");
|
|
44
|
+
return { kind: "upgrade", readiness, storage, policy: { scope: request, ...git.policy },
|
|
45
|
+
selection: { projectRoot: request.projectRoot, workspaceRoot: request.workspaceRoot,
|
|
46
|
+
integrationBranch: git.policy.integration_branch, gitExecutable: git.executable }, gitSha256: git.probe.executable_sha256 };
|
|
47
|
+
}
|
|
28
48
|
const readiness = readMemoryInitBootstrapReadiness(context, request), policy = readMemoryInitGitPolicy(context, request);
|
|
29
49
|
if (!readiness || !policy || readiness.projectId !== scope.projectId
|
|
30
50
|
|| readiness.authority.binding.execution_sha256 !== bundle.execution.sha256
|
|
@@ -36,18 +56,103 @@ function retained(context, scope, bundle) {
|
|
|
36
56
|
const gitSha256 = readiness.authority.binding.target.tool_probes?.git.executable_sha256;
|
|
37
57
|
if (typeof gitSha256 !== "string" || !/^[a-f0-9]{64}$/.test(gitSha256))
|
|
38
58
|
refuse("pinned_git_executable_required");
|
|
39
|
-
return { readiness, policy, selection, gitSha256 };
|
|
59
|
+
return { kind: "init", readiness, policy, selection, gitSha256 };
|
|
40
60
|
}
|
|
41
61
|
function assertGitExecutable(selection, sha256) {
|
|
42
62
|
if (createHash("sha256").update(readBoundedReleaseFile(selection.gitExecutable, 64 * 1024 * 1024)).digest("hex") !== sha256)
|
|
43
63
|
refuse("pinned_git_executable_changed");
|
|
44
64
|
}
|
|
65
|
+
/** Projection only: accepted review may advance project/customized documents,
|
|
66
|
+
* not canonical defaults, opaque links or CLI recovery records. */
|
|
67
|
+
export function projectMemoryUpgradeFinishInventory(plan, inventory, changes, reviewed, settledDocuments = null) {
|
|
68
|
+
const files = new Map(plan.beforeFiles().map(entry => [`.memory-bank/${entry.path}`, { ...entry, path: `.memory-bank/${entry.path}` }]));
|
|
69
|
+
const removed = new Set();
|
|
70
|
+
const removals = [];
|
|
71
|
+
for (const action of plan.actions()) {
|
|
72
|
+
const logical = `.memory-bank/${action.path}`;
|
|
73
|
+
if (action.after)
|
|
74
|
+
files.set(logical, { ...action.after, path: logical });
|
|
75
|
+
else {
|
|
76
|
+
files.delete(logical);
|
|
77
|
+
removed.add(logical);
|
|
78
|
+
if (!action.before || action.before.kind !== "file")
|
|
79
|
+
refuse("unsupported_upgrade_removal");
|
|
80
|
+
removals.push({ root: "workspace", path: logical, sha256: action.before.sha256,
|
|
81
|
+
...((action.before.mode & 0o111) ? { git_mode: "100755" } : {}) });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const ownership = new Map(inventory.entries.map(entry => [`.memory-bank/${entry.path}`, entry.ownership]));
|
|
85
|
+
const semantic = new Set(changes.filter(change => change.action === "semantic-work" && change.to).map(change => change.to));
|
|
86
|
+
const rootFiles = [];
|
|
87
|
+
const settled = new Map(settledDocuments?.map(entry => [entry.path, entry]));
|
|
88
|
+
for (const ref of reviewedMemoryGitFiles(reviewed)) {
|
|
89
|
+
if (ref.path === ".mb-lint.json") {
|
|
90
|
+
rootFiles.push(ref);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const before = files.get(ref.path);
|
|
94
|
+
if (removed.has(ref.path) || before && before.kind !== "file")
|
|
95
|
+
refuse("reviewed_upgrade_path_not_current_file");
|
|
96
|
+
if (before?.sha256 === ref.sha256)
|
|
97
|
+
continue;
|
|
98
|
+
const owner = ownership.get(ref.path), namespace = /^\.memory-bank\/(?:dd-flow|mbb)\//.test(ref.path);
|
|
99
|
+
if (ref.path.startsWith(".memory-bank/archive/mb-upgrade/") || owner === "generated"
|
|
100
|
+
|| (owner === "canonical" || namespace && owner !== "project" && owner !== "local-customized") && !semantic.has(ref.path))
|
|
101
|
+
refuse("review_changed_protected_upgrade_output");
|
|
102
|
+
const after = settled.get(ref.path);
|
|
103
|
+
if (!before && (!after || after.kind !== "file" || after.sha256 !== ref.sha256))
|
|
104
|
+
refuse("unretained_reviewed_output_mode");
|
|
105
|
+
files.set(ref.path, { path: ref.path, kind: "file", mode: before?.mode ?? (after.mode & 0o777), sha256: ref.sha256 });
|
|
106
|
+
}
|
|
107
|
+
const entries = [...files.values()].sort((a, b) => a.path.localeCompare(b.path, "en"));
|
|
108
|
+
if (settledDocuments && hybridDigest(settledDocuments.filter(entry => entry.kind !== "directory")
|
|
109
|
+
.map(entry => ({ ...entry, mode: entry.mode & 0o777 })).sort((a, b) => a.path.localeCompare(b.path, "en"))) !== hybridDigest(entries))
|
|
110
|
+
refuse("unreviewed_repair_inventory_change");
|
|
111
|
+
const references = [...rootFiles, ...entries.filter(entry => entry.kind === "file"
|
|
112
|
+
&& entry.path.split("/").every(part => localEnvironmentAllowed(part) && ![".git", "node_modules", ".dd-flow"].includes(part)))
|
|
113
|
+
.map(entry => ({ root: "workspace", path: entry.path, sha256: entry.sha256,
|
|
114
|
+
...((entry.mode & 0o111) ? { git_mode: "100755" } : {}) }))].sort((a, b) => a.path.localeCompare(b.path, "en"));
|
|
115
|
+
return { entries, references, removals: removals.sort((a, b) => a.path.localeCompare(b.path, "en")) };
|
|
116
|
+
}
|
|
117
|
+
export function readMemoryUpgradeFinishInventory(context, scope, bundle, review) {
|
|
118
|
+
if (!PreparedMemoryFinishReview.isPrepared(review))
|
|
119
|
+
refuse("accepted_review_required");
|
|
120
|
+
const reviewed = review.reviewedSources(context);
|
|
121
|
+
const selected = readMemoryFinishGitSelection(context, scope, bundle);
|
|
122
|
+
if (selected.kind !== "upgrade" || !selected.storage.selection)
|
|
123
|
+
refuse("upgrade_selection_required");
|
|
124
|
+
const root = readDescriptorRootWork(context, scope, bundle);
|
|
125
|
+
const registration = selectMemoryCompositionRegistration(context, { ...stageGraphScope(context, scope.projectId, scope.runId, "upgrade"),
|
|
126
|
+
stage: "upgrade", parent_work_id: root.workId });
|
|
127
|
+
const published = readCompletedMemoryUpgradePublication(context, { scope: registration.scope,
|
|
128
|
+
selection_sha256: selected.storage.selection.sha256, execution_sha256: bundle.execution.sha256,
|
|
129
|
+
registration_sha256: hybridDigest(registration), bootstrap_sha256: hybridDigest(selected.readiness.authority) });
|
|
130
|
+
const accepted = readAcceptedStageData(context, { ...scope, stage: "upgrade" }), value = JSON.parse(accepted.bytes.toString("utf8"));
|
|
131
|
+
const recordPath = `.memory-bank/archive/mb-upgrade/${selected.storage.selection.sha256}/migration.json`;
|
|
132
|
+
const record = published.plan.outputs().find(entry => `.memory-bank/${entry.path}` === recordPath);
|
|
133
|
+
const migrationRecord = record && { path: recordPath, sha256: record.sha256 };
|
|
134
|
+
if (!migrationRecord || accepted.data_schema_id !== "dd-flow/mb-upgrade-stage-result@1" || value.stage !== "upgrade" || value.outcome !== "accepted"
|
|
135
|
+
|| accepted.input_sha256 !== registration.input_sha256 || hybridDigest([value.details?.migration_record]) !== hybridDigest([migrationRecord])
|
|
136
|
+
|| !reviewed.some(ref => ref.root === "workspace" && ref.path === migrationRecord.path && ref.sha256 === migrationRecord.sha256))
|
|
137
|
+
refuse("accepted_upgrade_migration_record_changed");
|
|
138
|
+
const diff = readAcceptedStageData(context, { ...scope, stage: "diff-analysis" }), changes = JSON.parse(diff.bytes.toString("utf8")).details.path_map;
|
|
139
|
+
return { ...projectMemoryUpgradeFinishInventory(published.plan, selected.storage.selection.payload.current.inventory.inventory, changes, reviewed, review.reviewedDocumentInventory(context)),
|
|
140
|
+
migrationRecord, publicationSha256: published.plan.sha256, binding: published.binding };
|
|
141
|
+
}
|
|
142
|
+
export function observeMemoryUpgradeFinishInventory(root, expected) {
|
|
143
|
+
const snapshot = observeMemoryDocumentInventory(root);
|
|
144
|
+
const entries = snapshot.entries().filter(entry => entry.kind !== "directory").map(entry => ({ ...entry, mode: entry.mode & 0o777 }));
|
|
145
|
+
if (hybridDigest(entries.sort((a, b) => a.path.localeCompare(b.path, "en"))) !== hybridDigest(expected.entries))
|
|
146
|
+
refuse("upgrade_finish_inventory_changed");
|
|
147
|
+
return snapshot;
|
|
148
|
+
}
|
|
45
149
|
/** Physical Git operation only; callers must independently select accepted files
|
|
46
150
|
* and retain each mutation's intent. No ambient Git redirection or whole-tree add. */
|
|
47
|
-
export function integrateReviewedMemoryFiles(selection, environment, references, runId, mutation, policy) {
|
|
48
|
-
const env = Object.fromEntries(Object.entries(environment).filter(([key]) => !key.startsWith("GIT_")));
|
|
151
|
+
export function integrateReviewedMemoryFiles(selection, environment, references, runId, mutation, policy, removals = []) {
|
|
152
|
+
const env = { ...Object.fromEntries(Object.entries(environment).filter(([key]) => !key.startsWith("GIT_"))), GIT_LITERAL_PATHSPECS: "1" };
|
|
49
153
|
const git = (directory, args, write = false, absent = false) => {
|
|
50
|
-
const result = spawnSync(selection.gitExecutable, ["-c", "core.fsmonitor=false",
|
|
154
|
+
const result = spawnSync(selection.gitExecutable, ["-c", "core.fsmonitor=false",
|
|
155
|
+
...(write && process.platform !== "win32" ? ["-c", "core.filemode=true"] : []), "-C", directory, ...args], { env, encoding: "utf8", timeout: 30000, maxBuffer: 16 * 1024 * 1024, shell: false, windowsHide: true });
|
|
51
156
|
if (absent && !result.error && !result.signal && result.status !== 0)
|
|
52
157
|
return "";
|
|
53
158
|
if (result.error || result.signal || result.status !== 0)
|
|
@@ -55,13 +160,26 @@ export function integrateReviewedMemoryFiles(selection, environment, references,
|
|
|
55
160
|
next_action: write ? "reconcile_original_integration" : "resolve_git_configuration_or_workspace" });
|
|
56
161
|
return result.stdout.trim();
|
|
57
162
|
};
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|| references.some(ref =>
|
|
61
|
-
|
|
62
|
-
|
|
163
|
+
if (!Array.isArray(references) || !Array.isArray(removals) || !references.length
|
|
164
|
+
|| references.length + removals.length > 4096 || !references.some(ref => ref?.path === ".memory-bank/index.md")
|
|
165
|
+
|| [...references, ...removals].some(ref => !isReviewedMemoryGitFile(ref))
|
|
166
|
+
|| removals.some(ref => !ref.path.startsWith(".memory-bank/") || ref.path === ".memory-bank/index.md"))
|
|
167
|
+
refuse("invalid_owned_inventory");
|
|
168
|
+
const paths = [...references, ...removals].map(ref => ref.path);
|
|
169
|
+
if (new Set(paths).size !== paths.length)
|
|
63
170
|
refuse("invalid_owned_inventory");
|
|
64
171
|
const source = fs.realpathSync(selection.workspaceRoot), target = fs.realpathSync(selection.projectRoot);
|
|
172
|
+
const assertModes = () => {
|
|
173
|
+
if (process.platform === "win32")
|
|
174
|
+
return;
|
|
175
|
+
for (const ref of references) {
|
|
176
|
+
const executable = (fs.lstatSync(path.join(source, ref.path)).mode & 0o111) !== 0;
|
|
177
|
+
if (executable !== (ref.git_mode === "100755"))
|
|
178
|
+
refuse("reviewed_workspace_mode_changed");
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
assertModes();
|
|
182
|
+
assertMemoryGitRemovalSources(source, removals);
|
|
65
183
|
const branch = git(source, ["symbolic-ref", "--short", "HEAD"]);
|
|
66
184
|
if (target === source ? branch !== selection.integrationBranch
|
|
67
185
|
: git(target, ["symbolic-ref", "--short", "HEAD"]) !== selection.integrationBranch)
|
|
@@ -77,7 +195,7 @@ export function integrateReviewedMemoryFiles(selection, environment, references,
|
|
|
77
195
|
if (hashes.get(memorySourceKey(ref.root, ref.path)) !== ref.sha256)
|
|
78
196
|
refuse("reviewed_workspace_bytes_changed");
|
|
79
197
|
try {
|
|
80
|
-
return observeReviewedMemoryGitIntegration(selection, environment, references);
|
|
198
|
+
return observeReviewedMemoryGitIntegration(selection, environment, references, removals);
|
|
81
199
|
}
|
|
82
200
|
catch (error) {
|
|
83
201
|
if (!(error instanceof AppError) || error.code !== "memory_git_integration_invalid")
|
|
@@ -89,6 +207,22 @@ export function integrateReviewedMemoryFiles(selection, environment, references,
|
|
|
89
207
|
if (git(source, ["diff", "--name-only", "--diff-filter=U"]))
|
|
90
208
|
refuse("unmerged_workspace");
|
|
91
209
|
const sourceHead = git(source, ["rev-parse", "--verify", "HEAD"], false, true);
|
|
210
|
+
const deleting = [];
|
|
211
|
+
let removedBytes = 0;
|
|
212
|
+
for (const ref of removals) {
|
|
213
|
+
const entry = git(source, ["ls-tree", "-z", "HEAD", "--", ref.path], false, true);
|
|
214
|
+
if (!entry)
|
|
215
|
+
continue;
|
|
216
|
+
if (!new RegExp(`^${ref.git_mode ?? "100644"} blob [a-f0-9]{40,64}\\t`).test(entry))
|
|
217
|
+
refuse("removed_memory_source_changed");
|
|
218
|
+
const content = spawnSync(selection.gitExecutable, ["-C", source, "cat-file", "blob", `HEAD:${ref.path}`], { env, timeout: 10000, maxBuffer: 16 * 1024 * 1024, shell: false, windowsHide: true });
|
|
219
|
+
if (content.error || content.signal || content.status !== 0 || createHash("sha256").update(content.stdout).digest("hex") !== ref.sha256)
|
|
220
|
+
refuse("removed_memory_source_changed");
|
|
221
|
+
removedBytes += content.stdout.length;
|
|
222
|
+
if (removedBytes > 64 * 1024 * 1024)
|
|
223
|
+
refuse("removed_memory_bytes_exceed_bound");
|
|
224
|
+
deleting.push(ref.path);
|
|
225
|
+
}
|
|
92
226
|
if (source !== target) {
|
|
93
227
|
const targetHead = git(target, ["rev-parse", "HEAD"]), base = git(source, ["merge-base", targetHead, sourceHead]);
|
|
94
228
|
const changes = git(source, ["diff", "--name-only", "-z", base, sourceHead]).split("\0").filter(Boolean);
|
|
@@ -97,20 +231,25 @@ export function integrateReviewedMemoryFiles(selection, environment, references,
|
|
|
97
231
|
if (git(target, ["diff", "--cached", "--name-only"]) || git(target, ["diff", "--name-only", "--diff-filter=U"]))
|
|
98
232
|
refuse("integration_target_index_not_clean");
|
|
99
233
|
}
|
|
100
|
-
const needsCommit = references.some(ref => {
|
|
234
|
+
const needsCommit = deleting.length > 0 || references.some(ref => {
|
|
101
235
|
const bytes = spawnSync(selection.gitExecutable, ["-C", source, "cat-file", "blob", `HEAD:${ref.path}`], { env, timeout: 10000, maxBuffer: 16 * 1024 * 1024, shell: false, windowsHide: true });
|
|
102
|
-
|
|
236
|
+
const entry = git(source, ["ls-tree", "-z", "HEAD", "--", ref.path], false, true);
|
|
237
|
+
return bytes.status !== 0 || createHash("sha256").update(bytes.stdout).digest("hex") !== ref.sha256
|
|
238
|
+
|| !entry.startsWith(`${ref.git_mode ?? "100644"} blob `);
|
|
103
239
|
});
|
|
104
240
|
if (needsCommit) {
|
|
105
241
|
git(source, ["var", "GIT_AUTHOR_IDENT"]);
|
|
106
242
|
git(source, ["var", "GIT_COMMITTER_IDENT"]);
|
|
107
243
|
mutation("commit", sourceHead, () => {
|
|
108
244
|
observed.revalidate();
|
|
245
|
+
assertModes();
|
|
246
|
+
assertMemoryGitRemovalSources(source, removals);
|
|
109
247
|
if (git(source, ["rev-parse", "--verify", "HEAD"], false, true) !== sourceHead
|
|
110
248
|
|| git(source, ["symbolic-ref", "--short", "HEAD"]) !== branch)
|
|
111
249
|
refuse("source_git_baseline_changed");
|
|
112
|
-
|
|
113
|
-
git(source, ["
|
|
250
|
+
const commitPaths = [...references.map(ref => ref.path), ...deleting];
|
|
251
|
+
git(source, ["add", "-f", "--", ...commitPaths], true);
|
|
252
|
+
git(source, ["commit", "--only", "-m", `chore(${runId.toLowerCase()}): integrate reviewed Memory Bank`, "--", ...commitPaths], true);
|
|
114
253
|
});
|
|
115
254
|
}
|
|
116
255
|
observed.revalidate();
|
|
@@ -126,40 +265,50 @@ export function integrateReviewedMemoryFiles(selection, environment, references,
|
|
|
126
265
|
});
|
|
127
266
|
}
|
|
128
267
|
}
|
|
129
|
-
return observeReviewedMemoryGitIntegration(selection, environment, references);
|
|
268
|
+
return observeReviewedMemoryGitIntegration(selection, environment, references, removals);
|
|
130
269
|
}
|
|
131
270
|
/** The admitted Memory finish operation. A retained intent never licenses a
|
|
132
271
|
* second dispatch: replay first observes completed Git effects, or reports unknown. */
|
|
133
272
|
export function integrateMemoryFinish(context, input) {
|
|
134
|
-
if (!(input.bundle instanceof RetainedExecutionBundle) || input.bundle.descriptor.descriptor.id
|
|
273
|
+
if (!(input.bundle instanceof RetainedExecutionBundle) || !["mb-init", "mb-upgrade"].includes(input.bundle.descriptor.descriptor.id))
|
|
135
274
|
refuse("registered_memory_bundle_required");
|
|
136
275
|
if (context.db.isOwnedWriteTransaction?.())
|
|
137
276
|
refuse("preparation_outside_writer_required");
|
|
138
277
|
const stageScope = { ...input.scope, stage: "finish" };
|
|
139
278
|
readDescriptorStageInput(context, stageScope, input.bundle);
|
|
140
279
|
const review = PreparedMemoryFinishReview.prepare(context, input);
|
|
141
|
-
const selected = context.db.readSnapshot(() =>
|
|
280
|
+
const selected = context.db.readSnapshot(() => readMemoryFinishGitSelection(context, input.scope, input.bundle));
|
|
142
281
|
const request = { requestId: selected.policy.scope.requestId, projectRoot: selected.selection.projectRoot,
|
|
143
282
|
workspaceRoot: selected.selection.workspaceRoot };
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
283
|
+
let upgrade = null;
|
|
284
|
+
let references;
|
|
285
|
+
if (selected.kind === "upgrade") {
|
|
286
|
+
upgrade = readMemoryUpgradeFinishInventory(context, input.scope, input.bundle, review);
|
|
287
|
+
references = upgrade.references;
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
const bootstrap = readMemoryInitProjectBootstrap(context, request);
|
|
291
|
+
if (!bootstrap)
|
|
292
|
+
refuse("accepted_bootstrap_required");
|
|
293
|
+
const published = readPublishedMemoryReferenceInventory(context.ddFlowHome, { ...request, targetSha256: bootstrap.receipt.target_sha256,
|
|
294
|
+
layerSha256: bootstrap.receipt.layer_sha256, installationSha256: selected.readiness.authority.binding.installation_manifest_sha256,
|
|
295
|
+
packSha256: selected.readiness.authority.binding.pack_sha256 }, selected.readiness.authority.binding.target, bootstrap.receipt.layer_publication_sha256, bootstrap.receipt.draft_identity_sha256);
|
|
296
|
+
const files = new Map(reviewedMemoryGitFiles(review.reviewedSources(context)).map(ref => [ref.path, ref]));
|
|
297
|
+
for (const [logical, sha256] of published.artifacts) {
|
|
298
|
+
const ref = { root: "workspace", path: logical, sha256 };
|
|
299
|
+
if (files.has(ref.path)) {
|
|
300
|
+
if (published.canon.has(logical.slice(".memory-bank/".length)) && files.get(ref.path).sha256 !== ref.sha256)
|
|
301
|
+
refuse("selected_canon_changed");
|
|
302
|
+
}
|
|
303
|
+
else
|
|
304
|
+
files.set(ref.path, ref);
|
|
156
305
|
}
|
|
157
|
-
|
|
158
|
-
files.set(ref.path, ref);
|
|
306
|
+
references = [...files.values()].sort((a, b) => a.path.localeCompare(b.path, "en"));
|
|
159
307
|
}
|
|
160
|
-
const
|
|
308
|
+
const snapshot = upgrade ? observeMemoryUpgradeFinishInventory(selected.selection.workspaceRoot, upgrade) : null;
|
|
309
|
+
const removals = upgrade?.removals ?? [];
|
|
161
310
|
const identity = hybridDigest({ scope: input.scope, bundle: input.bundle.sha256, policy: selected.policy,
|
|
162
|
-
review: review.bytes().toString("base64"), references });
|
|
311
|
+
review: review.bytes().toString("base64"), references, ...(upgrade ? { upgrade } : {}) });
|
|
163
312
|
const events = context.db.all(`SELECT payload_json FROM audit_events WHERE project_id=?
|
|
164
313
|
AND event_type='memory.finish_integration_intent' AND json_extract(payload_json,'$.run_id')=?`, [input.scope.projectId, input.scope.runId]);
|
|
165
314
|
const intents = events.map(row => JSON.parse(row.payload_json));
|
|
@@ -168,7 +317,7 @@ export function integrateMemoryFinish(context, input) {
|
|
|
168
317
|
assertGitExecutable(selected.selection, selected.gitSha256);
|
|
169
318
|
if (intents.length) {
|
|
170
319
|
try {
|
|
171
|
-
observeReviewedMemoryGitIntegration(selected.selection, context.env, references);
|
|
320
|
+
observeReviewedMemoryGitIntegration(selected.selection, context.env, references, removals);
|
|
172
321
|
const accepted = PreparedMemoryFinishIntegration.prepare(context, input);
|
|
173
322
|
return { integrated: true, replayed: true, integration: accepted.value() };
|
|
174
323
|
}
|
|
@@ -189,11 +338,14 @@ export function integrateMemoryFinish(context, input) {
|
|
|
189
338
|
throw new AppError("memory_finish_integration_pending", "Original Git mutation remains unresolved", 1, { effect: "unknown", next_action: "reconcile_original_integration" });
|
|
190
339
|
review.revalidate(context);
|
|
191
340
|
assertGitExecutable(selected.selection, selected.gitSha256);
|
|
341
|
+
snapshot?.revalidate();
|
|
192
342
|
context.db.writeTransaction(() => {
|
|
193
343
|
readDescriptorStageInput(context, stageScope, input.bundle);
|
|
194
344
|
review.assertCustody(context);
|
|
195
|
-
if (hybridDigest(
|
|
345
|
+
if (hybridDigest(readMemoryFinishGitSelection(context, input.scope, input.bundle)) !== hybridDigest(selected))
|
|
196
346
|
refuse("integration_custody_changed");
|
|
347
|
+
if (upgrade && hybridDigest(readMemoryUpgradeFinishInventory(context, input.scope, input.bundle, review)) !== hybridDigest(upgrade))
|
|
348
|
+
refuse("upgrade_inventory_custody_changed");
|
|
197
349
|
const current = context.db.all(`SELECT payload_json FROM audit_events WHERE project_id=?
|
|
198
350
|
AND event_type='memory.finish_integration_intent' AND json_extract(payload_json,'$.run_id')=?`, [input.scope.projectId, input.scope.runId])
|
|
199
351
|
.map(row => JSON.parse(row.payload_json));
|
|
@@ -204,8 +356,11 @@ export function integrateMemoryFinish(context, input) {
|
|
|
204
356
|
}, "memoryFinish.integrate");
|
|
205
357
|
dispatched = true;
|
|
206
358
|
action();
|
|
207
|
-
}, selected.policy);
|
|
208
|
-
|
|
359
|
+
}, selected.policy, removals);
|
|
360
|
+
snapshot?.revalidate();
|
|
361
|
+
if (upgrade)
|
|
362
|
+
observeMemoryUpgradeFinishInventory(selected.selection.projectRoot, upgrade);
|
|
363
|
+
return { integrated: true, replayed: false, integration: { ...integration, ...(upgrade ? { upgrade_inventory_sha256: hybridDigest(upgrade) } : {}) } };
|
|
209
364
|
}
|
|
210
365
|
catch (error) {
|
|
211
366
|
if (!dispatched)
|
|
@@ -228,7 +383,8 @@ export class PreparedMemoryFinishIntegration {
|
|
|
228
383
|
#selection;
|
|
229
384
|
#observation;
|
|
230
385
|
#gitSha256;
|
|
231
|
-
|
|
386
|
+
#upgradeSha256;
|
|
387
|
+
constructor(key, scope, bundle, review, selected, observation, upgradeSha256 = null) {
|
|
232
388
|
if (key !== token)
|
|
233
389
|
refuse("finish_integration_token_required");
|
|
234
390
|
this.#scope = { ...scope };
|
|
@@ -238,22 +394,28 @@ export class PreparedMemoryFinishIntegration {
|
|
|
238
394
|
this.#selection = structuredClone(selected.selection);
|
|
239
395
|
this.#observation = structuredClone(observation);
|
|
240
396
|
this.#gitSha256 = selected.gitSha256;
|
|
397
|
+
this.#upgradeSha256 = upgradeSha256;
|
|
241
398
|
Object.freeze(this);
|
|
242
399
|
}
|
|
243
400
|
static prepare(context, input) {
|
|
244
|
-
if (!(input.bundle instanceof RetainedExecutionBundle) || input.bundle.descriptor.descriptor.id
|
|
401
|
+
if (!(input.bundle instanceof RetainedExecutionBundle) || !["mb-init", "mb-upgrade"].includes(input.bundle.descriptor.descriptor.id))
|
|
245
402
|
refuse("registered_memory_bundle_required");
|
|
246
403
|
if (context.db.isOwnedWriteTransaction?.())
|
|
247
404
|
refuse("preparation_outside_writer_required");
|
|
248
405
|
const review = PreparedMemoryFinishReview.prepare(context, input);
|
|
249
|
-
const selected = context.db.readSnapshot(() =>
|
|
250
|
-
const
|
|
406
|
+
const selected = context.db.readSnapshot(() => readMemoryFinishGitSelection(context, input.scope, input.bundle));
|
|
407
|
+
const upgrade = selected.kind === "upgrade" ? readMemoryUpgradeFinishInventory(context, input.scope, input.bundle, review) : null;
|
|
408
|
+
const references = upgrade?.references ?? reviewedMemoryGitFiles(review.reviewedSources(context));
|
|
251
409
|
assertGitExecutable(selected.selection, selected.gitSha256);
|
|
252
|
-
|
|
410
|
+
if (upgrade)
|
|
411
|
+
for (const root of new Set([selected.selection.projectRoot, selected.selection.workspaceRoot]))
|
|
412
|
+
observeMemoryUpgradeFinishInventory(root, upgrade);
|
|
413
|
+
const observation = { ...observeReviewedMemoryGitIntegration(selected.selection, context.env, references, upgrade?.removals),
|
|
414
|
+
...(upgrade ? { upgrade_inventory_sha256: hybridDigest(upgrade) } : {}) };
|
|
253
415
|
assertGitExecutable(selected.selection, selected.gitSha256);
|
|
254
416
|
if (observation.repository !== selected.policy.repository || observation.workspace_branch !== selected.policy.workspace_branch)
|
|
255
417
|
refuse("policy_repository_or_branch_changed");
|
|
256
|
-
const prepared = new PreparedMemoryFinishIntegration(token, input.scope, input.bundle, review, selected, observation);
|
|
418
|
+
const prepared = new PreparedMemoryFinishIntegration(token, input.scope, input.bundle, review, selected, observation, upgrade ? hybridDigest(upgrade) : null);
|
|
257
419
|
prepared.revalidate(context);
|
|
258
420
|
return prepared;
|
|
259
421
|
}
|
|
@@ -261,8 +423,10 @@ export class PreparedMemoryFinishIntegration {
|
|
|
261
423
|
/** SQL-only writer fence; physical observation must precede the writer. */
|
|
262
424
|
assertCustody(context) {
|
|
263
425
|
this.#review.assertCustody(context);
|
|
264
|
-
if (hybridDigest(
|
|
426
|
+
if (hybridDigest(readMemoryFinishGitSelection(context, this.#scope, this.#bundle)) !== this.#retainedSha256)
|
|
265
427
|
refuse("finish_integration_custody_changed");
|
|
428
|
+
if (this.#upgradeSha256 !== null && hybridDigest(readMemoryUpgradeFinishInventory(context, this.#scope, this.#bundle, this.#review)) !== this.#upgradeSha256)
|
|
429
|
+
refuse("upgrade_inventory_custody_changed");
|
|
266
430
|
}
|
|
267
431
|
revalidate(context) {
|
|
268
432
|
const review = this.#review;
|
|
@@ -270,9 +434,15 @@ export class PreparedMemoryFinishIntegration {
|
|
|
270
434
|
refuse("physical_revalidation_outside_writer_required");
|
|
271
435
|
review.revalidate(context);
|
|
272
436
|
this.assertCustody(context);
|
|
273
|
-
const
|
|
437
|
+
const upgrade = this.#upgradeSha256 === null ? null : readMemoryUpgradeFinishInventory(context, this.#scope, this.#bundle, this.#review);
|
|
438
|
+
const references = upgrade?.references ?? reviewedMemoryGitFiles(this.#review.reviewedSources(context));
|
|
274
439
|
assertGitExecutable(this.#selection, this.#gitSha256);
|
|
275
|
-
if (
|
|
440
|
+
if (upgrade)
|
|
441
|
+
for (const root of new Set([this.#selection.projectRoot, this.#selection.workspaceRoot]))
|
|
442
|
+
observeMemoryUpgradeFinishInventory(root, upgrade);
|
|
443
|
+
const observed = { ...observeReviewedMemoryGitIntegration(this.#selection, context.env, references, upgrade?.removals),
|
|
444
|
+
...(upgrade ? { upgrade_inventory_sha256: hybridDigest(upgrade) } : {}) };
|
|
445
|
+
if (hybridDigest(observed) !== hybridDigest(this.#observation))
|
|
276
446
|
refuse("finish_integration_observation_changed");
|
|
277
447
|
assertGitExecutable(this.#selection, this.#gitSha256);
|
|
278
448
|
this.assertCustody(context);
|
|
@@ -2,7 +2,7 @@ var _a;
|
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { AppError } from "../shared/errors.js";
|
|
4
4
|
import { RetainedExecutionBundle } from "./execution-bundles.js";
|
|
5
|
-
import { PreparedMemoryFinishIntegration } from "./memory-finish-integration.js";
|
|
5
|
+
import { PreparedMemoryFinishIntegration, readMemoryUpgradeFinishInventory, observeMemoryUpgradeFinishInventory } from "./memory-finish-integration.js";
|
|
6
6
|
import { PreparedMemoryFinishReview } from "./memory-finish-handler.js";
|
|
7
7
|
import { observeMemoryNormalFlowEntry } from "./memory-normal-flow-entry.js";
|
|
8
8
|
import { readMemoryInitBootstrapReadiness } from "./memory-init-bootstrap-readiness.js";
|
|
@@ -12,6 +12,9 @@ import { ObservedMemorySources } from "./memory-source-observations.js";
|
|
|
12
12
|
import { readBoundedReleaseFile } from "./release-source.js";
|
|
13
13
|
import { memorySourceKey } from "./memory-stage-results.js";
|
|
14
14
|
import { hybridDigest } from "./hybrid-contracts.js";
|
|
15
|
+
import { readMemoryUpgradeBootstrapReadiness } from "./memory-upgrade-bootstrap-readiness.js";
|
|
16
|
+
import { readUpgradeStorageReadiness } from "./memory-upgrade-storage-readiness.js";
|
|
17
|
+
import { getProjectVersionStatus } from "./version-status.js";
|
|
15
18
|
const token = Symbol("memory-finish-readiness");
|
|
16
19
|
function refuse(reason) {
|
|
17
20
|
throw new AppError("memory_finish_readiness_invalid", "Finish lacks reviewed ordinary-flow readiness", 2, { reason, effect: "no_effect" });
|
|
@@ -19,13 +22,32 @@ function refuse(reason) {
|
|
|
19
22
|
function accepted(context, scope, bundle) {
|
|
20
23
|
const frozen = bundle.execution.snapshot().scope;
|
|
21
24
|
const request = { requestId: frozen.requestId, projectRoot: frozen.projectRoot, workspaceRoot: frozen.workspace };
|
|
25
|
+
if (bundle.descriptor.descriptor.id === "mb-upgrade") {
|
|
26
|
+
const bootstrap = readMemoryUpgradeBootstrapReadiness(context, request), storage = readUpgradeStorageReadiness(context, request);
|
|
27
|
+
if (!bootstrap?.hasGitPolicy || !bootstrap.hasLintCapability || !storage?.selection || bootstrap.projectId !== scope.projectId
|
|
28
|
+
|| bootstrap.authority.binding.execution_sha256 !== bundle.execution.sha256
|
|
29
|
+
|| bootstrap.authority.binding.storage_sha256 !== storage.sha256)
|
|
30
|
+
refuse("accepted_bootstrap_required");
|
|
31
|
+
return { kind: "upgrade", request, bootstrap, storage };
|
|
32
|
+
}
|
|
22
33
|
const bootstrap = readMemoryInitBootstrapReadiness(context, request), registration = readMemoryInitProjectBootstrap(context, request);
|
|
23
34
|
if (!bootstrap || !registration || bootstrap.projectId !== scope.projectId
|
|
24
35
|
|| bootstrap.authority.binding.execution_sha256 !== bundle.execution.sha256)
|
|
25
36
|
refuse("accepted_bootstrap_required");
|
|
26
|
-
return { request, bootstrap, registration };
|
|
37
|
+
return { kind: "init", request, bootstrap, registration };
|
|
27
38
|
}
|
|
28
39
|
function tools(selected) {
|
|
40
|
+
if (selected.kind === "upgrade") {
|
|
41
|
+
const git = selected.storage.selection.payload.git, lint = selected.bootstrap.authority.binding.lint;
|
|
42
|
+
if (!git || !lint)
|
|
43
|
+
refuse("selected_tool_changed");
|
|
44
|
+
for (const tool of [git, lint]) {
|
|
45
|
+
const expected = "probe" in tool ? tool.probe.executable_sha256 : tool.executable_sha256;
|
|
46
|
+
if (createHash("sha256").update(readBoundedReleaseFile(tool.executable, 64 * 1024 * 1024)).digest("hex") !== expected)
|
|
47
|
+
refuse("selected_tool_changed");
|
|
48
|
+
}
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
29
51
|
const target = selected.bootstrap.authority.binding.target;
|
|
30
52
|
for (const name of ["git", "wt", "mb-lint"]) {
|
|
31
53
|
const expected = target.tool_probes?.[name].executable_sha256;
|
|
@@ -61,25 +83,49 @@ export class PreparedMemoryFinishReadiness {
|
|
|
61
83
|
static observe(context, scope, bundle, review, integration) {
|
|
62
84
|
const selected = context.db.readSnapshot(() => accepted(context, scope, bundle));
|
|
63
85
|
tools(selected);
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
86
|
+
const files = new Map();
|
|
87
|
+
let packSha256;
|
|
88
|
+
let upgrade = null;
|
|
89
|
+
if (selected.kind === "upgrade") {
|
|
90
|
+
upgrade = readMemoryUpgradeFinishInventory(context, scope, bundle, review);
|
|
91
|
+
if (integration.value().upgrade_inventory_sha256 !== hybridDigest(upgrade))
|
|
92
|
+
refuse("upgrade_integration_inventory_changed");
|
|
93
|
+
for (const item of upgrade.entries)
|
|
94
|
+
if (item.kind === "file")
|
|
95
|
+
files.set(item.path.slice(".memory-bank/".length), item.sha256);
|
|
96
|
+
for (const root of new Set([selected.request.projectRoot, selected.request.workspaceRoot])) {
|
|
97
|
+
observeMemoryUpgradeFinishInventory(root, upgrade);
|
|
98
|
+
const version = getProjectVersionStatus({ projectRoot: root, rootSource: "explicit", canon: null });
|
|
99
|
+
const release = selected.storage.selection.release;
|
|
100
|
+
if (!version || version.memory_bank.version !== release.canon.version || version.flow_pack.canon_version_at_source_commit !== release.canon.version
|
|
101
|
+
|| version.flow_pack.source_commit !== release.canon.commit)
|
|
102
|
+
refuse("upgrade_versions_inconsistent");
|
|
103
|
+
}
|
|
104
|
+
packSha256 = selected.bootstrap.authority.receipt.upgrade_target.pack_sha256;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
const layer = readRetainedMemoryLayer(context.ddFlowHome, { ...selected.request,
|
|
108
|
+
targetSha256: selected.registration.receipt.target_sha256, layerSha256: selected.registration.receipt.layer_sha256,
|
|
109
|
+
installationSha256: selected.bootstrap.authority.binding.installation_manifest_sha256 });
|
|
110
|
+
if (layer.packSha256 !== selected.bootstrap.authority.binding.pack_sha256)
|
|
111
|
+
refuse("selected_pack_changed");
|
|
112
|
+
for (const [logical, bytes] of layer.files())
|
|
113
|
+
files.set(logical, createHash("sha256").update(bytes).digest("hex"));
|
|
114
|
+
for (const root of new Set([selected.request.projectRoot, selected.request.workspaceRoot])) {
|
|
115
|
+
const observed = ObservedMemorySources.capture({ project: root }, [...files.keys()].map(logical => ({ root: "project", path: `.memory-bank/${logical}` })));
|
|
116
|
+
const hashes = observed.hashes();
|
|
117
|
+
for (const [logical, sha256] of files)
|
|
118
|
+
if (hashes.get(memorySourceKey("project", `.memory-bank/${logical}`)) !== sha256)
|
|
119
|
+
refuse("installed_pack_bytes_changed");
|
|
120
|
+
observed.revalidate();
|
|
121
|
+
}
|
|
122
|
+
packSha256 = layer.packSha256;
|
|
77
123
|
}
|
|
78
124
|
const entry = observeMemoryNormalFlowEntry(context, selected.request.projectRoot);
|
|
79
125
|
const reviewed = new Map(review.reviewedSources(context).filter(ref => ref.root === "workspace").map(ref => [ref.path, ref.sha256]));
|
|
80
126
|
for (const source of entry.sources) {
|
|
81
127
|
const canonical = files.get(source.path.slice(".memory-bank/".length));
|
|
82
|
-
if (canonical ?
|
|
128
|
+
if (canonical ? canonical !== source.sha256 : reviewed.get(source.path) !== source.sha256)
|
|
83
129
|
refuse("entry_source_not_reviewed_or_selected_canon");
|
|
84
130
|
}
|
|
85
131
|
tools(selected);
|
|
@@ -87,11 +133,12 @@ export class PreparedMemoryFinishReadiness {
|
|
|
87
133
|
review.assertCustody(context);
|
|
88
134
|
return { schema_id: "dd-flow/memory-finish-readiness-observation@1", scope: { ...scope },
|
|
89
135
|
bundle_sha256: bundle.sha256, execution_sha256: bundle.execution.sha256,
|
|
90
|
-
bootstrap_authority_sha256: hybridDigest(selected.bootstrap.authority), pack_sha256:
|
|
91
|
-
integration: integration.value(), entry, readiness: "entry_prepared", model_access: "unknown"
|
|
136
|
+
bootstrap_authority_sha256: hybridDigest(selected.bootstrap.authority), pack_sha256: packSha256,
|
|
137
|
+
integration: integration.value(), entry, readiness: "entry_prepared", model_access: "unknown",
|
|
138
|
+
...(upgrade ? { migration_record: upgrade.migrationRecord } : {}) };
|
|
92
139
|
}
|
|
93
140
|
static prepare(context, input) {
|
|
94
|
-
if (!(input.bundle instanceof RetainedExecutionBundle) || input.bundle.descriptor.descriptor.id
|
|
141
|
+
if (!(input.bundle instanceof RetainedExecutionBundle) || !["mb-init", "mb-upgrade"].includes(input.bundle.descriptor.descriptor.id))
|
|
95
142
|
refuse("registered_memory_bundle_required");
|
|
96
143
|
if (context.db.isOwnedWriteTransaction?.())
|
|
97
144
|
refuse("preparation_outside_writer_required");
|