@deksden-com/dd-flow-cli 0.9.0-beta.143 → 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 +65 -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 +74 -9
- 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
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { AppError } from "../shared/errors.js";
|
|
5
|
+
import { assertUpgradeRunStorageReady } from "./migrations.js";
|
|
6
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
7
|
+
import { appendAudit } from "./audit.js";
|
|
8
|
+
import { restoreMemoryUpgradeSelection, restoreMemoryUpgradeSelectionData, decodeMemoryUpgradeSelection } from "./memory-upgrade-selection.js";
|
|
9
|
+
import { prepareMemoryUpgradeInventory, matchesRetainedMemoryUpgradeInventory } from "./memory-upgrade-inventory.js";
|
|
10
|
+
import { observeDescriptorWorkspace } from "./descriptor-run-start.js";
|
|
11
|
+
import { PreparedMemoryInitRuntimeStorage } from "./memory-init-storage.js";
|
|
12
|
+
import { PreparedMemoryInitResourceStorage } from "./memory-init-resource-storage.js";
|
|
13
|
+
import { resourceHome } from "./managed-processes.js";
|
|
14
|
+
import semver from "semver";
|
|
15
|
+
import { PreparedMemoryLayer } from "./memory-layer.js";
|
|
16
|
+
import { readBoundedReleaseFile } from "./release-source.js";
|
|
17
|
+
import { observeMemoryGitPolicySelection } from "./memory-init-git-policy.js";
|
|
18
|
+
import { readRunStartRequest, hasRunStartTable } from "./run-start-custody.js";
|
|
19
|
+
import { runContextRelocations, projectPacketLocators } from "./stage-context.js";
|
|
20
|
+
// One historical decode per connection. SQL bytes, logical scope and audit
|
|
21
|
+
// custody are still observed on every read; callers receive private copies.
|
|
22
|
+
const retainedDecodes = new WeakMap();
|
|
23
|
+
function refuse(reason) {
|
|
24
|
+
throw new AppError("upgrade_storage_request_invalid", "Upgrade storage readiness must retain its original request", 1, { reason, effect: "no_effect", next_action: "reconcile_exact_request" });
|
|
25
|
+
}
|
|
26
|
+
function project(context, scope) {
|
|
27
|
+
if (!scope || Object.keys(scope).sort().join() !== "projectRoot,requestId,workspaceRoot"
|
|
28
|
+
|| typeof scope.requestId !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_.-]{0,95}$/.test(scope.requestId))
|
|
29
|
+
refuse("invalid_scope");
|
|
30
|
+
if ([scope.projectRoot, scope.workspaceRoot].some(root => typeof root !== "string" || !path.isAbsolute(root) || path.resolve(root) !== root))
|
|
31
|
+
refuse("invalid_scope");
|
|
32
|
+
const selected = context.db.get("SELECT id FROM projects WHERE root = ? AND status = 'active'", [scope.projectRoot]);
|
|
33
|
+
if (selected)
|
|
34
|
+
return selected.id;
|
|
35
|
+
const candidates = hasRunStartTable(context) ? context.db.all("SELECT p.id,p.root,s.run_id FROM projects p JOIN run_start_requests s ON s.project_id=p.id WHERE p.status='active' AND s.request_id=? AND json_extract(s.payload_json,'$.project_root')=? AND json_extract(s.payload_json,'$.workspace_root')=?", [scope.requestId, scope.projectRoot, scope.workspaceRoot]) : [];
|
|
36
|
+
const matches = candidates.filter(row => {
|
|
37
|
+
const request = readRunStartRequest(context, row.id, scope.requestId);
|
|
38
|
+
return request?.published && request.payload.initial_index.flow_kind === 'mb-upgrade'
|
|
39
|
+
&& projectPacketLocators(scope.projectRoot, runContextRelocations(context, row.id, row.run_id)) === row.root;
|
|
40
|
+
});
|
|
41
|
+
if (matches.length !== 1)
|
|
42
|
+
refuse("project_not_active");
|
|
43
|
+
return matches[0].id;
|
|
44
|
+
}
|
|
45
|
+
/** Physical imports use the source request's immutable logical scope. This is
|
|
46
|
+
* a historical read projection, never new storage/RUN admission. */
|
|
47
|
+
export function resolveUpgradeHistoricalScope(context, scope) {
|
|
48
|
+
const projectId = project(context, scope);
|
|
49
|
+
const row = hasRunStartTable(context) ? context.db.get("SELECT run_id FROM run_start_requests WHERE project_id=? AND request_id=?", [projectId, scope.requestId]) : undefined;
|
|
50
|
+
const relocations = row ? runContextRelocations(context, projectId, row.run_id) : [];
|
|
51
|
+
if (relocations.length) {
|
|
52
|
+
const request = readRunStartRequest(context, projectId, scope.requestId);
|
|
53
|
+
if (request?.published && request.payload.initial_index.flow_kind === 'mb-upgrade') {
|
|
54
|
+
const original = { requestId: scope.requestId, projectRoot: request.payload.project_root, workspaceRoot: request.payload.workspace_root };
|
|
55
|
+
if (projectPacketLocators(original.projectRoot, relocations) === scope.projectRoot && projectPacketLocators(original.workspaceRoot, relocations) === scope.workspaceRoot)
|
|
56
|
+
return { projectId, scope: original };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { projectId, scope };
|
|
60
|
+
}
|
|
61
|
+
function scopeDirectories(scope) {
|
|
62
|
+
for (const root of [scope.projectRoot, scope.workspaceRoot]) {
|
|
63
|
+
try {
|
|
64
|
+
if (typeof root !== "string" || fs.realpathSync(root) !== root || !fs.statSync(root).isDirectory())
|
|
65
|
+
refuse("noncanonical_scope");
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (error instanceof AppError)
|
|
69
|
+
throw error;
|
|
70
|
+
refuse("scope_unavailable");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const key = (projectId, requestId) => `mb-upgrade.storage.${projectId}.${requestId}`;
|
|
75
|
+
/** The selected engine issues these facts; SQL custody preserves them after
|
|
76
|
+
* the pre-context files are retired. Never rebuild historical evidence. */
|
|
77
|
+
export function upgradeStorageMigration(value, selectionSha256) {
|
|
78
|
+
const migration = value, sha = (value) => typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
|
|
79
|
+
if (!migration || !migration.request || !path.isAbsolute(migration.request.path) || !sha(migration.request.sha256)
|
|
80
|
+
|| !migration.proof?.reference || migration.proof.reference.path !== migration.request.path + ".result.json"
|
|
81
|
+
|| !sha(migration.proof.reference.sha256) || !Array.isArray(migration.proof.evidence) || migration.proof.evidence.length !== 5)
|
|
82
|
+
refuse("invalid_migration_proof");
|
|
83
|
+
const kinds = ["writer-drain", "storage-backup", "storage-plan", "storage-apply", "storage-verification"];
|
|
84
|
+
migration.proof.evidence.forEach((entry, index) => {
|
|
85
|
+
if (!entry || typeof entry.base64 !== "string" || entry.base64.length > 1024 * 1024)
|
|
86
|
+
refuse("invalid_migration_evidence");
|
|
87
|
+
const raw = Buffer.from(entry.base64, "base64");
|
|
88
|
+
if (entry.kind !== kinds[index] || entry.path !== `receipts/${entry.kind}.json` || raw.toString("base64") !== entry.base64
|
|
89
|
+
|| createHash("sha256").update(raw).digest("hex") !== entry.sha256)
|
|
90
|
+
refuse("invalid_migration_evidence");
|
|
91
|
+
let evidence;
|
|
92
|
+
try {
|
|
93
|
+
evidence = JSON.parse(raw.toString("utf8"));
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return refuse("invalid_migration_evidence");
|
|
97
|
+
}
|
|
98
|
+
if (evidence.schema_id !== "dd-flow/paired-storage-evidence@1" || evidence.kind !== entry.kind
|
|
99
|
+
|| evidence.request_sha256 !== migration.request.sha256 || evidence.selection_sha256 !== selectionSha256)
|
|
100
|
+
refuse("foreign_migration_evidence");
|
|
101
|
+
});
|
|
102
|
+
return migration;
|
|
103
|
+
}
|
|
104
|
+
/** Shared read-only project fence before either storage bootstrap or cutover. */
|
|
105
|
+
export function assertMemoryUpgradeProjectCurrent(selected, scope, env) {
|
|
106
|
+
const p = selected.payload;
|
|
107
|
+
if (p.git && createHash("sha256").update(readBoundedReleaseFile(p.git.executable, 64 * 1024 * 1024)).digest("hex") !== p.git.probe.executable_sha256)
|
|
108
|
+
refuse("selected_git_changed");
|
|
109
|
+
if (p.git && hybridDigest(observeMemoryGitPolicySelection(env, { scope, gitExecutable: p.git.executable,
|
|
110
|
+
selection: { route: p.git.policy.route, integrationBranch: p.git.policy.integration_branch } })) !== hybridDigest(p.git.policy))
|
|
111
|
+
refuse("git_policy_changed");
|
|
112
|
+
if (hybridDigest(observeDescriptorWorkspace({ env }, scope.projectRoot, scope.workspaceRoot, p.git?.executable)) !== hybridDigest(p.workspace)
|
|
113
|
+
|| !matchesRetainedMemoryUpgradeInventory(prepareMemoryUpgradeInventory(scope.workspaceRoot, selected.layer, selected.baselineLayer ?? undefined), p.current.inventory))
|
|
114
|
+
refuse("current_context_changed");
|
|
115
|
+
}
|
|
116
|
+
/** Current physical admission in the bound engine, separate from historical
|
|
117
|
+
* migration receipts. Shared by initial bootstrap and relocated RUN stages. */
|
|
118
|
+
export function assertUpgradeExecutionStorage(context, engine) {
|
|
119
|
+
if (context.db.isOwnedWriteTransaction?.())
|
|
120
|
+
refuse("preparation_outside_writer_required");
|
|
121
|
+
const proof = engine.admitMemoryUpgradeStorage(context.ddFlowHome, resourceHome(context));
|
|
122
|
+
const stores = [PreparedMemoryInitRuntimeStorage.prepareUpgrade(context).facts(), PreparedMemoryInitResourceStorage.prepare(context).facts()];
|
|
123
|
+
const runProof = proof.facts.run, resourceProof = proof.facts.resource;
|
|
124
|
+
if (stores.some(local => {
|
|
125
|
+
const remote = local.store_kind === "run" ? runProof : resourceProof;
|
|
126
|
+
return local.store_path !== remote.store_path || local.schema_sha256 !== remote.schema_sha256
|
|
127
|
+
|| local.executing_writer_version !== remote.executing_writer_version;
|
|
128
|
+
}))
|
|
129
|
+
refuse("executing_store_context_changed");
|
|
130
|
+
return proof;
|
|
131
|
+
}
|
|
132
|
+
/** New bootstrap path: source/target policy comes only from retained input.
|
|
133
|
+
* Physical stores are observed now, not inferred from a project's MB version. */
|
|
134
|
+
function frozenProof(context, scope, retained, retainedMigration) {
|
|
135
|
+
const original = retained ? null : restoreMemoryUpgradeSelection(context.ddFlowHome, scope);
|
|
136
|
+
const selected = original ?? restoreMemoryUpgradeSelectionData(Buffer.from(retained.payload_json, "utf8"), retained.sha256, retained.request_input, scope), p = selected.payload;
|
|
137
|
+
const requestInput = retained?.request_input ?? original.original.input;
|
|
138
|
+
if (p.impact.chain.status !== "ok")
|
|
139
|
+
refuse("incomplete_retained_impact_chain");
|
|
140
|
+
if (!retained && selected.baselineLayer) {
|
|
141
|
+
const baseline = PreparedMemoryLayer.fromCommittedCanon(context.ddFlowHome, JSON.parse(Buffer.from(p.current.manifest_base64, "base64").toString("utf8")), selected.baselineLayer.installationSha256);
|
|
142
|
+
if (!baseline || baseline.sha256 !== selected.baselineLayer.sha256 || baseline.installationSha256 !== selected.baselineLayer.installationSha256)
|
|
143
|
+
refuse("canonical_baseline_changed");
|
|
144
|
+
}
|
|
145
|
+
assertMemoryUpgradeProjectCurrent(selected, scope, context.env);
|
|
146
|
+
const compatibility = JSON.parse(selected.layer.files().get("dd-flow/compatibility.json").toString("utf8"));
|
|
147
|
+
const contract = JSON.parse(selected.layer.files().get("dd-flow/flow-contract.json").toString("utf8"));
|
|
148
|
+
const manifest = selected.engine.manifest();
|
|
149
|
+
if (compatibility.memory_bank_version !== selected.release.canon.version || compatibility.dd_flow_cli.flow_contract !== contract.id
|
|
150
|
+
|| compatibility.dd_flow_cli.package_name !== manifest.package_name || compatibility.engine.package_name !== manifest.package_name
|
|
151
|
+
|| !semver.valid(compatibility.dd_flow_cli.min_version) || semver.lt(manifest.package_version, compatibility.dd_flow_cli.min_version)
|
|
152
|
+
|| !semver.satisfies(manifest.package_version, compatibility.engine.version_range, { includePrerelease: true }))
|
|
153
|
+
refuse("retained_engine_incompatible");
|
|
154
|
+
const proof = assertUpgradeExecutionStorage(context, selected.engine);
|
|
155
|
+
let migration;
|
|
156
|
+
const file = original ? path.join(path.dirname(original.original.candidate.retain().file), "storage-migration.json")
|
|
157
|
+
: retainedMigration ? upgradeStorageMigration(retainedMigration, selected.sha256).request.path : undefined;
|
|
158
|
+
if (file && fs.existsSync(file)) {
|
|
159
|
+
const request = { path: file, sha256: createHash("sha256").update(readBoundedReleaseFile(file, 16 * 1024 * 1024)).digest("hex") };
|
|
160
|
+
const response = selected.engine.transitionMemoryUpgradeStorage({ home: context.ddFlowHome, resourceHome: resourceHome(context), scope,
|
|
161
|
+
selectionSha256: selected.sha256, action: "status", expectedSha256: request.sha256 });
|
|
162
|
+
const result = response.result;
|
|
163
|
+
if (result?.status !== "applied" || !result.proof)
|
|
164
|
+
refuse("migration_evidence_incomplete");
|
|
165
|
+
migration = upgradeStorageMigration({ request, proof: result.proof }, selected.sha256);
|
|
166
|
+
if (retainedMigration && hybridDigest(migration) !== hybridDigest(retainedMigration))
|
|
167
|
+
refuse("migration_evidence_changed");
|
|
168
|
+
}
|
|
169
|
+
else if (retainedMigration)
|
|
170
|
+
refuse("migration_request_unavailable");
|
|
171
|
+
return { selectionSha256: selected.sha256, selection: { sha256: selected.sha256,
|
|
172
|
+
payload_json: selected.bytes.toString("utf8"), request_input: requestInput }, proof: {
|
|
173
|
+
schema_id: migration ? "dd-flow/mb-upgrade-storage-readiness@2" : "dd-flow/mb-upgrade-storage-readiness@1",
|
|
174
|
+
route: migration ? "migration" : "verified-no-op", ...(migration ? { migration } : {}), selection_sha256: selected.sha256,
|
|
175
|
+
source_version: p.current.canon_version, target_version: selected.release.canon.version,
|
|
176
|
+
chain: p.impact.chain.steps, current_provenance: p.current.inventory.inventory.current_manifest,
|
|
177
|
+
target: { canon: selected.source.canonProvenance(), installation_sha256: p.release.manifest_sha256,
|
|
178
|
+
layer_sha256: selected.layer.sha256, pack_sha256: selected.layer.packSha256 }, selected_engine_storage: proof,
|
|
179
|
+
stores: [proof.facts.run, proof.facts.resource].map(store => ({ store_kind: store.store_kind, store_path: store.store_path,
|
|
180
|
+
store_dev: store.store_dev, store_ino: store.store_ino, schema_sha256: store.schema_sha256, executing_writer_version: store.executing_writer_version }))
|
|
181
|
+
} };
|
|
182
|
+
}
|
|
183
|
+
/** Historical evidence, not present-day physical admission. Project sync may
|
|
184
|
+
* change current files without rewriting the original upgrade's storage proof.
|
|
185
|
+
* Workspace/repository eligibility belongs to the upgrade target preflight.
|
|
186
|
+
*/
|
|
187
|
+
export function readUpgradeStorageReadiness(context, scope) {
|
|
188
|
+
return context.db.readSnapshot(() => retainedStorage(context, scope));
|
|
189
|
+
}
|
|
190
|
+
function retainedStorage(context, scope) {
|
|
191
|
+
let logical;
|
|
192
|
+
try {
|
|
193
|
+
logical = resolveUpgradeHistoricalScope(context, scope);
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
// A first upgrade may observe an existing MB in a new runtime Home. This
|
|
197
|
+
// is absence of history, not registration or permission to revive it.
|
|
198
|
+
if (error instanceof AppError && error.details.reason === 'project_not_active'
|
|
199
|
+
&& !context.db.get('SELECT 1 FROM projects WHERE root=?', [scope.projectRoot])
|
|
200
|
+
&& !(hasRunStartTable(context) && context.db.get("SELECT 1 FROM run_start_requests WHERE request_id=? AND json_extract(payload_json,'$.project_root')=?", [scope.requestId, scope.projectRoot])))
|
|
201
|
+
return null;
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
const projectId = logical.projectId;
|
|
205
|
+
scope = logical.scope;
|
|
206
|
+
const row = context.db.get("SELECT value_json FROM runtime_config WHERE key = ?", [key(projectId, scope.requestId)]);
|
|
207
|
+
if (!row) {
|
|
208
|
+
if (context.db.get("SELECT 1 FROM audit_events WHERE project_id=? AND event_type='memory.upgrade_storage_ready' AND json_extract(payload_json,'$.request_id')=? AND json_extract(payload_json,'$.selection_sha256') IS NOT NULL LIMIT 1", [projectId, scope.requestId]))
|
|
209
|
+
refuse("storage_readiness_custody_missing");
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
if (Buffer.byteLength(row.value_json) > 68 * 1024 * 1024)
|
|
213
|
+
refuse("unbounded_retained_receipt");
|
|
214
|
+
const cached = retainedDecodes.get(context.db);
|
|
215
|
+
if (cached?.raw === row.value_json && cached.value.receipt.project_id === projectId
|
|
216
|
+
&& hybridDigest(cached.value.receipt.scope) === hybridDigest(scope)) {
|
|
217
|
+
assertRetainedReadinessAudit(context, projectId, scope, cached.value);
|
|
218
|
+
return structuredClone(cached.value);
|
|
219
|
+
}
|
|
220
|
+
let receipt;
|
|
221
|
+
try {
|
|
222
|
+
receipt = JSON.parse(row.value_json);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
return refuse("invalid_retained_receipt");
|
|
226
|
+
}
|
|
227
|
+
const frozen = receipt?.schema_id === "dd-flow/mb-upgrade-storage-request@2" || receipt?.schema_id === "dd-flow/mb-upgrade-storage-request@3";
|
|
228
|
+
const migrated = receipt?.schema_id === "dd-flow/mb-upgrade-storage-request@3";
|
|
229
|
+
if (!receipt || Object.keys(receipt).sort().join() !== (frozen ? "project_id,proof,schema_id,scope,selection" : "project_id,proof,schema_id,scope")
|
|
230
|
+
|| !["dd-flow/mb-upgrade-storage-request@1", "dd-flow/mb-upgrade-storage-request@2", "dd-flow/mb-upgrade-storage-request@3"].includes(receipt.schema_id) || receipt.project_id !== projectId
|
|
231
|
+
|| hybridDigest(receipt.scope) !== hybridDigest(scope) || !receipt.proof
|
|
232
|
+
|| receipt.proof.schema_id !== (migrated ? "dd-flow/mb-upgrade-storage-readiness@2" : "dd-flow/mb-upgrade-storage-readiness@1")
|
|
233
|
+
|| receipt.proof.route !== (migrated ? "migration" : "verified-no-op"))
|
|
234
|
+
refuse("changed_request");
|
|
235
|
+
if (receipt.proof.selection_sha256 !== undefined && (typeof receipt.proof.selection_sha256 !== "string"
|
|
236
|
+
|| !/^[a-f0-9]{64}$/.test(receipt.proof.selection_sha256)))
|
|
237
|
+
refuse("changed_request");
|
|
238
|
+
const sha256 = hybridDigest(receipt);
|
|
239
|
+
if (!frozen)
|
|
240
|
+
return { receipt, sha256 };
|
|
241
|
+
const retained = receipt.selection;
|
|
242
|
+
if (!retained || Object.keys(retained).sort().join() !== "payload_json,request_input,sha256" || typeof retained.payload_json !== "string"
|
|
243
|
+
|| retained.sha256 !== receipt.proof.selection_sha256)
|
|
244
|
+
refuse("invalid_retained_selection");
|
|
245
|
+
const selection = decodeMemoryUpgradeSelection(Buffer.from(retained.payload_json, "utf8"), retained.sha256, retained.request_input, scope);
|
|
246
|
+
if (migrated)
|
|
247
|
+
upgradeStorageMigration(receipt.proof.migration, retained.sha256);
|
|
248
|
+
const value = { receipt, sha256, selection };
|
|
249
|
+
assertRetainedReadinessAudit(context, projectId, scope, value);
|
|
250
|
+
retainedDecodes.set(context.db, { raw: row.value_json, value });
|
|
251
|
+
return structuredClone(value);
|
|
252
|
+
}
|
|
253
|
+
function assertRetainedReadinessAudit(context, projectId, scope, value) {
|
|
254
|
+
const { receipt, sha256 } = value, retained = receipt.selection;
|
|
255
|
+
if (!retained)
|
|
256
|
+
refuse("invalid_retained_selection");
|
|
257
|
+
const audits = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='memory.upgrade_storage_ready' AND json_extract(payload_json,'$.request_id')=? LIMIT 2", [projectId, scope.requestId]);
|
|
258
|
+
if (audits.length !== 1 || audits.some(event => Buffer.byteLength(event.payload_json) > 16 * 1024)
|
|
259
|
+
|| hybridDigest(JSON.parse(audits[0].payload_json)) !== hybridDigest({ request_id: scope.requestId, project_root: scope.projectRoot,
|
|
260
|
+
workspace_root: scope.workspaceRoot, receipt_sha256: sha256, route: receipt.proof.route, selection_sha256: retained.sha256 }))
|
|
261
|
+
refuse("storage_readiness_custody_missing");
|
|
262
|
+
}
|
|
263
|
+
/** Retains physical admission plus selected-engine phase evidence when storage
|
|
264
|
+
* changed. Legacy apply markers are insufficient for migration bootstrap.
|
|
265
|
+
* This is a storage dependency, not complete bootstrap or provider readiness.
|
|
266
|
+
*/
|
|
267
|
+
export class PreparedUpgradeStorageReadiness {
|
|
268
|
+
#receipt;
|
|
269
|
+
#engine;
|
|
270
|
+
#selectionSha256;
|
|
271
|
+
constructor(receipt, engine, selectionSha256) {
|
|
272
|
+
if (Buffer.byteLength(JSON.stringify(receipt)) > 68 * 1024 * 1024)
|
|
273
|
+
refuse("unbounded_retained_receipt");
|
|
274
|
+
this.#receipt = structuredClone(receipt);
|
|
275
|
+
this.#engine = engine;
|
|
276
|
+
this.#selectionSha256 = selectionSha256;
|
|
277
|
+
Object.freeze(this);
|
|
278
|
+
}
|
|
279
|
+
static prepareFromSelection(context, scope) {
|
|
280
|
+
if (context.db.isOwnedWriteTransaction?.())
|
|
281
|
+
refuse("preparation_outside_writer_required");
|
|
282
|
+
const projectId = project(context, scope);
|
|
283
|
+
scopeDirectories(scope);
|
|
284
|
+
const prior = readUpgradeStorageReadiness(context, scope);
|
|
285
|
+
const selected = frozenProof(context, scope, prior?.receipt.selection, prior?.receipt.proof.migration);
|
|
286
|
+
const receipt = { schema_id: selected.proof.route === "migration" ? "dd-flow/mb-upgrade-storage-request@3" : "dd-flow/mb-upgrade-storage-request@2", scope: { ...scope }, project_id: projectId,
|
|
287
|
+
proof: selected.proof, selection: selected.selection };
|
|
288
|
+
if (prior && prior.sha256 !== hybridDigest(receipt))
|
|
289
|
+
refuse("changed_request");
|
|
290
|
+
return new PreparedUpgradeStorageReadiness(receipt, undefined, selected.selectionSha256);
|
|
291
|
+
}
|
|
292
|
+
static prepare(context, scope, engine) {
|
|
293
|
+
const projectId = project(context, scope);
|
|
294
|
+
scopeDirectories(scope);
|
|
295
|
+
const proof = assertUpgradeRunStorageReady(context, scope.projectRoot, engine);
|
|
296
|
+
if (proof.route !== "verified-no-op")
|
|
297
|
+
refuse("storage_migration_requires_physical_proof");
|
|
298
|
+
const receipt = { schema_id: "dd-flow/mb-upgrade-storage-request@1", scope: { ...scope }, project_id: projectId, proof };
|
|
299
|
+
const prior = readUpgradeStorageReadiness(context, scope);
|
|
300
|
+
if (prior && prior.sha256 !== hybridDigest(receipt))
|
|
301
|
+
refuse("changed_request");
|
|
302
|
+
return new PreparedUpgradeStorageReadiness(receipt, engine);
|
|
303
|
+
}
|
|
304
|
+
apply(context) {
|
|
305
|
+
const scope = this.#receipt.scope;
|
|
306
|
+
if (this.#selectionSha256 !== undefined) {
|
|
307
|
+
const prior = readUpgradeStorageReadiness(context, scope);
|
|
308
|
+
if (prior) {
|
|
309
|
+
if (prior.sha256 !== hybridDigest(this.#receipt))
|
|
310
|
+
refuse("changed_request");
|
|
311
|
+
// Historical acknowledgement only, never fresh RUN/storage admission.
|
|
312
|
+
return { ...prior, changed: false };
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const proof = this.#selectionSha256 === undefined ? assertUpgradeRunStorageReady(context, scope.projectRoot, this.#engine)
|
|
316
|
+
: frozenProof(context, scope).proof;
|
|
317
|
+
if (project(context, scope) !== this.#receipt.project_id
|
|
318
|
+
|| hybridDigest(proof) !== hybridDigest(this.#receipt.proof))
|
|
319
|
+
refuse("changed_after_preparation");
|
|
320
|
+
return context.db.writeTransaction(() => {
|
|
321
|
+
const prior = readUpgradeStorageReadiness(context, scope);
|
|
322
|
+
if (prior) {
|
|
323
|
+
if (prior.sha256 !== hybridDigest(this.#receipt))
|
|
324
|
+
refuse("changed_request");
|
|
325
|
+
return { ...prior, changed: false };
|
|
326
|
+
}
|
|
327
|
+
const now = context.now();
|
|
328
|
+
context.db.run("INSERT INTO runtime_config (key,value_json,created_at,updated_at) VALUES (?,?,?,?)", [key(this.#receipt.project_id, scope.requestId), JSON.stringify(this.#receipt), now, now]);
|
|
329
|
+
appendAudit(context, { projectId: this.#receipt.project_id, eventType: "memory.upgrade_storage_ready", payload: {
|
|
330
|
+
request_id: scope.requestId, project_root: scope.projectRoot, workspace_root: scope.workspaceRoot,
|
|
331
|
+
receipt_sha256: hybridDigest(this.#receipt), route: this.#receipt.proof.route,
|
|
332
|
+
...(this.#selectionSha256 ? { selection_sha256: this.#selectionSha256 } : {})
|
|
333
|
+
} });
|
|
334
|
+
const accepted = readUpgradeStorageReadiness(context, scope);
|
|
335
|
+
if (!accepted || accepted.sha256 !== hybridDigest(this.#receipt))
|
|
336
|
+
refuse("storage_readiness_readback_failed");
|
|
337
|
+
return { ...accepted, changed: true };
|
|
338
|
+
}, "memoryUpgrade.storageReadiness");
|
|
339
|
+
}
|
|
340
|
+
}
|
|
@@ -81,14 +81,16 @@ export class PreparedMemoryVerificationAcceptance {
|
|
|
81
81
|
|| input.composition.result().sha256 !== input.compose.sha256))
|
|
82
82
|
refuse("prepared_authority_required");
|
|
83
83
|
const outputs = input.composition?.outputReferences() ?? [];
|
|
84
|
+
const upgrade = compose.stage === "upgrade", authoring = upgrade ? "upgrade" : "compose";
|
|
84
85
|
input.checks.assertOwner({ ...input.scope, scope: "aggregate" });
|
|
85
86
|
const diagnostic = result.outcome === "needs_repair";
|
|
86
|
-
if (compose
|
|
87
|
-
|| result.run_id !== compose.run_id || result.run_id !== input.scope.runId || result.predecessors.length !== 1 || result.predecessors[0].stage !==
|
|
87
|
+
if (!["compose", "upgrade"].includes(compose.stage) || compose.outcome !== "accepted" || result.stage !== "verify" || !["accepted", "needs_repair"].includes(result.outcome)
|
|
88
|
+
|| result.run_id !== compose.run_id || result.run_id !== input.scope.runId || result.predecessors.length !== 1 || result.predecessors[0].stage !== authoring
|
|
88
89
|
|| result.predecessors[0].input_sha256 !== compose.input_sha256 || result.predecessors[0].result.sha256 !== input.compose.sha256
|
|
89
|
-
|| compose.findings.some(finding => finding.severity !== "minor" && finding.status !== "resolved")
|
|
90
|
-
|| (!diagnostic && (!input.checks.qualityPassed() || result.findings.some(finding => finding.severity !== "minor" && finding.status !== "resolved"))))
|
|
90
|
+
|| compose.findings.some(finding => finding.severity !== "minor" && finding.status !== "resolved"))
|
|
91
91
|
refuse("unaccepted_or_foreign_predecessor");
|
|
92
|
+
if (!diagnostic && (!input.checks.qualityPassed() || result.findings.some(finding => finding.severity !== "minor" && finding.status !== "resolved")))
|
|
93
|
+
throw new AppError("memory_verification_acceptance_invalid", "Accepted verification requires passed selected checks and no unresolved material findings. For a real gap use needs_repair with an open material finding; a pending later REVIEW is a separate gate, not a current defect. Do not mark unresolved findings resolved or rerun settled checks merely to pass", 2, { reason: "verification_not_accepted", phase: "prepare", effect: "no_effect", recoverable: true, result_sha256: input.proposalSha256 ?? input.result.sha256 });
|
|
92
94
|
const expected = input.checks.resultReferences().map(reference => hybridDigest(reference)), actual = result.details.checks.map(reference => hybridDigest(reference));
|
|
93
95
|
const allowedChecks = new Set(input.checks.sourceReferences().map(({ path, sha256 }) => hybridDigest({ path, sha256 })));
|
|
94
96
|
if (expected.some(reference => !actual.includes(reference))
|
|
@@ -97,26 +99,45 @@ export class PreparedMemoryVerificationAcceptance {
|
|
|
97
99
|
const units = new Map(compose.units.map(unit => [unit.id, unit]));
|
|
98
100
|
if (result.units.length !== units.size || new Set(result.units.map(unit => unit.id)).size !== units.size)
|
|
99
101
|
refuse("changed_unit_coverage");
|
|
100
|
-
const
|
|
101
|
-
if (
|
|
102
|
+
const selectedAssessments = upgrade ? result.details.preservation_assessments?.map(item => ({ ...item, verdict: item.outcome === "preserved" ? "accepted" : item.outcome })) : result.details.semantic_assessments;
|
|
103
|
+
if (!selectedAssessments)
|
|
104
|
+
refuse("missing_unit_assessments");
|
|
105
|
+
const assessments = new Map();
|
|
106
|
+
for (const assessment of selectedAssessments)
|
|
107
|
+
assessments.set(assessment.unit_id, [...assessments.get(assessment.unit_id) ?? [], assessment]);
|
|
108
|
+
if (!upgrade && assessments.size !== selectedAssessments.length || [...assessments.keys()].some(id => !units.has(id))
|
|
102
109
|
|| compose.units.some(unit => unit.applicability !== "not-applicable" && !assessments.has(unit.id)))
|
|
103
110
|
refuse("incomplete_or_duplicate_assessments");
|
|
111
|
+
if (upgrade && hybridDigest(input.result.preservationInvariants()) !== hybridDigest(input.compose.preservationInvariants()))
|
|
112
|
+
refuse("changed_preservation_invariants");
|
|
104
113
|
const key = (source) => memorySourceKey(input.roots && input.roots.projectRoot === input.roots.workspace && source.root === "project" ? "workspace" : source.root, source.path);
|
|
105
114
|
for (const unit of result.units) {
|
|
106
|
-
const original = units.get(unit.id),
|
|
107
|
-
|
|
115
|
+
const original = units.get(unit.id), selected = assessments.get(unit.id) ?? [];
|
|
116
|
+
// IDs bind assessments to accepted units; aspect is descriptive prose.
|
|
117
|
+
// REVIEW derives its assignments from composition, not this wording.
|
|
118
|
+
if (!original || unit.applicability !== original.applicability || unit.applicability === "unknown"
|
|
108
119
|
|| (unit.applicability === "not-applicable" ? unit.outcome !== "not-applicable" : !["accepted", ...(diagnostic ? ["needs_repair"] : [])].includes(unit.outcome))
|
|
109
|
-
|| (assessment
|
|
120
|
+
|| selected.some(assessment => !["accepted", ...(diagnostic ? ["needs_repair"] : [])].includes(assessment.verdict) || !assessment.summary.trim()))
|
|
110
121
|
refuse("unit_not_accepted_or_changed");
|
|
111
122
|
assertMemoryVerificationArtifactReferences(original.artifacts, unit.artifacts, outputs);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
123
|
+
for (const assessment of selected) {
|
|
124
|
+
// PreparedMemoryStageResult already binds every evidence reference to
|
|
125
|
+
// admitted bytes. Cross-unit and earlier-stage evidence is useful context,
|
|
126
|
+
// but cannot replace examination of this unit's own composed output.
|
|
127
|
+
if (unit.applicability !== "not-applicable" && !assessment.evidence.some(source => original.artifacts.some(artifact => key(artifact) === key(source) && artifact.sha256 === source.sha256))) {
|
|
128
|
+
throw new AppError("memory_verification_acceptance_invalid", "Assess the composed documents themselves and cite a direct output reference for EACH applicable unit; the listed references identify this unit's outputs. Work result receipts do not replace direct output examination", 2, { reason: "assessment_does_not_examine_composed_output", phase: "prepare", effect: "no_effect", recoverable: true,
|
|
129
|
+
unit_id: unit.id, output_references: structuredClone(original.artifacts), result_sha256: input.result.sha256 });
|
|
130
|
+
}
|
|
131
|
+
if (upgrade) {
|
|
132
|
+
const id = assessment.invariant_id;
|
|
133
|
+
const invariant = input.result.preservationInvariants().find(item => item.id === id);
|
|
134
|
+
const old = invariant?.evidence;
|
|
135
|
+
// A current output alone cannot substantiate old-to-new preservation.
|
|
136
|
+
// Identical old bytes may be examined through the retained RUN copy.
|
|
137
|
+
if (!old?.some(before => assessment.evidence.some(source => source.sha256 === before.sha256 && (key(source) === key(before) || source.root === "run"))))
|
|
138
|
+
throw new AppError("memory_verification_acceptance_invalid", "Compare the retained old evidence as well as the current unit output; correct the assessment without rerunning settled checks", 2, { reason: "preservation_assessment_does_not_examine_old_evidence", phase: "prepare", effect: "no_effect", recoverable: true,
|
|
139
|
+
unit_id: unit.id, invariant_id: id, old_references: structuredClone(old ?? []), result_sha256: input.result.sha256 });
|
|
140
|
+
}
|
|
120
141
|
}
|
|
121
142
|
}
|
|
122
143
|
if (diagnostic && !result.findings.some(finding => finding.severity !== "minor" && finding.status === "open")) {
|
|
@@ -14,15 +14,29 @@ import { PreparedMemoryBaselinePreservation } from "./memory-baseline-preservati
|
|
|
14
14
|
import { readBoundedReleaseFile } from "./release-source.js";
|
|
15
15
|
import { codeCheckProfileHash, projectPolicyCheckDeclarationsFromProfile, readCodeCheckProfile, runCodeChecks, PreparedCheckEvidence } from "./code-checks.js";
|
|
16
16
|
import { ObservedMemorySources } from "./memory-source-observations.js";
|
|
17
|
-
import { memoryVerificationActivePaths,
|
|
17
|
+
import { memoryVerificationActivePaths, memoryUpgradeLintGuidance, memoryVerificationSupplementalLintFiles, memoryCanonicalLintExceptions, memoryCanonicalTemplatePaths } from "./memory-lint-config.js";
|
|
18
18
|
import { isMemoryCorrectionInputOwner } from "./memory-correction-input-owner.js";
|
|
19
|
+
import { readMemoryUpgradeBootstrapReadiness } from "./memory-upgrade-bootstrap-readiness.js";
|
|
20
|
+
import { readUpgradeStorageReadiness } from "./memory-upgrade-storage-readiness.js";
|
|
21
|
+
import { PreparedAcceptedMemoryComposition } from "./memory-accepted-composition.js";
|
|
22
|
+
import { RetainedExecutionBundle } from "./execution-bundles.js";
|
|
19
23
|
const token = Symbol("memory-verification-checks");
|
|
20
24
|
const hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
21
25
|
const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
|
|
22
26
|
const profilePath = ".memory-bank/spec/engineering/code-check-profile.json";
|
|
27
|
+
export function memorySupplementalLintCommand(executable, workspace, files, exceptions = []) {
|
|
28
|
+
const config = JSON.stringify(exceptions.length ? { ignoreFindings: exceptions } : {});
|
|
29
|
+
return `printf '%s\\n' ${quote(config)} > "$DD_FLOW_EVIDENCE_DIR/mb-lint-active-config.json" && ${quote(executable)} --root ${quote(workspace)} --config "$DD_FLOW_EVIDENCE_DIR/mb-lint-active-config.json" --files ${files.map(quote).join(" ")} --format json > "$DD_FLOW_EVIDENCE_DIR/mb-lint-active.json"`;
|
|
30
|
+
}
|
|
23
31
|
function refuse(reason) {
|
|
24
32
|
throw new AppError("memory_verification_checks_invalid", "Verification checks lack admitted tool, policy or accepted composition custody", 2, { reason, effect: "no_effect" });
|
|
25
33
|
}
|
|
34
|
+
/** First introduction adds obligations to mandatory lint; an existing policy
|
|
35
|
+
* remains immutable, including in the next correction wave. */
|
|
36
|
+
export function assertRetainedMemoryVerificationPolicy(previous, current) {
|
|
37
|
+
if (previous !== null && current !== previous)
|
|
38
|
+
refuse("correction_policy_requires_readmission");
|
|
39
|
+
}
|
|
26
40
|
/** Finite project checks use the original Codex home, never a temporary
|
|
27
41
|
* provider Session overlay. Keep every other application environment input. */
|
|
28
42
|
export function memoryCheckEnvironment(context, projectId, runId) {
|
|
@@ -86,19 +100,29 @@ function accepted(context, input) {
|
|
|
86
100
|
}
|
|
87
101
|
const frozen = input.execution.snapshot(), scope = { requestId: frozen.scope.requestId, projectRoot: frozen.scope.projectRoot, workspaceRoot: frozen.scope.workspace };
|
|
88
102
|
const run = context.db.get("SELECT workspace_root,project_root,run_root,run_dir,index_json FROM runs WHERE project_id=? AND id=?", [input.projectId, input.runId]);
|
|
89
|
-
const
|
|
103
|
+
const upgrade = input.flow.descriptor.id === "mb-upgrade";
|
|
104
|
+
const init = upgrade ? null : readMemoryInitBootstrapReadiness(context, scope);
|
|
105
|
+
const updated = upgrade ? readMemoryUpgradeBootstrapReadiness(context, scope) : null;
|
|
106
|
+
const bootstrap = init ?? updated;
|
|
107
|
+
const lint = upgrade ? updated?.authority.binding.lint ? { facts: updated.authority.binding.lint, sha256: hybridDigest(updated.authority.binding.lint) } : null
|
|
108
|
+
: readMemoryInitProjectBootstrapLintCapability(context, scope);
|
|
90
109
|
if (!run || run.project_root !== scope.projectRoot || run.workspace_root !== scope.workspaceRoot || !bootstrap || !lint
|
|
91
110
|
|| bootstrap.projectId !== input.projectId || bootstrap.authority.binding.execution_sha256 !== input.execution.sha256)
|
|
92
111
|
refuse("accepted_bootstrap_scope_required");
|
|
93
|
-
const
|
|
94
|
-
|
|
112
|
+
const authoring = upgrade ? "upgrade" : "compose";
|
|
113
|
+
const compose = readAcceptedStageData(context, { projectId: input.projectId, runId: input.runId, stage: authoring });
|
|
114
|
+
if (compose.data_schema_id !== (upgrade ? "dd-flow/mb-upgrade-stage-result@1" : "dd-flow/mb-init-compose-result@1"))
|
|
95
115
|
refuse("accepted_composition_required");
|
|
116
|
+
const baselineProfile = upgrade ? readUpgradeStorageReadiness(context, scope)?.selection?.payload.current.inventory.inventory.entries
|
|
117
|
+
.find(entry => entry.path === profilePath.slice(13))?.current?.sha256 ?? null : null;
|
|
96
118
|
const active = input.corrected ? "review" : "verify";
|
|
97
119
|
const { index_json, ...roots } = run, index = JSON.parse(index_json), stage = index.stage_runs.find((stage) => stage.stage === active);
|
|
98
120
|
if (index.current_stage !== active || stage?.status !== "running" || typeof stage.input_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(stage.input_sha256))
|
|
99
121
|
refuse(input.corrected ? "active_corrected_review_required" : "active_verification_required");
|
|
100
122
|
const phase = { attempt: stage.attempt ?? null, cycle: stage.cycle ?? null, input_sha256: stage.input_sha256 };
|
|
101
|
-
|
|
123
|
+
const probe = init?.authority.binding.target.tool_probes?.["mb-lint"] ?? updated?.authority.binding.lint;
|
|
124
|
+
const executable = init?.authority.binding.target.tools["mb-lint"] ?? updated?.authority.binding.lint?.executable;
|
|
125
|
+
return { run: roots, bootstrap, lint, compose, scope, probe, executable, baselineProfile, authority: hybridDigest({ run: roots, phase, bootstrap: bootstrap.authority, lint, compose: compose.authority_sha256,
|
|
102
126
|
...(input.corrected ? { corrected_sha256: input.corrected.sha256 } : {}) }) };
|
|
103
127
|
}
|
|
104
128
|
/** Owning CLI policy, not agent-proposed commands. The bootstrap-selected
|
|
@@ -147,30 +171,52 @@ export class PreparedMemoryVerificationChecks {
|
|
|
147
171
|
if (context.db.isOwnedWriteTransaction?.())
|
|
148
172
|
refuse("preparation_outside_writer_required");
|
|
149
173
|
if (!(input.execution instanceof FrozenGenericExecution) || !(input.flow instanceof ResolvedFlowDescriptor)
|
|
150
|
-
|| input.flow.descriptor.id
|
|
174
|
+
|| !["mb-init", "mb-upgrade"].includes(input.flow.descriptor.id) || input.execution.settings.flow_id !== input.flow.descriptor.id
|
|
151
175
|
|| input.execution.settings.descriptor_sha256 !== input.flow.sha256)
|
|
152
176
|
refuse("frozen_memory_selection_required");
|
|
153
177
|
input.corrected?.revalidate(context);
|
|
154
178
|
const selected = context.db.readSnapshot(() => accepted(context, input)), value = JSON.parse(selected.compose.bytes.toString("utf8"));
|
|
155
|
-
const
|
|
179
|
+
const upgrade = input.flow.descriptor.id === "mb-upgrade", authoring = upgrade ? "upgrade" : "compose";
|
|
180
|
+
const schema = JSON.parse(input.flow.stageBlob(authoring, "result_schema").toString("utf8"));
|
|
156
181
|
if (!new Ajv2020({ strict: true, strictRequired: false, validateFormats: false }).compile(schema)(value))
|
|
157
182
|
refuse("accepted_composition_required");
|
|
158
183
|
const compose = value;
|
|
159
|
-
if (compose.run_id !== input.runId || compose.stage !==
|
|
184
|
+
if (compose.run_id !== input.runId || compose.stage !== authoring || compose.outcome !== "accepted")
|
|
160
185
|
refuse("accepted_composition_required");
|
|
161
|
-
const probe = selected.
|
|
162
|
-
if (!probe || selected.
|
|
186
|
+
const probe = selected.probe;
|
|
187
|
+
if (!probe || selected.executable !== selected.lint.facts.executable
|
|
163
188
|
|| probe.executable_sha256 !== selected.lint.facts.executable_sha256 || probe.version !== selected.lint.facts.version
|
|
164
189
|
|| hash(readBoundedReleaseFile(selected.lint.facts.executable, 64 * 1024 * 1024)) !== probe.executable_sha256)
|
|
165
190
|
refuse("selected_linter_changed");
|
|
166
|
-
const
|
|
191
|
+
const bundle = input.bundle ?? input.corrected?.bundle();
|
|
192
|
+
if (upgrade && (!(bundle instanceof RetainedExecutionBundle) || bundle.execution.sha256 !== input.execution.sha256 || bundle.descriptor.sha256 !== input.flow.sha256))
|
|
193
|
+
refuse("registered_upgrade_bundle_required");
|
|
194
|
+
if (input.composition && (!(input.composition instanceof PreparedAcceptedMemoryComposition) || input.composition.result().sha256 !== selected.compose.result.sha256))
|
|
195
|
+
refuse("foreign_accepted_composition");
|
|
196
|
+
const preservation = upgrade ? input.corrected ?? input.composition ?? PreparedAcceptedMemoryComposition.prepare(context, { scope: input, bundle: bundle }) : PreparedMemoryBaselinePreservation.prepare(context, input.execution);
|
|
167
197
|
const workspace = selected.run.workspace_root;
|
|
168
198
|
const config = lintConfig(workspace);
|
|
169
199
|
const corrected = input.corrected?.value();
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
200
|
+
let guidance;
|
|
201
|
+
let canonicalEntries = [];
|
|
202
|
+
let canonicalFiles = new Map();
|
|
203
|
+
let templates;
|
|
204
|
+
if (upgrade) {
|
|
205
|
+
const storage = readUpgradeStorageReadiness(context, selected.scope);
|
|
206
|
+
if (!storage?.selection)
|
|
207
|
+
refuse("retained_upgrade_inventory_required");
|
|
208
|
+
const diff = readAcceptedStageData(context, { projectId: input.projectId, runId: input.runId, stage: "diff-analysis" });
|
|
209
|
+
guidance = memoryUpgradeLintGuidance(storage.selection.payload.current.inventory.inventory.entries, JSON.parse(diff.bytes.toString("utf8")).details.path_map);
|
|
210
|
+
canonicalEntries = storage.selection.payload.current.inventory.inventory.entries;
|
|
211
|
+
canonicalFiles = new Map(storage.selection.payload.layer.files.map(file => [file.path, Buffer.from(file.base64, "base64")]));
|
|
212
|
+
const current = new Map(canonicalEntries.filter(entry => /^mbb\/templates\/.+\.(?:md|markdown)$/.test(entry.path) && entry.current?.kind === "file" && entry.target && ["canonical", "local-customized"].includes(entry.ownership))
|
|
213
|
+
.map(entry => [entry.path, readBoundedReleaseFile(path.join(workspace, ".memory-bank", entry.path), 64 * 1024 * 1024)]));
|
|
214
|
+
templates = memoryCanonicalTemplatePaths(canonicalEntries, canonicalFiles, current);
|
|
215
|
+
}
|
|
216
|
+
const supplementalFiles = config ? memoryVerificationSupplementalLintFiles(config, corrected
|
|
217
|
+
? memoryVerificationActivePaths(corrected.unit_inventory, corrected.assignments, guidance, templates)
|
|
218
|
+
: memoryVerificationActivePaths(compose.units, [], guidance, templates)) : [];
|
|
219
|
+
const canonicalExceptions = memoryCanonicalLintExceptions(canonicalEntries, canonicalFiles, supplementalFiles);
|
|
174
220
|
let observed = null;
|
|
175
221
|
let exists = false;
|
|
176
222
|
try {
|
|
@@ -183,6 +229,8 @@ export class PreparedMemoryVerificationChecks {
|
|
|
183
229
|
}
|
|
184
230
|
const profileRefs = input.corrected ? input.corrected.value().run_inputs : compose.units.flatMap(unit => unit.artifacts);
|
|
185
231
|
const expectedProfiles = new Set(profileRefs.filter(artifact => artifact.root === "workspace" && artifact.path === profilePath).map(artifact => artifact.sha256));
|
|
232
|
+
if (upgrade && exists && !expectedProfiles.size && selected.baselineProfile)
|
|
233
|
+
expectedProfiles.add(selected.baselineProfile);
|
|
186
234
|
if (expectedProfiles.size !== (exists ? 1 : 0))
|
|
187
235
|
refuse("profile_presence_differs_from_composition");
|
|
188
236
|
if (exists) {
|
|
@@ -194,21 +242,27 @@ export class PreparedMemoryVerificationChecks {
|
|
|
194
242
|
const { profile: policy, hash: profileHash } = readCodeCheckProfile(workspace);
|
|
195
243
|
if (profileHash !== (observed?.hashes().get(`workspace\0${profilePath}`) ?? null))
|
|
196
244
|
refuse("profile_changed_during_preparation");
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
245
|
+
const lintCheck = { id: "MEMORY/verify/lint-schema-links", canonical_ref: "MEMORY/verify/lint-schema-links",
|
|
246
|
+
command: `${quote(selected.lint.facts.executable)} --root ${quote(workspace)} --format json > "$DD_FLOW_EVIDENCE_DIR/mb-lint.json"`,
|
|
247
|
+
purpose: "Mandatory bootstrap-selected Memory Bank lint, frontmatter/schema and Markdown target checks", run_at: "readiness", availability: "available", required_artifacts: ["mb-lint.json"], inputs: ["."],
|
|
248
|
+
// The pinned linter reads project files/config and invokes Git; provider
|
|
249
|
+
// Session configuration is not one of its execution dependencies.
|
|
250
|
+
environment_inputs: ["PATH", "PATHEXT", "HOME", "XDG_CONFIG_HOME", "NODE_*", "UV_*", "GIT_*", "LANG", "LC_*", "TZ", "DYLD_*", "LD_*", "SYSTEMROOT", "COMSPEC", "TMPDIR", "TMP", "TEMP"] };
|
|
251
|
+
const checks = [lintCheck,
|
|
252
|
+
...(supplementalFiles.length ? [{ ...lintCheck, id: "MEMORY/verify/lint-active-documents", canonical_ref: "MEMORY/verify/lint-active-documents",
|
|
253
|
+
command: memorySupplementalLintCommand(selected.lint.facts.executable, workspace, supplementalFiles, canonicalExceptions),
|
|
254
|
+
purpose: "Mandatory independent lint of active Markdown excluded by preserved project policy",
|
|
255
|
+
required_artifacts: ["mb-lint-active-config.json", "mb-lint-active.json"] }] : []),
|
|
203
256
|
...projectPolicyCheckDeclarationsFromProfile(policy, "readiness")];
|
|
204
|
-
if (input.corrected
|
|
205
|
-
|
|
257
|
+
if (input.corrected)
|
|
258
|
+
assertRetainedMemoryVerificationPolicy(input.corrected.verificationProfileHash(context), codeCheckProfileHash(workspace, checks));
|
|
206
259
|
const prepared = new PreparedMemoryVerificationChecks(token, input, selected, observed, preservation, checks, config ? hash(config) : null);
|
|
207
260
|
prepared.#checkEnvironment = memoryCheckEnvironment(context, input.projectId, input.runId);
|
|
208
261
|
prepared.revalidate(context);
|
|
209
262
|
return prepared;
|
|
210
263
|
}
|
|
211
264
|
declarations() { return structuredClone(this.#checks); }
|
|
265
|
+
profileHash() { return this.#profileSha256; }
|
|
212
266
|
assertCorrectionOwner(corrected) {
|
|
213
267
|
if (!isMemoryCorrectionInputOwner(corrected) || this.#input.corrected !== corrected)
|
|
214
268
|
refuse("foreign_correction_check_owner");
|