@cleocode/core 2026.4.58 → 2026.4.60
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/dist/index.js +488 -11
- package/dist/index.js.map +3 -3
- package/dist/internal.js +497 -13
- package/dist/internal.js.map +3 -3
- package/dist/memory/graph-queries.d.ts.map +1 -1
- package/dist/store/agent-registry-accessor.d.ts +1 -1
- package/dist/store/brain-accessor.d.ts +57 -1
- package/dist/store/brain-accessor.d.ts.map +1 -1
- package/dist/store/brain-schema.d.ts +738 -0
- package/dist/store/brain-schema.d.ts.map +1 -1
- package/dist/store/brain-sqlite.d.ts.map +1 -1
- package/dist/store/nexus-schema.d.ts +1 -1
- package/dist/system/health.d.ts.map +1 -1
- package/dist/validation/doctor/checks.d.ts +7 -0
- package/dist/validation/doctor/checks.d.ts.map +1 -1
- package/migrations/drizzle-brain/20260416000001_t673-retrieval-log-plasticity-columns/migration.sql +57 -0
- package/migrations/drizzle-brain/20260416000002_t673-plasticity-events-expand/migration.sql +44 -0
- package/migrations/drizzle-brain/20260416000003_t673-page-edges-plasticity-columns/migration.sql +44 -0
- package/migrations/drizzle-brain/20260416000004_t673-new-plasticity-tables/migration.sql +73 -0
- package/package.json +8 -8
- package/src/memory/__tests__/brain-retrieval-m1.test.ts +250 -0
- package/src/memory/__tests__/brain-schema-m2-m3.test.ts +418 -0
- package/src/memory/__tests__/brain-schema-m4.test.ts +494 -0
- package/src/memory/brain-retrieval.ts +1 -1
- package/src/memory/graph-queries.ts +14 -0
- package/src/store/agent-registry-accessor.ts +1 -1
- package/src/store/brain-accessor.ts +120 -0
- package/src/store/brain-schema.ts +373 -1
- package/src/store/brain-sqlite.ts +123 -0
- package/src/system/health.ts +4 -1
- package/src/validation/doctor/checks.ts +107 -0
- package/src/validation/protocols/protocols-markdown/research.md +1 -1
|
@@ -668,12 +668,83 @@ export const brainPageEdges = sqliteTable(
|
|
|
668
668
|
provenance: text('provenance'),
|
|
669
669
|
|
|
670
670
|
createdAt: text('created_at').notNull().default(sql`(datetime('now'))`),
|
|
671
|
+
|
|
672
|
+
// === T673-M3: Plasticity tracking columns ===
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* ISO 8601 timestamp of the last LTP event applied to this edge.
|
|
676
|
+
* Used by the decay pass: edges with (now - last_reinforced_at) > decay_threshold_days
|
|
677
|
+
* receive a per-day weight decay. Null = never reinforced (structural/semantic edges).
|
|
678
|
+
* Only populated when plasticity_class IN ('hebbian', 'stdp').
|
|
679
|
+
*
|
|
680
|
+
* @task T706
|
|
681
|
+
*/
|
|
682
|
+
lastReinforcedAt: text('last_reinforced_at'),
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Count of LTP (potentiation) events applied to this edge lifetime.
|
|
686
|
+
* Incremented on every LTP write. Used to compute stability_score.
|
|
687
|
+
*
|
|
688
|
+
* @task T706
|
|
689
|
+
*/
|
|
690
|
+
reinforcementCount: integer('reinforcement_count').notNull().default(0),
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Plasticity class governing which algorithm(s) write to this edge.
|
|
694
|
+
*
|
|
695
|
+
* - 'static': Non-plastic edge (structural, semantic, etc.). Immune to decay.
|
|
696
|
+
* - 'hebbian': Written by strengthenCoRetrievedEdges. Subject to decay.
|
|
697
|
+
* - 'stdp': Written or refined by applyStdpPlasticity. Subject to decay + LTD.
|
|
698
|
+
*
|
|
699
|
+
* Edges start 'static' for all non-co_retrieved types.
|
|
700
|
+
* co_retrieved edges start 'hebbian' (seeded by M3 migration), can upgrade to 'stdp'.
|
|
701
|
+
*
|
|
702
|
+
* @task T706
|
|
703
|
+
*/
|
|
704
|
+
plasticityClass: text('plasticity_class', {
|
|
705
|
+
enum: ['static', 'hebbian', 'stdp'] as const,
|
|
706
|
+
})
|
|
707
|
+
.notNull()
|
|
708
|
+
.default('static'),
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* ISO 8601 timestamp of the last LTD (depression) event on this edge.
|
|
712
|
+
* Null = never depressed. Used for debugging and Studio viz animation.
|
|
713
|
+
*
|
|
714
|
+
* @task T706
|
|
715
|
+
*/
|
|
716
|
+
lastDepressedAt: text('last_depressed_at'),
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Count of LTD (depression) events applied to this edge lifetime.
|
|
720
|
+
* Enables analysis of edges that are persistently weakened.
|
|
721
|
+
*
|
|
722
|
+
* @task T706
|
|
723
|
+
*/
|
|
724
|
+
depressionCount: integer('depression_count').notNull().default(0),
|
|
725
|
+
|
|
726
|
+
/**
|
|
727
|
+
* Biological-analog stability score: 0.0 (unstable) – 1.0 (consolidated).
|
|
728
|
+
*
|
|
729
|
+
* Computed by runConsolidation decay pass as:
|
|
730
|
+
* stability = tanh(reinforcement_count / 10) × exp(-(days_since_reinforced / 30))
|
|
731
|
+
*
|
|
732
|
+
* Null = not yet computed (new edges). Enables fast filtering in decay pass:
|
|
733
|
+
* edges with stability > 0.9 skip the full decay recalculation.
|
|
734
|
+
* Updated at session-end consolidation, NOT per-event.
|
|
735
|
+
*
|
|
736
|
+
* @task T706
|
|
737
|
+
*/
|
|
738
|
+
stabilityScore: real('stability_score'),
|
|
671
739
|
},
|
|
672
740
|
(table) => [
|
|
673
741
|
primaryKey({ columns: [table.fromId, table.toId, table.edgeType] }),
|
|
674
742
|
index('idx_brain_edges_from').on(table.fromId),
|
|
675
743
|
index('idx_brain_edges_to').on(table.toId),
|
|
676
744
|
index('idx_brain_edges_type').on(table.edgeType),
|
|
745
|
+
index('idx_brain_edges_last_reinforced').on(table.lastReinforcedAt),
|
|
746
|
+
index('idx_brain_edges_plasticity_class').on(table.plasticityClass),
|
|
747
|
+
index('idx_brain_edges_stability').on(table.stabilityScore),
|
|
677
748
|
],
|
|
678
749
|
);
|
|
679
750
|
|
|
@@ -687,9 +758,20 @@ export const brainPageEdges = sqliteTable(
|
|
|
687
758
|
* - Co-retrieval edge strengthening (consolidation step 6)
|
|
688
759
|
* - Memory quality instrumentation (retrieval frequency tracking)
|
|
689
760
|
* - Citation count validation (corroboration for tier promotion)
|
|
761
|
+
* - STDP plasticity — spike-timing pairs derived from retrieval timestamps (T673)
|
|
690
762
|
*
|
|
691
763
|
* Each row records one retrieval event: the query, which entries were returned,
|
|
692
764
|
* and the retrieval source (find/fetch/hybrid).
|
|
765
|
+
*
|
|
766
|
+
* Column notes (T673-M1):
|
|
767
|
+
* entry_ids — stored as JSON array string '["id1","id2"]' (never CSV).
|
|
768
|
+
* Writer: JSON.stringify(entryIds). Readers: JSON.parse(row.entry_ids).
|
|
769
|
+
* M1 migration converts existing CSV rows to JSON format.
|
|
770
|
+
* session_id — synced to live table via M1 ALTER (was missing from live DDL).
|
|
771
|
+
* reward_signal — R-STDP third-factor: +1.0 verified | +0.5 done | -0.5 cancelled | null.
|
|
772
|
+
* retrieval_order — existed in live table via self-healing DDL but was absent in Drizzle.
|
|
773
|
+
* M1 brings Drizzle into sync (schema drift fix).
|
|
774
|
+
* delta_ms — same schema drift resolution as retrieval_order.
|
|
693
775
|
*/
|
|
694
776
|
export const brainRetrievalLog = sqliteTable(
|
|
695
777
|
'brain_retrieval_log',
|
|
@@ -699,7 +781,12 @@ export const brainRetrievalLog = sqliteTable(
|
|
|
699
781
|
/** The search query or fetch IDs that triggered this retrieval. */
|
|
700
782
|
query: text('query').notNull(),
|
|
701
783
|
|
|
702
|
-
/**
|
|
784
|
+
/**
|
|
785
|
+
* JSON array of entry IDs returned in this retrieval.
|
|
786
|
+
* Stored as JSON array string: '["obs:A","obs:B"]'.
|
|
787
|
+
* Always write with JSON.stringify() — NEVER join(',').
|
|
788
|
+
* Readers call JSON.parse(). Migration M1 converts any pre-existing CSV rows.
|
|
789
|
+
*/
|
|
703
790
|
entryIds: text('entry_ids').notNull(),
|
|
704
791
|
|
|
705
792
|
/** Number of entries returned. */
|
|
@@ -715,11 +802,29 @@ export const brainRetrievalLog = sqliteTable(
|
|
|
715
802
|
sessionId: text('session_id'),
|
|
716
803
|
|
|
717
804
|
createdAt: text('created_at').notNull().default(sql`(datetime('now'))`),
|
|
805
|
+
|
|
806
|
+
// === T673-M1: STDP plasticity columns ===
|
|
807
|
+
|
|
808
|
+
/** Sequence position of this retrieval within a batch query (0-based). */
|
|
809
|
+
retrievalOrder: integer('retrieval_order'),
|
|
810
|
+
|
|
811
|
+
/** Wall-clock ms since the previous retrieval row in the same batch. */
|
|
812
|
+
deltaMs: integer('delta_ms'),
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* R-STDP reward signal: scalar [-1.0, +1.0], null = unlabeled.
|
|
816
|
+
* Populated by backfillRewardSignals() at session end (Step 9a).
|
|
817
|
+
* +1.0 = task verified and passed | +0.5 = done (unverified) | -0.5 = cancelled.
|
|
818
|
+
* Per D-BRAIN-VIZ-13. backfillRewardSignals MUST skip rows where
|
|
819
|
+
* session_id LIKE 'ses_backfill_%' (synthetic historical sessions, no task correlation).
|
|
820
|
+
*/
|
|
821
|
+
rewardSignal: real('reward_signal'),
|
|
718
822
|
},
|
|
719
823
|
(table) => [
|
|
720
824
|
index('idx_retrieval_log_created').on(table.createdAt),
|
|
721
825
|
index('idx_retrieval_log_source').on(table.source),
|
|
722
826
|
index('idx_retrieval_log_session').on(table.sessionId),
|
|
827
|
+
index('idx_retrieval_log_reward').on(table.rewardSignal),
|
|
723
828
|
],
|
|
724
829
|
);
|
|
725
830
|
|
|
@@ -759,6 +864,53 @@ export const brainPlasticityEvents = sqliteTable(
|
|
|
759
864
|
timestamp: text('timestamp').notNull().default(sql`(datetime('now'))`),
|
|
760
865
|
/** Session ID that triggered the STDP pass, if available. */
|
|
761
866
|
sessionId: text('session_id'),
|
|
867
|
+
|
|
868
|
+
// === T673-M2: Observability columns ===
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* Edge weight immediately BEFORE this plasticity event was applied.
|
|
872
|
+
* Null on the first LTP event that inserts a new edge (edge didn't exist).
|
|
873
|
+
* Enables "show learning history" in Studio viz without querying brain_weight_history.
|
|
874
|
+
*
|
|
875
|
+
* @task T696
|
|
876
|
+
*/
|
|
877
|
+
weightBefore: real('weight_before'),
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* Edge weight immediately AFTER this plasticity event was applied.
|
|
881
|
+
* Computed as CLAMP(weight_before + delta_w, 0.0, 1.0).
|
|
882
|
+
* Redundant with delta_w but enables fast before/after display without arithmetic.
|
|
883
|
+
*
|
|
884
|
+
* @task T696
|
|
885
|
+
*/
|
|
886
|
+
weightAfter: real('weight_after'),
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* Soft FK to brain_retrieval_log.id — the retrieval row that triggered this pair.
|
|
890
|
+
* Null for externally-triggered or legacy events.
|
|
891
|
+
* Enables: "which memory retrieval caused this edge to strengthen?"
|
|
892
|
+
*
|
|
893
|
+
* @task T696
|
|
894
|
+
*/
|
|
895
|
+
retrievalLogId: integer('retrieval_log_id'),
|
|
896
|
+
|
|
897
|
+
/**
|
|
898
|
+
* R-STDP reward signal active when this event fired.
|
|
899
|
+
* Copied from the retrieval_log row's reward_signal at time of plasticity pass.
|
|
900
|
+
* Null = unmodulated. Denormalized for fast filtering without a JOIN.
|
|
901
|
+
*
|
|
902
|
+
* @task T696
|
|
903
|
+
*/
|
|
904
|
+
rewardSignal: real('reward_signal'),
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* Wall-clock milliseconds between the two spikes that generated this event.
|
|
908
|
+
* Pre-computed at INSERT time — avoids re-deriving from retrieval timestamps.
|
|
909
|
+
* Enables analysis of STDP window distribution.
|
|
910
|
+
*
|
|
911
|
+
* @task T696
|
|
912
|
+
*/
|
|
913
|
+
deltaTMs: integer('delta_t_ms'),
|
|
762
914
|
},
|
|
763
915
|
(table) => [
|
|
764
916
|
index('idx_plasticity_source').on(table.sourceNode),
|
|
@@ -766,6 +918,210 @@ export const brainPlasticityEvents = sqliteTable(
|
|
|
766
918
|
index('idx_plasticity_timestamp').on(table.timestamp),
|
|
767
919
|
index('idx_plasticity_session').on(table.sessionId),
|
|
768
920
|
index('idx_plasticity_kind').on(table.kind),
|
|
921
|
+
index('idx_plasticity_retrieval_log').on(table.retrievalLogId),
|
|
922
|
+
index('idx_plasticity_reward').on(table.rewardSignal),
|
|
923
|
+
],
|
|
924
|
+
);
|
|
925
|
+
|
|
926
|
+
// ============================================================================
|
|
927
|
+
// WEIGHT HISTORY — immutable per-edge Δw audit log (T673-M4, T697)
|
|
928
|
+
// ============================================================================
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* Immutable audit log of every edge weight change (LTP, LTD, Hebbian, prune,
|
|
932
|
+
* external). Routine exponential decay writes do NOT appear here — only discrete
|
|
933
|
+
* plasticity events that cross the 1e-6 negligibility threshold.
|
|
934
|
+
*
|
|
935
|
+
* Retention policy: rolling 90 days. runConsolidation Step 9d DELETE sweep
|
|
936
|
+
* purges rows older than 90 days. Actual pruning wired in Wave 3 (T690).
|
|
937
|
+
*
|
|
938
|
+
* Spec: docs/specs/stdp-wire-up-spec.md §2.1.4 (owner Q4 mandate — in scope).
|
|
939
|
+
*
|
|
940
|
+
* @task T697
|
|
941
|
+
* @epic T673
|
|
942
|
+
*/
|
|
943
|
+
export const brainWeightHistory = sqliteTable(
|
|
944
|
+
'brain_weight_history',
|
|
945
|
+
{
|
|
946
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
947
|
+
|
|
948
|
+
/** from_id of the affected brain_page_edges row. */
|
|
949
|
+
edgeFromId: text('edge_from_id').notNull(),
|
|
950
|
+
|
|
951
|
+
/** to_id of the affected brain_page_edges row. */
|
|
952
|
+
edgeToId: text('edge_to_id').notNull(),
|
|
953
|
+
|
|
954
|
+
/** Edge type of the affected brain_page_edges row (e.g. 'co_retrieved'). */
|
|
955
|
+
edgeType: text('edge_type').notNull(),
|
|
956
|
+
|
|
957
|
+
/** Edge weight immediately before this event. Null if the edge was just created. */
|
|
958
|
+
weightBefore: real('weight_before'),
|
|
959
|
+
|
|
960
|
+
/** Edge weight after this event. CLAMP(weightBefore + deltaWeight, 0, 1). NOT NULL. */
|
|
961
|
+
weightAfter: real('weight_after').notNull(),
|
|
962
|
+
|
|
963
|
+
/**
|
|
964
|
+
* Signed weight delta applied to the edge.
|
|
965
|
+
* Positive = potentiation (LTP/Hebbian), negative = depression (LTD).
|
|
966
|
+
* Prune events record the final weight that triggered deletion (negative).
|
|
967
|
+
*/
|
|
968
|
+
deltaWeight: real('delta_weight').notNull(),
|
|
969
|
+
|
|
970
|
+
/**
|
|
971
|
+
* Plasticity event kind.
|
|
972
|
+
* 'ltp' — Long-Term Potentiation (STDP pre-before-post)
|
|
973
|
+
* 'ltd' — Long-Term Depression (STDP post-before-pre)
|
|
974
|
+
* 'hebbian' — Co-retrieval Hebbian strengthening
|
|
975
|
+
* 'decay' — Temporal decay (only prune-triggering decays written here)
|
|
976
|
+
* 'prune' — Edge deleted (weight fell below min_weight threshold)
|
|
977
|
+
* 'external' — Manually-applied external weight change
|
|
978
|
+
*/
|
|
979
|
+
eventKind: text('event_kind').notNull(),
|
|
980
|
+
|
|
981
|
+
/** Soft FK to brain_plasticity_events.id — the STDP event that caused this. */
|
|
982
|
+
sourcePlasticityEventId: integer('source_plasticity_event_id'),
|
|
983
|
+
|
|
984
|
+
/** Soft FK to brain_retrieval_log.id — the retrieval batch that triggered this. */
|
|
985
|
+
retrievalLogId: integer('retrieval_log_id'),
|
|
986
|
+
|
|
987
|
+
/** R-STDP reward signal at time of event (copied from retrieval_log.reward_signal). */
|
|
988
|
+
rewardSignal: real('reward_signal'),
|
|
989
|
+
|
|
990
|
+
/** ISO 8601 timestamp when this weight change was applied. */
|
|
991
|
+
changedAt: text('changed_at').notNull().default(sql`(datetime('now'))`),
|
|
992
|
+
},
|
|
993
|
+
(table) => [
|
|
994
|
+
index('idx_weight_history_edge').on(table.edgeFromId, table.edgeToId, table.edgeType),
|
|
995
|
+
index('idx_weight_history_from').on(table.edgeFromId),
|
|
996
|
+
index('idx_weight_history_to').on(table.edgeToId),
|
|
997
|
+
index('idx_weight_history_changed_at').on(table.changedAt),
|
|
998
|
+
index('idx_weight_history_event_kind').on(table.eventKind),
|
|
999
|
+
index('idx_weight_history_plasticity_event').on(table.sourcePlasticityEventId),
|
|
1000
|
+
],
|
|
1001
|
+
);
|
|
1002
|
+
|
|
1003
|
+
// ============================================================================
|
|
1004
|
+
// BRAIN MODULATORS — R-STDP neuromodulator event log (T673-M4, T699)
|
|
1005
|
+
// ============================================================================
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* Discrete neuromodulator event log for R-STDP third-factor gating.
|
|
1009
|
+
* Records every reward/correction/feedback signal that modulates plasticity.
|
|
1010
|
+
* Inserted by backfillRewardSignals for each task outcome it processes.
|
|
1011
|
+
*
|
|
1012
|
+
* Both writes (retrieval_log UPDATE and modulators INSERT) use two separate
|
|
1013
|
+
* SQLite connections — no ATTACH — matching the cross-db-cleanup.ts pattern.
|
|
1014
|
+
*
|
|
1015
|
+
* Spec: docs/specs/stdp-wire-up-spec.md §2.1.5 (Lead A §4.5).
|
|
1016
|
+
*
|
|
1017
|
+
* @task T699
|
|
1018
|
+
* @epic T673
|
|
1019
|
+
*/
|
|
1020
|
+
export const brainModulators = sqliteTable(
|
|
1021
|
+
'brain_modulators',
|
|
1022
|
+
{
|
|
1023
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
1024
|
+
|
|
1025
|
+
/**
|
|
1026
|
+
* Modulator event type. String (not enum constraint) for extensibility.
|
|
1027
|
+
* Expected values: 'task_verified'|'task_completed'|'task_cancelled'|
|
|
1028
|
+
* 'owner_verify'|'session_success'|'session_blocker'|'external'
|
|
1029
|
+
*/
|
|
1030
|
+
modulatorType: text('modulator_type').notNull(),
|
|
1031
|
+
|
|
1032
|
+
/**
|
|
1033
|
+
* Reward valence in range [-1.0, +1.0].
|
|
1034
|
+
* +1.0 = strong reward (verified correct task)
|
|
1035
|
+
* +0.5 = moderate reward (done, unverified)
|
|
1036
|
+
* -0.5 = mild correction (cancelled task)
|
|
1037
|
+
* -1.0 = strong correction (explicit invalidation)
|
|
1038
|
+
* 0.0 = neutral signal
|
|
1039
|
+
*/
|
|
1040
|
+
valence: real('valence').notNull(),
|
|
1041
|
+
|
|
1042
|
+
/**
|
|
1043
|
+
* Magnitude 0.0–1.0 confidence scaling.
|
|
1044
|
+
* Effective reward = valence × magnitude.
|
|
1045
|
+
* Defaults to 1.0 (full confidence).
|
|
1046
|
+
*/
|
|
1047
|
+
magnitude: real('magnitude').notNull().default(1.0),
|
|
1048
|
+
|
|
1049
|
+
/** Polymorphic source event ID — task ID, memory entry ID, or other string ref. */
|
|
1050
|
+
sourceEventId: text('source_event_id'),
|
|
1051
|
+
|
|
1052
|
+
/** Session ID (soft FK to tasks.db sessions). */
|
|
1053
|
+
sessionId: text('session_id'),
|
|
1054
|
+
|
|
1055
|
+
/** Human-readable description of why this modulator was emitted. */
|
|
1056
|
+
description: text('description'),
|
|
1057
|
+
|
|
1058
|
+
/** ISO 8601 timestamp when this modulator event was recorded. */
|
|
1059
|
+
createdAt: text('created_at').notNull().default(sql`(datetime('now'))`),
|
|
1060
|
+
},
|
|
1061
|
+
(table) => [
|
|
1062
|
+
index('idx_modulators_type').on(table.modulatorType),
|
|
1063
|
+
index('idx_modulators_session').on(table.sessionId),
|
|
1064
|
+
index('idx_modulators_created_at').on(table.createdAt),
|
|
1065
|
+
index('idx_modulators_source_event').on(table.sourceEventId),
|
|
1066
|
+
index('idx_modulators_valence').on(table.valence),
|
|
1067
|
+
],
|
|
1068
|
+
);
|
|
1069
|
+
|
|
1070
|
+
// ============================================================================
|
|
1071
|
+
// BRAIN CONSOLIDATION EVENTS — pipeline run audit log (T673-M4, T701)
|
|
1072
|
+
// ============================================================================
|
|
1073
|
+
|
|
1074
|
+
/**
|
|
1075
|
+
* One row per runConsolidation execution. Enables T628 auto-dream scheduling
|
|
1076
|
+
* and pipeline observability. Required by the auto-dream cycle for scheduling.
|
|
1077
|
+
*
|
|
1078
|
+
* runConsolidation in brain-lifecycle.ts MUST accept an optional trigger
|
|
1079
|
+
* parameter and INSERT one row per run with step_results_json + duration_ms.
|
|
1080
|
+
*
|
|
1081
|
+
* Spec: docs/specs/stdp-wire-up-spec.md §2.1.6 (Lead A + Lead C joint).
|
|
1082
|
+
*
|
|
1083
|
+
* @task T701
|
|
1084
|
+
* @epic T673
|
|
1085
|
+
*/
|
|
1086
|
+
export const brainConsolidationEvents = sqliteTable(
|
|
1087
|
+
'brain_consolidation_events',
|
|
1088
|
+
{
|
|
1089
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
1090
|
+
|
|
1091
|
+
/**
|
|
1092
|
+
* What triggered this consolidation run. String (not enum constraint) for
|
|
1093
|
+
* forward compatibility with T628 scheduler.
|
|
1094
|
+
* Expected values: 'session_end' | 'maintenance' | 'scheduled' | 'manual'
|
|
1095
|
+
*/
|
|
1096
|
+
trigger: text('trigger').notNull(),
|
|
1097
|
+
|
|
1098
|
+
/** Session ID that initiated this consolidation (soft FK to tasks.db sessions). */
|
|
1099
|
+
sessionId: text('session_id'),
|
|
1100
|
+
|
|
1101
|
+
/**
|
|
1102
|
+
* JSON-serialized ConsolidationResult — all per-step counts and metrics.
|
|
1103
|
+
* Shape: { [stepName: string]: { count: number, durationMs?: number } }
|
|
1104
|
+
* Required NOT NULL — every run must record its results for T628 scheduling.
|
|
1105
|
+
*/
|
|
1106
|
+
stepResultsJson: text('step_results_json').notNull(),
|
|
1107
|
+
|
|
1108
|
+
/** Wall-clock milliseconds from start to completion. Null if run did not complete. */
|
|
1109
|
+
durationMs: integer('duration_ms'),
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* Whether the run succeeded.
|
|
1113
|
+
* Stored as integer(boolean) per Drizzle SQLite boolean convention.
|
|
1114
|
+
* true = completed without unhandled error, false = partial or error.
|
|
1115
|
+
*/
|
|
1116
|
+
succeeded: integer('succeeded', { mode: 'boolean' }).notNull().default(true),
|
|
1117
|
+
|
|
1118
|
+
/** ISO 8601 timestamp when this consolidation run started. */
|
|
1119
|
+
startedAt: text('started_at').notNull().default(sql`(datetime('now'))`),
|
|
1120
|
+
},
|
|
1121
|
+
(table) => [
|
|
1122
|
+
index('idx_consolidation_events_started_at').on(table.startedAt),
|
|
1123
|
+
index('idx_consolidation_events_trigger').on(table.trigger),
|
|
1124
|
+
index('idx_consolidation_events_session').on(table.sessionId),
|
|
769
1125
|
],
|
|
770
1126
|
);
|
|
771
1127
|
|
|
@@ -791,4 +1147,20 @@ export type BrainStickyNoteRow = typeof brainStickyNotes.$inferSelect;
|
|
|
791
1147
|
export type NewBrainStickyNoteRow = typeof brainStickyNotes.$inferInsert;
|
|
792
1148
|
export type BrainPlasticityEventRow = typeof brainPlasticityEvents.$inferSelect;
|
|
793
1149
|
export type NewBrainPlasticityEventRow = typeof brainPlasticityEvents.$inferInsert;
|
|
1150
|
+
|
|
1151
|
+
/** Row type for brain_weight_history SELECT queries. */
|
|
1152
|
+
export type BrainWeightHistoryRow = typeof brainWeightHistory.$inferSelect;
|
|
1153
|
+
/** Row type for brain_weight_history INSERT operations. */
|
|
1154
|
+
export type BrainWeightHistoryInsert = typeof brainWeightHistory.$inferInsert;
|
|
1155
|
+
|
|
1156
|
+
/** Row type for brain_modulators SELECT queries. */
|
|
1157
|
+
export type BrainModulatorRow = typeof brainModulators.$inferSelect;
|
|
1158
|
+
/** Row type for brain_modulators INSERT operations. */
|
|
1159
|
+
export type BrainModulatorInsert = typeof brainModulators.$inferInsert;
|
|
1160
|
+
|
|
1161
|
+
/** Row type for brain_consolidation_events SELECT queries. */
|
|
1162
|
+
export type BrainConsolidationEventRow = typeof brainConsolidationEvents.$inferSelect;
|
|
1163
|
+
/** Row type for brain_consolidation_events INSERT operations. */
|
|
1164
|
+
export type BrainConsolidationEventInsert = typeof brainConsolidationEvents.$inferInsert;
|
|
1165
|
+
|
|
794
1166
|
// BrainNodeType and BrainEdgeType are declared alongside their enum arrays above.
|
|
@@ -178,6 +178,129 @@ function runBrainMigrations(
|
|
|
178
178
|
)
|
|
179
179
|
.run();
|
|
180
180
|
}
|
|
181
|
+
|
|
182
|
+
// T673-M1: STDP plasticity columns on brain_retrieval_log.
|
|
183
|
+
// session_id was declared in the Drizzle schema but never applied to the live table.
|
|
184
|
+
// reward_signal, retrieval_order, delta_ms are new additions per spec §2.1.1.
|
|
185
|
+
if (tableExists(nativeDb, 'brain_retrieval_log')) {
|
|
186
|
+
ensureColumns(
|
|
187
|
+
nativeDb,
|
|
188
|
+
'brain_retrieval_log',
|
|
189
|
+
[
|
|
190
|
+
{ name: 'session_id', ddl: 'text' },
|
|
191
|
+
{ name: 'reward_signal', ddl: 'real' },
|
|
192
|
+
{ name: 'retrieval_order', ddl: 'integer' },
|
|
193
|
+
{ name: 'delta_ms', ddl: 'integer' },
|
|
194
|
+
],
|
|
195
|
+
'brain',
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// T673-M2: observability columns on brain_plasticity_events
|
|
200
|
+
// session_id is declared in Drizzle schema and included in M2 CREATE TABLE IF NOT EXISTS,
|
|
201
|
+
// but may be missing from installs where the table was created before M2.
|
|
202
|
+
if (tableExists(nativeDb, 'brain_plasticity_events')) {
|
|
203
|
+
ensureColumns(
|
|
204
|
+
nativeDb,
|
|
205
|
+
'brain_plasticity_events',
|
|
206
|
+
[
|
|
207
|
+
{ name: 'session_id', ddl: 'text' },
|
|
208
|
+
{ name: 'weight_before', ddl: 'real' },
|
|
209
|
+
{ name: 'weight_after', ddl: 'real' },
|
|
210
|
+
{ name: 'retrieval_log_id', ddl: 'integer' },
|
|
211
|
+
{ name: 'reward_signal', ddl: 'real' },
|
|
212
|
+
{ name: 'delta_t_ms', ddl: 'integer' },
|
|
213
|
+
],
|
|
214
|
+
'brain',
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// T673-M3: plasticity tracking columns on brain_page_edges
|
|
219
|
+
ensureColumns(
|
|
220
|
+
nativeDb,
|
|
221
|
+
'brain_page_edges',
|
|
222
|
+
[
|
|
223
|
+
{ name: 'last_reinforced_at', ddl: 'text' },
|
|
224
|
+
{ name: 'reinforcement_count', ddl: 'integer NOT NULL DEFAULT 0' },
|
|
225
|
+
{ name: 'plasticity_class', ddl: "text NOT NULL DEFAULT 'static'" },
|
|
226
|
+
{ name: 'last_depressed_at', ddl: 'text' },
|
|
227
|
+
{ name: 'depression_count', ddl: 'integer NOT NULL DEFAULT 0' },
|
|
228
|
+
{ name: 'stability_score', ddl: 'real' },
|
|
229
|
+
],
|
|
230
|
+
'brain',
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
// T673-M3: seed co_retrieved edges as hebbian (idempotent)
|
|
234
|
+
if (tableExists(nativeDb, 'brain_page_edges')) {
|
|
235
|
+
nativeDb
|
|
236
|
+
.prepare(
|
|
237
|
+
`UPDATE brain_page_edges SET plasticity_class = 'hebbian'
|
|
238
|
+
WHERE edge_type = 'co_retrieved' AND plasticity_class = 'static'`,
|
|
239
|
+
)
|
|
240
|
+
.run();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// T673-M4: new plasticity infrastructure tables — self-healing CREATE IF NOT EXISTS.
|
|
244
|
+
// These guards ensure the tables exist even on installs where the Drizzle migration
|
|
245
|
+
// journal was already partially applied. All three tables are CREATE IF NOT EXISTS
|
|
246
|
+
// so re-running is safe.
|
|
247
|
+
nativeDb.exec(
|
|
248
|
+
`CREATE TABLE IF NOT EXISTS brain_weight_history (
|
|
249
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
250
|
+
edge_from_id TEXT NOT NULL,
|
|
251
|
+
edge_to_id TEXT NOT NULL,
|
|
252
|
+
edge_type TEXT NOT NULL,
|
|
253
|
+
weight_before REAL,
|
|
254
|
+
weight_after REAL NOT NULL,
|
|
255
|
+
delta_weight REAL NOT NULL,
|
|
256
|
+
event_kind TEXT NOT NULL,
|
|
257
|
+
source_plasticity_event_id INTEGER,
|
|
258
|
+
retrieval_log_id INTEGER,
|
|
259
|
+
reward_signal REAL,
|
|
260
|
+
changed_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
261
|
+
)`,
|
|
262
|
+
);
|
|
263
|
+
nativeDb.exec(
|
|
264
|
+
`CREATE INDEX IF NOT EXISTS idx_weight_history_edge
|
|
265
|
+
ON brain_weight_history (edge_from_id, edge_to_id, edge_type)`,
|
|
266
|
+
);
|
|
267
|
+
nativeDb.exec(
|
|
268
|
+
`CREATE INDEX IF NOT EXISTS idx_weight_history_changed_at
|
|
269
|
+
ON brain_weight_history (changed_at)`,
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
nativeDb.exec(
|
|
273
|
+
`CREATE TABLE IF NOT EXISTS brain_modulators (
|
|
274
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
275
|
+
modulator_type TEXT NOT NULL,
|
|
276
|
+
valence REAL NOT NULL,
|
|
277
|
+
magnitude REAL NOT NULL DEFAULT 1.0,
|
|
278
|
+
source_event_id TEXT,
|
|
279
|
+
session_id TEXT,
|
|
280
|
+
description TEXT,
|
|
281
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
282
|
+
)`,
|
|
283
|
+
);
|
|
284
|
+
nativeDb.exec(
|
|
285
|
+
`CREATE INDEX IF NOT EXISTS idx_modulators_session
|
|
286
|
+
ON brain_modulators (session_id)`,
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
nativeDb.exec(
|
|
290
|
+
`CREATE TABLE IF NOT EXISTS brain_consolidation_events (
|
|
291
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
292
|
+
trigger TEXT NOT NULL,
|
|
293
|
+
session_id TEXT,
|
|
294
|
+
step_results_json TEXT NOT NULL,
|
|
295
|
+
duration_ms INTEGER,
|
|
296
|
+
succeeded INTEGER NOT NULL DEFAULT 1,
|
|
297
|
+
started_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
298
|
+
)`,
|
|
299
|
+
);
|
|
300
|
+
nativeDb.exec(
|
|
301
|
+
`CREATE INDEX IF NOT EXISTS idx_consolidation_events_started_at
|
|
302
|
+
ON brain_consolidation_events (started_at)`,
|
|
303
|
+
);
|
|
181
304
|
}
|
|
182
305
|
|
|
183
306
|
/**
|
package/src/system/health.ts
CHANGED
|
@@ -33,6 +33,7 @@ import { checkGlobalSchemas, type CheckResult as SchemaCheckResult } from '../sc
|
|
|
33
33
|
import { getAccessor } from '../store/data-accessor.js';
|
|
34
34
|
import {
|
|
35
35
|
type CheckResult,
|
|
36
|
+
checkCanonicalRcasdPaths,
|
|
36
37
|
checkCleoGitignore,
|
|
37
38
|
checkCoreFilesNotIgnored,
|
|
38
39
|
checkLegacyAgentOutputs,
|
|
@@ -824,11 +825,13 @@ export async function coreDoctorReport(projectRoot: string): Promise<DoctorRepor
|
|
|
824
825
|
}
|
|
825
826
|
}
|
|
826
827
|
|
|
827
|
-
// 5. Gitignore integrity, vital files, legacy paths (delegated to core checks)
|
|
828
|
+
// 5. Gitignore integrity, vital files, legacy paths, canonical paths (delegated to core checks)
|
|
828
829
|
checks.push(mapCheckResult(checkCleoGitignore(projectRoot)));
|
|
829
830
|
checks.push(mapCheckResult(checkVitalFilesTracked(projectRoot)));
|
|
830
831
|
checks.push(mapCheckResult(checkCoreFilesNotIgnored(projectRoot)));
|
|
831
832
|
checks.push(mapCheckResult(checkLegacyAgentOutputs(projectRoot)));
|
|
833
|
+
// ADR-045: canonical RCASD paths (T708)
|
|
834
|
+
checks.push(mapCheckResult(checkCanonicalRcasdPaths(projectRoot)));
|
|
832
835
|
|
|
833
836
|
// 5b. Isolated .cleo/.git checkpoint repo check (T4872)
|
|
834
837
|
const cleoGitHeadExists = existsSync(join(cleoDir, '.git', 'HEAD'));
|