helixevo 0.3.1 → 0.4.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 +28 -0
- package/README.md +28 -6
- package/dashboard/app/api/governance/route.ts +49 -0
- package/dashboard/app/api/run/route.ts +31 -4
- package/dashboard/app/api/topology-apply/route.ts +80 -0
- package/dashboard/app/api/topology-review/route.ts +51 -0
- package/dashboard/app/coevolution/client.tsx +560 -0
- package/dashboard/app/coevolution/page.tsx +9 -0
- package/dashboard/app/commands/page.tsx +24 -3
- package/dashboard/app/guide/page.tsx +12 -2
- package/dashboard/app/network/client.tsx +76 -2
- package/dashboard/app/page.tsx +46 -0
- package/dashboard/app/projects/client.tsx +83 -10
- package/dashboard/app/projects/page.tsx +8 -27
- package/dashboard/app/research/client.tsx +48 -8
- package/dashboard/app/topology/client.tsx +575 -0
- package/dashboard/app/topology/page.tsx +10 -0
- package/dashboard/components/sidebar-nav.tsx +2 -0
- package/dashboard/lib/data.ts +1301 -3
- package/dist/cli.js +1775 -50
- package/package.json +2 -2
package/dashboard/lib/data.ts
CHANGED
|
@@ -78,14 +78,14 @@ export interface CanaryEntry {
|
|
|
78
78
|
skillSlug: string; version: string; deployedAt: string; expiresAt: string
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
export type ArtifactProvenance = 'native-evolution' | 'derived-history'
|
|
81
|
+
export type ArtifactProvenance = 'native-evolution' | 'native-topology' | 'derived-history'
|
|
82
82
|
export type ActivationProvenance = 'capture-derived' | 'project-analysis' | 'derived-failure'
|
|
83
83
|
export type PressureProvenance = 'failure-native' | 'project-analysis-native' | 'failure-derived' | 'profile-gap-derived'
|
|
84
84
|
|
|
85
85
|
export interface EvidenceArtifact {
|
|
86
86
|
id: string
|
|
87
87
|
createdAt: string
|
|
88
|
-
artifactType: 'evolution' | 'replay' | 'regression' | 'canary' | 'project-analysis' | 'ontology-review'
|
|
88
|
+
artifactType: 'evolution' | 'replay' | 'regression' | 'canary' | 'project-analysis' | 'ontology-review' | 'topology-apply'
|
|
89
89
|
provenance: ArtifactProvenance
|
|
90
90
|
title: string
|
|
91
91
|
summary: string
|
|
@@ -144,12 +144,262 @@ export interface PressureSignal {
|
|
|
144
144
|
region?: string
|
|
145
145
|
description?: string
|
|
146
146
|
suggestedAction?: 'research' | 'specialize' | 'create'
|
|
147
|
+
confidence?: number
|
|
148
|
+
recurrenceCount?: number
|
|
147
149
|
relatedFailureId?: string
|
|
148
150
|
relatedActivationTraceId?: string
|
|
149
151
|
skillIds?: string[]
|
|
150
152
|
status?: 'open' | 'addressed'
|
|
151
153
|
}
|
|
152
154
|
|
|
155
|
+
export type GovernanceModeName = 'conservative' | 'balanced' | 'exploration' | 'project-critical' | 'consolidation-focused' | 'transfer-focused'
|
|
156
|
+
export type GovernanceSelectionMode = 'auto' | 'manual'
|
|
157
|
+
export type GovernanceSource = 'derived' | 'operator-selected'
|
|
158
|
+
export type TransferType = 'specialist-to-generalist' | 'generalist-to-project' | 'cross-project' | 'domain-transfer'
|
|
159
|
+
export type PressureInterventionType = 'research' | 'specialize' | 'evolve' | 'generalize' | 'manual-review'
|
|
160
|
+
export type TopologyReviewSource = 'graph-optimize' | 'network-health' | 'pressure-motif' | 'transfer-pattern' | 'manual-route'
|
|
161
|
+
export type TopologyReviewStatus = 'open' | 'accepted' | 'rejected' | 'deferred'
|
|
162
|
+
export type TopologyReviewDecisionStatus = Exclude<TopologyReviewStatus, 'open'>
|
|
163
|
+
export type TopologyReviewPriority = 'high' | 'medium' | 'low'
|
|
164
|
+
export type PressureInterventionStatus = 'started' | 'completed' | 'failed' | 'dry-run'
|
|
165
|
+
export type PressureInterventionImpact = 'exploratory' | 'structural' | 'resolving' | 'none'
|
|
166
|
+
export type PressureLifecycleState = 'open' | 'in-progress' | 'addressed' | 'stale'
|
|
167
|
+
export type PressureRouteScope = 'local' | 'project' | 'network'
|
|
168
|
+
export type PressureRouteTrigger = 'high-priority-local-gap' | 'project-bounded-recurrence' | 'recurring-cross-project-gap' | 'overlapping-skills' | 'accepted-iteration-pattern' | 'insufficient-evidence' | 'mixed-signal'
|
|
169
|
+
|
|
170
|
+
export interface PressureRouteRecommendation {
|
|
171
|
+
route: PressureInterventionType
|
|
172
|
+
scope: PressureRouteScope
|
|
173
|
+
confidence: number
|
|
174
|
+
governanceMode: GovernanceModeName
|
|
175
|
+
triggeredBy: PressureRouteTrigger
|
|
176
|
+
reasons: string[]
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export interface PressureIntervention {
|
|
180
|
+
id: string
|
|
181
|
+
interventionType: PressureInterventionType
|
|
182
|
+
status: PressureInterventionStatus
|
|
183
|
+
impact: PressureInterventionImpact
|
|
184
|
+
createdAt: string
|
|
185
|
+
completedAt?: string
|
|
186
|
+
sourceId: string
|
|
187
|
+
projectId?: string
|
|
188
|
+
projectPath?: string
|
|
189
|
+
scope?: PressureRouteScope
|
|
190
|
+
governanceMode?: GovernanceModeName
|
|
191
|
+
capabilities?: string[]
|
|
192
|
+
pressureSignalIds?: string[]
|
|
193
|
+
motifIds?: string[]
|
|
194
|
+
relatedFailureIds?: string[]
|
|
195
|
+
relatedActivationTraceIds?: string[]
|
|
196
|
+
relatedProposalIds?: string[]
|
|
197
|
+
relatedArtifactIds?: string[]
|
|
198
|
+
relatedTransferEventIds?: string[]
|
|
199
|
+
relatedSkillSlugs?: string[]
|
|
200
|
+
reasonSummary: string
|
|
201
|
+
notes?: string
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export interface PressureMotif {
|
|
205
|
+
id: string
|
|
206
|
+
key: string
|
|
207
|
+
kind: string
|
|
208
|
+
capability?: string
|
|
209
|
+
description: string
|
|
210
|
+
createdAt: string
|
|
211
|
+
lastSeenAt: string
|
|
212
|
+
projectIds: string[]
|
|
213
|
+
pressureSignalIds: string[]
|
|
214
|
+
sourceSkillIds: string[]
|
|
215
|
+
recurrenceCount: number
|
|
216
|
+
highPriorityCount: number
|
|
217
|
+
linkedInterventionIds: string[]
|
|
218
|
+
linkedTransferEventIds: string[]
|
|
219
|
+
suggestedRoute: PressureRouteRecommendation
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export interface TransferEvent {
|
|
223
|
+
id: string
|
|
224
|
+
timestamp: string
|
|
225
|
+
sourceProjectId?: string
|
|
226
|
+
targetProjectId?: string
|
|
227
|
+
sourceSkillIds: string[]
|
|
228
|
+
targetSkillIds?: string[]
|
|
229
|
+
capabilityIds?: string[]
|
|
230
|
+
motifIds?: string[]
|
|
231
|
+
transferType: TransferType
|
|
232
|
+
evidenceIds?: string[]
|
|
233
|
+
benefitEstimate?: number
|
|
234
|
+
status?: 'candidate' | 'realized' | 'rejected'
|
|
235
|
+
summary?: string
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export interface GovernanceProfile {
|
|
239
|
+
mode: GovernanceModeName
|
|
240
|
+
reviewThreshold: number
|
|
241
|
+
riskTolerance: number
|
|
242
|
+
plasticityBias: number
|
|
243
|
+
explanation: string
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export interface GovernanceState {
|
|
247
|
+
selectionMode: GovernanceSelectionMode
|
|
248
|
+
manualMode?: GovernanceModeName
|
|
249
|
+
updatedAt: string
|
|
250
|
+
note?: string
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export interface ActiveGovernanceSummary {
|
|
254
|
+
activeMode: GovernanceModeName
|
|
255
|
+
derivedMode: GovernanceModeName
|
|
256
|
+
source: GovernanceSource
|
|
257
|
+
manualMode?: GovernanceModeName
|
|
258
|
+
rationale: string[]
|
|
259
|
+
profile: GovernanceProfile
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export interface TopologyReviewCandidate {
|
|
263
|
+
id: string
|
|
264
|
+
reviewKey: string
|
|
265
|
+
createdAt: string
|
|
266
|
+
lastObservedAt: string
|
|
267
|
+
source: TopologyReviewSource
|
|
268
|
+
changeType: 'merge' | 'split' | 'promote' | 'demote' | 'rewire' | 'prune' | 'consolidate'
|
|
269
|
+
title: string
|
|
270
|
+
reasonSummary: string
|
|
271
|
+
description?: string
|
|
272
|
+
priority: TopologyReviewPriority
|
|
273
|
+
confidence: number
|
|
274
|
+
riskScore: number
|
|
275
|
+
governanceMode: GovernanceModeName
|
|
276
|
+
affectedNodeIds: string[]
|
|
277
|
+
affectedRelationIds?: string[]
|
|
278
|
+
projectIds?: string[]
|
|
279
|
+
relatedSignalIds?: string[]
|
|
280
|
+
relatedMotifIds?: string[]
|
|
281
|
+
relatedTransferEventIds?: string[]
|
|
282
|
+
relatedInterventionIds?: string[]
|
|
283
|
+
evidence: string[]
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export interface TopologyReviewDecision {
|
|
287
|
+
id: string
|
|
288
|
+
candidateId: string
|
|
289
|
+
decision: TopologyReviewDecisionStatus
|
|
290
|
+
decidedAt: string
|
|
291
|
+
governanceMode: GovernanceModeName
|
|
292
|
+
rationale: string
|
|
293
|
+
decidedBy?: 'operator' | 'system'
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export interface ResolvedTopologyReviewCandidate extends TopologyReviewCandidate {
|
|
297
|
+
status: TopologyReviewStatus
|
|
298
|
+
latestDecision?: TopologyReviewDecision
|
|
299
|
+
lastActivityAt: string
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export type TopologyExecutionMode = 'prepare-only' | 'apply'
|
|
303
|
+
export type TopologyExecutionStatus = 'prepared' | 'applied' | 'rolled-back' | 'failed'
|
|
304
|
+
|
|
305
|
+
export interface TopologyEdgeRef {
|
|
306
|
+
from: string
|
|
307
|
+
to: string
|
|
308
|
+
type: SkillEdge['type']
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export interface TopologyNodeOverride {
|
|
312
|
+
layer?: SkillNode['layer']
|
|
313
|
+
cognitiveRole?: CognitiveRole
|
|
314
|
+
stabilityState?: StabilityState
|
|
315
|
+
plasticityState?: PlasticityState
|
|
316
|
+
capabilities?: string[]
|
|
317
|
+
lineageId?: string
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export interface TopologyOverrides {
|
|
321
|
+
updatedAt: string
|
|
322
|
+
nodeOverrides: Record<string, TopologyNodeOverride>
|
|
323
|
+
suppressedEdges: TopologyEdgeRef[]
|
|
324
|
+
addedEdges: SkillEdge[]
|
|
325
|
+
appliedPlanIds: string[]
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export interface TopologySnapshotRef {
|
|
329
|
+
id: string
|
|
330
|
+
createdAt: string
|
|
331
|
+
label: string
|
|
332
|
+
candidateId?: string
|
|
333
|
+
planId?: string
|
|
334
|
+
executionRecordId?: string
|
|
335
|
+
snapshotDir: string
|
|
336
|
+
graphPath: string
|
|
337
|
+
overridesPath: string
|
|
338
|
+
skillFileSnapshots: { slug: string; path: string; snapshotPath: string }[]
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export interface TopologyApplyPlan {
|
|
342
|
+
id: string
|
|
343
|
+
candidateId: string
|
|
344
|
+
acceptedDecisionId: string
|
|
345
|
+
createdAt: string
|
|
346
|
+
changeType: ResolvedTopologyReviewCandidate['changeType']
|
|
347
|
+
governanceMode: GovernanceModeName
|
|
348
|
+
safeToApply: boolean
|
|
349
|
+
executionMode: TopologyExecutionMode
|
|
350
|
+
targetSkillSlugs: string[]
|
|
351
|
+
plannedFileChanges: string[]
|
|
352
|
+
plannedGraphChanges: string[]
|
|
353
|
+
plannedNodeOverrides?: Record<string, TopologyNodeOverride>
|
|
354
|
+
plannedEdgeSuppressions?: TopologyEdgeRef[]
|
|
355
|
+
plannedAddedEdges?: SkillEdge[]
|
|
356
|
+
warnings: string[]
|
|
357
|
+
rollbackStrategy: string
|
|
358
|
+
beforeSnapshotId: string
|
|
359
|
+
reasonSummary: string
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export interface TopologyExecutionRecord {
|
|
363
|
+
id: string
|
|
364
|
+
planId: string
|
|
365
|
+
candidateId: string
|
|
366
|
+
changeType: ResolvedTopologyReviewCandidate['changeType']
|
|
367
|
+
status: TopologyExecutionStatus
|
|
368
|
+
createdAt: string
|
|
369
|
+
executedAt?: string
|
|
370
|
+
rolledBackAt?: string
|
|
371
|
+
governanceMode: GovernanceModeName
|
|
372
|
+
beforeSnapshotId: string
|
|
373
|
+
afterSnapshotId?: string
|
|
374
|
+
artifactIds?: string[]
|
|
375
|
+
notes?: string[]
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export interface ResolvedTopologyApplyPlan extends TopologyApplyPlan {
|
|
379
|
+
latestStatus: TopologyExecutionStatus | 'pending'
|
|
380
|
+
latestExecution?: TopologyExecutionRecord
|
|
381
|
+
lastActivityAt: string
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export interface ResolvedPressureSignal extends PressureSignal {
|
|
385
|
+
lifecycle: PressureLifecycleState
|
|
386
|
+
linkedInterventions: PressureIntervention[]
|
|
387
|
+
interventionTypes: PressureInterventionType[]
|
|
388
|
+
motifIds?: string[]
|
|
389
|
+
routeRecommendation?: PressureRouteRecommendation
|
|
390
|
+
lastActivityAt: string
|
|
391
|
+
addressedAt?: string
|
|
392
|
+
responseSummary: string
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export interface ResolvedPressureMotif extends PressureMotif {
|
|
396
|
+
lifecycle: PressureLifecycleState
|
|
397
|
+
interventionTypes: PressureInterventionType[]
|
|
398
|
+
lastActivityAt: string
|
|
399
|
+
addressedAt?: string
|
|
400
|
+
responseSummary: string
|
|
401
|
+
}
|
|
402
|
+
|
|
153
403
|
export interface ProjectPressureSummary {
|
|
154
404
|
projectId: string
|
|
155
405
|
totalSignals: number
|
|
@@ -159,16 +409,92 @@ export interface ProjectPressureSummary {
|
|
|
159
409
|
topCapabilities: string[]
|
|
160
410
|
}
|
|
161
411
|
|
|
412
|
+
export interface ProjectResponseSummary extends ProjectPressureSummary {
|
|
413
|
+
openSignals: number
|
|
414
|
+
inProgressSignals: number
|
|
415
|
+
addressedSignals: number
|
|
416
|
+
staleSignals: number
|
|
417
|
+
activeInterventions: number
|
|
418
|
+
recentInterventionTypes: PressureInterventionType[]
|
|
419
|
+
}
|
|
420
|
+
|
|
162
421
|
export interface CoEvolutionDashboardSummary {
|
|
163
422
|
pressureSignals: {
|
|
164
423
|
total: number
|
|
165
424
|
native: number
|
|
166
425
|
derived: number
|
|
167
426
|
}
|
|
427
|
+
pressureInterventions: {
|
|
428
|
+
total: number
|
|
429
|
+
started: number
|
|
430
|
+
completed: number
|
|
431
|
+
failed: number
|
|
432
|
+
dryRun: number
|
|
433
|
+
}
|
|
434
|
+
pressureLifecycle: {
|
|
435
|
+
open: number
|
|
436
|
+
inProgress: number
|
|
437
|
+
addressed: number
|
|
438
|
+
stale: number
|
|
439
|
+
highPriorityOpen: number
|
|
440
|
+
}
|
|
441
|
+
pressureMotifs: {
|
|
442
|
+
total: number
|
|
443
|
+
open: number
|
|
444
|
+
inProgress: number
|
|
445
|
+
addressed: number
|
|
446
|
+
stale: number
|
|
447
|
+
promotionReady: number
|
|
448
|
+
}
|
|
449
|
+
governance: ActiveGovernanceSummary
|
|
450
|
+
topologyReviews: {
|
|
451
|
+
total: number
|
|
452
|
+
open: number
|
|
453
|
+
accepted: number
|
|
454
|
+
rejected: number
|
|
455
|
+
deferred: number
|
|
456
|
+
highPriorityOpen: number
|
|
457
|
+
generatedFromManualReview: number
|
|
458
|
+
byChangeType: Record<ResolvedTopologyReviewCandidate['changeType'], number>
|
|
459
|
+
}
|
|
460
|
+
topologyExecution: {
|
|
461
|
+
plans: number
|
|
462
|
+
pending: number
|
|
463
|
+
prepared: number
|
|
464
|
+
applied: number
|
|
465
|
+
rolledBack: number
|
|
466
|
+
failed: number
|
|
467
|
+
safeToApply: number
|
|
468
|
+
prepareOnly: number
|
|
469
|
+
snapshots: number
|
|
470
|
+
artifacts: number
|
|
471
|
+
}
|
|
168
472
|
topProjects: ProjectPressureSummary[]
|
|
473
|
+
responseProjects: ProjectResponseSummary[]
|
|
169
474
|
rolePressure: Record<CognitiveRole, number>
|
|
170
475
|
crossProjectGapAreas: { area: string; projects: string[]; count: number }[]
|
|
171
476
|
activeRecommendations: { action: 'research' | 'specialize' | 'create'; count: number }[]
|
|
477
|
+
governedRoutes: { route: PressureInterventionType; total: number }[]
|
|
478
|
+
interventionLanes: { type: PressureInterventionType; total: number; resolving: number; exploratory: number; none: number }[]
|
|
479
|
+
backlog: ResolvedPressureSignal[]
|
|
480
|
+
promoteQueue: ResolvedPressureMotif[]
|
|
481
|
+
recentAddressed: ResolvedPressureSignal[]
|
|
482
|
+
recentInterventions: PressureIntervention[]
|
|
483
|
+
recentTransfers: TransferEvent[]
|
|
484
|
+
topologyBacklog: ResolvedTopologyReviewCandidate[]
|
|
485
|
+
recentTopologyDecisions: ResolvedTopologyReviewCandidate[]
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
export interface TopologyDashboardSummary {
|
|
489
|
+
governance: ActiveGovernanceSummary
|
|
490
|
+
summary: CoEvolutionDashboardSummary['topologyReviews']
|
|
491
|
+
execution: CoEvolutionDashboardSummary['topologyExecution']
|
|
492
|
+
backlog: ResolvedTopologyReviewCandidate[]
|
|
493
|
+
recentDecisions: ResolvedTopologyReviewCandidate[]
|
|
494
|
+
acceptedReady: ResolvedTopologyReviewCandidate[]
|
|
495
|
+
preparedQueue: ResolvedTopologyApplyPlan[]
|
|
496
|
+
appliedHistory: ResolvedTopologyApplyPlan[]
|
|
497
|
+
candidates: ResolvedTopologyReviewCandidate[]
|
|
172
498
|
}
|
|
173
499
|
|
|
174
500
|
export interface OntologyDashboardSummary {
|
|
@@ -197,6 +523,66 @@ export interface OntologyDashboardSummary {
|
|
|
197
523
|
native: number
|
|
198
524
|
derived: number
|
|
199
525
|
}
|
|
526
|
+
pressureInterventions: {
|
|
527
|
+
total: number
|
|
528
|
+
started: number
|
|
529
|
+
completed: number
|
|
530
|
+
failed: number
|
|
531
|
+
dryRun: number
|
|
532
|
+
}
|
|
533
|
+
pressureLifecycle: {
|
|
534
|
+
open: number
|
|
535
|
+
inProgress: number
|
|
536
|
+
addressed: number
|
|
537
|
+
stale: number
|
|
538
|
+
}
|
|
539
|
+
pressureMotifs: {
|
|
540
|
+
total: number
|
|
541
|
+
open: number
|
|
542
|
+
inProgress: number
|
|
543
|
+
addressed: number
|
|
544
|
+
stale: number
|
|
545
|
+
promotionReady: number
|
|
546
|
+
}
|
|
547
|
+
governedRoutes: Record<PressureInterventionType, number>
|
|
548
|
+
transferEvents: {
|
|
549
|
+
total: number
|
|
550
|
+
realized: number
|
|
551
|
+
candidate: number
|
|
552
|
+
rejected: number
|
|
553
|
+
}
|
|
554
|
+
governance: {
|
|
555
|
+
activeMode: GovernanceModeName
|
|
556
|
+
derivedMode: GovernanceModeName
|
|
557
|
+
source: GovernanceSource
|
|
558
|
+
manualMode?: GovernanceModeName
|
|
559
|
+
rationale: string[]
|
|
560
|
+
reviewThreshold: number
|
|
561
|
+
riskTolerance: number
|
|
562
|
+
plasticityBias: number
|
|
563
|
+
}
|
|
564
|
+
topologyReviews: {
|
|
565
|
+
total: number
|
|
566
|
+
open: number
|
|
567
|
+
accepted: number
|
|
568
|
+
rejected: number
|
|
569
|
+
deferred: number
|
|
570
|
+
highPriorityOpen: number
|
|
571
|
+
generatedFromManualReview: number
|
|
572
|
+
byChangeType: Record<ResolvedTopologyReviewCandidate['changeType'], number>
|
|
573
|
+
}
|
|
574
|
+
topologyExecution: {
|
|
575
|
+
plans: number
|
|
576
|
+
pending: number
|
|
577
|
+
prepared: number
|
|
578
|
+
applied: number
|
|
579
|
+
rolledBack: number
|
|
580
|
+
failed: number
|
|
581
|
+
safeToApply: number
|
|
582
|
+
prepareOnly: number
|
|
583
|
+
snapshots: number
|
|
584
|
+
artifacts: number
|
|
585
|
+
}
|
|
200
586
|
annotatedFailures: {
|
|
201
587
|
pressureSignals: number
|
|
202
588
|
capabilityGaps: number
|
|
@@ -233,8 +619,38 @@ function inferPlasticityState(node: SkillNode): PlasticityState {
|
|
|
233
619
|
|
|
234
620
|
// ─── Loaders ────────────────────────────────────────────────────
|
|
235
621
|
|
|
622
|
+
function edgeKey(edge: { from: string; to: string; type: string }): string {
|
|
623
|
+
return `${edge.from}::${edge.to}::${edge.type}`
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
export function loadTopologyOverrides(): TopologyOverrides {
|
|
627
|
+
const overrides = readJson<TopologyOverrides>('topology-overrides.json', { updatedAt: '', nodeOverrides: {}, suppressedEdges: [], addedEdges: [], appliedPlanIds: [] })
|
|
628
|
+
return {
|
|
629
|
+
updatedAt: overrides.updatedAt || '',
|
|
630
|
+
nodeOverrides: overrides.nodeOverrides ?? {},
|
|
631
|
+
suppressedEdges: overrides.suppressedEdges ?? [],
|
|
632
|
+
addedEdges: overrides.addedEdges ?? [],
|
|
633
|
+
appliedPlanIds: overrides.appliedPlanIds ?? [],
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function applyTopologyOverridesToGraph(graph: SkillGraph, overrides: TopologyOverrides = loadTopologyOverrides()): SkillGraph {
|
|
638
|
+
const suppressed = new Set(overrides.suppressedEdges.map((edge) => edgeKey(edge)))
|
|
639
|
+
const edgeMap = new Map<string, SkillEdge>()
|
|
640
|
+
for (const edge of graph.edges) {
|
|
641
|
+
const key = edgeKey(edge)
|
|
642
|
+
if (!suppressed.has(key)) edgeMap.set(key, edge)
|
|
643
|
+
}
|
|
644
|
+
for (const edge of overrides.addedEdges) edgeMap.set(edgeKey(edge), edge)
|
|
645
|
+
return {
|
|
646
|
+
...graph,
|
|
647
|
+
nodes: graph.nodes.map((node) => ({ ...node, ...(overrides.nodeOverrides[node.id] ?? {}) })),
|
|
648
|
+
edges: [...edgeMap.values()],
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
236
652
|
export function loadGraph(): SkillGraph {
|
|
237
|
-
return readJson<SkillGraph>('skill-graph.json', { updated: '', nodes: [], edges: [], clusters: [] })
|
|
653
|
+
return applyTopologyOverridesToGraph(readJson<SkillGraph>('skill-graph.json', { updated: '', nodes: [], edges: [], clusters: [] }))
|
|
238
654
|
}
|
|
239
655
|
|
|
240
656
|
export function loadFailures(): Failure[] {
|
|
@@ -405,19 +821,688 @@ export function loadPressureSignals(): PressureSignal[] {
|
|
|
405
821
|
].sort((a, b) => b.detectedAt.localeCompare(a.detectedAt))
|
|
406
822
|
}
|
|
407
823
|
|
|
824
|
+
export function loadPressureInterventions(): PressureIntervention[] {
|
|
825
|
+
return readJsonl<PressureIntervention>('pressure-interventions.jsonl')
|
|
826
|
+
.slice()
|
|
827
|
+
.sort((a, b) => (b.completedAt ?? b.createdAt).localeCompare(a.completedAt ?? a.createdAt))
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
export function loadTransferEvents(): TransferEvent[] {
|
|
831
|
+
return readJsonl<TransferEvent>('transfer-events.jsonl')
|
|
832
|
+
.slice()
|
|
833
|
+
.sort((a, b) => b.timestamp.localeCompare(a.timestamp))
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
const DEFAULT_GOVERNANCE_STATE: GovernanceState = {
|
|
837
|
+
selectionMode: 'auto',
|
|
838
|
+
updatedAt: new Date(0).toISOString(),
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
const GOVERNANCE_PROFILES: Record<GovernanceModeName, GovernanceProfile> = {
|
|
842
|
+
conservative: { mode: 'conservative', reviewThreshold: 0.86, riskTolerance: 0.28, plasticityBias: 0.26, explanation: 'Prefer stronger evidence and slower structural change until risk falls.' },
|
|
843
|
+
balanced: { mode: 'balanced', reviewThreshold: 0.74, riskTolerance: 0.5, plasticityBias: 0.5, explanation: 'Keep local stabilization and structural adaptation in balance.' },
|
|
844
|
+
exploration: { mode: 'exploration', reviewThreshold: 0.62, riskTolerance: 0.7, plasticityBias: 0.72, explanation: 'Favor evidence gathering and wider structural exploration before locking in.' },
|
|
845
|
+
'project-critical': { mode: 'project-critical', reviewThreshold: 0.82, riskTolerance: 0.35, plasticityBias: 0.34, explanation: 'Bias toward project-bounded stability and cautious structural change.' },
|
|
846
|
+
'consolidation-focused': { mode: 'consolidation-focused', reviewThreshold: 0.78, riskTolerance: 0.42, plasticityBias: 0.44, explanation: 'Favor consolidating proven patterns into stable reusable structure.' },
|
|
847
|
+
'transfer-focused': { mode: 'transfer-focused', reviewThreshold: 0.68, riskTolerance: 0.6, plasticityBias: 0.68, explanation: 'Bias toward cross-project promotion and reusable topology upgrades.' },
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function topologyChangeCounts(): Record<ResolvedTopologyReviewCandidate['changeType'], number> {
|
|
851
|
+
return { merge: 0, split: 0, promote: 0, demote: 0, rewire: 0, prune: 0, consolidate: 0 }
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function maxTimestamp(...values: Array<string | undefined>): string {
|
|
855
|
+
return values.filter(Boolean).sort((a, b) => b!.localeCompare(a!))[0] ?? new Date(0).toISOString()
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function priorityRank(priority: TopologyReviewPriority): number {
|
|
859
|
+
return priority === 'high' ? 3 : priority === 'medium' ? 2 : 1
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
function topologyStatusRank(status: TopologyReviewStatus): number {
|
|
863
|
+
return status === 'open' ? 4 : status === 'deferred' ? 3 : status === 'accepted' ? 2 : 1
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
function governanceProfileForMode(mode: GovernanceModeName): GovernanceProfile {
|
|
867
|
+
return GOVERNANCE_PROFILES[mode]
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
export function loadGovernanceState(): GovernanceState {
|
|
871
|
+
const state = readJson<GovernanceState>('governance-state.json', DEFAULT_GOVERNANCE_STATE)
|
|
872
|
+
if (state.selectionMode === 'manual' && state.manualMode) return state
|
|
873
|
+
return { ...DEFAULT_GOVERNANCE_STATE, ...state, selectionMode: state.selectionMode === 'manual' && state.manualMode ? 'manual' : 'auto' }
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
export function loadStoredTopologyReviewCandidates(): TopologyReviewCandidate[] {
|
|
877
|
+
return readJson<TopologyReviewCandidate[]>('topology-review-candidates.json', [])
|
|
878
|
+
.slice()
|
|
879
|
+
.sort((a, b) => b.lastObservedAt.localeCompare(a.lastObservedAt) || a.title.localeCompare(b.title))
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
export function loadTopologyReviewDecisions(): TopologyReviewDecision[] {
|
|
883
|
+
return readJsonl<TopologyReviewDecision>('topology-review-decisions.jsonl')
|
|
884
|
+
.slice()
|
|
885
|
+
.sort((a, b) => b.decidedAt.localeCompare(a.decidedAt))
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
export function loadResolvedTopologyReviewCandidates(): ResolvedTopologyReviewCandidate[] {
|
|
889
|
+
const candidates = loadStoredTopologyReviewCandidates()
|
|
890
|
+
const latestDecisionByCandidate = new Map<string, TopologyReviewDecision>()
|
|
891
|
+
for (const decision of loadTopologyReviewDecisions()) {
|
|
892
|
+
const existing = latestDecisionByCandidate.get(decision.candidateId)
|
|
893
|
+
if (!existing || decision.decidedAt > existing.decidedAt) latestDecisionByCandidate.set(decision.candidateId, decision)
|
|
894
|
+
}
|
|
895
|
+
return candidates
|
|
896
|
+
.map((candidate) => {
|
|
897
|
+
const latestDecision = latestDecisionByCandidate.get(candidate.id)
|
|
898
|
+
const status: TopologyReviewStatus = latestDecision?.decision ?? 'open'
|
|
899
|
+
return {
|
|
900
|
+
...candidate,
|
|
901
|
+
status,
|
|
902
|
+
latestDecision,
|
|
903
|
+
lastActivityAt: maxTimestamp(candidate.lastObservedAt, latestDecision?.decidedAt),
|
|
904
|
+
} satisfies ResolvedTopologyReviewCandidate
|
|
905
|
+
})
|
|
906
|
+
.sort((a, b) => topologyStatusRank(b.status) - topologyStatusRank(a.status)
|
|
907
|
+
|| priorityRank(b.priority) - priorityRank(a.priority)
|
|
908
|
+
|| b.confidence - a.confidence
|
|
909
|
+
|| b.lastActivityAt.localeCompare(a.lastActivityAt)
|
|
910
|
+
|| a.title.localeCompare(b.title))
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
export function loadTopologySnapshots(): TopologySnapshotRef[] {
|
|
914
|
+
return readJson<TopologySnapshotRef[]>('topology-snapshots.json', [])
|
|
915
|
+
.slice()
|
|
916
|
+
.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
export function loadTopologyApplyPlans(): TopologyApplyPlan[] {
|
|
920
|
+
return readJson<TopologyApplyPlan[]>('topology-apply-plans.json', [])
|
|
921
|
+
.slice()
|
|
922
|
+
.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
export function loadTopologyExecutionRecords(): TopologyExecutionRecord[] {
|
|
926
|
+
return readJsonl<TopologyExecutionRecord>('topology-executions.jsonl')
|
|
927
|
+
.slice()
|
|
928
|
+
.sort((a, b) => (b.executedAt ?? b.rolledBackAt ?? b.createdAt).localeCompare(a.executedAt ?? a.rolledBackAt ?? a.createdAt))
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
export function loadTopologyArtifacts(): EvidenceArtifact[] {
|
|
932
|
+
return readJsonl<EvidenceArtifact>('topology-artifacts.jsonl')
|
|
933
|
+
.slice()
|
|
934
|
+
.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
export function loadResolvedTopologyApplyPlans(): ResolvedTopologyApplyPlan[] {
|
|
938
|
+
const plans = loadTopologyApplyPlans()
|
|
939
|
+
const latestByPlan = new Map<string, TopologyExecutionRecord>()
|
|
940
|
+
for (const record of loadTopologyExecutionRecords()) {
|
|
941
|
+
const existing = latestByPlan.get(record.planId)
|
|
942
|
+
const recordAt = record.executedAt ?? record.rolledBackAt ?? record.createdAt
|
|
943
|
+
const existingAt = existing ? (existing.executedAt ?? existing.rolledBackAt ?? existing.createdAt) : ''
|
|
944
|
+
if (!existing || recordAt > existingAt) latestByPlan.set(record.planId, record)
|
|
945
|
+
}
|
|
946
|
+
return plans
|
|
947
|
+
.map((plan) => {
|
|
948
|
+
const latestExecution = latestByPlan.get(plan.id)
|
|
949
|
+
return {
|
|
950
|
+
...plan,
|
|
951
|
+
latestStatus: latestExecution?.status ?? 'pending',
|
|
952
|
+
latestExecution,
|
|
953
|
+
lastActivityAt: maxTimestamp(plan.createdAt, latestExecution?.executedAt, latestExecution?.rolledBackAt, latestExecution?.createdAt),
|
|
954
|
+
} satisfies ResolvedTopologyApplyPlan
|
|
955
|
+
})
|
|
956
|
+
.sort((a, b) => (b.lastActivityAt.localeCompare(a.lastActivityAt)) || (b.safeToApply === a.safeToApply ? 0 : b.safeToApply ? 1 : -1))
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
export function getTopologyExecutionSummary() {
|
|
960
|
+
const plans = loadResolvedTopologyApplyPlans()
|
|
961
|
+
const snapshots = loadTopologySnapshots()
|
|
962
|
+
const artifacts = loadTopologyArtifacts()
|
|
963
|
+
const plannedCandidateIds = new Set(plans.map((plan) => plan.candidateId))
|
|
964
|
+
const acceptedReady = loadResolvedTopologyReviewCandidates()
|
|
965
|
+
.filter((candidate) => candidate.status === 'accepted' && !plannedCandidateIds.has(candidate.id))
|
|
966
|
+
.slice(0, 8)
|
|
967
|
+
return {
|
|
968
|
+
plans: plans.length,
|
|
969
|
+
pending: plans.filter((plan) => plan.latestStatus === 'pending').length,
|
|
970
|
+
prepared: plans.filter((plan) => plan.latestStatus === 'prepared').length,
|
|
971
|
+
applied: plans.filter((plan) => plan.latestStatus === 'applied').length,
|
|
972
|
+
rolledBack: plans.filter((plan) => plan.latestStatus === 'rolled-back').length,
|
|
973
|
+
failed: plans.filter((plan) => plan.latestStatus === 'failed').length,
|
|
974
|
+
safeToApply: plans.filter((plan) => plan.safeToApply).length,
|
|
975
|
+
prepareOnly: plans.filter((plan) => plan.executionMode === 'prepare-only').length,
|
|
976
|
+
snapshots: snapshots.length,
|
|
977
|
+
artifacts: artifacts.length,
|
|
978
|
+
acceptedReady,
|
|
979
|
+
preparedQueue: plans.filter((plan) => plan.latestStatus === 'prepared').slice(0, 8),
|
|
980
|
+
appliedHistory: plans.filter((plan) => plan.latestStatus === 'applied' || plan.latestStatus === 'rolled-back').slice(0, 8),
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
export function getTopologyReviewSummary() {
|
|
985
|
+
const candidates = loadResolvedTopologyReviewCandidates()
|
|
986
|
+
const byChangeType = topologyChangeCounts()
|
|
987
|
+
for (const candidate of candidates) byChangeType[candidate.changeType] += 1
|
|
988
|
+
return {
|
|
989
|
+
total: candidates.length,
|
|
990
|
+
open: candidates.filter((candidate) => candidate.status === 'open').length,
|
|
991
|
+
accepted: candidates.filter((candidate) => candidate.status === 'accepted').length,
|
|
992
|
+
rejected: candidates.filter((candidate) => candidate.status === 'rejected').length,
|
|
993
|
+
deferred: candidates.filter((candidate) => candidate.status === 'deferred').length,
|
|
994
|
+
highPriorityOpen: candidates.filter((candidate) => candidate.status === 'open' && candidate.priority === 'high').length,
|
|
995
|
+
generatedFromManualReview: candidates.filter((candidate) => candidate.source === 'manual-route').length,
|
|
996
|
+
byChangeType,
|
|
997
|
+
backlog: candidates.filter((candidate) => candidate.status === 'open' || candidate.status === 'deferred').slice(0, 10),
|
|
998
|
+
recentDecisions: candidates.filter((candidate) => candidate.status !== 'open').slice(0, 8),
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
export function loadTopologyDashboardSummary(): TopologyDashboardSummary {
|
|
1003
|
+
const governance = getActiveGovernanceSummary()
|
|
1004
|
+
const summary = getTopologyReviewSummary()
|
|
1005
|
+
const execution = getTopologyExecutionSummary()
|
|
1006
|
+
const candidates = loadResolvedTopologyReviewCandidates()
|
|
1007
|
+
return {
|
|
1008
|
+
governance,
|
|
1009
|
+
summary,
|
|
1010
|
+
execution,
|
|
1011
|
+
backlog: summary.backlog,
|
|
1012
|
+
recentDecisions: summary.recentDecisions,
|
|
1013
|
+
acceptedReady: execution.acceptedReady,
|
|
1014
|
+
preparedQueue: execution.preparedQueue,
|
|
1015
|
+
appliedHistory: execution.appliedHistory,
|
|
1016
|
+
candidates,
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
function normalizePressureValue(value: string | undefined): string {
|
|
1021
|
+
return (value ?? '')
|
|
1022
|
+
.trim()
|
|
1023
|
+
.toLowerCase()
|
|
1024
|
+
.replace(/[\s_/]+/g, '-')
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
function pressureRegionKey(signal: Pick<PressureSignal, 'capability' | 'region' | 'kind'>): string {
|
|
1028
|
+
return normalizePressureValue(signal.capability ?? signal.region ?? signal.kind) || 'general-pressure'
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
function motifIdForSignal(signal: Pick<PressureSignal, 'capability' | 'region' | 'kind'>): string {
|
|
1032
|
+
return `motif_${pressureRegionKey(signal)}`
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
function pressureComparisonKey(signal: PressureSignal): string {
|
|
1036
|
+
const projectKey = normalizePressureValue(signal.projectId ?? signal.projectPath ?? 'global')
|
|
1037
|
+
return `${projectKey}::${pressureRegionKey(signal)}`
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
function interventionComparisonKeys(intervention: PressureIntervention): string[] {
|
|
1041
|
+
const projectKey = normalizePressureValue(intervention.projectId ?? intervention.projectPath ?? 'global')
|
|
1042
|
+
const capabilityKeys = (intervention.capabilities?.length ?? 0) > 0
|
|
1043
|
+
? intervention.capabilities ?? []
|
|
1044
|
+
: ['']
|
|
1045
|
+
return capabilityKeys.map((capability) => `${projectKey}::${normalizePressureValue(capability)}`)
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
function signalMatchesInterventionFallback(signal: PressureSignal, intervention: PressureIntervention): boolean {
|
|
1049
|
+
if (intervention.pressureSignalIds?.includes(signal.id)) return true
|
|
1050
|
+
if (intervention.scope === 'network') return false
|
|
1051
|
+
if (signal.relatedFailureId && intervention.relatedFailureIds?.includes(signal.relatedFailureId)) return true
|
|
1052
|
+
if (signal.relatedActivationTraceId && intervention.relatedActivationTraceIds?.includes(signal.relatedActivationTraceId)) return true
|
|
1053
|
+
|
|
1054
|
+
const signalKey = pressureComparisonKey(signal)
|
|
1055
|
+
if (interventionComparisonKeys(intervention).includes(signalKey)) return true
|
|
1056
|
+
|
|
1057
|
+
if (signal.projectId && intervention.projectId && signal.projectId !== intervention.projectId) return false
|
|
1058
|
+
if (signal.projectPath && intervention.projectPath && signal.projectPath !== intervention.projectPath) return false
|
|
1059
|
+
|
|
1060
|
+
if (signal.capability && (intervention.capabilities?.length ?? 0) > 0) {
|
|
1061
|
+
return intervention.capabilities?.some((capability) => normalizePressureValue(capability) === normalizePressureValue(signal.capability)) ?? false
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
return false
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
function motifMatchesIntervention(motifId: string, signals: ResolvedPressureSignal[], intervention: PressureIntervention): boolean {
|
|
1068
|
+
if (intervention.motifIds?.includes(motifId)) return true
|
|
1069
|
+
if (signals.some((signal) => intervention.pressureSignalIds?.includes(signal.id))) return true
|
|
1070
|
+
if (intervention.scope === 'network') {
|
|
1071
|
+
const signalKeys = new Set(signals.map((signal) => pressureRegionKey(signal)))
|
|
1072
|
+
return (intervention.capabilities ?? []).some((capability) => signalKeys.has(normalizePressureValue(capability)))
|
|
1073
|
+
}
|
|
1074
|
+
return false
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
function transferMatchesMotif(motifId: string, signals: ResolvedPressureSignal[], event: TransferEvent): boolean {
|
|
1078
|
+
if (event.motifIds?.includes(motifId)) return true
|
|
1079
|
+
const capabilities = new Set(signals.map((signal) => normalizePressureValue(signal.capability ?? signal.kind)).filter(Boolean))
|
|
1080
|
+
return (event.capabilityIds ?? []).some((capability) => capabilities.has(normalizePressureValue(capability)))
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
function deriveGovernanceSummary(
|
|
1084
|
+
pressureSignals: PressureSignal[] = loadPressureSignals(),
|
|
1085
|
+
interventions: PressureIntervention[] = loadPressureInterventions(),
|
|
1086
|
+
): ActiveGovernanceSummary {
|
|
1087
|
+
const highPrioritySignals = pressureSignals.filter((signal) => signal.priority === 'high' && signal.status !== 'addressed').length
|
|
1088
|
+
const recurringCrossProjectRegions = new Map<string, Set<string>>()
|
|
1089
|
+
for (const signal of pressureSignals) {
|
|
1090
|
+
const projectId = signal.projectId ?? signal.projectPath
|
|
1091
|
+
if (!projectId) continue
|
|
1092
|
+
const projects = recurringCrossProjectRegions.get(pressureRegionKey(signal)) ?? new Set<string>()
|
|
1093
|
+
projects.add(projectId)
|
|
1094
|
+
recurringCrossProjectRegions.set(pressureRegionKey(signal), projects)
|
|
1095
|
+
}
|
|
1096
|
+
const crossProjectMotifs = [...recurringCrossProjectRegions.values()].filter((projects) => projects.size >= 2).length
|
|
1097
|
+
const exploratoryRuns = interventions.filter((intervention) => intervention.status === 'dry-run' || intervention.impact === 'exploratory').length
|
|
1098
|
+
const failedRuns = interventions.filter((intervention) => intervention.status === 'failed').length
|
|
1099
|
+
const resolvingRuns = interventions.filter((intervention) => intervention.status === 'completed' && intervention.impact === 'resolving').length
|
|
1100
|
+
|
|
1101
|
+
let derivedMode: GovernanceModeName = 'balanced'
|
|
1102
|
+
let derivedRationale: string[] = [
|
|
1103
|
+
'Current pressure is mixed across local response and broader transfer opportunities.',
|
|
1104
|
+
'Governance should keep local adaptation and cross-project learning in balance.',
|
|
1105
|
+
]
|
|
1106
|
+
|
|
1107
|
+
if (crossProjectMotifs >= 2) {
|
|
1108
|
+
derivedMode = 'transfer-focused'
|
|
1109
|
+
derivedRationale = [
|
|
1110
|
+
`${crossProjectMotifs} recurring pressure motifs now span multiple projects.`,
|
|
1111
|
+
'Promoting reusable abstractions has higher leverage than only local adaptation right now.',
|
|
1112
|
+
]
|
|
1113
|
+
} else if (highPrioritySignals >= 4) {
|
|
1114
|
+
derivedMode = 'project-critical'
|
|
1115
|
+
derivedRationale = [
|
|
1116
|
+
`${highPrioritySignals} high-priority pressure signals still need response.`,
|
|
1117
|
+
'Governance should bias toward project-bounded stabilization before broader promotion work.',
|
|
1118
|
+
]
|
|
1119
|
+
} else if (exploratoryRuns > resolvingRuns + 1) {
|
|
1120
|
+
derivedMode = 'exploration'
|
|
1121
|
+
derivedRationale = [
|
|
1122
|
+
`${exploratoryRuns} exploratory responses outweigh resolving outcomes.`,
|
|
1123
|
+
'The network is still learning where the strongest durable response should be routed.',
|
|
1124
|
+
]
|
|
1125
|
+
} else if (failedRuns >= 2 && resolvingRuns === 0) {
|
|
1126
|
+
derivedMode = 'conservative'
|
|
1127
|
+
derivedRationale = [
|
|
1128
|
+
`${failedRuns} recent response attempts failed without resolving evidence.`,
|
|
1129
|
+
'Governance should slow promotion and prefer clearer evidence before acting broadly.',
|
|
1130
|
+
]
|
|
1131
|
+
} else if (resolvingRuns >= 3 && highPrioritySignals <= 2) {
|
|
1132
|
+
derivedMode = 'consolidation-focused'
|
|
1133
|
+
derivedRationale = [
|
|
1134
|
+
`${resolvingRuns} resolving responses already landed recently.`,
|
|
1135
|
+
'The network is in a good position to consolidate what it has learned.',
|
|
1136
|
+
]
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
const state = loadGovernanceState()
|
|
1140
|
+
if (state.selectionMode === 'manual' && state.manualMode) {
|
|
1141
|
+
return {
|
|
1142
|
+
activeMode: state.manualMode,
|
|
1143
|
+
derivedMode,
|
|
1144
|
+
source: 'operator-selected',
|
|
1145
|
+
manualMode: state.manualMode,
|
|
1146
|
+
rationale: [
|
|
1147
|
+
`Operator selected ${state.manualMode} governance on ${state.updatedAt}.`,
|
|
1148
|
+
`Derived mode would currently be ${derivedMode}.`,
|
|
1149
|
+
...derivedRationale,
|
|
1150
|
+
],
|
|
1151
|
+
profile: governanceProfileForMode(state.manualMode),
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
return {
|
|
1156
|
+
activeMode: derivedMode,
|
|
1157
|
+
derivedMode,
|
|
1158
|
+
source: 'derived',
|
|
1159
|
+
rationale: derivedRationale,
|
|
1160
|
+
profile: governanceProfileForMode(derivedMode),
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
export function getActiveGovernanceSummary(): ActiveGovernanceSummary {
|
|
1165
|
+
return deriveGovernanceSummary()
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
function buildRouteRecommendation(params: {
|
|
1169
|
+
signal: PressureSignal
|
|
1170
|
+
relatedSignals: PressureSignal[]
|
|
1171
|
+
linkedInterventions: PressureIntervention[]
|
|
1172
|
+
governanceMode: GovernanceModeName
|
|
1173
|
+
}): PressureRouteRecommendation {
|
|
1174
|
+
const { signal, relatedSignals, linkedInterventions, governanceMode } = params
|
|
1175
|
+
const projectSpread = new Set(relatedSignals.map((entry) => entry.projectId ?? entry.projectPath).filter(Boolean)).size
|
|
1176
|
+
const recurrenceCount = relatedSignals.length
|
|
1177
|
+
const activeTypes = [...new Set(linkedInterventions.map((intervention) => intervention.interventionType))]
|
|
1178
|
+
const reasons: string[] = []
|
|
1179
|
+
|
|
1180
|
+
if (activeTypes.length >= 3 && !linkedInterventions.some((intervention) => intervention.status === 'completed' && intervention.impact === 'resolving')) {
|
|
1181
|
+
reasons.push('Multiple intervention lanes already touch this pressure region without clear closure.')
|
|
1182
|
+
reasons.push('Operator review is safer than blindly piling on another automated response.')
|
|
1183
|
+
return {
|
|
1184
|
+
route: 'manual-review',
|
|
1185
|
+
scope: projectSpread >= 2 ? 'network' : signal.projectId ? 'project' : 'local',
|
|
1186
|
+
confidence: 0.58,
|
|
1187
|
+
governanceMode,
|
|
1188
|
+
triggeredBy: 'mixed-signal',
|
|
1189
|
+
reasons,
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
if (projectSpread >= 2 && recurrenceCount >= 2) {
|
|
1194
|
+
reasons.push(`This pressure region repeats across ${projectSpread} projects.`)
|
|
1195
|
+
reasons.push('A network-level abstraction can absorb repeated local demand more efficiently than one-off fixes.')
|
|
1196
|
+
if (governanceMode !== 'project-critical') {
|
|
1197
|
+
return {
|
|
1198
|
+
route: 'generalize',
|
|
1199
|
+
scope: 'network',
|
|
1200
|
+
confidence: Math.min(0.95, 0.7 + Math.min(0.15, (projectSpread - 1) * 0.08)),
|
|
1201
|
+
governanceMode,
|
|
1202
|
+
triggeredBy: 'recurring-cross-project-gap',
|
|
1203
|
+
reasons,
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
if (signal.projectId && signal.priority === 'high' && signal.suggestedAction === 'specialize') {
|
|
1209
|
+
reasons.push('The pressure is high priority and bounded to a known project context.')
|
|
1210
|
+
reasons.push('Project-layer adaptation is the shortest truthful path to response.')
|
|
1211
|
+
return {
|
|
1212
|
+
route: 'specialize',
|
|
1213
|
+
scope: 'project',
|
|
1214
|
+
confidence: 0.82,
|
|
1215
|
+
governanceMode,
|
|
1216
|
+
triggeredBy: 'high-priority-local-gap',
|
|
1217
|
+
reasons,
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
if (signal.relatedFailureId && signal.severity >= 0.8) {
|
|
1222
|
+
reasons.push('This pressure is tied to a concrete failure with strong corrective evidence.')
|
|
1223
|
+
reasons.push('An evolution proposal can test a direct skill-level response against replay and regression evidence.')
|
|
1224
|
+
return {
|
|
1225
|
+
route: 'evolve',
|
|
1226
|
+
scope: signal.projectId ? 'project' : 'local',
|
|
1227
|
+
confidence: 0.78,
|
|
1228
|
+
governanceMode,
|
|
1229
|
+
triggeredBy: 'accepted-iteration-pattern',
|
|
1230
|
+
reasons,
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
if (governanceMode === 'exploration' || signal.suggestedAction === 'research' || !signal.projectId) {
|
|
1235
|
+
reasons.push('The pressure still looks under-specified or capability-oriented.')
|
|
1236
|
+
reasons.push('Research is the best way to widen evidence before committing to structural change.')
|
|
1237
|
+
return {
|
|
1238
|
+
route: 'research',
|
|
1239
|
+
scope: signal.projectId ? 'project' : 'local',
|
|
1240
|
+
confidence: 0.68,
|
|
1241
|
+
governanceMode,
|
|
1242
|
+
triggeredBy: 'insufficient-evidence',
|
|
1243
|
+
reasons,
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
if (signal.projectId) {
|
|
1248
|
+
reasons.push('The pressure is project-scoped and can likely be handled without network-wide promotion yet.')
|
|
1249
|
+
reasons.push('Specialization is the most direct bounded intervention available.')
|
|
1250
|
+
return {
|
|
1251
|
+
route: 'specialize',
|
|
1252
|
+
scope: 'project',
|
|
1253
|
+
confidence: 0.7,
|
|
1254
|
+
governanceMode,
|
|
1255
|
+
triggeredBy: 'project-bounded-recurrence',
|
|
1256
|
+
reasons,
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
reasons.push('No single route is dominant from the current evidence.')
|
|
1261
|
+
reasons.push('Research keeps the loop moving while preserving optionality.')
|
|
1262
|
+
return {
|
|
1263
|
+
route: 'research',
|
|
1264
|
+
scope: 'local',
|
|
1265
|
+
confidence: 0.6,
|
|
1266
|
+
governanceMode,
|
|
1267
|
+
triggeredBy: 'insufficient-evidence',
|
|
1268
|
+
reasons,
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
function interventionEvidenceAccepted(
|
|
1273
|
+
intervention: PressureIntervention,
|
|
1274
|
+
acceptedProposalIds: Set<string>,
|
|
1275
|
+
acceptedArtifactIds: Set<string>,
|
|
1276
|
+
realizedTransferEventIds: Set<string>,
|
|
1277
|
+
): boolean {
|
|
1278
|
+
if (intervention.status !== 'completed' || intervention.impact !== 'resolving') return false
|
|
1279
|
+
if (intervention.relatedTransferEventIds?.some((id) => realizedTransferEventIds.has(id))) return true
|
|
1280
|
+
if (intervention.relatedProposalIds?.some((id) => acceptedProposalIds.has(id))) {
|
|
1281
|
+
return !(intervention.interventionType === 'generalize' && intervention.scope === 'network')
|
|
1282
|
+
}
|
|
1283
|
+
if (intervention.relatedArtifactIds?.some((id) => acceptedArtifactIds.has(id))) {
|
|
1284
|
+
return !(intervention.interventionType === 'generalize' && intervention.scope === 'network')
|
|
1285
|
+
}
|
|
1286
|
+
if (intervention.interventionType !== 'generalize') return Boolean(intervention.relatedSkillSlugs?.length)
|
|
1287
|
+
return false
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
function interventionKeepsPressureActive(impact: PressureInterventionImpact, status: PressureInterventionStatus): boolean {
|
|
1291
|
+
if (status === 'failed' || status === 'dry-run') return false
|
|
1292
|
+
return impact === 'exploratory' || impact === 'structural' || impact === 'resolving'
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
export function loadResolvedPressureSignals(): ResolvedPressureSignal[] {
|
|
1296
|
+
const pressureSignals = loadPressureSignals()
|
|
1297
|
+
const interventions = loadPressureInterventions()
|
|
1298
|
+
const transferEvents = loadTransferEvents()
|
|
1299
|
+
const history = loadHistory()
|
|
1300
|
+
const artifacts = loadEvolutionArtifacts()
|
|
1301
|
+
const governance = deriveGovernanceSummary(pressureSignals, interventions)
|
|
1302
|
+
const acceptedProposalIds = new Set(
|
|
1303
|
+
history.iterations.flatMap((iteration) => iteration.proposals)
|
|
1304
|
+
.filter((proposal) => proposal.outcome === 'accepted')
|
|
1305
|
+
.map((proposal) => proposal.id),
|
|
1306
|
+
)
|
|
1307
|
+
const acceptedArtifactIds = new Set(
|
|
1308
|
+
artifacts.filter((artifact) => artifact.outcome === 'accepted').map((artifact) => artifact.id),
|
|
1309
|
+
)
|
|
1310
|
+
const realizedTransferEventIds = new Set(
|
|
1311
|
+
transferEvents.filter((event) => event.status === 'realized').map((event) => event.id),
|
|
1312
|
+
)
|
|
1313
|
+
const latestSignalByKey = new Map<string, string>()
|
|
1314
|
+
const signalsByRegion = new Map<string, PressureSignal[]>()
|
|
1315
|
+
|
|
1316
|
+
for (const signal of pressureSignals) {
|
|
1317
|
+
const comparisonKey = pressureComparisonKey(signal)
|
|
1318
|
+
const existing = latestSignalByKey.get(comparisonKey)
|
|
1319
|
+
if (!existing || signal.detectedAt > existing) {
|
|
1320
|
+
latestSignalByKey.set(comparisonKey, signal.detectedAt)
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
const regionKey = pressureRegionKey(signal)
|
|
1324
|
+
const regionSignals = signalsByRegion.get(regionKey) ?? []
|
|
1325
|
+
regionSignals.push(signal)
|
|
1326
|
+
signalsByRegion.set(regionKey, regionSignals)
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
return pressureSignals.map((signal) => {
|
|
1330
|
+
const linkedInterventions = interventions.filter((intervention) => signalMatchesInterventionFallback(signal, intervention))
|
|
1331
|
+
const addressingInterventions = linkedInterventions.filter((intervention) => interventionEvidenceAccepted(intervention, acceptedProposalIds, acceptedArtifactIds, realizedTransferEventIds))
|
|
1332
|
+
const activeInterventions = linkedInterventions.filter((intervention) => interventionKeepsPressureActive(intervention.impact, intervention.status))
|
|
1333
|
+
const latestInterventionAt = linkedInterventions.map((intervention) => intervention.completedAt ?? intervention.createdAt).sort((a, b) => b.localeCompare(a))[0]
|
|
1334
|
+
const latestSignalAt = latestSignalByKey.get(pressureComparisonKey(signal)) ?? signal.detectedAt
|
|
1335
|
+
const hasNewerEquivalentSignal = latestSignalAt > signal.detectedAt
|
|
1336
|
+
const relatedSignals = signalsByRegion.get(pressureRegionKey(signal)) ?? [signal]
|
|
1337
|
+
const projectSpread = new Set(relatedSignals.map((entry) => entry.projectId ?? entry.projectPath).filter(Boolean)).size
|
|
1338
|
+
const motifIds = relatedSignals.length >= 2 || projectSpread >= 2 ? [motifIdForSignal(signal)] : []
|
|
1339
|
+
const routeRecommendation = buildRouteRecommendation({
|
|
1340
|
+
signal,
|
|
1341
|
+
relatedSignals,
|
|
1342
|
+
linkedInterventions,
|
|
1343
|
+
governanceMode: governance.activeMode,
|
|
1344
|
+
})
|
|
1345
|
+
|
|
1346
|
+
const lifecycle: PressureLifecycleState = addressingInterventions.length > 0 || signal.status === 'addressed'
|
|
1347
|
+
? 'addressed'
|
|
1348
|
+
: activeInterventions.length > 0
|
|
1349
|
+
? 'in-progress'
|
|
1350
|
+
: hasNewerEquivalentSignal
|
|
1351
|
+
? 'stale'
|
|
1352
|
+
: 'open'
|
|
1353
|
+
|
|
1354
|
+
const lastActivityAt = [signal.detectedAt, latestInterventionAt].filter(Boolean).sort((a, b) => b.localeCompare(a))[0] ?? signal.detectedAt
|
|
1355
|
+
const interventionTypes = [...new Set(linkedInterventions.map((intervention) => intervention.interventionType))]
|
|
1356
|
+
const addressedAt = addressingInterventions.map((intervention) => intervention.completedAt ?? intervention.createdAt).sort((a, b) => b.localeCompare(a))[0]
|
|
1357
|
+
const responseSummary = lifecycle === 'addressed'
|
|
1358
|
+
? `Addressed by ${interventionTypes.join(', ') || 'linked intervention'} evidence`
|
|
1359
|
+
: lifecycle === 'in-progress'
|
|
1360
|
+
? `Response underway via ${interventionTypes.join(', ')}`
|
|
1361
|
+
: lifecycle === 'stale'
|
|
1362
|
+
? 'Superseded by newer pressure in the same project/capability region'
|
|
1363
|
+
: `No linked response yet • recommend ${routeRecommendation.route}`
|
|
1364
|
+
|
|
1365
|
+
return {
|
|
1366
|
+
...signal,
|
|
1367
|
+
lifecycle,
|
|
1368
|
+
linkedInterventions,
|
|
1369
|
+
interventionTypes,
|
|
1370
|
+
motifIds,
|
|
1371
|
+
routeRecommendation,
|
|
1372
|
+
lastActivityAt,
|
|
1373
|
+
addressedAt,
|
|
1374
|
+
responseSummary,
|
|
1375
|
+
}
|
|
1376
|
+
})
|
|
1377
|
+
}
|
|
1378
|
+
|
|
408
1379
|
function projectRoleForSkill(skillId: string, graph: SkillGraph): CognitiveRole {
|
|
409
1380
|
const node = graph.nodes.find((entry) => entry.id === skillId)
|
|
410
1381
|
if (!node) return 'hybrid'
|
|
411
1382
|
return inferCognitiveRole(node)
|
|
412
1383
|
}
|
|
413
1384
|
|
|
1385
|
+
export function loadPressureMotifs(): ResolvedPressureMotif[] {
|
|
1386
|
+
const resolvedSignals = loadResolvedPressureSignals()
|
|
1387
|
+
const interventions = loadPressureInterventions()
|
|
1388
|
+
const transferEvents = loadTransferEvents()
|
|
1389
|
+
const governance = deriveGovernanceSummary()
|
|
1390
|
+
const groups = new Map<string, ResolvedPressureSignal[]>()
|
|
1391
|
+
|
|
1392
|
+
for (const signal of resolvedSignals) {
|
|
1393
|
+
const regionKey = pressureRegionKey(signal)
|
|
1394
|
+
const entries = groups.get(regionKey) ?? []
|
|
1395
|
+
entries.push(signal)
|
|
1396
|
+
groups.set(regionKey, entries)
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
const motifs: ResolvedPressureMotif[] = []
|
|
1400
|
+
|
|
1401
|
+
for (const [regionKey, signals] of groups.entries()) {
|
|
1402
|
+
const projectIds = [...new Set(signals.map((signal) => signal.projectId ?? signal.projectPath).filter(Boolean))] as string[]
|
|
1403
|
+
if (signals.length < 2 && projectIds.length < 2) continue
|
|
1404
|
+
|
|
1405
|
+
const motifId = `motif_${regionKey}`
|
|
1406
|
+
const linkedInterventions = interventions.filter((intervention) => motifMatchesIntervention(motifId, signals, intervention))
|
|
1407
|
+
const linkedTransferEvents = transferEvents.filter((event) => transferMatchesMotif(motifId, signals, event))
|
|
1408
|
+
const activeInterventions = linkedInterventions.filter((intervention) => interventionKeepsPressureActive(intervention.impact, intervention.status))
|
|
1409
|
+
const realizedTransfers = linkedTransferEvents.filter((event) => event.status === 'realized')
|
|
1410
|
+
const allAddressed = signals.every((signal) => signal.lifecycle === 'addressed')
|
|
1411
|
+
const allStale = signals.every((signal) => signal.lifecycle === 'stale')
|
|
1412
|
+
const lifecycle: PressureLifecycleState = realizedTransfers.length > 0 || allAddressed
|
|
1413
|
+
? 'addressed'
|
|
1414
|
+
: activeInterventions.length > 0
|
|
1415
|
+
? 'in-progress'
|
|
1416
|
+
: allStale
|
|
1417
|
+
? 'stale'
|
|
1418
|
+
: 'open'
|
|
1419
|
+
const interventionTypes = [...new Set(linkedInterventions.map((intervention) => intervention.interventionType))]
|
|
1420
|
+
const recommendation = signals.find((signal) => signal.routeRecommendation?.route === 'generalize')?.routeRecommendation
|
|
1421
|
+
?? signals[0]?.routeRecommendation
|
|
1422
|
+
?? {
|
|
1423
|
+
route: 'research',
|
|
1424
|
+
scope: projectIds.length >= 2 ? 'network' : 'project',
|
|
1425
|
+
confidence: 0.5,
|
|
1426
|
+
governanceMode: governance.activeMode,
|
|
1427
|
+
triggeredBy: 'insufficient-evidence',
|
|
1428
|
+
reasons: ['Recurring pressure exists, but routing evidence remains coarse.'],
|
|
1429
|
+
}
|
|
1430
|
+
const lastActivityAt = [
|
|
1431
|
+
...signals.map((signal) => signal.lastActivityAt),
|
|
1432
|
+
...linkedInterventions.map((intervention) => intervention.completedAt ?? intervention.createdAt),
|
|
1433
|
+
...linkedTransferEvents.map((event) => event.timestamp),
|
|
1434
|
+
].sort((a, b) => b.localeCompare(a))[0] ?? signals[0]?.lastActivityAt ?? new Date(0).toISOString()
|
|
1435
|
+
const addressedAt = [
|
|
1436
|
+
...realizedTransfers.map((event) => event.timestamp),
|
|
1437
|
+
...signals.map((signal) => signal.addressedAt).filter(Boolean) as string[],
|
|
1438
|
+
].sort((a, b) => b.localeCompare(a))[0]
|
|
1439
|
+
const capability = signals.find((signal) => Boolean(signal.capability))?.capability
|
|
1440
|
+
const description = signals.find((signal) => Boolean(signal.description))?.description
|
|
1441
|
+
?? `Recurring pressure in ${capability ?? regionKey}`
|
|
1442
|
+
const sourceSkillIds = [...new Set(signals.flatMap((signal) => signal.skillIds ?? []))]
|
|
1443
|
+
const linkedInterventionIds = linkedInterventions.map((intervention) => intervention.id)
|
|
1444
|
+
const linkedTransferEventIds = linkedTransferEvents.map((event) => event.id)
|
|
1445
|
+
const highPriorityCount = signals.filter((signal) => signal.priority === 'high').length
|
|
1446
|
+
const responseSummary = lifecycle === 'addressed'
|
|
1447
|
+
? realizedTransfers.length > 0
|
|
1448
|
+
? 'Promoted into reusable transfer evidence'
|
|
1449
|
+
: 'All linked pressure in this recurring region is addressed'
|
|
1450
|
+
: lifecycle === 'in-progress'
|
|
1451
|
+
? `Promotion or response underway via ${interventionTypes.join(', ') || 'linked intervention'}`
|
|
1452
|
+
: lifecycle === 'stale'
|
|
1453
|
+
? 'Recurring pressure has been superseded by newer equivalent signals'
|
|
1454
|
+
: `Recurring demand is waiting for ${recommendation.route}`
|
|
1455
|
+
|
|
1456
|
+
motifs.push({
|
|
1457
|
+
id: motifId,
|
|
1458
|
+
key: regionKey,
|
|
1459
|
+
kind: signals[0]?.kind ?? regionKey,
|
|
1460
|
+
capability,
|
|
1461
|
+
description,
|
|
1462
|
+
createdAt: signals.map((signal) => signal.detectedAt).sort()[0] ?? lastActivityAt,
|
|
1463
|
+
lastSeenAt: signals.map((signal) => signal.detectedAt).sort((a, b) => b.localeCompare(a))[0] ?? lastActivityAt,
|
|
1464
|
+
projectIds,
|
|
1465
|
+
pressureSignalIds: signals.map((signal) => signal.id),
|
|
1466
|
+
sourceSkillIds,
|
|
1467
|
+
recurrenceCount: signals.length,
|
|
1468
|
+
highPriorityCount,
|
|
1469
|
+
linkedInterventionIds,
|
|
1470
|
+
linkedTransferEventIds,
|
|
1471
|
+
suggestedRoute: recommendation,
|
|
1472
|
+
lifecycle,
|
|
1473
|
+
interventionTypes,
|
|
1474
|
+
lastActivityAt,
|
|
1475
|
+
addressedAt,
|
|
1476
|
+
responseSummary,
|
|
1477
|
+
})
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
return motifs.sort((a, b) => {
|
|
1481
|
+
const lifecycleWeight = (value: PressureLifecycleState) => value === 'open' ? 4 : value === 'in-progress' ? 3 : value === 'addressed' ? 2 : 1
|
|
1482
|
+
return lifecycleWeight(b.lifecycle) - lifecycleWeight(a.lifecycle)
|
|
1483
|
+
|| b.highPriorityCount - a.highPriorityCount
|
|
1484
|
+
|| b.recurrenceCount - a.recurrenceCount
|
|
1485
|
+
|| b.lastActivityAt.localeCompare(a.lastActivityAt)
|
|
1486
|
+
})
|
|
1487
|
+
}
|
|
1488
|
+
|
|
414
1489
|
export function loadCoEvolutionSummary(): CoEvolutionDashboardSummary {
|
|
415
1490
|
const graph = loadGraph()
|
|
416
1491
|
const pressureSignals = loadPressureSignals()
|
|
1492
|
+
const resolvedSignals = loadResolvedPressureSignals()
|
|
1493
|
+
const pressureInterventions = loadPressureInterventions()
|
|
1494
|
+
const pressureMotifs = loadPressureMotifs()
|
|
1495
|
+
const transferEvents = loadTransferEvents()
|
|
1496
|
+
const governance = getActiveGovernanceSummary()
|
|
1497
|
+
const topologyReviews = getTopologyReviewSummary()
|
|
1498
|
+
const topologyExecution = getTopologyExecutionSummary()
|
|
417
1499
|
const rolePressure = emptyCounts(['generalist', 'specialist', 'hybrid'] as const)
|
|
418
1500
|
const projectBuckets = new Map<string, ProjectPressureSummary>()
|
|
1501
|
+
const projectResponses = new Map<string, ProjectResponseSummary>()
|
|
419
1502
|
const areaProjects = new Map<string, Set<string>>()
|
|
420
1503
|
const recommendationCounts = new Map<'research' | 'specialize' | 'create', number>()
|
|
1504
|
+
const routeCounts = new Map<PressureInterventionType, number>()
|
|
1505
|
+
const interventionLaneMap = new Map<PressureInterventionType, { type: PressureInterventionType; total: number; resolving: number; exploratory: number; none: number }>()
|
|
421
1506
|
|
|
422
1507
|
for (const signal of pressureSignals) {
|
|
423
1508
|
for (const skillId of signal.skillIds ?? []) {
|
|
@@ -454,10 +1539,63 @@ export function loadCoEvolutionSummary(): CoEvolutionDashboardSummary {
|
|
|
454
1539
|
}
|
|
455
1540
|
}
|
|
456
1541
|
|
|
1542
|
+
for (const signal of resolvedSignals) {
|
|
1543
|
+
if (signal.routeRecommendation) {
|
|
1544
|
+
routeCounts.set(signal.routeRecommendation.route, (routeCounts.get(signal.routeRecommendation.route) ?? 0) + 1)
|
|
1545
|
+
}
|
|
1546
|
+
if (!signal.projectId) continue
|
|
1547
|
+
const existing = projectResponses.get(signal.projectId) ?? {
|
|
1548
|
+
...(projectBuckets.get(signal.projectId) ?? {
|
|
1549
|
+
projectId: signal.projectId,
|
|
1550
|
+
totalSignals: 0,
|
|
1551
|
+
nativeSignals: 0,
|
|
1552
|
+
derivedSignals: 0,
|
|
1553
|
+
highPrioritySignals: 0,
|
|
1554
|
+
topCapabilities: [],
|
|
1555
|
+
}),
|
|
1556
|
+
openSignals: 0,
|
|
1557
|
+
inProgressSignals: 0,
|
|
1558
|
+
addressedSignals: 0,
|
|
1559
|
+
staleSignals: 0,
|
|
1560
|
+
activeInterventions: 0,
|
|
1561
|
+
recentInterventionTypes: [],
|
|
1562
|
+
}
|
|
1563
|
+
if (signal.lifecycle === 'open') existing.openSignals += 1
|
|
1564
|
+
if (signal.lifecycle === 'in-progress') existing.inProgressSignals += 1
|
|
1565
|
+
if (signal.lifecycle === 'addressed') existing.addressedSignals += 1
|
|
1566
|
+
if (signal.lifecycle === 'stale') existing.staleSignals += 1
|
|
1567
|
+
existing.activeInterventions += signal.linkedInterventions.filter((intervention) => interventionKeepsPressureActive(intervention.impact, intervention.status)).length
|
|
1568
|
+
for (const type of signal.interventionTypes) {
|
|
1569
|
+
if (!existing.recentInterventionTypes.includes(type) && existing.recentInterventionTypes.length < 3) {
|
|
1570
|
+
existing.recentInterventionTypes.push(type)
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
projectResponses.set(signal.projectId, existing)
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
for (const intervention of pressureInterventions) {
|
|
1577
|
+
const lane = interventionLaneMap.get(intervention.interventionType) ?? {
|
|
1578
|
+
type: intervention.interventionType,
|
|
1579
|
+
total: 0,
|
|
1580
|
+
resolving: 0,
|
|
1581
|
+
exploratory: 0,
|
|
1582
|
+
none: 0,
|
|
1583
|
+
}
|
|
1584
|
+
lane.total += 1
|
|
1585
|
+
if (intervention.impact === 'resolving') lane.resolving += 1
|
|
1586
|
+
else if (intervention.impact === 'exploratory' || intervention.impact === 'structural') lane.exploratory += 1
|
|
1587
|
+
else lane.none += 1
|
|
1588
|
+
interventionLaneMap.set(intervention.interventionType, lane)
|
|
1589
|
+
}
|
|
1590
|
+
|
|
457
1591
|
const topProjects = [...projectBuckets.values()]
|
|
458
1592
|
.sort((a, b) => b.totalSignals - a.totalSignals || b.highPrioritySignals - a.highPrioritySignals)
|
|
459
1593
|
.slice(0, 6)
|
|
460
1594
|
|
|
1595
|
+
const responseProjects = [...projectResponses.values()]
|
|
1596
|
+
.sort((a, b) => (b.openSignals + b.inProgressSignals) - (a.openSignals + a.inProgressSignals) || b.highPrioritySignals - a.highPrioritySignals)
|
|
1597
|
+
.slice(0, 8)
|
|
1598
|
+
|
|
461
1599
|
const crossProjectGapAreas = [...areaProjects.entries()]
|
|
462
1600
|
.filter(([, projects]) => projects.size >= 2)
|
|
463
1601
|
.map(([area, projects]) => ({ area, projects: [...projects], count: projects.size }))
|
|
@@ -468,16 +1606,98 @@ export function loadCoEvolutionSummary(): CoEvolutionDashboardSummary {
|
|
|
468
1606
|
.map(([action, count]) => ({ action, count }))
|
|
469
1607
|
.sort((a, b) => b.count - a.count)
|
|
470
1608
|
|
|
1609
|
+
const governedRoutes = [...routeCounts.entries()]
|
|
1610
|
+
.map(([route, total]) => ({ route, total }))
|
|
1611
|
+
.sort((a, b) => b.total - a.total || a.route.localeCompare(b.route))
|
|
1612
|
+
|
|
1613
|
+
const interventionLanes = [...interventionLaneMap.values()]
|
|
1614
|
+
.sort((a, b) => b.total - a.total || a.type.localeCompare(b.type))
|
|
1615
|
+
|
|
1616
|
+
const backlog = resolvedSignals
|
|
1617
|
+
.filter((signal) => signal.lifecycle === 'open' || signal.lifecycle === 'in-progress')
|
|
1618
|
+
.sort((a, b) => {
|
|
1619
|
+
const priorityScore = (value?: string) => value === 'high' ? 3 : value === 'medium' ? 2 : 1
|
|
1620
|
+
return priorityScore(b.priority) - priorityScore(a.priority) || b.severity - a.severity || b.detectedAt.localeCompare(a.detectedAt)
|
|
1621
|
+
})
|
|
1622
|
+
.slice(0, 12)
|
|
1623
|
+
|
|
1624
|
+
const promoteQueue = pressureMotifs
|
|
1625
|
+
.filter((motif) => motif.lifecycle !== 'addressed' && motif.suggestedRoute.route === 'generalize')
|
|
1626
|
+
.slice(0, 8)
|
|
1627
|
+
|
|
1628
|
+
const recentAddressed = resolvedSignals
|
|
1629
|
+
.filter((signal) => signal.lifecycle === 'addressed')
|
|
1630
|
+
.sort((a, b) => (b.addressedAt ?? b.lastActivityAt).localeCompare(a.addressedAt ?? a.lastActivityAt))
|
|
1631
|
+
.slice(0, 8)
|
|
1632
|
+
|
|
1633
|
+
const recentInterventions = pressureInterventions.slice(0, 8)
|
|
1634
|
+
const recentTransfers = transferEvents.slice(0, 8)
|
|
1635
|
+
|
|
471
1636
|
return {
|
|
472
1637
|
pressureSignals: {
|
|
473
1638
|
total: pressureSignals.length,
|
|
474
1639
|
native: pressureSignals.filter((signal) => signal.provenance === 'failure-native' || signal.provenance === 'project-analysis-native').length,
|
|
475
1640
|
derived: pressureSignals.filter((signal) => signal.provenance === 'failure-derived' || signal.provenance === 'profile-gap-derived').length,
|
|
476
1641
|
},
|
|
1642
|
+
pressureInterventions: {
|
|
1643
|
+
total: pressureInterventions.length,
|
|
1644
|
+
started: pressureInterventions.filter((intervention) => intervention.status === 'started').length,
|
|
1645
|
+
completed: pressureInterventions.filter((intervention) => intervention.status === 'completed').length,
|
|
1646
|
+
failed: pressureInterventions.filter((intervention) => intervention.status === 'failed').length,
|
|
1647
|
+
dryRun: pressureInterventions.filter((intervention) => intervention.status === 'dry-run').length,
|
|
1648
|
+
},
|
|
1649
|
+
pressureLifecycle: {
|
|
1650
|
+
open: resolvedSignals.filter((signal) => signal.lifecycle === 'open').length,
|
|
1651
|
+
inProgress: resolvedSignals.filter((signal) => signal.lifecycle === 'in-progress').length,
|
|
1652
|
+
addressed: resolvedSignals.filter((signal) => signal.lifecycle === 'addressed').length,
|
|
1653
|
+
stale: resolvedSignals.filter((signal) => signal.lifecycle === 'stale').length,
|
|
1654
|
+
highPriorityOpen: resolvedSignals.filter((signal) => signal.lifecycle !== 'addressed' && signal.priority === 'high').length,
|
|
1655
|
+
},
|
|
1656
|
+
pressureMotifs: {
|
|
1657
|
+
total: pressureMotifs.length,
|
|
1658
|
+
open: pressureMotifs.filter((motif) => motif.lifecycle === 'open').length,
|
|
1659
|
+
inProgress: pressureMotifs.filter((motif) => motif.lifecycle === 'in-progress').length,
|
|
1660
|
+
addressed: pressureMotifs.filter((motif) => motif.lifecycle === 'addressed').length,
|
|
1661
|
+
stale: pressureMotifs.filter((motif) => motif.lifecycle === 'stale').length,
|
|
1662
|
+
promotionReady: pressureMotifs.filter((motif) => motif.lifecycle !== 'addressed' && motif.suggestedRoute.route === 'generalize').length,
|
|
1663
|
+
},
|
|
1664
|
+
governance,
|
|
1665
|
+
topologyReviews: {
|
|
1666
|
+
total: topologyReviews.total,
|
|
1667
|
+
open: topologyReviews.open,
|
|
1668
|
+
accepted: topologyReviews.accepted,
|
|
1669
|
+
rejected: topologyReviews.rejected,
|
|
1670
|
+
deferred: topologyReviews.deferred,
|
|
1671
|
+
highPriorityOpen: topologyReviews.highPriorityOpen,
|
|
1672
|
+
generatedFromManualReview: topologyReviews.generatedFromManualReview,
|
|
1673
|
+
byChangeType: topologyReviews.byChangeType,
|
|
1674
|
+
},
|
|
1675
|
+
topologyExecution: {
|
|
1676
|
+
plans: topologyExecution.plans,
|
|
1677
|
+
pending: topologyExecution.pending,
|
|
1678
|
+
prepared: topologyExecution.prepared,
|
|
1679
|
+
applied: topologyExecution.applied,
|
|
1680
|
+
rolledBack: topologyExecution.rolledBack,
|
|
1681
|
+
failed: topologyExecution.failed,
|
|
1682
|
+
safeToApply: topologyExecution.safeToApply,
|
|
1683
|
+
prepareOnly: topologyExecution.prepareOnly,
|
|
1684
|
+
snapshots: topologyExecution.snapshots,
|
|
1685
|
+
artifacts: topologyExecution.artifacts,
|
|
1686
|
+
},
|
|
477
1687
|
topProjects,
|
|
1688
|
+
responseProjects,
|
|
478
1689
|
rolePressure,
|
|
479
1690
|
crossProjectGapAreas,
|
|
480
1691
|
activeRecommendations,
|
|
1692
|
+
governedRoutes,
|
|
1693
|
+
interventionLanes,
|
|
1694
|
+
backlog,
|
|
1695
|
+
promoteQueue,
|
|
1696
|
+
recentAddressed,
|
|
1697
|
+
recentInterventions,
|
|
1698
|
+
recentTransfers,
|
|
1699
|
+
topologyBacklog: topologyReviews.backlog,
|
|
1700
|
+
recentTopologyDecisions: topologyReviews.recentDecisions,
|
|
481
1701
|
}
|
|
482
1702
|
}
|
|
483
1703
|
|
|
@@ -556,9 +1776,23 @@ export function loadOntologySummary(): OntologyDashboardSummary {
|
|
|
556
1776
|
const artifacts = loadEvolutionArtifacts()
|
|
557
1777
|
const activationTraces = loadActivationTraces()
|
|
558
1778
|
const pressureSignals = loadPressureSignals()
|
|
1779
|
+
const pressureInterventions = loadPressureInterventions()
|
|
1780
|
+
const resolvedPressureSignals = loadResolvedPressureSignals()
|
|
1781
|
+
const pressureMotifs = loadPressureMotifs()
|
|
1782
|
+
const transferEvents = loadTransferEvents()
|
|
1783
|
+
const governance = getActiveGovernanceSummary()
|
|
1784
|
+
const topologyReviews = getTopologyReviewSummary()
|
|
1785
|
+
const topologyExecution = getTopologyExecutionSummary()
|
|
559
1786
|
const skillRoles = emptyCounts(['generalist', 'specialist', 'hybrid'] as const)
|
|
560
1787
|
const stabilityStates = emptyCounts(['stable', 'adaptive', 'experimental'] as const)
|
|
561
1788
|
const plasticityStates = emptyCounts(['consolidated', 'volatile', 'candidate'] as const)
|
|
1789
|
+
const governedRoutes: Record<PressureInterventionType, number> = {
|
|
1790
|
+
research: 0,
|
|
1791
|
+
specialize: 0,
|
|
1792
|
+
evolve: 0,
|
|
1793
|
+
generalize: 0,
|
|
1794
|
+
'manual-review': 0,
|
|
1795
|
+
}
|
|
562
1796
|
|
|
563
1797
|
for (const node of graph.nodes) {
|
|
564
1798
|
skillRoles[inferCognitiveRole(node)] += 1
|
|
@@ -566,6 +1800,10 @@ export function loadOntologySummary(): OntologyDashboardSummary {
|
|
|
566
1800
|
plasticityStates[inferPlasticityState(node)] += 1
|
|
567
1801
|
}
|
|
568
1802
|
|
|
1803
|
+
for (const signal of resolvedPressureSignals) {
|
|
1804
|
+
if (signal.routeRecommendation) governedRoutes[signal.routeRecommendation.route] += 1
|
|
1805
|
+
}
|
|
1806
|
+
|
|
569
1807
|
const proposals = history.iterations.flatMap((iteration) => iteration.proposals)
|
|
570
1808
|
const observedMutationActions = [...new Set(proposals.map((proposal) => proposal.action))].sort()
|
|
571
1809
|
const evidenceBackedProposals = proposals.filter((proposal) => {
|
|
@@ -607,6 +1845,66 @@ export function loadOntologySummary(): OntologyDashboardSummary {
|
|
|
607
1845
|
native: pressureSignals.filter((signal) => signal.provenance === 'failure-native' || signal.provenance === 'project-analysis-native').length,
|
|
608
1846
|
derived: pressureSignals.filter((signal) => signal.provenance === 'failure-derived' || signal.provenance === 'profile-gap-derived').length,
|
|
609
1847
|
},
|
|
1848
|
+
pressureInterventions: {
|
|
1849
|
+
total: pressureInterventions.length,
|
|
1850
|
+
started: pressureInterventions.filter((intervention) => intervention.status === 'started').length,
|
|
1851
|
+
completed: pressureInterventions.filter((intervention) => intervention.status === 'completed').length,
|
|
1852
|
+
failed: pressureInterventions.filter((intervention) => intervention.status === 'failed').length,
|
|
1853
|
+
dryRun: pressureInterventions.filter((intervention) => intervention.status === 'dry-run').length,
|
|
1854
|
+
},
|
|
1855
|
+
pressureLifecycle: {
|
|
1856
|
+
open: resolvedPressureSignals.filter((signal) => signal.lifecycle === 'open').length,
|
|
1857
|
+
inProgress: resolvedPressureSignals.filter((signal) => signal.lifecycle === 'in-progress').length,
|
|
1858
|
+
addressed: resolvedPressureSignals.filter((signal) => signal.lifecycle === 'addressed').length,
|
|
1859
|
+
stale: resolvedPressureSignals.filter((signal) => signal.lifecycle === 'stale').length,
|
|
1860
|
+
},
|
|
1861
|
+
pressureMotifs: {
|
|
1862
|
+
total: pressureMotifs.length,
|
|
1863
|
+
open: pressureMotifs.filter((motif) => motif.lifecycle === 'open').length,
|
|
1864
|
+
inProgress: pressureMotifs.filter((motif) => motif.lifecycle === 'in-progress').length,
|
|
1865
|
+
addressed: pressureMotifs.filter((motif) => motif.lifecycle === 'addressed').length,
|
|
1866
|
+
stale: pressureMotifs.filter((motif) => motif.lifecycle === 'stale').length,
|
|
1867
|
+
promotionReady: pressureMotifs.filter((motif) => motif.lifecycle !== 'addressed' && motif.suggestedRoute.route === 'generalize').length,
|
|
1868
|
+
},
|
|
1869
|
+
governedRoutes,
|
|
1870
|
+
transferEvents: {
|
|
1871
|
+
total: transferEvents.length,
|
|
1872
|
+
realized: transferEvents.filter((event) => event.status === 'realized').length,
|
|
1873
|
+
candidate: transferEvents.filter((event) => event.status === 'candidate').length,
|
|
1874
|
+
rejected: transferEvents.filter((event) => event.status === 'rejected').length,
|
|
1875
|
+
},
|
|
1876
|
+
governance: {
|
|
1877
|
+
activeMode: governance.activeMode,
|
|
1878
|
+
derivedMode: governance.derivedMode,
|
|
1879
|
+
source: governance.source,
|
|
1880
|
+
manualMode: governance.manualMode,
|
|
1881
|
+
rationale: governance.rationale,
|
|
1882
|
+
reviewThreshold: governance.profile.reviewThreshold,
|
|
1883
|
+
riskTolerance: governance.profile.riskTolerance,
|
|
1884
|
+
plasticityBias: governance.profile.plasticityBias,
|
|
1885
|
+
},
|
|
1886
|
+
topologyReviews: {
|
|
1887
|
+
total: topologyReviews.total,
|
|
1888
|
+
open: topologyReviews.open,
|
|
1889
|
+
accepted: topologyReviews.accepted,
|
|
1890
|
+
rejected: topologyReviews.rejected,
|
|
1891
|
+
deferred: topologyReviews.deferred,
|
|
1892
|
+
highPriorityOpen: topologyReviews.highPriorityOpen,
|
|
1893
|
+
generatedFromManualReview: topologyReviews.generatedFromManualReview,
|
|
1894
|
+
byChangeType: topologyReviews.byChangeType,
|
|
1895
|
+
},
|
|
1896
|
+
topologyExecution: {
|
|
1897
|
+
plans: topologyExecution.plans,
|
|
1898
|
+
pending: topologyExecution.pending,
|
|
1899
|
+
prepared: topologyExecution.prepared,
|
|
1900
|
+
applied: topologyExecution.applied,
|
|
1901
|
+
rolledBack: topologyExecution.rolledBack,
|
|
1902
|
+
failed: topologyExecution.failed,
|
|
1903
|
+
safeToApply: topologyExecution.safeToApply,
|
|
1904
|
+
prepareOnly: topologyExecution.prepareOnly,
|
|
1905
|
+
snapshots: topologyExecution.snapshots,
|
|
1906
|
+
artifacts: topologyExecution.artifacts,
|
|
1907
|
+
},
|
|
610
1908
|
annotatedFailures: {
|
|
611
1909
|
pressureSignals: failures.filter((failure) => (failure.pressureSignals?.length ?? 0) > 0).length,
|
|
612
1910
|
capabilityGaps: failures.filter((failure) => (failure.capabilityGaps?.length ?? 0) > 0).length,
|