@deksden-com/dd-flow-cli 0.9.0-beta.137 → 0.9.0-beta.139
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 +19 -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/runtime-process.js +1 -1
- package/dist/cli/setup.js +149 -0
- package/dist/cli.js +56 -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 +53 -22
- package/dist/harness-runtime/lib/dd-agy.mjs +16 -5
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +12 -6
- package/dist/harness-runtime/lib/dd-codex.mjs +24 -12
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +2 -1
- package/dist/harness-runtime/lib/dd-droid.mjs +32 -9
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +11 -3
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +100 -15
- package/dist/harness-runtime/lib/dd-opencode.mjs +51 -20
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +22 -10
- package/dist/harness-runtime/lib/dd-zcode.mjs +17 -4
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +1 -0
- package/dist/harness-runtime/lib/delegation-instructions.mjs +16 -3
- package/dist/harness-runtime/lib/lease-renewal.mjs +2 -1
- 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-children.d.mts +2 -0
- package/dist/harness-runtime/lib/native-children.mjs +20 -1
- 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 +32 -9
- package/dist/harness-runtime/lib/operation-progress.mjs +3 -1
- package/dist/harness-runtime/lib/provider-overload-policy.d.mts +13 -0
- package/dist/harness-runtime/lib/provider-overload-policy.mjs +138 -0
- 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 +26 -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-child-capacity.js +13 -0
- package/dist/services/controller-child-observer.js +18 -2
- 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 +93 -15
- package/dist/services/flow-catalog.js +21 -0
- package/dist/services/flow-descriptors.js +82 -0
- package/dist/services/frozen-engine.js +250 -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/managed-processes.js +31 -10
- 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 +296 -0
- package/dist/services/memory-init-preflight.js +330 -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 +181 -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 +178 -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/merge-server.js +2 -4
- 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/{codex-capacity.js → provider-capacity.js} +34 -16
- package/dist/services/recovery-snapshot-database.js +417 -2
- 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-adapter.js +1 -1
- 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 +5 -5
- package/dist/services/run-controller-state.js +1 -1
- package/dist/services/run-controller.js +158 -64
- package/dist/services/run-recovery-guard.js +5 -3
- package/dist/services/run-recovery-runtime.js +3 -3
- package/dist/services/run-recovery.js +145 -5
- package/dist/services/run-start-custody.js +177 -0
- package/dist/services/runs.js +413 -30
- 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-review.js +1 -1
- package/dist/services/vnext-code.js +6 -6
- package/dist/services/vnext-execution-profile.js +2 -2
- package/dist/services/vnext-fanout.js +73 -11
- package/dist/services/vnext-merge.js +8 -3
- package/dist/services/vnext-plan-review.js +7 -5
- package/dist/services/vnext-plan.js +3 -3
- package/dist/services/vnext-protocolize.js +20 -6
- package/dist/services/vnext-workspace-policy.js +7 -2
- 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
- package/dist/harness-runtime/lib/codex-capacity-policy.d.mts +0 -10
- package/dist/harness-runtime/lib/codex-capacity-policy.mjs +0 -102
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
import { RetainedExecutionBundle } from "./execution-bundles.js";
|
|
3
|
+
import { assertPreparedRunBundleSqlAuthority } from "./runs.js";
|
|
4
|
+
import { readDescriptorRootWork } from "./descriptor-root-work.js";
|
|
5
|
+
import { readAcceptedStageData } from "./stage-report-renderer.js";
|
|
6
|
+
import { PreparedAcceptedMemoryComposition } from "./memory-accepted-composition.js";
|
|
7
|
+
import { PreparedAcceptedMemoryVerification } from "./memory-verification-receipt.js";
|
|
8
|
+
import { PreparedMemoryBaselinePreservation } from "./memory-baseline-preservation.js";
|
|
9
|
+
import { ObservedMemorySources } from "./memory-source-observations.js";
|
|
10
|
+
import { memorySourceKey } from "./memory-stage-results.js";
|
|
11
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
12
|
+
import { reviewEnvironmentAllowed, reviewSourcePaths, reviewSourceHash } from "./review-copy.js";
|
|
13
|
+
import { assertRunMutationAllowed } from "./run-recovery-guard.js";
|
|
14
|
+
import { PreparedDescriptorStageCoordinator } from "./descriptor-stage-coordinator.js";
|
|
15
|
+
import { readDescriptorCoordinatorSession } from "./descriptor-coordinator-session.js";
|
|
16
|
+
import { readMemoryInitBootstrapReadiness } from "./memory-init-bootstrap-readiness.js";
|
|
17
|
+
/** Exact inherited evidence only; current artifact bytes remain independently observed. */
|
|
18
|
+
export function memoryReviewEvidenceTriples(units) {
|
|
19
|
+
return new Set(units.flatMap(unit => unit.evidence.map(ref => `${memorySourceKey(ref.root, ref.path)}\0${ref.sha256}`)));
|
|
20
|
+
}
|
|
21
|
+
function refuse(reason) {
|
|
22
|
+
throw new AppError("memory_review_input_invalid", "Memory reviewer input lacks accepted unit and document custody", 2, { reason, effect: "no_effect" });
|
|
23
|
+
}
|
|
24
|
+
/** Derives exact per-unit assignments. Source aliases from the original project
|
|
25
|
+
* are read through the assigned workspace, whose baseline is independently
|
|
26
|
+
* preserved. This function alone supplies neither acceptance nor SQL authority.
|
|
27
|
+
*/
|
|
28
|
+
export function memoryReviewUnitAssignments(units, historicalEvidence = new Set()) {
|
|
29
|
+
if (!Array.isArray(units) || !units.length || units.length > 256
|
|
30
|
+
|| units.some(unit => !unit || typeof unit.id !== "string" || !unit.id || typeof unit.aspect !== "string" || !unit.aspect)
|
|
31
|
+
|| new Set(units.map(unit => unit.id)).size !== units.length)
|
|
32
|
+
refuse("invalid_unit_inventory");
|
|
33
|
+
return units.filter(unit => {
|
|
34
|
+
if (!["applicable", "not-applicable"].includes(unit.applicability))
|
|
35
|
+
refuse("unresolved_unit_applicability");
|
|
36
|
+
return unit.applicability === "applicable";
|
|
37
|
+
}).map(unit => {
|
|
38
|
+
if (typeof unit.id !== "string" || !unit.id || typeof unit.aspect !== "string" || !unit.aspect
|
|
39
|
+
|| !Array.isArray(unit.evidence) || !Array.isArray(unit.artifacts) || !unit.artifacts.length)
|
|
40
|
+
refuse("invalid_unit_assignment");
|
|
41
|
+
const references = new Map();
|
|
42
|
+
for (const [role, refs] of [["evidence", unit.evidence], ["artifacts", unit.artifacts]])
|
|
43
|
+
for (const ref of refs) {
|
|
44
|
+
if (!ref || !["project", "workspace", "canon", "run"].includes(ref.root) || typeof ref.path !== "string" || !ref.path
|
|
45
|
+
|| ref.path.startsWith("/") || /^[A-Za-z]:/.test(ref.path) || ref.path.includes("\\") || ref.path.includes("\0")
|
|
46
|
+
|| ref.path.split("/").some((part) => !part || part === "." || part === "..")
|
|
47
|
+
|| typeof ref.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(ref.sha256))
|
|
48
|
+
refuse("invalid_unit_source");
|
|
49
|
+
const input = { root: ref.root === "run" ? "run" : "workspace",
|
|
50
|
+
path: ref.root === "canon" ? `.memory-bank/${ref.path}` : ref.path, sha256: ref.sha256 };
|
|
51
|
+
if (role === "evidence" && historicalEvidence.has(`${memorySourceKey(ref.root, ref.path)}\0${ref.sha256}`)
|
|
52
|
+
&& units.flatMap(item => item.artifacts).some(artifact => (artifact.root === "run" ? "run" : "workspace") === input.root
|
|
53
|
+
&& (artifact.root === "canon" ? `.memory-bank/${artifact.path}` : artifact.path) === input.path && artifact.sha256 !== ref.sha256))
|
|
54
|
+
continue;
|
|
55
|
+
const key = memorySourceKey(input.root, input.path), prior = references.get(key);
|
|
56
|
+
if (prior && prior.sha256 !== ref.sha256)
|
|
57
|
+
refuse("conflicting_unit_sources");
|
|
58
|
+
references.set(key, input);
|
|
59
|
+
}
|
|
60
|
+
if (references.size > 4096)
|
|
61
|
+
refuse("unbounded_unit_sources");
|
|
62
|
+
return { unit: structuredClone(unit), inputs: [...references.values()].sort((a, b) => a.path.localeCompare(b.path, "en")) };
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
export function readMemoryReviewPhase(context, scope, bundle) {
|
|
66
|
+
const run = assertPreparedRunBundleSqlAuthority(context, scope, bundle), index = JSON.parse(run.index_json);
|
|
67
|
+
const stage = index.stage_runs?.find((item) => item.stage === "review");
|
|
68
|
+
const root = readDescriptorRootWork(context, scope, bundle);
|
|
69
|
+
if (run.status !== "running" || index.current_stage !== "review" || stage?.status !== "running"
|
|
70
|
+
|| root.status !== "running" || !/^try-[0-9]{3,}$/.test(stage.attempt)
|
|
71
|
+
|| !/^[a-f0-9]{64}$/.test(stage.input_sha256))
|
|
72
|
+
refuse("active_review_required");
|
|
73
|
+
return { project_id: scope.projectId, run_id: scope.runId, stage: "review",
|
|
74
|
+
attempt: stage.attempt, input_sha256: stage.input_sha256, parent_work_id: root.workId,
|
|
75
|
+
execution_sha256: bundle.execution.sha256, bundle_sha256: bundle.sha256 };
|
|
76
|
+
}
|
|
77
|
+
const phase = readMemoryReviewPhase;
|
|
78
|
+
/** Initial-wave owner, not a repair view. Every applicable unit gets a separate
|
|
79
|
+
* immutable assignment while the wave shares one document/copy baseline for
|
|
80
|
+
* cross-file review. No allocation, publication, provider or Work claim occurs.
|
|
81
|
+
*/
|
|
82
|
+
export class PreparedMemoryInitialReviewInputs {
|
|
83
|
+
sha256;
|
|
84
|
+
#scope;
|
|
85
|
+
#bundle;
|
|
86
|
+
#phase;
|
|
87
|
+
#composition;
|
|
88
|
+
#verification;
|
|
89
|
+
#preservation;
|
|
90
|
+
#observed;
|
|
91
|
+
#view;
|
|
92
|
+
#generation;
|
|
93
|
+
#coordinator;
|
|
94
|
+
#coordinatorSession;
|
|
95
|
+
constructor(scope, bundle, selected, composition, verification, preservation, observed, view, generation, coordinator, coordinatorSession) {
|
|
96
|
+
this.#scope = { ...scope };
|
|
97
|
+
this.#bundle = bundle;
|
|
98
|
+
this.#phase = selected;
|
|
99
|
+
this.#composition = composition;
|
|
100
|
+
this.#verification = verification;
|
|
101
|
+
this.#preservation = preservation;
|
|
102
|
+
this.#observed = observed;
|
|
103
|
+
this.#view = structuredClone(view);
|
|
104
|
+
this.#generation = generation;
|
|
105
|
+
this.sha256 = hybridDigest(view);
|
|
106
|
+
Object.freeze(this);
|
|
107
|
+
this.#coordinator = coordinator;
|
|
108
|
+
this.#coordinatorSession = coordinatorSession;
|
|
109
|
+
}
|
|
110
|
+
static prepare(context, input) {
|
|
111
|
+
const { scope, bundle } = input;
|
|
112
|
+
if (!(bundle instanceof RetainedExecutionBundle) || bundle.descriptor.descriptor.id !== "mb-init")
|
|
113
|
+
refuse("registered_memory_bundle_required");
|
|
114
|
+
const generation = assertRunMutationAllowed(context, scope.projectId, scope.runId);
|
|
115
|
+
const workerSchema = JSON.parse(bundle.engine().schema("memory-review-work-result").toString("utf8"));
|
|
116
|
+
if (workerSchema.$id !== "dd-flow/memory-review-work-result@1")
|
|
117
|
+
refuse("frozen_review_worker_schema_required");
|
|
118
|
+
const selected = phase(context, scope, bundle);
|
|
119
|
+
const coordinator = PreparedDescriptorStageCoordinator.prepare(context, { projectRoot: bundle.execution.snapshot().scope.projectRoot, runId: scope.runId, stage: "review" });
|
|
120
|
+
coordinator.assertAllocatedCustody(context);
|
|
121
|
+
const coordinatorSession = readDescriptorCoordinatorSession(context, { scope: { ...scope, stage: "review" }, bundle, workId: selected.parent_work_id }, refuse);
|
|
122
|
+
const preservation = PreparedMemoryBaselinePreservation.prepare(context, bundle.execution);
|
|
123
|
+
const composition = PreparedAcceptedMemoryComposition.prepare(context, { scope, bundle });
|
|
124
|
+
const compose = readAcceptedStageData(context, { ...scope, stage: "compose" });
|
|
125
|
+
const verify = readAcceptedStageData(context, { ...scope, stage: "verify" });
|
|
126
|
+
const verification = PreparedAcceptedMemoryVerification.prepare(context, { ...scope, bytes: verify.bytes });
|
|
127
|
+
const unitInventory = composition.result().value().units;
|
|
128
|
+
const assignments = memoryReviewUnitAssignments(unitInventory, composition.historicalEvidence());
|
|
129
|
+
const diagnosticFindings = verification.qualityPassed(context) ? [] : verification.diagnosticFindings(context).filter(finding => finding.status === "open" && finding.severity !== "minor");
|
|
130
|
+
for (const finding of diagnosticFindings) {
|
|
131
|
+
const assignment = assignments.find(item => item.unit.id === finding.unit_id);
|
|
132
|
+
if (!assignment)
|
|
133
|
+
refuse("diagnostic_finding_outside_units");
|
|
134
|
+
for (const ref of finding.evidence)
|
|
135
|
+
if (ref.root === "run" && !assignment.inputs.some(input => input.root === ref.root && input.path === ref.path))
|
|
136
|
+
assignment.inputs.push({ ...ref, root: "run" });
|
|
137
|
+
}
|
|
138
|
+
const inputs = new Map();
|
|
139
|
+
const frozenScope = bundle.execution.snapshot().scope;
|
|
140
|
+
const bootstrap = readMemoryInitBootstrapReadiness(context, { requestId: frozenScope.requestId,
|
|
141
|
+
projectRoot: frozenScope.projectRoot, workspaceRoot: frozenScope.workspace });
|
|
142
|
+
const lintSha256 = bootstrap?.authority.binding.target.generated_lint_config_sha256;
|
|
143
|
+
if (lintSha256)
|
|
144
|
+
inputs.set(memorySourceKey("workspace", ".mb-lint.json"), { root: "workspace", path: ".mb-lint.json", sha256: lintSha256 });
|
|
145
|
+
for (const ref of [{ root: "run", ...compose.result }, { root: "run", ...verify.result }, ...assignments.flatMap(item => item.inputs)]) {
|
|
146
|
+
const key = memorySourceKey(ref.root, ref.path), previous = inputs.get(key);
|
|
147
|
+
if (previous && previous.sha256 !== ref.sha256)
|
|
148
|
+
refuse("conflicting_wave_sources");
|
|
149
|
+
inputs.set(key, ref);
|
|
150
|
+
}
|
|
151
|
+
if (inputs.size > 4096)
|
|
152
|
+
refuse("unbounded_wave_sources");
|
|
153
|
+
const runInputs = [...inputs.values()].sort((a, b) => `${a.root}:${a.path}`.localeCompare(`${b.root}:${b.path}`, "en"));
|
|
154
|
+
const paths = runInputs.filter(ref => ref.root === "workspace").map(ref => ref.path);
|
|
155
|
+
if (paths.some(path => !reviewEnvironmentAllowed(path)))
|
|
156
|
+
refuse("private_or_unavailable_review_sources");
|
|
157
|
+
const run = assertPreparedRunBundleSqlAuthority(context, scope, bundle), index = JSON.parse(run.index_json);
|
|
158
|
+
const roots = { run: run.run_root ?? run.run_dir, workspace: bundle.execution.snapshot().scope.workspace };
|
|
159
|
+
const observed = ObservedMemorySources.capture(roots, runInputs.map(({ root, path }) => ({ root, path }))), hashes = observed.hashes();
|
|
160
|
+
if (runInputs.some(ref => hashes.get(memorySourceKey(ref.root, ref.path)) !== ref.sha256))
|
|
161
|
+
refuse("changed_review_source");
|
|
162
|
+
const base = index.workspace?.base && index.workspace.base !== "unknown" ? index.workspace.base : "HEAD";
|
|
163
|
+
const retained = reviewSourcePaths(roots.workspace, paths, base);
|
|
164
|
+
if (retained === null || paths.some(path => !retained.includes(path)))
|
|
165
|
+
refuse("private_or_unavailable_review_sources");
|
|
166
|
+
const instruction = bundle.descriptor.stageBlob("review", "instruction");
|
|
167
|
+
const view = { schema_id: "dd-flow/memory-review-inputs@1", wave: 0, scope: selected,
|
|
168
|
+
source_policy: "review-inputs@1", source_sha256: reviewSourceHash(roots.workspace, paths, base), base,
|
|
169
|
+
run_inputs: runInputs, unit_inventory: structuredClone(unitInventory), assignments,
|
|
170
|
+
...(diagnosticFindings.length ? { diagnostic_findings: diagnosticFindings } : {}),
|
|
171
|
+
instruction: { sha256: bundle.descriptor.stage("review").instruction.sha256, base64: instruction.toString("base64") } };
|
|
172
|
+
if (Buffer.byteLength(JSON.stringify(view)) > 1024 * 1024)
|
|
173
|
+
refuse("unbounded_review_input");
|
|
174
|
+
const prepared = new PreparedMemoryInitialReviewInputs(scope, bundle, selected, composition, verification, preservation, observed, view, generation, coordinator, coordinatorSession);
|
|
175
|
+
prepared.assertCustody(context);
|
|
176
|
+
return prepared;
|
|
177
|
+
}
|
|
178
|
+
value() { return structuredClone(this.#view); }
|
|
179
|
+
assertCustody(context) {
|
|
180
|
+
assertRunMutationAllowed(context, this.#scope.projectId, this.#scope.runId, this.#generation);
|
|
181
|
+
this.#coordinator.assertAllocatedCustody(context);
|
|
182
|
+
if (readDescriptorCoordinatorSession(context, { scope: { ...this.#scope, stage: "review" }, bundle: this.#bundle, workId: this.#phase.parent_work_id }, refuse) !== this.#coordinatorSession)
|
|
183
|
+
refuse("coordinator_changed");
|
|
184
|
+
if (hybridDigest(phase(context, this.#scope, this.#bundle)) !== hybridDigest(this.#phase))
|
|
185
|
+
refuse("review_phase_changed");
|
|
186
|
+
this.#preservation.assertCustody(context);
|
|
187
|
+
this.#composition.assertCustody(context);
|
|
188
|
+
this.#verification.assertCustody(context);
|
|
189
|
+
}
|
|
190
|
+
revalidate(context) {
|
|
191
|
+
this.#preservation.revalidate(context);
|
|
192
|
+
this.#composition.revalidate(context);
|
|
193
|
+
this.#verification.revalidate(context);
|
|
194
|
+
this.#observed.revalidate();
|
|
195
|
+
const paths = this.#view.run_inputs.filter(ref => ref.root === "workspace").map(ref => ref.path);
|
|
196
|
+
if (reviewSourceHash(this.#bundle.execution.snapshot().scope.workspace, paths, this.#view.base) !== this.#view.source_sha256)
|
|
197
|
+
refuse("review_copy_source_changed");
|
|
198
|
+
this.assertCustody(context);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
2
|
+
import { AppError } from "../shared/errors.js";
|
|
3
|
+
import { appendAudit } from "./audit.js";
|
|
4
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
5
|
+
import { RetainedExecutionBundle } from "./execution-bundles.js";
|
|
6
|
+
import { assertPreparedRunBundleSqlAuthority } from "./runs.js";
|
|
7
|
+
import { readAcceptedStageData } from "./stage-report-renderer.js";
|
|
8
|
+
import { PreparedMemoryStageResult, memorySourceKey } from "./memory-stage-results.js";
|
|
9
|
+
import { PreparedMemoryInitialReviewAcceptance, assertMemoryInitialReviewProjection, readMemoryReviewWorkSettlement } from "./memory-review-acceptance.js";
|
|
10
|
+
import { readRegisteredMemoryReview, memoryReviewBatch } from "./memory-review-registration.js";
|
|
11
|
+
import { validateMemoryReviewWorkResult } from "./memory-review-work-results.js";
|
|
12
|
+
import { PreparedAcceptedMemoryVerification } from "./memory-verification-receipt.js";
|
|
13
|
+
import { ObservedMemorySources } from "./memory-source-observations.js";
|
|
14
|
+
import { runtimeHarness } from "./execution-policy.js";
|
|
15
|
+
const event = "memory.review_acceptance", token = Symbol("memory-review-acceptance-receipt");
|
|
16
|
+
function refuse(reason) {
|
|
17
|
+
throw new AppError("memory_review_receipt_invalid", "Review lacks independently accepted wave custody", 2, { reason, effect: "no_effect" });
|
|
18
|
+
}
|
|
19
|
+
function stage(context, scope) {
|
|
20
|
+
const run = context.db.get("SELECT index_json FROM runs WHERE project_id=? AND id=?", [scope.projectId, scope.runId]);
|
|
21
|
+
if (!run)
|
|
22
|
+
refuse("run_missing");
|
|
23
|
+
const index = JSON.parse(run.index_json), selected = index.stage_runs?.find((item) => item.stage === "review");
|
|
24
|
+
if (!selected || typeof selected.attempt !== "string" || !/^try-[0-9]{3,}$/.test(selected.attempt)
|
|
25
|
+
|| typeof selected.input_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(selected.input_sha256))
|
|
26
|
+
refuse("review_identity_missing");
|
|
27
|
+
return { current: index.current_stage, status: selected.status, attempt: selected.attempt, input_sha256: selected.input_sha256 };
|
|
28
|
+
}
|
|
29
|
+
function rows(context, scope) {
|
|
30
|
+
return context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type=? AND json_extract(payload_json,'$.run_id')=? LIMIT 2", [scope.projectId, event, scope.runId]);
|
|
31
|
+
}
|
|
32
|
+
function noCorrections(context, scope, proof) {
|
|
33
|
+
if (context.db.get("SELECT 1 FROM audit_events WHERE project_id=? AND event_type='memory.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 1", [scope.projectId, scope.runId, proof.registration_scope.attempt, proof.registration_scope.parent_work_id]))
|
|
34
|
+
refuse("initial_acceptance_cannot_close_corrections");
|
|
35
|
+
}
|
|
36
|
+
function makeReceipt(scope, selected, resultSha256, proof) {
|
|
37
|
+
if (proof.registration_scope.project_id !== scope.projectId || proof.registration_scope.run_id !== scope.runId
|
|
38
|
+
|| proof.registration_scope.stage !== "review" || proof.registration_scope.attempt !== selected.attempt
|
|
39
|
+
|| proof.registration_scope.input_sha256 !== selected.input_sha256 || proof.wave !== 0)
|
|
40
|
+
refuse("foreign_initial_wave");
|
|
41
|
+
const value = { schema_id: "dd-flow/memory-review-acceptance@1", project_id: scope.projectId, run_id: scope.runId,
|
|
42
|
+
stage: "review", attempt: selected.attempt, input_sha256: selected.input_sha256, result_sha256: resultSha256, ...structuredClone(proof) };
|
|
43
|
+
if (Buffer.byteLength(JSON.stringify(value)) > 2 * 1024 * 1024)
|
|
44
|
+
refuse("unbounded_review_receipt");
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
/** Independent wave-zero receipt commits with generic Stage acceptance.
|
|
48
|
+
* It cannot close a correction graph or substitute for a later review wave. */
|
|
49
|
+
export class PreparedMemoryInitialReviewReceipt {
|
|
50
|
+
#scope;
|
|
51
|
+
#wave;
|
|
52
|
+
#result;
|
|
53
|
+
#receipt;
|
|
54
|
+
constructor(key, scope, wave, result, receipt) {
|
|
55
|
+
if (key !== token)
|
|
56
|
+
refuse("receipt_token_required");
|
|
57
|
+
this.#scope = { ...scope };
|
|
58
|
+
this.#wave = wave;
|
|
59
|
+
this.#result = result;
|
|
60
|
+
this.#receipt = structuredClone(receipt);
|
|
61
|
+
Object.freeze(this);
|
|
62
|
+
}
|
|
63
|
+
static prepare(context, input) {
|
|
64
|
+
if (!PreparedMemoryInitialReviewAcceptance.isPrepared(input.wave) || !(input.result instanceof PreparedMemoryStageResult))
|
|
65
|
+
refuse("prepared_initial_wave_required");
|
|
66
|
+
const proof = input.wave.settlementProof(context, input.result), selected = stage(context, input.scope);
|
|
67
|
+
if (selected.current !== "review" || selected.status !== "running" || input.result.value().outcome !== "accepted")
|
|
68
|
+
refuse("accepted_active_review_required");
|
|
69
|
+
noCorrections(context, input.scope, proof);
|
|
70
|
+
if (rows(context, input.scope).length)
|
|
71
|
+
refuse("fresh_receipt_already_exists");
|
|
72
|
+
return new PreparedMemoryInitialReviewReceipt(token, input.scope, input.wave, input.result, makeReceipt(input.scope, selected, input.result.sha256, proof));
|
|
73
|
+
}
|
|
74
|
+
record(context) {
|
|
75
|
+
const scope = this.#scope;
|
|
76
|
+
if (!context.db.isOwnedWriteTransaction?.())
|
|
77
|
+
refuse("owned_acceptance_writer_required");
|
|
78
|
+
const proof = this.#wave.settlementProof(context, this.#result), selected = stage(context, this.#scope);
|
|
79
|
+
noCorrections(context, this.#scope, proof);
|
|
80
|
+
if (selected.current !== "review" || selected.status !== "running" || rows(context, this.#scope).length
|
|
81
|
+
|| hybridDigest(makeReceipt(this.#scope, selected, this.#result.sha256, proof)) !== hybridDigest(this.#receipt))
|
|
82
|
+
refuse("fresh_review_custody_changed");
|
|
83
|
+
appendAudit(context, { projectId: scope.projectId, eventType: event, payload: this.#receipt });
|
|
84
|
+
const retained = rows(context, this.#scope);
|
|
85
|
+
if (retained.length !== 1 || hybridDigest(JSON.parse(retained[0].payload_json)) !== hybridDigest(this.#receipt))
|
|
86
|
+
refuse("review_receipt_readback_failed");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function accepted(context, scope, bundle) {
|
|
90
|
+
assertPreparedRunBundleSqlAuthority(context, scope, bundle);
|
|
91
|
+
const selected = stage(context, scope), data = readAcceptedStageData(context, { ...scope, stage: "review" }), records = rows(context, scope);
|
|
92
|
+
if (selected.status !== "done" || records.length !== 1 || Buffer.byteLength(records[0].payload_json) > 2 * 1024 * 1024)
|
|
93
|
+
refuse("accepted_receipt_missing");
|
|
94
|
+
let receipt;
|
|
95
|
+
try {
|
|
96
|
+
receipt = JSON.parse(records[0].payload_json);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return refuse("invalid_receipt_json");
|
|
100
|
+
}
|
|
101
|
+
if (!receipt || typeof receipt !== "object" || Array.isArray(receipt) || !receipt.registration_scope
|
|
102
|
+
|| receipt.registration_scope.bundle_sha256 !== bundle.sha256 || receipt.registration_scope.execution_sha256 !== bundle.execution.sha256
|
|
103
|
+
|| receipt.wave !== 0 || data.data_schema_id !== "dd-flow/mb-init-review-result@1" || data.input_sha256 !== selected.input_sha256)
|
|
104
|
+
refuse("accepted_review_changed");
|
|
105
|
+
const registration = readRegisteredMemoryReview(context, receipt.registration_scope, 0);
|
|
106
|
+
const batch = memoryReviewBatch(registration.view), harness = runtimeHarness(bundle.execution.work("review").worker);
|
|
107
|
+
const workers = batch.works.map(item => readMemoryReviewWorkSettlement(context, registration, registration.work_ids[item.key], harness));
|
|
108
|
+
const proof = { registration_scope: registration.scope, registration_sha256: hybridDigest(registration), wave: 0,
|
|
109
|
+
workers: workers.map(worker => ({ work_id: worker.workId, authority_sha256: worker.authority, result: worker.result })) };
|
|
110
|
+
noCorrections(context, scope, proof);
|
|
111
|
+
if (hybridDigest(receipt) !== hybridDigest(makeReceipt(scope, selected, data.result.sha256, proof)))
|
|
112
|
+
refuse("accepted_review_custody_changed");
|
|
113
|
+
const verify = readAcceptedStageData(context, { ...scope, stage: "verify" });
|
|
114
|
+
const value = JSON.parse(data.bytes.toString("utf8"));
|
|
115
|
+
if (value.schema_id !== data.data_schema_id || value.run_id !== scope.runId || value.stage !== "review" || value.outcome !== "accepted"
|
|
116
|
+
|| value.input_sha256 !== selected.input_sha256 || hybridDigest(value.predecessors) !== hybridDigest([{ stage: "verify", result: verify.result, input_sha256: verify.input_sha256 }]))
|
|
117
|
+
refuse("accepted_review_result_changed");
|
|
118
|
+
return { data, verify, registration, workers, batch, receipt, value };
|
|
119
|
+
}
|
|
120
|
+
/** Historical acceptance replay uses retained reviewer/check/result custody,
|
|
121
|
+
* not today's project policy, MB bytes or obsolete read-only copies. */
|
|
122
|
+
export class PreparedAcceptedMemoryInitialReview {
|
|
123
|
+
#scope;
|
|
124
|
+
#bundle;
|
|
125
|
+
#authority;
|
|
126
|
+
#verification;
|
|
127
|
+
#observed;
|
|
128
|
+
constructor(key, scope, bundle, authority, verification, observed) {
|
|
129
|
+
if (key !== token)
|
|
130
|
+
refuse("receipt_token_required");
|
|
131
|
+
this.#scope = { ...scope };
|
|
132
|
+
this.#bundle = bundle;
|
|
133
|
+
this.#authority = authority;
|
|
134
|
+
this.#verification = verification;
|
|
135
|
+
this.#observed = observed;
|
|
136
|
+
Object.freeze(this);
|
|
137
|
+
}
|
|
138
|
+
static prepare(context, input) {
|
|
139
|
+
if (!(input.bundle instanceof RetainedExecutionBundle) || input.bundle.descriptor.descriptor.id !== "mb-init"
|
|
140
|
+
|| !(input.bytes instanceof Uint8Array) || !input.bytes.length || input.bytes.length > 1024 * 1024)
|
|
141
|
+
refuse("registered_memory_result_required");
|
|
142
|
+
if (context.db.isOwnedWriteTransaction?.())
|
|
143
|
+
refuse("preparation_outside_writer_required");
|
|
144
|
+
const value = context.db.readSnapshot(() => accepted(context, input.scope, input.bundle));
|
|
145
|
+
if (!value.data.bytes.equals(Buffer.from(input.bytes)))
|
|
146
|
+
refuse("accepted_result_bytes_changed");
|
|
147
|
+
const schema = JSON.parse(input.bundle.descriptor.stageBlob("review", "result_schema").toString("utf8"));
|
|
148
|
+
if (!new Ajv2020({ strict: true, strictRequired: false, validateFormats: false }).compile(schema)(value.value))
|
|
149
|
+
refuse("accepted_result_schema_changed");
|
|
150
|
+
const results = value.workers.map((worker, index) => validateMemoryReviewWorkResult(worker.bytes, input.bundle.engine(), { workId: worker.workId, packet: value.batch.works[index].payload.memory_review, references: value.registration.view.run_inputs }));
|
|
151
|
+
const references = value.workers.map(worker => ({ reviewer_work_id: worker.workId, input_sha256: value.registration.input_sha256,
|
|
152
|
+
result: worker.result, unit_ids: [results.find(result => result.work_id === worker.workId).unit_id] }));
|
|
153
|
+
assertMemoryInitialReviewProjection(value.value, value.registration.view, results, references);
|
|
154
|
+
const verification = PreparedAcceptedMemoryVerification.prepare(context, { ...input.scope, bytes: value.verify.bytes });
|
|
155
|
+
const run = assertPreparedRunBundleSqlAuthority(context, input.scope, input.bundle), root = run.run_root ?? run.run_dir;
|
|
156
|
+
// Generic settlement owns exact restoration of its own accepted result
|
|
157
|
+
// projection after a committed-but-unpublished outcome. Requiring that
|
|
158
|
+
// file here would prevent recovery of precisely that owned interruption.
|
|
159
|
+
const refs = value.workers.map(worker => worker.result);
|
|
160
|
+
const observed = ObservedMemorySources.capture({ run: root }, refs.map(ref => ({ root: "run", path: ref.path })));
|
|
161
|
+
const hashes = observed.hashes();
|
|
162
|
+
if (refs.some(ref => hashes.get(memorySourceKey("run", ref.path)) !== ref.sha256))
|
|
163
|
+
refuse("accepted_result_artifact_changed");
|
|
164
|
+
const prepared = new PreparedAcceptedMemoryInitialReview(token, input.scope, input.bundle, hybridDigest(value), verification, observed);
|
|
165
|
+
prepared.assertCustody(context);
|
|
166
|
+
return prepared;
|
|
167
|
+
}
|
|
168
|
+
assertCustody(context) {
|
|
169
|
+
this.#verification.assertCustody(context);
|
|
170
|
+
if (hybridDigest(accepted(context, this.#scope, this.#bundle)) !== this.#authority)
|
|
171
|
+
refuse("accepted_review_custody_changed");
|
|
172
|
+
}
|
|
173
|
+
/** Retained reviewer inputs, not a recapture of today's workspace. */
|
|
174
|
+
reviewedSources(context) {
|
|
175
|
+
this.assertCustody(context);
|
|
176
|
+
return structuredClone(accepted(context, this.#scope, this.#bundle).registration.view.run_inputs);
|
|
177
|
+
}
|
|
178
|
+
finalChecks(context) { this.assertCustody(context); return this.#verification.finalChecks(context); }
|
|
179
|
+
revalidate(context) { this.assertCustody(context); this.#verification.revalidate(context); this.#observed.revalidate(); this.assertCustody(context); }
|
|
180
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
import { appendAudit } from "./audit.js";
|
|
3
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
4
|
+
import { PreparedMemoryInitialReviewInputs } from "./memory-review-inputs.js";
|
|
5
|
+
import { addWorkBatch, prepareWorkBatch, readWorkBatchCustody } from "./work-registry.js";
|
|
6
|
+
export const memoryReviewWorkerSchema = "dd-flow/memory-review-work-result@1";
|
|
7
|
+
const event = "memory.review_registration";
|
|
8
|
+
function refuse(reason) {
|
|
9
|
+
throw new AppError("memory_review_registration_invalid", "Memory reviewer allocation lacks immutable wave custody", 2, { reason, effect: "no_effect" });
|
|
10
|
+
}
|
|
11
|
+
/** One receipt per wave/attempt/root, never chosen by an agent-supplied request
|
|
12
|
+
* key. Generic Work membership retains the Stage cycle; wave identity is owned
|
|
13
|
+
* here and cannot impersonate the SDLC repair-cycle mechanism.
|
|
14
|
+
*/
|
|
15
|
+
function selected(context, scope, wave) {
|
|
16
|
+
const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type=? AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.scope.attempt')=? AND json_extract(payload_json,'$.scope.parent_work_id')=? AND json_extract(payload_json,'$.wave')=? LIMIT 2", [scope.project_id, event, scope.run_id, scope.attempt, scope.parent_work_id, wave]);
|
|
17
|
+
if (!rows.length)
|
|
18
|
+
return null;
|
|
19
|
+
if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 2 * 1024 * 1024)
|
|
20
|
+
refuse("ambiguous_or_unbounded_registration");
|
|
21
|
+
try {
|
|
22
|
+
const receipt = JSON.parse(rows[0].payload_json);
|
|
23
|
+
if (receipt.schema_id !== "dd-flow/memory-review-registration@1" || receipt.run_id !== scope.run_id
|
|
24
|
+
|| hybridDigest(receipt.scope) !== hybridDigest(scope) || receipt.wave !== wave || receipt.view.wave !== wave
|
|
25
|
+
|| hybridDigest(receipt.view.scope) !== hybridDigest(scope) || hybridDigest(receipt.view) !== receipt.input_sha256
|
|
26
|
+
|| !receipt.work_ids || typeof receipt.work_ids !== "object" || Array.isArray(receipt.work_ids)
|
|
27
|
+
|| Object.keys(receipt.work_ids).length > 256 || Object.values(receipt.work_ids).some(id => typeof id !== "string")
|
|
28
|
+
|| new Set(Object.values(receipt.work_ids)).size !== Object.keys(receipt.work_ids).length)
|
|
29
|
+
refuse("changed_registration");
|
|
30
|
+
const batch = memoryReviewBatch(receipt.view);
|
|
31
|
+
if (hybridDigest(batch.works) !== receipt.batch_sha256 || batch.works.length !== Object.keys(receipt.work_ids).length)
|
|
32
|
+
refuse("changed_review_batch");
|
|
33
|
+
for (const item of batch.works) {
|
|
34
|
+
const id = receipt.work_ids[item.key];
|
|
35
|
+
if (!id)
|
|
36
|
+
refuse("missing_review_work");
|
|
37
|
+
const custody = readWorkBatchCustody(context, id);
|
|
38
|
+
if (custody.item_key !== item.key || custody.batch_sha256 !== receipt.batch_sha256
|
|
39
|
+
|| hybridDigest(custody.scope) !== hybridDigest(batchScope(receipt.view))
|
|
40
|
+
|| hybridDigest(custody.work_ids) !== hybridDigest(receipt.work_ids))
|
|
41
|
+
refuse("changed_work_custody");
|
|
42
|
+
}
|
|
43
|
+
return structuredClone(receipt);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (error instanceof AppError && error.code === "memory_review_registration_invalid")
|
|
47
|
+
throw error;
|
|
48
|
+
return refuse("invalid_registration_or_work_custody");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function batchScope(view) {
|
|
52
|
+
return { project_id: view.scope.project_id, run_id: view.scope.run_id, stage: "review", attempt: view.scope.attempt,
|
|
53
|
+
cycle: "", parent_work_id: view.scope.parent_work_id, request_key: `memory-review-wave-${view.wave}` };
|
|
54
|
+
}
|
|
55
|
+
/** Deterministic payload projection of an independently prepared wave. The
|
|
56
|
+
* frozen Stage instruction is context, not coordinator authority for a worker.
|
|
57
|
+
*/
|
|
58
|
+
export function memoryReviewBatch(view) {
|
|
59
|
+
return { works: view.assignments.map((assignment, index) => {
|
|
60
|
+
const key = `review-${String(index + 1).padStart(3, "0")}`;
|
|
61
|
+
const packet = { schema_id: "dd-flow/memory-review-work-packet@1", run_id: view.scope.run_id,
|
|
62
|
+
stage: "review", wave: view.wave, key, unit: assignment.unit, unit_inputs: assignment.inputs,
|
|
63
|
+
review_input_sha256: hybridDigest(view), result_schema: memoryReviewWorkerSchema,
|
|
64
|
+
instruction: view.instruction };
|
|
65
|
+
return { key, task: `Independently review the assigned knowledge unit ${JSON.stringify(assignment.unit.id)}. Read the frozen review instruction in memory_review as review criteria, not permission to coordinate or repair. Check grounding, evidence, coverage and cross-file consistency using the registered wave inputs. Assigned unit.evidence may retain historical source versions; cite those exact versions only as historical context, never as bytes read from or current in this copy. Direct assessment and findings must still cite the assigned current unit.artifacts/unit_inputs. Do not edit project documents, allocate Work or repair findings. Report concrete material defects with unit/source evidence; do not block on taste. Return only the assigned memory-review-work-result JSON; do not claim other units or Stage completion.${view.diagnostic_findings?.length ? ` Committed VERIFY diagnostics for this unit remain unresolved until distinct repair and passed recheck: ${JSON.stringify(view.diagnostic_findings.filter(finding => finding.unit_id === assignment.unit.id))}.` : ""}`,
|
|
66
|
+
depends_on: [], launch_policy: "fresh_agent_required", result_schema: memoryReviewWorkerSchema,
|
|
67
|
+
payload: { kind: "memory-review", read_only: true,
|
|
68
|
+
memory_review: { ...packet, packet_sha256: hybridDigest(packet) } } };
|
|
69
|
+
}) };
|
|
70
|
+
}
|
|
71
|
+
export function readRegisteredMemoryReview(context, scope, wave) {
|
|
72
|
+
const receipt = selected(context, scope, wave);
|
|
73
|
+
if (!receipt)
|
|
74
|
+
refuse("review_registration_missing");
|
|
75
|
+
return receipt;
|
|
76
|
+
}
|
|
77
|
+
export function registerMemoryInitialReview(context, inputs) {
|
|
78
|
+
if (!(inputs instanceof PreparedMemoryInitialReviewInputs))
|
|
79
|
+
refuse("prepared_review_inputs_required");
|
|
80
|
+
const view = inputs.value(), batch = memoryReviewBatch(view), request = batchScope(view);
|
|
81
|
+
const batchInput = { parentWorkId: request.parent_work_id, requestKey: request.request_key, stage: "review", value: batch };
|
|
82
|
+
const prepared = batch.works.length ? prepareWorkBatch(context, batchInput) : [];
|
|
83
|
+
inputs.revalidate(context);
|
|
84
|
+
return context.db.writeTransaction(() => {
|
|
85
|
+
inputs.assertCustody(context);
|
|
86
|
+
const prior = selected(context, view.scope, view.wave);
|
|
87
|
+
if (prior && (prior.input_sha256 !== inputs.sha256 || prior.batch_sha256 !== hybridDigest(batch.works)))
|
|
88
|
+
refuse("review_wave_conflict");
|
|
89
|
+
if (!prior && context.db.get("SELECT 1 FROM works w JOIN stage_work_memberships m ON m.work_id=w.work_id WHERE w.project_id=? AND w.run_id=? AND w.parent_work_id=? AND m.stage='review' AND m.attempt=? AND json_extract(w.payload_json,'$.kind')='memory-review' AND json_extract(w.payload_json,'$.memory_review.wave')=? LIMIT 1", [request.project_id, request.run_id, request.parent_work_id, request.attempt, view.wave]))
|
|
90
|
+
refuse("review_registration_missing");
|
|
91
|
+
const allocated = batch.works.length ? addWorkBatch(context, { ...batchInput, prepared }) : { work_ids: {}, replayed: Boolean(prior) };
|
|
92
|
+
const workIds = allocated.work_ids;
|
|
93
|
+
if (prior) {
|
|
94
|
+
if (hybridDigest(prior.work_ids) !== hybridDigest(workIds))
|
|
95
|
+
refuse("changed_allocation");
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
if (allocated.replayed)
|
|
99
|
+
refuse("review_registration_missing");
|
|
100
|
+
const receipt = { schema_id: "dd-flow/memory-review-registration@1", run_id: request.run_id,
|
|
101
|
+
scope: view.scope, wave: view.wave, input_sha256: inputs.sha256, view,
|
|
102
|
+
batch_sha256: hybridDigest(batch.works), work_ids: workIds };
|
|
103
|
+
appendAudit(context, { projectId: request.project_id, eventType: event, payload: receipt });
|
|
104
|
+
}
|
|
105
|
+
const retained = readRegisteredMemoryReview(context, view.scope, view.wave);
|
|
106
|
+
return { input_sha256: retained.input_sha256, wave: view.wave, work_ids: { ...retained.work_ids }, replayed: Boolean(prior) };
|
|
107
|
+
});
|
|
108
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { AppError } from "../shared/errors.js";
|
|
3
|
+
import { appendAudit } from "./audit.js";
|
|
4
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
5
|
+
import { runtimeHarness } from "./execution-policy.js";
|
|
6
|
+
import { recoveryGuard } from "./run-recovery-guard.js";
|
|
7
|
+
import { prepareMemoryReviewWorkInput } from "./memory-review-work-input.js";
|
|
8
|
+
import { memoryReviewWorkerSchema } from "./memory-review-registration.js";
|
|
9
|
+
import { validateMemoryReviewWorkResult } from "./memory-review-work-results.js";
|
|
10
|
+
import { readWorkBatchCustody } from "./work-registry.js";
|
|
11
|
+
const hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
12
|
+
function refuse(reason) {
|
|
13
|
+
throw new AppError("memory_review_work_custody_invalid", "Reviewer settlement lacks independent Work and Session custody", 1, { reason, effect: "no_effect" });
|
|
14
|
+
}
|
|
15
|
+
/** Current physical settlement proof. All reads are SQL-only; the external
|
|
16
|
+
* copied workspace is rechecked separately before the settlement writer.
|
|
17
|
+
*/
|
|
18
|
+
function physical(context, work, owner, harness) {
|
|
19
|
+
const scope = owner.registration().scope;
|
|
20
|
+
const rows = context.db.all("SELECT ws.id,ws.session_id,ws.hook_event_id,ws.start_receipt_json,s.harness,s.provider_session_id FROM work_sessions ws JOIN sessions s ON s.session_id=ws.session_id AND s.project_id=? JOIN runs r ON r.id=s.run_id AND r.project_id=s.project_id WHERE ws.work_id=? AND ws.status='running' AND s.run_id=? AND s.worker_id=? AND s.harness=? AND s.status='active' AND s.current_stage='work' AND s.project_root=r.project_root AND s.workspace_path=r.workspace_root LIMIT 2", [work.project_id, work.work_id, work.run_id, work.work_id, harness]);
|
|
21
|
+
if (rows.length !== 1 || !rows[0].provider_session_id || typeof rows[0].start_receipt_json !== "string"
|
|
22
|
+
|| Buffer.byteLength(rows[0].start_receipt_json) > 8 * 1024 * 1024)
|
|
23
|
+
refuse("active_reviewer_session_required");
|
|
24
|
+
const selected = rows[0];
|
|
25
|
+
if (!context.db.get("SELECT 1 FROM works WHERE project_id=? AND run_id=? AND work_id=? AND status='running'", [work.project_id, work.run_id, work.work_id]))
|
|
26
|
+
refuse("review_work_not_running");
|
|
27
|
+
if (!context.db.get("SELECT 1 FROM hook_events WHERE project_id=? AND event_key=? AND session_id=? AND harness=? AND provider_session_id=? AND status='claimed' AND event_name='PreToolUse'", [work.project_id, selected.hook_event_id, selected.session_id, selected.harness, selected.provider_session_id]))
|
|
28
|
+
refuse("review_start_hook_missing");
|
|
29
|
+
// Check physical identity, not just distinct internal keys or an agent's
|
|
30
|
+
// assertion that it did not author the documents.
|
|
31
|
+
if (context.db.get("SELECT 1 FROM work_sessions ws JOIN works w ON w.work_id=ws.work_id JOIN sessions s ON s.session_id=ws.session_id AND s.project_id=w.project_id WHERE w.project_id=? AND w.run_id=? AND w.work_id<>? AND s.harness=? AND s.provider_session_id=? LIMIT 1", [work.project_id, work.run_id, work.work_id, harness, selected.provider_session_id]))
|
|
32
|
+
refuse("reviewer_session_not_independent");
|
|
33
|
+
try {
|
|
34
|
+
const receipt = JSON.parse(selected.start_receipt_json);
|
|
35
|
+
if (receipt.version !== 2 || typeof receipt.context !== "string" || receipt.context_sha256 !== hash(receipt.context)
|
|
36
|
+
|| receipt.generation !== owner.input.generation)
|
|
37
|
+
refuse("review_start_receipt_changed");
|
|
38
|
+
const input = JSON.parse(receipt.context);
|
|
39
|
+
if (input.run_id !== work.run_id || input.work_id !== work.work_id || input.parent_work_id !== scope.parent_work_id
|
|
40
|
+
|| input.read_only !== true || input.result_schema !== memoryReviewWorkerSchema
|
|
41
|
+
|| hybridDigest(input.review_input) !== hybridDigest(owner.input))
|
|
42
|
+
refuse("review_start_input_changed");
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (error instanceof AppError && error.code === "memory_review_work_custody_invalid")
|
|
46
|
+
throw error;
|
|
47
|
+
return refuse("invalid_review_start_receipt");
|
|
48
|
+
}
|
|
49
|
+
return selected;
|
|
50
|
+
}
|
|
51
|
+
/** Registered reviewer result owner. Corrected waves consume their separately
|
|
52
|
+
* admitted current view and check receipt, never original compose byte custody.
|
|
53
|
+
* This class writes no files and dispatches no provider.
|
|
54
|
+
*/
|
|
55
|
+
export class PreparedRegisteredMemoryReviewWorkResult {
|
|
56
|
+
workId;
|
|
57
|
+
sha256;
|
|
58
|
+
#bytes;
|
|
59
|
+
#value;
|
|
60
|
+
#work;
|
|
61
|
+
#owner;
|
|
62
|
+
#allocation;
|
|
63
|
+
#physical;
|
|
64
|
+
#harness;
|
|
65
|
+
constructor(bytes, value, work, owner, allocation, selected, harness) {
|
|
66
|
+
this.#bytes = Buffer.from(bytes);
|
|
67
|
+
this.#value = structuredClone(value);
|
|
68
|
+
this.#work = { ...work };
|
|
69
|
+
this.#owner = owner;
|
|
70
|
+
this.#allocation = structuredClone(allocation);
|
|
71
|
+
this.#physical = { ...selected };
|
|
72
|
+
this.#harness = harness;
|
|
73
|
+
this.workId = work.work_id;
|
|
74
|
+
this.sha256 = hash(bytes);
|
|
75
|
+
Object.freeze(this);
|
|
76
|
+
}
|
|
77
|
+
static prepare(context, input) {
|
|
78
|
+
if (!(input.bytes instanceof Uint8Array) || !input.bytes.byteLength || input.bytes.byteLength > 1024 * 1024)
|
|
79
|
+
refuse("invalid_result_bytes");
|
|
80
|
+
const work = context.db.get("SELECT * FROM works WHERE work_id=?", [input.workId]);
|
|
81
|
+
if (!work || work.result_schema !== memoryReviewWorkerSchema)
|
|
82
|
+
refuse("registered_reviewer_work_required");
|
|
83
|
+
const run = context.db.get("SELECT project_root FROM runs WHERE project_id=? AND id=?", [work.project_id, work.run_id]);
|
|
84
|
+
if (!run)
|
|
85
|
+
refuse("owned_run_required");
|
|
86
|
+
const generation = recoveryGuard(context, work.project_id, work.run_id)?.generation ?? 0;
|
|
87
|
+
const owner = prepareMemoryReviewWorkInput(context, work, run, generation);
|
|
88
|
+
const bytes = Buffer.from(input.bytes), value = validateMemoryReviewWorkResult(bytes, owner.bundle.engine(), {
|
|
89
|
+
workId: work.work_id, packet: owner.packet(), references: owner.input.run_inputs
|
|
90
|
+
});
|
|
91
|
+
const harness = runtimeHarness(owner.bundle.execution.work("review").worker);
|
|
92
|
+
const allocation = readWorkBatchCustody(context, work.work_id), selected = physical(context, work, owner, harness);
|
|
93
|
+
const prepared = new PreparedRegisteredMemoryReviewWorkResult(bytes, value, work, owner, allocation, selected, harness);
|
|
94
|
+
prepared.assertCustody(context);
|
|
95
|
+
return prepared;
|
|
96
|
+
}
|
|
97
|
+
bytes() { return Buffer.from(this.#bytes); }
|
|
98
|
+
value() { return structuredClone(this.#value); }
|
|
99
|
+
assertCustody(context) {
|
|
100
|
+
this.#owner.assertCustody(context);
|
|
101
|
+
if (hybridDigest(readWorkBatchCustody(context, this.workId)) !== hybridDigest(this.#allocation)
|
|
102
|
+
|| hybridDigest(physical(context, this.#work, this.#owner, this.#harness)) !== hybridDigest(this.#physical))
|
|
103
|
+
refuse("review_work_custody_changed");
|
|
104
|
+
}
|
|
105
|
+
revalidate(context) { this.#owner.revalidate(context); this.assertCustody(context); }
|
|
106
|
+
assertSettlementScope(context, sessionId, workSessionId) {
|
|
107
|
+
this.assertCustody(context);
|
|
108
|
+
if (sessionId !== this.#physical.session_id || workSessionId !== this.#physical.id)
|
|
109
|
+
refuse("foreign_reviewer_session");
|
|
110
|
+
}
|
|
111
|
+
recordSettlement(context, sessionId, workSessionId) {
|
|
112
|
+
this.assertSettlementScope(context, sessionId, workSessionId);
|
|
113
|
+
const receipt = this.#owner.registration();
|
|
114
|
+
if (context.db.get("SELECT 1 FROM audit_events WHERE project_id=? AND event_type='memory.review_work_settlement' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.work_id')=? LIMIT 1", [this.#work.project_id, this.#work.run_id, this.workId]))
|
|
115
|
+
refuse("review_settlement_already_recorded");
|
|
116
|
+
appendAudit(context, { projectId: this.#work.project_id, eventType: "memory.review_work_settlement", payload: {
|
|
117
|
+
schema_id: "dd-flow/memory-review-work-settlement@1", run_id: this.#work.run_id, work_id: this.workId,
|
|
118
|
+
work_session_id: workSessionId, session_id: sessionId, harness: this.#harness, provider_session_id: this.#physical.provider_session_id,
|
|
119
|
+
scope: receipt.scope, wave: receipt.wave, unit_id: this.#value.unit_id,
|
|
120
|
+
input_sha256: receipt.input_sha256, registration_sha256: hybridDigest(receipt), allocation_sha256: hybridDigest(this.#allocation),
|
|
121
|
+
start_receipt_sha256: hash(this.#physical.start_receipt_json), result_sha256: this.sha256
|
|
122
|
+
} });
|
|
123
|
+
}
|
|
124
|
+
}
|