@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AppError } from "../shared/errors.js";
|
|
2
2
|
import { PreparedPublishedMemoryRepairWorkResult } from "./memory-repair-work-custody.js";
|
|
3
|
-
import { prepareMemoryRepairPrivateOutputFromStage, prepareMemoryRepairPublicationSourceFromStage,
|
|
3
|
+
import { prepareMemoryRepairPrivateOutputFromStage, prepareMemoryRepairPublicationSourceFromStage, restoreMemoryRepairPublicationSourceFromStage, reserveMemoryRepairPublicationAgainstSourceFromStage, reserveMemorySecondRepairPublicationFromStage, restoreMemoryRepairPublicationFromStage, prepareMemorySecondRepairPublicationFromStage, restoreStartedMemorySecondRepairLaunchFromStage } from "./memory-review-handler.js";
|
|
4
4
|
function refuse(reason) {
|
|
5
5
|
throw new AppError("memory_work_result_preparation_required", "Repair completion requires registered private output and physical start custody", 1, { reason, effect: "no_effect" });
|
|
6
6
|
}
|
|
@@ -47,47 +47,67 @@ export function prepareMemoryRepairWorkCompletion(context, work, result) {
|
|
|
47
47
|
const publication = restore();
|
|
48
48
|
if (!publication.bytes().equals(bytes))
|
|
49
49
|
refuse("retained_result_changed");
|
|
50
|
-
return { scope, bytes, wave, publication };
|
|
50
|
+
return { scope, bytes, wave, publication, source: null };
|
|
51
51
|
}
|
|
52
52
|
const outcome = wave === 1 ? prepareMemoryRepairPrivateOutputFromStage(context, { ...scope, bytes }).result().outcome
|
|
53
53
|
: restoreStartedMemorySecondRepairLaunchFromStage(context, scope).observeOutput(context, bytes).result.outcome;
|
|
54
|
-
if (outcome !== "accepted")
|
|
54
|
+
if (outcome !== "accepted" && outcome !== "reconciled")
|
|
55
55
|
refuse("accepted_private_output_required_use_managed_fail_for_blocker");
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
let source = null;
|
|
57
|
+
if (wave === 1) {
|
|
58
|
+
try {
|
|
59
|
+
source = restoreMemoryRepairPublicationSourceFromStage(context, scope);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (!(error instanceof AppError) || error.code !== "memory_repair_publication_source_invalid" || error.details.reason !== "registered_source_required")
|
|
63
|
+
throw error;
|
|
64
|
+
source = prepareMemoryRepairPublicationSourceFromStage(context, scope);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return { scope, bytes, wave, publication: null, source };
|
|
59
68
|
}
|
|
60
69
|
/** Called by managed Work finish after its normal Hook/actor admission.
|
|
61
|
-
*
|
|
70
|
+
* Sealed owners revalidate before effects; no caller-supplied object grants
|
|
62
71
|
* authority. Every action is terminal and advances the retained finite cursor. */
|
|
63
|
-
export function publishMemoryRepairWorkCompletion(context, work, result) {
|
|
72
|
+
export function publishMemoryRepairWorkCompletion(context, work, result, progress) {
|
|
73
|
+
progress?.(`Memory repair ${work.work_id}: preparing publication`);
|
|
64
74
|
const prepared = prepareMemoryRepairWorkCompletion(context, work, result);
|
|
65
75
|
const { scope, bytes, wave } = prepared;
|
|
76
|
+
progress?.(`Memory repair ${work.work_id}: publication input verified`);
|
|
66
77
|
try {
|
|
67
78
|
if (!prepared.publication) {
|
|
68
79
|
if (wave === 1) {
|
|
69
|
-
|
|
80
|
+
// Registration revalidates this invocation's source before and after
|
|
81
|
+
// its SQL effect; do not rebuild the entire same history graph again.
|
|
82
|
+
prepared.source.register(context);
|
|
83
|
+
progress?.(`Memory repair ${work.work_id}: publication source registered`);
|
|
70
84
|
reserveMemoryRepairPublicationAgainstSourceFromStage(context, { ...scope, bytes });
|
|
71
85
|
}
|
|
72
86
|
else
|
|
73
87
|
reserveMemorySecondRepairPublicationFromStage(context, { ...scope, bytes });
|
|
88
|
+
progress?.(`Memory repair ${work.work_id}: publication reserved`);
|
|
74
89
|
}
|
|
75
90
|
const publication = prepared.publication ?? (wave === 1 ? restoreMemoryRepairPublicationFromStage(context, scope) : prepareMemorySecondRepairPublicationFromStage(context, scope));
|
|
91
|
+
progress?.(`Memory repair ${work.work_id}: publication restored`);
|
|
76
92
|
const initial = publication.revalidate(context);
|
|
77
93
|
let completed = initial.completed;
|
|
94
|
+
progress?.(`Memory repair ${work.work_id}: publication ready (${completed}/${initial.total})`);
|
|
78
95
|
// The actual retained plan, not agent output or a guessed retry count,
|
|
79
96
|
// bounds execution. Even an already applied plan reconciles completion.
|
|
80
97
|
do {
|
|
81
98
|
const step = publication.applyNextAction(context);
|
|
82
99
|
if (step.total !== initial.total || step.completed < completed || step.completed > initial.total)
|
|
83
100
|
refuse("publication_cursor_changed");
|
|
101
|
+
if (step.status !== "applied" && step.completed !== completed + 1)
|
|
102
|
+
refuse("publication_did_not_advance");
|
|
103
|
+
progress?.(`Memory repair ${work.work_id}: publication advanced (${step.completed}/${step.total})`);
|
|
84
104
|
if (step.status === "applied")
|
|
85
105
|
break;
|
|
86
|
-
if (step.completed !== completed + 1)
|
|
87
|
-
refuse("publication_did_not_advance");
|
|
88
106
|
completed = step.completed;
|
|
89
107
|
} while (completed < initial.total);
|
|
90
|
-
|
|
108
|
+
const published = PreparedPublishedMemoryRepairWorkResult.prepare(context, publication);
|
|
109
|
+
progress?.(`Memory repair ${work.work_id}: published result verified`);
|
|
110
|
+
return published;
|
|
91
111
|
}
|
|
92
112
|
catch (error) {
|
|
93
113
|
throw new AppError("memory_repair_completion_pending", "Repair publication requires exact-request reconciliation before another completion attempt", 1, { effect: error instanceof AppError && error.details.effect === "unknown" ? "unknown" : "partial",
|
|
@@ -4,6 +4,16 @@ import { AppError } from "../shared/errors.js";
|
|
|
4
4
|
import { AdmittedFrozenEngine } from "./frozen-engine.js";
|
|
5
5
|
import { memoryRepairWorkerSchema } from "./memory-repair-registration.js";
|
|
6
6
|
import { memorySourceKey } from "./memory-stage-results.js";
|
|
7
|
+
import { parseMemoryWorkResultProposal } from "./memory-work-result-json.js";
|
|
8
|
+
/** Claim shape only, never settlement or finding closure. Reconciliation
|
|
9
|
+
* preserves documents and requires current grounded evidence plus the same
|
|
10
|
+
* fresh check and independent re-review owners as a physical repair. */
|
|
11
|
+
export function isCompletedMemoryRepairClaim(value) {
|
|
12
|
+
return Array.isArray(value?.changes) && (value.outcome === "accepted" && value.changes.length > 0
|
|
13
|
+
|| value.outcome === "reconciled" && value.changes.length === 0 && Array.isArray(value.resolutions) && value.resolutions.length > 0
|
|
14
|
+
&& value.resolutions.every(resolution => Array.isArray(resolution?.delta_paths) && resolution.delta_paths.length === 0
|
|
15
|
+
&& Array.isArray(resolution.evidence) && resolution.evidence.some(ref => ref?.root === "workspace")));
|
|
16
|
+
}
|
|
7
17
|
function refuse(reason) {
|
|
8
18
|
throw new AppError("memory_repair_work_result_invalid", "Repair result differs from registered findings and bounded write paths", 2, { reason, effect: "no_effect" });
|
|
9
19
|
}
|
|
@@ -24,7 +34,7 @@ export function validateMemoryRepairWorkResult(bytes, engine, binding) {
|
|
|
24
34
|
|| !binding.workId || binding.packet?.result_schema !== memoryRepairWorkerSchema)
|
|
25
35
|
refuse("invalid_preparation_binding");
|
|
26
36
|
const schema = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(engine.schema("memory-repair-work-result")));
|
|
27
|
-
const value =
|
|
37
|
+
const value = parseMemoryWorkResultProposal(bytes, "memory_repair_work_result_invalid");
|
|
28
38
|
if (schema.$id !== memoryRepairWorkerSchema)
|
|
29
39
|
refuse("invalid_result_schema");
|
|
30
40
|
const packet = binding.packet;
|
|
@@ -67,6 +77,15 @@ export function validateMemoryRepairWorkResult(bytes, engine, binding) {
|
|
|
67
77
|
if (sources.get(key) !== ref.sha256)
|
|
68
78
|
refuse("foreign_or_stale_resolution_evidence");
|
|
69
79
|
}
|
|
80
|
+
if (value.outcome === "reconciled") {
|
|
81
|
+
if (value.changes.length || resolution.delta_paths.length)
|
|
82
|
+
refuse("reconciliation_has_document_delta");
|
|
83
|
+
if (!resolution.evidence.some(ref => ref.root === "workspace"))
|
|
84
|
+
refuse("reconciliation_without_current_evidence");
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (!resolution.delta_paths.length)
|
|
88
|
+
refuse("resolution_without_actual_delta");
|
|
70
89
|
const causal = value.changes.filter(change => resolution.delta_paths.includes(change.path));
|
|
71
90
|
if (causal.some(change => change.after_sha256 !== null)) {
|
|
72
91
|
if (!resolution.evidence.some(ref => ref.root === "workspace" && causal.some(change => change.path === ref.path && change.after_sha256 !== null && change.after_sha256 === ref.sha256)))
|
|
@@ -75,7 +94,7 @@ export function validateMemoryRepairWorkResult(bytes, engine, binding) {
|
|
|
75
94
|
else if (!resolution.evidence.some(ref => ref.root === "run"))
|
|
76
95
|
refuse("deletion_without_retained_evidence");
|
|
77
96
|
}
|
|
78
|
-
if (value.outcome
|
|
97
|
+
if (value.outcome !== "blocked" && (!isCompletedMemoryRepairClaim(value) || ids.size !== packet.findings.length))
|
|
79
98
|
refuse("incomplete_repair_claim");
|
|
80
99
|
if (value.outcome === "blocked" && (value.changes.length || value.resolutions.length))
|
|
81
100
|
refuse("blocked_repair_has_productive_claims");
|
|
@@ -72,16 +72,25 @@ export class PreparedMemoryRepairWorkspace {
|
|
|
72
72
|
const exportedFiles = new Set(exported.entries().filter(entry => entry.kind === "file").map(entry => entry.path));
|
|
73
73
|
const selected = candidates.filter(logical => allowed(path.join(source, logical))
|
|
74
74
|
&& logical.split("/").every(part => ![".git", "node_modules", ".dd-flow"].includes(part.normalize("NFC").toLowerCase()))).filter(logical => {
|
|
75
|
+
const stat = fs.lstatSync(path.join(source, logical), { throwIfNoEntry: false });
|
|
76
|
+
// The sanitized writer copy contains regular inputs only. This applies
|
|
77
|
+
// to project command links too; never follow or materialize their targets.
|
|
78
|
+
if (stat?.isSymbolicLink())
|
|
79
|
+
return false;
|
|
75
80
|
if (!logical.startsWith(".memory-bank/") || exportedFiles.has(logical))
|
|
76
81
|
return true;
|
|
77
82
|
// Git's cached inventory includes tracked deletions. A retained export
|
|
78
83
|
// can explain an absent MB file, never a present unselected object.
|
|
79
|
-
if (
|
|
84
|
+
if (stat)
|
|
80
85
|
refuse("exported_source_changed");
|
|
81
86
|
return false;
|
|
82
87
|
});
|
|
83
88
|
const observed = ObservedMemorySources.capture({ workspace: source }, selected.map(logical => ({ root: "workspace", path: logical })));
|
|
84
|
-
const hashes = observed.hashes(), entries = new Map(exported.entries().map(entry =>
|
|
89
|
+
const hashes = observed.hashes(), entries = new Map(exported.entries().map(entry => {
|
|
90
|
+
if (entry.kind === "link")
|
|
91
|
+
refuse("linked_exported_source");
|
|
92
|
+
return [entry.path, { ...entry, kind: entry.kind }];
|
|
93
|
+
}));
|
|
85
94
|
for (const logical of selected) {
|
|
86
95
|
const file = path.join(source, logical), stat = fs.lstatSync(file);
|
|
87
96
|
const entry = { path: logical, kind: "file", mode: stat.mode & 0o7777, sha256: hashes.get(memorySourceKey("workspace", logical)) };
|
|
@@ -9,6 +9,7 @@ import { PreparedSecondMemoryRepairBaseline } from "./memory-second-repair-basel
|
|
|
9
9
|
import { PreparedMemoryRepairWorkspace } from "./memory-repair-workspace.js";
|
|
10
10
|
import { runtimeHarness } from "./execution-policy.js";
|
|
11
11
|
import { hookSessionIdentity } from "./hooks.js";
|
|
12
|
+
import { isCompletedMemoryRepairClaim } from "./memory-repair-work-results.js";
|
|
12
13
|
import { RetainedMemoryRepairPublicationPlan } from "./memory-repair-publication-plan.js";
|
|
13
14
|
import { PreparedMemoryRepairPublicationSource } from "./memory-repair-publication-source.js";
|
|
14
15
|
import { retainedMemoryRepairPublicationPlans } from "./memory-repair-publication-intent.js";
|
|
@@ -668,7 +669,7 @@ export class PreparedMemoryRepairPrivateOutput {
|
|
|
668
669
|
* Reservation alone grants no permission to apply files or settle Work.
|
|
669
670
|
*/
|
|
670
671
|
reservePublication(context) {
|
|
671
|
-
if (this.#result
|
|
672
|
+
if (!isCompletedMemoryRepairClaim(this.#result))
|
|
672
673
|
refuse("accepted_private_output_required");
|
|
673
674
|
this.revalidate(context);
|
|
674
675
|
const start = this.#copy.startedReceipt(), actions = this.#transition.actions();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
2
|
+
import { readMemoryUpgradeEvidence } from "./memory-upgrade-evidence.js";
|
|
2
3
|
import { AppError } from "../shared/errors.js";
|
|
3
4
|
import { ResolvedFlowDescriptor } from "./flow-descriptors.js";
|
|
4
5
|
import { FrozenGenericExecution } from "./generic-execution.js";
|
|
@@ -14,6 +15,9 @@ import { stageLifecycleMatchKey } from "./hooks.js";
|
|
|
14
15
|
import { captureRegisteredMemoryCompositionSources } from "./memory-preflight-handler.js";
|
|
15
16
|
import { memoryCompositionRegistrationDigest } from "./memory-composition-registration.js";
|
|
16
17
|
import { PreparedCheckEvidence } from "./code-checks.js";
|
|
18
|
+
import { readMemoryUpgradeBootstrapReadiness } from "./memory-upgrade-bootstrap-readiness.js";
|
|
19
|
+
import { readUpgradeStorageReadiness } from "./memory-upgrade-storage-readiness.js";
|
|
20
|
+
import { upgradeAuthoringPaths } from "./memory-upgrade-inventory.js";
|
|
17
21
|
function refuse() { throw new AppError("memory_result_binding_custody_invalid", "Memory result binding lacks independently accepted stage custody", 1, { effect: "no_effect" }); }
|
|
18
22
|
/** Select identity, predecessor and unit coverage from SQL, not result fields. */
|
|
19
23
|
function select(context, input) {
|
|
@@ -26,15 +30,17 @@ function select(context, input) {
|
|
|
26
30
|
const run = context.db.get("SELECT index_json, project_root, workspace_root FROM runs WHERE project_id = ? AND id = ?", [input.projectId, input.runId]);
|
|
27
31
|
if (!run)
|
|
28
32
|
refuse();
|
|
29
|
-
const index = JSON.parse(run.index_json), frozen = input.execution.snapshot();
|
|
33
|
+
const index = JSON.parse(run.index_json), frozen = input.execution.snapshot(), flowId = input.flow.descriptor.id;
|
|
34
|
+
const upgrade = flowId === "mb-upgrade";
|
|
35
|
+
const schemaId = (stage) => upgrade ? "dd-flow/mb-upgrade-stage-result@1" : `dd-flow/mb-init-${stage}-result@1`;
|
|
30
36
|
if (assertRunBundleSqlAuthority(context, input, index.execution_bundle)) {
|
|
31
37
|
if (!input.bundle || input.bundle.execution.sha256 !== input.execution.sha256 || input.bundle.descriptor.sha256 !== input.flow.sha256)
|
|
32
38
|
refuse();
|
|
33
39
|
assertPreparedRunBundleSqlAuthority(context, input, input.bundle);
|
|
34
40
|
}
|
|
35
41
|
const stage = index.stage_runs?.find((item) => item.stage === input.stage);
|
|
36
|
-
if (
|
|
37
|
-
|| index.execution_bundle && (index.execution_bundle.flow_id !==
|
|
42
|
+
if (!["mb-init", "mb-upgrade"].includes(flowId) || frozen.settings.flow_id !== flowId || frozen.settings.descriptor_sha256 !== input.flow.sha256
|
|
43
|
+
|| index.execution_bundle && (index.execution_bundle.flow_id !== flowId || index.execution_bundle.descriptor_sha256 !== input.flow.sha256)
|
|
38
44
|
|| frozen.scope.projectRoot !== run.project_root || frozen.scope.workspace !== run.workspace_root
|
|
39
45
|
|| (input.acceptedReplay ? stage?.status !== "done" : index.current_stage !== input.stage || stage?.status !== "running") || stage.dir !== input.flow.stage(input.stage).directory
|
|
40
46
|
|| typeof stage.attempt !== "string" || !/^try-[0-9]{3,}$/.test(stage.attempt)
|
|
@@ -42,7 +48,7 @@ function select(context, input) {
|
|
|
42
48
|
refuse();
|
|
43
49
|
const accepted = input.acceptedReplay ? readAcceptedStageData(context, input) : null;
|
|
44
50
|
const acceptedValue = accepted ? JSON.parse(accepted.bytes.toString("utf8")) : null;
|
|
45
|
-
if (accepted && (accepted.data_schema_id !==
|
|
51
|
+
if (accepted && (accepted.data_schema_id !== schemaId(input.stage)
|
|
46
52
|
|| acceptedValue.schema_id !== accepted.data_schema_id || acceptedValue.run_id !== input.runId
|
|
47
53
|
|| acceptedValue.stage !== input.stage || !(acceptedValue.outcome === "accepted" || input.stage === "verify" && acceptedValue.outcome === "needs_repair" && input.flow.successor("verify", "needs_repair") === "review")
|
|
48
54
|
|| acceptedValue.input_sha256 !== stage.input_sha256))
|
|
@@ -56,33 +62,87 @@ function select(context, input) {
|
|
|
56
62
|
const value = predecessor ? JSON.parse(predecessor.bytes.toString("utf8")) : null;
|
|
57
63
|
const preflight = entry ? null : readAcceptedStageData(context, { ...input, stage: "preflight" });
|
|
58
64
|
const preflightValue = preflight ? JSON.parse(preflight.bytes.toString("utf8")) : null;
|
|
59
|
-
if (preflight && (preflight.data_schema_id !== "
|
|
65
|
+
if (preflight && (preflight.data_schema_id !== schemaId("preflight")
|
|
60
66
|
|| preflightValue.schema_id !== preflight.data_schema_id || preflightValue.run_id !== input.runId
|
|
61
67
|
|| preflightValue.stage !== "preflight" || preflightValue.outcome !== "accepted"
|
|
62
68
|
|| preflightValue.input_sha256 !== preflight.input_sha256
|
|
63
69
|
|| typeof preflight.input_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(preflight.input_sha256)))
|
|
64
70
|
refuse();
|
|
65
|
-
if (predecessor && (predecessor.data_schema_id !==
|
|
71
|
+
if (predecessor && (predecessor.data_schema_id !== schemaId(edges[0].from)
|
|
66
72
|
|| value.schema_id !== predecessor.data_schema_id || value.run_id !== input.runId || value.stage !== edges[0].from
|
|
67
73
|
|| !(value.outcome === "accepted" || edges[0].from === "verify" && value.outcome === "needs_repair" && input.flow.successor("verify", "needs_repair") === input.stage)
|
|
68
74
|
|| typeof predecessor.input_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(predecessor.input_sha256)
|
|
69
75
|
|| value.input_sha256 !== predecessor.input_sha256 || !Array.isArray(value.units)))
|
|
70
76
|
refuse();
|
|
71
|
-
const unitIds = ["preflight", "discover"].includes(input.stage) ? null : value?.units.map((unit) => unit.id);
|
|
77
|
+
const unitIds = ["preflight", upgrade ? "diff-analysis" : "discover"].includes(input.stage) ? null : value?.units.map((unit) => unit.id);
|
|
72
78
|
if (unitIds !== null && (!Array.isArray(unitIds) || unitIds.some(id => typeof id !== "string") || new Set(unitIds).size !== unitIds.length))
|
|
73
79
|
refuse();
|
|
80
|
+
let upgradeBinding;
|
|
81
|
+
let upgradeAuthority = null;
|
|
82
|
+
let upgradeDiffValue = null;
|
|
83
|
+
const retainedEvidence = new Set(input.historicalEvidence ?? []);
|
|
84
|
+
if (upgrade) {
|
|
85
|
+
const scope = { requestId: frozen.scope.requestId, projectRoot: frozen.scope.projectRoot, workspaceRoot: frozen.scope.workspace };
|
|
86
|
+
const ready = readMemoryUpgradeBootstrapReadiness(context, scope), storage = readUpgradeStorageReadiness(context, scope);
|
|
87
|
+
if (!ready?.hasPreservationBaseline || ready.projectId !== input.projectId || !storage?.selection
|
|
88
|
+
|| ready.authority.binding.execution_sha256 !== input.execution.sha256
|
|
89
|
+
|| ready.authority.binding.storage_sha256 !== storage.sha256)
|
|
90
|
+
refuse();
|
|
91
|
+
const selected = storage.selection.payload;
|
|
92
|
+
if (selected.impact.chain.status !== "ok" || !selected.current.old_tree)
|
|
93
|
+
refuse();
|
|
94
|
+
const modes = ["patch", "targeted_minor", "full_migration"];
|
|
95
|
+
let minimum = selected.current.flow_pack_canon_version ? 2 : 0;
|
|
96
|
+
for (const step of selected.impact.chain.steps) {
|
|
97
|
+
const rank = modes.indexOf(step.minimum_upgrade_mode);
|
|
98
|
+
if (rank < 0 || typeof step.path_migration !== "boolean" || typeof step.runtime_migration !== "boolean")
|
|
99
|
+
refuse();
|
|
100
|
+
minimum = Math.max(minimum, step.path_migration || step.runtime_migration ? 2 : rank);
|
|
101
|
+
}
|
|
102
|
+
const invariants = preflightValue?.details.preservation_invariants;
|
|
103
|
+
if (!entry && !Array.isArray(invariants))
|
|
104
|
+
refuse();
|
|
105
|
+
const oldEvidence = readMemoryUpgradeEvidence(context, scope), evidence = oldEvidence.reference;
|
|
106
|
+
upgradeBinding = { minimumMode: modes[minimum], evidenceIndex: { path: evidence.path, sha256: evidence.sha256 }, ...(entry ? {} : { preservationInvariants: structuredClone(invariants) }) };
|
|
107
|
+
let diffAuthority = null;
|
|
108
|
+
if (!["preflight", "diff-analysis"].includes(input.stage)) {
|
|
109
|
+
const diff = readAcceptedStageData(context, { ...input, stage: "diff-analysis" });
|
|
110
|
+
const diffValue = JSON.parse(diff.bytes.toString("utf8"));
|
|
111
|
+
if (diff.data_schema_id !== schemaId("diff-analysis") || diffValue.schema_id !== diff.data_schema_id
|
|
112
|
+
|| diffValue.stage !== "diff-analysis" || diffValue.run_id !== input.runId || diffValue.outcome !== "accepted"
|
|
113
|
+
|| diffValue.input_sha256 !== diff.input_sha256 || !Array.isArray(diffValue.details?.path_map))
|
|
114
|
+
refuse();
|
|
115
|
+
upgradeBinding.authoringPaths = upgradeAuthoringPaths(selected.current.inventory.inventory, diffValue.details.path_map);
|
|
116
|
+
upgradeBinding.mechanicalUnits = new Set(diffValue.details.path_map
|
|
117
|
+
.map(change => change.unit_id).filter(id => !upgradeBinding.authoringPaths.has(id)));
|
|
118
|
+
upgradeDiffValue = diffValue;
|
|
119
|
+
diffAuthority = diff.authority_sha256;
|
|
120
|
+
}
|
|
121
|
+
// Old snapshots authorize historical evidence only, never current artifacts
|
|
122
|
+
// or reads through links/private paths. They do not replace source observation.
|
|
123
|
+
for (const { source } of oldEvidence.index.entries) {
|
|
124
|
+
retainedEvidence.add(`${memorySourceKey("workspace", source.path)}\0${source.sha256}`);
|
|
125
|
+
if (scope.projectRoot === scope.workspaceRoot)
|
|
126
|
+
retainedEvidence.add(`${memorySourceKey("project", source.path)}\0${source.sha256}`);
|
|
127
|
+
}
|
|
128
|
+
upgradeAuthority = hybridDigest({ bootstrap: ready.authority, storage: storage.sha256, diff: diffAuthority,
|
|
129
|
+
upgrade: { ...upgradeBinding, ...(upgradeBinding.authoringPaths ? { authoringPaths: [...upgradeBinding.authoringPaths] } : {}),
|
|
130
|
+
...(upgradeBinding.mechanicalUnits ? { mechanicalUnits: [...upgradeBinding.mechanicalUnits].sort() } : {}) } });
|
|
131
|
+
}
|
|
74
132
|
const binding = { runId: input.runId, stage: input.stage, inputSha256: stage.input_sha256,
|
|
75
133
|
predecessors: predecessor ? [{ stage: edges[0].from, result: predecessor.result, input_sha256: predecessor.input_sha256 }] : [],
|
|
76
134
|
unitIds, sources: new Map([...input.sources.hashes(), ...(input.checks?.artifactReferences() ?? []).map(ref => [memorySourceKey(ref.root, ref.path), ref.sha256])]),
|
|
77
|
-
historicalEvidence: new Set([...
|
|
135
|
+
historicalEvidence: new Set([...retainedEvidence, ...(value?.units ?? []).flatMap((unit) => unit.evidence.map(ref => `${memorySourceKey(ref.root, ref.path)}\0${ref.sha256}`))]),
|
|
78
136
|
execution: input.execution, executionSha256: input.execution.sha256,
|
|
137
|
+
...(upgradeBinding ? { upgrade: upgradeBinding } : {}),
|
|
79
138
|
...(preflight ? { preflight: { result: preflight.result, input_sha256: preflight.input_sha256,
|
|
80
139
|
semantic_scope: preflightValue.details.semantic_scope } } : {}) };
|
|
81
140
|
const authority = hybridDigest({ projectId: input.projectId, runId: input.runId, stage: input.stage,
|
|
82
141
|
attempt: stage.attempt, input: stage.input_sha256, directory: stage.dir, predecessor: predecessor?.authority_sha256 ?? null,
|
|
83
142
|
anchor: index.execution_bundle ?? null, execution: input.execution.sha256, preflight: preflight?.authority_sha256 ?? null,
|
|
84
|
-
accepted: accepted?.authority_sha256 ?? null, settlement: settlement ? hybridDigest(settlement) : null
|
|
85
|
-
|
|
143
|
+
accepted: accepted?.authority_sha256 ?? null, settlement: settlement ? hybridDigest(settlement) : null,
|
|
144
|
+
...(upgrade ? { upgrade: upgradeAuthority } : {}) });
|
|
145
|
+
return { binding, authority, predecessorStage: edges[0]?.from, value, preflightValue, accepted, acceptedValue, upgradeDiffValue };
|
|
86
146
|
}
|
|
87
147
|
/** Controller-owned input for result preparation and writer-safe rechecking.
|
|
88
148
|
* Semantic/check acceptance still belongs to productive stage handlers.
|
|
@@ -127,9 +187,14 @@ export class PreparedMemoryResultBinding {
|
|
|
127
187
|
if (!new Ajv2020({ strict: true, strictRequired: false, validateFormats: false }).compile(schema)(selected.preflightValue))
|
|
128
188
|
refuse();
|
|
129
189
|
}
|
|
190
|
+
if (selected.upgradeDiffValue) {
|
|
191
|
+
const schema = JSON.parse(input.flow.stageBlob("diff-analysis", "result_schema").toString("utf8"));
|
|
192
|
+
if (!new Ajv2020({ strict: true, strictRequired: false, validateFormats: false }).compile(schema)(selected.upgradeDiffValue))
|
|
193
|
+
refuse();
|
|
194
|
+
}
|
|
130
195
|
// Productive bound discovery/composition preserve independently accepted source
|
|
131
196
|
// bytes. Unbound data-only SDK fixtures gain no bootstrap qualification.
|
|
132
|
-
const preservation = input.bundle && ["discover", "compose"].includes(input.stage) ? PreparedMemoryBaselinePreservation.prepare(context, input.execution) : null;
|
|
197
|
+
const preservation = input.flow.descriptor.id === "mb-init" && input.bundle && ["discover", "compose"].includes(input.stage) ? PreparedMemoryBaselinePreservation.prepare(context, input.execution) : null;
|
|
133
198
|
const prepared = new PreparedMemoryResultBinding(preparedInput, selected, preservation);
|
|
134
199
|
prepared.assertCustody(context);
|
|
135
200
|
return prepared;
|
|
@@ -139,7 +204,7 @@ export class PreparedMemoryResultBinding {
|
|
|
139
204
|
|| input.bundle.execution.sha256 !== input.execution.sha256 || input.bundle.descriptor.sha256 !== input.flow.sha256)
|
|
140
205
|
refuse();
|
|
141
206
|
const observed = captureRegisteredMemoryCompositionSources(context, input.bundle, input.registrationScope);
|
|
142
|
-
const prepared = this.prepare(context, { ...input, stage:
|
|
207
|
+
const prepared = this.prepare(context, { ...input, stage: input.registrationScope.stage, sources: observed.sources });
|
|
143
208
|
if (prepared.#binding.inputSha256 !== observed.inputSha256)
|
|
144
209
|
refuse();
|
|
145
210
|
prepared.#registration = { scope: structuredClone(input.registrationScope), sha256: observed.registrationSha256 };
|
|
@@ -149,6 +214,7 @@ export class PreparedMemoryResultBinding {
|
|
|
149
214
|
}
|
|
150
215
|
binding() {
|
|
151
216
|
return { ...this.#binding, historicalEvidence: new Set(this.#binding.historicalEvidence), predecessors: structuredClone(this.#binding.predecessors), unitIds: structuredClone(this.#binding.unitIds),
|
|
217
|
+
...(this.#binding.upgrade ? { upgrade: structuredClone(this.#binding.upgrade) } : {}),
|
|
152
218
|
...(this.#binding.preflight ? { preflight: structuredClone(this.#binding.preflight) } : {}), sources: new Map(this.#binding.sources) };
|
|
153
219
|
}
|
|
154
220
|
/** Retained accepted data for handler replay, never caller-supplied bytes. */
|
|
@@ -18,16 +18,17 @@ function refuse(reason) {
|
|
|
18
18
|
}
|
|
19
19
|
function original(context, scope, bundle) {
|
|
20
20
|
const phase = readMemoryReviewPhase(context, scope, bundle), registration = readRegisteredMemoryReview(context, phase, 0);
|
|
21
|
-
const
|
|
21
|
+
const upgrade = bundle.descriptor.descriptor.id === "mb-upgrade", authoring = upgrade ? "upgrade" : "compose";
|
|
22
|
+
const compose = readAcceptedStageData(context, { ...scope, stage: authoring });
|
|
22
23
|
const verify = readAcceptedStageData(context, { ...scope, stage: "verify" });
|
|
23
24
|
const composed = JSON.parse(compose.bytes.toString("utf8")), verified = JSON.parse(verify.bytes.toString("utf8"));
|
|
24
25
|
const view = registration.view;
|
|
25
26
|
if (registration.input_sha256 !== hybridDigest(view) || view.wave !== 0 || view.scope.bundle_sha256 !== bundle.sha256
|
|
26
27
|
|| view.scope.execution_sha256 !== bundle.execution.sha256 || view.source_policy !== "review-inputs@1"
|
|
27
|
-
|| compose.data_schema_id !== "dd-flow/mb-init-compose-result@1" || composed.schema_id !== compose.data_schema_id
|
|
28
|
-
|| composed.run_id !== scope.runId || composed.stage !==
|
|
28
|
+
|| compose.data_schema_id !== (upgrade ? "dd-flow/mb-upgrade-stage-result@1" : "dd-flow/mb-init-compose-result@1") || composed.schema_id !== compose.data_schema_id
|
|
29
|
+
|| composed.run_id !== scope.runId || composed.stage !== authoring || composed.outcome !== "accepted"
|
|
29
30
|
|| composed.input_sha256 !== compose.input_sha256 || hybridDigest(composed.units) !== hybridDigest(view.unit_inventory)
|
|
30
|
-
|| verify.data_schema_id !== "dd-flow/mb-init-verify-result@1" || verified.schema_id !== verify.data_schema_id
|
|
31
|
+
|| verify.data_schema_id !== (upgrade ? "dd-flow/mb-upgrade-stage-result@1" : "dd-flow/mb-init-verify-result@1") || verified.schema_id !== verify.data_schema_id
|
|
31
32
|
|| verified.run_id !== scope.runId || verified.stage !== "verify" || !["accepted", "needs_repair"].includes(verified.outcome)
|
|
32
33
|
|| verified.input_sha256 !== verify.input_sha256)
|
|
33
34
|
refuse("changed_original_review_binding");
|
|
@@ -74,27 +75,35 @@ export class PreparedRetainedMemoryReviewAuthority {
|
|
|
74
75
|
Object.freeze(this);
|
|
75
76
|
}
|
|
76
77
|
static prepare(context, scope, bundle) {
|
|
77
|
-
if (!(bundle instanceof RetainedExecutionBundle) || bundle.descriptor.descriptor.id
|
|
78
|
+
if (!(bundle instanceof RetainedExecutionBundle) || !["mb-init", "mb-upgrade"].includes(bundle.descriptor.descriptor.id))
|
|
78
79
|
refuse("registered_memory_bundle_required");
|
|
79
80
|
const generation = assertRunMutationAllowed(context, scope.projectId, scope.runId);
|
|
80
81
|
const selected = context.db.readSnapshot(() => original(context, scope, bundle));
|
|
81
82
|
const run = assertPreparedRunBundleSqlAuthority(context, scope, bundle), root = run.run_root ?? run.run_dir;
|
|
82
|
-
const
|
|
83
|
+
const upgrade = bundle.descriptor.descriptor.id === "mb-upgrade";
|
|
84
|
+
const refs = upgrade ? selected.registration.view.run_inputs.filter(ref => ref.root === "run" || !ref.path.startsWith(".memory-bank/"))
|
|
85
|
+
: [selected.compose, selected.verify].map(data => ({ root: "run", ...data.result }));
|
|
86
|
+
const observed = ObservedMemorySources.capture({ run: root, workspace: bundle.execution.snapshot().scope.workspace }, refs.map(({ root, path }) => ({ root, path })));
|
|
83
87
|
const hashes = observed.hashes();
|
|
84
88
|
if ([selected.compose, selected.verify].some(data => hashes.get(memorySourceKey("run", data.result.path)) !== data.result.sha256))
|
|
85
89
|
refuse("retained_predecessor_bytes_changed");
|
|
90
|
+
if (refs.some(ref => hashes.get(memorySourceKey(ref.root, ref.path)) !== ref.sha256))
|
|
91
|
+
refuse("retained_source_bytes_changed");
|
|
86
92
|
const coordinator = PreparedDescriptorStageCoordinator.prepare(context, {
|
|
87
93
|
projectRoot: bundle.execution.snapshot().scope.projectRoot, runId: scope.runId, stage: "review"
|
|
88
94
|
});
|
|
89
95
|
coordinator.assertAllocatedCustody(context);
|
|
90
96
|
const session = readDescriptorCoordinatorSession(context, { scope: { ...scope, stage: "review" }, bundle,
|
|
91
97
|
workId: selected.registration.scope.parent_work_id }, refuse);
|
|
92
|
-
const preservation = PreparedMemoryBaselinePreservation.prepare(context, bundle.execution);
|
|
93
98
|
// Historical accepted compose binding intentionally observes only retained
|
|
94
99
|
// predecessor files. It does not claim today's repaired documents still
|
|
95
100
|
// have the original output hashes or rerun current project policy.
|
|
96
|
-
const compose = PreparedMemoryResultBinding.prepare(context, { ...scope, stage: "compose", flow: bundle.descriptor,
|
|
101
|
+
const compose = PreparedMemoryResultBinding.prepare(context, { ...scope, stage: upgrade ? "upgrade" : "compose", flow: bundle.descriptor,
|
|
97
102
|
execution: bundle.execution, bundle, acceptedReplay: true, sources: observed });
|
|
103
|
+
// The historical upgrade binding retains its original bootstrap/old tree;
|
|
104
|
+
// observed inputs preserve old RUN copies and original non-MB sources.
|
|
105
|
+
// Current repaired MB bytes belong to separately settled repair views.
|
|
106
|
+
const preservation = upgrade ? compose : PreparedMemoryBaselinePreservation.prepare(context, bundle.execution);
|
|
98
107
|
if (!compose.acceptedBytes()?.equals(selected.compose.bytes))
|
|
99
108
|
refuse("accepted_composition_changed");
|
|
100
109
|
const verify = PreparedAcceptedMemoryVerification.prepare(context, { ...scope, bytes: selected.verify.bytes });
|
|
@@ -46,7 +46,7 @@ export class PreparedRetainedSecondMemoryBaseline {
|
|
|
46
46
|
static prepare(context, input) {
|
|
47
47
|
if (!PreparedRetainedSecondMemoryRepairs.isPrepared(input.registration))
|
|
48
48
|
refuse("retained_second_registration_required");
|
|
49
|
-
if (!(input.bundle instanceof RetainedExecutionBundle) || input.bundle.descriptor.descriptor.id
|
|
49
|
+
if (!(input.bundle instanceof RetainedExecutionBundle) || !["mb-init", "mb-upgrade"].includes(input.bundle.descriptor.descriptor.id))
|
|
50
50
|
refuse("registered_memory_bundle_required");
|
|
51
51
|
if (context.db.isOwnedWriteTransaction?.())
|
|
52
52
|
refuse("preparation_outside_writer_required");
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { assertMemorySqlCustody } from "./memory-custody-snapshot.js";
|
|
2
3
|
import { AppError } from "../shared/errors.js";
|
|
3
4
|
import { RetainedExecutionBundle } from "./execution-bundles.js";
|
|
4
5
|
import { hybridDigest } from "./hybrid-contracts.js";
|
|
5
6
|
import { PreparedHistoricalOriginalMemoryReview } from "./memory-historical-original-review.js";
|
|
6
7
|
import { PreparedHistoricalCorrectedMemoryReview } from "./memory-historical-corrected-review.js";
|
|
7
8
|
import { readRetainedCorrectedMemoryReviewRegistration } from "./memory-corrected-review-registration.js";
|
|
8
|
-
import { validateMemoryReviewCorrectionPlan } from "./memory-review-correction-plan.js";
|
|
9
|
+
import { validateMemoryReviewCorrectionPlan, memoryRepairCanonicalPaths } from "./memory-review-correction-plan.js";
|
|
9
10
|
import { memoryRepairBatch, memoryRepairWorkerSchema } from "./memory-repair-registration.js";
|
|
10
11
|
import { readWorkBatchCustody } from "./work-registry.js";
|
|
11
12
|
const token = Symbol("retained-second-memory-repairs");
|
|
@@ -59,6 +60,7 @@ function assertAllocation(context, receipt, batch) {
|
|
|
59
60
|
/** Retained second graph only. Does not grant current source/write admission,
|
|
60
61
|
* allocate replacement Works or dispatch providers after receipt loss. */
|
|
61
62
|
export class PreparedRetainedSecondMemoryRepairs {
|
|
63
|
+
#custodyKey = {};
|
|
62
64
|
static isPrepared(value) {
|
|
63
65
|
return typeof value === "object" && value !== null && #receipt in value;
|
|
64
66
|
}
|
|
@@ -86,7 +88,7 @@ export class PreparedRetainedSecondMemoryRepairs {
|
|
|
86
88
|
return this.#prepare(context, input, true);
|
|
87
89
|
}
|
|
88
90
|
static #prepare(context, input, closed) {
|
|
89
|
-
if (!(input.bundle instanceof RetainedExecutionBundle) || input.bundle.descriptor.descriptor.id
|
|
91
|
+
if (!(input.bundle instanceof RetainedExecutionBundle) || !["mb-init", "mb-upgrade"].includes(input.bundle.descriptor.descriptor.id))
|
|
90
92
|
refuse("registered_memory_bundle_required");
|
|
91
93
|
if (context.db.isOwnedWriteTransaction?.())
|
|
92
94
|
refuse("preparation_outside_writer_required");
|
|
@@ -105,6 +107,7 @@ export class PreparedRetainedSecondMemoryRepairs {
|
|
|
105
107
|
refuse("changed_first_review");
|
|
106
108
|
const view = first.view, assignments = new Map(view.assignments.map(item => [item.unit.id, item.unit]));
|
|
107
109
|
const plan = validateMemoryReviewCorrectionPlan(bytes, input.bundle.engine(), { runId: scope.run_id, wave: 2,
|
|
110
|
+
canonicalPaths: memoryRepairCanonicalPaths(context, input.bundle),
|
|
108
111
|
reviewSha256: receipt.review_sha256, units: view.unit_inventory.map(unit => assignments.get(unit.id) ?? unit), findings: history.projection().findings });
|
|
109
112
|
if (history.projection().outcome === "blocked")
|
|
110
113
|
refuse("blocked_first_review");
|
|
@@ -147,10 +150,13 @@ export class PreparedRetainedSecondMemoryRepairs {
|
|
|
147
150
|
firstEffectiveReviewProof(context) { this.assertCustody(context); return this.#history.effectiveReviewProof(context); }
|
|
148
151
|
verificationProfileHash(context) { this.assertCustody(context); return this.#history.verificationProfileHash(context); }
|
|
149
152
|
assertCustody(context) {
|
|
150
|
-
this.#history
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
const history = this.#history;
|
|
154
|
+
assertMemorySqlCustody(context, this.#custodyKey, () => {
|
|
155
|
+
history.assertCustody(context);
|
|
156
|
+
if (hybridDigest(selected(context, this.#receipt.scope)) !== hybridDigest(this.#receipt))
|
|
157
|
+
refuse("registration_changed");
|
|
158
|
+
assertAllocation(context, this.#receipt, this.#batch);
|
|
159
|
+
});
|
|
154
160
|
}
|
|
155
161
|
revalidate(context) { this.#history.revalidate(context); this.assertCustody(context); }
|
|
156
162
|
}
|
|
@@ -8,32 +8,66 @@ import { ObservedMemorySources } from "./memory-source-observations.js";
|
|
|
8
8
|
import { memorySourceKey } from "./memory-stage-results.js";
|
|
9
9
|
import { reviewSourceHash, reviewEnvironmentAllowed } from "./review-copy.js";
|
|
10
10
|
import { memoryProjectConfigurationPath } from "./memory-composition-graph.js";
|
|
11
|
+
import { RetainedExecutionBundle } from "./execution-bundles.js";
|
|
12
|
+
import { readUpgradeStorageReadiness } from "./memory-upgrade-storage-readiness.js";
|
|
13
|
+
import { readAcceptedStageData } from "./stage-report-renderer.js";
|
|
14
|
+
import { upgradeAuthoringPaths } from "./memory-upgrade-inventory.js";
|
|
11
15
|
function refuse(reason) {
|
|
12
16
|
throw new AppError("memory_review_correction_invalid", "Correction plan differs from retained material findings", 2, { reason, effect: "no_effect" });
|
|
13
17
|
}
|
|
14
18
|
const alias = (value) => value.normalize("NFC").toLowerCase();
|
|
15
|
-
const
|
|
19
|
+
const overlapAliases = (a, b) => a === b || a.startsWith(`${b}/`) || b.startsWith(`${a}/`);
|
|
20
|
+
const overlap = (a, b) => overlapAliases(alias(a), alias(b));
|
|
16
21
|
/** Exact project-owned document paths shared by graph and physical admission. */
|
|
17
|
-
export function assertMemoryRepairWritePaths(paths) {
|
|
22
|
+
export function assertMemoryRepairWritePaths(paths, canonicalPaths = new Set()) {
|
|
18
23
|
if (!Array.isArray(paths) || !paths.length || paths.length > 512)
|
|
19
24
|
refuse("foreign_correction_write");
|
|
20
25
|
if (paths.some(logical => typeof logical !== "string"))
|
|
21
26
|
refuse("foreign_correction_write");
|
|
22
|
-
|
|
27
|
+
const normalizedPaths = paths.map(alias);
|
|
28
|
+
if (new Set(normalizedPaths).size !== paths.length)
|
|
23
29
|
refuse("aliased_correction_paths");
|
|
24
|
-
for (const logical of paths) {
|
|
25
|
-
if (typeof logical !== "string"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
for (const [index, logical] of paths.entries()) {
|
|
31
|
+
if (typeof logical !== "string")
|
|
32
|
+
refuse("foreign_correction_write");
|
|
33
|
+
const parts = logical.split("/");
|
|
34
|
+
if (!logical.startsWith(".memory-bank/") || logical.includes("\\") || logical.includes("\0")
|
|
35
|
+
|| logical.length > 4096 || parts.some(part => !part || part === "." || part === "..")
|
|
36
|
+
|| parts.some(part => { const normalized = alias(part); return !reviewEnvironmentAllowed(normalized) || [".git", "node_modules", ".dd-flow"].includes(normalized); })
|
|
37
|
+
|| !memoryProjectConfigurationPath(logical) && !canonicalPaths.has(logical)
|
|
38
|
+
&& [".memory-bank/dd-flow", ".memory-bank/mbb"].some(canonical => overlapAliases(normalizedPaths[index], canonical)))
|
|
30
39
|
refuse("foreign_correction_write");
|
|
31
40
|
}
|
|
32
41
|
}
|
|
42
|
+
/** Exact exceptions from the original CLI-selected ownership inventory, not
|
|
43
|
+
* path names or authority fields supplied in a repair proposal. Canonical
|
|
44
|
+
* defaults, generated markers, links and directory aliases remain excluded. */
|
|
45
|
+
export function memoryRepairCanonicalPaths(context, bundle) {
|
|
46
|
+
if (!(bundle instanceof RetainedExecutionBundle) || !["mb-init", "mb-upgrade"].includes(bundle.descriptor.descriptor.id))
|
|
47
|
+
refuse("registered_memory_bundle_required");
|
|
48
|
+
if (bundle.descriptor.descriptor.id === "mb-init")
|
|
49
|
+
return new Set();
|
|
50
|
+
const frozen = bundle.execution.snapshot().scope;
|
|
51
|
+
const storage = readUpgradeStorageReadiness(context, { requestId: frozen.requestId, projectRoot: frozen.projectRoot, workspaceRoot: frozen.workspace }), selected = storage?.selection;
|
|
52
|
+
if (!selected || selected.payload.execution.sha256 !== bundle.execution.sha256)
|
|
53
|
+
refuse("retained_upgrade_selection_required");
|
|
54
|
+
const data = readAcceptedStageData(context, { projectId: storage.receipt.project_id, runId: bundle.manifest.run_id, stage: "diff-analysis" });
|
|
55
|
+
const diff = JSON.parse(data.bytes.toString("utf8"));
|
|
56
|
+
if (data.data_schema_id !== "dd-flow/mb-upgrade-stage-result@1" || diff.stage !== "diff-analysis" || diff.outcome !== "accepted")
|
|
57
|
+
refuse("accepted_upgrade_diff_required");
|
|
58
|
+
const inventory = selected.payload.current.inventory.inventory;
|
|
59
|
+
const paths = [...upgradeAuthoringPaths(inventory, diff.details.path_map).values()].flat();
|
|
60
|
+
return new Set(paths.filter(logical => {
|
|
61
|
+
const entry = inventory.entries.find(entry => `.memory-bank/${entry.path}` === logical);
|
|
62
|
+
return [".memory-bank/dd-flow/", ".memory-bank/mbb/"].some(prefix => logical.startsWith(prefix))
|
|
63
|
+
&& logical.split("/").every(part => reviewEnvironmentAllowed(alias(part)) && ![".git", "node_modules", ".dd-flow"].includes(alias(part)))
|
|
64
|
+
&& (!entry || ["project", "local-customized"].includes(entry.ownership));
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
33
67
|
/** Semantic graph checks after strict frozen-schema admission. This projection
|
|
34
68
|
* is not Work allocation, source-byte admission or filesystem permission.
|
|
35
69
|
*/
|
|
36
|
-
export function validateMemoryReviewCorrectionGraph(plan, units, findings) {
|
|
70
|
+
export function validateMemoryReviewCorrectionGraph(plan, units, findings, canonicalPaths = new Set()) {
|
|
37
71
|
const material = new Map(findings.filter(finding => finding.severity !== "minor" && finding.status === "open").map(finding => [finding.id, finding]));
|
|
38
72
|
if (!material.size)
|
|
39
73
|
refuse("no_material_correction_required");
|
|
@@ -47,7 +81,7 @@ export function validateMemoryReviewCorrectionGraph(plan, units, findings) {
|
|
|
47
81
|
refuse("missing_correction_task");
|
|
48
82
|
if (work.depends_on.some(key => !byKey.has(key) || key === work.key))
|
|
49
83
|
refuse("foreign_correction_dependency");
|
|
50
|
-
assertMemoryRepairWritePaths(work.write_paths);
|
|
84
|
+
assertMemoryRepairWritePaths(work.write_paths, canonicalPaths);
|
|
51
85
|
const targetUnits = new Set();
|
|
52
86
|
for (const id of work.finding_ids) {
|
|
53
87
|
const finding = material.get(id);
|
|
@@ -109,7 +143,7 @@ export function validateMemoryReviewCorrectionPlan(bytes, engine, binding) {
|
|
|
109
143
|
refuse("invalid_correction_schema");
|
|
110
144
|
if (value.run_id !== binding.runId || value.wave !== binding.wave || value.review_sha256 !== binding.reviewSha256)
|
|
111
145
|
refuse("foreign_correction_identity");
|
|
112
|
-
validateMemoryReviewCorrectionGraph(value, binding.units, binding.findings);
|
|
146
|
+
validateMemoryReviewCorrectionGraph(value, binding.units, binding.findings, binding.canonicalPaths);
|
|
113
147
|
return structuredClone(value);
|
|
114
148
|
}
|
|
115
149
|
catch (error) {
|
|
@@ -144,6 +178,7 @@ export class PreparedMemoryInitialCorrectionPlan {
|
|
|
144
178
|
if (results.some(result => result.outcome === "blocked"))
|
|
145
179
|
refuse("blocked_review_requires_resolution");
|
|
146
180
|
const value = validateMemoryReviewCorrectionPlan(bytes, bundle.engine(), { runId: registration.run_id, wave: 1,
|
|
181
|
+
canonicalPaths: memoryRepairCanonicalPaths(context, bundle),
|
|
147
182
|
reviewSha256: review.sha256, units: registration.view.unit_inventory, findings: memoryReviewWaveProjection(results, registration.view.diagnostic_findings).findings });
|
|
148
183
|
for (const work of value.works)
|
|
149
184
|
bundle.execution.work("review", work.profile_id ?? undefined);
|