@cassiomc1/forgeloop 1.14.0 → 2.0.0-rc.4
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/CONTRIBUTING.md +11 -7
- package/DOCS_INDEX.md +2 -1
- package/ENG/c-development-eng.md +2 -2
- package/ENG/cpp-development-eng.md +2 -2
- package/ENG/dotnet-aspnetcore-development-eng.md +7 -1
- package/ENG/flutter-development-eng.md +4 -2
- package/ENG/go-development-eng.md +2 -2
- package/ENG/java-development-eng.md +3 -3
- package/ENG/nodejs-backend-development-eng.md +8 -4
- package/ENG/php-development-eng.md +3 -3
- package/ENG/python-development-eng.md +205 -0
- package/ENG/rust-development-eng.md +42 -1
- package/ENG/sql-development-eng.md +4 -4
- package/ENG/swift-development-eng.md +5 -4
- package/ENG/test-code-eng.md +1 -1
- package/ENG/typescript-development-eng.md +4 -4
- package/GUIDE_ROUTER.md +6 -0
- package/LICENSE +133 -21
- package/LOOP_SYSTEM_DESIGN.md +10 -10
- package/README.md +30 -40
- package/THIRD_PARTY_NOTICES.md +3 -4
- package/completions/_forgeloop +11 -3
- package/completions/forgeloop.bash +19 -3
- package/completions/forgeloop.fish +61 -1
- package/docs/AGENT_PROTOCOL_SUMMARY.md +50 -40
- package/docs/ARTIFACT_REFERENCE.md +50 -37
- package/docs/CLI_REFERENCE.md +147 -3
- package/docs/DOCUMENTATION_GUIDE.md +6 -4
- package/docs/GETTING_STARTED.md +3 -3
- package/docs/PACKAGE_CONTENTS.md +14 -13
- package/docs/RELEASE_CHECKLIST.md +110 -10
- package/docs/SELF_HOSTED_TESTS.md +121 -0
- package/docs/TROUBLESHOOTING.md +33 -0
- package/docs/documentation-manifest.json +25 -12
- package/package.json +15 -10
- package/schemas/task-bundle.schema.json +7 -2
- package/scripts/generate-agent-protocol-summary.mjs +2 -2
- package/scripts/run-mcp-clean-tests.mjs +30 -0
- package/src/adapters/agent-browser/provider.js +14 -11
- package/src/adapters/emulated-services/provider.js +11 -4
- package/src/adapters/temporary-paths.js +18 -0
- package/src/cli.js +146 -210
- package/src/commands/action-show.js +5 -2
- package/src/commands/advance.js +5 -1
- package/src/commands/baseline.js +13 -11
- package/src/commands/checkpoint-revalidate.js +24 -8
- package/src/commands/complete.js +19 -3
- package/src/commands/contract-create.js +3 -1
- package/src/commands/doctor.js +36 -13
- package/src/commands/gate-record.js +20 -31
- package/src/commands/gate-revalidate.js +11 -13
- package/src/commands/handoff-list.js +6 -1
- package/src/commands/handoff-show.js +6 -1
- package/src/commands/migrate-protocol.js +2 -2
- package/src/commands/model-route.js +9 -1
- package/src/commands/next.js +7 -1
- package/src/commands/policy-diff.js +25 -4
- package/src/commands/progress.js +17 -11
- package/src/commands/record-diagnosis.js +3 -0
- package/src/commands/semantic-plan.js +26 -2
- package/src/commands/status.js +6 -1
- package/src/commands/storage-backup.js +15 -0
- package/src/commands/storage-migrate.js +5 -0
- package/src/commands/storage-migration-resume.js +5 -0
- package/src/commands/storage-migration-status.js +44 -0
- package/src/commands/storage-restore-resume.js +7 -0
- package/src/commands/storage-restore.js +8 -0
- package/src/commands/task-abandon.js +1 -1
- package/src/commands/task-list.js +8 -43
- package/src/commands/task-migrate.js +3 -3
- package/src/commands/task-recover.js +10 -2
- package/src/commands/task-scope.js +18 -2
- package/src/commands/task-show.js +16 -2
- package/src/commands/validate-protocol.js +29 -14
- package/src/commands/validate-receipt.js +8 -1
- package/src/config/guides.json +4 -0
- package/src/core/action-ledger-projection.js +10 -3
- package/src/core/actions.js +38 -75
- package/src/core/activation.js +11 -3
- package/src/core/approvals.js +17 -51
- package/src/core/artifact-registry.js +59 -2
- package/src/core/artifacts.js +81 -27
- package/src/core/attachment-paths.js +45 -0
- package/src/core/attestation-coverage.js +6 -1
- package/src/core/attestation-verifier.js +21 -2
- package/src/core/attestation.js +17 -6
- package/src/core/audit.js +6 -1
- package/src/core/bundles.js +160 -66
- package/src/core/checkpoint-revalidation.js +12 -10
- package/src/core/cli-command-definitions.js +109 -7
- package/src/core/code-manifest.js +9 -3
- package/src/core/command-executors.js +182 -126
- package/src/core/command-input.js +21 -1
- package/src/core/completion-artifacts.js +85 -36
- package/src/core/completion-ownership.js +19 -8
- package/src/core/completion-recovery-rebind.js +11 -1
- package/src/core/completion.js +60 -9
- package/src/core/conformance.js +2 -1
- package/src/core/continuity-lint.js +13 -2
- package/src/core/continuity-reconciliation.js +16 -3
- package/src/core/continuity.js +16 -8
- package/src/core/contract-bootstrap-recovery.js +42 -41
- package/src/core/contract-revision.js +8 -9
- package/src/core/decision/artifact.js +2 -1
- package/src/core/decision/events.js +9 -6
- package/src/core/decision/resolver.js +19 -11
- package/src/core/decision/service.js +63 -38
- package/src/core/decision/task-bindings.js +6 -1
- package/src/core/diagnosis-model.js +11 -0
- package/src/core/diagnosis.js +10 -1
- package/src/core/diagnostic-projection.js +29 -12
- package/src/core/diagnostic-record.js +28 -3
- package/src/core/efficiency.js +6 -1
- package/src/core/events.js +316 -97
- package/src/core/execution-prerequisites.js +22 -13
- package/src/core/execution.js +26 -40
- package/src/core/filesystem.js +11 -0
- package/src/core/gate-artifact.js +105 -19
- package/src/core/gate-provenance.js +6 -5
- package/src/core/handoff.js +26 -14
- package/src/core/history.js +39 -35
- package/src/core/hypothesis-projection.js +32 -0
- package/src/core/information-gain-projection.js +104 -100
- package/src/core/inspect.js +12 -3
- package/src/core/integration-invocation-policy.js +10 -0
- package/src/core/integration-resources.js +41 -34
- package/src/core/ledger-event-collection.js +189 -0
- package/src/core/ledger-relations.js +23 -0
- package/src/core/native-storage.js +12 -0
- package/src/core/next-action-continuity.js +31 -9
- package/src/core/next-action-phases.js +1 -1
- package/src/core/next-action-review-phase.js +2 -2
- package/src/core/next-action-verification-phase.js +2 -1
- package/src/core/next-action.js +21 -2
- package/src/core/phase.js +138 -93
- package/src/core/policy-engine.js +12 -13
- package/src/core/preflight-consistency.js +72 -59
- package/src/core/preflight-loaders.js +19 -12
- package/src/core/preflight-model.js +6 -1
- package/src/core/preflight.js +29 -12
- package/src/core/progress.js +21 -10
- package/src/core/protocol-info.js +3 -0
- package/src/core/protocol-migration.js +7 -6
- package/src/core/reconcile-closure.js +162 -24
- package/src/core/recovery-history.js +66 -47
- package/src/core/reflection.js +72 -77
- package/src/core/responsibility.js +25 -13
- package/src/core/route-artifact.js +13 -3
- package/src/core/runtime-context.js +5 -0
- package/src/core/schema-validation.js +8 -3
- package/src/core/settlement.js +12 -1
- package/src/core/signing/bundle-file.js +53 -0
- package/src/core/signing/bundle-reference.js +15 -0
- package/src/core/signing/sigstore.js +60 -33
- package/src/core/signing/statement-file.js +27 -0
- package/src/core/storage-runtime-registry.js +41 -0
- package/src/core/structural-quality/artifacts.js +20 -14
- package/src/core/structural-quality/service.js +81 -56
- package/src/core/target-layout.js +1 -1
- package/src/core/task-claim-state.js +52 -18
- package/src/core/task-command.js +21 -1
- package/src/core/task-conflict-inspection.js +112 -107
- package/src/core/task-descriptor.js +29 -22
- package/src/core/task-discovery.js +288 -90
- package/src/core/task-lock.js +66 -306
- package/src/core/task-migration-validation.js +5 -5
- package/src/core/task-migration.js +17 -287
- package/src/core/task-snapshot.js +51 -8
- package/src/core/task-storage-migration.js +18 -0
- package/src/core/templates.js +20 -8
- package/src/core/test-intelligence/prune.js +5 -1
- package/src/core/trace.js +154 -99
- package/src/core/trajectory-evaluation.js +15 -3
- package/src/core/trajectory-metrics.js +47 -32
- package/src/core/transaction.js +16 -288
- package/src/core/verification-scope.js +20 -12
- package/src/core/work-state.js +100 -59
- package/src/repository-index/binary-manager.js +15 -7
- package/src/storage/artifact-bytes.js +22 -0
- package/src/storage/attachment-binding.js +11 -0
- package/src/storage/attachment-files.js +82 -0
- package/src/storage/attachment-references.js +36 -0
- package/src/storage/backup.js +53 -0
- package/src/storage/capabilities.js +31 -0
- package/src/storage/connection-owner.js +119 -0
- package/src/storage/connection.js +376 -0
- package/src/storage/existing-project-scope.js +45 -0
- package/src/storage/export-destination.js +44 -0
- package/src/storage/exporter.js +284 -0
- package/src/storage/file-durability.js +46 -0
- package/src/storage/importer.js +461 -0
- package/src/storage/index.js +74 -0
- package/src/storage/leases.js +63 -0
- package/src/storage/ledger-event-snapshot.js +150 -0
- package/src/storage/ledger-relations.js +220 -0
- package/src/storage/legacy-maintenance-boundary.js +18 -0
- package/src/storage/legacy-signature.js +115 -0
- package/src/storage/maintenance-handoff.js +129 -0
- package/src/storage/maintenance-owner.js +42 -0
- package/src/storage/maintenance.js +310 -0
- package/src/storage/metadata-json.js +35 -0
- package/src/storage/migration-activate.js +59 -0
- package/src/storage/migration-archive.js +57 -0
- package/src/storage/migration-attachment-root.js +61 -0
- package/src/storage/migration-candidate.js +398 -0
- package/src/storage/migration-publication.js +143 -0
- package/src/storage/migration-rollback-admission.js +54 -0
- package/src/storage/migration-rollback-preparation.js +132 -0
- package/src/storage/migration-rollback-publication.js +183 -0
- package/src/storage/migration-rollback-release.js +66 -0
- package/src/storage/migration-rollback-source-stage.js +103 -0
- package/src/storage/migration-rollback.js +43 -0
- package/src/storage/migration-source-partition.js +56 -0
- package/src/storage/migration-source.js +255 -0
- package/src/storage/migration-terminal.js +39 -0
- package/src/storage/migration-validation.js +75 -0
- package/src/storage/migration.js +43 -0
- package/src/storage/operational-attachments.js +49 -0
- package/src/storage/operational-context.js +50 -0
- package/src/storage/project-backup.js +98 -0
- package/src/storage/project-bootstrap.js +24 -0
- package/src/storage/project-boundary.js +79 -0
- package/src/storage/project-read-snapshot.js +18 -0
- package/src/storage/project-replacement.js +137 -0
- package/src/storage/project-restore.js +203 -0
- package/src/storage/record-definitions.js +45 -0
- package/src/storage/repository.js +507 -0
- package/src/storage/restore-execution-provenance.js +26 -0
- package/src/storage/restore-journal.js +62 -0
- package/src/storage/restore-layout.js +30 -0
- package/src/storage/restore-preparation.js +84 -0
- package/src/storage/restore-replacement-archive.js +162 -0
- package/src/storage/restore-replacement-owner.js +31 -0
- package/src/storage/restore-replacement-preparation.js +128 -0
- package/src/storage/restore-replacement.js +142 -0
- package/src/storage/restore-terminal.js +32 -0
- package/src/storage/runtime.js +31 -0
- package/src/storage/schema.js +214 -0
- package/src/storage/singleton-import.js +84 -0
- package/src/storage/snapshot-continuity.js +16 -0
- package/src/storage/snapshot.js +36 -0
- package/src/storage/storage-marker.js +79 -0
- package/src/storage/task-guards.js +107 -0
- package/src/storage/topology.js +42 -0
- package/src/storage/transaction.js +179 -0
- package/src/storage/unit-of-work.js +667 -0
- package/src/storage/windows-process-incarnation.js +206 -0
- package/.forgeloop/forgeloop.gitignore +0 -4
- package/LICENSE-DOCS.md +0 -13
- package/src/core/transaction-maintenance.js +0 -70
package/src/core/phase.js
CHANGED
|
@@ -63,6 +63,39 @@ const LATE_PHASES = new Set([
|
|
|
63
63
|
"COMPLETE",
|
|
64
64
|
]);
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Pure prerequisite for the `DIAGNOSING -> CORRECTING` edge.
|
|
68
|
+
*
|
|
69
|
+
* Extracted so the filesystem advance and the SQLite store advance enforce the
|
|
70
|
+
* identical rule through one owner. The store implementation must not restate
|
|
71
|
+
* this; it calls this function with its own loaded evidence.
|
|
72
|
+
*
|
|
73
|
+
* Throws `E_DIAGNOSIS_REQUIRED` when no append-only diagnosis exists for the
|
|
74
|
+
* active verification cycle, and `E_DIAGNOSIS_NO_NEW_INFORMATION` when the
|
|
75
|
+
* diagnosis carries no new information.
|
|
76
|
+
*/
|
|
77
|
+
export function assertDiagnosingToCorrectingTransition({ state, events, resolveDiagnosis }) {
|
|
78
|
+
const cycle = state?.verificationCycle ?? 1;
|
|
79
|
+
const diagEvent = resolveDiagnosis(events, state?.taskId, cycle);
|
|
80
|
+
if (!diagEvent) {
|
|
81
|
+
throw phaseError(
|
|
82
|
+
"E_DIAGNOSIS_REQUIRED",
|
|
83
|
+
"DIAGNOSING -> CORRECTING requires an append-only diagnosis record for the active verification cycle",
|
|
84
|
+
[],
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
const structured = diagEvent.sourceModel === "STRUCTURED_DIAGNOSTIC_CASE_V1";
|
|
88
|
+
const gainClassification = structured ? null : diagEvent.details?.informationGain;
|
|
89
|
+
if (!diagEvent.details || (structured ? false : gainClassification === "NONE")) {
|
|
90
|
+
throw phaseError(
|
|
91
|
+
"E_DIAGNOSIS_NO_NEW_INFORMATION",
|
|
92
|
+
"The proposed retry repeats the previous hypothesis with the same evidence without new information",
|
|
93
|
+
[],
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
return diagEvent;
|
|
97
|
+
}
|
|
98
|
+
|
|
66
99
|
async function readPhaseIdentityArtifacts(target, state, toPhase, packageRoot, options, paths) {
|
|
67
100
|
const scopedTaskId = options.taskId ?? null;
|
|
68
101
|
const requireContract = LATE_PHASES.has(state.phase)
|
|
@@ -75,6 +108,7 @@ async function readPhaseIdentityArtifacts(target, state, toPhase, packageRoot, o
|
|
|
75
108
|
try {
|
|
76
109
|
contract = await readContract(target, packageRoot, { taskId: scopedTaskId, contractPath: options.contractPath });
|
|
77
110
|
} catch (error) {
|
|
111
|
+
if (error.code === "E_STATE_REVISION_CONFLICT") throw error;
|
|
78
112
|
if (error.code === "ARTIFACT_MISSING" && !requireContract) {
|
|
79
113
|
contract = null;
|
|
80
114
|
} else {
|
|
@@ -88,6 +122,7 @@ async function readPhaseIdentityArtifacts(target, state, toPhase, packageRoot, o
|
|
|
88
122
|
try {
|
|
89
123
|
route = await readPersistedRoute(target, packageRoot, { taskId: scopedTaskId, routePath: options.routePath });
|
|
90
124
|
} catch (error) {
|
|
125
|
+
if (error.code === "E_STATE_REVISION_CONFLICT") throw error;
|
|
91
126
|
if (error.code === "ARTIFACT_MISSING" && !requireRoute) {
|
|
92
127
|
route = null;
|
|
93
128
|
} else {
|
|
@@ -185,24 +220,24 @@ function isRevisionPlanRepeat(events, eventType) {
|
|
|
185
220
|
&& isRevisionEpochPlanRepeat(events, events.length, { event: eventType });
|
|
186
221
|
}
|
|
187
222
|
|
|
223
|
+
async function assertRequiredPhaseArtifact(read, toPhase, relativePath) {
|
|
224
|
+
try { await read(); }
|
|
225
|
+
catch (error) {
|
|
226
|
+
if (error.code === "E_STATE_REVISION_CONFLICT") throw error;
|
|
227
|
+
throw phaseError("E_PHASE_PREREQUISITE_MISSING", `Phase ${toPhase} requires ${relativePath}: ${error.message}`, [relativePath]);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
188
231
|
async function assertPhasePrerequisites(target, state, toPhase, packageRoot, authorityContext, runtimeContext, options = {}) {
|
|
189
232
|
const scopedTaskId = options.taskId ?? null;
|
|
190
233
|
const contractRel = options.contractPath ?? (scopedTaskId ? taskArtifactPath(scopedTaskId, "contract") : ARTIFACT_PATHS.contract);
|
|
191
234
|
const routeRel = options.routePath ?? (scopedTaskId ? taskArtifactPath(scopedTaskId, "route") : ARTIFACT_PATHS.route);
|
|
192
235
|
|
|
193
236
|
if (toPhase === "CONTRACT_READY" || toPhase === "ROUTED" || toPhase === "EXECUTING") {
|
|
194
|
-
|
|
195
|
-
await readContract(target, packageRoot, { taskId: scopedTaskId, contractPath: options.contractPath });
|
|
196
|
-
} catch (error) {
|
|
197
|
-
throw phaseError("E_PHASE_PREREQUISITE_MISSING", `Phase ${toPhase} requires ${contractRel}: ${error.message}`, [contractRel]);
|
|
198
|
-
}
|
|
237
|
+
await assertRequiredPhaseArtifact(() => readContract(target, packageRoot, { taskId: scopedTaskId, contractPath: options.contractPath }), toPhase, contractRel);
|
|
199
238
|
}
|
|
200
239
|
if (toPhase === "ROUTED" || toPhase === "EXECUTING") {
|
|
201
|
-
|
|
202
|
-
await readPersistedRoute(target, packageRoot, { taskId: scopedTaskId, routePath: options.routePath });
|
|
203
|
-
} catch (error) {
|
|
204
|
-
throw phaseError("E_PHASE_PREREQUISITE_MISSING", `Phase ${toPhase} requires ${routeRel}: ${error.message}`, [routeRel]);
|
|
205
|
-
}
|
|
240
|
+
await assertRequiredPhaseArtifact(() => readPersistedRoute(target, packageRoot, { taskId: scopedTaskId, routePath: options.routePath }), toPhase, routeRel);
|
|
206
241
|
}
|
|
207
242
|
if (toPhase === "EXECUTING" && scopedTaskId) {
|
|
208
243
|
try {
|
|
@@ -215,6 +250,7 @@ async function assertPhasePrerequisites(target, state, toPhase, packageRoot, aut
|
|
|
215
250
|
try {
|
|
216
251
|
await assertExecutionPrerequisites({ target, state, packageRoot, ...options, taskId: scopedTaskId, runtimeContext });
|
|
217
252
|
} catch (error) {
|
|
253
|
+
if (error.code === "E_STATE_REVISION_CONFLICT") throw error;
|
|
218
254
|
throw phaseError(error.code, error.message, error.artifacts);
|
|
219
255
|
}
|
|
220
256
|
}
|
|
@@ -247,8 +283,75 @@ async function appendRepairRouteCheckpoint({ target, packageRoot, taskId, state,
|
|
|
247
283
|
}, packageRoot, { taskId, eventsPath: eventsRel });
|
|
248
284
|
}
|
|
249
285
|
|
|
286
|
+
/** Shared receipt refresh used by phase advancement on either storage source. */
|
|
287
|
+
export async function preparePhaseReceipt({ target, packageRoot, taskId, state, next, receiptRel, contractPath = null, routePath = null, statePath = null, authorityContext, runtimeContext, readers = null }) {
|
|
288
|
+
let nextReceipt = null;
|
|
289
|
+
try {
|
|
290
|
+
const receipt = await (readers?.readReceipt ?? readJsonArtifact)(target, receiptRel, "execution-receipt", packageRoot);
|
|
291
|
+
const contract = await (readers?.readContract ?? readContract)(target, packageRoot, { taskId, contractPath });
|
|
292
|
+
const route = await (readers?.readRoute ?? readPersistedRoute)(target, packageRoot, { taskId, routePath });
|
|
293
|
+
const preflight = await evaluatePreflight({ target, packageRoot, taskId, contractPath, routePath, statePath, readers });
|
|
294
|
+
const requiredEvidence = await requiredEvidenceForTarget({
|
|
295
|
+
target,
|
|
296
|
+
contract,
|
|
297
|
+
route,
|
|
298
|
+
packageRoot,
|
|
299
|
+
additionalEvidence: preflight.policy?.requiredEvidence ?? [],
|
|
300
|
+
});
|
|
301
|
+
await validateReceipt(receipt.value, packageRoot, {
|
|
302
|
+
target,
|
|
303
|
+
taskId: contract?.value?.taskId,
|
|
304
|
+
authorityContext,
|
|
305
|
+
runtimeContext,
|
|
306
|
+
});
|
|
307
|
+
assertCompletionRelationships({
|
|
308
|
+
contract,
|
|
309
|
+
route,
|
|
310
|
+
state,
|
|
311
|
+
receipt: receipt.value,
|
|
312
|
+
requiredEvidence,
|
|
313
|
+
requireRequiredChecks: false,
|
|
314
|
+
requireReceiptStateFingerprint: false,
|
|
315
|
+
target,
|
|
316
|
+
taskId: contract?.value?.taskId,
|
|
317
|
+
authorityContext,
|
|
318
|
+
runtimeContext,
|
|
319
|
+
});
|
|
320
|
+
nextReceipt = await createReceipt({
|
|
321
|
+
...receipt.value,
|
|
322
|
+
stateFingerprint: canonicalFingerprint(next),
|
|
323
|
+
verificationCycle: next.verificationCycle ?? receipt.value.verificationCycle ?? 1,
|
|
324
|
+
}, packageRoot, {
|
|
325
|
+
target,
|
|
326
|
+
taskId: contract?.value?.taskId,
|
|
327
|
+
authorityContext,
|
|
328
|
+
runtimeContext,
|
|
329
|
+
});
|
|
330
|
+
assertCompletionRelationships({
|
|
331
|
+
contract,
|
|
332
|
+
route,
|
|
333
|
+
state: next,
|
|
334
|
+
receipt: nextReceipt,
|
|
335
|
+
requiredEvidence,
|
|
336
|
+
requireRequiredChecks: false,
|
|
337
|
+
target,
|
|
338
|
+
taskId: contract?.value?.taskId,
|
|
339
|
+
authorityContext,
|
|
340
|
+
runtimeContext,
|
|
341
|
+
});
|
|
342
|
+
} catch (error) {
|
|
343
|
+
if (error.code !== "ARTIFACT_MISSING") throw error;
|
|
344
|
+
}
|
|
345
|
+
return nextReceipt;
|
|
346
|
+
}
|
|
347
|
+
|
|
250
348
|
export async function advanceWorkState(target, toPhase, options = {}) {
|
|
251
|
-
|
|
349
|
+
assertWorkPhase(toPhase);
|
|
350
|
+
let normalizedOptions = typeof options === "string" ? { packageRoot: options } : options;
|
|
351
|
+
if (!normalizedOptions.taskId) {
|
|
352
|
+
const state = await readWorkState(target, normalizedOptions);
|
|
353
|
+
normalizedOptions = { ...normalizedOptions, taskId: state?.taskId };
|
|
354
|
+
}
|
|
252
355
|
if (!normalizedOptions.taskId || await getTaskTransaction(target)) {
|
|
253
356
|
return advanceWorkStateInternal(target, toPhase, normalizedOptions);
|
|
254
357
|
}
|
|
@@ -274,8 +377,6 @@ async function advanceWorkStateInternal(target, toPhase, normalizedOptions) {
|
|
|
274
377
|
receiptPath = null,
|
|
275
378
|
eventsPath = null,
|
|
276
379
|
} = normalizedOptions;
|
|
277
|
-
assertWorkPhase(toPhase);
|
|
278
|
-
|
|
279
380
|
let state = await readWorkState(target, { packageRoot, taskId, statePath });
|
|
280
381
|
const stateRel = statePath ?? (taskId ? taskArtifactPath(taskId, "state") : ARTIFACT_PATHS.state);
|
|
281
382
|
if (!state) throw phaseError("E_PHASE_PREREQUISITE_MISSING", "Cannot advance without work state", [stateRel]);
|
|
@@ -392,35 +493,35 @@ async function advanceWorkStateInternal(target, toPhase, normalizedOptions) {
|
|
|
392
493
|
}
|
|
393
494
|
}
|
|
394
495
|
if (toPhase === "CORRECTING" && state.phase === "DIAGNOSING") {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
496
|
+
// Shared rule owner: the same pure prerequisite used by the SQLite store
|
|
497
|
+
// advance, so the two backends cannot drift on this edge.
|
|
498
|
+
let cycle = state.verificationCycle ?? 1;
|
|
499
|
+
let diagEvent;
|
|
500
|
+
try {
|
|
501
|
+
diagEvent = assertDiagnosingToCorrectingTransition({
|
|
502
|
+
state,
|
|
503
|
+
events: ledger.events,
|
|
504
|
+
resolveDiagnosis: resolveCurrentCycleDiagnostic,
|
|
505
|
+
});
|
|
506
|
+
} catch (error) {
|
|
507
|
+
throw phaseError(error.code, error.message, [eventsRel]);
|
|
403
508
|
}
|
|
404
|
-
|
|
509
|
+
cycle = state.verificationCycle ?? 1;
|
|
405
510
|
if (diagEvent.sourceModel === "STRUCTURED_DIAGNOSTIC_CASE_V1") {
|
|
406
|
-
//
|
|
511
|
+
// The shared rule intentionally omits the structured-stall evaluation,
|
|
512
|
+
// which depends on progress/reflect projections. Re-check it here so the
|
|
513
|
+
// filesystem behavior is unchanged.
|
|
407
514
|
const stall = evaluateStructuredDiagnosticStall(
|
|
408
515
|
buildInformationGainProjection(ledger.events, state.taskId),
|
|
409
516
|
{ verificationCycle: cycle },
|
|
410
517
|
);
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|| (diagEvent.sourceModel !== "STRUCTURED_DIAGNOSTIC_CASE_V1" && gainClassification === "NONE")) {
|
|
419
|
-
throw phaseError(
|
|
420
|
-
"E_DIAGNOSIS_NO_NEW_INFORMATION",
|
|
421
|
-
"The proposed retry repeats the previous hypothesis with the same evidence without new information",
|
|
422
|
-
[eventsRel],
|
|
423
|
-
);
|
|
518
|
+
if (stall.stalled) {
|
|
519
|
+
throw phaseError(
|
|
520
|
+
"E_DIAGNOSIS_NO_NEW_INFORMATION",
|
|
521
|
+
"The proposed retry repeats the previous hypothesis with the same evidence without new information",
|
|
522
|
+
[eventsRel],
|
|
523
|
+
);
|
|
524
|
+
}
|
|
424
525
|
}
|
|
425
526
|
}
|
|
426
527
|
if (toPhase === "VERIFYING" && state.phase === "CORRECTING") {
|
|
@@ -479,63 +580,7 @@ async function advanceWorkStateInternal(target, toPhase, normalizedOptions) {
|
|
|
479
580
|
stateRel,
|
|
480
581
|
eventsRel,
|
|
481
582
|
});
|
|
482
|
-
|
|
483
|
-
try {
|
|
484
|
-
const receipt = await readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot);
|
|
485
|
-
const contract = await readContract(target, packageRoot, { taskId, contractPath });
|
|
486
|
-
const route = await readPersistedRoute(target, packageRoot, { taskId, routePath });
|
|
487
|
-
const preflight = await evaluatePreflight({ target, packageRoot, taskId, contractPath, routePath, statePath });
|
|
488
|
-
const requiredEvidence = await requiredEvidenceForTarget({
|
|
489
|
-
target,
|
|
490
|
-
contract,
|
|
491
|
-
route,
|
|
492
|
-
packageRoot,
|
|
493
|
-
additionalEvidence: preflight.policy?.requiredEvidence ?? [],
|
|
494
|
-
});
|
|
495
|
-
await validateReceipt(receipt.value, packageRoot, {
|
|
496
|
-
target,
|
|
497
|
-
taskId: contract?.value?.taskId,
|
|
498
|
-
authorityContext,
|
|
499
|
-
runtimeContext,
|
|
500
|
-
});
|
|
501
|
-
assertCompletionRelationships({
|
|
502
|
-
contract,
|
|
503
|
-
route,
|
|
504
|
-
state,
|
|
505
|
-
receipt: receipt.value,
|
|
506
|
-
requiredEvidence,
|
|
507
|
-
requireRequiredChecks: false,
|
|
508
|
-
requireReceiptStateFingerprint: false,
|
|
509
|
-
target,
|
|
510
|
-
taskId: contract?.value?.taskId,
|
|
511
|
-
authorityContext,
|
|
512
|
-
runtimeContext,
|
|
513
|
-
});
|
|
514
|
-
nextReceipt = await createReceipt({
|
|
515
|
-
...receipt.value,
|
|
516
|
-
stateFingerprint: canonicalFingerprint(next),
|
|
517
|
-
verificationCycle: next.verificationCycle ?? receipt.value.verificationCycle ?? 1,
|
|
518
|
-
}, packageRoot, {
|
|
519
|
-
target,
|
|
520
|
-
taskId: contract?.value?.taskId,
|
|
521
|
-
authorityContext,
|
|
522
|
-
runtimeContext,
|
|
523
|
-
});
|
|
524
|
-
assertCompletionRelationships({
|
|
525
|
-
contract,
|
|
526
|
-
route,
|
|
527
|
-
state: next,
|
|
528
|
-
receipt: nextReceipt,
|
|
529
|
-
requiredEvidence,
|
|
530
|
-
requireRequiredChecks: false,
|
|
531
|
-
target,
|
|
532
|
-
taskId: contract?.value?.taskId,
|
|
533
|
-
authorityContext,
|
|
534
|
-
runtimeContext,
|
|
535
|
-
});
|
|
536
|
-
} catch (error) {
|
|
537
|
-
if (error.code !== "ARTIFACT_MISSING") throw error;
|
|
538
|
-
}
|
|
583
|
+
const nextReceipt = await preparePhaseReceipt({ target, packageRoot, taskId, state, next, receiptRel, contractPath, routePath, statePath, authorityContext, runtimeContext });
|
|
539
584
|
await mutateWorkState(target, {
|
|
540
585
|
expectedRevision: state.revision ?? 0,
|
|
541
586
|
packageRoot,
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { withNativeReadScope } from "./native-storage.js";
|
|
1
2
|
import { readFile } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { fileExists } from "./filesystem.js";
|
|
4
5
|
import { PROJECT_ARTIFACT_PATHS, taskArtifactPath } from "./task-paths.js";
|
|
5
6
|
import { assertJsonLimits } from "./json-safety.js";
|
|
6
7
|
import { assertSchema, readSchema } from "./schema-validation.js";
|
|
7
|
-
import { canonicalFingerprint, writeJsonArtifact } from "./artifacts.js";
|
|
8
|
+
import { canonicalFingerprint, readJsonArtifact, writeJsonArtifact } from "./artifacts.js";
|
|
8
9
|
import { sha256 } from "./manifest.js";
|
|
9
10
|
import { BUILTIN_POLICY_RULES, discoverPolicy } from "./policy-discovery.js";
|
|
10
11
|
import { getPolicyAdapter } from "./policy-adapters.js";
|
|
@@ -82,24 +83,22 @@ export async function writePolicyLock(target, lock, packageRoot) {
|
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
export async function readTaskPolicySnapshot(target, taskId, packageRoot) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
assertSchema(parsed, schema, "policy-snapshot");
|
|
95
|
-
return parsed;
|
|
86
|
+
return withNativeReadScope(target, async () => {
|
|
87
|
+
const relPath = taskArtifactPath(taskId, "policySnapshot");
|
|
88
|
+
try {
|
|
89
|
+
return (await readJsonArtifact(target, relPath, "policy-snapshot", packageRoot)).value;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (error.code === "ARTIFACT_MISSING") return null;
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
96
95
|
}
|
|
97
96
|
|
|
98
97
|
export async function writeTaskPolicySnapshot(target, taskId, snapshot, packageRoot) {
|
|
99
98
|
const relPath = taskArtifactPath(taskId, "policySnapshot");
|
|
100
99
|
const schema = await readSchema("policy-snapshot", packageRoot);
|
|
101
100
|
assertSchema(snapshot, schema, "policy-snapshot");
|
|
102
|
-
await writeJsonArtifact(target, relPath, snapshot, "policy-snapshot", packageRoot);
|
|
101
|
+
await writeJsonArtifact(target, relPath, snapshot, "policy-snapshot", packageRoot, { taskId });
|
|
103
102
|
return snapshot;
|
|
104
103
|
}
|
|
105
104
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ARTIFACT_PATHS } from "./artifacts.js";
|
|
2
2
|
import { readContract } from "./contract.js";
|
|
3
3
|
import { readPersistedRoute } from "./route-artifact.js";
|
|
4
|
-
import { appendProtocolEvent, validateEventLedger } from "./events.js";
|
|
4
|
+
import { appendProtocolEvent, validateEventLedger, withEventLedgerAudit } from "./events.js";
|
|
5
5
|
import { readWorkState } from "./work-state.js";
|
|
6
6
|
import { assertStateIdentity } from "./completion-relationships.js";
|
|
7
7
|
import { ensureResumableState, synchronizePreflightState } from "./resumability.js";
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
validatePersistedPreflight,
|
|
15
15
|
} from "./preflight-model.js";
|
|
16
16
|
|
|
17
|
+
import { ledgerEventsOfTypes, ledgerTypeSummary } from "./ledger-event-collection.js";
|
|
17
18
|
import { taskArtifactPath } from "./task-paths.js";
|
|
18
19
|
|
|
19
20
|
async function readOptionalIdentityArtifact(readArtifact, invalidCode, artifactPath) {
|
|
@@ -81,15 +82,15 @@ function sameReadyPreflightEvent(event, result) {
|
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
function latestContractRevisionSeq(events) {
|
|
84
|
-
return events
|
|
85
|
+
return ledgerTypeSummary(events, "CONTRACT_REVISED").latest?.seq ?? 0;
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
function hasCurrentRevisionGate(events, taskId, gate) {
|
|
88
89
|
const revisionSeq = latestContractRevisionSeq(events);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
for (const event of ledgerEventsOfTypes(events, ["GATE_SATISFIED"])) {
|
|
91
|
+
if (event.taskId === taskId && event.details?.gate === gate && event.seq > revisionSeq) return true;
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
export async function validateReadyProtocolConsistency({
|
|
@@ -102,68 +103,80 @@ export async function validateReadyProtocolConsistency({
|
|
|
102
103
|
} = {}) {
|
|
103
104
|
if (persisted?.status !== "READY") return [];
|
|
104
105
|
const result = current ?? await evaluateCurrentPreflight({ target, packageRoot, taskId });
|
|
105
|
-
|
|
106
|
+
return withEventLedgerAudit(target, packageRoot, { taskId }, async ledger => {
|
|
107
|
+
const errors = [...validatePersistedPreflight(persisted, result)];
|
|
106
108
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
const stateRel = taskId ? taskArtifactPath(taskId, "state") : ARTIFACT_PATHS.state;
|
|
110
|
+
const eventsRel = taskId ? taskArtifactPath(taskId, "events") : ARTIFACT_PATHS.events;
|
|
111
|
+
const preflightRel = taskId ? taskArtifactPath(taskId, "preflight") : ARTIFACT_PATHS.preflight;
|
|
112
|
+
const contractRel = taskId ? taskArtifactPath(taskId, "contract") : ARTIFACT_PATHS.contract;
|
|
113
|
+
const routeRel = taskId ? taskArtifactPath(taskId, "route") : ARTIFACT_PATHS.route;
|
|
114
|
+
const gatesRel = taskId ? taskArtifactPath(taskId, "gates") : ARTIFACT_PATHS.gates;
|
|
113
115
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
if (!state) {
|
|
121
|
-
errors.push(issue(
|
|
122
|
-
"E_STATE_MISSING_AFTER_PREFLIGHT_READY",
|
|
123
|
-
"A persisted READY preflight must have a resumable work-state checkpoint",
|
|
124
|
-
[preflightRel, stateRel],
|
|
125
|
-
));
|
|
126
|
-
} else {
|
|
127
|
-
if (state.taskId !== persisted.taskId) {
|
|
128
|
-
errors.push(issue("E_STATE_TASK_MISMATCH", "The resumable checkpoint does not belong to the READY preflight task", [stateRel, preflightRel]));
|
|
116
|
+
let state = null;
|
|
117
|
+
try {
|
|
118
|
+
state = await readWorkState(target, { packageRoot, taskId });
|
|
119
|
+
} catch (error) {
|
|
120
|
+
errors.push(issue("E_STATE_INVALID", error.message, [stateRel]));
|
|
129
121
|
}
|
|
130
|
-
if (state
|
|
131
|
-
errors.push(issue(
|
|
122
|
+
if (!state) {
|
|
123
|
+
errors.push(issue(
|
|
124
|
+
"E_STATE_MISSING_AFTER_PREFLIGHT_READY",
|
|
125
|
+
"A persisted READY preflight must have a resumable work-state checkpoint",
|
|
126
|
+
[preflightRel, stateRel],
|
|
127
|
+
));
|
|
128
|
+
} else {
|
|
129
|
+
if (state.taskId !== persisted.taskId) {
|
|
130
|
+
errors.push(issue("E_STATE_TASK_MISMATCH", "The resumable checkpoint does not belong to the READY preflight task", [stateRel, preflightRel]));
|
|
131
|
+
}
|
|
132
|
+
if (state.contractFingerprint !== result.fingerprints.contract) {
|
|
133
|
+
errors.push(issue("E_CONTRACT_STALE", "The resumable checkpoint does not match the READY contract fingerprint", [stateRel, contractRel]));
|
|
134
|
+
}
|
|
135
|
+
if (state.routeFingerprint !== result.fingerprints.routing) {
|
|
136
|
+
errors.push(issue("E_ROUTE_STALE", "The resumable checkpoint does not match the READY routing fingerprint", [stateRel, routeRel]));
|
|
137
|
+
}
|
|
138
|
+
if (JSON.stringify(state.selectedGuides) !== JSON.stringify(result.routing.guides)) {
|
|
139
|
+
errors.push(issue("E_ROUTE_GUIDE_MISMATCH", "The resumable checkpoint guides do not match the READY routing result", [stateRel, routeRel]));
|
|
140
|
+
}
|
|
141
|
+
if (!sameStringSet(state.requiredGates ?? [], persisted.requiredGates)
|
|
142
|
+
|| !sameStringSet(state.satisfiedGates ?? [], persisted.satisfiedGates)) {
|
|
143
|
+
errors.push(issue("E_PREFLIGHT_GATES_STALE", "The resumable checkpoint gate sets do not match the READY preflight", [stateRel, preflightRel, gatesRel]));
|
|
144
|
+
}
|
|
132
145
|
}
|
|
133
|
-
|
|
134
|
-
|
|
146
|
+
|
|
147
|
+
if (!ledger.valid) {
|
|
148
|
+
errors.push(...ledger.errors.map((error) => issue(error.code ?? "E_EVENT_INVALID", error.message, [eventsRel])));
|
|
135
149
|
}
|
|
136
|
-
|
|
137
|
-
|
|
150
|
+
const events = ledger.events ?? [];
|
|
151
|
+
const requiredEvents = ["CONTRACT_VALIDATED", "ROUTE_VALIDATED"];
|
|
152
|
+
const observedEvents = new Set();
|
|
153
|
+
for (const event of ledgerEventsOfTypes(events, requiredEvents)) {
|
|
154
|
+
if (event.taskId === persisted.taskId) observedEvents.add(event.event);
|
|
138
155
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
156
|
+
for (const requiredEvent of requiredEvents) {
|
|
157
|
+
if (!observedEvents.has(requiredEvent)) {
|
|
158
|
+
errors.push(issue("E_PREFLIGHT_EVENT_MISSING", `READY preflight is missing lifecycle event: ${requiredEvent}`, [eventsRel, preflightRel]));
|
|
159
|
+
}
|
|
142
160
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
errors.push(...ledger.errors.map((error) => issue(error.code ?? "E_EVENT_INVALID", error.message, [eventsRel])));
|
|
148
|
-
}
|
|
149
|
-
const events = ledger.events ?? [];
|
|
150
|
-
for (const requiredEvent of ["CONTRACT_VALIDATED", "ROUTE_VALIDATED"]) {
|
|
151
|
-
if (!events.some((event) => event.event === requiredEvent && event.taskId === persisted.taskId)) {
|
|
152
|
-
errors.push(issue("E_PREFLIGHT_EVENT_MISSING", `READY preflight is missing lifecycle event: ${requiredEvent}`, [eventsRel, preflightRel]));
|
|
161
|
+
for (const gate of persisted.satisfiedGates ?? []) {
|
|
162
|
+
if (!hasCurrentRevisionGate(events, persisted.taskId, gate)) {
|
|
163
|
+
errors.push(issue("E_PREFLIGHT_GATE_EVENT_MISSING", `READY preflight is missing lifecycle gate event: ${gate}`, [eventsRel, `${gatesRel}/${gate}.json`]));
|
|
164
|
+
}
|
|
153
165
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
166
|
+
let readyCount = 0;
|
|
167
|
+
let readyMatches = false;
|
|
168
|
+
for (const event of ledgerEventsOfTypes(events, ["PREFLIGHT_READY"])) {
|
|
169
|
+
if (event.taskId !== persisted.taskId) continue;
|
|
170
|
+
readyCount++;
|
|
171
|
+
if (sameReadyPreflightEvent(event, result)) readyMatches = true;
|
|
158
172
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
errors
|
|
165
|
-
}
|
|
166
|
-
return sortIssues(errors);
|
|
173
|
+
if (readyCount === 0) {
|
|
174
|
+
errors.push(issue("E_PREFLIGHT_READY_EVENT_MISSING", "Persisted READY preflight is missing the matching PREFLIGHT_READY lifecycle event", [preflightRel, eventsRel]));
|
|
175
|
+
} else if (!readyMatches) {
|
|
176
|
+
errors.push(issue("E_PREFLIGHT_READY_EVENT_MISMATCH", "PREFLIGHT_READY lifecycle details do not match the persisted READY preflight", [preflightRel, eventsRel]));
|
|
177
|
+
}
|
|
178
|
+
return sortIssues(errors);
|
|
179
|
+
});
|
|
167
180
|
}
|
|
168
181
|
|
|
169
182
|
function sameBlockedPreflightEvent(event, result) {
|
|
@@ -41,7 +41,7 @@ export async function optionalConfig(target, packageRoot, errors) {
|
|
|
41
41
|
|
|
42
42
|
export async function loadContract(target, packageRoot, errors, options = {}) {
|
|
43
43
|
try {
|
|
44
|
-
return await readContract(target, packageRoot, options);
|
|
44
|
+
return await (options.readers?.readContract ?? readContract)(target, packageRoot, options);
|
|
45
45
|
} catch (error) {
|
|
46
46
|
const path = options.contractPath ?? (options.taskId ? taskArtifactPath(options.taskId, "contract") : ARTIFACT_PATHS.contract);
|
|
47
47
|
errors.push(issue(error.code === "ARTIFACT_MISSING" ? "E_CONTRACT_MISSING" : "E_CONTRACT_INVALID", error.message, [path]));
|
|
@@ -51,7 +51,7 @@ export async function loadContract(target, packageRoot, errors, options = {}) {
|
|
|
51
51
|
|
|
52
52
|
export async function loadRoute(target, packageRoot, errors, options = {}) {
|
|
53
53
|
try {
|
|
54
|
-
return await readPersistedRoute(target, packageRoot, options);
|
|
54
|
+
return await (options.readers?.readRoute ?? readPersistedRoute)(target, packageRoot, options);
|
|
55
55
|
} catch (error) {
|
|
56
56
|
const path = options.routePath ?? (options.taskId ? taskArtifactPath(options.taskId, "route") : ARTIFACT_PATHS.route);
|
|
57
57
|
const code = error.code === "ARTIFACT_MISSING"
|
|
@@ -89,29 +89,36 @@ export async function loadSources(target, contract, packageRoot, errors) {
|
|
|
89
89
|
return registry;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
if (!route) return { required: [], satisfied: [], records: {} };
|
|
94
|
-
const guideGates = await requiredGatesForGuides(route.value.guides, packageRoot);
|
|
95
|
-
const required = [...new Set([...guideGates, ...(config.requiredGates ?? [])])].sort();
|
|
96
|
-
const satisfied = [];
|
|
97
|
-
const records = {};
|
|
98
|
-
const taskId = options.taskId ?? null;
|
|
92
|
+
async function readGateEvents(target, packageRoot, taskId, options) {
|
|
99
93
|
let events = [];
|
|
100
94
|
if (taskId) {
|
|
101
95
|
try {
|
|
102
|
-
events = await readEvents(target, packageRoot, { taskId });
|
|
96
|
+
events = await (options.readers?.readEvents ?? readEvents)(target, packageRoot, { taskId });
|
|
103
97
|
} catch {
|
|
104
98
|
events = [];
|
|
105
99
|
}
|
|
106
100
|
}
|
|
101
|
+
return events;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function inspectGates(target, contract, route, packageRoot, errors, config = {}, options = {}) {
|
|
105
|
+
if (!route) return { required: [], satisfied: [], records: {} };
|
|
106
|
+
const guideGates = await requiredGatesForGuides(route.value.guides, packageRoot);
|
|
107
|
+
const required = [...new Set([...guideGates, ...(config.requiredGates ?? [])])].sort();
|
|
108
|
+
const satisfied = [];
|
|
109
|
+
const records = {};
|
|
110
|
+
const taskId = options.taskId ?? null;
|
|
111
|
+
const events = await readGateEvents(target, packageRoot, taskId, options);
|
|
107
112
|
const latestContractRevisionSeq = events.findLast((event) => event.event === "CONTRACT_REVISED")?.seq ?? 0;
|
|
108
113
|
for (const gate of required) {
|
|
109
114
|
let artifact;
|
|
110
115
|
const defaultGateRel = taskId ? taskGatePath(taskId, gate) : `${ARTIFACT_PATHS.gates}/${gate}.json`;
|
|
111
116
|
try {
|
|
112
|
-
artifact = await readGateIfPresent(target, gate, packageRoot, { ...options, taskId });
|
|
117
|
+
artifact = await (options.readers?.readGate ?? readGateIfPresent)(target, gate, packageRoot, { ...options, taskId });
|
|
113
118
|
} catch (error) {
|
|
114
|
-
|
|
119
|
+
const code = error.code === "E_STORAGE_PAYLOAD_MISMATCH" && error.taskIdentityMismatch ? "E_GATE_TASK_MISMATCH"
|
|
120
|
+
: error.code === "ARTIFACT_MISSING" ? "E_GATE_UNVERIFIED" : "E_GATE_INVALID";
|
|
121
|
+
errors.push(issue(code, error.message, [defaultGateRel], { gate }));
|
|
115
122
|
continue;
|
|
116
123
|
}
|
|
117
124
|
if (!artifact) {
|
|
@@ -31,7 +31,12 @@ export function sameStringSet(left, right) {
|
|
|
31
31
|
&& [...leftSet].every((value) => rightSet.has(value));
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
export function validatePersistedPreflight(persisted, current) {
|
|
34
|
+
export function validatePersistedPreflight(persisted, current, readError = null) {
|
|
35
|
+
if (readError?.taskIdentityMismatch) return [issue("E_PREFLIGHT_TASK_MISMATCH", "Persisted preflight does not belong to the current task", [ARTIFACT_PATHS.preflight, ARTIFACT_PATHS.contract])];
|
|
36
|
+
return validatePersistedPreflightPayload(persisted, current);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function validatePersistedPreflightPayload(persisted, current) {
|
|
35
40
|
const errors = [];
|
|
36
41
|
if (persisted?.status !== "READY") {
|
|
37
42
|
if (Array.isArray(persisted?.errors) && persisted.errors.length > 0) {
|