govyn 0.0.1 → 0.2.5

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 (153) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +263 -1
  3. package/configs/multi-provider.yaml +68 -0
  4. package/configs/openai-only.yaml +45 -0
  5. package/configs/team-setup.yaml +88 -0
  6. package/dist/action-logger.d.ts +128 -0
  7. package/dist/action-logger.js +356 -0
  8. package/dist/action-logger.js.map +1 -0
  9. package/dist/admin-cli.d.ts +2 -0
  10. package/dist/admin-cli.js +36 -0
  11. package/dist/admin-cli.js.map +1 -0
  12. package/dist/agents.d.ts +23 -0
  13. package/dist/agents.js +59 -0
  14. package/dist/agents.js.map +1 -0
  15. package/dist/alert-api.d.ts +14 -0
  16. package/dist/alert-api.js +355 -0
  17. package/dist/alert-api.js.map +1 -0
  18. package/dist/alert-manager.d.ts +77 -0
  19. package/dist/alert-manager.js +267 -0
  20. package/dist/alert-manager.js.map +1 -0
  21. package/dist/approval-api.d.ts +19 -0
  22. package/dist/approval-api.js +82 -0
  23. package/dist/approval-api.js.map +1 -0
  24. package/dist/approval-timeout.d.ts +29 -0
  25. package/dist/approval-timeout.js +45 -0
  26. package/dist/approval-timeout.js.map +1 -0
  27. package/dist/approval.d.ts +78 -0
  28. package/dist/approval.js +101 -0
  29. package/dist/approval.js.map +1 -0
  30. package/dist/auth.d.ts +47 -0
  31. package/dist/auth.js +335 -0
  32. package/dist/auth.js.map +1 -0
  33. package/dist/budget-api.d.ts +20 -0
  34. package/dist/budget-api.js +85 -0
  35. package/dist/budget-api.js.map +1 -0
  36. package/dist/budget-enforcer.d.ts +102 -0
  37. package/dist/budget-enforcer.js +294 -0
  38. package/dist/budget-enforcer.js.map +1 -0
  39. package/dist/cli.d.ts +15 -0
  40. package/dist/cli.js +200 -0
  41. package/dist/cli.js.map +1 -0
  42. package/dist/config.d.ts +15 -0
  43. package/dist/config.js +267 -0
  44. package/dist/config.js.map +1 -0
  45. package/dist/cost-aggregator.d.ts +69 -0
  46. package/dist/cost-aggregator.js +305 -0
  47. package/dist/cost-aggregator.js.map +1 -0
  48. package/dist/cost-api.d.ts +29 -0
  49. package/dist/cost-api.js +128 -0
  50. package/dist/cost-api.js.map +1 -0
  51. package/dist/database-url.d.ts +6 -0
  52. package/dist/database-url.js +47 -0
  53. package/dist/database-url.js.map +1 -0
  54. package/dist/db-retention.d.ts +53 -0
  55. package/dist/db-retention.js +82 -0
  56. package/dist/db-retention.js.map +1 -0
  57. package/dist/db-schema.d.ts +17 -0
  58. package/dist/db-schema.js +167 -0
  59. package/dist/db-schema.js.map +1 -0
  60. package/dist/db-writer.d.ts +55 -0
  61. package/dist/db-writer.js +115 -0
  62. package/dist/db-writer.js.map +1 -0
  63. package/dist/db.d.ts +33 -0
  64. package/dist/db.js +78 -0
  65. package/dist/db.js.map +1 -0
  66. package/dist/events.d.ts +77 -0
  67. package/dist/events.js +12 -0
  68. package/dist/events.js.map +1 -0
  69. package/dist/health.d.ts +14 -0
  70. package/dist/health.js +49 -0
  71. package/dist/health.js.map +1 -0
  72. package/dist/index.d.ts +7 -0
  73. package/dist/index.js +14 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/init-wizard.d.ts +12 -0
  76. package/dist/init-wizard.js +206 -0
  77. package/dist/init-wizard.js.map +1 -0
  78. package/dist/log-api.d.ts +20 -0
  79. package/dist/log-api.js +371 -0
  80. package/dist/log-api.js.map +1 -0
  81. package/dist/log-rotator.d.ts +55 -0
  82. package/dist/log-rotator.js +157 -0
  83. package/dist/log-rotator.js.map +1 -0
  84. package/dist/loop-detector.d.ts +71 -0
  85. package/dist/loop-detector.js +122 -0
  86. package/dist/loop-detector.js.map +1 -0
  87. package/dist/persistence-types.d.ts +165 -0
  88. package/dist/persistence-types.js +2 -0
  89. package/dist/persistence-types.js.map +1 -0
  90. package/dist/persistence.d.ts +185 -0
  91. package/dist/persistence.js +785 -0
  92. package/dist/persistence.js.map +1 -0
  93. package/dist/policy-api.d.ts +25 -0
  94. package/dist/policy-api.js +347 -0
  95. package/dist/policy-api.js.map +1 -0
  96. package/dist/policy-engine.d.ts +76 -0
  97. package/dist/policy-engine.js +835 -0
  98. package/dist/policy-engine.js.map +1 -0
  99. package/dist/policy-file.d.ts +10 -0
  100. package/dist/policy-file.js +52 -0
  101. package/dist/policy-file.js.map +1 -0
  102. package/dist/policy-parser.d.ts +21 -0
  103. package/dist/policy-parser.js +560 -0
  104. package/dist/policy-parser.js.map +1 -0
  105. package/dist/policy-types.d.ts +216 -0
  106. package/dist/policy-types.js +8 -0
  107. package/dist/policy-types.js.map +1 -0
  108. package/dist/policy-watcher.d.ts +54 -0
  109. package/dist/policy-watcher.js +116 -0
  110. package/dist/policy-watcher.js.map +1 -0
  111. package/dist/pricing.d.ts +69 -0
  112. package/dist/pricing.js +93 -0
  113. package/dist/pricing.js.map +1 -0
  114. package/dist/prompt.d.ts +6 -0
  115. package/dist/prompt.js +47 -0
  116. package/dist/prompt.js.map +1 -0
  117. package/dist/providers/anthropic.d.ts +18 -0
  118. package/dist/providers/anthropic.js +61 -0
  119. package/dist/providers/anthropic.js.map +1 -0
  120. package/dist/providers/custom.d.ts +19 -0
  121. package/dist/providers/custom.js +54 -0
  122. package/dist/providers/custom.js.map +1 -0
  123. package/dist/providers/openai.d.ts +17 -0
  124. package/dist/providers/openai.js +48 -0
  125. package/dist/providers/openai.js.map +1 -0
  126. package/dist/proxy.d.ts +57 -0
  127. package/dist/proxy.js +477 -0
  128. package/dist/proxy.js.map +1 -0
  129. package/dist/router.d.ts +23 -0
  130. package/dist/router.js +89 -0
  131. package/dist/router.js.map +1 -0
  132. package/dist/runtime.d.ts +1 -0
  133. package/dist/runtime.js +139 -0
  134. package/dist/runtime.js.map +1 -0
  135. package/dist/security.d.ts +64 -0
  136. package/dist/security.js +422 -0
  137. package/dist/security.js.map +1 -0
  138. package/dist/server.d.ts +33 -0
  139. package/dist/server.js +1147 -0
  140. package/dist/server.js.map +1 -0
  141. package/dist/sqlite-schema.d.ts +6 -0
  142. package/dist/sqlite-schema.js +134 -0
  143. package/dist/sqlite-schema.js.map +1 -0
  144. package/dist/streaming.d.ts +24 -0
  145. package/dist/streaming.js +63 -0
  146. package/dist/streaming.js.map +1 -0
  147. package/dist/tokens.d.ts +45 -0
  148. package/dist/tokens.js +237 -0
  149. package/dist/tokens.js.map +1 -0
  150. package/dist/types.d.ts +344 -0
  151. package/dist/types.js +5 -0
  152. package/dist/types.js.map +1 -0
  153. package/package.json +66 -2
@@ -0,0 +1,29 @@
1
+ /**
2
+ * HTTP handler for the GET /api/costs endpoint.
3
+ *
4
+ * Returns per-agent, per-model, and per-period cost summaries from the
5
+ * in-memory CostAggregator. Supports query parameters for filtering:
6
+ * ?agent=<agentId> — filter to a specific agent
7
+ * ?period=<period> — 'hour', 'day'/'today', 'week', 'month', 'all' (default: 'all')
8
+ *
9
+ * Also serves GET /api/costs/timeseries for chart-ready bucketed spend history.
10
+ */
11
+ import type { IncomingMessage, ServerResponse } from 'node:http';
12
+ import { CostAggregator } from './cost-aggregator.js';
13
+ /**
14
+ * Handle GET /api/costs requests.
15
+ *
16
+ * - Returns 405 for non-GET methods.
17
+ * - Returns 200 JSON with cost data for GET.
18
+ *
19
+ * Response shape:
20
+ * {
21
+ * "period": "all",
22
+ * "generated_at": "2026-02-24T12:00:00Z",
23
+ * "agents": [ CostSummary... ],
24
+ * "models": { model: { cost, requests, inputTokens, outputTokens }, ... },
25
+ * "unpriced_models": ["unknown-model-xyz"],
26
+ * "totals": { cost, requests, input_tokens, output_tokens }
27
+ * }
28
+ */
29
+ export declare function handleCostApi(req: IncomingMessage, res: ServerResponse, aggregator: CostAggregator): void;
@@ -0,0 +1,128 @@
1
+ /**
2
+ * HTTP handler for the GET /api/costs endpoint.
3
+ *
4
+ * Returns per-agent, per-model, and per-period cost summaries from the
5
+ * in-memory CostAggregator. Supports query parameters for filtering:
6
+ * ?agent=<agentId> — filter to a specific agent
7
+ * ?period=<period> — 'hour', 'day'/'today', 'week', 'month', 'all' (default: 'all')
8
+ *
9
+ * Also serves GET /api/costs/timeseries for chart-ready bucketed spend history.
10
+ */
11
+ import { URL } from 'node:url';
12
+ /**
13
+ * Handle GET /api/costs requests.
14
+ *
15
+ * - Returns 405 for non-GET methods.
16
+ * - Returns 200 JSON with cost data for GET.
17
+ *
18
+ * Response shape:
19
+ * {
20
+ * "period": "all",
21
+ * "generated_at": "2026-02-24T12:00:00Z",
22
+ * "agents": [ CostSummary... ],
23
+ * "models": { model: { cost, requests, inputTokens, outputTokens }, ... },
24
+ * "unpriced_models": ["unknown-model-xyz"],
25
+ * "totals": { cost, requests, input_tokens, output_tokens }
26
+ * }
27
+ */
28
+ export function handleCostApi(req, res, aggregator) {
29
+ // Only allow GET
30
+ if (req.method !== 'GET') {
31
+ const body = JSON.stringify({ error: { message: 'Method not allowed', code: 'method_not_allowed' } });
32
+ res.writeHead(405, {
33
+ 'content-type': 'application/json',
34
+ 'content-length': Buffer.byteLength(body).toString(),
35
+ 'allow': 'GET',
36
+ });
37
+ res.end(body);
38
+ return;
39
+ }
40
+ // Parse query parameters from the request URL
41
+ // Use a dummy base to parse relative URLs
42
+ const baseUrl = `http://localhost`;
43
+ let parsedUrl;
44
+ try {
45
+ parsedUrl = new URL(req.url ?? '/api/costs', baseUrl);
46
+ }
47
+ catch {
48
+ parsedUrl = new URL('/api/costs', baseUrl);
49
+ }
50
+ const agentParam = parsedUrl.searchParams.get('agent') ?? undefined;
51
+ const periodParam = parsedUrl.searchParams.get('period') ?? 'all';
52
+ // Map 'today' to 'day' for convenience
53
+ let period;
54
+ switch (periodParam) {
55
+ case 'today':
56
+ period = 'day';
57
+ break;
58
+ case 'hour':
59
+ case 'day':
60
+ case 'week':
61
+ case 'month':
62
+ case 'all':
63
+ period = periodParam;
64
+ break;
65
+ default:
66
+ period = 'all';
67
+ break;
68
+ }
69
+ if (parsedUrl.pathname === '/api/costs/timeseries') {
70
+ const timeseries = aggregator.getTimeSeries({ agentId: agentParam, period });
71
+ const body = JSON.stringify({
72
+ period,
73
+ bucket: timeseries.bucket,
74
+ generated_at: new Date().toISOString(),
75
+ points: timeseries.points,
76
+ });
77
+ res.writeHead(200, {
78
+ 'content-type': 'application/json',
79
+ 'content-length': Buffer.byteLength(body).toString(),
80
+ });
81
+ res.end(body);
82
+ return;
83
+ }
84
+ if (parsedUrl.pathname !== '/api/costs') {
85
+ const body = JSON.stringify({ error: { message: 'Not found', code: 'not_found' } });
86
+ res.writeHead(404, {
87
+ 'content-type': 'application/json',
88
+ 'content-length': Buffer.byteLength(body).toString(),
89
+ });
90
+ res.end(body);
91
+ return;
92
+ }
93
+ // Query the aggregator
94
+ const agents = aggregator.getSummary({ agentId: agentParam, period });
95
+ const models = aggregator.getModelSummary({ period });
96
+ const unpricedModels = aggregator.getUnpricedModels();
97
+ // Compute totals by summing across all agent summaries
98
+ let totalCost = 0;
99
+ let totalRequests = 0;
100
+ let totalInputTokens = 0;
101
+ let totalOutputTokens = 0;
102
+ for (const summary of agents) {
103
+ totalCost += summary.totalCost;
104
+ totalRequests += summary.requestCount;
105
+ totalInputTokens += summary.totalInputTokens;
106
+ totalOutputTokens += summary.totalOutputTokens;
107
+ }
108
+ const responseData = {
109
+ period,
110
+ generated_at: new Date().toISOString(),
111
+ agents,
112
+ models,
113
+ unpriced_models: unpricedModels,
114
+ totals: {
115
+ cost: totalCost,
116
+ requests: totalRequests,
117
+ input_tokens: totalInputTokens,
118
+ output_tokens: totalOutputTokens,
119
+ },
120
+ };
121
+ const body = JSON.stringify(responseData);
122
+ res.writeHead(200, {
123
+ 'content-type': 'application/json',
124
+ 'content-length': Buffer.byteLength(body).toString(),
125
+ });
126
+ res.end(body);
127
+ }
128
+ //# sourceMappingURL=cost-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost-api.js","sourceRoot":"","sources":["../src/cost-api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export declare const DEFAULT_SQLITE_DATABASE_FILE = "./govyn.db";
2
+ export type DatabaseKind = 'sqlite' | 'postgres';
3
+ export declare function getDatabaseKind(databaseUrl: string): DatabaseKind;
4
+ export declare function defaultDatabaseUrl(configPath: string): string;
5
+ export declare function resolveDatabaseUrl(rawUrl: string, configPath: string): string;
6
+ export declare function sqlitePathFromUrl(databaseUrl: string): string;
@@ -0,0 +1,47 @@
1
+ import * as path from 'node:path';
2
+ export const DEFAULT_SQLITE_DATABASE_FILE = './govyn.db';
3
+ export function getDatabaseKind(databaseUrl) {
4
+ const normalized = databaseUrl.trim().toLowerCase();
5
+ if (normalized.startsWith('sqlite:')) {
6
+ return 'sqlite';
7
+ }
8
+ if (normalized.startsWith('postgres://') || normalized.startsWith('postgresql://')) {
9
+ return 'postgres';
10
+ }
11
+ throw new Error(`Unsupported database.url "${databaseUrl}". Use sqlite:./govyn.db or postgres://...`);
12
+ }
13
+ export function defaultDatabaseUrl(configPath) {
14
+ return `sqlite:${resolveSqlitePath(DEFAULT_SQLITE_DATABASE_FILE, configPath)}`;
15
+ }
16
+ export function resolveDatabaseUrl(rawUrl, configPath) {
17
+ const trimmed = rawUrl.trim();
18
+ if (trimmed.length === 0) {
19
+ throw new Error('database.url must not be empty');
20
+ }
21
+ const kind = getDatabaseKind(trimmed);
22
+ if (kind === 'postgres') {
23
+ return trimmed;
24
+ }
25
+ const sqlitePath = trimmed.slice('sqlite:'.length);
26
+ return `sqlite:${resolveSqlitePath(sqlitePath, configPath)}`;
27
+ }
28
+ export function sqlitePathFromUrl(databaseUrl) {
29
+ if (getDatabaseKind(databaseUrl) !== 'sqlite') {
30
+ throw new Error(`Expected a sqlite database URL, got "${databaseUrl}"`);
31
+ }
32
+ return databaseUrl.slice('sqlite:'.length);
33
+ }
34
+ function resolveSqlitePath(candidate, configPath) {
35
+ const trimmed = candidate.trim();
36
+ if (trimmed === ':memory:') {
37
+ return trimmed;
38
+ }
39
+ if (!trimmed) {
40
+ return resolveSqlitePath(DEFAULT_SQLITE_DATABASE_FILE, configPath);
41
+ }
42
+ if (path.isAbsolute(trimmed)) {
43
+ return trimmed;
44
+ }
45
+ return path.resolve(path.dirname(configPath), trimmed);
46
+ }
47
+ //# sourceMappingURL=database-url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database-url.js","sourceRoot":"","sources":["../src/database-url.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,MAAM,CAAC,MAAM,4BAA4B,GAAG,YAAY,CAAC;AAIzD,MAAM,UAAU,eAAe,CAAC,WAAmB;IACjD,MAAM,UAAU,GAAG,WAAW,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACpD,IAAI,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACrC,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,IAAI,UAAU,CAAC,UAAU,CAAC,aAAa,CAAC,IAAI,UAAU,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;QACnF,OAAO,UAAU,CAAC;IACpB,CAAC;IACD,MAAM,IAAI,KAAK,CACb,6BAA6B,WAAW,4CAA4C,CACrF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,UAAkB;IACnD,OAAO,UAAU,iBAAiB,CAAC,4BAA4B,EAAE,UAAU,CAAC,EAAE,CAAC;AACjF,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,MAAc,EAAE,UAAkB;IACnE,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IAC9B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,IAAI,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IACtC,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;QACxB,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACnD,OAAO,UAAU,iBAAiB,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,WAAmB;IACnD,IAAI,eAAe,CAAC,WAAW,CAAC,KAAK,QAAQ,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,wCAAwC,WAAW,GAAG,CAAC,CAAC;IAC1E,CAAC;IAED,OAAO,WAAW,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;AAC7C,CAAC;AAED,SAAS,iBAAiB,CAAC,SAAiB,EAAE,UAAkB;IAC9D,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC;IACjC,IAAI,OAAO,KAAK,UAAU,EAAE,CAAC;QAC3B,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,iBAAiB,CAAC,4BAA4B,EAAE,UAAU,CAAC,CAAC;IACrE,CAAC;IAED,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7B,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC,CAAC;AACzD,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Data retention manager for the Govyn proxy database.
3
+ *
4
+ * Handles cleanup of old records with pre-delete aggregation:
5
+ * - Cost records are aggregated into daily summaries before deletion
6
+ * - Policy evaluations are deleted after retention period (no aggregation needed)
7
+ * - Approval records have a separate, longer retention period
8
+ *
9
+ * Designed to run on a periodic schedule (e.g. every 6 hours).
10
+ */
11
+ import type postgres from 'postgres';
12
+ import type { RetentionStore } from './persistence-types.js';
13
+ /**
14
+ * RetentionManager aggregates and cleans up old database records.
15
+ */
16
+ export declare class RetentionManager {
17
+ private retentionDays;
18
+ private approvalRetentionDays;
19
+ private readonly store;
20
+ constructor(storeOrSql: RetentionStore | postgres.Sql, retentionDays: number, approvalRetentionDays: number);
21
+ /**
22
+ * Aggregate old cost records into daily summaries, then delete the raw records.
23
+ *
24
+ * Before deleting: INSERT INTO cost_daily_summary grouped by agent_id, model, provider, date.
25
+ * Uses UPSERT (ON CONFLICT UPDATE) so re-runs are safe.
26
+ * Then: DELETE FROM cost_records WHERE created_at < cutoff.
27
+ *
28
+ * @returns Counts of aggregated summary rows and deleted raw records
29
+ */
30
+ cleanupCostRecords(): Promise<{
31
+ aggregated: number;
32
+ deleted: number;
33
+ }>;
34
+ /**
35
+ * Delete old policy evaluation records past the retention period.
36
+ * No aggregation needed for policy evaluations.
37
+ *
38
+ * @returns Number of deleted records
39
+ */
40
+ cleanupPolicyEvaluations(): Promise<number>;
41
+ /**
42
+ * Delete old approval records past the approval retention period.
43
+ * Approvals use a separate, longer retention (default 365 days).
44
+ *
45
+ * @returns Number of deleted records
46
+ */
47
+ cleanupApprovalRecords(): Promise<number>;
48
+ /**
49
+ * Run all retention cleanup tasks.
50
+ * Logs results to stdout for observability.
51
+ */
52
+ runAll(): Promise<void>;
53
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Data retention manager for the Govyn proxy database.
3
+ *
4
+ * Handles cleanup of old records with pre-delete aggregation:
5
+ * - Cost records are aggregated into daily summaries before deletion
6
+ * - Policy evaluations are deleted after retention period (no aggregation needed)
7
+ * - Approval records have a separate, longer retention period
8
+ *
9
+ * Designed to run on a periodic schedule (e.g. every 6 hours).
10
+ */
11
+ import { adaptRetentionStore } from './persistence.js';
12
+ /**
13
+ * RetentionManager aggregates and cleans up old database records.
14
+ */
15
+ export class RetentionManager {
16
+ retentionDays;
17
+ approvalRetentionDays;
18
+ store;
19
+ constructor(storeOrSql, retentionDays, approvalRetentionDays) {
20
+ this.retentionDays = retentionDays;
21
+ this.approvalRetentionDays = approvalRetentionDays;
22
+ this.store = adaptRetentionStore(storeOrSql, retentionDays, approvalRetentionDays);
23
+ }
24
+ /**
25
+ * Aggregate old cost records into daily summaries, then delete the raw records.
26
+ *
27
+ * Before deleting: INSERT INTO cost_daily_summary grouped by agent_id, model, provider, date.
28
+ * Uses UPSERT (ON CONFLICT UPDATE) so re-runs are safe.
29
+ * Then: DELETE FROM cost_records WHERE created_at < cutoff.
30
+ *
31
+ * @returns Counts of aggregated summary rows and deleted raw records
32
+ */
33
+ async cleanupCostRecords() {
34
+ const cutoff = new Date(Date.now() - this.retentionDays * 24 * 60 * 60 * 1000);
35
+ return this.store.cleanupCostRecords(cutoff);
36
+ }
37
+ /**
38
+ * Delete old policy evaluation records past the retention period.
39
+ * No aggregation needed for policy evaluations.
40
+ *
41
+ * @returns Number of deleted records
42
+ */
43
+ async cleanupPolicyEvaluations() {
44
+ const cutoff = new Date(Date.now() - this.retentionDays * 24 * 60 * 60 * 1000);
45
+ return this.store.cleanupPolicyEvaluations(cutoff);
46
+ }
47
+ /**
48
+ * Delete old approval records past the approval retention period.
49
+ * Approvals use a separate, longer retention (default 365 days).
50
+ *
51
+ * @returns Number of deleted records
52
+ */
53
+ async cleanupApprovalRecords() {
54
+ const cutoff = new Date(Date.now() - this.approvalRetentionDays * 24 * 60 * 60 * 1000);
55
+ return this.store.cleanupApprovalRecords(cutoff);
56
+ }
57
+ /**
58
+ * Run all retention cleanup tasks.
59
+ * Logs results to stdout for observability.
60
+ */
61
+ async runAll() {
62
+ try {
63
+ const costResult = await this.cleanupCostRecords();
64
+ if (costResult.deleted > 0) {
65
+ console.log(`[govyn] Retention cleanup: aggregated ${costResult.aggregated} daily summaries, deleted ${costResult.deleted} cost records`);
66
+ }
67
+ const policyDeleted = await this.cleanupPolicyEvaluations();
68
+ if (policyDeleted > 0) {
69
+ console.log(`[govyn] Retention cleanup: deleted ${policyDeleted} policy evaluations`);
70
+ }
71
+ const approvalDeleted = await this.cleanupApprovalRecords();
72
+ if (approvalDeleted > 0) {
73
+ console.log(`[govyn] Retention cleanup: deleted ${approvalDeleted} approval records`);
74
+ }
75
+ }
76
+ catch (err) {
77
+ const message = err instanceof Error ? err.message : String(err);
78
+ process.stderr.write(`[govyn] Retention cleanup failed: ${message}\n`);
79
+ }
80
+ }
81
+ }
82
+ //# sourceMappingURL=db-retention.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db-retention.js","sourceRoot":"","sources":["../src/db-retention.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAEvD;;GAEG;AACH,MAAM,OAAO,gBAAgB;IAKjB;IACA;IALO,KAAK,CAAiB;IAEvC,YACE,UAAyC,EACjC,aAAqB,EACrB,qBAA6B;QAD7B,kBAAa,GAAb,aAAa,CAAQ;QACrB,0BAAqB,GAArB,qBAAqB,CAAQ;QAErC,IAAI,CAAC,KAAK,GAAG,mBAAmB,CAAC,UAAU,EAAE,aAAa,EAAE,qBAAqB,CAAC,CAAC;IACrF,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,kBAAkB;QACtB,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,aAAa,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC/E,OAAO,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC/C,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,wBAAwB;QAC5B,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,aAAa,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC/E,OAAO,IAAI,CAAC,KAAK,CAAC,wBAAwB,CAAC,MAAM,CAAC,CAAC;IACrD,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,sBAAsB;QAC1B,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,qBAAqB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QACvF,OAAO,IAAI,CAAC,KAAK,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC;IACnD,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,MAAM;QACV,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,kBAAkB,EAAE,CAAC;YACnD,IAAI,UAAU,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;gBAC3B,OAAO,CAAC,GAAG,CAAC,yCAAyC,UAAU,CAAC,UAAU,6BAA6B,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;YAC5I,CAAC;YAED,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,wBAAwB,EAAE,CAAC;YAC5D,IAAI,aAAa,GAAG,CAAC,EAAE,CAAC;gBACtB,OAAO,CAAC,GAAG,CAAC,sCAAsC,aAAa,qBAAqB,CAAC,CAAC;YACxF,CAAC;YAED,MAAM,eAAe,GAAG,MAAM,IAAI,CAAC,sBAAsB,EAAE,CAAC;YAC5D,IAAI,eAAe,GAAG,CAAC,EAAE,CAAC;gBACxB,OAAO,CAAC,GAAG,CAAC,sCAAsC,eAAe,mBAAmB,CAAC,CAAC;YACxF,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qCAAqC,OAAO,IAAI,CAAC,CAAC;QACzE,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * SQL schema definitions and versioned migration statements for the Govyn proxy.
3
+ *
4
+ * Migrations are applied in order by the migration runner (src/db.ts).
5
+ * Each migration runs inside a transaction and is tracked in govyn_migrations.
6
+ */
7
+ export interface Migration {
8
+ version: number;
9
+ name: string;
10
+ sql: string;
11
+ }
12
+ /**
13
+ * Ordered array of database migrations.
14
+ * New migrations are appended with incrementing version numbers.
15
+ * NEVER modify or reorder existing migrations — append only.
16
+ */
17
+ export declare const MIGRATIONS: Migration[];
@@ -0,0 +1,167 @@
1
+ /**
2
+ * SQL schema definitions and versioned migration statements for the Govyn proxy.
3
+ *
4
+ * Migrations are applied in order by the migration runner (src/db.ts).
5
+ * Each migration runs inside a transaction and is tracked in govyn_migrations.
6
+ */
7
+ /**
8
+ * Ordered array of database migrations.
9
+ * New migrations are appended with incrementing version numbers.
10
+ * NEVER modify or reorder existing migrations — append only.
11
+ */
12
+ export const MIGRATIONS = [
13
+ {
14
+ version: 1,
15
+ name: 'initial_schema',
16
+ sql: `
17
+ -- Migrations tracking table
18
+ CREATE TABLE IF NOT EXISTS govyn_migrations (
19
+ version INTEGER PRIMARY KEY,
20
+ name TEXT NOT NULL,
21
+ applied_at TIMESTAMPTZ DEFAULT NOW()
22
+ );
23
+
24
+ -- Cost records table
25
+ CREATE TABLE IF NOT EXISTS cost_records (
26
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
27
+ agent_id TEXT NOT NULL,
28
+ model TEXT NOT NULL,
29
+ provider TEXT NOT NULL,
30
+ input_tokens INTEGER NOT NULL,
31
+ output_tokens INTEGER NOT NULL,
32
+ input_cost NUMERIC(12,8) NOT NULL,
33
+ output_cost NUMERIC(12,8) NOT NULL,
34
+ total_cost NUMERIC(12,8) NOT NULL,
35
+ priced BOOLEAN NOT NULL DEFAULT true,
36
+ requested_model TEXT,
37
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
38
+ );
39
+
40
+ -- Indexes for time-windowed aggregation queries (DATA-02)
41
+ CREATE INDEX idx_cost_records_agent_created ON cost_records (agent_id, created_at);
42
+ CREATE INDEX idx_cost_records_created ON cost_records (created_at);
43
+ CREATE INDEX idx_cost_records_model ON cost_records (model);
44
+
45
+ -- Policy evaluation log table
46
+ CREATE TABLE IF NOT EXISTS policy_evaluations (
47
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
48
+ agent_id TEXT NOT NULL,
49
+ provider TEXT NOT NULL,
50
+ path TEXT NOT NULL,
51
+ allowed BOOLEAN NOT NULL,
52
+ evaluated_count INTEGER NOT NULL DEFAULT 0,
53
+ matched_count INTEGER NOT NULL DEFAULT 0,
54
+ denied_by TEXT,
55
+ denied_reason TEXT,
56
+ evaluation_time_ms REAL,
57
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
58
+ );
59
+
60
+ CREATE INDEX idx_policy_evals_agent_created ON policy_evaluations (agent_id, created_at);
61
+ CREATE INDEX idx_policy_evals_created ON policy_evaluations (created_at);
62
+
63
+ -- Approval requests table (foundation for Plan 02)
64
+ CREATE TABLE IF NOT EXISTS approval_requests (
65
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
66
+ agent_id TEXT NOT NULL,
67
+ provider TEXT NOT NULL,
68
+ model TEXT,
69
+ target_path TEXT NOT NULL,
70
+ policy_name TEXT NOT NULL,
71
+ policy_rule TEXT,
72
+ estimated_cost NUMERIC(12,8),
73
+ request_summary TEXT,
74
+ request_payload JSONB,
75
+ status TEXT NOT NULL DEFAULT 'pending'
76
+ CHECK (status IN ('pending', 'approved', 'denied', 'denied_timeout')),
77
+ decided_by TEXT,
78
+ decision_notes TEXT,
79
+ decided_at TIMESTAMPTZ,
80
+ timeout_seconds INTEGER NOT NULL DEFAULT 1800,
81
+ expires_at TIMESTAMPTZ NOT NULL,
82
+ approval_token UUID,
83
+ token_used BOOLEAN NOT NULL DEFAULT false,
84
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
85
+ );
86
+
87
+ CREATE INDEX idx_approvals_status ON approval_requests (status);
88
+ CREATE INDEX idx_approvals_agent_created ON approval_requests (agent_id, created_at);
89
+ CREATE INDEX idx_approvals_expires ON approval_requests (expires_at) WHERE status = 'pending';
90
+ CREATE INDEX idx_approvals_token ON approval_requests (approval_token) WHERE approval_token IS NOT NULL;
91
+
92
+ -- Daily cost summary table for retention aggregation
93
+ CREATE TABLE IF NOT EXISTS cost_daily_summary (
94
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
95
+ agent_id TEXT NOT NULL,
96
+ model TEXT NOT NULL,
97
+ provider TEXT NOT NULL,
98
+ date DATE NOT NULL,
99
+ total_requests INTEGER NOT NULL DEFAULT 0,
100
+ total_input_tokens BIGINT NOT NULL DEFAULT 0,
101
+ total_output_tokens BIGINT NOT NULL DEFAULT 0,
102
+ total_input_cost NUMERIC(12,8) NOT NULL DEFAULT 0,
103
+ total_output_cost NUMERIC(12,8) NOT NULL DEFAULT 0,
104
+ total_cost NUMERIC(12,8) NOT NULL DEFAULT 0,
105
+ UNIQUE (agent_id, model, provider, date)
106
+ );
107
+
108
+ CREATE INDEX idx_daily_summary_agent_date ON cost_daily_summary (agent_id, date);
109
+ `,
110
+ },
111
+ {
112
+ version: 2,
113
+ name: 'alert_tables',
114
+ sql: `
115
+ CREATE TABLE IF NOT EXISTS alert_rules (
116
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
117
+ name TEXT NOT NULL,
118
+ type TEXT NOT NULL CHECK (type IN ('budget_threshold', 'policy_trigger')),
119
+ enabled BOOLEAN NOT NULL DEFAULT true,
120
+ config JSONB NOT NULL,
121
+ webhook_url TEXT NOT NULL,
122
+ cooldown_minutes INTEGER NOT NULL DEFAULT 60,
123
+ last_fired_at TIMESTAMPTZ,
124
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
125
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
126
+ );
127
+
128
+ CREATE INDEX idx_alert_rules_type ON alert_rules (type);
129
+ CREATE INDEX idx_alert_rules_enabled ON alert_rules (enabled) WHERE enabled = true;
130
+
131
+ CREATE TABLE IF NOT EXISTS alert_history (
132
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
133
+ rule_id UUID NOT NULL REFERENCES alert_rules(id) ON DELETE CASCADE,
134
+ rule_name TEXT NOT NULL,
135
+ rule_type TEXT NOT NULL,
136
+ event_type TEXT NOT NULL,
137
+ event_payload JSONB NOT NULL,
138
+ webhook_url TEXT NOT NULL,
139
+ webhook_status INTEGER,
140
+ webhook_error TEXT,
141
+ fired_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
142
+ );
143
+
144
+ CREATE INDEX idx_alert_history_rule ON alert_history (rule_id, fired_at);
145
+ CREATE INDEX idx_alert_history_fired ON alert_history (fired_at);
146
+ `,
147
+ },
148
+ {
149
+ version: 3,
150
+ name: 'approval_request_binding',
151
+ sql: `
152
+ ALTER TABLE approval_requests
153
+ ADD COLUMN IF NOT EXISTS request_hash TEXT;
154
+
155
+ UPDATE approval_requests
156
+ SET request_hash = ''
157
+ WHERE request_hash IS NULL;
158
+
159
+ ALTER TABLE approval_requests
160
+ ALTER COLUMN request_hash SET NOT NULL;
161
+
162
+ CREATE INDEX IF NOT EXISTS idx_approvals_request_hash
163
+ ON approval_requests (request_hash);
164
+ `,
165
+ },
166
+ ];
167
+ //# sourceMappingURL=db-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db-schema.js","sourceRoot":"","sources":["../src/db-schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAgB;IACrC;QACE,OAAO,EAAE,CAAC;QACV,IAAI,EAAE,gBAAgB;QACtB,GAAG,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA6FJ;KACF;IACD;QACE,OAAO,EAAE,CAAC;QACV,IAAI,EAAE,cAAc;QACpB,GAAG,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAgCJ;KACF;IACD;QACE,OAAO,EAAE,CAAC;QACV,IAAI,EAAE,0BAA0B;QAChC,GAAG,EAAE;;;;;;;;;;;;;KAaJ;KACF;CACF,CAAC"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Async database writer for the Govyn proxy.
3
+ *
4
+ * Writes cost records and policy evaluation results to PostgreSQL.
5
+ * All write methods are async and designed to be called fire-and-forget
6
+ * from the proxy hot path — zero added latency to request processing.
7
+ *
8
+ * Supports fail-open (log errors to stderr, continue proxying) and
9
+ * fail-closed (rethrow errors, reject requests) modes.
10
+ */
11
+ import type postgres from 'postgres';
12
+ import type { CostRecord } from './types.js';
13
+ import type { PersistenceWriterStore, PolicyEvaluationRecord } from './persistence-types.js';
14
+ /**
15
+ * DbWriter handles async persistence of cost records and policy evaluations
16
+ * to PostgreSQL. All writes are designed for fire-and-forget usage.
17
+ */
18
+ export declare class DbWriter {
19
+ private failOpen;
20
+ private readonly store;
21
+ constructor(storeOrSql: PersistenceWriterStore | postgres.Sql, failOpen: boolean);
22
+ /**
23
+ * Write a cost record to the database.
24
+ * Fire-and-forget from the proxy hot path — never blocks response delivery.
25
+ *
26
+ * @param record - The cost record from the in-memory aggregator
27
+ */
28
+ writeCostRecord(record: CostRecord): Promise<void>;
29
+ /**
30
+ * Write a policy evaluation result to the database.
31
+ * Fire-and-forget from the proxy hot path.
32
+ *
33
+ * @param evalRecord - The policy evaluation data
34
+ */
35
+ writePolicyEvaluation(evalRecord: PolicyEvaluationRecord): Promise<void>;
36
+ /**
37
+ * Write an approval event to the database for audit trail logging.
38
+ * Fire-and-forget from the approval flow.
39
+ *
40
+ * @param event - The approval event data
41
+ */
42
+ writeApprovalEvent(event: {
43
+ requestId: string;
44
+ action: 'created' | 'approved' | 'denied' | 'denied_timeout' | 'token_consumed';
45
+ decidedBy?: string;
46
+ notes?: string;
47
+ }): Promise<void>;
48
+ /**
49
+ * Check if the database is currently available.
50
+ * Used by the approval flow (Plan 02) since approvals ALWAYS require DB.
51
+ *
52
+ * @returns true if DB responds to a simple query, false otherwise
53
+ */
54
+ isAvailable(): Promise<boolean>;
55
+ }