@deksden-com/dd-flow-cli 0.9.0-beta.136 → 0.9.0-beta.138
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -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 +56 -3
- package/dist/cli/command-option-descriptions.js +5 -0
- package/dist/cli/executor-onboarding.js +8 -3
- package/dist/cli/flows.js +29 -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/native-hook-ingress.js +1 -1
- package/dist/cli/response-file.js +2 -0
- package/dist/cli/run-cli.js +283 -160
- package/dist/cli/setup.js +9 -7
- package/dist/cli.js +54 -2
- package/dist/domain/stage-handler-identities.js +20 -0
- package/dist/harness-runtime/DD-ZCODE.md +13 -0
- package/dist/harness-runtime/bin/dd-opencode.mjs +3 -3
- package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +4 -3
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +4 -2
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +11 -5
- package/dist/harness-runtime/lib/dd-codex.mjs +22 -10
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +2 -1
- package/dist/harness-runtime/lib/dd-droid.mjs +2 -1
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +4 -2
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +20 -5
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +45 -10
- package/dist/harness-runtime/lib/dd-zcode.mjs +7 -3
- package/dist/harness-runtime/lib/delegation-instructions.mjs +5 -3
- package/dist/harness-runtime/lib/lifecycle-command.mjs +1 -0
- package/dist/harness-runtime/lib/managed-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +50 -15
- package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
- package/dist/harness-runtime/lib/native-hook-command.mjs +1 -1
- package/dist/harness-runtime/lib/native-request-proof.d.mts +2 -0
- package/dist/harness-runtime/lib/native-request-proof.mjs +65 -0
- package/dist/harness-runtime/lib/opencode-plugin.mjs +5 -1
- package/dist/harness-runtime/lib/operation-progress.mjs +3 -1
- package/dist/runtime/context.js +1 -1
- package/dist/schemas/discovery-result.schema.json +184 -0
- package/dist/schemas/execution-bundle.schema.json +205 -0
- package/dist/schemas/execution-settings.schema.json +162 -0
- package/dist/schemas/flow-descriptor.schema.json +54 -0
- package/dist/schemas/flow-run.schema.json +22 -0
- package/dist/schemas/harness-catalog.schema.json +80 -0
- package/dist/schemas/mb-init-compose-result.schema.json +457 -0
- package/dist/schemas/mb-init-discover-result.schema.json +500 -0
- package/dist/schemas/mb-init-finish-result.schema.json +422 -0
- package/dist/schemas/mb-init-preflight-result.schema.json +367 -0
- package/dist/schemas/mb-init-review-result.schema.json +396 -0
- package/dist/schemas/mb-init-verify-result.schema.json +391 -0
- package/dist/schemas/memory-bootstrap-receipt-v2.schema.json +67 -0
- package/dist/schemas/memory-bootstrap-receipt.schema.json +354 -0
- package/dist/schemas/memory-composition-work-result.schema.json +38 -0
- package/dist/schemas/memory-repair-work-result.schema.json +49 -0
- package/dist/schemas/memory-review-correction-plan.schema.json +30 -0
- package/dist/schemas/memory-review-work-result.schema.json +43 -0
- package/dist/schemas/model-catalog.schema.json +153 -0
- package/dist/schemas/profile-settings.schema.json +122 -0
- package/dist/schemas/release-manifest.schema.json +291 -0
- package/dist/schemas/run-engine-binding.schema.json +1 -1
- package/dist/services/check-runner-evidence.js +51 -4
- package/dist/services/cli-operation-classifier.js +8 -47
- package/dist/services/code-checks.js +387 -34
- package/dist/services/controller-child-observer.js +99 -0
- package/dist/services/controller-execution.js +88 -0
- package/dist/services/controller-fanout.js +79 -13
- 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/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/external-work-launch.js +91 -11
- package/dist/services/flow-catalog.js +21 -0
- package/dist/services/flow-descriptors.js +82 -0
- package/dist/services/frozen-engine.js +242 -0
- package/dist/services/generic-execution.js +164 -0
- package/dist/services/harness-adapter.js +28 -2
- package/dist/services/hooks.js +24 -6
- package/dist/services/hybrid-contracts.js +31 -3
- package/dist/services/ids.js +29 -1
- package/dist/services/installation-lock.js +7 -4
- package/dist/services/installed-flow-source.js +183 -0
- package/dist/services/lifecycle-invocations.js +257 -78
- package/dist/services/memory-accepted-composition.js +77 -0
- package/dist/services/memory-baseline-preservation.js +65 -0
- package/dist/services/memory-completed-correction-wave.js +320 -0
- package/dist/services/memory-compose-handler.js +258 -0
- package/dist/services/memory-composition-acceptance.js +199 -0
- package/dist/services/memory-composition-graph.js +134 -0
- package/dist/services/memory-composition-registration.js +146 -0
- package/dist/services/memory-composition-work-custody.js +151 -0
- package/dist/services/memory-composition-work-results.js +96 -0
- package/dist/services/memory-corrected-review-acceptance.js +129 -0
- package/dist/services/memory-corrected-review-batch.js +30 -0
- package/dist/services/memory-corrected-review-findings.js +106 -0
- package/dist/services/memory-corrected-review-inputs.js +210 -0
- package/dist/services/memory-corrected-review-projection.js +78 -0
- package/dist/services/memory-corrected-review-receipt.js +170 -0
- package/dist/services/memory-corrected-review-registration.js +191 -0
- package/dist/services/memory-correction-check-receipt.js +219 -0
- package/dist/services/memory-correction-input-owner.js +6 -0
- package/dist/services/memory-discover-handler.js +104 -0
- package/dist/services/memory-finish-evidence.js +435 -0
- package/dist/services/memory-finish-handler.js +215 -0
- package/dist/services/memory-finish-integration.js +280 -0
- package/dist/services/memory-finish-readiness.js +122 -0
- package/dist/services/memory-git-integration.js +151 -0
- package/dist/services/memory-historical-corrected-review.js +206 -0
- package/dist/services/memory-historical-original-review.js +130 -0
- package/dist/services/memory-historical-repair-registration.js +99 -0
- package/dist/services/memory-historical-second-review.js +128 -0
- package/dist/services/memory-init-bootstrap-readiness.js +210 -0
- package/dist/services/memory-init-contexts.js +89 -0
- package/dist/services/memory-init-controller-start.js +84 -0
- package/dist/services/memory-init-engine-release.js +223 -0
- package/dist/services/memory-init-git-policy.js +136 -0
- package/dist/services/memory-init-lint-capability.js +185 -0
- package/dist/services/memory-init-native-capacity.js +241 -0
- package/dist/services/memory-init-preflight.js +312 -0
- package/dist/services/memory-init-project-bootstrap.js +599 -0
- package/dist/services/memory-init-request-candidate.js +157 -0
- package/dist/services/memory-init-request-input.js +66 -0
- package/dist/services/memory-init-resource-storage.js +125 -0
- package/dist/services/memory-init-run-allocation.js +81 -0
- package/dist/services/memory-init-run-bundle.js +98 -0
- package/dist/services/memory-init-start.js +175 -0
- package/dist/services/memory-init-startup-custody.js +80 -0
- package/dist/services/memory-init-startup-input.js +240 -0
- package/dist/services/memory-init-storage.js +81 -0
- package/dist/services/memory-init-stores.js +359 -0
- package/dist/services/memory-init-worktrunk-capability.js +172 -0
- package/dist/services/memory-layer-publication.js +611 -0
- package/dist/services/memory-layer.js +156 -0
- package/dist/services/memory-lint-config.js +85 -0
- package/dist/services/memory-native-review-copy.js +165 -0
- package/dist/services/memory-normal-flow-entry.js +75 -0
- package/dist/services/memory-ordinary-defaults.js +112 -0
- package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
- package/dist/services/memory-preflight-handler.js +259 -0
- package/dist/services/memory-repair-current-view.js +299 -0
- package/dist/services/memory-repair-document-delta.js +451 -0
- package/dist/services/memory-repair-history.js +491 -0
- package/dist/services/memory-repair-physical-binding.js +312 -0
- package/dist/services/memory-repair-publication-files.js +152 -0
- package/dist/services/memory-repair-publication-intent.js +27 -0
- package/dist/services/memory-repair-publication-plan.js +160 -0
- package/dist/services/memory-repair-publication-source.js +225 -0
- package/dist/services/memory-repair-publication.js +794 -0
- package/dist/services/memory-repair-registration.js +239 -0
- package/dist/services/memory-repair-work-baseline.js +413 -0
- package/dist/services/memory-repair-work-completion.js +97 -0
- package/dist/services/memory-repair-work-custody.js +73 -0
- package/dist/services/memory-repair-work-results.js +89 -0
- package/dist/services/memory-repair-workspace.js +252 -0
- package/dist/services/memory-repair-writer-copy.js +735 -0
- package/dist/services/memory-result-binding.js +173 -0
- package/dist/services/memory-retained-review-authority.js +129 -0
- package/dist/services/memory-retained-review-results.js +70 -0
- package/dist/services/memory-retained-second-baseline.js +102 -0
- package/dist/services/memory-retained-second-repairs.js +156 -0
- package/dist/services/memory-review-acceptance.js +214 -0
- package/dist/services/memory-review-correction-plan.js +171 -0
- package/dist/services/memory-review-handler.js +585 -0
- package/dist/services/memory-review-inputs.js +200 -0
- package/dist/services/memory-review-receipt.js +180 -0
- package/dist/services/memory-review-registration.js +108 -0
- package/dist/services/memory-review-work-custody.js +124 -0
- package/dist/services/memory-review-work-input.js +72 -0
- package/dist/services/memory-review-work-results.js +73 -0
- package/dist/services/memory-second-corrected-review-inputs.js +165 -0
- package/dist/services/memory-second-correction-plan.js +76 -0
- package/dist/services/memory-second-repair-baseline.js +91 -0
- package/dist/services/memory-second-repair-launch.js +614 -0
- package/dist/services/memory-second-repair-registration.js +88 -0
- package/dist/services/memory-second-repair-source.js +187 -0
- package/dist/services/memory-second-review-acceptance.js +115 -0
- package/dist/services/memory-source-observations.js +117 -0
- package/dist/services/memory-stage-dispatch.js +28 -0
- package/dist/services/memory-stage-results.js +199 -0
- package/dist/services/memory-verification-acceptance.js +135 -0
- package/dist/services/memory-verification-checks.js +252 -0
- package/dist/services/memory-verification-receipt.js +159 -0
- package/dist/services/memory-verify-handler.js +135 -0
- package/dist/services/memory-work-result-preparation.js +63 -0
- package/dist/services/projects.js +17 -2
- package/dist/services/recovery-snapshot-database.js +416 -1
- package/dist/services/repair-intents.js +1 -1
- package/dist/services/review-copy.js +10 -2
- package/dist/services/run-control.js +80 -9
- package/dist/services/run-controller-capture.js +10 -3
- package/dist/services/run-controller-process.js +21 -0
- package/dist/services/run-controller-recovery.js +3 -1
- package/dist/services/run-controller-state.js +1 -1
- package/dist/services/run-controller.js +184 -48
- package/dist/services/run-recovery-guard.js +2 -0
- package/dist/services/run-recovery.js +145 -5
- package/dist/services/run-start-custody.js +177 -0
- package/dist/services/runs.js +319 -25
- package/dist/services/stage-context.js +9 -1
- package/dist/services/stage-handlers.js +74 -0
- package/dist/services/stage-lifecycle.js +2 -1
- package/dist/services/stage-report-renderer.js +76 -0
- package/dist/services/stage-work-graph.js +17 -0
- package/dist/services/vnext-code.js +3 -3
- package/dist/services/vnext-execution-profile.js +2 -2
- package/dist/services/vnext-fanout.js +136 -10
- package/dist/services/vnext-merge.js +8 -3
- package/dist/services/vnext-plan-review.js +6 -4
- package/dist/services/vnext-protocolize.js +60 -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
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
import { appendAudit } from "./audit.js";
|
|
3
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
4
|
+
import { PreparedMemorySecondCorrectionPlan } from "./memory-second-correction-plan.js";
|
|
5
|
+
import { memoryRepairBatch, memoryRepairWorkerSchema } from "./memory-repair-registration.js";
|
|
6
|
+
import { addWorkBatch, prepareWorkBatch, readWorkBatchCustody } from "./work-registry.js";
|
|
7
|
+
const event = "memory.second_repair_registration";
|
|
8
|
+
function refuse(reason) {
|
|
9
|
+
throw new AppError("memory_second_repair_registration_invalid", "Second repair allocation lacks independently settled first-wave custody", 2, { reason, effect: "no_effect" });
|
|
10
|
+
}
|
|
11
|
+
/** Allocation only. The generic repair dispatch guard remains closed until
|
|
12
|
+
* wave-two baseline/copy/publication and re-review owners are implemented. */
|
|
13
|
+
export function registerMemorySecondCorrections(context, plan) {
|
|
14
|
+
if (!PreparedMemorySecondCorrectionPlan.isPrepared(plan))
|
|
15
|
+
refuse("prepared_second_plan_required");
|
|
16
|
+
if (context.db.isOwnedWriteTransaction?.())
|
|
17
|
+
refuse("preparation_outside_writer_required");
|
|
18
|
+
const history = plan.historicalReview(context), review = plan.review(), registration = review.registration();
|
|
19
|
+
const first = registration.receipt(), view = first.view, scope = first.scope, bundle = registration.inputs().bundle();
|
|
20
|
+
const schema = JSON.parse(bundle.engine().schema("memory-repair-work-result").toString("utf8"));
|
|
21
|
+
if (schema.$id !== memoryRepairWorkerSchema)
|
|
22
|
+
refuse("frozen_repair_worker_schema_required");
|
|
23
|
+
const references = new Map(view.run_inputs.map(ref => [`${ref.root}:${ref.path}`, ref]));
|
|
24
|
+
for (const item of review.references()) {
|
|
25
|
+
const ref = { root: "run", ...item.result }, key = `run:${ref.path}`, prior = references.get(key);
|
|
26
|
+
if (prior && prior.sha256 !== ref.sha256)
|
|
27
|
+
refuse("conflicting_review_reference");
|
|
28
|
+
references.set(key, ref);
|
|
29
|
+
}
|
|
30
|
+
const batch = memoryRepairBatch({ scope, plan: plan.value(), planSha256: plan.sha256,
|
|
31
|
+
originalRegistrationSha256: hybridDigest(first), references: [...references.values()], findings: history.projection().findings });
|
|
32
|
+
const requestKey = "memory-repair-wave-2", batchScope = { project_id: scope.project_id, run_id: scope.run_id, stage: "review",
|
|
33
|
+
attempt: scope.attempt, cycle: "", parent_work_id: scope.parent_work_id, request_key: requestKey };
|
|
34
|
+
const batchInput = { parentWorkId: scope.parent_work_id, requestKey, stage: "review", value: batch };
|
|
35
|
+
const prepared = prepareWorkBatch(context, batchInput);
|
|
36
|
+
const identity = { schema_id: "dd-flow/memory-second-repair-registration@1", run_id: scope.run_id, scope, wave: 2,
|
|
37
|
+
first_review_registration_sha256: hybridDigest(first), first_review_input_sha256: first.input_sha256,
|
|
38
|
+
first_check_receipt_sha256: first.check_receipt_sha256, review_sha256: history.reviewSha256(),
|
|
39
|
+
projection_sha256: hybridDigest(history.projection()), plan_sha256: plan.sha256, plan_base64: plan.bytes().toString("base64"),
|
|
40
|
+
batch_sha256: hybridDigest(batch.works) };
|
|
41
|
+
function selected() {
|
|
42
|
+
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')=? LIMIT 2", [scope.project_id, event, scope.run_id, scope.attempt, scope.parent_work_id]);
|
|
43
|
+
if (!rows.length)
|
|
44
|
+
return null;
|
|
45
|
+
if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 2 * 1024 * 1024)
|
|
46
|
+
refuse("ambiguous_or_unbounded_registration");
|
|
47
|
+
let retained;
|
|
48
|
+
try {
|
|
49
|
+
retained = JSON.parse(rows[0].payload_json);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return refuse("invalid_registration");
|
|
53
|
+
}
|
|
54
|
+
const ids = retained.work_ids;
|
|
55
|
+
if (!ids || typeof ids !== "object" || Array.isArray(ids) || Object.keys(ids).length !== batch.works.length
|
|
56
|
+
|| new Set(Object.values(ids)).size !== batch.works.length || Object.values(ids).some(id => typeof id !== "string" || !id)
|
|
57
|
+
|| hybridDigest(retained) !== hybridDigest({ ...identity, work_ids: ids }))
|
|
58
|
+
refuse("registration_replay_conflict");
|
|
59
|
+
for (const item of batch.works) {
|
|
60
|
+
const id = ids[item.key];
|
|
61
|
+
if (!id)
|
|
62
|
+
refuse("missing_repair_work");
|
|
63
|
+
const custody = readWorkBatchCustody(context, id);
|
|
64
|
+
if (custody.item_key !== item.key || custody.batch_sha256 !== identity.batch_sha256
|
|
65
|
+
|| hybridDigest(custody.scope) !== hybridDigest(batchScope) || hybridDigest(custody.work_ids) !== hybridDigest(ids))
|
|
66
|
+
refuse("changed_work_custody");
|
|
67
|
+
}
|
|
68
|
+
return retained;
|
|
69
|
+
}
|
|
70
|
+
plan.revalidate(context);
|
|
71
|
+
history.revalidate(context);
|
|
72
|
+
return context.db.writeTransaction(() => {
|
|
73
|
+
plan.assertCustody(context);
|
|
74
|
+
history.assertCustody(context);
|
|
75
|
+
const prior = selected();
|
|
76
|
+
if (!prior && context.db.get("SELECT 1 FROM works WHERE project_id=? AND run_id=? AND parent_work_id=? AND json_extract(payload_json,'$.kind')='memory-repair' AND json_extract(payload_json,'$.memory_repair.wave')=2 LIMIT 1", [scope.project_id, scope.run_id, scope.parent_work_id]))
|
|
77
|
+
refuse("repair_registration_missing");
|
|
78
|
+
const allocated = addWorkBatch(context, { ...batchInput, prepared });
|
|
79
|
+
if (Boolean(prior) !== allocated.replayed || prior && hybridDigest(prior.work_ids) !== hybridDigest(allocated.work_ids))
|
|
80
|
+
refuse("repair_registration_missing");
|
|
81
|
+
if (!prior)
|
|
82
|
+
appendAudit(context, { projectId: scope.project_id, eventType: event, payload: { ...identity, work_ids: allocated.work_ids } });
|
|
83
|
+
const retained = selected();
|
|
84
|
+
if (!retained)
|
|
85
|
+
refuse("repair_registration_missing");
|
|
86
|
+
return { wave: 2, plan_sha256: retained.plan_sha256, work_ids: { ...retained.work_ids }, replayed: Boolean(prior) };
|
|
87
|
+
});
|
|
88
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
import { RetainedExecutionBundle } from "./execution-bundles.js";
|
|
3
|
+
import { PreparedRetainedSecondMemoryRepairs } from "./memory-retained-second-repairs.js";
|
|
4
|
+
import { PreparedDescriptorStageCoordinator } from "./descriptor-stage-coordinator.js";
|
|
5
|
+
import { readDescriptorCoordinatorSession } from "./descriptor-coordinator-session.js";
|
|
6
|
+
import { readMemoryReviewPhase } from "./memory-review-inputs.js";
|
|
7
|
+
import { assertRunMutationAllowed } from "./run-recovery-guard.js";
|
|
8
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
9
|
+
import { reviewSourceHash, reviewSourcePaths } from "./review-copy.js";
|
|
10
|
+
import { readWorkBatchCustody } from "./work-registry.js";
|
|
11
|
+
import { ObservedMemorySources } from "./memory-source-observations.js";
|
|
12
|
+
import { memorySourceKey } from "./memory-stage-results.js";
|
|
13
|
+
import { PreparedMemoryRepairCurrentView } from "./memory-repair-current-view.js";
|
|
14
|
+
const token = Symbol("second-memory-repair-source");
|
|
15
|
+
function refuse(reason) {
|
|
16
|
+
throw new AppError("memory_second_repair_source_invalid", "Second repair source lacks explained settled documents and active coordinator custody", 2, { reason, effect: "no_effect" });
|
|
17
|
+
}
|
|
18
|
+
/** Initial or independently registered post-peer source admission.
|
|
19
|
+
* Dependent second Works cannot rebase unexplained current bytes.
|
|
20
|
+
* No baseline receipt, copy, Work start or provider dispatch is performed. */
|
|
21
|
+
export class PreparedSecondMemoryRepairSource {
|
|
22
|
+
static isPrepared(value) {
|
|
23
|
+
return typeof value === "object" && value !== null && #registration in value;
|
|
24
|
+
}
|
|
25
|
+
#registration;
|
|
26
|
+
#bundle;
|
|
27
|
+
#workId;
|
|
28
|
+
#generation;
|
|
29
|
+
#coordinator;
|
|
30
|
+
#session;
|
|
31
|
+
#baseline;
|
|
32
|
+
#observed;
|
|
33
|
+
#current;
|
|
34
|
+
constructor(key, registration, bundle, workId, generation, coordinator, session, baseline, observed, current = null) {
|
|
35
|
+
if (key !== token)
|
|
36
|
+
refuse("source_token_required");
|
|
37
|
+
this.#registration = registration;
|
|
38
|
+
this.#bundle = bundle;
|
|
39
|
+
this.#workId = workId;
|
|
40
|
+
this.#generation = generation;
|
|
41
|
+
this.#coordinator = coordinator;
|
|
42
|
+
this.#session = session;
|
|
43
|
+
this.#baseline = baseline;
|
|
44
|
+
this.#observed = observed;
|
|
45
|
+
this.#current = current;
|
|
46
|
+
Object.freeze(this);
|
|
47
|
+
}
|
|
48
|
+
static prepare(context, input) {
|
|
49
|
+
return this.#prepare(context, input, null);
|
|
50
|
+
}
|
|
51
|
+
static prepareCurrent(context, input) {
|
|
52
|
+
if (!PreparedMemoryRepairCurrentView.isPrepared(input.current))
|
|
53
|
+
refuse("registered_second_current_view_required");
|
|
54
|
+
return this.#prepare(context, input, input.current);
|
|
55
|
+
}
|
|
56
|
+
static #prepare(context, input, current) {
|
|
57
|
+
if (!PreparedRetainedSecondMemoryRepairs.isPrepared(input.registration))
|
|
58
|
+
refuse("retained_second_registration_required");
|
|
59
|
+
if (!(input.bundle instanceof RetainedExecutionBundle) || input.bundle.descriptor.descriptor.id !== "mb-init")
|
|
60
|
+
refuse("registered_memory_bundle_required");
|
|
61
|
+
if (context.db.isOwnedWriteTransaction?.())
|
|
62
|
+
refuse("preparation_outside_writer_required");
|
|
63
|
+
const registration = input.registration, bundle = input.bundle, scope = registration.receipt().scope;
|
|
64
|
+
registration.revalidate(context);
|
|
65
|
+
const assigned = registration.batch().works.find(item => registration.receipt().work_ids[item.key] === input.workId);
|
|
66
|
+
if (!assigned)
|
|
67
|
+
refuse("foreign_second_repair_work");
|
|
68
|
+
const phase = readMemoryReviewPhase(context, { projectId: scope.project_id, runId: scope.run_id }, bundle);
|
|
69
|
+
if (hybridDigest(phase) !== hybridDigest(scope))
|
|
70
|
+
refuse("foreign_review_phase");
|
|
71
|
+
const generation = assertRunMutationAllowed(context, scope.project_id, scope.run_id);
|
|
72
|
+
const coordinator = PreparedDescriptorStageCoordinator.prepare(context, { projectRoot: bundle.execution.snapshot().scope.projectRoot,
|
|
73
|
+
runId: scope.run_id, stage: "review" });
|
|
74
|
+
coordinator.assertAllocatedCustody(context);
|
|
75
|
+
const session = readDescriptorCoordinatorSession(context, { scope: { projectId: scope.project_id, runId: scope.run_id, stage: "review" },
|
|
76
|
+
bundle, workId: scope.parent_work_id }, refuse);
|
|
77
|
+
const view = registration.initialInputView(), workspace = bundle.execution.snapshot().scope.workspace;
|
|
78
|
+
if (current) {
|
|
79
|
+
if (current.workId !== input.workId || current.receipt().registration_sha256 !== registration.sha256)
|
|
80
|
+
refuse("foreign_second_current_view");
|
|
81
|
+
current.revalidate(context);
|
|
82
|
+
}
|
|
83
|
+
// A settled peer changes the source view before another writer is admitted.
|
|
84
|
+
// Request its existing owner before comparing original bytes. That owner
|
|
85
|
+
// still rejects unexplained physical drift; never promote an old baseline.
|
|
86
|
+
if (!current && (assigned.depends_on.length || registration.batch().works.some(item => context.db.get("SELECT 1 FROM works WHERE project_id=? AND run_id=? AND work_id=? AND status='completed'", [scope.project_id, scope.run_id, registration.receipt().work_ids[item.key]])))) {
|
|
87
|
+
if (context.db.get("SELECT status FROM works WHERE project_id=? AND run_id=? AND work_id=?", [scope.project_id, scope.run_id, input.workId])?.status !== "created")
|
|
88
|
+
refuse("unstarted_target_required");
|
|
89
|
+
if (context.db.get("SELECT 1 FROM audit_events WHERE project_id=? AND event_type='memory.repair_work_baseline' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.work_id')=? LIMIT 1", [scope.project_id, scope.run_id, input.workId]))
|
|
90
|
+
refuse("settled_second_peer_view_required");
|
|
91
|
+
refuse("registered_second_current_view_required");
|
|
92
|
+
}
|
|
93
|
+
const baseline = current ? current.baseline() : registration.initialDocumentBaseline().withWritePaths(assigned.payload.memory_repair.write_paths), refs = view.run_inputs.filter(ref => ref.root === "workspace");
|
|
94
|
+
const paths = current ? current.workspaceManifest().entries().filter(entry => entry.kind === "file").map(entry => entry.path) : refs.map(ref => ref.path);
|
|
95
|
+
const observed = ObservedMemorySources.capture({ workspace }, paths.map(path => ({ root: "workspace", path })));
|
|
96
|
+
const hashes = observed.hashes();
|
|
97
|
+
if (!current && refs.some(ref => hashes.get(memorySourceKey("workspace", ref.path)) !== ref.sha256))
|
|
98
|
+
refuse("first_wave_source_changed");
|
|
99
|
+
const prepared = new PreparedSecondMemoryRepairSource(token, registration, bundle, input.workId, generation, coordinator, session, baseline, observed, current);
|
|
100
|
+
prepared.revalidate(context);
|
|
101
|
+
return prepared;
|
|
102
|
+
}
|
|
103
|
+
baseline() { return this.#baseline; }
|
|
104
|
+
copyInput() {
|
|
105
|
+
if (this.#current)
|
|
106
|
+
return this.#current.copyInput();
|
|
107
|
+
const view = this.#registration.initialInputView();
|
|
108
|
+
return { source_sha256: view.source_sha256, base: view.base, explicit_inputs: view.run_inputs.filter(ref => ref.root === "workspace").map(ref => ref.path) };
|
|
109
|
+
}
|
|
110
|
+
workerRoute() {
|
|
111
|
+
const receipt = this.#registration.receipt(), assigned = this.#registration.batch().works.find(item => receipt.work_ids[item.key] === this.#workId);
|
|
112
|
+
return this.#bundle.execution.work("review", assigned.payload.execution_override?.agent_profile_id);
|
|
113
|
+
}
|
|
114
|
+
exportedBaseline() {
|
|
115
|
+
if (this.#current)
|
|
116
|
+
return this.#current.exportedBaseline();
|
|
117
|
+
const view = this.#registration.initialInputView(), workspace = this.#bundle.execution.snapshot().scope.workspace;
|
|
118
|
+
const selected = reviewSourcePaths(workspace, view.run_inputs.filter(ref => ref.root === "workspace").map(ref => ref.path), view.base);
|
|
119
|
+
if (selected === null)
|
|
120
|
+
refuse("export_source_inventory_unavailable");
|
|
121
|
+
const files = new Set(this.#baseline.entries().filter(entry => entry.kind === "file").map(entry => entry.path));
|
|
122
|
+
return this.#baseline.exported(selected.filter(path => path.startsWith(".memory-bank/") && files.has(path)));
|
|
123
|
+
}
|
|
124
|
+
/** SQL-only identity for an independent pre-launch baseline receipt. */
|
|
125
|
+
baselineBinding(context) {
|
|
126
|
+
this.assertCustody(context);
|
|
127
|
+
const receipt = this.#registration.receipt(), assigned = this.#registration.batch().works.find(item => receipt.work_ids[item.key] === this.#workId);
|
|
128
|
+
return { project_id: receipt.scope.project_id, run_id: receipt.run_id, work_id: this.#workId, generation: this.#generation,
|
|
129
|
+
registration_sha256: hybridDigest(receipt), allocation_sha256: hybridDigest(readWorkBatchCustody(context, this.#workId)),
|
|
130
|
+
packet_sha256: assigned.payload.memory_repair.packet_sha256, workspace_root: this.#bundle.execution.snapshot().scope.workspace,
|
|
131
|
+
first_review_input_sha256: receipt.first_review_input_sha256, first_check_receipt_sha256: receipt.first_check_receipt_sha256,
|
|
132
|
+
...(this.#current ? { current_view_sha256: this.#current.sha256, current_view_receipt_sha256: hybridDigest(this.#current.receipt()) } : {}) };
|
|
133
|
+
}
|
|
134
|
+
assertCustody(context) {
|
|
135
|
+
this.#registration.assertCustody(context);
|
|
136
|
+
const receipt = this.#registration.receipt(), scope = receipt.scope;
|
|
137
|
+
assertRunMutationAllowed(context, scope.project_id, scope.run_id, this.#generation);
|
|
138
|
+
this.#coordinator.assertAllocatedCustody(context);
|
|
139
|
+
if (hybridDigest(readMemoryReviewPhase(context, { projectId: scope.project_id, runId: scope.run_id }, this.#bundle)) !== hybridDigest(scope)
|
|
140
|
+
|| readDescriptorCoordinatorSession(context, { scope: { projectId: scope.project_id, runId: scope.run_id, stage: "review" },
|
|
141
|
+
bundle: this.#bundle, workId: scope.parent_work_id }, refuse) !== this.#session)
|
|
142
|
+
refuse("coordinator_custody_changed");
|
|
143
|
+
const batch = this.#registration.batch(), target = batch.works.find(item => receipt.work_ids[item.key] === this.#workId);
|
|
144
|
+
if (!target)
|
|
145
|
+
refuse("foreign_second_repair_work");
|
|
146
|
+
if (this.#current) {
|
|
147
|
+
this.#current.assertCustody(context);
|
|
148
|
+
const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='memory.repair_current_view' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.work_id')=? LIMIT 2", [scope.project_id, scope.run_id, this.#workId]);
|
|
149
|
+
if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 40 * 1024 * 1024)
|
|
150
|
+
refuse("registered_second_current_view_required");
|
|
151
|
+
try {
|
|
152
|
+
if (hybridDigest(JSON.parse(rows[0].payload_json)) !== hybridDigest(this.#current.receipt()))
|
|
153
|
+
refuse("registered_second_current_view_changed");
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
if (error instanceof AppError)
|
|
157
|
+
throw error;
|
|
158
|
+
refuse("registered_second_current_view_changed");
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
else if (target.depends_on.length)
|
|
162
|
+
refuse("settled_second_dependency_view_required");
|
|
163
|
+
if (context.db.get("SELECT 1 FROM work_sessions WHERE work_id=? LIMIT 1", [this.#workId])
|
|
164
|
+
|| (context.db.get("SELECT 1 FROM sqlite_schema WHERE type='table' AND name='work_launches'")
|
|
165
|
+
&& context.db.get("SELECT 1 FROM work_launches WHERE work_id=? LIMIT 1", [this.#workId])))
|
|
166
|
+
refuse("unstarted_target_required");
|
|
167
|
+
for (const item of batch.works) {
|
|
168
|
+
const id = receipt.work_ids[item.key], row = context.db.get("SELECT status FROM works WHERE project_id=? AND run_id=? AND work_id=?", [scope.project_id, scope.run_id, id]);
|
|
169
|
+
if (!row || !(this.#current ? ["created", "running", "completed"] : ["created", "running"]).includes(row.status))
|
|
170
|
+
refuse("settled_second_peer_view_required");
|
|
171
|
+
if (id === this.#workId && row.status !== "created")
|
|
172
|
+
refuse("unstarted_target_required");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
revalidate(context) {
|
|
176
|
+
this.assertCustody(context);
|
|
177
|
+
this.#registration.revalidate(context);
|
|
178
|
+
this.#observed.revalidate();
|
|
179
|
+
const workspace = this.#bundle.execution.snapshot().scope.workspace;
|
|
180
|
+
this.#baseline.revalidate(workspace);
|
|
181
|
+
if (this.#current)
|
|
182
|
+
this.#current.revalidate(context);
|
|
183
|
+
const input = this.copyInput();
|
|
184
|
+
if (reviewSourceHash(workspace, input.explicit_inputs, input.base) !== input.source_sha256)
|
|
185
|
+
refuse("first_wave_source_changed");
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
3
|
+
import { runtimeHarness } from "./execution-policy.js";
|
|
4
|
+
import { PreparedRegisteredCorrectedMemoryReview } from "./memory-corrected-review-registration.js";
|
|
5
|
+
import { PreparedSecondCorrectedMemoryReviewInputs } from "./memory-second-corrected-review-inputs.js";
|
|
6
|
+
import { readMemoryReviewWorkSettlement } from "./memory-review-acceptance.js";
|
|
7
|
+
import { validateMemoryReviewWorkResult } from "./memory-review-work-results.js";
|
|
8
|
+
import { assertSelectiveMemoryReviewCoverage } from "./memory-corrected-review-acceptance.js";
|
|
9
|
+
import { correctedMemoryReviewFindings } from "./memory-corrected-review-findings.js";
|
|
10
|
+
import { correctedMemoryReviewUnits } from "./memory-corrected-review-projection.js";
|
|
11
|
+
import { ObservedMemorySources } from "./memory-source-observations.js";
|
|
12
|
+
import { memorySourceKey } from "./memory-stage-results.js";
|
|
13
|
+
import { PreparedMemoryStageResult } from "./memory-stage-results.js";
|
|
14
|
+
const token = Symbol("second-memory-review-acceptance");
|
|
15
|
+
function refuse(reason) {
|
|
16
|
+
throw new AppError("memory_second_review_acceptance_invalid", "Final selective review lacks independent second settlements and first-review history", 2, { reason, effect: "no_effect" });
|
|
17
|
+
}
|
|
18
|
+
/** Independent second fan-in only. Material defects block the final review;
|
|
19
|
+
* this owner cannot allocate another correction wave or close the Stage. */
|
|
20
|
+
export class PreparedSecondMemoryReviewAcceptance {
|
|
21
|
+
static isPrepared(value) {
|
|
22
|
+
return typeof value === "object" && value !== null && #registration in value;
|
|
23
|
+
}
|
|
24
|
+
sha256;
|
|
25
|
+
#registration;
|
|
26
|
+
#workers;
|
|
27
|
+
#results;
|
|
28
|
+
#previous;
|
|
29
|
+
#harness;
|
|
30
|
+
#observed;
|
|
31
|
+
constructor(key, registration, workers, results, previous, harness, observed) {
|
|
32
|
+
if (key !== token)
|
|
33
|
+
refuse("acceptance_token_required");
|
|
34
|
+
this.#registration = registration;
|
|
35
|
+
this.#workers = workers;
|
|
36
|
+
this.#results = structuredClone(results);
|
|
37
|
+
this.#previous = structuredClone(previous);
|
|
38
|
+
this.#harness = harness;
|
|
39
|
+
this.#observed = observed;
|
|
40
|
+
this.sha256 = hybridDigest({ registration_sha256: registration.sha256, previous, workers: workers.map(worker => ({
|
|
41
|
+
work_id: worker.workId, authority_sha256: worker.authority, result: worker.result
|
|
42
|
+
})), results });
|
|
43
|
+
Object.freeze(this);
|
|
44
|
+
}
|
|
45
|
+
static prepare(context, registration) {
|
|
46
|
+
if (!PreparedRegisteredCorrectedMemoryReview.isPrepared(registration)
|
|
47
|
+
|| !PreparedSecondCorrectedMemoryReviewInputs.isPrepared(registration.inputs()))
|
|
48
|
+
refuse("prepared_second_registration_required");
|
|
49
|
+
if (context.db.isOwnedWriteTransaction?.())
|
|
50
|
+
refuse("preparation_outside_writer_required");
|
|
51
|
+
registration.revalidate(context);
|
|
52
|
+
const receipt = registration.receipt(), view = receipt.view, bundle = registration.inputs().bundle(), batch = registration.batch();
|
|
53
|
+
if (receipt.wave !== 2 || view.scope.bundle_sha256 !== bundle.sha256 || view.scope.execution_sha256 !== bundle.execution.sha256)
|
|
54
|
+
refuse("foreign_second_bundle");
|
|
55
|
+
const harness = runtimeHarness(bundle.execution.work("review").worker);
|
|
56
|
+
const workers = context.db.readSnapshot(() => batch.works.map(item => readMemoryReviewWorkSettlement(context, receipt, receipt.work_ids[item.key], harness)));
|
|
57
|
+
const results = workers.map((worker, index) => validateMemoryReviewWorkResult(worker.bytes, bundle.engine(), {
|
|
58
|
+
workId: worker.workId, packet: batch.works[index].payload.memory_review, references: view.run_inputs
|
|
59
|
+
}));
|
|
60
|
+
assertSelectiveMemoryReviewCoverage(view.affected_unit_ids, results);
|
|
61
|
+
const previous = registration.inputs().completedWave().registration().firstEffectiveReviewProof(context);
|
|
62
|
+
const root = context.db.get("SELECT run_root,run_dir FROM runs WHERE project_id=? AND id=?", [view.scope.project_id, view.scope.run_id]);
|
|
63
|
+
if (!root)
|
|
64
|
+
refuse("owned_run_missing");
|
|
65
|
+
const observed = ObservedMemorySources.capture({ run: root.run_root ?? root.run_dir }, workers.map(worker => ({ root: "run", path: worker.result.path })));
|
|
66
|
+
const hashes = observed.hashes();
|
|
67
|
+
if (workers.some(worker => hashes.get(memorySourceKey("run", worker.result.path)) !== worker.result.sha256))
|
|
68
|
+
refuse("published_result_changed");
|
|
69
|
+
const prepared = new PreparedSecondMemoryReviewAcceptance(token, registration, workers, results, previous, harness, observed);
|
|
70
|
+
prepared.resultProjection();
|
|
71
|
+
prepared.revalidate(context);
|
|
72
|
+
return prepared;
|
|
73
|
+
}
|
|
74
|
+
registration() { return this.#registration; }
|
|
75
|
+
results() { return structuredClone(this.#results); }
|
|
76
|
+
references() {
|
|
77
|
+
return this.#workers.map((worker, index) => ({ reviewer_work_id: worker.workId,
|
|
78
|
+
input_sha256: this.#registration.receipt().input_sha256, result: { ...worker.result }, unit_ids: [this.#results[index].unit_id] }));
|
|
79
|
+
}
|
|
80
|
+
resultProjection() {
|
|
81
|
+
const wave = this.#registration.inputs().completedWave(), registration = wave.registration(), receipt = registration.receipt(), batch = registration.batch();
|
|
82
|
+
const view = this.#registration.receipt().view, references = this.references();
|
|
83
|
+
const coverage = correctedMemoryReviewUnits({ view, original: this.#previous.results, current: this.results(),
|
|
84
|
+
originalReferences: this.#previous.references, currentReferences: references, repairWorkIds: wave.repairResults().map(repair => repair.work_id) });
|
|
85
|
+
const findings = correctedMemoryReviewFindings({ original: this.#previous.results, current: this.results(), affected: view.affected_unit_ids,
|
|
86
|
+
originalReferences: this.#previous.references, currentReferences: references, findingHistory: this.#previous.projection.findings,
|
|
87
|
+
repairs: wave.repairResults().map(repair => ({ ...repair, unit_ids: batch.works.find(item => receipt.work_ids[item.key] === repair.work_id).payload.memory_repair.unit_ids })) });
|
|
88
|
+
return { ...coverage, ...findings, outcome: findings.outcome === "accepted" ? "accepted" : "blocked" };
|
|
89
|
+
}
|
|
90
|
+
assertResult(result) {
|
|
91
|
+
if (!(result instanceof PreparedMemoryStageResult))
|
|
92
|
+
refuse("prepared_result_required");
|
|
93
|
+
const value = result.value(), receipt = this.#registration.receipt();
|
|
94
|
+
if (value.run_id !== receipt.run_id || value.stage !== "review" || value.input_sha256 !== receipt.scope.input_sha256
|
|
95
|
+
|| hybridDigest({ units: value.units, findings: value.findings, outcome: value.outcome, details: value.details }) !== hybridDigest(this.resultProjection()))
|
|
96
|
+
refuse("review_claim_differs_from_settled_second_wave");
|
|
97
|
+
}
|
|
98
|
+
settlementProof(context) {
|
|
99
|
+
this.assertCustody(context);
|
|
100
|
+
const receipt = this.#registration.receipt();
|
|
101
|
+
return { registration_scope: structuredClone(receipt.scope), registration_sha256: this.#registration.sha256, wave: 2,
|
|
102
|
+
check_receipt_sha256: receipt.check_receipt_sha256, first_review_proof_sha256: hybridDigest(this.#previous),
|
|
103
|
+
workers: this.#workers.map(worker => ({ work_id: worker.workId, authority_sha256: worker.authority, result: { ...worker.result } })) };
|
|
104
|
+
}
|
|
105
|
+
assertCustody(context) {
|
|
106
|
+
this.#registration.assertCustody(context);
|
|
107
|
+
if (hybridDigest(this.#registration.inputs().completedWave().registration().firstEffectiveReviewProof(context)) !== hybridDigest(this.#previous))
|
|
108
|
+
refuse("first_review_history_changed");
|
|
109
|
+
const receipt = this.#registration.receipt();
|
|
110
|
+
for (const worker of this.#workers)
|
|
111
|
+
if (readMemoryReviewWorkSettlement(context, receipt, worker.workId, this.#harness).authority !== worker.authority)
|
|
112
|
+
refuse("second_settlement_changed");
|
|
113
|
+
}
|
|
114
|
+
revalidate(context) { this.#registration.revalidate(context); this.#observed.revalidate(); this.assertCustody(context); }
|
|
115
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { AppError } from "../shared/errors.js";
|
|
5
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
6
|
+
import { memorySourceKey } from "./memory-stage-results.js";
|
|
7
|
+
import { localEnvironmentAllowed } from "./workspace-files.js";
|
|
8
|
+
function refuse(reason) {
|
|
9
|
+
throw new AppError("memory_source_observation_failed", "Selected Memory Bank evidence cannot be observed", 2, { reason, phase: "prepare", effect: "no_effect" });
|
|
10
|
+
}
|
|
11
|
+
function contained(logical) {
|
|
12
|
+
if (!logical || path.posix.isAbsolute(logical) || path.win32.isAbsolute(logical) || logical.includes("\\") || logical.includes("\0")
|
|
13
|
+
|| logical.split("/").some(segment => !segment || segment === "." || segment === ".."))
|
|
14
|
+
refuse("unsafe_source_path");
|
|
15
|
+
if (logical.split("/").some(segment => !localEnvironmentAllowed(segment)))
|
|
16
|
+
refuse("private_source_path");
|
|
17
|
+
}
|
|
18
|
+
function directory(file) { if (!fs.lstatSync(file).isDirectory())
|
|
19
|
+
refuse("linked_or_non_directory_source"); }
|
|
20
|
+
function read(file, remaining) {
|
|
21
|
+
const selected = fs.lstatSync(file);
|
|
22
|
+
if (!selected.isFile())
|
|
23
|
+
refuse("linked_or_non_regular_source");
|
|
24
|
+
const fd = fs.openSync(file, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
|
|
25
|
+
try {
|
|
26
|
+
const before = fs.fstatSync(fd);
|
|
27
|
+
if (before.dev !== selected.dev || before.ino !== selected.ino)
|
|
28
|
+
refuse("source_changed_during_observation");
|
|
29
|
+
if (!before.isFile() || before.size > 16 * 1024 * 1024 || before.size > remaining)
|
|
30
|
+
refuse("source_size_exceeds_bound");
|
|
31
|
+
const content = Buffer.alloc(before.size + 1);
|
|
32
|
+
let length = 0;
|
|
33
|
+
while (length < content.length) {
|
|
34
|
+
const count = fs.readSync(fd, content, length, content.length - length, length);
|
|
35
|
+
if (!count)
|
|
36
|
+
break;
|
|
37
|
+
length += count;
|
|
38
|
+
}
|
|
39
|
+
const after = fs.fstatSync(fd);
|
|
40
|
+
const retained = fs.lstatSync(file);
|
|
41
|
+
if (length !== before.size || after.size !== before.size || after.mtimeMs !== before.mtimeMs
|
|
42
|
+
|| after.ctimeMs !== before.ctimeMs || !retained.isFile() || retained.dev !== before.dev
|
|
43
|
+
|| retained.ino !== before.ino)
|
|
44
|
+
refuse("source_changed_during_observation");
|
|
45
|
+
return content.subarray(0, length);
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
fs.closeSync(fd);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Byte observations for a CLI-selected inventory, not arbitrary paths proposed by an agent.
|
|
52
|
+
* No directory scan, Git mutation, implicit root fallback or symlink following occurs.
|
|
53
|
+
* Receipt bytes do not establish authorship, settled Work or semantic/check success.
|
|
54
|
+
*/
|
|
55
|
+
export class ObservedMemorySources {
|
|
56
|
+
sha256;
|
|
57
|
+
#roots;
|
|
58
|
+
#locations;
|
|
59
|
+
#bytes;
|
|
60
|
+
#hashes;
|
|
61
|
+
constructor(roots, locations, content) {
|
|
62
|
+
this.#roots = structuredClone(roots);
|
|
63
|
+
this.#locations = structuredClone(locations);
|
|
64
|
+
this.#bytes = content;
|
|
65
|
+
this.#hashes = new Map([...content].map(([key, bytes]) => [key, createHash("sha256").update(bytes).digest("hex")]));
|
|
66
|
+
this.sha256 = hybridDigest({ roots, sources: [...this.#hashes] });
|
|
67
|
+
Object.freeze(this);
|
|
68
|
+
}
|
|
69
|
+
static capture(roots, selected) {
|
|
70
|
+
try {
|
|
71
|
+
if (!Array.isArray(selected) || selected.length > 20_000 || !roots || Object.keys(roots).some(key => !["project", "workspace", "canon", "run"].includes(key)))
|
|
72
|
+
refuse("invalid_inventory");
|
|
73
|
+
const locations = structuredClone([...selected]).sort((a, b) => memorySourceKey(a.root, a.path).localeCompare(memorySourceKey(b.root, b.path), "en"));
|
|
74
|
+
const content = new Map();
|
|
75
|
+
let total = 0;
|
|
76
|
+
for (const location of locations) {
|
|
77
|
+
if (!location || Object.keys(location).some(key => !["root", "path"].includes(key)))
|
|
78
|
+
refuse("invalid_source_location");
|
|
79
|
+
contained(location.path);
|
|
80
|
+
if (!Object.hasOwn(roots, location.root))
|
|
81
|
+
refuse("missing_or_noncanonical_root");
|
|
82
|
+
const root = roots[location.root];
|
|
83
|
+
if (typeof root !== "string" || !path.isAbsolute(root) || fs.realpathSync(root) !== path.resolve(root))
|
|
84
|
+
refuse("missing_or_noncanonical_root");
|
|
85
|
+
directory(root);
|
|
86
|
+
const key = memorySourceKey(location.root, location.path);
|
|
87
|
+
if (content.has(key))
|
|
88
|
+
refuse("duplicate_source_location");
|
|
89
|
+
let parent = root;
|
|
90
|
+
for (const segment of location.path.split("/").slice(0, -1)) {
|
|
91
|
+
parent = path.join(parent, segment);
|
|
92
|
+
directory(parent);
|
|
93
|
+
}
|
|
94
|
+
const bytes = read(path.join(root, location.path), 64 * 1024 * 1024 - total);
|
|
95
|
+
total += bytes.length;
|
|
96
|
+
content.set(key, Buffer.from(bytes));
|
|
97
|
+
}
|
|
98
|
+
return new ObservedMemorySources(roots, locations, content);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (error instanceof AppError && error.code === "memory_source_observation_failed")
|
|
102
|
+
throw error;
|
|
103
|
+
return refuse("source_unavailable");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
hashes() { return new Map(this.#hashes); }
|
|
107
|
+
bytes(location) {
|
|
108
|
+
const content = this.#bytes.get(memorySourceKey(location.root, location.path));
|
|
109
|
+
if (!content)
|
|
110
|
+
refuse("unselected_source_location");
|
|
111
|
+
return Buffer.from(content);
|
|
112
|
+
}
|
|
113
|
+
revalidate() {
|
|
114
|
+
if (ObservedMemorySources.capture(this.#roots, this.#locations).sha256 !== this.sha256)
|
|
115
|
+
refuse("source_changed_after_observation");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
import { startMemoryPreflight, finishMemoryPreflight } from "./memory-preflight-handler.js";
|
|
3
|
+
import { startMemoryDiscovery, finishMemoryDiscovery } from "./memory-discover-handler.js";
|
|
4
|
+
import { startMemoryComposition, finishMemoryComposition } from "./memory-compose-handler.js";
|
|
5
|
+
import { startMemoryVerification, finishMemoryVerification } from "./memory-verify-handler.js";
|
|
6
|
+
import { startMemoryReview, finishMemoryReview } from "./memory-review-handler.js";
|
|
7
|
+
import { startMemoryFinish, finishMemoryFinish } from "./memory-finish-handler.js";
|
|
8
|
+
import { memoryInitHandlerIdentities as ids } from "../domain/stage-handler-identities.js";
|
|
9
|
+
// Engine-owned functions only; this is dispatch, not descriptor admission.
|
|
10
|
+
export const memoryInitStageHandlers = Object.freeze({
|
|
11
|
+
preflight: Object.freeze({ ...ids.preflight, entry: startMemoryPreflight, finish: finishMemoryPreflight }),
|
|
12
|
+
discover: Object.freeze({ ...ids.discover, entry: startMemoryDiscovery, finish: finishMemoryDiscovery }),
|
|
13
|
+
compose: Object.freeze({ ...ids.compose, entry: startMemoryComposition, finish: finishMemoryComposition }),
|
|
14
|
+
verify: Object.freeze({ ...ids.verify, entry: startMemoryVerification, finish: finishMemoryVerification }),
|
|
15
|
+
review: Object.freeze({ ...ids.review, entry: startMemoryReview, finish: finishMemoryReview }),
|
|
16
|
+
finish: Object.freeze({ ...ids.finish, entry: startMemoryFinish, finish: finishMemoryFinish })
|
|
17
|
+
});
|
|
18
|
+
function stage(input) {
|
|
19
|
+
if (!Object.hasOwn(memoryInitStageHandlers, input.stage) || !(input.bytes instanceof Uint8Array) || input.bytes.length > 1024 * 1024)
|
|
20
|
+
throw new AppError("memory_stage_dispatch_invalid", "Memory stage dispatch requires a bounded declared stage input", 2, { effect: "no_effect", phase: "prepare" });
|
|
21
|
+
return input.stage;
|
|
22
|
+
}
|
|
23
|
+
export function startMemoryStage(context, input) {
|
|
24
|
+
return memoryInitStageHandlers[stage(input)].entry(context, input);
|
|
25
|
+
}
|
|
26
|
+
export function finishMemoryStage(context, input) {
|
|
27
|
+
return memoryInitStageHandlers[stage(input)].finish(context, input);
|
|
28
|
+
}
|