@haaaiawd/second-nature 0.2.4 → 0.2.6

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.
Files changed (39) hide show
  1. package/openclaw.plugin.json +1 -1
  2. package/package.json +1 -1
  3. package/runtime/cli/commands/index.d.ts +4 -0
  4. package/runtime/cli/commands/index.js +179 -5
  5. package/runtime/cli/index.js +2 -0
  6. package/runtime/cli/ops/ops-router.js +27 -17
  7. package/runtime/connectors/base/contract.d.ts +1 -0
  8. package/runtime/connectors/base/failure-taxonomy.js +45 -26
  9. package/runtime/connectors/services/connector-cooldown-port.d.ts +22 -0
  10. package/runtime/connectors/services/connector-cooldown-port.js +123 -0
  11. package/runtime/connectors/services/connector-executor-adapter.js +10 -4
  12. package/runtime/connectors/services/credential-route-context.d.ts +3 -2
  13. package/runtime/connectors/services/credential-route-context.js +19 -3
  14. package/runtime/core/second-nature/action/action-closure-recorder.d.ts +4 -0
  15. package/runtime/core/second-nature/action/action-closure-recorder.js +5 -0
  16. package/runtime/core/second-nature/action/action-proposal-builder.js +1 -0
  17. package/runtime/core/second-nature/control-plane/heartbeat-orchestrator.d.ts +2 -0
  18. package/runtime/core/second-nature/control-plane/heartbeat-orchestrator.js +76 -0
  19. package/runtime/core/second-nature/control-plane/real-runtime-spine.d.ts +2 -0
  20. package/runtime/core/second-nature/control-plane/real-runtime-spine.js +1 -0
  21. package/runtime/core/second-nature/quiet-dream/daily-rhythm-scheduler.d.ts +1 -1
  22. package/runtime/core/second-nature/quiet-dream/daily-rhythm-scheduler.js +10 -5
  23. package/runtime/core/second-nature/quiet-dream/memory-projection-lifecycle.d.ts +2 -2
  24. package/runtime/core/second-nature/quiet-dream/memory-projection-lifecycle.js +10 -28
  25. package/runtime/observability/db/index.d.ts +2 -0
  26. package/runtime/observability/db/index.js +6 -0
  27. package/runtime/observability/living-loop-health-gate.d.ts +6 -2
  28. package/runtime/observability/living-loop-health-gate.js +52 -5
  29. package/runtime/observability/loop-status.d.ts +19 -0
  30. package/runtime/observability/loop-status.js +121 -7
  31. package/runtime/observability/services/heartbeat-digest-assembler.d.ts +9 -0
  32. package/runtime/observability/services/heartbeat-digest-assembler.js +44 -9
  33. package/runtime/shared/types/v8-contracts.d.ts +1 -1
  34. package/runtime/storage/db/index.d.ts +2 -0
  35. package/runtime/storage/db/index.js +28 -2
  36. package/runtime/storage/db/schema/v8-entities.d.ts +288 -0
  37. package/runtime/storage/db/schema/v8-entities.js +23 -1
  38. package/runtime/storage/v8-state-stores.d.ts +10 -1
  39. package/runtime/storage/v8-state-stores.js +86 -1
@@ -816,6 +816,44 @@ export declare const actionClosureRecord: import("drizzle-orm/sqlite-core").SQLi
816
816
  }, {}, {
817
817
  length: number | undefined;
818
818
  }>;
819
+ platformId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
820
+ name: "platform_id";
821
+ tableName: "action_closure_record";
822
+ dataType: "string";
823
+ columnType: "SQLiteText";
824
+ data: string;
825
+ driverParam: string;
826
+ notNull: false;
827
+ hasDefault: false;
828
+ isPrimaryKey: false;
829
+ isAutoincrement: false;
830
+ hasRuntimeDefault: false;
831
+ enumValues: [string, ...string[]];
832
+ baseColumn: never;
833
+ identity: undefined;
834
+ generated: undefined;
835
+ }, {}, {
836
+ length: number | undefined;
837
+ }>;
838
+ capabilityId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
839
+ name: "capability_id";
840
+ tableName: "action_closure_record";
841
+ dataType: "string";
842
+ columnType: "SQLiteText";
843
+ data: string;
844
+ driverParam: string;
845
+ notNull: false;
846
+ hasDefault: false;
847
+ isPrimaryKey: false;
848
+ isAutoincrement: false;
849
+ hasRuntimeDefault: false;
850
+ enumValues: [string, ...string[]];
851
+ baseColumn: never;
852
+ identity: undefined;
853
+ generated: undefined;
854
+ }, {}, {
855
+ length: number | undefined;
856
+ }>;
819
857
  proposalId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
820
858
  name: "proposal_id";
821
859
  tableName: "action_closure_record";
@@ -2557,3 +2595,253 @@ export declare const dailyRhythmState: import("drizzle-orm/sqlite-core").SQLiteT
2557
2595
  }>;
2558
2596
  export type DailyRhythmStateRecord = typeof dailyRhythmState.$inferSelect;
2559
2597
  export type NewDailyRhythmStateRecord = typeof dailyRhythmState.$inferInsert;
2598
+ export declare const connectorCooldownState: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
2599
+ name: "connector_cooldown_state";
2600
+ schema: undefined;
2601
+ columns: {
2602
+ id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2603
+ name: "id";
2604
+ tableName: "connector_cooldown_state";
2605
+ dataType: "string";
2606
+ columnType: "SQLiteText";
2607
+ data: string;
2608
+ driverParam: string;
2609
+ notNull: true;
2610
+ hasDefault: false;
2611
+ isPrimaryKey: true;
2612
+ isAutoincrement: false;
2613
+ hasRuntimeDefault: false;
2614
+ enumValues: [string, ...string[]];
2615
+ baseColumn: never;
2616
+ identity: undefined;
2617
+ generated: undefined;
2618
+ }, {}, {
2619
+ length: number | undefined;
2620
+ }>;
2621
+ platformId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2622
+ name: "platform_id";
2623
+ tableName: "connector_cooldown_state";
2624
+ dataType: "string";
2625
+ columnType: "SQLiteText";
2626
+ data: string;
2627
+ driverParam: string;
2628
+ notNull: true;
2629
+ hasDefault: false;
2630
+ isPrimaryKey: false;
2631
+ isAutoincrement: false;
2632
+ hasRuntimeDefault: false;
2633
+ enumValues: [string, ...string[]];
2634
+ baseColumn: never;
2635
+ identity: undefined;
2636
+ generated: undefined;
2637
+ }, {}, {
2638
+ length: number | undefined;
2639
+ }>;
2640
+ capabilityId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2641
+ name: "capability_id";
2642
+ tableName: "connector_cooldown_state";
2643
+ dataType: "string";
2644
+ columnType: "SQLiteText";
2645
+ data: string;
2646
+ driverParam: string;
2647
+ notNull: true;
2648
+ hasDefault: false;
2649
+ isPrimaryKey: false;
2650
+ isAutoincrement: false;
2651
+ hasRuntimeDefault: false;
2652
+ enumValues: [string, ...string[]];
2653
+ baseColumn: never;
2654
+ identity: undefined;
2655
+ generated: undefined;
2656
+ }, {}, {
2657
+ length: number | undefined;
2658
+ }>;
2659
+ failureClass: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2660
+ name: "failure_class";
2661
+ tableName: "connector_cooldown_state";
2662
+ dataType: "string";
2663
+ columnType: "SQLiteText";
2664
+ data: string;
2665
+ driverParam: string;
2666
+ notNull: true;
2667
+ hasDefault: false;
2668
+ isPrimaryKey: false;
2669
+ isAutoincrement: false;
2670
+ hasRuntimeDefault: false;
2671
+ enumValues: [string, ...string[]];
2672
+ baseColumn: never;
2673
+ identity: undefined;
2674
+ generated: undefined;
2675
+ }, {}, {
2676
+ length: number | undefined;
2677
+ }>;
2678
+ retryAfterMs: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2679
+ name: "retry_after_ms";
2680
+ tableName: "connector_cooldown_state";
2681
+ dataType: "number";
2682
+ columnType: "SQLiteInteger";
2683
+ data: number;
2684
+ driverParam: number;
2685
+ notNull: false;
2686
+ hasDefault: false;
2687
+ isPrimaryKey: false;
2688
+ isAutoincrement: false;
2689
+ hasRuntimeDefault: false;
2690
+ enumValues: undefined;
2691
+ baseColumn: never;
2692
+ identity: undefined;
2693
+ generated: undefined;
2694
+ }, {}, {}>;
2695
+ blockedUntil: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2696
+ name: "blocked_until";
2697
+ tableName: "connector_cooldown_state";
2698
+ dataType: "string";
2699
+ columnType: "SQLiteText";
2700
+ data: string;
2701
+ driverParam: string;
2702
+ notNull: true;
2703
+ hasDefault: false;
2704
+ isPrimaryKey: false;
2705
+ isAutoincrement: false;
2706
+ hasRuntimeDefault: false;
2707
+ enumValues: [string, ...string[]];
2708
+ baseColumn: never;
2709
+ identity: undefined;
2710
+ generated: undefined;
2711
+ }, {}, {
2712
+ length: number | undefined;
2713
+ }>;
2714
+ failureCount: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2715
+ name: "failure_count";
2716
+ tableName: "connector_cooldown_state";
2717
+ dataType: "number";
2718
+ columnType: "SQLiteInteger";
2719
+ data: number;
2720
+ driverParam: number;
2721
+ notNull: true;
2722
+ hasDefault: true;
2723
+ isPrimaryKey: false;
2724
+ isAutoincrement: false;
2725
+ hasRuntimeDefault: false;
2726
+ enumValues: undefined;
2727
+ baseColumn: never;
2728
+ identity: undefined;
2729
+ generated: undefined;
2730
+ }, {}, {}>;
2731
+ terminalCount: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2732
+ name: "terminal_count";
2733
+ tableName: "connector_cooldown_state";
2734
+ dataType: "number";
2735
+ columnType: "SQLiteInteger";
2736
+ data: number;
2737
+ driverParam: number;
2738
+ notNull: true;
2739
+ hasDefault: true;
2740
+ isPrimaryKey: false;
2741
+ isAutoincrement: false;
2742
+ hasRuntimeDefault: false;
2743
+ enumValues: undefined;
2744
+ baseColumn: never;
2745
+ identity: undefined;
2746
+ generated: undefined;
2747
+ }, {}, {}>;
2748
+ sourceRefsJson: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2749
+ name: "source_refs_json";
2750
+ tableName: "connector_cooldown_state";
2751
+ dataType: "string";
2752
+ columnType: "SQLiteText";
2753
+ data: string;
2754
+ driverParam: string;
2755
+ notNull: true;
2756
+ hasDefault: false;
2757
+ isPrimaryKey: false;
2758
+ isAutoincrement: false;
2759
+ hasRuntimeDefault: false;
2760
+ enumValues: [string, ...string[]];
2761
+ baseColumn: never;
2762
+ identity: undefined;
2763
+ generated: undefined;
2764
+ }, {}, {
2765
+ length: number | undefined;
2766
+ }>;
2767
+ redactionClass: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2768
+ name: "redaction_class";
2769
+ tableName: "connector_cooldown_state";
2770
+ dataType: "string";
2771
+ columnType: "SQLiteText";
2772
+ data: string;
2773
+ driverParam: string;
2774
+ notNull: true;
2775
+ hasDefault: true;
2776
+ isPrimaryKey: false;
2777
+ isAutoincrement: false;
2778
+ hasRuntimeDefault: false;
2779
+ enumValues: [string, ...string[]];
2780
+ baseColumn: never;
2781
+ identity: undefined;
2782
+ generated: undefined;
2783
+ }, {}, {
2784
+ length: number | undefined;
2785
+ }>;
2786
+ payloadJson: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2787
+ name: "payload_json";
2788
+ tableName: "connector_cooldown_state";
2789
+ dataType: "string";
2790
+ columnType: "SQLiteText";
2791
+ data: string;
2792
+ driverParam: string;
2793
+ notNull: false;
2794
+ hasDefault: false;
2795
+ isPrimaryKey: false;
2796
+ isAutoincrement: false;
2797
+ hasRuntimeDefault: false;
2798
+ enumValues: [string, ...string[]];
2799
+ baseColumn: never;
2800
+ identity: undefined;
2801
+ generated: undefined;
2802
+ }, {}, {
2803
+ length: number | undefined;
2804
+ }>;
2805
+ createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2806
+ name: "created_at";
2807
+ tableName: "connector_cooldown_state";
2808
+ dataType: "string";
2809
+ columnType: "SQLiteText";
2810
+ data: string;
2811
+ driverParam: string;
2812
+ notNull: true;
2813
+ hasDefault: false;
2814
+ isPrimaryKey: false;
2815
+ isAutoincrement: false;
2816
+ hasRuntimeDefault: false;
2817
+ enumValues: [string, ...string[]];
2818
+ baseColumn: never;
2819
+ identity: undefined;
2820
+ generated: undefined;
2821
+ }, {}, {
2822
+ length: number | undefined;
2823
+ }>;
2824
+ updatedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2825
+ name: "updated_at";
2826
+ tableName: "connector_cooldown_state";
2827
+ dataType: "string";
2828
+ columnType: "SQLiteText";
2829
+ data: string;
2830
+ driverParam: string;
2831
+ notNull: true;
2832
+ hasDefault: false;
2833
+ isPrimaryKey: false;
2834
+ isAutoincrement: false;
2835
+ hasRuntimeDefault: false;
2836
+ enumValues: [string, ...string[]];
2837
+ baseColumn: never;
2838
+ identity: undefined;
2839
+ generated: undefined;
2840
+ }, {}, {
2841
+ length: number | undefined;
2842
+ }>;
2843
+ };
2844
+ dialect: "sqlite";
2845
+ }>;
2846
+ export type ConnectorCooldownStateRecord = typeof connectorCooldownState.$inferSelect;
2847
+ export type NewConnectorCooldownStateRecord = typeof connectorCooldownState.$inferInsert;
@@ -12,7 +12,7 @@
12
12
  * Dependencies: drizzle-orm/sqlite-core
13
13
  * Boundary: Schema definitions only; no runtime logic.
14
14
  */
15
- import { sqliteTable, text, integer, real } from "drizzle-orm/sqlite-core";
15
+ import { sqliteTable, text, integer, real, index } from "drizzle-orm/sqlite-core";
16
16
  // ───────────────────────────────────────────────────────────────
17
17
  // 1. EvidenceItem
18
18
  // ───────────────────────────────────────────────────────────────
@@ -73,6 +73,8 @@ export const actionClosureRecord = sqliteTable("action_closure_record", {
73
73
  id: text("id").primaryKey(),
74
74
  createdAt: text("created_at").notNull(),
75
75
  cycleId: text("cycle_id").notNull(),
76
+ platformId: text("platform_id"),
77
+ capabilityId: text("capability_id"),
76
78
  proposalId: text("proposal_id"),
77
79
  decisionId: text("decision_id"),
78
80
  status: text("status").notNull(),
@@ -197,3 +199,23 @@ export const dailyRhythmState = sqliteTable("daily_rhythm_state", {
197
199
  payloadJson: text("payload_json"),
198
200
  updatedAt: text("updated_at").notNull(),
199
201
  });
202
+ // ───────────────────────────────────────────────────────────────
203
+ // 12. ConnectorCooldownState
204
+ // ───────────────────────────────────────────────────────────────
205
+ export const connectorCooldownState = sqliteTable("connector_cooldown_state", {
206
+ id: text("id").primaryKey(),
207
+ platformId: text("platform_id").notNull(),
208
+ capabilityId: text("capability_id").notNull(),
209
+ failureClass: text("failure_class").notNull(),
210
+ retryAfterMs: integer("retry_after_ms"),
211
+ blockedUntil: text("blocked_until").notNull(),
212
+ failureCount: integer("failure_count").notNull().default(1),
213
+ terminalCount: integer("terminal_count").notNull().default(0),
214
+ sourceRefsJson: text("source_refs_json").notNull(),
215
+ redactionClass: text("redaction_class").notNull().default("none"),
216
+ payloadJson: text("payload_json"),
217
+ createdAt: text("created_at").notNull(),
218
+ updatedAt: text("updated_at").notNull(),
219
+ }, (table) => ({
220
+ platformCapabilityIdx: index("connector_cooldown_state_platform_capability_idx").on(table.platformId, table.capabilityId),
221
+ }));
@@ -22,7 +22,7 @@
22
22
  * Test coverage: tests/unit/storage/v8-state-stores.test.ts
23
23
  */
24
24
  import type { StateDatabase } from "./db/index.js";
25
- import { type EvidenceItemRecord, type NewEvidenceItemRecord, type PerceptionCardRecord, type NewPerceptionCardRecord, type JudgmentVerdictRecord, type NewJudgmentVerdictRecord, type ActionClosureRecordSelect, type ActionClosureRecordInsert, type QuietDailyReviewRecord, type NewQuietDailyReviewRecord, type DreamConsolidationRunRecord, type NewDreamConsolidationRunRecord, type LongTermMemoryProjectionRecord, type NewLongTermMemoryProjectionRecord, type HeartbeatCycleTraceRecord, type NewHeartbeatCycleTraceRecord, type LoopStageEventRecord, type NewLoopStageEventRecord, type ImpulseContextArtifactRecord, type NewImpulseContextArtifactRecord, type DailyRhythmStateRecord, type NewDailyRhythmStateRecord } from "./db/schema/v8-entities.js";
25
+ import { type EvidenceItemRecord, type NewEvidenceItemRecord, type PerceptionCardRecord, type NewPerceptionCardRecord, type JudgmentVerdictRecord, type NewJudgmentVerdictRecord, type ActionClosureRecordSelect, type ActionClosureRecordInsert, type QuietDailyReviewRecord, type NewQuietDailyReviewRecord, type DreamConsolidationRunRecord, type NewDreamConsolidationRunRecord, type LongTermMemoryProjectionRecord, type NewLongTermMemoryProjectionRecord, type HeartbeatCycleTraceRecord, type NewHeartbeatCycleTraceRecord, type LoopStageEventRecord, type NewLoopStageEventRecord, type ImpulseContextArtifactRecord, type NewImpulseContextArtifactRecord, type DailyRhythmStateRecord, type NewDailyRhythmStateRecord, type ConnectorCooldownStateRecord, type NewConnectorCooldownStateRecord } from "./db/schema/v8-entities.js";
26
26
  import type { SourceRef, DegradedOperationResult } from "../shared/types/v8-contracts.js";
27
27
  export interface WriteValidationError {
28
28
  ok: false;
@@ -172,6 +172,15 @@ export declare function readDailyRhythmStateByDay(db: StateDatabase, day: string
172
172
  row?: DailyRhythmStateRecord;
173
173
  degraded?: DegradedOperationResult;
174
174
  }>;
175
+ export declare function readConnectorCooldownState(db: StateDatabase, platformId: string, capabilityId: string): Promise<{
176
+ row?: ConnectorCooldownStateRecord;
177
+ degraded?: DegradedOperationResult;
178
+ }>;
179
+ export declare function writeConnectorCooldownState(db: StateDatabase, row: Omit<NewConnectorCooldownStateRecord, "sourceRefsJson"> & {
180
+ sourceRefs: SourceRef[];
181
+ }): Promise<{
182
+ id: string;
183
+ } | DegradedOperationResult>;
175
184
  export declare function extractSourceRefs(row: {
176
185
  sourceRefsJson: string | null;
177
186
  }): SourceRef[];
@@ -22,7 +22,7 @@
22
22
  * Test coverage: tests/unit/storage/v8-state-stores.test.ts
23
23
  */
24
24
  import { eq, and, desc, like, isNull } from "drizzle-orm";
25
- import { evidenceItem, perceptionCard, judgmentVerdict, actionClosureRecord, quietDailyReview, dreamConsolidationRun, longTermMemoryProjection, heartbeatCycleTrace, loopStageEvent, impulseContextArtifact, dailyRhythmState, } from "./db/schema/v8-entities.js";
25
+ import { evidenceItem, perceptionCard, judgmentVerdict, actionClosureRecord, quietDailyReview, dreamConsolidationRun, longTermMemoryProjection, heartbeatCycleTrace, loopStageEvent, impulseContextArtifact, dailyRhythmState, connectorCooldownState, } from "./db/schema/v8-entities.js";
26
26
  // ───────────────────────────────────────────────────────────────
27
27
  // Shared helpers
28
28
  // ───────────────────────────────────────────────────────────────
@@ -99,10 +99,62 @@ export async function readEvidenceItemsByStatus(db, lifecycleStatus) {
99
99
  // ───────────────────────────────────────────────────────────────
100
100
  // PerceptionCard store
101
101
  // ───────────────────────────────────────────────────────────────
102
+ const CANONICAL_NOVELTY_CLASSES = ["new", "changed", "duplicate", "stale"];
103
+ const CANONICAL_RELEVANCE_CLASSES = ["low", "medium", "high"];
104
+ function validatePerceptionCardCanonical(row) {
105
+ // Validate noveltyClass
106
+ if (row.novelty && !CANONICAL_NOVELTY_CLASSES.includes(row.novelty)) {
107
+ return {
108
+ ok: false,
109
+ degraded: {
110
+ status: "degraded",
111
+ reason: "perception_contract_drift",
112
+ ownerStage: "perception",
113
+ sourceRefs: row.sourceRefs,
114
+ operatorNextAction: `novelty "${row.novelty}" is not canonical. Expected one of: ${CANONICAL_NOVELTY_CLASSES.join(", ")}`,
115
+ retryable: false,
116
+ },
117
+ };
118
+ }
119
+ // Validate relevanceScore range
120
+ if (row.relevance !== undefined && row.relevance !== null) {
121
+ if (row.relevance < 0 || row.relevance > 1) {
122
+ return {
123
+ ok: false,
124
+ degraded: {
125
+ status: "degraded",
126
+ reason: "perception_contract_drift",
127
+ ownerStage: "perception",
128
+ sourceRefs: row.sourceRefs,
129
+ operatorNextAction: `relevanceScore ${row.relevance} out of range [0, 1]`,
130
+ retryable: false,
131
+ },
132
+ };
133
+ }
134
+ }
135
+ // Validate relevanceClass
136
+ if (row.relevanceClass && !CANONICAL_RELEVANCE_CLASSES.includes(row.relevanceClass)) {
137
+ return {
138
+ ok: false,
139
+ degraded: {
140
+ status: "degraded",
141
+ reason: "perception_contract_drift",
142
+ ownerStage: "perception",
143
+ sourceRefs: row.sourceRefs,
144
+ operatorNextAction: `relevanceClass "${row.relevanceClass}" is not canonical. Expected one of: ${CANONICAL_RELEVANCE_CLASSES.join(", ")}`,
145
+ retryable: false,
146
+ },
147
+ };
148
+ }
149
+ return { ok: true };
150
+ }
102
151
  export async function writePerceptionCard(db, row) {
103
152
  const validated = validateSourceRefs(row.sourceRefs, "perception");
104
153
  if (!validated.ok)
105
154
  return validated.degraded;
155
+ const canonicalCheck = validatePerceptionCardCanonical(row);
156
+ if (!canonicalCheck.ok)
157
+ return canonicalCheck.degraded;
106
158
  try {
107
159
  const record = {
108
160
  ...row,
@@ -602,6 +654,39 @@ export async function readDailyRhythmStateByDay(db, day) {
602
654
  };
603
655
  }
604
656
  }
657
+ export async function readConnectorCooldownState(db, platformId, capabilityId) {
658
+ try {
659
+ const rows = await db.db
660
+ .select()
661
+ .from(connectorCooldownState)
662
+ .where(and(eq(connectorCooldownState.platformId, platformId), eq(connectorCooldownState.capabilityId, capabilityId)))
663
+ .orderBy(desc(connectorCooldownState.updatedAt))
664
+ .limit(1);
665
+ return { row: rows[0] };
666
+ }
667
+ catch {
668
+ return {
669
+ degraded: makeDegraded("state_unreadable", "ingestion", `Check state database connectivity for cooldown ${platformId}:${capabilityId}`),
670
+ };
671
+ }
672
+ }
673
+ export async function writeConnectorCooldownState(db, row) {
674
+ const validated = validateSourceRefs(row.sourceRefs, "ingestion");
675
+ if (!validated.ok)
676
+ return validated.degraded;
677
+ try {
678
+ const record = {
679
+ ...row,
680
+ sourceRefsJson: serializeSourceRefs(validated.record),
681
+ };
682
+ await db.db.delete(connectorCooldownState).where(eq(connectorCooldownState.id, row.id));
683
+ await db.db.insert(connectorCooldownState).values(record);
684
+ return { id: row.id };
685
+ }
686
+ catch {
687
+ return makeDegraded("state_unreadable", "ingestion", "Retry connector cooldown state write after DB recovery", validated.record);
688
+ }
689
+ }
605
690
  // ───────────────────────────────────────────────────────────────
606
691
  // SourceRef round-trip helper (for tests and consumers)
607
692
  // ───────────────────────────────────────────────────────────────