@cassiomc1/forgeloop 1.5.0 → 1.6.1
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/CONTRACT_COVERAGE.md +1 -0
- package/DOCS_INDEX.md +20 -10
- package/EXECUTION_STATE.md +20 -0
- package/LOOP_ENGINEERING.md +103 -1
- package/LOOP_SYSTEM_DESIGN.md +32 -0
- package/PROTOCOL_INTEGRATION.md +90 -0
- package/QUALITY_SCORECARD.md +2 -0
- package/README.md +47 -9
- package/THIRD_PARTY_NOTICES.md +15 -0
- package/THREAT_MODEL.md +41 -1
- package/docs/ARTIFACT_REFERENCE.md +159 -0
- package/docs/CLI_REFERENCE.md +294 -3
- package/docs/DIAGNOSTIC_MODEL.md +181 -0
- package/docs/DOCUMENTATION_GUIDE.md +22 -13
- package/docs/EXECUTION_TRACE.md +76 -0
- package/docs/MCP.md +34 -1
- package/docs/RECIPES.md +67 -0
- package/docs/RELEASE_CHECKLIST_1_6_1.md +121 -0
- package/docs/TROUBLESHOOTING.md +133 -2
- 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 +13 -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/execution.schema.json +15 -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/trajectory-evaluation.schema.json +64 -0
- package/schemas/trajectory-scenario.schema.json +42 -0
- package/src/cli.js +94 -0
- 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/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 +49 -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/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/run-check.js +1 -0
- package/src/commands/trace.js +34 -0
- package/src/commands/validate-protocol.js +21 -13
- package/src/core/action-authorization.js +106 -0
- package/src/core/action-constants.js +86 -0
- package/src/core/action-execution.js +106 -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 +48 -0
- package/src/core/audit.js +25 -0
- package/src/core/bundles.js +15 -0
- package/src/core/capability-policy.js +226 -0
- package/src/core/cli-command-definitions.js +210 -1
- package/src/core/command-executors.js +171 -15
- package/src/core/command-runtime.js +12 -1
- package/src/core/completion-artifacts.js +71 -14
- 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 +363 -0
- package/src/core/events.js +41 -1
- package/src/core/execution-prerequisites.js +4 -1
- package/src/core/execution.js +29 -188
- package/src/core/failure-signature.js +70 -0
- package/src/core/failure-surface.js +57 -0
- 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 +105 -7
- package/src/core/integration-invocation-policy.js +55 -0
- package/src/core/integration-resources.js +51 -0
- package/src/core/next-action-model.js +35 -1
- package/src/core/next-action.js +459 -3
- package/src/core/phase.js +40 -21
- 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 +256 -0
- package/src/core/progress.js +41 -4
- package/src/core/protocol-info.js +56 -0
- package/src/core/protocol.js +14 -0
- package/src/core/receipt.js +1 -0
- package/src/core/reconcile-closure.js +15 -12
- package/src/core/reflection.js +305 -0
- package/src/core/resumability.js +57 -3
- package/src/core/runtime-context.js +19 -1
- package/src/core/schema-validation.js +8 -0
- package/src/core/strategy-analysis.js +97 -0
- package/src/core/task-paths.js +28 -0
- package/src/core/task-snapshot.js +53 -0
- package/src/core/templates.js +8 -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 +8 -0
- package/src/core/verification-execution.js +257 -0
- package/src/core/work-state.js +10 -5
- package/src/integration.js +8 -0
- package/docs/assets/forgeloop-flow.svg +0 -1
- package/docs/forgeloop-flow.mmd +0 -51
|
@@ -3,6 +3,7 @@ import { ARTIFACT_REGISTRY } from "./artifact-registry.js";
|
|
|
3
3
|
import { PUBLIC_ERROR_REGISTRY } from "./error-codes.js";
|
|
4
4
|
import { GUIDE_REGISTRY } from "./guide-registry.js";
|
|
5
5
|
import { PROTOCOL_VERSION, WORK_PHASES, WORK_TRANSITIONS } from "./protocol.js";
|
|
6
|
+
import { VERIFICATION_ISOLATION_MODES } from "./verification-execution.js";
|
|
6
7
|
|
|
7
8
|
export const SCHEMA_COMPATIBILITY_POLICY = Object.freeze({
|
|
8
9
|
protocolVersion: PROTOCOL_VERSION,
|
|
@@ -45,6 +46,61 @@ export function protocolInfo({ packageVersion = null } = {}) {
|
|
|
45
46
|
structuredCommandRuntime: true,
|
|
46
47
|
canonicalResources: true,
|
|
47
48
|
},
|
|
49
|
+
executionHistory: {
|
|
50
|
+
version: 1,
|
|
51
|
+
supported: true,
|
|
52
|
+
schemaVersion: 1,
|
|
53
|
+
},
|
|
54
|
+
verificationExecutionIsolation: {
|
|
55
|
+
version: 1,
|
|
56
|
+
supported: true,
|
|
57
|
+
adapter: true,
|
|
58
|
+
modes: [...VERIFICATION_ISOLATION_MODES],
|
|
59
|
+
protocolProjectRootSeparateFromExecutionCwd: true,
|
|
60
|
+
},
|
|
61
|
+
structuredTrace: {
|
|
62
|
+
version: 1,
|
|
63
|
+
supported: true,
|
|
64
|
+
schemaVersion: 1,
|
|
65
|
+
},
|
|
66
|
+
taskInspection: {
|
|
67
|
+
version: 1,
|
|
68
|
+
supported: true,
|
|
69
|
+
schemaVersion: 1,
|
|
70
|
+
},
|
|
71
|
+
reflection: {
|
|
72
|
+
version: 1,
|
|
73
|
+
supported: true,
|
|
74
|
+
schemaVersion: 1,
|
|
75
|
+
},
|
|
76
|
+
diagnostics: {
|
|
77
|
+
legacyDiagnosis: true,
|
|
78
|
+
structuredDiagnosticCase: true,
|
|
79
|
+
multifactorContributors: true,
|
|
80
|
+
hypothesisDisposition: true,
|
|
81
|
+
interventionLedger: true,
|
|
82
|
+
informationGainV2: true,
|
|
83
|
+
strategyOscillationDetection: true,
|
|
84
|
+
},
|
|
85
|
+
durableActions: {
|
|
86
|
+
version: 1,
|
|
87
|
+
supported: true,
|
|
88
|
+
states: ["PROPOSED", "AUTHORIZED", "STARTED", "COMMITTED", "VERIFIED", "FAILED", "COMMIT_UNKNOWN", "CANCELLED"],
|
|
89
|
+
reconciliation: true,
|
|
90
|
+
exactlyOnce: false,
|
|
91
|
+
},
|
|
92
|
+
capabilityPolicy: { version: 1, supported: true, decisions: ["ALLOW", "DENY", "REQUIRE_AUTHORITY", "REQUIRE_APPROVAL"] },
|
|
93
|
+
durableApprovals: { version: 1, supported: true, fingerprintBound: true, hostAttestationMinting: false },
|
|
94
|
+
trajectoryMetrics: { version: 1, supported: true, usageUnknownWhenUnreported: true, overallScore: false },
|
|
95
|
+
trajectoryEvaluation: { version: 1, supported: true, requiresReferenceScenario: true, source: "PROJECT_LOCAL_REFERENCE" },
|
|
96
|
+
observabilityStability: {
|
|
97
|
+
executionHistory: "stable",
|
|
98
|
+
structuredTrace: "stable",
|
|
99
|
+
taskInspection: "stable",
|
|
100
|
+
reflection: "stable",
|
|
101
|
+
informationGainV2: "stable",
|
|
102
|
+
strategyOscillationDetection: "stable",
|
|
103
|
+
},
|
|
48
104
|
},
|
|
49
105
|
lifecycle: { phases: WORK_PHASES, transitions: WORK_TRANSITIONS },
|
|
50
106
|
guides: Object.values(GUIDE_REGISTRY),
|
package/src/core/protocol.js
CHANGED
|
@@ -99,6 +99,20 @@ export const FAILURE_CODES = Object.freeze([
|
|
|
99
99
|
"E_DIAGNOSIS_CYCLE_MISMATCH",
|
|
100
100
|
"E_DIAGNOSIS_NO_NEW_INFORMATION",
|
|
101
101
|
"E_PROGRESS_STALLED",
|
|
102
|
+
"E_DIAGNOSTIC_CASE_INVALID",
|
|
103
|
+
"E_DIAGNOSTIC_CASE_CYCLE_MISMATCH",
|
|
104
|
+
"E_DIAGNOSTIC_CASE_EVIDENCE_INVALID",
|
|
105
|
+
"E_OBSERVATION_INVALID",
|
|
106
|
+
"E_CONTRIBUTOR_INVALID",
|
|
107
|
+
"E_CONTRIBUTOR_REFERENCE_INVALID",
|
|
108
|
+
"E_HYPOTHESIS_INVALID",
|
|
109
|
+
"E_HYPOTHESIS_SETTLEMENT_MISSING",
|
|
110
|
+
"E_HYPOTHESIS_DISPOSITION_INVALID",
|
|
111
|
+
"E_HYPOTHESIS_DISPOSITION_EVIDENCE_INVALID",
|
|
112
|
+
"E_INTERVENTION_INVALID",
|
|
113
|
+
"E_INTERVENTION_HYPOTHESIS_MISSING",
|
|
114
|
+
"E_INTERVENTION_REFERENCE_INVALID",
|
|
115
|
+
"E_STRATEGY_OSCILLATION",
|
|
102
116
|
"E_DECISION_CRITERION_INVALID",
|
|
103
117
|
"E_DECISION_NOT_UNRESOLVED",
|
|
104
118
|
]);
|
package/src/core/receipt.js
CHANGED
|
@@ -158,6 +158,7 @@ export async function createReceipt(input, packageRoot, options = {}) {
|
|
|
158
158
|
selectedGuides: [...(input.selectedGuides ?? [])],
|
|
159
159
|
changedPaths: [...(input.changedPaths ?? [])],
|
|
160
160
|
checks: [...(input.checks ?? [])],
|
|
161
|
+
...(input.actions !== undefined ? { actions: structuredClone(input.actions) } : {}),
|
|
161
162
|
evidence: [...(input.evidence ?? [])],
|
|
162
163
|
...(input.evidenceCoverage !== undefined ? { evidenceCoverage: [...input.evidenceCoverage] } : {}),
|
|
163
164
|
review: input.review ?? { status: "not-run", independent: false },
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readContract } from "./contract.js";
|
|
2
2
|
import { canonicalFingerprint, readJsonArtifact, writeJsonArtifact } from "./artifacts.js";
|
|
3
|
-
import { appendProtocolEvent, validateEventLedger
|
|
3
|
+
import { appendProtocolEvent, validateEventLedger } from "./events.js";
|
|
4
|
+
import { authorizeCompletionRecoveryOrRebind } from "./completion-recovery-rebind.js";
|
|
4
5
|
import { runCommandExecution } from "./execution.js";
|
|
5
6
|
import { createReceipt } from "./receipt.js";
|
|
6
7
|
import { currentRepositoryFingerprint } from "./repository.js";
|
|
@@ -68,7 +69,7 @@ export async function runReconcileClosure({
|
|
|
68
69
|
const eventsRel = taskArtifactPath(taskId, "events");
|
|
69
70
|
const receiptRel = taskArtifactPath(taskId, "receipt");
|
|
70
71
|
|
|
71
|
-
|
|
72
|
+
let state = await readWorkState(target, { packageRoot, taskId });
|
|
72
73
|
if (!state) {
|
|
73
74
|
throw reconcileError("E_RECONCILE_PHASE_INVALID", "Cannot reconcile without work state", [stateRel]);
|
|
74
75
|
}
|
|
@@ -80,22 +81,24 @@ export async function runReconcileClosure({
|
|
|
80
81
|
);
|
|
81
82
|
}
|
|
82
83
|
if (state.phase === "REVIEWING") {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const first = recoveryAuth.errors?.[0] ?? {};
|
|
84
|
+
const recovery = await authorizeCompletionRecoveryOrRebind({
|
|
85
|
+
target,
|
|
86
|
+
packageRoot,
|
|
87
|
+
taskId,
|
|
88
|
+
authorityContext,
|
|
89
|
+
runtimeContext,
|
|
90
|
+
});
|
|
91
|
+
if (!recovery.recoveryAuth.authorized) {
|
|
92
|
+
const first = recovery.recoveryAuth.errors?.[0] ?? {};
|
|
93
93
|
throw reconcileError(
|
|
94
94
|
first.code ?? "E_COMPLETION_RECOVERY_UNAUTHORIZED",
|
|
95
95
|
`REVIEWING reconciliation requires authorized completion recovery: ${first.message ?? "unauthorized"}`,
|
|
96
96
|
[stateRel, receiptRel],
|
|
97
97
|
);
|
|
98
98
|
}
|
|
99
|
+
if (recovery.rebound) {
|
|
100
|
+
state = recovery.state;
|
|
101
|
+
}
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
const freshness = await classifyLoadedWorkState({ target, state, contractFile: contractRel });
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { buildTaskTrace } from "./trace.js";
|
|
2
|
+
import { readEvents } from "./events.js";
|
|
3
|
+
import { findTaskById } from "./task-discovery.js";
|
|
4
|
+
import { projectHypothesisStates } from "./hypothesis-projection.js";
|
|
5
|
+
import {
|
|
6
|
+
buildInformationGainProjection,
|
|
7
|
+
evaluateStructuredDiagnosticStall,
|
|
8
|
+
} from "./information-gain-projection.js";
|
|
9
|
+
import { computeFailureSignature } from "./failure-signature.js";
|
|
10
|
+
import {
|
|
11
|
+
computeStrategyFingerprints as computeStrategyFingerprintsImpl,
|
|
12
|
+
detectOscillation as detectOscillationImpl,
|
|
13
|
+
evaluateInterventionEffectiveness as evaluateInterventionEffectivenessImpl,
|
|
14
|
+
} from "./strategy-analysis.js";
|
|
15
|
+
|
|
16
|
+
export const REFLECTION_STATUS = Object.freeze({
|
|
17
|
+
ADVANCING: "ADVANCING",
|
|
18
|
+
WATCH: "WATCH",
|
|
19
|
+
STALLED: "STALLED",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
export function summarizeHypotheses(trace) {
|
|
24
|
+
const summary = { created: 0, supported: 0, weakened: 0, falsified: 0, superseded: 0, unresolved: 0, open: 0 };
|
|
25
|
+
|
|
26
|
+
const pseudoEvents = [
|
|
27
|
+
...trace.diagnostics.cases.map((diagnosticCase) => ({
|
|
28
|
+
seq: diagnosticCase.sequence,
|
|
29
|
+
event: "DIAGNOSTIC_CASE_RECORDED",
|
|
30
|
+
details: diagnosticCase,
|
|
31
|
+
})),
|
|
32
|
+
...trace.diagnostics.legacyDiagnoses.map((legacy) => ({
|
|
33
|
+
seq: legacy.sequence,
|
|
34
|
+
event: "DIAGNOSIS_RECORDED",
|
|
35
|
+
details: legacy,
|
|
36
|
+
})),
|
|
37
|
+
...trace.diagnostics.dispositions.map((disposition) => ({
|
|
38
|
+
seq: disposition.sequence,
|
|
39
|
+
event: "HYPOTHESIS_DISPOSITION_RECORDED",
|
|
40
|
+
details: disposition,
|
|
41
|
+
})),
|
|
42
|
+
];
|
|
43
|
+
const projection = projectHypothesisStates(pseudoEvents);
|
|
44
|
+
|
|
45
|
+
for (const hypothesis of projection.hypotheses) {
|
|
46
|
+
if (hypothesis.id === "h-legacy" && hypothesis.sourceEventSeq !== null) {
|
|
47
|
+
summary.created += 1;
|
|
48
|
+
} else if (hypothesis.id !== "h-legacy") {
|
|
49
|
+
summary.created += 1;
|
|
50
|
+
}
|
|
51
|
+
const status = hypothesis.currentStatus.toLowerCase();
|
|
52
|
+
if (summary[status] !== undefined) summary[status] += 1;
|
|
53
|
+
}
|
|
54
|
+
summary.open = projection.openHypotheses.length;
|
|
55
|
+
return summary;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function detectOscillation(strategies) {
|
|
59
|
+
return detectOscillationImpl(strategies);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function evaluateInterventionEffectiveness(trace, surfacesByCycle) {
|
|
63
|
+
return evaluateInterventionEffectivenessImpl(trace, surfacesByCycle);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function failedRequirementSurfacesFromEvents(events = [], state = null) {
|
|
67
|
+
const byCycle = new Map();
|
|
68
|
+
const record = (cycle, requirement) => {
|
|
69
|
+
if (!requirement) return;
|
|
70
|
+
if (!byCycle.has(cycle)) byCycle.set(cycle, new Set());
|
|
71
|
+
byCycle.get(cycle).add(requirement);
|
|
72
|
+
};
|
|
73
|
+
for (const event of events) {
|
|
74
|
+
if (event.event !== "VERIFICATION_RECORDED") continue;
|
|
75
|
+
const d = event.details ?? {};
|
|
76
|
+
if (d.status === "failed" || d.status === "blocked") {
|
|
77
|
+
record(d.verificationCycle ?? 1, d.requirement ?? d.id ?? d.checkId);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
for (const check of state?.checks ?? []) {
|
|
81
|
+
if (check.status !== "failed" && check.status !== "blocked") continue;
|
|
82
|
+
record(check.details?.verificationCycle ?? state?.verificationCycle ?? 1, check.requirement ?? check.id ?? check.checkId);
|
|
83
|
+
}
|
|
84
|
+
return byCycle;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function signatureSetsByCycle(trace) {
|
|
88
|
+
const byCycle = new Map();
|
|
89
|
+
for (const entry of trace.failureSignatures) {
|
|
90
|
+
for (const cycle of entry.cycles ?? []) {
|
|
91
|
+
if (!byCycle.has(cycle)) byCycle.set(cycle, new Set());
|
|
92
|
+
byCycle.get(cycle).add(entry.signature);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return byCycle;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function deriveDiagnosticContext(events = [], state = null) {
|
|
99
|
+
const cycle = state?.verificationCycle ?? null;
|
|
100
|
+
const taskEvents = events.filter((event) => !state?.taskId || event.taskId === state.taskId);
|
|
101
|
+
|
|
102
|
+
// Canonical failure-signature hashes for the active verification cycle.
|
|
103
|
+
const activeFailureSignatures = [...new Set(
|
|
104
|
+
taskEvents
|
|
105
|
+
.filter((event) => event.event === "VERIFICATION_RECORDED"
|
|
106
|
+
&& ["failed", "blocked"].includes(event.details?.status)
|
|
107
|
+
&& (cycle === null || event.details?.verificationCycle === cycle))
|
|
108
|
+
.map((event) => {
|
|
109
|
+
const d = event.details;
|
|
110
|
+
return computeFailureSignature({
|
|
111
|
+
requirement: d.requirement ?? d.id ?? d.checkId,
|
|
112
|
+
status: d.status,
|
|
113
|
+
exitCode: Number.isInteger(d.exitCode) ? d.exitCode : null,
|
|
114
|
+
failureToken: typeof d.failureToken === "string" && d.failureToken
|
|
115
|
+
? d.failureToken
|
|
116
|
+
: (typeof d.details?.failureToken === "string" ? d.details.failureToken : null),
|
|
117
|
+
});
|
|
118
|
+
}),
|
|
119
|
+
)].sort();
|
|
120
|
+
|
|
121
|
+
const activeFailedRequirements = [...new Set(
|
|
122
|
+
[
|
|
123
|
+
...taskEvents
|
|
124
|
+
.filter((event) => event.event === "VERIFICATION_RECORDED"
|
|
125
|
+
&& ["failed", "blocked"].includes(event.details?.status)
|
|
126
|
+
&& (cycle === null || event.details?.verificationCycle === cycle))
|
|
127
|
+
.map((event) => event.details?.requirement ?? event.details?.id ?? event.details?.checkId),
|
|
128
|
+
...((state?.checks ?? []))
|
|
129
|
+
.filter((check) => ["failed", "blocked"].includes(check.status)
|
|
130
|
+
&& (cycle === null || check.details?.verificationCycle === cycle))
|
|
131
|
+
.map((check) => check.requirement ?? check.id ?? check.checkId),
|
|
132
|
+
].filter(Boolean),
|
|
133
|
+
)].sort();
|
|
134
|
+
|
|
135
|
+
const projection = projectHypothesisStates(taskEvents);
|
|
136
|
+
const openHypotheses = [...projection.openHypotheses].sort();
|
|
137
|
+
|
|
138
|
+
const interventions = taskEvents.filter((event) => event.event === "INTERVENTION_RECORDED");
|
|
139
|
+
const latestIntervention = interventions.at(-1)?.details?.intervention?.id ?? null;
|
|
140
|
+
|
|
141
|
+
// doNotRepeat requires semantic repetition AND at least two completed
|
|
142
|
+
// post-intervention verification cycles AND unchanged failure surface.
|
|
143
|
+
const surfacesByCycle = failedRequirementSurfacesFromEvents(taskEvents, state);
|
|
144
|
+
const completedCycles = [...new Set(
|
|
145
|
+
taskEvents.filter((event) => event.event === "VERIFICATION_STARTED")
|
|
146
|
+
.map((event) => event.details?.verificationCycle)
|
|
147
|
+
.filter(Number.isInteger),
|
|
148
|
+
)].sort((a, b) => a - b);
|
|
149
|
+
|
|
150
|
+
const fingerprintGroups = new Map();
|
|
151
|
+
for (const event of interventions) {
|
|
152
|
+
const fingerprint = event.details?.interventionSemanticFingerprint;
|
|
153
|
+
if (!fingerprint) continue;
|
|
154
|
+
if (!fingerprintGroups.has(fingerprint)) fingerprintGroups.set(fingerprint, []);
|
|
155
|
+
fingerprintGroups.get(fingerprint).push(event);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const doNotRepeat = [];
|
|
159
|
+
for (const [fingerprint, group] of fingerprintGroups.entries()) {
|
|
160
|
+
if (group.length < 2) continue;
|
|
161
|
+
const lastInterventionCycle = Math.max(...group.map((event) => event.details?.verificationCycle ?? 1));
|
|
162
|
+
const postCycles = completedCycles.filter((completed) => completed > lastInterventionCycle);
|
|
163
|
+
if (postCycles.length < 2) continue;
|
|
164
|
+
const firstSurface = [...(surfacesByCycle.get(lastInterventionCycle) ?? [])].sort();
|
|
165
|
+
const latestSurface = [...(surfacesByCycle.get(postCycles.at(-1)) ?? [])].sort();
|
|
166
|
+
if (JSON.stringify(firstSurface) === JSON.stringify(latestSurface)) {
|
|
167
|
+
doNotRepeat.push(fingerprint);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
doNotRepeat.sort();
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
activeFailureSignatures,
|
|
174
|
+
activeFailedRequirements,
|
|
175
|
+
openHypotheses,
|
|
176
|
+
latestIntervention,
|
|
177
|
+
nextExperiment: null,
|
|
178
|
+
doNotRepeat,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export async function buildTaskReflection({ target, packageRoot, taskId = null } = {}) {
|
|
183
|
+
const trace = await buildTaskTrace({ target, packageRoot, taskId });
|
|
184
|
+
// Modern tasks own a scoped ledger; legacy singleton callers still pass a
|
|
185
|
+
// taskId that must be used only as a filter over the canonical ledger.
|
|
186
|
+
const task = taskId ? await findTaskById(target, taskId, packageRoot) : null;
|
|
187
|
+
const rawEvents = await readEvents(target, packageRoot, task ? { taskId } : {});
|
|
188
|
+
|
|
189
|
+
// Authoritative surfaces come from the canonical trace projection.
|
|
190
|
+
const surfaceEntries = {};
|
|
191
|
+
for (const entry of trace.failureSurfaces) {
|
|
192
|
+
surfaceEntries[entry.verificationCycle] = { surface: entry.surface, signatures: [] };
|
|
193
|
+
}
|
|
194
|
+
const signatureSets = signatureSetsByCycle(trace);
|
|
195
|
+
for (const [cycle, signatures] of signatureSets.entries()) {
|
|
196
|
+
if (!surfaceEntries[cycle]) surfaceEntries[cycle] = { surface: [], signatures: [] };
|
|
197
|
+
surfaceEntries[cycle].signatures = [...signatures].sort();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const cycles = [...new Set([
|
|
201
|
+
...trace.failureSurfaces.map((entry) => entry.verificationCycle),
|
|
202
|
+
...trace.diagnostics.cases.map((diagnosticCase) => diagnosticCase.verificationCycle),
|
|
203
|
+
])].sort((a, b) => a - b);
|
|
204
|
+
|
|
205
|
+
const strategies = computeStrategyFingerprintsImpl(trace);
|
|
206
|
+
const oscillation = detectOscillation(strategies);
|
|
207
|
+
|
|
208
|
+
const signals = [];
|
|
209
|
+
let status = REFLECTION_STATUS.ADVANCING;
|
|
210
|
+
|
|
211
|
+
const evaluatedInterventions = evaluateInterventionEffectiveness(trace, surfaceEntries);
|
|
212
|
+
const repeatedNonInformative = evaluatedInterventions
|
|
213
|
+
.filter((intervention) => intervention.effectiveness === "NON_INFORMATIVE");
|
|
214
|
+
if (oscillation.detected) {
|
|
215
|
+
signals.push("OSCILLATING_STRATEGY");
|
|
216
|
+
status = REFLECTION_STATUS.WATCH;
|
|
217
|
+
}
|
|
218
|
+
if (repeatedNonInformative.length >= 2) {
|
|
219
|
+
signals.push("REPEATED_INTERVENTION");
|
|
220
|
+
status = REFLECTION_STATUS.WATCH;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const lastTwoSameStrategy = strategies.length >= 2
|
|
224
|
+
&& strategies.at(-1).strategyFingerprint === strategies.at(-2).strategyFingerprint
|
|
225
|
+
&& JSON.stringify(surfaceEntries[strategies.at(-1)?.verificationCycle]?.signatures ?? [])
|
|
226
|
+
=== JSON.stringify(surfaceEntries[strategies.at(-2)?.verificationCycle]?.signatures ?? []);
|
|
227
|
+
if (lastTwoSameStrategy && !signals.includes("OSCILLATING_STRATEGY")) {
|
|
228
|
+
status = status === REFLECTION_STATUS.ADVANCING ? REFLECTION_STATUS.WATCH : status;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Information Gain v2 truth comes fully from the authoritative cycle
|
|
232
|
+
// analysis projection. Consumers must not redefine gain semantics.
|
|
233
|
+
const gainProjection = buildInformationGainProjection(rawEvents, taskId ?? null);
|
|
234
|
+
const cyclesWithoutEffectiveGain = gainProjection
|
|
235
|
+
.filter((entry) => !entry.effectiveGain)
|
|
236
|
+
.map((entry) => entry.verificationCycle);
|
|
237
|
+
|
|
238
|
+
// One canonical structured-stall truth shared with phase and progress.
|
|
239
|
+
const stallAnalysis = evaluateStructuredDiagnosticStall(gainProjection);
|
|
240
|
+
const latestNoGain = Boolean(stallAnalysis.stalled);
|
|
241
|
+
let consecutiveNoGainCycles = 0;
|
|
242
|
+
for (let i = gainProjection.length - 1; i >= 0 && gainProjection[i].effectiveGain === false; i--) {
|
|
243
|
+
consecutiveNoGainCycles += 1;
|
|
244
|
+
}
|
|
245
|
+
const previousEntry = gainProjection.length >= 2 ? gainProjection.at(-2) : null;
|
|
246
|
+
const currentEntry = gainProjection.at(-1) ?? null;
|
|
247
|
+
if (latestNoGain) {
|
|
248
|
+
status = REFLECTION_STATUS.STALLED;
|
|
249
|
+
if (!signals.includes("NO_EFFECTIVE_INFORMATION_GAIN")) signals.push("NO_EFFECTIVE_INFORMATION_GAIN");
|
|
250
|
+
}
|
|
251
|
+
if ((trace.actions?.ambiguous ?? 0) > 0) {
|
|
252
|
+
signals.push("EXTERNAL_ACTION_RECONCILIATION_REQUIRED");
|
|
253
|
+
status = status === REFLECTION_STATUS.ADVANCING ? REFLECTION_STATUS.WATCH : status;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const recommendedProtocolAction = (trace.actions?.ambiguous ?? 0) > 0
|
|
257
|
+
? "RECONCILE_EXTERNAL_ACTION"
|
|
258
|
+
: oscillation.detected
|
|
259
|
+
? "INTRODUCE_NEW_OBSERVATION"
|
|
260
|
+
: (status === REFLECTION_STATUS.STALLED ? "REQUIRE_NEW_DIAGNOSTIC_INFORMATION" : "CONTINUE");
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
schemaVersion: 1,
|
|
264
|
+
command: "reflect",
|
|
265
|
+
taskId: trace.task.id,
|
|
266
|
+
taskPhase: trace.task.phase,
|
|
267
|
+
integrityValid: trace.integrity.valid,
|
|
268
|
+
snapshotConsistent: trace.snapshot.consistent,
|
|
269
|
+
status,
|
|
270
|
+
verificationCycles: cycles.length,
|
|
271
|
+
failureSurfaces: trace.failureSurfaces,
|
|
272
|
+
hypotheses: summarizeHypotheses(trace),
|
|
273
|
+
interventions: (() => {
|
|
274
|
+
const evaluated = evaluatedInterventions;
|
|
275
|
+
return {
|
|
276
|
+
count: evaluated.length,
|
|
277
|
+
informative: evaluated.filter((intervention) => ["INFORMATIVE", "IMPROVED"].includes(intervention.effectiveness)).length,
|
|
278
|
+
nonInformative: evaluated.filter((intervention) => intervention.effectiveness === "NON_INFORMATIVE").length,
|
|
279
|
+
details: evaluated,
|
|
280
|
+
};
|
|
281
|
+
})(),
|
|
282
|
+
informationGain: {
|
|
283
|
+
cyclesWithCases: trace.diagnostics.cases.map((diagnosticCase) => diagnosticCase.verificationCycle),
|
|
284
|
+
cyclesWithoutEffectiveGain,
|
|
285
|
+
cycles: gainProjection.map(({ dimensions, ...rest }) => ({ ...rest, dimensions })),
|
|
286
|
+
},
|
|
287
|
+
strategies,
|
|
288
|
+
oscillation,
|
|
289
|
+
signals,
|
|
290
|
+
stallAnalysis: {
|
|
291
|
+
latestNoGain,
|
|
292
|
+
consecutiveNoGainCycles,
|
|
293
|
+
sameStrategyAsPrevious: Boolean(previousEntry && currentEntry
|
|
294
|
+
&& previousEntry.evidence.strategyFingerprint === currentEntry.evidence.strategyFingerprint),
|
|
295
|
+
sameFailureSurfaceAsPrevious: Boolean(previousEntry && currentEntry
|
|
296
|
+
&& JSON.stringify(previousEntry.evidence.failureSurface) === JSON.stringify(currentEntry.evidence.failureSurface)),
|
|
297
|
+
sameFailureSignaturesAsPrevious: Boolean(previousEntry && currentEntry
|
|
298
|
+
&& JSON.stringify(previousEntry.evidence.failureSignatures) === JSON.stringify(currentEntry.evidence.failureSignatures)),
|
|
299
|
+
},
|
|
300
|
+
recommendedProtocolAction,
|
|
301
|
+
actions: trace.actions,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export { computeStrategyFingerprintsImpl as computeStrategyFingerprints };
|
package/src/core/resumability.js
CHANGED
|
@@ -1,22 +1,76 @@
|
|
|
1
1
|
import { currentRepositoryFingerprint } from "./repository.js";
|
|
2
|
+
import { validateEventLedger } from "./events.js";
|
|
2
3
|
import { createWorkState, initializeWorkState, readWorkState, mutateWorkState } from "./work-state.js";
|
|
3
4
|
|
|
4
5
|
const DEFAULT_PENDING_STEPS = ["planning", "implementation", "verification"];
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Resume phase derived from the highest lifecycle milestone already recorded in
|
|
9
|
+
* a validated ledger. Recreating a checkpoint at ROUTED for a task whose ledger
|
|
10
|
+
* already passed EXECUTION_STARTED would make every subsequent advance append a
|
|
11
|
+
* duplicate non-repeatable milestone and invalidate the ledger, so restoration
|
|
12
|
+
* must resume at the phase the recorded chronology supports.
|
|
13
|
+
*/
|
|
14
|
+
const RESUME_PHASE_BY_MILESTONE = Object.freeze({
|
|
15
|
+
PLAN_RECORDED: "PLANNED",
|
|
16
|
+
EXECUTION_STARTED: "EXECUTING",
|
|
17
|
+
VERIFICATION_STARTED: "VERIFYING",
|
|
18
|
+
VERIFICATION_RECORDED: "VERIFYING",
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
async function deriveResumePhaseFromLedger(target, packageRoot, taskId) {
|
|
22
|
+
let ledger;
|
|
23
|
+
try {
|
|
24
|
+
ledger = await validateEventLedger(target, packageRoot, { taskId });
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
if (!ledger?.valid) return null;
|
|
29
|
+
const scoped = (ledger.events ?? []).filter((event) => event.taskId === taskId);
|
|
30
|
+
if (scoped.some((event) => event.event === "COMPLETION_VALIDATED")) return null;
|
|
31
|
+
const positions = RESUME_PHASE_BY_MILESTONE;
|
|
32
|
+
let derived = null;
|
|
33
|
+
for (const event of scoped) {
|
|
34
|
+
const phase = positions[event.event];
|
|
35
|
+
if (!phase) continue;
|
|
36
|
+
if (!derived) {
|
|
37
|
+
derived = phase;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (phase === "VERIFYING") derived = "VERIFYING";
|
|
41
|
+
}
|
|
42
|
+
return derived;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function resumeSteps(phase) {
|
|
46
|
+
if (phase === "EXECUTING" || phase === "VERIFYING") {
|
|
47
|
+
return {
|
|
48
|
+
completedSteps: ["contract", "route", "planning", "implementation"],
|
|
49
|
+
pendingSteps: ["verification"],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
completedSteps: ["contract", "route"],
|
|
54
|
+
pendingSteps: [...DEFAULT_PENDING_STEPS],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
6
58
|
export async function ensureResumableState({ target, packageRoot, contract, route, taskId, statePath }) {
|
|
7
59
|
if (!contract || !route) return null;
|
|
8
60
|
const existing = await readWorkState(target, { packageRoot, taskId, statePath });
|
|
9
61
|
if (existing) return existing;
|
|
10
62
|
|
|
63
|
+
const resumedPhase = await deriveResumePhaseFromLedger(target, packageRoot, taskId) ?? "ROUTED";
|
|
64
|
+
const steps = resumeSteps(resumedPhase);
|
|
11
65
|
const state = createWorkState({
|
|
12
66
|
taskId: contract.value.taskId,
|
|
13
67
|
contractFingerprint: contract.fingerprint,
|
|
14
68
|
routeFingerprint: route.fingerprint,
|
|
15
69
|
repositoryFingerprint: await currentRepositoryFingerprint(target),
|
|
16
|
-
phase:
|
|
70
|
+
phase: resumedPhase,
|
|
17
71
|
selectedGuides: route.value.guides,
|
|
18
|
-
completedSteps:
|
|
19
|
-
pendingSteps:
|
|
72
|
+
completedSteps: steps.completedSteps,
|
|
73
|
+
pendingSteps: steps.pendingSteps,
|
|
20
74
|
checks: [],
|
|
21
75
|
failures: [],
|
|
22
76
|
blockers: [],
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
E_VERIFICATION_ISOLATION_UNAVAILABLE,
|
|
3
|
+
isVerificationExecutionAdapter,
|
|
4
|
+
normalizeVerificationExecutionPolicy,
|
|
5
|
+
} from "./verification-execution.js";
|
|
6
|
+
|
|
1
7
|
export const AUTHORITY_TRUST_MODES = Object.freeze(["NONE", "HOST_ATTESTED"]);
|
|
2
8
|
|
|
3
9
|
const AUTHORITY_CONTEXT_FIELDS = Object.freeze([
|
|
@@ -76,5 +82,17 @@ export function resolveAuthorityContext(options = {}) {
|
|
|
76
82
|
|
|
77
83
|
export function createForgeLoopContext(options = {}) {
|
|
78
84
|
const authorityContext = createAuthorityContext(options);
|
|
79
|
-
|
|
85
|
+
const context = { authorityContext };
|
|
86
|
+
if (options?.verificationExecutionAdapter !== undefined) {
|
|
87
|
+
if (!isVerificationExecutionAdapter(options.verificationExecutionAdapter)) {
|
|
88
|
+
const error = new Error("Verification execution adapter is unavailable or invalid");
|
|
89
|
+
error.code = E_VERIFICATION_ISOLATION_UNAVAILABLE;
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
context.verificationExecutionAdapter = options.verificationExecutionAdapter;
|
|
93
|
+
}
|
|
94
|
+
if (options?.verificationExecutionPolicy !== undefined) {
|
|
95
|
+
context.verificationExecutionPolicy = normalizeVerificationExecutionPolicy(options.verificationExecutionPolicy);
|
|
96
|
+
}
|
|
97
|
+
return Object.freeze(context);
|
|
80
98
|
}
|
|
@@ -34,6 +34,14 @@ export const SHIPPED_SCHEMA_NAMES = Object.freeze([
|
|
|
34
34
|
"authority",
|
|
35
35
|
"task-descriptor",
|
|
36
36
|
"task-recovery",
|
|
37
|
+
"diagnostic-case",
|
|
38
|
+
"intervention",
|
|
39
|
+
"hypothesis-disposition",
|
|
40
|
+
"action",
|
|
41
|
+
"approval",
|
|
42
|
+
"capability-policy",
|
|
43
|
+
"trajectory-evaluation",
|
|
44
|
+
"trajectory-scenario",
|
|
37
45
|
]);
|
|
38
46
|
|
|
39
47
|
export class SchemaValidationError extends Error {
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { canonicalFingerprint } from "./artifacts.js";
|
|
2
|
+
import { compareFailureSurface } from "./failure-surface.js";
|
|
3
|
+
|
|
4
|
+
export function computeStrategyFingerprints(trace) {
|
|
5
|
+
const byCycle = new Map();
|
|
6
|
+
const add = (cycle, component, fingerprint) => {
|
|
7
|
+
if (!byCycle.has(cycle)) byCycle.set(cycle, { hypotheses: new Set(), contributors: new Set(), interventions: new Set() });
|
|
8
|
+
byCycle.get(cycle)[component].add(fingerprint);
|
|
9
|
+
};
|
|
10
|
+
for (const diagnosticCase of trace.diagnostics.cases) {
|
|
11
|
+
for (const hypothesis of diagnosticCase.hypotheses ?? []) {
|
|
12
|
+
add(diagnosticCase.verificationCycle, "hypotheses", `${hypothesis.statement}`.trim().toLowerCase());
|
|
13
|
+
}
|
|
14
|
+
for (const contributor of diagnosticCase.contributors ?? []) {
|
|
15
|
+
add(diagnosticCase.verificationCycle, "contributors", `${contributor.statement}`.trim().toLowerCase());
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
for (const legacy of trace.diagnostics.legacyDiagnoses) {
|
|
19
|
+
add(legacy.verificationCycle, "hypotheses", `${legacy.hypothesis}`.trim().toLowerCase());
|
|
20
|
+
}
|
|
21
|
+
for (const intervention of trace.diagnostics.interventions) {
|
|
22
|
+
add(intervention.verificationCycle, "interventions", intervention.interventionSemanticFingerprint ?? `${intervention.intervention?.statement ?? ""}`.trim().toLowerCase());
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return [...byCycle.entries()]
|
|
26
|
+
.sort(([a], [b]) => a - b)
|
|
27
|
+
.map(([cycle, components]) => ({
|
|
28
|
+
verificationCycle: cycle,
|
|
29
|
+
strategyFingerprint: JSON.stringify([
|
|
30
|
+
[...components.hypotheses].sort(),
|
|
31
|
+
[...components.contributors].sort(),
|
|
32
|
+
[...components.interventions].sort(),
|
|
33
|
+
]),
|
|
34
|
+
components: {
|
|
35
|
+
hypotheses: [...components.hypotheses].sort(),
|
|
36
|
+
contributors: [...components.contributors].sort(),
|
|
37
|
+
interventions: [...components.interventions].sort(),
|
|
38
|
+
},
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function detectOscillation(strategies) {
|
|
43
|
+
const fingerprints = strategies.map((strategy) => strategy.strategyFingerprint);
|
|
44
|
+
const patterns = [];
|
|
45
|
+
for (let i = 2; i < fingerprints.length; i++) {
|
|
46
|
+
if (fingerprints[i] === fingerprints[i - 2] && fingerprints[i] !== fingerprints[i - 1]) {
|
|
47
|
+
patterns.push({ kind: "A_B_A", cycles: [strategies[i - 2].verificationCycle, strategies[i - 1].verificationCycle, strategies[i].verificationCycle] });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
for (let i = 3; i < fingerprints.length; i++) {
|
|
51
|
+
if (fingerprints[i] === fingerprints[i - 2]
|
|
52
|
+
&& fingerprints[i - 1] === fingerprints[i - 3]
|
|
53
|
+
&& fingerprints[i] !== fingerprints[i - 1]) {
|
|
54
|
+
patterns.push({ kind: "A_B_A_B", cycles: [strategies[i - 3].verificationCycle, strategies[i - 2].verificationCycle, strategies[i - 1].verificationCycle, strategies[i].verificationCycle] });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const detected = patterns.length > 0;
|
|
58
|
+
return { detected, patterns };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function evaluateInterventionEffectiveness(trace, surfacesByCycle) {
|
|
62
|
+
const interventions = [];
|
|
63
|
+
const list = trace.diagnostics.interventions;
|
|
64
|
+
for (const entry of list) {
|
|
65
|
+
const cycle = entry.verificationCycle;
|
|
66
|
+
const laterVerificationCycles = Object.keys(surfacesByCycle).map(Number).filter((value) => value > cycle);
|
|
67
|
+
const nextCycle = laterVerificationCycles.length > 0 ? Math.min(...laterVerificationCycles) : null;
|
|
68
|
+
|
|
69
|
+
let effectiveness = "PENDING";
|
|
70
|
+
if (nextCycle !== null) {
|
|
71
|
+
const previousSurface = surfacesByCycle[cycle]?.surface ?? [];
|
|
72
|
+
const currentSurface = surfacesByCycle[nextCycle]?.surface ?? [];
|
|
73
|
+
const comparison = compareFailureSurface(previousSurface, currentSurface);
|
|
74
|
+
const signatureChanged = JSON.stringify(surfacesByCycle[cycle]?.signatures ?? []) !== JSON.stringify(surfacesByCycle[nextCycle]?.signatures ?? []);
|
|
75
|
+
if (comparison.direction === "REDUCED") effectiveness = "IMPROVED";
|
|
76
|
+
else if (comparison.direction === "EXPANDED") effectiveness = "REGRESSED";
|
|
77
|
+
else if (signatureChanged || comparison.changed) effectiveness = "INFORMATIVE";
|
|
78
|
+
else effectiveness = "NON_INFORMATIVE";
|
|
79
|
+
}
|
|
80
|
+
interventions.push({
|
|
81
|
+
id: entry.intervention?.id ?? null,
|
|
82
|
+
sequence: entry.sequence,
|
|
83
|
+
verificationCycle: cycle,
|
|
84
|
+
kind: entry.intervention?.kind ?? null,
|
|
85
|
+
reversible: entry.intervention?.reversible ?? null,
|
|
86
|
+
semanticFingerprint: entry.interventionSemanticFingerprint ?? null,
|
|
87
|
+
effectiveness,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return interventions;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
export function interventionSemanticFingerprintOf(entry) {
|
|
95
|
+
return entry?.interventionSemanticFingerprint
|
|
96
|
+
?? (entry?.intervention ? canonicalFingerprint(entry.intervention) : null);
|
|
97
|
+
}
|