@cassiomc1/forgeloop 0.1.15 → 1.0.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/DOCS_INDEX.md +60 -0
- package/EXECUTION_STATE.md +9 -0
- package/LOOP_ENGINEERING.md +15 -0
- package/LOOP_SYSTEM_DESIGN.md +8 -0
- package/PROTOCOL_INTEGRATION.md +8 -0
- package/QUALITY_SCORECARD.md +2 -0
- package/README.md +183 -654
- package/TERMINOLOGY.md +4 -0
- package/THREAT_MODEL.md +9 -0
- package/docs/assets/forgeloop-flow.svg +1 -0
- package/docs/forgeloop-flow.mmd +51 -0
- package/package.json +16 -3
- package/schemas/continuity.schema.json +57 -0
- package/scripts/CI_VALIDATORS.md +32 -0
- package/src/cli.js +307 -204
- package/src/commands/clear-continuity.js +9 -0
- package/src/commands/continuity.js +25 -0
- package/src/commands/doctor.js +17 -2
- package/src/commands/reconcile-continuity.js +23 -0
- package/src/commands/record-continuity.js +63 -0
- package/src/commands/status.js +14 -1
- package/src/commands/update.js +12 -13
- package/src/commands/validate-protocol.js +19 -0
- package/src/core/artifacts.js +1 -0
- package/src/core/bundles.js +6 -0
- package/src/core/command-resolution.js +295 -0
- package/src/core/command-tokenizer.js +122 -0
- package/src/core/conformance.js +8 -2
- package/src/core/continuity-cli-options.js +58 -0
- package/src/core/continuity-conformance.js +46 -0
- package/src/core/continuity-observability.js +20 -0
- package/src/core/continuity-reconciliation.js +224 -0
- package/src/core/continuity.js +245 -0
- package/src/core/inspect.js +9 -1
- package/src/core/installation-authority.js +178 -0
- package/src/core/json-safety.js +17 -13
- package/src/core/next-action-artifacts.js +118 -0
- package/src/core/next-action-continuity.js +65 -0
- package/src/core/next-action-model.js +127 -0
- package/src/core/next-action-phases.js +12 -0
- package/src/core/next-action.js +22 -249
- package/src/core/npm-classifier.js +343 -0
- package/src/core/package-manager-classifiers.js +37 -0
- package/src/core/preflight-consistency.js +221 -0
- package/src/core/preflight-loaders.js +112 -0
- package/src/core/preflight-model.js +83 -0
- package/src/core/preflight.js +34 -444
- package/src/core/protocol.js +7 -0
- package/src/core/schema-validation.js +15 -1
- package/src/core/templates.js +2 -0
- package/src/core/verification-capability.js +31 -1106
- package/src/core/verification-constants.js +61 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { ARTIFACT_PATHS } from "./artifacts.js";
|
|
2
|
+
import { reconcileContinuity } from "./continuity-reconciliation.js";
|
|
3
|
+
import { NEXT_ACTIONS, result } from "./next-action-model.js";
|
|
4
|
+
|
|
5
|
+
const REQUIRED_ARTIFACTS = Object.freeze([ARTIFACT_PATHS.state, ARTIFACT_PATHS.continuity]);
|
|
6
|
+
|
|
7
|
+
function reason(code, message, artifacts = REQUIRED_ARTIFACTS) {
|
|
8
|
+
return { code, message, artifacts };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function nextActionForContinuity({ context, continuity } = {}) {
|
|
12
|
+
if (!continuity || ["ABSENT", "NOT_APPLICABLE"].includes(continuity.classification)) return null;
|
|
13
|
+
|
|
14
|
+
if (continuity.classification === "FRESH") {
|
|
15
|
+
const remaining = continuity.continuity?.remainingWork ?? [];
|
|
16
|
+
if (remaining.length === 0) return null;
|
|
17
|
+
return result({
|
|
18
|
+
...context,
|
|
19
|
+
nextAction: NEXT_ACTIONS.CONTINUE_IMPLEMENTATION,
|
|
20
|
+
reasons: [reason(
|
|
21
|
+
"CONTINUITY_REMAINING_WORK",
|
|
22
|
+
`Execution continuity records ${remaining.length} remaining implementation item${remaining.length === 1 ? "" : "s"}.`,
|
|
23
|
+
[ARTIFACT_PATHS.continuity],
|
|
24
|
+
)],
|
|
25
|
+
requiredArtifacts: REQUIRED_ARTIFACTS,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (continuity.classification === "RECONCILIATION_REQUIRED") {
|
|
30
|
+
return result({
|
|
31
|
+
...context,
|
|
32
|
+
nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
|
|
33
|
+
commands: ["forgeloop reconcile-continuity"],
|
|
34
|
+
reasons: [reason(
|
|
35
|
+
"E_CONTINUITY_RECONCILIATION_REQUIRED",
|
|
36
|
+
"Execution continuity no longer matches canonical state or the current checkout; reconcile it before advancing verification.",
|
|
37
|
+
)],
|
|
38
|
+
requiredArtifacts: REQUIRED_ARTIFACTS,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (["INVALID", "INCONSISTENT"].includes(continuity.classification)) {
|
|
43
|
+
const codes = continuity.reasonCodes?.length > 0
|
|
44
|
+
? continuity.reasonCodes
|
|
45
|
+
: [continuity.classification === "INVALID" ? "E_CONTINUITY_INVALID" : "E_CONTINUITY_INCONSISTENT"];
|
|
46
|
+
return result({
|
|
47
|
+
...context,
|
|
48
|
+
nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
|
|
49
|
+
reasons: codes.map((code, index) => reason(
|
|
50
|
+
code,
|
|
51
|
+
continuity.reasons?.[index] ?? `Execution continuity is ${continuity.classification.toLowerCase()}.`,
|
|
52
|
+
)),
|
|
53
|
+
requiredArtifacts: REQUIRED_ARTIFACTS,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function evaluateContinuityNextAction({ target, packageRoot, context } = {}) {
|
|
61
|
+
return nextActionForContinuity({
|
|
62
|
+
context,
|
|
63
|
+
continuity: await reconcileContinuity({ target, packageRoot }),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { PROTOCOL_VERSION } from "./protocol.js";
|
|
2
|
+
import { sha256 } from "./manifest.js";
|
|
3
|
+
|
|
4
|
+
export const NEXT_ACTIONS = Object.freeze({
|
|
5
|
+
DISCOVER: "DISCOVER",
|
|
6
|
+
CREATE_CONTRACT: "CREATE_CONTRACT",
|
|
7
|
+
ROUTE: "ROUTE",
|
|
8
|
+
SATISFY_GATES: "SATISFY_GATES",
|
|
9
|
+
RUN_PREFLIGHT: "RUN_PREFLIGHT",
|
|
10
|
+
PLAN: "PLAN",
|
|
11
|
+
START_EXECUTION: "START_EXECUTION",
|
|
12
|
+
ENTER_VERIFYING: "ENTER_VERIFYING",
|
|
13
|
+
CONTINUE_IMPLEMENTATION: "CONTINUE_IMPLEMENTATION",
|
|
14
|
+
RECORD_VERIFICATION: "RECORD_VERIFICATION",
|
|
15
|
+
DIAGNOSE: "DIAGNOSE",
|
|
16
|
+
CORRECT: "CORRECT",
|
|
17
|
+
ENTER_REVIEWING: "ENTER_REVIEWING",
|
|
18
|
+
RECORD_TERMINAL_RESULT: "RECORD_TERMINAL_RESULT",
|
|
19
|
+
PREPARE_COMPLETION: "PREPARE_COMPLETION",
|
|
20
|
+
RUN_COMPLETE: "RUN_COMPLETE",
|
|
21
|
+
RESOLVE_STALE_ROUTE: "RESOLVE_STALE_ROUTE",
|
|
22
|
+
RESOLVE_BLOCKER: "RESOLVE_BLOCKER",
|
|
23
|
+
NONE: "NONE",
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export function result({
|
|
27
|
+
taskId = "unknown",
|
|
28
|
+
currentPhase = "RECEIVED",
|
|
29
|
+
nextAction,
|
|
30
|
+
reasons = [],
|
|
31
|
+
commands = [],
|
|
32
|
+
commandSpecs = [],
|
|
33
|
+
requiredArtifacts = [],
|
|
34
|
+
missingArtifacts = [],
|
|
35
|
+
}) {
|
|
36
|
+
const normalizedReasons = reasons
|
|
37
|
+
.map((reason) => ({
|
|
38
|
+
code: reason.code ?? "E_NEXT_ACTION_BLOCKED",
|
|
39
|
+
message: reason.message ?? String(reason),
|
|
40
|
+
artifacts: uniqueSorted(reason.artifacts ?? []),
|
|
41
|
+
}))
|
|
42
|
+
.sort((left, right) => left.code.localeCompare(right.code)
|
|
43
|
+
|| left.artifacts.join("\0").localeCompare(right.artifacts.join("\0"))
|
|
44
|
+
|| left.message.localeCompare(right.message));
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
schemaVersion: 1,
|
|
48
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
49
|
+
taskId,
|
|
50
|
+
currentPhase,
|
|
51
|
+
nextAction,
|
|
52
|
+
terminal: nextAction === NEXT_ACTIONS.NONE,
|
|
53
|
+
reasonCodes: uniqueSorted(normalizedReasons.map((reason) => reason.code)),
|
|
54
|
+
reasons: normalizedReasons,
|
|
55
|
+
commands: uniqueSorted(commands),
|
|
56
|
+
commandSpecs: [...new Map(commandSpecs.map((spec) => [JSON.stringify(spec), spec])).values()]
|
|
57
|
+
.sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right))),
|
|
58
|
+
requiredArtifacts: uniqueSorted(requiredArtifacts),
|
|
59
|
+
missingArtifacts: uniqueSorted(missingArtifacts),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function commandFor(action) {
|
|
64
|
+
return {
|
|
65
|
+
[NEXT_ACTIONS.PLAN]: "forgeloop advance --to PLANNED",
|
|
66
|
+
[NEXT_ACTIONS.RUN_PREFLIGHT]: "forgeloop preflight --json",
|
|
67
|
+
[NEXT_ACTIONS.START_EXECUTION]: "forgeloop advance --to EXECUTING",
|
|
68
|
+
[NEXT_ACTIONS.ENTER_VERIFYING]: "forgeloop advance --to VERIFYING",
|
|
69
|
+
[NEXT_ACTIONS.DIAGNOSE]: "forgeloop advance --to DIAGNOSING",
|
|
70
|
+
[NEXT_ACTIONS.CORRECT]: "forgeloop advance --to CORRECTING",
|
|
71
|
+
[NEXT_ACTIONS.ENTER_REVIEWING]: "forgeloop advance --to REVIEWING",
|
|
72
|
+
[NEXT_ACTIONS.RECORD_TERMINAL_RESULT]: "forgeloop record-terminal-result",
|
|
73
|
+
[NEXT_ACTIONS.PREPARE_COMPLETION]: "forgeloop prepare-completion --json",
|
|
74
|
+
[NEXT_ACTIONS.RUN_COMPLETE]: "forgeloop complete --json",
|
|
75
|
+
}[action];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function recordCheckCommandSpec(requirement) {
|
|
79
|
+
const checkId = `requirement-${sha256(Buffer.from(requirement)).slice(0, 16)}`;
|
|
80
|
+
return {
|
|
81
|
+
commandId: "record-check",
|
|
82
|
+
executable: "forgeloop",
|
|
83
|
+
subcommand: "record-check",
|
|
84
|
+
argv: ["record-check", `--id=${checkId}`, `--requirement=${requirement}`],
|
|
85
|
+
requiredInputs: [
|
|
86
|
+
{ name: "status", option: "--status=<passed|failed|blocked|not-run>" },
|
|
87
|
+
{ name: "evidenceKind", option: "--evidence-kind=<OBSERVED|INFERRED|NOT_VERIFIED|BLOCKED>" },
|
|
88
|
+
{ name: "result", option: "--result=<text>" },
|
|
89
|
+
{ name: "exitCode", option: "--exit-code=<number>", optional: true },
|
|
90
|
+
],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function recordTerminalResultCommandSpec(requirement) {
|
|
95
|
+
const reqId = requirement.id ?? requirement;
|
|
96
|
+
const type = requirement.type ?? "PUBLICATION";
|
|
97
|
+
const status = requirement.requiredPublicationStatus ?? (type === "PUBLICATION" ? "published" : "ready");
|
|
98
|
+
return {
|
|
99
|
+
commandId: "record-terminal-result",
|
|
100
|
+
executable: "forgeloop",
|
|
101
|
+
subcommand: "record-terminal-result",
|
|
102
|
+
argv: ["record-terminal-result", `--requirement=${reqId}`, `--type=${type}`, `--status=${status}`],
|
|
103
|
+
requiredInputs: [
|
|
104
|
+
{ name: "source", option: "--source=<text>" },
|
|
105
|
+
{ name: "result", option: "--result=<text>" },
|
|
106
|
+
{ name: "details", option: "--details=<json>", optional: true },
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function decision(input, action, reason, requiredArtifacts = [], missingArtifacts = []) {
|
|
112
|
+
const command = commandFor(action);
|
|
113
|
+
return result({
|
|
114
|
+
...input,
|
|
115
|
+
nextAction: action,
|
|
116
|
+
reasons: [reason],
|
|
117
|
+
...(command ? { commands: [command] } : {}),
|
|
118
|
+
requiredArtifacts,
|
|
119
|
+
missingArtifacts,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function uniqueSorted(values) {
|
|
124
|
+
return [...new Set(values.filter(Boolean))].sort();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export { uniqueSorted };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const PHASES_REQUIRING_EXECUTION_CHRONOLOGY = new Set([
|
|
2
|
+
"EXECUTING",
|
|
3
|
+
"VERIFYING",
|
|
4
|
+
"DIAGNOSING",
|
|
5
|
+
"CORRECTING",
|
|
6
|
+
"REVIEWING",
|
|
7
|
+
"COMPLETE",
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
export function phaseRequiresExecutionChronology(phase) {
|
|
11
|
+
return PHASES_REQUIRING_EXECUTION_CHRONOLOGY.has(phase);
|
|
12
|
+
}
|
package/src/core/next-action.js
CHANGED
|
@@ -1,267 +1,38 @@
|
|
|
1
1
|
import { ARTIFACT_PATHS, readJsonArtifact } from "./artifacts.js";
|
|
2
2
|
import { completionIdentityErrors, evaluateCompletion } from "./completion.js";
|
|
3
|
-
import { requiredEvidenceForTarget } from "./completion-artifacts.js";
|
|
4
|
-
import { coverageForRequirements } from "./coverage.js";
|
|
5
3
|
import { readContract } from "./contract.js";
|
|
6
4
|
import { evaluatePreflight, validatePersistedPreflight, validateReadyProtocolConsistency } from "./preflight.js";
|
|
7
|
-
import { PROTOCOL_VERSION } from "./protocol.js";
|
|
8
5
|
import { validateReceipt } from "./receipt.js";
|
|
9
6
|
import { readPersistedRoute } from "./route-artifact.js";
|
|
10
|
-
import { assertCheckList } from "./checks.js";
|
|
11
7
|
import { completionRelationshipErrors } from "./completion-relationships.js";
|
|
12
8
|
import { classifyLoadedWorkState, readWorkState } from "./work-state.js";
|
|
13
|
-
import { sha256 } from "./manifest.js";
|
|
14
9
|
import { evaluateRequiredEvidence, authoritativeChecksForRequirements, ordinaryLeafRequirements, classifyRequirement } from "./evidence-readiness.js";
|
|
15
10
|
import {
|
|
16
11
|
evaluateStartExecutionPrerequisites,
|
|
17
12
|
PREFLIGHT_ROUTE_IDENTITY_ERROR_MESSAGE,
|
|
18
13
|
} from "./execution-prerequisites.js";
|
|
19
14
|
import { validateEventLedger, validateCompletionRecoveryAuthorization } from "./events.js";
|
|
15
|
+
import {
|
|
16
|
+
NEXT_ACTIONS,
|
|
17
|
+
commandFor,
|
|
18
|
+
decision,
|
|
19
|
+
recordCheckCommandSpec,
|
|
20
|
+
recordTerminalResultCommandSpec,
|
|
21
|
+
result,
|
|
22
|
+
uniqueSorted,
|
|
23
|
+
} from "./next-action-model.js";
|
|
24
|
+
import {
|
|
25
|
+
artifactError,
|
|
26
|
+
checkListReasons,
|
|
27
|
+
freshnessReasons,
|
|
28
|
+
loadArtifact,
|
|
29
|
+
requirementsAndCoverage,
|
|
30
|
+
staleReasons,
|
|
31
|
+
} from "./next-action-artifacts.js";
|
|
32
|
+
import { PHASES_REQUIRING_EXECUTION_CHRONOLOGY } from "./next-action-phases.js";
|
|
33
|
+
import { evaluateContinuityNextAction } from "./next-action-continuity.js";
|
|
20
34
|
|
|
21
|
-
export
|
|
22
|
-
DISCOVER: "DISCOVER",
|
|
23
|
-
CREATE_CONTRACT: "CREATE_CONTRACT",
|
|
24
|
-
ROUTE: "ROUTE",
|
|
25
|
-
SATISFY_GATES: "SATISFY_GATES",
|
|
26
|
-
RUN_PREFLIGHT: "RUN_PREFLIGHT",
|
|
27
|
-
PLAN: "PLAN",
|
|
28
|
-
START_EXECUTION: "START_EXECUTION",
|
|
29
|
-
ENTER_VERIFYING: "ENTER_VERIFYING",
|
|
30
|
-
RECORD_VERIFICATION: "RECORD_VERIFICATION",
|
|
31
|
-
DIAGNOSE: "DIAGNOSE",
|
|
32
|
-
CORRECT: "CORRECT",
|
|
33
|
-
ENTER_REVIEWING: "ENTER_REVIEWING",
|
|
34
|
-
RECORD_TERMINAL_RESULT: "RECORD_TERMINAL_RESULT",
|
|
35
|
-
PREPARE_COMPLETION: "PREPARE_COMPLETION",
|
|
36
|
-
RUN_COMPLETE: "RUN_COMPLETE",
|
|
37
|
-
RESOLVE_STALE_ROUTE: "RESOLVE_STALE_ROUTE",
|
|
38
|
-
RESOLVE_BLOCKER: "RESOLVE_BLOCKER",
|
|
39
|
-
NONE: "NONE",
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
const PHASES_REQUIRING_EXECUTION_CHRONOLOGY = new Set([
|
|
43
|
-
"EXECUTING",
|
|
44
|
-
"VERIFYING",
|
|
45
|
-
"DIAGNOSING",
|
|
46
|
-
"CORRECTING",
|
|
47
|
-
"REVIEWING",
|
|
48
|
-
"COMPLETE",
|
|
49
|
-
]);
|
|
50
|
-
|
|
51
|
-
function artifactError(code, message, artifacts = []) {
|
|
52
|
-
return { code, message, artifacts };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function uniqueSorted(values) {
|
|
56
|
-
return [...new Set(values.filter(Boolean))].sort();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function result({
|
|
60
|
-
taskId = "unknown",
|
|
61
|
-
currentPhase = "RECEIVED",
|
|
62
|
-
nextAction,
|
|
63
|
-
reasons = [],
|
|
64
|
-
commands = [],
|
|
65
|
-
commandSpecs = [],
|
|
66
|
-
requiredArtifacts = [],
|
|
67
|
-
missingArtifacts = [],
|
|
68
|
-
}) {
|
|
69
|
-
const normalizedReasons = reasons
|
|
70
|
-
.map((reason) => ({
|
|
71
|
-
code: reason.code ?? "E_NEXT_ACTION_BLOCKED",
|
|
72
|
-
message: reason.message ?? String(reason),
|
|
73
|
-
artifacts: uniqueSorted(reason.artifacts ?? []),
|
|
74
|
-
}))
|
|
75
|
-
.sort((left, right) => left.code.localeCompare(right.code)
|
|
76
|
-
|| left.artifacts.join("\0").localeCompare(right.artifacts.join("\0"))
|
|
77
|
-
|| left.message.localeCompare(right.message));
|
|
78
|
-
|
|
79
|
-
return {
|
|
80
|
-
schemaVersion: 1,
|
|
81
|
-
protocolVersion: PROTOCOL_VERSION,
|
|
82
|
-
taskId,
|
|
83
|
-
currentPhase,
|
|
84
|
-
nextAction,
|
|
85
|
-
terminal: nextAction === NEXT_ACTIONS.NONE,
|
|
86
|
-
reasonCodes: uniqueSorted(normalizedReasons.map((reason) => reason.code)),
|
|
87
|
-
reasons: normalizedReasons,
|
|
88
|
-
commands: uniqueSorted(commands),
|
|
89
|
-
commandSpecs: [...new Map(commandSpecs.map((spec) => [JSON.stringify(spec), spec])).values()]
|
|
90
|
-
.sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right))),
|
|
91
|
-
requiredArtifacts: uniqueSorted(requiredArtifacts),
|
|
92
|
-
missingArtifacts: uniqueSorted(missingArtifacts),
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function commandFor(action) {
|
|
97
|
-
return {
|
|
98
|
-
[NEXT_ACTIONS.PLAN]: "forgeloop advance --to PLANNED",
|
|
99
|
-
[NEXT_ACTIONS.RUN_PREFLIGHT]: "forgeloop preflight --json",
|
|
100
|
-
[NEXT_ACTIONS.START_EXECUTION]: "forgeloop advance --to EXECUTING",
|
|
101
|
-
[NEXT_ACTIONS.ENTER_VERIFYING]: "forgeloop advance --to VERIFYING",
|
|
102
|
-
[NEXT_ACTIONS.DIAGNOSE]: "forgeloop advance --to DIAGNOSING",
|
|
103
|
-
[NEXT_ACTIONS.CORRECT]: "forgeloop advance --to CORRECTING",
|
|
104
|
-
[NEXT_ACTIONS.ENTER_REVIEWING]: "forgeloop advance --to REVIEWING",
|
|
105
|
-
[NEXT_ACTIONS.RECORD_TERMINAL_RESULT]: "forgeloop record-terminal-result",
|
|
106
|
-
[NEXT_ACTIONS.PREPARE_COMPLETION]: "forgeloop prepare-completion --json",
|
|
107
|
-
[NEXT_ACTIONS.RUN_COMPLETE]: "forgeloop complete --json",
|
|
108
|
-
}[action];
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function recordCheckCommandSpec(requirement) {
|
|
112
|
-
const checkId = `requirement-${sha256(Buffer.from(requirement)).slice(0, 16)}`;
|
|
113
|
-
return {
|
|
114
|
-
commandId: "record-check",
|
|
115
|
-
executable: "forgeloop",
|
|
116
|
-
subcommand: "record-check",
|
|
117
|
-
argv: ["record-check", `--id=${checkId}`, `--requirement=${requirement}`],
|
|
118
|
-
requiredInputs: [
|
|
119
|
-
{ name: "status", option: "--status=<passed|failed|blocked|not-run>" },
|
|
120
|
-
{ name: "evidenceKind", option: "--evidence-kind=<OBSERVED|INFERRED|NOT_VERIFIED|BLOCKED>" },
|
|
121
|
-
{ name: "result", option: "--result=<text>" },
|
|
122
|
-
{ name: "exitCode", option: "--exit-code=<number>", optional: true },
|
|
123
|
-
],
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function recordTerminalResultCommandSpec(requirement) {
|
|
128
|
-
const reqId = requirement.id ?? requirement;
|
|
129
|
-
const type = requirement.type ?? "PUBLICATION";
|
|
130
|
-
const status = requirement.requiredPublicationStatus ?? (type === "PUBLICATION" ? "published" : "ready");
|
|
131
|
-
return {
|
|
132
|
-
commandId: "record-terminal-result",
|
|
133
|
-
executable: "forgeloop",
|
|
134
|
-
subcommand: "record-terminal-result",
|
|
135
|
-
argv: ["record-terminal-result", `--requirement=${reqId}`, `--type=${type}`, `--status=${status}`],
|
|
136
|
-
requiredInputs: [
|
|
137
|
-
{ name: "source", option: "--source=<text>" },
|
|
138
|
-
{ name: "result", option: "--result=<text>" },
|
|
139
|
-
{ name: "details", option: "--details=<json>", optional: true },
|
|
140
|
-
],
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function decision(input, action, reason, requiredArtifacts = [], missingArtifacts = []) {
|
|
145
|
-
const command = commandFor(action);
|
|
146
|
-
return result({
|
|
147
|
-
...input,
|
|
148
|
-
nextAction: action,
|
|
149
|
-
reasons: [reason],
|
|
150
|
-
...(command ? { commands: [command] } : {}),
|
|
151
|
-
requiredArtifacts,
|
|
152
|
-
missingArtifacts,
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function missingArtifact(error, fallback) {
|
|
157
|
-
return error?.code === "ARTIFACT_MISSING"
|
|
158
|
-
? (error.artifacts?.length ? error.artifacts : [fallback])
|
|
159
|
-
: [];
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function staleReasons(state, contract, route) {
|
|
163
|
-
const reasons = [];
|
|
164
|
-
if (state.contractFingerprint !== contract.fingerprint) {
|
|
165
|
-
reasons.push(artifactError(
|
|
166
|
-
"E_CONTRACT_STALE",
|
|
167
|
-
"Work state references a different current contract",
|
|
168
|
-
[ARTIFACT_PATHS.state, ARTIFACT_PATHS.contract],
|
|
169
|
-
));
|
|
170
|
-
}
|
|
171
|
-
if (route.value.contractFingerprint !== undefined && route.value.contractFingerprint !== contract.fingerprint) {
|
|
172
|
-
reasons.push(artifactError(
|
|
173
|
-
"E_ROUTE_STALE",
|
|
174
|
-
"Routing result references a different current contract",
|
|
175
|
-
[ARTIFACT_PATHS.route, ARTIFACT_PATHS.contract],
|
|
176
|
-
));
|
|
177
|
-
}
|
|
178
|
-
if (state.routeFingerprint !== route.fingerprint) {
|
|
179
|
-
reasons.push(artifactError(
|
|
180
|
-
"E_ROUTE_STALE",
|
|
181
|
-
"Work state references a different routing result",
|
|
182
|
-
[ARTIFACT_PATHS.state, ARTIFACT_PATHS.route],
|
|
183
|
-
));
|
|
184
|
-
}
|
|
185
|
-
if (JSON.stringify(state.selectedGuides) !== JSON.stringify(route.value.guides)) {
|
|
186
|
-
reasons.push(artifactError(
|
|
187
|
-
"E_ROUTE_STALE",
|
|
188
|
-
"Work state guides do not match the persisted routing result",
|
|
189
|
-
[ARTIFACT_PATHS.state, ARTIFACT_PATHS.route],
|
|
190
|
-
));
|
|
191
|
-
}
|
|
192
|
-
return reasons;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function freshnessReasons(state, classification) {
|
|
196
|
-
const requiredArtifactPaths = state.requiredArtifacts?.map((artifact) => artifact.path) ?? [];
|
|
197
|
-
const reasons = [artifactError(
|
|
198
|
-
"E_STATE_REVALIDATION_REQUIRED",
|
|
199
|
-
`Work-state checkpoint requires revalidation: ${classification.reasons.join(", ")}`,
|
|
200
|
-
[ARTIFACT_PATHS.state],
|
|
201
|
-
)];
|
|
202
|
-
for (const reason of classification.reasons) {
|
|
203
|
-
const contractRelated = ["CONTRACT_CHANGED", "CONTRACT_INVALID", "CONTRACT_NOT_VERIFIED"].includes(reason);
|
|
204
|
-
const artifactRelated = reason.startsWith("REQUIRED_ARTIFACT");
|
|
205
|
-
reasons.push(artifactError(
|
|
206
|
-
contractRelated ? "E_CONTRACT_STALE" : artifactRelated ? "E_REQUIRED_ARTIFACT_STALE" : "E_REPOSITORY_CHANGED",
|
|
207
|
-
`Work-state freshness check failed: ${reason}`,
|
|
208
|
-
uniqueSorted([
|
|
209
|
-
ARTIFACT_PATHS.state,
|
|
210
|
-
...(contractRelated ? [ARTIFACT_PATHS.contract] : []),
|
|
211
|
-
...(artifactRelated ? requiredArtifactPaths : []),
|
|
212
|
-
]),
|
|
213
|
-
));
|
|
214
|
-
}
|
|
215
|
-
return reasons;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function checkListReasons(state) {
|
|
219
|
-
try {
|
|
220
|
-
assertCheckList(state.checks, "work-state.checks");
|
|
221
|
-
return [];
|
|
222
|
-
} catch (error) {
|
|
223
|
-
return [artifactError(
|
|
224
|
-
error.code ?? "E_CHECK_INVALID",
|
|
225
|
-
error.message,
|
|
226
|
-
[ARTIFACT_PATHS.state],
|
|
227
|
-
)];
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
async function loadArtifact(loader, fallback) {
|
|
232
|
-
try {
|
|
233
|
-
return { value: await loader(), error: null, missingArtifacts: [] };
|
|
234
|
-
} catch (error) {
|
|
235
|
-
return { value: null, error, missingArtifacts: missingArtifact(error, fallback) };
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
async function requirementsAndCoverage({
|
|
240
|
-
target,
|
|
241
|
-
packageRoot,
|
|
242
|
-
contract,
|
|
243
|
-
route,
|
|
244
|
-
checks,
|
|
245
|
-
additionalEvidence = [],
|
|
246
|
-
authorityContext,
|
|
247
|
-
runtimeContext,
|
|
248
|
-
}) {
|
|
249
|
-
const requirements = await requiredEvidenceForTarget({
|
|
250
|
-
target,
|
|
251
|
-
contract,
|
|
252
|
-
route,
|
|
253
|
-
packageRoot,
|
|
254
|
-
additionalEvidence,
|
|
255
|
-
});
|
|
256
|
-
return {
|
|
257
|
-
requirements,
|
|
258
|
-
coverage: coverageForRequirements(requirements, checks, {
|
|
259
|
-
target,
|
|
260
|
-
taskId: contract?.value?.taskId,
|
|
261
|
-
options: { authorityContext, runtimeContext },
|
|
262
|
-
}),
|
|
263
|
-
};
|
|
264
|
-
}
|
|
35
|
+
export { NEXT_ACTIONS } from "./next-action-model.js";
|
|
265
36
|
|
|
266
37
|
export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
267
38
|
const normalized = typeof targetOrOptions === "string"
|
|
@@ -541,6 +312,8 @@ export async function getNextAction(targetOrOptions = {}, packageRootOption) {
|
|
|
541
312
|
return decision(context, NEXT_ACTIONS.START_EXECUTION, artifactError("PHASE_PLANNED", "The persisted preflight is READY"));
|
|
542
313
|
}
|
|
543
314
|
if (state.phase === "EXECUTING") {
|
|
315
|
+
const continuityAction = await evaluateContinuityNextAction({ target, packageRoot, context });
|
|
316
|
+
if (continuityAction) return continuityAction;
|
|
544
317
|
return decision(context, NEXT_ACTIONS.ENTER_VERIFYING, artifactError("PHASE_EXECUTING", "Execution is complete enough to enter verification"));
|
|
545
318
|
}
|
|
546
319
|
if (state.phase === "VERIFYING") {
|