openplanr 1.13.2 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -4
- package/dist/cli/commands/artifact.d.ts.map +1 -1
- package/dist/cli/commands/artifact.js +17 -6
- package/dist/cli/commands/artifact.js.map +1 -1
- package/dist/cli/commands/operate.d.ts +3 -0
- package/dist/cli/commands/operate.d.ts.map +1 -0
- package/dist/cli/commands/operate.js +568 -0
- package/dist/cli/commands/operate.js.map +1 -0
- package/dist/cli/index.js +42 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/services/ai-service.d.ts +9 -2
- package/dist/services/ai-service.d.ts.map +1 -1
- package/dist/services/ai-service.js +14 -6
- package/dist/services/ai-service.js.map +1 -1
- package/dist/services/artifact-pipeline-service.d.ts +1 -0
- package/dist/services/artifact-pipeline-service.d.ts.map +1 -1
- package/dist/services/artifact-pipeline-service.js.map +1 -1
- package/dist/services/delta-apply-service.js +1 -1
- package/dist/services/delta-apply-service.js.map +1 -1
- package/dist/services/operate/advisors.d.ts +219 -0
- package/dist/services/operate/advisors.d.ts.map +1 -0
- package/dist/services/operate/advisors.js +745 -0
- package/dist/services/operate/advisors.js.map +1 -0
- package/dist/services/operate/artifact-route-generation.d.ts +111 -0
- package/dist/services/operate/artifact-route-generation.d.ts.map +1 -0
- package/dist/services/operate/artifact-route-generation.js +459 -0
- package/dist/services/operate/artifact-route-generation.js.map +1 -0
- package/dist/services/operate/artifacts.d.ts +26 -0
- package/dist/services/operate/artifacts.d.ts.map +1 -0
- package/dist/services/operate/artifacts.js +92 -0
- package/dist/services/operate/artifacts.js.map +1 -0
- package/dist/services/operate/canonical.d.ts +18 -0
- package/dist/services/operate/canonical.d.ts.map +1 -0
- package/dist/services/operate/canonical.js +157 -0
- package/dist/services/operate/canonical.js.map +1 -0
- package/dist/services/operate/config.d.ts +56 -0
- package/dist/services/operate/config.d.ts.map +1 -0
- package/dist/services/operate/config.js +644 -0
- package/dist/services/operate/config.js.map +1 -0
- package/dist/services/operate/consolidation.d.ts +18 -0
- package/dist/services/operate/consolidation.d.ts.map +1 -0
- package/dist/services/operate/consolidation.js +539 -0
- package/dist/services/operate/consolidation.js.map +1 -0
- package/dist/services/operate/doctor.d.ts +12 -0
- package/dist/services/operate/doctor.d.ts.map +1 -0
- package/dist/services/operate/doctor.js +307 -0
- package/dist/services/operate/doctor.js.map +1 -0
- package/dist/services/operate/engine.d.ts +53 -0
- package/dist/services/operate/engine.d.ts.map +1 -0
- package/dist/services/operate/engine.js +1097 -0
- package/dist/services/operate/engine.js.map +1 -0
- package/dist/services/operate/event-store.d.ts +57 -0
- package/dist/services/operate/event-store.d.ts.map +1 -0
- package/dist/services/operate/event-store.js +232 -0
- package/dist/services/operate/event-store.js.map +1 -0
- package/dist/services/operate/evidence-cache.d.ts +17 -0
- package/dist/services/operate/evidence-cache.d.ts.map +1 -0
- package/dist/services/operate/evidence-cache.js +96 -0
- package/dist/services/operate/evidence-cache.js.map +1 -0
- package/dist/services/operate/evidence-import.d.ts +40 -0
- package/dist/services/operate/evidence-import.d.ts.map +1 -0
- package/dist/services/operate/evidence-import.js +340 -0
- package/dist/services/operate/evidence-import.js.map +1 -0
- package/dist/services/operate/evidence-readiness.d.ts +8 -0
- package/dist/services/operate/evidence-readiness.d.ts.map +1 -0
- package/dist/services/operate/evidence-readiness.js +78 -0
- package/dist/services/operate/evidence-readiness.js.map +1 -0
- package/dist/services/operate/evidence.d.ts +71 -0
- package/dist/services/operate/evidence.d.ts.map +1 -0
- package/dist/services/operate/evidence.js +1022 -0
- package/dist/services/operate/evidence.js.map +1 -0
- package/dist/services/operate/ids.d.ts +7 -0
- package/dist/services/operate/ids.d.ts.map +1 -0
- package/dist/services/operate/ids.js +23 -0
- package/dist/services/operate/ids.js.map +1 -0
- package/dist/services/operate/index.d.ts +14 -0
- package/dist/services/operate/index.d.ts.map +1 -0
- package/dist/services/operate/index.js +881 -0
- package/dist/services/operate/index.js.map +1 -0
- package/dist/services/operate/journal.d.ts +34 -0
- package/dist/services/operate/journal.d.ts.map +1 -0
- package/dist/services/operate/journal.js +229 -0
- package/dist/services/operate/journal.js.map +1 -0
- package/dist/services/operate/legacy-import-service.d.ts +58 -0
- package/dist/services/operate/legacy-import-service.d.ts.map +1 -0
- package/dist/services/operate/legacy-import-service.js +922 -0
- package/dist/services/operate/legacy-import-service.js.map +1 -0
- package/dist/services/operate/lifecycle.d.ts +88 -0
- package/dist/services/operate/lifecycle.d.ts.map +1 -0
- package/dist/services/operate/lifecycle.js +556 -0
- package/dist/services/operate/lifecycle.js.map +1 -0
- package/dist/services/operate/lock-service.d.ts +37 -0
- package/dist/services/operate/lock-service.d.ts.map +1 -0
- package/dist/services/operate/lock-service.js +208 -0
- package/dist/services/operate/lock-service.js.map +1 -0
- package/dist/services/operate/maintenance.d.ts +38 -0
- package/dist/services/operate/maintenance.d.ts.map +1 -0
- package/dist/services/operate/maintenance.js +699 -0
- package/dist/services/operate/maintenance.js.map +1 -0
- package/dist/services/operate/outcomes.d.ts +48 -0
- package/dist/services/operate/outcomes.d.ts.map +1 -0
- package/dist/services/operate/outcomes.js +312 -0
- package/dist/services/operate/outcomes.js.map +1 -0
- package/dist/services/operate/pipeline-handoff.d.ts +138 -0
- package/dist/services/operate/pipeline-handoff.d.ts.map +1 -0
- package/dist/services/operate/pipeline-handoff.js +175 -0
- package/dist/services/operate/pipeline-handoff.js.map +1 -0
- package/dist/services/operate/projection-persistence.d.ts +55 -0
- package/dist/services/operate/projection-persistence.d.ts.map +1 -0
- package/dist/services/operate/projection-persistence.js +393 -0
- package/dist/services/operate/projection-persistence.js.map +1 -0
- package/dist/services/operate/projection.d.ts +4 -0
- package/dist/services/operate/projection.d.ts.map +1 -0
- package/dist/services/operate/projection.js +128 -0
- package/dist/services/operate/projection.js.map +1 -0
- package/dist/services/operate/protocol.d.ts +60 -0
- package/dist/services/operate/protocol.d.ts.map +1 -0
- package/dist/services/operate/protocol.js +62 -0
- package/dist/services/operate/protocol.js.map +1 -0
- package/dist/services/operate/read-only-providers.d.ts +38 -0
- package/dist/services/operate/read-only-providers.d.ts.map +1 -0
- package/dist/services/operate/read-only-providers.js +286 -0
- package/dist/services/operate/read-only-providers.js.map +1 -0
- package/dist/services/operate/redaction.d.ts +47 -0
- package/dist/services/operate/redaction.d.ts.map +1 -0
- package/dist/services/operate/redaction.js +279 -0
- package/dist/services/operate/redaction.js.map +1 -0
- package/dist/services/operate/reducer.d.ts +10 -0
- package/dist/services/operate/reducer.d.ts.map +1 -0
- package/dist/services/operate/reducer.js +11 -0
- package/dist/services/operate/reducer.js.map +1 -0
- package/dist/services/operate/routes.d.ts +49 -0
- package/dist/services/operate/routes.d.ts.map +1 -0
- package/dist/services/operate/routes.js +996 -0
- package/dist/services/operate/routes.js.map +1 -0
- package/dist/services/operate/shipment-observer.d.ts +14 -0
- package/dist/services/operate/shipment-observer.d.ts.map +1 -0
- package/dist/services/operate/shipment-observer.js +321 -0
- package/dist/services/operate/shipment-observer.js.map +1 -0
- package/dist/services/operate/stalled-item-service.d.ts +18 -0
- package/dist/services/operate/stalled-item-service.d.ts.map +1 -0
- package/dist/services/operate/stalled-item-service.js +121 -0
- package/dist/services/operate/stalled-item-service.js.map +1 -0
- package/dist/services/operate/subprocess-env.d.ts +4 -0
- package/dist/services/operate/subprocess-env.d.ts.map +1 -0
- package/dist/services/operate/subprocess-env.js +45 -0
- package/dist/services/operate/subprocess-env.js.map +1 -0
- package/dist/services/operate/types.d.ts +729 -0
- package/dist/services/operate/types.d.ts.map +1 -0
- package/dist/services/operate/types.js +13 -0
- package/dist/services/operate/types.js.map +1 -0
- package/dist/services/operate/workspace.d.ts +59 -0
- package/dist/services/operate/workspace.d.ts.map +1 -0
- package/dist/services/operate/workspace.js +316 -0
- package/dist/services/operate/workspace.js.map +1 -0
- package/dist/services/runtime-manager-service.d.ts.map +1 -1
- package/dist/services/runtime-manager-service.js +71 -2
- package/dist/services/runtime-manager-service.js.map +1 -1
- package/package.json +10 -4
|
@@ -0,0 +1,1097 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, readFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { loadConfig } from '../config-service.js';
|
|
5
|
+
import { advisorFailureGaps, buildAdvisorOperatingContext, configuredAdvisorProviderPolicy, createConfiguredStructuredAdapter, createOfflineAdvisorAdapter, dispatchOperatingAdvisors, ensureOperatingProviderConsent, } from './advisors.js';
|
|
6
|
+
import { canonicalDigest, canonicalize } from './canonical.js';
|
|
7
|
+
import { operatingProjectKey, validateOperatingConfiguration } from './config.js';
|
|
8
|
+
import { consolidateOperatingResults } from './consolidation.js';
|
|
9
|
+
import { OperatingEventStore } from './event-store.js';
|
|
10
|
+
import { collectOperatingEvidence, evidenceProjectionSources, unqualifiedCommercialEvidenceGaps, } from './evidence.js';
|
|
11
|
+
import { OperatingEvidenceCache } from './evidence-cache.js';
|
|
12
|
+
import { evaluateEvidenceReadiness } from './evidence-readiness.js';
|
|
13
|
+
import { nextCycleId } from './ids.js';
|
|
14
|
+
import { applyJournalTransaction, prepareJournalTransaction, readJournal, rollbackJournalTransaction, } from './journal.js';
|
|
15
|
+
import { reconcileOperatingDecisionDeadlines } from './lifecycle.js';
|
|
16
|
+
import { withOperatingLock } from './lock-service.js';
|
|
17
|
+
import { reconcileOperatingOutcomeFiles } from './outcomes.js';
|
|
18
|
+
import { persistOperatingProjections } from './projection-persistence.js';
|
|
19
|
+
import { assertOperatingArtifact, loadOperatingProtocol } from './protocol.js';
|
|
20
|
+
import { maximumSensitivity } from './redaction.js';
|
|
21
|
+
import { createOperatingRoutePlan, nextOperatingSpecOrdinal } from './routes.js';
|
|
22
|
+
import { OPERATE_PROTOCOL_VERSION, OPERATE_SCHEMA_VERSION, OperateError, } from './types.js';
|
|
23
|
+
import { assertOperatingProject, refreshOperatingWorkspaceManifest, resolveOperatingPaths, } from './workspace.js';
|
|
24
|
+
const PRODUCT_VERSION = '1.14.0';
|
|
25
|
+
function normalizeFocus(focus) {
|
|
26
|
+
const supported = new Set(['strategy', 'product', 'growth', 'operations', 'technology', 'all']);
|
|
27
|
+
const normalized = [...new Set((focus ?? []).map(String))]
|
|
28
|
+
.filter((value) => supported.has(value))
|
|
29
|
+
.sort();
|
|
30
|
+
return normalized.length > 0 ? normalized : ['all'];
|
|
31
|
+
}
|
|
32
|
+
function loadJson(target) {
|
|
33
|
+
return readFile(target, 'utf8').then((raw) => JSON.parse(raw));
|
|
34
|
+
}
|
|
35
|
+
async function snapshotWorkspace(projectRoot, localRoot) {
|
|
36
|
+
return refreshOperatingWorkspaceManifest(projectRoot, { localRoot });
|
|
37
|
+
}
|
|
38
|
+
async function readPreferences(projectRoot, localRoot) {
|
|
39
|
+
const paths = resolveOperatingPaths(projectRoot, { localRoot });
|
|
40
|
+
return loadJson(path.join(paths.localRoot, 'preferences.json')).catch(() => ({
|
|
41
|
+
runtime: 'auto',
|
|
42
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
43
|
+
sensitivityCeiling: 'internal',
|
|
44
|
+
evidenceTtlMs: 7 * 24 * 60 * 60 * 1_000,
|
|
45
|
+
enabledSources: [],
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
function activeCycle(state) {
|
|
49
|
+
return (state.cycles.find((cycle) => !['closed', 'cancelled', 'failed'].includes(cycle.state)) ?? null);
|
|
50
|
+
}
|
|
51
|
+
function maximumOrdinal(records, prefix) {
|
|
52
|
+
return records.reduce((maximum, record) => {
|
|
53
|
+
const match = String(record.id ?? '').match(new RegExp(`^${prefix}-(\\d+)$`));
|
|
54
|
+
return Math.max(maximum, match ? Number(match[1]) : 0);
|
|
55
|
+
}, 0);
|
|
56
|
+
}
|
|
57
|
+
async function remapReadinessGaps(readiness, state) {
|
|
58
|
+
let ordinal = maximumOrdinal(state.dataGaps, 'GAP');
|
|
59
|
+
const remapped = {
|
|
60
|
+
...readiness,
|
|
61
|
+
roles: readiness.roles.map((role) => ({
|
|
62
|
+
...role,
|
|
63
|
+
gapId: role.gapId
|
|
64
|
+
? (state.dataGaps.find((gap) => gap.cycleId === readiness.cycleId &&
|
|
65
|
+
!['closed', 'superseded'].includes(String(gap.status)) &&
|
|
66
|
+
Array.isArray(gap.affectedRoles) &&
|
|
67
|
+
gap.affectedRoles.includes(role.roleId))?.id ?? `GAP-${String(++ordinal).padStart(3, '0')}`)
|
|
68
|
+
: null,
|
|
69
|
+
})),
|
|
70
|
+
};
|
|
71
|
+
return assertOperatingArtifact('operating-evidence-readiness', remapped);
|
|
72
|
+
}
|
|
73
|
+
function readinessGaps(readiness, decisionOwner, now) {
|
|
74
|
+
return readiness.roles
|
|
75
|
+
.filter((role) => role.gapId)
|
|
76
|
+
.map((role) => ({
|
|
77
|
+
kind: 'operating-data-gap',
|
|
78
|
+
schemaVersion: OPERATE_SCHEMA_VERSION,
|
|
79
|
+
protocolVersion: OPERATE_PROTOCOL_VERSION,
|
|
80
|
+
id: role.gapId,
|
|
81
|
+
cycleId: readiness.cycleId,
|
|
82
|
+
question: `What evidence is required for ${role.roleId}?`,
|
|
83
|
+
reason: role.missingEvidence.join('; ') || 'Minimum evidence was not available.',
|
|
84
|
+
unblocks: [],
|
|
85
|
+
affectedRoles: [role.roleId],
|
|
86
|
+
status: 'open',
|
|
87
|
+
owner: decisionOwner,
|
|
88
|
+
evidenceRefs: [...role.evidenceRefs],
|
|
89
|
+
createdAt: now,
|
|
90
|
+
updatedAt: now,
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
function requiredOpenEvidenceRefs(state) {
|
|
94
|
+
const records = [
|
|
95
|
+
...state.findings.filter((finding) => !['done', 'rejected', 'superseded'].includes(String(finding.status))),
|
|
96
|
+
...state.decisions.filter((decision) => !['closed', 'superseded'].includes(String(decision.status))),
|
|
97
|
+
...state.outcomes.filter((outcome) => ['pending', 'observing', 'inconclusive'].includes(String(outcome.status))),
|
|
98
|
+
];
|
|
99
|
+
return [
|
|
100
|
+
...new Set(records.flatMap((record) => Array.isArray(record.evidenceRefs)
|
|
101
|
+
? record.evidenceRefs.filter((reference) => typeof reference === 'string')
|
|
102
|
+
: [])),
|
|
103
|
+
].sort();
|
|
104
|
+
}
|
|
105
|
+
export function buildChairEvidence(evidence, results, now) {
|
|
106
|
+
const evidenceById = new Map(evidence.items.map((item) => [item.id, item]));
|
|
107
|
+
const synthetic = results.flatMap((result) => {
|
|
108
|
+
const evidenceRefs = [
|
|
109
|
+
...new Set(result.proposals.flatMap((proposal) => proposal.evidenceRefs)),
|
|
110
|
+
].sort();
|
|
111
|
+
const missingRefs = evidenceRefs.filter((reference) => !evidenceById.has(reference));
|
|
112
|
+
if (missingRefs.length > 0) {
|
|
113
|
+
throw new OperateError('E_OPERATE_EVIDENCE_REJECTED', `${result.roleId} advisor result cites unavailable evidence: ${missingRefs.join(', ')}.`);
|
|
114
|
+
}
|
|
115
|
+
const sensitivity = maximumSensitivity([
|
|
116
|
+
'internal',
|
|
117
|
+
...evidenceRefs.map((reference) => evidenceById.get(reference)?.sensitivity ?? 'public'),
|
|
118
|
+
]);
|
|
119
|
+
const bodies = [
|
|
120
|
+
{
|
|
121
|
+
id: `EVD-advisor-results-${result.roleId}-context`,
|
|
122
|
+
location: `${result.roleId}/context`,
|
|
123
|
+
body: {
|
|
124
|
+
roleId: result.roleId,
|
|
125
|
+
outcome: result.outcome,
|
|
126
|
+
gaps: [...new Set(result.gaps)].sort(),
|
|
127
|
+
conflicts: [...new Set(result.conflicts)].sort(),
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
...result.proposals.map((proposal) => ({
|
|
131
|
+
id: `EVD-advisor-results-${result.roleId}-${proposal.proposalKey}`,
|
|
132
|
+
location: `${result.roleId}/proposals/${proposal.proposalKey}`,
|
|
133
|
+
body: {
|
|
134
|
+
roleId: result.roleId,
|
|
135
|
+
proposal: structuredClone(proposal),
|
|
136
|
+
},
|
|
137
|
+
})),
|
|
138
|
+
];
|
|
139
|
+
return bodies.map(({ id, location, body }) => ({
|
|
140
|
+
id,
|
|
141
|
+
source: 'advisor-results',
|
|
142
|
+
location,
|
|
143
|
+
digest: canonicalDigest(body),
|
|
144
|
+
collectedAt: now,
|
|
145
|
+
observedFrom: now,
|
|
146
|
+
observedTo: now,
|
|
147
|
+
freshness: 'fresh',
|
|
148
|
+
sensitivity,
|
|
149
|
+
claimTypes: ['verified-advisor-result'],
|
|
150
|
+
summary: canonicalize(body),
|
|
151
|
+
}));
|
|
152
|
+
});
|
|
153
|
+
return {
|
|
154
|
+
...evidence,
|
|
155
|
+
items: [...evidence.items, ...synthetic],
|
|
156
|
+
fingerprint: canonicalDigest({
|
|
157
|
+
source: evidence.fingerprint,
|
|
158
|
+
advisors: synthetic
|
|
159
|
+
.map(({ id, digest, sensitivity }) => ({ id, digest, sensitivity }))
|
|
160
|
+
.sort((left, right) => left.id.localeCompare(right.id)),
|
|
161
|
+
}),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
const SENSITIVE_ROUTE_LANGUAGE = /\b(?:payment|billing|security|privacy|legal|tenant|isolation|destructive|delete|purge|credential|secret)\b/i;
|
|
165
|
+
function containsSensitiveRouteProposal(results) {
|
|
166
|
+
return results.some((result) => result.proposals.some((proposal) => SENSITIVE_ROUTE_LANGUAGE.test(`${proposal.type} ${proposal.title} ${proposal.problem} ${proposal.proposal}`)));
|
|
167
|
+
}
|
|
168
|
+
async function createCycleManifest(input) {
|
|
169
|
+
return assertOperatingArtifact('operating-cycle-manifest', {
|
|
170
|
+
kind: 'operating-cycle-manifest',
|
|
171
|
+
schemaVersion: OPERATE_SCHEMA_VERSION,
|
|
172
|
+
protocolVersion: OPERATE_PROTOCOL_VERSION,
|
|
173
|
+
id: input.id,
|
|
174
|
+
state: input.state,
|
|
175
|
+
depth: input.depth,
|
|
176
|
+
focus: input.focus,
|
|
177
|
+
inputDigest: input.inputDigest,
|
|
178
|
+
enabledRoles: [...input.enabledRoles],
|
|
179
|
+
enabledProviders: [...input.enabledProviders],
|
|
180
|
+
createdAt: input.now,
|
|
181
|
+
updatedAt: input.now,
|
|
182
|
+
producer: {
|
|
183
|
+
product: 'openplanr',
|
|
184
|
+
version: PRODUCT_VERSION,
|
|
185
|
+
runtime: input.runtime,
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
async function appendLocked(store, lock, head, input) {
|
|
190
|
+
const event = await store.append({ ...input, expectedHead: head.hash });
|
|
191
|
+
const next = { sequence: event.sequence, hash: event.eventHash };
|
|
192
|
+
await lock.advanceEventHead(head, next);
|
|
193
|
+
return { event, head: next };
|
|
194
|
+
}
|
|
195
|
+
function remapConsolidation(state, findings, parked, decisions, gaps) {
|
|
196
|
+
let findingOrdinal = maximumOrdinal(state.findings, 'FND');
|
|
197
|
+
let decisionOrdinal = maximumOrdinal(state.decisions, 'DEC');
|
|
198
|
+
let gapOrdinal = maximumOrdinal(state.dataGaps, 'GAP');
|
|
199
|
+
const findingIds = new Map();
|
|
200
|
+
for (const finding of [...findings, ...parked]) {
|
|
201
|
+
findingIds.set(finding.id, `FND-${String(++findingOrdinal).padStart(3, '0')}`);
|
|
202
|
+
}
|
|
203
|
+
const remapFinding = (finding) => ({
|
|
204
|
+
...finding,
|
|
205
|
+
id: findingIds.get(finding.id),
|
|
206
|
+
dependsOn: finding.dependsOn.map((dependency) => findingIds.get(dependency) ?? dependency),
|
|
207
|
+
});
|
|
208
|
+
return {
|
|
209
|
+
findings: findings.map(remapFinding),
|
|
210
|
+
parked: parked.map(remapFinding),
|
|
211
|
+
decisions: decisions.map((decision) => ({
|
|
212
|
+
...decision,
|
|
213
|
+
id: `DEC-${String(++decisionOrdinal).padStart(3, '0')}`,
|
|
214
|
+
})),
|
|
215
|
+
gaps: gaps.map((gap) => ({
|
|
216
|
+
...gap,
|
|
217
|
+
id: `GAP-${String(++gapOrdinal).padStart(3, '0')}`,
|
|
218
|
+
})),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function findingIdentity(value) {
|
|
222
|
+
const finding = value;
|
|
223
|
+
if (typeof finding.fingerprint === 'string' &&
|
|
224
|
+
/^sha256:[a-f0-9]{64}$/.test(finding.fingerprint)) {
|
|
225
|
+
return finding.fingerprint;
|
|
226
|
+
}
|
|
227
|
+
return canonicalDigest({
|
|
228
|
+
title: String(finding.title ?? '')
|
|
229
|
+
.trim()
|
|
230
|
+
.toLowerCase(),
|
|
231
|
+
lane: String(finding.lane ?? ''),
|
|
232
|
+
owner: String(finding.owner ?? ''),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
function suppressRepeatedFindings(state, findings, cycleId) {
|
|
236
|
+
const priorByIdentity = new Map(state.findings
|
|
237
|
+
.filter((finding) => finding.cycleId !== cycleId)
|
|
238
|
+
.map((finding) => [findingIdentity(finding), finding]));
|
|
239
|
+
let suppressed = 0;
|
|
240
|
+
const retained = [];
|
|
241
|
+
for (const finding of findings) {
|
|
242
|
+
const prior = priorByIdentity.get(findingIdentity(finding));
|
|
243
|
+
if (!prior) {
|
|
244
|
+
retained.push(finding);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
const priorEvidence = Array.isArray(prior.evidenceRefs)
|
|
248
|
+
? [...prior.evidenceRefs].sort()
|
|
249
|
+
: [];
|
|
250
|
+
const currentEvidence = [...finding.evidenceRefs].sort();
|
|
251
|
+
if (canonicalDigest(priorEvidence) === canonicalDigest(currentEvidence)) {
|
|
252
|
+
suppressed += 1;
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
retained.push({
|
|
256
|
+
...finding,
|
|
257
|
+
dependsOn: [...new Set([...finding.dependsOn, String(prior.id)])].sort(),
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
return { findings: retained, suppressed };
|
|
261
|
+
}
|
|
262
|
+
function committedCycleFindings(events, cycleId) {
|
|
263
|
+
return events
|
|
264
|
+
.filter((event) => event.type === 'finding.proposed' &&
|
|
265
|
+
event.cycleId === cycleId &&
|
|
266
|
+
event.payload.record &&
|
|
267
|
+
typeof event.payload.record === 'object')
|
|
268
|
+
.map((event) => structuredClone(event.payload.record));
|
|
269
|
+
}
|
|
270
|
+
function reconcileCycleFindings(events, cycleId, findings) {
|
|
271
|
+
const existing = committedCycleFindings(events, cycleId);
|
|
272
|
+
const existingByIdentity = new Map(existing.map((finding) => [findingIdentity(finding), finding]));
|
|
273
|
+
return findings.map((finding) => {
|
|
274
|
+
const prior = existingByIdentity.get(findingIdentity(finding));
|
|
275
|
+
if (!prior)
|
|
276
|
+
return finding;
|
|
277
|
+
return canonicalDigest([...prior.evidenceRefs].sort()) ===
|
|
278
|
+
canonicalDigest([...finding.evidenceRefs].sort())
|
|
279
|
+
? prior
|
|
280
|
+
: finding;
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
function recordContent(value) {
|
|
284
|
+
return structuredClone(value);
|
|
285
|
+
}
|
|
286
|
+
async function persistedCycleEvidence(store, events, cycleId) {
|
|
287
|
+
const event = [...events]
|
|
288
|
+
.reverse()
|
|
289
|
+
.find((candidate) => candidate.cycleId === cycleId &&
|
|
290
|
+
candidate.type === 'evidence.collected' &&
|
|
291
|
+
typeof candidate.payload.recordDigest === 'string');
|
|
292
|
+
if (!event)
|
|
293
|
+
return null;
|
|
294
|
+
const record = await store.readRecord(event.payload.recordDigest);
|
|
295
|
+
if (record.recordType !== 'evidence-metadata') {
|
|
296
|
+
throw new OperateError('E_OPERATE_STATE_INVALID', `Cycle ${cycleId} evidence event references a ${record.recordType} record.`);
|
|
297
|
+
}
|
|
298
|
+
return assertOperatingArtifact('operating-evidence', record.content);
|
|
299
|
+
}
|
|
300
|
+
async function persistedCycleRoleResults(store, events, cycleId) {
|
|
301
|
+
const byRole = new Map();
|
|
302
|
+
for (const event of events) {
|
|
303
|
+
if (event.cycleId !== cycleId ||
|
|
304
|
+
event.type !== 'advisory.recorded' ||
|
|
305
|
+
typeof event.payload.recordDigest !== 'string') {
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
const record = await store.readRecord(event.payload.recordDigest);
|
|
309
|
+
if (record.recordType !== 'advisor-result') {
|
|
310
|
+
throw new OperateError('E_OPERATE_STATE_INVALID', `Cycle ${cycleId} advisory event references a ${record.recordType} record.`);
|
|
311
|
+
}
|
|
312
|
+
const result = await assertOperatingArtifact('operating-role-result', record.content);
|
|
313
|
+
(await loadOperatingProtocol()).validateOperatingRoleResultDigest(result);
|
|
314
|
+
if (result.cycleId !== cycleId) {
|
|
315
|
+
throw new OperateError('E_OPERATE_STATE_INVALID', `Advisor result ${result.roleId} belongs to another cycle.`);
|
|
316
|
+
}
|
|
317
|
+
byRole.set(result.roleId, result);
|
|
318
|
+
}
|
|
319
|
+
return [...byRole.values()].sort((left, right) => left.roleId.localeCompare(right.roleId));
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Full evidence → advisors → deterministic consolidation lifecycle. All
|
|
323
|
+
* mutations are serialized under the process-identity lease; preview and
|
|
324
|
+
* dry-run never enter the mutating branch.
|
|
325
|
+
*/
|
|
326
|
+
export async function runOperatingCycle(input) {
|
|
327
|
+
const projectRoot = await assertOperatingProject(input.projectRoot);
|
|
328
|
+
const config = await validateOperatingConfiguration(projectRoot);
|
|
329
|
+
const paths = resolveOperatingPaths(projectRoot, { localRoot: input.localRoot });
|
|
330
|
+
const initializedWorkspace = await loadJson(paths.workspace);
|
|
331
|
+
await assertOperatingArtifact('operating-workspace-manifest', initializedWorkspace);
|
|
332
|
+
const preferences = await readPreferences(projectRoot, input.localRoot);
|
|
333
|
+
const store = new OperatingEventStore(projectRoot, { localRoot: input.localRoot });
|
|
334
|
+
const now = input.now ?? new Date();
|
|
335
|
+
const deadlineReconciliation = !input.preview && !input.dryRun
|
|
336
|
+
? await reconcileOperatingDecisionDeadlines({
|
|
337
|
+
projectRoot,
|
|
338
|
+
localRoot: input.localRoot,
|
|
339
|
+
now,
|
|
340
|
+
})
|
|
341
|
+
: { transitioned: 0 };
|
|
342
|
+
const initial = await store.replay();
|
|
343
|
+
const initialState = await store.state();
|
|
344
|
+
const current = activeCycle(initialState);
|
|
345
|
+
const timestamp = now.toISOString();
|
|
346
|
+
if (input.reviewOnly) {
|
|
347
|
+
const persisted = initialState.cycles.at(-1);
|
|
348
|
+
if (!persisted) {
|
|
349
|
+
throw new OperateError('E_OPERATE_STATE_INVALID', 'Review-only mode requires at least one committed operating cycle.');
|
|
350
|
+
}
|
|
351
|
+
const persistedCycle = await assertOperatingArtifact('operating-cycle-manifest', persisted);
|
|
352
|
+
const reconciled = input.dryRun
|
|
353
|
+
? { reconciled: 0, shipObserved: 0, state: initialState }
|
|
354
|
+
: await reconcileOperatingOutcomeFiles({
|
|
355
|
+
projectRoot,
|
|
356
|
+
localRoot: input.localRoot,
|
|
357
|
+
});
|
|
358
|
+
return {
|
|
359
|
+
preview: false,
|
|
360
|
+
dryRun: Boolean(input.dryRun),
|
|
361
|
+
cycle: persistedCycle,
|
|
362
|
+
provider: null,
|
|
363
|
+
state: reconciled.state,
|
|
364
|
+
modelCalls: 0,
|
|
365
|
+
warnings: [
|
|
366
|
+
`Review-only mode reconciled ${reconciled.reconciled} outcome observation(s) without evidence or model calls.`,
|
|
367
|
+
...(deadlineReconciliation.transitioned > 0
|
|
368
|
+
? [
|
|
369
|
+
`Marked ${deadlineReconciliation.transitioned} elapsed decision deadline(s) default-due without executing an action.`,
|
|
370
|
+
]
|
|
371
|
+
: []),
|
|
372
|
+
],
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
const resumableStates = new Set([
|
|
376
|
+
'preparing',
|
|
377
|
+
'collecting',
|
|
378
|
+
'advising',
|
|
379
|
+
'consolidating',
|
|
380
|
+
'blocked',
|
|
381
|
+
]);
|
|
382
|
+
if (current && !resumableStates.has(current.state)) {
|
|
383
|
+
throw new OperateError('E_OPERATE_CYCLE_ACTIVE', `Operating cycle ${current.id} is already ${current.state}; close or cancel it before starting another.`);
|
|
384
|
+
}
|
|
385
|
+
const depth = input.depth ?? current?.depth ?? 'standard';
|
|
386
|
+
const focus = normalizeFocus(input.focus ?? current?.focus);
|
|
387
|
+
const resolvedRuntime = input.runtime && input.runtime !== 'auto' ? input.runtime : preferences.runtime;
|
|
388
|
+
const cycleStart = current
|
|
389
|
+
? initial.events.find((event) => event.type === 'cycle.preparing' &&
|
|
390
|
+
event.cycleId === current.id &&
|
|
391
|
+
event.entityId === current.id)
|
|
392
|
+
: null;
|
|
393
|
+
if (current && !cycleStart) {
|
|
394
|
+
throw new OperateError('E_OPERATE_STATE_INVALID', `Operating cycle ${current.id} has no canonical preparing event.`);
|
|
395
|
+
}
|
|
396
|
+
const inputHead = cycleStart
|
|
397
|
+
? {
|
|
398
|
+
sequence: cycleStart.sequence - 1,
|
|
399
|
+
hash: cycleStart.previousEventHash,
|
|
400
|
+
}
|
|
401
|
+
: initial.eventHead;
|
|
402
|
+
// Preview is intentionally machine-state-free: it does not require local
|
|
403
|
+
// component-root mappings and binds only a prospective digest. A committed
|
|
404
|
+
// cycle always snapshots the live roots at its actual start.
|
|
405
|
+
const workspace = input.preview
|
|
406
|
+
? initializedWorkspace
|
|
407
|
+
: await snapshotWorkspace(projectRoot, input.localRoot);
|
|
408
|
+
const inputDigest = canonicalDigest({
|
|
409
|
+
workspaceDigest: workspace.workspaceDigest,
|
|
410
|
+
config: canonicalDigest(config),
|
|
411
|
+
eventHead: inputHead,
|
|
412
|
+
depth,
|
|
413
|
+
focus,
|
|
414
|
+
runtime: resolvedRuntime,
|
|
415
|
+
});
|
|
416
|
+
if (current && current.inputDigest !== inputDigest) {
|
|
417
|
+
throw new OperateError('E_OPERATE_CYCLE_INPUT_CONFLICT', `Operating cycle ${current.id} can resume only with its original workspace, configuration, depth, and focus.`, {
|
|
418
|
+
cycleId: current.id,
|
|
419
|
+
expectedInputDigest: current.inputDigest,
|
|
420
|
+
actualInputDigest: inputDigest,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
const cycle = current
|
|
424
|
+
? await assertOperatingArtifact('operating-cycle-manifest', current)
|
|
425
|
+
: await createCycleManifest({
|
|
426
|
+
id: nextCycleId(initialState),
|
|
427
|
+
state: 'preparing',
|
|
428
|
+
depth,
|
|
429
|
+
focus,
|
|
430
|
+
inputDigest,
|
|
431
|
+
enabledRoles: config.enabledRoles,
|
|
432
|
+
enabledProviders: config.enabledProviders,
|
|
433
|
+
runtime: resolvedRuntime,
|
|
434
|
+
now: timestamp,
|
|
435
|
+
});
|
|
436
|
+
if (input.preview) {
|
|
437
|
+
return {
|
|
438
|
+
preview: true,
|
|
439
|
+
dryRun: false,
|
|
440
|
+
cycle,
|
|
441
|
+
provider: null,
|
|
442
|
+
modelCalls: 0,
|
|
443
|
+
warnings: [],
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
const assertCycleWorkspace = async () => {
|
|
447
|
+
const observed = await snapshotWorkspace(projectRoot, input.localRoot);
|
|
448
|
+
if (observed.workspaceDigest !== workspace.workspaceDigest) {
|
|
449
|
+
throw new OperateError('E_OPERATE_HEAD_DIVERGED', 'Workspace revisions, branches, remotes, or material dirty fingerprints changed during the operating cycle.', {
|
|
450
|
+
expectedWorkspaceDigest: workspace.workspaceDigest,
|
|
451
|
+
actualWorkspaceDigest: observed.workspaceDigest,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
const existingEvidence = current && current.state !== 'blocked'
|
|
456
|
+
? await persistedCycleEvidence(store, initial.events, cycle.id)
|
|
457
|
+
: null;
|
|
458
|
+
const existingRoleResults = current
|
|
459
|
+
? await persistedCycleRoleResults(store, initial.events, cycle.id)
|
|
460
|
+
: [];
|
|
461
|
+
const advisorContext = await buildAdvisorOperatingContext({
|
|
462
|
+
charterPath: paths.charter,
|
|
463
|
+
state: initialState,
|
|
464
|
+
cycleId: cycle.id,
|
|
465
|
+
});
|
|
466
|
+
const evaluate = async (persist) => {
|
|
467
|
+
await assertCycleWorkspace();
|
|
468
|
+
const evidence = existingEvidence ??
|
|
469
|
+
(await collectOperatingEvidence({
|
|
470
|
+
projectRoot,
|
|
471
|
+
cycleId: cycle.id,
|
|
472
|
+
workspace,
|
|
473
|
+
providers: config.enabledProviders,
|
|
474
|
+
sensitivityCeiling: preferences.sensitivityCeiling,
|
|
475
|
+
budgets: {
|
|
476
|
+
...config.budgets,
|
|
477
|
+
maxItemBytes: Math.min(config.budgets.maxBytes, 256 * 1024),
|
|
478
|
+
},
|
|
479
|
+
localRoot: input.localRoot,
|
|
480
|
+
now,
|
|
481
|
+
incremental: depth === 'standard',
|
|
482
|
+
persistIncremental: persist && depth === 'standard',
|
|
483
|
+
requiredEvidenceRefs: requiredOpenEvidenceRefs(initialState),
|
|
484
|
+
}));
|
|
485
|
+
const evidenceGaps = await unqualifiedCommercialEvidenceGaps({
|
|
486
|
+
cycleId: cycle.id,
|
|
487
|
+
evidence,
|
|
488
|
+
owner: config.decisionOwner,
|
|
489
|
+
now: timestamp,
|
|
490
|
+
});
|
|
491
|
+
const nonChair = config.enabledRoles.filter((role) => role !== 'chair');
|
|
492
|
+
let readiness = await evaluateEvidenceReadiness({
|
|
493
|
+
cycleId: cycle.id,
|
|
494
|
+
evidence,
|
|
495
|
+
enabledRoles: nonChair,
|
|
496
|
+
now,
|
|
497
|
+
});
|
|
498
|
+
readiness = await remapReadinessGaps(readiness, initialState);
|
|
499
|
+
let adapter = input.adapter ?? null;
|
|
500
|
+
let provider = null;
|
|
501
|
+
const resolveAdapter = async () => {
|
|
502
|
+
if (adapter)
|
|
503
|
+
return adapter;
|
|
504
|
+
if (input.offline) {
|
|
505
|
+
adapter = createOfflineAdvisorAdapter();
|
|
506
|
+
return adapter;
|
|
507
|
+
}
|
|
508
|
+
adapter = await createConfiguredStructuredAdapter(projectRoot, {
|
|
509
|
+
quiet: input.quiet,
|
|
510
|
+
});
|
|
511
|
+
const openPlanrConfig = await loadConfig(projectRoot);
|
|
512
|
+
const consent = await ensureOperatingProviderConsent({
|
|
513
|
+
projectRoot,
|
|
514
|
+
provider: configuredAdvisorProviderPolicy({
|
|
515
|
+
config: openPlanrConfig,
|
|
516
|
+
adapterId: adapter.id,
|
|
517
|
+
runtime: resolvedRuntime,
|
|
518
|
+
}),
|
|
519
|
+
confirmed: Boolean(input.confirmed),
|
|
520
|
+
persist,
|
|
521
|
+
now: timestamp,
|
|
522
|
+
});
|
|
523
|
+
provider = consent.manifest;
|
|
524
|
+
return adapter;
|
|
525
|
+
};
|
|
526
|
+
const existingByRole = new Map(existingRoleResults.map((result) => [result.roleId, result]));
|
|
527
|
+
const missingNonChair = readiness.roles.filter((role) => role.roleId !== 'chair' && !existingByRole.has(role.roleId));
|
|
528
|
+
const runnableMissingNonChair = missingNonChair.filter((role) => role.modelCallAllowed);
|
|
529
|
+
if (input.deferAdvisors && runnableMissingNonChair.length > 0) {
|
|
530
|
+
return {
|
|
531
|
+
preview: false,
|
|
532
|
+
dryRun: false,
|
|
533
|
+
cycle,
|
|
534
|
+
evidence,
|
|
535
|
+
readiness,
|
|
536
|
+
roleResults: existingRoleResults.filter((result) => result.roleId !== 'chair'),
|
|
537
|
+
findings: [],
|
|
538
|
+
decisions: [],
|
|
539
|
+
gaps: readinessGaps(readiness, config.decisionOwner, timestamp),
|
|
540
|
+
routes: [],
|
|
541
|
+
provider: null,
|
|
542
|
+
modelCalls: 0,
|
|
543
|
+
warnings: ['Native advisor execution is required before consolidation.'],
|
|
544
|
+
nativeHandoff: {
|
|
545
|
+
phase: 'advisors',
|
|
546
|
+
cycleId: cycle.id,
|
|
547
|
+
evidenceDigest: evidence.fingerprint,
|
|
548
|
+
roles: runnableMissingNonChair.map((role) => role.roleId).sort(),
|
|
549
|
+
},
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
await assertCycleWorkspace();
|
|
553
|
+
const first = missingNonChair.length > 0
|
|
554
|
+
? await dispatchOperatingAdvisors({
|
|
555
|
+
cycleId: cycle.id,
|
|
556
|
+
evidence,
|
|
557
|
+
readiness: { ...readiness, roles: missingNonChair },
|
|
558
|
+
context: advisorContext,
|
|
559
|
+
adapter: await resolveAdapter(),
|
|
560
|
+
depth,
|
|
561
|
+
runtime: resolvedRuntime,
|
|
562
|
+
})
|
|
563
|
+
: { results: [], modelCalls: 0, blocked: false, skipped: [], failed: [] };
|
|
564
|
+
let roleResults = [
|
|
565
|
+
...existingRoleResults.filter((result) => result.roleId !== 'chair'),
|
|
566
|
+
...first.results,
|
|
567
|
+
];
|
|
568
|
+
let modelCalls = first.modelCalls;
|
|
569
|
+
let blocked = first.blocked;
|
|
570
|
+
const skipped = [...first.skipped];
|
|
571
|
+
const failed = [...first.failed];
|
|
572
|
+
let consolidationEvidence = evidence;
|
|
573
|
+
if (config.enabledRoles.includes('chair')) {
|
|
574
|
+
const chairEvidence = buildChairEvidence(evidence, roleResults, timestamp);
|
|
575
|
+
consolidationEvidence = chairEvidence;
|
|
576
|
+
let chairReadiness = await evaluateEvidenceReadiness({
|
|
577
|
+
cycleId: cycle.id,
|
|
578
|
+
evidence: chairEvidence,
|
|
579
|
+
enabledRoles: ['chair'],
|
|
580
|
+
now,
|
|
581
|
+
});
|
|
582
|
+
chairReadiness = await remapReadinessGaps(chairReadiness, {
|
|
583
|
+
...initialState,
|
|
584
|
+
dataGaps: [
|
|
585
|
+
...initialState.dataGaps,
|
|
586
|
+
...readiness.roles
|
|
587
|
+
.filter((role) => role.gapId)
|
|
588
|
+
.map((role) => ({ id: role.gapId, status: 'open' })),
|
|
589
|
+
],
|
|
590
|
+
});
|
|
591
|
+
readiness = {
|
|
592
|
+
...readiness,
|
|
593
|
+
roles: [...readiness.roles, ...chairReadiness.roles],
|
|
594
|
+
};
|
|
595
|
+
await assertOperatingArtifact('operating-evidence-readiness', readiness);
|
|
596
|
+
await assertCycleWorkspace();
|
|
597
|
+
const persistedChair = existingByRole.get('chair');
|
|
598
|
+
const runnableChair = chairReadiness.roles.filter((role) => role.modelCallAllowed && !persistedChair);
|
|
599
|
+
if (input.deferAdvisors && runnableChair.length > 0) {
|
|
600
|
+
return {
|
|
601
|
+
preview: false,
|
|
602
|
+
dryRun: false,
|
|
603
|
+
cycle,
|
|
604
|
+
evidence,
|
|
605
|
+
readiness,
|
|
606
|
+
roleResults,
|
|
607
|
+
findings: [],
|
|
608
|
+
decisions: [],
|
|
609
|
+
gaps: readinessGaps(readiness, config.decisionOwner, timestamp),
|
|
610
|
+
routes: [],
|
|
611
|
+
provider: null,
|
|
612
|
+
modelCalls,
|
|
613
|
+
warnings: ['Native Chair execution is required before consolidation.'],
|
|
614
|
+
nativeHandoff: {
|
|
615
|
+
phase: 'chair',
|
|
616
|
+
cycleId: cycle.id,
|
|
617
|
+
evidenceDigest: evidence.fingerprint,
|
|
618
|
+
roles: ['chair'],
|
|
619
|
+
},
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
const chair = persistedChair
|
|
623
|
+
? {
|
|
624
|
+
results: [persistedChair],
|
|
625
|
+
modelCalls: 0,
|
|
626
|
+
blocked: false,
|
|
627
|
+
skipped: [],
|
|
628
|
+
failed: [],
|
|
629
|
+
}
|
|
630
|
+
: await dispatchOperatingAdvisors({
|
|
631
|
+
cycleId: cycle.id,
|
|
632
|
+
evidence: chairEvidence,
|
|
633
|
+
readiness: chairReadiness,
|
|
634
|
+
context: advisorContext,
|
|
635
|
+
adapter: await resolveAdapter(),
|
|
636
|
+
depth,
|
|
637
|
+
runtime: resolvedRuntime,
|
|
638
|
+
});
|
|
639
|
+
roleResults = [...roleResults, ...chair.results];
|
|
640
|
+
modelCalls += chair.modelCalls;
|
|
641
|
+
skipped.push(...chair.skipped);
|
|
642
|
+
failed.push(...chair.failed);
|
|
643
|
+
blocked ||= chair.blocked || chairReadiness.roles.some((role) => !role.modelCallAllowed);
|
|
644
|
+
}
|
|
645
|
+
const technologyRiskReady = readiness.roles.some((role) => role.roleId === 'technology-risk' && role.modelCallAllowed);
|
|
646
|
+
const technologyRiskFailed = failed.some((entry) => entry.roleId === 'technology-risk');
|
|
647
|
+
const sensitiveRouteBlocked = containsSensitiveRouteProposal(roleResults) && (!technologyRiskReady || technologyRiskFailed);
|
|
648
|
+
blocked ||= sensitiveRouteBlocked;
|
|
649
|
+
const consolidated = await consolidateOperatingResults({
|
|
650
|
+
cycleId: cycle.id,
|
|
651
|
+
results: roleResults,
|
|
652
|
+
evidence: consolidationEvidence,
|
|
653
|
+
config,
|
|
654
|
+
now: timestamp,
|
|
655
|
+
existingGapCount: initialState.dataGaps.length,
|
|
656
|
+
});
|
|
657
|
+
const failureGaps = await advisorFailureGaps({
|
|
658
|
+
cycleId: cycle.id,
|
|
659
|
+
failed,
|
|
660
|
+
readiness,
|
|
661
|
+
owner: config.decisionOwner,
|
|
662
|
+
now: timestamp,
|
|
663
|
+
});
|
|
664
|
+
const surfacedDedup = suppressRepeatedFindings(initialState, consolidated.findings, cycle.id);
|
|
665
|
+
const overflowDedup = suppressRepeatedFindings(initialState, consolidated.criticalOverflow, cycle.id);
|
|
666
|
+
const parkedDedup = suppressRepeatedFindings(initialState, consolidated.parked, cycle.id);
|
|
667
|
+
const suppressedFindings = surfacedDedup.suppressed + overflowDedup.suppressed + parkedDedup.suppressed;
|
|
668
|
+
const criticalOverflowCount = overflowDedup.findings.length;
|
|
669
|
+
blocked ||= criticalOverflowCount > 0;
|
|
670
|
+
const remapped = remapConsolidation({
|
|
671
|
+
...initialState,
|
|
672
|
+
dataGaps: [
|
|
673
|
+
...initialState.dataGaps,
|
|
674
|
+
...readiness.roles
|
|
675
|
+
.filter((role) => role.gapId)
|
|
676
|
+
.map((role) => ({ id: role.gapId, status: 'open' })),
|
|
677
|
+
],
|
|
678
|
+
}, [...surfacedDedup.findings, ...overflowDedup.findings], parkedDedup.findings, consolidated.decisions, [...evidenceGaps, ...consolidated.gaps, ...failureGaps]);
|
|
679
|
+
const warnings = [
|
|
680
|
+
...evidence.warnings,
|
|
681
|
+
...(evidenceGaps.length > 0
|
|
682
|
+
? [
|
|
683
|
+
`${evidenceGaps.length} numeric commercial evidence item(s) require query and observation-window identity.`,
|
|
684
|
+
]
|
|
685
|
+
: []),
|
|
686
|
+
...skipped.map((entry) => `${entry.roleId} not evaluated: ${entry.reason}`),
|
|
687
|
+
...failed.map((entry) => `${entry.roleId} failed: ${entry.message}`),
|
|
688
|
+
...(sensitiveRouteBlocked
|
|
689
|
+
? [
|
|
690
|
+
'Sensitive payment/security/privacy/legal/tenant/destructive routing is blocked until technology-risk completes.',
|
|
691
|
+
]
|
|
692
|
+
: []),
|
|
693
|
+
...(criticalOverflowCount > 0
|
|
694
|
+
? [
|
|
695
|
+
`${criticalOverflowCount} verified critical finding(s) exceeded a configured cap; the cycle is blocked instead of parking them.`,
|
|
696
|
+
]
|
|
697
|
+
: []),
|
|
698
|
+
...(suppressedFindings > 0
|
|
699
|
+
? [
|
|
700
|
+
`${suppressedFindings} unchanged cross-cycle finding(s) were suppressed instead of duplicated.`,
|
|
701
|
+
]
|
|
702
|
+
: []),
|
|
703
|
+
];
|
|
704
|
+
if (blocked)
|
|
705
|
+
warnings.push('The cycle is blocked by required advisor evidence or failure.');
|
|
706
|
+
const reconciledFindings = reconcileCycleFindings(initial.events, cycle.id, remapped.findings);
|
|
707
|
+
const reconciledParked = reconcileCycleFindings(initial.events, cycle.id, remapped.parked);
|
|
708
|
+
return {
|
|
709
|
+
preview: false,
|
|
710
|
+
dryRun: !persist,
|
|
711
|
+
cycle,
|
|
712
|
+
evidence,
|
|
713
|
+
readiness,
|
|
714
|
+
roleResults,
|
|
715
|
+
findings: [...reconciledFindings, ...reconciledParked],
|
|
716
|
+
decisions: remapped.decisions,
|
|
717
|
+
gaps: [...readinessGaps(readiness, config.decisionOwner, timestamp), ...remapped.gaps],
|
|
718
|
+
routes: [],
|
|
719
|
+
provider,
|
|
720
|
+
modelCalls,
|
|
721
|
+
warnings,
|
|
722
|
+
};
|
|
723
|
+
};
|
|
724
|
+
if (input.dryRun)
|
|
725
|
+
return evaluate(false);
|
|
726
|
+
return withOperatingLock(projectRoot, {
|
|
727
|
+
projectKey: operatingProjectKey(projectRoot),
|
|
728
|
+
expectedEventHead: initial.eventHead,
|
|
729
|
+
currentEventHead: initial.eventHead,
|
|
730
|
+
localRoot: input.localRoot,
|
|
731
|
+
}, async (lock) => {
|
|
732
|
+
const latest = await store.replay();
|
|
733
|
+
if (latest.eventHead.sequence !== initial.eventHead.sequence ||
|
|
734
|
+
latest.eventHead.hash !== initial.eventHead.hash) {
|
|
735
|
+
throw new OperateError('E_OPERATE_HEAD_DIVERGED', 'Operating state changed while preparing the cycle.');
|
|
736
|
+
}
|
|
737
|
+
let head = initial.eventHead;
|
|
738
|
+
let started = Boolean(current);
|
|
739
|
+
let phase = current?.state ?? null;
|
|
740
|
+
const correlationId = randomUUID();
|
|
741
|
+
try {
|
|
742
|
+
await assertCycleWorkspace();
|
|
743
|
+
if (!current) {
|
|
744
|
+
const prepared = await appendLocked(store, lock, head, {
|
|
745
|
+
type: 'cycle.preparing',
|
|
746
|
+
cycleId: cycle.id,
|
|
747
|
+
entityId: cycle.id,
|
|
748
|
+
correlationId,
|
|
749
|
+
payload: { record: cycle },
|
|
750
|
+
});
|
|
751
|
+
started = true;
|
|
752
|
+
head = prepared.head;
|
|
753
|
+
phase = 'preparing';
|
|
754
|
+
}
|
|
755
|
+
if (phase === 'preparing' || phase === 'blocked') {
|
|
756
|
+
const collecting = await appendLocked(store, lock, head, {
|
|
757
|
+
type: 'cycle.collecting',
|
|
758
|
+
cycleId: cycle.id,
|
|
759
|
+
entityId: cycle.id,
|
|
760
|
+
correlationId,
|
|
761
|
+
payload: {},
|
|
762
|
+
});
|
|
763
|
+
head = collecting.head;
|
|
764
|
+
phase = 'collecting';
|
|
765
|
+
}
|
|
766
|
+
const evaluated = await evaluate(true);
|
|
767
|
+
await assertCycleWorkspace();
|
|
768
|
+
const evidence = evaluated.evidence;
|
|
769
|
+
if (phase === 'collecting') {
|
|
770
|
+
if (!existingEvidence) {
|
|
771
|
+
const evidenceRecord = await store.putRecord('evidence-metadata', recordContent(evidence), { correlationId, createdAt: timestamp });
|
|
772
|
+
const cache = new OperatingEvidenceCache(resolveOperatingPaths(projectRoot, { localRoot: input.localRoot }).evidence, preferences.sensitivityCeiling);
|
|
773
|
+
await cache.put(evidence.fingerprint.slice('sha256:'.length), evidence, preferences.evidenceTtlMs, now);
|
|
774
|
+
const evidenceEvent = await appendLocked(store, lock, head, {
|
|
775
|
+
type: 'evidence.collected',
|
|
776
|
+
cycleId: cycle.id,
|
|
777
|
+
entityId: cycle.id,
|
|
778
|
+
correlationId,
|
|
779
|
+
evidenceRefs: evidence.items.map((item) => item.id),
|
|
780
|
+
payload: {
|
|
781
|
+
recordDigest: evidenceRecord.digest,
|
|
782
|
+
sources: evidenceProjectionSources(evidence),
|
|
783
|
+
},
|
|
784
|
+
});
|
|
785
|
+
head = evidenceEvent.head;
|
|
786
|
+
}
|
|
787
|
+
const advising = await appendLocked(store, lock, head, {
|
|
788
|
+
type: 'cycle.advising',
|
|
789
|
+
cycleId: cycle.id,
|
|
790
|
+
entityId: cycle.id,
|
|
791
|
+
correlationId,
|
|
792
|
+
payload: {},
|
|
793
|
+
});
|
|
794
|
+
head = advising.head;
|
|
795
|
+
phase = 'advising';
|
|
796
|
+
}
|
|
797
|
+
if (phase === 'advising' && evaluated.nativeHandoff) {
|
|
798
|
+
const state = await store.state();
|
|
799
|
+
await store.writeCheckpoint(state);
|
|
800
|
+
await persistOperatingProjections({
|
|
801
|
+
projectRoot,
|
|
802
|
+
localRoot: input.localRoot,
|
|
803
|
+
state,
|
|
804
|
+
revalidateEventHead: async () => (await store.replay()).eventHead,
|
|
805
|
+
});
|
|
806
|
+
return { ...evaluated, state };
|
|
807
|
+
}
|
|
808
|
+
if (phase === 'advising') {
|
|
809
|
+
const recordedRoles = new Set(existingRoleResults.map((result) => result.roleId));
|
|
810
|
+
for (const result of evaluated.roleResults ?? []) {
|
|
811
|
+
if (recordedRoles.has(result.roleId))
|
|
812
|
+
continue;
|
|
813
|
+
const record = await store.putRecord('advisor-result', recordContent(result), {
|
|
814
|
+
correlationId,
|
|
815
|
+
createdAt: timestamp,
|
|
816
|
+
});
|
|
817
|
+
const appended = await appendLocked(store, lock, head, {
|
|
818
|
+
type: 'advisory.recorded',
|
|
819
|
+
cycleId: cycle.id,
|
|
820
|
+
entityId: `${cycle.id}-${result.roleId}`,
|
|
821
|
+
correlationId,
|
|
822
|
+
evidenceRefs: result.proposals.flatMap((proposal) => proposal.evidenceRefs),
|
|
823
|
+
payload: { recordDigest: record.digest },
|
|
824
|
+
});
|
|
825
|
+
head = appended.head;
|
|
826
|
+
}
|
|
827
|
+
const existingGapIds = new Set(initialState.dataGaps.filter((gap) => gap.cycleId === cycle.id).map((gap) => gap.id));
|
|
828
|
+
for (const gap of evaluated.gaps ?? []) {
|
|
829
|
+
if (existingGapIds.has(gap.id))
|
|
830
|
+
continue;
|
|
831
|
+
await assertOperatingArtifact('operating-data-gap', gap);
|
|
832
|
+
await store.putRecord('data-gap', recordContent(gap), {
|
|
833
|
+
correlationId,
|
|
834
|
+
createdAt: timestamp,
|
|
835
|
+
});
|
|
836
|
+
const appended = await appendLocked(store, lock, head, {
|
|
837
|
+
type: 'gap.open',
|
|
838
|
+
cycleId: cycle.id,
|
|
839
|
+
entityId: gap.id,
|
|
840
|
+
correlationId,
|
|
841
|
+
evidenceRefs: gap.evidenceRefs,
|
|
842
|
+
payload: { record: gap },
|
|
843
|
+
});
|
|
844
|
+
head = appended.head;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
if (phase === 'advising' &&
|
|
848
|
+
evaluated.warnings.some((warning) => warning.includes('cycle is blocked'))) {
|
|
849
|
+
const blocked = await appendLocked(store, lock, head, {
|
|
850
|
+
type: 'cycle.blocked',
|
|
851
|
+
cycleId: cycle.id,
|
|
852
|
+
entityId: cycle.id,
|
|
853
|
+
correlationId,
|
|
854
|
+
payload: { patch: { health: 'blocked' }, warnings: evaluated.warnings },
|
|
855
|
+
});
|
|
856
|
+
head = blocked.head;
|
|
857
|
+
const state = await store.state();
|
|
858
|
+
await store.writeCheckpoint(state);
|
|
859
|
+
await persistOperatingProjections({
|
|
860
|
+
projectRoot,
|
|
861
|
+
localRoot: input.localRoot,
|
|
862
|
+
state,
|
|
863
|
+
revalidateEventHead: async () => (await store.replay()).eventHead,
|
|
864
|
+
});
|
|
865
|
+
return { ...evaluated, state };
|
|
866
|
+
}
|
|
867
|
+
if (phase === 'advising') {
|
|
868
|
+
const consolidating = await appendLocked(store, lock, head, {
|
|
869
|
+
type: 'cycle.consolidating',
|
|
870
|
+
cycleId: cycle.id,
|
|
871
|
+
entityId: cycle.id,
|
|
872
|
+
correlationId,
|
|
873
|
+
payload: {},
|
|
874
|
+
});
|
|
875
|
+
head = consolidating.head;
|
|
876
|
+
phase = 'consolidating';
|
|
877
|
+
}
|
|
878
|
+
const existingFindingIds = new Set(initialState.findings
|
|
879
|
+
.filter((finding) => finding.cycleId === cycle.id)
|
|
880
|
+
.map((finding) => finding.id));
|
|
881
|
+
for (const finding of evaluated.findings ?? []) {
|
|
882
|
+
if (existingFindingIds.has(finding.id))
|
|
883
|
+
continue;
|
|
884
|
+
await store.putRecord('finding', recordContent(finding), {
|
|
885
|
+
correlationId,
|
|
886
|
+
createdAt: timestamp,
|
|
887
|
+
});
|
|
888
|
+
const appended = await appendLocked(store, lock, head, {
|
|
889
|
+
type: 'finding.proposed',
|
|
890
|
+
cycleId: cycle.id,
|
|
891
|
+
entityId: finding.id,
|
|
892
|
+
correlationId,
|
|
893
|
+
evidenceRefs: finding.evidenceRefs,
|
|
894
|
+
payload: { record: finding },
|
|
895
|
+
});
|
|
896
|
+
head = appended.head;
|
|
897
|
+
}
|
|
898
|
+
const existingDecisionQuestions = new Set(initialState.decisions
|
|
899
|
+
.filter((decision) => decision.cycleId === cycle.id)
|
|
900
|
+
.map((decision) => String(decision.question ?? '')));
|
|
901
|
+
for (const decision of evaluated.decisions ?? []) {
|
|
902
|
+
if (existingDecisionQuestions.has(decision.question))
|
|
903
|
+
continue;
|
|
904
|
+
await store.putRecord('decision', recordContent(decision), {
|
|
905
|
+
correlationId,
|
|
906
|
+
createdAt: timestamp,
|
|
907
|
+
});
|
|
908
|
+
const appended = await appendLocked(store, lock, head, {
|
|
909
|
+
type: 'decision.open',
|
|
910
|
+
cycleId: cycle.id,
|
|
911
|
+
entityId: decision.id,
|
|
912
|
+
correlationId,
|
|
913
|
+
evidenceRefs: decision.evidenceRefs,
|
|
914
|
+
payload: { record: decision },
|
|
915
|
+
});
|
|
916
|
+
head = appended.head;
|
|
917
|
+
}
|
|
918
|
+
const routeBase = await store.state();
|
|
919
|
+
const providerDigest = evaluated.provider?.policyDigest ?? canonicalDigest({ provider: 'offline' });
|
|
920
|
+
const routes = [];
|
|
921
|
+
let routeOrdinal = maximumOrdinal(routeBase.routes, 'ACT');
|
|
922
|
+
let specOrdinal = await nextOperatingSpecOrdinal(projectRoot);
|
|
923
|
+
const existingRoutedFindingIds = new Set(initial.events
|
|
924
|
+
.filter((event) => event.type === 'route.proposed' &&
|
|
925
|
+
event.payload.record &&
|
|
926
|
+
typeof event.payload.record === 'object')
|
|
927
|
+
.flatMap((event) => {
|
|
928
|
+
const record = event.payload.record;
|
|
929
|
+
return (record.actions ?? [])
|
|
930
|
+
.map((action) => action.findingId)
|
|
931
|
+
.filter((findingId) => Boolean(findingId));
|
|
932
|
+
}));
|
|
933
|
+
for (const finding of (evaluated.findings ?? []).filter((candidate) => !candidate.parked && !existingRoutedFindingIds.has(candidate.id))) {
|
|
934
|
+
const isDev = finding.lane === 'DEV';
|
|
935
|
+
routes.push(await createOperatingRoutePlan({
|
|
936
|
+
projectRoot,
|
|
937
|
+
cycleId: cycle.id,
|
|
938
|
+
finding,
|
|
939
|
+
config,
|
|
940
|
+
workspace,
|
|
941
|
+
eventHead: head,
|
|
942
|
+
evidenceDigest: evidence.fingerprint,
|
|
943
|
+
providerDigest,
|
|
944
|
+
sequence: ++routeOrdinal,
|
|
945
|
+
localRoot: input.localRoot,
|
|
946
|
+
...(isDev ? { specId: `SPEC-${String(specOrdinal++).padStart(3, '0')}` } : {}),
|
|
947
|
+
now: timestamp,
|
|
948
|
+
}));
|
|
949
|
+
}
|
|
950
|
+
if (routes.length > 0) {
|
|
951
|
+
await assertCycleWorkspace();
|
|
952
|
+
for (const route of routes) {
|
|
953
|
+
const relativePath = `.planr/operate/routes/${route.id}.json`;
|
|
954
|
+
const content = `${canonicalize(route)}\n`;
|
|
955
|
+
const transactionId = `TXN-${cycle.id}-${route.id}-proposal`;
|
|
956
|
+
const transactionRoot = path.join(resolveOperatingPaths(projectRoot, { localRoot: input.localRoot }).transactions, transactionId);
|
|
957
|
+
const manifestPath = path.join(transactionRoot, 'journal.json');
|
|
958
|
+
const existingBytes = await readFile(path.join(projectRoot, relativePath), 'utf8').catch((error) => {
|
|
959
|
+
if (error.code === 'ENOENT')
|
|
960
|
+
return null;
|
|
961
|
+
throw error;
|
|
962
|
+
});
|
|
963
|
+
const journal = existingBytes === null
|
|
964
|
+
? await prepareJournalTransaction(projectRoot, {
|
|
965
|
+
writes: [
|
|
966
|
+
{
|
|
967
|
+
relativePath,
|
|
968
|
+
operation: 'create',
|
|
969
|
+
content,
|
|
970
|
+
},
|
|
971
|
+
],
|
|
972
|
+
eventHead: head,
|
|
973
|
+
previewDigest: route.previewDigest,
|
|
974
|
+
transactionId,
|
|
975
|
+
localRoot: input.localRoot,
|
|
976
|
+
})
|
|
977
|
+
: {
|
|
978
|
+
root: transactionRoot,
|
|
979
|
+
manifestPath,
|
|
980
|
+
record: await readJournal(manifestPath),
|
|
981
|
+
};
|
|
982
|
+
if (existingBytes !== null) {
|
|
983
|
+
if (existingBytes !== content ||
|
|
984
|
+
journal.record.state !== 'committed' ||
|
|
985
|
+
journal.record.previewDigest !== route.previewDigest) {
|
|
986
|
+
throw new OperateError('E_OPERATE_TRANSACTION_INVALID', `Orphaned route proposal ${route.id} does not match its committed journal.`);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
else {
|
|
990
|
+
await applyJournalTransaction(projectRoot, journal, {
|
|
991
|
+
currentEventHead: head,
|
|
992
|
+
revalidateEventHead: async () => (await store.replay()).eventHead,
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
try {
|
|
996
|
+
await store.putRecord('route', recordContent(route), {
|
|
997
|
+
correlationId,
|
|
998
|
+
createdAt: timestamp,
|
|
999
|
+
});
|
|
1000
|
+
const appended = await appendLocked(store, lock, head, {
|
|
1001
|
+
type: 'route.proposed',
|
|
1002
|
+
cycleId: cycle.id,
|
|
1003
|
+
entityId: route.id,
|
|
1004
|
+
correlationId,
|
|
1005
|
+
evidenceRefs: route.actions.flatMap((action) => action.evidenceRefs),
|
|
1006
|
+
payload: { record: route },
|
|
1007
|
+
});
|
|
1008
|
+
head = appended.head;
|
|
1009
|
+
}
|
|
1010
|
+
catch (error) {
|
|
1011
|
+
await rollbackJournalTransaction(projectRoot, journal).catch(() => undefined);
|
|
1012
|
+
throw error;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
const quiet = (evaluated.findings?.length ?? 0) === 0 &&
|
|
1017
|
+
(evaluated.decisions?.length ?? 0) === 0 &&
|
|
1018
|
+
(evaluated.gaps?.length ?? 0) === 0;
|
|
1019
|
+
const health = quiet ? 'quiet' : evaluated.warnings.length > 0 ? 'partial' : 'normal';
|
|
1020
|
+
const reviewable = await appendLocked(store, lock, head, {
|
|
1021
|
+
type: 'cycle.reviewable',
|
|
1022
|
+
cycleId: cycle.id,
|
|
1023
|
+
entityId: cycle.id,
|
|
1024
|
+
correlationId,
|
|
1025
|
+
payload: {
|
|
1026
|
+
patch: {
|
|
1027
|
+
health,
|
|
1028
|
+
},
|
|
1029
|
+
warnings: evaluated.warnings,
|
|
1030
|
+
},
|
|
1031
|
+
});
|
|
1032
|
+
head = reviewable.head;
|
|
1033
|
+
if (quiet) {
|
|
1034
|
+
const closed = await appendLocked(store, lock, head, {
|
|
1035
|
+
type: 'cycle.closed',
|
|
1036
|
+
cycleId: cycle.id,
|
|
1037
|
+
entityId: cycle.id,
|
|
1038
|
+
correlationId,
|
|
1039
|
+
payload: {
|
|
1040
|
+
patch: {
|
|
1041
|
+
health,
|
|
1042
|
+
completedAt: timestamp,
|
|
1043
|
+
},
|
|
1044
|
+
},
|
|
1045
|
+
});
|
|
1046
|
+
head = closed.head;
|
|
1047
|
+
}
|
|
1048
|
+
const state = await store.state();
|
|
1049
|
+
await store.writeCheckpoint(state);
|
|
1050
|
+
await persistOperatingProjections({
|
|
1051
|
+
projectRoot,
|
|
1052
|
+
localRoot: input.localRoot,
|
|
1053
|
+
state,
|
|
1054
|
+
revalidateEventHead: async () => (await store.replay()).eventHead,
|
|
1055
|
+
});
|
|
1056
|
+
await mkdir(resolveOperatingPaths(projectRoot).cycles, {
|
|
1057
|
+
recursive: true,
|
|
1058
|
+
mode: 0o700,
|
|
1059
|
+
});
|
|
1060
|
+
return { ...evaluated, routes, state };
|
|
1061
|
+
}
|
|
1062
|
+
catch (error) {
|
|
1063
|
+
if (started) {
|
|
1064
|
+
const currentHead = await store.replay();
|
|
1065
|
+
try {
|
|
1066
|
+
const blockedByCriticalCap = error instanceof OperateError && error.code === 'E_OPERATE_CRITICAL_CAP';
|
|
1067
|
+
const failed = await appendLocked(store, lock, currentHead.eventHead, {
|
|
1068
|
+
type: blockedByCriticalCap ? 'cycle.blocked' : 'cycle.failed',
|
|
1069
|
+
cycleId: cycle.id,
|
|
1070
|
+
entityId: cycle.id,
|
|
1071
|
+
correlationId,
|
|
1072
|
+
payload: {
|
|
1073
|
+
patch: { health: 'blocked' },
|
|
1074
|
+
errorCode: error instanceof OperateError ? error.code : 'E_OPERATE_STATE_INVALID',
|
|
1075
|
+
...(blockedByCriticalCap ? { details: error.details } : {}),
|
|
1076
|
+
},
|
|
1077
|
+
});
|
|
1078
|
+
head = failed.head;
|
|
1079
|
+
const failedState = await store.state();
|
|
1080
|
+
await store.writeCheckpoint(failedState);
|
|
1081
|
+
await persistOperatingProjections({
|
|
1082
|
+
projectRoot,
|
|
1083
|
+
localRoot: input.localRoot,
|
|
1084
|
+
state: failedState,
|
|
1085
|
+
revalidateEventHead: async () => (await store.replay()).eventHead,
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
catch {
|
|
1089
|
+
// Preserve the original failure; integrity diagnostics will expose
|
|
1090
|
+
// any inability to append the terminal audit event.
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
throw error;
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
//# sourceMappingURL=engine.js.map
|