@deksden-com/dd-flow-cli 0.9.0-beta.144 → 0.9.0-beta.149
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 +130 -0
- package/README.md +13 -8
- package/dist/build-info.json +7 -7
- package/dist/cli/command-inputs.js +13 -3
- package/dist/cli/command-metadata.js +51 -16
- package/dist/cli/diagnostic-invocation.js +46 -0
- package/dist/cli/diagnostic.js +120 -0
- package/dist/cli/help.js +67 -1
- package/dist/cli/hook-ingress.js +27 -3
- package/dist/cli/memory-flow-admission.js +9 -3
- package/dist/cli/memory-init-invocation.js +10 -3
- package/dist/cli/memory-init.js +77 -68
- package/dist/cli/memory-storage-admission.js +3 -2
- package/dist/cli/memory-storage-qualification.js +105 -0
- package/dist/cli/memory-storage-transition.js +123 -0
- package/dist/cli/memory-upgrade.js +189 -0
- package/dist/cli/native-hook-ingress.js +14 -4
- package/dist/cli/native-hook-preparation.js +55 -0
- package/dist/cli/response-file.js +2 -1
- package/dist/cli/run-cli.js +79 -40
- package/dist/cli/setup.js +2 -1
- package/dist/cli.js +18 -0
- package/dist/domain/diagnostic-aspects.js +103 -0
- package/dist/domain/diagnostic-flows.js +8 -0
- package/dist/domain/stage-handler-identities.js +8 -0
- package/dist/harness-runtime/bin/dd-agy.mjs +3 -3
- package/dist/harness-runtime/bin/dd-grok.mjs +3 -3
- package/dist/harness-runtime/bin/dd-zcode.mjs +5 -5
- package/dist/harness-runtime/lib/agy-error-boundary.mjs +58 -0
- package/dist/harness-runtime/lib/agy-native-inspection.mjs +216 -0
- package/dist/harness-runtime/lib/agy-native_pb.js +117 -0
- package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +84 -10
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +366 -52
- package/dist/harness-runtime/lib/dd-agy.mjs +17 -3
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +27 -13
- package/dist/harness-runtime/lib/dd-codex.mjs +4 -0
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +4 -3
- package/dist/harness-runtime/lib/dd-droid.mjs +71 -29
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +73 -16
- package/dist/harness-runtime/lib/dd-grok.mjs +6 -3
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +66 -20
- package/dist/harness-runtime/lib/dd-opencode.mjs +30 -3
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +7 -6
- package/dist/harness-runtime/lib/dd-zcode.mjs +44 -14
- package/dist/harness-runtime/lib/delegation-instructions.mjs +1 -1
- package/dist/harness-runtime/lib/driver-recovery.mjs +9 -8
- package/dist/harness-runtime/lib/entity-references.d.mts +2 -0
- package/dist/harness-runtime/lib/entity-references.mjs +9 -0
- package/dist/harness-runtime/lib/managed-daemon.d.mts +1 -1
- package/dist/harness-runtime/lib/managed-daemon.mjs +24 -9
- package/dist/harness-runtime/lib/native-children.d.mts +8 -1
- package/dist/harness-runtime/lib/native-children.mjs +114 -8
- package/dist/harness-runtime/lib/native-hook-command.mjs +4 -4
- package/dist/harness-runtime/lib/native-request-proof.mjs +1 -1
- package/dist/harness-runtime/lib/observation-clock.d.mts +2 -2
- package/dist/harness-runtime/lib/observation-clock.mjs +7 -4
- package/dist/harness-runtime/lib/opencode-plugin.mjs +3 -3
- package/dist/harness-runtime/lib/operation-errors.mjs +1 -1
- package/dist/harness-runtime/lib/provider-overload-policy.d.mts +5 -3
- package/dist/harness-runtime/lib/provider-overload-policy.mjs +20 -7
- package/dist/harness-runtime/lib/runner-events.mjs +3 -1
- package/dist/harness-runtime/lib/setup-progress.mjs +8 -6
- package/dist/harness-runtime/lib/tool-observations.mjs +1 -1
- package/dist/schemas/diagnostic-stage-result.schema.json +354 -0
- package/dist/schemas/diagnostic-work-result.schema.json +296 -0
- package/dist/schemas/execution-bundle.schema.json +7 -1
- package/dist/schemas/mb-upgrade-stage-result.schema.json +863 -0
- package/dist/schemas/memory-repair-work-result.schema.json +9 -2
- package/dist/schemas/project-flow-pack-manifest.schema.json +1 -1
- package/dist/schemas/release-impact.schema.json +3 -3
- package/dist/schemas/stage-start-response.schema.json +4 -0
- package/dist/services/accepted-intent.js +13 -1
- package/dist/services/canon.js +43 -7
- package/dist/services/check-environment.js +21 -0
- package/dist/services/check-runner-evidence.js +267 -15
- package/dist/services/cli-operation-classifier.js +1 -1
- package/dist/services/cli-upgrade-allowlist.js +1 -1
- package/dist/services/code-checks.js +34 -19
- package/dist/services/compatibility-preflight.js +5 -2
- package/dist/services/controller-execution.js +10 -4
- package/dist/services/controller-fanout.js +34 -10
- package/dist/services/controller-memory-task.js +45 -0
- package/dist/services/controller-memory-worker.js +49 -0
- package/dist/services/dashboard.js +2 -2
- package/dist/services/descriptor-run-settlement.js +5 -3
- package/dist/services/descriptor-run-start.js +46 -20
- package/dist/services/descriptor-stage-coordinator.js +66 -13
- package/dist/services/descriptor-stage-settlement.js +2 -1
- package/dist/services/descriptor-stage-start.js +40 -4
- package/dist/services/diagnostic-evidence.js +73 -0
- package/dist/services/diagnostic-report-publication.js +72 -0
- package/dist/services/diagnostic-report.js +67 -0
- package/dist/services/diagnostic-run-bundle.js +74 -0
- package/dist/services/diagnostic-snapshot.js +159 -0
- package/dist/services/diagnostic-stage-handlers.js +108 -0
- package/dist/services/diagnostic-stage-results.js +132 -0
- package/dist/services/diagnostic-start.js +84 -0
- package/dist/services/diagnostic-work-batch.js +34 -0
- package/dist/services/diagnostic-work-collection.js +97 -0
- package/dist/services/diagnostic-work-registration.js +101 -0
- package/dist/services/diagnostic-work-results.js +93 -0
- package/dist/services/engines.js +64 -14
- package/dist/services/eval-snapshots.js +186 -119
- package/dist/services/execution-bundles.js +19 -7
- package/dist/services/external-work-launch.js +5 -3
- package/dist/services/flow-catalog.js +13 -4
- package/dist/services/frozen-engine.js +148 -11
- package/dist/services/hook-memory-routing.js +188 -0
- package/dist/services/hook-recovery-guard.js +14 -0
- package/dist/services/hook-session-observation.js +24 -0
- package/dist/services/hooks.js +27 -8
- package/dist/services/hybrid-contracts.js +4 -13
- package/dist/services/hybrid-digest.js +14 -0
- package/dist/services/installed-flow-source.js +47 -4
- package/dist/services/lifecycle-invocations.js +179 -38
- package/dist/services/memory-accepted-composition.js +5 -4
- package/dist/services/memory-completed-correction-wave.js +23 -15
- package/dist/services/memory-compose-handler.js +163 -21
- package/dist/services/memory-composition-acceptance.js +186 -10
- package/dist/services/memory-composition-graph.js +42 -11
- package/dist/services/memory-composition-registration.js +23 -8
- package/dist/services/memory-composition-work-custody.js +14 -9
- package/dist/services/memory-composition-work-results.js +15 -3
- package/dist/services/memory-corrected-review-batch.js +10 -3
- package/dist/services/memory-corrected-review-findings.js +4 -2
- package/dist/services/memory-corrected-review-inputs.js +10 -5
- package/dist/services/memory-corrected-review-receipt.js +17 -9
- package/dist/services/memory-corrected-review-registration.js +3 -2
- package/dist/services/memory-correction-check-receipt.js +20 -4
- package/dist/services/memory-custody-snapshot.js +36 -0
- package/dist/services/memory-finish-evidence.js +44 -22
- package/dist/services/memory-finish-handler.js +37 -23
- package/dist/services/memory-finish-integration.js +218 -48
- package/dist/services/memory-finish-readiness.js +66 -19
- package/dist/services/memory-git-integration.js +87 -18
- package/dist/services/memory-historical-corrected-review.js +21 -15
- package/dist/services/memory-historical-original-review.js +22 -9
- package/dist/services/memory-historical-repair-registration.js +14 -8
- package/dist/services/memory-historical-second-review.js +30 -11
- package/dist/services/memory-init-contexts.js +44 -2
- package/dist/services/memory-init-controller-start.js +27 -8
- package/dist/services/memory-init-git-policy.js +50 -21
- package/dist/services/memory-init-lint-capability.js +24 -6
- package/dist/services/memory-init-native-capacity.js +17 -4
- package/dist/services/memory-init-preflight.js +21 -17
- package/dist/services/memory-init-request-candidate.js +73 -17
- package/dist/services/memory-init-request-input.js +23 -7
- package/dist/services/memory-init-run-bundle.js +49 -12
- package/dist/services/memory-init-storage.js +17 -7
- package/dist/services/memory-init-worktrunk-capability.js +3 -7
- package/dist/services/memory-layer.js +71 -6
- package/dist/services/memory-lint-config.js +111 -2
- package/dist/services/memory-native-review-copy.js +5 -67
- package/dist/services/memory-normal-flow-entry.js +12 -0
- package/dist/services/memory-ordinary-defaults.js +22 -5
- package/dist/services/memory-preflight-handler.js +15 -0
- package/dist/services/memory-repair-current-view.js +1 -1
- package/dist/services/memory-repair-document-delta.js +70 -33
- package/dist/services/memory-repair-history.js +12 -7
- package/dist/services/memory-repair-physical-binding.js +1 -119
- package/dist/services/memory-repair-publication-plan.js +5 -3
- package/dist/services/memory-repair-publication-source.js +1 -1
- package/dist/services/memory-repair-publication.js +3 -2
- package/dist/services/memory-repair-registration.js +2 -1
- package/dist/services/memory-repair-work-baseline.js +4 -2
- package/dist/services/memory-repair-work-completion.js +32 -12
- package/dist/services/memory-repair-work-results.js +21 -2
- package/dist/services/memory-repair-workspace.js +11 -2
- package/dist/services/memory-repair-writer-copy.js +2 -1
- package/dist/services/memory-result-binding.js +78 -12
- package/dist/services/memory-retained-review-authority.js +17 -8
- package/dist/services/memory-retained-second-baseline.js +1 -1
- package/dist/services/memory-retained-second-repairs.js +12 -6
- package/dist/services/memory-review-correction-plan.js +47 -12
- package/dist/services/memory-review-handler.js +17 -17
- package/dist/services/memory-review-inputs.js +55 -9
- package/dist/services/memory-review-limits.js +10 -0
- package/dist/services/memory-review-receipt.js +2 -2
- package/dist/services/memory-review-registration.js +13 -4
- package/dist/services/memory-review-work-custody.js +3 -1
- package/dist/services/memory-review-work-input.js +26 -7
- package/dist/services/memory-review-work-results.js +26 -4
- package/dist/services/memory-second-corrected-review-inputs.js +5 -3
- package/dist/services/memory-second-correction-plan.js +2 -1
- package/dist/services/memory-second-repair-launch.js +4 -4
- package/dist/services/memory-second-repair-source.js +1 -1
- package/dist/services/memory-stage-dispatch.js +38 -1
- package/dist/services/memory-stage-results.js +113 -22
- package/dist/services/memory-tool-probe.js +25 -0
- package/dist/services/memory-upgrade-analysis-handler.js +243 -0
- package/dist/services/memory-upgrade-bootstrap-readiness.js +251 -0
- package/dist/services/memory-upgrade-evidence.js +100 -0
- package/dist/services/memory-upgrade-inventory.js +442 -0
- package/dist/services/memory-upgrade-publication-custody.js +73 -0
- package/dist/services/memory-upgrade-publication.js +413 -0
- package/dist/services/memory-upgrade-selection.js +430 -0
- package/dist/services/memory-upgrade-start.js +60 -0
- package/dist/services/memory-upgrade-storage-readiness.js +340 -0
- package/dist/services/memory-verification-acceptance.js +38 -17
- package/dist/services/memory-verification-checks.js +92 -27
- package/dist/services/memory-verification-receipt.js +6 -5
- package/dist/services/memory-verify-handler.js +15 -13
- package/dist/services/memory-work-result-json.js +12 -0
- package/dist/services/memory-work-result-preparation.js +5 -4
- package/dist/services/merge-server.js +4 -2
- package/dist/services/migrations.js +127 -98
- package/dist/services/native-daemon-history.js +2 -1
- package/dist/services/physical-startup.js +45 -0
- package/dist/services/project-registry.js +97 -0
- package/dist/services/project-summary.js +1 -1
- package/dist/services/projects.js +3 -93
- package/dist/services/prompt-delivery.js +71 -0
- package/dist/services/provider-capacity.js +112 -46
- package/dist/services/recovery-snapshot-database.js +32 -4
- package/dist/services/release-cache.js +8 -0
- package/dist/services/repair-intents.js +1 -12
- package/dist/services/repair-receipt.js +13 -0
- package/dist/services/run-control-worker.js +35 -18
- package/dist/services/run-control.js +2 -1
- package/dist/services/run-controller-adapter.js +2 -1
- package/dist/services/run-controller-process.js +4 -7
- package/dist/services/run-controller-recovery.js +8 -2
- package/dist/services/run-controller.js +215 -94
- package/dist/services/run-fork.js +1 -1
- package/dist/services/run-recovery-guard.js +30 -0
- package/dist/services/run-recovery.js +11 -44
- package/dist/services/run-start-custody.js +17 -5
- package/dist/services/runs.js +6 -1
- package/dist/services/runtime-scope-resume.js +12 -6
- package/dist/services/runtime-scope-stop.js +3 -2
- package/dist/services/runtime-scope-worker.js +46 -19
- package/dist/services/runtime-service.js +2 -1
- package/dist/services/sessions.js +3 -24
- package/dist/services/setup.js +63 -13
- package/dist/services/stage-handlers.js +22 -7
- package/dist/services/stage-pause.js +111 -44
- package/dist/services/version-status.js +1 -1
- package/dist/services/vnext-code.js +2 -2
- package/dist/services/vnext-fanout.js +51 -9
- package/dist/services/vnext-plan-review.js +3 -5
- package/dist/services/work-registry.js +117 -39
- package/dist/services/workspace-bootstrap.js +2 -1
- package/dist/services/workspace-files.js +1 -1
- package/dist/shared/entity-references.js +1 -8
- package/dist/storage/data-preservation.js +65 -0
- package/dist/storage/database.js +53 -19
- package/dist/storage/import-guard.js +84 -27
- package/dist/storage/memory-upgrade-schema-bootstrap.js +38 -0
- package/dist/storage/paired-writer-migration.js +424 -0
- package/dist/storage/writer-contract.js +11 -4
- package/dist/storage/writer-migration.js +313 -52
- package/dist/storage/writer-restore.js +217 -0
- package/package.json +13 -6
|
@@ -32,12 +32,16 @@ function inventory(root) {
|
|
|
32
32
|
if (depth > 64 || entries.length >= 20_000 || logical.length > 4096)
|
|
33
33
|
refuse("unbounded_document_inventory");
|
|
34
34
|
const file = path.join(root, logical), stat = fs.lstatSync(file);
|
|
35
|
-
if (stat.isSymbolicLink()
|
|
35
|
+
if (!stat.isSymbolicLink() && !stat.isDirectory() && !stat.isFile() || stat.isFile() && stat.nlink !== 1)
|
|
36
36
|
refuse("linked_or_special_document");
|
|
37
37
|
if (aliases.has(alias(logical)))
|
|
38
38
|
refuse("aliased_document_inventory");
|
|
39
39
|
aliases.add(alias(logical));
|
|
40
|
-
|
|
40
|
+
const link = stat.isSymbolicLink() ? fs.readlinkSync(file, { encoding: "buffer" }) : null;
|
|
41
|
+
if (link && link.length > 64 * 1024)
|
|
42
|
+
refuse("unbounded_document_inventory");
|
|
43
|
+
entries.push({ path: logical, kind: stat.isSymbolicLink() ? "link" : stat.isDirectory() ? "directory" : "file", mode: stat.mode & 0o7777,
|
|
44
|
+
sha256: link ? createHash("sha256").update(link).digest("hex") : null });
|
|
41
45
|
if (stat.isDirectory())
|
|
42
46
|
for (const name of fs.readdirSync(file).sort()) {
|
|
43
47
|
if (!name || name.includes("\\") || name.includes("\0") || name === "." || name === "..")
|
|
@@ -83,7 +87,7 @@ function capture(root) {
|
|
|
83
87
|
if (hybridDigest(inventory(root)) !== hybridDigest(initial))
|
|
84
88
|
refuse("inventory_changed_during_capture");
|
|
85
89
|
const hashes = new Map([...observed.hashes(), ...protectedBefore]), entries = initial.map(entry => ({ ...entry,
|
|
86
|
-
sha256: entry.kind === "file" ? hashes.get(memorySourceKey("workspace", entry.path)) :
|
|
90
|
+
sha256: entry.kind === "file" ? hashes.get(memorySourceKey("workspace", entry.path)) : entry.sha256 }));
|
|
87
91
|
return { entries, observed, sha256: hybridDigest(entries) };
|
|
88
92
|
}
|
|
89
93
|
catch (error) {
|
|
@@ -92,6 +96,15 @@ function capture(root) {
|
|
|
92
96
|
return refuse("documents_unavailable_or_changed");
|
|
93
97
|
}
|
|
94
98
|
}
|
|
99
|
+
/** Reuse the protected inventory without inventing repair write permissions.
|
|
100
|
+
* Private files and symlinks remain opaque hashes, never exported bytes. */
|
|
101
|
+
export function observeMemoryDocumentInventory(workspace) {
|
|
102
|
+
const snapshot = capture(workspace);
|
|
103
|
+
return { entries: () => structuredClone(snapshot.entries), revalidate: () => {
|
|
104
|
+
if (hybridDigest(capture(workspace).entries) !== hybridDigest(snapshot.entries))
|
|
105
|
+
refuse("documents_changed_after_baseline");
|
|
106
|
+
} };
|
|
107
|
+
}
|
|
95
108
|
/** Physical MB-only observation, not provider/Session authority or permission to
|
|
96
109
|
* write the shared workspace. A registered owner must bind this start baseline,
|
|
97
110
|
* inspect the rest of its private workspace and journal bounded publication.
|
|
@@ -101,47 +114,60 @@ export class MemoryRepairDocumentBaseline {
|
|
|
101
114
|
sha256;
|
|
102
115
|
#entries;
|
|
103
116
|
#paths;
|
|
104
|
-
|
|
117
|
+
#canonical;
|
|
118
|
+
#material(entries = this.#entries, paths = this.#paths) {
|
|
119
|
+
return { entries, write_paths: paths, ...(this.#canonical.length ? { canonical_write_paths: this.#canonical } : {}) };
|
|
120
|
+
}
|
|
121
|
+
constructor(entries, paths, canonical = []) {
|
|
105
122
|
this.#entries = structuredClone(entries);
|
|
106
123
|
this.#paths = [...paths];
|
|
107
|
-
this
|
|
124
|
+
this.#canonical = [...canonical].sort();
|
|
125
|
+
this.sha256 = hybridDigest(this.#material());
|
|
108
126
|
Object.freeze(this);
|
|
109
127
|
}
|
|
110
|
-
static capture(workspace, writePaths) {
|
|
111
|
-
assertMemoryRepairWritePaths(writePaths);
|
|
128
|
+
static capture(workspace, writePaths, canonicalPaths = new Set()) {
|
|
129
|
+
assertMemoryRepairWritePaths(writePaths, canonicalPaths);
|
|
112
130
|
const snapshot = capture(workspace), byPath = new Map(snapshot.entries.map(entry => [entry.path, entry]));
|
|
131
|
+
const aliases = new Map(snapshot.entries.map(entry => [alias(entry.path), entry.path]));
|
|
113
132
|
for (const logical of writePaths) {
|
|
114
133
|
if (byPath.get(logical)?.kind === "directory")
|
|
115
134
|
refuse("write_path_is_directory");
|
|
116
|
-
if (
|
|
135
|
+
if (byPath.get(logical)?.kind === "link")
|
|
136
|
+
refuse("write_path_is_link");
|
|
137
|
+
const existingAlias = aliases.get(alias(logical));
|
|
138
|
+
if (existingAlias !== undefined && existingAlias !== logical)
|
|
117
139
|
refuse("aliased_write_path");
|
|
118
140
|
const parts = logical.split("/");
|
|
119
141
|
for (let index = 1; index < parts.length; index++) {
|
|
120
142
|
const parent = parts.slice(0, index).join("/");
|
|
121
|
-
|
|
143
|
+
const parentAlias = aliases.get(alias(parent));
|
|
144
|
+
if (byPath.has(parent) && byPath.get(parent)?.kind !== "directory" || parentAlias !== undefined && parentAlias !== parent)
|
|
122
145
|
refuse("invalid_write_parent");
|
|
123
146
|
}
|
|
124
147
|
}
|
|
125
|
-
|
|
148
|
+
const prepared = new MemoryRepairDocumentBaseline(snapshot.entries, writePaths, [...canonicalPaths]);
|
|
149
|
+
return MemoryRepairDocumentBaseline.restore(prepared.bytes(), prepared.sha256);
|
|
126
150
|
}
|
|
127
151
|
entries() { return structuredClone(this.#entries); }
|
|
152
|
+
canonicalWritePaths() { return new Set(this.#canonical); }
|
|
128
153
|
/** Adapt an independently accepted document view to the next registered
|
|
129
154
|
* Work's approved paths without re-capturing mutable workspace bytes.
|
|
130
155
|
* The caller still owns graph/current-view authority; this grants none.
|
|
131
156
|
*/
|
|
132
157
|
withWritePaths(writePaths) {
|
|
133
|
-
assertMemoryRepairWritePaths(writePaths);
|
|
134
|
-
const
|
|
135
|
-
return MemoryRepairDocumentBaseline.restore(Buffer.from(JSON.stringify(
|
|
158
|
+
assertMemoryRepairWritePaths(writePaths, new Set(this.#canonical));
|
|
159
|
+
const material = this.#material(this.entries(), [...writePaths]);
|
|
160
|
+
return MemoryRepairDocumentBaseline.restore(Buffer.from(JSON.stringify({ schema_id: "dd-flow/memory-repair-document-baseline@1", ...material })), hybridDigest(material));
|
|
136
161
|
}
|
|
137
162
|
/** Metadata composition only, never admission or filesystem publication.
|
|
138
163
|
* A caller must independently prove both retained views belong to a settled
|
|
139
164
|
* repair. Disjoint concurrent deltas preserve the current inventory; file
|
|
140
165
|
* conflicts never rebase. Identical new parent directories may be shared.
|
|
141
166
|
*/
|
|
142
|
-
composeSettledDelta(before, after) {
|
|
167
|
+
composeSettledDelta(before, after, outcome = "accepted") {
|
|
143
168
|
if (!(before instanceof MemoryRepairDocumentBaseline) || !(after instanceof MemoryRepairDocumentBaseline)
|
|
144
|
-
||
|
|
169
|
+
|| !["accepted", "reconciled"].includes(outcome)
|
|
170
|
+
|| hybridDigest(before.#paths) !== hybridDigest(after.#paths) || hybridDigest(before.#canonical) !== hybridDigest(after.#canonical))
|
|
145
171
|
refuse("invalid_settled_document_views");
|
|
146
172
|
const prior = new Map(before.#entries.map(entry => [entry.path, entry]));
|
|
147
173
|
const final = new Map(after.#entries.map(entry => [entry.path, entry]));
|
|
@@ -160,7 +186,7 @@ export class MemoryRepairDocumentBaseline {
|
|
|
160
186
|
refuse("settled_document_conflict");
|
|
161
187
|
}
|
|
162
188
|
else {
|
|
163
|
-
if (!before.#paths.includes(logical) || old
|
|
189
|
+
if (!before.#paths.includes(logical) || old && old.kind !== "file" || next && next.kind !== "file"
|
|
164
190
|
|| next && (old ? next.mode !== old.mode : ![0o600, 0o644].includes(next.mode))
|
|
165
191
|
|| old && next && old.sha256 === next.sha256)
|
|
166
192
|
refuse("invalid_settled_file_delta");
|
|
@@ -173,10 +199,10 @@ export class MemoryRepairDocumentBaseline {
|
|
|
173
199
|
else
|
|
174
200
|
current.delete(logical);
|
|
175
201
|
}
|
|
176
|
-
if (
|
|
177
|
-
refuse("empty_settled_document_delta");
|
|
178
|
-
const
|
|
179
|
-
return MemoryRepairDocumentBaseline.restore(Buffer.from(JSON.stringify(
|
|
202
|
+
if (outcome === "reconciled" ? changedFiles !== 0 : changedFiles === 0)
|
|
203
|
+
refuse(outcome === "reconciled" ? "nonempty_reconciled_document_delta" : "empty_settled_document_delta");
|
|
204
|
+
const material = this.#material(ordered(current.values()));
|
|
205
|
+
return MemoryRepairDocumentBaseline.restore(Buffer.from(JSON.stringify({ schema_id: "dd-flow/memory-repair-document-baseline@1", ...material })), hybridDigest(material));
|
|
180
206
|
}
|
|
181
207
|
/** Project only the independently selected regular files into a sanitized
|
|
182
208
|
* writer view. The protected source snapshot stays intact. This is metadata
|
|
@@ -214,12 +240,12 @@ export class MemoryRepairDocumentBaseline {
|
|
|
214
240
|
refuse("write_target_not_exported");
|
|
215
241
|
parents(logical);
|
|
216
242
|
}
|
|
217
|
-
const exported = new MemoryRepairDocumentBaseline(this.#entries.filter(entry => retained.has(entry.path)), this.#paths);
|
|
243
|
+
const exported = new MemoryRepairDocumentBaseline(this.#entries.filter(entry => retained.has(entry.path)), this.#paths, this.#canonical);
|
|
218
244
|
// Apply the same bounded, strict representation used by SQL restoration.
|
|
219
245
|
return MemoryRepairDocumentBaseline.restore(exported.bytes(), exported.sha256);
|
|
220
246
|
}
|
|
221
247
|
bytes() {
|
|
222
|
-
const bytes = Buffer.from(JSON.stringify({ schema_id: "dd-flow/memory-repair-document-baseline@1",
|
|
248
|
+
const bytes = Buffer.from(JSON.stringify({ schema_id: "dd-flow/memory-repair-document-baseline@1", ...this.#material() }));
|
|
223
249
|
if (bytes.length > 8 * 1024 * 1024)
|
|
224
250
|
refuse("unbounded_baseline_receipt");
|
|
225
251
|
return bytes;
|
|
@@ -233,39 +259,48 @@ export class MemoryRepairDocumentBaseline {
|
|
|
233
259
|
|| typeof expectedSha256 !== "string" || !/^[a-f0-9]{64}$/.test(expectedSha256))
|
|
234
260
|
refuse("invalid_baseline_receipt");
|
|
235
261
|
const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
236
|
-
if (!value || Object.keys(value).sort().join(",") !== "entries,schema_id,write_paths"
|
|
262
|
+
if (!value || Object.keys(value).filter(key => key !== "canonical_write_paths").sort().join(",") !== "entries,schema_id,write_paths"
|
|
237
263
|
|| value.schema_id !== "dd-flow/memory-repair-document-baseline@1" || !Array.isArray(value.entries)
|
|
238
264
|
|| !value.entries.length || value.entries.length > 20_000)
|
|
239
265
|
refuse("invalid_baseline_receipt");
|
|
240
|
-
|
|
241
|
-
|
|
266
|
+
const canonical = value.canonical_write_paths ?? [];
|
|
267
|
+
if (!Array.isArray(canonical) || canonical.length > 20_000 || new Set(canonical).size !== canonical.length
|
|
268
|
+
|| canonical.some(logical => typeof logical !== "string" || ![".memory-bank/dd-flow/", ".memory-bank/mbb/"].some(prefix => logical.startsWith(prefix))))
|
|
269
|
+
refuse("invalid_baseline_receipt");
|
|
270
|
+
const permitted = new Set(canonical);
|
|
271
|
+
for (const logical of canonical)
|
|
272
|
+
assertMemoryRepairWritePaths([logical], permitted);
|
|
273
|
+
assertMemoryRepairWritePaths(value.write_paths, permitted);
|
|
274
|
+
const byPath = new Map(), aliases = new Map();
|
|
242
275
|
for (const entry of value.entries) {
|
|
243
276
|
if (!entry || Object.keys(entry).sort().join(",") !== "kind,mode,path,sha256"
|
|
244
277
|
|| typeof entry.path !== "string" || entry.path.length > 4096 || entry.path.split("/").length > 65
|
|
245
278
|
|| entry.path !== ".memory-bank" && !entry.path.startsWith(".memory-bank/")
|
|
246
279
|
|| entry.path.includes("\\") || entry.path.includes("\0") || entry.path.split("/").some(part => !part || part === "." || part === "..")
|
|
247
|
-
|| !["file", "directory"].includes(entry.kind) || !Number.isInteger(entry.mode) || entry.mode < 0 || entry.mode > 0o7777
|
|
280
|
+
|| !["file", "directory", "link"].includes(entry.kind) || !Number.isInteger(entry.mode) || entry.mode < 0 || entry.mode > 0o7777
|
|
248
281
|
|| entry.kind === "directory" && entry.sha256 !== null
|
|
249
|
-
|| entry.kind
|
|
282
|
+
|| entry.kind !== "directory" && (typeof entry.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(entry.sha256))
|
|
250
283
|
|| aliases.has(alias(entry.path)))
|
|
251
284
|
refuse("invalid_baseline_receipt");
|
|
252
285
|
if (entry.path === ".memory-bank" ? byPath.size !== 0 || entry.kind !== "directory"
|
|
253
286
|
: byPath.get(path.posix.dirname(entry.path))?.kind !== "directory")
|
|
254
287
|
refuse("invalid_baseline_receipt");
|
|
255
|
-
aliases.
|
|
288
|
+
aliases.set(alias(entry.path), entry.path);
|
|
256
289
|
byPath.set(entry.path, entry);
|
|
257
290
|
}
|
|
258
291
|
for (const logical of value.write_paths) {
|
|
259
|
-
|
|
292
|
+
const existingAlias = aliases.get(alias(logical));
|
|
293
|
+
if (byPath.has(logical) && byPath.get(logical)?.kind !== "file" || existingAlias !== undefined && existingAlias !== logical)
|
|
260
294
|
refuse("invalid_baseline_receipt");
|
|
261
295
|
const parts = logical.split("/");
|
|
262
296
|
for (let index = 1; index < parts.length; index++) {
|
|
263
297
|
const parent = parts.slice(0, index).join("/");
|
|
264
|
-
|
|
298
|
+
const parentAlias = aliases.get(alias(parent));
|
|
299
|
+
if (byPath.has(parent) && byPath.get(parent)?.kind !== "directory" || parentAlias !== undefined && parentAlias !== parent)
|
|
265
300
|
refuse("invalid_baseline_receipt");
|
|
266
301
|
}
|
|
267
302
|
}
|
|
268
|
-
const prepared = new MemoryRepairDocumentBaseline(value.entries, value.write_paths);
|
|
303
|
+
const prepared = new MemoryRepairDocumentBaseline(value.entries, value.write_paths, canonical);
|
|
269
304
|
if (prepared.sha256 !== expectedSha256 || !prepared.bytes().equals(Buffer.from(bytes)))
|
|
270
305
|
refuse("baseline_receipt_changed");
|
|
271
306
|
return prepared;
|
|
@@ -344,6 +379,8 @@ export class MemoryRepairDocumentBaseline {
|
|
|
344
379
|
}
|
|
345
380
|
if (!this.#paths.includes(logical))
|
|
346
381
|
refuse("undeclared_document_delta");
|
|
382
|
+
if (old?.kind === "link" || current?.kind === "link")
|
|
383
|
+
refuse("link_document_delta");
|
|
347
384
|
if (old && current && old.mode !== current.mode || !old && current && ![0o600, 0o644].includes(current.mode))
|
|
348
385
|
refuse("document_mode_delta");
|
|
349
386
|
if (old?.sha256 === current?.sha256)
|
|
@@ -354,7 +391,7 @@ export class MemoryRepairDocumentBaseline {
|
|
|
354
391
|
changes.sort((a, b) => a.path.localeCompare(b.path, "en"));
|
|
355
392
|
if (hybridDigest(changes) !== hybridDigest(proposed))
|
|
356
393
|
refuse("claimed_delta_differs_from_bytes");
|
|
357
|
-
if (result.outcome
|
|
394
|
+
if (["blocked", "reconciled"].includes(result.outcome) && changed.length || result.outcome === "accepted" && !changes.length)
|
|
358
395
|
refuse("outcome_differs_from_physical_delta");
|
|
359
396
|
return new ObservedMemoryRepairDelta(observationToken, this.sha256, workspace, snapshot.sha256, changes, snapshot.observed, snapshot.entries);
|
|
360
397
|
}
|
|
@@ -423,7 +460,7 @@ export class MemoryRepairDocumentTransition {
|
|
|
423
460
|
}
|
|
424
461
|
const retained = JSON.parse(this.#before.bytes().toString("utf8"));
|
|
425
462
|
const value = { ...retained, entries: ordered(entries.values()) };
|
|
426
|
-
const expected = hybridDigest({
|
|
463
|
+
const expected = hybridDigest({ ...value, schema_id: undefined });
|
|
427
464
|
return MemoryRepairDocumentBaseline.restore(Buffer.from(JSON.stringify(value)), expected);
|
|
428
465
|
}
|
|
429
466
|
after() { return this.state(this.#actions.length); }
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { readMemoryRepairPhysicalBinding } from "./memory-repair-physical-binding.js";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
|
+
import { assertMemorySqlCustody } from "./memory-custody-snapshot.js";
|
|
4
5
|
import { AppError } from "../shared/errors.js";
|
|
5
6
|
import { hybridDigest } from "./hybrid-contracts.js";
|
|
6
7
|
import { PreparedRegisteredMemoryCorrections, memoryRepairWorkerSchema } from "./memory-repair-registration.js";
|
|
@@ -12,7 +13,7 @@ import { RetainedMemoryRepairPublicationPlan } from "./memory-repair-publication
|
|
|
12
13
|
import { deriveRetainedMemoryRepairViews } from "./memory-repair-publication.js";
|
|
13
14
|
import { retainedMemoryRepairPublicationPlans } from "./memory-repair-publication-intent.js";
|
|
14
15
|
import { PreparedMemoryRepairWorkspace } from "./memory-repair-workspace.js";
|
|
15
|
-
import { memoryRepairCurrentReferences, validateMemoryRepairWorkResult } from "./memory-repair-work-results.js";
|
|
16
|
+
import { isCompletedMemoryRepairClaim, memoryRepairCurrentReferences, validateMemoryRepairWorkResult } from "./memory-repair-work-results.js";
|
|
16
17
|
import { readMemoryRepairCurrentViewReceipt } from "./memory-repair-current-view.js";
|
|
17
18
|
import { runtimeHarness } from "./execution-policy.js";
|
|
18
19
|
import { ObservedMemorySources } from "./memory-source-observations.js";
|
|
@@ -190,7 +191,7 @@ function snapshot(context, registration, workId, validateReferencedViews = true)
|
|
|
190
191
|
const after = MemoryRepairDocumentBaseline.restore(bytes(peer.intent.after_base64, 8 * 1024 * 1024), peer.intent.after_sha256);
|
|
191
192
|
if (!prefixes.has(hybridDigest(before.entries())))
|
|
192
193
|
refuse("historical_second_current_view_prefix_changed");
|
|
193
|
-
composed = composed.composeSettledDelta(before, after);
|
|
194
|
+
composed = composed.composeSettledDelta(before, after, JSON.parse(peer.work.result).outcome);
|
|
194
195
|
prefixes.add(hybridDigest(composed.entries()));
|
|
195
196
|
const peerInputs = JSON.parse(bytes(peer.build.workspace_manifest_base64, 8 * 1024 * 1024).toString("utf8")).entries
|
|
196
197
|
.filter((entry) => entry.path !== ".memory-bank" && !entry.path.startsWith(".memory-bank/"));
|
|
@@ -332,7 +333,7 @@ function snapshot(context, registration, workId, validateReferencedViews = true)
|
|
|
332
333
|
}
|
|
333
334
|
if (!versions.has(hybridDigest(peerBefore.entries())))
|
|
334
335
|
refuse("historical_publication_source_prefix_changed");
|
|
335
|
-
composed = composed.composeSettledDelta(peerBefore, peerAfter);
|
|
336
|
+
composed = composed.composeSettledDelta(peerBefore, peerAfter, JSON.parse(peer.work.result).outcome);
|
|
336
337
|
versions.add(hybridDigest(composed.entries()));
|
|
337
338
|
const peerInputs = JSON.parse(bytes(peer.build.workspace_manifest_base64, 8 * 1024 * 1024).toString("utf8")).entries.filter((entry) => entry.path !== ".memory-bank" && !entry.path.startsWith(".memory-bank/"));
|
|
338
339
|
if (hybridDigest(peerInputs) !== immutableInputs)
|
|
@@ -407,6 +408,7 @@ function viewInput(registration) {
|
|
|
407
408
|
* and compare the physical shared inventory before admitting new work.
|
|
408
409
|
*/
|
|
409
410
|
export class PreparedSettledMemoryRepair {
|
|
411
|
+
#custodyKey = {};
|
|
410
412
|
sha256;
|
|
411
413
|
workId;
|
|
412
414
|
#registration;
|
|
@@ -448,7 +450,7 @@ export class PreparedSettledMemoryRepair {
|
|
|
448
450
|
const result = validateMemoryRepairWorkResult(Buffer.from(value.work.result), historyBundle(registration).engine(), { workId, packet: value.assigned.payload.memory_repair,
|
|
449
451
|
...(value.currentView || PreparedRetainedSecondMemoryRepairs.isPrepared(registration)
|
|
450
452
|
? { effectiveInputs: memoryRepairCurrentReferences(value.assigned.payload.memory_repair, workspace) } : {}) });
|
|
451
|
-
if (result
|
|
453
|
+
if (!isCompletedMemoryRepairClaim(result))
|
|
452
454
|
refuse("accepted_settled_repair_required");
|
|
453
455
|
const { plan, privatePlan } = retainedMemoryRepairPublicationPlans(value.intent);
|
|
454
456
|
const derived = deriveRetainedMemoryRepairViews(before, exported, plan, result, privatePlan);
|
|
@@ -478,14 +480,17 @@ export class PreparedSettledMemoryRepair {
|
|
|
478
480
|
if (context.db.isOwnedWriteTransaction?.())
|
|
479
481
|
refuse("historical_preparation_outside_writer_required");
|
|
480
482
|
this.revalidate(context);
|
|
481
|
-
const composed = current.composeSettledDelta(this.#before, this.#after);
|
|
483
|
+
const composed = current.composeSettledDelta(this.#before, this.#after, this.#result.outcome);
|
|
482
484
|
this.assertCustody(context);
|
|
483
485
|
return composed;
|
|
484
486
|
}
|
|
485
487
|
reference() { return { ...this.#reference }; }
|
|
486
488
|
assertCustody(context) {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
+
const registration = this.#registration;
|
|
490
|
+
assertMemorySqlCustody(context, this.#custodyKey, () => {
|
|
491
|
+
if (hybridDigest(snapshot(context, registration, this.workId)) !== this.sha256)
|
|
492
|
+
refuse("historical_custody_changed");
|
|
493
|
+
});
|
|
489
494
|
}
|
|
490
495
|
revalidate(context) { this.assertCustody(context); this.#observed.revalidate(); }
|
|
491
496
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import fs from "node:fs";
|
|
3
2
|
import { createHash } from "node:crypto";
|
|
4
3
|
import { AppError } from "../shared/errors.js";
|
|
5
4
|
import { appendAudit } from "./audit.js";
|
|
@@ -8,7 +7,6 @@ import { hybridDigest } from "./hybrid-contracts.js";
|
|
|
8
7
|
import { runtimeHarness } from "./execution-policy.js";
|
|
9
8
|
import { parseLifecycleCommand, commandPosition } from "./lifecycle-command.js";
|
|
10
9
|
import { resolveWorkReference } from "../storage/work-references.js";
|
|
11
|
-
import { assertRunMutationAllowed } from "./run-recovery-guard.js";
|
|
12
10
|
import { currentLifecycleAdmission } from "./lifecycle-invocations.js";
|
|
13
11
|
import { recoveryGuard } from "./run-recovery-guard.js";
|
|
14
12
|
const event = "memory.repair_native_binding";
|
|
@@ -53,123 +51,7 @@ export function memoryRepairExecutionIdentity(context, input) {
|
|
|
53
51
|
return refuse("repair_execution_cwd_unproven");
|
|
54
52
|
}
|
|
55
53
|
}
|
|
56
|
-
|
|
57
|
-
* establishes a command's assigned directory, not claim/repair authority. */
|
|
58
|
-
export function memoryRepairAssignedHookCwd(context, input) {
|
|
59
|
-
const work = context.db.get("SELECT payload_json,result_schema,status FROM works WHERE project_id=? AND run_id=? AND work_id=?", [input.projectId, input.runId, input.workId]);
|
|
60
|
-
let payload;
|
|
61
|
-
try {
|
|
62
|
-
payload = JSON.parse(work?.payload_json ?? "null");
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
if (payload?.kind !== "memory-repair" && work?.result_schema !== "dd-flow/memory-repair-work-result@1")
|
|
68
|
-
return null;
|
|
69
|
-
const run = context.db.get("SELECT r.run_root,r.run_dir,r.flow_kind,p.root AS project_root FROM runs r JOIN projects p ON p.id=r.project_id WHERE r.project_id=? AND r.id=?", [input.projectId, input.runId]);
|
|
70
|
-
if (!run || run.flow_kind !== "mb-init" || run.project_root !== input.projectRoot || payload.read_only !== false
|
|
71
|
-
|| ![1, 2].includes(payload.memory_repair?.wave ?? 0) || work?.result_schema !== "dd-flow/memory-repair-work-result@1")
|
|
72
|
-
refuse("registered_memory_repair_required");
|
|
73
|
-
const generation = assertRunMutationAllowed(context, input.projectId, input.runId), runRoot = run.run_root ?? run.run_dir;
|
|
74
|
-
const external = context.db.get("SELECT 1 FROM sqlite_schema WHERE type='table' AND name='work_launches'")
|
|
75
|
-
? context.db.get("SELECT operation_id,status,profile_json,profile_hash,provider_session_id FROM work_launches WHERE work_id=?", [input.workId]) : undefined;
|
|
76
|
-
const native = !external;
|
|
77
|
-
const admitted = (root) => {
|
|
78
|
-
if (native && input.observedCwd !== root && input.literalExecutionCwd !== root)
|
|
79
|
-
throw new AppError("memory_repair_native_copy_route_unavailable", "Native repair requires a genuinely observed child execution directory inside its owned writer copy", 1, { phase: "prepare", effect: "no_effect", recoverable: false, expected: root, actual: input.observedCwd ?? null,
|
|
80
|
-
next_action: "Use only a documented native child/tool capability that establishes this exact directory; otherwise stop and select external delegation in a new RUN." });
|
|
81
|
-
return { root, native };
|
|
82
|
-
};
|
|
83
|
-
if (work.status === "created") {
|
|
84
|
-
// Routing consumes registered copy custody only. Actual Work start owns
|
|
85
|
-
// physical source/copy/profile admission after the durable native Hook.
|
|
86
|
-
const rows = (eventType) => 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", [input.projectId, eventType, input.runId, input.workId, generation]);
|
|
87
|
-
const intents = rows("memory.repair_writer_copy_intent"), copies = rows("memory.repair_writer_copy_ready");
|
|
88
|
-
if (intents.length !== 1 || copies.length !== 1 || Buffer.byteLength(intents[0].payload_json) > 1024 * 1024
|
|
89
|
-
|| Buffer.byteLength(copies[0].payload_json) > 12 * 1024 * 1024)
|
|
90
|
-
refuse("owned_ready_copy_required");
|
|
91
|
-
try {
|
|
92
|
-
const intent = JSON.parse(intents[0].payload_json), ready = JSON.parse(copies[0].payload_json);
|
|
93
|
-
const workspace_manifest_sha256 = ready.workspace_manifest_sha256, readyIntent = { ...ready };
|
|
94
|
-
delete readyIntent.workspace_manifest_sha256;
|
|
95
|
-
delete readyIntent.workspace_manifest_base64;
|
|
96
|
-
const relative = `works/${input.workId}/writer-workspaces/generation-${generation}`;
|
|
97
|
-
if (intent.schema_id !== "dd-flow/memory-repair-writer-copy-intent@1" || intent.project_id !== input.projectId || intent.run_id !== input.runId
|
|
98
|
-
|| intent.work_id !== input.workId || intent.generation !== generation || intent.run_root !== runRoot || !path.isAbsolute(runRoot)
|
|
99
|
-
|| intent.copy_relative !== relative || !/^[a-f0-9]{64}$/.test(intent.baseline_registration_sha256)
|
|
100
|
-
|| !/^[a-f0-9]{64}$/.test(workspace_manifest_sha256) || hybridDigest(readyIntent) !== hybridDigest(intent))
|
|
101
|
-
refuse("owned_ready_copy_required");
|
|
102
|
-
if (external) {
|
|
103
|
-
if (external.provider_session_id !== input.providerSessionId)
|
|
104
|
-
refuse("foreign_physical_session");
|
|
105
|
-
const profile = JSON.parse(external.profile_json), metadata = profile.memory_repair_copy;
|
|
106
|
-
if (profile.delegation?.mode !== "external" || hybridDigest(metadata) !== hybridDigest({ root: relative, copy_intent_sha256: hybridDigest(intent),
|
|
107
|
-
workspace_manifest_sha256, baseline_registration_sha256: intent.baseline_registration_sha256 }))
|
|
108
|
-
refuse("native_copy_cwd_mismatch");
|
|
109
|
-
}
|
|
110
|
-
if (fs.realpathSync(runRoot) !== path.resolve(runRoot))
|
|
111
|
-
refuse("owned_ready_copy_required");
|
|
112
|
-
let destination = runRoot;
|
|
113
|
-
for (const part of relative.split('/')) {
|
|
114
|
-
destination = path.join(destination, part);
|
|
115
|
-
if (!fs.lstatSync(destination).isDirectory())
|
|
116
|
-
refuse("owned_ready_copy_required");
|
|
117
|
-
}
|
|
118
|
-
return admitted(destination);
|
|
119
|
-
}
|
|
120
|
-
catch (error) {
|
|
121
|
-
if (error instanceof AppError)
|
|
122
|
-
throw error;
|
|
123
|
-
return refuse("owned_ready_copy_required");
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
// Running writers retain their committed directory, not today's source bytes.
|
|
127
|
-
let binding = external;
|
|
128
|
-
let nativeStart;
|
|
129
|
-
if (!binding) {
|
|
130
|
-
const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='memory.repair_native_binding' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.work_id')=? LIMIT 2", [input.projectId, input.runId, input.workId]);
|
|
131
|
-
if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 1024 * 1024)
|
|
132
|
-
refuse("native_registered_start_changed");
|
|
133
|
-
try {
|
|
134
|
-
const retained = JSON.parse(rows[0].payload_json);
|
|
135
|
-
if (retained.schema_id !== "dd-flow/memory-repair-native-binding@1" || retained.project_id !== input.projectId || retained.run_id !== input.runId || retained.work_id !== input.workId
|
|
136
|
-
|| typeof retained.work_session_id !== "string" || typeof retained.hook_event_key !== "string")
|
|
137
|
-
refuse("native_registered_start_changed");
|
|
138
|
-
binding = retained.binding;
|
|
139
|
-
nativeStart = retained;
|
|
140
|
-
}
|
|
141
|
-
catch (error) {
|
|
142
|
-
if (error instanceof AppError)
|
|
143
|
-
throw error;
|
|
144
|
-
return refuse("native_registered_start_changed");
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
const starts = context.db.all("SELECT ws.id,ws.hook_event_id,ws.start_receipt_json FROM work_sessions ws JOIN sessions s ON s.project_id=? AND s.session_id=ws.session_id WHERE ws.work_id=? AND ws.status='running' AND s.run_id=? AND s.worker_id=? AND s.provider_session_id=? LIMIT 2", [input.projectId, input.workId, input.runId, input.workId, input.providerSessionId]);
|
|
148
|
-
if (work.status !== "running" || !binding || binding.provider_session_id !== input.providerSessionId || starts.length !== 1)
|
|
149
|
-
refuse("native_registered_start_changed");
|
|
150
|
-
if (nativeStart && (nativeStart.work_session_id !== starts[0].id || nativeStart.hook_event_key !== starts[0].hook_event_id
|
|
151
|
-
|| binding.operation_id !== `native-hook:${nativeStart.hook_event_key}`
|
|
152
|
-
|| !context.db.get("SELECT 1 FROM hook_events WHERE project_id=? AND event_key=? AND status='claimed' AND provider_session_id=?", [input.projectId, nativeStart.hook_event_key, input.providerSessionId])))
|
|
153
|
-
refuse("native_registered_start_changed");
|
|
154
|
-
try {
|
|
155
|
-
const text = starts[0].start_receipt_json;
|
|
156
|
-
if (typeof text !== "string" || Buffer.byteLength(text) > 16 * 1024 * 1024)
|
|
157
|
-
refuse("native_registered_start_changed");
|
|
158
|
-
const start = JSON.parse(text), packet = JSON.parse(start.context), profile = JSON.parse(binding.profile_json), metadata = profile.memory_repair_copy;
|
|
159
|
-
if (profile.delegation?.mode !== (native ? "native" : "external") || start.version !== 2 || createHash("sha256").update(start.context).digest("hex") !== start.context_sha256
|
|
160
|
-
|| typeof metadata?.root !== "string" || metadata.root !== `works/${input.workId}/writer-workspaces/generation-${start.generation}`
|
|
161
|
-
|| packet.work_id !== input.workId || packet.run_id !== input.runId || packet.workspace_root !== path.join(runRoot, metadata.root)
|
|
162
|
-
|| packet.memory_repair_input?.provider_session_id !== input.providerSessionId
|
|
163
|
-
|| packet.memory_repair_input?.launch_operation_id !== binding.operation_id)
|
|
164
|
-
refuse("native_registered_start_changed");
|
|
165
|
-
return admitted(packet.workspace_root);
|
|
166
|
-
}
|
|
167
|
-
catch (error) {
|
|
168
|
-
if (error instanceof AppError)
|
|
169
|
-
throw error;
|
|
170
|
-
return refuse("native_registered_start_changed");
|
|
171
|
-
}
|
|
172
|
-
}
|
|
54
|
+
export { memoryRepairAssignedHookCwd } from "./hook-memory-routing.js";
|
|
173
55
|
/** Native children have no external launch row. Read the immutable physical
|
|
174
56
|
* Hook and copy journal instead; preparation cannot manufacture a Session. */
|
|
175
57
|
export function readMemoryRepairPhysicalBinding(context, workId, route, operationId) {
|
|
@@ -57,6 +57,8 @@ export class RetainedMemoryRepairPublicationPlan {
|
|
|
57
57
|
const writes = JSON.parse(before.bytes().toString("utf8")).write_paths;
|
|
58
58
|
if (hybridDigest(writes) !== hybridDigest(JSON.parse(after.bytes().toString("utf8")).write_paths))
|
|
59
59
|
refuse("write_paths_changed");
|
|
60
|
+
if (hybridDigest([...before.canonicalWritePaths()]) !== hybridDigest([...after.canonicalWritePaths()]))
|
|
61
|
+
refuse("canonical_permissions_changed");
|
|
60
62
|
const current = new Map(before.entries().map(entry => [entry.path, entry]));
|
|
61
63
|
const final = new Map(after.entries().map(entry => [entry.path, entry]));
|
|
62
64
|
const directories = new Set();
|
|
@@ -133,11 +135,11 @@ export class RetainedMemoryRepairPublicationPlan {
|
|
|
133
135
|
else
|
|
134
136
|
entries.delete(action.path);
|
|
135
137
|
}
|
|
136
|
-
const value = {
|
|
137
|
-
return MemoryRepairDocumentBaseline.restore(Buffer.from(JSON.stringify(value)), hybridDigest({
|
|
138
|
+
const value = { ...JSON.parse(this.#before.bytes().toString("utf8")), entries: ordered(entries.values()) };
|
|
139
|
+
return MemoryRepairDocumentBaseline.restore(Buffer.from(JSON.stringify(value)), hybridDigest({ ...value, schema_id: undefined }));
|
|
138
140
|
}
|
|
139
141
|
classify(workspace) {
|
|
140
|
-
const actual = MemoryRepairDocumentBaseline.capture(workspace, this.#writePaths);
|
|
142
|
+
const actual = MemoryRepairDocumentBaseline.capture(workspace, this.#writePaths, this.#before.canonicalWritePaths());
|
|
141
143
|
const entries = new Map(actual.entries().map(entry => [entry.path, entry]));
|
|
142
144
|
let completed = 0, pending = false;
|
|
143
145
|
for (const action of this.#receipt.actions) {
|
|
@@ -54,7 +54,7 @@ function compose(start, histories, paths) {
|
|
|
54
54
|
for (const history of histories) {
|
|
55
55
|
if (!versions.has(hybridDigest(history.before().entries())))
|
|
56
56
|
refuse("unexplained_peer_input_view");
|
|
57
|
-
current = current.composeSettledDelta(history.before(), history.after());
|
|
57
|
+
current = current.composeSettledDelta(history.before(), history.after(), history.result().outcome);
|
|
58
58
|
versions.add(hybridDigest(current.entries()));
|
|
59
59
|
}
|
|
60
60
|
if (!versions.has(hybridDigest(start.entries())))
|
|
@@ -5,6 +5,7 @@ import { AppError } from "../shared/errors.js";
|
|
|
5
5
|
import { hybridDigest } from "./hybrid-contracts.js";
|
|
6
6
|
import { PreparedMemoryRepairWorkBaseline } from "./memory-repair-work-baseline.js";
|
|
7
7
|
import { PreparedBoundMemoryRepairCopy } from "./memory-repair-writer-copy.js";
|
|
8
|
+
import { isCompletedMemoryRepairClaim } from "./memory-repair-work-results.js";
|
|
8
9
|
import { appendAudit } from "./audit.js";
|
|
9
10
|
import { stageRetainedMemoryRepairFile, verifyRetainedMemoryRepairFile } from "./memory-repair-publication-files.js";
|
|
10
11
|
import { acquireInstallationLock } from "./installation-lock.js";
|
|
@@ -30,7 +31,7 @@ export function deriveRetainedMemoryRepairViews(start, exported, plan, result, p
|
|
|
30
31
|
|| hybridDigest(ordered(changes(privatePlan))) !== hybridDigest(ordered(result.changes)))
|
|
31
32
|
refuse("reserved_delta_differs_from_result");
|
|
32
33
|
const after = privatePlan.state(privatePlan.actions().length);
|
|
33
|
-
const merged = plan.state(0).composeSettledDelta(start, after);
|
|
34
|
+
const merged = plan.state(0).composeSettledDelta(start, after, result.outcome);
|
|
34
35
|
if (merged.sha256 !== plan.state(actions.length).sha256)
|
|
35
36
|
refuse("unexplained_retained_publication_view");
|
|
36
37
|
const available = new Set(after.entries().filter(entry => entry.kind === "file").map(entry => entry.path));
|
|
@@ -103,7 +104,7 @@ export class PreparedRetainedMemoryRepairPublication {
|
|
|
103
104
|
if (bytes.length > 1024 * 1024 || bytes.toString("base64") !== receipt.result_base64 || hash(bytes) !== receipt.result_sha256)
|
|
104
105
|
refuse("retained_result_changed");
|
|
105
106
|
const result = baseline.validateResult(context, bytes);
|
|
106
|
-
if (result
|
|
107
|
+
if (!isCompletedMemoryRepairClaim(result))
|
|
107
108
|
refuse("accepted_reserved_result_required");
|
|
108
109
|
const { exportedAfter, deltaSha256, transitionSha256 } = deriveRetainedMemoryRepairViews(baseline.baseline(), baseline.exportedBaseline(), plan, result, privatePlan);
|
|
109
110
|
const outputSha256 = hybridDigest({ input: copy.input(), start_receipt_sha256: hybridDigest(copy.startedReceipt()), work_session_id: workSessionId,
|
|
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { AppError } from "../shared/errors.js";
|
|
3
3
|
import { appendAudit } from "./audit.js";
|
|
4
4
|
import { hybridDigest } from "./hybrid-contracts.js";
|
|
5
|
-
import { PreparedMemoryInitialCorrectionPlan, validateMemoryReviewCorrectionPlan } from "./memory-review-correction-plan.js";
|
|
5
|
+
import { PreparedMemoryInitialCorrectionPlan, validateMemoryReviewCorrectionPlan, memoryRepairCanonicalPaths } from "./memory-review-correction-plan.js";
|
|
6
6
|
import { PreparedRetainedMemoryReviewResults } from "./memory-retained-review-results.js";
|
|
7
7
|
import { memoryReviewWaveProjection } from "./memory-review-acceptance.js";
|
|
8
8
|
import { addWorkBatch, prepareWorkBatch, readWorkBatchCustody } from "./work-registry.js";
|
|
@@ -155,6 +155,7 @@ export class PreparedRegisteredMemoryCorrections {
|
|
|
155
155
|
if (expectedBytes !== undefined && (!(expectedBytes instanceof Uint8Array) || !bytes.equals(Buffer.from(expectedBytes))))
|
|
156
156
|
refuse("plan_replay_conflict");
|
|
157
157
|
const plan = validateMemoryReviewCorrectionPlan(bytes, bundle.engine(), { runId: original.run_id, wave: 1,
|
|
158
|
+
canonicalPaths: memoryRepairCanonicalPaths(context, bundle),
|
|
158
159
|
reviewSha256: review.sha256, units: original.view.unit_inventory, findings: memoryReviewWaveProjection(review.results(), original.view.diagnostic_findings).findings });
|
|
159
160
|
for (const work of plan.works)
|
|
160
161
|
bundle.execution.work("review", work.profile_id ?? undefined);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { memoryRepairCanonicalPaths } from "./memory-review-correction-plan.js";
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
3
|
import { AppError } from "../shared/errors.js";
|
|
3
4
|
import { appendAudit } from "./audit.js";
|
|
@@ -108,7 +109,7 @@ export class PreparedMemoryRepairWorkBaseline {
|
|
|
108
109
|
const sourceHash = () => reviewSourceHash(workspace, view.run_inputs.filter(ref => ref.root === "workspace").map(ref => ref.path), view.base);
|
|
109
110
|
if (sourceHash() !== view.source_sha256)
|
|
110
111
|
refuse("original_source_changed_before_writer");
|
|
111
|
-
const baseline = MemoryRepairDocumentBaseline.capture(workspace, assigned.payload.memory_repair.write_paths);
|
|
112
|
+
const baseline = MemoryRepairDocumentBaseline.capture(workspace, assigned.payload.memory_repair.write_paths, memoryRepairCanonicalPaths(context, registration.review().original().bundle()));
|
|
112
113
|
const selected = reviewSourcePaths(workspace, view.run_inputs.filter(ref => ref.root === "workspace").map(ref => ref.path), view.base);
|
|
113
114
|
if (selected === null)
|
|
114
115
|
refuse("export_source_inventory_unavailable");
|
|
@@ -351,11 +352,12 @@ export class PreparedMemoryRepairWorkBaseline {
|
|
|
351
352
|
refuse("registered_baseline_receipt_required");
|
|
352
353
|
}
|
|
353
354
|
assertCustody(context) {
|
|
354
|
-
this.#assertCurrentView(context);
|
|
355
355
|
if (this.#startedWorkSessionId) {
|
|
356
|
+
// The registered start owner also rechecks this baseline's current view.
|
|
356
357
|
this.assertRegisteredStartCustody(context, this.#startedWorkSessionId);
|
|
357
358
|
return;
|
|
358
359
|
}
|
|
360
|
+
this.#assertCurrentView(context);
|
|
359
361
|
this.#registration.assertCustody(context);
|
|
360
362
|
assertRunMutationAllowed(context, this.#scope.project_id, this.#scope.run_id, this.#scope.generation);
|
|
361
363
|
if (hybridDigest(readWorkBatchCustody(context, this.workId)) !== hybridDigest(this.#allocation))
|