@deksden-com/dd-flow-cli 0.9.0-beta.152 → 0.9.0-beta.156
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 +31 -0
- package/dist/build-info.json +8 -8
- package/dist/cli/command-inputs.js +33 -3
- package/dist/cli/command-metadata.js +73 -2
- package/dist/cli/delivery.js +108 -0
- package/dist/cli/diagnostic-invocation.js +23 -7
- package/dist/cli/diagnostic.js +14 -3
- package/dist/cli/help.js +117 -1
- package/dist/cli/hook-ingress.js +36 -49
- package/dist/cli/interactive-invocation.js +33 -0
- package/dist/cli/interactive.js +114 -0
- package/dist/cli/memory-fix-invocation.js +52 -0
- package/dist/cli/memory-fix.js +100 -0
- package/dist/cli/memory-flow-admission.js +7 -3
- package/dist/cli/memory-init.js +5 -4
- package/dist/cli/native-hook-ingress.js +5 -1
- package/dist/cli/native-hook-preparation.js +31 -4
- package/dist/cli/run-cli.js +274 -19
- package/dist/cli.js +17 -3
- package/dist/domain/delivery-flows.js +8 -0
- package/dist/domain/distill-flow.js +20 -0
- package/dist/domain/interactive-flow.js +1 -0
- package/dist/domain/memory-fix-flow.js +2 -0
- package/dist/domain/review-fix-flow.js +2 -0
- package/dist/domain/source-snapshot-flows.js +4 -0
- package/dist/harness-runtime/bin/dd-agy.mjs +18 -3
- package/dist/harness-runtime/bin/dd-grok.mjs +15 -5
- package/dist/harness-runtime/bin/dd-zcode.mjs +15 -7
- package/dist/harness-runtime/lib/agy-error-boundary.mjs +18 -10
- package/dist/harness-runtime/lib/agy-native-inspection.mjs +12 -3
- package/dist/harness-runtime/lib/agy-native-inventory.mjs +19 -0
- package/dist/harness-runtime/lib/agy-terminal-envelope.mjs +13 -6
- package/dist/harness-runtime/lib/agy-transcript-stream.mjs +54 -0
- package/dist/harness-runtime/lib/daemon-observation.d.mts +1 -0
- package/dist/harness-runtime/lib/daemon-observation.mjs +12 -4
- package/dist/harness-runtime/lib/daemon-operations.mjs +20 -5
- package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +371 -114
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +43 -3
- package/dist/harness-runtime/lib/dd-codex.mjs +36 -4
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +25 -11
- package/dist/harness-runtime/lib/dd-droid.mjs +124 -11
- package/dist/harness-runtime/lib/dd-grok-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +137 -25
- package/dist/harness-runtime/lib/dd-grok.mjs +4 -2
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +115 -17
- package/dist/harness-runtime/lib/dd-opencode.mjs +10 -7
- package/dist/harness-runtime/lib/dd-zcode-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +115 -22
- package/dist/harness-runtime/lib/dd-zcode.mjs +75 -36
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +2 -1
- package/dist/harness-runtime/lib/delegation-instructions.mjs +15 -5
- package/dist/harness-runtime/lib/delivery-command.mjs +84 -0
- package/dist/harness-runtime/lib/driver-recovery.mjs +0 -19
- package/dist/harness-runtime/lib/droid-observation.mjs +9 -5
- package/dist/harness-runtime/lib/lifecycle-contract.d.mts +1 -0
- package/dist/harness-runtime/lib/lifecycle-contract.mjs +2 -1
- package/dist/harness-runtime/lib/native-hook-command.d.mts +3 -2
- package/dist/harness-runtime/lib/native-hook-command.mjs +63 -8
- package/dist/harness-runtime/lib/native-streams.mjs +3 -10
- package/dist/harness-runtime/lib/opencode-plugin.mjs +36 -9
- package/dist/harness-runtime/lib/operation-errors.mjs +13 -0
- package/dist/harness-runtime/lib/operation-progress.mjs +6 -2
- package/dist/harness-runtime/lib/process-json.mjs +4 -5
- package/dist/harness-runtime/lib/tool-observations.mjs +20 -10
- package/dist/harness-runtime/lib/zcode-native-hook-ownership.mjs +109 -0
- package/dist/schemas/code-work-result.schema.json +2 -1
- package/dist/schemas/delivery-stage-result.schema.json +286 -0
- package/dist/schemas/distill-stage-result.schema.json +667 -0
- package/dist/schemas/distill-work-result.schema.json +428 -0
- package/dist/schemas/execution-bundle.schema.json +6 -2
- package/dist/schemas/interactive-stage-result.schema.json +47 -0
- package/dist/schemas/interactive-work-result.schema.json +63 -0
- package/dist/schemas/mb-fix-stage-result.schema.json +93 -0
- package/dist/schemas/project-flow-pack-manifest.schema.json +2 -2
- package/dist/schemas/review-fix-stage-result.schema.json +210 -0
- package/dist/schemas/run-control-receipt.schema.json +1 -0
- package/dist/services/assigned-lifecycle.js +382 -0
- package/dist/services/check-runner-evidence.js +45 -8
- package/dist/services/cli-operation-classifier.js +6 -1
- package/dist/services/code-checks.js +46 -29
- package/dist/services/codex-hook-delivery.js +11 -2
- package/dist/services/controller-child-repair.js +95 -0
- package/dist/services/controller-execution.js +4 -1
- package/dist/services/controller-fanout.js +19 -9
- package/dist/services/controller-memory-worker.js +6 -4
- package/dist/services/controller-result-authority.js +122 -0
- package/dist/services/dashboard.js +4 -2
- package/dist/services/delivery-access.js +123 -0
- package/dist/services/delivery-approval.js +68 -0
- package/dist/services/delivery-artifact.js +86 -0
- package/dist/services/delivery-checks.js +178 -0
- package/dist/services/delivery-execution.js +181 -0
- package/dist/services/delivery-intents.js +212 -0
- package/dist/services/delivery-operation.js +81 -0
- package/dist/services/delivery-policy.js +91 -0
- package/dist/services/delivery-procedures.js +43 -0
- package/dist/services/delivery-request.js +99 -0
- package/dist/services/delivery-result.js +144 -0
- package/dist/services/delivery-stage-commands.js +26 -0
- package/dist/services/delivery-stage-entry.js +56 -0
- package/dist/services/delivery-stage-handlers.js +135 -0
- package/dist/services/delivery-stage-results.js +94 -0
- package/dist/services/descriptor-coordinator-session.js +3 -1
- package/dist/services/descriptor-run-start.js +24 -4
- package/dist/services/descriptor-stage-coordinator.js +78 -10
- package/dist/services/descriptor-stage-settlement.js +59 -14
- package/dist/services/diagnostic-repair-intake.js +123 -0
- package/dist/services/diagnostic-repair-source-intake.js +102 -0
- package/dist/services/diagnostic-report.js +30 -0
- package/dist/services/diagnostic-run-bundle.js +48 -9
- package/dist/services/diagnostic-snapshot.js +2 -2
- package/dist/services/diagnostic-stage-handlers.js +3 -2
- package/dist/services/diagnostic-stage-results.js +26 -0
- package/dist/services/diagnostic-start.js +56 -6
- package/dist/services/diagnostic-work-collection.js +3 -3
- package/dist/services/distill-document-publication.js +280 -0
- package/dist/services/distill-promotion.js +197 -0
- package/dist/services/distill-publication-custody.js +162 -0
- package/dist/services/distill-stage-handlers.js +249 -0
- package/dist/services/distill-stage-results.js +105 -0
- package/dist/services/distill-user-inputs.js +4 -0
- package/dist/services/distill-work-batch.js +42 -0
- package/dist/services/distill-work-collection.js +74 -0
- package/dist/services/distill-work-registration.js +93 -0
- package/dist/services/distill-work-results.js +98 -0
- package/dist/services/eval-snapshots.js +17 -7
- package/dist/services/external-work-launch.js +8 -2
- package/dist/services/flow-catalog.js +24 -0
- package/dist/services/frozen-engine.js +19 -2
- package/dist/services/generic-execution.js +4 -3
- package/dist/services/harness-adapter.js +2 -14
- package/dist/services/hook-memory-routing.js +1 -1
- package/dist/services/hooks.js +31 -6
- package/dist/services/hybrid-contracts.js +6 -1
- package/dist/services/installed-flow-source.js +37 -2
- package/dist/services/interactive-attached-session.js +215 -0
- package/dist/services/interactive-baseline-custody.js +54 -0
- package/dist/services/interactive-before-versions.js +148 -0
- package/dist/services/interactive-bundle-data.js +85 -0
- package/dist/services/interactive-checks.js +179 -0
- package/dist/services/interactive-execution.js +33 -0
- package/dist/services/interactive-finish-integration.js +236 -0
- package/dist/services/interactive-git-baseline.js +230 -0
- package/dist/services/interactive-git-dispatch.js +278 -0
- package/dist/services/interactive-input-delivery.js +82 -0
- package/dist/services/interactive-mutation-scope.js +231 -0
- package/dist/services/interactive-request.js +287 -0
- package/dist/services/interactive-run-bundle.js +109 -0
- package/dist/services/interactive-stage-entry.js +41 -0
- package/dist/services/interactive-stage-handlers.js +289 -0
- package/dist/services/interactive-stage-observation.js +136 -0
- package/dist/services/interactive-stage-results.js +111 -0
- package/dist/services/interactive-user-inputs.js +142 -0
- package/dist/services/interactive-work-batch.js +43 -0
- package/dist/services/interactive-work-collection.js +119 -0
- package/dist/services/interactive-work-completion.js +180 -0
- package/dist/services/interactive-work-context.js +249 -0
- package/dist/services/interactive-work-history.js +112 -0
- package/dist/services/interactive-work-registration.js +124 -0
- package/dist/services/interactive-work-results.js +115 -0
- package/dist/services/interactive-work-start.js +155 -0
- package/dist/services/lifecycle-invocations.js +250 -33
- package/dist/services/managed-daemon-binding.js +52 -1
- package/dist/services/memory-compose-handler.js +2 -22
- package/dist/services/memory-finish-integration.js +58 -17
- package/dist/services/memory-fix-finish-integration.js +129 -0
- package/dist/services/memory-fix-git-dispatch.js +139 -0
- package/dist/services/memory-fix-intake-custody.js +11 -0
- package/dist/services/memory-fix-intake-settlement.js +127 -0
- package/dist/services/memory-fix-publication.js +196 -0
- package/dist/services/memory-fix-repair-admission.js +116 -0
- package/dist/services/memory-fix-request.js +141 -0
- package/dist/services/memory-fix-run-bundle.js +83 -0
- package/dist/services/memory-fix-scope.js +78 -0
- package/dist/services/memory-fix-sources.js +85 -0
- package/dist/services/memory-fix-stage-entry.js +24 -0
- package/dist/services/memory-fix-stage-handlers.js +282 -0
- package/dist/services/memory-fix-stage-results.js +106 -0
- package/dist/services/memory-fix-workspace.js +127 -0
- package/dist/services/memory-flow-user-inputs.js +88 -0
- package/dist/services/memory-init-contexts.js +55 -5
- package/dist/services/memory-init-controller-start.js +20 -4
- package/dist/services/memory-layer.js +7 -3
- package/dist/services/memory-native-review-copy.js +27 -3
- package/dist/services/memory-repair-document-delta.js +1 -0
- package/dist/services/project-flow-source.js +195 -0
- package/dist/services/project-git-integration.js +92 -0
- package/dist/services/repair-intents.js +2 -7
- package/dist/services/retained-native-hook.js +274 -0
- package/dist/services/review-fix-handoff.js +82 -0
- package/dist/services/review-fix-intake-custody.js +17 -0
- package/dist/services/review-fix-stage-entry.js +43 -0
- package/dist/services/review-fix-stage-handlers.js +176 -0
- package/dist/services/review-fix-stage-results.js +86 -0
- package/dist/services/run-control-worker.js +42 -1
- package/dist/services/run-control.js +40 -5
- package/dist/services/run-controller-adapter.js +37 -3
- package/dist/services/run-controller-process.js +25 -4
- package/dist/services/run-controller-recovery.js +46 -6
- package/dist/services/run-controller-state.js +3 -2
- package/dist/services/run-controller.js +257 -44
- package/dist/services/run-recovery.js +31 -2
- package/dist/services/run-timeline.js +59 -0
- package/dist/services/runs.js +18 -31
- package/dist/services/sessions.js +2 -1
- package/dist/services/stage-handlers.js +55 -3
- package/dist/services/stage-lifecycle.js +4 -3
- package/dist/services/stage-pause.js +26 -9
- package/dist/services/stage-report-renderer.js +2 -6
- package/dist/services/technical-notes.js +31 -0
- package/dist/services/verification-matrix.js +1 -1
- package/dist/services/version-status.js +1 -1
- package/dist/services/vnext-code-review.js +74 -5
- package/dist/services/vnext-code.js +8 -32
- package/dist/services/vnext-fanout.js +53 -8
- package/dist/services/vnext-merge.js +10 -5
- package/dist/services/vnext-plan-review.js +1 -1
- package/dist/services/vnext-plan.js +1 -1
- package/dist/services/vnext-protocolize.js +1 -1
- package/dist/services/vnext-specify.js +10 -5
- package/dist/services/work-registry.js +255 -69
- package/dist/services/worktrees.js +7 -4
- package/dist/storage/database.js +2 -1
- package/dist/verification-components.json +82 -0
- package/package.json +3 -2
- package/tools/verification-jobs.mjs +35 -10
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { AppError } from "../shared/errors.js";
|
|
3
|
+
import { PreparedInstalledFlowSource } from "./installed-flow-source.js";
|
|
4
|
+
import { FrozenGenericExecution } from "./generic-execution.js";
|
|
5
|
+
import { AdmittedFrozenEngine } from "./frozen-engine.js";
|
|
6
|
+
import { RetainedExecutionBundle } from "./execution-bundles.js";
|
|
7
|
+
import { nextDescriptorRunId } from "./descriptor-run-start.js";
|
|
8
|
+
import { bundledHandlerContext } from "./stage-handlers.js";
|
|
9
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
10
|
+
import { prepareMemoryFixSourceIntake } from "./memory-fix-intake-custody.js";
|
|
11
|
+
import { prepareMemoryFixGitPolicy } from "./memory-fix-finish-integration.js";
|
|
12
|
+
import { readMemoryFixRequestArtifacts } from "./memory-fix-request.js";
|
|
13
|
+
const hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
14
|
+
const encode = (value) => Buffer.from(JSON.stringify(value));
|
|
15
|
+
function refuse(reason) {
|
|
16
|
+
throw new AppError("memory_fix_run_bundle_invalid", "Repair RUN requires its original source selection, request and Git policy", 2, { reason, effect: "no_effect" });
|
|
17
|
+
}
|
|
18
|
+
/** Initial construction outside the writer only; no RUN or workspace is created.
|
|
19
|
+
* The request owner must retain this selection before allocation and use the
|
|
20
|
+
* retained bundle on retry, never recapture a later diagnostic or Git policy.
|
|
21
|
+
*/
|
|
22
|
+
export function prepareMemoryFixRunBundle(context, input) {
|
|
23
|
+
if (context.db.isOwnedWriteTransaction?.())
|
|
24
|
+
refuse("preparation_outside_writer_required");
|
|
25
|
+
if (!(input.source instanceof PreparedInstalledFlowSource) || !(input.execution instanceof FrozenGenericExecution)
|
|
26
|
+
|| !(input.engine instanceof AdmittedFrozenEngine) || !Number.isFinite(Date.parse(input.boundAt)))
|
|
27
|
+
refuse("unprepared_selection");
|
|
28
|
+
if (typeof input.requestText !== "string" || !input.requestText.trim() || Buffer.byteLength(input.requestText) > 64 * 1024)
|
|
29
|
+
refuse("original_request_required");
|
|
30
|
+
const frozen = input.execution.snapshot(), flow = input.source.resolveRegisteredFlow();
|
|
31
|
+
if (frozen.settings.flow_id !== "mb-fix" || flow.descriptor.id !== "mb-fix"
|
|
32
|
+
|| flow.sha256 !== frozen.settings.descriptor_sha256)
|
|
33
|
+
refuse("flow_selection_changed");
|
|
34
|
+
input.source.revalidate();
|
|
35
|
+
input.engine.admitMemoryFixFlow(input.source);
|
|
36
|
+
const retained = readMemoryFixRequestArtifacts(context, input);
|
|
37
|
+
const restored = (path) => ({ path, bytes: Buffer.from(retained.artifacts.find(item => item.path === path).base64, "base64") });
|
|
38
|
+
const intake = retained ? restored("runtime/repair-intake.json") : prepareMemoryFixSourceIntake(context, { projectRoot: frozen.scope.projectRoot,
|
|
39
|
+
sourceRunId: input.sourceRunId, findingIds: input.findingIds });
|
|
40
|
+
const git = retained ? restored("runtime/git-policy.json") : prepareMemoryFixGitPolicy(context.env, { scope: { requestId: frozen.scope.requestId,
|
|
41
|
+
projectRoot: frozen.scope.projectRoot, workspaceRoot: frozen.scope.workspace }, gitExecutable: input.gitExecutable, selection: input.gitPolicy });
|
|
42
|
+
const manifest = input.engine.manifest(), checksum = manifest.integrity.checksum;
|
|
43
|
+
if (typeof checksum !== "string")
|
|
44
|
+
refuse("engine_identity_missing");
|
|
45
|
+
const runId = nextDescriptorRunId(context, { projectRoot: frozen.scope.projectRoot, requestId: frozen.scope.requestId, slug: "mb-fix" });
|
|
46
|
+
const binding = { schema_id: "dd-flow/run-engine-binding@1", run_id: runId,
|
|
47
|
+
project_root: frozen.scope.projectRoot, bound_at: retained?.bound_at ?? input.boundAt, source: "run_creation", reason: "Request-bound selected Memory Bank repair",
|
|
48
|
+
engine: { package_name: manifest.package_name, package_version: manifest.package_version, engine_version: manifest.engine_version,
|
|
49
|
+
integrity_checksum: checksum, snapshot_root: manifest.snapshot_root }, probe: { status: "not_required", command: null } };
|
|
50
|
+
const blobs = input.source.blobs();
|
|
51
|
+
const put = (logical, content) => {
|
|
52
|
+
if (blobs.has(logical) || !content.length || content.length > 1024 * 1024)
|
|
53
|
+
refuse("bundle_path_collision_or_size");
|
|
54
|
+
blobs.set(logical, content);
|
|
55
|
+
return { path: logical, sha256: hash(content) };
|
|
56
|
+
};
|
|
57
|
+
put("runtime/source-request.md", Buffer.from(input.requestText));
|
|
58
|
+
put(intake.path, intake.bytes);
|
|
59
|
+
put(git.path, git.bytes);
|
|
60
|
+
for (const reference of [frozen.settings.attached_session_receipt, frozen.settings.delegation.native_capacity_receipt]) {
|
|
61
|
+
if (!reference)
|
|
62
|
+
continue;
|
|
63
|
+
const content = input.receiptBytes?.get(reference.sha256);
|
|
64
|
+
if (!content || hash(content) !== reference.sha256 || !input.receipts?.has(reference.sha256))
|
|
65
|
+
refuse("execution_receipt_missing");
|
|
66
|
+
put(reference.path, Buffer.from(content));
|
|
67
|
+
}
|
|
68
|
+
const profiles = { schema_id: "dd-flow/profile-settings@1", profiles: frozen.profiles, aliases: {}, default_profile: null };
|
|
69
|
+
const profile = put("runtime/profiles.json", encode({ ...profiles, revision: hybridDigest(profiles) }));
|
|
70
|
+
const execution = put("runtime/execution.json", encode(frozen.settings));
|
|
71
|
+
const engineBinding = put("runtime/engine-binding.json", encode(binding));
|
|
72
|
+
const engineManifest = put("runtime/engine-manifest.json", input.engine.manifestBytes());
|
|
73
|
+
const provenance = { canon: input.source.canonProvenance(), project_pack: null };
|
|
74
|
+
const bundle = { schema_id: "dd-flow/execution-bundle@1", run_id: runId, flow_id: "mb-fix",
|
|
75
|
+
descriptor: input.source.descriptorReference(), execution_settings: execution, profile_settings: profile,
|
|
76
|
+
engine_binding: engineBinding, engine_manifest: engineManifest, bootstrap_receipts: [], ...provenance,
|
|
77
|
+
inventory: [...blobs].map(([logical, content]) => ({ path: logical, sha256: hash(content) })) };
|
|
78
|
+
const bytes = encode(bundle);
|
|
79
|
+
return RetainedExecutionBundle.admit(bytes, hash(bytes), blobs, { runId, executionScope: frozen.scope,
|
|
80
|
+
bundleProjectRoot: frozen.scope.projectRoot, descriptorSha256: flow.sha256, engineChecksum: checksum,
|
|
81
|
+
engineBindingSha256: engineBinding.sha256, engineManifestSha256: engineManifest.sha256,
|
|
82
|
+
descriptorContext: bundledHandlerContext(new Map()), bundleProvenance: provenance, receipts: input.receipts ?? new Map() });
|
|
83
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
import { resolveProjectRoot } from "../storage/paths.js";
|
|
3
|
+
import { requireProjectByRoot } from "./projects.js";
|
|
4
|
+
import { readFlowRunExecutionBundle, assertPreparedRunBundleSqlAuthority } from "./runs.js";
|
|
5
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
6
|
+
import { validateMemoryFixRouting } from "./diagnostic-repair-intake.js";
|
|
7
|
+
import { readMemoryFixSourceIntake, assertMemoryFixSourceIntakeCustody } from "./memory-fix-intake-custody.js";
|
|
8
|
+
import { observeRetainedMemoryFixSources, observeMemoryFixWriteTargets } from "./memory-fix-sources.js";
|
|
9
|
+
import { readPreparedMemoryFlowUserInputs } from "./memory-flow-user-inputs.js";
|
|
10
|
+
import { assertMemoryRepairWritePaths } from "./memory-review-correction-plan.js";
|
|
11
|
+
function refuse(reason) {
|
|
12
|
+
throw new AppError("memory_fix_scope_invalid", "Memory repair scope requires the selected findings and actual delivered user agreement", 2, { reason, effect: "no_effect" });
|
|
13
|
+
}
|
|
14
|
+
/** Prepares scope evidence, not semantic consent, Stage settlement or permission
|
|
15
|
+
* to apply files. The active intake coordinator must read the full delivered
|
|
16
|
+
* conversation, assess drift and bind its decision through the ordinary hook.
|
|
17
|
+
* Later publication must fence this same input set and original baseline again.
|
|
18
|
+
*/
|
|
19
|
+
export function prepareMemoryFixScopeEvidence(context, input) {
|
|
20
|
+
if (context.db.isOwnedWriteTransaction?.())
|
|
21
|
+
refuse("preparation_outside_writer_required");
|
|
22
|
+
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
23
|
+
const bundle = readFlowRunExecutionBundle(context, input);
|
|
24
|
+
if (!bundle || bundle.manifest.flow_id !== "mb-fix")
|
|
25
|
+
refuse("memory_fix_bundle_required");
|
|
26
|
+
const scope = { projectId: project.id, runId: input.runId };
|
|
27
|
+
const run = assertPreparedRunBundleSqlAuthority(context, scope, bundle);
|
|
28
|
+
const source = readMemoryFixSourceIntake(context, input, bundle);
|
|
29
|
+
const routing = validateMemoryFixRouting(source.intake.selected_findings.map(finding => finding.id), input.routing);
|
|
30
|
+
const users = readPreparedMemoryFlowUserInputs(context, scope, "mb-fix", bundle), userInputsSha256 = hybridDigest(users);
|
|
31
|
+
const writeScope = structuredClone(input.writeScope);
|
|
32
|
+
const repairs = routing.filter(decision => decision.route === "memory_repair").map(decision => decision.finding_id);
|
|
33
|
+
if (!repairs.length) {
|
|
34
|
+
if (writeScope !== null)
|
|
35
|
+
refuse("scope_without_repair_findings");
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
if (!writeScope || typeof writeScope !== "object" || Array.isArray(writeScope)
|
|
39
|
+
|| Object.keys(writeScope).sort().join() !== "approval_source,finding_ids,paths,user_inputs_sha256"
|
|
40
|
+
|| !Array.isArray(writeScope.finding_ids) || !Array.isArray(writeScope.paths)
|
|
41
|
+
|| hybridDigest(writeScope.finding_ids) !== hybridDigest(repairs))
|
|
42
|
+
refuse("exact_selected_scope_required");
|
|
43
|
+
assertMemoryRepairWritePaths(writeScope.paths);
|
|
44
|
+
const paths = [...new Set(routing.flatMap(decision => decision.write_paths))].sort();
|
|
45
|
+
if (hybridDigest([...writeScope.paths].sort()) !== hybridDigest(paths))
|
|
46
|
+
refuse("routing_write_scope_changed");
|
|
47
|
+
const agreement = writeScope.approval_source;
|
|
48
|
+
if (!agreement || Object.keys(agreement).sort().join() !== "path,root,sha256" || agreement.root !== "run"
|
|
49
|
+
|| !users.sources.some(packet => ["request", "answer"].includes(packet.role)
|
|
50
|
+
&& packet.path === agreement.path && packet.sha256 === agreement.sha256))
|
|
51
|
+
refuse("delivered_agreement_required");
|
|
52
|
+
if (writeScope.user_inputs_sha256 !== userInputsSha256)
|
|
53
|
+
refuse("user_inputs_changed");
|
|
54
|
+
}
|
|
55
|
+
const workspaceRoot = resolveProjectRoot(run.workspace_root), runRoot = run.run_root;
|
|
56
|
+
if (workspaceRoot !== run.workspace_root || !runRoot)
|
|
57
|
+
refuse("workspace_identity_required");
|
|
58
|
+
const observation = observeRetainedMemoryFixSources(workspaceRoot, source.intake);
|
|
59
|
+
const writeTargets = observeMemoryFixWriteTargets(workspaceRoot, writeScope?.paths ?? []);
|
|
60
|
+
const sourceObservation = { ...observation.snapshot(), write_targets: writeTargets };
|
|
61
|
+
const evidence = { intake_sha256: source.sha256, user_inputs_sha256: userInputsSha256,
|
|
62
|
+
workspace_root: workspaceRoot, routing, write_scope: writeScope, source_observation: sourceObservation,
|
|
63
|
+
source_observation_sha256: hybridDigest(sourceObservation) };
|
|
64
|
+
return { snapshot: () => structuredClone(evidence), assertSourcesCurrent: () => {
|
|
65
|
+
if (context.db.isOwnedWriteTransaction?.())
|
|
66
|
+
refuse("physical_observation_outside_writer_required");
|
|
67
|
+
observation.revalidate();
|
|
68
|
+
if (hybridDigest(observeMemoryFixWriteTargets(workspaceRoot, writeScope?.paths ?? [])) !== hybridDigest(writeTargets))
|
|
69
|
+
refuse("write_targets_changed");
|
|
70
|
+
}, assertCustody: (writer) => {
|
|
71
|
+
assertMemoryFixSourceIntakeCustody(writer, scope, bundle, source.sha256);
|
|
72
|
+
const current = assertPreparedRunBundleSqlAuthority(writer, scope, bundle);
|
|
73
|
+
if (current.workspace_root !== workspaceRoot || current.run_root !== runRoot)
|
|
74
|
+
refuse("workspace_identity_changed");
|
|
75
|
+
if (hybridDigest(readPreparedMemoryFlowUserInputs(writer, scope, "mb-fix", bundle)) !== userInputsSha256)
|
|
76
|
+
refuse("user_inputs_changed");
|
|
77
|
+
} };
|
|
78
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
import { resolveProjectRoot } from "../storage/paths.js";
|
|
3
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
4
|
+
import { readDiagnosticRepairIntake } from "./diagnostic-repair-intake.js";
|
|
5
|
+
import { ObservedMemorySources } from "./memory-source-observations.js";
|
|
6
|
+
import { memorySourceKey } from "./memory-stage-results.js";
|
|
7
|
+
import { MemoryRepairDocumentBaseline } from "./memory-repair-document-delta.js";
|
|
8
|
+
function refuse(reason) {
|
|
9
|
+
throw new AppError("memory_fix_source_invalid", "Memory repair source observations changed or exceed their bounds", 2, { reason, effect: "no_effect" });
|
|
10
|
+
}
|
|
11
|
+
/** Only selected target metadata, including absence, becomes intake authority.
|
|
12
|
+
* Neighboring current documents remain protected by the later publication plan
|
|
13
|
+
* but unrelated edits do not invalidate target approval by themselves. */
|
|
14
|
+
export function observeMemoryFixWriteTargets(projectRoot, paths) {
|
|
15
|
+
if (!paths.length)
|
|
16
|
+
return [];
|
|
17
|
+
const baseline = MemoryRepairDocumentBaseline.capture(projectRoot, paths);
|
|
18
|
+
return memoryFixWriteTargetsFromBaseline(baseline);
|
|
19
|
+
}
|
|
20
|
+
export function memoryFixWriteTargetsFromBaseline(baseline) {
|
|
21
|
+
const value = JSON.parse(baseline.bytes().toString("utf8"));
|
|
22
|
+
const entries = new Map(baseline.entries().map(entry => [entry.path, entry]));
|
|
23
|
+
return value.write_paths.slice().sort().map(logical => ({ path: logical, before: entries.get(logical) ?? null }));
|
|
24
|
+
}
|
|
25
|
+
/** Read-only drift evidence, never a replacement for the original finding or
|
|
26
|
+
* permission to repair/close it. The Stage owner must assess drift and revalidate
|
|
27
|
+
* this same observation before admitting any writer. Unavailable is not unchanged.
|
|
28
|
+
*/
|
|
29
|
+
export function observeMemoryFixSources(context, input) {
|
|
30
|
+
return observeSources(resolveProjectRoot(input.projectRoot), () => readDiagnosticRepairIntake(context, input));
|
|
31
|
+
}
|
|
32
|
+
/** The handler must first admit this intake from its own frozen RUN bundle.
|
|
33
|
+
* A scoped restore need not contain the neighboring source audit's SQL rows.
|
|
34
|
+
* This helper grants no intake/approval authority and never searches another Home.
|
|
35
|
+
*/
|
|
36
|
+
export function observeRetainedMemoryFixSources(projectRoot, intake) {
|
|
37
|
+
const retained = structuredClone(intake);
|
|
38
|
+
return observeSources(resolveProjectRoot(projectRoot), () => retained);
|
|
39
|
+
}
|
|
40
|
+
function observeSources(projectRoot, readIntake) {
|
|
41
|
+
const capture = () => {
|
|
42
|
+
const intake = readIntake(), references = new Map();
|
|
43
|
+
for (const finding of intake.selected_findings) {
|
|
44
|
+
for (const reference of [...finding.evidence, ...finding.resolution.evidence,
|
|
45
|
+
...finding.critic_decisions.flatMap(critic => critic.decision.evidence)]) {
|
|
46
|
+
if (typeof reference.path !== "string" || reference.path.length > 4096 || reference.path.split("/").includes(".git")
|
|
47
|
+
|| !/^[a-f0-9]{64}$/.test(reference.sha256))
|
|
48
|
+
refuse("invalid_original_source");
|
|
49
|
+
if (references.has(reference.path) && references.get(reference.path) !== reference.sha256)
|
|
50
|
+
refuse("conflicting_original_source");
|
|
51
|
+
references.set(reference.path, reference.sha256);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (references.size > 20_000)
|
|
55
|
+
refuse("source_inventory_exceeds_bound");
|
|
56
|
+
let total = 0;
|
|
57
|
+
const observations = [...references].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([path, original_sha256]) => {
|
|
58
|
+
try {
|
|
59
|
+
const location = { root: "project", path };
|
|
60
|
+
const observed = ObservedMemorySources.capture({ project: projectRoot }, [location]);
|
|
61
|
+
total += observed.bytes(location).length;
|
|
62
|
+
if (total > 64 * 1024 * 1024)
|
|
63
|
+
refuse("source_bytes_exceed_bound");
|
|
64
|
+
const current_sha256 = observed.hashes().get(memorySourceKey("project", path));
|
|
65
|
+
return { path, original_sha256, current_sha256,
|
|
66
|
+
state: current_sha256 === original_sha256 ? "unchanged" : "changed", reason: null };
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (!(error instanceof AppError) || error.code !== "memory_source_observation_failed")
|
|
70
|
+
throw error;
|
|
71
|
+
return { path, original_sha256, current_sha256: null, state: "unavailable",
|
|
72
|
+
reason: String(error.details?.reason ?? "source_unavailable") };
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
return { schema_id: "dd-flow/mb-fix-source-observation@1", source: intake.source,
|
|
76
|
+
finding_ids: intake.selected_findings.map(finding => finding.id), observations,
|
|
77
|
+
requires_reassessment: !observations.length || observations.some(source => source.state !== "unchanged"),
|
|
78
|
+
authorization: "not_granted" };
|
|
79
|
+
};
|
|
80
|
+
const original = capture();
|
|
81
|
+
return { snapshot: () => structuredClone(original), revalidate: () => {
|
|
82
|
+
if (hybridDigest(capture()) !== hybridDigest(original))
|
|
83
|
+
refuse("sources_changed_after_observation");
|
|
84
|
+
} };
|
|
85
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
import { memoryFixStages } from "../domain/memory-fix-flow.js";
|
|
3
|
+
import { readFlowRunExecutionBundle } from "./runs.js";
|
|
4
|
+
import { requireProjectByRoot } from "./projects.js";
|
|
5
|
+
import { resolveProjectRoot } from "../storage/paths.js";
|
|
6
|
+
import { readMemoryFixSourceIntake, assertMemoryFixSourceIntakeCustody } from "./memory-fix-intake-custody.js";
|
|
7
|
+
import { PreparedDescriptorStageStart } from "./descriptor-stage-start.js";
|
|
8
|
+
import { PreparedDescriptorStageCoordinator } from "./descriptor-stage-coordinator.js";
|
|
9
|
+
/** Registered entry implementation, not standalone RUN-start permission.
|
|
10
|
+
* Generic runtime alone owns attempts, root Work, coordinator and replay.
|
|
11
|
+
*/
|
|
12
|
+
export function startMemoryFixStage(context, input) {
|
|
13
|
+
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
14
|
+
const bundle = readFlowRunExecutionBundle(context, input);
|
|
15
|
+
if (!bundle || bundle.manifest.flow_id !== "mb-fix" || !memoryFixStages.includes(input.stage))
|
|
16
|
+
throw new AppError("memory_fix_stage_invalid", "Memory repair requires its registered five-stage flow and retained intake", 2, { reason: "memory_fix_stage_required", effect: "no_effect" });
|
|
17
|
+
bundle.descriptor.stage(input.stage);
|
|
18
|
+
const source = readMemoryFixSourceIntake(context, input, bundle);
|
|
19
|
+
const scope = { projectId: project.id, runId: input.runId };
|
|
20
|
+
const fence = (writer) => assertMemoryFixSourceIntakeCustody(writer, scope, bundle, source.sha256);
|
|
21
|
+
const start = PreparedDescriptorStageStart.prepare(context, input).apply(context, fence);
|
|
22
|
+
const coordinator = PreparedDescriptorStageCoordinator.prepare(context, input).apply(context, fence);
|
|
23
|
+
return { ...start, ...coordinator, intake_sha256: source.sha256, source_intake: source.reference };
|
|
24
|
+
}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { AppError } from "../shared/errors.js";
|
|
4
|
+
import { resolveProjectRoot } from "../storage/paths.js";
|
|
5
|
+
import { entityReferenceVariants } from "../shared/entity-references.js";
|
|
6
|
+
import { requireProjectByRoot } from "./projects.js";
|
|
7
|
+
import { readFlowRunExecutionBundle, assertPreparedRunBundleSqlAuthority } from "./runs.js";
|
|
8
|
+
import { readDescriptorStageInput } from "./descriptor-stage-coordinator.js";
|
|
9
|
+
import { PreparedDescriptorStageSettlement, readAcceptedDescriptorStageSettlement, readActiveDescriptorStageCoordinator } from "./descriptor-stage-settlement.js";
|
|
10
|
+
import { readAcceptedStageData, prepareStagePacket, acceptStagePacket, resolveStagePublicationPath, prepareStageReport, acceptStageReport, publishStageReport } from "./stage-report-renderer.js";
|
|
11
|
+
import { readMemoryFixSourceIntake, assertMemoryFixSourceIntakeCustody } from "./memory-fix-intake-custody.js";
|
|
12
|
+
import { finishMemoryFixIntake, readAcceptedMemoryFixIntakeEvidence } from "./memory-fix-intake-settlement.js";
|
|
13
|
+
import { readMemoryFixPublication, readMemoryFixPublicationReference } from "./memory-fix-publication.js";
|
|
14
|
+
import { memoryFixBeforeDocumentReferences, publishMemoryFixBeforeDocuments } from "./distill-document-publication.js";
|
|
15
|
+
import { readPreparedMemoryFlowUserInputs } from "./memory-flow-user-inputs.js";
|
|
16
|
+
import { validateMemoryFixStageResult, assertMemoryFixStageScope, memoryFixClosureProjection } from "./memory-fix-stage-results.js";
|
|
17
|
+
import { PreparedDescriptorRunSettlement } from "./descriptor-run-settlement.js";
|
|
18
|
+
import { prepareMemoryFixFinishIntegration } from "./memory-fix-finish-integration.js";
|
|
19
|
+
import { stageLifecycleMatchKey } from "./hooks.js";
|
|
20
|
+
import { assertDiagnosticSessionIndependence } from "./diagnostic-work-collection.js";
|
|
21
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
22
|
+
import { memoryFixStages } from "../domain/memory-fix-flow.js";
|
|
23
|
+
import { startMemoryFixStage } from "./memory-fix-stage-entry.js";
|
|
24
|
+
/** Engine-owned handlers only; source admission does not allocate or start a RUN. */
|
|
25
|
+
export const memoryFixStageHandlers = Object.freeze(memoryFixStages.map(stage => Object.freeze({
|
|
26
|
+
flowId: "mb-fix", stage, entryId: "memory-fix-stage-entry", finishId: "memory-fix-stage-finish",
|
|
27
|
+
entry: startMemoryFixStage, finish: finishMemoryFixStage
|
|
28
|
+
})));
|
|
29
|
+
function refuse(reason) {
|
|
30
|
+
const correctable = reason.startsWith("changed_") || ["invalid_result_json", "invalid_result_details", "result_exceeds_bound",
|
|
31
|
+
"preservation_evidence_required", "output_evidence_required"].includes(reason);
|
|
32
|
+
throw new AppError("memory_fix_stage_invalid", "Repair Stage requires its admitted publication and independent observations", 2, { reason, effect: "no_effect", ...(correctable ? { phase: "prepare", recoverable: true } : {}) });
|
|
33
|
+
}
|
|
34
|
+
function readTerminalReport(context, scope, attempt, root) {
|
|
35
|
+
const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='flow_run.stage_report' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.stage')='finish' AND json_extract(payload_json,'$.attempt')=? LIMIT 2", [scope.projectId, scope.runId, attempt]);
|
|
36
|
+
if (!rows.length)
|
|
37
|
+
return null;
|
|
38
|
+
if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 8 * 1024 * 1024)
|
|
39
|
+
refuse("terminal_report_custody_invalid");
|
|
40
|
+
try {
|
|
41
|
+
const retained = JSON.parse(rows[0].payload_json);
|
|
42
|
+
const report = prepareStageReport(JSON.parse(retained.bytes.json));
|
|
43
|
+
if (retained.sha256 !== report.sha256 || hybridDigest(retained.bytes) !== hybridDigest(report.bytes)
|
|
44
|
+
|| resolveStagePublicationPath(context, scope.projectId, scope.runId, retained.root) !== root)
|
|
45
|
+
refuse("terminal_report_changed");
|
|
46
|
+
return report;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (error instanceof AppError)
|
|
50
|
+
throw error;
|
|
51
|
+
return refuse("terminal_report_custody_invalid");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Common Stage settlement; public launch still requires the real RUN factory,
|
|
55
|
+
* command dispatch and installed family acceptance. */
|
|
56
|
+
export function finishMemoryFixStage(context, input) {
|
|
57
|
+
if (input.stage === "intake")
|
|
58
|
+
return finishMemoryFixIntake(context, input);
|
|
59
|
+
if (!["repair", "verify", "review", "finish"].includes(input.stage))
|
|
60
|
+
refuse("repair_stage_required");
|
|
61
|
+
const stage = input.stage;
|
|
62
|
+
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
63
|
+
const bundle = readFlowRunExecutionBundle(context, input);
|
|
64
|
+
if (!bundle || bundle.manifest.flow_id !== "mb-fix")
|
|
65
|
+
refuse("memory_fix_bundle_required");
|
|
66
|
+
const scope = { projectId: project.id, runId: input.runId, stage };
|
|
67
|
+
const source = readMemoryFixSourceIntake(context, input, bundle), run = assertPreparedRunBundleSqlAuthority(context, scope, bundle);
|
|
68
|
+
const packet = readDescriptorStageInput(context, scope, bundle, true, true);
|
|
69
|
+
const previousStage = stage === "repair" ? "intake" : stage === "verify" ? "repair" : stage === "review" ? "verify" : "review";
|
|
70
|
+
const previous = readAcceptedStageData(context, { ...scope, stage: previousStage });
|
|
71
|
+
if (packet.input.predecessor?.stage !== previousStage || hybridDigest(packet.input.predecessor.result) !== hybridDigest(previous.result))
|
|
72
|
+
refuse("predecessor_changed");
|
|
73
|
+
const prior = JSON.parse(previous.bytes.toString("utf8"));
|
|
74
|
+
const intakeFile = path.join(packet.runRoot, bundle.descriptor.stage("intake").directory, "intake-evidence.json");
|
|
75
|
+
const intake = readAcceptedMemoryFixIntakeEvidence(context, scope, intakeFile);
|
|
76
|
+
if (intake.intake_sha256 !== source.sha256)
|
|
77
|
+
refuse("source_intake_changed");
|
|
78
|
+
const publication = readMemoryFixPublication(context, { project_id: project.id, run_id: input.runId });
|
|
79
|
+
const repairInput = stage === "repair" ? packet : readDescriptorStageInput(context, { ...scope, stage: "repair" }, bundle, true, true);
|
|
80
|
+
const intakeResult = readAcceptedStageData(context, { ...scope, stage: "intake" });
|
|
81
|
+
if (intake.write_scope) {
|
|
82
|
+
if (!publication?.completed || publication.binding.bundle_sha256 !== bundle.sha256
|
|
83
|
+
|| publication.binding.attempt !== repairInput.attempt || publication.binding.input_sha256 !== repairInput.inputSha256
|
|
84
|
+
|| publication.binding.intake_sha256 !== source.sha256 || publication.binding.source_observation_sha256 !== intake.source_observation_sha256
|
|
85
|
+
|| hybridDigest(publication.binding.intake_result) !== hybridDigest(intakeResult.result)
|
|
86
|
+
|| hybridDigest(publication.binding.write_scope) !== hybridDigest(intake.write_scope))
|
|
87
|
+
refuse("completed_publication_required");
|
|
88
|
+
}
|
|
89
|
+
else if (publication)
|
|
90
|
+
refuse("publication_without_accepted_scope");
|
|
91
|
+
const reference = publication ? readMemoryFixPublicationReference(context, publication, `${bundle.descriptor.stage("repair").directory}/publication.json`) : null;
|
|
92
|
+
if (publication && !reference)
|
|
93
|
+
refuse("publication_reference_required");
|
|
94
|
+
if (!input.bytes.length || input.bytes.length > 1024 * 1024)
|
|
95
|
+
refuse("result_exceeds_bound");
|
|
96
|
+
let result;
|
|
97
|
+
try {
|
|
98
|
+
result = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(input.bytes));
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return refuse("invalid_result_json");
|
|
102
|
+
}
|
|
103
|
+
if (!result?.details || typeof result.details !== "object" || !Array.isArray(result.details.limitations))
|
|
104
|
+
refuse("invalid_result_details");
|
|
105
|
+
const insert = (object, field, expected) => {
|
|
106
|
+
const proposed = object[field];
|
|
107
|
+
if (proposed != null && !(Array.isArray(proposed) && !proposed.length) && hybridDigest(proposed) !== hybridDigest(expected))
|
|
108
|
+
refuse(`changed_${field}`);
|
|
109
|
+
Object.assign(object, { [field]: structuredClone(expected) });
|
|
110
|
+
};
|
|
111
|
+
for (const [field, expected] of Object.entries({ schema_id: "dd-flow/mb-fix-stage-result@1", flow_id: "mb-fix", run_id: input.runId, stage,
|
|
112
|
+
input_sha256: packet.inputSha256, intake_sha256: source.sha256, source_observation_sha256: intake.source_observation_sha256,
|
|
113
|
+
predecessors: [{ stage: previousStage, ...previous.result }] }))
|
|
114
|
+
insert(result, field, expected);
|
|
115
|
+
insert(result.details, "routing", intake.routing);
|
|
116
|
+
insert(result.details, "write_scope", intake.write_scope);
|
|
117
|
+
insert(result.details, "repair_receipt", reference);
|
|
118
|
+
if (stage === "review" || stage === "finish")
|
|
119
|
+
insert(result.details, "verification", prior.details.verification);
|
|
120
|
+
if (stage === "finish")
|
|
121
|
+
insert(result.details, "review", prior.details.review);
|
|
122
|
+
result.details.limitations = [...new Set([...prior.details.limitations, ...source.intake.limitations, ...result.details.limitations])];
|
|
123
|
+
const bytes = Buffer.from(JSON.stringify(result));
|
|
124
|
+
const index = JSON.parse(run.index_json), replay = index.stage_runs.some((entry) => entry.stage === stage && entry.status === "done");
|
|
125
|
+
const logicalReadback = `${bundle.descriptor.stage(stage).directory}/publication-readback.json`;
|
|
126
|
+
const readback = prepareStagePacket(path.join(packet.runRoot, logicalReadback), Buffer.from(JSON.stringify({
|
|
127
|
+
schema_id: "dd-flow/mb-fix-publication-readback@1", run_id: input.runId, stage, input_sha256: packet.inputSha256,
|
|
128
|
+
result_sha256: createHash("sha256").update(bytes).digest("hex"), publication: reference,
|
|
129
|
+
documents: publication ? publication.binding.write_scope.paths.map(logical => ({ path: logical,
|
|
130
|
+
after: publication.plan.state(publication.plan.actions().length).entries().find(entry => entry.path === logical) ?? null })) : []
|
|
131
|
+
})));
|
|
132
|
+
if (replay) {
|
|
133
|
+
if (!readAcceptedStageData(context, scope).bytes.equals(bytes))
|
|
134
|
+
refuse("accepted_result_changed");
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
if (publication) {
|
|
138
|
+
const actual = publication.plan.classify(run.workspace_root);
|
|
139
|
+
if (actual.completed !== actual.total)
|
|
140
|
+
refuse("publication_not_observed");
|
|
141
|
+
publishMemoryFixBeforeDocuments(packet.runRoot, publication.plan, publication.beforeDocuments);
|
|
142
|
+
}
|
|
143
|
+
result = validateMemoryFixStageResult(bytes, bundle.descriptor.stageBlob(stage, "result_schema"), {
|
|
144
|
+
runId: input.runId, stage, inputSha256: packet.inputSha256, intakeSha256: source.sha256, sourceObservationSha256: intake.source_observation_sha256,
|
|
145
|
+
findingIds: source.intake.selected_findings.map(finding => finding.id), projectRoot: run.workspace_root, runRoot: packet.runRoot,
|
|
146
|
+
predecessors: [{ stage: previousStage, ...previous.result }], writeScope: intake.write_scope, repairReceipt: reference
|
|
147
|
+
});
|
|
148
|
+
if (publication && stage !== "repair") {
|
|
149
|
+
const evidence = result.details.verification.flatMap(item => item.evidence);
|
|
150
|
+
if (memoryFixBeforeDocumentReferences(publication.plan).some(document => !evidence.some(ref => ref.root === "run"
|
|
151
|
+
&& ref.path === document.source.path && ref.sha256 === document.source.sha256)))
|
|
152
|
+
refuse("preservation_evidence_required");
|
|
153
|
+
const after = publication.plan.state(publication.plan.actions().length).entries();
|
|
154
|
+
if (publication.binding.write_scope.paths.some(logical => {
|
|
155
|
+
const document = after.find(entry => entry.path === logical);
|
|
156
|
+
return document?.kind === "file" && !evidence.some(ref => ref.root === "project" && ref.path === logical && ref.sha256 === document.sha256);
|
|
157
|
+
}))
|
|
158
|
+
refuse("output_evidence_required");
|
|
159
|
+
}
|
|
160
|
+
assertMemoryFixStageScope(result, prior);
|
|
161
|
+
}
|
|
162
|
+
const settlement = PreparedDescriptorStageSettlement.prepare(context, { ...input, bytes });
|
|
163
|
+
const keys = (name) => entityReferenceVariants(input.runId).map(ref => stageLifecycleMatchKey("stage_finish", ref, name, input.projectRoot));
|
|
164
|
+
const repairKeys = stage !== "repair" || replay ? keys("repair") : [];
|
|
165
|
+
const verifyKeys = stage === "review" || stage === "finish" ? keys("verify") : [];
|
|
166
|
+
const reviewKeys = stage === "finish" ? keys("review") : [];
|
|
167
|
+
const readOwner = (writer) => {
|
|
168
|
+
const physical = (sessionId) => {
|
|
169
|
+
const session = writer.db.get("SELECT harness,provider_session_id FROM sessions WHERE project_id=? AND run_id=? AND session_id=?", [project.id, input.runId, sessionId]);
|
|
170
|
+
if (!session?.harness || !session.provider_session_id)
|
|
171
|
+
refuse("physical_owner_missing");
|
|
172
|
+
return session;
|
|
173
|
+
};
|
|
174
|
+
const repair = stage !== "repair" || replay ? readAcceptedDescriptorStageSettlement(writer, { ...scope, stage: "repair" }, bundle, repairKeys) : null;
|
|
175
|
+
const verify = stage === "review" || stage === "finish" ? readAcceptedDescriptorStageSettlement(writer, { ...scope, stage: "verify" }, bundle, verifyKeys) : null;
|
|
176
|
+
const review = stage === "finish" ? readAcceptedDescriptorStageSettlement(writer, { ...scope, stage: "review" }, bundle, reviewKeys) : null;
|
|
177
|
+
const authorId = repair?.binding.sessionId ?? settlement.coordinatorSessionId(), author = physical(authorId);
|
|
178
|
+
const authorProof = repair ? { work_id: repair.work_id, work_session_id: repair.work_session_id, session_id: authorId,
|
|
179
|
+
...author, start_hook: repair.binding.startHook, association_sha256: repair.binding.associationSha256,
|
|
180
|
+
physical_binding_sha256: repair.binding.physicalBindingSha256, prompt_path: repair.binding.promptPath }
|
|
181
|
+
: readActiveDescriptorStageCoordinator(writer, scope, bundle);
|
|
182
|
+
if (publication && hybridDigest(publication.binding.author) !== hybridDigest(authorProof))
|
|
183
|
+
refuse("publication_author_changed");
|
|
184
|
+
if (stage === "finish") {
|
|
185
|
+
assertDiagnosticSessionIndependence([physical(verify.binding.sessionId)], [author]);
|
|
186
|
+
assertDiagnosticSessionIndependence([physical(review.binding.sessionId)], [author, physical(verify.binding.sessionId)]);
|
|
187
|
+
}
|
|
188
|
+
else if (stage !== "repair")
|
|
189
|
+
assertDiagnosticSessionIndependence([physical(settlement.coordinatorSessionId())], [author, ...(verify ? [physical(verify.binding.sessionId)] : [])]);
|
|
190
|
+
return { repair, verify, review, author };
|
|
191
|
+
};
|
|
192
|
+
const owner = context.db.readSnapshot(() => readOwner(context));
|
|
193
|
+
const closure = stage === "finish" ? memoryFixClosureProjection(result) : null;
|
|
194
|
+
const reportRoot = path.join(packet.runRoot, bundle.descriptor.stage(stage).directory);
|
|
195
|
+
const retainedReport = stage === "finish" ? readTerminalReport(context, scope, packet.attempt, reportRoot) : null;
|
|
196
|
+
if (stage === "finish" && replay && !retainedReport)
|
|
197
|
+
refuse("accepted_terminal_report_missing");
|
|
198
|
+
const acceptedRepair = !!closure && !!publication && !closure.some(item => item.status === "open");
|
|
199
|
+
const integration = stage === "finish" && acceptedRepair && (!replay || run.status !== "done")
|
|
200
|
+
? prepareMemoryFixFinishIntegration(context, scope, bundle) : null;
|
|
201
|
+
const integrationOutcome = replay && retainedReport ? retainedReport.normalized.integration
|
|
202
|
+
: integration ? { state: "integrated", observation: integration.value() }
|
|
203
|
+
: { state: publication ? "not_integrated_open_findings" : "not_applicable" };
|
|
204
|
+
const report = closure ? prepareStageReport({ run_id: input.runId, stage: "finish",
|
|
205
|
+
verdict: closure.some(item => item.status === "open") ? "completed_with_open_findings" : "completed", summary: result.summary,
|
|
206
|
+
memory_fix: { result, closure, source: source.intake.source, source_intake: source.reference,
|
|
207
|
+
unselected_ids: source.intake.unselected_ids, limitations: source.intake.limitations }, integration: integrationOutcome,
|
|
208
|
+
semantic: { acceptance: closure.map(item => `${item.finding_id}: ${item.status}; route=${item.route}`),
|
|
209
|
+
checks: result.details.verification.map(item => `${item.finding_id}: ${item.verdict}; ${item.summary}`),
|
|
210
|
+
evidence: [...result.details.verification, ...result.details.review].flatMap(item => item.evidence.map(ref => `${ref.root}:${ref.path}@${ref.sha256}`)),
|
|
211
|
+
next_action: closure.some(item => item.status === "open") ? "Open findings remain open; retain unintegrated work for explicit follow-up. Do not silently retry or close DEF records."
|
|
212
|
+
: "Only this accepted scope is complete; routed, deferred and unselected findings are not repaired by this RUN." } }) : null;
|
|
213
|
+
if (report && Buffer.byteLength(JSON.stringify(report.bytes)) > 7 * 1024 * 1024)
|
|
214
|
+
refuse("terminal_report_exceeds_bound");
|
|
215
|
+
if (retainedReport && report && retainedReport.sha256 !== report.sha256)
|
|
216
|
+
refuse("accepted_terminal_report_changed");
|
|
217
|
+
integration?.revalidate();
|
|
218
|
+
if (!replay && publication) {
|
|
219
|
+
const observed = publication.plan.classify(run.workspace_root);
|
|
220
|
+
if (observed.completed !== observed.total)
|
|
221
|
+
refuse("publication_not_observed");
|
|
222
|
+
}
|
|
223
|
+
const fence = (writer) => {
|
|
224
|
+
assertMemoryFixSourceIntakeCustody(writer, scope, bundle, source.sha256);
|
|
225
|
+
if (hybridDigest(readAcceptedStageData(writer, { ...scope, stage: previousStage })) !== hybridDigest(previous)
|
|
226
|
+
|| hybridDigest(readAcceptedMemoryFixIntakeEvidence(writer, scope, intakeFile)) !== hybridDigest(intake)
|
|
227
|
+
|| hybridDigest(readOwner(writer)) !== hybridDigest(owner))
|
|
228
|
+
refuse("stage_authority_changed");
|
|
229
|
+
const current = readMemoryFixPublication(writer, { project_id: project.id, run_id: input.runId });
|
|
230
|
+
if (publication && (!current?.completed || current.plan.sha256 !== publication.plan.sha256 || hybridDigest(current.binding) !== hybridDigest(publication.binding)
|
|
231
|
+
|| hybridDigest(readMemoryFixPublicationReference(writer, current, reference.path)) !== hybridDigest(reference)))
|
|
232
|
+
refuse("publication_changed");
|
|
233
|
+
if (!publication && current)
|
|
234
|
+
refuse("publication_without_accepted_scope");
|
|
235
|
+
if (stage === "repair" && !replay && intake.user_inputs_sha256 !== hybridDigest(readPreparedMemoryFlowUserInputs(writer, scope, "mb-fix", bundle)))
|
|
236
|
+
refuse("user_inputs_changed");
|
|
237
|
+
const rows = writer.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='flow_run.stage_packet' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.stage')=? AND json_extract(payload_json,'$.attempt')=? LIMIT 33", [project.id, input.runId, stage, packet.attempt]);
|
|
238
|
+
if (rows.length > 32 || rows.some(row => Buffer.byteLength(row.payload_json) > 8 * 1024 * 1024))
|
|
239
|
+
refuse("readback_custody_invalid");
|
|
240
|
+
const packets = rows.map(row => JSON.parse(row.payload_json)).filter(value => typeof value.file === "string"
|
|
241
|
+
&& resolveStagePublicationPath(writer, project.id, input.runId, value.file) === readback.file);
|
|
242
|
+
if (packets.length > 1 || packets.some(value => value.sha256 !== readback.sha256 || value.base64 !== readback.bytes.toString("base64")))
|
|
243
|
+
refuse("readback_custody_changed");
|
|
244
|
+
if (!packets.length) {
|
|
245
|
+
if (replay)
|
|
246
|
+
refuse("accepted_readback_missing");
|
|
247
|
+
acceptStagePacket(writer, { ...scope, prepared: readback });
|
|
248
|
+
}
|
|
249
|
+
integration?.assertCustody(writer);
|
|
250
|
+
if (report) {
|
|
251
|
+
const currentReport = readTerminalReport(writer, scope, packet.attempt, reportRoot);
|
|
252
|
+
if (currentReport && currentReport.sha256 !== report.sha256)
|
|
253
|
+
refuse("terminal_report_changed");
|
|
254
|
+
if (!currentReport) {
|
|
255
|
+
if (replay)
|
|
256
|
+
refuse("accepted_terminal_report_missing");
|
|
257
|
+
acceptStageReport(writer, { ...scope, root: reportRoot, prepared: report });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
try {
|
|
262
|
+
const accepted = settlement.apply(context, fence);
|
|
263
|
+
if (stage !== "finish")
|
|
264
|
+
return accepted;
|
|
265
|
+
integration?.revalidate();
|
|
266
|
+
const complete = PreparedDescriptorRunSettlement.prepare(context, input).apply(context, fence);
|
|
267
|
+
publishStageReport(context, project.id, input.runId, "finish", reportRoot);
|
|
268
|
+
return { ...accepted, ...complete, closure, reports: ["json", "md", "html"].map(extension => path.join(reportRoot, `stage-report.${extension}`)) };
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
if (stage !== "finish")
|
|
272
|
+
throw error;
|
|
273
|
+
let committed = false;
|
|
274
|
+
try {
|
|
275
|
+
committed = readAcceptedStageData(context, scope).bytes.equals(bytes);
|
|
276
|
+
}
|
|
277
|
+
catch { /* No accepted terminal data. */ }
|
|
278
|
+
if (!committed)
|
|
279
|
+
throw error;
|
|
280
|
+
throw new AppError("memory_fix_finish_pending", "Accepted finish requires projection or RUN settlement reconciliation", 1, { effect: "committed", run_id: input.runId, next_action: "reconcile_accepted_finish", cause: error instanceof AppError ? error.code : "finish_settlement_failed" });
|
|
281
|
+
}
|
|
282
|
+
}
|