@cassiomc1/forgeloop 0.1.14 → 0.1.16

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.
Files changed (61) hide show
  1. package/.forgeloop/forgeloop.gitignore +1 -0
  2. package/DOCS_INDEX.md +60 -0
  3. package/EXECUTION_STATE.md +9 -0
  4. package/LOOP_ENGINEERING.md +44 -6
  5. package/LOOP_SYSTEM_DESIGN.md +8 -0
  6. package/PROTOCOL_INTEGRATION.md +41 -0
  7. package/QUALITY_SCORECARD.md +2 -0
  8. package/README.md +183 -643
  9. package/TERMINOLOGY.md +4 -0
  10. package/THREAT_MODEL.md +14 -0
  11. package/docs/assets/forgeloop-flow.svg +1 -0
  12. package/docs/forgeloop-flow.mmd +51 -0
  13. package/package.json +16 -3
  14. package/schemas/check.schema.json +2 -0
  15. package/schemas/continuity.schema.json +57 -0
  16. package/schemas/execution.schema.json +63 -0
  17. package/scripts/CI_VALIDATORS.md +32 -0
  18. package/src/cli.js +351 -196
  19. package/src/commands/clear-continuity.js +9 -0
  20. package/src/commands/continuity.js +25 -0
  21. package/src/commands/doctor.js +17 -2
  22. package/src/commands/reconcile-continuity.js +23 -0
  23. package/src/commands/record-continuity.js +63 -0
  24. package/src/commands/run-check.js +83 -0
  25. package/src/commands/status.js +14 -1
  26. package/src/commands/update.js +12 -13
  27. package/src/commands/validate-protocol.js +37 -0
  28. package/src/core/artifacts.js +9 -0
  29. package/src/core/bundles.js +78 -0
  30. package/src/core/checks.js +16 -0
  31. package/src/core/command-resolution.js +295 -0
  32. package/src/core/command-tokenizer.js +122 -0
  33. package/src/core/completion-artifacts.js +229 -47
  34. package/src/core/completion.js +19 -1
  35. package/src/core/conformance.js +8 -2
  36. package/src/core/continuity-cli-options.js +58 -0
  37. package/src/core/continuity-conformance.js +46 -0
  38. package/src/core/continuity-observability.js +20 -0
  39. package/src/core/continuity-reconciliation.js +224 -0
  40. package/src/core/continuity.js +245 -0
  41. package/src/core/evidence-readiness.js +26 -1
  42. package/src/core/execution.js +185 -0
  43. package/src/core/inspect.js +9 -1
  44. package/src/core/installation-authority.js +178 -0
  45. package/src/core/json-safety.js +17 -13
  46. package/src/core/next-action-artifacts.js +118 -0
  47. package/src/core/next-action-continuity.js +65 -0
  48. package/src/core/next-action-model.js +127 -0
  49. package/src/core/next-action-phases.js +12 -0
  50. package/src/core/next-action.js +22 -249
  51. package/src/core/npm-classifier.js +343 -0
  52. package/src/core/package-manager-classifiers.js +37 -0
  53. package/src/core/preflight-consistency.js +221 -0
  54. package/src/core/preflight-loaders.js +112 -0
  55. package/src/core/preflight-model.js +83 -0
  56. package/src/core/preflight.js +34 -444
  57. package/src/core/protocol.js +7 -0
  58. package/src/core/schema-validation.js +16 -1
  59. package/src/core/templates.js +3 -0
  60. package/src/core/verification-capability.js +31 -495
  61. package/src/core/verification-constants.js +61 -0
@@ -1,205 +1,44 @@
1
- import { ARTIFACT_PATHS, readJsonArtifact, writeJsonArtifact } from "./artifacts.js";
1
+ import { ARTIFACT_PATHS, writeJsonArtifact } from "./artifacts.js";
2
2
  import { readContract } from "./contract.js";
3
- import { readGateIfPresent, validateGateArtifacts } from "./gate-artifact.js";
4
- import { requiredGatesForGuides } from "./guide-metadata.js";
5
3
  import { assertRouteInvariants } from "./router.js";
6
- import { assertSourceProvenance } from "./sources.js";
7
- import { readPersistedRoute } from "./route-artifact.js";
8
- import { appendProtocolEvent, validateEventLedger } from "./events.js";
4
+ import { validateProfileSources } from "./profile.js";
9
5
  import { readWorkState } from "./work-state.js";
10
- import { readConfig } from "./config.js";
11
- import { assertSafePath, ensureWithin, fileExists, readBytes } from "./filesystem.js";
12
- import { sha256 } from "./manifest.js";
13
- import { findProfilePath, validateProfileSources } from "./profile.js";
14
- import { assertStateIdentity } from "./completion-relationships.js";
15
- import { ensureResumableState, synchronizePreflightState } from "./resumability.js";
6
+ import { ensureResumableState, synchronizePreflightState } from "./preflight-consistency.js";
7
+ import {
8
+ issue,
9
+ preflightError,
10
+ sortIssues,
11
+ validatePersistedPreflight,
12
+ } from "./preflight-model.js";
13
+ import {
14
+ loadContract,
15
+ loadRoute,
16
+ loadSources,
17
+ inspectGates,
18
+ optionalConfig,
19
+ readProfile,
20
+ } from "./preflight-loaders.js";
21
+ import {
22
+ appendActivationEvents,
23
+ assertExistingReadyLifecycleCompatibility,
24
+ assertPreflightPersistenceSafety,
25
+ assertPreflightResultPersistenceSafety,
26
+ validateReadyProtocolConsistency as validateReadyProtocolConsistencyCore,
27
+ } from "./preflight-consistency.js";
16
28
  import { PROFILE_PATH } from "./target-layout.js";
29
+ import { readPersistedRoute } from "./route-artifact.js";
30
+ import { validateEventLedger } from "./events.js";
17
31
 
18
32
  const PREVIEW_DECISION_LIMIT = 10;
19
33
  const PREVIEW_DECISION_MAX_LENGTH = 240;
20
34
 
21
- function issue(code, message, artifacts = [], details = {}) {
22
- return { code, message, artifacts, ...details };
23
- }
24
-
25
- function sortIssues(errors) {
26
- const unique = [...new Map(errors.map((error) => [
27
- `${error.code}\0${error.artifacts.join("\0")}\0${error.message}`,
28
- error,
29
- ])).values()];
30
- return unique.sort((left, right) => left.code.localeCompare(right.code)
31
- || left.artifacts.join("\0").localeCompare(right.artifacts.join("\0"))
32
- || left.message.localeCompare(right.message));
33
- }
34
-
35
- function preflightError(code, message, artifacts = []) {
36
- const error = new Error(message);
37
- error.code = code;
38
- error.artifacts = artifacts;
39
- return error;
40
- }
41
-
42
- function sameStringSet(left, right) {
43
- if (!Array.isArray(left) || !Array.isArray(right)) return false;
44
- const leftSet = new Set(left);
45
- const rightSet = new Set(right);
46
- return leftSet.size === left.length
47
- && rightSet.size === right.length
48
- && leftSet.size === rightSet.size
49
- && [...leftSet].every((value) => rightSet.has(value));
50
- }
51
-
52
- export function validatePersistedPreflight(persisted, current) {
53
- const errors = [];
54
- if (persisted?.status !== "READY") {
55
- errors.push(issue("E_PREFLIGHT_NOT_READY", "A persisted READY preflight is required", [ARTIFACT_PATHS.preflight]));
56
- return errors;
57
- }
58
- if (current?.status !== "READY") {
59
- errors.push(issue("E_PREFLIGHT_NOT_READY", "The current preflight evaluation is not READY", [ARTIFACT_PATHS.preflight]));
60
- }
61
- if (persisted.taskId !== current?.taskId) {
62
- errors.push(issue(
63
- "E_PREFLIGHT_TASK_MISMATCH",
64
- "Persisted preflight does not belong to the current task",
65
- [ARTIFACT_PATHS.preflight, ARTIFACT_PATHS.contract],
66
- ));
67
- }
68
- if (persisted.fingerprints?.contract !== current?.fingerprints?.contract
69
- || persisted.contract?.fingerprint !== current?.contract?.fingerprint) {
70
- errors.push(issue(
71
- "E_PREFLIGHT_CONTRACT_STALE",
72
- "Persisted preflight does not match the current contract fingerprint",
73
- [ARTIFACT_PATHS.preflight, ARTIFACT_PATHS.contract],
74
- ));
75
- }
76
- if (persisted.fingerprints?.routing !== current?.fingerprints?.routing
77
- || persisted.routing?.fingerprint !== current?.routing?.fingerprint) {
78
- errors.push(issue(
79
- "E_PREFLIGHT_ROUTE_STALE",
80
- "Persisted preflight does not match the current routing fingerprint",
81
- [ARTIFACT_PATHS.preflight, ARTIFACT_PATHS.route],
82
- ));
83
- }
84
- if (!sameStringSet(persisted.requiredGates, current?.requiredGates)
85
- || !sameStringSet(persisted.satisfiedGates, current?.satisfiedGates)) {
86
- errors.push(issue(
87
- "E_PREFLIGHT_GATES_STALE",
88
- "Persisted preflight gate sets do not match the current evaluation",
89
- [ARTIFACT_PATHS.preflight, ARTIFACT_PATHS.gates],
90
- ));
91
- }
92
- return sortIssues(errors);
93
- }
94
-
95
- async function readProfile(target) {
96
- const relativePath = await findProfilePath(target);
97
- if (!relativePath) return { status: "missing", fingerprint: null };
98
- const filePath = ensureWithin(target, relativePath);
99
- const bytes = await readBytes(filePath);
100
- const text = bytes.toString("utf8");
101
- const mode = text.match(/^profile-mode:\s*([^\s]+)\s*$/m)?.[1] ?? null;
102
- const status = text.match(/^profile-status:\s*([^\s]+)\s*$/m)?.[1] ?? null;
103
- return {
104
- status: status === "verified" && mode !== "template" ? "verified" : "unverified",
105
- mode,
106
- profileStatus: status,
107
- fingerprint: sha256(bytes),
108
- };
109
- }
110
-
111
- async function optionalConfig(target, packageRoot, errors) {
112
- try {
113
- const artifact = await readJsonArtifact(target, ARTIFACT_PATHS.config, "config", packageRoot);
114
- return artifact.value;
115
- } catch (error) {
116
- if (error.code === "ARTIFACT_MISSING") return { schemaVersion: 1, protocolVersion: 1, complianceMode: "standard" };
117
- errors.push(issue("E_CONFIG_INVALID", error.message, [ARTIFACT_PATHS.config]));
118
- return { schemaVersion: 1, protocolVersion: 1, complianceMode: "standard" };
119
- }
120
- }
121
-
122
- async function loadContract(target, packageRoot, errors) {
123
- try {
124
- return await readContract(target, packageRoot);
125
- } catch (error) {
126
- errors.push(issue(error.code === "ARTIFACT_MISSING" ? "E_CONTRACT_MISSING" : "E_CONTRACT_INVALID", error.message, [ARTIFACT_PATHS.contract]));
127
- return null;
128
- }
129
- }
130
-
131
- async function loadRoute(target, packageRoot, errors) {
132
- try {
133
- return await readPersistedRoute(target, packageRoot);
134
- } catch (error) {
135
- const code = error.code === "ARTIFACT_MISSING"
136
- ? "E_ROUTE_MISSING"
137
- : ["E_ROUTE_REASON_MISSING", "E_ROUTE_INVALID"].includes(error.code) ? error.code : "E_ROUTE_INVALID";
138
- errors.push(issue(code, error.message, [ARTIFACT_PATHS.route]));
139
- return null;
140
- }
141
- }
142
-
143
- async function loadSources(target, contract, packageRoot, errors) {
144
- if (!contract?.value?.sourceRefs?.length) return null;
145
- let registry;
146
- try {
147
- registry = (await readJsonArtifact(target, ARTIFACT_PATHS.sources, "source-registry", packageRoot)).value;
148
- } catch (error) {
149
- errors.push(issue(error.code === "ARTIFACT_MISSING" ? "E_PROFILE_SOURCE_MISSING" : "E_PROFILE_SOURCE_UNKNOWN", error.message, [ARTIFACT_PATHS.sources]));
150
- return null;
151
- }
152
- try {
153
- assertSourceProvenance(registry, contract.value.sourceRefs);
154
- } catch (error) {
155
- errors.push(issue(error.code ?? "E_PROFILE_SOURCE_UNKNOWN", error.message, [ARTIFACT_PATHS.sources]));
156
- }
157
- return registry;
158
- }
159
-
160
- async function inspectGates(target, contract, route, packageRoot, errors, config = {}) {
161
- if (!route) return { required: [], satisfied: [], records: {} };
162
- const guideGates = await requiredGatesForGuides(route.value.guides, packageRoot);
163
- const required = [...new Set([...guideGates, ...(config.requiredGates ?? [])])].sort();
164
- const satisfied = [];
165
- const records = {};
166
- for (const gate of required) {
167
- let artifact;
168
- try {
169
- artifact = await readGateIfPresent(target, gate, packageRoot);
170
- } catch (error) {
171
- errors.push(issue(error.code === "ARTIFACT_MISSING" ? "E_GATE_UNVERIFIED" : "E_GATE_INVALID", error.message, [`${ARTIFACT_PATHS.gates}/${gate}.json`], { gate }));
172
- continue;
173
- }
174
- if (!artifact) {
175
- errors.push(issue("E_GATE_UNVERIFIED", `Required gate is missing or unverified: ${gate}`, [`${ARTIFACT_PATHS.gates}/${gate}.json`], { gate }));
176
- continue;
177
- }
178
- records[gate] = artifact;
179
- if (artifact.value.taskId !== contract?.value?.taskId) {
180
- errors.push(issue("E_GATE_TASK_MISMATCH", `Gate ${gate} belongs to a different task`, [artifact.path], { gate }));
181
- continue;
182
- }
183
- if (artifact.value.status !== "satisfied") {
184
- errors.push(issue("E_GATE_UNVERIFIED", `Required gate is ${artifact.value.status}: ${gate}`, [artifact.path], { gate }));
185
- continue;
186
- }
187
- const stale = await validateGateArtifacts(target, artifact.value, packageRoot);
188
- if (stale.length > 0) {
189
- errors.push(issue("E_GATE_STALE", `Gate ${gate} references stale artifacts`, [artifact.path], { gate, stale }));
190
- continue;
191
- }
192
- satisfied.push(gate);
193
- }
194
- return { required, satisfied: satisfied.sort(), records };
195
- }
35
+ export { validatePersistedPreflight } from "./preflight-model.js";
196
36
 
197
37
  export async function evaluatePreflight({ target, packageRoot, strict = false } = {}) {
198
38
  const errors = [];
199
39
  const profile = await readProfile(target);
200
40
  const profileProvenance = await validateProfileSources(target, packageRoot);
201
- const provenanceErrors = profileProvenance.errors ?? [];
202
- errors.push(...provenanceErrors);
41
+ errors.push(...(profileProvenance.errors ?? []));
203
42
  const contract = await loadContract(target, packageRoot, errors);
204
43
  const route = await loadRoute(target, packageRoot, errors);
205
44
  const config = await optionalConfig(target, packageRoot, errors);
@@ -207,6 +46,7 @@ export async function evaluatePreflight({ target, packageRoot, strict = false }
207
46
  if (effectiveStrict && profile.status !== "verified") {
208
47
  errors.push(issue("E_PROFILE_UNVERIFIED", "Strict preflight requires a verified project profile", [PROFILE_PATH]));
209
48
  }
49
+
210
50
  const unresolvedDecisions = contract?.value?.unresolvedDecisions ?? [];
211
51
  if (unresolvedDecisions.length > 0) {
212
52
  errors.push(issue(
@@ -281,261 +121,11 @@ export async function evaluatePreflight({ target, packageRoot, strict = false }
281
121
  };
282
122
  }
283
123
 
284
- async function readOptionalIdentityArtifact(readArtifact, invalidCode, artifactPath) {
285
- try {
286
- return await readArtifact();
287
- } catch (error) {
288
- if (error.code === "ARTIFACT_MISSING") return null;
289
- throw preflightError(invalidCode, error.message, [artifactPath]);
290
- }
291
- }
292
-
293
- async function assertPreflightPersistenceSafety(target, packageRoot, taskId) {
294
- let state;
295
- try {
296
- state = await readWorkState(target, packageRoot);
297
- } catch (error) {
298
- throw preflightError("E_STATE_INVALID", error.message, [ARTIFACT_PATHS.state]);
299
- }
300
- const contract = await readOptionalIdentityArtifact(
301
- () => readContract(target, packageRoot),
302
- "E_CONTRACT_INVALID",
303
- ARTIFACT_PATHS.contract,
304
- );
305
- const route = await readOptionalIdentityArtifact(
306
- () => readPersistedRoute(target, packageRoot),
307
- "E_ROUTE_INVALID",
308
- ARTIFACT_PATHS.route,
309
- );
310
- if (state) {
311
- if (contract || route) assertStateIdentity({ contract, route, state });
312
- }
313
-
314
- if (taskId === "unknown") return null;
315
- const ledger = await validateEventLedger(target, packageRoot);
316
- if (!ledger.valid) {
317
- const first = ledger.errors[0];
318
- throw preflightError(first.code, first.message, [ARTIFACT_PATHS.events]);
319
- }
320
- if (ledger.events.some((event) => event.taskId !== taskId)) {
321
- throw preflightError(
322
- "E_PHASE_CHRONOLOGY_INVALID",
323
- "Preflight cannot append events to a ledger owned by a different task",
324
- [ARTIFACT_PATHS.events, ARTIFACT_PATHS.contract],
325
- );
326
- }
327
- return ledger;
328
- }
329
-
330
- const PREFLIGHT_IDENTITY_BARRIER_CODES = new Set([
331
- "E_CONTRACT_STALE",
332
- "E_GATE_TASK_MISMATCH",
333
- "E_ROUTE_STALE",
334
- "E_STATE_TASK_MISMATCH",
335
- "E_ROUTE_GUIDE_MISMATCH",
336
- ]);
337
-
338
- function assertPreflightResultPersistenceSafety(result) {
339
- const identityError = result.errors.find((error) => PREFLIGHT_IDENTITY_BARRIER_CODES.has(error.code));
340
- if (identityError) {
341
- throw preflightError(identityError.code, identityError.message, identityError.artifacts);
342
- }
343
- }
344
-
345
- function sameReadyPreflightEvent(event, result) {
346
- return event.fingerprint === result.fingerprints.contract
347
- && event.details?.routingFingerprint === result.fingerprints.routing
348
- && sameStringSet(event.details?.requiredGates, result.requiredGates)
349
- && sameStringSet(event.details?.satisfiedGates, result.satisfiedGates);
350
- }
351
-
352
- export async function validateReadyProtocolConsistency({
353
- target,
354
- packageRoot,
355
- persisted,
356
- current = null,
357
- } = {}) {
358
- if (persisted?.status !== "READY") return [];
359
- const result = current ?? await evaluatePreflight({ target, packageRoot });
360
- const errors = [...validatePersistedPreflight(persisted, result)];
361
-
362
- let state = null;
363
- try {
364
- state = await readWorkState(target, packageRoot);
365
- } catch (error) {
366
- errors.push(issue("E_STATE_INVALID", error.message, [ARTIFACT_PATHS.state]));
367
- }
368
- if (!state) {
369
- errors.push(issue(
370
- "E_STATE_MISSING_AFTER_PREFLIGHT_READY",
371
- "A persisted READY preflight must have a resumable work-state checkpoint",
372
- [ARTIFACT_PATHS.preflight, ARTIFACT_PATHS.state],
373
- ));
374
- } else {
375
- if (state.taskId !== persisted.taskId) {
376
- errors.push(issue(
377
- "E_STATE_TASK_MISMATCH",
378
- "The resumable checkpoint does not belong to the READY preflight task",
379
- [ARTIFACT_PATHS.state, ARTIFACT_PATHS.preflight],
380
- ));
381
- }
382
- if (state.contractFingerprint !== result.fingerprints.contract) {
383
- errors.push(issue(
384
- "E_CONTRACT_STALE",
385
- "The resumable checkpoint does not match the READY contract fingerprint",
386
- [ARTIFACT_PATHS.state, ARTIFACT_PATHS.contract],
387
- ));
388
- }
389
- if (state.routeFingerprint !== result.fingerprints.routing) {
390
- errors.push(issue(
391
- "E_ROUTE_STALE",
392
- "The resumable checkpoint does not match the READY routing fingerprint",
393
- [ARTIFACT_PATHS.state, ARTIFACT_PATHS.route],
394
- ));
395
- }
396
- if (JSON.stringify(state.selectedGuides) !== JSON.stringify(result.routing.guides)) {
397
- errors.push(issue(
398
- "E_ROUTE_GUIDE_MISMATCH",
399
- "The resumable checkpoint guides do not match the READY routing result",
400
- [ARTIFACT_PATHS.state, ARTIFACT_PATHS.route],
401
- ));
402
- }
403
- if (!sameStringSet(state.requiredGates ?? [], persisted.requiredGates)
404
- || !sameStringSet(state.satisfiedGates ?? [], persisted.satisfiedGates)) {
405
- errors.push(issue(
406
- "E_PREFLIGHT_GATES_STALE",
407
- "The resumable checkpoint gate sets do not match the READY preflight",
408
- [ARTIFACT_PATHS.state, ARTIFACT_PATHS.preflight, ARTIFACT_PATHS.gates],
409
- ));
410
- }
411
- }
412
-
413
- const ledger = await validateEventLedger(target, packageRoot);
414
- if (!ledger.valid) {
415
- errors.push(...ledger.errors.map((error) => issue(
416
- error.code ?? "E_EVENT_INVALID",
417
- error.message,
418
- [ARTIFACT_PATHS.events],
419
- )));
420
- }
421
- const events = ledger.events ?? [];
422
- for (const requiredEvent of ["CONTRACT_VALIDATED", "ROUTE_VALIDATED"]) {
423
- if (!events.some((event) => event.event === requiredEvent && event.taskId === persisted.taskId)) {
424
- errors.push(issue(
425
- "E_PREFLIGHT_EVENT_MISSING",
426
- `READY preflight is missing lifecycle event: ${requiredEvent}`,
427
- [ARTIFACT_PATHS.events, ARTIFACT_PATHS.preflight],
428
- ));
429
- }
430
- }
431
- for (const gate of persisted.satisfiedGates ?? []) {
432
- if (!events.some((event) => event.event === "GATE_SATISFIED"
433
- && event.taskId === persisted.taskId
434
- && event.details?.gate === gate)) {
435
- errors.push(issue(
436
- "E_PREFLIGHT_GATE_EVENT_MISSING",
437
- `READY preflight is missing lifecycle gate event: ${gate}`,
438
- [ARTIFACT_PATHS.events, `${ARTIFACT_PATHS.gates}/${gate}.json`],
439
- ));
440
- }
441
- }
442
- const readyEvents = events.filter((event) => event.event === "PREFLIGHT_READY" && event.taskId === persisted.taskId);
443
- if (readyEvents.length === 0) {
444
- errors.push(issue(
445
- "E_PREFLIGHT_READY_EVENT_MISSING",
446
- "Persisted READY preflight is missing the matching PREFLIGHT_READY lifecycle event",
447
- [ARTIFACT_PATHS.preflight, ARTIFACT_PATHS.events],
448
- ));
449
- } else if (!readyEvents.some((event) => sameReadyPreflightEvent(event, result))) {
450
- errors.push(issue(
451
- "E_PREFLIGHT_READY_EVENT_MISMATCH",
452
- "PREFLIGHT_READY lifecycle details do not match the persisted READY preflight",
453
- [ARTIFACT_PATHS.preflight, ARTIFACT_PATHS.events],
454
- ));
455
- }
456
- return sortIssues(errors);
457
- }
458
-
459
- function sameBlockedPreflightEvent(event, result) {
460
- return event.event === "PREFLIGHT_BLOCKED"
461
- && event.taskId === result.taskId
462
- && event.fingerprint === (result.fingerprints.contract ?? undefined)
463
- && sameStringSet(event.details?.requiredGates, result.requiredGates)
464
- && sameStringSet(event.details?.satisfiedGates, result.satisfiedGates)
465
- && event.details?.routingFingerprint === result.fingerprints.routing;
466
- }
467
-
468
- function assertExistingReadyLifecycleCompatibility(ledger, result) {
469
- if (result.status !== "READY") return;
470
- const existingReady = ledger?.events?.find((event) => event.event === "PREFLIGHT_READY" && event.taskId === result.taskId);
471
- if (existingReady && !sameReadyPreflightEvent(existingReady, result)) {
472
- throw preflightError(
473
- "E_PHASE_CHRONOLOGY_INVALID",
474
- "PREFLIGHT_READY already exists with different READY preflight details; repair the contract, route, or gate lifecycle before refreshing preflight",
475
- [ARTIFACT_PATHS.preflight, ARTIFACT_PATHS.events, ARTIFACT_PATHS.contract, ARTIFACT_PATHS.route, ARTIFACT_PATHS.gates],
476
- );
477
- }
478
- }
479
-
480
- async function appendActivationEvents(target, packageRoot, ledger, result) {
481
- const events = [...(ledger?.events ?? [])];
482
- const hasEvent = (eventName) => events.some((event) => event.event === eventName && event.taskId === result.taskId);
483
- const append = async (input) => {
484
- const event = await appendProtocolEvent(target, input, packageRoot);
485
- events.push(event);
486
- };
487
-
488
- if (events.length === 0) {
489
- await append({ taskId: result.taskId, event: "TASK_RECEIVED" });
490
- }
491
- if (result.contract.status === "valid" && !hasEvent("CONTRACT_VALIDATED")) {
492
- await append({ taskId: result.taskId, event: "CONTRACT_VALIDATED", fingerprint: result.fingerprints.contract });
493
- }
494
- if (result.routing.status === "valid" && !hasEvent("ROUTE_VALIDATED")) {
495
- await append({ taskId: result.taskId, event: "ROUTE_VALIDATED", fingerprint: result.fingerprints.routing });
496
- }
497
- for (const gate of result.satisfiedGates) {
498
- if (!events.some((event) => event.event === "GATE_SATISFIED"
499
- && event.taskId === result.taskId
500
- && event.details?.gate === gate)) {
501
- await append({ taskId: result.taskId, event: "GATE_SATISFIED", details: { gate } });
502
- }
503
- }
504
-
505
- const existingReady = events.find((event) => event.event === "PREFLIGHT_READY" && event.taskId === result.taskId);
506
- if (result.status === "READY") {
507
- if (existingReady && !sameReadyPreflightEvent(existingReady, result)) {
508
- throw preflightError(
509
- "E_PHASE_CHRONOLOGY_INVALID",
510
- "PREFLIGHT_READY already exists with different READY preflight details; repair the contract, route, or gate lifecycle before refreshing preflight",
511
- [ARTIFACT_PATHS.preflight, ARTIFACT_PATHS.events, ARTIFACT_PATHS.contract, ARTIFACT_PATHS.route, ARTIFACT_PATHS.gates],
512
- );
513
- }
514
- if (!existingReady) {
515
- await append({
516
- taskId: result.taskId,
517
- event: "PREFLIGHT_READY",
518
- fingerprint: result.fingerprints.contract ?? undefined,
519
- details: {
520
- requiredGates: result.requiredGates,
521
- satisfiedGates: result.satisfiedGates,
522
- routingFingerprint: result.fingerprints.routing,
523
- },
524
- });
525
- }
526
- } else if (!events.some((event) => sameBlockedPreflightEvent(event, result))) {
527
- await append({
528
- taskId: result.taskId,
529
- event: "PREFLIGHT_BLOCKED",
530
- fingerprint: result.fingerprints.contract ?? undefined,
531
- details: {
532
- requiredGates: result.requiredGates,
533
- satisfiedGates: result.satisfiedGates,
534
- routingFingerprint: result.fingerprints.routing,
535
- },
536
- });
537
- }
538
- return events;
124
+ export async function validateReadyProtocolConsistency(options = {}) {
125
+ return validateReadyProtocolConsistencyCore({
126
+ ...options,
127
+ evaluateCurrentPreflight: evaluatePreflight,
128
+ });
539
129
  }
540
130
 
541
131
  export async function runPreflight({ target, packageRoot, strict = false, persist = true } = {}) {
@@ -86,6 +86,13 @@ export const FAILURE_CODES = Object.freeze([
86
86
  "E_AUTHORITY_SCOPE_MISMATCH",
87
87
  "E_AUTHORITY_UNTRUSTED_SOURCE",
88
88
  "E_VERIFICATION_TOOL_UNAVAILABLE",
89
+ "E_CONTINUITY_INVALID",
90
+ "E_CONTINUITY_SCHEMA_UNSUPPORTED",
91
+ "E_CONTINUITY_STATE_MISSING",
92
+ "E_CONTINUITY_TASK_MISMATCH",
93
+ "E_CONTINUITY_CONTRACT_MISMATCH",
94
+ "E_CONTINUITY_PHASE_MISMATCH",
95
+ "E_CONTINUITY_RECONCILIATION_REQUIRED",
89
96
  ]);
90
97
 
91
98
  export const WORK_PHASES = Object.freeze([
@@ -9,6 +9,7 @@ export const SHIPPED_SCHEMA_NAMES = Object.freeze([
9
9
  "routing-input",
10
10
  "routing-result",
11
11
  "work-state",
12
+ "continuity",
12
13
  "execution-receipt",
13
14
  "task-brief",
14
15
  "delegated-result",
@@ -24,6 +25,8 @@ export const SHIPPED_SCHEMA_NAMES = Object.freeze([
24
25
  "activation",
25
26
  "policy",
26
27
  "task-bundle",
28
+ "execution",
29
+ "authority",
27
30
  ]);
28
31
 
29
32
  export class SchemaValidationError extends Error {
@@ -160,10 +163,22 @@ export function assertSchema(value, schema, label = "value") {
160
163
  return value;
161
164
  }
162
165
 
166
+ const SCHEMA_CACHE = new Map();
167
+
168
+ export function clearSchemaCache() {
169
+ SCHEMA_CACHE.clear();
170
+ }
171
+
163
172
  export async function readSchema(name, packageRoot = getPackageRoot()) {
164
173
  const filename = name.endsWith(".schema.json") ? name : `${name}.schema.json`;
165
174
  const schemaPath = path.join(packageRoot, "schemas", filename);
166
- return JSON.parse(await readFile(schemaPath, "utf8"));
175
+ const cacheKey = `${packageRoot}:${filename}`;
176
+ if (SCHEMA_CACHE.has(cacheKey)) {
177
+ return SCHEMA_CACHE.get(cacheKey);
178
+ }
179
+ const schema = JSON.parse(await readFile(schemaPath, "utf8"));
180
+ SCHEMA_CACHE.set(cacheKey, schema);
181
+ return schema;
167
182
  }
168
183
 
169
184
  export async function inspectSchemaHealth(packageRoot = getPackageRoot()) {
@@ -49,6 +49,7 @@ export const TEMPLATE_PATHS = [
49
49
  "schemas/routing-input.schema.json",
50
50
  "schemas/routing-result.schema.json",
51
51
  "schemas/work-state.schema.json",
52
+ "schemas/continuity.schema.json",
52
53
  "schemas/execution-receipt.schema.json",
53
54
  "schemas/task-brief.schema.json",
54
55
  "schemas/delegated-result.schema.json",
@@ -59,11 +60,13 @@ export const TEMPLATE_PATHS = [
59
60
  "schemas/config.schema.json",
60
61
  "schemas/preflight.schema.json",
61
62
  "schemas/check.schema.json",
63
+ "schemas/execution.schema.json",
62
64
  "schemas/evidence-coverage.schema.json",
63
65
  "schemas/event.schema.json",
64
66
  "schemas/activation.schema.json",
65
67
  "schemas/policy.schema.json",
66
68
  "schemas/task-bundle.schema.json",
69
+ "schemas/authority.schema.json",
67
70
  ];
68
71
 
69
72
  export function getPackageRoot() {