@cassiomc1/forgeloop 1.12.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/.github/copilot-instructions.md +1 -1
- package/AGENTS.md +1 -1
- package/AGENT_COMPATIBILITY.md +8 -0
- package/CLAUDE.md +1 -1
- package/CONTRIBUTING.md +90 -0
- package/DOCS_INDEX.md +46 -12
- package/ENG/c-development-eng.md +112 -0
- package/ENG/cpp-development-eng.md +109 -0
- package/ENG/dotnet-aspnetcore-development-eng.md +401 -0
- package/ENG/go-development-eng.md +103 -0
- package/ENG/java-development-eng.md +125 -0
- package/ENG/nodejs-backend-development-eng.md +605 -0
- package/ENG/php-development-eng.md +104 -0
- package/ENG/rust-development-eng.md +422 -0
- package/ENG/sec-code-eng.md +7 -7
- package/ENG/sql-development-eng.md +108 -0
- package/ENG/swift-development-eng.md +111 -0
- package/ENG/typescript-development-eng.md +108 -0
- package/EXECUTION_STATE.md +12 -0
- package/GUIDE_ROUTER.md +418 -9
- package/LOOP_ENGINEERING.md +28 -2
- package/ORCHESTRATOR_INTEGRATION.md +9 -5
- package/PROTOCOL_INTEGRATION.md +55 -2
- package/QUALITY_SCORECARD.md +1 -0
- package/README.md +78 -52
- package/TERMINOLOGY.md +2 -0
- package/THIRD_PARTY_NOTICES.md +19 -7
- package/THREAT_MODEL.md +140 -1
- package/completions/_forgeloop +22 -4
- package/completions/forgeloop.bash +40 -4
- package/completions/forgeloop.fish +130 -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 +81 -3
- 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 +392 -10
- package/docs/CODE_ATTESTATION.md +2 -2
- package/docs/DOCUMENTATION_GUIDE.md +34 -12
- package/docs/GETTING_STARTED.md +59 -0
- 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 +60 -19
- package/docs/PROVIDERS.md +126 -0
- package/docs/PROVIDER_ARCHITECTURE.md +199 -0
- package/docs/RECIPES.md +32 -0
- package/docs/RELEASE_CHECKLIST.md +66 -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 +298 -3
- 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 +1397 -0
- package/docs/protocol-requirements.json +101 -0
- package/package.json +46 -4
- package/schemas/config.schema.json +14 -0
- package/schemas/context-plan.schema.json +18 -0
- package/schemas/routing-input.schema.json +1 -1
- package/schemas/semantic-decision.schema.json +46 -0
- package/schemas/test-utility.schema.json +44 -0
- package/scripts/CI_VALIDATORS.md +84 -11
- package/scripts/benchmark-jev.mjs +5 -0
- package/scripts/benchmark-test-intelligence.mjs +4 -0
- package/scripts/generate-agent-protocol-summary.mjs +40 -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/next.js +19 -7
- 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-create.js +84 -25
- package/src/commands/task-list.js +22 -2
- 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/config/guides.json +44 -0
- 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/build-script.js +151 -0
- package/src/core/c-cpp-project.js +143 -0
- package/src/core/checkpoint-revalidation.js +319 -0
- package/src/core/cli-command-definitions.js +249 -1
- package/src/core/command-executors.js +115 -3
- package/src/core/command-input.js +212 -102
- 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-presets.js +82 -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 +281 -3
- 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/filesystem.js +1 -10
- package/src/core/gate-provenance.js +124 -0
- package/src/core/go-project.js +206 -0
- package/src/core/integration-invocation-policy.js +27 -4
- package/src/core/integration-resources.js +86 -61
- package/src/core/java-project.js +403 -0
- 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/multi-language-project.js +117 -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/next-explanation.js +63 -0
- package/src/core/phase.js +128 -10
- package/src/core/php-project.js +85 -0
- package/src/core/preflight-consistency.js +23 -9
- package/src/core/preflight-loaders.js +37 -5
- package/src/core/project-detection.js +1760 -52
- package/src/core/protocol-info.js +65 -0
- package/src/core/protocol.js +20 -0
- package/src/core/reconcile-closure.js +132 -53
- 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 +223 -4
- package/src/core/runtime-context.js +118 -61
- package/src/core/rust-project.js +400 -0
- 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/sql-project.js +141 -0
- package/src/core/swift-project.js +200 -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/core/typescript-project.js +349 -0
- package/src/core/xml-structure.js +123 -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
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);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createLanguageProject, PHP_SOURCE_EXTENSIONS } from "./multi-language-project.js";
|
|
2
|
+
|
|
3
|
+
function isPlainObject(value) {
|
|
4
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function invalidComposer() {
|
|
8
|
+
return {
|
|
9
|
+
valid: false,
|
|
10
|
+
name: null,
|
|
11
|
+
type: null,
|
|
12
|
+
autoload: false,
|
|
13
|
+
requirements: [],
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function hasValidComposerSections(value) {
|
|
18
|
+
const requirementFields = ["require", "require-dev", "replace", "provide", "conflict"];
|
|
19
|
+
if (requirementFields.some((field) => value[field] !== undefined && !isPlainObject(value[field]))) return false;
|
|
20
|
+
const autoload = value.autoload === undefined || isPlainObject(value.autoload);
|
|
21
|
+
const autoloadDev = value["autoload-dev"] === undefined || isPlainObject(value["autoload-dev"]);
|
|
22
|
+
return autoload && autoloadDev;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function composerStringField(value, field) {
|
|
26
|
+
if (value[field] === undefined) return null;
|
|
27
|
+
return typeof value[field] === "string" && value[field].trim() !== "" ? value[field] : undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function hasComposerIdentity(value) {
|
|
31
|
+
return value.name !== undefined
|
|
32
|
+
|| value.require !== undefined
|
|
33
|
+
|| value.autoload !== undefined
|
|
34
|
+
|| value["autoload-dev"] !== undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseComposerValue(value) {
|
|
38
|
+
if (!isPlainObject(value) || !hasValidComposerSections(value)) return invalidComposer();
|
|
39
|
+
const name = composerStringField(value, "name");
|
|
40
|
+
const type = composerStringField(value, "type");
|
|
41
|
+
if (name === undefined || type === undefined || !hasComposerIdentity(value)) return invalidComposer();
|
|
42
|
+
const hasPhpIdentity = name !== null || value.require !== undefined || value.autoload !== undefined || value["autoload-dev"] !== undefined;
|
|
43
|
+
if (!hasPhpIdentity) return invalidComposer();
|
|
44
|
+
return {
|
|
45
|
+
valid: true,
|
|
46
|
+
name,
|
|
47
|
+
type,
|
|
48
|
+
autoload: value.autoload !== undefined || value["autoload-dev"] !== undefined,
|
|
49
|
+
requirements: Object.keys(value.require ?? {}).sort((left, right) => left.localeCompare(right)),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function parseComposerJson(text) {
|
|
54
|
+
if (typeof text !== "string" || text.length > 1024 * 1024 || /\r(?!\n)/u.test(text)) return invalidComposer();
|
|
55
|
+
try {
|
|
56
|
+
return parseComposerValue(JSON.parse(text));
|
|
57
|
+
} catch {
|
|
58
|
+
return invalidComposer();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function phpSourceLooksExecutable(text) {
|
|
63
|
+
return typeof text === "string" && /<\?(?:php(?:\s|$)|=)/iu.test(text);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function inspectPhpProject({ projectRoot, manifestRelative, manifestText }) {
|
|
67
|
+
const parsed = parseComposerJson(manifestText);
|
|
68
|
+
const primary = parsed.valid;
|
|
69
|
+
const primarySignals = primary ? [`${manifestRelative}:composer`] : [];
|
|
70
|
+
const supportingSignals = [];
|
|
71
|
+
if (parsed.name) supportingSignals.push(`${manifestRelative}:name=${parsed.name}`);
|
|
72
|
+
if (parsed.type) supportingSignals.push(`${manifestRelative}:type=${parsed.type}`);
|
|
73
|
+
if (parsed.autoload) supportingSignals.push(`${manifestRelative}:autoload`);
|
|
74
|
+
for (const requirement of parsed.requirements) supportingSignals.push(`${manifestRelative}:require=${requirement}`);
|
|
75
|
+
return createLanguageProject({
|
|
76
|
+
kind: "php",
|
|
77
|
+
root: projectRoot,
|
|
78
|
+
manifest: manifestRelative,
|
|
79
|
+
frameworks: primary ? ["php"] : [],
|
|
80
|
+
primary,
|
|
81
|
+
primarySignals,
|
|
82
|
+
supportingSignals,
|
|
83
|
+
internal: { ...parsed, phpSourceExtensions: [...PHP_SOURCE_EXTENSIONS] },
|
|
84
|
+
});
|
|
85
|
+
}
|
|
@@ -80,6 +80,18 @@ function sameReadyPreflightEvent(event, result) {
|
|
|
80
80
|
&& sameStringSet(event.details?.satisfiedGates, result.satisfiedGates);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
function latestContractRevisionSeq(events) {
|
|
84
|
+
return events.findLast((event) => event.event === "CONTRACT_REVISED")?.seq ?? 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function hasCurrentRevisionGate(events, taskId, gate) {
|
|
88
|
+
const revisionSeq = latestContractRevisionSeq(events);
|
|
89
|
+
return events.some((event) => event.event === "GATE_SATISFIED"
|
|
90
|
+
&& event.taskId === taskId
|
|
91
|
+
&& event.details?.gate === gate
|
|
92
|
+
&& event.seq > revisionSeq);
|
|
93
|
+
}
|
|
94
|
+
|
|
83
95
|
export async function validateReadyProtocolConsistency({
|
|
84
96
|
target,
|
|
85
97
|
packageRoot,
|
|
@@ -141,9 +153,7 @@ export async function validateReadyProtocolConsistency({
|
|
|
141
153
|
}
|
|
142
154
|
}
|
|
143
155
|
for (const gate of persisted.satisfiedGates ?? []) {
|
|
144
|
-
if (!events
|
|
145
|
-
&& event.taskId === persisted.taskId
|
|
146
|
-
&& event.details?.gate === gate)) {
|
|
156
|
+
if (!hasCurrentRevisionGate(events, persisted.taskId, gate)) {
|
|
147
157
|
errors.push(issue("E_PREFLIGHT_GATE_EVENT_MISSING", `READY preflight is missing lifecycle gate event: ${gate}`, [eventsRel, `${gatesRel}/${gate}.json`]));
|
|
148
158
|
}
|
|
149
159
|
}
|
|
@@ -189,9 +199,12 @@ export function assertExistingReadyLifecycleCompatibility(ledger, result) {
|
|
|
189
199
|
const existingReady = events.findLast((event) => event.event === "PREFLIGHT_READY" && event.taskId === result.taskId);
|
|
190
200
|
if (!existingReady) return;
|
|
191
201
|
const latestOutcome = latestPreflightOutcomeEvent(events, result.taskId);
|
|
202
|
+
const revisedAfterReady = events.some((event) => event.event === "CONTRACT_REVISED"
|
|
203
|
+
&& event.taskId === result.taskId
|
|
204
|
+
&& event.seq > existingReady.seq);
|
|
192
205
|
// A READY outcome superseded by a later BLOCKED outcome may be replaced by a
|
|
193
206
|
// fresh READY with different details once the blocked preflight is resolved.
|
|
194
|
-
if (latestOutcome?.event === "PREFLIGHT_BLOCKED") return;
|
|
207
|
+
if (latestOutcome?.event === "PREFLIGHT_BLOCKED" || revisedAfterReady) return;
|
|
195
208
|
if (!sameReadyPreflightEvent(existingReady, result)) {
|
|
196
209
|
throw preflightError(
|
|
197
210
|
"E_PHASE_CHRONOLOGY_INVALID",
|
|
@@ -217,9 +230,7 @@ export async function appendActivationEvents(target, packageRoot, ledger, result
|
|
|
217
230
|
await append({ taskId: result.taskId, event: "ROUTE_VALIDATED", fingerprint: result.fingerprints.routing });
|
|
218
231
|
}
|
|
219
232
|
for (const gate of result.satisfiedGates) {
|
|
220
|
-
if (!events
|
|
221
|
-
&& event.taskId === result.taskId
|
|
222
|
-
&& event.details?.gate === gate)) {
|
|
233
|
+
if (!hasCurrentRevisionGate(events, result.taskId, gate)) {
|
|
223
234
|
await append({ taskId: result.taskId, event: "GATE_SATISFIED", details: { gate } });
|
|
224
235
|
}
|
|
225
236
|
}
|
|
@@ -227,15 +238,18 @@ export async function appendActivationEvents(target, packageRoot, ledger, result
|
|
|
227
238
|
const existingReady = events.findLast((event) => event.event === "PREFLIGHT_READY" && event.taskId === result.taskId);
|
|
228
239
|
const latestOutcome = latestPreflightOutcomeEvent(events, result.taskId);
|
|
229
240
|
const readySupersededByBlocked = existingReady && latestOutcome?.event === "PREFLIGHT_BLOCKED";
|
|
241
|
+
const readySupersededByContractRevision = existingReady && events.some((event) => event.event === "CONTRACT_REVISED"
|
|
242
|
+
&& event.taskId === result.taskId
|
|
243
|
+
&& event.seq > existingReady.seq);
|
|
230
244
|
if (result.status === "READY") {
|
|
231
|
-
if (existingReady && !readySupersededByBlocked && !sameReadyPreflightEvent(existingReady, result)) {
|
|
245
|
+
if (existingReady && !readySupersededByBlocked && !readySupersededByContractRevision && !sameReadyPreflightEvent(existingReady, result)) {
|
|
232
246
|
throw preflightError(
|
|
233
247
|
"E_PHASE_CHRONOLOGY_INVALID",
|
|
234
248
|
"PREFLIGHT_READY already exists with different READY preflight details; repair the contract, route, or gate lifecycle before refreshing preflight",
|
|
235
249
|
[ARTIFACT_PATHS.preflight, ARTIFACT_PATHS.events, ARTIFACT_PATHS.contract, ARTIFACT_PATHS.route, ARTIFACT_PATHS.gates],
|
|
236
250
|
);
|
|
237
251
|
}
|
|
238
|
-
if (!existingReady || readySupersededByBlocked) {
|
|
252
|
+
if (!existingReady || readySupersededByBlocked || readySupersededByContractRevision) {
|
|
239
253
|
await append({
|
|
240
254
|
taskId: result.taskId,
|
|
241
255
|
event: "PREFLIGHT_READY",
|
|
@@ -2,13 +2,15 @@ import { ARTIFACT_PATHS, readJsonArtifact } from "./artifacts.js";
|
|
|
2
2
|
import { readContract } from "./contract.js";
|
|
3
3
|
import { readGateIfPresent, validateGateArtifacts } from "./gate-artifact.js";
|
|
4
4
|
import { requiredGatesForGuides } from "./guide-metadata.js";
|
|
5
|
-
import {
|
|
5
|
+
import { assertContractPresetRefs, assertContractSourceProvenance, externalContractSourceRefs } from "./sources.js";
|
|
6
6
|
import { readPersistedRoute } from "./route-artifact.js";
|
|
7
7
|
import { ensureWithin, readBytes } from "./filesystem.js";
|
|
8
8
|
import { sha256 } from "./manifest.js";
|
|
9
9
|
import { findProfilePath } from "./profile.js";
|
|
10
10
|
import { issue } from "./preflight-model.js";
|
|
11
11
|
import { taskGatePath, taskArtifactPath } from "./task-paths.js";
|
|
12
|
+
import { readEvents } from "./events.js";
|
|
13
|
+
import { hasCurrentGateEvidence } from "./gate-provenance.js";
|
|
12
14
|
|
|
13
15
|
export async function readProfile(target) {
|
|
14
16
|
const relativePath = await findProfilePath(target);
|
|
@@ -62,15 +64,25 @@ export async function loadRoute(target, packageRoot, errors, options = {}) {
|
|
|
62
64
|
|
|
63
65
|
export async function loadSources(target, contract, packageRoot, errors) {
|
|
64
66
|
if (!contract?.value?.sourceRefs?.length) return null;
|
|
65
|
-
|
|
67
|
+
const refs = contract.value.sourceRefs;
|
|
66
68
|
try {
|
|
67
|
-
|
|
69
|
+
assertContractPresetRefs(refs);
|
|
68
70
|
} catch (error) {
|
|
69
|
-
errors.push(issue(error.code
|
|
71
|
+
errors.push(issue(error.code ?? "E_PROFILE_SOURCE_UNKNOWN", error.message, [ARTIFACT_PATHS.sources]));
|
|
70
72
|
return null;
|
|
71
73
|
}
|
|
74
|
+
const externalRefs = externalContractSourceRefs(refs);
|
|
75
|
+
let registry;
|
|
76
|
+
if (externalRefs.length > 0) {
|
|
77
|
+
try {
|
|
78
|
+
registry = (await readJsonArtifact(target, ARTIFACT_PATHS.sources, "source-registry", packageRoot)).value;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
errors.push(issue(error.code === "ARTIFACT_MISSING" ? "E_PROFILE_SOURCE_MISSING" : "E_PROFILE_SOURCE_UNKNOWN", error.message, [ARTIFACT_PATHS.sources]));
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
72
84
|
try {
|
|
73
|
-
|
|
85
|
+
assertContractSourceProvenance(registry, refs);
|
|
74
86
|
} catch (error) {
|
|
75
87
|
errors.push(issue(error.code ?? "E_PROFILE_SOURCE_UNKNOWN", error.message, [ARTIFACT_PATHS.sources]));
|
|
76
88
|
}
|
|
@@ -84,6 +96,15 @@ export async function inspectGates(target, contract, route, packageRoot, errors,
|
|
|
84
96
|
const satisfied = [];
|
|
85
97
|
const records = {};
|
|
86
98
|
const taskId = options.taskId ?? null;
|
|
99
|
+
let events = [];
|
|
100
|
+
if (taskId) {
|
|
101
|
+
try {
|
|
102
|
+
events = await readEvents(target, packageRoot, { taskId });
|
|
103
|
+
} catch {
|
|
104
|
+
events = [];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const latestContractRevisionSeq = events.findLast((event) => event.event === "CONTRACT_REVISED")?.seq ?? 0;
|
|
87
108
|
for (const gate of required) {
|
|
88
109
|
let artifact;
|
|
89
110
|
const defaultGateRel = taskId ? taskGatePath(taskId, gate) : `${ARTIFACT_PATHS.gates}/${gate}.json`;
|
|
@@ -111,6 +132,17 @@ export async function inspectGates(target, contract, route, packageRoot, errors,
|
|
|
111
132
|
errors.push(issue("E_GATE_STALE", `Gate ${gate} references stale artifacts`, [artifact.path], { gate, stale }));
|
|
112
133
|
continue;
|
|
113
134
|
}
|
|
135
|
+
const currentEpochSatisfied = latestContractRevisionSeq === 0
|
|
136
|
+
|| hasCurrentGateEvidence(events, taskId, gate, latestContractRevisionSeq);
|
|
137
|
+
if (!currentEpochSatisfied) {
|
|
138
|
+
errors.push(issue(
|
|
139
|
+
"E_GATE_UNVERIFIED",
|
|
140
|
+
`Required gate ${gate} was satisfied before the current contract revision`,
|
|
141
|
+
[defaultGateRel],
|
|
142
|
+
{ gate },
|
|
143
|
+
));
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
114
146
|
satisfied.push(gate);
|
|
115
147
|
}
|
|
116
148
|
return { required, satisfied: satisfied.sort(), records };
|