helixevo 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/README.md +20 -4
- package/dashboard/app/api/ontology/route.ts +89 -0
- package/dashboard/app/coevolution/client.tsx +32 -2
- package/dashboard/app/coevolution/page.tsx +3 -2
- package/dashboard/app/commands/page.tsx +80 -3
- package/dashboard/app/guide/page.tsx +80 -7
- package/dashboard/app/ontology/client.tsx +295 -0
- package/dashboard/app/ontology/page.tsx +9 -0
- package/dashboard/app/page.tsx +22 -2
- package/dashboard/app/topology/client.tsx +4 -0
- package/dashboard/components/sidebar-nav.tsx +10 -5
- package/dashboard/lib/data.ts +953 -53
- package/dist/cli.js +1133 -41
- package/package.json +1 -1
package/dashboard/lib/data.ts
CHANGED
|
@@ -16,6 +16,20 @@ function readJsonl<T>(filename: string): T[] {
|
|
|
16
16
|
return readFileSync(path, 'utf-8').trim().split('\n').filter(Boolean).map(l => JSON.parse(l))
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
function readOntologyJson<T>(filename: string, fallback: T): T {
|
|
20
|
+
const path = join(SG_DIR, 'ontology', filename)
|
|
21
|
+
if (!existsSync(path)) return fallback
|
|
22
|
+
return JSON.parse(readFileSync(path, 'utf-8'))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readOntologyJsonl<T>(filename: string): T[] {
|
|
26
|
+
const path = join(SG_DIR, 'ontology', filename)
|
|
27
|
+
if (!existsSync(path)) return []
|
|
28
|
+
const raw = readFileSync(path, 'utf-8').trim()
|
|
29
|
+
if (!raw) return []
|
|
30
|
+
return raw.split('\n').filter(Boolean).map((l) => JSON.parse(l))
|
|
31
|
+
}
|
|
32
|
+
|
|
19
33
|
// ─── Types ──────────────────────────────────────────────────────
|
|
20
34
|
|
|
21
35
|
export type CognitiveRole = 'generalist' | 'specialist' | 'hybrid'
|
|
@@ -78,7 +92,7 @@ export interface CanaryEntry {
|
|
|
78
92
|
skillSlug: string; version: string; deployedAt: string; expiresAt: string
|
|
79
93
|
}
|
|
80
94
|
|
|
81
|
-
export type ArtifactProvenance = 'native-evolution' | 'native-topology' | 'derived-history'
|
|
95
|
+
export type ArtifactProvenance = 'native-evolution' | 'native-topology' | 'native-ontology' | 'derived-history'
|
|
82
96
|
export type ActivationProvenance = 'capture-derived' | 'project-analysis' | 'derived-failure'
|
|
83
97
|
export type PressureProvenance = 'failure-native' | 'project-analysis-native' | 'failure-derived' | 'profile-gap-derived'
|
|
84
98
|
|
|
@@ -174,6 +188,8 @@ export interface PressureRouteRecommendation {
|
|
|
174
188
|
governanceMode: GovernanceModeName
|
|
175
189
|
triggeredBy: PressureRouteTrigger
|
|
176
190
|
reasons: string[]
|
|
191
|
+
semanticInfluence?: OntologySemanticInfluence
|
|
192
|
+
semanticConceptIds?: string[]
|
|
177
193
|
}
|
|
178
194
|
|
|
179
195
|
export interface PressureIntervention {
|
|
@@ -296,6 +312,8 @@ export interface TopologyReviewDecision {
|
|
|
296
312
|
export interface ResolvedTopologyReviewCandidate extends TopologyReviewCandidate {
|
|
297
313
|
status: TopologyReviewStatus
|
|
298
314
|
latestDecision?: TopologyReviewDecision
|
|
315
|
+
semanticBindings?: ResolvedOntologyBinding[]
|
|
316
|
+
semanticConceptIds?: string[]
|
|
299
317
|
lastActivityAt: string
|
|
300
318
|
}
|
|
301
319
|
|
|
@@ -387,6 +405,8 @@ export interface ResolvedPressureSignal extends PressureSignal {
|
|
|
387
405
|
interventionTypes: PressureInterventionType[]
|
|
388
406
|
motifIds?: string[]
|
|
389
407
|
routeRecommendation?: PressureRouteRecommendation
|
|
408
|
+
semanticBindings?: ResolvedOntologyBinding[]
|
|
409
|
+
semanticConceptIds?: string[]
|
|
390
410
|
lastActivityAt: string
|
|
391
411
|
addressedAt?: string
|
|
392
412
|
responseSummary: string
|
|
@@ -395,6 +415,8 @@ export interface ResolvedPressureSignal extends PressureSignal {
|
|
|
395
415
|
export interface ResolvedPressureMotif extends PressureMotif {
|
|
396
416
|
lifecycle: PressureLifecycleState
|
|
397
417
|
interventionTypes: PressureInterventionType[]
|
|
418
|
+
semanticBindings?: ResolvedOntologyBinding[]
|
|
419
|
+
semanticConceptIds?: string[]
|
|
398
420
|
lastActivityAt: string
|
|
399
421
|
addressedAt?: string
|
|
400
422
|
responseSummary: string
|
|
@@ -418,6 +440,11 @@ export interface ProjectResponseSummary extends ProjectPressureSummary {
|
|
|
418
440
|
recentInterventionTypes: PressureInterventionType[]
|
|
419
441
|
}
|
|
420
442
|
|
|
443
|
+
export interface ResolvedTransferEvent extends TransferEvent {
|
|
444
|
+
semanticBindings?: ResolvedOntologyBinding[]
|
|
445
|
+
semanticConceptIds?: string[]
|
|
446
|
+
}
|
|
447
|
+
|
|
421
448
|
export interface CoEvolutionDashboardSummary {
|
|
422
449
|
pressureSignals: {
|
|
423
450
|
total: number
|
|
@@ -480,7 +507,7 @@ export interface CoEvolutionDashboardSummary {
|
|
|
480
507
|
promoteQueue: ResolvedPressureMotif[]
|
|
481
508
|
recentAddressed: ResolvedPressureSignal[]
|
|
482
509
|
recentInterventions: PressureIntervention[]
|
|
483
|
-
recentTransfers:
|
|
510
|
+
recentTransfers: ResolvedTransferEvent[]
|
|
484
511
|
topologyBacklog: ResolvedTopologyReviewCandidate[]
|
|
485
512
|
recentTopologyDecisions: ResolvedTopologyReviewCandidate[]
|
|
486
513
|
}
|
|
@@ -497,9 +524,128 @@ export interface TopologyDashboardSummary {
|
|
|
497
524
|
candidates: ResolvedTopologyReviewCandidate[]
|
|
498
525
|
}
|
|
499
526
|
|
|
527
|
+
export type OntologyConceptKind = 'entity' | 'relation-subtype' | 'capability' | 'pressure-type' | 'topology-motif' | 'health-signal' | 'governance-pattern'
|
|
528
|
+
export type OntologyLayer = 'kernel' | 'extension' | 'frontier'
|
|
529
|
+
export type OntologyReviewDecisionStatus = 'promote' | 'reject' | 'defer'
|
|
530
|
+
export type OntologyReviewStatus = 'open' | 'promoted' | 'rejected' | 'deferred'
|
|
531
|
+
export type OntologyBindingTargetType = 'pressure-signal' | 'pressure-motif' | 'route-recommendation' | 'transfer-event' | 'topology-review'
|
|
532
|
+
export type OntologyBindingSourceKind = 'capability-match' | 'pressure-region' | 'motif-match' | 'transfer-motif' | 'topology-descriptor'
|
|
533
|
+
export type OntologySemanticInfluence = 'none' | 'explanatory' | 'weighted'
|
|
534
|
+
|
|
535
|
+
export interface OntologyKernelSnapshot {
|
|
536
|
+
version: string
|
|
537
|
+
updatedAt: string
|
|
538
|
+
pillars: string[]
|
|
539
|
+
layers: string[]
|
|
540
|
+
entityNames: string[]
|
|
541
|
+
relationFamilies: string[]
|
|
542
|
+
mutationOperations: string[]
|
|
543
|
+
invariantIds: string[]
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
export interface OntologyConcept {
|
|
547
|
+
id: string
|
|
548
|
+
name: string
|
|
549
|
+
conceptKind: OntologyConceptKind
|
|
550
|
+
layer: OntologyLayer
|
|
551
|
+
status: 'active' | 'provisional' | 'deprecated'
|
|
552
|
+
createdAt: string
|
|
553
|
+
parentConceptId?: string
|
|
554
|
+
description?: string
|
|
555
|
+
aliases?: string[]
|
|
556
|
+
promotionCriteria?: string[]
|
|
557
|
+
migrationMap?: Record<string, string>
|
|
558
|
+
reviewKey?: string
|
|
559
|
+
lastObservedAt?: string
|
|
560
|
+
sourceSummary?: string
|
|
561
|
+
projectIds?: string[]
|
|
562
|
+
evidenceIds?: string[]
|
|
563
|
+
observationCount?: number
|
|
564
|
+
confidence?: number
|
|
565
|
+
relatedSignalIds?: string[]
|
|
566
|
+
relatedMotifIds?: string[]
|
|
567
|
+
relatedReviewIds?: string[]
|
|
568
|
+
relatedTransferEventIds?: string[]
|
|
569
|
+
derivedFromKinds?: string[]
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export interface OntologyReviewDecision {
|
|
573
|
+
id: string
|
|
574
|
+
conceptId: string
|
|
575
|
+
decision: OntologyReviewDecisionStatus
|
|
576
|
+
decidedAt: string
|
|
577
|
+
governanceMode: GovernanceModeName
|
|
578
|
+
rationale: string
|
|
579
|
+
decidedBy?: 'operator' | 'system'
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
export interface ResolvedOntologyConcept extends OntologyConcept {
|
|
583
|
+
reviewStatus: OntologyReviewStatus
|
|
584
|
+
latestReview?: OntologyReviewDecision
|
|
585
|
+
lastActivityAt: string
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
export interface ResolvedOntologyBinding {
|
|
589
|
+
id: string
|
|
590
|
+
conceptId: string
|
|
591
|
+
conceptName: string
|
|
592
|
+
conceptKind: OntologyConceptKind
|
|
593
|
+
targetType: OntologyBindingTargetType
|
|
594
|
+
targetId: string
|
|
595
|
+
sourceKind: OntologyBindingSourceKind
|
|
596
|
+
confidence: number
|
|
597
|
+
reasons: string[]
|
|
598
|
+
effectSummary?: string
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
export interface OntologyConsumerSummary {
|
|
602
|
+
conceptId: string
|
|
603
|
+
conceptName: string
|
|
604
|
+
conceptKind: OntologyConceptKind
|
|
605
|
+
status: OntologyConcept['status']
|
|
606
|
+
totalBindings: number
|
|
607
|
+
bindingsByTargetType: Partial<Record<OntologyBindingTargetType, number>>
|
|
608
|
+
activeTargetIds: string[]
|
|
609
|
+
lastActivityAt?: string
|
|
610
|
+
warning?: string
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
export interface ResolvedOntologyExtension extends OntologyConcept {
|
|
614
|
+
semanticBindings: ResolvedOntologyBinding[]
|
|
615
|
+
adoptionCount: number
|
|
616
|
+
bindingsByTargetType: Partial<Record<OntologyBindingTargetType, number>>
|
|
617
|
+
lastActivityAt: string
|
|
618
|
+
warning?: string
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
export interface OntologyAdoptionSummary {
|
|
622
|
+
activeConcepts: number
|
|
623
|
+
unusedExtensions: number
|
|
624
|
+
totalBindings: number
|
|
625
|
+
routesInfluenced: number
|
|
626
|
+
conceptsWithConsumers: number
|
|
627
|
+
conceptsAtDeprecationRisk: number
|
|
628
|
+
bindingsByTargetType: Partial<Record<OntologyBindingTargetType, number>>
|
|
629
|
+
usageByConceptKind: Partial<Record<OntologyConceptKind, number>>
|
|
630
|
+
topActiveConcepts: OntologyConsumerSummary[]
|
|
631
|
+
atRiskConcepts: OntologyConsumerSummary[]
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
export interface OntologyChangeEvent {
|
|
635
|
+
id: string
|
|
636
|
+
timestamp: string
|
|
637
|
+
changeType: 'concept-hypothesized' | 'concept-promoted' | 'concept-rejected' | 'concept-split' | 'concept-merged' | 'concept-deprecated' | 'relation-refined' | 'mapping-migrated' | 'invariant-added' | 'invariant-relaxed'
|
|
638
|
+
conceptIds: string[]
|
|
639
|
+
reason: string
|
|
640
|
+
evidenceIds?: string[]
|
|
641
|
+
proposalId?: string
|
|
642
|
+
approvedBy?: string
|
|
643
|
+
ontologyVersion?: string
|
|
644
|
+
}
|
|
645
|
+
|
|
500
646
|
export interface OntologyDashboardSummary {
|
|
501
647
|
specVersion: string
|
|
502
|
-
source: 'graph' | 'compat-derived'
|
|
648
|
+
source: 'graph' | 'compat-derived' | 'hybrid-native-derived'
|
|
503
649
|
skillRoles: Record<CognitiveRole, number>
|
|
504
650
|
stabilityStates: Record<StabilityState, number>
|
|
505
651
|
plasticityStates: Record<PlasticityState, number>
|
|
@@ -551,6 +697,27 @@ export interface OntologyDashboardSummary {
|
|
|
551
697
|
candidate: number
|
|
552
698
|
rejected: number
|
|
553
699
|
}
|
|
700
|
+
ontologyLoop: {
|
|
701
|
+
kernelConcepts: number
|
|
702
|
+
extensions: number
|
|
703
|
+
frontier: number
|
|
704
|
+
reviewOpen: number
|
|
705
|
+
promoted: number
|
|
706
|
+
rejected: number
|
|
707
|
+
deferred: number
|
|
708
|
+
deprecated: number
|
|
709
|
+
changeEvents: number
|
|
710
|
+
byConceptKind: Partial<Record<OntologyConceptKind, number>>
|
|
711
|
+
adoption: {
|
|
712
|
+
activeConcepts: number
|
|
713
|
+
unusedExtensions: number
|
|
714
|
+
totalBindings: number
|
|
715
|
+
routesInfluenced: number
|
|
716
|
+
conceptsAtDeprecationRisk: number
|
|
717
|
+
bindingsByTargetType: Partial<Record<OntologyBindingTargetType, number>>
|
|
718
|
+
usageByConceptKind: Partial<Record<OntologyConceptKind, number>>
|
|
719
|
+
}
|
|
720
|
+
}
|
|
554
721
|
governance: {
|
|
555
722
|
activeMode: GovernanceModeName
|
|
556
723
|
derivedMode: GovernanceModeName
|
|
@@ -590,12 +757,30 @@ export interface OntologyDashboardSummary {
|
|
|
590
757
|
}
|
|
591
758
|
}
|
|
592
759
|
|
|
760
|
+
export interface OntologyControlDashboardSummary {
|
|
761
|
+
governance: ActiveGovernanceSummary
|
|
762
|
+
kernel: OntologyKernelSnapshot
|
|
763
|
+
summary: OntologyDashboardSummary['ontologyLoop']
|
|
764
|
+
adoption: OntologyAdoptionSummary
|
|
765
|
+
frontier: ResolvedOntologyConcept[]
|
|
766
|
+
extensions: ResolvedOntologyExtension[]
|
|
767
|
+
recentChanges: OntologyChangeEvent[]
|
|
768
|
+
}
|
|
769
|
+
|
|
593
770
|
const DASHBOARD_ONTOLOGY_SPEC_VERSION = '0.1.0'
|
|
594
771
|
|
|
595
772
|
function emptyCounts<T extends string>(values: readonly T[]): Record<T, number> {
|
|
596
773
|
return Object.fromEntries(values.map((value) => [value, 0])) as Record<T, number>
|
|
597
774
|
}
|
|
598
775
|
|
|
776
|
+
function uniqueStrings(values: Array<string | undefined | null>): string[] {
|
|
777
|
+
return [...new Set(values.filter((value): value is string => Boolean(value && value.trim())))]
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
function slugFragment(value: string): string {
|
|
781
|
+
return value.replace(/[^a-zA-Z0-9_-]/g, '-').replace(/-+/g, '-').slice(0, 80)
|
|
782
|
+
}
|
|
783
|
+
|
|
599
784
|
function inferCognitiveRole(node: SkillNode): CognitiveRole {
|
|
600
785
|
if (node.cognitiveRole) return node.cognitiveRole
|
|
601
786
|
if (node.layer === 'project' || Boolean(node.project)) return 'specialist'
|
|
@@ -679,6 +864,211 @@ export function loadCanaries(): { entries: CanaryEntry[] } {
|
|
|
679
864
|
return readJson<{ entries: CanaryEntry[] }>('canary-registry.json', { entries: [] })
|
|
680
865
|
}
|
|
681
866
|
|
|
867
|
+
export function loadOntologyKernelSnapshot(): OntologyKernelSnapshot {
|
|
868
|
+
return readOntologyJson<OntologyKernelSnapshot>('kernel.json', {
|
|
869
|
+
version: DASHBOARD_ONTOLOGY_SPEC_VERSION,
|
|
870
|
+
updatedAt: '',
|
|
871
|
+
pillars: [
|
|
872
|
+
'Operational World',
|
|
873
|
+
'Perception / Pressure',
|
|
874
|
+
'Working Memory / Activation',
|
|
875
|
+
'Long-Term Memory',
|
|
876
|
+
'Plasticity / Topology',
|
|
877
|
+
'Evidence / Immune System',
|
|
878
|
+
'Executive / Governance',
|
|
879
|
+
'Metacognition / Health',
|
|
880
|
+
'Time / Lineage',
|
|
881
|
+
],
|
|
882
|
+
layers: ['kernel', 'extension', 'frontier'],
|
|
883
|
+
entityNames: [
|
|
884
|
+
'ProjectRecord',
|
|
885
|
+
'SessionRecord',
|
|
886
|
+
'TaskRecord',
|
|
887
|
+
'FailureRecord',
|
|
888
|
+
'PressureSignal',
|
|
889
|
+
'Capability',
|
|
890
|
+
'SkillNode',
|
|
891
|
+
'SkillRelation',
|
|
892
|
+
'ActivationEvent',
|
|
893
|
+
'MutationProposal',
|
|
894
|
+
'TopologyChange',
|
|
895
|
+
'EvidenceArtifact',
|
|
896
|
+
'EvaluationResult',
|
|
897
|
+
'TransferEvent',
|
|
898
|
+
'GovernanceMode',
|
|
899
|
+
'HealthMetric',
|
|
900
|
+
'LineageRecord',
|
|
901
|
+
'OntologyConcept',
|
|
902
|
+
'OntologyChangeEvent',
|
|
903
|
+
],
|
|
904
|
+
relationFamilies: [
|
|
905
|
+
'belongs_to',
|
|
906
|
+
'derived_from',
|
|
907
|
+
'replaced_by',
|
|
908
|
+
'parent_of',
|
|
909
|
+
'child_of',
|
|
910
|
+
'specializes',
|
|
911
|
+
'generalizes_from',
|
|
912
|
+
'depends_on',
|
|
913
|
+
'enhances',
|
|
914
|
+
'conflicts_with',
|
|
915
|
+
'co_evolves_with',
|
|
916
|
+
'covers',
|
|
917
|
+
'activated_for',
|
|
918
|
+
'suppressed_by',
|
|
919
|
+
'contributed_to',
|
|
920
|
+
'competed_with',
|
|
921
|
+
'triggered_by',
|
|
922
|
+
'validated_by',
|
|
923
|
+
'contradicted_by',
|
|
924
|
+
'survived_in',
|
|
925
|
+
'requires_review',
|
|
926
|
+
'proposed_due_to',
|
|
927
|
+
'benefits',
|
|
928
|
+
'transferred_to',
|
|
929
|
+
'refined_by',
|
|
930
|
+
'promoted_from',
|
|
931
|
+
'executed_under',
|
|
932
|
+
'deferred_by',
|
|
933
|
+
'prioritized_by',
|
|
934
|
+
'locked_by',
|
|
935
|
+
'measures',
|
|
936
|
+
'signals_weakness_in',
|
|
937
|
+
'suggests_repair_for',
|
|
938
|
+
],
|
|
939
|
+
mutationOperations: [
|
|
940
|
+
'create_skill',
|
|
941
|
+
'edit_skill',
|
|
942
|
+
'specialize_skill',
|
|
943
|
+
'generalize_skill',
|
|
944
|
+
'promote_skill',
|
|
945
|
+
'demote_skill',
|
|
946
|
+
'retire_skill',
|
|
947
|
+
'merge_skills',
|
|
948
|
+
'split_skill',
|
|
949
|
+
'rewire_relation',
|
|
950
|
+
'prune_branch',
|
|
951
|
+
'consolidate_cluster',
|
|
952
|
+
],
|
|
953
|
+
invariantIds: [
|
|
954
|
+
'accepted-change-requires-evidence',
|
|
955
|
+
'specialist-requires-local-context',
|
|
956
|
+
'promotion-requires-repeated-evidence',
|
|
957
|
+
'relation-requires-family-and-confidence',
|
|
958
|
+
'topology-change-preserves-lineage',
|
|
959
|
+
'deprecated-state-remains-recoverable',
|
|
960
|
+
'ontology-promotion-requires-migration',
|
|
961
|
+
'frontier-concepts-stay-provisional',
|
|
962
|
+
'transfer-requires-source-and-target',
|
|
963
|
+
'high-risk-topology-change-requires-validation',
|
|
964
|
+
],
|
|
965
|
+
})
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
export function loadOntologyExtensions(): OntologyConcept[] {
|
|
969
|
+
return readOntologyJson<OntologyConcept[]>('extensions.json', [])
|
|
970
|
+
.slice()
|
|
971
|
+
.sort((a, b) => (b.lastObservedAt ?? b.createdAt).localeCompare(a.lastObservedAt ?? a.createdAt) || a.name.localeCompare(b.name))
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
export function loadOntologyFrontier(): OntologyConcept[] {
|
|
975
|
+
return readOntologyJson<OntologyConcept[]>('frontier.json', [])
|
|
976
|
+
.slice()
|
|
977
|
+
.sort((a, b) => (b.lastObservedAt ?? b.createdAt).localeCompare(a.lastObservedAt ?? a.createdAt) || a.name.localeCompare(b.name))
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
export function loadOntologyReviewDecisions(): OntologyReviewDecision[] {
|
|
981
|
+
return readOntologyJsonl<OntologyReviewDecision>('reviews.jsonl')
|
|
982
|
+
.slice()
|
|
983
|
+
.sort((a, b) => b.decidedAt.localeCompare(a.decidedAt))
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
export function loadOntologyChangeEvents(): OntologyChangeEvent[] {
|
|
987
|
+
return readOntologyJsonl<OntologyChangeEvent>('change-log.jsonl')
|
|
988
|
+
.slice()
|
|
989
|
+
.sort((a, b) => b.timestamp.localeCompare(a.timestamp))
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
function ontologyStatusRank(status: OntologyReviewStatus): number {
|
|
993
|
+
return status === 'open' ? 4 : status === 'deferred' ? 3 : status === 'rejected' ? 2 : 1
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
export function loadResolvedOntologyFrontierConcepts(): ResolvedOntologyConcept[] {
|
|
997
|
+
const concepts = loadOntologyFrontier()
|
|
998
|
+
const latestByConcept = new Map<string, OntologyReviewDecision>()
|
|
999
|
+
for (const decision of loadOntologyReviewDecisions()) {
|
|
1000
|
+
const existing = latestByConcept.get(decision.conceptId)
|
|
1001
|
+
if (!existing || decision.decidedAt > existing.decidedAt) latestByConcept.set(decision.conceptId, decision)
|
|
1002
|
+
}
|
|
1003
|
+
return concepts
|
|
1004
|
+
.map((concept) => {
|
|
1005
|
+
const latestReview = latestByConcept.get(concept.id)
|
|
1006
|
+
const reviewStatus: OntologyReviewStatus = latestReview?.decision === 'promote'
|
|
1007
|
+
? 'promoted'
|
|
1008
|
+
: latestReview?.decision === 'reject'
|
|
1009
|
+
? 'rejected'
|
|
1010
|
+
: latestReview?.decision === 'defer'
|
|
1011
|
+
? 'deferred'
|
|
1012
|
+
: 'open'
|
|
1013
|
+
return {
|
|
1014
|
+
...concept,
|
|
1015
|
+
reviewStatus,
|
|
1016
|
+
latestReview,
|
|
1017
|
+
lastActivityAt: maxTimestamp(concept.lastObservedAt, latestReview?.decidedAt, concept.createdAt),
|
|
1018
|
+
}
|
|
1019
|
+
})
|
|
1020
|
+
.sort((a, b) => ontologyStatusRank(b.reviewStatus) - ontologyStatusRank(a.reviewStatus)
|
|
1021
|
+
|| (b.confidence ?? 0) - (a.confidence ?? 0)
|
|
1022
|
+
|| (b.observationCount ?? 0) - (a.observationCount ?? 0)
|
|
1023
|
+
|| b.lastActivityAt.localeCompare(a.lastActivityAt)
|
|
1024
|
+
|| a.name.localeCompare(b.name))
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
function ontologyKindCounts(): Partial<Record<OntologyConceptKind, number>> {
|
|
1028
|
+
return {}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
export function getOntologyReviewSummary() {
|
|
1032
|
+
const kernel = loadOntologyKernelSnapshot()
|
|
1033
|
+
const frontier = loadResolvedOntologyFrontierConcepts()
|
|
1034
|
+
const extensions = loadOntologyExtensions()
|
|
1035
|
+
const resolvedExtensions = loadResolvedOntologyExtensions()
|
|
1036
|
+
const changes = loadOntologyChangeEvents()
|
|
1037
|
+
const adoption = getOntologyAdoptionSummary()
|
|
1038
|
+
const byConceptKind = ontologyKindCounts()
|
|
1039
|
+
for (const concept of [...frontier, ...extensions]) {
|
|
1040
|
+
byConceptKind[concept.conceptKind] = (byConceptKind[concept.conceptKind] ?? 0) + 1
|
|
1041
|
+
}
|
|
1042
|
+
return {
|
|
1043
|
+
kernelConcepts: kernel.entityNames.length,
|
|
1044
|
+
extensions: extensions.length,
|
|
1045
|
+
frontier: frontier.length,
|
|
1046
|
+
reviewOpen: frontier.filter((concept) => concept.reviewStatus === 'open').length,
|
|
1047
|
+
promoted: extensions.filter((concept) => concept.status === 'active').length,
|
|
1048
|
+
rejected: frontier.filter((concept) => concept.reviewStatus === 'rejected').length,
|
|
1049
|
+
deferred: frontier.filter((concept) => concept.reviewStatus === 'deferred').length,
|
|
1050
|
+
deprecated: extensions.filter((concept) => concept.status === 'deprecated').length,
|
|
1051
|
+
changeEvents: changes.length,
|
|
1052
|
+
byConceptKind,
|
|
1053
|
+
adoption,
|
|
1054
|
+
backlog: frontier.filter((concept) => concept.reviewStatus === 'open' || concept.reviewStatus === 'deferred').slice(0, 10),
|
|
1055
|
+
recentChanges: changes.slice(0, 10),
|
|
1056
|
+
recentExtensions: resolvedExtensions.slice(0, 8),
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
export function loadOntologyControlSummary(): OntologyControlDashboardSummary {
|
|
1061
|
+
return {
|
|
1062
|
+
governance: getActiveGovernanceSummary(),
|
|
1063
|
+
kernel: loadOntologyKernelSnapshot(),
|
|
1064
|
+
summary: getOntologyReviewSummary(),
|
|
1065
|
+
adoption: getOntologyAdoptionSummary(),
|
|
1066
|
+
frontier: loadResolvedOntologyFrontierConcepts(),
|
|
1067
|
+
extensions: loadResolvedOntologyExtensions(),
|
|
1068
|
+
recentChanges: loadOntologyChangeEvents().slice(0, 10),
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
682
1072
|
export function loadSkillContent(slug: string): string {
|
|
683
1073
|
const path = join(SG_DIR, 'general', slug, 'SKILL.md')
|
|
684
1074
|
if (!existsSync(path)) return ''
|
|
@@ -827,9 +1217,18 @@ export function loadPressureInterventions(): PressureIntervention[] {
|
|
|
827
1217
|
.sort((a, b) => (b.completedAt ?? b.createdAt).localeCompare(a.completedAt ?? a.createdAt))
|
|
828
1218
|
}
|
|
829
1219
|
|
|
830
|
-
export function loadTransferEvents():
|
|
1220
|
+
export function loadTransferEvents(): ResolvedTransferEvent[] {
|
|
1221
|
+
const concepts = activeOntologyExtensions()
|
|
831
1222
|
return readJsonl<TransferEvent>('transfer-events.jsonl')
|
|
832
1223
|
.slice()
|
|
1224
|
+
.map((event) => {
|
|
1225
|
+
const semanticBindings = matchTransferOntologyBindings(event, concepts)
|
|
1226
|
+
return {
|
|
1227
|
+
...event,
|
|
1228
|
+
semanticBindings,
|
|
1229
|
+
semanticConceptIds: uniqueStrings(semanticBindings.map((binding) => binding.conceptId)),
|
|
1230
|
+
} satisfies ResolvedTransferEvent
|
|
1231
|
+
})
|
|
833
1232
|
.sort((a, b) => b.timestamp.localeCompare(a.timestamp))
|
|
834
1233
|
}
|
|
835
1234
|
|
|
@@ -887,6 +1286,7 @@ export function loadTopologyReviewDecisions(): TopologyReviewDecision[] {
|
|
|
887
1286
|
|
|
888
1287
|
export function loadResolvedTopologyReviewCandidates(): ResolvedTopologyReviewCandidate[] {
|
|
889
1288
|
const candidates = loadStoredTopologyReviewCandidates()
|
|
1289
|
+
const concepts = activeOntologyExtensions()
|
|
890
1290
|
const latestDecisionByCandidate = new Map<string, TopologyReviewDecision>()
|
|
891
1291
|
for (const decision of loadTopologyReviewDecisions()) {
|
|
892
1292
|
const existing = latestDecisionByCandidate.get(decision.candidateId)
|
|
@@ -896,10 +1296,18 @@ export function loadResolvedTopologyReviewCandidates(): ResolvedTopologyReviewCa
|
|
|
896
1296
|
.map((candidate) => {
|
|
897
1297
|
const latestDecision = latestDecisionByCandidate.get(candidate.id)
|
|
898
1298
|
const status: TopologyReviewStatus = latestDecision?.decision ?? 'open'
|
|
1299
|
+
const semanticBindings = matchTopologyOntologyBindings({
|
|
1300
|
+
...candidate,
|
|
1301
|
+
status,
|
|
1302
|
+
latestDecision,
|
|
1303
|
+
lastActivityAt: maxTimestamp(candidate.lastObservedAt, latestDecision?.decidedAt),
|
|
1304
|
+
}, concepts)
|
|
899
1305
|
return {
|
|
900
1306
|
...candidate,
|
|
901
1307
|
status,
|
|
902
1308
|
latestDecision,
|
|
1309
|
+
semanticBindings,
|
|
1310
|
+
semanticConceptIds: uniqueStrings(semanticBindings.map((binding) => binding.conceptId)),
|
|
903
1311
|
lastActivityAt: maxTimestamp(candidate.lastObservedAt, latestDecision?.decidedAt),
|
|
904
1312
|
} satisfies ResolvedTopologyReviewCandidate
|
|
905
1313
|
})
|
|
@@ -1165,22 +1573,356 @@ export function getActiveGovernanceSummary(): ActiveGovernanceSummary {
|
|
|
1165
1573
|
return deriveGovernanceSummary()
|
|
1166
1574
|
}
|
|
1167
1575
|
|
|
1576
|
+
function emptyOntologyBindingCounts(): Partial<Record<OntologyBindingTargetType, number>> {
|
|
1577
|
+
return {}
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
function ontologyReviewKeyBody(reviewKey?: string): string {
|
|
1581
|
+
return normalizePressureValue((reviewKey ?? '').split(':').slice(1).join('-'))
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
function ontologyMotifTerm(motifId: string): string {
|
|
1585
|
+
return normalizePressureValue(motifId.replace(/^motif_/, ''))
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
function ontologyConceptTerms(concept: OntologyConcept): string[] {
|
|
1589
|
+
return uniqueStrings([
|
|
1590
|
+
normalizePressureValue(concept.name),
|
|
1591
|
+
...((concept.aliases ?? []).map((alias) => normalizePressureValue(alias))),
|
|
1592
|
+
normalizePressureValue(concept.reviewKey),
|
|
1593
|
+
ontologyReviewKeyBody(concept.reviewKey),
|
|
1594
|
+
...((concept.relatedMotifIds ?? []).map((id) => ontologyMotifTerm(id))),
|
|
1595
|
+
...((concept.derivedFromKinds ?? []).map((value) => normalizePressureValue(value))),
|
|
1596
|
+
].filter(Boolean))
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
function ontologyBindingId(conceptId: string, targetType: OntologyBindingTargetType, targetId: string, sourceKind: OntologyBindingSourceKind): string {
|
|
1600
|
+
return `ontology_binding_${slugFragment(conceptId)}_${targetType}_${slugFragment(targetId)}_${sourceKind}`
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
function buildOntologyBinding(params: {
|
|
1604
|
+
concept: OntologyConcept
|
|
1605
|
+
targetType: OntologyBindingTargetType
|
|
1606
|
+
targetId: string
|
|
1607
|
+
sourceKind: OntologyBindingSourceKind
|
|
1608
|
+
confidence: number
|
|
1609
|
+
reasons: string[]
|
|
1610
|
+
effectSummary?: string
|
|
1611
|
+
}): ResolvedOntologyBinding {
|
|
1612
|
+
return {
|
|
1613
|
+
id: ontologyBindingId(params.concept.id, params.targetType, params.targetId, params.sourceKind),
|
|
1614
|
+
conceptId: params.concept.id,
|
|
1615
|
+
conceptName: params.concept.name,
|
|
1616
|
+
conceptKind: params.concept.conceptKind,
|
|
1617
|
+
targetType: params.targetType,
|
|
1618
|
+
targetId: params.targetId,
|
|
1619
|
+
sourceKind: params.sourceKind,
|
|
1620
|
+
confidence: Math.min(0.95, Math.max(0.5, params.confidence)),
|
|
1621
|
+
reasons: params.reasons,
|
|
1622
|
+
effectSummary: params.effectSummary,
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
function dedupeOntologyBindings(bindings: ResolvedOntologyBinding[]): ResolvedOntologyBinding[] {
|
|
1627
|
+
const byKey = new Map<string, ResolvedOntologyBinding>()
|
|
1628
|
+
for (const binding of bindings) {
|
|
1629
|
+
const key = `${binding.conceptId}::${binding.targetType}::${binding.targetId}`
|
|
1630
|
+
const existing = byKey.get(key)
|
|
1631
|
+
if (!existing || binding.confidence > existing.confidence) byKey.set(key, binding)
|
|
1632
|
+
}
|
|
1633
|
+
return [...byKey.values()]
|
|
1634
|
+
.sort((a, b) => b.confidence - a.confidence || a.conceptName.localeCompare(b.conceptName))
|
|
1635
|
+
.slice(0, 4)
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
function activeOntologyExtensions(): OntologyConcept[] {
|
|
1639
|
+
return loadOntologyExtensions().filter((concept) => concept.status === 'active')
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
function matchSignalOntologyBindings(signal: PressureSignal, motifIds: string[], concepts: OntologyConcept[]): ResolvedOntologyBinding[] {
|
|
1643
|
+
const capability = normalizePressureValue(signal.capability)
|
|
1644
|
+
const region = normalizePressureValue(signal.region)
|
|
1645
|
+
const kind = normalizePressureValue(signal.kind)
|
|
1646
|
+
const motifTerms = uniqueStrings(motifIds.map((id) => ontologyMotifTerm(id)).filter(Boolean))
|
|
1647
|
+
const bindings: ResolvedOntologyBinding[] = []
|
|
1648
|
+
|
|
1649
|
+
for (const concept of concepts) {
|
|
1650
|
+
const terms = ontologyConceptTerms(concept)
|
|
1651
|
+
if (concept.conceptKind === 'capability') {
|
|
1652
|
+
if (capability && terms.includes(capability)) {
|
|
1653
|
+
bindings.push(buildOntologyBinding({
|
|
1654
|
+
concept,
|
|
1655
|
+
targetType: 'pressure-signal',
|
|
1656
|
+
targetId: signal.id,
|
|
1657
|
+
sourceKind: 'capability-match',
|
|
1658
|
+
confidence: 0.86,
|
|
1659
|
+
reasons: [`Signal capability ${signal.capability} matches approved capability concept ${concept.name}.`],
|
|
1660
|
+
effectSummary: 'Sharpens project or capability interpretation for this pressure signal.',
|
|
1661
|
+
}))
|
|
1662
|
+
continue
|
|
1663
|
+
}
|
|
1664
|
+
if (region && terms.includes(region)) {
|
|
1665
|
+
bindings.push(buildOntologyBinding({
|
|
1666
|
+
concept,
|
|
1667
|
+
targetType: 'pressure-signal',
|
|
1668
|
+
targetId: signal.id,
|
|
1669
|
+
sourceKind: 'pressure-region',
|
|
1670
|
+
confidence: 0.74,
|
|
1671
|
+
reasons: [`Signal region ${signal.region} aligns with approved capability concept ${concept.name}.`],
|
|
1672
|
+
effectSummary: 'Adds semantic coverage to a recurring pressure region.',
|
|
1673
|
+
}))
|
|
1674
|
+
}
|
|
1675
|
+
continue
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
if (concept.conceptKind === 'pressure-type') {
|
|
1679
|
+
if ((concept.relatedMotifIds ?? []).some((id) => motifIds.includes(id))) {
|
|
1680
|
+
bindings.push(buildOntologyBinding({
|
|
1681
|
+
concept,
|
|
1682
|
+
targetType: 'pressure-signal',
|
|
1683
|
+
targetId: signal.id,
|
|
1684
|
+
sourceKind: 'motif-match',
|
|
1685
|
+
confidence: 0.9,
|
|
1686
|
+
reasons: [`Signal participates in approved motif family ${concept.name}.`],
|
|
1687
|
+
effectSummary: 'Marks this signal as part of an approved recurring pressure family.',
|
|
1688
|
+
}))
|
|
1689
|
+
continue
|
|
1690
|
+
}
|
|
1691
|
+
if (motifTerms.some((term) => terms.includes(term))) {
|
|
1692
|
+
bindings.push(buildOntologyBinding({
|
|
1693
|
+
concept,
|
|
1694
|
+
targetType: 'pressure-signal',
|
|
1695
|
+
targetId: signal.id,
|
|
1696
|
+
sourceKind: 'motif-match',
|
|
1697
|
+
confidence: 0.84,
|
|
1698
|
+
reasons: [`Signal motif region aligns with approved pressure family ${concept.name}.`],
|
|
1699
|
+
effectSummary: 'Carries an approved recurring pressure family into live signal interpretation.',
|
|
1700
|
+
}))
|
|
1701
|
+
continue
|
|
1702
|
+
}
|
|
1703
|
+
if (kind && terms.includes(kind)) {
|
|
1704
|
+
bindings.push(buildOntologyBinding({
|
|
1705
|
+
concept,
|
|
1706
|
+
targetType: 'pressure-signal',
|
|
1707
|
+
targetId: signal.id,
|
|
1708
|
+
sourceKind: 'pressure-region',
|
|
1709
|
+
confidence: 0.78,
|
|
1710
|
+
reasons: [`Signal kind ${signal.kind} matches approved pressure family ${concept.name}.`],
|
|
1711
|
+
effectSummary: 'Adds semantic identity to recurring pressure of the same kind.',
|
|
1712
|
+
}))
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
return dedupeOntologyBindings(bindings)
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
function matchMotifOntologyBindings(motif: Pick<ResolvedPressureMotif, 'id' | 'key' | 'kind' | 'capability'>, concepts: OntologyConcept[]): ResolvedOntologyBinding[] {
|
|
1721
|
+
const capability = normalizePressureValue(motif.capability)
|
|
1722
|
+
const key = normalizePressureValue(motif.key)
|
|
1723
|
+
const kind = normalizePressureValue(motif.kind)
|
|
1724
|
+
const bindings: ResolvedOntologyBinding[] = []
|
|
1725
|
+
|
|
1726
|
+
for (const concept of concepts) {
|
|
1727
|
+
const terms = ontologyConceptTerms(concept)
|
|
1728
|
+
if (concept.conceptKind === 'capability' && capability && terms.includes(capability)) {
|
|
1729
|
+
bindings.push(buildOntologyBinding({
|
|
1730
|
+
concept,
|
|
1731
|
+
targetType: 'pressure-motif',
|
|
1732
|
+
targetId: motif.id,
|
|
1733
|
+
sourceKind: 'capability-match',
|
|
1734
|
+
confidence: 0.84,
|
|
1735
|
+
reasons: [`Motif capability ${motif.capability} matches approved capability concept ${concept.name}.`],
|
|
1736
|
+
effectSummary: 'Makes recurring capability pressure visible as semantic adoption.',
|
|
1737
|
+
}))
|
|
1738
|
+
continue
|
|
1739
|
+
}
|
|
1740
|
+
if (concept.conceptKind === 'pressure-type') {
|
|
1741
|
+
if ((concept.relatedMotifIds ?? []).includes(motif.id)) {
|
|
1742
|
+
bindings.push(buildOntologyBinding({
|
|
1743
|
+
concept,
|
|
1744
|
+
targetType: 'pressure-motif',
|
|
1745
|
+
targetId: motif.id,
|
|
1746
|
+
sourceKind: 'motif-match',
|
|
1747
|
+
confidence: 0.92,
|
|
1748
|
+
reasons: [`Motif ${motif.key} directly matches approved pressure family ${concept.name}.`],
|
|
1749
|
+
effectSummary: 'Confirms this recurring motif is an approved semantic family.',
|
|
1750
|
+
}))
|
|
1751
|
+
continue
|
|
1752
|
+
}
|
|
1753
|
+
if ([key, kind].filter(Boolean).some((value) => terms.includes(value))) {
|
|
1754
|
+
bindings.push(buildOntologyBinding({
|
|
1755
|
+
concept,
|
|
1756
|
+
targetType: 'pressure-motif',
|
|
1757
|
+
targetId: motif.id,
|
|
1758
|
+
sourceKind: 'pressure-region',
|
|
1759
|
+
confidence: 0.86,
|
|
1760
|
+
reasons: [`Motif identity aligns with approved pressure family ${concept.name}.`],
|
|
1761
|
+
effectSummary: 'Carries approved semantic family identity into motif-level routing.',
|
|
1762
|
+
}))
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
return dedupeOntologyBindings(bindings)
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
function matchTransferOntologyBindings(event: TransferEvent, concepts: OntologyConcept[]): ResolvedOntologyBinding[] {
|
|
1771
|
+
const capabilityTerms = uniqueStrings((event.capabilityIds ?? []).map((value) => normalizePressureValue(value)).filter(Boolean))
|
|
1772
|
+
const motifTerms = uniqueStrings((event.motifIds ?? []).map((value) => ontologyMotifTerm(value)).filter(Boolean))
|
|
1773
|
+
const bindings: ResolvedOntologyBinding[] = []
|
|
1774
|
+
|
|
1775
|
+
for (const concept of concepts) {
|
|
1776
|
+
const terms = ontologyConceptTerms(concept)
|
|
1777
|
+
if (concept.conceptKind === 'capability' && capabilityTerms.some((value) => terms.includes(value))) {
|
|
1778
|
+
bindings.push(buildOntologyBinding({
|
|
1779
|
+
concept,
|
|
1780
|
+
targetType: 'transfer-event',
|
|
1781
|
+
targetId: event.id,
|
|
1782
|
+
sourceKind: 'capability-match',
|
|
1783
|
+
confidence: 0.82,
|
|
1784
|
+
reasons: [`Transfer capabilities align with approved concept ${concept.name}.`],
|
|
1785
|
+
effectSummary: 'Shows semantic adoption through reusable transfer evidence.',
|
|
1786
|
+
}))
|
|
1787
|
+
continue
|
|
1788
|
+
}
|
|
1789
|
+
if (concept.conceptKind === 'pressure-type') {
|
|
1790
|
+
if ((concept.relatedMotifIds ?? []).some((id) => (event.motifIds ?? []).includes(id))) {
|
|
1791
|
+
bindings.push(buildOntologyBinding({
|
|
1792
|
+
concept,
|
|
1793
|
+
targetType: 'transfer-event',
|
|
1794
|
+
targetId: event.id,
|
|
1795
|
+
sourceKind: 'transfer-motif',
|
|
1796
|
+
confidence: 0.88,
|
|
1797
|
+
reasons: [`Transfer realizes approved recurring family ${concept.name}.`],
|
|
1798
|
+
effectSummary: 'Connects realized transfer evidence to an approved recurring pressure family.',
|
|
1799
|
+
}))
|
|
1800
|
+
continue
|
|
1801
|
+
}
|
|
1802
|
+
if (motifTerms.some((value) => terms.includes(value))) {
|
|
1803
|
+
bindings.push(buildOntologyBinding({
|
|
1804
|
+
concept,
|
|
1805
|
+
targetType: 'transfer-event',
|
|
1806
|
+
targetId: event.id,
|
|
1807
|
+
sourceKind: 'transfer-motif',
|
|
1808
|
+
confidence: 0.8,
|
|
1809
|
+
reasons: [`Transfer motif linkage aligns with approved family ${concept.name}.`],
|
|
1810
|
+
effectSummary: 'Carries approved semantic family identity into realized transfer outcomes.',
|
|
1811
|
+
}))
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
return dedupeOntologyBindings(bindings)
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
function topologyMotifDescriptor(candidate: ResolvedTopologyReviewCandidate): string | null {
|
|
1820
|
+
if (candidate.changeType === 'split' && candidate.title.startsWith('Split overloaded skill:')) return 'overloaded-skill-split-pressure'
|
|
1821
|
+
if (candidate.changeType === 'rewire' && candidate.title.startsWith('Resolve conflicting pair:')) return 'conflict-rewire-pressure'
|
|
1822
|
+
if (candidate.changeType === 'promote' && candidate.title.startsWith('Promote recurring motif:')) return 'motif-promotion-pressure'
|
|
1823
|
+
if (candidate.changeType === 'merge' && candidate.title.startsWith('Merge overlap:')) return 'overlap-merge-pressure'
|
|
1824
|
+
if (candidate.changeType === 'consolidate') return 'consolidation-pressure'
|
|
1825
|
+
return null
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
function matchTopologyOntologyBindings(candidate: ResolvedTopologyReviewCandidate, concepts: OntologyConcept[]): ResolvedOntologyBinding[] {
|
|
1829
|
+
const descriptor = normalizePressureValue(topologyMotifDescriptor(candidate) ?? candidate.changeType)
|
|
1830
|
+
const bindings: ResolvedOntologyBinding[] = []
|
|
1831
|
+
|
|
1832
|
+
for (const concept of concepts) {
|
|
1833
|
+
const terms = ontologyConceptTerms(concept)
|
|
1834
|
+
if (concept.conceptKind === 'topology-motif' && descriptor && terms.includes(descriptor)) {
|
|
1835
|
+
bindings.push(buildOntologyBinding({
|
|
1836
|
+
concept,
|
|
1837
|
+
targetType: 'topology-review',
|
|
1838
|
+
targetId: candidate.id,
|
|
1839
|
+
sourceKind: 'topology-descriptor',
|
|
1840
|
+
confidence: 0.86,
|
|
1841
|
+
reasons: [`Topology review descriptor ${descriptor} aligns with approved topology concept ${concept.name}.`],
|
|
1842
|
+
effectSummary: 'Adds semantic identity to recurring structural review patterns.',
|
|
1843
|
+
}))
|
|
1844
|
+
continue
|
|
1845
|
+
}
|
|
1846
|
+
if (concept.conceptKind === 'pressure-type' && (concept.relatedMotifIds ?? []).some((id) => (candidate.relatedMotifIds ?? []).includes(id))) {
|
|
1847
|
+
bindings.push(buildOntologyBinding({
|
|
1848
|
+
concept,
|
|
1849
|
+
targetType: 'topology-review',
|
|
1850
|
+
targetId: candidate.id,
|
|
1851
|
+
sourceKind: 'motif-match',
|
|
1852
|
+
confidence: 0.72,
|
|
1853
|
+
reasons: [`Topology candidate inherits recurring pressure motif semantics from approved family ${concept.name}.`],
|
|
1854
|
+
effectSummary: 'Links structural review back to an approved recurring pressure family.',
|
|
1855
|
+
}))
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
return dedupeOntologyBindings(bindings)
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
function applyOntologyInfluenceToRoute(params: {
|
|
1863
|
+
recommendation: PressureRouteRecommendation
|
|
1864
|
+
signal: PressureSignal
|
|
1865
|
+
semanticBindings: ResolvedOntologyBinding[]
|
|
1866
|
+
governanceProfile: GovernanceProfile
|
|
1867
|
+
}): PressureRouteRecommendation {
|
|
1868
|
+
const { recommendation, signal, semanticBindings, governanceProfile } = params
|
|
1869
|
+
if (semanticBindings.length === 0) return recommendation
|
|
1870
|
+
|
|
1871
|
+
const conceptIds = uniqueStrings(semanticBindings.map((binding) => binding.conceptId))
|
|
1872
|
+
const conceptNames = uniqueStrings(semanticBindings.map((binding) => binding.conceptName))
|
|
1873
|
+
const hasCapability = semanticBindings.some((binding) => binding.conceptKind === 'capability')
|
|
1874
|
+
const hasPressureType = semanticBindings.some((binding) => binding.conceptKind === 'pressure-type')
|
|
1875
|
+
const hasTopologyMotif = semanticBindings.some((binding) => binding.conceptKind === 'topology-motif')
|
|
1876
|
+
const reasons = [...recommendation.reasons]
|
|
1877
|
+
let confidence = recommendation.confidence
|
|
1878
|
+
let semanticInfluence: OntologySemanticInfluence = 'explanatory'
|
|
1879
|
+
|
|
1880
|
+
if (recommendation.route === 'generalize' && hasPressureType && governanceProfile.riskTolerance >= 0.4 && governanceProfile.reviewThreshold <= 0.82) {
|
|
1881
|
+
confidence = Math.min(0.95, confidence + 0.04)
|
|
1882
|
+
semanticInfluence = 'weighted'
|
|
1883
|
+
reasons.push(`Approved semantic family ${conceptNames.join(', ')} reinforces network-level reuse inside the current governance envelope.`)
|
|
1884
|
+
} else if (recommendation.route === 'specialize' && hasCapability && signal.projectId && governanceProfile.reviewThreshold <= 0.86) {
|
|
1885
|
+
confidence = Math.min(0.95, confidence + 0.03)
|
|
1886
|
+
semanticInfluence = 'weighted'
|
|
1887
|
+
reasons.push(`Approved capability concept ${conceptNames.join(', ')} sharpens project-bounded specialization without widening scope.`)
|
|
1888
|
+
} else if (recommendation.route === 'evolve' && hasPressureType && signal.relatedFailureId) {
|
|
1889
|
+
confidence = Math.min(0.95, confidence + 0.02)
|
|
1890
|
+
semanticInfluence = 'weighted'
|
|
1891
|
+
reasons.push(`Approved semantic family ${conceptNames.join(', ')} sharpens this failure-linked repair lane.`)
|
|
1892
|
+
} else if (recommendation.route === 'manual-review' && (hasTopologyMotif || hasPressureType)) {
|
|
1893
|
+
reasons.push(`Approved semantic family ${conceptNames.join(', ')} confirms the pattern is real, but governance still prefers explicit operator review.`)
|
|
1894
|
+
} else {
|
|
1895
|
+
reasons.push(`Approved semantic family ${conceptNames.join(', ')} is visible here, but current governance/evidence still keeps the route bounded to ${recommendation.route}.`)
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
return {
|
|
1899
|
+
...recommendation,
|
|
1900
|
+
confidence,
|
|
1901
|
+
reasons,
|
|
1902
|
+
semanticInfluence,
|
|
1903
|
+
semanticConceptIds: conceptIds,
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1168
1907
|
function buildRouteRecommendation(params: {
|
|
1169
1908
|
signal: PressureSignal
|
|
1170
1909
|
relatedSignals: PressureSignal[]
|
|
1171
1910
|
linkedInterventions: PressureIntervention[]
|
|
1172
1911
|
governanceMode: GovernanceModeName
|
|
1912
|
+
governanceProfile: GovernanceProfile
|
|
1913
|
+
semanticBindings: ResolvedOntologyBinding[]
|
|
1173
1914
|
}): PressureRouteRecommendation {
|
|
1174
|
-
const { signal, relatedSignals, linkedInterventions, governanceMode } = params
|
|
1915
|
+
const { signal, relatedSignals, linkedInterventions, governanceMode, governanceProfile, semanticBindings } = params
|
|
1175
1916
|
const projectSpread = new Set(relatedSignals.map((entry) => entry.projectId ?? entry.projectPath).filter(Boolean)).size
|
|
1176
1917
|
const recurrenceCount = relatedSignals.length
|
|
1177
1918
|
const activeTypes = [...new Set(linkedInterventions.map((intervention) => intervention.interventionType))]
|
|
1178
1919
|
const reasons: string[] = []
|
|
1920
|
+
let recommendation: PressureRouteRecommendation
|
|
1179
1921
|
|
|
1180
1922
|
if (activeTypes.length >= 3 && !linkedInterventions.some((intervention) => intervention.status === 'completed' && intervention.impact === 'resolving')) {
|
|
1181
1923
|
reasons.push('Multiple intervention lanes already touch this pressure region without clear closure.')
|
|
1182
1924
|
reasons.push('Operator review is safer than blindly piling on another automated response.')
|
|
1183
|
-
|
|
1925
|
+
recommendation = {
|
|
1184
1926
|
route: 'manual-review',
|
|
1185
1927
|
scope: projectSpread >= 2 ? 'network' : signal.projectId ? 'project' : 'local',
|
|
1186
1928
|
confidence: 0.58,
|
|
@@ -1188,27 +1930,21 @@ function buildRouteRecommendation(params: {
|
|
|
1188
1930
|
triggeredBy: 'mixed-signal',
|
|
1189
1931
|
reasons,
|
|
1190
1932
|
}
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
if (projectSpread >= 2 && recurrenceCount >= 2) {
|
|
1933
|
+
} else if (projectSpread >= 2 && recurrenceCount >= 2 && governanceMode !== 'project-critical') {
|
|
1194
1934
|
reasons.push(`This pressure region repeats across ${projectSpread} projects.`)
|
|
1195
1935
|
reasons.push('A network-level abstraction can absorb repeated local demand more efficiently than one-off fixes.')
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
reasons,
|
|
1204
|
-
}
|
|
1936
|
+
recommendation = {
|
|
1937
|
+
route: 'generalize',
|
|
1938
|
+
scope: 'network',
|
|
1939
|
+
confidence: Math.min(0.95, 0.7 + Math.min(0.15, (projectSpread - 1) * 0.08)),
|
|
1940
|
+
governanceMode,
|
|
1941
|
+
triggeredBy: 'recurring-cross-project-gap',
|
|
1942
|
+
reasons,
|
|
1205
1943
|
}
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
if (signal.projectId && signal.priority === 'high' && signal.suggestedAction === 'specialize') {
|
|
1944
|
+
} else if (signal.projectId && signal.priority === 'high' && signal.suggestedAction === 'specialize') {
|
|
1209
1945
|
reasons.push('The pressure is high priority and bounded to a known project context.')
|
|
1210
1946
|
reasons.push('Project-layer adaptation is the shortest truthful path to response.')
|
|
1211
|
-
|
|
1947
|
+
recommendation = {
|
|
1212
1948
|
route: 'specialize',
|
|
1213
1949
|
scope: 'project',
|
|
1214
1950
|
confidence: 0.82,
|
|
@@ -1216,12 +1952,10 @@ function buildRouteRecommendation(params: {
|
|
|
1216
1952
|
triggeredBy: 'high-priority-local-gap',
|
|
1217
1953
|
reasons,
|
|
1218
1954
|
}
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
if (signal.relatedFailureId && signal.severity >= 0.8) {
|
|
1955
|
+
} else if (signal.relatedFailureId && signal.severity >= 0.8) {
|
|
1222
1956
|
reasons.push('This pressure is tied to a concrete failure with strong corrective evidence.')
|
|
1223
1957
|
reasons.push('An evolution proposal can test a direct skill-level response against replay and regression evidence.')
|
|
1224
|
-
|
|
1958
|
+
recommendation = {
|
|
1225
1959
|
route: 'evolve',
|
|
1226
1960
|
scope: signal.projectId ? 'project' : 'local',
|
|
1227
1961
|
confidence: 0.78,
|
|
@@ -1229,12 +1963,10 @@ function buildRouteRecommendation(params: {
|
|
|
1229
1963
|
triggeredBy: 'accepted-iteration-pattern',
|
|
1230
1964
|
reasons,
|
|
1231
1965
|
}
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
if (governanceMode === 'exploration' || signal.suggestedAction === 'research' || !signal.projectId) {
|
|
1966
|
+
} else if (governanceMode === 'exploration' || signal.suggestedAction === 'research' || !signal.projectId) {
|
|
1235
1967
|
reasons.push('The pressure still looks under-specified or capability-oriented.')
|
|
1236
1968
|
reasons.push('Research is the best way to widen evidence before committing to structural change.')
|
|
1237
|
-
|
|
1969
|
+
recommendation = {
|
|
1238
1970
|
route: 'research',
|
|
1239
1971
|
scope: signal.projectId ? 'project' : 'local',
|
|
1240
1972
|
confidence: 0.68,
|
|
@@ -1242,12 +1974,10 @@ function buildRouteRecommendation(params: {
|
|
|
1242
1974
|
triggeredBy: 'insufficient-evidence',
|
|
1243
1975
|
reasons,
|
|
1244
1976
|
}
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
if (signal.projectId) {
|
|
1977
|
+
} else if (signal.projectId) {
|
|
1248
1978
|
reasons.push('The pressure is project-scoped and can likely be handled without network-wide promotion yet.')
|
|
1249
1979
|
reasons.push('Specialization is the most direct bounded intervention available.')
|
|
1250
|
-
|
|
1980
|
+
recommendation = {
|
|
1251
1981
|
route: 'specialize',
|
|
1252
1982
|
scope: 'project',
|
|
1253
1983
|
confidence: 0.7,
|
|
@@ -1255,18 +1985,25 @@ function buildRouteRecommendation(params: {
|
|
|
1255
1985
|
triggeredBy: 'project-bounded-recurrence',
|
|
1256
1986
|
reasons,
|
|
1257
1987
|
}
|
|
1988
|
+
} else {
|
|
1989
|
+
reasons.push('No single route is dominant from the current evidence.')
|
|
1990
|
+
reasons.push('Research keeps the loop moving while preserving optionality.')
|
|
1991
|
+
recommendation = {
|
|
1992
|
+
route: 'research',
|
|
1993
|
+
scope: 'local',
|
|
1994
|
+
confidence: 0.6,
|
|
1995
|
+
governanceMode,
|
|
1996
|
+
triggeredBy: 'insufficient-evidence',
|
|
1997
|
+
reasons,
|
|
1998
|
+
}
|
|
1258
1999
|
}
|
|
1259
2000
|
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
governanceMode,
|
|
1267
|
-
triggeredBy: 'insufficient-evidence',
|
|
1268
|
-
reasons,
|
|
1269
|
-
}
|
|
2001
|
+
return applyOntologyInfluenceToRoute({
|
|
2002
|
+
recommendation,
|
|
2003
|
+
signal,
|
|
2004
|
+
semanticBindings,
|
|
2005
|
+
governanceProfile,
|
|
2006
|
+
})
|
|
1270
2007
|
}
|
|
1271
2008
|
|
|
1272
2009
|
function interventionEvidenceAccepted(
|
|
@@ -1299,6 +2036,7 @@ export function loadResolvedPressureSignals(): ResolvedPressureSignal[] {
|
|
|
1299
2036
|
const history = loadHistory()
|
|
1300
2037
|
const artifacts = loadEvolutionArtifacts()
|
|
1301
2038
|
const governance = deriveGovernanceSummary(pressureSignals, interventions)
|
|
2039
|
+
const concepts = activeOntologyExtensions()
|
|
1302
2040
|
const acceptedProposalIds = new Set(
|
|
1303
2041
|
history.iterations.flatMap((iteration) => iteration.proposals)
|
|
1304
2042
|
.filter((proposal) => proposal.outcome === 'accepted')
|
|
@@ -1336,11 +2074,14 @@ export function loadResolvedPressureSignals(): ResolvedPressureSignal[] {
|
|
|
1336
2074
|
const relatedSignals = signalsByRegion.get(pressureRegionKey(signal)) ?? [signal]
|
|
1337
2075
|
const projectSpread = new Set(relatedSignals.map((entry) => entry.projectId ?? entry.projectPath).filter(Boolean)).size
|
|
1338
2076
|
const motifIds = relatedSignals.length >= 2 || projectSpread >= 2 ? [motifIdForSignal(signal)] : []
|
|
2077
|
+
const semanticBindings = matchSignalOntologyBindings(signal, motifIds, concepts)
|
|
1339
2078
|
const routeRecommendation = buildRouteRecommendation({
|
|
1340
2079
|
signal,
|
|
1341
2080
|
relatedSignals,
|
|
1342
2081
|
linkedInterventions,
|
|
1343
2082
|
governanceMode: governance.activeMode,
|
|
2083
|
+
governanceProfile: governance.profile,
|
|
2084
|
+
semanticBindings,
|
|
1344
2085
|
})
|
|
1345
2086
|
|
|
1346
2087
|
const lifecycle: PressureLifecycleState = addressingInterventions.length > 0 || signal.status === 'addressed'
|
|
@@ -1354,13 +2095,15 @@ export function loadResolvedPressureSignals(): ResolvedPressureSignal[] {
|
|
|
1354
2095
|
const lastActivityAt = [signal.detectedAt, latestInterventionAt].filter(Boolean).sort((a, b) => b.localeCompare(a))[0] ?? signal.detectedAt
|
|
1355
2096
|
const interventionTypes = [...new Set(linkedInterventions.map((intervention) => intervention.interventionType))]
|
|
1356
2097
|
const addressedAt = addressingInterventions.map((intervention) => intervention.completedAt ?? intervention.createdAt).sort((a, b) => b.localeCompare(a))[0]
|
|
2098
|
+
const semanticNames = uniqueStrings(semanticBindings.map((binding) => binding.conceptName))
|
|
2099
|
+
const semanticNote = semanticNames.length > 0 ? ` • semantic family ${semanticNames.join(', ')}` : ''
|
|
1357
2100
|
const responseSummary = lifecycle === 'addressed'
|
|
1358
|
-
? `Addressed by ${interventionTypes.join(', ') || 'linked intervention'} evidence`
|
|
2101
|
+
? `Addressed by ${interventionTypes.join(', ') || 'linked intervention'} evidence${semanticNote}`
|
|
1359
2102
|
: lifecycle === 'in-progress'
|
|
1360
|
-
? `Response underway via ${interventionTypes.join(', ')}`
|
|
2103
|
+
? `Response underway via ${interventionTypes.join(', ')}${semanticNote}`
|
|
1361
2104
|
: lifecycle === 'stale'
|
|
1362
2105
|
? 'Superseded by newer pressure in the same project/capability region'
|
|
1363
|
-
: `No linked response yet • recommend ${routeRecommendation.route}`
|
|
2106
|
+
: `No linked response yet • recommend ${routeRecommendation.route}${semanticNote}`
|
|
1364
2107
|
|
|
1365
2108
|
return {
|
|
1366
2109
|
...signal,
|
|
@@ -1369,6 +2112,8 @@ export function loadResolvedPressureSignals(): ResolvedPressureSignal[] {
|
|
|
1369
2112
|
interventionTypes,
|
|
1370
2113
|
motifIds,
|
|
1371
2114
|
routeRecommendation,
|
|
2115
|
+
semanticBindings,
|
|
2116
|
+
semanticConceptIds: uniqueStrings(semanticBindings.map((binding) => binding.conceptId)),
|
|
1372
2117
|
lastActivityAt,
|
|
1373
2118
|
addressedAt,
|
|
1374
2119
|
responseSummary,
|
|
@@ -1387,6 +2132,7 @@ export function loadPressureMotifs(): ResolvedPressureMotif[] {
|
|
|
1387
2132
|
const interventions = loadPressureInterventions()
|
|
1388
2133
|
const transferEvents = loadTransferEvents()
|
|
1389
2134
|
const governance = deriveGovernanceSummary()
|
|
2135
|
+
const concepts = activeOntologyExtensions()
|
|
1390
2136
|
const groups = new Map<string, ResolvedPressureSignal[]>()
|
|
1391
2137
|
|
|
1392
2138
|
for (const signal of resolvedSignals) {
|
|
@@ -1443,15 +2189,23 @@ export function loadPressureMotifs(): ResolvedPressureMotif[] {
|
|
|
1443
2189
|
const linkedInterventionIds = linkedInterventions.map((intervention) => intervention.id)
|
|
1444
2190
|
const linkedTransferEventIds = linkedTransferEvents.map((event) => event.id)
|
|
1445
2191
|
const highPriorityCount = signals.filter((signal) => signal.priority === 'high').length
|
|
2192
|
+
const semanticBindings = matchMotifOntologyBindings({
|
|
2193
|
+
id: motifId,
|
|
2194
|
+
key: regionKey,
|
|
2195
|
+
kind: signals[0]?.kind ?? regionKey,
|
|
2196
|
+
capability,
|
|
2197
|
+
}, concepts)
|
|
2198
|
+
const semanticNames = uniqueStrings(semanticBindings.map((binding) => binding.conceptName))
|
|
2199
|
+
const semanticNote = semanticNames.length > 0 ? ` • semantic family ${semanticNames.join(', ')}` : ''
|
|
1446
2200
|
const responseSummary = lifecycle === 'addressed'
|
|
1447
2201
|
? realizedTransfers.length > 0
|
|
1448
|
-
?
|
|
1449
|
-
:
|
|
2202
|
+
? `Promoted into reusable transfer evidence${semanticNote}`
|
|
2203
|
+
: `All linked pressure in this recurring region is addressed${semanticNote}`
|
|
1450
2204
|
: lifecycle === 'in-progress'
|
|
1451
|
-
? `Promotion or response underway via ${interventionTypes.join(', ') || 'linked intervention'}`
|
|
2205
|
+
? `Promotion or response underway via ${interventionTypes.join(', ') || 'linked intervention'}${semanticNote}`
|
|
1452
2206
|
: lifecycle === 'stale'
|
|
1453
2207
|
? 'Recurring pressure has been superseded by newer equivalent signals'
|
|
1454
|
-
: `Recurring demand is waiting for ${recommendation.route}`
|
|
2208
|
+
: `Recurring demand is waiting for ${recommendation.route}${semanticNote}`
|
|
1455
2209
|
|
|
1456
2210
|
motifs.push({
|
|
1457
2211
|
id: motifId,
|
|
@@ -1471,6 +2225,8 @@ export function loadPressureMotifs(): ResolvedPressureMotif[] {
|
|
|
1471
2225
|
suggestedRoute: recommendation,
|
|
1472
2226
|
lifecycle,
|
|
1473
2227
|
interventionTypes,
|
|
2228
|
+
semanticBindings,
|
|
2229
|
+
semanticConceptIds: uniqueStrings(semanticBindings.map((binding) => binding.conceptId)),
|
|
1474
2230
|
lastActivityAt,
|
|
1475
2231
|
addressedAt,
|
|
1476
2232
|
responseSummary,
|
|
@@ -1486,6 +2242,126 @@ export function loadPressureMotifs(): ResolvedPressureMotif[] {
|
|
|
1486
2242
|
})
|
|
1487
2243
|
}
|
|
1488
2244
|
|
|
2245
|
+
function collectOntologyAdoptionState(): { summary: OntologyAdoptionSummary, consumerMap: Map<string, OntologyConsumerSummary> } {
|
|
2246
|
+
const extensions = loadOntologyExtensions()
|
|
2247
|
+
const activeExtensions = extensions.filter((concept) => concept.status === 'active')
|
|
2248
|
+
const byConceptKind = ontologyKindCounts()
|
|
2249
|
+
const bindingsByTargetType = emptyOntologyBindingCounts()
|
|
2250
|
+
const consumerMap = new Map<string, OntologyConsumerSummary>()
|
|
2251
|
+
const conceptById = new Map(extensions.map((concept) => [concept.id, concept]))
|
|
2252
|
+
|
|
2253
|
+
function ensureConsumer(concept: OntologyConcept): OntologyConsumerSummary {
|
|
2254
|
+
const existing = consumerMap.get(concept.id)
|
|
2255
|
+
if (existing) return existing
|
|
2256
|
+
const created: OntologyConsumerSummary = {
|
|
2257
|
+
conceptId: concept.id,
|
|
2258
|
+
conceptName: concept.name,
|
|
2259
|
+
conceptKind: concept.conceptKind,
|
|
2260
|
+
status: concept.status,
|
|
2261
|
+
totalBindings: 0,
|
|
2262
|
+
bindingsByTargetType: emptyOntologyBindingCounts(),
|
|
2263
|
+
activeTargetIds: [],
|
|
2264
|
+
lastActivityAt: concept.lastObservedAt ?? concept.createdAt,
|
|
2265
|
+
}
|
|
2266
|
+
consumerMap.set(concept.id, created)
|
|
2267
|
+
return created
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
function registerBinding(binding: ResolvedOntologyBinding) {
|
|
2271
|
+
const concept = conceptById.get(binding.conceptId)
|
|
2272
|
+
if (!concept || concept.status !== 'active') return
|
|
2273
|
+
const consumer = ensureConsumer(concept)
|
|
2274
|
+
consumer.totalBindings += 1
|
|
2275
|
+
consumer.bindingsByTargetType[binding.targetType] = (consumer.bindingsByTargetType[binding.targetType] ?? 0) + 1
|
|
2276
|
+
if (!consumer.activeTargetIds.includes(binding.targetId) && consumer.activeTargetIds.length < 12) {
|
|
2277
|
+
consumer.activeTargetIds.push(binding.targetId)
|
|
2278
|
+
}
|
|
2279
|
+
consumer.lastActivityAt = maxTimestamp(consumer.lastActivityAt, concept.lastObservedAt, concept.createdAt)
|
|
2280
|
+
bindingsByTargetType[binding.targetType] = (bindingsByTargetType[binding.targetType] ?? 0) + 1
|
|
2281
|
+
byConceptKind[concept.conceptKind] = (byConceptKind[concept.conceptKind] ?? 0) + 1
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
const signals = loadResolvedPressureSignals()
|
|
2285
|
+
for (const signal of signals) {
|
|
2286
|
+
for (const binding of signal.semanticBindings ?? []) registerBinding(binding)
|
|
2287
|
+
if (signal.routeRecommendation?.semanticConceptIds?.length) {
|
|
2288
|
+
for (const conceptId of signal.routeRecommendation.semanticConceptIds) {
|
|
2289
|
+
const concept = conceptById.get(conceptId)
|
|
2290
|
+
if (!concept || concept.status !== 'active') continue
|
|
2291
|
+
registerBinding(buildOntologyBinding({
|
|
2292
|
+
concept,
|
|
2293
|
+
targetType: 'route-recommendation',
|
|
2294
|
+
targetId: `${signal.id}:route`,
|
|
2295
|
+
sourceKind: 'pressure-region',
|
|
2296
|
+
confidence: signal.routeRecommendation.semanticInfluence === 'weighted' ? 0.82 : 0.72,
|
|
2297
|
+
reasons: signal.routeRecommendation.reasons.slice(0, 2),
|
|
2298
|
+
effectSummary: `Route recommendation ${signal.routeRecommendation.route} is semantically informed by ${concept.name}.`,
|
|
2299
|
+
}))
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
for (const motif of loadPressureMotifs()) {
|
|
2305
|
+
for (const binding of motif.semanticBindings ?? []) registerBinding(binding)
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
for (const event of loadTransferEvents()) {
|
|
2309
|
+
for (const binding of event.semanticBindings ?? []) registerBinding(binding)
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
for (const candidate of loadResolvedTopologyReviewCandidates()) {
|
|
2313
|
+
for (const binding of candidate.semanticBindings ?? []) registerBinding(binding)
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
const activeConsumers = [...consumerMap.values()]
|
|
2317
|
+
.sort((a, b) => b.totalBindings - a.totalBindings || (b.lastActivityAt ?? '').localeCompare(a.lastActivityAt ?? ''))
|
|
2318
|
+
|
|
2319
|
+
const atRiskConcepts = activeConsumers
|
|
2320
|
+
.filter((consumer) => consumer.totalBindings > 0)
|
|
2321
|
+
.map((consumer) => ({
|
|
2322
|
+
...consumer,
|
|
2323
|
+
warning: `${consumer.totalBindings} active consumer${consumer.totalBindings === 1 ? '' : 's'} would be affected by deprecating ${consumer.conceptName}.`,
|
|
2324
|
+
}))
|
|
2325
|
+
|
|
2326
|
+
return {
|
|
2327
|
+
summary: {
|
|
2328
|
+
activeConcepts: activeConsumers.length,
|
|
2329
|
+
unusedExtensions: activeExtensions.filter((concept) => !consumerMap.has(concept.id)).length,
|
|
2330
|
+
totalBindings: activeConsumers.reduce((sum, consumer) => sum + consumer.totalBindings, 0),
|
|
2331
|
+
routesInfluenced: signals.filter((signal) => signal.routeRecommendation?.semanticInfluence && signal.routeRecommendation.semanticInfluence !== 'none').length,
|
|
2332
|
+
conceptsWithConsumers: activeConsumers.length,
|
|
2333
|
+
conceptsAtDeprecationRisk: atRiskConcepts.length,
|
|
2334
|
+
bindingsByTargetType,
|
|
2335
|
+
usageByConceptKind: byConceptKind,
|
|
2336
|
+
topActiveConcepts: activeConsumers.slice(0, 8),
|
|
2337
|
+
atRiskConcepts: atRiskConcepts.slice(0, 8),
|
|
2338
|
+
},
|
|
2339
|
+
consumerMap: new Map(activeConsumers.map((consumer) => [consumer.conceptId, consumer])),
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
export function getOntologyAdoptionSummary(): OntologyAdoptionSummary {
|
|
2344
|
+
return collectOntologyAdoptionState().summary
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
export function loadResolvedOntologyExtensions(): ResolvedOntologyExtension[] {
|
|
2348
|
+
const extensions = loadOntologyExtensions()
|
|
2349
|
+
const { consumerMap } = collectOntologyAdoptionState()
|
|
2350
|
+
return extensions
|
|
2351
|
+
.map((concept) => {
|
|
2352
|
+
const consumer = consumerMap.get(concept.id)
|
|
2353
|
+
return {
|
|
2354
|
+
...concept,
|
|
2355
|
+
semanticBindings: [],
|
|
2356
|
+
adoptionCount: consumer?.totalBindings ?? 0,
|
|
2357
|
+
bindingsByTargetType: consumer?.bindingsByTargetType ?? emptyOntologyBindingCounts(),
|
|
2358
|
+
lastActivityAt: maxTimestamp(concept.lastObservedAt, consumer?.lastActivityAt, concept.createdAt),
|
|
2359
|
+
warning: consumer?.warning,
|
|
2360
|
+
} satisfies ResolvedOntologyExtension
|
|
2361
|
+
})
|
|
2362
|
+
.sort((a, b) => b.adoptionCount - a.adoptionCount || b.lastActivityAt.localeCompare(a.lastActivityAt) || a.name.localeCompare(b.name))
|
|
2363
|
+
}
|
|
2364
|
+
|
|
1489
2365
|
export function loadCoEvolutionSummary(): CoEvolutionDashboardSummary {
|
|
1490
2366
|
const graph = loadGraph()
|
|
1491
2367
|
const pressureSignals = loadPressureSignals()
|
|
@@ -1783,6 +2659,7 @@ export function loadOntologySummary(): OntologyDashboardSummary {
|
|
|
1783
2659
|
const governance = getActiveGovernanceSummary()
|
|
1784
2660
|
const topologyReviews = getTopologyReviewSummary()
|
|
1785
2661
|
const topologyExecution = getTopologyExecutionSummary()
|
|
2662
|
+
const ontologyReview = getOntologyReviewSummary()
|
|
1786
2663
|
const skillRoles = emptyCounts(['generalist', 'specialist', 'hybrid'] as const)
|
|
1787
2664
|
const stabilityStates = emptyCounts(['stable', 'adaptive', 'experimental'] as const)
|
|
1788
2665
|
const plasticityStates = emptyCounts(['consolidated', 'volatile', 'candidate'] as const)
|
|
@@ -1815,7 +2692,9 @@ export function loadOntologySummary(): OntologyDashboardSummary {
|
|
|
1815
2692
|
|
|
1816
2693
|
return {
|
|
1817
2694
|
specVersion: graph.ontologyVersion ?? DASHBOARD_ONTOLOGY_SPEC_VERSION,
|
|
1818
|
-
source:
|
|
2695
|
+
source: ontologyReview.frontier > 0 || ontologyReview.extensions > 0 || ontologyReview.changeEvents > 0
|
|
2696
|
+
? 'hybrid-native-derived'
|
|
2697
|
+
: graph.ontologyVersion ? 'graph' : 'compat-derived',
|
|
1819
2698
|
skillRoles,
|
|
1820
2699
|
stabilityStates,
|
|
1821
2700
|
plasticityStates,
|
|
@@ -1832,7 +2711,7 @@ export function loadOntologySummary(): OntologyDashboardSummary {
|
|
|
1832
2711
|
evidenceBackedProposals,
|
|
1833
2712
|
artifacts: {
|
|
1834
2713
|
total: artifacts.length,
|
|
1835
|
-
native: artifacts.filter((artifact) => artifact.provenance === 'native-evolution').length,
|
|
2714
|
+
native: artifacts.filter((artifact) => artifact.provenance === 'native-evolution' || artifact.provenance === 'native-ontology').length,
|
|
1836
2715
|
derived: artifacts.filter((artifact) => artifact.provenance === 'derived-history').length,
|
|
1837
2716
|
},
|
|
1838
2717
|
activationTraces: {
|
|
@@ -1873,6 +2752,27 @@ export function loadOntologySummary(): OntologyDashboardSummary {
|
|
|
1873
2752
|
candidate: transferEvents.filter((event) => event.status === 'candidate').length,
|
|
1874
2753
|
rejected: transferEvents.filter((event) => event.status === 'rejected').length,
|
|
1875
2754
|
},
|
|
2755
|
+
ontologyLoop: {
|
|
2756
|
+
kernelConcepts: ontologyReview.kernelConcepts,
|
|
2757
|
+
extensions: ontologyReview.extensions,
|
|
2758
|
+
frontier: ontologyReview.frontier,
|
|
2759
|
+
reviewOpen: ontologyReview.reviewOpen,
|
|
2760
|
+
promoted: ontologyReview.promoted,
|
|
2761
|
+
rejected: ontologyReview.rejected,
|
|
2762
|
+
deferred: ontologyReview.deferred,
|
|
2763
|
+
deprecated: ontologyReview.deprecated,
|
|
2764
|
+
changeEvents: ontologyReview.changeEvents,
|
|
2765
|
+
byConceptKind: ontologyReview.byConceptKind,
|
|
2766
|
+
adoption: {
|
|
2767
|
+
activeConcepts: ontologyReview.adoption.activeConcepts,
|
|
2768
|
+
unusedExtensions: ontologyReview.adoption.unusedExtensions,
|
|
2769
|
+
totalBindings: ontologyReview.adoption.totalBindings,
|
|
2770
|
+
routesInfluenced: ontologyReview.adoption.routesInfluenced,
|
|
2771
|
+
conceptsAtDeprecationRisk: ontologyReview.adoption.conceptsAtDeprecationRisk,
|
|
2772
|
+
bindingsByTargetType: ontologyReview.adoption.bindingsByTargetType,
|
|
2773
|
+
usageByConceptKind: ontologyReview.adoption.usageByConceptKind,
|
|
2774
|
+
},
|
|
2775
|
+
},
|
|
1876
2776
|
governance: {
|
|
1877
2777
|
activeMode: governance.activeMode,
|
|
1878
2778
|
derivedMode: governance.derivedMode,
|