@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,160 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { AppError } from "../shared/errors.js";
|
|
3
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
4
|
+
import { MemoryRepairDocumentBaseline } from "./memory-repair-document-delta.js";
|
|
5
|
+
const token = Symbol("retained-memory-repair-publication-plan");
|
|
6
|
+
function refuse(reason) {
|
|
7
|
+
throw new AppError("memory_repair_publication_plan_invalid", "Retained publication metadata or bytes are inconsistent", 1, { reason, effect: "no_effect" });
|
|
8
|
+
}
|
|
9
|
+
function decode(value, limit) {
|
|
10
|
+
if (typeof value !== "string" || value.length > Math.ceil(limit / 3) * 4)
|
|
11
|
+
refuse("unbounded_or_invalid_bytes");
|
|
12
|
+
const bytes = Buffer.from(value, "base64");
|
|
13
|
+
if (bytes.length > limit || bytes.toString("base64") !== value)
|
|
14
|
+
refuse("noncanonical_retained_bytes");
|
|
15
|
+
return bytes;
|
|
16
|
+
}
|
|
17
|
+
function ordered(entries) {
|
|
18
|
+
return [...entries].sort((a, b) => {
|
|
19
|
+
const left = a.path.split("/"), right = b.path.split("/");
|
|
20
|
+
for (let index = 0; index < Math.min(left.length, right.length); index++) {
|
|
21
|
+
if (left[index] !== right[index])
|
|
22
|
+
return left[index] < right[index] ? -1 : 1;
|
|
23
|
+
}
|
|
24
|
+
return left.length - right.length;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/** Strict retained metadata decoder, not SQL/Session authority or permission
|
|
28
|
+
* to apply files. The publication owner must independently select its receipt.
|
|
29
|
+
* Restoration never captures today's partly published workspace as baseline.
|
|
30
|
+
*/
|
|
31
|
+
export class RetainedMemoryRepairPublicationPlan {
|
|
32
|
+
sha256;
|
|
33
|
+
#receipt;
|
|
34
|
+
#before;
|
|
35
|
+
#writePaths;
|
|
36
|
+
constructor(key, receipt, before) {
|
|
37
|
+
if (key !== token)
|
|
38
|
+
refuse("retained_plan_token_required");
|
|
39
|
+
this.#receipt = structuredClone(receipt);
|
|
40
|
+
this.#before = before;
|
|
41
|
+
this.#writePaths = JSON.parse(before.bytes().toString("utf8")).write_paths;
|
|
42
|
+
this.sha256 = hybridDigest(receipt);
|
|
43
|
+
Object.freeze(this);
|
|
44
|
+
}
|
|
45
|
+
static restore(bytes, expectedSha256) {
|
|
46
|
+
try {
|
|
47
|
+
if (!(bytes instanceof Uint8Array) || !bytes.length || bytes.length > 160 * 1024 * 1024
|
|
48
|
+
|| typeof expectedSha256 !== "string" || !/^[a-f0-9]{64}$/.test(expectedSha256))
|
|
49
|
+
refuse("invalid_retained_plan");
|
|
50
|
+
const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
51
|
+
if (!value || Object.keys(value).sort().join(",") !== "actions,after_base64,after_sha256,before_base64,before_sha256"
|
|
52
|
+
|| !Array.isArray(value.actions) || value.actions.length > 20_000 || hybridDigest(value) !== expectedSha256
|
|
53
|
+
|| !Buffer.from(JSON.stringify(value)).equals(Buffer.from(bytes)))
|
|
54
|
+
refuse("invalid_retained_plan");
|
|
55
|
+
const before = MemoryRepairDocumentBaseline.restore(decode(value.before_base64, 8 * 1024 * 1024), value.before_sha256);
|
|
56
|
+
const after = MemoryRepairDocumentBaseline.restore(decode(value.after_base64, 8 * 1024 * 1024), value.after_sha256);
|
|
57
|
+
const writes = JSON.parse(before.bytes().toString("utf8")).write_paths;
|
|
58
|
+
if (hybridDigest(writes) !== hybridDigest(JSON.parse(after.bytes().toString("utf8")).write_paths))
|
|
59
|
+
refuse("write_paths_changed");
|
|
60
|
+
const current = new Map(before.entries().map(entry => [entry.path, entry]));
|
|
61
|
+
const final = new Map(after.entries().map(entry => [entry.path, entry]));
|
|
62
|
+
const directories = new Set();
|
|
63
|
+
for (const entry of final.values())
|
|
64
|
+
if (entry.kind === "file") {
|
|
65
|
+
const parts = entry.path.split("/");
|
|
66
|
+
for (let length = 1; length < parts.length; length++)
|
|
67
|
+
directories.add(parts.slice(0, length).join("/"));
|
|
68
|
+
}
|
|
69
|
+
const seen = new Set();
|
|
70
|
+
let total = 0;
|
|
71
|
+
for (const action of value.actions) {
|
|
72
|
+
if (!action || Object.keys(action).sort().join(",") !== "after,before,bytes_base64,path" || typeof action.path !== "string"
|
|
73
|
+
|| seen.has(action.path) || hybridDigest(action.before) !== hybridDigest(current.get(action.path) ?? null)
|
|
74
|
+
|| hybridDigest(action.after) !== hybridDigest(final.get(action.path) ?? null)
|
|
75
|
+
|| hybridDigest(action.before) === hybridDigest(action.after))
|
|
76
|
+
refuse("invalid_retained_action");
|
|
77
|
+
seen.add(action.path);
|
|
78
|
+
if (action.after?.kind === "directory") {
|
|
79
|
+
if (action.before || ![0o700, 0o755].includes(action.after.mode) || action.bytes_base64 !== null
|
|
80
|
+
|| !directories.has(action.path))
|
|
81
|
+
refuse("unassigned_directory_action");
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
if (!writes.includes(action.path) || action.before?.kind === "directory"
|
|
85
|
+
|| action.after && action.after.kind !== "file")
|
|
86
|
+
refuse("unassigned_file_action");
|
|
87
|
+
if (action.after) {
|
|
88
|
+
if (action.before ? action.before.mode !== action.after.mode : ![0o600, 0o644].includes(action.after.mode))
|
|
89
|
+
refuse("file_mode_action");
|
|
90
|
+
const content = decode(action.bytes_base64, 16 * 1024 * 1024);
|
|
91
|
+
total += content.length;
|
|
92
|
+
if (total > 64 * 1024 * 1024 || createHash("sha256").update(content).digest("hex") !== action.after.sha256
|
|
93
|
+
|| action.before?.sha256 === action.after.sha256)
|
|
94
|
+
refuse("retained_output_bytes_changed");
|
|
95
|
+
}
|
|
96
|
+
else if (!action.before || action.bytes_base64 !== null)
|
|
97
|
+
refuse("invalid_delete_action");
|
|
98
|
+
}
|
|
99
|
+
if (action.after) {
|
|
100
|
+
const parent = action.path.slice(0, action.path.lastIndexOf("/"));
|
|
101
|
+
if (current.get(parent)?.kind !== "directory")
|
|
102
|
+
refuse("parent_action_out_of_order");
|
|
103
|
+
current.set(action.path, action.after);
|
|
104
|
+
}
|
|
105
|
+
else
|
|
106
|
+
current.delete(action.path);
|
|
107
|
+
}
|
|
108
|
+
if (hybridDigest(ordered(current.values())) !== hybridDigest(after.entries()))
|
|
109
|
+
refuse("unexplained_after_view");
|
|
110
|
+
return new RetainedMemoryRepairPublicationPlan(token, value, before);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
if (error instanceof AppError && error.code === "memory_repair_publication_plan_invalid")
|
|
114
|
+
throw error;
|
|
115
|
+
return refuse("invalid_retained_plan");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
actions() { return structuredClone(this.#receipt.actions); }
|
|
119
|
+
bytes() { return Buffer.from(JSON.stringify(this.#receipt)); }
|
|
120
|
+
output(logical) {
|
|
121
|
+
const action = this.#receipt.actions.find(item => item.path === logical && item.after?.kind === "file");
|
|
122
|
+
if (!action)
|
|
123
|
+
refuse("unselected_output");
|
|
124
|
+
return decode(action.bytes_base64, 16 * 1024 * 1024);
|
|
125
|
+
}
|
|
126
|
+
state(completed) {
|
|
127
|
+
if (!Number.isSafeInteger(completed) || completed < 0 || completed > this.#receipt.actions.length)
|
|
128
|
+
refuse("invalid_cursor");
|
|
129
|
+
const entries = new Map(this.#before.entries().map(entry => [entry.path, entry]));
|
|
130
|
+
for (const action of this.#receipt.actions.slice(0, completed)) {
|
|
131
|
+
if (action.after)
|
|
132
|
+
entries.set(action.path, action.after);
|
|
133
|
+
else
|
|
134
|
+
entries.delete(action.path);
|
|
135
|
+
}
|
|
136
|
+
const value = { schema_id: "dd-flow/memory-repair-document-baseline@1", entries: ordered(entries.values()), write_paths: this.#writePaths };
|
|
137
|
+
return MemoryRepairDocumentBaseline.restore(Buffer.from(JSON.stringify(value)), hybridDigest({ entries: value.entries, write_paths: value.write_paths }));
|
|
138
|
+
}
|
|
139
|
+
classify(workspace) {
|
|
140
|
+
const actual = MemoryRepairDocumentBaseline.capture(workspace, this.#writePaths);
|
|
141
|
+
const entries = new Map(actual.entries().map(entry => [entry.path, entry]));
|
|
142
|
+
let completed = 0, pending = false;
|
|
143
|
+
for (const action of this.#receipt.actions) {
|
|
144
|
+
const current = entries.get(action.path) ?? null;
|
|
145
|
+
if (hybridDigest(current) === hybridDigest(action.after)) {
|
|
146
|
+
if (pending)
|
|
147
|
+
refuse("unknown_publication_prefix");
|
|
148
|
+
completed++;
|
|
149
|
+
}
|
|
150
|
+
else if (hybridDigest(current) === hybridDigest(action.before))
|
|
151
|
+
pending = true;
|
|
152
|
+
else
|
|
153
|
+
refuse("unknown_publication_prefix");
|
|
154
|
+
}
|
|
155
|
+
if (actual.sha256 !== this.state(completed).sha256)
|
|
156
|
+
refuse("unknown_publication_prefix");
|
|
157
|
+
return { completed, total: this.#receipt.actions.length,
|
|
158
|
+
status: completed === this.#receipt.actions.length ? "after" : completed === 0 ? "before" : "partial" };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
import { appendAudit } from "./audit.js";
|
|
3
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
4
|
+
import { selectMemoryRepairGraph } from "./memory-repair-current-view.js";
|
|
5
|
+
import { PreparedSettledMemoryRepair } from "./memory-repair-history.js";
|
|
6
|
+
import { PreparedRegisteredMemoryCorrections } from "./memory-repair-registration.js";
|
|
7
|
+
import { PreparedMemoryRepairWorkBaseline } from "./memory-repair-work-baseline.js";
|
|
8
|
+
import { PreparedBoundMemoryRepairCopy } from "./memory-repair-writer-copy.js";
|
|
9
|
+
const token = Symbol("memory-repair-publication-source"), event = "memory.repair_publication_source";
|
|
10
|
+
const nonMemory = (entry) => entry.path !== ".memory-bank" && !entry.path.startsWith(".memory-bank/");
|
|
11
|
+
function refuse(reason) {
|
|
12
|
+
throw new AppError("memory_repair_publication_source_invalid", "Publication source lacks independently settled custody", 1, { reason, effect: "no_effect" });
|
|
13
|
+
}
|
|
14
|
+
function retained(context, baseline) {
|
|
15
|
+
const scope = baseline.receipt();
|
|
16
|
+
const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type=? AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.work_id')=? AND json_extract(payload_json,'$.generation')=? LIMIT 2", [scope.project_id, event, scope.run_id, scope.work_id, scope.generation]);
|
|
17
|
+
if (!rows.length)
|
|
18
|
+
return null;
|
|
19
|
+
if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 40 * 1024 * 1024)
|
|
20
|
+
refuse("ambiguous_or_unbounded_source_receipt");
|
|
21
|
+
try {
|
|
22
|
+
const value = JSON.parse(rows[0].payload_json);
|
|
23
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
24
|
+
refuse("invalid_source_receipt");
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (error instanceof AppError)
|
|
29
|
+
throw error;
|
|
30
|
+
return refuse("invalid_source_receipt");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function assertOwners(context, registration, baseline, copy, workSessionId) {
|
|
34
|
+
if (!(registration instanceof PreparedRegisteredMemoryCorrections))
|
|
35
|
+
refuse("registered_corrections_required");
|
|
36
|
+
if (!PreparedMemoryRepairWorkBaseline.isPrepared(baseline) || !PreparedBoundMemoryRepairCopy.isPrepared(copy))
|
|
37
|
+
refuse("registered_start_owners_required");
|
|
38
|
+
if (typeof workSessionId !== "string" || !workSessionId || workSessionId.length > 256)
|
|
39
|
+
refuse("registered_start_session_required");
|
|
40
|
+
registration.assertCustody(context);
|
|
41
|
+
baseline.assertRegisteredCustody(context);
|
|
42
|
+
copy.assertCommittedStartCustody(context, workSessionId);
|
|
43
|
+
const scope = baseline.receipt(), owner = registration.receipt(), input = copy.input();
|
|
44
|
+
if (scope.registration_sha256 !== registration.sha256 || scope.project_id !== owner.scope.project_id || scope.run_id !== owner.run_id
|
|
45
|
+
|| scope.workspace_root !== copy.sourceRoot() || input.work_id !== scope.work_id || input.generation !== scope.generation
|
|
46
|
+
|| input.baseline_registration_sha256 !== baseline.sha256)
|
|
47
|
+
refuse("foreign_start_owners");
|
|
48
|
+
}
|
|
49
|
+
function compose(start, histories, paths) {
|
|
50
|
+
if (!histories.length)
|
|
51
|
+
return start;
|
|
52
|
+
let current = histories[0].before().withWritePaths(paths);
|
|
53
|
+
const versions = new Set([hybridDigest(current.entries())]);
|
|
54
|
+
for (const history of histories) {
|
|
55
|
+
if (!versions.has(hybridDigest(history.before().entries())))
|
|
56
|
+
refuse("unexplained_peer_input_view");
|
|
57
|
+
current = current.composeSettledDelta(history.before(), history.after());
|
|
58
|
+
versions.add(hybridDigest(current.entries()));
|
|
59
|
+
}
|
|
60
|
+
if (!versions.has(hybridDigest(start.entries())))
|
|
61
|
+
refuse("unexplained_writer_start_view");
|
|
62
|
+
return current;
|
|
63
|
+
}
|
|
64
|
+
/** Running-Work shared before-view. Explicit registration records only source
|
|
65
|
+
* custody; it neither authorizes a private delta nor applies/settles anything.
|
|
66
|
+
* Retained restoration does not adopt current shared bytes after partial apply.
|
|
67
|
+
*/
|
|
68
|
+
export class PreparedMemoryRepairPublicationSource {
|
|
69
|
+
static isPrepared(value) {
|
|
70
|
+
return typeof value === "object" && value !== null && #graph in value;
|
|
71
|
+
}
|
|
72
|
+
sha256;
|
|
73
|
+
#registration;
|
|
74
|
+
#baseline;
|
|
75
|
+
#copy;
|
|
76
|
+
#workSessionId;
|
|
77
|
+
#graph;
|
|
78
|
+
#histories;
|
|
79
|
+
#before;
|
|
80
|
+
#body;
|
|
81
|
+
#registered = false;
|
|
82
|
+
constructor(key, registration, baseline, copy, workSessionId, graph, histories, before, startSha256) {
|
|
83
|
+
if (key !== token)
|
|
84
|
+
refuse("source_token_required");
|
|
85
|
+
this.#registration = registration;
|
|
86
|
+
this.#baseline = baseline;
|
|
87
|
+
this.#copy = copy;
|
|
88
|
+
this.#workSessionId = workSessionId;
|
|
89
|
+
this.#graph = structuredClone(graph);
|
|
90
|
+
this.#histories = histories;
|
|
91
|
+
this.#before = before;
|
|
92
|
+
const scope = baseline.receipt();
|
|
93
|
+
this.#body = { schema_id: "dd-flow/memory-repair-publication-source@1", project_id: scope.project_id, run_id: scope.run_id,
|
|
94
|
+
work_id: scope.work_id, generation: scope.generation, work_session_id: workSessionId,
|
|
95
|
+
registration_sha256: registration.sha256, baseline_registration_sha256: baseline.sha256, start_receipt_sha256: startSha256,
|
|
96
|
+
copy_input_sha256: hybridDigest(copy.input()), source_root: copy.sourceRoot(), selected: structuredClone(graph),
|
|
97
|
+
histories: histories.map(history => ({ work_id: history.workId, sha256: history.sha256 })),
|
|
98
|
+
before_sha256: before.sha256, before_base64: before.bytes().toString("base64") };
|
|
99
|
+
this.sha256 = hybridDigest(this.#body);
|
|
100
|
+
Object.freeze(this);
|
|
101
|
+
}
|
|
102
|
+
static #observe(context, registration, baseline, copy, workSessionId, states) {
|
|
103
|
+
assertOwners(context, registration, baseline, copy, workSessionId);
|
|
104
|
+
if (context.db.isOwnedWriteTransaction?.())
|
|
105
|
+
refuse("preparation_outside_writer_required");
|
|
106
|
+
const graph = context.db.readSnapshot(() => {
|
|
107
|
+
assertOwners(context, registration, baseline, copy, workSessionId);
|
|
108
|
+
const selected = selectMemoryRepairGraph(context, registration, baseline.workId, "running", states);
|
|
109
|
+
if (selected.generation !== baseline.receipt().generation)
|
|
110
|
+
refuse("foreign_source_generation");
|
|
111
|
+
return selected;
|
|
112
|
+
});
|
|
113
|
+
const histories = graph.settlements.map(item => PreparedSettledMemoryRepair.prepare(context, registration, item.work_id));
|
|
114
|
+
const immutableInputs = hybridDigest(copy.workspaceManifest().entries().filter(nonMemory));
|
|
115
|
+
if (histories.some(history => hybridDigest(history.workspaceManifest().entries().filter(nonMemory)) !== immutableInputs))
|
|
116
|
+
refuse("peer_non_memory_inputs_changed");
|
|
117
|
+
const before = compose(baseline.baseline(), histories, graph.target.payload.memory_repair.write_paths);
|
|
118
|
+
return new PreparedMemoryRepairPublicationSource(token, registration, baseline, copy, workSessionId, graph, histories, before, copy.startReceiptSha256(context, workSessionId));
|
|
119
|
+
}
|
|
120
|
+
static prepare(context, registration, baseline, copy, workSessionId) {
|
|
121
|
+
const prepared = this.#observe(context, registration, baseline, copy, workSessionId);
|
|
122
|
+
prepared.#assertNoIntent(context);
|
|
123
|
+
prepared.revalidate(context);
|
|
124
|
+
return prepared;
|
|
125
|
+
}
|
|
126
|
+
static restore(context, registration, baseline, copy, workSessionId) {
|
|
127
|
+
assertOwners(context, registration, baseline, copy, workSessionId);
|
|
128
|
+
if (context.db.isOwnedWriteTransaction?.())
|
|
129
|
+
refuse("preparation_outside_writer_required");
|
|
130
|
+
const value = retained(context, baseline);
|
|
131
|
+
if (!value)
|
|
132
|
+
refuse("registered_source_required");
|
|
133
|
+
const selected = value.selected;
|
|
134
|
+
if (!selected || !Array.isArray(selected.states))
|
|
135
|
+
refuse("invalid_source_receipt");
|
|
136
|
+
const prepared = this.#observe(context, registration, baseline, copy, workSessionId, selected.states);
|
|
137
|
+
if (hybridDigest(prepared.receipt()) !== hybridDigest(value))
|
|
138
|
+
refuse("registered_source_changed");
|
|
139
|
+
prepared.#registered = true;
|
|
140
|
+
prepared.assertCustody(context);
|
|
141
|
+
return prepared;
|
|
142
|
+
}
|
|
143
|
+
receipt() {
|
|
144
|
+
const value = { ...structuredClone(this.#body), source_view_sha256: this.sha256 };
|
|
145
|
+
if (Buffer.byteLength(JSON.stringify(value)) > 40 * 1024 * 1024)
|
|
146
|
+
refuse("unbounded_source_receipt");
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
before() { return this.#before; }
|
|
150
|
+
matches(baseline, copy, workSessionId) {
|
|
151
|
+
return baseline === this.#baseline && copy === this.#copy && workSessionId === this.#workSessionId;
|
|
152
|
+
}
|
|
153
|
+
#assertNoIntent(context) {
|
|
154
|
+
const scope = this.#baseline.receipt();
|
|
155
|
+
if (context.db.get("SELECT 1 FROM audit_events WHERE project_id=? AND event_type='memory.repair_publication_intent' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.work_id')=? AND json_extract(payload_json,'$.generation')=? LIMIT 1", [scope.project_id, scope.run_id, scope.work_id, scope.generation]))
|
|
156
|
+
refuse("source_registration_before_intent_required");
|
|
157
|
+
}
|
|
158
|
+
#assertRegisteredOrder(context) {
|
|
159
|
+
const scope = this.#baseline.receipt();
|
|
160
|
+
const rows = context.db.all("SELECT id,event_type FROM audit_events WHERE project_id=? AND event_type IN ('memory.repair_work_start','memory.repair_publication_source','memory.repair_publication_intent') AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.work_id')=? AND json_extract(payload_json,'$.generation')=? ORDER BY id LIMIT 4", [scope.project_id, scope.run_id, scope.work_id, scope.generation]);
|
|
161
|
+
const starts = rows.filter(row => row.event_type === "memory.repair_work_start");
|
|
162
|
+
const sources = rows.filter(row => row.event_type === event);
|
|
163
|
+
const intents = rows.filter(row => row.event_type === "memory.repair_publication_intent");
|
|
164
|
+
if (rows.length > 3 || starts.length !== 1 || sources.length !== 1 || intents.length > 1
|
|
165
|
+
|| starts[0].id >= sources[0].id || intents.some(row => row.id <= sources[0].id)
|
|
166
|
+
|| this.#graph.settlements.some(row => row.id >= sources[0].id))
|
|
167
|
+
refuse("source_receipt_order_changed");
|
|
168
|
+
}
|
|
169
|
+
assertCustody(context) {
|
|
170
|
+
assertOwners(context, this.#registration, this.#baseline, this.#copy, this.#workSessionId);
|
|
171
|
+
const selected = selectMemoryRepairGraph(context, this.#registration, this.#baseline.workId, "running", this.#registered ? this.#graph.states : undefined);
|
|
172
|
+
if (hybridDigest(selected) !== hybridDigest(this.#graph)
|
|
173
|
+
|| this.#copy.startReceiptSha256(context, this.#workSessionId) !== this.#body.start_receipt_sha256)
|
|
174
|
+
refuse("source_custody_changed");
|
|
175
|
+
for (const history of this.#histories)
|
|
176
|
+
history.assertCustody(context);
|
|
177
|
+
if (this.#registered) {
|
|
178
|
+
if (hybridDigest(retained(context, this.#baseline)) !== hybridDigest(this.receipt()))
|
|
179
|
+
refuse("registered_source_changed");
|
|
180
|
+
this.#assertRegisteredOrder(context);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
assertRegisteredCustody(context) {
|
|
184
|
+
if (!this.#registered)
|
|
185
|
+
refuse("registered_source_required");
|
|
186
|
+
this.assertCustody(context);
|
|
187
|
+
}
|
|
188
|
+
revalidate(context) {
|
|
189
|
+
if (context.db.isOwnedWriteTransaction?.())
|
|
190
|
+
refuse("preparation_outside_writer_required");
|
|
191
|
+
this.assertCustody(context);
|
|
192
|
+
this.#copy.verifyPrivateInputs(context, this.#workSessionId);
|
|
193
|
+
this.#copy.verifyPublicationSourceInputs(context, this.#workSessionId, this.#before);
|
|
194
|
+
for (const history of this.#histories)
|
|
195
|
+
history.revalidate(context);
|
|
196
|
+
this.assertCustody(context);
|
|
197
|
+
}
|
|
198
|
+
register(context) {
|
|
199
|
+
this.revalidate(context);
|
|
200
|
+
const receipt = this.receipt(), expected = hybridDigest(receipt), scope = this.#baseline.receipt();
|
|
201
|
+
const result = context.db.writeTransaction(() => {
|
|
202
|
+
if (!context.db.isOwnedWriteTransaction?.())
|
|
203
|
+
refuse("owned_source_writer_required");
|
|
204
|
+
this.assertCustody(context);
|
|
205
|
+
this.#assertNoIntent(context);
|
|
206
|
+
const prior = retained(context, this.#baseline);
|
|
207
|
+
if (prior && hybridDigest(prior) !== expected)
|
|
208
|
+
refuse("registered_source_changed");
|
|
209
|
+
if (!prior)
|
|
210
|
+
appendAudit(context, { projectId: scope.project_id, eventType: event, payload: receipt });
|
|
211
|
+
if (hybridDigest(retained(context, this.#baseline)) !== expected)
|
|
212
|
+
refuse("source_readback_failed");
|
|
213
|
+
this.#assertRegisteredOrder(context);
|
|
214
|
+
return { work_id: scope.work_id, source_view_sha256: this.sha256, receipt_sha256: expected, replayed: Boolean(prior) };
|
|
215
|
+
});
|
|
216
|
+
this.#registered = true;
|
|
217
|
+
try {
|
|
218
|
+
this.revalidate(context);
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
throw new AppError("memory_repair_publication_source_pending", "Source view committed but custody changed before publication", 1, { effect: "committed", source_view_sha256: this.sha256, cause: error instanceof Error ? error.message : String(error) });
|
|
222
|
+
}
|
|
223
|
+
return result;
|
|
224
|
+
}
|
|
225
|
+
}
|