@deksden-com/dd-flow-cli 0.9.0-beta.144 → 0.9.0-beta.145
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/README.md +13 -8
- package/dist/build-info.json +7 -7
- package/dist/cli/command-inputs.js +9 -3
- package/dist/cli/command-metadata.js +33 -14
- package/dist/cli/help.js +32 -1
- package/dist/cli/hook-ingress.js +27 -3
- package/dist/cli/memory-flow-admission.js +7 -3
- package/dist/cli/memory-init-invocation.js +10 -3
- package/dist/cli/memory-init.js +74 -68
- package/dist/cli/memory-storage-admission.js +3 -2
- package/dist/cli/memory-storage-qualification.js +105 -0
- package/dist/cli/memory-storage-transition.js +123 -0
- package/dist/cli/memory-upgrade.js +189 -0
- package/dist/cli/native-hook-ingress.js +14 -4
- package/dist/cli/native-hook-preparation.js +55 -0
- package/dist/cli/response-file.js +2 -1
- package/dist/cli/run-cli.js +70 -32
- package/dist/cli/setup.js +2 -1
- package/dist/cli.js +12 -0
- package/dist/domain/stage-handler-identities.js +8 -0
- package/dist/harness-runtime/bin/dd-agy.mjs +3 -3
- package/dist/harness-runtime/bin/dd-grok.mjs +3 -3
- package/dist/harness-runtime/bin/dd-zcode.mjs +5 -5
- package/dist/harness-runtime/lib/agy-error-boundary.mjs +58 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +62 -20
- package/dist/harness-runtime/lib/dd-agy.mjs +17 -3
- package/dist/harness-runtime/lib/dd-droid.mjs +1 -1
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +70 -13
- package/dist/harness-runtime/lib/dd-grok.mjs +6 -3
- package/dist/harness-runtime/lib/dd-zcode.mjs +44 -14
- package/dist/harness-runtime/lib/delegation-instructions.mjs +1 -1
- package/dist/harness-runtime/lib/entity-references.d.mts +2 -0
- package/dist/harness-runtime/lib/entity-references.mjs +9 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +13 -6
- package/dist/harness-runtime/lib/native-hook-command.mjs +4 -4
- package/dist/harness-runtime/lib/opencode-plugin.mjs +3 -3
- package/dist/harness-runtime/lib/operation-errors.mjs +1 -1
- package/dist/harness-runtime/lib/provider-overload-policy.d.mts +4 -2
- package/dist/harness-runtime/lib/provider-overload-policy.mjs +19 -6
- package/dist/harness-runtime/lib/runner-events.mjs +3 -1
- package/dist/harness-runtime/lib/setup-progress.mjs +8 -6
- package/dist/schemas/mb-upgrade-stage-result.schema.json +863 -0
- package/dist/schemas/memory-repair-work-result.schema.json +9 -2
- package/dist/schemas/release-impact.schema.json +3 -3
- package/dist/schemas/stage-start-response.schema.json +4 -0
- package/dist/services/accepted-intent.js +13 -1
- package/dist/services/canon.js +43 -7
- package/dist/services/cli-operation-classifier.js +1 -1
- package/dist/services/cli-upgrade-allowlist.js +1 -1
- package/dist/services/code-checks.js +11 -4
- package/dist/services/compatibility-preflight.js +5 -2
- package/dist/services/controller-fanout.js +21 -4
- package/dist/services/controller-memory-task.js +45 -0
- package/dist/services/controller-memory-worker.js +48 -0
- package/dist/services/dashboard.js +2 -2
- package/dist/services/descriptor-run-start.js +26 -19
- package/dist/services/descriptor-stage-coordinator.js +30 -10
- package/dist/services/descriptor-stage-start.js +40 -4
- package/dist/services/engines.js +64 -14
- package/dist/services/eval-snapshots.js +104 -93
- package/dist/services/external-work-launch.js +5 -3
- package/dist/services/flow-catalog.js +8 -4
- package/dist/services/frozen-engine.js +113 -11
- package/dist/services/hook-memory-routing.js +188 -0
- package/dist/services/hook-recovery-guard.js +14 -0
- package/dist/services/hook-session-observation.js +24 -0
- package/dist/services/hooks.js +27 -8
- package/dist/services/hybrid-contracts.js +2 -13
- package/dist/services/hybrid-digest.js +14 -0
- package/dist/services/installed-flow-source.js +25 -4
- package/dist/services/lifecycle-invocations.js +33 -5
- package/dist/services/memory-accepted-composition.js +5 -4
- package/dist/services/memory-completed-correction-wave.js +23 -15
- package/dist/services/memory-compose-handler.js +163 -21
- package/dist/services/memory-composition-acceptance.js +186 -10
- package/dist/services/memory-composition-graph.js +42 -11
- package/dist/services/memory-composition-registration.js +23 -8
- package/dist/services/memory-composition-work-custody.js +14 -9
- package/dist/services/memory-composition-work-results.js +15 -3
- package/dist/services/memory-corrected-review-batch.js +10 -3
- package/dist/services/memory-corrected-review-findings.js +4 -2
- package/dist/services/memory-corrected-review-inputs.js +10 -5
- package/dist/services/memory-corrected-review-receipt.js +17 -9
- package/dist/services/memory-corrected-review-registration.js +3 -2
- package/dist/services/memory-correction-check-receipt.js +20 -4
- package/dist/services/memory-custody-snapshot.js +36 -0
- package/dist/services/memory-finish-evidence.js +44 -22
- package/dist/services/memory-finish-handler.js +37 -23
- package/dist/services/memory-finish-integration.js +218 -48
- package/dist/services/memory-finish-readiness.js +66 -19
- package/dist/services/memory-git-integration.js +87 -18
- package/dist/services/memory-historical-corrected-review.js +21 -15
- package/dist/services/memory-historical-original-review.js +22 -9
- package/dist/services/memory-historical-repair-registration.js +14 -8
- package/dist/services/memory-historical-second-review.js +30 -11
- package/dist/services/memory-init-contexts.js +28 -2
- package/dist/services/memory-init-controller-start.js +19 -8
- package/dist/services/memory-init-git-policy.js +50 -21
- package/dist/services/memory-init-lint-capability.js +21 -3
- package/dist/services/memory-init-native-capacity.js +13 -0
- package/dist/services/memory-init-preflight.js +11 -1
- package/dist/services/memory-init-request-candidate.js +73 -17
- package/dist/services/memory-init-request-input.js +23 -7
- package/dist/services/memory-init-run-bundle.js +49 -12
- package/dist/services/memory-init-storage.js +17 -7
- package/dist/services/memory-layer.js +68 -4
- package/dist/services/memory-lint-config.js +111 -2
- package/dist/services/memory-native-review-copy.js +5 -67
- package/dist/services/memory-normal-flow-entry.js +12 -0
- package/dist/services/memory-ordinary-defaults.js +22 -5
- package/dist/services/memory-preflight-handler.js +15 -0
- package/dist/services/memory-repair-current-view.js +1 -1
- package/dist/services/memory-repair-document-delta.js +70 -33
- package/dist/services/memory-repair-history.js +12 -7
- package/dist/services/memory-repair-physical-binding.js +1 -119
- package/dist/services/memory-repair-publication-plan.js +5 -3
- package/dist/services/memory-repair-publication-source.js +1 -1
- package/dist/services/memory-repair-publication.js +3 -2
- package/dist/services/memory-repair-registration.js +2 -1
- package/dist/services/memory-repair-work-baseline.js +4 -2
- package/dist/services/memory-repair-work-completion.js +32 -12
- package/dist/services/memory-repair-work-results.js +21 -2
- package/dist/services/memory-repair-workspace.js +11 -2
- package/dist/services/memory-repair-writer-copy.js +2 -1
- package/dist/services/memory-result-binding.js +78 -12
- package/dist/services/memory-retained-review-authority.js +17 -8
- package/dist/services/memory-retained-second-baseline.js +1 -1
- package/dist/services/memory-retained-second-repairs.js +12 -6
- package/dist/services/memory-review-correction-plan.js +47 -12
- package/dist/services/memory-review-handler.js +17 -17
- package/dist/services/memory-review-inputs.js +55 -9
- package/dist/services/memory-review-limits.js +10 -0
- package/dist/services/memory-review-receipt.js +2 -2
- package/dist/services/memory-review-registration.js +13 -4
- package/dist/services/memory-review-work-custody.js +3 -1
- package/dist/services/memory-review-work-input.js +26 -7
- package/dist/services/memory-review-work-results.js +26 -4
- package/dist/services/memory-second-corrected-review-inputs.js +5 -3
- package/dist/services/memory-second-correction-plan.js +2 -1
- package/dist/services/memory-second-repair-launch.js +4 -4
- package/dist/services/memory-second-repair-source.js +1 -1
- package/dist/services/memory-stage-dispatch.js +38 -1
- package/dist/services/memory-stage-results.js +113 -22
- package/dist/services/memory-upgrade-analysis-handler.js +243 -0
- package/dist/services/memory-upgrade-bootstrap-readiness.js +251 -0
- package/dist/services/memory-upgrade-evidence.js +100 -0
- package/dist/services/memory-upgrade-inventory.js +442 -0
- package/dist/services/memory-upgrade-publication-custody.js +73 -0
- package/dist/services/memory-upgrade-publication.js +413 -0
- package/dist/services/memory-upgrade-selection.js +430 -0
- package/dist/services/memory-upgrade-start.js +60 -0
- package/dist/services/memory-upgrade-storage-readiness.js +340 -0
- package/dist/services/memory-verification-acceptance.js +38 -17
- package/dist/services/memory-verification-checks.js +77 -23
- package/dist/services/memory-verification-receipt.js +6 -5
- package/dist/services/memory-verify-handler.js +15 -13
- package/dist/services/memory-work-result-json.js +12 -0
- package/dist/services/memory-work-result-preparation.js +5 -4
- package/dist/services/merge-server.js +4 -2
- package/dist/services/migrations.js +127 -98
- package/dist/services/physical-startup.js +45 -0
- package/dist/services/project-registry.js +97 -0
- package/dist/services/project-summary.js +1 -1
- package/dist/services/projects.js +3 -93
- package/dist/services/prompt-delivery.js +71 -0
- package/dist/services/provider-capacity.js +69 -8
- package/dist/services/recovery-snapshot-database.js +32 -4
- package/dist/services/repair-intents.js +1 -12
- package/dist/services/repair-receipt.js +13 -0
- package/dist/services/run-control-worker.js +35 -18
- package/dist/services/run-controller-process.js +2 -6
- package/dist/services/run-controller-recovery.js +6 -1
- package/dist/services/run-controller.js +131 -80
- package/dist/services/run-recovery-guard.js +30 -0
- package/dist/services/run-recovery.js +11 -44
- package/dist/services/run-start-custody.js +17 -5
- package/dist/services/runtime-scope-worker.js +46 -19
- package/dist/services/sessions.js +3 -24
- package/dist/services/setup.js +63 -13
- package/dist/services/stage-handlers.js +9 -7
- package/dist/services/stage-pause.js +108 -44
- package/dist/services/version-status.js +1 -1
- package/dist/services/vnext-fanout.js +21 -9
- package/dist/services/vnext-plan-review.js +3 -5
- package/dist/services/work-registry.js +80 -33
- package/dist/services/workspace-files.js +1 -1
- package/dist/shared/entity-references.js +1 -8
- package/dist/storage/data-preservation.js +65 -0
- package/dist/storage/database.js +53 -19
- package/dist/storage/import-guard.js +84 -27
- package/dist/storage/memory-upgrade-schema-bootstrap.js +38 -0
- package/dist/storage/paired-writer-migration.js +424 -0
- package/dist/storage/writer-contract.js +11 -4
- package/dist/storage/writer-migration.js +313 -52
- package/dist/storage/writer-restore.js +217 -0
- package/package.json +8 -4
|
@@ -6,6 +6,9 @@ import { startMemoryVerification, finishMemoryVerification } from "./memory-veri
|
|
|
6
6
|
import { startMemoryReview, finishMemoryReview } from "./memory-review-handler.js";
|
|
7
7
|
import { startMemoryFinish, finishMemoryFinish } from "./memory-finish-handler.js";
|
|
8
8
|
import { memoryInitHandlerIdentities as ids } from "../domain/stage-handler-identities.js";
|
|
9
|
+
import { memoryUpgradeHandlerIdentities as upgradeIds } from "../domain/stage-handler-identities.js";
|
|
10
|
+
import { startMemoryUpgradeAnalysis, finishMemoryUpgradeAnalysis } from "./memory-upgrade-analysis-handler.js";
|
|
11
|
+
import { parseMemoryWorkResultProposal } from "./memory-work-result-json.js";
|
|
9
12
|
// Engine-owned functions only; this is dispatch, not descriptor admission.
|
|
10
13
|
export const memoryInitStageHandlers = Object.freeze({
|
|
11
14
|
preflight: Object.freeze({ ...ids.preflight, entry: startMemoryPreflight, finish: finishMemoryPreflight }),
|
|
@@ -15,6 +18,18 @@ export const memoryInitStageHandlers = Object.freeze({
|
|
|
15
18
|
review: Object.freeze({ ...ids.review, entry: startMemoryReview, finish: finishMemoryReview }),
|
|
16
19
|
finish: Object.freeze({ ...ids.finish, entry: startMemoryFinish, finish: finishMemoryFinish })
|
|
17
20
|
});
|
|
21
|
+
export const memoryUpgradeStageHandlers = Object.freeze({
|
|
22
|
+
preflight: Object.freeze({ ...upgradeIds.preflight,
|
|
23
|
+
entry: (context, input) => startMemoryUpgradeAnalysis(context, { ...input, stage: "preflight" }),
|
|
24
|
+
finish: (context, input) => finishMemoryUpgradeAnalysis(context, { ...input, stage: "preflight" }) }),
|
|
25
|
+
"diff-analysis": Object.freeze({ ...upgradeIds["diff-analysis"],
|
|
26
|
+
entry: (context, input) => startMemoryUpgradeAnalysis(context, { ...input, stage: "diff-analysis" }),
|
|
27
|
+
finish: (context, input) => finishMemoryUpgradeAnalysis(context, { ...input, stage: "diff-analysis" }) }),
|
|
28
|
+
upgrade: Object.freeze({ ...upgradeIds.upgrade, entry: startMemoryComposition, finish: finishMemoryComposition }),
|
|
29
|
+
verify: Object.freeze({ ...upgradeIds.verify, entry: startMemoryVerification, finish: finishMemoryVerification }),
|
|
30
|
+
review: Object.freeze({ ...upgradeIds.review, entry: startMemoryReview, finish: finishMemoryReview }),
|
|
31
|
+
finish: Object.freeze({ ...upgradeIds.finish, entry: startMemoryFinish, finish: finishMemoryFinish })
|
|
32
|
+
});
|
|
18
33
|
function stage(input) {
|
|
19
34
|
if (!Object.hasOwn(memoryInitStageHandlers, input.stage) || !(input.bytes instanceof Uint8Array) || input.bytes.length > 1024 * 1024)
|
|
20
35
|
throw new AppError("memory_stage_dispatch_invalid", "Memory stage dispatch requires a bounded declared stage input", 2, { effect: "no_effect", phase: "prepare" });
|
|
@@ -24,5 +39,27 @@ export function startMemoryStage(context, input) {
|
|
|
24
39
|
return memoryInitStageHandlers[stage(input)].entry(context, input);
|
|
25
40
|
}
|
|
26
41
|
export function finishMemoryStage(context, input) {
|
|
27
|
-
|
|
42
|
+
const selected = stage(input);
|
|
43
|
+
validateFinishProposal(input.bytes);
|
|
44
|
+
return memoryInitStageHandlers[selected].finish(context, input);
|
|
45
|
+
}
|
|
46
|
+
// Syntax belongs to the submitted proposal, not retained composition/receipt
|
|
47
|
+
// custody. Validate it before any stage-specific projection parses the bytes.
|
|
48
|
+
function validateFinishProposal(bytes) {
|
|
49
|
+
if (!bytes.length)
|
|
50
|
+
throw new AppError("memory_stage_dispatch_invalid", "Memory finish requires a nonempty result proposal", 2, { effect: "no_effect", phase: "prepare" });
|
|
51
|
+
parseMemoryWorkResultProposal(bytes, "memory_stage_result_invalid");
|
|
52
|
+
}
|
|
53
|
+
function upgradeStage(input) {
|
|
54
|
+
if (!Object.hasOwn(memoryUpgradeStageHandlers, input.stage) || !(input.bytes instanceof Uint8Array) || input.bytes.length > 1024 * 1024)
|
|
55
|
+
throw new AppError("memory_stage_dispatch_invalid", "Memory stage dispatch requires a bounded declared stage input", 2, { effect: "no_effect", phase: "prepare" });
|
|
56
|
+
return input.stage;
|
|
57
|
+
}
|
|
58
|
+
export function startMemoryUpgradeStage(context, input) {
|
|
59
|
+
return memoryUpgradeStageHandlers[upgradeStage(input)].entry(context, input);
|
|
60
|
+
}
|
|
61
|
+
export function finishMemoryUpgradeStage(context, input) {
|
|
62
|
+
const selected = upgradeStage(input);
|
|
63
|
+
validateFinishProposal(input.bytes);
|
|
64
|
+
return memoryUpgradeStageHandlers[selected].finish(context, input);
|
|
28
65
|
}
|
|
@@ -3,19 +3,38 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
4
4
|
import { AppError } from "../shared/errors.js";
|
|
5
5
|
import { hybridDigest } from "./hybrid-contracts.js";
|
|
6
|
-
import { validateMemoryCompositionGraph } from "./memory-composition-graph.js";
|
|
6
|
+
import { validateMemoryCompositionGraph, memoryCompositionOutputAllowed } from "./memory-composition-graph.js";
|
|
7
7
|
import { FrozenGenericExecution } from "./generic-execution.js";
|
|
8
|
-
const
|
|
8
|
+
const memoryStages = {
|
|
9
|
+
"mb-init": ["preflight", "discover", "compose", "verify", "review", "finish"],
|
|
10
|
+
"mb-upgrade": ["preflight", "diff-analysis", "upgrade", "verify", "review", "finish"]
|
|
11
|
+
};
|
|
9
12
|
const sha = (value) => /^[a-f0-9]{64}$/.test(value);
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
// Proposal relationships/scopes can be corrected before any acceptance or Work
|
|
14
|
+
// allocation. Missing/foreign runtime bindings and identities remain fatal.
|
|
15
|
+
const correctableProposalReasons = new Set([
|
|
16
|
+
"unsafe_reference_path",
|
|
17
|
+
"foreign_predecessor_reference", "duplicate_unit", "changed_unit_coverage", "duplicate_finding", "foreign_finding_unit",
|
|
18
|
+
"duplicate_fact", "foreign_fact_unit", "foreign_unit_reference", "unobserved_or_changed_source",
|
|
19
|
+
"duplicate_preservation_invariant", "weakened_upgrade_mode", "changed_preservation_invariants", "missing_applicable_unit_preservation",
|
|
20
|
+
"duplicate_preservation_assessment", "incomplete_preservation_assessment", "foreign_preservation_unit", "unreviewable_preservation_unit", "unapproved_composition_profile",
|
|
21
|
+
"unresolved_composition_applicability", "duplicate_composition_work", "unknown_composition_dependency", "duplicate_composition_scope",
|
|
22
|
+
"unbounded_composition_write", "foreign_composition_write", "canonical_composition_write", "private_composition_write",
|
|
23
|
+
"authority_composition_write", "foreign_composition_run_write", "cyclic_composition_dependency", "invalid_assembly_membership",
|
|
24
|
+
"mechanical_unit_cannot_allocate_agent_work", "inapplicable_composition_work", "incomplete_composition_coverage",
|
|
25
|
+
"assembly_precedes_unit_writers", "unordered_composition_writers", "unordered_composition_read_write"
|
|
26
|
+
]);
|
|
27
|
+
function refuse(reason, details = {}) {
|
|
28
|
+
throw new AppError("memory_stage_result_invalid", reason === "missing_applicable_unit_preservation"
|
|
29
|
+
? "Every applicable upgrade unit requires a source-backed preservation invariant. In preflight add the invariant; in diff-analysis keep the accepted invariants unchanged and assign the planned work to their existing units"
|
|
30
|
+
: "Memory stage result is not bound to its retained input", 2, { ...details, reason, phase: "prepare", effect: "no_effect" });
|
|
12
31
|
}
|
|
13
|
-
function contained(logical, allowRoot = false) {
|
|
32
|
+
function contained(logical, allowRoot = false, proposal = false) {
|
|
14
33
|
if (allowRoot && logical === ".")
|
|
15
34
|
return;
|
|
16
35
|
if (!logical || path.posix.isAbsolute(logical) || path.win32.isAbsolute(logical) || logical.includes("\\")
|
|
17
36
|
|| logical.includes("\0") || logical.split("/").some(segment => !segment || segment === "." || segment === ".."))
|
|
18
|
-
refuse("unsafe_reference_path");
|
|
37
|
+
refuse("unsafe_reference_path", { recoverable: proposal });
|
|
19
38
|
}
|
|
20
39
|
export function memorySourceKey(root, logical) { return `${root}\0${logical}`; }
|
|
21
40
|
function unique(values, reason) {
|
|
@@ -34,23 +53,36 @@ export class PreparedMemoryStageResult {
|
|
|
34
53
|
#routes;
|
|
35
54
|
#executionSha256;
|
|
36
55
|
#preflight;
|
|
37
|
-
|
|
56
|
+
#upgradeWritePaths;
|
|
57
|
+
#mechanicalUnits;
|
|
58
|
+
#preservationInvariants;
|
|
59
|
+
#upgradeEvidenceIndex;
|
|
60
|
+
constructor(bytes, value, routes, executionSha256, preflight, upgradeWritePaths, mechanicalUnits, preservationInvariants, upgradeEvidenceIndex) {
|
|
38
61
|
this.#bytes = Buffer.from(bytes);
|
|
39
62
|
this.#value = structuredClone(value);
|
|
40
63
|
this.#routes = structuredClone(routes);
|
|
41
64
|
this.#executionSha256 = executionSha256;
|
|
42
65
|
this.#preflight = structuredClone(preflight);
|
|
66
|
+
this.#upgradeWritePaths = upgradeWritePaths ? new Set(upgradeWritePaths) : null;
|
|
67
|
+
this.#mechanicalUnits = new Set(mechanicalUnits);
|
|
68
|
+
this.#preservationInvariants = structuredClone(preservationInvariants);
|
|
69
|
+
this.#upgradeEvidenceIndex = structuredClone(upgradeEvidenceIndex);
|
|
43
70
|
this.sha256 = createHash("sha256").update(bytes).digest("hex");
|
|
44
71
|
Object.freeze(this);
|
|
45
72
|
}
|
|
46
73
|
static prepare(input, flow, binding) {
|
|
47
74
|
try {
|
|
48
|
-
|
|
75
|
+
const stages = memoryStages[flow.descriptor.id], upgrade = flow.descriptor.id === "mb-upgrade";
|
|
76
|
+
if (input.byteLength < 1 || input.byteLength > 1024 * 1024 || !stages
|
|
49
77
|
|| !stages.includes(binding.stage) || !binding.runId || !sha(binding.inputSha256))
|
|
50
78
|
refuse("invalid_input_binding");
|
|
79
|
+
if (upgrade && (!binding.upgrade || !["patch", "targeted_minor", "full_migration"].includes(binding.upgrade.minimumMode)))
|
|
80
|
+
refuse("missing_upgrade_impact_binding");
|
|
81
|
+
if (binding.upgrade?.evidenceIndex && (binding.upgrade.evidenceIndex.path !== "upgrade-baseline.json" || !sha(binding.upgrade.evidenceIndex.sha256)))
|
|
82
|
+
refuse("invalid_upgrade_evidence_binding");
|
|
51
83
|
const bytes = Buffer.from(input), value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
52
84
|
const schema = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(flow.stageBlob(binding.stage, "result_schema")));
|
|
53
|
-
if (schema.$id !== `dd-flow/mb-init-${binding.stage}-result@1`)
|
|
85
|
+
if (schema.$id !== (upgrade ? "dd-flow/mb-upgrade-stage-result@1" : `dd-flow/mb-init-${binding.stage}-result@1`))
|
|
54
86
|
refuse("invalid_result_schema");
|
|
55
87
|
const validate = new Ajv2020({ strict: true, strictRequired: false, validateFormats: false }).compile(schema);
|
|
56
88
|
if (!validate(value))
|
|
@@ -71,7 +103,7 @@ export class PreparedMemoryStageResult {
|
|
|
71
103
|
refuse("foreign_predecessor_reference");
|
|
72
104
|
const units = unique(value.units.map(unit => unit.id), "duplicate_unit");
|
|
73
105
|
if (binding.unitIds === null) {
|
|
74
|
-
if (!["preflight", "discover"].includes(binding.stage))
|
|
106
|
+
if (!["preflight", upgrade ? "diff-analysis" : "discover"].includes(binding.stage))
|
|
75
107
|
refuse("missing_unit_inventory");
|
|
76
108
|
}
|
|
77
109
|
else {
|
|
@@ -97,11 +129,11 @@ export class PreparedMemoryStageResult {
|
|
|
97
129
|
return;
|
|
98
130
|
const record = node;
|
|
99
131
|
if (typeof record.unit_id === "string" && !units.has(record.unit_id))
|
|
100
|
-
refuse("foreign_unit_reference");
|
|
132
|
+
refuse("foreign_unit_reference", { unit_id: record.unit_id.slice(0, 256) });
|
|
101
133
|
if (Array.isArray(record.unit_ids) && record.unit_ids.some(id => typeof id !== "string" || !units.has(id)))
|
|
102
|
-
refuse("foreign_unit_reference");
|
|
134
|
+
refuse("foreign_unit_reference", { unit_ids: record.unit_ids.filter(id => typeof id !== "string" || !units.has(id)).slice(0, 8).map(id => String(id).slice(0, 256)) });
|
|
103
135
|
if (typeof record.path === "string" && (typeof record.root === "string" || typeof record.sha256 === "string"))
|
|
104
|
-
contained(record.path, typeof record.root === "string" && record.sha256 === undefined);
|
|
136
|
+
contained(record.path, typeof record.root === "string" && record.sha256 === undefined, true);
|
|
105
137
|
if (typeof record.root === "string" && typeof record.path === "string" && typeof record.sha256 === "string") {
|
|
106
138
|
const key = memorySourceKey(record.root, record.path);
|
|
107
139
|
if (binding.sources.get(key) !== record.sha256 && !(role === "evidence" && binding.historicalEvidence?.has(`${key}\0${record.sha256}`)))
|
|
@@ -110,15 +142,60 @@ export class PreparedMemoryStageResult {
|
|
|
110
142
|
Object.entries(record).forEach(([key, child]) => visit(child, key === "evidence" || key === "artifacts" ? key : role));
|
|
111
143
|
};
|
|
112
144
|
visit(value);
|
|
145
|
+
if (upgrade) {
|
|
146
|
+
const invariants = value.details.preservation_invariants;
|
|
147
|
+
if (invariants)
|
|
148
|
+
unique(invariants.map(item => String(item.id)), "duplicate_preservation_invariant");
|
|
149
|
+
const expected = binding.upgrade.preservationInvariants;
|
|
150
|
+
if (binding.stage !== "preflight" && !expected)
|
|
151
|
+
refuse("missing_upgrade_preservation_binding");
|
|
152
|
+
if (binding.stage === "diff-analysis") {
|
|
153
|
+
const modes = ["patch", "targeted_minor", "full_migration"];
|
|
154
|
+
if (modes.indexOf(String(value.details.mode)) < modes.indexOf(binding.upgrade.minimumMode))
|
|
155
|
+
refuse("weakened_upgrade_mode");
|
|
156
|
+
const ordered = (items) => [...items].sort((a, b) => String(a.id).localeCompare(String(b.id), "en"));
|
|
157
|
+
if (hybridDigest(ordered(invariants)) !== hybridDigest(ordered(expected)))
|
|
158
|
+
refuse("changed_preservation_invariants");
|
|
159
|
+
for (const change of value.details.path_map) {
|
|
160
|
+
if (change.from !== null)
|
|
161
|
+
contained(change.from, false, true);
|
|
162
|
+
if (change.to !== null)
|
|
163
|
+
contained(change.to, false, true);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const unreviewable = (invariants ?? expected ?? []).filter(item => !value.units.some(unit => unit.id === item.unit_id && unit.applicability === "applicable"));
|
|
167
|
+
if (unreviewable.length)
|
|
168
|
+
refuse("unreviewable_preservation_unit", {
|
|
169
|
+
unit_ids: [...new Set(unreviewable.map(item => item.unit_id))].slice(0, 8),
|
|
170
|
+
invariant_ids: unreviewable.map(item => item.id).slice(0, 8),
|
|
171
|
+
hint: binding.stage === "preflight"
|
|
172
|
+
? "Each preservation criterion needs an applicable assessment unit. Bind a no-migration criterion to an applicable provenance/readiness unit; this does not require migration Work or an apply receipt."
|
|
173
|
+
: "Keep each retained preservation criterion assigned to an applicable assessment unit. Correct proposed applicability, not accepted invariant identities or predecessor bytes."
|
|
174
|
+
});
|
|
175
|
+
if (binding.stage === "preflight" || binding.stage === "diff-analysis") {
|
|
176
|
+
const uncovered = value.units.filter(unit => unit.applicability === "applicable" && !invariants.some(item => item.unit_id === unit.id));
|
|
177
|
+
if (uncovered.length)
|
|
178
|
+
refuse("missing_applicable_unit_preservation", { unit_ids: uncovered.map(unit => unit.id).slice(0, 8) });
|
|
179
|
+
}
|
|
180
|
+
if (binding.stage === "verify") {
|
|
181
|
+
const assessments = value.details.preservation_assessments;
|
|
182
|
+
const selected = unique(assessments.map(item => item.invariant_id), "duplicate_preservation_assessment");
|
|
183
|
+
if (selected.size !== expected.length || expected.some(item => !selected.has(String(item.id))))
|
|
184
|
+
refuse("incomplete_preservation_assessment");
|
|
185
|
+
if (assessments.some(item => expected.find(original => original.id === item.invariant_id)?.unit_id !== item.unit_id))
|
|
186
|
+
refuse("foreign_preservation_unit");
|
|
187
|
+
}
|
|
188
|
+
}
|
|
113
189
|
let routes = null;
|
|
114
|
-
|
|
115
|
-
|
|
190
|
+
const authoringPaths = upgrade && binding.stage === "upgrade" ? binding.upgrade?.authoringPaths : undefined;
|
|
191
|
+
if (binding.stage === "compose" || authoringPaths) {
|
|
192
|
+
validateMemoryCompositionGraph(value, authoringPaths, authoringPaths ? binding.upgrade?.mechanicalUnits : undefined);
|
|
116
193
|
if (!(binding.execution instanceof FrozenGenericExecution) || binding.execution.settings.flow_id !== flow.descriptor.id
|
|
117
194
|
|| binding.execution.settings.descriptor_sha256 !== flow.sha256 || binding.execution.sha256 !== binding.executionSha256)
|
|
118
195
|
refuse("missing_or_foreign_composition_execution");
|
|
119
196
|
const works = value.details.works;
|
|
120
197
|
try {
|
|
121
|
-
routes = works.map(work => ({ key: work.key, execution: binding.execution.work(
|
|
198
|
+
routes = works.map(work => ({ key: work.key, execution: binding.execution.work(binding.stage, work.profile_id ?? undefined) }));
|
|
122
199
|
}
|
|
123
200
|
catch {
|
|
124
201
|
refuse("unapproved_composition_profile");
|
|
@@ -141,16 +218,29 @@ export class PreparedMemoryStageResult {
|
|
|
141
218
|
}
|
|
142
219
|
}
|
|
143
220
|
}
|
|
144
|
-
return new PreparedMemoryStageResult(bytes, value, routes, routes ? binding.executionSha256 : null, binding.preflight ?? null);
|
|
221
|
+
return new PreparedMemoryStageResult(bytes, value, routes, routes ? binding.executionSha256 : null, binding.preflight ?? null, authoringPaths ? new Set([...authoringPaths.values()].flatMap(paths => [...paths])) : null, authoringPaths ? binding.upgrade?.mechanicalUnits ?? new Set() : new Set(), upgrade ? binding.upgrade?.preservationInvariants ?? [] : [], upgrade ? binding.upgrade?.evidenceIndex ?? null : null);
|
|
145
222
|
}
|
|
146
223
|
catch (error) {
|
|
147
|
-
if (error instanceof AppError && error.code === "memory_stage_result_invalid")
|
|
224
|
+
if (error instanceof AppError && error.code === "memory_stage_result_invalid") {
|
|
225
|
+
if (correctableProposalReasons.has(String(error.details.reason)) && error.details.recoverable !== false)
|
|
226
|
+
throw new AppError(error.code, `Correct the proposed result (${error.details.reason}) without changing retained RUN, predecessor or execution bindings`, error.exitCode, { ...error.details, recoverable: true, result_sha256: createHash("sha256").update(input).digest("hex") });
|
|
148
227
|
throw error;
|
|
228
|
+
}
|
|
149
229
|
return refuse("invalid_result_or_binding");
|
|
150
230
|
}
|
|
151
231
|
}
|
|
152
232
|
bytes() { return Buffer.from(this.#bytes); }
|
|
233
|
+
mechanicalUnitIds() { return new Set(this.#mechanicalUnits); }
|
|
234
|
+
preservationInvariants() { return structuredClone(this.#preservationInvariants); }
|
|
153
235
|
value() { return structuredClone(this.#value); }
|
|
236
|
+
authoringStage() {
|
|
237
|
+
if (!this.#routes || !["compose", "upgrade"].includes(this.#value.stage))
|
|
238
|
+
refuse("not_a_composition_result");
|
|
239
|
+
return this.#value.stage;
|
|
240
|
+
}
|
|
241
|
+
allowsCompositionOutput(root, logical) {
|
|
242
|
+
return memoryCompositionOutputAllowed(logical, root === "workspace" && this.#upgradeWritePaths ? this.#upgradeWritePaths : undefined);
|
|
243
|
+
}
|
|
154
244
|
compositionRoutes() {
|
|
155
245
|
if (!this.#routes)
|
|
156
246
|
refuse("not_a_composition_result");
|
|
@@ -161,8 +251,7 @@ export class PreparedMemoryStageResult {
|
|
|
161
251
|
* this result's predecessor/source custody before passing the batch to its writer.
|
|
162
252
|
*/
|
|
163
253
|
compositionBatch(binding) {
|
|
164
|
-
|
|
165
|
-
refuse("not_a_composition_result");
|
|
254
|
+
const stage = this.authoringStage();
|
|
166
255
|
// Registration assigns unfinished authoring; only final acceptance attests
|
|
167
256
|
// settled outputs. A repairable blueprint is not a blocked dispatch.
|
|
168
257
|
if (this.#value.outcome === "blocked")
|
|
@@ -184,12 +273,14 @@ export class PreparedMemoryStageResult {
|
|
|
184
273
|
if (typeof work.task !== "string" || !work.task.trim() || work.task.length > 16384)
|
|
185
274
|
refuse("missing_composition_work_task");
|
|
186
275
|
const packet = { schema_id: "dd-flow/memory-composition-work-packet@1", run_id: binding.runId,
|
|
187
|
-
stage
|
|
276
|
+
stage, key: work.key, role: work.role, unit_ids: work.unit_ids,
|
|
188
277
|
task: work.task, depends_on: work.depends_on, result_schema: binding.resultSchema,
|
|
189
278
|
read_scopes: work.read_scopes, write_scopes: work.write_scopes,
|
|
190
279
|
compose_result_sha256: this.sha256, compose_input_sha256: binding.inputSha256,
|
|
191
280
|
execution_sha256: binding.executionSha256, predecessors: this.#value.predecessors };
|
|
192
|
-
const scopedPacket = { ...packet, ...(this.#preflight ? { preflight: structuredClone(this.#preflight) } : {})
|
|
281
|
+
const scopedPacket = { ...packet, ...(this.#preflight ? { preflight: structuredClone(this.#preflight) } : {}),
|
|
282
|
+
...(this.#upgradeEvidenceIndex ? { upgrade_baseline: { root: "run", ...this.#upgradeEvidenceIndex },
|
|
283
|
+
preservation_invariants: this.#preservationInvariants.filter(item => work.unit_ids.includes(String(item.unit_id))) } : {}) };
|
|
193
284
|
return { key: work.key, task: work.task, depends_on: [...work.depends_on],
|
|
194
285
|
launch_policy: "fresh_agent_required", result_schema: binding.resultSchema,
|
|
195
286
|
payload: structuredClone({ memory_composition: { ...scopedPacket, packet_sha256: hybridDigest(scopedPacket) },
|
|
@@ -0,0 +1,243 @@
|
|
|
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 { requireProjectByRoot } from "./projects.js";
|
|
6
|
+
import { resolveProjectRoot } from "../storage/paths.js";
|
|
7
|
+
import { assertPreparedRunBundleSqlAuthority, readFlowRunExecutionBundle } from "./runs.js";
|
|
8
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
9
|
+
import { PreparedMemoryUpgradeBootstrapReadiness, readMemoryUpgradeBootstrapReadiness } from "./memory-upgrade-bootstrap-readiness.js";
|
|
10
|
+
import { readUpgradeStorageReadiness, resolveUpgradeHistoricalScope } from "./memory-upgrade-storage-readiness.js";
|
|
11
|
+
import { PreparedDescriptorStageStart } from "./descriptor-stage-start.js";
|
|
12
|
+
import { PreparedDescriptorStageCoordinator, readDescriptorStageInput } from "./descriptor-stage-coordinator.js";
|
|
13
|
+
import { assertMemoryOrdinaryUpgradeConfigurationCoverage } from "./memory-ordinary-defaults.js";
|
|
14
|
+
import { PreparedDescriptorStageSettlement } from "./descriptor-stage-settlement.js";
|
|
15
|
+
import { PreparedMemoryResultBinding } from "./memory-result-binding.js";
|
|
16
|
+
import { PreparedMemoryStageResult } from "./memory-stage-results.js";
|
|
17
|
+
import { ObservedMemorySources } from "./memory-source-observations.js";
|
|
18
|
+
import { readAcceptedStageData } from "./stage-report-renderer.js";
|
|
19
|
+
import { localEnvironmentAllowed, workspaceGitFiles } from "./workspace-files.js";
|
|
20
|
+
import { validateUpgradePathMap } from "./memory-upgrade-inventory.js";
|
|
21
|
+
import { readMemoryUpgradeEvidence, readIssuedMemoryUpgradeTarget } from "./memory-upgrade-evidence.js";
|
|
22
|
+
function refuse(reason) {
|
|
23
|
+
throw new AppError("memory_upgrade_analysis_handler_invalid", "Upgrade analysis requires its accepted bootstrap and original project", 2, { reason, effect: "no_effect" });
|
|
24
|
+
}
|
|
25
|
+
function rejectResult(bytes, reason, reference) {
|
|
26
|
+
const message = reason === "missing_impact_checks" ? "Include every required check from the issued adjacent release-impact chain in the proposed analysis result"
|
|
27
|
+
: reason === "symlink_source_reference" ? "Cite the issued run upgrade-baseline.json project_link_metadata for link identity, and a selected regular target file for contents; direct symlink sources are not admitted"
|
|
28
|
+
: reason === "analysis_not_accepted" ? "Accepted analysis cannot carry unresolved major/blocking findings. Represent planned upgrade work in units, path_map and required_checks; for a real blocker use the issued pause command. Do not mark unresolved work resolved merely to pass this gate"
|
|
29
|
+
: "Correct the result evidence using issued stage inputs and old/target references; controller response views and logs are not admitted evidence";
|
|
30
|
+
throw new AppError("memory_stage_result_invalid", message, 2, { reason, phase: "prepare", effect: "no_effect", recoverable: true,
|
|
31
|
+
result_sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
32
|
+
...(reference ? { reference: { root: String(reference.root).slice(0, 32), path: String(reference.path).slice(0, 1024) } } : {}) });
|
|
33
|
+
}
|
|
34
|
+
function selected(context, input) {
|
|
35
|
+
if (!["preflight", "diff-analysis"].includes(input.stage) || !(input.bytes instanceof Uint8Array) || input.bytes.length > 1024 * 1024)
|
|
36
|
+
refuse("invalid_stage_input");
|
|
37
|
+
const bundle = readFlowRunExecutionBundle(context, input);
|
|
38
|
+
if (!bundle || bundle.descriptor.descriptor.id !== "mb-upgrade"
|
|
39
|
+
|| bundle.descriptor.descriptor.entry_stage !== "preflight" || bundle.descriptor.successor("preflight", "accepted") !== "diff-analysis")
|
|
40
|
+
refuse("registered_upgrade_bundle_required");
|
|
41
|
+
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
42
|
+
const scope = { projectId: project.id, runId: input.runId, stage: input.stage };
|
|
43
|
+
const run = assertPreparedRunBundleSqlAuthority(context, scope, bundle), index = JSON.parse(run.index_json);
|
|
44
|
+
const frozen = bundle.execution.snapshot().scope;
|
|
45
|
+
const bootstrapScope = { requestId: frozen.requestId, projectRoot: frozen.projectRoot, workspaceRoot: frozen.workspace };
|
|
46
|
+
const ready = readMemoryUpgradeBootstrapReadiness(context, bootstrapScope);
|
|
47
|
+
if (!ready?.hasPreservationBaseline || ready.projectId !== project.id || ready.authority.binding.execution_sha256 !== bundle.execution.sha256)
|
|
48
|
+
refuse("accepted_upgrade_bootstrap_required");
|
|
49
|
+
const predecessor = input.stage === "diff-analysis" ? readAcceptedStageData(context, { ...scope, stage: "preflight" }) : null;
|
|
50
|
+
const authority = hybridDigest(ready.authority);
|
|
51
|
+
const fence = (writer) => {
|
|
52
|
+
const current = readMemoryUpgradeBootstrapReadiness(writer, bootstrapScope);
|
|
53
|
+
if (!current || hybridDigest(current.authority) !== authority
|
|
54
|
+
|| predecessor && readAcceptedStageData(writer, { ...scope, stage: "preflight" }).authority_sha256 !== predecessor.authority_sha256)
|
|
55
|
+
refuse("analysis_dependencies_changed");
|
|
56
|
+
};
|
|
57
|
+
return { bundle, scope, bootstrapScope, predecessor, fence, accepted: index.stage_runs.find((stage) => stage.stage === input.stage)?.status === "done" };
|
|
58
|
+
}
|
|
59
|
+
/** Agent references select current observations from the CLI inventory, not
|
|
60
|
+
* arbitrary local files. Canonical/receipt inputs come from the retained RUN
|
|
61
|
+
* bundle; old document evidence is independently supplied by the SQL binding.
|
|
62
|
+
*/
|
|
63
|
+
function sources(context, input, state) {
|
|
64
|
+
let value;
|
|
65
|
+
try {
|
|
66
|
+
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(input.bytes));
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return rejectResult(input.bytes, "invalid_result_json");
|
|
70
|
+
}
|
|
71
|
+
const storage = readUpgradeStorageReadiness(context, state.bootstrapScope);
|
|
72
|
+
if (!storage?.selection)
|
|
73
|
+
refuse("retained_selection_required");
|
|
74
|
+
const run = assertPreparedRunBundleSqlAuthority(context, state.scope, state.bundle);
|
|
75
|
+
const workspace = run.workspace_root ?? run.project_root;
|
|
76
|
+
const projectIsWorkspace = workspace === run.project_root;
|
|
77
|
+
const allowed = new Set([...(workspaceGitFiles(workspace, ["--cached", "--others", "--exclude-standard"]) ?? []),
|
|
78
|
+
...storage.selection.payload.current.inventory.inventory.entries.filter(entry => entry.current?.kind === "file" || entry.target).map(entry => `.memory-bank/${entry.path}`)]);
|
|
79
|
+
const allowedRun = new Set(state.bundle.manifest.inventory.map(entry => `execution-bundle/${entry.path}`));
|
|
80
|
+
const oldEvidence = readMemoryUpgradeEvidence(context, state.bootstrapScope);
|
|
81
|
+
const oldRunHashes = new Map([[oldEvidence.reference.path, oldEvidence.reference.sha256],
|
|
82
|
+
...oldEvidence.index.entries.map(entry => [entry.retained.path, entry.retained.sha256])]);
|
|
83
|
+
const target = readIssuedMemoryUpgradeTarget(context, state.bootstrapScope, state.scope.projectId);
|
|
84
|
+
if (target)
|
|
85
|
+
for (const ref of [target.reference, ...target.index.entries.map(entry => entry.retained)]) {
|
|
86
|
+
allowedRun.add(ref.path);
|
|
87
|
+
oldRunHashes.set(ref.path, ref.sha256);
|
|
88
|
+
}
|
|
89
|
+
const inputStage = JSON.parse(run.index_json).stage_runs.find((stage) => stage.stage === state.scope.stage);
|
|
90
|
+
const inputPath = `${state.bundle.descriptor.stage(state.scope.stage).directory}/stage-input.json`;
|
|
91
|
+
if (typeof inputStage?.input_sha256 === "string")
|
|
92
|
+
allowedRun.add(inputPath);
|
|
93
|
+
allowedRun.add(oldEvidence.reference.path);
|
|
94
|
+
for (const entry of oldEvidence.index.entries)
|
|
95
|
+
allowedRun.add(entry.retained.path);
|
|
96
|
+
if (state.predecessor)
|
|
97
|
+
allowedRun.add(state.predecessor.result.path);
|
|
98
|
+
const position = state.bundle.descriptor.descriptor.stages.findIndex(stage => stage.id === state.scope.stage);
|
|
99
|
+
for (const stage of state.bundle.descriptor.descriptor.stages.slice(0, position)) {
|
|
100
|
+
const accepted = readAcceptedStageData(context, { ...state.scope, stage: stage.id });
|
|
101
|
+
allowedRun.add(accepted.result.path);
|
|
102
|
+
// Accepted invariants can cite the packet that originally established
|
|
103
|
+
// their scope. Retain that exact issued input, not arbitrary RUN files.
|
|
104
|
+
if (typeof accepted.input_sha256 === "string") {
|
|
105
|
+
const logical = `${stage.directory}/stage-input.json`;
|
|
106
|
+
allowedRun.add(logical);
|
|
107
|
+
oldRunHashes.set(logical, accepted.input_sha256);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const historical = new Set(oldEvidence.index.entries.map(entry => `${entry.source.path}\0${entry.source.sha256}`));
|
|
111
|
+
const locations = new Map(), pending = [{ value, role: "source" }];
|
|
112
|
+
let nodes = 0;
|
|
113
|
+
while (pending.length) {
|
|
114
|
+
if (++nodes > 100_000)
|
|
115
|
+
rejectResult(input.bytes, "unbounded_result_tree");
|
|
116
|
+
const item = pending.pop(), node = item.value;
|
|
117
|
+
if (Array.isArray(node)) {
|
|
118
|
+
for (const child of node)
|
|
119
|
+
pending.push({ value: child, role: item.role });
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (!node || typeof node !== "object")
|
|
123
|
+
continue;
|
|
124
|
+
const ref = node;
|
|
125
|
+
if (typeof ref.root === "string" && typeof ref.path === "string" && typeof ref.sha256 === "string") {
|
|
126
|
+
if (ref.root === "workspace" || ref.root === "project" && projectIsWorkspace) {
|
|
127
|
+
if (!allowed.has(ref.path) || ref.path.split("/").some(part => [".git", "node_modules", ".dd-flow"].includes(part) || !localEnvironmentAllowed(part)))
|
|
128
|
+
rejectResult(input.bytes, "unselected_project_source", ref);
|
|
129
|
+
if (!(item.role === "evidence" && historical.has(`${ref.path}\0${ref.sha256}`))) {
|
|
130
|
+
// Historical retained bytes need not exist in today's tree.
|
|
131
|
+
let parent = workspace;
|
|
132
|
+
for (const part of ref.path.split("/").slice(0, -1)) {
|
|
133
|
+
parent = path.join(parent, part);
|
|
134
|
+
if (!fs.lstatSync(parent).isDirectory())
|
|
135
|
+
rejectResult(input.bytes, "symlink_source_reference", ref);
|
|
136
|
+
}
|
|
137
|
+
if (fs.lstatSync(path.join(workspace, ref.path), { throwIfNoEntry: false })?.isSymbolicLink())
|
|
138
|
+
rejectResult(input.bytes, "symlink_source_reference", ref);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else if (ref.root === "run") {
|
|
142
|
+
if (!allowedRun.has(ref.path))
|
|
143
|
+
rejectResult(input.bytes, "unselected_run_source", ref);
|
|
144
|
+
if (ref.path === inputPath && ref.sha256 !== inputStage?.input_sha256)
|
|
145
|
+
rejectResult(input.bytes, "changed_stage_input", ref);
|
|
146
|
+
if (oldRunHashes.has(ref.path) && oldRunHashes.get(ref.path) !== ref.sha256)
|
|
147
|
+
rejectResult(input.bytes, "changed_old_evidence", ref);
|
|
148
|
+
}
|
|
149
|
+
else
|
|
150
|
+
rejectResult(input.bytes, "unselected_source_root", ref);
|
|
151
|
+
// The accepted old tree remains evidence after a canonical path is
|
|
152
|
+
// replaced or archived. It never qualifies a current output artifact.
|
|
153
|
+
if (!(item.role === "evidence" && ["workspace", "project"].includes(ref.root) && historical.has(`${ref.path}\0${ref.sha256}`)))
|
|
154
|
+
locations.set(`${ref.root}\0${ref.path}`, { root: ref.root, path: ref.path });
|
|
155
|
+
}
|
|
156
|
+
for (const [key, child] of Object.entries(ref))
|
|
157
|
+
pending.push({ value: child, role: key === "evidence" || key === "artifacts" ? key : item.role });
|
|
158
|
+
}
|
|
159
|
+
return ObservedMemorySources.capture({ workspace, ...(projectIsWorkspace ? { project: workspace } : {}), run: run.run_root ?? run.run_dir }, [...locations.values()]);
|
|
160
|
+
}
|
|
161
|
+
/** Shared upgrade observation path for registered authoring/review consumers.
|
|
162
|
+
* The source witness grants no Stage, Work or canonical-write authority. */
|
|
163
|
+
export function captureMemoryUpgradeSources(context, bundle, stage, bytes) {
|
|
164
|
+
if (bundle.descriptor.descriptor.id !== "mb-upgrade" || !(bytes instanceof Uint8Array) || bytes.length > 1024 * 1024)
|
|
165
|
+
refuse("invalid_stage_input");
|
|
166
|
+
bundle.descriptor.stage(stage);
|
|
167
|
+
const frozen = bundle.execution.snapshot().scope;
|
|
168
|
+
const bootstrapScope = { requestId: frozen.requestId, projectRoot: frozen.projectRoot, workspaceRoot: frozen.workspace };
|
|
169
|
+
const { projectId } = resolveUpgradeHistoricalScope(context, bootstrapScope);
|
|
170
|
+
const scope = { projectId, runId: bundle.manifest.run_id, stage };
|
|
171
|
+
const ready = readMemoryUpgradeBootstrapReadiness(context, bootstrapScope);
|
|
172
|
+
if (!ready?.hasPreservationBaseline || ready.projectId !== projectId || ready.authority.binding.execution_sha256 !== bundle.execution.sha256)
|
|
173
|
+
refuse("accepted_upgrade_bootstrap_required");
|
|
174
|
+
const edges = bundle.descriptor.descriptor.edges.filter(edge => edge.to === stage && edge.on === "accepted");
|
|
175
|
+
if (edges.length !== (stage === bundle.descriptor.descriptor.entry_stage ? 0 : 1))
|
|
176
|
+
refuse("invalid_stage_predecessor");
|
|
177
|
+
const predecessor = edges.length ? readAcceptedStageData(context, { ...scope, stage: edges[0].from }) : null;
|
|
178
|
+
return sources(context, { bytes }, { bundle, scope, bootstrapScope, predecessor });
|
|
179
|
+
}
|
|
180
|
+
/** Upgrade analysis uses the registered six-stage group. Generic lifecycle
|
|
181
|
+
* owns Work, prompts and dispatch; RUN admission still owns bootstrap readiness.
|
|
182
|
+
*/
|
|
183
|
+
export function startMemoryUpgradeAnalysis(context, input) {
|
|
184
|
+
const state = selected(context, input);
|
|
185
|
+
if (state.accepted)
|
|
186
|
+
refuse("use_accepted_finish_replay");
|
|
187
|
+
PreparedMemoryUpgradeBootstrapReadiness.assertForRetainedRun(context, state.bundle);
|
|
188
|
+
const start = PreparedDescriptorStageStart.prepare(context, input);
|
|
189
|
+
const stage = start.apply(context, state.fence);
|
|
190
|
+
const coordinator = PreparedDescriptorStageCoordinator.prepare(context, input);
|
|
191
|
+
PreparedMemoryUpgradeBootstrapReadiness.assertForRetainedRun(context, state.bundle);
|
|
192
|
+
return { ...stage, ...coordinator.apply(context, state.fence) };
|
|
193
|
+
}
|
|
194
|
+
export function finishMemoryUpgradeAnalysis(context, input) {
|
|
195
|
+
const state = selected(context, input);
|
|
196
|
+
if (state.accepted) {
|
|
197
|
+
// Historical acknowledgement must not reinterpret old artifacts after the
|
|
198
|
+
// upgrade has intentionally changed them. SQL settlement owns acceptance.
|
|
199
|
+
const binding = PreparedMemoryResultBinding.prepare(context, { ...state.scope, flow: state.bundle.descriptor,
|
|
200
|
+
execution: state.bundle.execution, bundle: state.bundle, sources: ObservedMemorySources.capture({}, []), acceptedReplay: true });
|
|
201
|
+
if (!binding.acceptedBytes()?.equals(input.bytes))
|
|
202
|
+
refuse("accepted_result_changed");
|
|
203
|
+
const settlement = PreparedDescriptorStageSettlement.prepare(context, input);
|
|
204
|
+
return settlement.apply(context, writer => { state.fence(writer); binding.assertCustody(writer); });
|
|
205
|
+
}
|
|
206
|
+
const observed = sources(context, input, state);
|
|
207
|
+
const binding = PreparedMemoryResultBinding.prepare(context, { ...state.scope, flow: state.bundle.descriptor,
|
|
208
|
+
execution: state.bundle.execution, bundle: state.bundle, sources: observed, acceptedReplay: state.accepted });
|
|
209
|
+
const accepted = binding.acceptedBytes();
|
|
210
|
+
if (accepted && !accepted.equals(input.bytes))
|
|
211
|
+
refuse("accepted_result_changed");
|
|
212
|
+
const result = PreparedMemoryStageResult.prepare(input.bytes, state.bundle.descriptor, binding.binding()), value = result.value();
|
|
213
|
+
if (value.outcome !== "accepted" || value.findings.some(finding => finding.severity !== "minor" && finding.status !== "resolved"))
|
|
214
|
+
rejectResult(input.bytes, "analysis_not_accepted");
|
|
215
|
+
const details = value.details;
|
|
216
|
+
if (input.stage === "diff-analysis") {
|
|
217
|
+
const defaults = readDescriptorStageInput(context, state.scope, state.bundle).input.ordinary_defaults;
|
|
218
|
+
if (defaults) {
|
|
219
|
+
assertMemoryOrdinaryUpgradeConfigurationCoverage(value, defaults);
|
|
220
|
+
if (defaults.missing.length && !context.db.get("SELECT 1 FROM audit_events WHERE project_id=? AND event_type='flow_run.stage_resumed' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.stage')='diff-analysis' LIMIT 1", [state.scope.projectId, state.scope.runId])) {
|
|
221
|
+
throw new AppError("memory_stage_result_invalid", "Resolve the ordinary configuration choice through the existing diff-analysis pause/answer path before accepting the upgrade plan", 2, { phase: "prepare", effect: "no_effect", recoverable: true, reason: "ordinary_configuration_choice_required", missing: defaults.missing });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const storage = readUpgradeStorageReadiness(context, state.bootstrapScope);
|
|
225
|
+
const checks = details.required_checks;
|
|
226
|
+
if (!storage?.selection || storage.selection.payload.impact.chain.steps.some(step => !Array.isArray(step.required_checks)
|
|
227
|
+
|| step.required_checks.some(check => typeof check !== "string" || !checks.includes(check))))
|
|
228
|
+
rejectResult(input.bytes, "missing_impact_checks");
|
|
229
|
+
try {
|
|
230
|
+
validateUpgradePathMap(storage.selection.payload.current.inventory.inventory, details.path_map);
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
if (!(error instanceof AppError) || error.code !== "upgrade_inventory_invalid")
|
|
234
|
+
throw error;
|
|
235
|
+
throw new AppError("memory_stage_result_invalid", "Correct the proposed upgrade path map before it becomes an immutable accepted plan", 2, { reason: "invalid_upgrade_path_map", cause: error.details.reason, pointer: "/details/path_map", phase: "prepare", effect: "no_effect",
|
|
236
|
+
recoverable: true, result_sha256: result.sha256 });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const settlement = PreparedDescriptorStageSettlement.prepare(context, { ...input, bytes: result.bytes() });
|
|
240
|
+
PreparedMemoryUpgradeBootstrapReadiness.assertForRetainedRun(context, state.bundle);
|
|
241
|
+
binding.revalidate(context);
|
|
242
|
+
return settlement.apply(context, writer => { state.fence(writer); binding.assertCustody(writer); });
|
|
243
|
+
}
|