@cassiomc1/forgeloop 1.3.0 → 1.6.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 -0
- package/AGENTS.md +1 -0
- package/CLAUDE.md +1 -0
- package/DOCS_INDEX.md +20 -8
- package/EXECUTION_STATE.md +60 -0
- package/LOOP_ENGINEERING.md +135 -5
- package/LOOP_SYSTEM_DESIGN.md +54 -1
- package/PROTOCOL_INTEGRATION.md +87 -0
- package/QUALITY_SCORECARD.md +2 -0
- package/README.md +69 -9
- package/TERMINOLOGY.md +15 -0
- package/THIRD_PARTY_NOTICES.md +30 -0
- package/THREAT_MODEL.md +59 -1
- package/docs/ARTIFACT_REFERENCE.md +183 -0
- package/docs/CLI_REFERENCE.md +391 -6
- package/docs/CROSS_HARNESS_CONTINUITY.md +23 -0
- package/docs/DIAGNOSTIC_MODEL.md +181 -0
- package/docs/DOCUMENTATION_GUIDE.md +36 -13
- package/docs/EXECUTION_TRACE.md +76 -0
- package/docs/GETTING_STARTED.md +1 -0
- package/docs/MCP.md +159 -0
- package/docs/RECIPES.md +149 -0
- package/docs/RELEASE_CHECKLIST_1_4.md +38 -0
- package/docs/RELEASE_CHECKLIST_1_5_MCP.md +78 -0
- package/docs/TROUBLESHOOTING.md +217 -3
- package/docs/UNIVERSAL_INTEGRATION.md +48 -0
- package/docs/assets/diagrams/forgeloop-engineering-flow.html +13797 -0
- package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +37 -0
- package/docs/assets/diagrams/forgeloop-engineering-flow.svg +5002 -0
- package/docs/diagrams/README.md +55 -0
- package/docs/diagrams/forgeloop-engineering-flow.workflow.json +122 -0
- package/docs/diagrams/manifest.json +42 -0
- package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +20 -0
- package/package.json +21 -8
- package/schemas/action.schema.json +100 -0
- package/schemas/approval.schema.json +51 -0
- package/schemas/capability-policy.schema.json +41 -0
- package/schemas/diagnostic-case.schema.json +85 -0
- package/schemas/execution-receipt.schema.json +16 -0
- package/schemas/hypothesis-disposition.schema.json +16 -0
- package/schemas/intervention.schema.json +27 -0
- package/schemas/policy-lock.schema.json +1 -0
- package/schemas/policy-snapshot.schema.json +2 -0
- package/schemas/task-recovery.schema.json +61 -0
- package/schemas/trajectory-evaluation.schema.json +64 -0
- package/schemas/trajectory-scenario.schema.json +42 -0
- package/src/cli.js +267 -347
- package/src/commands/action-authorize.js +41 -0
- package/src/commands/action-propose.js +10 -0
- package/src/commands/action-reconcile.js +10 -0
- package/src/commands/action-record.js +47 -0
- package/src/commands/action-show.js +10 -0
- package/src/commands/action-verify.js +10 -0
- package/src/commands/advance.js +7 -2
- package/src/commands/approval-request.js +64 -0
- package/src/commands/approval-resolve.js +10 -0
- package/src/commands/audit.js +5 -0
- package/src/commands/baseline.js +3 -3
- package/src/commands/eval.js +6 -0
- package/src/commands/history.js +18 -0
- package/src/commands/init.js +2 -2
- package/src/commands/inspect.js +55 -0
- package/src/commands/metrics.js +7 -0
- package/src/commands/next.js +8 -2
- package/src/commands/policy-discover.js +2 -2
- package/src/commands/progress.js +6 -2
- package/src/commands/record-diagnosis.js +37 -1
- package/src/commands/record-hypothesis-disposition.js +45 -0
- package/src/commands/record-intervention.js +35 -0
- package/src/commands/reflect.js +38 -0
- package/src/commands/report.js +9 -1
- package/src/commands/run-action.js +18 -0
- package/src/commands/status.js +17 -0
- package/src/commands/task-create.js +39 -1
- package/src/commands/task-list.js +14 -1
- package/src/commands/task-lock-status.js +2 -2
- package/src/commands/task-recover.js +202 -0
- package/src/commands/task-repair-legacy-recovery.js +417 -0
- package/src/commands/task-resume.js +172 -0
- package/src/commands/task-scope.js +23 -4
- package/src/commands/task-show.js +18 -4
- package/src/commands/trace.js +34 -0
- package/src/commands/validate-protocol.js +40 -15
- package/src/core/action-authorization.js +106 -0
- package/src/core/action-constants.js +86 -0
- package/src/core/action-execution.js +105 -0
- package/src/core/action-ledger-projection.js +302 -0
- package/src/core/action-model.js +581 -0
- package/src/core/action-readiness.js +141 -0
- package/src/core/action-reconciliation-policy.js +49 -0
- package/src/core/action-reconciliation.js +66 -0
- package/src/core/action-verification.js +111 -0
- package/src/core/actions.js +462 -0
- package/src/core/approvals.js +405 -0
- package/src/core/artifact-registry.js +60 -0
- package/src/core/audit.js +45 -4
- package/src/core/bundles.js +30 -0
- package/src/core/capability-policy.js +226 -0
- package/src/core/cli-command-definitions.js +260 -5
- package/src/core/command-executors.js +543 -0
- package/src/core/command-input.js +107 -0
- package/src/core/command-runtime.js +117 -0
- package/src/core/completion-artifacts.js +39 -15
- package/src/core/completion-ownership.js +88 -0
- package/src/core/completion-recovery-rebind.js +194 -0
- package/src/core/completion.js +70 -0
- package/src/core/continuity-reconciliation.js +24 -5
- package/src/core/diagnostic-model.js +396 -0
- package/src/core/diagnostic-projection.js +51 -0
- package/src/core/diagnostic-record.js +360 -0
- package/src/core/error-codes.js +461 -1
- package/src/core/events.js +171 -2
- package/src/core/execution-prerequisites.js +4 -1
- package/src/core/execution.js +26 -188
- package/src/core/failure-signature.js +70 -0
- package/src/core/failure-surface.js +57 -0
- package/src/core/filesystem.js +55 -6
- package/src/core/history.js +110 -0
- package/src/core/hypothesis-projection.js +85 -0
- package/src/core/information-gain-projection.js +283 -0
- package/src/core/information-gain.js +138 -0
- package/src/core/inspect.js +132 -7
- package/src/core/integration-invocation-policy.js +217 -0
- package/src/core/integration-limits.js +20 -0
- package/src/core/integration-resources.js +178 -0
- package/src/core/next-action-model.js +94 -0
- package/src/core/next-action.js +490 -3
- package/src/core/phase.js +42 -22
- package/src/core/policy-engine.js +113 -6
- package/src/core/preflight-consistency.js +31 -5
- package/src/core/preflight.js +19 -2
- package/src/core/prepared-execution.js +227 -0
- package/src/core/progress.js +41 -4
- package/src/core/project-root.js +21 -0
- package/src/core/protocol-info.js +61 -0
- package/src/core/protocol.js +14 -0
- package/src/core/receipt.js +1 -0
- package/src/core/reconcile-closure.js +35 -10
- package/src/core/recovery-history.js +116 -0
- package/src/core/reflection.js +305 -0
- package/src/core/resumability.js +57 -3
- package/src/core/schema-validation.js +9 -0
- package/src/core/strategy-analysis.js +97 -0
- package/src/core/task-claim-state.js +272 -0
- package/src/core/task-command.js +5 -1
- package/src/core/task-conflict-inspection.js +321 -0
- package/src/core/task-context.js +32 -29
- package/src/core/task-discovery.js +14 -1
- package/src/core/task-lock.js +216 -22
- package/src/core/task-paths.js +31 -2
- package/src/core/task-recovery-migration.js +192 -0
- package/src/core/task-recovery.js +205 -0
- package/src/core/task-scope.js +33 -1
- package/src/core/task-snapshot.js +53 -0
- package/src/core/templates.js +9 -0
- package/src/core/trace.js +548 -0
- package/src/core/trajectory-evaluation.js +71 -0
- package/src/core/trajectory-metrics.js +80 -0
- package/src/core/transaction.js +36 -2
- package/src/core/work-state.js +10 -5
- package/src/integration.js +47 -0
- package/docs/assets/forgeloop-flow.svg +0 -1
- package/docs/forgeloop-flow.mmd +0 -51
|
@@ -136,13 +136,15 @@ export async function detectPolicyCapability(target, packageRoot) {
|
|
|
136
136
|
const baselineRel = PROJECT_ARTIFACT_PATHS.policyBaseline;
|
|
137
137
|
const discoveryRel = PROJECT_ARTIFACT_PATHS.policyDiscovery;
|
|
138
138
|
const lockRel = PROJECT_ARTIFACT_PATHS.policyLock;
|
|
139
|
+
const capabilityRel = PROJECT_ARTIFACT_PATHS.capabilityPolicy;
|
|
139
140
|
|
|
140
141
|
const hasRules = await fileExists(path.join(target, rulesRel));
|
|
141
142
|
const hasBaseline = await fileExists(path.join(target, baselineRel));
|
|
142
143
|
const hasDiscovery = await fileExists(path.join(target, discoveryRel));
|
|
143
144
|
const hasLock = await fileExists(path.join(target, lockRel));
|
|
145
|
+
const hasCapabilityPolicy = await fileExists(path.join(target, capabilityRel));
|
|
144
146
|
|
|
145
|
-
if (!hasRules && !hasBaseline && !hasDiscovery && !hasLock) {
|
|
147
|
+
if (!hasRules && !hasBaseline && !hasDiscovery && !hasLock && !hasCapabilityPolicy) {
|
|
146
148
|
return "NOT_PRESENT";
|
|
147
149
|
}
|
|
148
150
|
|
|
@@ -151,6 +153,13 @@ export async function detectPolicyCapability(target, packageRoot) {
|
|
|
151
153
|
if (hasBaseline) await readBaseline(target, packageRoot);
|
|
152
154
|
if (hasDiscovery) await readDiscoveryReport(target, packageRoot);
|
|
153
155
|
if (hasLock) await readPolicyLock(target, packageRoot);
|
|
156
|
+
// A capability policy is policy configuration: its presence makes the
|
|
157
|
+
// executable-policy subsystem applicable and it must fail closed when
|
|
158
|
+
// malformed.
|
|
159
|
+
if (hasCapabilityPolicy) {
|
|
160
|
+
const { loadCapabilityPolicy } = await import("./capability-policy.js");
|
|
161
|
+
await loadCapabilityPolicy(target, packageRoot);
|
|
162
|
+
}
|
|
154
163
|
return "AVAILABLE";
|
|
155
164
|
} catch {
|
|
156
165
|
return "INVALID";
|
|
@@ -190,12 +199,22 @@ export function canonicalizeBaseline(baseline) {
|
|
|
190
199
|
};
|
|
191
200
|
}
|
|
192
201
|
|
|
193
|
-
export function computePolicyLockData(rules, baseline) {
|
|
202
|
+
export function computePolicyLockData(rules, baseline, capabilityPolicy = null) {
|
|
194
203
|
const canonicalRules = canonicalizeRules(rules);
|
|
195
204
|
const canonicalBase = canonicalizeBaseline(baseline);
|
|
196
205
|
const rulesDigest = sha256(canonicalFingerprint(canonicalRules));
|
|
197
206
|
const baselineDigest = sha256(canonicalFingerprint(canonicalBase));
|
|
198
|
-
|
|
207
|
+
// The capability policy participates in the lock only when the artifact
|
|
208
|
+
// exists, keeping historical locks stable for projects that never adopt
|
|
209
|
+
// durable actions.
|
|
210
|
+
const capabilityPolicyDigest =
|
|
211
|
+
capabilityPolicy && typeof capabilityPolicy === "object"
|
|
212
|
+
? sha256(canonicalFingerprint(capabilityPolicy))
|
|
213
|
+
: null;
|
|
214
|
+
const fullDigest =
|
|
215
|
+
capabilityPolicyDigest === null
|
|
216
|
+
? sha256(`${rulesDigest}:${baselineDigest}`)
|
|
217
|
+
: sha256(`${rulesDigest}:${baselineDigest}:${capabilityPolicyDigest}`);
|
|
199
218
|
|
|
200
219
|
return {
|
|
201
220
|
schemaVersion: 1,
|
|
@@ -203,10 +222,78 @@ export function computePolicyLockData(rules, baseline) {
|
|
|
203
222
|
digest: `sha256:${fullDigest}`,
|
|
204
223
|
rulesDigest: `sha256:${rulesDigest}`,
|
|
205
224
|
baselineDigest: `sha256:${baselineDigest}`,
|
|
225
|
+
...(capabilityPolicyDigest === null
|
|
226
|
+
? {}
|
|
227
|
+
: { capabilityPolicyDigest: `sha256:${capabilityPolicyDigest}` }),
|
|
206
228
|
capturedAt: new Date().toISOString(),
|
|
207
229
|
};
|
|
208
230
|
}
|
|
209
231
|
|
|
232
|
+
export async function readCapabilityPolicyIdentityForLock(target, packageRoot) {
|
|
233
|
+
const { readCapabilityPolicyIdentity } = await import("./capability-policy.js");
|
|
234
|
+
return readCapabilityPolicyIdentity(target, packageRoot);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Canonical policy identity for durable-action authorization. Fails closed
|
|
239
|
+
* with deterministic codes whenever the capability policy, the persisted
|
|
240
|
+
* lock, or the task snapshot do not agree.
|
|
241
|
+
*/
|
|
242
|
+
export async function loadPolicyIdentity(target, packageRoot, taskId) {
|
|
243
|
+
const { E_ACTION_POLICY_DRIFT, E_ACTION_POLICY_LOCK_REQUIRED } = await import("./error-codes.js");
|
|
244
|
+
const capability = await readCapabilityPolicyIdentityForLock(target, packageRoot);
|
|
245
|
+
const lock = await verifyPolicyLock(target, packageRoot);
|
|
246
|
+
const snapshot = taskId ? await readTaskPolicySnapshot(target, taskId, packageRoot) : null;
|
|
247
|
+
|
|
248
|
+
if (capability.policy && lock.status !== "VALID") {
|
|
249
|
+
return {
|
|
250
|
+
status: lock.status === "MISMATCH" ? "DRIFT" : "INVALID",
|
|
251
|
+
code: lock.status === "MISMATCH" ? E_ACTION_POLICY_DRIFT : E_ACTION_POLICY_LOCK_REQUIRED,
|
|
252
|
+
...(lock.status === "MISMATCH" ? { mismatches: lock.mismatches } : {}),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// A modern task snapshot must bind the current capability policy before any
|
|
257
|
+
// side-effecting action may be authorized.
|
|
258
|
+
if (
|
|
259
|
+
capability.digest
|
|
260
|
+
&& snapshot
|
|
261
|
+
&& snapshot.capabilityPolicyDigest !== capability.digest
|
|
262
|
+
) {
|
|
263
|
+
return {
|
|
264
|
+
status: "DRIFT",
|
|
265
|
+
code: E_ACTION_POLICY_DRIFT,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// A modern capability policy without a task snapshot has no epoch to bind:
|
|
270
|
+
// authorization cannot proceed for a task-scoped action.
|
|
271
|
+
if (capability.digest && taskId && !snapshot) {
|
|
272
|
+
return {
|
|
273
|
+
status: "INVALID",
|
|
274
|
+
code: E_ACTION_POLICY_LOCK_REQUIRED,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
status: "VALID",
|
|
280
|
+
lockDigest: lock.digest ?? null,
|
|
281
|
+
taskPolicyDigest: snapshot?.policyDigest ?? null,
|
|
282
|
+
capabilityPolicyFingerprint: capability.fingerprint,
|
|
283
|
+
capabilityPolicyDigest: capability.digest,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Compute policy-lock data for persistence, binding the current capability
|
|
289
|
+
* policy whenever it exists so locks, snapshots, and authorization evidence
|
|
290
|
+
* share one policy identity.
|
|
291
|
+
*/
|
|
292
|
+
export async function computePersistedPolicyLockData(target, packageRoot, rules, baseline) {
|
|
293
|
+
const identity = await readCapabilityPolicyIdentityForLock(target, packageRoot);
|
|
294
|
+
return computePolicyLockData(rules, baseline, identity.policy);
|
|
295
|
+
}
|
|
296
|
+
|
|
210
297
|
export async function verifyPolicyLock(target, packageRoot) {
|
|
211
298
|
const capability = await detectPolicyCapability(target, packageRoot);
|
|
212
299
|
if (capability === "NOT_PRESENT") {
|
|
@@ -223,7 +310,18 @@ export async function verifyPolicyLock(target, packageRoot) {
|
|
|
223
310
|
|
|
224
311
|
const rules = await loadEffectiveRules(target, packageRoot);
|
|
225
312
|
const baseline = await readBaseline(target, packageRoot);
|
|
226
|
-
|
|
313
|
+
let capabilityPolicy = null;
|
|
314
|
+
try {
|
|
315
|
+
const { loadCapabilityPolicy } = await import("./capability-policy.js");
|
|
316
|
+
const loadedCapabilityPolicy = await loadCapabilityPolicy(target, packageRoot);
|
|
317
|
+
capabilityPolicy = loadedCapabilityPolicy?.policy ?? null;
|
|
318
|
+
} catch (error) {
|
|
319
|
+
if (error.code === "E_POLICY_INVALID") {
|
|
320
|
+
return { status: "INVALID", error: "Capability policy artifact is malformed" };
|
|
321
|
+
}
|
|
322
|
+
throw error;
|
|
323
|
+
}
|
|
324
|
+
const expectedLock = computePolicyLockData(rules, baseline, capabilityPolicy);
|
|
227
325
|
|
|
228
326
|
const mismatches = [];
|
|
229
327
|
if (persistedLock.algorithm !== expectedLock.algorithm) {
|
|
@@ -238,6 +336,12 @@ export async function verifyPolicyLock(target, packageRoot) {
|
|
|
238
336
|
if (persistedLock.baselineDigest !== expectedLock.baselineDigest) {
|
|
239
337
|
mismatches.push("baselineDigest");
|
|
240
338
|
}
|
|
339
|
+
if (
|
|
340
|
+
expectedLock.capabilityPolicyDigest !== undefined &&
|
|
341
|
+
persistedLock.capabilityPolicyDigest !== expectedLock.capabilityPolicyDigest
|
|
342
|
+
) {
|
|
343
|
+
mismatches.push("capabilityPolicyDigest");
|
|
344
|
+
}
|
|
241
345
|
|
|
242
346
|
if (mismatches.length > 0) {
|
|
243
347
|
return {
|
|
@@ -254,6 +358,9 @@ export async function verifyPolicyLock(target, packageRoot) {
|
|
|
254
358
|
digest: persistedLock.digest ?? null,
|
|
255
359
|
rulesDigest: persistedLock.rulesDigest ?? null,
|
|
256
360
|
baselineDigest: persistedLock.baselineDigest ?? null,
|
|
361
|
+
...(expectedLock.capabilityPolicyDigest === undefined
|
|
362
|
+
? {}
|
|
363
|
+
: { capabilityPolicyDigest: persistedLock.capabilityPolicyDigest ?? null }),
|
|
257
364
|
},
|
|
258
365
|
};
|
|
259
366
|
}
|
|
@@ -476,7 +583,7 @@ export async function evaluateTargetPolicy({
|
|
|
476
583
|
if (taskId) {
|
|
477
584
|
const taskSnapshot = await readTaskPolicySnapshot(target, taskId, packageRoot);
|
|
478
585
|
if (taskSnapshot) {
|
|
479
|
-
const currentLock =
|
|
586
|
+
const currentLock = await computePersistedPolicyLockData(target, packageRoot, rules, baseline);
|
|
480
587
|
if (taskSnapshot.policyDigest !== currentLock.digest) {
|
|
481
588
|
const policyDiff = diffPolicies(
|
|
482
589
|
{ rules: taskSnapshot.rules, baseline: taskSnapshot.baseline, baselineDigest: taskSnapshot.baselineDigest },
|
|
@@ -513,7 +620,7 @@ export async function evaluateTargetPolicy({
|
|
|
513
620
|
}
|
|
514
621
|
}
|
|
515
622
|
|
|
516
|
-
const currentLock =
|
|
623
|
+
const currentLock = await computePersistedPolicyLockData(target, packageRoot, rules, baseline);
|
|
517
624
|
|
|
518
625
|
return {
|
|
519
626
|
status: errors.length === 0 ? "VALID" : "INVALID",
|
|
@@ -165,10 +165,34 @@ function sameBlockedPreflightEvent(event, result) {
|
|
|
165
165
|
&& event.details?.routingFingerprint === result.fingerprints.routing;
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Returns the latest recorded preflight outcome event (READY or BLOCKED) for
|
|
170
|
+
* the task. An append-only lifecycle may legitimately contain an older READY
|
|
171
|
+
* event that was superseded by a later BLOCKED outcome (for example after the
|
|
172
|
+
* contract evolved or a gate requirement was added); the binding chronology is
|
|
173
|
+
* the latest outcome, not the first READY ever recorded.
|
|
174
|
+
*/
|
|
175
|
+
function latestPreflightOutcomeEvent(events, taskId) {
|
|
176
|
+
let latest = null;
|
|
177
|
+
for (const event of events) {
|
|
178
|
+
if ((event.event === "PREFLIGHT_READY" || event.event === "PREFLIGHT_BLOCKED")
|
|
179
|
+
&& event.taskId === taskId) {
|
|
180
|
+
latest = event;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return latest;
|
|
184
|
+
}
|
|
185
|
+
|
|
168
186
|
export function assertExistingReadyLifecycleCompatibility(ledger, result) {
|
|
169
187
|
if (result.status !== "READY") return;
|
|
170
|
-
const
|
|
171
|
-
|
|
188
|
+
const events = ledger?.events ?? [];
|
|
189
|
+
const existingReady = events.findLast((event) => event.event === "PREFLIGHT_READY" && event.taskId === result.taskId);
|
|
190
|
+
if (!existingReady) return;
|
|
191
|
+
const latestOutcome = latestPreflightOutcomeEvent(events, result.taskId);
|
|
192
|
+
// A READY outcome superseded by a later BLOCKED outcome may be replaced by a
|
|
193
|
+
// fresh READY with different details once the blocked preflight is resolved.
|
|
194
|
+
if (latestOutcome?.event === "PREFLIGHT_BLOCKED") return;
|
|
195
|
+
if (!sameReadyPreflightEvent(existingReady, result)) {
|
|
172
196
|
throw preflightError(
|
|
173
197
|
"E_PHASE_CHRONOLOGY_INVALID",
|
|
174
198
|
"PREFLIGHT_READY already exists with different READY preflight details; repair the contract, route, or gate lifecycle before refreshing preflight",
|
|
@@ -200,16 +224,18 @@ export async function appendActivationEvents(target, packageRoot, ledger, result
|
|
|
200
224
|
}
|
|
201
225
|
}
|
|
202
226
|
|
|
203
|
-
const existingReady = events.
|
|
227
|
+
const existingReady = events.findLast((event) => event.event === "PREFLIGHT_READY" && event.taskId === result.taskId);
|
|
228
|
+
const latestOutcome = latestPreflightOutcomeEvent(events, result.taskId);
|
|
229
|
+
const readySupersededByBlocked = existingReady && latestOutcome?.event === "PREFLIGHT_BLOCKED";
|
|
204
230
|
if (result.status === "READY") {
|
|
205
|
-
if (existingReady && !sameReadyPreflightEvent(existingReady, result)) {
|
|
231
|
+
if (existingReady && !readySupersededByBlocked && !sameReadyPreflightEvent(existingReady, result)) {
|
|
206
232
|
throw preflightError(
|
|
207
233
|
"E_PHASE_CHRONOLOGY_INVALID",
|
|
208
234
|
"PREFLIGHT_READY already exists with different READY preflight details; repair the contract, route, or gate lifecycle before refreshing preflight",
|
|
209
235
|
[ARTIFACT_PATHS.preflight, ARTIFACT_PATHS.events, ARTIFACT_PATHS.contract, ARTIFACT_PATHS.route, ARTIFACT_PATHS.gates],
|
|
210
236
|
);
|
|
211
237
|
}
|
|
212
|
-
if (!existingReady) {
|
|
238
|
+
if (!existingReady || readySupersededByBlocked) {
|
|
213
239
|
await append({
|
|
214
240
|
taskId: result.taskId,
|
|
215
241
|
event: "PREFLIGHT_READY",
|
package/src/core/preflight.js
CHANGED
|
@@ -200,7 +200,7 @@ export async function runPreflight({
|
|
|
200
200
|
detectPolicyCapability,
|
|
201
201
|
loadEffectiveRules,
|
|
202
202
|
readBaseline,
|
|
203
|
-
|
|
203
|
+
computePersistedPolicyLockData,
|
|
204
204
|
readTaskPolicySnapshot,
|
|
205
205
|
writeTaskPolicySnapshot,
|
|
206
206
|
} = await import("./policy-engine.js");
|
|
@@ -225,13 +225,30 @@ export async function runPreflight({
|
|
|
225
225
|
if (!existingSnapshot) {
|
|
226
226
|
const rules = await loadEffectiveRules(target, packageRoot);
|
|
227
227
|
const baseline = await readBaseline(target, packageRoot);
|
|
228
|
-
const lock =
|
|
228
|
+
const lock = await computePersistedPolicyLockData(target, packageRoot, rules, baseline);
|
|
229
|
+
// Bind the current capability policy into the snapshot so policy
|
|
230
|
+
// drift is detectable before any side-effecting action launch.
|
|
231
|
+
const { readCapabilityPolicyIdentity } = await import("./capability-policy.js");
|
|
232
|
+
const capabilityIdentity = await readCapabilityPolicyIdentity(target, packageRoot);
|
|
233
|
+
if (capabilityIdentity.policy && !lock.capabilityPolicyDigest) {
|
|
234
|
+
throw preflightError(
|
|
235
|
+
"E_POLICY_SNAPSHOT_WRITE_FAILED",
|
|
236
|
+
"Capability policy identity could not be bound to the task policy snapshot",
|
|
237
|
+
[taskArtifactPath(result.taskId, "policySnapshot")],
|
|
238
|
+
);
|
|
239
|
+
}
|
|
229
240
|
const snapshot = {
|
|
230
241
|
schemaVersion: 1,
|
|
231
242
|
policyDigest: lock.digest,
|
|
232
243
|
rules,
|
|
233
244
|
baseline: baseline ?? { schemaVersion: 1, entries: [] },
|
|
234
245
|
baselineDigest: lock.baselineDigest,
|
|
246
|
+
...(capabilityIdentity.digest
|
|
247
|
+
? {
|
|
248
|
+
capabilityPolicyDigest: capabilityIdentity.digest,
|
|
249
|
+
capabilityPolicyFingerprint: capabilityIdentity.fingerprint,
|
|
250
|
+
}
|
|
251
|
+
: {}),
|
|
235
252
|
capturedAt: new Date().toISOString(),
|
|
236
253
|
};
|
|
237
254
|
await writeTaskPolicySnapshot(target, result.taskId, snapshot, packageRoot);
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
resolveExecutionResolution,
|
|
4
|
+
validateVerificationAuthority,
|
|
5
|
+
E_COMMAND_RESOLUTION_AMBIGUOUS,
|
|
6
|
+
} from "./verification-capability.js";
|
|
7
|
+
|
|
8
|
+
export { E_COMMAND_RESOLUTION_AMBIGUOUS };
|
|
9
|
+
|
|
10
|
+
function executionError(code, message, artifacts = []) {
|
|
11
|
+
const error = new Error(message);
|
|
12
|
+
error.code = code;
|
|
13
|
+
error.artifacts = artifacts;
|
|
14
|
+
return error;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Deterministic pre-launch preparation. Normalizes exact argv, resolves the
|
|
19
|
+
* command, rejects ambiguous resolution contexts, and validates installation
|
|
20
|
+
* authority. Performs no process launch and writes no execution artifact
|
|
21
|
+
* (INV-EXEC-01): ACTION_STARTED is only meaningful after all of these checks
|
|
22
|
+
* succeed.
|
|
23
|
+
*/
|
|
24
|
+
export async function prepareCommandExecution({
|
|
25
|
+
target,
|
|
26
|
+
taskId,
|
|
27
|
+
argv,
|
|
28
|
+
details,
|
|
29
|
+
authorityContext,
|
|
30
|
+
runtimeContext,
|
|
31
|
+
}) {
|
|
32
|
+
if (!Array.isArray(argv) || argv.length === 0 || argv.some((item) => typeof item !== "string" || item.trim() === "")) {
|
|
33
|
+
throw executionError("E_EXECUTION_INVALID", "Execution argv must contain at least one non-empty string");
|
|
34
|
+
}
|
|
35
|
+
const commandArgv = [...argv];
|
|
36
|
+
const resolution = await resolveExecutionResolution({
|
|
37
|
+
argv: commandArgv,
|
|
38
|
+
cwd: target,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (
|
|
42
|
+
resolution.resolutionMode === "UNKNOWN"
|
|
43
|
+
&& resolution.mayInstall === true
|
|
44
|
+
&& (
|
|
45
|
+
resolution.reason === "NPM_WORKSPACE_SCRIPT_UNRESOLVED"
|
|
46
|
+
|| resolution.reason === "NPM_SUBCOMMAND_AMBIGUOUS"
|
|
47
|
+
|| resolution.reason === "NPM_COMMAND_UNCLASSIFIED"
|
|
48
|
+
|| resolution.reason === "NPM_OPTION_VALUE_AMBIGUOUS"
|
|
49
|
+
)
|
|
50
|
+
) {
|
|
51
|
+
const error = new Error(
|
|
52
|
+
resolution.reason === "NPM_WORKSPACE_SCRIPT_UNRESOLVED"
|
|
53
|
+
? "npm workspace script execution cannot be proven from the current target. Run ForgeLoop against the selected workspace directory."
|
|
54
|
+
: "Command execution context could not be proven safe before launch."
|
|
55
|
+
);
|
|
56
|
+
error.code = E_COMMAND_RESOLUTION_AMBIGUOUS;
|
|
57
|
+
error.resolution = resolution;
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (resolution.mayInstall) {
|
|
62
|
+
const check = {
|
|
63
|
+
kind: "command",
|
|
64
|
+
source: commandArgv[0],
|
|
65
|
+
details: {
|
|
66
|
+
...(details ?? {}),
|
|
67
|
+
execution: { resolution },
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
const authority = validateVerificationAuthority(check, {
|
|
71
|
+
target,
|
|
72
|
+
taskId,
|
|
73
|
+
authorityContext,
|
|
74
|
+
runtimeContext,
|
|
75
|
+
});
|
|
76
|
+
if (!authority.valid) {
|
|
77
|
+
throw executionError(authority.error.code ?? "E_INSTALLATION_AUTHORITY_REQUIRED", authority.error.message);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return { argv: commandArgv, resolution };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const MAX_CAPTURED_OUTPUT_BYTES = 64 * 1024;
|
|
85
|
+
|
|
86
|
+
async function executePreparedProcess(argv, cwd, { timeoutMs = null } = {}) {
|
|
87
|
+
const { spawn } = await import("node:child_process");
|
|
88
|
+
return new Promise((resolve) => {
|
|
89
|
+
let spawnError = null;
|
|
90
|
+
let timedOut = false;
|
|
91
|
+
let settled = false;
|
|
92
|
+
let timeout = null;
|
|
93
|
+
let forceTermination = null;
|
|
94
|
+
const stdout = [];
|
|
95
|
+
const stderr = [];
|
|
96
|
+
let stdoutBytes = 0;
|
|
97
|
+
let stderrBytes = 0;
|
|
98
|
+
let outputTruncated = false;
|
|
99
|
+
const capture = (chunks, chunk, total) => {
|
|
100
|
+
const available = MAX_CAPTURED_OUTPUT_BYTES - total;
|
|
101
|
+
if (available <= 0) {
|
|
102
|
+
outputTruncated = true;
|
|
103
|
+
return total;
|
|
104
|
+
}
|
|
105
|
+
if (chunk.length > available) {
|
|
106
|
+
chunks.push(chunk.subarray(0, available));
|
|
107
|
+
outputTruncated = true;
|
|
108
|
+
return total + available;
|
|
109
|
+
}
|
|
110
|
+
chunks.push(chunk);
|
|
111
|
+
return total + chunk.length;
|
|
112
|
+
};
|
|
113
|
+
const finish = (result) => {
|
|
114
|
+
if (settled) return;
|
|
115
|
+
settled = true;
|
|
116
|
+
if (timeout) clearTimeout(timeout);
|
|
117
|
+
if (forceTermination) clearTimeout(forceTermination);
|
|
118
|
+
resolve({
|
|
119
|
+
...result,
|
|
120
|
+
timedOut,
|
|
121
|
+
stdout: Buffer.concat(stdout),
|
|
122
|
+
stderr: Buffer.concat(stderr),
|
|
123
|
+
stdoutBytes,
|
|
124
|
+
stderrBytes,
|
|
125
|
+
outputTruncated,
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
try {
|
|
129
|
+
const child = spawn(argv[0], argv.slice(1), {
|
|
130
|
+
cwd,
|
|
131
|
+
shell: false,
|
|
132
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
133
|
+
});
|
|
134
|
+
child.stdout?.on("data", (chunk) => { stdoutBytes = capture(stdout, chunk, stdoutBytes); });
|
|
135
|
+
child.stderr?.on("data", (chunk) => { stderrBytes = capture(stderr, chunk, stderrBytes); });
|
|
136
|
+
child.once("error", (error) => {
|
|
137
|
+
spawnError = error;
|
|
138
|
+
});
|
|
139
|
+
child.once("close", (exitCode, signal) => {
|
|
140
|
+
finish({ exitCode, signal, spawnError });
|
|
141
|
+
});
|
|
142
|
+
if (Number.isInteger(timeoutMs) && timeoutMs > 0) {
|
|
143
|
+
timeout = setTimeout(() => {
|
|
144
|
+
timedOut = true;
|
|
145
|
+
child.kill("SIGTERM");
|
|
146
|
+
forceTermination = setTimeout(() => {
|
|
147
|
+
child.kill("SIGKILL");
|
|
148
|
+
}, TERMINATION_GRACE_MS_PREPARED);
|
|
149
|
+
}, timeoutMs);
|
|
150
|
+
}
|
|
151
|
+
} catch (error) {
|
|
152
|
+
finish({ exitCode: null, signal: null, spawnError: error });
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export const TERMINATION_GRACE_MS_PREPARED = 1_000;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Launch a prepared execution. The caller must have already recorded
|
|
161
|
+
* ACTION_STARTED; this function never performs deterministic pre-launch
|
|
162
|
+
* validation again.
|
|
163
|
+
*/
|
|
164
|
+
export async function runPreparedCommandExecution({
|
|
165
|
+
target,
|
|
166
|
+
packageRoot,
|
|
167
|
+
taskId,
|
|
168
|
+
checkId,
|
|
169
|
+
requirement,
|
|
170
|
+
verificationCycle = 1,
|
|
171
|
+
prepared,
|
|
172
|
+
timeoutMs = null,
|
|
173
|
+
executionPath,
|
|
174
|
+
}) {
|
|
175
|
+
const { createHash } = await import("node:crypto");
|
|
176
|
+
const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
177
|
+
const executionId = `exec-${randomUUID()}`;
|
|
178
|
+
const startedAt = new Date().toISOString();
|
|
179
|
+
const processResult = await executePreparedProcess(prepared.argv, target, { timeoutMs });
|
|
180
|
+
const finishedAt = new Date().toISOString();
|
|
181
|
+
const execution = {
|
|
182
|
+
schemaVersion: 1,
|
|
183
|
+
protocolVersion: 1,
|
|
184
|
+
executionId,
|
|
185
|
+
taskId,
|
|
186
|
+
checkId,
|
|
187
|
+
requirement,
|
|
188
|
+
verificationCycle,
|
|
189
|
+
kind: "COMMAND_EXECUTION",
|
|
190
|
+
argv: prepared.argv,
|
|
191
|
+
cwd: target,
|
|
192
|
+
resolution: {
|
|
193
|
+
resolutionMode: prepared.resolution.resolutionMode,
|
|
194
|
+
mayInstall: prepared.resolution.mayInstall,
|
|
195
|
+
installer: prepared.resolution.installer,
|
|
196
|
+
tool: prepared.resolution.tool,
|
|
197
|
+
},
|
|
198
|
+
...(prepared.resolution.dispatch ? { dispatch: prepared.resolution.dispatch } : {}),
|
|
199
|
+
startedAt,
|
|
200
|
+
finishedAt,
|
|
201
|
+
status: processResult.exitCode === 0 && !processResult.spawnError && !processResult.timedOut ? "passed" : "failed",
|
|
202
|
+
exitCode: processResult.exitCode,
|
|
203
|
+
durationMs: Math.max(0, Date.parse(finishedAt) - Date.parse(startedAt)),
|
|
204
|
+
termination: processResult.spawnError ? "spawn-error" : processResult.timedOut ? "timeout" : processResult.signal ? "signal" : "exit",
|
|
205
|
+
signal: processResult.signal ?? null,
|
|
206
|
+
stdoutSha256: digest(processResult.stdout),
|
|
207
|
+
stderrSha256: digest(processResult.stderr),
|
|
208
|
+
stdoutBytes: processResult.stdoutBytes,
|
|
209
|
+
stderrBytes: processResult.stderrBytes,
|
|
210
|
+
outputTruncated: processResult.outputTruncated,
|
|
211
|
+
...(Number.isInteger(timeoutMs) && timeoutMs > 0 ? { timeoutMs, terminationGraceMs: TERMINATION_GRACE_MS_PREPARED } : {}),
|
|
212
|
+
};
|
|
213
|
+
const { writeJsonArtifact } = await import("./artifacts.js");
|
|
214
|
+
const written = await writeJsonArtifact(target, executionPath ?? await resolveExecutionArtifactPathFor(target, taskId, executionId), execution, "execution", packageRoot);
|
|
215
|
+
return {
|
|
216
|
+
path: written.path,
|
|
217
|
+
execution: written.value,
|
|
218
|
+
result: processResult.spawnError
|
|
219
|
+
? "process failed to start"
|
|
220
|
+
: `process exited with code ${processResult.exitCode}`,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function resolveExecutionArtifactPathFor(target, taskId, executionId) {
|
|
225
|
+
const { resolveExecutionArtifactPath } = await import("./execution.js");
|
|
226
|
+
return resolveExecutionArtifactPath(target, taskId, executionId);
|
|
227
|
+
}
|
package/src/core/progress.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { diagnosisEventsForTask } from "./diagnosis-model.js";
|
|
2
|
+
import { resolveCurrentCycleDiagnostic } from "./diagnostic-projection.js";
|
|
3
|
+
import {
|
|
4
|
+
buildInformationGainProjection,
|
|
5
|
+
evaluateStructuredDiagnosticStall,
|
|
6
|
+
} from "./information-gain-projection.js";
|
|
2
7
|
|
|
3
8
|
export const PROGRESS_STATUS = Object.freeze({
|
|
4
9
|
ADVANCING: "ADVANCING",
|
|
@@ -32,7 +37,25 @@ export function evaluateProgress({ state, events = [] } = {}) {
|
|
|
32
37
|
|
|
33
38
|
const taskEvents = Array.isArray(events) ? events.filter((e) => !state.taskId || e.taskId === state.taskId) : [];
|
|
34
39
|
const diagEvents = diagnosisEventsForTask(taskEvents, state.taskId);
|
|
35
|
-
const
|
|
40
|
+
const resolvedDiagnostic = resolveCurrentCycleDiagnostic(taskEvents, state.taskId, state.verificationCycle ?? null);
|
|
41
|
+
const latestDiag = resolvedDiagnostic?.details ?? diagEvents.at(-1)?.details ?? null;
|
|
42
|
+
|
|
43
|
+
let latestGainClassification = latestDiag?.informationGain ?? null;
|
|
44
|
+
let structuredStall = null;
|
|
45
|
+
if (resolvedDiagnostic?.sourceModel === "STRUCTURED_DIAGNOSTIC_CASE_V1") {
|
|
46
|
+
if (!Array.isArray(latestDiag.evidenceRefs)) {
|
|
47
|
+
latestDiag.evidenceRefs = [...new Set(
|
|
48
|
+
(latestDiag.hypotheses ?? []).flatMap((hypothesis) => hypothesis.evidenceRefs ?? []),
|
|
49
|
+
)];
|
|
50
|
+
}
|
|
51
|
+
// Canonical structured-stall truth; compatibility classification is presentation only.
|
|
52
|
+
structuredStall = evaluateStructuredDiagnosticStall(
|
|
53
|
+
buildInformationGainProjection(taskEvents, state.taskId),
|
|
54
|
+
{ verificationCycle: state.verificationCycle ?? null },
|
|
55
|
+
);
|
|
56
|
+
latestDiag.effectiveInformationGain = !(structuredStall.stalled);
|
|
57
|
+
latestGainClassification = structuredStall.latestGain?.classification ?? null;
|
|
58
|
+
}
|
|
36
59
|
|
|
37
60
|
// Build checksById index for resolving requirement from check IDs
|
|
38
61
|
const checksById = new Map();
|
|
@@ -48,13 +71,27 @@ export function evaluateProgress({ state, events = [] } = {}) {
|
|
|
48
71
|
}
|
|
49
72
|
|
|
50
73
|
// 1. Check if latest diagnosis has NO information gain (global stall)
|
|
51
|
-
|
|
74
|
+
const legacyStalled = resolvedDiagnostic?.sourceModel !== "STRUCTURED_DIAGNOSTIC_CASE_V1"
|
|
75
|
+
&& Boolean(latestDiag) && latestGainClassification === "NONE";
|
|
76
|
+
// Single normalized diagnostic-stall truth: structured diagnostics defer to
|
|
77
|
+
// the canonical structured stall evaluator; legacy diagnoses keep their
|
|
78
|
+
// compatibility rule (informationGain NONE).
|
|
79
|
+
const diagnosticStalled =
|
|
80
|
+
resolvedDiagnostic?.sourceModel === "STRUCTURED_DIAGNOSTIC_CASE_V1"
|
|
81
|
+
? Boolean(structuredStall?.stalled)
|
|
82
|
+
: Boolean(legacyStalled);
|
|
83
|
+
if (diagnosticStalled) {
|
|
52
84
|
status = PROGRESS_STATUS.STALLED;
|
|
53
85
|
signals.push({
|
|
54
86
|
code: PROGRESS_SIGNAL.NO_DIAGNOSTIC_INFORMATION_GAIN,
|
|
55
87
|
severity: "BLOCKING_FOR_RETRY",
|
|
56
|
-
message:
|
|
88
|
+
message: resolvedDiagnostic?.sourceModel === "STRUCTURED_DIAGNOSTIC_CASE_V1"
|
|
89
|
+
? "Latest diagnostic state introduces no effective new information relative to the prior comparable diagnostic state."
|
|
90
|
+
: "Latest diagnosis repeats the prior hypothesis with the same evidence.",
|
|
57
91
|
verificationCycles: [latestDiag.verificationCycle],
|
|
92
|
+
...(structuredStall?.latestGain
|
|
93
|
+
? { classification: structuredStall.latestGain.classification, effectiveGain: false }
|
|
94
|
+
: {}),
|
|
58
95
|
});
|
|
59
96
|
}
|
|
60
97
|
|
|
@@ -88,7 +125,7 @@ export function evaluateProgress({ state, events = [] } = {}) {
|
|
|
88
125
|
for (const [req, cyclesSet] of [...reqCycles.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
89
126
|
if (cyclesSet.size >= 3) {
|
|
90
127
|
const sortedCycles = [...cyclesSet].sort((a, b) => a - b);
|
|
91
|
-
const isLatestStalledForThisReq =
|
|
128
|
+
const isLatestStalledForThisReq = diagnosticStalled && latestDiagReqs.includes(req);
|
|
92
129
|
if (isLatestStalledForThisReq) {
|
|
93
130
|
if (!signals.some((s) => s.code === PROGRESS_SIGNAL.REPEATED_FAILURE_WITH_SAME_DIAGNOSIS && s.requirement === req)) {
|
|
94
131
|
signals.push({
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { realpath } from "node:fs/promises";
|
|
3
|
+
|
|
4
|
+
import { resolveTarget } from "./filesystem.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Canonical project-root resolution for integrations. Applies exactly the
|
|
8
|
+
* same semantics as the CLI target resolver — the path must exist, must be a
|
|
9
|
+
* real directory (not a symlink), and is returned as an absolute path.
|
|
10
|
+
*
|
|
11
|
+
* Symlinked roots are rejected so that every transport agrees on whether a
|
|
12
|
+
* given project path is acceptable; use the resolved real directory instead.
|
|
13
|
+
*/
|
|
14
|
+
export async function resolveForgeLoopProjectRoot(projectPath, { cwd = process.cwd() } = {}) {
|
|
15
|
+
const target = await resolveTarget(cwd, projectPath);
|
|
16
|
+
return realpath(target);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function defaultIntegrationProjectPath() {
|
|
20
|
+
return path.resolve(".");
|
|
21
|
+
}
|
|
@@ -33,6 +33,67 @@ export function protocolInfo({ packageVersion = null } = {}) {
|
|
|
33
33
|
readsSchemaVersions: schemaVersions,
|
|
34
34
|
writesSchemaVersions: schemaVersions,
|
|
35
35
|
compatibility: SCHEMA_COMPATIBILITY_POLICY,
|
|
36
|
+
features: {
|
|
37
|
+
taskClaimRecovery: {
|
|
38
|
+
version: 1,
|
|
39
|
+
durableRecoveryState: true,
|
|
40
|
+
explicitResume: true,
|
|
41
|
+
validatedClaimProjection: true,
|
|
42
|
+
},
|
|
43
|
+
integrationApi: {
|
|
44
|
+
version: 1,
|
|
45
|
+
structuredCommandRuntime: true,
|
|
46
|
+
canonicalResources: true,
|
|
47
|
+
},
|
|
48
|
+
executionHistory: {
|
|
49
|
+
version: 1,
|
|
50
|
+
supported: true,
|
|
51
|
+
schemaVersion: 1,
|
|
52
|
+
},
|
|
53
|
+
structuredTrace: {
|
|
54
|
+
version: 1,
|
|
55
|
+
supported: true,
|
|
56
|
+
schemaVersion: 1,
|
|
57
|
+
},
|
|
58
|
+
taskInspection: {
|
|
59
|
+
version: 1,
|
|
60
|
+
supported: true,
|
|
61
|
+
schemaVersion: 1,
|
|
62
|
+
},
|
|
63
|
+
reflection: {
|
|
64
|
+
version: 1,
|
|
65
|
+
supported: true,
|
|
66
|
+
schemaVersion: 1,
|
|
67
|
+
},
|
|
68
|
+
diagnostics: {
|
|
69
|
+
legacyDiagnosis: true,
|
|
70
|
+
structuredDiagnosticCase: true,
|
|
71
|
+
multifactorContributors: true,
|
|
72
|
+
hypothesisDisposition: true,
|
|
73
|
+
interventionLedger: true,
|
|
74
|
+
informationGainV2: true,
|
|
75
|
+
strategyOscillationDetection: true,
|
|
76
|
+
},
|
|
77
|
+
durableActions: {
|
|
78
|
+
version: 1,
|
|
79
|
+
supported: true,
|
|
80
|
+
states: ["PROPOSED", "AUTHORIZED", "STARTED", "COMMITTED", "VERIFIED", "FAILED", "COMMIT_UNKNOWN", "CANCELLED"],
|
|
81
|
+
reconciliation: true,
|
|
82
|
+
exactlyOnce: false,
|
|
83
|
+
},
|
|
84
|
+
capabilityPolicy: { version: 1, supported: true, decisions: ["ALLOW", "DENY", "REQUIRE_AUTHORITY", "REQUIRE_APPROVAL"] },
|
|
85
|
+
durableApprovals: { version: 1, supported: true, fingerprintBound: true, hostAttestationMinting: false },
|
|
86
|
+
trajectoryMetrics: { version: 1, supported: true, usageUnknownWhenUnreported: true, overallScore: false },
|
|
87
|
+
trajectoryEvaluation: { version: 1, supported: true, requiresReferenceScenario: true, source: "PROJECT_LOCAL_REFERENCE" },
|
|
88
|
+
observabilityStability: {
|
|
89
|
+
executionHistory: "stable",
|
|
90
|
+
structuredTrace: "stable",
|
|
91
|
+
taskInspection: "stable",
|
|
92
|
+
reflection: "stable",
|
|
93
|
+
informationGainV2: "stable",
|
|
94
|
+
strategyOscillationDetection: "stable",
|
|
95
|
+
},
|
|
96
|
+
},
|
|
36
97
|
lifecycle: { phases: WORK_PHASES, transitions: WORK_TRANSITIONS },
|
|
37
98
|
guides: Object.values(GUIDE_REGISTRY),
|
|
38
99
|
commands: Object.values(CLI_COMMAND_DEFINITIONS).map(({ name, category, mutation, description }) => ({ name, category, mutation, description })),
|