@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,65 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
import { FrozenGenericExecution } from "./generic-execution.js";
|
|
3
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
4
|
+
import { readMemoryInitBootstrapReadiness } from "./memory-init-bootstrap-readiness.js";
|
|
5
|
+
import { readMemoryInitProjectBootstrap } from "./memory-init-project-bootstrap.js";
|
|
6
|
+
import { assertPublishedMemoryLayerOwnership } from "./memory-layer-publication.js";
|
|
7
|
+
import { observeMemoryInitSourceInventory } from "./memory-init-preflight.js";
|
|
8
|
+
function refuse(reason) {
|
|
9
|
+
throw new AppError("memory_baseline_preservation_invalid", "Composition cannot accept changed bootstrap sources", 1, { reason, effect: "no_effect" });
|
|
10
|
+
}
|
|
11
|
+
/** Pre-integration source preservation, not semantic scope or stage authority.
|
|
12
|
+
* Only the accepted publisher's ownership is checked before using its MB exclusion.
|
|
13
|
+
* This admits no caller-supplied baseline and never repairs or republishes files.
|
|
14
|
+
*/
|
|
15
|
+
export class PreparedMemoryBaselinePreservation {
|
|
16
|
+
sourceSha256;
|
|
17
|
+
#execution;
|
|
18
|
+
#authoritySha256;
|
|
19
|
+
#inventorySha256;
|
|
20
|
+
constructor(execution, authoritySha256, inventorySha256, sourceSha256) {
|
|
21
|
+
this.#execution = execution;
|
|
22
|
+
this.#authoritySha256 = authoritySha256;
|
|
23
|
+
this.#inventorySha256 = inventorySha256;
|
|
24
|
+
this.sourceSha256 = sourceSha256;
|
|
25
|
+
Object.freeze(this);
|
|
26
|
+
}
|
|
27
|
+
static accepted(context, execution) {
|
|
28
|
+
if (!(execution instanceof FrozenGenericExecution) || execution.settings.flow_id !== "mb-init")
|
|
29
|
+
refuse("unprepared_execution");
|
|
30
|
+
const scope = execution.snapshot().scope;
|
|
31
|
+
const accepted = readMemoryInitBootstrapReadiness(context, { requestId: scope.requestId, projectRoot: scope.projectRoot, workspaceRoot: scope.workspace });
|
|
32
|
+
if (!accepted || accepted.authority.binding.execution_sha256 !== execution.sha256)
|
|
33
|
+
refuse("accepted_bootstrap_required");
|
|
34
|
+
return accepted;
|
|
35
|
+
}
|
|
36
|
+
static observe(context, accepted) {
|
|
37
|
+
const binding = accepted.authority.binding, scope = binding.scope;
|
|
38
|
+
const registered = readMemoryInitProjectBootstrap(context, scope);
|
|
39
|
+
if (!registered)
|
|
40
|
+
refuse("accepted_bootstrap_required");
|
|
41
|
+
assertPublishedMemoryLayerOwnership(context.ddFlowHome, { ...scope, targetSha256: registered.receipt.target_sha256,
|
|
42
|
+
layerSha256: registered.receipt.layer_sha256, packSha256: binding.pack_sha256, installationSha256: binding.installation_manifest_sha256 }, binding.target, registered.receipt.layer_publication_sha256, registered.receipt.draft_identity_sha256);
|
|
43
|
+
return observeMemoryInitSourceInventory(binding.target, accepted.authority.receipt.baseline_sha256, context.env);
|
|
44
|
+
}
|
|
45
|
+
static prepare(context, execution) {
|
|
46
|
+
const accepted = this.accepted(context, execution);
|
|
47
|
+
const inventory = this.observe(context, accepted);
|
|
48
|
+
const prepared = new PreparedMemoryBaselinePreservation(execution, hybridDigest(accepted.authority), inventory.sha256, accepted.authority.binding.target.source_sha256);
|
|
49
|
+
prepared.assertCustody(context);
|
|
50
|
+
return prepared;
|
|
51
|
+
}
|
|
52
|
+
/** SQL-only acceptance fence. Physical checks must precede the writer. */
|
|
53
|
+
assertCustody(context) {
|
|
54
|
+
if (hybridDigest(PreparedMemoryBaselinePreservation.accepted(context, this.#execution).authority) !== this.#authoritySha256)
|
|
55
|
+
refuse("bootstrap_authority_changed");
|
|
56
|
+
}
|
|
57
|
+
revalidate(context) {
|
|
58
|
+
this.assertCustody(context);
|
|
59
|
+
const accepted = PreparedMemoryBaselinePreservation.accepted(context, this.#execution);
|
|
60
|
+
const inventory = PreparedMemoryBaselinePreservation.observe(context, accepted);
|
|
61
|
+
if (inventory.sha256 !== this.#inventorySha256)
|
|
62
|
+
refuse("baseline_inventory_changed");
|
|
63
|
+
this.assertCustody(context);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
3
|
+
import { PreparedRegisteredMemoryCorrections } from "./memory-repair-registration.js";
|
|
4
|
+
import { PreparedRetainedSecondMemoryRepairs } from "./memory-retained-second-repairs.js";
|
|
5
|
+
import { PreparedSettledMemoryRepair } from "./memory-repair-history.js";
|
|
6
|
+
import { PreparedMemoryRepairWorkspace } from "./memory-repair-workspace.js";
|
|
7
|
+
import { reviewSourcePaths } from "./review-copy.js";
|
|
8
|
+
import { readAcceptedStageData } from "./stage-report-renderer.js";
|
|
9
|
+
const token = Symbol("completed-memory-correction-wave");
|
|
10
|
+
const historicalToken = Symbol("historical-second-memory-correction-wave");
|
|
11
|
+
function refuse(reason) {
|
|
12
|
+
throw new AppError("memory_correction_wave_invalid", "Re-review lacks a completely settled correction graph and current document custody", 2, { reason, effect: "no_effect" });
|
|
13
|
+
}
|
|
14
|
+
/** Pure selection only. A repair claim does not establish current bytes or
|
|
15
|
+
* resolve a finding. Registered wave custody must validate these claims first.
|
|
16
|
+
* Assigned units always need independent re-review; previously accepted units
|
|
17
|
+
* only need it when their own evidence/artifacts intersect an actual delta. */
|
|
18
|
+
export function memoryCorrectionCurrentUnits(units, assignments) {
|
|
19
|
+
if (!Array.isArray(units) || !units.length || units.length > 256 || !Array.isArray(assignments) || assignments.length > 256
|
|
20
|
+
|| units.some(unit => !unit || typeof unit.id !== "string" || !unit.id)
|
|
21
|
+
|| new Set(units.map(unit => unit.id)).size !== units.length
|
|
22
|
+
|| assignments.some(item => !item?.unit || !units.some(unit => unit.id === item.unit.id && unit.aspect === item.unit.aspect
|
|
23
|
+
&& unit.applicability === item.unit.applicability))
|
|
24
|
+
|| new Set(assignments.map(item => item.unit.id)).size !== assignments.length)
|
|
25
|
+
refuse("invalid_current_unit_inventory");
|
|
26
|
+
const current = new Map(assignments.map(item => [item.unit.id, item.unit]));
|
|
27
|
+
return units.map(unit => structuredClone(current.get(unit.id) ?? unit));
|
|
28
|
+
}
|
|
29
|
+
export function memoryCorrectionAffectedUnits(units, repairs, composition = []) {
|
|
30
|
+
if (!Array.isArray(units) || !units.length || units.length > 256 || !Array.isArray(repairs) || !repairs.length || repairs.length > 256
|
|
31
|
+
|| units.some(unit => !unit || typeof unit.id !== "string" || !unit.id || !["applicable", "not-applicable"].includes(unit.applicability)
|
|
32
|
+
|| !Array.isArray(unit.evidence) || !Array.isArray(unit.artifacts)
|
|
33
|
+
|| [...unit.evidence, ...unit.artifacts].some(ref => !ref || typeof ref.root !== "string"
|
|
34
|
+
|| !["project", "workspace", "canon", "run"].includes(ref.root) || typeof ref.path !== "string" || !ref.path
|
|
35
|
+
|| ref.path.startsWith("/") || /^[A-Za-z]:/.test(ref.path) || ref.path.includes("\\") || ref.path.includes("\0")
|
|
36
|
+
|| ref.path.split("/").some((part) => !part || part === "." || part === "..")
|
|
37
|
+
|| typeof ref.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(ref.sha256)))
|
|
38
|
+
|| new Set(units.map(unit => unit.id)).size !== units.length)
|
|
39
|
+
refuse("invalid_unit_inventory");
|
|
40
|
+
const applicable = new Set(units.filter(unit => unit.applicability === "applicable").map(unit => unit.id));
|
|
41
|
+
const affected = new Set(), changed = new Set();
|
|
42
|
+
for (const repair of repairs) {
|
|
43
|
+
if (!repair || !Array.isArray(repair.unit_ids) || !repair.unit_ids.length || new Set(repair.unit_ids).size !== repair.unit_ids.length
|
|
44
|
+
|| repair.unit_ids.some((id) => !applicable.has(id)) || repair.result?.outcome !== "accepted"
|
|
45
|
+
|| !Array.isArray(repair.result.changes) || !repair.result.changes.length)
|
|
46
|
+
refuse("invalid_settled_repair_projection");
|
|
47
|
+
for (const id of repair.unit_ids)
|
|
48
|
+
affected.add(id);
|
|
49
|
+
for (const delta of repair.result.changes) {
|
|
50
|
+
if (!delta || typeof delta.path !== "string" || !delta.path.startsWith(".memory-bank/") || delta.path.includes("\\")
|
|
51
|
+
|| delta.path.includes("\0") || delta.path.split("/").some((part) => !part || part === "." || part === "..")
|
|
52
|
+
|| [delta.before_sha256, delta.after_sha256].some(hash => hash !== null && (typeof hash !== "string" || !/^[a-f0-9]{64}$/.test(hash)))
|
|
53
|
+
|| delta.before_sha256 === delta.after_sha256)
|
|
54
|
+
refuse("invalid_settled_delta_projection");
|
|
55
|
+
changed.add(delta.path);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
for (const unit of units)
|
|
59
|
+
if (unit.applicability === "applicable" && [...unit.evidence, ...unit.artifacts].some(ref => ["project", "workspace"].includes(ref.root) && changed.has(ref.path)))
|
|
60
|
+
affected.add(unit.id);
|
|
61
|
+
if (!Array.isArray(composition) || composition.length > 256 || composition.some(work => !work || typeof work.key !== "string" || !work.key
|
|
62
|
+
|| !Array.isArray(work.unit_ids) || !work.unit_ids.length || work.unit_ids.some((id) => !applicable.has(id))
|
|
63
|
+
|| !Array.isArray(work.depends_on) || new Set(work.depends_on).size !== work.depends_on.length)
|
|
64
|
+
|| new Set(composition.map(work => work.key)).size !== composition.length)
|
|
65
|
+
refuse("invalid_composition_dependencies");
|
|
66
|
+
const remaining = new Map(composition.map(work => [work.key, work])), done = new Set(), impacted = new Set();
|
|
67
|
+
if (composition.some(work => work.depends_on.some((key) => !remaining.has(key) || key === work.key)))
|
|
68
|
+
refuse("invalid_composition_dependencies");
|
|
69
|
+
while (remaining.size) {
|
|
70
|
+
const ready = [...remaining.values()].filter(work => work.depends_on.every((key) => done.has(key)));
|
|
71
|
+
if (!ready.length)
|
|
72
|
+
refuse("invalid_composition_dependencies");
|
|
73
|
+
for (const work of ready) {
|
|
74
|
+
remaining.delete(work.key);
|
|
75
|
+
done.add(work.key);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Units may span several composition Works. Reach a fixed point rather
|
|
79
|
+
// than making coverage depend on which independent branch sorted first.
|
|
80
|
+
let expanded;
|
|
81
|
+
do {
|
|
82
|
+
expanded = false;
|
|
83
|
+
for (const work of composition) {
|
|
84
|
+
const dependent = work.depends_on.some((key) => impacted.has(key));
|
|
85
|
+
if (!impacted.has(work.key) && (dependent || work.unit_ids.some((id) => affected.has(id)))) {
|
|
86
|
+
impacted.add(work.key);
|
|
87
|
+
expanded = true;
|
|
88
|
+
}
|
|
89
|
+
// Co-packing independent units into one Work is not a dependency.
|
|
90
|
+
// Only a declared changed predecessor expands its consumer units.
|
|
91
|
+
if (dependent)
|
|
92
|
+
for (const id of work.unit_ids)
|
|
93
|
+
if (!affected.has(id)) {
|
|
94
|
+
affected.add(id);
|
|
95
|
+
expanded = true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} while (expanded);
|
|
99
|
+
return units.filter(unit => affected.has(unit.id)).map(unit => unit.id);
|
|
100
|
+
}
|
|
101
|
+
function assertNoActiveChildren(context, registration) {
|
|
102
|
+
const scope = registration.receipt().scope;
|
|
103
|
+
if (context.db.get("SELECT 1 FROM works w JOIN stage_work_memberships m ON m.work_id=w.work_id AND m.project_id=w.project_id AND m.run_id=w.run_id WHERE w.project_id=? AND w.run_id=? AND w.parent_work_id=? AND m.stage='review' AND m.attempt=? AND w.status IN ('created','running','paused') LIMIT 1", [scope.project_id, scope.run_id, scope.parent_work_id, scope.attempt]))
|
|
104
|
+
refuse("review_children_active");
|
|
105
|
+
}
|
|
106
|
+
function selection(context, registration) {
|
|
107
|
+
registration.assertCustody(context);
|
|
108
|
+
const receipt = registration.receipt(), batch = registration.batch(), scope = receipt.scope;
|
|
109
|
+
if (receipt.wave !== (PreparedRetainedSecondMemoryRepairs.isPrepared(registration) ? 2 : 1))
|
|
110
|
+
refuse("unsupported_correction_wave");
|
|
111
|
+
const states = batch.works.map(item => {
|
|
112
|
+
const workId = receipt.work_ids[item.key];
|
|
113
|
+
const row = context.db.get("SELECT status FROM works WHERE project_id=? AND run_id=? AND work_id=? AND parent_work_id=?", [scope.project_id, scope.run_id, workId, scope.parent_work_id]);
|
|
114
|
+
if (row?.status !== "completed")
|
|
115
|
+
refuse("correction_graph_not_completed");
|
|
116
|
+
return { key: item.key, work_id: workId };
|
|
117
|
+
});
|
|
118
|
+
const ids = new Set(states.map(item => item.work_id));
|
|
119
|
+
if (!ids.size)
|
|
120
|
+
refuse("settlement_history_missing_or_ambiguous");
|
|
121
|
+
const all = context.db.all(`SELECT id,json_extract(payload_json,'$.work_id') AS work_id FROM audit_events WHERE project_id=? AND event_type='memory.repair_work_settlement' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.work_id') IN (${[...ids].map(() => "?").join(",")}) ORDER BY id LIMIT 257`, [scope.project_id, scope.run_id, ...ids]);
|
|
122
|
+
if (all.length > 256)
|
|
123
|
+
refuse("unbounded_settlement_history");
|
|
124
|
+
const settlements = all.filter(row => ids.has(row.work_id));
|
|
125
|
+
if (!states.length || settlements.length !== states.length || new Set(settlements.map(row => row.work_id)).size !== states.length)
|
|
126
|
+
refuse("settlement_history_missing_or_ambiguous");
|
|
127
|
+
const order = new Map(settlements.map((row, index) => [row.work_id, index]));
|
|
128
|
+
for (const item of batch.works)
|
|
129
|
+
for (const dependency of item.depends_on) {
|
|
130
|
+
const predecessor = receipt.work_ids[dependency], workId = receipt.work_ids[item.key];
|
|
131
|
+
if (!predecessor || order.get(predecessor) >= order.get(workId))
|
|
132
|
+
refuse("dependency_settlement_order_changed");
|
|
133
|
+
}
|
|
134
|
+
return { registration_sha256: registration.sha256, states, settlements };
|
|
135
|
+
}
|
|
136
|
+
const nonMemory = (entry) => entry.path !== ".memory-bank" && !entry.path.startsWith(".memory-bank/");
|
|
137
|
+
/** Closed second graph custody, not current workspace admission or accepted
|
|
138
|
+
* review. Reconstruction starts at independently retained first-wave state;
|
|
139
|
+
* neither current files nor a second writer's claimed before-view are anchors. */
|
|
140
|
+
export class PreparedHistoricalSecondMemoryCorrectionWave {
|
|
141
|
+
static isPrepared(value) {
|
|
142
|
+
return typeof value === "object" && value !== null && #registration in value;
|
|
143
|
+
}
|
|
144
|
+
#registration;
|
|
145
|
+
#selected;
|
|
146
|
+
#histories;
|
|
147
|
+
#baseline;
|
|
148
|
+
#affected;
|
|
149
|
+
constructor(key, registration, selected, histories, baseline, affected) {
|
|
150
|
+
if (key !== historicalToken)
|
|
151
|
+
refuse("historical_second_wave_token_required");
|
|
152
|
+
this.#registration = registration;
|
|
153
|
+
this.#selected = structuredClone(selected);
|
|
154
|
+
this.#histories = histories;
|
|
155
|
+
this.#baseline = baseline;
|
|
156
|
+
this.#affected = [...affected];
|
|
157
|
+
Object.freeze(this);
|
|
158
|
+
}
|
|
159
|
+
static prepare(context, input) {
|
|
160
|
+
if (context.db.isOwnedWriteTransaction?.())
|
|
161
|
+
refuse("preparation_outside_writer_required");
|
|
162
|
+
const registration = PreparedRetainedSecondMemoryRepairs.prepareAfterReview(context, input);
|
|
163
|
+
const selected = context.db.readSnapshot(() => selection(context, registration));
|
|
164
|
+
const histories = selected.settlements.map(row => PreparedSettledMemoryRepair.prepare(context, registration, row.work_id));
|
|
165
|
+
let baseline = registration.initialDocumentBaseline();
|
|
166
|
+
const prefixes = new Set([hybridDigest(baseline.entries())]);
|
|
167
|
+
for (const history of histories) {
|
|
168
|
+
if (!prefixes.has(hybridDigest(history.before().entries())))
|
|
169
|
+
refuse("unexplained_historical_input_view");
|
|
170
|
+
baseline = baseline.composeSettledDelta(history.before(), history.after());
|
|
171
|
+
prefixes.add(hybridDigest(baseline.entries()));
|
|
172
|
+
}
|
|
173
|
+
const batch = registration.batch(), receipt = registration.receipt(), first = registration.initialInputView();
|
|
174
|
+
const compose = readAcceptedStageData(context, { ...input.scope, stage: "compose" });
|
|
175
|
+
const affected = memoryCorrectionAffectedUnits(memoryCorrectionCurrentUnits(first.unit_inventory, first.assignments), histories.map(history => ({ result: history.result(),
|
|
176
|
+
unit_ids: batch.works.find(item => receipt.work_ids[item.key] === history.workId).payload.memory_repair.unit_ids })), JSON.parse(compose.bytes.toString("utf8")).details.works);
|
|
177
|
+
const prepared = new PreparedHistoricalSecondMemoryCorrectionWave(historicalToken, registration, selected, histories, baseline, affected);
|
|
178
|
+
prepared.assertCustody(context);
|
|
179
|
+
return prepared;
|
|
180
|
+
}
|
|
181
|
+
registration() { return this.#registration; }
|
|
182
|
+
baseline() { return this.#baseline; }
|
|
183
|
+
histories() { return [...this.#histories]; }
|
|
184
|
+
affectedUnitIds() { return [...this.#affected]; }
|
|
185
|
+
repairResults() {
|
|
186
|
+
const batch = this.#registration.batch(), receipt = this.#registration.receipt();
|
|
187
|
+
return this.#histories.map(history => ({ work_id: history.workId, result: history.result(), reference: history.reference(),
|
|
188
|
+
authority_sha256: history.sha256,
|
|
189
|
+
unit_ids: batch.works.find(item => receipt.work_ids[item.key] === history.workId).payload.memory_repair.unit_ids }));
|
|
190
|
+
}
|
|
191
|
+
assertCustody(context) {
|
|
192
|
+
if (hybridDigest(selection(context, this.#registration)) !== hybridDigest(this.#selected))
|
|
193
|
+
refuse("historical_second_graph_changed");
|
|
194
|
+
for (const history of this.#histories)
|
|
195
|
+
history.assertCustody(context);
|
|
196
|
+
}
|
|
197
|
+
revalidate(context) {
|
|
198
|
+
this.#registration.revalidate(context);
|
|
199
|
+
for (const history of this.#histories)
|
|
200
|
+
history.revalidate(context);
|
|
201
|
+
this.assertCustody(context);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/** Current post-correction admission, not a historical acceptance receipt.
|
|
205
|
+
* Every Work must have independently settled publication custody. Shared
|
|
206
|
+
* documents are compared with composed retained transitions, never adopted
|
|
207
|
+
* from today's filesystem. This owner neither allocates reviewers nor marks
|
|
208
|
+
* repair findings resolved; independent re-review must consume it separately. */
|
|
209
|
+
export class PreparedCompletedMemoryCorrectionWave {
|
|
210
|
+
static isPrepared(value) {
|
|
211
|
+
return typeof value === "object" && value !== null && #selected in value;
|
|
212
|
+
}
|
|
213
|
+
sha256;
|
|
214
|
+
#registration;
|
|
215
|
+
#selected;
|
|
216
|
+
#histories;
|
|
217
|
+
#baseline;
|
|
218
|
+
#workspace;
|
|
219
|
+
#root;
|
|
220
|
+
#input;
|
|
221
|
+
#affected;
|
|
222
|
+
constructor(key, registration, selected, histories, baseline, workspace, root, input, affected) {
|
|
223
|
+
if (key !== token)
|
|
224
|
+
refuse("completed_wave_token_required");
|
|
225
|
+
this.#registration = registration;
|
|
226
|
+
this.#selected = structuredClone(selected);
|
|
227
|
+
this.#histories = histories;
|
|
228
|
+
this.#baseline = baseline;
|
|
229
|
+
this.#workspace = workspace;
|
|
230
|
+
this.#root = root;
|
|
231
|
+
this.#input = structuredClone(input);
|
|
232
|
+
this.#affected = [...affected];
|
|
233
|
+
this.sha256 = hybridDigest({ selected, histories: histories.map(history => history.sha256), baseline_sha256: baseline.sha256,
|
|
234
|
+
workspace_sha256: workspace.sha256, input, affected_unit_ids: affected });
|
|
235
|
+
Object.freeze(this);
|
|
236
|
+
}
|
|
237
|
+
static prepare(context, registration) {
|
|
238
|
+
if (!PreparedRegisteredMemoryCorrections.isPrepared(registration)
|
|
239
|
+
&& !PreparedRetainedSecondMemoryRepairs.isPrepared(registration))
|
|
240
|
+
refuse("registered_corrections_required");
|
|
241
|
+
if (context.db.isOwnedWriteTransaction?.())
|
|
242
|
+
refuse("preparation_outside_writer_required");
|
|
243
|
+
// Preparing a read-only current view must also work after its successor
|
|
244
|
+
// reviewers have been allocated. Only fresh allocation requires no active
|
|
245
|
+
// children; that fence is consumed by its writer below.
|
|
246
|
+
const selected = context.db.readSnapshot(() => selection(context, registration));
|
|
247
|
+
const histories = selected.settlements.map(row => PreparedSettledMemoryRepair.prepare(context, registration, row.work_id));
|
|
248
|
+
const paths = [...new Set(registration.plan().works.flatMap(work => work.write_paths))].sort();
|
|
249
|
+
let baseline = (PreparedRetainedSecondMemoryRepairs.isPrepared(registration)
|
|
250
|
+
? registration.initialDocumentBaseline() : histories[0].before()).withWritePaths(paths);
|
|
251
|
+
const known = new Set([hybridDigest(baseline.entries())]);
|
|
252
|
+
for (const history of histories) {
|
|
253
|
+
if (!known.has(hybridDigest(history.before().entries())))
|
|
254
|
+
refuse("unexplained_historical_input_view");
|
|
255
|
+
baseline = baseline.composeSettledDelta(history.before(), history.after());
|
|
256
|
+
known.add(hybridDigest(baseline.entries()));
|
|
257
|
+
}
|
|
258
|
+
const view = PreparedRetainedSecondMemoryRepairs.isPrepared(registration) ? registration.initialInputView()
|
|
259
|
+
: registration.review().original().registration().view;
|
|
260
|
+
const bundle = PreparedRetainedSecondMemoryRepairs.isPrepared(registration) ? registration.bundle()
|
|
261
|
+
: registration.review().original().bundle();
|
|
262
|
+
const root = bundle.execution.snapshot().scope.workspace;
|
|
263
|
+
baseline.revalidate(root);
|
|
264
|
+
const input = { base: view.base, explicit_inputs: view.run_inputs.filter(ref => ref.root === "workspace").map(ref => ref.path) };
|
|
265
|
+
const candidates = reviewSourcePaths(root, input.explicit_inputs, input.base);
|
|
266
|
+
if (!candidates)
|
|
267
|
+
refuse("source_inventory_unavailable");
|
|
268
|
+
const available = new Set(baseline.entries().filter(entry => entry.kind === "file").map(entry => entry.path));
|
|
269
|
+
const exported = baseline.exported(candidates.filter(logical => logical.startsWith(".memory-bank/") && available.has(logical)));
|
|
270
|
+
const workspace = PreparedMemoryRepairWorkspace.capture(root, input, exported);
|
|
271
|
+
const sourceSha256 = hybridDigest(workspace.entries().filter(nonMemory));
|
|
272
|
+
if (histories.some(history => hybridDigest(history.workspaceManifest().entries().filter(nonMemory)) !== sourceSha256))
|
|
273
|
+
refuse("non_memory_source_changed");
|
|
274
|
+
const batch = registration.batch(), receipt = registration.receipt();
|
|
275
|
+
const compose = readAcceptedStageData(context, { projectId: receipt.scope.project_id, runId: receipt.run_id, stage: "compose" });
|
|
276
|
+
const dependencies = JSON.parse(compose.bytes.toString("utf8")).details.works;
|
|
277
|
+
const currentUnits = PreparedRetainedSecondMemoryRepairs.isPrepared(registration)
|
|
278
|
+
? memoryCorrectionCurrentUnits(registration.initialInputView().unit_inventory, registration.initialInputView().assignments)
|
|
279
|
+
: view.unit_inventory;
|
|
280
|
+
const affected = memoryCorrectionAffectedUnits(currentUnits, histories.map(history => ({
|
|
281
|
+
unit_ids: batch.works.find(item => receipt.work_ids[item.key] === history.workId).payload.memory_repair.unit_ids,
|
|
282
|
+
result: history.result()
|
|
283
|
+
})), dependencies);
|
|
284
|
+
const prepared = new PreparedCompletedMemoryCorrectionWave(token, registration, selected, histories, baseline, workspace, root, input, affected);
|
|
285
|
+
prepared.revalidate(context);
|
|
286
|
+
return prepared;
|
|
287
|
+
}
|
|
288
|
+
registration() { return this.#registration; }
|
|
289
|
+
affectedUnitIds() { return [...this.#affected]; }
|
|
290
|
+
baseline() { return this.#baseline; }
|
|
291
|
+
workspaceManifest() { return this.#workspace; }
|
|
292
|
+
repairResults() { return this.#histories.map(history => ({ work_id: history.workId, result: history.result(), reference: history.reference(), authority_sha256: history.sha256 })); }
|
|
293
|
+
/** Only SQL custody is inspected inside the future review-allocation writer. */
|
|
294
|
+
assertCustody(context) {
|
|
295
|
+
const selected = this.#selected;
|
|
296
|
+
if (hybridDigest(selection(context, this.#registration)) !== hybridDigest(selected))
|
|
297
|
+
refuse("completed_wave_custody_changed");
|
|
298
|
+
for (const history of this.#histories)
|
|
299
|
+
history.assertCustody(context);
|
|
300
|
+
}
|
|
301
|
+
/** Fresh allocation fence, before adding successor reviewers. General
|
|
302
|
+
* custody deliberately stays valid when those admitted children exist. */
|
|
303
|
+
assertAllocationAllowed(context) {
|
|
304
|
+
this.assertCustody(context);
|
|
305
|
+
assertNoActiveChildren(context, this.#registration);
|
|
306
|
+
}
|
|
307
|
+
revalidate(context) {
|
|
308
|
+
const root = this.#root;
|
|
309
|
+
this.assertCustody(context);
|
|
310
|
+
this.#registration.revalidate(context);
|
|
311
|
+
for (const history of this.#histories)
|
|
312
|
+
history.revalidate(context);
|
|
313
|
+
this.#baseline.revalidate(root);
|
|
314
|
+
this.#workspace.revalidateSource();
|
|
315
|
+
// Re-read the selected inventory as well as observed files: a newly added
|
|
316
|
+
// non-MB source must not slip between preparation and reviewer allocation.
|
|
317
|
+
this.#workspace.verifySelectedSourceInputs(root, this.#input, this.#baseline);
|
|
318
|
+
this.assertCustody(context);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { AppError } from "../shared/errors.js";
|
|
3
|
+
import { assertMemoryOrdinaryConfigurationCoverage, assertMemoryOrdinaryConfigurationOutputs } from "./memory-ordinary-defaults.js";
|
|
4
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
5
|
+
import { runtimeHarness } from "./execution-policy.js";
|
|
6
|
+
import { readFlowRunExecutionBundle, assertPreparedRunBundleSqlAuthority } from "./runs.js";
|
|
7
|
+
import { requireProjectByRoot } from "./projects.js";
|
|
8
|
+
import { resolveProjectRoot } from "../storage/paths.js";
|
|
9
|
+
import { PreparedDescriptorStageCoordinator, readDescriptorStageInput } from "./descriptor-stage-coordinator.js";
|
|
10
|
+
import { readDescriptorRootWork } from "./descriptor-root-work.js";
|
|
11
|
+
import { stageGraphScope } from "./stage-work-graph.js";
|
|
12
|
+
import { PreparedMemoryResultBinding } from "./memory-result-binding.js";
|
|
13
|
+
import { PreparedMemoryStageResult, memorySourceKey } from "./memory-stage-results.js";
|
|
14
|
+
import { captureMemoryPreflightSources } from "./memory-preflight-handler.js";
|
|
15
|
+
import { registerMemoryComposition, selectMemoryCompositionRegistration } from "./memory-composition-registration.js";
|
|
16
|
+
import { PreparedMemoryBaselinePreservation } from "./memory-baseline-preservation.js";
|
|
17
|
+
import { PreparedDescriptorStageStart } from "./descriptor-stage-start.js";
|
|
18
|
+
import { PreparedDescriptorStageSettlement } from "./descriptor-stage-settlement.js";
|
|
19
|
+
import { PreparedMemoryCompositionAcceptance } from "./memory-composition-acceptance.js";
|
|
20
|
+
import { readAcceptedStageData } from "./stage-report-renderer.js";
|
|
21
|
+
import { ObservedMemorySources } from "./memory-source-observations.js";
|
|
22
|
+
import { PreparedCheckEvidence } from "./code-checks.js";
|
|
23
|
+
function refuse(reason) {
|
|
24
|
+
throw new AppError("memory_compose_handler_invalid", "Memory composition lacks retained scope, plan or output custody", 2, { reason, effect: "no_effect" });
|
|
25
|
+
}
|
|
26
|
+
function selected(context, input) {
|
|
27
|
+
if (!(input.bytes instanceof Uint8Array) || input.bytes.length > 1024 * 1024)
|
|
28
|
+
refuse("invalid_input_bytes");
|
|
29
|
+
const bundle = readFlowRunExecutionBundle(context, input);
|
|
30
|
+
if (!bundle || bundle.descriptor.descriptor.id !== "mb-init" || bundle.descriptor.successor("discover", "accepted") !== "compose"
|
|
31
|
+
|| bundle.descriptor.successor("compose", "accepted") !== "verify")
|
|
32
|
+
refuse("registered_memory_bundle_required");
|
|
33
|
+
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot)), scope = { projectId: project.id, runId: input.runId, stage: "compose" };
|
|
34
|
+
const run = assertPreparedRunBundleSqlAuthority(context, scope, bundle), index = JSON.parse(run.index_json);
|
|
35
|
+
return { bundle, scope, run, accepted: index.stage_runs.find((stage) => stage.stage === "compose")?.status === "done" };
|
|
36
|
+
}
|
|
37
|
+
/** SQL-only physical binding proof; allocation/prompt custody is checked
|
|
38
|
+
* separately. No new Session, hook claim or provider invocation is created.
|
|
39
|
+
*/
|
|
40
|
+
function coordinatorSession(context, scope, bundle, workId) {
|
|
41
|
+
const rows = context.db.all("SELECT ws.id,ws.session_id,ws.hook_event_id,ws.prompt_path,ws.result_path,s.harness,s.provider_session_id FROM work_sessions ws JOIN sessions s ON s.session_id=ws.session_id AND s.project_id=? WHERE ws.work_id=? AND ws.status='running' AND s.run_id=? AND s.current_stage=? AND s.status='active' LIMIT 2", [scope.projectId, workId, scope.runId, scope.stage]);
|
|
42
|
+
if (rows.length !== 1)
|
|
43
|
+
refuse("active_coordinator_session_required");
|
|
44
|
+
const session = rows[0], route = bundle.execution.stage(scope.stage);
|
|
45
|
+
const input = readDescriptorStageInput(context, scope, bundle), directory = bundle.descriptor.stage(scope.stage).directory;
|
|
46
|
+
if (session.prompt_path !== path.join(input.runRoot, directory, "coordinator-prompt.md")
|
|
47
|
+
|| session.result_path !== path.join(input.runRoot, directory, "result-input.json"))
|
|
48
|
+
refuse("coordinator_packet_link_changed");
|
|
49
|
+
if (session.harness !== runtimeHarness(route.profile)
|
|
50
|
+
|| !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'", [scope.projectId, session.hook_event_id, session.session_id, session.harness, session.provider_session_id]))
|
|
51
|
+
refuse("coordinator_start_custody_missing");
|
|
52
|
+
const bindings = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='stage.coordinator_binding' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.scope.stage')=? AND json_extract(payload_json,'$.work_session_id')=? LIMIT 33", [scope.projectId, scope.runId, scope.stage, session.id]);
|
|
53
|
+
if (!bindings.length || bindings.length > 32 || bindings.some(row => Buffer.byteLength(row.payload_json) > 32768))
|
|
54
|
+
refuse("physical_binding_missing");
|
|
55
|
+
const binding = JSON.parse(bindings[0].payload_json);
|
|
56
|
+
if (bindings.some(row => hybridDigest(JSON.parse(row.payload_json)) !== hybridDigest(binding))
|
|
57
|
+
|| binding.work_id !== workId || binding.session_id !== session.session_id || binding.harness !== session.harness || binding.provider_session_id !== session.provider_session_id
|
|
58
|
+
|| binding.execution_sha256 !== bundle.execution.sha256 || binding.profile_id !== route.profile.id || binding.profile_sha256 !== hybridDigest(route.profile)
|
|
59
|
+
|| binding.session_mode !== route.session_mode || hybridDigest(binding.scope) !== hybridDigest({ ...stageGraphScope(context, scope.projectId, scope.runId, scope.stage), stage: scope.stage }))
|
|
60
|
+
refuse("physical_binding_changed");
|
|
61
|
+
return hybridDigest({ session, binding });
|
|
62
|
+
}
|
|
63
|
+
/** Actual registration path for the composition handler, awaiting complete
|
|
64
|
+
* memory-flow admission. Registers the immutable blueprint before workers run;
|
|
65
|
+
* it is NOT final composition acceptance or proof of produced artifacts.
|
|
66
|
+
*/
|
|
67
|
+
export function registerMemoryCompositionFromStage(context, input) {
|
|
68
|
+
if (!(input.bytes instanceof Uint8Array) || !input.bytes.length || input.bytes.length > 1024 * 1024)
|
|
69
|
+
refuse("invalid_plan_bytes");
|
|
70
|
+
const { bundle, scope, accepted } = selected(context, input);
|
|
71
|
+
if (accepted)
|
|
72
|
+
refuse("use_accepted_finish_replay");
|
|
73
|
+
const coordinator = PreparedDescriptorStageCoordinator.prepare(context, { ...input, stage: "compose" });
|
|
74
|
+
const allocated = context.db.readSnapshot(() => {
|
|
75
|
+
coordinator.assertAllocatedCustody(context);
|
|
76
|
+
const root = readDescriptorRootWork(context, scope, bundle);
|
|
77
|
+
return { workId: root.workId, physical: coordinatorSession(context, scope, bundle, root.workId) };
|
|
78
|
+
});
|
|
79
|
+
const binding = PreparedMemoryResultBinding.prepare(context, { ...scope, flow: bundle.descriptor, execution: bundle.execution, bundle,
|
|
80
|
+
sources: captureMemoryPreflightSources(context, bundle, input.bytes) });
|
|
81
|
+
let composition;
|
|
82
|
+
try {
|
|
83
|
+
composition = PreparedMemoryStageResult.prepare(input.bytes, bundle.descriptor, binding.binding());
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
// Only proposal validation is correctable here: no registration writer has
|
|
87
|
+
// run. Source/coordinator custody failures and registration conflicts remain
|
|
88
|
+
// outside this boundary and cannot authorize another authoring attempt.
|
|
89
|
+
if (error instanceof AppError && error.code === "memory_stage_result_invalid"
|
|
90
|
+
&& error.details.phase === "prepare" && error.details.effect === "no_effect")
|
|
91
|
+
error.details.recoverable = true;
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
const ordinaryDefaults = readDescriptorStageInput(context, scope, bundle).input.ordinary_defaults;
|
|
95
|
+
if (ordinaryDefaults) {
|
|
96
|
+
assertMemoryOrdinaryConfigurationCoverage(composition.value(), ordinaryDefaults);
|
|
97
|
+
if (ordinaryDefaults.missing.length && !context.db.get("SELECT 1 FROM audit_events WHERE project_id=? AND event_type='flow_run.stage_resumed' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.stage')='compose' LIMIT 1", [scope.projectId, scope.runId])) {
|
|
98
|
+
throw new AppError("memory_stage_result_invalid", "Resolve the missing ordinary configuration choice through the existing compose pause/answer path before registering Work", 2, { phase: "prepare", effect: "no_effect", recoverable: true, reason: "ordinary_configuration_choice_required", missing: ordinaryDefaults.missing });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
binding.revalidate(context);
|
|
102
|
+
return registerMemoryComposition(context, { parentWorkId: allocated.workId, requestKey: input.requestKey, composition,
|
|
103
|
+
binding: { runId: input.runId, inputSha256: binding.binding().inputSha256, executionSha256: bundle.execution.sha256 } }, writer => {
|
|
104
|
+
binding.assertCustody(writer);
|
|
105
|
+
coordinator.assertAllocatedCustody(writer);
|
|
106
|
+
if (coordinatorSession(writer, scope, bundle, allocated.workId) !== allocated.physical)
|
|
107
|
+
refuse("coordinator_changed");
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/** Unregistered until all six memory handlers are complete. Entry consumes
|
|
111
|
+
* accepted discovery/preflight; it does not create a second RUN or dispatch.
|
|
112
|
+
*/
|
|
113
|
+
export function startMemoryComposition(context, input) {
|
|
114
|
+
const { bundle, scope, accepted } = selected(context, input);
|
|
115
|
+
if (accepted)
|
|
116
|
+
refuse("use_accepted_finish_replay");
|
|
117
|
+
const preservation = PreparedMemoryBaselinePreservation.prepare(context, bundle.execution);
|
|
118
|
+
const inherited = context.db.readSnapshot(() => ({ discovery: readAcceptedStageData(context, { ...scope, stage: "discover" }), preflight: readAcceptedStageData(context, { ...scope, stage: "preflight" }) }));
|
|
119
|
+
const preflight = JSON.parse(inherited.preflight.bytes.toString("utf8")), discovery = JSON.parse(inherited.discovery.bytes.toString("utf8"));
|
|
120
|
+
if (preflight.schema_id !== "dd-flow/mb-init-preflight-result@1" || preflight.run_id !== input.runId || preflight.outcome !== "accepted"
|
|
121
|
+
|| discovery.schema_id !== "dd-flow/mb-init-discover-result@1"
|
|
122
|
+
|| discovery.run_id !== input.runId || discovery.outcome !== "accepted")
|
|
123
|
+
refuse("accepted_discovery_and_baseline_required");
|
|
124
|
+
const fence = (writer) => {
|
|
125
|
+
preservation.assertCustody(writer);
|
|
126
|
+
if (readAcceptedStageData(writer, { ...scope, stage: "discover" }).authority_sha256 !== inherited.discovery.authority_sha256
|
|
127
|
+
|| readAcceptedStageData(writer, { ...scope, stage: "preflight" }).authority_sha256 !== inherited.preflight.authority_sha256)
|
|
128
|
+
refuse("inherited_custody_changed");
|
|
129
|
+
};
|
|
130
|
+
const start = PreparedDescriptorStageStart.prepare(context, { ...input, stage: "compose" });
|
|
131
|
+
preservation.revalidate(context);
|
|
132
|
+
const stage = start.apply(context, fence), coordinator = PreparedDescriptorStageCoordinator.prepare(context, { ...input, stage: "compose" });
|
|
133
|
+
preservation.revalidate(context);
|
|
134
|
+
return { ...stage, ...coordinator.apply(context, fence) };
|
|
135
|
+
}
|
|
136
|
+
/** Agent references are selectors, not permissions. Supplement independent
|
|
137
|
+
* baseline evidence only with current outputs admitted by completed Work.
|
|
138
|
+
*/
|
|
139
|
+
export function captureMemoryCompositionSources(context, bundle, runRoot, bytes, outputs, checks, proposalSha256) {
|
|
140
|
+
let value;
|
|
141
|
+
try {
|
|
142
|
+
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
refuse("invalid_result_json");
|
|
146
|
+
}
|
|
147
|
+
const pending = [{ value, role: "source" }], hashes = outputs.outputHashes(), historical = outputs.historicalEvidence();
|
|
148
|
+
let count = 0;
|
|
149
|
+
const rejectSelectedReference = (reason, root, path, sha256) => {
|
|
150
|
+
if (proposalSha256 === undefined)
|
|
151
|
+
refuse(reason);
|
|
152
|
+
throw new AppError("memory_compose_handler_invalid", "Correct the proposed evidence reference to the selected bytes below, or omit the unsupported citation. Do not rerun settled checks or change the accepted output", 2, { reason, phase: "prepare", effect: "no_effect", recoverable: true, result_sha256: proposalSha256, selected_reference: { root, path, sha256 } });
|
|
153
|
+
};
|
|
154
|
+
const executionScope = bundle.execution.snapshot().scope;
|
|
155
|
+
const sharedProjectWorkspace = resolveProjectRoot(executionScope.projectRoot) === resolveProjectRoot(executionScope.workspace);
|
|
156
|
+
const workResults = new Map(outputs.resultEvidence().map(ref => [memorySourceKey(ref.root, ref.path), ref.sha256])), resultLocations = new Map();
|
|
157
|
+
if (checks !== undefined) {
|
|
158
|
+
if (!(checks instanceof PreparedCheckEvidence))
|
|
159
|
+
refuse("prepared_checks_required");
|
|
160
|
+
const scope = bundle.execution.snapshot().scope;
|
|
161
|
+
checks.assertOwner({ projectId: requireProjectByRoot(context, scope.projectRoot).id, runId: bundle.manifest.run_id });
|
|
162
|
+
checks.assertCustody(context);
|
|
163
|
+
for (const ref of checks.sourceReferences()) {
|
|
164
|
+
hashes.set(memorySourceKey(ref.root, ref.path), ref.sha256);
|
|
165
|
+
resultLocations.set(memorySourceKey(ref.root, ref.path), { root: ref.root, path: ref.path });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
while (pending.length) {
|
|
169
|
+
if (++count > 100000)
|
|
170
|
+
refuse("result_tree_exceeds_bound");
|
|
171
|
+
const { value: node, role } = pending.pop();
|
|
172
|
+
if (Array.isArray(node)) {
|
|
173
|
+
for (const child of node)
|
|
174
|
+
pending.push({ value: child, role });
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (!node || typeof node !== "object")
|
|
178
|
+
continue;
|
|
179
|
+
const record = node;
|
|
180
|
+
if (typeof record.root === "string" && typeof record.path === "string" && typeof record.sha256 === "string") {
|
|
181
|
+
// Inherited blueprint evidence describes the prior input, not today's
|
|
182
|
+
// artifact bytes. Its prepared original owner retains source custody.
|
|
183
|
+
const inherited = role === "evidence" && historical.has(`${memorySourceKey(record.root, record.path)}\0${record.sha256}`);
|
|
184
|
+
const resultSha = role === "evidence" ? workResults.get(memorySourceKey(record.root, record.path)) : undefined;
|
|
185
|
+
if (resultSha !== undefined) {
|
|
186
|
+
if (record.sha256 !== resultSha)
|
|
187
|
+
rejectSelectedReference("unsettled_work_result_reference", record.root, record.path, resultSha);
|
|
188
|
+
resultLocations.set(memorySourceKey(record.root, record.path), { root: record.root, path: record.path });
|
|
189
|
+
}
|
|
190
|
+
const key = memorySourceKey(record.root, record.path);
|
|
191
|
+
const expected = hashes.get(key) ?? (sharedProjectWorkspace && record.root === "project"
|
|
192
|
+
&& ["evidence", "artifacts"].includes(role) ? hashes.get(memorySourceKey("workspace", record.path)) : undefined);
|
|
193
|
+
if (expected !== undefined && !inherited) {
|
|
194
|
+
if (record.sha256 !== expected)
|
|
195
|
+
rejectSelectedReference("unsettled_output_reference", record.root, record.path, expected);
|
|
196
|
+
hashes.set(key, expected);
|
|
197
|
+
resultLocations.set(key, { root: record.root, path: record.path });
|
|
198
|
+
}
|
|
199
|
+
if (expected !== undefined || inherited || resultSha !== undefined) {
|
|
200
|
+
delete record.root;
|
|
201
|
+
delete record.path;
|
|
202
|
+
delete record.sha256;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
for (const [key, child] of Object.entries(record))
|
|
206
|
+
pending.push({ value: child, role: key === "evidence" || key === "artifacts" ? key : role });
|
|
207
|
+
}
|
|
208
|
+
// Keep evidence/artifact roles for the shared collector; settled child output
|
|
209
|
+
// references already have their independent output owner above.
|
|
210
|
+
const observedBaseline = captureMemoryPreflightSources(context, bundle, Buffer.from(JSON.stringify(value))), locations = new Map();
|
|
211
|
+
for (const key of observedBaseline.hashes().keys()) {
|
|
212
|
+
const [root, logical] = key.split("\0");
|
|
213
|
+
locations.set(key, { root: root, path: logical });
|
|
214
|
+
}
|
|
215
|
+
for (const location of outputs.outputLocations())
|
|
216
|
+
locations.set(memorySourceKey(location.root, location.path), location);
|
|
217
|
+
for (const [key, location] of resultLocations)
|
|
218
|
+
locations.set(key, location);
|
|
219
|
+
const scope = bundle.execution.snapshot().scope, observations = ObservedMemorySources.capture({ project: scope.projectRoot, workspace: scope.workspace,
|
|
220
|
+
canon: path.join(scope.workspace, ".memory-bank"), run: runRoot }, [...locations.values()]);
|
|
221
|
+
const actual = observations.hashes();
|
|
222
|
+
for (const [key, expected] of [...observedBaseline.hashes(), ...hashes, ...[...workResults].filter(([key]) => resultLocations.has(key))])
|
|
223
|
+
if (actual.get(key) !== expected)
|
|
224
|
+
refuse("source_changed_during_final_preparation");
|
|
225
|
+
return observations;
|
|
226
|
+
}
|
|
227
|
+
/** Final acceptance keeps the registration blueprint immutable. Fresh/replay
|
|
228
|
+
* RUN/input/coordinator authority is selected independently; registered child
|
|
229
|
+
* outputs and their current bytes are rechecked before and within settlement.
|
|
230
|
+
* Semantic quality remains the following VERIFY and independent REVIEW gates.
|
|
231
|
+
*/
|
|
232
|
+
export function finishMemoryComposition(context, input) {
|
|
233
|
+
const { bundle, scope, run, accepted } = selected(context, input), root = readDescriptorRootWork(context, scope, bundle);
|
|
234
|
+
if (!input.bytes.length)
|
|
235
|
+
refuse("invalid_result_bytes");
|
|
236
|
+
const registration = selectMemoryCompositionRegistration(context, { ...stageGraphScope(context, scope.projectId, scope.runId, scope.stage), stage: "compose", parent_work_id: root.workId });
|
|
237
|
+
const registrationScope = registration.scope;
|
|
238
|
+
const blueprintBinding = PreparedMemoryResultBinding.prepareRegisteredComposition(context, { ...scope, flow: bundle.descriptor, execution: bundle.execution, bundle,
|
|
239
|
+
registrationScope, acceptedReplay: accepted });
|
|
240
|
+
const outputs = PreparedMemoryCompositionAcceptance.prepare(context, { scope: registrationScope, flow: bundle.descriptor, engine: bundle.engine(), binding: blueprintBinding.binding() });
|
|
241
|
+
const sources = captureMemoryCompositionSources(context, bundle, run.run_root ?? run.run_dir, input.bytes, outputs);
|
|
242
|
+
const binding = PreparedMemoryResultBinding.prepare(context, { ...scope, flow: bundle.descriptor, execution: bundle.execution, bundle, sources, acceptedReplay: accepted, historicalEvidence: outputs.historicalEvidence() });
|
|
243
|
+
const replay = binding.acceptedBytes();
|
|
244
|
+
if (replay && !replay.equals(input.bytes))
|
|
245
|
+
refuse("accepted_result_changed");
|
|
246
|
+
const result = PreparedMemoryStageResult.prepare(input.bytes, bundle.descriptor, binding.binding());
|
|
247
|
+
outputs.assertResult(result);
|
|
248
|
+
const ordinaryDefaults = readDescriptorStageInput(context, scope, bundle, true, accepted).input.ordinary_defaults;
|
|
249
|
+
if (ordinaryDefaults && !replay) {
|
|
250
|
+
const selected = bundle.execution.snapshot().scope;
|
|
251
|
+
assertMemoryOrdinaryConfigurationOutputs(context, selected.workspace, ordinaryDefaults, result.value().units.flatMap(unit => unit.artifacts), { requestId: selected.requestId, projectRoot: selected.projectRoot, workspaceRoot: selected.workspace });
|
|
252
|
+
}
|
|
253
|
+
const settlement = PreparedDescriptorStageSettlement.prepare(context, { ...input, stage: "compose", bytes: result.bytes() });
|
|
254
|
+
blueprintBinding.revalidate(context);
|
|
255
|
+
binding.revalidate(context);
|
|
256
|
+
outputs.revalidate(context);
|
|
257
|
+
return settlement.apply(context, writer => { blueprintBinding.assertCustody(writer); binding.assertCustody(writer); outputs.assertCustody(writer); outputs.assertResult(result); });
|
|
258
|
+
}
|