helixevo 0.6.0 → 0.7.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/CHANGELOG.md +21 -0
- package/README.md +9 -2
- package/dashboard/app/api/proof/route.ts +71 -0
- package/dashboard/app/api/run/route.ts +1 -0
- package/dashboard/app/coevolution/client.tsx +60 -31
- package/dashboard/app/coevolution/page.tsx +3 -1
- package/dashboard/app/commands/page.tsx +32 -5
- package/dashboard/app/guide/page.tsx +34 -21
- package/dashboard/app/ontology/client.tsx +39 -17
- package/dashboard/app/ontology/page.tsx +3 -1
- package/dashboard/app/page.tsx +34 -0
- package/dashboard/app/projects/client.tsx +13 -19
- package/dashboard/app/proof/client.tsx +295 -0
- package/dashboard/app/proof/page.tsx +9 -0
- package/dashboard/app/research/client.tsx +29 -8
- package/dashboard/app/topology/client.tsx +60 -29
- package/dashboard/app/topology/page.tsx +3 -1
- package/dashboard/components/guide-deep-link.tsx +22 -0
- package/dashboard/components/next-step-empty-state.tsx +53 -0
- package/dashboard/components/operator-loop-trail.tsx +46 -0
- package/dashboard/components/sidebar-nav.tsx +1 -0
- package/dashboard/components/surface-jump-links.tsx +69 -0
- package/dashboard/lib/loop-map.ts +210 -0
- package/dashboard/lib/proof.ts +577 -0
- package/dashboard/lib/release-spotlight.ts +17 -0
- package/dist/cli.js +500 -0
- package/package.json +2 -2
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs'
|
|
2
|
+
import { homedir } from 'os'
|
|
3
|
+
import { join } from 'path'
|
|
4
|
+
import {
|
|
5
|
+
loadPressureInterventions,
|
|
6
|
+
loadResolvedPressureSignals,
|
|
7
|
+
loadPressureMotifs,
|
|
8
|
+
loadTransferEvents,
|
|
9
|
+
loadResolvedTopologyApplyPlans,
|
|
10
|
+
loadResolvedTopologyReviewCandidates,
|
|
11
|
+
loadResolvedOntologyExtensions,
|
|
12
|
+
type PressureIntervention,
|
|
13
|
+
type ResolvedPressureSignal,
|
|
14
|
+
type ResolvedPressureMotif,
|
|
15
|
+
type ResolvedTransferEvent,
|
|
16
|
+
type ResolvedTopologyApplyPlan,
|
|
17
|
+
type ResolvedTopologyReviewCandidate,
|
|
18
|
+
type ResolvedOntologyExtension,
|
|
19
|
+
} from './data'
|
|
20
|
+
|
|
21
|
+
const HELIX_DIR = join(homedir(), '.helix')
|
|
22
|
+
const PROOF_REVIEW_FILE = join(HELIX_DIR, 'proof-reviews.jsonl')
|
|
23
|
+
const METRICS_FILE = join(HELIX_DIR, 'metrics.json')
|
|
24
|
+
const MEASURING_WINDOW_DAYS = 2
|
|
25
|
+
|
|
26
|
+
export type ProofTargetType = 'pressure-intervention' | 'transfer-event' | 'topology-execution' | 'ontology-extension' | 'evolution-impact'
|
|
27
|
+
export type ProofOutcomeState = 'measuring' | 'effective' | 'mixed' | 'regressed' | 'insufficient-evidence'
|
|
28
|
+
export type ProofReviewDecisionStatus = 'verify' | 'defer' | 'contest'
|
|
29
|
+
export type ProofReviewStatus = 'open' | 'verified' | 'deferred' | 'contested'
|
|
30
|
+
|
|
31
|
+
export interface ProofReviewRecord {
|
|
32
|
+
id: string
|
|
33
|
+
recordId: string
|
|
34
|
+
targetType: ProofTargetType
|
|
35
|
+
targetId: string
|
|
36
|
+
decision: ProofReviewDecisionStatus
|
|
37
|
+
decidedAt: string
|
|
38
|
+
rationale: string
|
|
39
|
+
decidedBy?: 'operator' | 'system'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ResolvedProofRecord {
|
|
43
|
+
id: string
|
|
44
|
+
targetType: ProofTargetType
|
|
45
|
+
targetId: string
|
|
46
|
+
title: string
|
|
47
|
+
summary: string
|
|
48
|
+
createdAt: string
|
|
49
|
+
lastActivityAt: string
|
|
50
|
+
outcomeState: ProofOutcomeState
|
|
51
|
+
reviewStatus: ProofReviewStatus
|
|
52
|
+
latestReview?: ProofReviewRecord
|
|
53
|
+
confidence: number
|
|
54
|
+
reasons: string[]
|
|
55
|
+
metrics?: Record<string, number>
|
|
56
|
+
relatedProjectIds?: string[]
|
|
57
|
+
relatedSignalIds?: string[]
|
|
58
|
+
relatedMotifIds?: string[]
|
|
59
|
+
relatedTransferEventIds?: string[]
|
|
60
|
+
semanticConceptIds?: string[]
|
|
61
|
+
recommendedAction?: string
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ProofSummary {
|
|
65
|
+
total: number
|
|
66
|
+
effective: number
|
|
67
|
+
mixed: number
|
|
68
|
+
regressed: number
|
|
69
|
+
measuring: number
|
|
70
|
+
insufficientEvidence: number
|
|
71
|
+
reviewOpen: number
|
|
72
|
+
verified: number
|
|
73
|
+
deferred: number
|
|
74
|
+
contested: number
|
|
75
|
+
byTargetType: Record<ProofTargetType, number>
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface ProofTargetBreakdown {
|
|
79
|
+
targetType: ProofTargetType
|
|
80
|
+
total: number
|
|
81
|
+
effective: number
|
|
82
|
+
mixed: number
|
|
83
|
+
regressed: number
|
|
84
|
+
measuring: number
|
|
85
|
+
insufficientEvidence: number
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface ProofDashboardSummary {
|
|
89
|
+
summary: ProofSummary
|
|
90
|
+
records: ResolvedProofRecord[]
|
|
91
|
+
reviewQueue: ResolvedProofRecord[]
|
|
92
|
+
recentVerified: ResolvedProofRecord[]
|
|
93
|
+
recentContested: ResolvedProofRecord[]
|
|
94
|
+
byTargetType: ProofTargetBreakdown[]
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
type LifecycleCarrier = {
|
|
98
|
+
lifecycle: 'open' | 'in-progress' | 'addressed' | 'stale'
|
|
99
|
+
lastActivityAt: string
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
type EvolutionImpactRecord = {
|
|
103
|
+
skillSlug: string
|
|
104
|
+
generation: number
|
|
105
|
+
evolvedAt: string
|
|
106
|
+
correctionRateBefore: number
|
|
107
|
+
correctionRateAfter: number
|
|
108
|
+
improvement: number
|
|
109
|
+
samplesBefore: number
|
|
110
|
+
samplesAfter: number
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function uniqueStrings(values: Array<string | null | undefined>): string[] {
|
|
114
|
+
return [...new Set(values.filter((value): value is string => Boolean(value && value.trim())))]
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function maxTimestamp(...values: Array<string | null | undefined>): string {
|
|
118
|
+
const filtered = values.filter((value): value is string => Boolean(value)).sort()
|
|
119
|
+
return filtered[filtered.length - 1] ?? new Date(0).toISOString()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function ageDays(value: string | undefined): number {
|
|
123
|
+
if (!value) return Number.POSITIVE_INFINITY
|
|
124
|
+
return (Date.now() - new Date(value).getTime()) / (1000 * 60 * 60 * 24)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function buildRecordId(targetType: ProofTargetType, targetId: string): string {
|
|
128
|
+
return `${targetType}:${targetId}`
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function loadProofReviews(): ProofReviewRecord[] {
|
|
132
|
+
if (!existsSync(PROOF_REVIEW_FILE)) return []
|
|
133
|
+
const raw = readFileSync(PROOF_REVIEW_FILE, 'utf-8').trim()
|
|
134
|
+
if (!raw) return []
|
|
135
|
+
return raw.split('\n').filter(Boolean).map((line) => JSON.parse(line) as ProofReviewRecord).sort((a, b) => b.decidedAt.localeCompare(a.decidedAt))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function loadEvolutionImpact(): EvolutionImpactRecord[] {
|
|
139
|
+
if (!existsSync(METRICS_FILE)) return []
|
|
140
|
+
const raw = JSON.parse(readFileSync(METRICS_FILE, 'utf-8')) as { evolutionImpact?: EvolutionImpactRecord[] }
|
|
141
|
+
return raw.evolutionImpact ?? []
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function reviewStatusFor(decision?: ProofReviewRecord): ProofReviewStatus {
|
|
145
|
+
if (!decision) return 'open'
|
|
146
|
+
if (decision.decision === 'verify') return 'verified'
|
|
147
|
+
if (decision.decision === 'defer') return 'deferred'
|
|
148
|
+
return 'contested'
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function confidenceFor(outcome: ProofOutcomeState, evidenceCount: number): number {
|
|
152
|
+
const base = outcome === 'effective'
|
|
153
|
+
? 0.76
|
|
154
|
+
: outcome === 'regressed'
|
|
155
|
+
? 0.82
|
|
156
|
+
: outcome === 'mixed'
|
|
157
|
+
? 0.64
|
|
158
|
+
: outcome === 'measuring'
|
|
159
|
+
? 0.52
|
|
160
|
+
: 0.42
|
|
161
|
+
return Math.min(0.96, base + Math.min(evidenceCount, 8) * 0.035)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function evaluateLifecycleOutcome(items: LifecycleCarrier[], targetAt: string, explicitRegression = false): ProofOutcomeState {
|
|
165
|
+
if (explicitRegression) return 'regressed'
|
|
166
|
+
const addressed = items.filter((item) => item.lifecycle === 'addressed').length
|
|
167
|
+
const active = items.filter((item) => item.lifecycle === 'open' || item.lifecycle === 'in-progress').length
|
|
168
|
+
|
|
169
|
+
if (items.length === 0) {
|
|
170
|
+
return ageDays(targetAt) <= MEASURING_WINDOW_DAYS ? 'measuring' : 'insufficient-evidence'
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (addressed > 0 && active === 0) return 'effective'
|
|
174
|
+
if (addressed > 0 && active > 0) return 'mixed'
|
|
175
|
+
if (ageDays(targetAt) <= MEASURING_WINDOW_DAYS) return 'measuring'
|
|
176
|
+
return 'insufficient-evidence'
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function latestReviewByRecord(): Map<string, ProofReviewRecord> {
|
|
180
|
+
const latest = new Map<string, ProofReviewRecord>()
|
|
181
|
+
for (const review of loadProofReviews()) {
|
|
182
|
+
const existing = latest.get(review.recordId)
|
|
183
|
+
if (!existing || review.decidedAt > existing.decidedAt) latest.set(review.recordId, review)
|
|
184
|
+
}
|
|
185
|
+
return latest
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function buildInterventionProofRecords(): ResolvedProofRecord[] {
|
|
189
|
+
const signals = loadResolvedPressureSignals()
|
|
190
|
+
const motifs = loadPressureMotifs()
|
|
191
|
+
|
|
192
|
+
return loadPressureInterventions().map((intervention) => {
|
|
193
|
+
const linkedSignals = signals.filter((signal) =>
|
|
194
|
+
intervention.pressureSignalIds?.includes(signal.id)
|
|
195
|
+
|| signal.linkedInterventions.some((linked) => linked.id === intervention.id),
|
|
196
|
+
)
|
|
197
|
+
const linkedMotifs = motifs.filter((motif) =>
|
|
198
|
+
intervention.motifIds?.includes(motif.id)
|
|
199
|
+
|| motif.linkedInterventionIds.includes(intervention.id),
|
|
200
|
+
)
|
|
201
|
+
const targetAt = intervention.completedAt ?? intervention.createdAt
|
|
202
|
+
const lifecycleItems: LifecycleCarrier[] = [...linkedSignals, ...linkedMotifs]
|
|
203
|
+
const outcomeState = intervention.status === 'failed'
|
|
204
|
+
? 'regressed'
|
|
205
|
+
: intervention.status === 'dry-run'
|
|
206
|
+
? 'insufficient-evidence'
|
|
207
|
+
: evaluateLifecycleOutcome(lifecycleItems, targetAt)
|
|
208
|
+
const evidenceCount = linkedSignals.length + linkedMotifs.length
|
|
209
|
+
|
|
210
|
+
return {
|
|
211
|
+
id: buildRecordId('pressure-intervention', intervention.id),
|
|
212
|
+
targetType: 'pressure-intervention',
|
|
213
|
+
targetId: intervention.id,
|
|
214
|
+
title: `${intervention.interventionType} intervention`,
|
|
215
|
+
summary: intervention.reasonSummary,
|
|
216
|
+
createdAt: intervention.createdAt,
|
|
217
|
+
lastActivityAt: maxTimestamp(intervention.completedAt, ...linkedSignals.map((signal) => signal.lastActivityAt), ...linkedMotifs.map((motif) => motif.lastActivityAt), intervention.createdAt),
|
|
218
|
+
outcomeState,
|
|
219
|
+
reviewStatus: 'open',
|
|
220
|
+
confidence: confidenceFor(outcomeState, evidenceCount),
|
|
221
|
+
reasons: [
|
|
222
|
+
`${intervention.interventionType} recorded as ${intervention.status} with ${evidenceCount} linked pressure target${evidenceCount === 1 ? '' : 's'}.`,
|
|
223
|
+
outcomeState === 'effective'
|
|
224
|
+
? 'All linked pressure evidence currently resolves to addressed state.'
|
|
225
|
+
: outcomeState === 'mixed'
|
|
226
|
+
? 'Linked pressure evidence is split between addressed and still-active states.'
|
|
227
|
+
: outcomeState === 'regressed'
|
|
228
|
+
? 'The intervention failed explicitly, so proof treats it as regressed.'
|
|
229
|
+
: outcomeState === 'measuring'
|
|
230
|
+
? 'The intervention is still too recent or too active for a stronger proof verdict.'
|
|
231
|
+
: intervention.status === 'dry-run'
|
|
232
|
+
? 'Dry-run interventions do not create live effect evidence.'
|
|
233
|
+
: 'Linked evidence is too weak to attribute a stronger effect safely.',
|
|
234
|
+
],
|
|
235
|
+
metrics: {
|
|
236
|
+
linkedSignals: linkedSignals.length,
|
|
237
|
+
linkedMotifs: linkedMotifs.length,
|
|
238
|
+
addressed: lifecycleItems.filter((item) => item.lifecycle === 'addressed').length,
|
|
239
|
+
},
|
|
240
|
+
relatedProjectIds: uniqueStrings([intervention.projectId, ...linkedSignals.map((signal) => signal.projectId), ...linkedMotifs.flatMap((motif) => motif.projectIds)]),
|
|
241
|
+
relatedSignalIds: linkedSignals.map((signal) => signal.id),
|
|
242
|
+
relatedMotifIds: linkedMotifs.map((motif) => motif.id),
|
|
243
|
+
relatedTransferEventIds: uniqueStrings([...(intervention.relatedTransferEventIds ?? []), ...linkedMotifs.flatMap((motif) => motif.linkedTransferEventIds ?? [])]),
|
|
244
|
+
semanticConceptIds: uniqueStrings([
|
|
245
|
+
...linkedSignals.flatMap((signal) => signal.semanticConceptIds ?? []),
|
|
246
|
+
...linkedMotifs.flatMap((motif) => motif.semanticConceptIds ?? []),
|
|
247
|
+
]),
|
|
248
|
+
recommendedAction: outcomeState === 'effective'
|
|
249
|
+
? 'Verify if this resolving pattern should remain a trusted lane.'
|
|
250
|
+
: outcomeState === 'mixed'
|
|
251
|
+
? 'Inspect Co-Evolution and compare remaining active pressure before re-routing.'
|
|
252
|
+
: outcomeState === 'regressed'
|
|
253
|
+
? 'Inspect Co-Evolution for a different governed response or manual review.'
|
|
254
|
+
: outcomeState === 'measuring'
|
|
255
|
+
? 'Let the intervention age or accumulate more downstream evidence before review.'
|
|
256
|
+
: 'Link stronger downstream evidence before treating this intervention as proven.',
|
|
257
|
+
}
|
|
258
|
+
})
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function buildTransferProofRecords(): ResolvedProofRecord[] {
|
|
262
|
+
const motifs = loadPressureMotifs()
|
|
263
|
+
const signals = loadResolvedPressureSignals()
|
|
264
|
+
|
|
265
|
+
return loadTransferEvents()
|
|
266
|
+
.filter((event) => event.status === 'realized')
|
|
267
|
+
.map((event) => {
|
|
268
|
+
const linkedMotifs = motifs.filter((motif) => event.motifIds?.includes(motif.id) || motif.linkedTransferEventIds.includes(event.id))
|
|
269
|
+
const linkedSignals = signals.filter((signal) => signal.motifIds?.some((motifId) => event.motifIds?.includes(motifId)))
|
|
270
|
+
const lifecycleItems: LifecycleCarrier[] = [...linkedSignals, ...linkedMotifs]
|
|
271
|
+
const outcomeState = evaluateLifecycleOutcome(lifecycleItems, event.timestamp)
|
|
272
|
+
const evidenceCount = linkedSignals.length + linkedMotifs.length
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
id: buildRecordId('transfer-event', event.id),
|
|
276
|
+
targetType: 'transfer-event',
|
|
277
|
+
targetId: event.id,
|
|
278
|
+
title: `${event.transferType} transfer`,
|
|
279
|
+
summary: event.summary ?? 'Realized transfer event',
|
|
280
|
+
createdAt: event.timestamp,
|
|
281
|
+
lastActivityAt: maxTimestamp(event.timestamp, ...linkedSignals.map((signal) => signal.lastActivityAt), ...linkedMotifs.map((motif) => motif.lastActivityAt)),
|
|
282
|
+
outcomeState,
|
|
283
|
+
reviewStatus: 'open',
|
|
284
|
+
confidence: confidenceFor(outcomeState, evidenceCount),
|
|
285
|
+
reasons: [
|
|
286
|
+
`${event.transferType} transfer is realized and linked to ${linkedMotifs.length} motifs plus ${linkedSignals.length} correlated signals.`,
|
|
287
|
+
outcomeState === 'effective'
|
|
288
|
+
? 'Linked motif pressure currently resolves cleanly to addressed state.'
|
|
289
|
+
: outcomeState === 'mixed'
|
|
290
|
+
? 'Some linked motif pressure is addressed, but some remains active.'
|
|
291
|
+
: outcomeState === 'measuring'
|
|
292
|
+
? 'The realized transfer is still recent enough that proof stays in measuring mode.'
|
|
293
|
+
: 'Transfer evidence exists, but downstream relief is too weak to call it effective yet.',
|
|
294
|
+
],
|
|
295
|
+
metrics: {
|
|
296
|
+
linkedSignals: linkedSignals.length,
|
|
297
|
+
linkedMotifs: linkedMotifs.length,
|
|
298
|
+
addressed: lifecycleItems.filter((item) => item.lifecycle === 'addressed').length,
|
|
299
|
+
},
|
|
300
|
+
relatedProjectIds: uniqueStrings([event.sourceProjectId, event.targetProjectId, ...linkedMotifs.flatMap((motif) => motif.projectIds)]),
|
|
301
|
+
relatedSignalIds: linkedSignals.map((signal) => signal.id),
|
|
302
|
+
relatedMotifIds: linkedMotifs.map((motif) => motif.id),
|
|
303
|
+
relatedTransferEventIds: [event.id],
|
|
304
|
+
semanticConceptIds: uniqueStrings([
|
|
305
|
+
...(event.semanticConceptIds ?? []),
|
|
306
|
+
...linkedSignals.flatMap((signal) => signal.semanticConceptIds ?? []),
|
|
307
|
+
...linkedMotifs.flatMap((motif) => motif.semanticConceptIds ?? []),
|
|
308
|
+
]),
|
|
309
|
+
recommendedAction: outcomeState === 'effective'
|
|
310
|
+
? 'Verify whether this transfer pattern should be treated as proven reusable evidence.'
|
|
311
|
+
: outcomeState === 'mixed'
|
|
312
|
+
? 'Inspect remaining active motifs before treating this transfer as fully proven.'
|
|
313
|
+
: outcomeState === 'measuring'
|
|
314
|
+
? 'Wait for more motif pressure movement before review.'
|
|
315
|
+
: 'Accumulate more downstream motif or signal relief before verifying this transfer.',
|
|
316
|
+
}
|
|
317
|
+
})
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function buildTopologyProofRecords(): ResolvedProofRecord[] {
|
|
321
|
+
const candidatesById = new Map(loadResolvedTopologyReviewCandidates().map((candidate) => [candidate.id, candidate]))
|
|
322
|
+
const signals = loadResolvedPressureSignals()
|
|
323
|
+
const motifs = loadPressureMotifs()
|
|
324
|
+
|
|
325
|
+
return loadResolvedTopologyApplyPlans().map((plan) => {
|
|
326
|
+
const candidate = candidatesById.get(plan.candidateId)
|
|
327
|
+
const latestStatus = plan.latestStatus
|
|
328
|
+
const linkedSignals = signals.filter((signal) => candidate?.relatedSignalIds?.includes(signal.id))
|
|
329
|
+
const linkedMotifs = motifs.filter((motif) => candidate?.relatedMotifIds?.includes(motif.id))
|
|
330
|
+
const lifecycleItems: LifecycleCarrier[] = [...linkedSignals, ...linkedMotifs]
|
|
331
|
+
const targetAt = plan.latestExecution?.executedAt ?? plan.createdAt
|
|
332
|
+
const explicitRegression = latestStatus === 'rolled-back' || latestStatus === 'failed'
|
|
333
|
+
const outcomeState = latestStatus === 'pending' || latestStatus === 'prepared'
|
|
334
|
+
? 'measuring'
|
|
335
|
+
: evaluateLifecycleOutcome(lifecycleItems, targetAt, explicitRegression)
|
|
336
|
+
const evidenceCount = linkedSignals.length + linkedMotifs.length
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
id: buildRecordId('topology-execution', plan.id),
|
|
340
|
+
targetType: 'topology-execution',
|
|
341
|
+
targetId: plan.id,
|
|
342
|
+
title: `${plan.changeType} topology execution`,
|
|
343
|
+
summary: plan.reasonSummary,
|
|
344
|
+
createdAt: plan.createdAt,
|
|
345
|
+
lastActivityAt: maxTimestamp(plan.lastActivityAt, candidate?.lastActivityAt, ...linkedSignals.map((signal) => signal.lastActivityAt), ...linkedMotifs.map((motif) => motif.lastActivityAt)),
|
|
346
|
+
outcomeState,
|
|
347
|
+
reviewStatus: 'open',
|
|
348
|
+
confidence: confidenceFor(outcomeState, evidenceCount + (latestStatus === 'applied' ? 1 : 0)),
|
|
349
|
+
reasons: [
|
|
350
|
+
`${plan.changeType} topology plan is currently ${latestStatus}.`,
|
|
351
|
+
explicitRegression
|
|
352
|
+
? 'Rollback or failed execution is treated as explicit negative evidence.'
|
|
353
|
+
: outcomeState === 'effective'
|
|
354
|
+
? 'Linked structural pressure currently resolves to addressed state after apply.'
|
|
355
|
+
: outcomeState === 'mixed'
|
|
356
|
+
? 'Structural apply has some correlated relief, but not a clean closure.'
|
|
357
|
+
: outcomeState === 'measuring'
|
|
358
|
+
? 'Prepared or recently applied topology changes remain in measuring mode until more evidence accrues.'
|
|
359
|
+
: 'There is not enough downstream structural-pressure evidence for a stronger verdict yet.',
|
|
360
|
+
],
|
|
361
|
+
metrics: {
|
|
362
|
+
linkedSignals: linkedSignals.length,
|
|
363
|
+
linkedMotifs: linkedMotifs.length,
|
|
364
|
+
safeToApply: plan.safeToApply ? 1 : 0,
|
|
365
|
+
},
|
|
366
|
+
relatedProjectIds: uniqueStrings(candidate?.projectIds ?? []),
|
|
367
|
+
relatedSignalIds: linkedSignals.map((signal) => signal.id),
|
|
368
|
+
relatedMotifIds: linkedMotifs.map((motif) => motif.id),
|
|
369
|
+
relatedTransferEventIds: candidate?.relatedTransferEventIds ?? [],
|
|
370
|
+
semanticConceptIds: uniqueStrings([
|
|
371
|
+
...(candidate?.semanticConceptIds ?? []),
|
|
372
|
+
...linkedSignals.flatMap((signal) => signal.semanticConceptIds ?? []),
|
|
373
|
+
...linkedMotifs.flatMap((motif) => motif.semanticConceptIds ?? []),
|
|
374
|
+
]),
|
|
375
|
+
recommendedAction: outcomeState === 'effective'
|
|
376
|
+
? 'Verify this topology change if the structural relief looks durable.'
|
|
377
|
+
: outcomeState === 'mixed'
|
|
378
|
+
? 'Inspect Topology and Co-Evolution together before trusting this execution fully.'
|
|
379
|
+
: outcomeState === 'regressed'
|
|
380
|
+
? 'Inspect the execution notes and rollback path before retrying.'
|
|
381
|
+
: outcomeState === 'measuring'
|
|
382
|
+
? 'Wait for more downstream pressure movement before review.'
|
|
383
|
+
: 'Link stronger post-apply evidence before verifying this structural change.',
|
|
384
|
+
}
|
|
385
|
+
})
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function buildOntologyProofRecords(): ResolvedProofRecord[] {
|
|
389
|
+
const signals = loadResolvedPressureSignals()
|
|
390
|
+
const motifs = loadPressureMotifs()
|
|
391
|
+
const transfers = loadTransferEvents()
|
|
392
|
+
const topologyCandidates = loadResolvedTopologyReviewCandidates()
|
|
393
|
+
|
|
394
|
+
return loadResolvedOntologyExtensions()
|
|
395
|
+
.filter((concept) => concept.status === 'active')
|
|
396
|
+
.map((concept) => {
|
|
397
|
+
const boundSignals = signals.filter((signal) => signal.semanticConceptIds?.includes(concept.id))
|
|
398
|
+
const boundMotifs = motifs.filter((motif) => motif.semanticConceptIds?.includes(concept.id))
|
|
399
|
+
const boundTransfers = transfers.filter((event) => event.semanticConceptIds?.includes(concept.id))
|
|
400
|
+
const boundTopology = topologyCandidates.filter((candidate) => candidate.semanticConceptIds?.includes(concept.id))
|
|
401
|
+
const lifecycleItems: LifecycleCarrier[] = [...boundSignals, ...boundMotifs]
|
|
402
|
+
const evidenceCount = concept.adoptionCount + boundTransfers.length + boundTopology.length
|
|
403
|
+
const outcomeState = concept.adoptionCount === 0
|
|
404
|
+
? (ageDays(concept.createdAt) <= MEASURING_WINDOW_DAYS ? 'measuring' : 'insufficient-evidence')
|
|
405
|
+
: evaluateLifecycleOutcome(lifecycleItems, concept.lastActivityAt)
|
|
406
|
+
|
|
407
|
+
return {
|
|
408
|
+
id: buildRecordId('ontology-extension', concept.id),
|
|
409
|
+
targetType: 'ontology-extension',
|
|
410
|
+
targetId: concept.id,
|
|
411
|
+
title: `${concept.name} semantic adoption`,
|
|
412
|
+
summary: concept.description ?? concept.sourceSummary ?? 'Approved ontology extension',
|
|
413
|
+
createdAt: concept.createdAt,
|
|
414
|
+
lastActivityAt: maxTimestamp(concept.lastActivityAt, ...boundSignals.map((signal) => signal.lastActivityAt), ...boundMotifs.map((motif) => motif.lastActivityAt)),
|
|
415
|
+
outcomeState,
|
|
416
|
+
reviewStatus: 'open',
|
|
417
|
+
confidence: Math.min(0.88, confidenceFor(outcomeState, evidenceCount) - 0.04),
|
|
418
|
+
reasons: [
|
|
419
|
+
`${concept.name} currently has ${concept.adoptionCount} semantic bindings across live consumers.`,
|
|
420
|
+
outcomeState === 'effective'
|
|
421
|
+
? 'Correlated semantic consumers now align with addressed downstream pressure evidence.'
|
|
422
|
+
: outcomeState === 'mixed'
|
|
423
|
+
? 'Semantic adoption is active, but downstream pressure evidence remains mixed.'
|
|
424
|
+
: outcomeState === 'measuring'
|
|
425
|
+
? 'Semantic adoption is live but still too recent or incomplete for a stronger claim.'
|
|
426
|
+
: 'This record is treated as semantic-adoption evidence, not strong direct causality proof.',
|
|
427
|
+
],
|
|
428
|
+
metrics: {
|
|
429
|
+
adoptionCount: concept.adoptionCount,
|
|
430
|
+
boundSignals: boundSignals.length,
|
|
431
|
+
boundMotifs: boundMotifs.length,
|
|
432
|
+
boundTransfers: boundTransfers.length,
|
|
433
|
+
boundTopology: boundTopology.length,
|
|
434
|
+
},
|
|
435
|
+
relatedProjectIds: uniqueStrings([...(concept.projectIds ?? []), ...boundSignals.map((signal) => signal.projectId), ...boundMotifs.flatMap((motif) => motif.projectIds)]),
|
|
436
|
+
relatedSignalIds: boundSignals.map((signal) => signal.id),
|
|
437
|
+
relatedMotifIds: boundMotifs.map((motif) => motif.id),
|
|
438
|
+
relatedTransferEventIds: boundTransfers.map((event) => event.id),
|
|
439
|
+
semanticConceptIds: [concept.id],
|
|
440
|
+
recommendedAction: outcomeState === 'effective'
|
|
441
|
+
? 'Verify this concept if semantic adoption looks stable enough to trust.'
|
|
442
|
+
: outcomeState === 'mixed'
|
|
443
|
+
? 'Inspect Ontology and Co-Evolution together before treating this concept as proven.'
|
|
444
|
+
: outcomeState === 'measuring'
|
|
445
|
+
? 'Let semantic adoption spread or age before review.'
|
|
446
|
+
: 'Treat this as bounded semantic-adoption evidence until stronger downstream relief appears.',
|
|
447
|
+
}
|
|
448
|
+
})
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function buildEvolutionProofRecords(): ResolvedProofRecord[] {
|
|
452
|
+
return loadEvolutionImpact().map((impact) => {
|
|
453
|
+
const age = ageDays(impact.evolvedAt)
|
|
454
|
+
const outcomeState: ProofOutcomeState = impact.samplesAfter <= 0
|
|
455
|
+
? (age <= MEASURING_WINDOW_DAYS ? 'measuring' : 'insufficient-evidence')
|
|
456
|
+
: age <= MEASURING_WINDOW_DAYS || impact.samplesAfter < 3
|
|
457
|
+
? 'measuring'
|
|
458
|
+
: impact.improvement < -10
|
|
459
|
+
? 'effective'
|
|
460
|
+
: impact.improvement > 10
|
|
461
|
+
? 'regressed'
|
|
462
|
+
: 'mixed'
|
|
463
|
+
const targetId = `${impact.skillSlug}:${impact.generation}:${impact.evolvedAt}`
|
|
464
|
+
|
|
465
|
+
return {
|
|
466
|
+
id: buildRecordId('evolution-impact', targetId),
|
|
467
|
+
targetType: 'evolution-impact',
|
|
468
|
+
targetId,
|
|
469
|
+
title: `${impact.skillSlug} evolution impact`,
|
|
470
|
+
summary: `Generation ${impact.generation} evolution baseline recorded at ${impact.evolvedAt}.`,
|
|
471
|
+
createdAt: impact.evolvedAt,
|
|
472
|
+
lastActivityAt: impact.evolvedAt,
|
|
473
|
+
outcomeState,
|
|
474
|
+
reviewStatus: 'open',
|
|
475
|
+
confidence: confidenceFor(outcomeState, impact.samplesBefore + impact.samplesAfter),
|
|
476
|
+
reasons: [
|
|
477
|
+
`${impact.skillSlug} generation ${impact.generation} has ${impact.samplesBefore} baseline failures and ${impact.samplesAfter} post-evolution samples.`,
|
|
478
|
+
outcomeState === 'effective'
|
|
479
|
+
? 'Measured failure rate dropped enough to clear the effectiveness threshold.'
|
|
480
|
+
: outcomeState === 'regressed'
|
|
481
|
+
? 'Measured failure rate increased enough to count as regression.'
|
|
482
|
+
: outcomeState === 'mixed'
|
|
483
|
+
? 'The run changed behavior, but not enough for a clean positive or negative verdict.'
|
|
484
|
+
: outcomeState === 'measuring'
|
|
485
|
+
? 'The evolution record is still collecting enough post-change evidence.'
|
|
486
|
+
: 'There is not enough post-change sample depth for a stronger verdict.',
|
|
487
|
+
],
|
|
488
|
+
metrics: {
|
|
489
|
+
improvement: impact.improvement,
|
|
490
|
+
samplesBefore: impact.samplesBefore,
|
|
491
|
+
samplesAfter: impact.samplesAfter,
|
|
492
|
+
},
|
|
493
|
+
recommendedAction: outcomeState === 'effective'
|
|
494
|
+
? 'Verify this evolution result if the lower correction rate looks durable.'
|
|
495
|
+
: outcomeState === 'regressed'
|
|
496
|
+
? 'Inspect evolution history and judges before trusting this mutation lane again.'
|
|
497
|
+
: outcomeState === 'measuring'
|
|
498
|
+
? 'Wait for more post-evolution samples before review.'
|
|
499
|
+
: 'Use metrics alongside proof review before concluding anything stronger.',
|
|
500
|
+
}
|
|
501
|
+
})
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
export function loadResolvedProofRecords(): ResolvedProofRecord[] {
|
|
505
|
+
const latestReviews = latestReviewByRecord()
|
|
506
|
+
const records = [
|
|
507
|
+
...buildInterventionProofRecords(),
|
|
508
|
+
...buildTransferProofRecords(),
|
|
509
|
+
...buildTopologyProofRecords(),
|
|
510
|
+
...buildOntologyProofRecords(),
|
|
511
|
+
...buildEvolutionProofRecords(),
|
|
512
|
+
].map((record) => {
|
|
513
|
+
const latestReview = latestReviews.get(record.id)
|
|
514
|
+
return {
|
|
515
|
+
...record,
|
|
516
|
+
latestReview,
|
|
517
|
+
reviewStatus: reviewStatusFor(latestReview),
|
|
518
|
+
}
|
|
519
|
+
})
|
|
520
|
+
|
|
521
|
+
const outcomeWeight = (value: ProofOutcomeState) => value === 'regressed' ? 5 : value === 'mixed' ? 4 : value === 'effective' ? 3 : value === 'measuring' ? 2 : 1
|
|
522
|
+
const reviewWeight = (value: ProofReviewStatus) => value === 'contested' ? 4 : value === 'open' ? 3 : value === 'deferred' ? 2 : 1
|
|
523
|
+
|
|
524
|
+
return records.sort((a, b) => reviewWeight(b.reviewStatus) - reviewWeight(a.reviewStatus) || outcomeWeight(b.outcomeState) - outcomeWeight(a.outcomeState) || b.lastActivityAt.localeCompare(a.lastActivityAt) || a.targetType.localeCompare(b.targetType))
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
export function getProofSummary(records = loadResolvedProofRecords()): ProofSummary {
|
|
528
|
+
const byTargetType: Record<ProofTargetType, number> = {
|
|
529
|
+
'pressure-intervention': 0,
|
|
530
|
+
'transfer-event': 0,
|
|
531
|
+
'topology-execution': 0,
|
|
532
|
+
'ontology-extension': 0,
|
|
533
|
+
'evolution-impact': 0,
|
|
534
|
+
}
|
|
535
|
+
for (const record of records) byTargetType[record.targetType] += 1
|
|
536
|
+
return {
|
|
537
|
+
total: records.length,
|
|
538
|
+
effective: records.filter((record) => record.outcomeState === 'effective').length,
|
|
539
|
+
mixed: records.filter((record) => record.outcomeState === 'mixed').length,
|
|
540
|
+
regressed: records.filter((record) => record.outcomeState === 'regressed').length,
|
|
541
|
+
measuring: records.filter((record) => record.outcomeState === 'measuring').length,
|
|
542
|
+
insufficientEvidence: records.filter((record) => record.outcomeState === 'insufficient-evidence').length,
|
|
543
|
+
reviewOpen: records.filter((record) => record.reviewStatus === 'open').length,
|
|
544
|
+
verified: records.filter((record) => record.reviewStatus === 'verified').length,
|
|
545
|
+
deferred: records.filter((record) => record.reviewStatus === 'deferred').length,
|
|
546
|
+
contested: records.filter((record) => record.reviewStatus === 'contested').length,
|
|
547
|
+
byTargetType,
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function buildTargetBreakdown(records: ResolvedProofRecord[]): ProofTargetBreakdown[] {
|
|
552
|
+
const targetTypes: ProofTargetType[] = ['pressure-intervention', 'transfer-event', 'topology-execution', 'ontology-extension', 'evolution-impact']
|
|
553
|
+
return targetTypes.map((targetType) => {
|
|
554
|
+
const subset = records.filter((record) => record.targetType === targetType)
|
|
555
|
+
return {
|
|
556
|
+
targetType,
|
|
557
|
+
total: subset.length,
|
|
558
|
+
effective: subset.filter((record) => record.outcomeState === 'effective').length,
|
|
559
|
+
mixed: subset.filter((record) => record.outcomeState === 'mixed').length,
|
|
560
|
+
regressed: subset.filter((record) => record.outcomeState === 'regressed').length,
|
|
561
|
+
measuring: subset.filter((record) => record.outcomeState === 'measuring').length,
|
|
562
|
+
insufficientEvidence: subset.filter((record) => record.outcomeState === 'insufficient-evidence').length,
|
|
563
|
+
}
|
|
564
|
+
}).filter((entry) => entry.total > 0)
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
export function loadProofDashboardSummary(): ProofDashboardSummary {
|
|
568
|
+
const records = loadResolvedProofRecords()
|
|
569
|
+
return {
|
|
570
|
+
summary: getProofSummary(records),
|
|
571
|
+
records,
|
|
572
|
+
reviewQueue: records.filter((record) => record.reviewStatus === 'open').slice(0, 12),
|
|
573
|
+
recentVerified: records.filter((record) => record.reviewStatus === 'verified').slice(0, 8),
|
|
574
|
+
recentContested: records.filter((record) => record.reviewStatus === 'contested').slice(0, 8),
|
|
575
|
+
byTargetType: buildTargetBreakdown(records),
|
|
576
|
+
}
|
|
577
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const CURRENT_RELEASE_SPOTLIGHT = {
|
|
2
|
+
version: '0.7.0',
|
|
3
|
+
title: 'What changed in 0.7.0',
|
|
4
|
+
summary:
|
|
5
|
+
'This release turns Prove into a first-class operator stage: HelixEvo now unifies bounded outcome attribution across interventions, semantic adoption, topology execution, transfer, and legacy evolution impact.',
|
|
6
|
+
bullets: [
|
|
7
|
+
'HelixEvo now ships a dedicated Proof route and proof review API instead of treating metrics alone as the full prove surface.',
|
|
8
|
+
'Overview, Co-Evolution, Ontology, and Topology now expose proof visibility and direct handoffs into bounded outcome review.',
|
|
9
|
+
'The operator loop now lands Prove on /proof, keeping navigation, handoffs, Commands, and Guide aligned with the shipped product.',
|
|
10
|
+
'Proof remains deliberately bounded: measuring, mixed, regressed, and insufficient-evidence states stay visible instead of being overclaimed as certainty.',
|
|
11
|
+
],
|
|
12
|
+
nextActions: [
|
|
13
|
+
{ label: 'Open proof control', href: '/proof', tone: 'blue' as const },
|
|
14
|
+
{ label: 'Inspect co-evolution', href: '/coevolution', tone: 'purple' as const },
|
|
15
|
+
{ label: 'Read proof + metrics', href: '/guide#metrics', tone: 'green' as const },
|
|
16
|
+
],
|
|
17
|
+
} as const
|