@deksden-com/dd-flow-cli 0.9.0-beta.144 → 0.9.0-beta.145
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 +59 -0
- package/README.md +13 -8
- package/dist/build-info.json +7 -7
- package/dist/cli/command-inputs.js +9 -3
- package/dist/cli/command-metadata.js +33 -14
- package/dist/cli/help.js +32 -1
- package/dist/cli/hook-ingress.js +27 -3
- package/dist/cli/memory-flow-admission.js +7 -3
- package/dist/cli/memory-init-invocation.js +10 -3
- package/dist/cli/memory-init.js +74 -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 +70 -32
- package/dist/cli/setup.js +2 -1
- package/dist/cli.js +12 -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/dd-agy-daemon.mjs +62 -20
- package/dist/harness-runtime/lib/dd-agy.mjs +17 -3
- package/dist/harness-runtime/lib/dd-droid.mjs +1 -1
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +70 -13
- package/dist/harness-runtime/lib/dd-grok.mjs +6 -3
- 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/entity-references.d.mts +2 -0
- package/dist/harness-runtime/lib/entity-references.mjs +9 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +13 -6
- package/dist/harness-runtime/lib/native-hook-command.mjs +4 -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 +4 -2
- package/dist/harness-runtime/lib/provider-overload-policy.mjs +19 -6
- package/dist/harness-runtime/lib/runner-events.mjs +3 -1
- package/dist/harness-runtime/lib/setup-progress.mjs +8 -6
- 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/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/cli-operation-classifier.js +1 -1
- package/dist/services/cli-upgrade-allowlist.js +1 -1
- package/dist/services/code-checks.js +11 -4
- package/dist/services/compatibility-preflight.js +5 -2
- package/dist/services/controller-fanout.js +21 -4
- package/dist/services/controller-memory-task.js +45 -0
- package/dist/services/controller-memory-worker.js +48 -0
- package/dist/services/dashboard.js +2 -2
- package/dist/services/descriptor-run-start.js +26 -19
- package/dist/services/descriptor-stage-coordinator.js +30 -10
- package/dist/services/descriptor-stage-start.js +40 -4
- package/dist/services/engines.js +64 -14
- package/dist/services/eval-snapshots.js +104 -93
- package/dist/services/external-work-launch.js +5 -3
- package/dist/services/flow-catalog.js +8 -4
- package/dist/services/frozen-engine.js +113 -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 +2 -13
- package/dist/services/hybrid-digest.js +14 -0
- package/dist/services/installed-flow-source.js +25 -4
- package/dist/services/lifecycle-invocations.js +33 -5
- 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 +28 -2
- package/dist/services/memory-init-controller-start.js +19 -8
- package/dist/services/memory-init-git-policy.js +50 -21
- package/dist/services/memory-init-lint-capability.js +21 -3
- package/dist/services/memory-init-native-capacity.js +13 -0
- package/dist/services/memory-init-preflight.js +11 -1
- 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-layer.js +68 -4
- 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-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 +77 -23
- 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/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 +69 -8
- package/dist/services/recovery-snapshot-database.js +32 -4
- 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-controller-process.js +2 -6
- package/dist/services/run-controller-recovery.js +6 -1
- package/dist/services/run-controller.js +131 -80
- 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/runtime-scope-worker.js +46 -19
- package/dist/services/sessions.js +3 -24
- package/dist/services/setup.js +63 -13
- package/dist/services/stage-handlers.js +9 -7
- package/dist/services/stage-pause.js +108 -44
- package/dist/services/version-status.js +1 -1
- package/dist/services/vnext-fanout.js +21 -9
- package/dist/services/vnext-plan-review.js +3 -5
- package/dist/services/work-registry.js +80 -33
- 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 +8 -4
|
@@ -8,6 +8,11 @@ import { ObservedMemorySources } from "./memory-source-observations.js";
|
|
|
8
8
|
import { readWorkBatchCustody } from "./work-registry.js";
|
|
9
9
|
import { readMemoryCompositionRegistration, readRegisteredMemoryComposition } from "./memory-composition-registration.js";
|
|
10
10
|
import { validateMemoryCompositionWorkResult } from "./memory-composition-work-results.js";
|
|
11
|
+
import { readMemoryUpgradePublication } from "./memory-upgrade-publication-custody.js";
|
|
12
|
+
import { readMemoryUpgradeBootstrapReadiness } from "./memory-upgrade-bootstrap-readiness.js";
|
|
13
|
+
import { readUpgradeStorageReadiness } from "./memory-upgrade-storage-readiness.js";
|
|
14
|
+
import { readAcceptedStageData } from "./stage-report-renderer.js";
|
|
15
|
+
import { getProjectVersionStatus } from "./version-status.js";
|
|
11
16
|
const hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
12
17
|
function refuse(reason) {
|
|
13
18
|
throw new AppError("memory_composition_acceptance_invalid", "Composition lacks registered, settled and current output custody", 2, { reason, effect: "no_effect" });
|
|
@@ -77,17 +82,34 @@ export class PreparedMemoryCompositionAcceptance {
|
|
|
77
82
|
#registrationSha256;
|
|
78
83
|
#workers;
|
|
79
84
|
#artifacts;
|
|
85
|
+
#semanticArtifacts;
|
|
86
|
+
#publication = null;
|
|
87
|
+
#migrationRecord = null;
|
|
88
|
+
#historicalEvidence = new Set();
|
|
80
89
|
#observed;
|
|
81
90
|
constructor(scope, original, registrationSha256, workers, artifacts, observed) {
|
|
82
91
|
this.#scope = structuredClone(scope);
|
|
83
92
|
this.#original = original;
|
|
84
93
|
this.#registrationSha256 = registrationSha256;
|
|
85
94
|
this.#workers = workers;
|
|
86
|
-
this.#artifacts = artifacts;
|
|
95
|
+
this.#artifacts = structuredClone(artifacts);
|
|
96
|
+
this.#semanticArtifacts = structuredClone(artifacts);
|
|
87
97
|
this.#observed = observed;
|
|
88
98
|
Object.freeze(this);
|
|
89
99
|
}
|
|
90
100
|
static prepare(context, input) {
|
|
101
|
+
return this.#prepare(context, input, true);
|
|
102
|
+
}
|
|
103
|
+
/** Restore settled semantic Work independently of today's possibly partly
|
|
104
|
+
* published marker bytes. The publication owner still checks the before view
|
|
105
|
+
* before retaining its intent; this method does not accept current artifacts.
|
|
106
|
+
*/
|
|
107
|
+
static prepareForUpgradePublication(context, input) {
|
|
108
|
+
if (input.scope.stage !== "upgrade")
|
|
109
|
+
refuse("upgrade_publication_scope_required");
|
|
110
|
+
return this.#prepare(context, input, false);
|
|
111
|
+
}
|
|
112
|
+
static #prepare(context, input, observe) {
|
|
91
113
|
const registration = readMemoryCompositionRegistration(context, input.scope);
|
|
92
114
|
const original = readRegisteredMemoryComposition(context, input), value = original.value();
|
|
93
115
|
const works = value.details.works;
|
|
@@ -137,21 +159,127 @@ export class PreparedMemoryCompositionAcceptance {
|
|
|
137
159
|
for (const record of current.values())
|
|
138
160
|
for (const id of record.units)
|
|
139
161
|
artifacts.set(id, [...(artifacts.get(id) ?? []), record.artifact]);
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
162
|
+
const prepared = new PreparedMemoryCompositionAcceptance(input.scope, original, registrationSha256, selected.map(item => ({ id: item.id, harness: item.harness, authority: item.authority, evidence: item.evidence })), artifacts, null);
|
|
163
|
+
if (input.scope.stage === "upgrade")
|
|
164
|
+
prepared.#historicalEvidence = new Set(input.binding.historicalEvidence);
|
|
165
|
+
if (observe) {
|
|
166
|
+
if (input.scope.stage === "upgrade")
|
|
167
|
+
prepared.#includePublication(context, input.binding);
|
|
168
|
+
prepared.#observed = ObservedMemorySources.capture(assigned, prepared.outputLocations());
|
|
169
|
+
const hashes = prepared.#observed.hashes();
|
|
170
|
+
if ([...prepared.#artifacts.values()].flat().some(artifact => hashes.get(memorySourceKey(artifact.root, artifact.path)) !== artifact.sha256))
|
|
171
|
+
refuse("current_output_bytes_changed");
|
|
172
|
+
}
|
|
145
173
|
prepared.assertCustody(context);
|
|
146
174
|
return prepared;
|
|
147
175
|
}
|
|
176
|
+
#includePublication(context, binding) {
|
|
177
|
+
const frozen = binding.execution?.snapshot().scope;
|
|
178
|
+
if (!frozen)
|
|
179
|
+
refuse("upgrade_execution_required");
|
|
180
|
+
const scope = { requestId: frozen.requestId, projectRoot: frozen.projectRoot, workspaceRoot: frozen.workspace };
|
|
181
|
+
const ready = readMemoryUpgradeBootstrapReadiness(context, scope), storage = readUpgradeStorageReadiness(context, scope);
|
|
182
|
+
if (!ready || !storage?.selection || ready.authority.binding.storage_sha256 !== storage.sha256)
|
|
183
|
+
refuse("upgrade_bootstrap_required");
|
|
184
|
+
const identity = { scope: this.#scope, selection_sha256: storage.selection.sha256, execution_sha256: binding.executionSha256,
|
|
185
|
+
registration_sha256: this.#registrationSha256, bootstrap_sha256: hybridDigest(ready.authority), work_custody_sha256: this.custodySha256() };
|
|
186
|
+
const publication = readMemoryUpgradePublication(context, identity);
|
|
187
|
+
if (!publication?.completed)
|
|
188
|
+
refuse("completed_upgrade_publication_required");
|
|
189
|
+
const state = publication.plan.classify();
|
|
190
|
+
if (state.completed !== state.total)
|
|
191
|
+
refuse("upgrade_publication_not_current");
|
|
192
|
+
const actions = new Map(publication.plan.actions().map(action => [`.memory-bank/${action.path}`, action]));
|
|
193
|
+
for (const artifacts of this.#artifacts.values())
|
|
194
|
+
for (const artifact of artifacts) {
|
|
195
|
+
const action = artifact.root === "workspace" ? actions.get(artifact.path) : undefined;
|
|
196
|
+
if (!action)
|
|
197
|
+
continue;
|
|
198
|
+
// Only the exact CLI transition can advance a settled worker hash. No
|
|
199
|
+
// caller-provided allowlist may excuse an arbitrary changed document.
|
|
200
|
+
if (action.before?.sha256 !== artifact.sha256 || action.after?.kind !== "file")
|
|
201
|
+
refuse("unbound_semantic_output_transition");
|
|
202
|
+
artifact.sha256 = action.after.sha256;
|
|
203
|
+
}
|
|
204
|
+
const outputs = new Map(publication.plan.outputs().map(output => [`.memory-bank/${output.path}`, output]));
|
|
205
|
+
const recordPath = `.memory-bank/archive/mb-upgrade/${identity.selection_sha256}/migration.json`, record = outputs.get(recordPath);
|
|
206
|
+
if (!record)
|
|
207
|
+
refuse("upgrade_migration_record_missing");
|
|
208
|
+
this.#migrationRecord = { path: recordPath, sha256: record.sha256 };
|
|
209
|
+
const add = (unit, path, sha256) => {
|
|
210
|
+
const values = this.#artifacts.get(unit) ?? [], artifact = { root: "workspace", path, sha256 };
|
|
211
|
+
const prior = values.find(value => value.root === artifact.root && value.path === path);
|
|
212
|
+
if (prior && prior.sha256 !== sha256)
|
|
213
|
+
refuse("conflicting_mechanical_artifact");
|
|
214
|
+
if (!prior)
|
|
215
|
+
values.push(artifact);
|
|
216
|
+
this.#artifacts.set(unit, values);
|
|
217
|
+
};
|
|
218
|
+
const diff = readAcceptedStageData(context, { projectId: this.#scope.project_id, runId: this.#scope.run_id, stage: "diff-analysis" });
|
|
219
|
+
const changes = JSON.parse(diff.bytes.toString("utf8")).details.path_map;
|
|
220
|
+
const old = new Map(storage.selection.payload.current.inventory.inventory.entries.map(entry => [`.memory-bank/${entry.path}`, entry]));
|
|
221
|
+
const final = new Map(publication.plan.beforeFiles().map(entry => [entry.path, entry]));
|
|
222
|
+
for (const action of publication.plan.actions()) {
|
|
223
|
+
if (action.after)
|
|
224
|
+
final.set(action.path, action.after);
|
|
225
|
+
else
|
|
226
|
+
final.delete(action.path);
|
|
227
|
+
}
|
|
228
|
+
for (const entry of storage.selection.payload.current.inventory.inventory.entries) {
|
|
229
|
+
const name = `.memory-bank/${entry.path}`, actual = final.get(entry.path);
|
|
230
|
+
const semantic = changes.some(change => change.to === name && change.action === "semantic-work");
|
|
231
|
+
if (entry.target && (entry.ownership === "generated" || entry.ownership === "canonical" && !semantic)
|
|
232
|
+
&& (actual?.kind !== "file" || actual.sha256 !== entry.target.sha256))
|
|
233
|
+
refuse("incomplete_canonical_publication");
|
|
234
|
+
if (entry.ownership === "canonical" && entry.action === "archive" && actual)
|
|
235
|
+
refuse("obsolete_canonical_path_active");
|
|
236
|
+
}
|
|
237
|
+
const status = getProjectVersionStatus({ projectRoot: frozen.workspace, rootSource: "explicit", canon: null });
|
|
238
|
+
if (status?.memory_bank.version !== storage.selection.release.canon.version
|
|
239
|
+
|| status.flow_pack.canon_version_at_source_commit !== storage.selection.release.canon.version
|
|
240
|
+
|| status.flow_pack.source_commit !== storage.selection.release.canon.commit)
|
|
241
|
+
refuse("upgrade_versions_inconsistent");
|
|
242
|
+
for (const change of changes) {
|
|
243
|
+
if (change.action === "retain" && change.from) {
|
|
244
|
+
const source = old.get(change.from)?.current;
|
|
245
|
+
if (source?.kind === "file")
|
|
246
|
+
add(change.unit_id, change.from, outputs.get(change.from)?.sha256 ?? source.sha256);
|
|
247
|
+
}
|
|
248
|
+
else if (["canonical", "generated"].includes(change.ownership) && change.action !== "semantic-work") {
|
|
249
|
+
if (change.to) {
|
|
250
|
+
const output = outputs.get(change.to) ?? final.get(change.to.slice(13));
|
|
251
|
+
if (!output || output.kind !== "file")
|
|
252
|
+
refuse("missing_mechanical_artifact");
|
|
253
|
+
add(change.unit_id, change.to, output.sha256);
|
|
254
|
+
}
|
|
255
|
+
if (change.from) {
|
|
256
|
+
const archive = `.memory-bank/archive/mb-upgrade/${identity.selection_sha256}/files/${change.from.slice(13)}.bin`, output = outputs.get(archive);
|
|
257
|
+
if (!output)
|
|
258
|
+
refuse("missing_recovery_artifact");
|
|
259
|
+
add(change.unit_id, archive, output.sha256);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
for (const unit of this.#original.value().units)
|
|
264
|
+
if (unit.applicability === "applicable")
|
|
265
|
+
add(unit.id, recordPath, record.sha256);
|
|
266
|
+
this.#publication = { binding: identity, sha256: publication.plan.sha256 };
|
|
267
|
+
}
|
|
148
268
|
/** Only these independently settled output locations may supplement baseline
|
|
149
269
|
* evidence in the final result binding. The returned map is a defensive copy.
|
|
150
270
|
*/
|
|
151
|
-
outputHashes() {
|
|
271
|
+
outputHashes() { if (!this.#observed)
|
|
272
|
+
refuse("current_output_observation_required"); return this.#observed.hashes(); }
|
|
273
|
+
/** SQL-settled before-publication facts, not current filesystem witnesses. */
|
|
274
|
+
settledArtifacts() {
|
|
275
|
+
return [...this.#semanticArtifacts].flatMap(([unit_id, artifacts]) => artifacts.map(artifact => ({ unit_id, artifact: structuredClone(artifact) })));
|
|
276
|
+
}
|
|
277
|
+
custodySha256() {
|
|
278
|
+
return hybridDigest({ scope: this.#scope, registration_sha256: this.#registrationSha256, workers: this.#workers, artifacts: [...this.#semanticArtifacts] });
|
|
279
|
+
}
|
|
152
280
|
historicalEvidence() {
|
|
153
281
|
const units = this.#original.value().units;
|
|
154
|
-
return new Set(units.flatMap(unit => unit.evidence.map(ref => `${memorySourceKey(ref.root, ref.path)}\0${ref.sha256}`)));
|
|
282
|
+
return new Set([...this.#historicalEvidence, ...units.flatMap(unit => unit.evidence.map(ref => `${memorySourceKey(ref.root, ref.path)}\0${ref.sha256}`))]);
|
|
155
283
|
}
|
|
156
284
|
resultEvidence() { return this.#workers.map(worker => structuredClone(worker.evidence)); }
|
|
157
285
|
outputLocations() {
|
|
@@ -161,14 +289,48 @@ export class PreparedMemoryCompositionAcceptance {
|
|
|
161
289
|
selected.set(memorySourceKey(artifact.root, artifact.path), { root: artifact.root, path: artifact.path });
|
|
162
290
|
return [...selected.values()];
|
|
163
291
|
}
|
|
292
|
+
/** Runtime projection from completed SQL publication, never an agent map.
|
|
293
|
+
* Keep substantive fields and immutable graph intact. Existing invented
|
|
294
|
+
* references are rejected rather than silently overwritten by this helper.
|
|
295
|
+
*/
|
|
296
|
+
projectResult(bytes) {
|
|
297
|
+
if (!this.#migrationRecord)
|
|
298
|
+
return Buffer.from(bytes);
|
|
299
|
+
const value = object(Buffer.from(bytes).toString("utf8"), "invalid_result_json");
|
|
300
|
+
const reject = (pointer) => {
|
|
301
|
+
throw new AppError("memory_stage_result_invalid", "Use the selected upgrade outputs; do not invent or rerun publication", 2, { reason: "unselected_upgrade_output", phase: "prepare", effect: "no_effect", recoverable: true, result_sha256: hash(bytes), pointer });
|
|
302
|
+
};
|
|
303
|
+
if (Array.isArray(value.units))
|
|
304
|
+
for (const [index, unit] of value.units.entries()) {
|
|
305
|
+
if (!unit || typeof unit !== "object" || typeof unit.id !== "string" || !this.#original.mechanicalUnitIds().has(unit.id) && !this.#artifacts.has(unit.id))
|
|
306
|
+
continue;
|
|
307
|
+
const expected = this.#artifacts.get(unit.id) ?? [], before = this.#semanticArtifacts.get(unit.id) ?? [];
|
|
308
|
+
if (unit.artifacts !== undefined && !Array.isArray(unit.artifacts))
|
|
309
|
+
continue;
|
|
310
|
+
for (const reference of unit.artifacts ?? [])
|
|
311
|
+
if (![...expected, ...before].some(artifact => hybridDigest(artifact) === hybridDigest(reference)))
|
|
312
|
+
reject(`/units/${index}/artifacts`);
|
|
313
|
+
unit.artifacts = structuredClone(expected);
|
|
314
|
+
}
|
|
315
|
+
if (value.details && typeof value.details === "object" && !Array.isArray(value.details)) {
|
|
316
|
+
const details = value.details, original = this.#original.value().details;
|
|
317
|
+
if (details.migration_record !== undefined && ![original.migration_record, this.#migrationRecord].some(reference => hybridDigest(reference) === hybridDigest(details.migration_record)))
|
|
318
|
+
reject("/details/migration_record");
|
|
319
|
+
details.migration_record = { ...this.#migrationRecord };
|
|
320
|
+
}
|
|
321
|
+
return Buffer.from(JSON.stringify(value));
|
|
322
|
+
}
|
|
164
323
|
assertResult(result) {
|
|
165
324
|
if (!(result instanceof PreparedMemoryStageResult))
|
|
166
325
|
refuse("prepared_result_required");
|
|
326
|
+
if (this.#scope.stage === "upgrade" && !this.#publication)
|
|
327
|
+
refuse("completed_upgrade_publication_required");
|
|
167
328
|
const original = this.#original.value(), final = result.value(), value = final;
|
|
168
329
|
const identity = ["schema_id", "run_id", "input_sha256", "predecessors"];
|
|
169
330
|
if (hybridDigest(identity.map(field => original[field])) !== hybridDigest(identity.map(field => final[field])))
|
|
170
331
|
refuse("foreign_result_identity");
|
|
171
|
-
|
|
332
|
+
const details = this.#migrationRecord ? { ...original.details, migration_record: this.#migrationRecord } : original.details;
|
|
333
|
+
if (value.stage !== this.#original.authoringStage() || value.outcome !== "accepted" || hybridDigest(value.details) !== hybridDigest(details)
|
|
172
334
|
|| value.findings.some(finding => finding.severity !== "minor" && finding.status !== "resolved"))
|
|
173
335
|
refuse("composition_not_accepted_or_plan_changed");
|
|
174
336
|
const inventory = new Map(original.units.map(unit => [unit.id, unit]));
|
|
@@ -193,7 +355,21 @@ export class PreparedMemoryCompositionAcceptance {
|
|
|
193
355
|
for (const worker of this.#workers)
|
|
194
356
|
if (settled(context, this.#scope, worker.id, worker.harness, this.#registrationSha256, assigned).authority !== worker.authority)
|
|
195
357
|
refuse("work_settlement_changed");
|
|
358
|
+
if (this.#publication) {
|
|
359
|
+
const publication = readMemoryUpgradePublication(context, this.#publication.binding);
|
|
360
|
+
if (!publication?.completed || publication.plan.sha256 !== this.#publication.sha256)
|
|
361
|
+
refuse("publication_custody_changed");
|
|
362
|
+
}
|
|
196
363
|
});
|
|
197
364
|
}
|
|
198
|
-
revalidate(context) {
|
|
365
|
+
revalidate(context) {
|
|
366
|
+
if (this.#observed)
|
|
367
|
+
this.#observed.revalidate();
|
|
368
|
+
else {
|
|
369
|
+
const hashes = ObservedMemorySources.capture(roots(context, this.#scope), this.outputLocations()).hashes();
|
|
370
|
+
if (this.settledArtifacts().some(({ artifact }) => hashes.get(memorySourceKey(artifact.root, artifact.path)) !== artifact.sha256))
|
|
371
|
+
refuse("current_output_bytes_changed");
|
|
372
|
+
}
|
|
373
|
+
this.assertCustody(context);
|
|
374
|
+
}
|
|
199
375
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AppError } from "../shared/errors.js";
|
|
2
2
|
import { localEnvironmentAllowed } from "./workspace-files.js";
|
|
3
|
-
function refuse(reason) {
|
|
4
|
-
throw new AppError("memory_stage_result_invalid", "Memory composition graph cannot be dispatched", 2, { reason, phase: "prepare", effect: "no_effect" });
|
|
3
|
+
function refuse(reason, details = {}) {
|
|
4
|
+
throw new AppError("memory_stage_result_invalid", "Memory composition graph cannot be dispatched", 2, { ...details, reason, phase: "prepare", effect: "no_effect" });
|
|
5
5
|
}
|
|
6
6
|
// Portable scopes must remain safe on case-insensitive/normalizing filesystems.
|
|
7
7
|
const physicalPath = (value) => value.normalize("NFC").toLowerCase();
|
|
@@ -9,8 +9,10 @@ const projectConfigurationPaths = [".memory-bank/dd-flow/project-execution.json"
|
|
|
9
9
|
/** Exact project-owned defaults, never directory or case aliases of canon. */
|
|
10
10
|
export function memoryProjectConfigurationPath(value) { return projectConfigurationPaths.includes(value); }
|
|
11
11
|
/** Check both assigned directories and their concrete output descendants. */
|
|
12
|
-
export function memoryCompositionOutputAllowed(value) {
|
|
13
|
-
if (
|
|
12
|
+
export function memoryCompositionOutputAllowed(value, upgradeWritePaths) {
|
|
13
|
+
if (upgradeWritePaths && !upgradeWritePaths.has(value))
|
|
14
|
+
return false;
|
|
15
|
+
if (!upgradeWritePaths && overlaps(value, ".memory-bank/dd-flow") && !memoryProjectConfigurationPath(value))
|
|
14
16
|
return false;
|
|
15
17
|
return value.split("/").every(part => localEnvironmentAllowed(part)
|
|
16
18
|
&& ![".git", "node_modules", ".dd-flow"].includes(physicalPath(part)));
|
|
@@ -23,7 +25,9 @@ function overlaps(left, right) {
|
|
|
23
25
|
* semantic graph checks precede Work allocation, not merely worker dispatch.
|
|
24
26
|
* They do not grant filesystem access or replace frozen profile/Work custody.
|
|
25
27
|
*/
|
|
26
|
-
export function validateMemoryCompositionGraph(value) {
|
|
28
|
+
export function validateMemoryCompositionGraph(value, upgradePaths, mechanicalUnits) {
|
|
29
|
+
if (mechanicalUnits && !upgradePaths)
|
|
30
|
+
refuse("missing_upgrade_authority");
|
|
27
31
|
const result = value;
|
|
28
32
|
const unresolved = result.units.findIndex(unit => unit.applicability === "unknown");
|
|
29
33
|
if (unresolved !== -1)
|
|
@@ -49,17 +53,24 @@ export function validateMemoryCompositionGraph(value) {
|
|
|
49
53
|
// entry, not selected canon. Never grant their enclosing directory or
|
|
50
54
|
// case/normalization aliases; the installed prompt/schema pack is sealed.
|
|
51
55
|
const projectConfig = memoryProjectConfigurationPath(scope.path);
|
|
52
|
-
if (scope.root === "workspace" && !projectConfig && [".memory-bank/mbb", ".memory-bank/dd-flow"].some(canonical => overlaps(scope.path, canonical)))
|
|
56
|
+
if (!upgradePaths && scope.root === "workspace" && !projectConfig && [".memory-bank/mbb", ".memory-bank/dd-flow"].some(canonical => overlaps(scope.path, canonical)))
|
|
53
57
|
refuse("canonical_composition_write");
|
|
58
|
+
const selectedPaths = upgradePaths && scope.root === "workspace" ? new Set(work.unit_ids.flatMap(id => [...upgradePaths.get(id) ?? []])) : undefined;
|
|
54
59
|
// A contained semantic scope is not permission to alter credentials,
|
|
55
60
|
// repository metadata, installed dependencies or runtime state.
|
|
56
|
-
if (!memoryCompositionOutputAllowed(scope.path))
|
|
57
|
-
refuse("private_composition_write"
|
|
61
|
+
if (!memoryCompositionOutputAllowed(scope.path, selectedPaths))
|
|
62
|
+
refuse("private_composition_write", {
|
|
63
|
+
work_key: work.key, scope: { ...scope },
|
|
64
|
+
hint: selectedPaths && work.unit_ids.every(id => mechanicalUnits?.has(id))
|
|
65
|
+
? "These units are mechanical: remove their agent Work and use memory upgrade publish after graph registration. Do not change accepted predecessor or ownership files."
|
|
66
|
+
: selectedPaths ? "This path is outside this Work's accepted upgrade authoring paths. Mechanical canonical/generated changes use memory upgrade publish, not agent workspace writes. Correct only the proposed scopes; keep accepted predecessor and ownership files unchanged."
|
|
67
|
+
: "Remove the private filesystem scope from the proposed Work; credentials, repository metadata and runtime state are not semantic outputs."
|
|
68
|
+
});
|
|
58
69
|
if (scope.root === "run" && ["execution-bundle", "run.json"].some(authority => overlaps(scope.path, authority)))
|
|
59
70
|
refuse("authority_composition_write");
|
|
60
71
|
// Mechanical RUN files and another Work's evidence are never semantic outputs.
|
|
61
72
|
if (scope.root === "run") {
|
|
62
|
-
const namespace = `memory-artifacts
|
|
73
|
+
const namespace = `memory-artifacts/${upgradePaths ? "upgrade" : "compose"}/${work.key}`;
|
|
63
74
|
if (scope.path !== namespace && !scope.path.startsWith(`${namespace}/`))
|
|
64
75
|
refuse("foreign_composition_run_write");
|
|
65
76
|
}
|
|
@@ -107,15 +118,35 @@ export function validateMemoryCompositionGraph(value) {
|
|
|
107
118
|
|| assemblies.some(work => !declared.has(work.key)))
|
|
108
119
|
refuse("invalid_assembly_membership");
|
|
109
120
|
const applicable = new Set(result.units.filter(unit => unit.applicability !== "not-applicable").map(unit => unit.id));
|
|
121
|
+
for (const unit of mechanicalUnits ?? []) {
|
|
122
|
+
if (!applicable.has(unit) || upgradePaths?.has(unit))
|
|
123
|
+
refuse("invalid_mechanical_unit");
|
|
124
|
+
const assigned = works.filter(work => work.unit_ids.includes(unit));
|
|
125
|
+
if (assigned.length)
|
|
126
|
+
refuse("mechanical_unit_cannot_allocate_agent_work", {
|
|
127
|
+
unit_id: unit, work_keys: assigned.map(work => work.key).slice(0, 8),
|
|
128
|
+
hint: "Remove this unit from agent Work assignments, but keep the unit in the result inventory. CLI owns its mechanical publication through memory upgrade publish."
|
|
129
|
+
});
|
|
130
|
+
}
|
|
110
131
|
for (const work of works)
|
|
111
132
|
if (work.unit_ids.some(id => !applicable.has(id)))
|
|
112
133
|
refuse("inapplicable_composition_work");
|
|
113
134
|
for (const unit of applicable) {
|
|
135
|
+
if (mechanicalUnits?.has(unit))
|
|
136
|
+
continue;
|
|
114
137
|
const authors = works.filter(work => work.unit_ids.includes(unit));
|
|
115
138
|
const writers = authors.filter(work => work.role === "writer");
|
|
116
139
|
const collectors = assemblies.filter(work => work.unit_ids.includes(unit));
|
|
117
|
-
|
|
118
|
-
|
|
140
|
+
// Init assembles a new knowledge shelf. Upgrade may reconcile one existing
|
|
141
|
+
// document without changing its navigation; do not invent a collector Work.
|
|
142
|
+
// Explicit collectors still depend on every writer, in either flow.
|
|
143
|
+
if (!authors.length || !upgradePaths && !collectors.length)
|
|
144
|
+
refuse("incomplete_composition_coverage", {
|
|
145
|
+
unit_id: unit,
|
|
146
|
+
hint: upgradePaths
|
|
147
|
+
? "Assign this applicable non-mechanical unit to Work. For preservation evidence only, group it into an existing evidence Work with its RUN-local output; keep accepted unit identities and workspace write scopes unchanged."
|
|
148
|
+
: "Assign this applicable unit to composition Work and its index-assembly collector. Keep accepted unit identities unchanged."
|
|
149
|
+
});
|
|
119
150
|
if (collectors.some(collector => writers.some(writer => !dependsOn(collector.key, writer.key))))
|
|
120
151
|
refuse("assembly_precedes_unit_writers");
|
|
121
152
|
}
|
|
@@ -23,7 +23,7 @@ function readReceipt(context, scope) {
|
|
|
23
23
|
|| receipt.run_id !== scope.run_id || hybridDigest(receipt.scope) !== requestSha256 || receipt.result_schema !== workerSchema
|
|
24
24
|
|| [receipt.batch_sha256, receipt.compose_result_sha256, receipt.input_sha256, receipt.execution_sha256].some(hash => typeof hash !== "string" || !/^[a-f0-9]{64}$/.test(hash))
|
|
25
25
|
|| typeof receipt.result_base64 !== "string" || !receipt.work_ids || Array.isArray(receipt.work_ids)
|
|
26
|
-
|| typeof receipt.work_ids !== "object" || !Object.keys(receipt.work_ids).length || Object.keys(receipt.work_ids).length > 4096
|
|
26
|
+
|| typeof receipt.work_ids !== "object" || !Object.keys(receipt.work_ids).length && scope.stage !== "upgrade" || Object.keys(receipt.work_ids).length > 4096
|
|
27
27
|
|| Object.values(receipt.work_ids).some(id => typeof id !== "string")
|
|
28
28
|
|| new Set(Object.values(receipt.work_ids)).size !== Object.keys(receipt.work_ids).length)
|
|
29
29
|
refuse("foreign_receipt_binding");
|
|
@@ -37,6 +37,15 @@ function readReceipt(context, scope) {
|
|
|
37
37
|
|| hybridDigest(custody.work_ids) !== hybridDigest(receipt.work_ids) || hybridDigest(custody.scope) !== requestSha256)
|
|
38
38
|
refuse("changed_work_custody");
|
|
39
39
|
}
|
|
40
|
+
if (!Object.keys(receipt.work_ids).length) {
|
|
41
|
+
// No invented worker for deterministic-only upgrade. Its empty batch
|
|
42
|
+
// still has the same independent allocator receipt and active scope.
|
|
43
|
+
const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='work.batch_registration' AND json_extract(payload_json,'$.request_sha256')=? LIMIT 2", [scope.project_id, requestSha256]);
|
|
44
|
+
if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 32768 || hybridDigest(JSON.parse(rows[0].payload_json)) !== hybridDigest({
|
|
45
|
+
run_id: scope.run_id, scope, request_sha256: requestSha256, batch_sha256: hybridDigest([]), work_ids: {}, work_sha256: {}
|
|
46
|
+
}) || receipt.batch_sha256 !== hybridDigest([]))
|
|
47
|
+
refuse("changed_empty_batch_custody");
|
|
48
|
+
}
|
|
40
49
|
return receipt;
|
|
41
50
|
}
|
|
42
51
|
catch (error) {
|
|
@@ -85,24 +94,28 @@ export function selectMemoryCompositionRegistration(context, scope) {
|
|
|
85
94
|
export function registerMemoryComposition(context, input, assertHandlerCustody) {
|
|
86
95
|
if (!(input.composition instanceof PreparedMemoryStageResult))
|
|
87
96
|
refuse("foreign_prepared_result");
|
|
97
|
+
const stage = input.composition.authoringStage();
|
|
88
98
|
const value = input.composition.compositionBatch({ ...input.binding, resultSchema: workerSchema });
|
|
89
99
|
const bytes = input.composition.bytes();
|
|
90
|
-
const batchInput = { parentWorkId: input.parentWorkId, requestKey: input.requestKey, stage
|
|
91
|
-
|
|
100
|
+
const batchInput = { parentWorkId: input.parentWorkId, requestKey: input.requestKey, stage, value };
|
|
101
|
+
// Generic CLI batches remain non-empty. The admitted upgrade graph alone
|
|
102
|
+
// may register zero agent tasks; addWorkBatch still checks parent/run/stage
|
|
103
|
+
// and records exact batch custody in the existing writer transaction.
|
|
104
|
+
const prepared = stage === "upgrade" && !value.works.length ? [] : prepareWorkBatch(context, batchInput);
|
|
92
105
|
return context.db.writeTransaction(() => {
|
|
93
106
|
assertHandlerCustody?.(context);
|
|
94
107
|
const parent = context.db.get("SELECT project_id, run_id FROM works WHERE work_id = ?", [input.parentWorkId]);
|
|
95
108
|
if (!parent || parent.run_id !== input.binding.runId)
|
|
96
109
|
refuse("foreign_coordinator");
|
|
97
|
-
const scope = { ...stageGraphScope(context, parent.project_id, parent.run_id,
|
|
98
|
-
stage
|
|
110
|
+
const scope = { ...stageGraphScope(context, parent.project_id, parent.run_id, stage),
|
|
111
|
+
stage, parent_work_id: input.parentWorkId, request_key: input.requestKey };
|
|
99
112
|
// One immutable blueprint per coordinator/attempt. A different request key
|
|
100
113
|
// cannot create another productive graph or bypass a lost original receipt.
|
|
101
|
-
const originals = context.db.all("SELECT json_extract(payload_json,'$.request_sha256') AS request_sha256 FROM audit_events WHERE project_id=? AND event_type='memory.composition_registration' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.scope.stage')
|
|
114
|
+
const originals = context.db.all("SELECT json_extract(payload_json,'$.request_sha256') AS request_sha256 FROM audit_events WHERE project_id=? AND event_type='memory.composition_registration' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.scope.stage')=? AND json_extract(payload_json,'$.scope.attempt')=? AND json_extract(payload_json,'$.scope.cycle')=? AND json_extract(payload_json,'$.scope.parent_work_id')=? LIMIT 2", [scope.project_id, scope.run_id, stage, scope.attempt, scope.cycle, scope.parent_work_id]);
|
|
102
115
|
if (originals.length > 1 || originals.length === 1 && originals[0].request_sha256 !== hybridDigest(scope))
|
|
103
116
|
refuse("original_composition_already_registered");
|
|
104
117
|
const prior = readReceipt(context, scope);
|
|
105
|
-
if (!prior && context.db.get("SELECT 1 FROM works w JOIN stage_work_memberships m ON m.work_id=w.work_id AND m.project_id=w.project_id AND m.run_id=w.run_id WHERE w.project_id=? AND w.run_id=? AND w.parent_work_id=? AND w.result_schema=? AND m.stage
|
|
118
|
+
if (!prior && context.db.get("SELECT 1 FROM works w JOIN stage_work_memberships m ON m.work_id=w.work_id AND m.project_id=w.project_id AND m.run_id=w.run_id WHERE w.project_id=? AND w.run_id=? AND w.parent_work_id=? AND w.result_schema=? AND m.stage=? AND m.attempt=? AND m.cycle=? LIMIT 1", [scope.project_id, scope.run_id, scope.parent_work_id, workerSchema, stage, scope.attempt, scope.cycle]))
|
|
106
119
|
refuse("composition_receipt_missing");
|
|
107
120
|
if (prior && (prior.compose_result_sha256 !== input.composition.sha256 || prior.batch_sha256 !== hybridDigest(value.works)
|
|
108
121
|
|| prior.input_sha256 !== input.binding.inputSha256 || prior.execution_sha256 !== input.binding.executionSha256))
|
|
@@ -133,10 +146,12 @@ export function readRegisteredMemoryComposition(context, input) {
|
|
|
133
146
|
const receipt = readReceipt(context, input.scope);
|
|
134
147
|
if (!receipt)
|
|
135
148
|
refuse("composition_receipt_missing");
|
|
136
|
-
if (input.binding.runId !== input.scope.run_id || input.binding.stage !==
|
|
149
|
+
if (input.binding.runId !== input.scope.run_id || input.binding.stage !== input.scope.stage
|
|
137
150
|
|| input.binding.inputSha256 !== receipt.input_sha256 || input.binding.executionSha256 !== receipt.execution_sha256)
|
|
138
151
|
refuse("foreign_restore_binding");
|
|
139
152
|
const result = PreparedMemoryStageResult.prepare(Buffer.from(receipt.result_base64, "base64"), input.flow, input.binding);
|
|
153
|
+
if (result.authoringStage() !== input.scope.stage)
|
|
154
|
+
refuse("foreign_restore_binding");
|
|
140
155
|
const batch = result.compositionBatch({ runId: input.binding.runId, inputSha256: receipt.input_sha256,
|
|
141
156
|
executionSha256: receipt.execution_sha256, resultSchema: workerSchema });
|
|
142
157
|
if (result.sha256 !== receipt.compose_result_sha256 || hybridDigest(batch.works) !== receipt.batch_sha256
|
|
@@ -46,10 +46,11 @@ export class PreparedRegisteredMemoryCompositionWorkResult {
|
|
|
46
46
|
*/
|
|
47
47
|
static prepareFromStageCustody(context, input, engine, binding) {
|
|
48
48
|
const custody = readWorkBatchCustody(context, binding.workId);
|
|
49
|
+
const stage = binding.flow.descriptor.id === "mb-init" ? "compose" : binding.flow.descriptor.id === "mb-upgrade" ? "upgrade" : refuse();
|
|
49
50
|
const selected = binding.bundle ? PreparedMemoryResultBinding.prepareRegisteredComposition(context, { ...binding, bundle: binding.bundle,
|
|
50
|
-
registrationScope: { project_id: binding.projectId, run_id: binding.runId, stage
|
|
51
|
+
registrationScope: { project_id: binding.projectId, run_id: binding.runId, stage, attempt: custody.scope.attempt, cycle: custody.scope.cycle,
|
|
51
52
|
parent_work_id: binding.coordinatorWorkId, request_key: binding.requestKey } })
|
|
52
|
-
: binding.sources ? PreparedMemoryResultBinding.prepare(context, { ...binding, sources: binding.sources, stage
|
|
53
|
+
: binding.sources ? PreparedMemoryResultBinding.prepare(context, { ...binding, sources: binding.sources, stage }) : refuse();
|
|
53
54
|
const prepared = this.prepareFromRegistration(context, input, engine, { ...binding, resultBinding: selected.binding() });
|
|
54
55
|
prepared.#stageBinding = selected;
|
|
55
56
|
prepared.assertCustody(context);
|
|
@@ -57,11 +58,12 @@ export class PreparedRegisteredMemoryCompositionWorkResult {
|
|
|
57
58
|
}
|
|
58
59
|
static prepareFromRegistration(context, input, engine, binding) {
|
|
59
60
|
const custody = readWorkBatchCustody(context, binding.workId);
|
|
60
|
-
|
|
61
|
+
const stage = binding.flow.descriptor.id === "mb-init" ? "compose" : binding.flow.descriptor.id === "mb-upgrade" ? "upgrade" : refuse();
|
|
62
|
+
if (custody.scope.stage !== stage || custody.scope.parent_work_id !== binding.coordinatorWorkId
|
|
61
63
|
|| custody.scope.request_key !== binding.requestKey || !binding.resultBinding.executionSha256)
|
|
62
64
|
refuse();
|
|
63
65
|
const scope = { project_id: custody.scope.project_id, run_id: custody.scope.run_id,
|
|
64
|
-
stage
|
|
66
|
+
stage, attempt: custody.scope.attempt, cycle: custody.scope.cycle,
|
|
65
67
|
parent_work_id: binding.coordinatorWorkId, request_key: binding.requestKey };
|
|
66
68
|
const registrationSha256 = memoryCompositionRegistrationDigest(context, scope);
|
|
67
69
|
const composition = readRegisteredMemoryComposition(context, { scope, flow: binding.flow, binding: binding.resultBinding });
|
|
@@ -75,7 +77,7 @@ export class PreparedRegisteredMemoryCompositionWorkResult {
|
|
|
75
77
|
static prepare(context, input, engine, binding) {
|
|
76
78
|
const custody = readWorkBatchCustody(context, binding.workId);
|
|
77
79
|
if (!(binding.composition instanceof PreparedMemoryStageResult) || binding.composition.sha256 !== binding.compositionSha256
|
|
78
|
-
|| custody.scope.run_id !== binding.runId || custody.scope.stage !==
|
|
80
|
+
|| custody.scope.run_id !== binding.runId || custody.scope.stage !== binding.composition.authoringStage()
|
|
79
81
|
|| custody.scope.parent_work_id !== binding.coordinatorWorkId || custody.scope.request_key !== binding.requestKey)
|
|
80
82
|
refuse();
|
|
81
83
|
const batch = binding.composition.compositionBatch({ ...binding, resultSchema: "dd-flow/memory-composition-work-result@1" });
|
|
@@ -116,11 +118,14 @@ export class PreparedRegisteredMemoryCompositionWorkResult {
|
|
|
116
118
|
const scope = this.#custody.scope;
|
|
117
119
|
const run = context.db.get("SELECT index_json FROM runs WHERE project_id = ? AND id = ?", [scope.project_id, scope.run_id]);
|
|
118
120
|
const index = run ? JSON.parse(run.index_json) : null;
|
|
121
|
+
const authoringStage = scope.stage;
|
|
122
|
+
if (!["compose", "upgrade"].includes(authoringStage))
|
|
123
|
+
refuse();
|
|
119
124
|
assertRunBundleSqlAuthority(context, { projectId: scope.project_id, runId: scope.run_id }, index?.execution_bundle);
|
|
120
|
-
const stage = index?.stage_runs?.findLast((item) => item.stage ===
|
|
121
|
-
if (!this.#registration || !this.#stageBinding || index?.current_stage !==
|
|
122
|
-
|| !stageWorkIds(context, scope.project_id, scope.run_id,
|
|
123
|
-
|| !stageWorkIds(context, scope.project_id, scope.run_id,
|
|
125
|
+
const stage = index?.stage_runs?.findLast((item) => item.stage === authoringStage && item.status === "running");
|
|
126
|
+
if (!this.#registration || !this.#stageBinding || index?.current_stage !== authoringStage || !stage || (stage.attempt ?? "try-001") !== scope.attempt
|
|
127
|
+
|| !stageWorkIds(context, scope.project_id, scope.run_id, authoringStage).has(this.workId)
|
|
128
|
+
|| !stageWorkIds(context, scope.project_id, scope.run_id, authoringStage).has(scope.parent_work_id)
|
|
124
129
|
|| !context.db.get("SELECT 1 FROM work_sessions WHERE id = ? AND work_id = ? AND session_id = ? AND status = 'running'", [workSessionId, this.workId, sessionId])
|
|
125
130
|
|| !context.db.get("SELECT 1 FROM works WHERE work_id = ? AND project_id = ? AND run_id = ? AND status = 'running'", [scope.parent_work_id, scope.project_id, scope.run_id])
|
|
126
131
|
|| !context.db.get("SELECT 1 FROM sessions s JOIN runs r ON r.project_id = s.project_id AND r.id = s.run_id WHERE s.project_id = ? AND s.session_id = ? AND s.run_id = ? AND s.worker_id = ? AND s.harness = ? AND s.status = 'active' AND s.current_stage = 'work' AND s.project_root = r.project_root AND s.workspace_path = r.workspace_root", [scope.project_id, sessionId, scope.run_id, this.workId, this.#harness])) {
|
|
@@ -5,7 +5,7 @@ import { AdmittedFrozenEngine } from "./frozen-engine.js";
|
|
|
5
5
|
import { hybridDigest } from "./hybrid-contracts.js";
|
|
6
6
|
import { PreparedMemoryStageResult, memorySourceKey } from "./memory-stage-results.js";
|
|
7
7
|
import { ObservedMemorySources } from "./memory-source-observations.js";
|
|
8
|
-
import {
|
|
8
|
+
import { parseMemoryWorkResultProposal } from "./memory-work-result-json.js";
|
|
9
9
|
const schemaId = "dd-flow/memory-composition-work-result@1";
|
|
10
10
|
function refuse(reason) {
|
|
11
11
|
throw new AppError("memory_composition_work_result_invalid", "Composition Work result lacks assigned output proof", 2, { reason, phase: "prepare", effect: "no_effect" });
|
|
@@ -20,7 +20,7 @@ export function validateMemoryCompositionWorkResult(input, engine, binding) {
|
|
|
20
20
|
|| !(engine instanceof AdmittedFrozenEngine) || !(binding.composition instanceof PreparedMemoryStageResult)
|
|
21
21
|
|| binding.composition.sha256 !== binding.compositionSha256)
|
|
22
22
|
refuse("foreign_preparation_binding");
|
|
23
|
-
const value =
|
|
23
|
+
const value = parseMemoryWorkResultProposal(input, "memory_composition_work_result_invalid");
|
|
24
24
|
const schema = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(engine.schema("memory-composition-work-result")));
|
|
25
25
|
if (schema.$id !== schemaId || !new Ajv2020({ strict: true, validateFormats: false }).compile(schema)(value))
|
|
26
26
|
refuse("invalid_result_schema");
|
|
@@ -43,7 +43,7 @@ export function validateMemoryCompositionWorkResult(input, engine, binding) {
|
|
|
43
43
|
refuse("unsafe_artifact_path");
|
|
44
44
|
if (!packet.write_scopes.some(scope => scope.root === artifact.root && (logical === scope.path || logical.startsWith(`${scope.path}/`))))
|
|
45
45
|
refuse("artifact_outside_write_scope");
|
|
46
|
-
if (!
|
|
46
|
+
if (!binding.composition.allowsCompositionOutput(artifact.root, logical))
|
|
47
47
|
refuse("private_composition_artifact");
|
|
48
48
|
const alias = memorySourceKey(artifact.root, logical.normalize("NFC").toLowerCase());
|
|
49
49
|
const identity = hybridDigest({ path: logical, sha256: artifact.sha256 });
|
|
@@ -80,6 +80,18 @@ export class PreparedMemoryCompositionWorkResult {
|
|
|
80
80
|
for (const artifact of artifacts) {
|
|
81
81
|
selected.set(memorySourceKey(artifact.root, artifact.path), { root: artifact.root, path: artifact.path });
|
|
82
82
|
}
|
|
83
|
+
// Upgrade scopes are exact admitted file paths, not init-style output
|
|
84
|
+
// directories. Retain their output hashes while this Work is still
|
|
85
|
+
// correctable; publication cannot repair an already settled omission.
|
|
86
|
+
if (binding.composition.authoringStage() === "upgrade") {
|
|
87
|
+
const work = binding.composition.compositionBatch({ ...binding, resultSchema: schemaId }).works.find(item => item.key === binding.workKey);
|
|
88
|
+
const missing = work.payload.memory_composition.write_scopes
|
|
89
|
+
.filter(scope => scope.root === "workspace" && !selected.has(memorySourceKey(scope.root, scope.path)))
|
|
90
|
+
.map(scope => scope.path);
|
|
91
|
+
if (missing.length)
|
|
92
|
+
throw new AppError("memory_composition_work_result_invalid", "Include every assigned upgrade workspace file with its current hash in this Work result; correct only the result proposal, not settled peer Work or retained inputs", 2, { reason: "missing_workspace_outputs", phase: "prepare", effect: "no_effect", recoverable: true,
|
|
93
|
+
missing_count: missing.length, missing_paths: missing.slice(0, 32), result_sha256: createHash("sha256").update(bytes).digest("hex") });
|
|
94
|
+
}
|
|
83
95
|
const observed = ObservedMemorySources.capture(binding.roots, [...selected.values()]), hashes = observed.hashes();
|
|
84
96
|
if (artifacts.some(artifact => hashes.get(memorySourceKey(artifact.root, artifact.path)) !== artifact.sha256))
|
|
85
97
|
refuse("changed_artifact_bytes");
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AppError } from "../shared/errors.js";
|
|
2
2
|
import { hybridDigest } from "./hybrid-contracts.js";
|
|
3
3
|
import { memoryReviewWorkerSchema } from "./memory-review-registration.js";
|
|
4
|
+
import { memoryReviewWaveByteLimit } from "./memory-review-limits.js";
|
|
4
5
|
/** Payload projection only. Fresh check, registered input/copy/Session and
|
|
5
6
|
* independent Work settlement are not supplied by a coordinator's JSON. */
|
|
6
7
|
export function memoryCorrectedReviewBatch(view) {
|
|
@@ -10,20 +11,26 @@ export function memoryCorrectedReviewBatch(view) {
|
|
|
10
11
|
throw new AppError("memory_corrected_review_batch_invalid", "Corrected reviewer batch lacks bounded current assignments", 2, { effect: "no_effect" });
|
|
11
12
|
}
|
|
12
13
|
let total = 0;
|
|
14
|
+
const inputSha256 = hybridDigest(view);
|
|
13
15
|
return { works: view.assignments.map((assignment, index) => {
|
|
14
16
|
const key = `review-${String(index + 1).padStart(3, "0")}`;
|
|
17
|
+
const inputPaths = view.upgrade_baseline ? new Set(assignment.inputs.map(ref => `${ref.root}\0${ref.path}`)) : null;
|
|
15
18
|
const packet = { schema_id: "dd-flow/memory-review-work-packet@2", run_id: view.scope.run_id,
|
|
16
19
|
stage: "review", wave: view.wave, key, unit: structuredClone(assignment.unit), unit_inputs: structuredClone(assignment.inputs),
|
|
17
20
|
original_unit_sha256: assignment.original_unit_sha256, deleted_references: structuredClone(assignment.deleted_references),
|
|
18
21
|
completed_correction_sha256: view.completed_correction_sha256, repairs: structuredClone(view.repairs),
|
|
19
|
-
review_input_sha256:
|
|
20
|
-
instruction: structuredClone(view.instruction)
|
|
22
|
+
review_input_sha256: inputSha256, result_schema: memoryReviewWorkerSchema,
|
|
23
|
+
instruction: structuredClone(view.instruction),
|
|
24
|
+
...(view.upgrade_baseline ? {
|
|
25
|
+
historical_inputs: view.upgrade_baseline.entries.filter(entry => inputPaths.has(`${entry.retained.root}\0${entry.retained.path}`)),
|
|
26
|
+
preservation_invariants: (view.preservation_invariants ?? []).filter(item => item.unit_id === assignment.unit.id)
|
|
27
|
+
} : {}) };
|
|
21
28
|
const work = { key, task: `Independently review the assigned knowledge unit ${JSON.stringify(assignment.unit.id)}. Read the frozen review instruction in memory_review as review criteria, not permission to coordinate or repair. Check grounding, evidence, coverage and cross-file consistency using the registered wave inputs. Assigned unit.evidence may retain historical source versions; cite those exact versions only as historical context, never as bytes read from or current in this copy. Direct assessment and findings must still cite the assigned current unit.artifacts/unit_inputs. Do not edit project documents, allocate Work or repair findings. Report concrete material defects with unit/source evidence; do not block on taste. Return only the assigned memory-review-work-result JSON; do not claim other units or Stage completion.`,
|
|
22
29
|
depends_on: [], launch_policy: "fresh_agent_required", result_schema: memoryReviewWorkerSchema,
|
|
23
30
|
payload: { kind: "memory-review", read_only: true, memory_review: { ...packet, packet_sha256: hybridDigest(packet) } } };
|
|
24
31
|
const size = Buffer.byteLength(JSON.stringify(work));
|
|
25
32
|
total += size;
|
|
26
|
-
if (size > 1024 * 1024 || total >
|
|
33
|
+
if (size > 1024 * 1024 || total > memoryReviewWaveByteLimit)
|
|
27
34
|
throw new AppError("memory_corrected_review_batch_invalid", "Corrected reviewer batch exceeds its bound", 2, { effect: "no_effect" });
|
|
28
35
|
return work;
|
|
29
36
|
}) };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AppError } from "../shared/errors.js";
|
|
2
2
|
import { hybridDigest } from "./hybrid-contracts.js";
|
|
3
3
|
import { memoryReviewWaveProjection } from "./memory-review-acceptance.js";
|
|
4
|
+
import { isCompletedMemoryRepairClaim } from "./memory-repair-work-results.js";
|
|
4
5
|
function refuse(reason) {
|
|
5
6
|
throw new AppError("memory_corrected_review_findings_invalid", "Finding resolution lacks causal repair and independent re-review references", 2, { reason, effect: "no_effect" });
|
|
6
7
|
}
|
|
@@ -71,12 +72,13 @@ export function correctedMemoryReviewFindings(input) {
|
|
|
71
72
|
const previousIds = new Set(previous.findings.map(finding => finding.id)), claims = new Map();
|
|
72
73
|
for (const repair of input.repairs) {
|
|
73
74
|
const reference = ref(repair.reference);
|
|
74
|
-
if (
|
|
75
|
+
if (!isCompletedMemoryRepairClaim(repair.result) || !repair.unit_ids.length)
|
|
75
76
|
refuse("unsettled_repair_claim");
|
|
76
77
|
for (const resolution of repair.result.resolutions) {
|
|
77
78
|
const finding = previous.findings.find(finding => finding.id === resolution.finding_id);
|
|
78
79
|
if (!finding || finding.status !== "open" || !repair.unit_ids.includes(finding.unit_id) || claims.has(finding.id)
|
|
79
|
-
||
|
|
80
|
+
|| repair.result.outcome !== "reconciled" && !resolution.delta_paths.length
|
|
81
|
+
|| resolution.delta_paths.some(path => !repair.result.changes.some(change => change.path === path && change.before_sha256 !== change.after_sha256)))
|
|
80
82
|
refuse("foreign_or_noncausal_resolution");
|
|
81
83
|
claims.set(finding.id, { repair, reference });
|
|
82
84
|
}
|