cartethyia 2.6.7 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +106 -113
- package/dist/dashboard/assets/{BannedPage-Dskhyp0l.js → BannedPage-P76QLGJA.js} +1 -1
- package/dist/dashboard/assets/BannedPage-P76QLGJA.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolDetailPage--SJhk7qQ.js → CliToolDetailPage-DdBggJS9.js} +1 -1
- package/dist/dashboard/assets/CliToolDetailPage-DdBggJS9.js.gz +0 -0
- package/dist/dashboard/assets/{CliToolsPage-C203VT-c.js → CliToolsPage-aN1iXhhP.js} +1 -1
- package/dist/dashboard/assets/CliToolsPage-aN1iXhhP.js.gz +0 -0
- package/dist/dashboard/assets/{CombosPage-BcS7kGG1.js → CombosPage-DE-F1nxZ.js} +1 -1
- package/dist/dashboard/assets/CombosPage-DE-F1nxZ.js.gz +0 -0
- package/dist/dashboard/assets/{ConsoleLogPage-ChmiSLwO.js → ConsoleLogPage-5SpmfLyw.js} +1 -1
- package/dist/dashboard/assets/ConsoleLogPage-5SpmfLyw.js.gz +0 -0
- package/dist/dashboard/assets/{CustomizationPage-uujbBYwC.js → CustomizationPage-3CUxMy2X.js} +1 -1
- package/dist/dashboard/assets/CustomizationPage-3CUxMy2X.js.gz +0 -0
- package/dist/dashboard/assets/{HealthEventsModal-fKAWd-OQ.js → HealthEventsModal-BkmJQKC0.js} +1 -1
- package/dist/dashboard/assets/HealthEventsModal-BkmJQKC0.js.gz +0 -0
- package/dist/dashboard/assets/{LoginPage-DoOUthbG.js → LoginPage-B_0FUKWc.js} +1 -1
- package/dist/dashboard/assets/LoginPage-B_0FUKWc.js.gz +0 -0
- package/dist/dashboard/assets/{ModelPicker-CA1kLs45.js → ModelPicker-DKVslqG3.js} +1 -1
- package/dist/dashboard/assets/ModelPicker-DKVslqG3.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-D-H4MEiT.js +1 -0
- package/dist/dashboard/assets/OverviewPage-D-H4MEiT.js.gz +0 -0
- package/dist/dashboard/assets/{ProviderDetailPage-CFu1qGcT.js → ProviderDetailPage-CEJw6Guo.js} +3 -3
- package/dist/dashboard/assets/ProviderDetailPage-CEJw6Guo.js.gz +0 -0
- package/dist/dashboard/assets/{ProvidersPage-CnlJZbxF.js → ProvidersPage-CEplAR-i.js} +1 -1
- package/dist/dashboard/assets/ProvidersPage-CEplAR-i.js.gz +0 -0
- package/dist/dashboard/assets/{ProxyPage-B9mBTweh.js → ProxyPage-CAN7hy0f.js} +1 -1
- package/dist/dashboard/assets/ProxyPage-CAN7hy0f.js.gz +0 -0
- package/dist/dashboard/assets/{QuotaPage-BueSWX5D.js → QuotaPage-BjldQwIU.js} +1 -1
- package/dist/dashboard/assets/QuotaPage-BjldQwIU.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-DUIoLRxk.js +1 -0
- package/dist/dashboard/assets/SettingsPage-DUIoLRxk.js.gz +0 -0
- package/dist/dashboard/assets/{SetupPage-DQ9oDRpi.js → SetupPage-_bCaTM0v.js} +1 -1
- package/dist/dashboard/assets/SetupPage-_bCaTM0v.js.gz +0 -0
- package/dist/dashboard/assets/{SortableList-DrKeMcGN.js → SortableList-fI7JkdIB.js} +1 -1
- package/dist/dashboard/assets/SortableList-fI7JkdIB.js.gz +0 -0
- package/dist/dashboard/assets/{StudioPage-qjuYtccc.js → StudioPage-CHVZqskZ.js} +1 -1
- package/dist/dashboard/assets/StudioPage-CHVZqskZ.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-DiFBymHj.js +56 -0
- package/dist/dashboard/assets/UsagePage-DiFBymHj.js.gz +0 -0
- package/dist/dashboard/assets/{api-keys-C23S9sgp.js → api-keys-DFY_fZU4.js} +1 -1
- package/dist/dashboard/assets/api-keys-DFY_fZU4.js.gz +0 -0
- package/dist/dashboard/assets/app-BOJ1_xUB.css +2 -0
- package/dist/dashboard/assets/app-BOJ1_xUB.css.gz +0 -0
- package/dist/dashboard/assets/{assert-DU6qrMA_.js → assert-CwdnZARt.js} +1 -1
- package/dist/dashboard/assets/assert-CwdnZARt.js.gz +0 -0
- package/dist/dashboard/assets/{badge-Bkz_-RrM.js → badge-Dr-uf0Au.js} +1 -1
- package/dist/dashboard/assets/badge-Dr-uf0Au.js.gz +0 -0
- package/dist/dashboard/assets/{banner-C-7y3VKV.js → banner-BddmMdoS.js} +1 -1
- package/dist/dashboard/assets/banner-BddmMdoS.js.gz +0 -0
- package/dist/dashboard/assets/{card-BOPBuBzD.js → card-CK0PXWlX.js} +1 -1
- package/dist/dashboard/assets/card-CK0PXWlX.js.gz +0 -0
- package/dist/dashboard/assets/{cli-tools-BaFUuFFF.js → cli-tools-CcB6mGh2.js} +1 -1
- package/dist/dashboard/assets/cli-tools-CcB6mGh2.js.gz +0 -0
- package/dist/dashboard/assets/{clipboard-button-e-Mlk0FH.js → clipboard-button-DGb2P9Yx.js} +1 -1
- package/dist/dashboard/assets/clipboard-button-DGb2P9Yx.js.gz +0 -0
- package/dist/dashboard/assets/{dialog-Iec_ywbD.js → dialog-LkfuL50u.js} +1 -1
- package/dist/dashboard/assets/dialog-LkfuL50u.js.gz +0 -0
- package/dist/dashboard/assets/{drawer-Bs8MIIHc.js → drawer-ZbEZ_84c.js} +1 -1
- package/dist/dashboard/assets/drawer-ZbEZ_84c.js.gz +0 -0
- package/dist/dashboard/assets/{entry-DcobaKFu.js → entry-BJfsVMvA.js} +1 -1
- package/dist/dashboard/assets/entry-BJfsVMvA.js.gz +0 -0
- package/dist/dashboard/assets/{entry-DOddwoc0.js → entry-CWBY5A0f.js} +3 -3
- package/dist/dashboard/assets/entry-CWBY5A0f.js.gz +0 -0
- package/dist/dashboard/assets/{entry-BRszm4_Z.js → entry-CvC1-Zqx.js} +1 -1
- package/dist/dashboard/assets/entry-CvC1-Zqx.js.gz +0 -0
- package/dist/dashboard/assets/{github-badge-BuDsrsPn.js → github-badge-BxpznFLt.js} +1 -1
- package/dist/dashboard/assets/github-badge-BxpznFLt.js.gz +0 -0
- package/dist/dashboard/assets/{index-BeLRhsmt.js → index-CDzP3ovJ.js} +2 -2
- package/dist/dashboard/assets/index-CDzP3ovJ.js.gz +0 -0
- package/dist/dashboard/assets/{inline-DToMICwl.js → inline-Ae13MQ5w.js} +1 -1
- package/dist/dashboard/assets/inline-Ae13MQ5w.js.gz +0 -0
- package/dist/dashboard/assets/{input-WlyHWeG2.js → input-D23fo7cp.js} +1 -1
- package/dist/dashboard/assets/input-D23fo7cp.js.gz +0 -0
- package/dist/dashboard/assets/{layout-DaJ5baJr.js → layout-CSsxjj5P.js} +1 -1
- package/dist/dashboard/assets/layout-CSsxjj5P.js.gz +0 -0
- package/dist/dashboard/assets/{list-row-B3oSK13z.js → list-row-dmbwZMDY.js} +1 -1
- package/dist/dashboard/assets/list-row-dmbwZMDY.js.gz +0 -0
- package/dist/dashboard/assets/{live-ta6GMb-8.js → live-BG1oUsZZ.js} +1 -1
- package/dist/dashboard/assets/live-BG1oUsZZ.js.gz +0 -0
- package/dist/dashboard/assets/minimize-2-D-MN8W8a.js +1 -0
- package/dist/dashboard/assets/minimize-2-D-MN8W8a.js.gz +0 -0
- package/dist/dashboard/assets/{mutation-g_fduiNw.js → mutation-DSF-e4ho.js} +1 -1
- package/dist/dashboard/assets/mutation-DSF-e4ho.js.gz +0 -0
- package/dist/dashboard/assets/{numbers-Dulqgys5.js → numbers-Bm_aECY7.js} +1 -1
- package/dist/dashboard/assets/{numbers-Dulqgys5.js.gz → numbers-Bm_aECY7.js.gz} +0 -0
- package/dist/dashboard/assets/{query-keys-UETqfytp.js → query-keys-CF56nYIV.js} +1 -1
- package/dist/dashboard/assets/query-keys-CF56nYIV.js.gz +0 -0
- package/dist/dashboard/assets/{routing-BPUo-W8d.js → routing-CeA71n7G.js} +1 -1
- package/dist/dashboard/assets/routing-CeA71n7G.js.gz +0 -0
- package/dist/dashboard/assets/{select-BfZ3SCTM.js → select-KIT0rC6G.js} +1 -1
- package/dist/dashboard/assets/select-KIT0rC6G.js.gz +0 -0
- package/dist/dashboard/assets/settings-B_2gkePf.js +1 -0
- package/dist/dashboard/assets/settings-B_2gkePf.js.gz +0 -0
- package/dist/dashboard/assets/{stack-B1w5BM0u.js → stack-D7WOSzcX.js} +1 -1
- package/dist/dashboard/assets/stack-D7WOSzcX.js.gz +0 -0
- package/dist/dashboard/assets/{state-DhZBfWgx.js → state-BBxtKZOQ.js} +1 -1
- package/dist/dashboard/assets/state-BBxtKZOQ.js.gz +0 -0
- package/dist/dashboard/assets/{switch-yD7D99Lg.js → switch-DNS_D5Ea.js} +1 -1
- package/dist/dashboard/assets/switch-DNS_D5Ea.js.gz +0 -0
- package/dist/dashboard/assets/{useMutation-EDFRkn1i.js → useMutation-BgoxdoMk.js} +1 -1
- package/dist/dashboard/assets/useMutation-BgoxdoMk.js.gz +0 -0
- package/dist/dashboard/index.html +1 -1
- package/dist/dashboard/index.html.gz +0 -0
- package/dist/dashboard/precompressed-assets.json +51 -51
- package/package.json +96 -96
- package/src/boot/app.ts +714 -722
- package/src/boot/dependencies.ts +729 -750
- package/src/config/resources.ts +70 -137
- package/src/config/security.ts +37 -73
- package/src/config/spec.ts +358 -378
- package/src/console/console-router.ts +225 -233
- package/src/console/domain-registration.ts +381 -391
- package/src/console/remote-support/contracts.ts +3 -1
- package/src/console/remote-support/service.ts +115 -6
- package/src/main.ts +167 -169
- package/src/observability/metrics.ts +467 -457
- package/src/observability/runtime-metrics.ts +269 -254
- package/src/protocol/gateway-error.ts +232 -233
- package/src/providers/integrations/commandcode/commandcode.ts +134 -104
- package/src/providers/integrations/commandcode/discovery.ts +6 -5
- package/src/providers/integrations/qoder/qoder.ts +597 -23
- package/src/security/abuse.ts +17 -0
- package/src/transport/guards/auth-guard.ts +109 -127
- package/src/transport/guards/pipeline.ts +220 -226
- package/src/transport/guards/request-context.ts +238 -318
- package/src/transport/request/inflight.ts +21 -2
- package/dist/dashboard/assets/BannedPage-Dskhyp0l.js.gz +0 -0
- package/dist/dashboard/assets/CliToolDetailPage--SJhk7qQ.js.gz +0 -0
- package/dist/dashboard/assets/CliToolsPage-C203VT-c.js.gz +0 -0
- package/dist/dashboard/assets/CombosPage-BcS7kGG1.js.gz +0 -0
- package/dist/dashboard/assets/ConsoleLogPage-ChmiSLwO.js.gz +0 -0
- package/dist/dashboard/assets/CustomizationPage-uujbBYwC.js.gz +0 -0
- package/dist/dashboard/assets/HealthEventsModal-fKAWd-OQ.js.gz +0 -0
- package/dist/dashboard/assets/LoginPage-DoOUthbG.js.gz +0 -0
- package/dist/dashboard/assets/ModelPicker-CA1kLs45.js.gz +0 -0
- package/dist/dashboard/assets/OverviewPage-C5Z-UBz1.js +0 -1
- package/dist/dashboard/assets/OverviewPage-C5Z-UBz1.js.gz +0 -0
- package/dist/dashboard/assets/ProviderDetailPage-CFu1qGcT.js.gz +0 -0
- package/dist/dashboard/assets/ProvidersPage-CnlJZbxF.js.gz +0 -0
- package/dist/dashboard/assets/ProxyPage-B9mBTweh.js.gz +0 -0
- package/dist/dashboard/assets/QuotaPage-BueSWX5D.js.gz +0 -0
- package/dist/dashboard/assets/SettingsPage-B0x00MVY.js +0 -1
- package/dist/dashboard/assets/SettingsPage-B0x00MVY.js.gz +0 -0
- package/dist/dashboard/assets/SetupPage-DQ9oDRpi.js.gz +0 -0
- package/dist/dashboard/assets/SortableList-DrKeMcGN.js.gz +0 -0
- package/dist/dashboard/assets/StudioPage-qjuYtccc.js.gz +0 -0
- package/dist/dashboard/assets/UsagePage-Be7aNviQ.js +0 -56
- package/dist/dashboard/assets/UsagePage-Be7aNviQ.js.gz +0 -0
- package/dist/dashboard/assets/api-keys-C23S9sgp.js.gz +0 -0
- package/dist/dashboard/assets/app-BUSiYL6F.css +0 -2
- package/dist/dashboard/assets/app-BUSiYL6F.css.gz +0 -0
- package/dist/dashboard/assets/assert-DU6qrMA_.js.gz +0 -0
- package/dist/dashboard/assets/badge-Bkz_-RrM.js.gz +0 -0
- package/dist/dashboard/assets/banner-C-7y3VKV.js.gz +0 -0
- package/dist/dashboard/assets/card-BOPBuBzD.js.gz +0 -0
- package/dist/dashboard/assets/cli-tools-BaFUuFFF.js.gz +0 -0
- package/dist/dashboard/assets/clipboard-button-e-Mlk0FH.js.gz +0 -0
- package/dist/dashboard/assets/dialog-Iec_ywbD.js.gz +0 -0
- package/dist/dashboard/assets/drawer-Bs8MIIHc.js.gz +0 -0
- package/dist/dashboard/assets/entry-BRszm4_Z.js.gz +0 -0
- package/dist/dashboard/assets/entry-DOddwoc0.js.gz +0 -0
- package/dist/dashboard/assets/entry-DcobaKFu.js.gz +0 -0
- package/dist/dashboard/assets/github-badge-BuDsrsPn.js.gz +0 -0
- package/dist/dashboard/assets/index-BeLRhsmt.js.gz +0 -0
- package/dist/dashboard/assets/inline-DToMICwl.js.gz +0 -0
- package/dist/dashboard/assets/input-WlyHWeG2.js.gz +0 -0
- package/dist/dashboard/assets/layout-DaJ5baJr.js.gz +0 -0
- package/dist/dashboard/assets/list-row-B3oSK13z.js.gz +0 -0
- package/dist/dashboard/assets/live-ta6GMb-8.js.gz +0 -0
- package/dist/dashboard/assets/model-bans-BmYTmbAk.js +0 -1
- package/dist/dashboard/assets/model-bans-BmYTmbAk.js.gz +0 -0
- package/dist/dashboard/assets/mutation-g_fduiNw.js.gz +0 -0
- package/dist/dashboard/assets/query-keys-UETqfytp.js.gz +0 -0
- package/dist/dashboard/assets/routing-BPUo-W8d.js.gz +0 -0
- package/dist/dashboard/assets/select-BfZ3SCTM.js.gz +0 -0
- package/dist/dashboard/assets/settings--3hxSyry.js +0 -1
- package/dist/dashboard/assets/settings--3hxSyry.js.gz +0 -0
- package/dist/dashboard/assets/stack-B1w5BM0u.js.gz +0 -0
- package/dist/dashboard/assets/state-DhZBfWgx.js.gz +0 -0
- package/dist/dashboard/assets/switch-yD7D99Lg.js.gz +0 -0
- package/dist/dashboard/assets/useMutation-EDFRkn1i.js.gz +0 -0
- package/src/console/admin/model-abuse/contracts.ts +0 -38
- package/src/console/admin/model-abuse/routes.ts +0 -97
- package/src/security/model-abuse.ts +0 -704
- package/src/transport/guards/concurrency-guard.ts +0 -125
- /package/dist/dashboard/assets/{app-MdVfwraB.js → app-CaXZ9SCn.js} +0 -0
- /package/dist/dashboard/assets/{app-MdVfwraB.js.gz → app-CaXZ9SCn.js.gz} +0 -0
package/src/config/resources.ts
CHANGED
|
@@ -1,137 +1,70 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Canonical resource policy: how much memory this process may use, and how
|
|
3
|
-
* much
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
*
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
export
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
readonly maxBodyBytes: number;
|
|
72
|
-
readonly override?: number | undefined;
|
|
73
|
-
}): number {
|
|
74
|
-
if (options.override !== undefined) {
|
|
75
|
-
if (!Number.isInteger(options.override)) return 1;
|
|
76
|
-
return Math.min(MAX_PROCESS_CONCURRENCY, Math.max(1, options.override));
|
|
77
|
-
}
|
|
78
|
-
const body = Math.max(1, options.maxBodyBytes);
|
|
79
|
-
const perRequest = REQUEST_BODY_TRANSIENT_COPIES * body;
|
|
80
|
-
const budget = Math.max(1, Math.floor(options.memoryLimitBytes * REQUEST_BODY_BUDGET_FRACTION));
|
|
81
|
-
const derived = Math.floor(budget / perRequest);
|
|
82
|
-
return Math.min(MAX_PROCESS_CONCURRENCY, Math.max(1, derived));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Bytes kept free below the memory limit.
|
|
87
|
-
*
|
|
88
|
-
* A guard that only reacts once RSS equals the limit has already lost: the
|
|
89
|
-
* allocation that breaches it happens between two samples, and the spike that
|
|
90
|
-
* matters — an upstream response landing all at once — arrives in far less
|
|
91
|
-
* than one sample interval.
|
|
92
|
-
*/
|
|
93
|
-
export function deriveMemoryHeadroomBytes(memoryLimitBytes: number): number {
|
|
94
|
-
return Math.ceil(memoryLimitBytes * MEMORY_HEADROOM_FRACTION);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* The optional operator override, read through the config spec so "unset" and
|
|
99
|
-
* "out of range" are treated the way every other knob treats them.
|
|
100
|
-
*/
|
|
101
|
-
export function resolveProcessConcurrencyOverride(): number | undefined {
|
|
102
|
-
return readOptionalInt(
|
|
103
|
-
"CARTETHYIA_PROCESS_CONCURRENCY_LIMIT",
|
|
104
|
-
CONFIG_SPEC.CARTETHYIA_PROCESS_CONCURRENCY_LIMIT,
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Slack added on top of every key's concurrency limit.
|
|
110
|
-
*
|
|
111
|
-
* Operator configuration, not something a key carries about itself: it is
|
|
112
|
-
* deliberate hidden allowance so a run that lands a few requests over the
|
|
113
|
-
* number an operator chose is not refused at that number. The effective
|
|
114
|
-
* ceiling is `limit + tolerance`, and only requests past it are refused.
|
|
115
|
-
*/
|
|
116
|
-
export function resolveConcurrencyTolerance(): number {
|
|
117
|
-
const value = readOptionalInt(
|
|
118
|
-
"CARTETHYIA_CONCURRENCY_TOLERANCE",
|
|
119
|
-
CONFIG_SPEC.CARTETHYIA_CONCURRENCY_TOLERANCE,
|
|
120
|
-
);
|
|
121
|
-
return value === undefined ? 0 : Math.max(0, value);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* The effective concurrency for this process.
|
|
126
|
-
*
|
|
127
|
-
* Takes the limit as an argument rather than resolving it here: the limit
|
|
128
|
-
* owner is `resolveMemoryLimitBytes()` in `observability/runtime-metrics.ts`,
|
|
129
|
-
* and duplicating that resolution would let the two drift.
|
|
130
|
-
*/
|
|
131
|
-
export function resolveProcessConcurrency(memoryLimitBytes: number): number {
|
|
132
|
-
return deriveProcessConcurrency({
|
|
133
|
-
memoryLimitBytes,
|
|
134
|
-
maxBodyBytes: resolveMaxBodyBytes(),
|
|
135
|
-
override: resolveProcessConcurrencyOverride(),
|
|
136
|
-
});
|
|
137
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Canonical resource policy: how much memory this process may use, and how
|
|
3
|
+
* much room it must keep free.
|
|
4
|
+
*
|
|
5
|
+
* The memory figure used to be decided in unrelated places — a host-wide
|
|
6
|
+
* `os.totalmem()` in the headroom guard, a fallback constant in
|
|
7
|
+
* `runtime-metrics.ts` — which meant a container limited to 512 MiB shed on
|
|
8
|
+
* the same threshold as a 4 GiB one and only reacted once the *host* was
|
|
9
|
+
* nearly full. On a 1-core / 2 GiB VPS that is the difference between
|
|
10
|
+
* shedding early and being OOM-killed, and the OOM kills the dashboard too —
|
|
11
|
+
* the one surface that would have explained what happened.
|
|
12
|
+
*
|
|
13
|
+
* How many requests may run at once is NOT decided here. That is admission's
|
|
14
|
+
* job: the tenant ceiling and each key's own limit, both set in Settings. A
|
|
15
|
+
* second, process-wide ceiling derived from memory sat below the number an
|
|
16
|
+
* operator could configure and refused requests that every configured limit
|
|
17
|
+
* still allowed, which is why it is gone.
|
|
18
|
+
*/
|
|
19
|
+
import { CONFIG_SPEC, readOptionalInt } from "./spec";
|
|
20
|
+
|
|
21
|
+
/** Used when no limit is discoverable at all (native dev, macOS, Windows). */
|
|
22
|
+
export const FALLBACK_MEMORY_LIMIT_BYTES = 512 * 1024 * 1024;
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* How many copies of a request body are resident at once.
|
|
27
|
+
*
|
|
28
|
+
* Measured, not assumed: the socket read buffer, the parsed object graph, and
|
|
29
|
+
* the upstream re-serialization each hold a body-sized allocation, and garbage
|
|
30
|
+
* collection lags rather than keeping them to one. Budgeting one copy halves
|
|
31
|
+
* the real peak and lets the process admit twice the concurrency it can hold.
|
|
32
|
+
*/
|
|
33
|
+
export const REQUEST_BODY_TRANSIENT_COPIES = 2;
|
|
34
|
+
|
|
35
|
+
/** Share of the memory limit all in-flight request bodies together may hold. */
|
|
36
|
+
export const REQUEST_BODY_BUDGET_FRACTION = 0.25;
|
|
37
|
+
|
|
38
|
+
/** Share of the memory limit kept free for allocation spikes and GC slack. */
|
|
39
|
+
export const MEMORY_HEADROOM_FRACTION = 0.2;
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Bytes kept free below the memory limit.
|
|
44
|
+
*
|
|
45
|
+
* A guard that only reacts once RSS equals the limit has already lost: the
|
|
46
|
+
* allocation that breaches it happens between two samples, and the spike that
|
|
47
|
+
* matters — an upstream response landing all at once — arrives in far less
|
|
48
|
+
* than one sample interval.
|
|
49
|
+
*/
|
|
50
|
+
export function deriveMemoryHeadroomBytes(memoryLimitBytes: number): number {
|
|
51
|
+
return Math.ceil(memoryLimitBytes * MEMORY_HEADROOM_FRACTION);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Slack added on top of every key's concurrency limit.
|
|
57
|
+
*
|
|
58
|
+
* Operator configuration, not something a key carries about itself: it is
|
|
59
|
+
* deliberate hidden allowance so a run that lands a few requests over the
|
|
60
|
+
* number an operator chose is not refused at that number. The effective
|
|
61
|
+
* ceiling is `limit + tolerance`, and only requests past it are refused.
|
|
62
|
+
*/
|
|
63
|
+
export function resolveConcurrencyTolerance(): number {
|
|
64
|
+
const value = readOptionalInt(
|
|
65
|
+
"CARTETHYIA_CONCURRENCY_TOLERANCE",
|
|
66
|
+
CONFIG_SPEC.CARTETHYIA_CONCURRENCY_TOLERANCE,
|
|
67
|
+
);
|
|
68
|
+
return value === undefined ? 0 : Math.max(0, value);
|
|
69
|
+
}
|
|
70
|
+
|
package/src/config/security.ts
CHANGED
|
@@ -1,73 +1,37 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Security resolvers: the application encryption key
|
|
3
|
-
* ceiling
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { CONFIG_SPEC, readInt, readRequired } from "./spec";
|
|
7
|
-
|
|
8
|
-
const ENCRYPTION_KEY_LENGTH = 32;
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Per-IP request ceiling for the unauthenticated abuse-protection layer.
|
|
12
|
-
* Defaults to 240 requests per window so an unset env does not mean unlimited;
|
|
13
|
-
* operators tune it via `IP_RATE_MAX_PER_WINDOW`.
|
|
14
|
-
*/
|
|
15
|
-
export function resolveIpRateLimit(): number {
|
|
16
|
-
return readInt("IP_RATE_MAX_PER_WINDOW", CONFIG_SPEC.IP_RATE_MAX_PER_WINDOW);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
export function
|
|
21
|
-
return
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* The longest model-abuse ban the console will record, however long a request
|
|
42
|
-
* asks for.
|
|
43
|
-
*
|
|
44
|
-
* A separate knob from the ban TTL because the two are different decisions:
|
|
45
|
-
* the TTL is what a graduated ban gets, this is the ceiling on what an
|
|
46
|
-
* operator may ask for by hand. Raising one was never meant to raise the other.
|
|
47
|
-
*/
|
|
48
|
-
export function resolveModelBanMaxTtlMs(): number {
|
|
49
|
-
return readInt(
|
|
50
|
-
"CARTETHYIA_MODEL_BAN_MAX_TTL_MS",
|
|
51
|
-
CONFIG_SPEC.CARTETHYIA_MODEL_BAN_MAX_TTL_MS,
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** Reads the application encryption key without caching it. */
|
|
56
|
-
export function requireEncryptionKeyEnv(): string {
|
|
57
|
-
return readRequired("CARTETHYIA_ENCRYPTION_KEY", CONFIG_SPEC.CARTETHYIA_ENCRYPTION_KEY);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** Decodes and validates a base64 or hexadecimal 256-bit encryption key. */
|
|
61
|
-
export function decodeEncryptionKey(raw: string): Buffer {
|
|
62
|
-
const candidate = raw.trim();
|
|
63
|
-
const buf = /^[0-9a-fA-F]{64}$/.test(candidate)
|
|
64
|
-
? Buffer.from(candidate, "hex")
|
|
65
|
-
: Buffer.from(candidate, "base64");
|
|
66
|
-
if (buf.length !== ENCRYPTION_KEY_LENGTH) {
|
|
67
|
-
throw new Error(
|
|
68
|
-
`CARTETHYIA_ENCRYPTION_KEY must decode to exactly ${ENCRYPTION_KEY_LENGTH} bytes (got ${buf.length}); ` +
|
|
69
|
-
"provide a base64 or hex encoded 256-bit key",
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
return buf;
|
|
73
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Security resolvers: the application encryption key and the abuse-protection
|
|
3
|
+
* ceiling.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { CONFIG_SPEC, readInt, readRequired } from "./spec";
|
|
7
|
+
|
|
8
|
+
const ENCRYPTION_KEY_LENGTH = 32;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Per-IP request ceiling for the unauthenticated abuse-protection layer.
|
|
12
|
+
* Defaults to 240 requests per window so an unset env does not mean unlimited;
|
|
13
|
+
* operators tune it via `IP_RATE_MAX_PER_WINDOW`.
|
|
14
|
+
*/
|
|
15
|
+
export function resolveIpRateLimit(): number {
|
|
16
|
+
return readInt("IP_RATE_MAX_PER_WINDOW", CONFIG_SPEC.IP_RATE_MAX_PER_WINDOW);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Reads the application encryption key without caching it. */
|
|
20
|
+
export function requireEncryptionKeyEnv(): string {
|
|
21
|
+
return readRequired("CARTETHYIA_ENCRYPTION_KEY", CONFIG_SPEC.CARTETHYIA_ENCRYPTION_KEY);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Decodes and validates a base64 or hexadecimal 256-bit encryption key. */
|
|
25
|
+
export function decodeEncryptionKey(raw: string): Buffer {
|
|
26
|
+
const candidate = raw.trim();
|
|
27
|
+
const buf = /^[0-9a-fA-F]{64}$/.test(candidate)
|
|
28
|
+
? Buffer.from(candidate, "hex")
|
|
29
|
+
: Buffer.from(candidate, "base64");
|
|
30
|
+
if (buf.length !== ENCRYPTION_KEY_LENGTH) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`CARTETHYIA_ENCRYPTION_KEY must decode to exactly ${ENCRYPTION_KEY_LENGTH} bytes (got ${buf.length}); ` +
|
|
33
|
+
"provide a base64 or hex encoded 256-bit key",
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return buf;
|
|
37
|
+
}
|