@aiassesstech/nole 0.4.14 → 0.5.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.
Files changed (39) hide show
  1. package/agent/AGENTS.md +4 -0
  2. package/agent/SOUL.md +9 -0
  3. package/agent/decisions.md +20 -0
  4. package/dist/plugin.d.ts.map +1 -1
  5. package/dist/plugin.js +51 -1
  6. package/dist/plugin.js.map +1 -1
  7. package/dist/resilience/circuit-breaker.d.ts +46 -0
  8. package/dist/resilience/circuit-breaker.d.ts.map +1 -0
  9. package/dist/resilience/circuit-breaker.js +102 -0
  10. package/dist/resilience/circuit-breaker.js.map +1 -0
  11. package/dist/resilience/index.d.ts +8 -0
  12. package/dist/resilience/index.d.ts.map +1 -0
  13. package/dist/resilience/index.js +8 -0
  14. package/dist/resilience/index.js.map +1 -0
  15. package/dist/resilience/post-queue.d.ts +41 -0
  16. package/dist/resilience/post-queue.d.ts.map +1 -0
  17. package/dist/resilience/post-queue.js +106 -0
  18. package/dist/resilience/post-queue.js.map +1 -0
  19. package/dist/resilience/queue-drain.d.ts +35 -0
  20. package/dist/resilience/queue-drain.d.ts.map +1 -0
  21. package/dist/resilience/queue-drain.js +92 -0
  22. package/dist/resilience/queue-drain.js.map +1 -0
  23. package/dist/resilience/resilient-client.d.ts +54 -0
  24. package/dist/resilience/resilient-client.d.ts.map +1 -0
  25. package/dist/resilience/resilient-client.js +120 -0
  26. package/dist/resilience/resilient-client.js.map +1 -0
  27. package/dist/resilience/retry.d.ts +22 -0
  28. package/dist/resilience/retry.d.ts.map +1 -0
  29. package/dist/resilience/retry.js +68 -0
  30. package/dist/resilience/retry.js.map +1 -0
  31. package/dist/resilience/service-configs.d.ts +14 -0
  32. package/dist/resilience/service-configs.d.ts.map +1 -0
  33. package/dist/resilience/service-configs.js +45 -0
  34. package/dist/resilience/service-configs.js.map +1 -0
  35. package/dist/resilience/service-registry.d.ts +39 -0
  36. package/dist/resilience/service-registry.d.ts.map +1 -0
  37. package/dist/resilience/service-registry.js +82 -0
  38. package/dist/resilience/service-registry.js.map +1 -0
  39. package/package.json +1 -1
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Circuit Breaker — Three-state pattern for external service protection.
3
+ *
4
+ * CLOSED → requests pass through, failures counted
5
+ * OPEN → requests rejected immediately, cooldown timer running
6
+ * HALF-OPEN → single probe request allowed, success closes, failure reopens
7
+ *
8
+ * @see SPEC-nole-operational-resilience.md §3
9
+ */
10
+ export type CircuitState = 'closed' | 'open' | 'half-open';
11
+ export interface CircuitBreakerConfig {
12
+ name: string;
13
+ failureThreshold: number;
14
+ failureWindowMs: number;
15
+ cooldownMs: number;
16
+ maxCooldownMs: number;
17
+ cooldownMultiplier: number;
18
+ failureStatusCodes: number[];
19
+ onStateChange?: (from: CircuitState, to: CircuitState, service: string) => void;
20
+ }
21
+ export declare class CircuitBreaker {
22
+ private config;
23
+ private state;
24
+ private failures;
25
+ private lastStateChange;
26
+ private currentCooldownMs;
27
+ private consecutiveOpenings;
28
+ private probeAllowed;
29
+ constructor(config: CircuitBreakerConfig);
30
+ canExecute(): boolean;
31
+ recordSuccess(): void;
32
+ recordFailure(error: string): void;
33
+ isFailureStatus(status: number): boolean;
34
+ getState(): CircuitState;
35
+ getStatus(): {
36
+ state: CircuitState;
37
+ failureCount: number;
38
+ consecutiveOpenings: number;
39
+ currentCooldownMs: number;
40
+ lastStateChange: number;
41
+ recentFailures: string[];
42
+ };
43
+ private transition;
44
+ private pruneOldFailures;
45
+ }
46
+ //# sourceMappingURL=circuit-breaker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"circuit-breaker.d.ts","sourceRoot":"","sources":["../../src/resilience/circuit-breaker.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAE3D,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjF;AAOD,qBAAa,cAAc;IAQb,OAAO,CAAC,MAAM;IAP1B,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,eAAe,CAAsB;IAC7C,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,mBAAmB,CAAa;IACxC,OAAO,CAAC,YAAY,CAAS;gBAET,MAAM,EAAE,oBAAoB;IAIhD,UAAU,IAAI,OAAO;IAsBrB,aAAa,IAAI,IAAI;IASrB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IA6BlC,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAIxC,QAAQ,IAAI,YAAY;IAUxB,SAAS;;;;;;;;IAYT,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,gBAAgB;CAIzB"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Circuit Breaker — Three-state pattern for external service protection.
3
+ *
4
+ * CLOSED → requests pass through, failures counted
5
+ * OPEN → requests rejected immediately, cooldown timer running
6
+ * HALF-OPEN → single probe request allowed, success closes, failure reopens
7
+ *
8
+ * @see SPEC-nole-operational-resilience.md §3
9
+ */
10
+ export class CircuitBreaker {
11
+ config;
12
+ state = 'closed';
13
+ failures = [];
14
+ lastStateChange = Date.now();
15
+ currentCooldownMs;
16
+ consecutiveOpenings = 0;
17
+ probeAllowed = false;
18
+ constructor(config) {
19
+ this.config = config;
20
+ this.currentCooldownMs = config.cooldownMs;
21
+ }
22
+ canExecute() {
23
+ this.pruneOldFailures();
24
+ switch (this.state) {
25
+ case 'closed':
26
+ return true;
27
+ case 'open': {
28
+ const elapsed = Date.now() - this.lastStateChange;
29
+ if (elapsed >= this.currentCooldownMs) {
30
+ this.transition('half-open');
31
+ this.probeAllowed = false;
32
+ return true;
33
+ }
34
+ return false;
35
+ }
36
+ case 'half-open':
37
+ return false;
38
+ }
39
+ }
40
+ recordSuccess() {
41
+ if (this.state === 'half-open') {
42
+ this.consecutiveOpenings = 0;
43
+ this.currentCooldownMs = this.config.cooldownMs;
44
+ this.transition('closed');
45
+ this.failures = [];
46
+ }
47
+ }
48
+ recordFailure(error) {
49
+ const ts = Date.now();
50
+ switch (this.state) {
51
+ case 'closed':
52
+ this.failures.push({ timestamp: ts, error });
53
+ this.pruneOldFailures();
54
+ if (this.failures.length >= this.config.failureThreshold) {
55
+ this.consecutiveOpenings++;
56
+ this.currentCooldownMs = Math.min(this.config.cooldownMs * Math.pow(this.config.cooldownMultiplier, this.consecutiveOpenings - 1), this.config.maxCooldownMs);
57
+ this.transition('open');
58
+ }
59
+ break;
60
+ case 'half-open':
61
+ this.consecutiveOpenings++;
62
+ this.currentCooldownMs = Math.min(this.currentCooldownMs * this.config.cooldownMultiplier, this.config.maxCooldownMs);
63
+ this.transition('open');
64
+ break;
65
+ }
66
+ }
67
+ isFailureStatus(status) {
68
+ return this.config.failureStatusCodes.includes(status);
69
+ }
70
+ getState() {
71
+ if (this.state === 'open') {
72
+ const elapsed = Date.now() - this.lastStateChange;
73
+ if (elapsed >= this.currentCooldownMs) {
74
+ this.transition('half-open');
75
+ }
76
+ }
77
+ return this.state;
78
+ }
79
+ getStatus() {
80
+ this.pruneOldFailures();
81
+ return {
82
+ state: this.getState(),
83
+ failureCount: this.failures.length,
84
+ consecutiveOpenings: this.consecutiveOpenings,
85
+ currentCooldownMs: this.currentCooldownMs,
86
+ lastStateChange: this.lastStateChange,
87
+ recentFailures: this.failures.slice(-3).map(f => f.error),
88
+ };
89
+ }
90
+ transition(to) {
91
+ const from = this.state;
92
+ this.state = to;
93
+ this.lastStateChange = Date.now();
94
+ this.probeAllowed = false;
95
+ this.config.onStateChange?.(from, to, this.config.name);
96
+ }
97
+ pruneOldFailures() {
98
+ const cutoff = Date.now() - this.config.failureWindowMs;
99
+ this.failures = this.failures.filter(f => f.timestamp > cutoff);
100
+ }
101
+ }
102
+ //# sourceMappingURL=circuit-breaker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"circuit-breaker.js","sourceRoot":"","sources":["../../src/resilience/circuit-breaker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export { CircuitBreaker, type CircuitBreakerConfig, type CircuitState } from './circuit-breaker.js';
2
+ export { type RetryConfig, DEFAULT_RETRY_CONFIG, calculateDelay, isRetryable, extractRetryAfter, sleep, } from './retry.js';
3
+ export { ResilientClient, CircuitOpenError, type ResilientClientConfig, type ResilientResponse, } from './resilient-client.js';
4
+ export { ServiceRegistry, type ServiceHealth, type ServiceStatus, type RegistrySnapshot, } from './service-registry.js';
5
+ export { PostQueue, type QueuedPost, type PostPlatform, type PostQueueConfig, DEFAULT_QUEUE_CONFIG, } from './post-queue.js';
6
+ export { QueueDrain, type PostSender, type QueueDrainConfig, DEFAULT_DRAIN_CONFIG, } from './queue-drain.js';
7
+ export { PLATFORM_API_BREAKER, X_API_BREAKER, MOLTBOOK_API_BREAKER, BLOCKCHAIN_BREAKER, } from './service-configs.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/resilience/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,oBAAoB,EAAE,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpG,OAAO,EACL,KAAK,WAAW,EAChB,oBAAoB,EACpB,cAAc,EACd,WAAW,EACX,iBAAiB,EACjB,KAAK,GACN,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,GACvB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,eAAe,EACf,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,gBAAgB,GACtB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,SAAS,EACT,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,UAAU,EACV,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,oBAAoB,EACpB,aAAa,EACb,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,8 @@
1
+ export { CircuitBreaker } from './circuit-breaker.js';
2
+ export { DEFAULT_RETRY_CONFIG, calculateDelay, isRetryable, extractRetryAfter, sleep, } from './retry.js';
3
+ export { ResilientClient, CircuitOpenError, } from './resilient-client.js';
4
+ export { ServiceRegistry, } from './service-registry.js';
5
+ export { PostQueue, DEFAULT_QUEUE_CONFIG, } from './post-queue.js';
6
+ export { QueueDrain, DEFAULT_DRAIN_CONFIG, } from './queue-drain.js';
7
+ export { PLATFORM_API_BREAKER, X_API_BREAKER, MOLTBOOK_API_BREAKER, BLOCKCHAIN_BREAKER, } from './service-configs.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/resilience/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAgD,MAAM,sBAAsB,CAAC;AAEpG,OAAO,EAEL,oBAAoB,EACpB,cAAc,EACd,WAAW,EACX,iBAAiB,EACjB,KAAK,GACN,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,eAAe,EACf,gBAAgB,GAGjB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,eAAe,GAIhB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,SAAS,EAIT,oBAAoB,GACrB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,UAAU,EAGV,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,oBAAoB,EACpB,aAAa,EACb,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * PostQueue — Durable queue for social media posts during outages.
3
+ *
4
+ * When X/Twitter or MoltBook circuit is open, posts are queued to
5
+ * a JSON file and drained when the service recovers.
6
+ *
7
+ * @see SPEC-nole-operational-resilience.md §7
8
+ */
9
+ export type PostPlatform = 'x-twitter' | 'moltbook';
10
+ export interface QueuedPost {
11
+ id: string;
12
+ platform: PostPlatform;
13
+ content: string;
14
+ metadata: Record<string, unknown>;
15
+ queuedAt: number;
16
+ retries: number;
17
+ lastRetryAt: number | null;
18
+ error: string | null;
19
+ }
20
+ export interface PostQueueConfig {
21
+ dataDir: string;
22
+ maxQueueSize: number;
23
+ maxRetries: number;
24
+ maxAgeMs: number;
25
+ }
26
+ export declare const DEFAULT_QUEUE_CONFIG: PostQueueConfig;
27
+ export declare class PostQueue {
28
+ private config;
29
+ private queue;
30
+ private filePath;
31
+ constructor(config: PostQueueConfig);
32
+ enqueue(platform: PostPlatform, content: string, metadata?: Record<string, unknown>): QueuedPost;
33
+ dequeue(platform: PostPlatform): QueuedPost | null;
34
+ requeue(post: QueuedPost, error: string): void;
35
+ peek(platform: PostPlatform): QueuedPost[];
36
+ size(platform?: PostPlatform): number;
37
+ private pruneExpired;
38
+ private load;
39
+ private save;
40
+ }
41
+ //# sourceMappingURL=post-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post-queue.d.ts","sourceRoot":"","sources":["../../src/resilience/post-queue.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,UAAU,CAAC;AAEpD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,oBAAoB,EAAE,eAKlC,CAAC;AAEF,qBAAa,SAAS;IAIR,OAAO,CAAC,MAAM;IAH1B,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,QAAQ,CAAS;gBAEL,MAAM,EAAE,eAAe;IAK3C,OAAO,CAAC,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,UAAU;IAuBpG,OAAO,CAAC,QAAQ,EAAE,YAAY,GAAG,UAAU,GAAG,IAAI;IAclD,OAAO,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAY9C,IAAI,CAAC,QAAQ,EAAE,YAAY,GAAG,UAAU,EAAE;IAK1C,IAAI,CAAC,QAAQ,CAAC,EAAE,YAAY,GAAG,MAAM;IAQrC,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,IAAI;IAWZ,OAAO,CAAC,IAAI;CAWb"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * PostQueue — Durable queue for social media posts during outages.
3
+ *
4
+ * When X/Twitter or MoltBook circuit is open, posts are queued to
5
+ * a JSON file and drained when the service recovers.
6
+ *
7
+ * @see SPEC-nole-operational-resilience.md §7
8
+ */
9
+ import fs from 'node:fs';
10
+ import path from 'node:path';
11
+ export const DEFAULT_QUEUE_CONFIG = {
12
+ dataDir: '',
13
+ maxQueueSize: 100,
14
+ maxRetries: 5,
15
+ maxAgeMs: 24 * 60 * 60 * 1000, // 24h
16
+ };
17
+ export class PostQueue {
18
+ config;
19
+ queue = [];
20
+ filePath;
21
+ constructor(config) {
22
+ this.config = config;
23
+ this.filePath = path.join(config.dataDir, 'post-queue.json');
24
+ this.load();
25
+ }
26
+ enqueue(platform, content, metadata = {}) {
27
+ this.pruneExpired();
28
+ if (this.queue.length >= this.config.maxQueueSize) {
29
+ throw new Error(`Post queue full (${this.config.maxQueueSize} items) — cannot enqueue`);
30
+ }
31
+ const post = {
32
+ id: `post_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
33
+ platform,
34
+ content,
35
+ metadata,
36
+ queuedAt: Date.now(),
37
+ retries: 0,
38
+ lastRetryAt: null,
39
+ error: null,
40
+ };
41
+ this.queue.push(post);
42
+ this.save();
43
+ return post;
44
+ }
45
+ dequeue(platform) {
46
+ this.pruneExpired();
47
+ const idx = this.queue.findIndex(p => p.platform === platform && p.retries < this.config.maxRetries);
48
+ if (idx === -1)
49
+ return null;
50
+ const [post] = this.queue.splice(idx, 1);
51
+ this.save();
52
+ return post;
53
+ }
54
+ requeue(post, error) {
55
+ post.retries++;
56
+ post.lastRetryAt = Date.now();
57
+ post.error = error;
58
+ if (post.retries < this.config.maxRetries) {
59
+ this.queue.push(post);
60
+ }
61
+ this.save();
62
+ }
63
+ peek(platform) {
64
+ this.pruneExpired();
65
+ return this.queue.filter(p => p.platform === platform);
66
+ }
67
+ size(platform) {
68
+ this.pruneExpired();
69
+ if (platform) {
70
+ return this.queue.filter(p => p.platform === platform).length;
71
+ }
72
+ return this.queue.length;
73
+ }
74
+ pruneExpired() {
75
+ const cutoff = Date.now() - this.config.maxAgeMs;
76
+ const before = this.queue.length;
77
+ this.queue = this.queue.filter(p => p.queuedAt > cutoff && p.retries < this.config.maxRetries);
78
+ if (this.queue.length !== before) {
79
+ this.save();
80
+ }
81
+ }
82
+ load() {
83
+ try {
84
+ if (fs.existsSync(this.filePath)) {
85
+ const data = fs.readFileSync(this.filePath, 'utf-8');
86
+ this.queue = JSON.parse(data);
87
+ }
88
+ }
89
+ catch {
90
+ this.queue = [];
91
+ }
92
+ }
93
+ save() {
94
+ try {
95
+ const dir = path.dirname(this.filePath);
96
+ if (!fs.existsSync(dir)) {
97
+ fs.mkdirSync(dir, { recursive: true });
98
+ }
99
+ fs.writeFileSync(this.filePath, JSON.stringify(this.queue, null, 2), 'utf-8');
100
+ }
101
+ catch {
102
+ // Non-fatal — queue is best-effort durable
103
+ }
104
+ }
105
+ }
106
+ //# sourceMappingURL=post-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post-queue.js","sourceRoot":"","sources":["../../src/resilience/post-queue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * QueueDrain — Periodic processor that drains the PostQueue when
3
+ * service circuits recover to closed or half-open.
4
+ *
5
+ * @see SPEC-nole-operational-resilience.md §7.2
6
+ */
7
+ import type { PostQueue, PostPlatform } from './post-queue.js';
8
+ import type { ServiceRegistry } from './service-registry.js';
9
+ export type PostSender = (platform: PostPlatform, content: string, metadata: Record<string, unknown>) => Promise<void>;
10
+ export interface QueueDrainConfig {
11
+ intervalMs: number;
12
+ batchSize: number;
13
+ interPostDelayMs: number;
14
+ logger?: {
15
+ debug: (msg: string, meta?: Record<string, unknown>) => void;
16
+ warn: (msg: string, meta?: Record<string, unknown>) => void;
17
+ error: (msg: string, meta?: Record<string, unknown>) => void;
18
+ };
19
+ }
20
+ export declare const DEFAULT_DRAIN_CONFIG: QueueDrainConfig;
21
+ export declare class QueueDrain {
22
+ private queue;
23
+ private registry;
24
+ private sender;
25
+ private config;
26
+ private timer;
27
+ private running;
28
+ private logger;
29
+ constructor(queue: PostQueue, registry: ServiceRegistry, sender: PostSender, config?: QueueDrainConfig);
30
+ start(): void;
31
+ stop(): void;
32
+ drain(): Promise<number>;
33
+ isRunning(): boolean;
34
+ }
35
+ //# sourceMappingURL=queue-drain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue-drain.d.ts","sourceRoot":"","sources":["../../src/resilience/queue-drain.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,MAAM,MAAM,UAAU,GAAG,CAAC,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEvH,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE;QACP,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QAC7D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QAC5D,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;KAC9D,CAAC;CACH;AAED,eAAO,MAAM,oBAAoB,EAAE,gBAIlC,CAAC;AAEF,qBAAa,UAAU;IAMnB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,MAAM;IARhB,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAA0C;gBAG9C,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,eAAe,EACzB,MAAM,EAAE,UAAU,EAClB,MAAM,GAAE,gBAAuC;IASzD,KAAK,IAAI,IAAI;IAMb,IAAI,IAAI,IAAI;IAQN,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAgD9B,SAAS,IAAI,OAAO;CAGrB"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * QueueDrain — Periodic processor that drains the PostQueue when
3
+ * service circuits recover to closed or half-open.
4
+ *
5
+ * @see SPEC-nole-operational-resilience.md §7.2
6
+ */
7
+ export const DEFAULT_DRAIN_CONFIG = {
8
+ intervalMs: 60_000,
9
+ batchSize: 5,
10
+ interPostDelayMs: 5_000,
11
+ };
12
+ export class QueueDrain {
13
+ queue;
14
+ registry;
15
+ sender;
16
+ config;
17
+ timer = null;
18
+ running = false;
19
+ logger;
20
+ constructor(queue, registry, sender, config = DEFAULT_DRAIN_CONFIG) {
21
+ this.queue = queue;
22
+ this.registry = registry;
23
+ this.sender = sender;
24
+ this.config = config;
25
+ this.logger = config.logger ?? {
26
+ debug: () => { },
27
+ warn: () => { },
28
+ error: () => { },
29
+ };
30
+ }
31
+ start() {
32
+ if (this.timer)
33
+ return;
34
+ this.timer = setInterval(() => void this.drain(), this.config.intervalMs);
35
+ this.logger.debug('Queue drain started', { intervalMs: this.config.intervalMs });
36
+ }
37
+ stop() {
38
+ if (this.timer) {
39
+ clearInterval(this.timer);
40
+ this.timer = null;
41
+ }
42
+ this.logger.debug('Queue drain stopped');
43
+ }
44
+ async drain() {
45
+ if (this.running)
46
+ return 0;
47
+ this.running = true;
48
+ let sent = 0;
49
+ try {
50
+ const platforms = ['x-twitter', 'moltbook'];
51
+ for (const platform of platforms) {
52
+ if (!this.registry.isAvailable(platform)) {
53
+ this.logger.debug(`${platform}: circuit still open, skipping drain`);
54
+ continue;
55
+ }
56
+ const queued = this.queue.size(platform);
57
+ if (queued === 0)
58
+ continue;
59
+ this.logger.debug(`${platform}: draining ${queued} queued posts`);
60
+ for (let i = 0; i < this.config.batchSize; i++) {
61
+ const post = this.queue.dequeue(platform);
62
+ if (!post)
63
+ break;
64
+ try {
65
+ await this.sender(platform, post.content, post.metadata);
66
+ sent++;
67
+ this.registry.recordSuccess(platform);
68
+ this.logger.debug(`${platform}: sent queued post ${post.id}`);
69
+ if (i < this.config.batchSize - 1) {
70
+ await new Promise(r => setTimeout(r, this.config.interPostDelayMs));
71
+ }
72
+ }
73
+ catch (err) {
74
+ const error = err instanceof Error ? err.message : String(err);
75
+ this.queue.requeue(post, error);
76
+ this.registry.recordFailure(platform);
77
+ this.logger.warn(`${platform}: failed to send ${post.id}, requeued`, { error });
78
+ break;
79
+ }
80
+ }
81
+ }
82
+ }
83
+ finally {
84
+ this.running = false;
85
+ }
86
+ return sent;
87
+ }
88
+ isRunning() {
89
+ return this.timer !== null;
90
+ }
91
+ }
92
+ //# sourceMappingURL=queue-drain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue-drain.js","sourceRoot":"","sources":["../../src/resilience/queue-drain.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAkBH,MAAM,CAAC,MAAM,oBAAoB,GAAqB;IACpD,UAAU,EAAE,MAAM;IAClB,SAAS,EAAE,CAAC;IACZ,gBAAgB,EAAE,KAAK;CACxB,CAAC;AAEF,MAAM,OAAO,UAAU;IAMX;IACA;IACA;IACA;IARF,KAAK,GAA0C,IAAI,CAAC;IACpD,OAAO,GAAG,KAAK,CAAC;IAChB,MAAM,CAA0C;IAExD,YACU,KAAgB,EAChB,QAAyB,EACzB,MAAkB,EAClB,SAA2B,oBAAoB;QAH/C,UAAK,GAAL,KAAK,CAAW;QAChB,aAAQ,GAAR,QAAQ,CAAiB;QACzB,WAAM,GAAN,MAAM,CAAY;QAClB,WAAM,GAAN,MAAM,CAAyC;QAEvD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI;YAC7B,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC;YACf,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC;YACd,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC;SAChB,CAAC;IACJ,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO;QACvB,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC1E,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;IACnF,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACpB,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO,CAAC,CAAC;QAC3B,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,IAAI,GAAG,CAAC,CAAC;QAEb,IAAI,CAAC;YACH,MAAM,SAAS,GAAmB,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;YAE5D,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;gBACjC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;oBACzC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,QAAQ,sCAAsC,CAAC,CAAC;oBACrE,SAAS;gBACX,CAAC;gBAED,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACzC,IAAI,MAAM,KAAK,CAAC;oBAAE,SAAS;gBAE3B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,QAAQ,cAAc,MAAM,eAAe,CAAC,CAAC;gBAElE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC;oBAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;oBAC1C,IAAI,CAAC,IAAI;wBAAE,MAAM;oBAEjB,IAAI,CAAC;wBACH,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;wBACzD,IAAI,EAAE,CAAC;wBACP,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;wBACtC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,QAAQ,sBAAsB,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;wBAE9D,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,GAAG,CAAC,EAAE,CAAC;4BAClC,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC;wBACtE,CAAC;oBACH,CAAC;oBAAC,OAAO,GAAG,EAAE,CAAC;wBACb,MAAM,KAAK,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;wBAC/D,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;wBAChC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;wBACtC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,oBAAoB,IAAI,CAAC,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;wBAChF,MAAM;oBACR,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACvB,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC;IAC7B,CAAC;CACF"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * ResilientClient — Unified wrapper combining circuit breaker + retry.
3
+ *
4
+ * Replaces raw fetch() calls in Nole's service clients with
5
+ * protection against transient failures and cascading outages.
6
+ *
7
+ * @see SPEC-nole-operational-resilience.md §5
8
+ */
9
+ import { type CircuitBreakerConfig, type CircuitState } from './circuit-breaker.js';
10
+ import { type RetryConfig } from './retry.js';
11
+ export declare class CircuitOpenError extends Error {
12
+ readonly service: string;
13
+ readonly state: CircuitState;
14
+ readonly cooldownRemainingMs: number;
15
+ constructor(service: string, cooldownRemainingMs: number);
16
+ }
17
+ export interface ResilientClientConfig {
18
+ circuitBreaker: CircuitBreakerConfig;
19
+ retry?: RetryConfig;
20
+ timeoutMs: number;
21
+ logger?: {
22
+ debug: (msg: string, meta?: Record<string, unknown>) => void;
23
+ warn: (msg: string, meta?: Record<string, unknown>) => void;
24
+ error: (msg: string, meta?: Record<string, unknown>) => void;
25
+ };
26
+ fetchFn?: typeof globalThis.fetch;
27
+ }
28
+ export interface ResilientResponse {
29
+ status: number;
30
+ headers: Headers;
31
+ body: string;
32
+ attempts: number;
33
+ totalDurationMs: number;
34
+ }
35
+ export declare class ResilientClient {
36
+ private config;
37
+ private breaker;
38
+ private retryConfig;
39
+ private timeoutMs;
40
+ private logger;
41
+ private fetchFn;
42
+ constructor(config: ResilientClientConfig);
43
+ execute(url: string, init?: RequestInit): Promise<ResilientResponse>;
44
+ getCircuitStatus(): {
45
+ state: CircuitState;
46
+ failureCount: number;
47
+ consecutiveOpenings: number;
48
+ currentCooldownMs: number;
49
+ lastStateChange: number;
50
+ recentFailures: string[];
51
+ };
52
+ get serviceName(): string;
53
+ }
54
+ //# sourceMappingURL=resilient-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resilient-client.d.ts","sourceRoot":"","sources":["../../src/resilience/resilient-client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAkB,KAAK,oBAAoB,EAAE,KAAK,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpG,OAAO,EACL,KAAK,WAAW,EAMjB,MAAM,YAAY,CAAC;AAEpB,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;gBAEzB,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM;CAOzD;AAED,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,oBAAoB,CAAC;IACrC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE;QACP,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QAC7D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QAC5D,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;KAC9D,CAAC;IACF,OAAO,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACnC;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,eAAe;IAOd,OAAO,CAAC,MAAM;IAN1B,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,MAAM,CAA+C;IAC7D,OAAO,CAAC,OAAO,CAA0B;gBAErB,MAAM,EAAE,qBAAqB;IAY3C,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAyF1E,gBAAgB;;;;;;;;IAIhB,IAAI,WAAW,IAAI,MAAM,CAExB;CACF"}