@deksden-com/dd-flow-cli 0.9.0-beta.137 → 0.9.0-beta.138
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 +4 -0
- package/README.md +9 -0
- package/dist/build-info.json +6 -6
- package/dist/cli/argument-values.js +5 -5
- package/dist/cli/command-inputs.js +89 -20
- package/dist/cli/command-metadata.js +197 -0
- package/dist/cli/command-option-descriptions.js +191 -0
- package/dist/cli/completion-cli.js +70 -0
- package/dist/cli/completion.js +57 -0
- package/dist/cli/error-output.js +52 -0
- package/dist/cli/executor-onboarding.js +76 -0
- package/dist/cli/flows.js +29 -0
- package/dist/cli/harnesses.js +84 -0
- package/dist/cli/help.js +240 -6
- package/dist/cli/input-preparation.js +54 -5
- package/dist/cli/memory-flow-admission.js +44 -0
- package/dist/cli/memory-init-invocation.js +78 -0
- package/dist/cli/memory-init.js +240 -0
- package/dist/cli/memory-storage-admission.js +53 -0
- package/dist/cli/models.js +39 -0
- package/dist/cli/native-hook-ingress.js +1 -1
- package/dist/cli/operator-profiles.js +61 -0
- package/dist/cli/output-options.js +35 -0
- package/dist/cli/response-file.js +71 -0
- package/dist/cli/run-cli.js +283 -160
- package/dist/cli/setup.js +149 -0
- package/dist/cli.js +54 -2
- package/dist/domain/stage-handler-identities.js +20 -0
- package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +4 -3
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +4 -2
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +11 -5
- package/dist/harness-runtime/lib/dd-codex.mjs +22 -10
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +2 -1
- package/dist/harness-runtime/lib/dd-droid.mjs +2 -1
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +4 -2
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +6 -4
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +9 -9
- package/dist/harness-runtime/lib/delegation-instructions.mjs +5 -3
- package/dist/harness-runtime/lib/lifecycle-command.mjs +1 -0
- package/dist/harness-runtime/lib/managed-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +50 -15
- package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
- package/dist/harness-runtime/lib/native-hook-command.mjs +1 -1
- package/dist/harness-runtime/lib/native-request-proof.d.mts +2 -0
- package/dist/harness-runtime/lib/native-request-proof.mjs +65 -0
- package/dist/harness-runtime/lib/opencode-plugin.mjs +2 -0
- package/dist/harness-runtime/lib/operation-progress.mjs +3 -1
- package/dist/runtime/context.js +1 -1
- package/dist/schemas/discovery-result.schema.json +184 -0
- package/dist/schemas/execution-bundle.schema.json +205 -0
- package/dist/schemas/execution-settings.schema.json +162 -0
- package/dist/schemas/flow-descriptor.schema.json +54 -0
- package/dist/schemas/flow-run.schema.json +22 -0
- package/dist/schemas/harness-catalog.schema.json +80 -0
- package/dist/schemas/mb-init-compose-result.schema.json +457 -0
- package/dist/schemas/mb-init-discover-result.schema.json +500 -0
- package/dist/schemas/mb-init-finish-result.schema.json +422 -0
- package/dist/schemas/mb-init-preflight-result.schema.json +367 -0
- package/dist/schemas/mb-init-review-result.schema.json +396 -0
- package/dist/schemas/mb-init-verify-result.schema.json +391 -0
- package/dist/schemas/memory-bootstrap-receipt-v2.schema.json +67 -0
- package/dist/schemas/memory-bootstrap-receipt.schema.json +354 -0
- package/dist/schemas/memory-composition-work-result.schema.json +38 -0
- package/dist/schemas/memory-repair-work-result.schema.json +49 -0
- package/dist/schemas/memory-review-correction-plan.schema.json +30 -0
- package/dist/schemas/memory-review-work-result.schema.json +43 -0
- package/dist/schemas/model-catalog.schema.json +153 -0
- package/dist/schemas/profile-settings.schema.json +122 -0
- package/dist/schemas/release-manifest.schema.json +291 -0
- package/dist/schemas/run-engine-binding.schema.json +1 -1
- package/dist/services/check-runner-evidence.js +51 -4
- package/dist/services/cli-operation-classifier.js +8 -47
- package/dist/services/cli-upgrade-allowlist.js +27 -0
- package/dist/services/code-checks.js +387 -34
- package/dist/services/controller-execution.js +88 -0
- package/dist/services/controller-fanout.js +71 -12
- package/dist/services/dashboard.js +8 -2
- package/dist/services/descriptor-coordinator-session.js +35 -0
- package/dist/services/descriptor-root-work.js +53 -0
- package/dist/services/descriptor-run-settlement.js +105 -0
- package/dist/services/descriptor-run-start.js +449 -0
- package/dist/services/descriptor-stage-coordinator.js +197 -0
- package/dist/services/descriptor-stage-settlement.js +222 -0
- package/dist/services/descriptor-stage-start.js +139 -0
- package/dist/services/discovery/agy-discovery.js +2 -0
- package/dist/services/discovery/codex-discovery.js +2 -0
- package/dist/services/discovery/droid-discovery.js +2 -0
- package/dist/services/discovery/grok-discovery.js +2 -0
- package/dist/services/discovery/opencode-discovery.js +2 -0
- package/dist/services/discovery/static.js +31 -0
- package/dist/services/discovery/zcode-discovery.js +2 -0
- package/dist/services/engines.js +39 -22
- package/dist/services/eval-snapshots.js +83 -25
- package/dist/services/execution-bundles.js +263 -0
- package/dist/services/execution-policy.js +20 -16
- package/dist/services/executor-onboarding.js +56 -0
- package/dist/services/external-work-launch.js +91 -11
- package/dist/services/flow-catalog.js +21 -0
- package/dist/services/flow-descriptors.js +82 -0
- package/dist/services/frozen-engine.js +242 -0
- package/dist/services/generic-execution.js +164 -0
- package/dist/services/harness-adapter.js +28 -2
- package/dist/services/harness-discovery.js +29 -0
- package/dist/services/harness-doctor.js +75 -0
- package/dist/services/harness-probe-effects.js +51 -0
- package/dist/services/harness-version-probe.js +100 -0
- package/dist/services/hooks.js +24 -6
- package/dist/services/hybrid-contracts.js +483 -0
- package/dist/services/ids.js +29 -1
- package/dist/services/installation-lock.js +95 -0
- package/dist/services/installed-flow-source.js +183 -0
- package/dist/services/lifecycle-invocations.js +257 -78
- package/dist/services/memory-accepted-composition.js +77 -0
- package/dist/services/memory-baseline-preservation.js +65 -0
- package/dist/services/memory-completed-correction-wave.js +320 -0
- package/dist/services/memory-compose-handler.js +258 -0
- package/dist/services/memory-composition-acceptance.js +199 -0
- package/dist/services/memory-composition-graph.js +134 -0
- package/dist/services/memory-composition-registration.js +146 -0
- package/dist/services/memory-composition-work-custody.js +151 -0
- package/dist/services/memory-composition-work-results.js +96 -0
- package/dist/services/memory-corrected-review-acceptance.js +129 -0
- package/dist/services/memory-corrected-review-batch.js +30 -0
- package/dist/services/memory-corrected-review-findings.js +106 -0
- package/dist/services/memory-corrected-review-inputs.js +210 -0
- package/dist/services/memory-corrected-review-projection.js +78 -0
- package/dist/services/memory-corrected-review-receipt.js +170 -0
- package/dist/services/memory-corrected-review-registration.js +191 -0
- package/dist/services/memory-correction-check-receipt.js +219 -0
- package/dist/services/memory-correction-input-owner.js +6 -0
- package/dist/services/memory-discover-handler.js +104 -0
- package/dist/services/memory-finish-evidence.js +435 -0
- package/dist/services/memory-finish-handler.js +215 -0
- package/dist/services/memory-finish-integration.js +280 -0
- package/dist/services/memory-finish-readiness.js +122 -0
- package/dist/services/memory-git-integration.js +151 -0
- package/dist/services/memory-historical-corrected-review.js +206 -0
- package/dist/services/memory-historical-original-review.js +130 -0
- package/dist/services/memory-historical-repair-registration.js +99 -0
- package/dist/services/memory-historical-second-review.js +128 -0
- package/dist/services/memory-init-bootstrap-readiness.js +210 -0
- package/dist/services/memory-init-contexts.js +89 -0
- package/dist/services/memory-init-controller-start.js +84 -0
- package/dist/services/memory-init-engine-release.js +223 -0
- package/dist/services/memory-init-git-policy.js +136 -0
- package/dist/services/memory-init-lint-capability.js +185 -0
- package/dist/services/memory-init-native-capacity.js +241 -0
- package/dist/services/memory-init-preflight.js +312 -0
- package/dist/services/memory-init-project-bootstrap.js +599 -0
- package/dist/services/memory-init-request-candidate.js +157 -0
- package/dist/services/memory-init-request-input.js +66 -0
- package/dist/services/memory-init-resource-storage.js +125 -0
- package/dist/services/memory-init-run-allocation.js +81 -0
- package/dist/services/memory-init-run-bundle.js +98 -0
- package/dist/services/memory-init-start.js +175 -0
- package/dist/services/memory-init-startup-custody.js +80 -0
- package/dist/services/memory-init-startup-input.js +240 -0
- package/dist/services/memory-init-storage.js +81 -0
- package/dist/services/memory-init-stores.js +359 -0
- package/dist/services/memory-init-worktrunk-capability.js +172 -0
- package/dist/services/memory-layer-publication.js +611 -0
- package/dist/services/memory-layer.js +156 -0
- package/dist/services/memory-lint-config.js +85 -0
- package/dist/services/memory-native-review-copy.js +165 -0
- package/dist/services/memory-normal-flow-entry.js +75 -0
- package/dist/services/memory-ordinary-defaults.js +112 -0
- package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
- package/dist/services/memory-preflight-handler.js +259 -0
- package/dist/services/memory-repair-current-view.js +299 -0
- package/dist/services/memory-repair-document-delta.js +451 -0
- package/dist/services/memory-repair-history.js +491 -0
- package/dist/services/memory-repair-physical-binding.js +312 -0
- package/dist/services/memory-repair-publication-files.js +152 -0
- package/dist/services/memory-repair-publication-intent.js +27 -0
- package/dist/services/memory-repair-publication-plan.js +160 -0
- package/dist/services/memory-repair-publication-source.js +225 -0
- package/dist/services/memory-repair-publication.js +794 -0
- package/dist/services/memory-repair-registration.js +239 -0
- package/dist/services/memory-repair-work-baseline.js +413 -0
- package/dist/services/memory-repair-work-completion.js +97 -0
- package/dist/services/memory-repair-work-custody.js +73 -0
- package/dist/services/memory-repair-work-results.js +89 -0
- package/dist/services/memory-repair-workspace.js +252 -0
- package/dist/services/memory-repair-writer-copy.js +735 -0
- package/dist/services/memory-result-binding.js +173 -0
- package/dist/services/memory-retained-review-authority.js +129 -0
- package/dist/services/memory-retained-review-results.js +70 -0
- package/dist/services/memory-retained-second-baseline.js +102 -0
- package/dist/services/memory-retained-second-repairs.js +156 -0
- package/dist/services/memory-review-acceptance.js +214 -0
- package/dist/services/memory-review-correction-plan.js +171 -0
- package/dist/services/memory-review-handler.js +585 -0
- package/dist/services/memory-review-inputs.js +200 -0
- package/dist/services/memory-review-receipt.js +180 -0
- package/dist/services/memory-review-registration.js +108 -0
- package/dist/services/memory-review-work-custody.js +124 -0
- package/dist/services/memory-review-work-input.js +72 -0
- package/dist/services/memory-review-work-results.js +73 -0
- package/dist/services/memory-second-corrected-review-inputs.js +165 -0
- package/dist/services/memory-second-correction-plan.js +76 -0
- package/dist/services/memory-second-repair-baseline.js +91 -0
- package/dist/services/memory-second-repair-launch.js +614 -0
- package/dist/services/memory-second-repair-registration.js +88 -0
- package/dist/services/memory-second-repair-source.js +187 -0
- package/dist/services/memory-second-review-acceptance.js +115 -0
- package/dist/services/memory-source-observations.js +117 -0
- package/dist/services/memory-stage-dispatch.js +28 -0
- package/dist/services/memory-stage-results.js +199 -0
- package/dist/services/memory-verification-acceptance.js +135 -0
- package/dist/services/memory-verification-checks.js +252 -0
- package/dist/services/memory-verification-receipt.js +159 -0
- package/dist/services/memory-verify-handler.js +135 -0
- package/dist/services/memory-work-result-preparation.js +63 -0
- package/dist/services/model-catalog.js +109 -0
- package/dist/services/model-discovery.js +46 -0
- package/dist/services/operator-profiles.js +236 -0
- package/dist/services/projects.js +17 -2
- package/dist/services/recovery-snapshot-database.js +416 -1
- package/dist/services/release-archive-producer.js +52 -0
- package/dist/services/release-archive.js +143 -0
- package/dist/services/release-cache.js +142 -0
- package/dist/services/release-compatibility.js +18 -0
- package/dist/services/release-download.js +67 -0
- package/dist/services/release-envelope.js +104 -0
- package/dist/services/release-manifest.js +45 -0
- package/dist/services/release-payload.js +114 -0
- package/dist/services/release-producer.js +63 -0
- package/dist/services/release-provenance.js +160 -0
- package/dist/services/release-registry.js +66 -0
- package/dist/services/release-source.js +194 -0
- package/dist/services/repair-intents.js +1 -1
- package/dist/services/review-copy.js +10 -2
- package/dist/services/run-control.js +80 -9
- package/dist/services/run-controller-capture.js +10 -3
- package/dist/services/run-controller-process.js +21 -0
- package/dist/services/run-controller-recovery.js +3 -1
- package/dist/services/run-controller-state.js +1 -1
- package/dist/services/run-controller.js +101 -40
- package/dist/services/run-recovery-guard.js +2 -0
- package/dist/services/run-recovery.js +145 -5
- package/dist/services/run-start-custody.js +177 -0
- package/dist/services/runs.js +319 -25
- package/dist/services/setup.js +377 -0
- package/dist/services/stage-context.js +9 -1
- package/dist/services/stage-handlers.js +74 -0
- package/dist/services/stage-report-renderer.js +63 -0
- package/dist/services/stage-work-graph.js +17 -0
- package/dist/services/vnext-code.js +3 -3
- package/dist/services/vnext-execution-profile.js +2 -2
- package/dist/services/vnext-fanout.js +70 -9
- package/dist/services/vnext-merge.js +8 -3
- package/dist/services/vnext-plan-review.js +6 -4
- package/dist/services/work-registry.js +616 -71
- package/dist/services/workspace-files.js +1 -1
- package/dist/storage/database.js +84 -6
- package/dist/storage/writer-contract.js +11 -7
- package/package.json +13 -3
- package/tools/create-canon-release.mjs +33 -0
- package/tools/produce-hybrid-release.mjs +41 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
2
|
+
import { AppError } from "../shared/errors.js";
|
|
3
|
+
import { ResolvedFlowDescriptor } from "./flow-descriptors.js";
|
|
4
|
+
import { FrozenGenericExecution } from "./generic-execution.js";
|
|
5
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
6
|
+
import { ObservedMemorySources } from "./memory-source-observations.js";
|
|
7
|
+
import { memorySourceKey } from "./memory-stage-results.js";
|
|
8
|
+
import { assertPreparedRunBundleAuthority, assertPreparedRunBundleSqlAuthority, assertRunBundleSqlAuthority } from "./runs.js";
|
|
9
|
+
import { readAcceptedStageData } from "./stage-report-renderer.js";
|
|
10
|
+
import { PreparedMemoryBaselinePreservation } from "./memory-baseline-preservation.js";
|
|
11
|
+
import { readAcceptedDescriptorStageSettlement } from "./descriptor-stage-settlement.js";
|
|
12
|
+
import { entityReferenceVariants } from "../shared/entity-references.js";
|
|
13
|
+
import { stageLifecycleMatchKey } from "./hooks.js";
|
|
14
|
+
import { captureRegisteredMemoryCompositionSources } from "./memory-preflight-handler.js";
|
|
15
|
+
import { memoryCompositionRegistrationDigest } from "./memory-composition-registration.js";
|
|
16
|
+
import { PreparedCheckEvidence } from "./code-checks.js";
|
|
17
|
+
function refuse() { throw new AppError("memory_result_binding_custody_invalid", "Memory result binding lacks independently accepted stage custody", 1, { effect: "no_effect" }); }
|
|
18
|
+
/** Select identity, predecessor and unit coverage from SQL, not result fields. */
|
|
19
|
+
function select(context, input) {
|
|
20
|
+
if (input.checks !== undefined) {
|
|
21
|
+
if (!(input.checks instanceof PreparedCheckEvidence))
|
|
22
|
+
refuse();
|
|
23
|
+
input.checks.assertOwner({ ...input, scope: "aggregate" });
|
|
24
|
+
input.checks.assertCustody(context);
|
|
25
|
+
}
|
|
26
|
+
const run = context.db.get("SELECT index_json, project_root, workspace_root FROM runs WHERE project_id = ? AND id = ?", [input.projectId, input.runId]);
|
|
27
|
+
if (!run)
|
|
28
|
+
refuse();
|
|
29
|
+
const index = JSON.parse(run.index_json), frozen = input.execution.snapshot();
|
|
30
|
+
if (assertRunBundleSqlAuthority(context, input, index.execution_bundle)) {
|
|
31
|
+
if (!input.bundle || input.bundle.execution.sha256 !== input.execution.sha256 || input.bundle.descriptor.sha256 !== input.flow.sha256)
|
|
32
|
+
refuse();
|
|
33
|
+
assertPreparedRunBundleSqlAuthority(context, input, input.bundle);
|
|
34
|
+
}
|
|
35
|
+
const stage = index.stage_runs?.find((item) => item.stage === input.stage);
|
|
36
|
+
if (input.flow.descriptor.id !== "mb-init" || frozen.settings.flow_id !== "mb-init" || frozen.settings.descriptor_sha256 !== input.flow.sha256
|
|
37
|
+
|| index.execution_bundle && (index.execution_bundle.flow_id !== "mb-init" || index.execution_bundle.descriptor_sha256 !== input.flow.sha256)
|
|
38
|
+
|| frozen.scope.projectRoot !== run.project_root || frozen.scope.workspace !== run.workspace_root
|
|
39
|
+
|| (input.acceptedReplay ? stage?.status !== "done" : index.current_stage !== input.stage || stage?.status !== "running") || stage.dir !== input.flow.stage(input.stage).directory
|
|
40
|
+
|| typeof stage.attempt !== "string" || !/^try-[0-9]{3,}$/.test(stage.attempt)
|
|
41
|
+
|| typeof stage.input_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(stage.input_sha256))
|
|
42
|
+
refuse();
|
|
43
|
+
const accepted = input.acceptedReplay ? readAcceptedStageData(context, input) : null;
|
|
44
|
+
const acceptedValue = accepted ? JSON.parse(accepted.bytes.toString("utf8")) : null;
|
|
45
|
+
if (accepted && (accepted.data_schema_id !== `dd-flow/mb-init-${input.stage}-result@1`
|
|
46
|
+
|| acceptedValue.schema_id !== accepted.data_schema_id || acceptedValue.run_id !== input.runId
|
|
47
|
+
|| acceptedValue.stage !== input.stage || !(acceptedValue.outcome === "accepted" || input.stage === "verify" && acceptedValue.outcome === "needs_repair" && input.flow.successor("verify", "needs_repair") === "review")
|
|
48
|
+
|| acceptedValue.input_sha256 !== stage.input_sha256))
|
|
49
|
+
refuse();
|
|
50
|
+
const settlement = accepted && input.bundle ? readAcceptedDescriptorStageSettlement(context, input, input.bundle, input.matchKeys) : null;
|
|
51
|
+
const edges = input.flow.descriptor.edges.filter(edge => edge.to === input.stage && edge.on === "accepted");
|
|
52
|
+
const entry = input.stage === input.flow.descriptor.entry_stage;
|
|
53
|
+
if (edges.length !== (entry ? 0 : 1))
|
|
54
|
+
refuse();
|
|
55
|
+
const predecessor = entry ? null : readAcceptedStageData(context, { ...input, stage: edges[0].from });
|
|
56
|
+
const value = predecessor ? JSON.parse(predecessor.bytes.toString("utf8")) : null;
|
|
57
|
+
const preflight = entry ? null : readAcceptedStageData(context, { ...input, stage: "preflight" });
|
|
58
|
+
const preflightValue = preflight ? JSON.parse(preflight.bytes.toString("utf8")) : null;
|
|
59
|
+
if (preflight && (preflight.data_schema_id !== "dd-flow/mb-init-preflight-result@1"
|
|
60
|
+
|| preflightValue.schema_id !== preflight.data_schema_id || preflightValue.run_id !== input.runId
|
|
61
|
+
|| preflightValue.stage !== "preflight" || preflightValue.outcome !== "accepted"
|
|
62
|
+
|| preflightValue.input_sha256 !== preflight.input_sha256
|
|
63
|
+
|| typeof preflight.input_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(preflight.input_sha256)))
|
|
64
|
+
refuse();
|
|
65
|
+
if (predecessor && (predecessor.data_schema_id !== `dd-flow/mb-init-${edges[0].from}-result@1`
|
|
66
|
+
|| value.schema_id !== predecessor.data_schema_id || value.run_id !== input.runId || value.stage !== edges[0].from
|
|
67
|
+
|| !(value.outcome === "accepted" || edges[0].from === "verify" && value.outcome === "needs_repair" && input.flow.successor("verify", "needs_repair") === input.stage)
|
|
68
|
+
|| typeof predecessor.input_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(predecessor.input_sha256)
|
|
69
|
+
|| value.input_sha256 !== predecessor.input_sha256 || !Array.isArray(value.units)))
|
|
70
|
+
refuse();
|
|
71
|
+
const unitIds = ["preflight", "discover"].includes(input.stage) ? null : value?.units.map((unit) => unit.id);
|
|
72
|
+
if (unitIds !== null && (!Array.isArray(unitIds) || unitIds.some(id => typeof id !== "string") || new Set(unitIds).size !== unitIds.length))
|
|
73
|
+
refuse();
|
|
74
|
+
const binding = { runId: input.runId, stage: input.stage, inputSha256: stage.input_sha256,
|
|
75
|
+
predecessors: predecessor ? [{ stage: edges[0].from, result: predecessor.result, input_sha256: predecessor.input_sha256 }] : [],
|
|
76
|
+
unitIds, sources: new Map([...input.sources.hashes(), ...(input.checks?.artifactReferences() ?? []).map(ref => [memorySourceKey(ref.root, ref.path), ref.sha256])]),
|
|
77
|
+
historicalEvidence: new Set([...input.historicalEvidence ?? [], ...(value?.units ?? []).flatMap((unit) => unit.evidence.map(ref => `${memorySourceKey(ref.root, ref.path)}\0${ref.sha256}`))]),
|
|
78
|
+
execution: input.execution, executionSha256: input.execution.sha256,
|
|
79
|
+
...(preflight ? { preflight: { result: preflight.result, input_sha256: preflight.input_sha256,
|
|
80
|
+
semantic_scope: preflightValue.details.semantic_scope } } : {}) };
|
|
81
|
+
const authority = hybridDigest({ projectId: input.projectId, runId: input.runId, stage: input.stage,
|
|
82
|
+
attempt: stage.attempt, input: stage.input_sha256, directory: stage.dir, predecessor: predecessor?.authority_sha256 ?? null,
|
|
83
|
+
anchor: index.execution_bundle ?? null, execution: input.execution.sha256, preflight: preflight?.authority_sha256 ?? null,
|
|
84
|
+
accepted: accepted?.authority_sha256 ?? null, settlement: settlement ? hybridDigest(settlement) : null });
|
|
85
|
+
return { binding, authority, predecessorStage: edges[0]?.from, value, preflightValue, accepted, acceptedValue };
|
|
86
|
+
}
|
|
87
|
+
/** Controller-owned input for result preparation and writer-safe rechecking.
|
|
88
|
+
* Semantic/check acceptance still belongs to productive stage handlers.
|
|
89
|
+
*/
|
|
90
|
+
export class PreparedMemoryResultBinding {
|
|
91
|
+
#selection;
|
|
92
|
+
#binding;
|
|
93
|
+
#authority;
|
|
94
|
+
#preservation;
|
|
95
|
+
#accepted;
|
|
96
|
+
#registration = null;
|
|
97
|
+
constructor(input, selected, preservation) {
|
|
98
|
+
this.#selection = { ...input };
|
|
99
|
+
this.#binding = selected.binding;
|
|
100
|
+
this.#authority = selected.authority;
|
|
101
|
+
this.#preservation = preservation;
|
|
102
|
+
this.#accepted = selected.accepted ? Buffer.from(selected.accepted.bytes) : null;
|
|
103
|
+
Object.freeze(this);
|
|
104
|
+
}
|
|
105
|
+
static prepare(context, input) {
|
|
106
|
+
if (!(input.flow instanceof ResolvedFlowDescriptor) || !(input.execution instanceof FrozenGenericExecution) || !(input.sources instanceof ObservedMemorySources))
|
|
107
|
+
refuse();
|
|
108
|
+
if (input.acceptedReplay !== undefined && typeof input.acceptedReplay !== "boolean")
|
|
109
|
+
refuse();
|
|
110
|
+
if (input.bundle)
|
|
111
|
+
assertPreparedRunBundleAuthority(context, input, input.bundle);
|
|
112
|
+
const preparedInput = { ...input, matchKeys: input.acceptedReplay && input.bundle
|
|
113
|
+
? entityReferenceVariants(input.runId).map(reference => stageLifecycleMatchKey("stage_finish", reference, input.stage, input.execution.snapshot().scope.projectRoot)) : [] };
|
|
114
|
+
const selected = context.db.readSnapshot(() => select(context, preparedInput));
|
|
115
|
+
if (selected.acceptedValue) {
|
|
116
|
+
const schema = JSON.parse(input.flow.stageBlob(input.stage, "result_schema").toString("utf8"));
|
|
117
|
+
if (!new Ajv2020({ strict: true, strictRequired: false, validateFormats: false }).compile(schema)(selected.acceptedValue))
|
|
118
|
+
refuse();
|
|
119
|
+
}
|
|
120
|
+
if (selected.predecessorStage) {
|
|
121
|
+
const schema = JSON.parse(input.flow.stageBlob(selected.predecessorStage, "result_schema").toString("utf8"));
|
|
122
|
+
if (!new Ajv2020({ strict: true, strictRequired: false, validateFormats: false }).compile(schema)(selected.value))
|
|
123
|
+
refuse();
|
|
124
|
+
}
|
|
125
|
+
if (selected.preflightValue) {
|
|
126
|
+
const schema = JSON.parse(input.flow.stageBlob("preflight", "result_schema").toString("utf8"));
|
|
127
|
+
if (!new Ajv2020({ strict: true, strictRequired: false, validateFormats: false }).compile(schema)(selected.preflightValue))
|
|
128
|
+
refuse();
|
|
129
|
+
}
|
|
130
|
+
// Productive bound discovery/composition preserve independently accepted source
|
|
131
|
+
// bytes. Unbound data-only SDK fixtures gain no bootstrap qualification.
|
|
132
|
+
const preservation = input.bundle && ["discover", "compose"].includes(input.stage) ? PreparedMemoryBaselinePreservation.prepare(context, input.execution) : null;
|
|
133
|
+
const prepared = new PreparedMemoryResultBinding(preparedInput, selected, preservation);
|
|
134
|
+
prepared.assertCustody(context);
|
|
135
|
+
return prepared;
|
|
136
|
+
}
|
|
137
|
+
static prepareRegisteredComposition(context, input) {
|
|
138
|
+
if (input.registrationScope.project_id !== input.projectId || input.registrationScope.run_id !== input.runId
|
|
139
|
+
|| input.bundle.execution.sha256 !== input.execution.sha256 || input.bundle.descriptor.sha256 !== input.flow.sha256)
|
|
140
|
+
refuse();
|
|
141
|
+
const observed = captureRegisteredMemoryCompositionSources(context, input.bundle, input.registrationScope);
|
|
142
|
+
const prepared = this.prepare(context, { ...input, stage: "compose", sources: observed.sources });
|
|
143
|
+
if (prepared.#binding.inputSha256 !== observed.inputSha256)
|
|
144
|
+
refuse();
|
|
145
|
+
prepared.#registration = { scope: structuredClone(input.registrationScope), sha256: observed.registrationSha256 };
|
|
146
|
+
prepared.#binding = { ...prepared.#binding, sources: new Map([...prepared.#binding.sources, ...observed.historical]) };
|
|
147
|
+
prepared.assertCustody(context);
|
|
148
|
+
return prepared;
|
|
149
|
+
}
|
|
150
|
+
binding() {
|
|
151
|
+
return { ...this.#binding, historicalEvidence: new Set(this.#binding.historicalEvidence), predecessors: structuredClone(this.#binding.predecessors), unitIds: structuredClone(this.#binding.unitIds),
|
|
152
|
+
...(this.#binding.preflight ? { preflight: structuredClone(this.#binding.preflight) } : {}), sources: new Map(this.#binding.sources) };
|
|
153
|
+
}
|
|
154
|
+
/** Retained accepted data for handler replay, never caller-supplied bytes. */
|
|
155
|
+
acceptedBytes() { return this.#accepted ? Buffer.from(this.#accepted) : null; }
|
|
156
|
+
assertCustody(context) {
|
|
157
|
+
context.db.readSnapshot(() => {
|
|
158
|
+
this.#preservation?.assertCustody(context);
|
|
159
|
+
if (this.#registration && memoryCompositionRegistrationDigest(context, this.#registration.scope) !== this.#registration.sha256)
|
|
160
|
+
refuse();
|
|
161
|
+
if (select(context, this.#selection).authority !== this.#authority)
|
|
162
|
+
refuse();
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
revalidate(context) {
|
|
166
|
+
this.#selection.checks?.revalidate(context);
|
|
167
|
+
this.#preservation?.revalidate(context);
|
|
168
|
+
this.#selection.sources.revalidate();
|
|
169
|
+
if (this.#selection.bundle)
|
|
170
|
+
assertPreparedRunBundleAuthority(context, this.#selection, this.#selection.bundle);
|
|
171
|
+
this.assertCustody(context);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
3
|
+
import { RetainedExecutionBundle } from "./execution-bundles.js";
|
|
4
|
+
import { assertPreparedRunBundleSqlAuthority } from "./runs.js";
|
|
5
|
+
import { assertRunMutationAllowed } from "./run-recovery-guard.js";
|
|
6
|
+
import { readMemoryReviewPhase } from "./memory-review-inputs.js";
|
|
7
|
+
import { readRegisteredMemoryReview } from "./memory-review-registration.js";
|
|
8
|
+
import { PreparedDescriptorStageCoordinator } from "./descriptor-stage-coordinator.js";
|
|
9
|
+
import { readDescriptorCoordinatorSession } from "./descriptor-coordinator-session.js";
|
|
10
|
+
import { PreparedMemoryBaselinePreservation } from "./memory-baseline-preservation.js";
|
|
11
|
+
import { PreparedAcceptedMemoryVerification } from "./memory-verification-receipt.js";
|
|
12
|
+
import { PreparedMemoryResultBinding } from "./memory-result-binding.js";
|
|
13
|
+
import { ObservedMemorySources } from "./memory-source-observations.js";
|
|
14
|
+
import { readAcceptedStageData } from "./stage-report-renderer.js";
|
|
15
|
+
import { memorySourceKey } from "./memory-stage-results.js";
|
|
16
|
+
function refuse(reason) {
|
|
17
|
+
throw new AppError("memory_retained_review_invalid", "Retained review lacks independent original-wave authority", 2, { reason, effect: "no_effect" });
|
|
18
|
+
}
|
|
19
|
+
function original(context, scope, bundle) {
|
|
20
|
+
const phase = readMemoryReviewPhase(context, scope, bundle), registration = readRegisteredMemoryReview(context, phase, 0);
|
|
21
|
+
const compose = readAcceptedStageData(context, { ...scope, stage: "compose" });
|
|
22
|
+
const verify = readAcceptedStageData(context, { ...scope, stage: "verify" });
|
|
23
|
+
const composed = JSON.parse(compose.bytes.toString("utf8")), verified = JSON.parse(verify.bytes.toString("utf8"));
|
|
24
|
+
const view = registration.view;
|
|
25
|
+
if (registration.input_sha256 !== hybridDigest(view) || view.wave !== 0 || view.scope.bundle_sha256 !== bundle.sha256
|
|
26
|
+
|| view.scope.execution_sha256 !== bundle.execution.sha256 || view.source_policy !== "review-inputs@1"
|
|
27
|
+
|| compose.data_schema_id !== "dd-flow/mb-init-compose-result@1" || composed.schema_id !== compose.data_schema_id
|
|
28
|
+
|| composed.run_id !== scope.runId || composed.stage !== "compose" || composed.outcome !== "accepted"
|
|
29
|
+
|| composed.input_sha256 !== compose.input_sha256 || hybridDigest(composed.units) !== hybridDigest(view.unit_inventory)
|
|
30
|
+
|| verify.data_schema_id !== "dd-flow/mb-init-verify-result@1" || verified.schema_id !== verify.data_schema_id
|
|
31
|
+
|| verified.run_id !== scope.runId || verified.stage !== "verify" || !["accepted", "needs_repair"].includes(verified.outcome)
|
|
32
|
+
|| verified.input_sha256 !== verify.input_sha256)
|
|
33
|
+
refuse("changed_original_review_binding");
|
|
34
|
+
for (const accepted of [compose, verify]) {
|
|
35
|
+
const refs = view.run_inputs.filter(ref => ref.root === "run" && ref.path === accepted.result.path);
|
|
36
|
+
if (refs.length !== 1 || refs[0].sha256 !== accepted.result.sha256)
|
|
37
|
+
refuse("original_predecessor_missing_or_changed");
|
|
38
|
+
}
|
|
39
|
+
return { registration, compose, verify,
|
|
40
|
+
sha256: hybridDigest({ phase, registration, compose: compose.authority_sha256, verify: verify.authority_sha256 }) };
|
|
41
|
+
}
|
|
42
|
+
/** Historical original-wave proof for correction/restart, not current document
|
|
43
|
+
* admission. Changed MB outputs must be justified separately by registered
|
|
44
|
+
* repair settlements; this owner never rebases them or grants Work writes.
|
|
45
|
+
* Original user sources, accepted SQL predecessors, independent verification
|
|
46
|
+
* executions and their retained result files remain mandatory.
|
|
47
|
+
*/
|
|
48
|
+
export class PreparedRetainedMemoryReviewAuthority {
|
|
49
|
+
sha256;
|
|
50
|
+
#scope;
|
|
51
|
+
#bundle;
|
|
52
|
+
#registration;
|
|
53
|
+
#original;
|
|
54
|
+
#generation;
|
|
55
|
+
#coordinator;
|
|
56
|
+
#session;
|
|
57
|
+
#preservation;
|
|
58
|
+
#compose;
|
|
59
|
+
#verify;
|
|
60
|
+
#observed;
|
|
61
|
+
constructor(scope, bundle, selected, generation, coordinator, session, preservation, compose, verify, observed) {
|
|
62
|
+
this.#scope = { ...scope };
|
|
63
|
+
this.#bundle = bundle;
|
|
64
|
+
this.#registration = structuredClone(selected.registration);
|
|
65
|
+
this.#original = selected.sha256;
|
|
66
|
+
this.#generation = generation;
|
|
67
|
+
this.#coordinator = coordinator;
|
|
68
|
+
this.#session = session;
|
|
69
|
+
this.#preservation = preservation;
|
|
70
|
+
this.#compose = compose;
|
|
71
|
+
this.#verify = verify;
|
|
72
|
+
this.#observed = observed;
|
|
73
|
+
this.sha256 = hybridDigest({ registration: selected.registration, original: selected.sha256 });
|
|
74
|
+
Object.freeze(this);
|
|
75
|
+
}
|
|
76
|
+
static prepare(context, scope, bundle) {
|
|
77
|
+
if (!(bundle instanceof RetainedExecutionBundle) || bundle.descriptor.descriptor.id !== "mb-init")
|
|
78
|
+
refuse("registered_memory_bundle_required");
|
|
79
|
+
const generation = assertRunMutationAllowed(context, scope.projectId, scope.runId);
|
|
80
|
+
const selected = context.db.readSnapshot(() => original(context, scope, bundle));
|
|
81
|
+
const run = assertPreparedRunBundleSqlAuthority(context, scope, bundle), root = run.run_root ?? run.run_dir;
|
|
82
|
+
const observed = ObservedMemorySources.capture({ run: root }, [selected.compose, selected.verify].map(data => ({ root: "run", path: data.result.path })));
|
|
83
|
+
const hashes = observed.hashes();
|
|
84
|
+
if ([selected.compose, selected.verify].some(data => hashes.get(memorySourceKey("run", data.result.path)) !== data.result.sha256))
|
|
85
|
+
refuse("retained_predecessor_bytes_changed");
|
|
86
|
+
const coordinator = PreparedDescriptorStageCoordinator.prepare(context, {
|
|
87
|
+
projectRoot: bundle.execution.snapshot().scope.projectRoot, runId: scope.runId, stage: "review"
|
|
88
|
+
});
|
|
89
|
+
coordinator.assertAllocatedCustody(context);
|
|
90
|
+
const session = readDescriptorCoordinatorSession(context, { scope: { ...scope, stage: "review" }, bundle,
|
|
91
|
+
workId: selected.registration.scope.parent_work_id }, refuse);
|
|
92
|
+
const preservation = PreparedMemoryBaselinePreservation.prepare(context, bundle.execution);
|
|
93
|
+
// Historical accepted compose binding intentionally observes only retained
|
|
94
|
+
// predecessor files. It does not claim today's repaired documents still
|
|
95
|
+
// have the original output hashes or rerun current project policy.
|
|
96
|
+
const compose = PreparedMemoryResultBinding.prepare(context, { ...scope, stage: "compose", flow: bundle.descriptor,
|
|
97
|
+
execution: bundle.execution, bundle, acceptedReplay: true, sources: observed });
|
|
98
|
+
if (!compose.acceptedBytes()?.equals(selected.compose.bytes))
|
|
99
|
+
refuse("accepted_composition_changed");
|
|
100
|
+
const verify = PreparedAcceptedMemoryVerification.prepare(context, { ...scope, bytes: selected.verify.bytes });
|
|
101
|
+
const prepared = new PreparedRetainedMemoryReviewAuthority(scope, bundle, selected, generation, coordinator, session, preservation, compose, verify, observed);
|
|
102
|
+
prepared.assertCustody(context);
|
|
103
|
+
return prepared;
|
|
104
|
+
}
|
|
105
|
+
registration() { return structuredClone(this.#registration); }
|
|
106
|
+
bundle() { return this.#bundle; }
|
|
107
|
+
verificationProfileHash(context) {
|
|
108
|
+
this.assertCustody(context);
|
|
109
|
+
return this.#verify.profileHash(context);
|
|
110
|
+
}
|
|
111
|
+
assertCustody(context) {
|
|
112
|
+
assertRunMutationAllowed(context, this.#scope.projectId, this.#scope.runId, this.#generation);
|
|
113
|
+
this.#coordinator.assertAllocatedCustody(context);
|
|
114
|
+
if (original(context, this.#scope, this.#bundle).sha256 !== this.#original
|
|
115
|
+
|| readDescriptorCoordinatorSession(context, { scope: { ...this.#scope, stage: "review" }, bundle: this.#bundle,
|
|
116
|
+
workId: this.#registration.scope.parent_work_id }, refuse) !== this.#session)
|
|
117
|
+
refuse("original_review_authority_changed");
|
|
118
|
+
this.#preservation.assertCustody(context);
|
|
119
|
+
this.#compose.assertCustody(context);
|
|
120
|
+
this.#verify.assertCustody(context);
|
|
121
|
+
}
|
|
122
|
+
revalidate(context) {
|
|
123
|
+
this.#preservation.revalidate(context);
|
|
124
|
+
this.#compose.revalidate(context);
|
|
125
|
+
this.#verify.revalidate(context);
|
|
126
|
+
this.#observed.revalidate();
|
|
127
|
+
this.assertCustody(context);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
3
|
+
import { runtimeHarness } from "./execution-policy.js";
|
|
4
|
+
import { PreparedRetainedMemoryReviewAuthority } from "./memory-retained-review-authority.js";
|
|
5
|
+
import { readMemoryReviewWorkSettlement } from "./memory-review-acceptance.js";
|
|
6
|
+
import { memoryReviewBatch } from "./memory-review-registration.js";
|
|
7
|
+
import { validateMemoryReviewWorkResult } from "./memory-review-work-results.js";
|
|
8
|
+
import { ObservedMemorySources } from "./memory-source-observations.js";
|
|
9
|
+
import { memorySourceKey } from "./memory-stage-results.js";
|
|
10
|
+
function refuse(reason) {
|
|
11
|
+
throw new AppError("memory_retained_review_results_invalid", "Correction lacks retained independent review results", 2, { reason, effect: "no_effect" });
|
|
12
|
+
}
|
|
13
|
+
/** Independent initial-review findings survive authorized document corrections.
|
|
14
|
+
* This proves historical results, not current document completeness, permission
|
|
15
|
+
* to mutate, resolution of a finding, or admission of another reviewer wave.
|
|
16
|
+
*/
|
|
17
|
+
export class PreparedRetainedMemoryReviewResults {
|
|
18
|
+
sha256;
|
|
19
|
+
#owner;
|
|
20
|
+
#workers;
|
|
21
|
+
#results;
|
|
22
|
+
#observed;
|
|
23
|
+
#harness;
|
|
24
|
+
constructor(owner, workers, results, observed, harness) {
|
|
25
|
+
this.#owner = owner;
|
|
26
|
+
this.#workers = workers;
|
|
27
|
+
this.#results = results;
|
|
28
|
+
this.#observed = observed;
|
|
29
|
+
this.#harness = harness;
|
|
30
|
+
this.sha256 = hybridDigest({ original: owner.sha256, workers: workers.map(worker => ({ work_id: worker.workId,
|
|
31
|
+
authority: worker.authority, result: worker.result })), results });
|
|
32
|
+
Object.freeze(this);
|
|
33
|
+
}
|
|
34
|
+
static prepare(context, owner) {
|
|
35
|
+
if (!(owner instanceof PreparedRetainedMemoryReviewAuthority))
|
|
36
|
+
refuse("prepared_original_review_required");
|
|
37
|
+
owner.revalidate(context);
|
|
38
|
+
const registration = owner.registration(), bundle = owner.bundle(), view = registration.view;
|
|
39
|
+
const batch = memoryReviewBatch(view), harness = runtimeHarness(bundle.execution.work("review").worker);
|
|
40
|
+
const workers = context.db.readSnapshot(() => batch.works.map(item => readMemoryReviewWorkSettlement(context, registration, registration.work_ids[item.key], harness)));
|
|
41
|
+
const results = workers.map((worker, index) => validateMemoryReviewWorkResult(worker.bytes, bundle.engine(), {
|
|
42
|
+
workId: worker.workId, packet: batch.works[index].payload.memory_review, references: view.run_inputs
|
|
43
|
+
}));
|
|
44
|
+
const root = context.db.get("SELECT run_root,run_dir FROM runs WHERE project_id=? AND id=?", [view.scope.project_id, view.scope.run_id]);
|
|
45
|
+
if (!root)
|
|
46
|
+
refuse("owned_run_missing");
|
|
47
|
+
const observed = ObservedMemorySources.capture({ run: root.run_root ?? root.run_dir }, workers.map(worker => ({ root: "run", path: worker.result.path })));
|
|
48
|
+
const hashes = observed.hashes();
|
|
49
|
+
if (workers.some(worker => hashes.get(memorySourceKey("run", worker.result.path)) !== worker.result.sha256))
|
|
50
|
+
refuse("retained_review_result_changed");
|
|
51
|
+
const prepared = new PreparedRetainedMemoryReviewResults(owner, workers, results, observed, harness);
|
|
52
|
+
prepared.assertCustody(context);
|
|
53
|
+
return prepared;
|
|
54
|
+
}
|
|
55
|
+
original() { return this.#owner; }
|
|
56
|
+
results() { return structuredClone(this.#results); }
|
|
57
|
+
references() {
|
|
58
|
+
return this.#workers.map(worker => ({ reviewer_work_id: worker.workId,
|
|
59
|
+
input_sha256: this.#owner.registration().input_sha256, result: { ...worker.result },
|
|
60
|
+
unit_ids: [this.#results.find(result => result.work_id === worker.workId).unit_id] }));
|
|
61
|
+
}
|
|
62
|
+
assertCustody(context) {
|
|
63
|
+
this.#owner.assertCustody(context);
|
|
64
|
+
const registration = this.#owner.registration();
|
|
65
|
+
for (const worker of this.#workers)
|
|
66
|
+
if (readMemoryReviewWorkSettlement(context, registration, worker.workId, this.#harness).authority !== worker.authority)
|
|
67
|
+
refuse("retained_review_settlement_changed");
|
|
68
|
+
}
|
|
69
|
+
revalidate(context) { this.#owner.revalidate(context); this.#observed.revalidate(); this.assertCustody(context); }
|
|
70
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
import { RetainedExecutionBundle } from "./execution-bundles.js";
|
|
3
|
+
import { PreparedRetainedSecondMemoryRepairs } from "./memory-retained-second-repairs.js";
|
|
4
|
+
import { readWorkBatchCustody } from "./work-registry.js";
|
|
5
|
+
import { assertRunMutationAllowed } from "./run-recovery-guard.js";
|
|
6
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
7
|
+
import { PreparedMemoryRepairCurrentView } from "./memory-repair-current-view.js";
|
|
8
|
+
const token = Symbol("retained-second-memory-baseline");
|
|
9
|
+
function refuse(reason) {
|
|
10
|
+
throw new AppError("memory_retained_second_baseline_invalid", "Second baseline lacks independent original receipt and settled source custody", 2, { reason, effect: "no_effect" });
|
|
11
|
+
}
|
|
12
|
+
function selected(context, scope, workId) {
|
|
13
|
+
const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='memory.repair_work_baseline' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.work_id')=? LIMIT 2", [scope.project_id, scope.run_id, workId]);
|
|
14
|
+
if (!rows.length)
|
|
15
|
+
refuse("registered_baseline_required");
|
|
16
|
+
if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 24 * 1024 * 1024)
|
|
17
|
+
refuse("ambiguous_or_unbounded_baseline");
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(rows[0].payload_json);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return refuse("invalid_baseline");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Retained initial or registered post-peer second baseline, not start authority.
|
|
26
|
+
* No obsolete workspace/private bytes are inspected or recaptured. */
|
|
27
|
+
export class PreparedRetainedSecondMemoryBaseline {
|
|
28
|
+
static isPrepared(value) {
|
|
29
|
+
return typeof value === "object" && value !== null && #registration in value;
|
|
30
|
+
}
|
|
31
|
+
#registration;
|
|
32
|
+
#receipt;
|
|
33
|
+
#baseline;
|
|
34
|
+
#exported;
|
|
35
|
+
#current;
|
|
36
|
+
constructor(key, registration, receipt, baseline, exported, current = null) {
|
|
37
|
+
if (key !== token)
|
|
38
|
+
refuse("retained_baseline_token_required");
|
|
39
|
+
this.#registration = registration;
|
|
40
|
+
this.#receipt = structuredClone(receipt);
|
|
41
|
+
this.#baseline = baseline;
|
|
42
|
+
this.#exported = exported;
|
|
43
|
+
this.#current = current;
|
|
44
|
+
Object.freeze(this);
|
|
45
|
+
}
|
|
46
|
+
static prepare(context, input) {
|
|
47
|
+
if (!PreparedRetainedSecondMemoryRepairs.isPrepared(input.registration))
|
|
48
|
+
refuse("retained_second_registration_required");
|
|
49
|
+
if (!(input.bundle instanceof RetainedExecutionBundle) || input.bundle.descriptor.descriptor.id !== "mb-init")
|
|
50
|
+
refuse("registered_memory_bundle_required");
|
|
51
|
+
if (context.db.isOwnedWriteTransaction?.())
|
|
52
|
+
refuse("preparation_outside_writer_required");
|
|
53
|
+
const registration = input.registration, owner = registration.receipt(), scope = owner.scope;
|
|
54
|
+
registration.revalidate(context);
|
|
55
|
+
if (scope.bundle_sha256 !== input.bundle.sha256 || scope.execution_sha256 !== input.bundle.execution.sha256)
|
|
56
|
+
refuse("foreign_bundle");
|
|
57
|
+
const assigned = registration.batch().works.find(item => owner.work_ids[item.key] === input.workId);
|
|
58
|
+
if (!assigned)
|
|
59
|
+
refuse("foreign_second_work");
|
|
60
|
+
const receipt = selected(context, scope, input.workId), generation = assertRunMutationAllowed(context, scope.project_id, scope.run_id);
|
|
61
|
+
const current = receipt.schema_id === "dd-flow/memory-repair-work-baseline@5"
|
|
62
|
+
? PreparedMemoryRepairCurrentView.restore(context, registration, input.workId) : null;
|
|
63
|
+
if (assigned.depends_on.length && !current)
|
|
64
|
+
refuse("settled_second_dependency_view_required");
|
|
65
|
+
const baseline = current ? current.baseline() : registration.initialDocumentBaseline().withWritePaths(assigned.payload.memory_repair.write_paths);
|
|
66
|
+
const files = new Set(baseline.entries().filter(entry => entry.kind === "file").map(entry => entry.path));
|
|
67
|
+
const selectedFiles = registration.initialInputView().run_inputs.filter(ref => ref.root === "workspace" && ref.path.startsWith(".memory-bank/") && files.has(ref.path)).map(ref => ref.path);
|
|
68
|
+
const exported = current ? current.exportedBaseline() : baseline.exported(selectedFiles);
|
|
69
|
+
const expected = { schema_id: current ? "dd-flow/memory-repair-work-baseline@5" : "dd-flow/memory-repair-work-baseline@4", wave: 2, project_id: scope.project_id, run_id: scope.run_id,
|
|
70
|
+
work_id: input.workId, generation, registration_sha256: hybridDigest(owner), allocation_sha256: hybridDigest(readWorkBatchCustody(context, input.workId)),
|
|
71
|
+
packet_sha256: assigned.payload.memory_repair.packet_sha256, workspace_root: input.bundle.execution.snapshot().scope.workspace,
|
|
72
|
+
first_review_input_sha256: owner.first_review_input_sha256, first_check_receipt_sha256: owner.first_check_receipt_sha256,
|
|
73
|
+
baseline_sha256: baseline.sha256, baseline_base64: baseline.bytes().toString("base64"), export_policy: "memory-repair-export@1",
|
|
74
|
+
exported_baseline_sha256: exported.sha256, exported_baseline_base64: exported.bytes().toString("base64"),
|
|
75
|
+
...(current ? { current_view_sha256: current.sha256, current_view_receipt_sha256: hybridDigest(current.receipt()) } : {}) };
|
|
76
|
+
if (hybridDigest(receipt) !== hybridDigest(expected))
|
|
77
|
+
refuse("registered_baseline_changed");
|
|
78
|
+
const prepared = new PreparedRetainedSecondMemoryBaseline(token, registration, receipt, baseline, exported, current);
|
|
79
|
+
prepared.assertCustody(context);
|
|
80
|
+
return prepared;
|
|
81
|
+
}
|
|
82
|
+
receipt() { return structuredClone(this.#receipt); }
|
|
83
|
+
baseline() { return this.#baseline; }
|
|
84
|
+
exportedBaseline() { return this.#exported; }
|
|
85
|
+
copyInput() {
|
|
86
|
+
if (this.#current)
|
|
87
|
+
return this.#current.copyInput();
|
|
88
|
+
const view = this.#registration.initialInputView();
|
|
89
|
+
return { source_sha256: view.source_sha256, base: view.base, explicit_inputs: view.run_inputs.filter(ref => ref.root === "workspace").map(ref => ref.path) };
|
|
90
|
+
}
|
|
91
|
+
currentView() { return this.#current?.receipt() ?? null; }
|
|
92
|
+
assertCustody(context) {
|
|
93
|
+
this.#registration.assertCustody(context);
|
|
94
|
+
this.#current?.assertCustody(context);
|
|
95
|
+
const receipt = this.#receipt;
|
|
96
|
+
assertRunMutationAllowed(context, receipt.project_id, receipt.run_id, receipt.generation);
|
|
97
|
+
if (hybridDigest(selected(context, receipt, receipt.work_id)) !== hybridDigest(receipt)
|
|
98
|
+
|| hybridDigest(readWorkBatchCustody(context, receipt.work_id)) !== receipt.allocation_sha256)
|
|
99
|
+
refuse("retained_baseline_custody_changed");
|
|
100
|
+
}
|
|
101
|
+
revalidate(context) { this.#registration.revalidate(context); this.assertCustody(context); }
|
|
102
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { AppError } from "../shared/errors.js";
|
|
3
|
+
import { RetainedExecutionBundle } from "./execution-bundles.js";
|
|
4
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
5
|
+
import { PreparedHistoricalOriginalMemoryReview } from "./memory-historical-original-review.js";
|
|
6
|
+
import { PreparedHistoricalCorrectedMemoryReview } from "./memory-historical-corrected-review.js";
|
|
7
|
+
import { readRetainedCorrectedMemoryReviewRegistration } from "./memory-corrected-review-registration.js";
|
|
8
|
+
import { validateMemoryReviewCorrectionPlan } from "./memory-review-correction-plan.js";
|
|
9
|
+
import { memoryRepairBatch, memoryRepairWorkerSchema } from "./memory-repair-registration.js";
|
|
10
|
+
import { readWorkBatchCustody } from "./work-registry.js";
|
|
11
|
+
const token = Symbol("retained-second-memory-repairs");
|
|
12
|
+
function refuse(reason) {
|
|
13
|
+
throw new AppError("memory_retained_second_repairs_invalid", "Second repair history lacks independent plan, review and allocation custody", 2, { reason, effect: "no_effect" });
|
|
14
|
+
}
|
|
15
|
+
function selected(context, scope) {
|
|
16
|
+
const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='memory.second_repair_registration' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.scope.attempt')=? AND json_extract(payload_json,'$.scope.parent_work_id')=? LIMIT 2", [scope.project_id, scope.run_id, scope.attempt, scope.parent_work_id]);
|
|
17
|
+
if (!rows.length)
|
|
18
|
+
refuse("registration_missing");
|
|
19
|
+
if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 2 * 1024 * 1024)
|
|
20
|
+
refuse("ambiguous_or_unbounded_registration");
|
|
21
|
+
let receipt;
|
|
22
|
+
try {
|
|
23
|
+
receipt = JSON.parse(rows[0].payload_json);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return refuse("invalid_registration");
|
|
27
|
+
}
|
|
28
|
+
if (!receipt || receipt.schema_id !== "dd-flow/memory-second-repair-registration@1" || receipt.run_id !== scope.run_id
|
|
29
|
+
|| receipt.wave !== 2 || hybridDigest(receipt.scope) !== hybridDigest(scope)
|
|
30
|
+
|| [receipt.first_review_registration_sha256, receipt.first_review_input_sha256, receipt.first_check_receipt_sha256,
|
|
31
|
+
receipt.review_sha256, receipt.projection_sha256, receipt.plan_sha256, receipt.batch_sha256].some(hash => typeof hash !== "string" || !/^[a-f0-9]{64}$/.test(hash))
|
|
32
|
+
|| typeof receipt.plan_base64 !== "string")
|
|
33
|
+
refuse("changed_registration");
|
|
34
|
+
const bytes = Buffer.from(receipt.plan_base64, "base64");
|
|
35
|
+
if (!bytes.length || bytes.length > 1024 * 1024 || bytes.toString("base64") !== receipt.plan_base64
|
|
36
|
+
|| createHash("sha256").update(bytes).digest("hex") !== receipt.plan_sha256)
|
|
37
|
+
refuse("changed_plan_bytes");
|
|
38
|
+
return receipt;
|
|
39
|
+
}
|
|
40
|
+
function assertAllocation(context, receipt, batch) {
|
|
41
|
+
const ids = receipt.work_ids, scope = receipt.scope;
|
|
42
|
+
if (!ids || typeof ids !== "object" || Array.isArray(ids) || Object.keys(ids).length !== batch.works.length
|
|
43
|
+
|| Object.values(ids).some(id => typeof id !== "string" || !id) || new Set(Object.values(ids)).size !== batch.works.length
|
|
44
|
+
|| hybridDigest(batch.works) !== receipt.batch_sha256)
|
|
45
|
+
refuse("changed_repair_batch");
|
|
46
|
+
for (const item of batch.works) {
|
|
47
|
+
const id = ids[item.key];
|
|
48
|
+
if (!id)
|
|
49
|
+
refuse("missing_repair_work");
|
|
50
|
+
const custody = readWorkBatchCustody(context, id);
|
|
51
|
+
if (custody.item_key !== item.key || custody.batch_sha256 !== receipt.batch_sha256
|
|
52
|
+
|| hybridDigest(custody.work_ids) !== hybridDigest(ids) || hybridDigest(custody.scope) !== hybridDigest({
|
|
53
|
+
project_id: scope.project_id, run_id: scope.run_id, stage: "review", attempt: scope.attempt, cycle: "",
|
|
54
|
+
parent_work_id: scope.parent_work_id, request_key: "memory-repair-wave-2"
|
|
55
|
+
}))
|
|
56
|
+
refuse("changed_work_custody");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Retained second graph only. Does not grant current source/write admission,
|
|
60
|
+
* allocate replacement Works or dispatch providers after receipt loss. */
|
|
61
|
+
export class PreparedRetainedSecondMemoryRepairs {
|
|
62
|
+
static isPrepared(value) {
|
|
63
|
+
return typeof value === "object" && value !== null && #receipt in value;
|
|
64
|
+
}
|
|
65
|
+
#history;
|
|
66
|
+
#receipt;
|
|
67
|
+
#batch;
|
|
68
|
+
#plan;
|
|
69
|
+
#bundle;
|
|
70
|
+
constructor(key, history, receipt, batch, plan) {
|
|
71
|
+
if (key !== token)
|
|
72
|
+
refuse("retained_token_required");
|
|
73
|
+
this.#history = history;
|
|
74
|
+
this.#receipt = structuredClone(receipt);
|
|
75
|
+
this.#batch = structuredClone(batch);
|
|
76
|
+
this.#plan = structuredClone(plan);
|
|
77
|
+
this.#bundle = history.bundle();
|
|
78
|
+
Object.freeze(this);
|
|
79
|
+
}
|
|
80
|
+
static prepare(context, input) {
|
|
81
|
+
return this.#prepare(context, input, false);
|
|
82
|
+
}
|
|
83
|
+
/** Closed-review history only. The original owner pins closed SQL custody;
|
|
84
|
+
* this never reopens review or grants new writer/launch admission. */
|
|
85
|
+
static prepareAfterReview(context, input) {
|
|
86
|
+
return this.#prepare(context, input, true);
|
|
87
|
+
}
|
|
88
|
+
static #prepare(context, input, closed) {
|
|
89
|
+
if (!(input.bundle instanceof RetainedExecutionBundle) || input.bundle.descriptor.descriptor.id !== "mb-init")
|
|
90
|
+
refuse("registered_memory_bundle_required");
|
|
91
|
+
if (context.db.isOwnedWriteTransaction?.())
|
|
92
|
+
refuse("preparation_outside_writer_required");
|
|
93
|
+
const original = closed ? PreparedHistoricalOriginalMemoryReview.prepare(context, input)
|
|
94
|
+
: PreparedHistoricalOriginalMemoryReview.prepareWhileReviewOpen(context, input);
|
|
95
|
+
const scope = original.registration().scope;
|
|
96
|
+
const receipt = context.db.readSnapshot(() => selected(context, scope)), bytes = Buffer.from(receipt.plan_base64, "base64");
|
|
97
|
+
if (input.expectedBytes !== undefined && (!(input.expectedBytes instanceof Uint8Array) || !bytes.equals(Buffer.from(input.expectedBytes))))
|
|
98
|
+
refuse("plan_replay_conflict");
|
|
99
|
+
const first = readRetainedCorrectedMemoryReviewRegistration(context, scope, receipt.first_review_input_sha256, receipt.first_check_receipt_sha256);
|
|
100
|
+
if (!first || hybridDigest(first) !== receipt.first_review_registration_sha256)
|
|
101
|
+
refuse("changed_first_registration");
|
|
102
|
+
const history = PreparedHistoricalCorrectedMemoryReview.prepare(context, { original, correctedInputSha256: first.input_sha256,
|
|
103
|
+
checkReceiptSha256: first.check_receipt_sha256 });
|
|
104
|
+
if (history.reviewSha256() !== receipt.review_sha256 || hybridDigest(history.projection()) !== receipt.projection_sha256)
|
|
105
|
+
refuse("changed_first_review");
|
|
106
|
+
const view = first.view, assignments = new Map(view.assignments.map(item => [item.unit.id, item.unit]));
|
|
107
|
+
const plan = validateMemoryReviewCorrectionPlan(bytes, input.bundle.engine(), { runId: scope.run_id, wave: 2,
|
|
108
|
+
reviewSha256: receipt.review_sha256, units: view.unit_inventory.map(unit => assignments.get(unit.id) ?? unit), findings: history.projection().findings });
|
|
109
|
+
if (history.projection().outcome === "blocked")
|
|
110
|
+
refuse("blocked_first_review");
|
|
111
|
+
for (const item of plan.works)
|
|
112
|
+
input.bundle.execution.work("review", item.profile_id ?? undefined);
|
|
113
|
+
if (JSON.parse(input.bundle.engine().schema("memory-repair-work-result").toString("utf8")).$id !== memoryRepairWorkerSchema)
|
|
114
|
+
refuse("frozen_worker_schema_required");
|
|
115
|
+
const refs = new Map(view.run_inputs.map(ref => [`${ref.root}:${ref.path}`, ref]));
|
|
116
|
+
for (const worker of history.settlementProof(context).workers) {
|
|
117
|
+
const ref = { root: "run", ...worker.result }, key = `run:${ref.path}`, prior = refs.get(key);
|
|
118
|
+
if (prior && prior.sha256 !== ref.sha256)
|
|
119
|
+
refuse("conflicting_review_reference");
|
|
120
|
+
refs.set(key, ref);
|
|
121
|
+
}
|
|
122
|
+
const batch = memoryRepairBatch({ scope, plan, planSha256: receipt.plan_sha256, originalRegistrationSha256: hybridDigest(first),
|
|
123
|
+
references: [...refs.values()], findings: history.projection().findings });
|
|
124
|
+
const expected = { schema_id: "dd-flow/memory-second-repair-registration@1", run_id: scope.run_id, scope, wave: 2,
|
|
125
|
+
first_review_registration_sha256: hybridDigest(first), first_review_input_sha256: first.input_sha256,
|
|
126
|
+
first_check_receipt_sha256: first.check_receipt_sha256, review_sha256: history.reviewSha256(), projection_sha256: hybridDigest(history.projection()),
|
|
127
|
+
plan_sha256: receipt.plan_sha256, plan_base64: bytes.toString("base64"), batch_sha256: hybridDigest(batch.works), work_ids: receipt.work_ids };
|
|
128
|
+
if (hybridDigest(expected) !== hybridDigest(receipt))
|
|
129
|
+
refuse("changed_registration");
|
|
130
|
+
const prepared = new PreparedRetainedSecondMemoryRepairs(token, history, receipt, batch, plan);
|
|
131
|
+
prepared.revalidate(context);
|
|
132
|
+
return prepared;
|
|
133
|
+
}
|
|
134
|
+
receipt() { return structuredClone(this.#receipt); }
|
|
135
|
+
get sha256() { return hybridDigest(this.#receipt); }
|
|
136
|
+
bundle() { return this.#bundle; }
|
|
137
|
+
batch() { return structuredClone(this.#batch); }
|
|
138
|
+
plan() { return structuredClone(this.#plan); }
|
|
139
|
+
/** The first wave's actual settled baseline, with only this registered
|
|
140
|
+
* second plan's admitted write paths. No current bytes are adopted here. */
|
|
141
|
+
initialDocumentBaseline() {
|
|
142
|
+
const paths = [...new Set(this.#batch.works.flatMap(item => item.payload.memory_repair.write_paths))].sort();
|
|
143
|
+
return this.#history.settledDocumentBaseline().withWritePaths(paths);
|
|
144
|
+
}
|
|
145
|
+
initialInputView() { return this.#history.retainedInputView(); }
|
|
146
|
+
firstReviewProof(context) { this.assertCustody(context); return this.#history.settlementProof(context); }
|
|
147
|
+
firstEffectiveReviewProof(context) { this.assertCustody(context); return this.#history.effectiveReviewProof(context); }
|
|
148
|
+
verificationProfileHash(context) { this.assertCustody(context); return this.#history.verificationProfileHash(context); }
|
|
149
|
+
assertCustody(context) {
|
|
150
|
+
this.#history.assertCustody(context);
|
|
151
|
+
if (hybridDigest(selected(context, this.#receipt.scope)) !== hybridDigest(this.#receipt))
|
|
152
|
+
refuse("registration_changed");
|
|
153
|
+
assertAllocation(context, this.#receipt, this.#batch);
|
|
154
|
+
}
|
|
155
|
+
revalidate(context) { this.#history.revalidate(context); this.assertCustody(context); }
|
|
156
|
+
}
|