@cassiomc1/forgeloop 1.1.1 → 1.2.2
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 +1 -1
- package/.github/copilot-instructions.md +1 -1
- package/AGENTS.md +1 -1
- package/CLAUDE.md +1 -1
- package/DOCS_INDEX.md +3 -0
- package/ENG/design-code-eng.md +124 -0
- package/ENG/premium-sites-studio-eng.md +28 -0
- package/ENG/taste-frontend-eng.md +3 -2
- package/ENG/test-code-eng.md +45 -0
- package/LOOP_ENGINEERING.md +74 -0
- package/LOOP_SYSTEM_DESIGN.md +9 -5
- package/ORCHESTRATOR_INTEGRATION.md +41 -6
- package/PROTOCOL_INTEGRATION.md +13 -0
- package/README.md +40 -6
- package/TERMINOLOGY.md +10 -0
- package/THIRD_PARTY_NOTICES.md +58 -1
- package/THREAT_MODEL.md +12 -1
- package/docs/ARTIFACT_REFERENCE.md +152 -2
- package/docs/CLI_REFERENCE.md +346 -30
- package/docs/CROSS_HARNESS_CONTINUITY.md +1 -0
- package/docs/DOCUMENTATION_GUIDE.md +41 -4
- package/docs/GETTING_STARTED.md +39 -8
- package/docs/RECIPES.md +66 -7
- package/docs/TROUBLESHOOTING.md +279 -6
- package/package.json +1 -1
- package/schemas/policy-baseline.schema.json +26 -0
- package/schemas/policy-discovery.schema.json +45 -0
- package/schemas/policy-lock.schema.json +16 -0
- package/schemas/policy-rules.schema.json +48 -0
- package/schemas/policy-snapshot.schema.json +16 -0
- package/src/cli.js +102 -1
- package/src/commands/baseline.js +120 -0
- package/src/commands/init.js +304 -6
- package/src/commands/next.js +15 -1
- package/src/commands/policy-diff.js +51 -0
- package/src/commands/policy-discover.js +42 -0
- package/src/commands/policy-status.js +33 -0
- package/src/commands/profile-interview.js +50 -0
- package/src/commands/progress.js +51 -0
- package/src/commands/reconcile-closure.js +49 -0
- package/src/commands/record-decision-criterion.js +34 -0
- package/src/commands/record-diagnosis.js +49 -0
- package/src/commands/rule-verify.js +36 -0
- package/src/commands/validate-receipt.js +38 -3
- package/src/core/artifact-registry.js +60 -0
- package/src/core/audit.js +24 -0
- package/src/core/cli-command-definitions.js +163 -7
- package/src/core/cli-metadata.js +1 -1
- package/src/core/completion-artifacts.js +29 -3
- package/src/core/completion.js +101 -10
- package/src/core/diagnosis-model.js +214 -0
- package/src/core/diagnosis.js +171 -0
- package/src/core/error-codes.js +292 -0
- package/src/core/events.js +47 -1
- package/src/core/execution-prerequisites.js +38 -20
- package/src/core/execution.js +20 -3
- package/src/core/native-adapters.js +14 -4
- package/src/core/next-action-model.js +40 -5
- package/src/core/next-action.js +234 -91
- package/src/core/phase.js +29 -0
- package/src/core/policy-adapters.js +276 -0
- package/src/core/policy-baseline.js +144 -0
- package/src/core/policy-diff.js +133 -0
- package/src/core/policy-discovery.js +225 -0
- package/src/core/policy-engine.js +533 -0
- package/src/core/policy-mutation.js +139 -0
- package/src/core/preflight-consistency.js +23 -15
- package/src/core/preflight-model.js +10 -2
- package/src/core/preflight.js +65 -1
- package/src/core/progress.js +143 -0
- package/src/core/protocol.js +8 -0
- package/src/core/reconcile-closure.js +173 -0
- package/src/core/schema-validation.js +6 -0
- package/src/core/settlement-model.js +85 -0
- package/src/core/settlement.js +78 -0
- package/src/core/task-context.js +11 -0
- package/src/core/task-discovery.js +67 -1
- package/src/core/task-paths.js +9 -0
- package/src/core/templates.js +5 -0
package/src/core/next-action.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ARTIFACT_PATHS, readJsonArtifact } from "./artifacts.js";
|
|
2
|
+
import { taskArtifactPath } from "./task-paths.js";
|
|
2
3
|
import { completionIdentityErrors, evaluateCompletion } from "./completion.js";
|
|
3
4
|
import { readContract } from "./contract.js";
|
|
4
5
|
import { evaluatePreflight, validatePersistedPreflight, validateReadyProtocolConsistency } from "./preflight.js";
|
|
@@ -17,6 +18,7 @@ import {
|
|
|
17
18
|
commandFor,
|
|
18
19
|
decision,
|
|
19
20
|
recordCheckCommandSpec,
|
|
21
|
+
recordDiagnosisCommandSpec,
|
|
20
22
|
recordTerminalResultCommandSpec,
|
|
21
23
|
result,
|
|
22
24
|
uniqueSorted,
|
|
@@ -31,75 +33,113 @@ import {
|
|
|
31
33
|
} from "./next-action-artifacts.js";
|
|
32
34
|
import { PHASES_REQUIRING_EXECUTION_CHRONOLOGY } from "./next-action-phases.js";
|
|
33
35
|
import { evaluateContinuityNextAction } from "./next-action-continuity.js";
|
|
36
|
+
import { currentCycleDiagnosis } from "./diagnosis-model.js";
|
|
37
|
+
import { evaluateProgress, PROGRESS_STATUS } from "./progress.js";
|
|
38
|
+
import { criterionForDecision } from "./settlement-model.js";
|
|
39
|
+
import { readEvents } from "./events.js";
|
|
34
40
|
|
|
35
41
|
export { NEXT_ACTIONS } from "./next-action-model.js";
|
|
36
42
|
|
|
37
|
-
export
|
|
43
|
+
export function policyRecoveryAction(errors = []) {
|
|
44
|
+
if (errors.some((e) => e.code === "E_POLICY_WEAKENING" || e.code === "E_POLICY_LOCK_MISMATCH")) {
|
|
45
|
+
return NEXT_ACTIONS.RESTORE_POLICY;
|
|
46
|
+
}
|
|
47
|
+
if (errors.some((e) => e.code === "E_CHECK_MUTATION_EXECUTION_ERROR" || e.code === "E_CHECK_MUTATION_NOT_DETECTED")) {
|
|
48
|
+
return NEXT_ACTIONS.REPAIR_CHECKER;
|
|
49
|
+
}
|
|
50
|
+
if (errors.some((e) => e.code === "E_POLICY_INVALID" || e.code === "E_POLICY_EVALUATION_FAILED")) {
|
|
51
|
+
return NEXT_ACTIONS.REPAIR_POLICY;
|
|
52
|
+
}
|
|
53
|
+
if (errors.some((e) => e.code === "E_POLICY_DRIFT_UNKNOWN")) {
|
|
54
|
+
return NEXT_ACTIONS.REVERIFY_AFTER_POLICY_CHANGE;
|
|
55
|
+
}
|
|
56
|
+
if (errors.some((e) => e.code === "E_BASELINE_EXPANSION")) {
|
|
57
|
+
return NEXT_ACTIONS.RESTORE_BASELINE;
|
|
58
|
+
}
|
|
59
|
+
if (errors.some((e) => e.code === "E_BASELINE_RECORD_DURING_ACTIVE_TASK")) {
|
|
60
|
+
return NEXT_ACTIONS.CONTINUE_WITH_EXISTING_BASELINE;
|
|
61
|
+
}
|
|
62
|
+
if (errors.some((e) => e.code === "E_CHECK_INERT")) {
|
|
63
|
+
return NEXT_ACTIONS.RESOLVE_INERT_CHECK;
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function computeNextAction(targetOrOptions = {}, packageRootOption) {
|
|
38
69
|
const normalized = typeof targetOrOptions === "string"
|
|
39
70
|
? { target: targetOrOptions, packageRoot: packageRootOption }
|
|
40
71
|
: targetOrOptions;
|
|
41
|
-
const { target, packageRoot, authorityContext, runtimeContext } = normalized ?? {};
|
|
72
|
+
const { target, packageRoot, taskId: explicitTaskId, authorityContext, runtimeContext } = normalized ?? {};
|
|
73
|
+
|
|
74
|
+
const stateRel = explicitTaskId ? taskArtifactPath(explicitTaskId, "state") : ARTIFACT_PATHS.state;
|
|
75
|
+
const preflightRel = explicitTaskId ? taskArtifactPath(explicitTaskId, "preflight") : ARTIFACT_PATHS.preflight;
|
|
76
|
+
const contractRel = explicitTaskId ? taskArtifactPath(explicitTaskId, "contract") : ARTIFACT_PATHS.contract;
|
|
77
|
+
const routeRel = explicitTaskId ? taskArtifactPath(explicitTaskId, "route") : ARTIFACT_PATHS.route;
|
|
78
|
+
const receiptRel = explicitTaskId ? taskArtifactPath(explicitTaskId, "receipt") : ARTIFACT_PATHS.receipt;
|
|
79
|
+
const eventsRel = explicitTaskId ? taskArtifactPath(explicitTaskId, "events") : ARTIFACT_PATHS.events;
|
|
80
|
+
|
|
42
81
|
const workState = await loadArtifact(
|
|
43
|
-
() => readWorkState(target, packageRoot),
|
|
44
|
-
|
|
82
|
+
() => readWorkState(target, { packageRoot, taskId: explicitTaskId }),
|
|
83
|
+
stateRel,
|
|
45
84
|
);
|
|
46
85
|
if (workState.error) {
|
|
47
86
|
return decision(
|
|
48
87
|
{},
|
|
49
88
|
NEXT_ACTIONS.RESOLVE_BLOCKER,
|
|
50
|
-
artifactError("WORK_STATE_INVALID", workState.error.message, [
|
|
51
|
-
[
|
|
89
|
+
artifactError("WORK_STATE_INVALID", workState.error.message, [stateRel]),
|
|
90
|
+
[stateRel],
|
|
52
91
|
workState.missingArtifacts,
|
|
53
92
|
);
|
|
54
93
|
}
|
|
55
94
|
if (!workState.value) {
|
|
56
95
|
const persistedPreflight = await loadArtifact(
|
|
57
|
-
() => readJsonArtifact(target,
|
|
58
|
-
|
|
96
|
+
() => readJsonArtifact(target, preflightRel, "preflight", packageRoot),
|
|
97
|
+
preflightRel,
|
|
59
98
|
);
|
|
60
99
|
if (!persistedPreflight.error && persistedPreflight.value?.value?.status === "READY") {
|
|
61
100
|
try {
|
|
62
101
|
const consistencyErrors = await validateReadyProtocolConsistency({
|
|
63
102
|
target,
|
|
64
103
|
packageRoot,
|
|
104
|
+
taskId: explicitTaskId,
|
|
65
105
|
persisted: persistedPreflight.value.value,
|
|
66
106
|
});
|
|
67
107
|
if (consistencyErrors.length > 0) {
|
|
68
108
|
return result({
|
|
69
|
-
taskId: persistedPreflight.value.value.taskId ?? "unknown",
|
|
109
|
+
taskId: persistedPreflight.value.value.taskId ?? explicitTaskId ?? "unknown",
|
|
70
110
|
currentPhase: "ROUTED",
|
|
71
111
|
nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
|
|
72
112
|
reasons: consistencyErrors,
|
|
73
113
|
requiredArtifacts: [
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
114
|
+
stateRel,
|
|
115
|
+
contractRel,
|
|
116
|
+
routeRel,
|
|
117
|
+
preflightRel,
|
|
118
|
+
eventsRel,
|
|
79
119
|
],
|
|
80
|
-
missingArtifacts: [
|
|
120
|
+
missingArtifacts: [stateRel],
|
|
81
121
|
});
|
|
82
122
|
}
|
|
83
123
|
} catch (error) {
|
|
84
124
|
return result({
|
|
85
|
-
taskId: persistedPreflight.value.value.taskId ?? "unknown",
|
|
125
|
+
taskId: persistedPreflight.value.value.taskId ?? explicitTaskId ?? "unknown",
|
|
86
126
|
currentPhase: "ROUTED",
|
|
87
127
|
nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
|
|
88
128
|
reasons: [artifactError(
|
|
89
129
|
error.code ?? "E_PREFLIGHT_READY_INCONSISTENT",
|
|
90
130
|
error.message,
|
|
91
|
-
error.artifacts ?? [
|
|
131
|
+
error.artifacts ?? [preflightRel, eventsRel],
|
|
92
132
|
)],
|
|
93
|
-
requiredArtifacts: [
|
|
133
|
+
requiredArtifacts: [preflightRel, eventsRel],
|
|
94
134
|
});
|
|
95
135
|
}
|
|
96
136
|
}
|
|
97
137
|
return decision(
|
|
98
138
|
{},
|
|
99
139
|
NEXT_ACTIONS.DISCOVER,
|
|
100
|
-
artifactError("WORK_STATE_ABSENT", "No work-state checkpoint is present", [
|
|
101
|
-
[
|
|
102
|
-
[
|
|
140
|
+
artifactError("WORK_STATE_ABSENT", "No work-state checkpoint is present", [stateRel]),
|
|
141
|
+
[stateRel],
|
|
142
|
+
[stateRel],
|
|
103
143
|
);
|
|
104
144
|
}
|
|
105
145
|
|
|
@@ -110,7 +150,7 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
110
150
|
context,
|
|
111
151
|
NEXT_ACTIONS.DISCOVER,
|
|
112
152
|
artifactError("PHASE_RECEIVED", "Discovery has not started"),
|
|
113
|
-
[
|
|
153
|
+
[stateRel],
|
|
114
154
|
);
|
|
115
155
|
}
|
|
116
156
|
if (state.phase === "DISCOVERING") {
|
|
@@ -118,14 +158,14 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
118
158
|
context,
|
|
119
159
|
NEXT_ACTIONS.CREATE_CONTRACT,
|
|
120
160
|
artifactError("PHASE_DISCOVERING", "Create and validate the task contract"),
|
|
121
|
-
[
|
|
161
|
+
[stateRel],
|
|
122
162
|
);
|
|
123
163
|
}
|
|
124
164
|
const contractResult = await loadArtifact(
|
|
125
|
-
() => readContract(target, packageRoot),
|
|
126
|
-
|
|
165
|
+
() => readContract(target, packageRoot, { taskId: explicitTaskId }),
|
|
166
|
+
contractRel,
|
|
127
167
|
);
|
|
128
|
-
const contractArtifacts = [
|
|
168
|
+
const contractArtifacts = [stateRel, contractRel];
|
|
129
169
|
|
|
130
170
|
if (contractResult.error) {
|
|
131
171
|
return result({
|
|
@@ -154,7 +194,7 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
154
194
|
const freshness = await classifyLoadedWorkState({
|
|
155
195
|
target,
|
|
156
196
|
state,
|
|
157
|
-
contractFile:
|
|
197
|
+
contractFile: contractRel,
|
|
158
198
|
});
|
|
159
199
|
if (freshness.status === "REVALIDATION_REQUIRED") {
|
|
160
200
|
return result({
|
|
@@ -162,8 +202,8 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
162
202
|
nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
|
|
163
203
|
reasons: freshnessReasons(state, freshness),
|
|
164
204
|
requiredArtifacts: uniqueSorted([
|
|
165
|
-
|
|
166
|
-
|
|
205
|
+
stateRel,
|
|
206
|
+
contractRel,
|
|
167
207
|
...(state.requiredArtifacts?.map((artifact) => artifact.path) ?? []),
|
|
168
208
|
]),
|
|
169
209
|
});
|
|
@@ -174,14 +214,14 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
174
214
|
NEXT_ACTIONS.ROUTE,
|
|
175
215
|
artifactError("PHASE_CONTRACT_READY", "Persist deterministic routing for the validated contract"),
|
|
176
216
|
contractArtifacts,
|
|
177
|
-
[
|
|
217
|
+
[routeRel],
|
|
178
218
|
);
|
|
179
219
|
}
|
|
180
220
|
const routeResult = await loadArtifact(
|
|
181
|
-
() => readPersistedRoute(target, packageRoot),
|
|
182
|
-
|
|
221
|
+
() => readPersistedRoute(target, packageRoot, { taskId: explicitTaskId }),
|
|
222
|
+
routeRel,
|
|
183
223
|
);
|
|
184
|
-
const requiredArtifacts = [...contractArtifacts,
|
|
224
|
+
const requiredArtifacts = [...contractArtifacts, routeRel];
|
|
185
225
|
const missingArtifacts = [...routeResult.missingArtifacts];
|
|
186
226
|
|
|
187
227
|
if (routeResult.error) {
|
|
@@ -213,7 +253,7 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
213
253
|
let executionPrerequisites = null;
|
|
214
254
|
if (phaseNeedsChronology) {
|
|
215
255
|
try {
|
|
216
|
-
executionPrerequisites = await evaluateStartExecutionPrerequisites({ target, state, packageRoot });
|
|
256
|
+
executionPrerequisites = await evaluateStartExecutionPrerequisites({ target, state, packageRoot, taskId: explicitTaskId });
|
|
217
257
|
} catch (error) {
|
|
218
258
|
return result({
|
|
219
259
|
...context,
|
|
@@ -221,14 +261,14 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
221
261
|
reasons: [artifactError(
|
|
222
262
|
error?.code ?? "E_PHASE_CHRONOLOGY_INVALID",
|
|
223
263
|
`Unable to evaluate post-execution prerequisites: ${error?.message ?? String(error)}`,
|
|
224
|
-
Array.isArray(error?.artifacts) ? error.artifacts : [
|
|
264
|
+
Array.isArray(error?.artifacts) ? error.artifacts : [eventsRel],
|
|
225
265
|
)],
|
|
226
266
|
requiredArtifacts: [
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
267
|
+
stateRel,
|
|
268
|
+
contractRel,
|
|
269
|
+
routeRel,
|
|
270
|
+
preflightRel,
|
|
271
|
+
eventsRel,
|
|
232
272
|
],
|
|
233
273
|
});
|
|
234
274
|
}
|
|
@@ -242,15 +282,15 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
242
282
|
}
|
|
243
283
|
}
|
|
244
284
|
|
|
245
|
-
const preflight = executionPrerequisites?.preflight ?? await evaluatePreflight({ target, packageRoot });
|
|
285
|
+
const preflight = executionPrerequisites?.preflight ?? await evaluatePreflight({ target, packageRoot, taskId: explicitTaskId });
|
|
246
286
|
const preflightArtifact = phaseNeedsChronology
|
|
247
287
|
? null
|
|
248
288
|
: await loadArtifact(
|
|
249
|
-
() => readJsonArtifact(target,
|
|
250
|
-
|
|
289
|
+
() => readJsonArtifact(target, preflightRel, "preflight", packageRoot),
|
|
290
|
+
preflightRel,
|
|
251
291
|
);
|
|
252
292
|
const missingGates = preflight.requiredGates.filter((gate) => !preflight.satisfiedGates.includes(gate));
|
|
253
|
-
const preflightArtifacts = [...requiredArtifacts,
|
|
293
|
+
const preflightArtifacts = [...requiredArtifacts, preflightRel];
|
|
254
294
|
const persistedPreflightErrors = phaseNeedsChronology
|
|
255
295
|
? []
|
|
256
296
|
: validatePersistedPreflight(preflightArtifact.value?.value, preflight);
|
|
@@ -285,7 +325,7 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
285
325
|
return decision(context, NEXT_ACTIONS.PLAN, artifactError("PHASE_DESIGNING", "Required gates are ready for planning"));
|
|
286
326
|
}
|
|
287
327
|
if (state.phase === "PLANNED") {
|
|
288
|
-
const prerequisites = await evaluateStartExecutionPrerequisites({ target, state, packageRoot });
|
|
328
|
+
const prerequisites = await evaluateStartExecutionPrerequisites({ target, state, packageRoot, taskId: explicitTaskId });
|
|
289
329
|
if (prerequisites.errors.length > 0) {
|
|
290
330
|
const preflightOnly = prerequisites.errors.every((error) => error.code.startsWith("E_PREFLIGHT_")
|
|
291
331
|
|| (error.code === "E_PHASE_CHRONOLOGY_INVALID"
|
|
@@ -345,7 +385,7 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
345
385
|
return decision(
|
|
346
386
|
context,
|
|
347
387
|
NEXT_ACTIONS.DIAGNOSE,
|
|
348
|
-
artifactError("E_CHECK_FAILED", `Observed check failed: ${failed.check.id}`, [
|
|
388
|
+
artifactError("E_CHECK_FAILED", `Observed check failed: ${failed.check.id}`, [stateRel]),
|
|
349
389
|
);
|
|
350
390
|
}
|
|
351
391
|
const blocked = authoritative.find(({ check }) => check?.status === "blocked");
|
|
@@ -353,28 +393,28 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
353
393
|
return decision(
|
|
354
394
|
context,
|
|
355
395
|
NEXT_ACTIONS.RESOLVE_BLOCKER,
|
|
356
|
-
artifactError("E_CHECK_BLOCKED", `Observed check is blocked: ${blocked.check.id}`, [
|
|
396
|
+
artifactError("E_CHECK_BLOCKED", `Observed check is blocked: ${blocked.check.id}`, [stateRel]),
|
|
357
397
|
);
|
|
358
398
|
}
|
|
359
399
|
const receipt = await loadArtifact(
|
|
360
|
-
() => readJsonArtifact(target,
|
|
361
|
-
|
|
400
|
+
() => readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot),
|
|
401
|
+
receiptRel,
|
|
362
402
|
);
|
|
363
403
|
if (receipt.error) {
|
|
364
404
|
if (receipt.error.code === "ARTIFACT_MISSING") {
|
|
365
405
|
return decision(
|
|
366
406
|
context,
|
|
367
407
|
NEXT_ACTIONS.PREPARE_COMPLETION,
|
|
368
|
-
artifactError("E_RECEIPT_MISSING", "Prepare the execution receipt before recording verification checks", [
|
|
369
|
-
[...requiredArtifacts,
|
|
408
|
+
artifactError("E_RECEIPT_MISSING", "Prepare the execution receipt before recording verification checks", [receiptRel]),
|
|
409
|
+
[...requiredArtifacts, receiptRel],
|
|
370
410
|
receipt.missingArtifacts,
|
|
371
411
|
);
|
|
372
412
|
}
|
|
373
413
|
return decision(
|
|
374
414
|
context,
|
|
375
415
|
NEXT_ACTIONS.RESOLVE_BLOCKER,
|
|
376
|
-
artifactError("E_RECEIPT_INVALID", `Repair or remove the invalid execution receipt before continuing: ${receipt.error.message}`, [
|
|
377
|
-
[...requiredArtifacts,
|
|
416
|
+
artifactError("E_RECEIPT_INVALID", `Repair or remove the invalid execution receipt before continuing: ${receipt.error.message}`, [receiptRel]),
|
|
417
|
+
[...requiredArtifacts, receiptRel],
|
|
378
418
|
);
|
|
379
419
|
}
|
|
380
420
|
try {
|
|
@@ -388,8 +428,8 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
388
428
|
return decision(
|
|
389
429
|
context,
|
|
390
430
|
NEXT_ACTIONS.RESOLVE_BLOCKER,
|
|
391
|
-
artifactError("E_RECEIPT_INVALID", `Repair or remove the invalid execution receipt before continuing: ${error.message}`, [
|
|
392
|
-
[...requiredArtifacts,
|
|
431
|
+
artifactError("E_RECEIPT_INVALID", `Repair or remove the invalid execution receipt before continuing: ${error.message}`, [receiptRel]),
|
|
432
|
+
[...requiredArtifacts, receiptRel],
|
|
393
433
|
);
|
|
394
434
|
}
|
|
395
435
|
const readiness = evaluateRequiredEvidence({
|
|
@@ -422,15 +462,15 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
422
462
|
return decision(
|
|
423
463
|
context,
|
|
424
464
|
NEXT_ACTIONS.PREPARE_COMPLETION,
|
|
425
|
-
artifactError("E_RECEIPT_STATE_MISMATCH", "Run forgeloop prepare-completion to refresh the execution receipt with current state", [
|
|
426
|
-
[...requiredArtifacts,
|
|
465
|
+
artifactError("E_RECEIPT_STATE_MISMATCH", "Run forgeloop prepare-completion to refresh the execution receipt with current state", [receiptRel]),
|
|
466
|
+
[...requiredArtifacts, receiptRel],
|
|
427
467
|
);
|
|
428
468
|
}
|
|
429
469
|
return result({
|
|
430
470
|
...context,
|
|
431
471
|
nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
|
|
432
472
|
reasons: receiptRelationships,
|
|
433
|
-
requiredArtifacts: [...requiredArtifacts,
|
|
473
|
+
requiredArtifacts: [...requiredArtifacts, receiptRel],
|
|
434
474
|
});
|
|
435
475
|
}
|
|
436
476
|
if (readiness.ready) {
|
|
@@ -450,25 +490,56 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
450
490
|
? "E_EVIDENCE_PARTIAL"
|
|
451
491
|
: "E_EVIDENCE_REQUIRED",
|
|
452
492
|
"Run the required check and record observed evidence through the structured command specification.",
|
|
453
|
-
[
|
|
493
|
+
[stateRel],
|
|
454
494
|
)),
|
|
455
495
|
requiredArtifacts: requiredArtifacts,
|
|
456
496
|
});
|
|
457
497
|
}
|
|
458
498
|
if (state.phase === "DIAGNOSING") {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
499
|
+
const ledger = await validateEventLedger(target, packageRoot, { taskId: normalized.taskId ?? null });
|
|
500
|
+
const cycle = state.verificationCycle ?? 1;
|
|
501
|
+
const diagEvent = currentCycleDiagnosis(ledger.events, state.taskId, cycle);
|
|
502
|
+
if (!diagEvent) {
|
|
503
|
+
return result({
|
|
504
|
+
...context,
|
|
505
|
+
nextAction: NEXT_ACTIONS.RECORD_DIAGNOSIS,
|
|
506
|
+
reasons: [
|
|
507
|
+
artifactError(
|
|
508
|
+
"E_DIAGNOSIS_REQUIRED",
|
|
509
|
+
"Current correction cycle has no append-only diagnosis record.",
|
|
510
|
+
[eventsRel],
|
|
511
|
+
),
|
|
512
|
+
],
|
|
513
|
+
commandSpecs: [recordDiagnosisCommandSpec()],
|
|
514
|
+
requiredArtifacts: [...requiredArtifacts, eventsRel],
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
const progress = evaluateProgress({ state, events: ledger.events });
|
|
518
|
+
if (progress.status === PROGRESS_STATUS.STALLED) {
|
|
519
|
+
return result({
|
|
520
|
+
...context,
|
|
521
|
+
nextAction: NEXT_ACTIONS.CHANGE_STRATEGY,
|
|
522
|
+
reasons: [
|
|
523
|
+
artifactError(
|
|
524
|
+
"E_PROGRESS_STALLED",
|
|
525
|
+
"The current correction strategy has no new diagnostic information.",
|
|
526
|
+
[stateRel, eventsRel],
|
|
527
|
+
),
|
|
528
|
+
],
|
|
529
|
+
progress,
|
|
468
530
|
requiredArtifacts,
|
|
469
|
-
);
|
|
531
|
+
});
|
|
470
532
|
}
|
|
471
|
-
return
|
|
533
|
+
return result({
|
|
534
|
+
...context,
|
|
535
|
+
nextAction: NEXT_ACTIONS.CORRECT,
|
|
536
|
+
commands: [commandFor(NEXT_ACTIONS.CORRECT)],
|
|
537
|
+
reasons: [
|
|
538
|
+
artifactError("PHASE_DIAGNOSING", "The persisted diagnosis hypothesis permits correction"),
|
|
539
|
+
],
|
|
540
|
+
...(progress.status === PROGRESS_STATUS.WATCH ? { progress } : {}),
|
|
541
|
+
requiredArtifacts,
|
|
542
|
+
});
|
|
472
543
|
}
|
|
473
544
|
if (state.phase === "CORRECTING") {
|
|
474
545
|
return decision(context, NEXT_ACTIONS.ENTER_VERIFYING, artifactError("PHASE_CORRECTING", "Correction is ready for verification"));
|
|
@@ -504,10 +575,10 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
504
575
|
let recoveryAuthorized = false;
|
|
505
576
|
if (state.lastCompletionAttempt?.status === "REJECTED") {
|
|
506
577
|
try {
|
|
507
|
-
const ledger = await validateEventLedger(target, packageRoot);
|
|
578
|
+
const ledger = await validateEventLedger(target, packageRoot, { taskId: explicitTaskId, eventsPath: eventsRel });
|
|
508
579
|
let currentReceipt = null;
|
|
509
580
|
try {
|
|
510
|
-
const receiptArtifact = await readJsonArtifact(target,
|
|
581
|
+
const receiptArtifact = await readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot);
|
|
511
582
|
currentReceipt = receiptArtifact?.value;
|
|
512
583
|
} catch {}
|
|
513
584
|
const recoveryAuth = validateCompletionRecoveryAuthorization({
|
|
@@ -534,22 +605,22 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
534
605
|
? "E_EVIDENCE_REQUIRED"
|
|
535
606
|
: item.reasonCode ?? "E_EVIDENCE_PARTIAL",
|
|
536
607
|
`Evidence is not ready: ${item.text}`,
|
|
537
|
-
[
|
|
608
|
+
[stateRel],
|
|
538
609
|
)),
|
|
539
610
|
requiredArtifacts,
|
|
540
611
|
});
|
|
541
612
|
}
|
|
542
613
|
const receipt = await loadArtifact(
|
|
543
|
-
() => readJsonArtifact(target,
|
|
544
|
-
|
|
614
|
+
() => readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot),
|
|
615
|
+
receiptRel,
|
|
545
616
|
);
|
|
546
617
|
if (receipt.error) {
|
|
547
618
|
if (receipt.error.code !== "ARTIFACT_MISSING") {
|
|
548
619
|
return decision(
|
|
549
620
|
context,
|
|
550
621
|
NEXT_ACTIONS.RESOLVE_BLOCKER,
|
|
551
|
-
artifactError("E_RECEIPT_INVALID", `Repair or remove the invalid execution receipt before continuing: ${receipt.error.message}`, [
|
|
552
|
-
[...requiredArtifacts,
|
|
622
|
+
artifactError("E_RECEIPT_INVALID", `Repair or remove the invalid execution receipt before continuing: ${receipt.error.message}`, [receiptRel]),
|
|
623
|
+
[...requiredArtifacts, receiptRel],
|
|
553
624
|
);
|
|
554
625
|
}
|
|
555
626
|
return decision(
|
|
@@ -558,9 +629,9 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
558
629
|
artifactError(
|
|
559
630
|
receipt.error.code === "ARTIFACT_MISSING" ? "E_RECEIPT_MISSING" : "E_RECEIPT_INVALID",
|
|
560
631
|
receipt.error.message,
|
|
561
|
-
[
|
|
632
|
+
[receiptRel],
|
|
562
633
|
),
|
|
563
|
-
[...requiredArtifacts,
|
|
634
|
+
[...requiredArtifacts, receiptRel],
|
|
564
635
|
receipt.missingArtifacts,
|
|
565
636
|
);
|
|
566
637
|
}
|
|
@@ -575,8 +646,8 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
575
646
|
return decision(
|
|
576
647
|
context,
|
|
577
648
|
NEXT_ACTIONS.RESOLVE_BLOCKER,
|
|
578
|
-
artifactError("E_RECEIPT_INVALID", `Repair or remove the invalid execution receipt before continuing: ${error.message}`, [
|
|
579
|
-
[...requiredArtifacts,
|
|
649
|
+
artifactError("E_RECEIPT_INVALID", `Repair or remove the invalid execution receipt before continuing: ${error.message}`, [receiptRel]),
|
|
650
|
+
[...requiredArtifacts, receiptRel],
|
|
580
651
|
);
|
|
581
652
|
}
|
|
582
653
|
const receiptRelationships = completionRelationshipErrors({
|
|
@@ -601,18 +672,19 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
601
672
|
return decision(
|
|
602
673
|
context,
|
|
603
674
|
NEXT_ACTIONS.PREPARE_COMPLETION,
|
|
604
|
-
artifactError("E_RECEIPT_STATE_MISMATCH", "Run forgeloop prepare-completion to refresh the execution receipt with current state", [
|
|
605
|
-
[...requiredArtifacts,
|
|
675
|
+
artifactError("E_RECEIPT_STATE_MISMATCH", "Run forgeloop prepare-completion to refresh the execution receipt with current state", [receiptRel]),
|
|
676
|
+
[...requiredArtifacts, receiptRel],
|
|
606
677
|
);
|
|
607
678
|
}
|
|
608
679
|
return result({
|
|
609
680
|
...context,
|
|
610
681
|
nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
|
|
611
682
|
reasons: receiptRelationships,
|
|
612
|
-
requiredArtifacts: [...requiredArtifacts,
|
|
683
|
+
requiredArtifacts: [...requiredArtifacts, receiptRel],
|
|
613
684
|
});
|
|
614
685
|
}
|
|
615
|
-
|
|
686
|
+
|
|
687
|
+
const completion = await evaluateCompletion({ target, packageRoot, taskId: explicitTaskId, authorityContext, runtimeContext });
|
|
616
688
|
if (completion.status !== "VALID") {
|
|
617
689
|
const terminalPendingErrors = completion.errors.filter((err) => (
|
|
618
690
|
err.code === "E_PUBLICATION_REQUIREMENT_PENDING" || err.code === "E_PRODUCTION_REQUIREMENT_PENDING"
|
|
@@ -631,28 +703,38 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
631
703
|
commands: ["forgeloop record-terminal-result"],
|
|
632
704
|
commandSpecs: terminalPendingReqs.map(recordTerminalResultCommandSpec),
|
|
633
705
|
reasons: completion.errors,
|
|
634
|
-
requiredArtifacts: [...requiredArtifacts,
|
|
706
|
+
requiredArtifacts: [...requiredArtifacts, receiptRel, eventsRel],
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
const policyAction = policyRecoveryAction(completion.errors);
|
|
710
|
+
if (policyAction) {
|
|
711
|
+
return result({
|
|
712
|
+
...context,
|
|
713
|
+
nextAction: policyAction,
|
|
714
|
+
reasons: completion.errors,
|
|
715
|
+
requiredArtifacts: [...requiredArtifacts, receiptRel, eventsRel],
|
|
635
716
|
});
|
|
636
717
|
}
|
|
637
718
|
return result({
|
|
638
719
|
...context,
|
|
639
720
|
nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
|
|
640
721
|
reasons: completion.errors,
|
|
641
|
-
requiredArtifacts: [...requiredArtifacts,
|
|
722
|
+
requiredArtifacts: [...requiredArtifacts, receiptRel, eventsRel],
|
|
642
723
|
});
|
|
643
724
|
}
|
|
644
725
|
return decision(context, NEXT_ACTIONS.RUN_COMPLETE, artifactError("COMPLETION_READY", "Completion artifacts and cross-artifact validation are valid"));
|
|
645
726
|
}
|
|
646
727
|
if (state.phase === "COMPLETE") {
|
|
647
|
-
const completion = await evaluateCompletion({ target, packageRoot, authorityContext, runtimeContext });
|
|
728
|
+
const completion = await evaluateCompletion({ target, packageRoot, taskId: explicitTaskId, authorityContext, runtimeContext });
|
|
648
729
|
if (completion.status === "VALID") {
|
|
649
730
|
return decision(context, NEXT_ACTIONS.NONE, artifactError("PHASE_COMPLETE", "Completion is validator-backed and terminal"));
|
|
650
731
|
}
|
|
732
|
+
const policyAction = policyRecoveryAction(completion.errors);
|
|
651
733
|
return result({
|
|
652
734
|
...context,
|
|
653
|
-
nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
|
|
735
|
+
nextAction: policyAction ?? NEXT_ACTIONS.RESOLVE_BLOCKER,
|
|
654
736
|
reasons: completion.errors,
|
|
655
|
-
requiredArtifacts: [...requiredArtifacts,
|
|
737
|
+
requiredArtifacts: [...requiredArtifacts, receiptRel, eventsRel],
|
|
656
738
|
});
|
|
657
739
|
}
|
|
658
740
|
if (state.phase === "BLOCKED") {
|
|
@@ -662,7 +744,7 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
662
744
|
reasons: state.blockers.map((blocker) => artifactError(
|
|
663
745
|
"WORK_STATE_BLOCKED",
|
|
664
746
|
blocker.reason ?? "Work state has a recorded blocker",
|
|
665
|
-
[
|
|
747
|
+
[stateRel],
|
|
666
748
|
)),
|
|
667
749
|
requiredArtifacts,
|
|
668
750
|
});
|
|
@@ -675,3 +757,64 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
675
757
|
requiredArtifacts,
|
|
676
758
|
);
|
|
677
759
|
}
|
|
760
|
+
|
|
761
|
+
export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
762
|
+
const res = await computeNextAction(targetOrOptions, packageRootOption);
|
|
763
|
+
if (res && res.reasons && res.reasons.some((r) => r.code === "E_CONTRACT_UNRESOLVED_DECISION" || r.code === "E_UNRESOLVED_DECISION")) {
|
|
764
|
+
const normalized = typeof targetOrOptions === "string" ? { target: targetOrOptions, packageRoot: packageRootOption } : targetOrOptions;
|
|
765
|
+
const { target, packageRoot, taskId } = normalized ?? {};
|
|
766
|
+
try {
|
|
767
|
+
const explicitTaskId = taskId ?? null;
|
|
768
|
+
const contract = await readContract(target, packageRoot, { taskId: explicitTaskId });
|
|
769
|
+
const events = await readEvents(target, packageRoot, { taskId: explicitTaskId });
|
|
770
|
+
if (contract?.value?.unresolvedDecisions?.length > 0) {
|
|
771
|
+
const foundCriteria = [];
|
|
772
|
+
for (const dec of contract.value.unresolvedDecisions) {
|
|
773
|
+
const criterion = criterionForDecision(events, res.taskId, dec, contract.fingerprint);
|
|
774
|
+
if (criterion) {
|
|
775
|
+
foundCriteria.push({
|
|
776
|
+
decisionId: criterion.decisionId,
|
|
777
|
+
decision: dec,
|
|
778
|
+
settledBy: criterion.settledBy,
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
if (foundCriteria.length > 0) {
|
|
784
|
+
let resolution;
|
|
785
|
+
if (foundCriteria.length === 1) {
|
|
786
|
+
resolution = {
|
|
787
|
+
kind: "SETTLEMENT_CRITERION",
|
|
788
|
+
itemId: foundCriteria[0].decisionId,
|
|
789
|
+
decision: foundCriteria[0].decision,
|
|
790
|
+
settledBy: foundCriteria[0].settledBy,
|
|
791
|
+
};
|
|
792
|
+
} else {
|
|
793
|
+
resolution = {
|
|
794
|
+
kind: "SETTLEMENT_CRITERIA",
|
|
795
|
+
items: foundCriteria,
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
const enrichedReasons = res.reasons.map((r) => {
|
|
800
|
+
if (r.code === "E_CONTRACT_UNRESOLVED_DECISION" || r.code === "E_UNRESOLVED_DECISION") {
|
|
801
|
+
return {
|
|
802
|
+
...r,
|
|
803
|
+
resolution,
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
return r;
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
return result({
|
|
810
|
+
...res,
|
|
811
|
+
reasons: enrichedReasons,
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
} catch {
|
|
816
|
+
// Ignore read errors
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
return res;
|
|
820
|
+
}
|
package/src/core/phase.js
CHANGED
|
@@ -21,6 +21,7 @@ import { discoverTasks } from "./task-discovery.js";
|
|
|
21
21
|
import { assertNoScopeConflicts, assertScopeClean } from "./task-scope.js";
|
|
22
22
|
import { readTaskDescriptor } from "./task-descriptor.js";
|
|
23
23
|
import { E_TASK_SCOPE_REQUIRED } from "./error-codes.js";
|
|
24
|
+
import { currentCycleDiagnosis } from "./diagnosis-model.js";
|
|
24
25
|
|
|
25
26
|
function phaseError(code, message, artifacts = []) {
|
|
26
27
|
const error = new Error(message);
|
|
@@ -242,7 +243,35 @@ export async function advanceWorkState(target, toPhase, options = {}) {
|
|
|
242
243
|
);
|
|
243
244
|
}
|
|
244
245
|
}
|
|
246
|
+
if (toPhase === "CORRECTING" && state.phase === "DIAGNOSING") {
|
|
247
|
+
const cycle = state.verificationCycle ?? 1;
|
|
248
|
+
const diagEvent = currentCycleDiagnosis(ledger.events, state.taskId, cycle);
|
|
249
|
+
if (!diagEvent) {
|
|
250
|
+
throw phaseError(
|
|
251
|
+
"E_DIAGNOSIS_REQUIRED",
|
|
252
|
+
"DIAGNOSING -> CORRECTING requires an append-only diagnosis record for the active verification cycle",
|
|
253
|
+
[eventsRel],
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
const details = diagEvent.details;
|
|
257
|
+
if (!details || details.informationGain === "NONE") {
|
|
258
|
+
throw phaseError(
|
|
259
|
+
"E_DIAGNOSIS_NO_NEW_INFORMATION",
|
|
260
|
+
"The proposed retry repeats the previous hypothesis with the same evidence without new information",
|
|
261
|
+
[eventsRel],
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
245
265
|
if (toPhase === "VERIFYING" && state.phase === "CORRECTING") {
|
|
266
|
+
const cycle = state.verificationCycle ?? 1;
|
|
267
|
+
const diagEvent = currentCycleDiagnosis(ledger.events, state.taskId, cycle);
|
|
268
|
+
if (!diagEvent) {
|
|
269
|
+
throw phaseError(
|
|
270
|
+
"E_DIAGNOSIS_REQUIRED",
|
|
271
|
+
"CORRECTING -> VERIFYING requires an append-only diagnosis record for the current cycle",
|
|
272
|
+
[eventsRel],
|
|
273
|
+
);
|
|
274
|
+
}
|
|
246
275
|
if (typeof state.diagnosedHypothesis !== "string" || !state.diagnosedHypothesis.trim()) {
|
|
247
276
|
throw phaseError(
|
|
248
277
|
"E_PHASE_PREREQUISITE_MISSING",
|