@cassiomc1/forgeloop 1.0.0 → 1.1.0
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/.cursor/rules/project-loop.mdc +3 -2
- package/.github/copilot-instructions.md +1 -0
- package/AGENTS.md +2 -1
- package/CLAUDE.md +1 -0
- package/DOCS_INDEX.md +36 -0
- package/ENG/design-code-eng.md +31 -0
- package/ENG/documentation-quality-eng.md +400 -0
- package/EXECUTION_STATE.md +23 -13
- package/GUIDE_ROUTER.md +23 -8
- package/LOOP_ENGINEERING.md +77 -12
- package/PROTOCOL_INTEGRATION.md +9 -6
- package/README.md +82 -39
- package/docs/ARTIFACT_REFERENCE.md +442 -0
- package/docs/CLI_REFERENCE.md +892 -0
- package/docs/CROSS_HARNESS_CONTINUITY.md +198 -0
- package/docs/DOCUMENTATION_GUIDE.md +161 -0
- package/docs/GETTING_STARTED.md +348 -0
- package/docs/RECIPES.md +250 -0
- package/docs/TROUBLESHOOTING.md +345 -0
- package/docs/assets/forgeloop-flow.svg +1 -1
- package/docs/forgeloop-flow.mmd +1 -1
- package/package.json +13 -2
- package/schemas/task-descriptor.schema.json +56 -0
- package/schemas/work-state.schema.json +18 -1
- package/scripts/CI_VALIDATORS.md +7 -0
- package/src/cli.js +280 -388
- package/src/commands/advance.js +5 -2
- package/src/commands/audit.js +11 -1
- package/src/commands/clear-continuity.js +5 -2
- package/src/commands/clear-state.js +5 -2
- package/src/commands/complete.js +9 -1
- package/src/commands/continuity.js +5 -2
- package/src/commands/inspect.js +10 -2
- package/src/commands/next.js +5 -2
- package/src/commands/preflight.js +9 -1
- package/src/commands/prepare-completion.js +5 -2
- package/src/commands/reconcile-continuity.js +5 -2
- package/src/commands/record-check.js +7 -1
- package/src/commands/record-continuity.js +21 -14
- package/src/commands/record-terminal-result.js +7 -1
- package/src/commands/route.js +22 -18
- package/src/commands/run-check.js +52 -44
- package/src/commands/status.js +18 -12
- package/src/commands/task-create.js +94 -0
- package/src/commands/task-list.js +48 -0
- package/src/commands/task-migrate.js +34 -0
- package/src/commands/task-scope.js +75 -0
- package/src/commands/task-show.js +81 -0
- package/src/commands/task-unlock.js +35 -0
- package/src/commands/validate-protocol.js +37 -20
- package/src/commands/validate-state.js +24 -18
- package/src/config/guides.json +42 -0
- package/src/core/activation.js +8 -4
- package/src/core/artifact-registry.js +166 -0
- package/src/core/audit.js +65 -12
- package/src/core/bundles.js +76 -50
- package/src/core/cli-command-definitions.js +611 -0
- package/src/core/cli-metadata.js +23 -0
- package/src/core/completion-artifacts.js +161 -74
- package/src/core/completion.js +134 -76
- package/src/core/continuity.js +20 -13
- package/src/core/contract.js +6 -3
- package/src/core/error-codes.js +197 -0
- package/src/core/events.js +19 -14
- package/src/core/execution.js +38 -6
- package/src/core/gate-artifact.js +12 -9
- package/src/core/gates.js +4 -2
- package/src/core/guide-metadata.js +7 -11
- package/src/core/guide-registry.js +29 -0
- package/src/core/inspect.js +7 -4
- package/src/core/native-adapters.js +6 -0
- package/src/core/phase.js +85 -33
- package/src/core/preflight-consistency.js +24 -14
- package/src/core/preflight-loaders.js +16 -11
- package/src/core/preflight.js +44 -25
- package/src/core/protocol.js +2 -11
- package/src/core/receipt.js +1 -1
- package/src/core/report.js +2 -2
- package/src/core/repository.js +46 -12
- package/src/core/resumability.js +6 -4
- package/src/core/route-artifact.js +9 -5
- package/src/core/router.js +11 -7
- package/src/core/schema-validation.js +1 -0
- package/src/core/task-command.js +41 -0
- package/src/core/task-context.js +126 -0
- package/src/core/task-descriptor.js +81 -0
- package/src/core/task-discovery.js +116 -0
- package/src/core/task-identity.js +76 -0
- package/src/core/task-lock.js +209 -0
- package/src/core/task-migration-validation.js +140 -0
- package/src/core/task-migration.js +361 -0
- package/src/core/task-paths.js +96 -0
- package/src/core/task-scope.js +179 -0
- package/src/core/templates.js +3 -9
- package/src/core/work-state.js +24 -13
package/src/core/completion.js
CHANGED
|
@@ -10,7 +10,8 @@ import { completionRelationshipErrors } from "./completion-relationships.js";
|
|
|
10
10
|
import { assertSafePath, ensureWithin, fileExists } from "./filesystem.js";
|
|
11
11
|
import { evaluateStartExecutionPrerequisites, hasExecutionStarted } from "./execution-prerequisites.js";
|
|
12
12
|
import { isRecoverableCompletionEvidenceCode } from "./completion-recovery.js";
|
|
13
|
-
import {
|
|
13
|
+
import { evaluateTerminalRequirements } from "./evidence-readiness.js";
|
|
14
|
+
import { taskArtifactPath } from "./task-paths.js";
|
|
14
15
|
|
|
15
16
|
function issue(code, message, artifacts = [], details = {}) {
|
|
16
17
|
return { code, message, artifacts, ...details };
|
|
@@ -56,6 +57,7 @@ function repairNext(error) {
|
|
|
56
57
|
return "Split ordinary verification from terminal lifecycle criteria.";
|
|
57
58
|
case "E_PUBLICATION_REQUIREMENT_PENDING":
|
|
58
59
|
return "The contract explicitly requires publication. Record authoritative publication evidence or revise the contract if publication is not in scope.";
|
|
60
|
+
case "E_PRODUCTION_READINESS_REQUIREMENT_PENDING":
|
|
59
61
|
case "E_PRODUCTION_REQUIREMENT_PENDING":
|
|
60
62
|
return "The contract explicitly requires production readiness. Record authoritative deployment/readiness evidence before completion.";
|
|
61
63
|
case "E_TERMINAL_REQUIREMENT_PENDING":
|
|
@@ -99,28 +101,25 @@ function withRepairGuidance(error) {
|
|
|
99
101
|
message: error.message ?? String(error),
|
|
100
102
|
artifacts: error.artifacts ?? [],
|
|
101
103
|
};
|
|
102
|
-
if (
|
|
103
|
-
return normalized;
|
|
104
|
+
if (normalized.next) return normalized;
|
|
105
|
+
return { ...normalized, next: repairNext(normalized) };
|
|
104
106
|
}
|
|
105
107
|
|
|
106
|
-
function sortIssues(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
`${error.code}\0${(error.artifacts ?? []).join("\0")}\0${error.message}`,
|
|
111
|
-
error,
|
|
112
|
-
];
|
|
113
|
-
})).values()];
|
|
114
|
-
return unique.sort((left, right) => left.code.localeCompare(right.code)
|
|
115
|
-
|| left.artifacts.join("\0").localeCompare(right.artifacts.join("\0"))
|
|
116
|
-
|| left.message.localeCompare(right.message));
|
|
108
|
+
function sortIssues(issues) {
|
|
109
|
+
return issues
|
|
110
|
+
.map(withRepairGuidance)
|
|
111
|
+
.sort((a, b) => (a.code || "").localeCompare(b.code || "") || (a.message || "").localeCompare(b.message || ""));
|
|
117
112
|
}
|
|
118
113
|
|
|
119
|
-
async function loadRequired(
|
|
114
|
+
async function loadRequired(readArtifact, errorCode, errorMessage, artifacts, errors) {
|
|
120
115
|
try {
|
|
121
|
-
return await
|
|
116
|
+
return await readArtifact();
|
|
122
117
|
} catch (error) {
|
|
123
|
-
|
|
118
|
+
if (error.code === "ARTIFACT_MISSING") {
|
|
119
|
+
errors.push(issue(errorCode, errorMessage, artifacts));
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
errors.push(issue(error.code ?? errorCode, error.message ?? errorMessage, artifacts));
|
|
124
123
|
return null;
|
|
125
124
|
}
|
|
126
125
|
}
|
|
@@ -133,54 +132,75 @@ function publicationStatus(receipt) {
|
|
|
133
132
|
return receipt.changedPaths?.length > 0 ? "local-only" : "not-published";
|
|
134
133
|
}
|
|
135
134
|
|
|
136
|
-
async function validateLedger(target,
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
135
|
+
async function validateLedger(target, scopedTaskId, contractTaskId, state, errors, packageRoot, options = {}) {
|
|
136
|
+
const eventsRel = options.eventsPath ?? (scopedTaskId ? taskArtifactPath(scopedTaskId, "events") : ARTIFACT_PATHS.events);
|
|
137
|
+
const stateRel = options.statePath ?? (scopedTaskId ? taskArtifactPath(scopedTaskId, "state") : ARTIFACT_PATHS.state);
|
|
138
|
+
await assertSafePath(target, eventsRel);
|
|
139
|
+
const eventsFilePath = ensureWithin(target, eventsRel);
|
|
140
|
+
if (!(await fileExists(eventsFilePath))) {
|
|
141
|
+
errors.push(issue("E_PHASE_CHRONOLOGY_INVALID", "Protocol event ledger is required before completion", [eventsRel]));
|
|
141
142
|
return { valid: false, events: [], errors: [] };
|
|
142
143
|
}
|
|
143
|
-
const ledger = await validateEventLedger(target, packageRoot);
|
|
144
|
-
for (const error of ledger.errors) errors.push({ ...error, artifacts: [
|
|
144
|
+
const ledger = await validateEventLedger(target, packageRoot, { taskId: scopedTaskId, eventsPath: options.eventsPath });
|
|
145
|
+
for (const error of ledger.errors) errors.push({ ...error, artifacts: [eventsRel] });
|
|
145
146
|
for (const error of validateStateLedgerCoherence(state, ledger.events)) {
|
|
146
|
-
errors.push({ ...error, artifacts: [
|
|
147
|
+
errors.push({ ...error, artifacts: [stateRel, eventsRel] });
|
|
147
148
|
}
|
|
148
149
|
const requiredEvents = LIFECYCLE_MILESTONES.slice(0, state?.phase === "COMPLETE" ? undefined : -1);
|
|
149
|
-
const observed = new Set(ledger.events.filter((event) => event.taskId ===
|
|
150
|
+
const observed = new Set(ledger.events.filter((event) => event.taskId === contractTaskId).map((event) => event.event));
|
|
150
151
|
for (const event of requiredEvents) {
|
|
151
152
|
if (!observed.has(event)) {
|
|
152
|
-
errors.push(issue("E_PHASE_CHRONOLOGY_INVALID", `Required protocol event is missing: ${event}`, [
|
|
153
|
+
errors.push(issue("E_PHASE_CHRONOLOGY_INVALID", `Required protocol event is missing: ${event}`, [eventsRel], { event }));
|
|
153
154
|
}
|
|
154
155
|
}
|
|
155
|
-
if (ledger.events.some((event) => event.taskId !==
|
|
156
|
-
errors.push(issue("E_PHASE_CHRONOLOGY_INVALID", "Protocol events must belong to the current task", [
|
|
156
|
+
if (ledger.events.some((event) => event.taskId !== contractTaskId)) {
|
|
157
|
+
errors.push(issue("E_PHASE_CHRONOLOGY_INVALID", "Protocol events must belong to the current task", [eventsRel]));
|
|
157
158
|
}
|
|
158
159
|
if (state?.phase !== "COMPLETE" && observed.has("COMPLETION_VALIDATED")) {
|
|
159
|
-
errors.push(issue("E_PHASE_CHRONOLOGY_INVALID", "COMPLETION_VALIDATED requires COMPLETE state", [
|
|
160
|
+
errors.push(issue("E_PHASE_CHRONOLOGY_INVALID", "COMPLETION_VALIDATED requires COMPLETE state", [eventsRel]));
|
|
160
161
|
}
|
|
161
162
|
return ledger;
|
|
162
163
|
}
|
|
163
164
|
|
|
164
|
-
export async function evaluateCompletion({
|
|
165
|
+
export async function evaluateCompletion({
|
|
166
|
+
target,
|
|
167
|
+
packageRoot,
|
|
168
|
+
strict = false,
|
|
169
|
+
authorityContext,
|
|
170
|
+
runtimeContext,
|
|
171
|
+
taskId = null,
|
|
172
|
+
contractPath = null,
|
|
173
|
+
routePath = null,
|
|
174
|
+
statePath = null,
|
|
175
|
+
receiptPath = null,
|
|
176
|
+
eventsPath = null,
|
|
177
|
+
preflightPath = null,
|
|
178
|
+
} = {}) {
|
|
165
179
|
const errors = [];
|
|
166
|
-
const preflight = await evaluatePreflight({ target, packageRoot, strict });
|
|
180
|
+
const preflight = await evaluatePreflight({ target, packageRoot, strict, taskId, contractPath, routePath, statePath });
|
|
167
181
|
errors.push(...preflight.errors);
|
|
182
|
+
|
|
183
|
+
const contractRel = contractPath ?? (taskId ? taskArtifactPath(taskId, "contract") : ARTIFACT_PATHS.contract);
|
|
184
|
+
const routeRel = routePath ?? (taskId ? taskArtifactPath(taskId, "route") : ARTIFACT_PATHS.route);
|
|
185
|
+
const stateRel = statePath ?? (taskId ? taskArtifactPath(taskId, "state") : ARTIFACT_PATHS.state);
|
|
186
|
+
const receiptRel = receiptPath ?? (taskId ? taskArtifactPath(taskId, "receipt") : ARTIFACT_PATHS.receipt);
|
|
187
|
+
|
|
168
188
|
const contract = await loadRequired(
|
|
169
|
-
() => readContract(target, packageRoot),
|
|
189
|
+
() => readContract(target, packageRoot, { taskId, contractPath }),
|
|
170
190
|
"E_CONTRACT_MISSING",
|
|
171
191
|
"Current contract is not available",
|
|
172
|
-
[
|
|
192
|
+
[contractRel],
|
|
173
193
|
errors,
|
|
174
194
|
);
|
|
175
195
|
const route = await loadRequired(
|
|
176
|
-
() => readPersistedRoute(target, packageRoot),
|
|
196
|
+
() => readPersistedRoute(target, packageRoot, { taskId, routePath }),
|
|
177
197
|
"E_ROUTE_MISSING",
|
|
178
198
|
"Persisted routing result is not available",
|
|
179
|
-
[
|
|
199
|
+
[routeRel],
|
|
180
200
|
errors,
|
|
181
201
|
);
|
|
182
202
|
const state = await loadRequired(
|
|
183
|
-
() => readWorkState(target, packageRoot).then((value) => {
|
|
203
|
+
() => readWorkState(target, { packageRoot, taskId, statePath }).then((value) => {
|
|
184
204
|
if (!value) {
|
|
185
205
|
const error = new Error("Work state is missing");
|
|
186
206
|
error.code = "ARTIFACT_MISSING";
|
|
@@ -190,19 +210,19 @@ export async function evaluateCompletion({ target, packageRoot, strict = false,
|
|
|
190
210
|
}),
|
|
191
211
|
"E_STATE_MISSING",
|
|
192
212
|
"Work state is not available",
|
|
193
|
-
[
|
|
213
|
+
[stateRel],
|
|
194
214
|
errors,
|
|
195
215
|
);
|
|
196
216
|
const receipt = await loadRequired(
|
|
197
|
-
() => readJsonArtifact(target,
|
|
217
|
+
() => readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot),
|
|
198
218
|
"E_RECEIPT_MISSING",
|
|
199
219
|
"Execution receipt is not available",
|
|
200
|
-
[
|
|
220
|
+
[receiptRel],
|
|
201
221
|
errors,
|
|
202
222
|
);
|
|
203
223
|
|
|
204
224
|
if (state && hasExecutionStarted(state.phase)) {
|
|
205
|
-
const prerequisites = await evaluateStartExecutionPrerequisites({ target, state, packageRoot });
|
|
225
|
+
const prerequisites = await evaluateStartExecutionPrerequisites({ target, state, packageRoot, taskId, statePath, contractPath, routePath });
|
|
206
226
|
errors.push(...prerequisites.errors);
|
|
207
227
|
}
|
|
208
228
|
|
|
@@ -215,27 +235,35 @@ export async function evaluateCompletion({ target, packageRoot, strict = false,
|
|
|
215
235
|
runtimeContext,
|
|
216
236
|
});
|
|
217
237
|
} catch (error) {
|
|
218
|
-
errors.push(issue(error.code ?? "E_RECEIPT_INVALID", `Execution receipt is invalid: ${error.message}`, [
|
|
238
|
+
errors.push(issue(error.code ?? "E_RECEIPT_INVALID", `Execution receipt is invalid: ${error.message}`, [receiptRel]));
|
|
219
239
|
}
|
|
220
240
|
}
|
|
221
241
|
|
|
222
242
|
if (contract) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
243
|
+
try {
|
|
244
|
+
await validateChecksExecutionProvenance(state?.checks, {
|
|
245
|
+
target,
|
|
246
|
+
packageRoot,
|
|
247
|
+
taskId: contract.value.taskId,
|
|
248
|
+
artifactPath: stateRel,
|
|
249
|
+
});
|
|
250
|
+
} catch (error) {
|
|
251
|
+
errors.push(issue(error.code ?? "E_EXECUTION_REF_INVALID", error.message, error.artifacts ?? [stateRel]));
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
await validateChecksExecutionProvenance(receipt?.value?.checks, {
|
|
255
|
+
target,
|
|
256
|
+
packageRoot,
|
|
257
|
+
taskId: contract.value.taskId,
|
|
258
|
+
artifactPath: receiptRel,
|
|
259
|
+
});
|
|
260
|
+
} catch (error) {
|
|
261
|
+
errors.push(issue(error.code ?? "E_EXECUTION_REF_INVALID", error.message, error.artifacts ?? [receiptRel]));
|
|
262
|
+
}
|
|
235
263
|
}
|
|
236
264
|
|
|
237
265
|
if (state && !["REVIEWING", "COMPLETE"].includes(state.phase)) {
|
|
238
|
-
errors.push(issue("E_PHASE_PREREQUISITE_MISSING", `Completion requires REVIEWING or COMPLETE state, found ${state.phase}`, [
|
|
266
|
+
errors.push(issue("E_PHASE_PREREQUISITE_MISSING", `Completion requires REVIEWING or COMPLETE state, found ${state.phase}`, [stateRel]));
|
|
239
267
|
}
|
|
240
268
|
|
|
241
269
|
let coverage = [];
|
|
@@ -263,7 +291,7 @@ export async function evaluateCompletion({ target, packageRoot, strict = false,
|
|
|
263
291
|
}
|
|
264
292
|
|
|
265
293
|
const ledger = contract && state
|
|
266
|
-
? await validateLedger(target, contract.value.taskId, state, errors, packageRoot)
|
|
294
|
+
? await validateLedger(target, taskId, contract.value.taskId, state, errors, packageRoot, { eventsPath, statePath })
|
|
267
295
|
: { valid: false, events: [], errors: [] };
|
|
268
296
|
|
|
269
297
|
if (state) {
|
|
@@ -271,15 +299,15 @@ export async function evaluateCompletion({ target, packageRoot, strict = false,
|
|
|
271
299
|
const classification = await classifyLoadedWorkState({
|
|
272
300
|
target,
|
|
273
301
|
state,
|
|
274
|
-
contractFile:
|
|
302
|
+
contractFile: contractRel,
|
|
275
303
|
});
|
|
276
304
|
if (classification.status === "REVALIDATION_REQUIRED") {
|
|
277
305
|
for (const reason of classification.reasons) {
|
|
278
|
-
errors.push(issue(reason === "CONTRACT_CHANGED" ? "E_CONTRACT_STALE" : "E_PHASE_ARTIFACT_STALE", `State freshness check failed: ${reason}`, [
|
|
306
|
+
errors.push(issue(reason === "CONTRACT_CHANGED" ? "E_CONTRACT_STALE" : "E_PHASE_ARTIFACT_STALE", `State freshness check failed: ${reason}`, [stateRel]));
|
|
279
307
|
}
|
|
280
308
|
}
|
|
281
309
|
} catch (error) {
|
|
282
|
-
errors.push(issue("E_PHASE_ARTIFACT_STALE", error.message, [
|
|
310
|
+
errors.push(issue("E_PHASE_ARTIFACT_STALE", error.message, [stateRel]));
|
|
283
311
|
}
|
|
284
312
|
}
|
|
285
313
|
|
|
@@ -299,7 +327,7 @@ export async function evaluateCompletion({ target, packageRoot, strict = false,
|
|
|
299
327
|
errors.push(issue(
|
|
300
328
|
termErr.code,
|
|
301
329
|
termErr.message,
|
|
302
|
-
[
|
|
330
|
+
[contractRel, receiptRel],
|
|
303
331
|
{ requirementId: termErr.requirementId },
|
|
304
332
|
));
|
|
305
333
|
}
|
|
@@ -324,8 +352,38 @@ export async function evaluateCompletion({ target, packageRoot, strict = false,
|
|
|
324
352
|
};
|
|
325
353
|
}
|
|
326
354
|
|
|
327
|
-
export async function runComplete({
|
|
328
|
-
|
|
355
|
+
export async function runComplete({
|
|
356
|
+
target,
|
|
357
|
+
packageRoot,
|
|
358
|
+
strict = false,
|
|
359
|
+
persist = true,
|
|
360
|
+
authorityContext,
|
|
361
|
+
runtimeContext,
|
|
362
|
+
taskId = null,
|
|
363
|
+
contractPath = null,
|
|
364
|
+
routePath = null,
|
|
365
|
+
statePath = null,
|
|
366
|
+
receiptPath = null,
|
|
367
|
+
eventsPath = null,
|
|
368
|
+
preflightPath = null,
|
|
369
|
+
} = {}) {
|
|
370
|
+
const result = await evaluateCompletion({
|
|
371
|
+
target,
|
|
372
|
+
packageRoot,
|
|
373
|
+
strict,
|
|
374
|
+
authorityContext,
|
|
375
|
+
runtimeContext,
|
|
376
|
+
taskId,
|
|
377
|
+
contractPath,
|
|
378
|
+
routePath,
|
|
379
|
+
statePath,
|
|
380
|
+
receiptPath,
|
|
381
|
+
eventsPath,
|
|
382
|
+
preflightPath,
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const receiptRel = receiptPath ?? (taskId ? taskArtifactPath(taskId, "receipt") : ARTIFACT_PATHS.receipt);
|
|
386
|
+
|
|
329
387
|
const rejectionCodes = [...new Set(result.errors.map((error) => error.code))].sort();
|
|
330
388
|
const evidenceOnlyRejection = rejectionCodes.length > 0
|
|
331
389
|
&& rejectionCodes.every(isRecoverableCompletionEvidenceCode);
|
|
@@ -336,7 +394,7 @@ export async function runComplete({ target, packageRoot, strict = false, persist
|
|
|
336
394
|
"E_AUTHORITY_UNTRUSTED_SOURCE",
|
|
337
395
|
].includes(code));
|
|
338
396
|
if (persist && result.status === "REJECTED" && evidenceOnlyRejection && !authorityRejection) {
|
|
339
|
-
const state = await readWorkState(target, packageRoot);
|
|
397
|
+
const state = await readWorkState(target, { packageRoot, taskId, statePath });
|
|
340
398
|
if (state?.phase === "REVIEWING") {
|
|
341
399
|
const reasonCodes = rejectionCodes;
|
|
342
400
|
const missingRequirementIds = [...new Set(result.errors.map((error) => error.requirementId).filter(Boolean))].sort();
|
|
@@ -364,11 +422,11 @@ export async function runComplete({ target, packageRoot, strict = false, persist
|
|
|
364
422
|
};
|
|
365
423
|
let receipt = null;
|
|
366
424
|
try {
|
|
367
|
-
receipt = await readJsonArtifact(target,
|
|
425
|
+
receipt = await readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot);
|
|
368
426
|
} catch {
|
|
369
427
|
// The evaluator already reports a missing or invalid receipt; evidence-only rejection can persist without one.
|
|
370
428
|
}
|
|
371
|
-
await writeWorkState(target, next, { packageRoot });
|
|
429
|
+
await writeWorkState(target, next, { packageRoot, taskId, statePath });
|
|
372
430
|
let nextReceipt = null;
|
|
373
431
|
if (receipt) {
|
|
374
432
|
nextReceipt = await createReceipt({
|
|
@@ -376,9 +434,9 @@ export async function runComplete({ target, packageRoot, strict = false, persist
|
|
|
376
434
|
stateFingerprint: canonicalFingerprint(next),
|
|
377
435
|
verificationCycle: next.verificationCycle ?? receipt.value.verificationCycle ?? 1,
|
|
378
436
|
}, packageRoot, { target, taskId: state.taskId, authorityContext, runtimeContext });
|
|
379
|
-
await writeJsonArtifact(target,
|
|
437
|
+
await writeJsonArtifact(target, receiptRel, nextReceipt, "execution-receipt", packageRoot);
|
|
380
438
|
}
|
|
381
|
-
const ledger = await validateEventLedger(target, packageRoot);
|
|
439
|
+
const ledger = await validateEventLedger(target, packageRoot, { taskId, eventsPath });
|
|
382
440
|
const lastRejection = ledger.events.filter((e) => e.taskId === state.taskId && e.event === "COMPLETION_REJECTED").at(-1);
|
|
383
441
|
const lastDetails = lastRejection?.details ?? {};
|
|
384
442
|
const isIdentical = lastRejection
|
|
@@ -399,14 +457,14 @@ export async function runComplete({ target, packageRoot, strict = false, persist
|
|
|
399
457
|
stateFingerprint: canonicalFingerprint(next),
|
|
400
458
|
...(nextReceipt ? { receiptFingerprint: canonicalFingerprint(nextReceipt) } : {}),
|
|
401
459
|
},
|
|
402
|
-
}, packageRoot);
|
|
460
|
+
}, packageRoot, { taskId, eventsPath });
|
|
403
461
|
}
|
|
404
462
|
}
|
|
405
463
|
}
|
|
406
464
|
if (persist && result.status === "VALID") {
|
|
407
|
-
const state = await readWorkState(target, packageRoot);
|
|
465
|
+
const state = await readWorkState(target, { packageRoot, taskId, statePath });
|
|
408
466
|
if (state && state.phase !== "COMPLETE") {
|
|
409
|
-
const receipt = await readJsonArtifact(target,
|
|
467
|
+
const receipt = await readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot);
|
|
410
468
|
const next = {
|
|
411
469
|
...state,
|
|
412
470
|
previousPhase: state.phase,
|
|
@@ -421,13 +479,13 @@ export async function runComplete({ target, packageRoot, strict = false, persist
|
|
|
421
479
|
stateFingerprint: canonicalFingerprint(next),
|
|
422
480
|
verificationCycle: next.verificationCycle ?? receipt.value.verificationCycle ?? 1,
|
|
423
481
|
}, packageRoot, { target, taskId: state.taskId, authorityContext, runtimeContext });
|
|
424
|
-
await writeWorkState(target, next, { packageRoot });
|
|
425
|
-
await writeJsonArtifact(target,
|
|
482
|
+
await writeWorkState(target, next, { packageRoot, taskId, statePath });
|
|
483
|
+
await writeJsonArtifact(target, receiptRel, nextReceipt, "execution-receipt", packageRoot);
|
|
426
484
|
}
|
|
427
|
-
const contract = await readContract(target, packageRoot);
|
|
428
|
-
const ledger = await validateEventLedger(target, packageRoot);
|
|
429
|
-
if (!ledger.events.some((event) => event.event === "COMPLETION_VALIDATED")) {
|
|
430
|
-
await appendProtocolEvent(target, { taskId: contract.value.taskId, event: "COMPLETION_VALIDATED" }, packageRoot);
|
|
485
|
+
const contract = await readContract(target, packageRoot, { taskId, contractPath });
|
|
486
|
+
const ledger = await validateEventLedger(target, packageRoot, { taskId, eventsPath });
|
|
487
|
+
if (!ledger.events.some((event) => event.taskId === contract.value.taskId && event.event === "COMPLETION_VALIDATED")) {
|
|
488
|
+
await appendProtocolEvent(target, { taskId: contract.value.taskId, event: "COMPLETION_VALIDATED" }, packageRoot, { taskId, eventsPath });
|
|
431
489
|
}
|
|
432
490
|
}
|
|
433
491
|
return result;
|
package/src/core/continuity.js
CHANGED
|
@@ -11,6 +11,7 @@ import { assertSafePath, ensureWithin, fileExists } from "./filesystem.js";
|
|
|
11
11
|
import { PROTOCOL_VERSION, WORK_PHASES } from "./protocol.js";
|
|
12
12
|
import { assertSecretFree } from "./receipt.js";
|
|
13
13
|
import { getPackageRoot } from "./templates.js";
|
|
14
|
+
import { taskArtifactPath } from "./task-paths.js";
|
|
14
15
|
|
|
15
16
|
export const CONTINUITY_PATH = ARTIFACT_PATHS.continuity;
|
|
16
17
|
export const CONTINUITY_SCHEMA_VERSION = 1;
|
|
@@ -182,8 +183,12 @@ export function createContinuity(input) {
|
|
|
182
183
|
});
|
|
183
184
|
}
|
|
184
185
|
|
|
185
|
-
export async function readContinuity(target,
|
|
186
|
-
const
|
|
186
|
+
export async function readContinuity(target, options = {}) {
|
|
187
|
+
const packageRoot = typeof options === "string" ? options : (options?.packageRoot ?? getPackageRoot());
|
|
188
|
+
const relPath = typeof options === "object" && options !== null
|
|
189
|
+
? (options.continuityPath ?? options.relativePath ?? (options.taskId ? taskArtifactPath(options.taskId, "continuity") : CONTINUITY_PATH))
|
|
190
|
+
: CONTINUITY_PATH;
|
|
191
|
+
const artifact = await readJsonArtifact(target, relPath, "continuity", packageRoot);
|
|
187
192
|
return { ...artifact, value: assertContinuitySemantics(artifact.value) };
|
|
188
193
|
}
|
|
189
194
|
|
|
@@ -192,25 +197,25 @@ export async function writeContinuity(target, operationalInput = {}, options = {
|
|
|
192
197
|
let state = options.state;
|
|
193
198
|
if (!state) {
|
|
194
199
|
const { readWorkState } = await import("./work-state.js");
|
|
195
|
-
state = await readWorkState(target, packageRoot);
|
|
200
|
+
state = await readWorkState(target, { packageRoot, taskId: options.taskId, statePath: options.statePath });
|
|
196
201
|
}
|
|
197
|
-
if (!state) throw continuityError("E_CONTINUITY_STATE_MISSING", "Cannot record continuity without work state", [ARTIFACT_PATHS.state]);
|
|
202
|
+
if (!state) throw continuityError("E_CONTINUITY_STATE_MISSING", "Cannot record continuity without work state", [options.statePath ?? (options.taskId ? taskArtifactPath(options.taskId, "state") : ARTIFACT_PATHS.state)]);
|
|
198
203
|
|
|
199
204
|
let contract = options.contract;
|
|
200
205
|
if (!contract) {
|
|
201
206
|
const { readContract } = await import("./contract.js");
|
|
202
|
-
contract = await readContract(target, packageRoot);
|
|
207
|
+
contract = await readContract(target, packageRoot, { taskId: options.taskId, contractPath: options.contractPath });
|
|
203
208
|
}
|
|
204
209
|
const currentContractFingerprint = contract?.fingerprint
|
|
205
210
|
?? (contract ? canonicalFingerprint(contract.value ?? contract) : null);
|
|
206
211
|
if (!currentContractFingerprint) {
|
|
207
|
-
throw continuityError("E_CONTINUITY_CONTRACT_MISMATCH", "Cannot bind continuity to the current contract", [ARTIFACT_PATHS.contract]);
|
|
212
|
+
throw continuityError("E_CONTINUITY_CONTRACT_MISMATCH", "Cannot bind continuity to the current contract", [options.contractPath ?? (options.taskId ? taskArtifactPath(options.taskId, "contract") : ARTIFACT_PATHS.contract)]);
|
|
208
213
|
}
|
|
209
214
|
if (state.contractFingerprint !== currentContractFingerprint) {
|
|
210
215
|
throw continuityError(
|
|
211
216
|
"E_CONTINUITY_CONTRACT_MISMATCH",
|
|
212
217
|
"Work state contract fingerprint does not match the current contract",
|
|
213
|
-
[ARTIFACT_PATHS.state, ARTIFACT_PATHS.contract],
|
|
218
|
+
[options.statePath ?? (options.taskId ? taskArtifactPath(options.taskId, "state") : ARTIFACT_PATHS.state), options.contractPath ?? (options.taskId ? taskArtifactPath(options.taskId, "contract") : ARTIFACT_PATHS.contract)],
|
|
214
219
|
);
|
|
215
220
|
}
|
|
216
221
|
const value = createContinuity({
|
|
@@ -231,15 +236,17 @@ export async function writeContinuity(target, operationalInput = {}, options = {
|
|
|
231
236
|
inspectFirst: operationalInput.inspectFirst ?? [],
|
|
232
237
|
...(operationalInput.resumeNote !== undefined ? { resumeNote: operationalInput.resumeNote } : {}),
|
|
233
238
|
});
|
|
234
|
-
|
|
239
|
+
const relPath = options.continuityPath ?? options.relativePath ?? (options.taskId ? taskArtifactPath(options.taskId, "continuity") : CONTINUITY_PATH);
|
|
240
|
+
return writeJsonArtifact(target, relPath, value, "continuity", packageRoot, {
|
|
235
241
|
dryRun: options.dryRun ?? false,
|
|
236
242
|
});
|
|
237
243
|
}
|
|
238
244
|
|
|
239
|
-
export async function clearContinuity(target) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
245
|
+
export async function clearContinuity(target, options = {}) {
|
|
246
|
+
const relPath = options.continuityPath ?? options.relativePath ?? (options.taskId ? taskArtifactPath(options.taskId, "continuity") : CONTINUITY_PATH);
|
|
247
|
+
await assertSafePath(target, relPath);
|
|
248
|
+
const artifactPath = ensureWithin(target, relPath);
|
|
249
|
+
if (!(await fileExists(artifactPath))) return { removed: false, path: relPath };
|
|
243
250
|
await unlink(artifactPath);
|
|
244
|
-
return { removed: true, path:
|
|
251
|
+
return { removed: true, path: relPath };
|
|
245
252
|
}
|
package/src/core/contract.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PROTOCOL_VERSION } from "./protocol.js";
|
|
2
2
|
import { ARTIFACT_PATHS, canonicalFingerprint, readJsonArtifact, writeJsonArtifact } from "./artifacts.js";
|
|
3
|
+
import { taskArtifactPath } from "./task-paths.js";
|
|
3
4
|
import { assertSchema, readSchema } from "./schema-validation.js";
|
|
4
5
|
import { assertSecretFree } from "./receipt.js";
|
|
5
6
|
import { REQUIREMENT_TYPES, isMixedTerminalRequirement } from "./evidence-readiness.js";
|
|
@@ -199,8 +200,9 @@ export async function validateContract(contract, packageRoot) {
|
|
|
199
200
|
return contract;
|
|
200
201
|
}
|
|
201
202
|
|
|
202
|
-
export async function readContract(target, packageRoot) {
|
|
203
|
-
const
|
|
203
|
+
export async function readContract(target, packageRoot, options = {}) {
|
|
204
|
+
const contractPath = options.contractPath ?? options.contractFile ?? options.relativePath ?? (options.taskId ? taskArtifactPath(options.taskId, "contract") : ARTIFACT_PATHS.contract);
|
|
205
|
+
const artifact = await readJsonArtifact(target, contractPath, "current-contract", packageRoot);
|
|
204
206
|
await validateContract(artifact.value, packageRoot);
|
|
205
207
|
return artifact;
|
|
206
208
|
}
|
|
@@ -208,9 +210,10 @@ export async function readContract(target, packageRoot) {
|
|
|
208
210
|
export async function writeContract(target, contract, packageRoot, options = {}) {
|
|
209
211
|
assertSecretFree(contract);
|
|
210
212
|
assertAssumptions(contract.assumptions ?? []);
|
|
213
|
+
const contractPath = options.contractPath ?? options.contractFile ?? options.relativePath ?? (options.taskId ? taskArtifactPath(options.taskId, "contract") : ARTIFACT_PATHS.contract);
|
|
211
214
|
return writeJsonArtifact(
|
|
212
215
|
target,
|
|
213
|
-
|
|
216
|
+
contractPath,
|
|
214
217
|
contract,
|
|
215
218
|
"current-contract",
|
|
216
219
|
packageRoot,
|