@cassiomc1/forgeloop 1.13.0 → 1.14.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/AGENT_COMPATIBILITY.md +8 -0
- package/DOCS_INDEX.md +35 -3
- package/ENG/nodejs-backend-development-eng.md +2 -2
- package/ENG/sec-code-eng.md +7 -7
- package/EXECUTION_STATE.md +12 -0
- package/LOOP_ENGINEERING.md +28 -2
- package/ORCHESTRATOR_INTEGRATION.md +9 -5
- package/PROTOCOL_INTEGRATION.md +55 -2
- package/README.md +40 -25
- package/TERMINOLOGY.md +2 -0
- package/THREAT_MODEL.md +140 -1
- package/completions/_forgeloop +19 -1
- package/completions/forgeloop.bash +37 -1
- package/completions/forgeloop.fish +123 -1
- package/docs/ADVISORY_CONTEXT.md +25 -0
- package/docs/AGENT_BROWSER_ADAPTER.md +81 -0
- package/docs/AGENT_BROWSER_VERIFICATION.md +6 -0
- package/docs/AGENT_PROTOCOL_SUMMARY.md +27 -2
- package/docs/AGENT_SKILL.md +66 -0
- package/docs/ARTIFACT_REFERENCE.md +123 -0
- package/docs/AUDIT_UX.md +46 -0
- package/docs/BROWSER_VERIFICATION.md +136 -0
- package/docs/CLI_REFERENCE.md +366 -6
- package/docs/CODE_ATTESTATION.md +2 -2
- package/docs/DOCUMENTATION_GUIDE.md +32 -11
- package/docs/JEV_BENCHMARKS.md +31 -0
- package/docs/MODEL_ROUTING.md +37 -0
- package/docs/OPENSRC_ADAPTER.md +241 -0
- package/docs/PACKAGE_CONTENTS.md +35 -8
- package/docs/PROVIDERS.md +126 -0
- package/docs/PROVIDER_ARCHITECTURE.md +199 -0
- package/docs/RECIPES.md +9 -0
- package/docs/RELEASE_CHECKLIST.md +38 -5
- package/docs/SECURITY_REVIEW.md +71 -0
- package/docs/SEMANTIC_DECISION_PLANE.md +71 -0
- package/docs/TEST_INTELLIGENCE.md +29 -0
- package/docs/TEST_PRUNING.md +14 -0
- package/docs/TROUBLESHOOTING.md +198 -1
- package/docs/UNIVERSAL_INTEGRATION.md +31 -0
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.html +2 -2
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.receipt.json +5 -5
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.svg +1 -1
- package/docs/assets/diagrams/forgeloop-engineering-flow.html +39 -26
- package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +6 -6
- package/docs/assets/diagrams/forgeloop-engineering-flow.svg +26 -26
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.html +2 -1
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.receipt.json +5 -5
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.svg +1 -1
- package/docs/diagrams/README.md +13 -9
- package/docs/diagrams/forgeloop-code-attestation-flow.workflow.json +1 -1
- package/docs/diagrams/forgeloop-engineering-flow.workflow.json +24 -19
- package/docs/diagrams/forgeloop-verification-trust-flow.workflow.json +1 -0
- package/docs/diagrams/reviews/forgeloop-code-attestation-flow.review.json +4 -4
- package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +4 -4
- package/docs/diagrams/reviews/forgeloop-verification-trust-flow.review.json +4 -4
- package/docs/documentation-manifest.json +750 -5
- package/docs/protocol-requirements.json +24 -0
- package/package.json +30 -3
- package/schemas/config.schema.json +14 -0
- package/schemas/context-plan.schema.json +18 -0
- package/schemas/semantic-decision.schema.json +46 -0
- package/schemas/test-utility.schema.json +44 -0
- package/scripts/CI_VALIDATORS.md +6 -6
- package/scripts/benchmark-jev.mjs +5 -0
- package/scripts/benchmark-test-intelligence.mjs +4 -0
- package/scripts/generate-agent-protocol-summary.mjs +4 -1
- package/scripts/generate-forgeloop-skill.mjs +133 -0
- package/scripts/jev-smoke.mjs +19 -0
- package/skills/forgeloop/README.md +9 -0
- package/skills/forgeloop/SKILL.md +77 -0
- package/skills/forgeloop/references/lifecycle.md +9 -0
- package/skills/forgeloop/references/recovery.md +7 -0
- package/skills/forgeloop/references/verification.md +7 -0
- package/src/adapters/agent-browser/assertions.js +47 -0
- package/src/adapters/agent-browser/commands.js +54 -0
- package/src/adapters/agent-browser/index.js +3 -0
- package/src/adapters/agent-browser/locator.js +40 -0
- package/src/adapters/agent-browser/process.js +215 -0
- package/src/adapters/agent-browser/provider.js +313 -0
- package/src/adapters/emulated-services/constants.js +24 -0
- package/src/adapters/emulated-services/index.js +7 -0
- package/src/adapters/emulated-services/process.js +162 -0
- package/src/adapters/emulated-services/provider.js +282 -0
- package/src/adapters/opensrc/normalize.js +90 -0
- package/src/adapters/opensrc/process.js +248 -0
- package/src/adapters/opensrc/provider.js +338 -0
- package/src/adapters/opensrc/search.js +264 -0
- package/src/adapters/typesafe/client.js +28 -0
- package/src/adapters/typesafe/engine.js +63 -0
- package/src/adapters/typesafe/normalize.js +41 -0
- package/src/cli.js +108 -0
- package/src/commands/checkpoint-revalidate.js +176 -0
- package/src/commands/context-plan.js +38 -0
- package/src/commands/contract-create.js +264 -0
- package/src/commands/contract-revise.js +236 -0
- package/src/commands/decision-show.js +14 -0
- package/src/commands/decision-status.js +22 -0
- package/src/commands/discover.js +41 -0
- package/src/commands/doctor.js +15 -0
- package/src/commands/gate-record.js +205 -0
- package/src/commands/gate-revalidate.js +137 -0
- package/src/commands/model-route.js +32 -0
- package/src/commands/route.js +146 -18
- package/src/commands/semantic-plan.js +17 -0
- package/src/commands/task-abandon.js +224 -0
- package/src/commands/task-migrate-contract-bootstrap-repair.js +288 -0
- package/src/commands/task-repair-contract-bootstrap.js +263 -0
- package/src/commands/test-inventory.js +5 -0
- package/src/commands/test-prune-plan.js +5 -0
- package/src/commands/test-prune-probe.js +5 -0
- package/src/commands/test-utility.js +5 -0
- package/src/commands/validate-protocol.js +10 -1
- package/src/core/artifact-registry.js +24 -0
- package/src/core/audit-ux.js +514 -0
- package/src/core/browser-verification/constants.js +149 -0
- package/src/core/browser-verification/normalize.js +254 -0
- package/src/core/browser-verification/provider.js +519 -0
- package/src/core/browser-verification/service.js +115 -0
- package/src/core/checkpoint-revalidation.js +319 -0
- package/src/core/cli-command-definitions.js +241 -0
- package/src/core/command-executors.js +110 -0
- package/src/core/command-input.js +115 -43
- package/src/core/completion-artifacts.js +14 -5
- package/src/core/completion.js +4 -6
- package/src/core/config.js +3 -0
- package/src/core/context-compiler/budget.js +9 -0
- package/src/core/context-compiler/candidates.js +39 -0
- package/src/core/context-compiler/compiler.js +63 -0
- package/src/core/context-compiler/fingerprint.js +11 -0
- package/src/core/context-compiler/policy.js +13 -0
- package/src/core/context-compiler/result.js +23 -0
- package/src/core/contract-bootstrap-recovery.js +655 -0
- package/src/core/contract-revision.js +210 -0
- package/src/core/decision/artifact.js +69 -0
- package/src/core/decision/benchmarks.js +103 -0
- package/src/core/decision/cache.js +27 -0
- package/src/core/decision/constants.js +58 -0
- package/src/core/decision/cutover.js +34 -0
- package/src/core/decision/engine.js +22 -0
- package/src/core/decision/errors.js +68 -0
- package/src/core/decision/events.js +101 -0
- package/src/core/decision/freshness.js +19 -0
- package/src/core/decision/normalizers/index.js +115 -0
- package/src/core/decision/policy.js +18 -0
- package/src/core/decision/projection.js +16 -0
- package/src/core/decision/question-registry.js +201 -0
- package/src/core/decision/request.js +26 -0
- package/src/core/decision/resolver.js +130 -0
- package/src/core/decision/result.js +58 -0
- package/src/core/decision/service.js +156 -0
- package/src/core/decision/state-builder.js +65 -0
- package/src/core/decision/task-bindings.js +30 -0
- package/src/core/decision/test-provider.js +32 -0
- package/src/core/decision/thresholds.js +15 -0
- package/src/core/error-codes.js +278 -0
- package/src/core/events.js +226 -57
- package/src/core/evidence-readiness.js +9 -0
- package/src/core/execution-prerequisites.js +14 -0
- package/src/core/execution-profile.js +63 -38
- package/src/core/gate-provenance.js +124 -0
- package/src/core/integration-invocation-policy.js +27 -4
- package/src/core/integration-resources.js +86 -61
- package/src/core/model-router/constants.js +10 -0
- package/src/core/model-router/policy.js +103 -0
- package/src/core/model-router/router.js +37 -0
- package/src/core/next-action-model.js +58 -0
- package/src/core/next-action-phases.js +130 -42
- package/src/core/next-action-refresh.js +43 -9
- package/src/core/next-action-review-phase.js +7 -2
- package/src/core/next-action.js +35 -7
- package/src/core/phase.js +128 -10
- package/src/core/preflight-consistency.js +23 -9
- package/src/core/preflight-loaders.js +37 -5
- package/src/core/protocol-info.js +65 -0
- package/src/core/protocol.js +20 -0
- package/src/core/reconcile-closure.js +128 -52
- package/src/core/recovery-history.js +1 -0
- package/src/core/resumability.js +154 -44
- package/src/core/route-artifact.js +15 -1
- package/src/core/router.js +67 -1
- package/src/core/runtime-context.js +118 -61
- package/src/core/schema-validation.js +3 -0
- package/src/core/security-review/constants.js +64 -0
- package/src/core/security-review/normalize.js +245 -0
- package/src/core/security-review/provider.js +204 -0
- package/src/core/security-review/service.js +134 -0
- package/src/core/semantic-planning/constants.js +19 -0
- package/src/core/semantic-planning/projection.js +94 -0
- package/src/core/semantic-planning/service.js +15 -0
- package/src/core/sources.js +37 -0
- package/src/core/task-claim-state.js +201 -1
- package/src/core/task-conflict-inspection.js +31 -5
- package/src/core/task-paths.js +13 -0
- package/src/core/task-recovery.js +1 -0
- package/src/core/templates.js +3 -0
- package/src/core/test-intelligence/benchmarks.js +68 -0
- package/src/core/test-intelligence/inventory.js +73 -0
- package/src/core/test-intelligence/prune.js +90 -0
- package/src/core/test-intelligence/semantic-state.js +15 -0
- package/src/core/test-intelligence/service.js +40 -0
- package/src/core/test-intelligence/utility.js +50 -0
- package/src/core/trace.js +11 -7
- package/src/core/transaction.js +1 -0
- package/src/integration.d.ts +492 -0
- package/src/integration.js +54 -0
- package/src/providers/README.md +47 -0
- package/src/providers/capabilities.js +46 -0
- package/src/providers/errors.js +15 -0
- package/src/providers/index.js +29 -0
- package/src/providers/json-snapshot.js +105 -0
- package/src/providers/registry.js +152 -0
|
@@ -5,7 +5,7 @@ import { resolvePlannedPhase } from "./next-action-planned-phase.js";
|
|
|
5
5
|
import { resolvePendingActionGuidance } from "./next-action-pending-actions.js";
|
|
6
6
|
import { preExecutionRefreshGuidance } from "./next-action-refresh.js";
|
|
7
7
|
import { ARTIFACT_PATHS, readJsonArtifact } from "./artifacts.js";
|
|
8
|
-
import { taskArtifactPath } from "./task-paths.js";
|
|
8
|
+
import { taskArtifactPath, taskGatePath } from "./task-paths.js";
|
|
9
9
|
import { completionIdentityErrors, evaluateCompletion } from "./completion.js";
|
|
10
10
|
import { readContract } from "./contract.js";
|
|
11
11
|
import { evaluatePreflight, validatePersistedPreflight } from "./preflight.js";
|
|
@@ -16,10 +16,16 @@ import { classifyLoadedWorkState } from "./work-state.js";
|
|
|
16
16
|
|
|
17
17
|
import { evaluateStartExecutionPrerequisites } from "./execution-prerequisites.js";
|
|
18
18
|
|
|
19
|
-
import { NEXT_ACTIONS, commandFor, decision, recoveryGuidanceForClassification, result, uniqueSorted } from "./next-action-model.js";
|
|
19
|
+
import { NEXT_ACTIONS, commandFor, decision, gateRevalidationCommandSpec, recoveryGuidanceForClassification, result, routeCommandSpec, uniqueSorted } from "./next-action-model.js";
|
|
20
20
|
import { artifactError, freshnessReasons, loadArtifact, staleReasons } from "./next-action-artifacts.js";
|
|
21
21
|
|
|
22
22
|
import { inspectTaskConflictState } from "./task-conflict-inspection.js";
|
|
23
|
+
import { validateEventLedger } from "./events.js";
|
|
24
|
+
import {
|
|
25
|
+
isContractBootstrapRepairCandidate,
|
|
26
|
+
isLegacyContractBootstrapRepairMigrationCandidate,
|
|
27
|
+
} from "./contract-bootstrap-recovery.js";
|
|
28
|
+
import { contractBootstrapRepairGuidance, contractBootstrapRepairMigrationGuidance } from "./next-action-model.js";
|
|
23
29
|
|
|
24
30
|
import { evaluateContinuityNextAction } from "./next-action-continuity.js";
|
|
25
31
|
|
|
@@ -32,10 +38,85 @@ export const PHASES_REQUIRING_EXECUTION_CHRONOLOGY = new Set([
|
|
|
32
38
|
"COMPLETE",
|
|
33
39
|
]);
|
|
34
40
|
|
|
41
|
+
const PHASES_ALLOWING_GATE_REVALIDATION = new Set([
|
|
42
|
+
"EXECUTING", "VERIFYING", "DIAGNOSING", "CORRECTING", "REVIEWING",
|
|
43
|
+
]);
|
|
44
|
+
|
|
35
45
|
export function phaseRequiresExecutionChronology(phase) {
|
|
36
46
|
return PHASES_REQUIRING_EXECUTION_CHRONOLOGY.has(phase);
|
|
37
47
|
}
|
|
38
48
|
|
|
49
|
+
async function contractBootstrapRepairGuidanceForExplicitTask({ target, packageRoot, explicitTaskId, context, eventsRel, stateRel }) {
|
|
50
|
+
const ledger = await validateEventLedger(target, packageRoot, { taskId: explicitTaskId });
|
|
51
|
+
const candidate = isContractBootstrapRepairCandidate(ledger.events, ledger.errors, explicitTaskId);
|
|
52
|
+
if (candidate) {
|
|
53
|
+
const guidance = contractBootstrapRepairGuidance(explicitTaskId);
|
|
54
|
+
return result({
|
|
55
|
+
...context,
|
|
56
|
+
nextAction: guidance.nextAction,
|
|
57
|
+
commands: guidance.commands,
|
|
58
|
+
commandSpecs: guidance.commandSpecs,
|
|
59
|
+
reasons: [artifactError(guidance.reason.code, guidance.reason.message, [eventsRel, taskArtifactPath(explicitTaskId, "state")])],
|
|
60
|
+
requiredArtifacts: [eventsRel, taskArtifactPath(explicitTaskId, "contract")],
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const tolerantLedger = await validateEventLedger(target, packageRoot, {
|
|
64
|
+
taskId: explicitTaskId,
|
|
65
|
+
allowUnmigratedLegacyContractBootstrapRepairMarkers: true,
|
|
66
|
+
});
|
|
67
|
+
if (!tolerantLedger.valid || !isLegacyContractBootstrapRepairMigrationCandidate(tolerantLedger.events, explicitTaskId)) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const guidance = contractBootstrapRepairMigrationGuidance(explicitTaskId);
|
|
71
|
+
return result({
|
|
72
|
+
...context,
|
|
73
|
+
nextAction: guidance.nextAction,
|
|
74
|
+
commands: guidance.commands,
|
|
75
|
+
commandSpecs: guidance.commandSpecs,
|
|
76
|
+
reasons: [artifactError(guidance.reason.code, guidance.reason.message, [eventsRel, stateRel])],
|
|
77
|
+
requiredArtifacts: [eventsRel, taskArtifactPath(explicitTaskId, "contract"), stateRel],
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function explicitTaskConflictGuidance({ target, packageRoot, explicitTaskId, context, eventsRel, stateRel, state }) {
|
|
82
|
+
try {
|
|
83
|
+
const migrationGuidance = await contractBootstrapRepairGuidanceForExplicitTask({
|
|
84
|
+
target, packageRoot, explicitTaskId, context, eventsRel, stateRel,
|
|
85
|
+
});
|
|
86
|
+
if (migrationGuidance) return migrationGuidance;
|
|
87
|
+
} catch {
|
|
88
|
+
// The regular conflict inspection below remains the fail-closed path.
|
|
89
|
+
}
|
|
90
|
+
let inspection;
|
|
91
|
+
try {
|
|
92
|
+
inspection = await inspectTaskConflictState(target, {
|
|
93
|
+
taskId: explicitTaskId,
|
|
94
|
+
packageRoot,
|
|
95
|
+
});
|
|
96
|
+
} catch (error) {
|
|
97
|
+
inspection = {
|
|
98
|
+
classification: "INCONSISTENT",
|
|
99
|
+
reasonCodes: [error.code ?? "E_TASK_RECOVERY_INCONSISTENT"],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (["ACTIVE", "COMPLETE"].includes(inspection.classification)) return null;
|
|
103
|
+
if (PHASES_ALLOWING_GATE_REVALIDATION.has(state?.phase)
|
|
104
|
+
&& inspection.reasonCodes?.includes("E_GATE_STALE")) return null;
|
|
105
|
+
const guidance = recoveryGuidanceForClassification(inspection.classification, explicitTaskId);
|
|
106
|
+
return result({
|
|
107
|
+
...context,
|
|
108
|
+
nextAction: guidance.nextAction,
|
|
109
|
+
commands: guidance.commands,
|
|
110
|
+
commandSpecs: guidance.commandSpecs,
|
|
111
|
+
reasons: inspection.reasonCodes.map((code) => artifactError(
|
|
112
|
+
code,
|
|
113
|
+
`Task conflict state is ${inspection.classification}; follow the structured recovery guidance.`,
|
|
114
|
+
[stateRel, eventsRel, taskArtifactPath(explicitTaskId, "recovery")],
|
|
115
|
+
)),
|
|
116
|
+
requiredArtifacts: [stateRel, eventsRel],
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
39
120
|
export async function resolveNextActionPhase({
|
|
40
121
|
target,
|
|
41
122
|
packageRoot,
|
|
@@ -55,33 +136,10 @@ export async function resolveNextActionPhase({
|
|
|
55
136
|
} = {}) {
|
|
56
137
|
const { policyRecoveryAction } = helpers;
|
|
57
138
|
if (explicitTaskId) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
packageRoot,
|
|
63
|
-
});
|
|
64
|
-
} catch (error) {
|
|
65
|
-
inspection = {
|
|
66
|
-
classification: "INCONSISTENT",
|
|
67
|
-
reasonCodes: [error.code ?? "E_TASK_RECOVERY_INCONSISTENT"],
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
if (!["ACTIVE", "COMPLETE"].includes(inspection.classification)) {
|
|
71
|
-
const guidance = recoveryGuidanceForClassification(inspection.classification, explicitTaskId);
|
|
72
|
-
return result({
|
|
73
|
-
...context,
|
|
74
|
-
nextAction: guidance.nextAction,
|
|
75
|
-
commands: guidance.commands,
|
|
76
|
-
commandSpecs: guidance.commandSpecs,
|
|
77
|
-
reasons: inspection.reasonCodes.map((code) => artifactError(
|
|
78
|
-
code,
|
|
79
|
-
`Task conflict state is ${inspection.classification}; follow the structured recovery guidance.`,
|
|
80
|
-
[stateRel, eventsRel, taskArtifactPath(explicitTaskId, "recovery")],
|
|
81
|
-
)),
|
|
82
|
-
requiredArtifacts: [stateRel, eventsRel],
|
|
83
|
-
});
|
|
84
|
-
}
|
|
139
|
+
const taskConflictGuidance = await explicitTaskConflictGuidance({
|
|
140
|
+
target, packageRoot, explicitTaskId, context, eventsRel, stateRel, state,
|
|
141
|
+
});
|
|
142
|
+
if (taskConflictGuidance) return taskConflictGuidance;
|
|
85
143
|
}
|
|
86
144
|
const actionGuidance = await resolvePendingActionGuidance({ target, packageRoot, state, context, eventsRel, authorityContext, runtimeContext, helpers });
|
|
87
145
|
if (actionGuidance) return actionGuidance;
|
|
@@ -137,11 +195,11 @@ export async function resolveNextActionPhase({
|
|
|
137
195
|
contractFile: contractRel,
|
|
138
196
|
});
|
|
139
197
|
if (freshness.status === "REVALIDATION_REQUIRED") {
|
|
140
|
-
const refreshGuidance = await preExecutionRefreshGuidance({ target, packageRoot, state, contract });
|
|
198
|
+
const refreshGuidance = await preExecutionRefreshGuidance({ target, packageRoot, state, contract, freshness });
|
|
141
199
|
return result({
|
|
142
200
|
...context,
|
|
143
201
|
...refreshGuidance,
|
|
144
|
-
nextAction:
|
|
202
|
+
nextAction: refreshGuidance.nextAction,
|
|
145
203
|
reasons: freshnessReasons(state, freshness),
|
|
146
204
|
requiredArtifacts: uniqueSorted([
|
|
147
205
|
stateRel,
|
|
@@ -151,13 +209,15 @@ export async function resolveNextActionPhase({
|
|
|
151
209
|
});
|
|
152
210
|
}
|
|
153
211
|
if (state.phase === "CONTRACT_READY") {
|
|
154
|
-
return
|
|
155
|
-
context,
|
|
156
|
-
NEXT_ACTIONS.ROUTE,
|
|
157
|
-
artifactError("PHASE_CONTRACT_READY", "Persist deterministic routing for the validated contract"),
|
|
158
|
-
|
|
159
|
-
[
|
|
160
|
-
|
|
212
|
+
return result({
|
|
213
|
+
...context,
|
|
214
|
+
nextAction: NEXT_ACTIONS.ROUTE,
|
|
215
|
+
reasons: [artifactError("PHASE_CONTRACT_READY", "Persist deterministic routing for the validated contract")],
|
|
216
|
+
commands: [`forgeloop route --task ${explicitTaskId} --work <type> --json`],
|
|
217
|
+
commandSpecs: [routeCommandSpec(explicitTaskId)],
|
|
218
|
+
requiredArtifacts: contractArtifacts,
|
|
219
|
+
missingArtifacts: [routeRel],
|
|
220
|
+
});
|
|
161
221
|
}
|
|
162
222
|
const routeResult = await loadArtifact(
|
|
163
223
|
() => readPersistedRoute(target, packageRoot, { taskId: explicitTaskId }),
|
|
@@ -187,6 +247,8 @@ export async function resolveNextActionPhase({
|
|
|
187
247
|
...context,
|
|
188
248
|
nextAction: NEXT_ACTIONS.RESOLVE_STALE_ROUTE,
|
|
189
249
|
reasons: stale,
|
|
250
|
+
commands: [`forgeloop route --task ${explicitTaskId} --work <type> --json`],
|
|
251
|
+
commandSpecs: [routeCommandSpec(explicitTaskId)],
|
|
190
252
|
requiredArtifacts,
|
|
191
253
|
});
|
|
192
254
|
}
|
|
@@ -214,6 +276,20 @@ export async function resolveNextActionPhase({
|
|
|
214
276
|
],
|
|
215
277
|
});
|
|
216
278
|
}
|
|
279
|
+
const staleGateErrors = PHASES_ALLOWING_GATE_REVALIDATION.has(state.phase)
|
|
280
|
+
? (executionPrerequisites.preflight?.errors ?? []).filter((error) => error.code === "E_GATE_STALE")
|
|
281
|
+
: [];
|
|
282
|
+
if (staleGateErrors.length > 0) {
|
|
283
|
+
const gates = [...new Set(staleGateErrors.map((error) => error.gate).filter(Boolean))].sort();
|
|
284
|
+
return result({
|
|
285
|
+
...context,
|
|
286
|
+
nextAction: NEXT_ACTIONS.REVALIDATE_GATES,
|
|
287
|
+
reasons: staleGateErrors,
|
|
288
|
+
commands: gates.map((gate) => `forgeloop gate-revalidate --task ${explicitTaskId} --gate ${gate} --acknowledge-stale --json`),
|
|
289
|
+
commandSpecs: gates.map((gate) => gateRevalidationCommandSpec(explicitTaskId, gate)),
|
|
290
|
+
requiredArtifacts: executionPrerequisites.requiredArtifacts,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
217
293
|
if (executionPrerequisites.errors.length > 0) {
|
|
218
294
|
return result({
|
|
219
295
|
...context,
|
|
@@ -238,16 +314,28 @@ export async function resolveNextActionPhase({
|
|
|
238
314
|
: validatePersistedPreflight(preflightArtifact.value?.value, preflight);
|
|
239
315
|
|
|
240
316
|
if (["ROUTED", "DESIGNING", "PLANNED"].includes(state.phase) && missingGates.length > 0) {
|
|
317
|
+
const missingGatePaths = missingGates.map((gate) => taskGatePath(state.taskId, gate));
|
|
318
|
+
const gateRecordSpec = {
|
|
319
|
+
commandId: "gate-record",
|
|
320
|
+
executable: "forgeloop",
|
|
321
|
+
subcommand: "gate-record",
|
|
322
|
+
argv: ["gate-record", `--task=${state.taskId}`, `--gate=${missingGates[0]}`, "--status=satisfied", "--json"],
|
|
323
|
+
requiredInputs: [
|
|
324
|
+
{ name: "artifact", option: "--artifact=<project-relative-path>", repeatable: true },
|
|
325
|
+
{ name: "decision", option: "--decision=<text>", repeatable: true },
|
|
326
|
+
],
|
|
327
|
+
};
|
|
241
328
|
return result({
|
|
242
329
|
...context,
|
|
243
330
|
nextAction: NEXT_ACTIONS.SATISFY_GATES,
|
|
244
|
-
reasons: missingGates.map((gate) => artifactError(
|
|
331
|
+
reasons: missingGates.map((gate, index) => artifactError(
|
|
245
332
|
"E_GATE_UNVERIFIED",
|
|
246
333
|
`Required gate is missing or unverified: ${gate}`,
|
|
247
|
-
[
|
|
334
|
+
[missingGatePaths[index]],
|
|
248
335
|
)),
|
|
249
|
-
requiredArtifacts: [...preflightArtifacts, ...
|
|
250
|
-
missingArtifacts:
|
|
336
|
+
requiredArtifacts: [...preflightArtifacts, ...missingGatePaths],
|
|
337
|
+
missingArtifacts: missingGatePaths,
|
|
338
|
+
commandSpecs: [gateRecordSpec],
|
|
251
339
|
});
|
|
252
340
|
}
|
|
253
341
|
if (state.phase === "ROUTED") {
|
|
@@ -1,19 +1,53 @@
|
|
|
1
|
-
import { validateEventLedger } from "./events.js";
|
|
1
|
+
import { validateEventLedger, validateStateLedgerCoherence } from "./events.js";
|
|
2
2
|
import { readPersistedRoute } from "./route-artifact.js";
|
|
3
|
-
import { directCommandSpec } from "./next-action-model.js";
|
|
3
|
+
import { directCommandSpec, NEXT_ACTIONS } from "./next-action-model.js";
|
|
4
|
+
import { resolveTaskClaimState } from "./task-claim-state.js";
|
|
5
|
+
import { sameCanonicalGuideList } from "./contract-bootstrap-recovery.js";
|
|
6
|
+
import { CHECKPOINT_REVALIDATION_PHASES } from "./checkpoint-revalidation.js";
|
|
4
7
|
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Select a safe pre-execution refresh mutation. Repository-only drift keeps
|
|
10
|
+
* the checkpoint and its route identity; the legacy clear-state path remains
|
|
11
|
+
* limited to a documented blocked-preflight recovery.
|
|
12
|
+
*/
|
|
13
|
+
export async function preExecutionRefreshGuidance({ target, packageRoot, state, contract, freshness }) {
|
|
14
|
+
if (!["ROUTED", "DESIGNING", "PLANNED"].includes(state.phase)) return { nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER };
|
|
8
15
|
const ledger = await validateEventLedger(target, packageRoot, { taskId: state.taskId });
|
|
9
|
-
if (!ledger.valid || ledger.events.some((entry) => entry.event === "EXECUTION_STARTED"))
|
|
16
|
+
if (!ledger.valid || ledger.events.some((entry) => entry.event === "EXECUTION_STARTED")) {
|
|
17
|
+
return { nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER };
|
|
18
|
+
}
|
|
19
|
+
if (CHECKPOINT_REVALIDATION_PHASES.includes(state.phase)
|
|
20
|
+
&& freshness?.status === "REVALIDATION_REQUIRED"
|
|
21
|
+
&& [...new Set(freshness.reasons ?? [])].sort().join(",") === "REPOSITORY_CHANGED"
|
|
22
|
+
&& state.contractFingerprint === contract.fingerprint) {
|
|
23
|
+
try {
|
|
24
|
+
const route = await readPersistedRoute(target, packageRoot, { taskId: state.taskId });
|
|
25
|
+
const ownership = await resolveTaskClaimState(target, { taskId: state.taskId, packageRoot });
|
|
26
|
+
const coherenceErrors = validateStateLedgerCoherence(state, ledger.events);
|
|
27
|
+
const repositoryAvailable = typeof freshness.repository?.head === "string" && freshness.repository.head.length > 0;
|
|
28
|
+
const routeIdentityValid = route.value?.contractFingerprint === contract.fingerprint
|
|
29
|
+
&& state.routeFingerprint === route.fingerprint
|
|
30
|
+
&& sameCanonicalGuideList(state.selectedGuides, route.value.guides);
|
|
31
|
+
if (ownership.ownershipValid && ownership.mutationAllowed && repositoryAvailable
|
|
32
|
+
&& coherenceErrors.length === 0 && routeIdentityValid) {
|
|
33
|
+
return {
|
|
34
|
+
nextAction: NEXT_ACTIONS.REVALIDATE_CHECKPOINT,
|
|
35
|
+
commands: [`forgeloop checkpoint-revalidate --task ${state.taskId} --json`],
|
|
36
|
+
commandSpecs: [directCommandSpec("checkpoint-revalidate", state.taskId)],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
// Unsupported or ambiguous identity stays on the fail-closed blocker.
|
|
41
|
+
}
|
|
42
|
+
}
|
|
10
43
|
const lastPreflight = ledger.events.filter((entry) => ["PREFLIGHT_READY", "PREFLIGHT_BLOCKED"].includes(entry.event)).at(-1);
|
|
11
|
-
if (lastPreflight?.event !== "PREFLIGHT_BLOCKED") return {};
|
|
44
|
+
if (lastPreflight?.event !== "PREFLIGHT_BLOCKED") return { nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER };
|
|
12
45
|
let route;
|
|
13
46
|
try { route = await readPersistedRoute(target, packageRoot, { taskId: state.taskId }); }
|
|
14
|
-
catch { return {}; }
|
|
15
|
-
if (route.value.contractFingerprint !== contract.fingerprint) return {};
|
|
47
|
+
catch { return { nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER }; }
|
|
48
|
+
if (route.value.contractFingerprint !== contract.fingerprint) return { nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER };
|
|
16
49
|
return {
|
|
50
|
+
nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
|
|
17
51
|
commands: [`forgeloop clear-state --task ${state.taskId}`, `forgeloop preflight --task ${state.taskId}`],
|
|
18
52
|
commandSpecs: [directCommandSpec("clear-state", state.taskId), directCommandSpec("preflight", state.taskId)],
|
|
19
53
|
};
|
|
@@ -2,7 +2,7 @@ import { readJsonArtifact } from "./artifacts.js";
|
|
|
2
2
|
import { evaluateCompletion } from "./completion.js";
|
|
3
3
|
import { validateReceipt } from "./receipt.js";
|
|
4
4
|
import { completionRelationshipErrors } from "./completion-relationships.js";
|
|
5
|
-
import { evaluateRequiredEvidence, classifyRequirement } from "./evidence-readiness.js";
|
|
5
|
+
import { evaluateRequiredEvidence, classifyRequirement, terminalRequirementsForContract } from "./evidence-readiness.js";
|
|
6
6
|
import { validateEventLedger, validateCompletionRecoveryAuthorization } from "./events.js";
|
|
7
7
|
import { NEXT_ACTIONS, commandFor, decision, recordTerminalResultCommandSpec, result } from "./next-action-model.js";
|
|
8
8
|
import { artifactError, checkListReasons, loadArtifact, requirementsAndCoverage } from "./next-action-artifacts.js";
|
|
@@ -153,9 +153,14 @@ export async function resolveReviewingPhase({ state, context, requiredArtifacts,
|
|
|
153
153
|
err.code === "E_PUBLICATION_REQUIREMENT_PENDING" || err.code === "E_PRODUCTION_REQUIREMENT_PENDING"
|
|
154
154
|
));
|
|
155
155
|
if (terminalPendingErrors.length > 0 && terminalPendingErrors.length === completion.errors.length) {
|
|
156
|
+
const canonicalTerminalRequirements = terminalRequirementsForContract(contract.value, {
|
|
157
|
+
additionalEvidence: preflight.policy?.requiredEvidence ?? [],
|
|
158
|
+
});
|
|
156
159
|
const terminalPendingReqs = terminalPendingErrors.map((err) => {
|
|
157
160
|
const reqId = err.requirementId;
|
|
158
|
-
const matchingReq =
|
|
161
|
+
const matchingReq = canonicalTerminalRequirements.find((r) => r.id === reqId)
|
|
162
|
+
?? canonicalTerminalRequirements.find((r) => r.text === reqId)
|
|
163
|
+
?? evidence.requirements.find((r) => r.id === reqId)
|
|
159
164
|
?? evidence.requirements.find((r) => r.text === reqId)
|
|
160
165
|
?? classifyRequirement(reqId ?? err.message);
|
|
161
166
|
return matchingReq;
|
package/src/core/next-action.js
CHANGED
|
@@ -287,13 +287,41 @@ async function computeNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
287
287
|
});
|
|
288
288
|
}
|
|
289
289
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
290
|
+
const events = await readEvents(target, packageRoot, { taskId: explicitTaskId });
|
|
291
|
+
if (events.some((event) => event.event === "DISCOVERY_STARTED")) {
|
|
292
|
+
return result({
|
|
293
|
+
taskId: explicitTaskId ?? "unknown",
|
|
294
|
+
currentPhase: "DISCOVERING",
|
|
295
|
+
nextAction: NEXT_ACTIONS.CREATE_CONTRACT,
|
|
296
|
+
reasons: [artifactError("PHASE_DISCOVERING", "Create and validate the task contract", [contractRel])],
|
|
297
|
+
commands: [`forgeloop contract-create --task ${explicitTaskId} --preset <documentation|bug|feature|release> --json`],
|
|
298
|
+
commandSpecs: [{
|
|
299
|
+
commandId: "contract-create",
|
|
300
|
+
executable: "forgeloop",
|
|
301
|
+
subcommand: "contract-create",
|
|
302
|
+
argv: ["contract-create", `--task=${explicitTaskId}`, "--json"],
|
|
303
|
+
requiredInputs: [{ name: "preset", option: "--preset=<documentation|bug|feature|release>" }],
|
|
304
|
+
}],
|
|
305
|
+
requiredArtifacts: [contractRel],
|
|
306
|
+
missingArtifacts: [contractRel, stateRel],
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
return result({
|
|
310
|
+
taskId: explicitTaskId ?? "unknown",
|
|
311
|
+
currentPhase: "RECEIVED",
|
|
312
|
+
nextAction: NEXT_ACTIONS.DISCOVER,
|
|
313
|
+
reasons: [artifactError("WORK_STATE_ABSENT", "No work-state checkpoint is present", [stateRel])],
|
|
314
|
+
commands: [`forgeloop discover --task ${explicitTaskId} --json`],
|
|
315
|
+
commandSpecs: [{
|
|
316
|
+
commandId: "discover",
|
|
317
|
+
executable: "forgeloop",
|
|
318
|
+
subcommand: "discover",
|
|
319
|
+
argv: ["discover", `--task=${explicitTaskId}`, "--json"],
|
|
320
|
+
requiredInputs: [],
|
|
321
|
+
}],
|
|
322
|
+
requiredArtifacts: [stateRel],
|
|
323
|
+
missingArtifacts: [stateRel],
|
|
324
|
+
});
|
|
297
325
|
}
|
|
298
326
|
|
|
299
327
|
return resolveNextActionPhase({
|
package/src/core/phase.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
LIFECYCLE_MILESTONES,
|
|
6
6
|
validateEventLedger,
|
|
7
7
|
validateStateLedgerCoherence,
|
|
8
|
+
isRevisionEpochPlanRepeat,
|
|
8
9
|
} from "./events.js";
|
|
9
10
|
import { readPersistedRoute } from "./route-artifact.js";
|
|
10
11
|
import { assertWorkPhase, isValidTransition } from "./protocol.js";
|
|
@@ -13,7 +14,7 @@ import { authorizeCompletionRecoveryOrRebind } from "./completion-recovery-rebin
|
|
|
13
14
|
import { evaluateCompletion, runComplete as persistCompletion } from "./completion.js";
|
|
14
15
|
import { evaluatePreflight } from "./preflight.js";
|
|
15
16
|
import { requiredEvidenceForTarget } from "./completion-artifacts.js";
|
|
16
|
-
import { assertCompletionRelationships, assertStateIdentity } from "./completion-relationships.js";
|
|
17
|
+
import { assertCompletionRelationships, assertStateIdentity, stateIdentityErrors } from "./completion-relationships.js";
|
|
17
18
|
import { createReceipt, validateReceipt } from "./receipt.js";
|
|
18
19
|
import { assertExecutionPrerequisites, hasExecutionStarted } from "./execution-prerequisites.js";
|
|
19
20
|
import { taskArtifactPath } from "./task-paths.js";
|
|
@@ -27,6 +28,13 @@ import {
|
|
|
27
28
|
evaluateStructuredDiagnosticStall,
|
|
28
29
|
} from "./information-gain-projection.js";
|
|
29
30
|
import { assertStructuralQualityExecutionReady } from "./structural-quality/service.js";
|
|
31
|
+
import { getTaskTransaction, withTaskTransaction } from "./transaction.js";
|
|
32
|
+
import {
|
|
33
|
+
CONTRACT_BOOTSTRAP_REPAIR_EVENT,
|
|
34
|
+
ROUTE_CHECKPOINT_BOUND_EVENT,
|
|
35
|
+
resolveCanonicalPostRepairCheckpointBinding,
|
|
36
|
+
sameCanonicalGuideList,
|
|
37
|
+
} from "./contract-bootstrap-recovery.js";
|
|
30
38
|
|
|
31
39
|
function phaseError(code, message, artifacts = []) {
|
|
32
40
|
const error = new Error(message);
|
|
@@ -55,15 +63,13 @@ const LATE_PHASES = new Set([
|
|
|
55
63
|
"COMPLETE",
|
|
56
64
|
]);
|
|
57
65
|
|
|
58
|
-
async function
|
|
66
|
+
async function readPhaseIdentityArtifacts(target, state, toPhase, packageRoot, options, paths) {
|
|
59
67
|
const scopedTaskId = options.taskId ?? null;
|
|
60
68
|
const requireContract = LATE_PHASES.has(state.phase)
|
|
61
69
|
|| ["CONTRACT_READY", "ROUTED", "EXECUTING"].includes(toPhase);
|
|
62
|
-
const requireRoute =
|
|
70
|
+
const requireRoute = state.phase === "ROUTED"
|
|
71
|
+
|| LATE_PHASES.has(state.phase)
|
|
63
72
|
|| ["ROUTED", "EXECUTING"].includes(toPhase);
|
|
64
|
-
const contractRel = options.contractPath ?? (scopedTaskId ? taskArtifactPath(scopedTaskId, "contract") : ARTIFACT_PATHS.contract);
|
|
65
|
-
const routeRel = options.routePath ?? (scopedTaskId ? taskArtifactPath(scopedTaskId, "route") : ARTIFACT_PATHS.route);
|
|
66
|
-
|
|
67
73
|
let contract = null;
|
|
68
74
|
let route = null;
|
|
69
75
|
try {
|
|
@@ -75,7 +81,7 @@ async function assertPersistedStateIdentity(target, state, toPhase, packageRoot,
|
|
|
75
81
|
throw phaseError(
|
|
76
82
|
requireContract ? "E_PHASE_PREREQUISITE_MISSING" : error.code ?? "E_CONTRACT_INVALID",
|
|
77
83
|
`${requireContract ? `Phase ${toPhase} requires current contract` : "Unable to validate current contract"}: ${error.message}`,
|
|
78
|
-
[contractRel],
|
|
84
|
+
[paths.contractRel],
|
|
79
85
|
);
|
|
80
86
|
}
|
|
81
87
|
}
|
|
@@ -88,11 +94,69 @@ async function assertPersistedStateIdentity(target, state, toPhase, packageRoot,
|
|
|
88
94
|
throw phaseError(
|
|
89
95
|
requireRoute ? "E_PHASE_PREREQUISITE_MISSING" : error.code ?? "E_ROUTE_INVALID",
|
|
90
96
|
`${requireRoute ? `Phase ${toPhase} requires persisted route` : "Unable to validate persisted route"}: ${error.message}`,
|
|
91
|
-
[routeRel],
|
|
97
|
+
[paths.routeRel],
|
|
92
98
|
);
|
|
93
99
|
}
|
|
94
100
|
}
|
|
101
|
+
return { contract, route };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function assertRepairedCheckpointIdentity({ contract, route, state, events, contractRel, routeRel, stateRel }) {
|
|
105
|
+
const repairMarker = events.find((event) => event.event === CONTRACT_BOOTSTRAP_REPAIR_EVENT);
|
|
106
|
+
if (!repairMarker || state.phase === "ROUTED") return false;
|
|
107
|
+
const checkpoint = resolveCanonicalPostRepairCheckpointBinding(events, repairMarker);
|
|
108
|
+
if (!checkpoint) {
|
|
109
|
+
throw phaseError(
|
|
110
|
+
"E_PHASE_CHRONOLOGY_INVALID",
|
|
111
|
+
"Repaired late-phase state requires a canonical route checkpoint binding",
|
|
112
|
+
[contractRel, routeRel],
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
const identityErrors = [
|
|
116
|
+
...stateIdentityErrors({ contract, state }),
|
|
117
|
+
...stateIdentityErrors({ contract, route }),
|
|
118
|
+
];
|
|
119
|
+
if (state.routeFingerprint !== checkpoint.routeFingerprint
|
|
120
|
+
|| !sameCanonicalGuideList(state.selectedGuides, checkpoint.selectedGuides)) {
|
|
121
|
+
identityErrors.push({
|
|
122
|
+
code: "E_ROUTE_GUIDE_MISMATCH",
|
|
123
|
+
message: "Work state does not match the immutable route checkpoint identity",
|
|
124
|
+
artifacts: [routeRel, stateRel],
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (identityErrors.length > 0) {
|
|
128
|
+
const first = identityErrors[0];
|
|
129
|
+
throw phaseError(first.code, first.message, first.artifacts);
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function assertPersistedStateIdentity(target, state, toPhase, packageRoot, options = {}) {
|
|
135
|
+
const scopedTaskId = options.taskId ?? null;
|
|
136
|
+
const events = options.events ?? [];
|
|
137
|
+
const contractRel = options.contractPath ?? (scopedTaskId ? taskArtifactPath(scopedTaskId, "contract") : ARTIFACT_PATHS.contract);
|
|
138
|
+
const routeRel = options.routePath ?? (scopedTaskId ? taskArtifactPath(scopedTaskId, "route") : ARTIFACT_PATHS.route);
|
|
139
|
+
const stateRel = options.statePath ?? (scopedTaskId ? taskArtifactPath(scopedTaskId, "state") : ARTIFACT_PATHS.state);
|
|
140
|
+
const { contract, route } = await readPhaseIdentityArtifacts(
|
|
141
|
+
target,
|
|
142
|
+
state,
|
|
143
|
+
toPhase,
|
|
144
|
+
packageRoot,
|
|
145
|
+
options,
|
|
146
|
+
{ contractRel, routeRel },
|
|
147
|
+
);
|
|
95
148
|
if (!contract && !route) return;
|
|
149
|
+
if (toPhase !== "ROUTED" && assertRepairedCheckpointIdentity({
|
|
150
|
+
contract,
|
|
151
|
+
route,
|
|
152
|
+
state,
|
|
153
|
+
events,
|
|
154
|
+
contractRel,
|
|
155
|
+
routeRel,
|
|
156
|
+
stateRel,
|
|
157
|
+
})) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
96
160
|
try {
|
|
97
161
|
assertStateIdentity({ contract, route, state });
|
|
98
162
|
} catch (error) {
|
|
@@ -116,6 +180,11 @@ function reconcileImplementationStep(state, toPhase) {
|
|
|
116
180
|
};
|
|
117
181
|
}
|
|
118
182
|
|
|
183
|
+
function isRevisionPlanRepeat(events, eventType) {
|
|
184
|
+
return eventType === "PLAN_RECORDED"
|
|
185
|
+
&& isRevisionEpochPlanRepeat(events, events.length, { event: eventType });
|
|
186
|
+
}
|
|
187
|
+
|
|
119
188
|
async function assertPhasePrerequisites(target, state, toPhase, packageRoot, authorityContext, runtimeContext, options = {}) {
|
|
120
189
|
const scopedTaskId = options.taskId ?? null;
|
|
121
190
|
const contractRel = options.contractPath ?? (scopedTaskId ? taskArtifactPath(scopedTaskId, "contract") : ARTIFACT_PATHS.contract);
|
|
@@ -160,8 +229,39 @@ async function assertPhasePrerequisites(target, state, toPhase, packageRoot, aut
|
|
|
160
229
|
}
|
|
161
230
|
}
|
|
162
231
|
|
|
232
|
+
async function appendRepairRouteCheckpoint({ target, packageRoot, taskId, state, events, stateRel, eventsRel }) {
|
|
233
|
+
const repairMarker = events.find((event) => event.event === CONTRACT_BOOTSTRAP_REPAIR_EVENT);
|
|
234
|
+
const checkpointAlreadyBound = events.some((event) => event.event === ROUTE_CHECKPOINT_BOUND_EVENT);
|
|
235
|
+
if (!repairMarker || state.phase !== "ROUTED" || checkpointAlreadyBound) return;
|
|
236
|
+
if (typeof state.routeFingerprint !== "string") {
|
|
237
|
+
throw phaseError("E_ROUTE_GUIDE_MISMATCH", "ROUTED state requires a route fingerprint before checkpoint binding", [stateRel, eventsRel]);
|
|
238
|
+
}
|
|
239
|
+
await appendProtocolEvent(target, {
|
|
240
|
+
taskId: state.taskId,
|
|
241
|
+
event: ROUTE_CHECKPOINT_BOUND_EVENT,
|
|
242
|
+
details: {
|
|
243
|
+
routeFingerprint: state.routeFingerprint,
|
|
244
|
+
contractFingerprint: state.contractFingerprint,
|
|
245
|
+
selectedGuides: [...state.selectedGuides].sort(),
|
|
246
|
+
},
|
|
247
|
+
}, packageRoot, { taskId, eventsPath: eventsRel });
|
|
248
|
+
}
|
|
249
|
+
|
|
163
250
|
export async function advanceWorkState(target, toPhase, options = {}) {
|
|
164
251
|
const normalizedOptions = typeof options === "string" ? { packageRoot: options } : options;
|
|
252
|
+
if (!normalizedOptions.taskId || await getTaskTransaction(target)) {
|
|
253
|
+
return advanceWorkStateInternal(target, toPhase, normalizedOptions);
|
|
254
|
+
}
|
|
255
|
+
return withTaskTransaction({
|
|
256
|
+
target,
|
|
257
|
+
taskId: normalizedOptions.taskId,
|
|
258
|
+
operation: "advance",
|
|
259
|
+
packageRoot: normalizedOptions.packageRoot,
|
|
260
|
+
recordCommitEvent: true,
|
|
261
|
+
}, async () => advanceWorkStateInternal(target, toPhase, normalizedOptions));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async function advanceWorkStateInternal(target, toPhase, normalizedOptions) {
|
|
165
265
|
const {
|
|
166
266
|
packageRoot,
|
|
167
267
|
now = new Date().toISOString(),
|
|
@@ -211,7 +311,6 @@ export async function advanceWorkState(target, toPhase, options = {}) {
|
|
|
211
311
|
}
|
|
212
312
|
|
|
213
313
|
await assertPhasePrerequisites(target, state, toPhase, packageRoot, authorityContext, runtimeContext, { taskId, statePath, contractPath, routePath, receiptPath, eventsPath });
|
|
214
|
-
await assertPersistedStateIdentity(target, state, toPhase, packageRoot, { taskId, contractPath, routePath });
|
|
215
314
|
|
|
216
315
|
if (toPhase === "EXECUTING" && taskId) {
|
|
217
316
|
// Multi-task checkout scope checks
|
|
@@ -259,6 +358,12 @@ export async function advanceWorkState(target, toPhase, options = {}) {
|
|
|
259
358
|
if (coherenceErrors.length > 0) {
|
|
260
359
|
throw phaseError(coherenceErrors[0].code, coherenceErrors[0].message, [stateRel, eventsRel]);
|
|
261
360
|
}
|
|
361
|
+
await assertPersistedStateIdentity(target, state, toPhase, packageRoot, {
|
|
362
|
+
taskId,
|
|
363
|
+
contractPath,
|
|
364
|
+
routePath,
|
|
365
|
+
events: ledger.events,
|
|
366
|
+
});
|
|
262
367
|
const eventType = PHASE_EVENTS[toPhase];
|
|
263
368
|
const reenteringVerification = toPhase === "VERIFYING" && ["CORRECTING", "REVIEWING"].includes(state.phase);
|
|
264
369
|
if (toPhase === "VERIFYING" && state.phase === "REVIEWING") {
|
|
@@ -338,11 +443,15 @@ export async function advanceWorkState(target, toPhase, options = {}) {
|
|
|
338
443
|
}
|
|
339
444
|
const repeatedReview = state.phase === "VERIFYING" && toPhase === "REVIEWING"
|
|
340
445
|
&& ledger.events.some((event) => event.taskId === state.taskId && event.event === "REVIEW_STARTED");
|
|
446
|
+
const revisionPlanRepeat = isRevisionPlanRepeat(ledger.events, eventType);
|
|
341
447
|
if (eventType && !reenteringVerification && !repeatedReview
|
|
448
|
+
&& !revisionPlanRepeat
|
|
342
449
|
&& ledger.events.some((event) => event.taskId === state.taskId && event.event === eventType)) {
|
|
343
450
|
throw phaseError("E_PHASE_CHRONOLOGY_INVALID", `Lifecycle milestone already exists: ${eventType}`, [eventsRel]);
|
|
344
451
|
}
|
|
345
|
-
const milestoneIndex = reenteringVerification || repeatedReview
|
|
452
|
+
const milestoneIndex = reenteringVerification || repeatedReview || revisionPlanRepeat
|
|
453
|
+
? -1
|
|
454
|
+
: LIFECYCLE_MILESTONES.indexOf(eventType);
|
|
346
455
|
if (milestoneIndex >= 0) {
|
|
347
456
|
const lastMilestone = ledger.events.reduce((last, event) => Math.max(last, LIFECYCLE_MILESTONES.indexOf(event.event)), -1);
|
|
348
457
|
if (lastMilestone !== milestoneIndex - 1) {
|
|
@@ -361,6 +470,15 @@ export async function advanceWorkState(target, toPhase, options = {}) {
|
|
|
361
470
|
}
|
|
362
471
|
if (reenteringVerification) delete next.lastCompletionAttempt;
|
|
363
472
|
next.revision = (state.revision ?? 0) + 1;
|
|
473
|
+
await appendRepairRouteCheckpoint({
|
|
474
|
+
target,
|
|
475
|
+
packageRoot,
|
|
476
|
+
taskId,
|
|
477
|
+
state,
|
|
478
|
+
events: ledger.events,
|
|
479
|
+
stateRel,
|
|
480
|
+
eventsRel,
|
|
481
|
+
});
|
|
364
482
|
let nextReceipt = null;
|
|
365
483
|
try {
|
|
366
484
|
const receipt = await readJsonArtifact(target, receiptRel, "execution-receipt", packageRoot);
|