okengine 0.17.2 → 0.18.4
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/AGENTS.md +5 -3
- package/README.md +14 -10
- package/manifest.v1.schema.json +61 -2
- package/package.json +19 -5
- package/site/content/docs/ai/index.mdx +1 -1
- package/site/content/docs/ai/mcp.mdx +10 -1
- package/site/content/docs/ai/skills.mdx +9 -9
- package/site/content/docs/elements/ai.mdx +1 -1
- package/site/content/docs/elements/clock.mdx +1 -1
- package/site/content/docs/elements/flow.mdx +25 -1
- package/site/content/docs/elements/gate.mdx +3 -2
- package/site/content/docs/elements/store.mdx +289 -341
- package/site/content/docs/elements/vault.mdx +5 -5
- package/site/content/docs/get-started/basic-usage.mdx +3 -10
- package/site/content/docs/get-started/index.mdx +1 -1
- package/site/content/docs/get-started/installation.mdx +2 -3
- package/site/content/docs/get-started/introduction.mdx +58 -121
- package/site/content/docs/get-started/meta.json +9 -1
- package/site/content/docs/get-started/project-structure.mdx +4 -11
- package/site/content/docs/get-started/testing.mdx +328 -0
- package/site/content/docs/get-started/why.mdx +93 -71
- package/site/content/docs/index.mdx +44 -11
- package/site/content/docs/meta.json +8 -5
- package/site/content/docs/plugins/apple.mdx +151 -0
- package/site/content/docs/plugins/discord.mdx +139 -0
- package/site/content/docs/plugins/facebook.mdx +134 -0
- package/site/content/docs/plugins/figma.mdx +138 -0
- package/site/content/docs/plugins/github.mdx +138 -0
- package/site/content/docs/plugins/google.mdx +153 -0
- package/site/content/docs/plugins/index.mdx +47 -1
- package/site/content/docs/plugins/meta.json +10 -0
- package/site/content/docs/plugins/microsoft.mdx +151 -0
- package/site/content/docs/plugins/oauth.mdx +188 -0
- package/site/content/docs/plugins/x.mdx +125 -0
- package/site/content/docs/providers/index.mdx +2 -0
- package/site/content/docs/recipes/index.mdx +2 -0
- package/site/content/docs/reference/cli.md +3 -2
- package/site/content/docs/reference/client.mdx +58 -1
- package/site/content/docs/reference/configuration.mdx +2 -4
- package/site/content/docs/reference/fx.mdx +3 -1
- package/site/content/docs/reference/index.mdx +0 -5
- package/site/content/docs/reference/meta.json +2 -2
- package/site/content/docs/reference/okid.mdx +137 -0
- package/src/auth/bindings.ts +1 -1
- package/src/auth/config.ts +9 -0
- package/src/auth/identity-sql.ts +314 -0
- package/src/auth/identity.ts +140 -2
- package/src/auth/index.ts +17 -1
- package/src/auth/method-context.ts +3 -0
- package/src/auth/oauth-as/cimd.ts +132 -0
- package/src/auth/oauth-as/crypto.test.ts +101 -0
- package/src/auth/oauth-as/crypto.ts +393 -0
- package/src/auth/oauth-as/errors.ts +68 -0
- package/src/auth/oauth-as/http.test.ts +419 -0
- package/src/auth/oauth-as/http.ts +842 -0
- package/src/auth/oauth-as/stores.ts +61 -0
- package/src/auth/oauth-as/tables.ts +142 -0
- package/src/auth/tables.ts +0 -11
- package/src/bench/README.md +83 -0
- package/src/bench/REPORT.md +176 -0
- package/src/bench/g01-rls-stamp.bench.ts +194 -0
- package/src/bench/g02-clock-per-tenant.bench.ts +158 -0
- package/src/bench/g03-signal-once.bench.ts +157 -0
- package/src/bench/g03-signal-reconnect.bench.ts +254 -0
- package/src/bench/g03-signal-sse-memory.bench.ts +191 -0
- package/src/bench/g04-auth-vault-hotpath.bench.ts +170 -0
- package/src/bench/g05-sustained-full.bench.ts +265 -0
- package/src/bench/g06-mixed-load.bench.ts +260 -0
- package/src/bench/g07-vault-crypto.bench.ts +100 -0
- package/src/bench/g07-vault-rotate-under-read.bench.ts +285 -0
- package/src/bench/g08-conn-oversubscribe.bench.ts +194 -0
- package/src/bench/g08-store-kv-durable.bench.ts +133 -0
- package/src/bench/g08-store-sql.bench.ts +178 -0
- package/src/bench/g09-journal-sustained.bench.ts +203 -0
- package/src/bench/g10-observability-contention.bench.ts +246 -0
- package/src/bench/g11-cold-start-cycle.bench.ts +164 -0
- package/src/bench/g13-elements.bench.ts +427 -0
- package/src/bench/g14-graceful-shutdown.bench.ts +244 -0
- package/src/bench/g15-postgres-degradation.bench.ts +264 -0
- package/src/bench/g16-live-query-fanout.bench.ts +206 -0
- package/src/bench/lib/event-loop-lag.ts +26 -0
- package/src/bench/lib/infra.ts +60 -0
- package/src/bench/lib/report.ts +52 -0
- package/src/bench/lib/rss-sampler.ts +61 -0
- package/src/bench/lib/signal-pg.ts +88 -0
- package/src/bench/load-app.ts +337 -0
- package/src/bench/load-child.ts +108 -0
- package/src/bench/smoke.bench.ts +43 -0
- package/src/cli/competitor-mention-removal.test.ts +28 -0
- package/src/cli/doctor-fd.ts +117 -0
- package/src/cli/doctor.test.ts +192 -0
- package/src/cli/doctor.ts +129 -1
- package/src/client/create.ts +95 -1
- package/src/client/index.ts +9 -2
- package/src/client/transport.ts +11 -4
- package/src/client/use-live-query.ts +154 -0
- package/src/client-react/index.ts +15 -1
- package/src/client-react/live-resource.ts +246 -0
- package/src/client-react/use-live-query.test.ts +475 -0
- package/src/client-react/use-live-query.ts +530 -0
- package/src/compiler/extract.test.ts +518 -0
- package/src/compiler/extract.ts +386 -19
- package/src/console/server/invoke-user-flow.ts +2 -1
- package/src/console/ui-next/dist/assets/{access-page-DnWbnGzq.js → access-page-De7Lc2JC.js} +1 -1
- package/src/console/ui-next/dist/assets/{flows-page-BiZ4-6yQ.js → flows-page-RGy7VEA_.js} +1 -1
- package/src/console/ui-next/dist/assets/{index-C8NRK2R-.js → index-_rgpdVzo.js} +3 -3
- package/src/console/ui-next/dist/assets/{observability-page-CrB6vd1T.js → observability-page-Ds6pcnh-.js} +1 -1
- package/src/console/ui-next/dist/assets/{store-page-CS5-aETQ.js → store-page-02xOiqIK.js} +3 -3
- package/src/console/ui-next/dist/assets/{units-page-CjtdlW8l.js → units-page-4rHOePuE.js} +1 -1
- package/src/console/ui-next/dist/assets/{vault-page-C6Xxm9SA.js → vault-page-DISPgxLM.js} +1 -1
- package/src/console/ui-next/dist/index.html +1 -1
- package/src/console/ui-next/src/features/store/lib/fields-from-table.ts +36 -2
- package/src/drivers/cdc-outbox.ts +389 -0
- package/src/drivers/memory.ts +20 -0
- package/src/drivers/oauth-apple.ts +156 -0
- package/src/drivers/oauth-discord.ts +79 -0
- package/src/drivers/oauth-facebook.ts +80 -0
- package/src/drivers/oauth-figma.ts +116 -0
- package/src/drivers/oauth-github.ts +92 -0
- package/src/drivers/oauth-google.ts +142 -0
- package/src/drivers/oauth-microsoft.ts +174 -0
- package/src/drivers/oauth-oidc.ts +293 -0
- package/src/drivers/oauth-shared.ts +326 -0
- package/src/drivers/oauth-types.ts +159 -0
- package/src/drivers/oauth-x.ts +77 -0
- package/src/drivers/oauth2-common.ts +95 -0
- package/src/drivers/oauth2-token.ts +61 -0
- package/src/drivers/pg-rls-row-passes.ts +251 -0
- package/src/drivers/pg-rls.ts +2 -0
- package/src/drivers/postgres.ts +45 -2
- package/src/drivers/signal-postgres.ts +2 -1
- package/src/elements/channel/runtime.ts +29 -2
- package/src/elements/channel.test.ts +52 -0
- package/src/elements/gate/boot.ts +29 -2
- package/src/elements/store/emit-drizzle.ts +147 -14
- package/src/elements/store/field-ddl.test.ts +118 -0
- package/src/elements/store/field-types.test.ts +455 -0
- package/src/elements/store/list-query.golden.json +777 -0
- package/src/elements/store/list-query.parity.test.ts +396 -0
- package/src/elements/store/list-query.ts +792 -0
- package/src/elements/store/live-default.test.ts +136 -0
- package/src/elements/store/live-http.test.ts +160 -0
- package/src/elements/store/live-isolation.test.ts +291 -0
- package/src/elements/store/live-query-runtime.test.ts +323 -0
- package/src/elements/store/live-query-runtime.ts +403 -0
- package/src/elements/store/live-query-server.test.ts +377 -0
- package/src/elements/store/live-query-server.ts +102 -0
- package/src/elements/store/live-query.ts +97 -0
- package/src/elements/store/resource.ts +189 -680
- package/src/elements/store/rls-row-passes-policies.parity.test.ts +665 -0
- package/src/elements/store/schema-decl.ts +539 -41
- package/src/elements/store/sql-rls-stamp.test.ts +27 -0
- package/src/elements/store/sql-session.ts +297 -35
- package/src/elements/store/table.ts +102 -21
- package/src/elements/store.test.ts +3 -1
- package/src/elements/store.ts +12 -1
- package/src/elements/vault/chaos-child.ts +74 -1
- package/src/elements/vault/chaos.test.ts +4 -2
- package/src/elements/vault/storage.ts +4 -2
- package/src/index.ts +5 -2
- package/src/kernel/app-auth.ts +1 -0
- package/src/kernel/app.ts +116 -2
- package/src/kernel/auth-sharing.test.ts +196 -0
- package/src/kernel/boot.test.ts +3 -3
- package/src/kernel/errors.ts +8 -0
- package/src/kernel/fx.test.ts +1 -0
- package/src/kernel/fx.ts +14 -2
- package/src/kernel/horizontal-child.ts +2 -1
- package/src/kernel/http-resource.ts +33 -7
- package/src/kernel/identity-host-persist.test.ts +119 -0
- package/src/kernel/instance-id.ts +4 -2
- package/src/kernel/journal.ts +2 -1
- package/src/kernel/mcp-tool.test.ts +95 -0
- package/src/kernel/on.ts +9 -0
- package/src/kernel/realtime-bind.ts +326 -0
- package/src/kernel/resource-live.ts +117 -0
- package/src/kernel/triggers.ts +86 -4
- package/src/manifest/diff.ts +37 -0
- package/src/manifest/types.ts +64 -2
- package/src/okid.bench.test.ts +64 -0
- package/src/okid.test.ts +338 -0
- package/src/okid.ts +245 -0
- package/src/plugins/anonymous.ts +19 -1
- package/src/plugins/auth/shared.ts +15 -0
- package/src/plugins/index.ts +2 -0
- package/src/plugins/magic-link.ts +10 -8
- package/src/plugins/mcp-oauth.ts +208 -0
- package/src/plugins/oauth/flow-store.ts +117 -0
- package/src/plugins/oauth/link.ts +69 -0
- package/src/plugins/oauth/shared.ts +108 -0
- package/src/plugins/oauth/token-vault.ts +100 -0
- package/src/plugins/oauth.security.test.ts +535 -0
- package/src/plugins/oauth.ts +532 -0
- package/src/plugins/otp.ts +48 -6
- package/src/plugins/passkey.ts +20 -1
- package/src/plugins/two-factor.ts +11 -0
- package/src/plugins/username.ts +40 -7
- package/src/release/build-lib.ts +7 -1
- package/src/release/measure.ts +1 -0
- package/src/release/official-plugins.ts +4 -1
- package/src/runs/collect.ts +2 -1
- package/src/runs/drivers/files.ts +2 -1
- package/src/test/create-test-app.ts +114 -5
- package/src/test/export-bundle.test.ts +33 -0
- package/src/test/live-signals.test.ts +83 -0
- package/src/test/tenant-isolation.test.ts +175 -0
- package/src/testing.ts +26 -0
- package/src/upgrade/codemods.ts +1 -1
- package/site/content/docs/reference/migrating-environments.mdx +0 -158
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authorization Server store bundle factory — in-memory rows mirroring the
|
|
3
|
+
* declared `oke_oauth_*` tables (same pattern as `SessionStore`).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createAsKeyStore } from "./crypto.ts";
|
|
7
|
+
import type {
|
|
8
|
+
AccessTokenRow,
|
|
9
|
+
AsRefreshTokenRow,
|
|
10
|
+
AuthCodeRow,
|
|
11
|
+
ClientCacheRow,
|
|
12
|
+
ConsentRow,
|
|
13
|
+
} from "./tables.ts";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Create an empty AS store bundle.
|
|
17
|
+
*/
|
|
18
|
+
export function createAsStores(): {
|
|
19
|
+
keys: ReturnType<typeof createAsKeyStore>;
|
|
20
|
+
authCodes: Map<string, AuthCodeRow>;
|
|
21
|
+
accessTokens: Map<string, AccessTokenRow>;
|
|
22
|
+
refreshTokens: Map<string, AsRefreshTokenRow>;
|
|
23
|
+
consents: Map<string, ConsentRow>;
|
|
24
|
+
clientCache: Map<string, ClientCacheRow>;
|
|
25
|
+
pending: Map<
|
|
26
|
+
string,
|
|
27
|
+
{
|
|
28
|
+
id: string;
|
|
29
|
+
userId: string;
|
|
30
|
+
clientId: string;
|
|
31
|
+
clientName: string | null;
|
|
32
|
+
redirectUri: string;
|
|
33
|
+
resource: string;
|
|
34
|
+
scope: readonly string[];
|
|
35
|
+
codeChallenge: string;
|
|
36
|
+
state: string | undefined;
|
|
37
|
+
expiresAt: number;
|
|
38
|
+
}
|
|
39
|
+
>;
|
|
40
|
+
} {
|
|
41
|
+
return {
|
|
42
|
+
keys: createAsKeyStore(),
|
|
43
|
+
authCodes: new Map(),
|
|
44
|
+
accessTokens: new Map(),
|
|
45
|
+
refreshTokens: new Map(),
|
|
46
|
+
consents: new Map(),
|
|
47
|
+
clientCache: new Map(),
|
|
48
|
+
pending: new Map(),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** SHA-256 hex of a raw secret (codes / refresh tokens are never stored raw). */
|
|
53
|
+
export async function hashSecret(raw: string): Promise<string> {
|
|
54
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(raw));
|
|
55
|
+
return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Cryptographically random id (no dashes), same hygiene as session ids. */
|
|
59
|
+
export function cryptoId(): string {
|
|
60
|
+
return crypto.randomUUID().replace(/-/g, "");
|
|
61
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authorization Server table names — `oke_oauth_` prefix, kept off
|
|
3
|
+
* `AUTH_TABLES` so Store-only / non-MCP graphs never pin the AS schema.
|
|
4
|
+
*
|
|
5
|
+
* Same opt-in discipline as tenant tables (`oke_tenants`). MCP is the
|
|
6
|
+
* Resource Server *surface*; OAuth is the persistence domain, hence the
|
|
7
|
+
* secondary prefix is `oauth_`, never `mcp_`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Opt-in via the `mcpOauth` plugin. */
|
|
11
|
+
export const OAUTH_AS_TABLES = {
|
|
12
|
+
/** ES256 signing keys — public JWK inline, private material sealed. */
|
|
13
|
+
signingKeys: "oke_oauth_signing_keys",
|
|
14
|
+
/** CIMD metadata cache — cache, not a DCR registry (locked decision 1). */
|
|
15
|
+
clientCache: "oke_oauth_client_cache",
|
|
16
|
+
/** Single-use authorization codes (SHA-256 hashed at rest). */
|
|
17
|
+
authCodes: "oke_oauth_auth_codes",
|
|
18
|
+
/** Issued access tokens — `resource` (RFC 8707) + `cnf.jkt` (RFC 9449). */
|
|
19
|
+
accessTokens: "oke_oauth_access_tokens",
|
|
20
|
+
/** Rotating refresh tokens — same binding fields as access tokens. */
|
|
21
|
+
refreshTokens: "oke_oauth_refresh_tokens",
|
|
22
|
+
/** Per-(user, client) consent grants — audit source for Console. */
|
|
23
|
+
consents: "oke_oauth_consents",
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
/** Metadata document fetched from a CIMD `client_id` URL (draft-ietf-oauth-client-id-metadata-document). */
|
|
27
|
+
export interface CimdClientMetadata {
|
|
28
|
+
/** Must equal the CIMD URL exactly. */
|
|
29
|
+
readonly client_id: string;
|
|
30
|
+
readonly client_name?: string;
|
|
31
|
+
/** Exact registered redirect URIs — never derived from a request. */
|
|
32
|
+
readonly redirect_uris: readonly string[];
|
|
33
|
+
readonly grant_types?: readonly string[];
|
|
34
|
+
readonly response_types?: readonly string[];
|
|
35
|
+
readonly token_endpoint_auth_method?: string;
|
|
36
|
+
/** Space-separated scope vocabulary the client may request. */
|
|
37
|
+
readonly scope?: string;
|
|
38
|
+
readonly jwks_uri?: string;
|
|
39
|
+
readonly jwks?: unknown;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Cached CIMD snapshot. */
|
|
43
|
+
export interface ClientCacheRow {
|
|
44
|
+
readonly clientId: string;
|
|
45
|
+
readonly metadata: CimdClientMetadata;
|
|
46
|
+
readonly fetchedAt: number;
|
|
47
|
+
deniedAt: number | null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** One ES256 signing key. Lifecycle fields mutate in place (rotation). */
|
|
51
|
+
export interface SigningKeyRow {
|
|
52
|
+
readonly kid: string;
|
|
53
|
+
readonly alg: "ES256";
|
|
54
|
+
readonly publicJwk: Record<string, string>;
|
|
55
|
+
readonly privateKey: CryptoKey;
|
|
56
|
+
active: boolean;
|
|
57
|
+
readonly createdAt: number;
|
|
58
|
+
rotatedAt: number | null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** One authorization code row — the code itself is never stored. */
|
|
62
|
+
export interface AuthCodeRow {
|
|
63
|
+
readonly id: string;
|
|
64
|
+
/** SHA-256 hex of the raw code. */
|
|
65
|
+
readonly codeHash: string;
|
|
66
|
+
readonly userId: string;
|
|
67
|
+
readonly clientId: string;
|
|
68
|
+
/** Exact redirect URI captured at authorize time. */
|
|
69
|
+
readonly redirectUri: string;
|
|
70
|
+
/** Canonical RFC 8707 resource URI. */
|
|
71
|
+
readonly resource: string;
|
|
72
|
+
readonly scope: readonly string[];
|
|
73
|
+
readonly codeChallenge: string;
|
|
74
|
+
readonly codeChallengeMethod: "S256";
|
|
75
|
+
/** DPoP JWK thumbprint bound at authorize time (null = no proof yet). */
|
|
76
|
+
readonly jkt: string | null;
|
|
77
|
+
readonly expiresAt: number;
|
|
78
|
+
consumedAt: number | null;
|
|
79
|
+
readonly createdAt: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** One issued access token row, keyed by `jti`. */
|
|
83
|
+
export interface AccessTokenRow {
|
|
84
|
+
readonly id: string;
|
|
85
|
+
readonly userId: string;
|
|
86
|
+
readonly clientId: string;
|
|
87
|
+
readonly resource: string;
|
|
88
|
+
readonly scope: readonly string[];
|
|
89
|
+
readonly jkt: string | null;
|
|
90
|
+
readonly expiresAt: number;
|
|
91
|
+
revokedAt: number | null;
|
|
92
|
+
readonly createdAt: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** One rotating refresh-token row (family model mirrors `oke_refresh_tokens`). */
|
|
96
|
+
export interface AsRefreshTokenRow {
|
|
97
|
+
readonly id: string;
|
|
98
|
+
readonly familyId: string;
|
|
99
|
+
readonly userId: string;
|
|
100
|
+
readonly clientId: string;
|
|
101
|
+
readonly resource: string;
|
|
102
|
+
readonly scope: readonly string[];
|
|
103
|
+
readonly jkt: string | null;
|
|
104
|
+
/** SHA-256 hex of the raw token. */
|
|
105
|
+
readonly hash: string;
|
|
106
|
+
readonly expiresAt: number;
|
|
107
|
+
usedAt: number | null;
|
|
108
|
+
revokedAt: number | null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Per-(user, client) consent — who consented to what, when.
|
|
113
|
+
* Scope merges on re-approval; revocation is in place.
|
|
114
|
+
*/
|
|
115
|
+
export interface ConsentRow {
|
|
116
|
+
readonly userId: string;
|
|
117
|
+
readonly clientId: string;
|
|
118
|
+
clientName: string | null;
|
|
119
|
+
readonly resource: string;
|
|
120
|
+
/** Cumulative granted scopes across approvals until revoked. */
|
|
121
|
+
scope: readonly string[];
|
|
122
|
+
readonly grantedAt: number;
|
|
123
|
+
updatedAt: number;
|
|
124
|
+
revokedAt: number | null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* A validated authorize request parked for the app consent screen.
|
|
129
|
+
* Created only after full validation; consumed once by approve/deny.
|
|
130
|
+
*/
|
|
131
|
+
export interface PendingAuthorizeRow {
|
|
132
|
+
readonly id: string;
|
|
133
|
+
readonly userId: string;
|
|
134
|
+
readonly clientId: string;
|
|
135
|
+
readonly clientName: string | null;
|
|
136
|
+
readonly redirectUri: string;
|
|
137
|
+
readonly resource: string;
|
|
138
|
+
readonly scope: readonly string[];
|
|
139
|
+
readonly codeChallenge: string;
|
|
140
|
+
readonly state: string | undefined;
|
|
141
|
+
readonly expiresAt: number;
|
|
142
|
+
}
|
package/src/auth/tables.ts
CHANGED
|
@@ -56,17 +56,6 @@ export interface OperatorSsoLinkRow {
|
|
|
56
56
|
subject: string;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
/** User-plane identity. */
|
|
60
|
-
export interface IdentityRow {
|
|
61
|
-
id: string;
|
|
62
|
-
provider: string;
|
|
63
|
-
subject: string;
|
|
64
|
-
email: string;
|
|
65
|
-
name: string;
|
|
66
|
-
status: "active" | "suspended";
|
|
67
|
-
lastSeenAt: number | null;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
59
|
/** Role (data, not code). */
|
|
71
60
|
export interface RoleRow {
|
|
72
61
|
id: string;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# src/bench/ — System Load Harness (15 Groups)
|
|
2
|
+
|
|
3
|
+
Reusable load-test harness modeled on `src/kernel/horizontal-child.ts` +
|
|
4
|
+
`horizontal.integration.test.ts`. One full-featured app child
|
|
5
|
+
(`load-app.ts` / `load-child.ts`) serves all bench routes; one `.bench.ts`
|
|
6
|
+
file per group emits a disclaimer-stamped JSON artifact into
|
|
7
|
+
`src/bench/results/` (gitignored).
|
|
8
|
+
|
|
9
|
+
> **Disclaimer (applies to every artifact):** these numbers are for
|
|
10
|
+
> **regression trend analysis on this hardware** — not production SLA
|
|
11
|
+
> targets. macOS/Bun/JSC behavior differs from Linux production (kernel
|
|
12
|
+
> scheduler, TCP stack, memory allocator). Use them to detect relative
|
|
13
|
+
> regressions between commits, not to size prod capacity.
|
|
14
|
+
|
|
15
|
+
## Prerequisites
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
cd examples/keel && oke dev # keep running: pg :15850, redis :16850, pgdog :24850
|
|
19
|
+
export OKE_BENCH=1
|
|
20
|
+
export OKE_TEST_POSTGRES=1
|
|
21
|
+
export DATABASE_URL=postgres://...@127.0.0.1:24850/oke # via pgdog
|
|
22
|
+
export OKE_TEST_REDIS_URL=redis://127.0.0.1:16850
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Credentials are read from the environment at runtime — never hardcode or
|
|
26
|
+
print them (`examples/keel/.env.local` is the local source).
|
|
27
|
+
|
|
28
|
+
## Env gating
|
|
29
|
+
|
|
30
|
+
Mirrors `horizontal.integration.test.ts`:
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
const LIVE_PG = process.env.OKE_TEST_POSTGRES_URL?.trim()
|
|
34
|
+
?? (process.env.OKE_TEST_POSTGRES === "1" ? process.env.DATABASE_URL : undefined);
|
|
35
|
+
const LIVE_REDIS = process.env.OKE_TEST_REDIS_URL ?? process.env.REDIS_URL;
|
|
36
|
+
describe.skipIf(!process.env.OKE_BENCH || !LIVE)(...);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Commands
|
|
40
|
+
|
|
41
|
+
| Command | What |
|
|
42
|
+
| ------------------------------------------- | ------------------------------------------------------------ |
|
|
43
|
+
| `bun run bench:load` | run every `src/bench/*.bench.ts` group (10 min timeout each) |
|
|
44
|
+
| `bun test src/bench/g01-rls-stamp.bench.ts` | run one group |
|
|
45
|
+
|
|
46
|
+
### Group execution order
|
|
47
|
+
|
|
48
|
+
Phase 0 (harness) → **G12** (doctor fd check) → G1 → G2, G4, G7, G8, G9,
|
|
49
|
+
G13 → G3 (re-verifies G12 fd estimate) → G5 → G6 → G10 → G11 → G14 → G15
|
|
50
|
+
|
|
51
|
+
- **G16** — Live query fan-out (subscribers × stamped EXISTS).
|
|
52
|
+
|
|
53
|
+
### Manual child modes
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
bun run src/bench/load-child.ts serve 6601 &
|
|
57
|
+
curl -s localhost:6601/_/ready # {"ready":true,"pid":...,"port":6601,...}
|
|
58
|
+
kill %1
|
|
59
|
+
|
|
60
|
+
bun run src/bench/load-child.ts flood-sse http://127.0.0.1:6601 bench-live 200
|
|
61
|
+
bun run src/bench/load-child.ts sustained http://127.0.0.1:6601 50 60
|
|
62
|
+
bun run src/bench/load-child.ts shutdown-test # drains on SIGTERM
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Fix protocol
|
|
66
|
+
|
|
67
|
+
Run → capture JSON + command → if issue found: minimal root-cause fix
|
|
68
|
+
(+ unit test when touching doctor/kernel/store/signal) → re-run affected
|
|
69
|
+
group(s) only; sustained runs always restart from t=0. Record
|
|
70
|
+
`measured → issue → fix → re-measured` in the artifact. If a fix is too
|
|
71
|
+
large/risky (e.g. pooling redesign): stop and report explicitly.
|
|
72
|
+
|
|
73
|
+
## Layout
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
lib/report.ts artifact writer + percentile helpers
|
|
77
|
+
lib/infra.ts live-env resolution, waitForFile, jsonl loader
|
|
78
|
+
lib/rss-sampler.ts RSS/open-fd sampler + leak slope
|
|
79
|
+
lib/event-loop-lag.ts setInterval-drift probe
|
|
80
|
+
load-app.ts the served app (all bench routes)
|
|
81
|
+
load-child.ts multi-mode entrypoint (serve / flood-sse / sustained / shutdown-test)
|
|
82
|
+
results/ JSON artifacts (gitignored)
|
|
83
|
+
```
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# OKE System-wide Load Testing — 15 Groups (Final Report)
|
|
2
|
+
|
|
3
|
+
> These numbers are for **regression trend analysis on this hardware** — not production SLA targets. macOS/Bun/JSC behavior will differ from Linux production (kernel scheduler, TCP stack, memory allocator). Use them to detect relative regressions between commits, not to size prod capacity.
|
|
4
|
+
|
|
5
|
+
- **Hardware:** Apple M4 MacBook Pro, 24 GB RAM (macOS scheduler, TCP stack, JSC allocator)
|
|
6
|
+
- **Date:** 2026-08-25 · **Engine:** Bun ≥1.3.14 · **Branch:** dev
|
|
7
|
+
- **Infra:** `examples/keel` docker stack — Postgres :15850, pgdog :24850, Redis :16850, Mailpit SMTP :20850
|
|
8
|
+
- **Harness:** `src/bench/` (committed) · per-group JSON artifacts in `src/bench/results/` (gitignored)
|
|
9
|
+
- **Run-all:** `bun run bench:load` (requires `OKE_BENCH=1` + live-infra env vars; benches are NOT in `bun run ci`)
|
|
10
|
+
|
|
11
|
+
## Fix protocol outcome (summary)
|
|
12
|
+
|
|
13
|
+
Six real product defects were found and fixed during the campaign, each recorded as measured → issue → fix → re-measured in its group's artifact:
|
|
14
|
+
|
|
15
|
+
| # | Defect | Root cause | Fix | Verified by |
|
|
16
|
+
| --- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------- |
|
|
17
|
+
| 1 | `rotateMaster` → `ERR_POSTGRES_UNSAFE_TRANSACTION` on shared pool (G7b) | manual BEGIN needs a dedicated client | `connectPostgres` honors `pool.max === 1` as opt-in dedicated client (`src/drivers/postgres.ts`) | G7b full rerun, vault suites |
|
|
18
|
+
| 2 | False "lease held by another instance" during rotation (G7b) | rotate-lease claim used `FOR UPDATE SKIP LOCKED`; audit-append contention misread as lease loss | plain `FOR UPDATE` in `src/elements/vault/storage.ts` | 5/5 green vault+driver suite runs |
|
|
19
|
+
| 3 | 25× `tuple concurrently updated` under concurrent identity bags (G8c) | RLS helper install memoized per handle → raced `CREATE OR REPLACE FUNCTION` | per-connection dedupe via `WeakMap<SqlConnection, Promise>` (`src/elements/store/sql-session.ts`) + regression test | G8c full sweep, G1 blast-radius rerun |
|
|
20
|
+
| 4 | Out-of-the-box email always fails (~3s retry then dead) (G13d) | default sender `oke@localhost` dotless → SMTP validation reject | default → `oke@localhost.test` (`src/elements/channel/runtime.ts`) + regression test | 150/150 Mailpit deliveries |
|
|
21
|
+
| 5 | All concurrent sends through one transport die ("Unexpected SMTP response for DATA end") (G13d) | shared SMTP socket wire interleaving | per-transport send serialization in `createChannelRuntime` + regression test | full send sweep from t=0 |
|
|
22
|
+
| 6 | Doctor fd estimate off by ~4,400% (G3b vs G12) | formula budgeted 64 fds/SSE subscriber; reality ≈1.5 | measured constant `FD_COST_PER_SUBSCRIBER = 1.5` exported from `src/cli/doctor-fd.ts` + linear-scaling test | re-measured Δ +6.7% |
|
|
23
|
+
|
|
24
|
+
Plus one pre-phase fix delivered by Group 12 itself: **`oke doctor file_descriptor_limit` check** (`src/cli/doctor-fd.ts`, wired into `runDoctor()` after port checks, injectable `detectFdPressure`; severity error when `softLimit < estimatedNeed`, warn when `< estimatedNeed × 2`). Doctor test suite: 15 pass / 0 fail. Live CLI: warns at `ulimit -n 256`, clean at 65536.
|
|
25
|
+
|
|
26
|
+
Harness-only bugs (no product impact, each fixed before final runs, sustained reruns restarted from t=0): thinned-sample RPS accounting (G5), tail-RSS window too small (G5), single emit ≠ M messages (G3a), fd count taken after teardown (G3c), POST vs GET `/ping` (G6), gate 429s misclassified as errors (G5), `Math.max(...arr)` overflow at ~3M samples (G5), post-SIGTERM refusals miscounted as dropped work (G14).
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## G1 — Serialized RLS queue (PGlite)
|
|
31
|
+
|
|
32
|
+
**Command:** `OKE_BENCH=1 bun test ./src/bench/g01-rls-stamp.bench.ts --timeout 120000`
|
|
33
|
+
Sweep N ∈ {10,50,100,200,500} × bind shapes {3,4 binds} on one PGlite connection behind `withRlsStampLock`.
|
|
34
|
+
|
|
35
|
+
| N | p50 (ms) | p99 (ms) | ops/s |
|
|
36
|
+
| --- | -------- | -------- | ----- |
|
|
37
|
+
| 10 | 1.9 | 4.2 | 2,803 |
|
|
38
|
+
| 50 | 6.7 | 14.5 | 3,560 |
|
|
39
|
+
| 100 | 13.3 | 27.1 | 3,703 |
|
|
40
|
+
| 200 | 27.5 | 56.2 | 3,761 |
|
|
41
|
+
| 500 | 60.2 | 128.7 | 4,026 |
|
|
42
|
+
|
|
43
|
+
Latency degrades linearly with N while throughput _rises_ (queue pipelines cleanly) — no super-linear collapse. Correctness sanity (concurrent identities don't leak across stamps): pass.
|
|
44
|
+
**Issues:** none. Re-run post-G8-fix (blast radius): same shape, confirmed unchanged. Artifact: `G1-1787650152550.json`
|
|
45
|
+
|
|
46
|
+
## G2 — Tenancy at scale (clock.perTenant)
|
|
47
|
+
|
|
48
|
+
**Command:** `OKE_BENCH=1 OKE_TEST_POSTGRES=1 DATABASE_URL=$DATABASE_URL bun test ./src/bench/g02-clock-per-tenant.bench.ts --timeout 180000`
|
|
49
|
+
120 perTenant rows seeded; concurrent lease acquisition via `CLAIM_LEASE_SQL` on live PG through pgdog.
|
|
50
|
+
|
|
51
|
+
| k | claims | p50 | p99 | claims/s | duplicate fires |
|
|
52
|
+
| --- | ------ | ------ | ------ | -------- | --------------- |
|
|
53
|
+
| 4 | 1,440 | 49 ms | 105 ms | 4,867 | **0** |
|
|
54
|
+
| 16 | 5,760 | 182 ms | 355 ms | 5,366 | **0** |
|
|
55
|
+
|
|
56
|
+
Zero duplicate fires across all 7,200 claims. Artifact: `G2-1787646500776.json`
|
|
57
|
+
|
|
58
|
+
## G3 — Signal under pressure (+ G12 fd re-verification)
|
|
59
|
+
|
|
60
|
+
**Commands:** three files, all `OKE_BENCH=1`.
|
|
61
|
+
|
|
62
|
+
- **G3a once-consumer sweep** (`g03-signal-once.bench.ts`, live PG outbox): 200 msgs × consumers {1,2,4,8,16} → 461→628 msgs/s delivery, zero loss/dupes at every level; `throughputPerSec()` scales 200→638. Note: Bun.SQL lacks LISTEN/NOTIFY — wakeup fanout is in-process (`src/bench/lib/signal-pg.ts`); cross-process wakeup untested. Artifact: `G3a-signal-once-…json`
|
|
63
|
+
- **G3b SSE memory + fd growth** (`g03-signal-sse-memory.bench.ts`, `load-child flood-sse`): 100→500 SSE subscribers sampled every 5 s. Observed **1.41 fds/subscriber**, RSS slope 0.96 MB/min (flat), peak 90 MB.
|
|
64
|
+
- **First measurement FAILED the G12 cross-check**: doctor estimated 64 fds/subscriber (Δ ≈ +4,076%). → Fix #6 above → re-measured Δ **+6.7%** ✅. Artifact carries both runs. Artifact: `G3b-signal-sse-memory-…json`
|
|
65
|
+
- Residual note: wave-2 did not reach ~500 simultaneous SSE (steady-state achieved below target); trend still valid.
|
|
66
|
+
- **G3c reconnect storm** (`g03-signal-reconnect.bench.ts`): backoff verified pure — 500 ms clamp floor, exactly 30 s ceiling; live storm: SIGKILL + restart, **200/200 clients reconnected**, observed backoffs ∈ [500, 2000] ms. Artifact: `G3c-signal-reconnect-…json`
|
|
67
|
+
|
|
68
|
+
## G4 — Identity/secrets hot path
|
|
69
|
+
|
|
70
|
+
**Command:** `OKE_BENCH=1 bun test ./src/bench/g04-auth-vault-hotpath.bench.ts`
|
|
71
|
+
|
|
72
|
+
| Probe | Throughput | p99 |
|
|
73
|
+
| --------------------------------------- | -------------------- | ------------------------------------ |
|
|
74
|
+
| `authenticateApiKey` (1k/10k/50k iters) | ~87k–102k ops/s flat | ~0.03 ms |
|
|
75
|
+
| raw HMAC verify | ~64k/s | — |
|
|
76
|
+
| boot-bag `vaultRuntime.read` | ~15.3M reads/s | ~0 |
|
|
77
|
+
| request-time `fx.vault.get` | ~3.4M reads/s | 0.001 ms |
|
|
78
|
+
| **added p99 (request-time − boot-bag)** | **0.001 ms** | well inside the <1 ms routing budget |
|
|
79
|
+
|
|
80
|
+
Artifacts: `G4-auth-…json`, `G4-vault-…json`
|
|
81
|
+
|
|
82
|
+
## G5 — Sustained baseline + memory
|
|
83
|
+
|
|
84
|
+
**Command:** `OKE_BENCH=1 bun test ./src/bench/g05-sustained-full.bench.ts --timeout 600000` (auto-extended 300→540 s per rising-RSS rule)
|
|
85
|
+
Concurrency 50 closed-loop, 30× `/ping` + 20× mixed endpoints: **3,198,945 requests, 5,924 req/s total (ping 5,506 rps, mixed 418 rps), 0 errors**, ping p99 8.1 ms, mixed p99 317 ms. Six GC spike windows annotated as JSC-GC (window-p99 median 46 ms). RSS slope 6.52 MB/min full-run but **flat tail (0.57 MB/min last minute)** → JSC heap expansion ramp, not an unbounded leak (recorded in artifact issues). Gate's 48,956 deliberate 429s counted separately, not errors. Artifact: `G5-sustained-full-…json`
|
|
86
|
+
|
|
87
|
+
## G6 — Realistic mixed load
|
|
88
|
+
|
|
89
|
+
**Command:** `OKE_BENCH=1 bun test ./src/bench/g06-mixed-load.bench.ts --timeout 300000`
|
|
90
|
+
Single child: HTTP flood + once emit/drain + live emits + 50 SSE + clock tick concurrently.
|
|
91
|
+
Ping 4,381 rps (**−20.4 % vs G5 baseline** under contention), live SSE fan-out 9,082 events/s (592k frames received), event-loop lag p99 1.38 ms (max 1.88), 5 errors in 300k+ requests (<0.001 %). No anomaly → `--cpu-prof` deep dive not triggered. Artifact: `G6-mixed-load-…json`
|
|
92
|
+
|
|
93
|
+
## G7 — Vault under load
|
|
94
|
+
|
|
95
|
+
- **G7a crypto** (`g07-vault-crypto.bench.ts`): AES-256-GCM 1 KiB encrypt/decrypt 87k/96k ops/s (round-trip 47k); 64 KiB 28.4k/25.9k (round-trip 13.6k). No issues. Artifact: `G7a-crypto-…json`
|
|
96
|
+
- **G7b rotate-under-read** (`g07-vault-rotate-under-read.bench.ts`, extends `chaos-child` with additive `bench-read-loop` mode):
|
|
97
|
+
- First attempt aborted pre-rotation: shared-pool transaction error → **Fix #1**; flaky false lease losses → **Fix #2**.
|
|
98
|
+
- Re-measured from t=0: Arm A (in-process reader across rotation): 65 reads, **0 failed, 0 torn**, reader p99 5.0 ms during a 113 ms rotation window. Arm B (foreign process holding only the old key): 3,041 reads, 0 torn, 0 silent wrong-errors; 2,987 loud unwrap failures = correct old-key physics (chaos §3b contract). Artifacts: `G7b-rotate-under-read-…json`
|
|
99
|
+
|
|
100
|
+
## G8 — Raw Store SQL
|
|
101
|
+
|
|
102
|
+
**Command:** `OKE_BENCH=1 OKE_TEST_POSTGRES=1 DATABASE_URL=$DATABASE_URL bun test ./src/bench/g08-{store-sql,store-kv-durable,conn-oversubscribe}.bench.ts --timeout 300000`
|
|
103
|
+
|
|
104
|
+
- **G8a sustained SQL:** insert ~4,100/s (p99 0.99 ms), update 3,083/s (p99 0.95 ms), mixed read+write 1,782/s. Artifact: `G8a-…json`
|
|
105
|
+
- **G8b durable KV (`openDurableKv` tenant-prefix):** set 1,728/s, get-hit 3,344/s, get-miss 3,245/s, delete 3,301/s; 2k-key `list()` in 1.8 ms. Artifact: `G8b-…json`
|
|
106
|
+
- **G8c oversubscription (sharedSqlConn, N ∈ {50,100,200}):** calibration exposed race → **Fix #3**; re-measured from t=0: graceful stamp-lock queueing — p50 40.6→96.6 ms, throughput 1,001→1,676 ops/s rising with N, **0 pool-exhaustion errors**. Blast-radius: G1 re-run green. Artifact: `G8c-…json`
|
|
107
|
+
|
|
108
|
+
## G9 — Durable journal / fx.step
|
|
109
|
+
|
|
110
|
+
**Command:** `OKE_BENCH=1 OKE_TEST_POSTGRES=1 bun test ./src/bench/g09-journal-sustained.bench.ts --timeout 420000` (300 s, concurrency 16, real postgres SKIP LOCKED path)
|
|
111
|
+
**558,053 durable runs completed; 5,580 fx.steps/s; 9,301 journal writes/s.** Run p50 7.7 ms / p99 25.6 ms; backlog avg 12.6, final 12 (= concurrency, fully drained); **0 failed runs**; steps = 3 × completed runs exactly. Artifact: `G9-…json`
|
|
112
|
+
|
|
113
|
+
## G10 — Observability overhead
|
|
114
|
+
|
|
115
|
+
**Command:** `OKE_BENCH=1 bun test ./src/bench/g10-observability-contention.bench.ts --timeout 600000` (120 s per arm)
|
|
116
|
+
Identical load twice; arm B adds concurrent console `projectSignalsList()` loops on the same pool: **−13.4 % total throughput cost** (ping −13.4 %, durable −12.9 %, store −13.3 %). Console queries sustained 3,713 q/s at p99 1.4 ms; zero errors both arms. Artifact: `G10-observability-contention-…json`
|
|
117
|
+
|
|
118
|
+
## G11 — Repeated cold starts
|
|
119
|
+
|
|
120
|
+
**Command:** `OKE_BENCH=1 bun test ./src/bench/g11-cold-start-cycle.bench.ts --timeout 300000`
|
|
121
|
+
100 fresh `Bun.spawn` cycles (measure-style probe, no shared module cache): cycles 1–10 p50 **9.43 ms** vs 91–100 p50 **8.82 ms** (drop = expected OS/FS warmup — noted in artifact, not claimed as a win); overall p50 8.88 ms / p99 20.1 ms; parent RSS +0.05 MB/cycle (+5.3 MB total) — no leak signature. Artifact: `G11-cold-start-cycle-…json`
|
|
122
|
+
|
|
123
|
+
## G12 — OS fd limits (doctor)
|
|
124
|
+
|
|
125
|
+
Delivered before any SSE work: `file_descriptor_limit` finding in `oke doctor` (see Fix protocol outcome). Unit suite 15/15; live CLI warn/clean at ulimit 256/65536. **Field-validated by G3b**: initial estimate wrong by ~4,400 % → corrected to measured 1.5 fds/subscriber → re-measured Δ +6.7 %. Artifacts: G3b JSON (both runs) + doctor tests.
|
|
126
|
+
|
|
127
|
+
## G13 — Remaining elements
|
|
128
|
+
|
|
129
|
+
**Command:** `OKE_BENCH=1 OKE_TEST_REDIS_URL=$OKE_TEST_REDIS_URL bun test ./src/bench/g13-elements.bench.ts --timeout 300000`
|
|
130
|
+
|
|
131
|
+
| Sub-bench | Result |
|
|
132
|
+
| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
133
|
+
| Gate `takeRate` @ high RPS (live redis EVAL) | 95,304 takes/s, p99 1.5 ms; exact accounting: 5,000 allowed / 5,000 denied vs bucket max |
|
|
134
|
+
| `gated()` cumulative cost | 0.64 µs/op @1k → 0.30 µs/op @50k — sub-linear, no cumulative growth |
|
|
135
|
+
| `fx.json.stream` concurrent (encodeSseStream) | 11,025 streams/s = 363,824 SSE frames/s @ concurrency 25; stream p99 5.6 ms |
|
|
136
|
+
| Channel bulk send (RetryTransport → Mailpit) | first runs dead → **Fixes #4 & #5**; re-measured from t=0: **150/150 delivered** (Mailpit delta exactly 150), 294 sends/s serialized, p99 63 ms |
|
|
137
|
+
|
|
138
|
+
Artifacts: `G13{a,b,c,d}-…json` (G13d carries both issue→fix→re-measured trails)
|
|
139
|
+
|
|
140
|
+
## G14 — Graceful shutdown under load
|
|
141
|
+
|
|
142
|
+
**Command:** `OKE_BENCH=1 OKE_TEST_POSTGRES=1 … DATABASE_URL=$DATABASE_URL bun test ./src/bench/g14-graceful-shutdown.bench.ts --timeout 120000`
|
|
143
|
+
Full `load-child serve` + `installGracefulShutdown`; 220 held SSE + 60 workers (~122k reqs, 3,026 RLS stamp writes) → frozen batch of 50 stamp writes → SIGTERM mid-handler: **50/50 in-flight drained, 0 dropped**, all SSE closed, time-to-exit ~202–219 ms. Methodology note in artifact: first calibration miscounted post-SIGTERM new-connection refusals as dropped work → replaced with frozen-batch design (harness-only change). Artifact: `G14-graceful-shutdown-…json`
|
|
144
|
+
|
|
145
|
+
## G15 — Cascading failure (slow Postgres)
|
|
146
|
+
|
|
147
|
+
**Command:** `OKE_BENCH=1 OKE_BENCH_PG_DELAY_MS=500 OKE_TEST_POSTGRES=1 DATABASE_URL=$DATABASE_URL bun test ./src/bench/g15-postgres-degradation.bench.ts --timeout 300000`
|
|
148
|
+
Test-only `delayedPostgresDriver()` wrapper sleeps before every query; live pgdog PG only.
|
|
149
|
+
|
|
150
|
+
| Arrival rate | Achieved | Max in-flight | Errors |
|
|
151
|
+
| ------------------ | ---------- | ------------------ | ------ |
|
|
152
|
+
| 10/s | 9.83 ops/s | 61 | 0 |
|
|
153
|
+
| 20/s | 19.7 ops/s | 122 | 0 |
|
|
154
|
+
| 40/s (2× overload) | 38.3 ops/s | 239 → drains to 12 | 0 |
|
|
155
|
+
|
|
156
|
+
RSS growth bounded (+15–23 MB); event-loop lag p99 ≤3 ms. Verdict: **bounded graceful backpressure** — no unbounded `withRlsStampLock` queue/OOM; Bun.SQL pool parallelism keeps service ≈ arrival even at 2× overload.
|
|
157
|
+
|
|
158
|
+
> **Honesty constraint (mandatory, also stamped in the artifact):** this is **not real chaos engineering** — it tests in-process queue/backpressure behavior when SQL is artificially stalled. It does **not** simulate network jitter, disk I/O contention, or connection pooler failure modes. Live Postgres only (G1's PGlite queue physics differ).
|
|
159
|
+
|
|
160
|
+
Artifact: `G15-postgres-degradation-…json` (+ extra stress-level artifacts)
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Verification status
|
|
165
|
+
|
|
166
|
+
- `bun run typecheck` ✅ · `bun run lint` ✅ (full repo, including keel + create-oke workspaces)
|
|
167
|
+
- Doctor suite 15/15 · channel suite 45 pass · store/vault/driver suites green after fixes
|
|
168
|
+
- Benches excluded from `bun run ci` (require Docker/live infra + long runtime); gated behind `OKE_BENCH=1`
|
|
169
|
+
- Existing `bun run bench` budget gates untouched
|
|
170
|
+
|
|
171
|
+
## Known limitations / follow-ups (trend-analysis scope)
|
|
172
|
+
|
|
173
|
+
1. Cross-process signal wakeup (LISTEN/NOTIFY substitute) untested — Bun.SQL limitation, documented in G3a artifact.
|
|
174
|
+
2. G3b wave-2 steady-state sat below the ~500-subscriber target; fd-per-subscriber trend unaffected.
|
|
175
|
+
3. Channel bulk throughput now bounded by per-transport serialization (correctness over throughput); revisit if fan-out email volume matters.
|
|
176
|
+
4. G15 is in-process backpressure only (see honesty note).
|