@cosmicdrift/kumiko-server-runtime 0.329.0 → 0.330.2
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/README.md +15 -0
- package/dist/boot/apply-boot-seeds.d.ts +8 -0
- package/{src/boot/apply-boot-seeds.ts → dist/boot/apply-boot-seeds.js} +2 -10
- package/dist/boot/boot-crypto.d.ts +9 -0
- package/dist/boot/boot-crypto.js +39 -0
- package/dist/boot/job-run-logger.d.ts +19 -0
- package/dist/boot/job-run-logger.js +44 -0
- package/dist/boot/pii-event-backfill-on-boot.d.ts +13 -0
- package/dist/boot/pii-event-backfill-on-boot.js +25 -0
- package/dist/build-prod-bundle.d.ts +103 -0
- package/dist/build-prod-bundle.js +751 -0
- package/dist/bun-serve-options.d.ts +32 -0
- package/dist/bun-serve-options.js +49 -0
- package/dist/compose-features.d.ts +31 -0
- package/dist/compose-features.js +124 -0
- package/dist/extra-routes-deps.d.ts +30 -0
- package/dist/extra-routes-deps.js +4 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +7 -0
- package/dist/pii-boot-gate.d.ts +13 -0
- package/dist/pii-boot-gate.js +46 -0
- package/dist/renderer-web-fonts.d.ts +9 -0
- package/{src/renderer-web-fonts.ts → dist/renderer-web-fonts.js} +14 -18
- package/dist/resolve-tailwind-cli.d.ts +14 -0
- package/dist/resolve-tailwind-cli.js +36 -0
- package/dist/run-prod-app-boot-context.d.ts +43 -0
- package/dist/run-prod-app-boot-context.js +198 -0
- package/dist/run-prod-app-static-files.d.ts +32 -0
- package/dist/run-prod-app-static-files.js +280 -0
- package/dist/run-prod-app.d.ts +471 -0
- package/dist/run-prod-app.js +741 -0
- package/dist/run-worker-app.d.ts +92 -0
- package/dist/run-worker-app.js +220 -0
- package/dist/security-headers.d.ts +20 -0
- package/dist/security-headers.js +49 -0
- package/dist/session-boot-gate.d.ts +11 -0
- package/dist/session-boot-gate.js +22 -0
- package/dist/session-wiring.d.ts +11 -0
- package/{src/session-wiring.ts → dist/session-wiring.js} +2 -6
- package/dist/try-hono-first.d.ts +24 -0
- package/{src/try-hono-first.ts → dist/try-hono-first.js} +8 -36
- package/package.json +102 -36
- package/src/changes.json +6 -0
- package/src/__tests__/boot-extra-context.test.ts +0 -163
- package/src/__tests__/boot-probe-fixture.ts +0 -64
- package/src/__tests__/build-prod-bundle.inprocess.test.ts +0 -233
- package/src/__tests__/build-prod-bundle.test.ts +0 -724
- package/src/__tests__/cache-headers.test.ts +0 -83
- package/src/__tests__/compose-features-mfa-wiring.integration.test.ts +0 -308
- package/src/__tests__/compose-features-wiring.integration.test.ts +0 -609
- package/src/__tests__/compose-features.test.ts +0 -206
- package/src/__tests__/config-seed-boot.integration.test.ts +0 -158
- package/src/__tests__/pii-boot-gate.test.ts +0 -112
- package/src/__tests__/renderer-web-css-relocation.integration.test.ts +0 -85
- package/src/__tests__/renderer-web-shell-sentinel.test.ts +0 -35
- package/src/__tests__/renderer-web-theme-plugin.integration.test.ts +0 -77
- package/src/__tests__/require-env.test.ts +0 -34
- package/src/__tests__/resolve-auth-mail.test.ts +0 -144
- package/src/__tests__/resolve-tailwind-cli.test.ts +0 -81
- package/src/__tests__/run-prod-app-dry-run.test.ts +0 -135
- package/src/__tests__/run-prod-app-env-source.test.ts +0 -118
- package/src/__tests__/run-prod-app-kms-slots.test.ts +0 -108
- package/src/__tests__/run-prod-app-spec.test.ts +0 -167
- package/src/__tests__/run-prod-app-static-files.test.ts +0 -298
- package/src/__tests__/run-prod-app.integration.test.ts +0 -1525
- package/src/__tests__/run-worker-app-boot.test.ts +0 -183
- package/src/__tests__/run-worker-app.integration.test.ts +0 -218
- package/src/__tests__/security-headers.test.ts +0 -88
- package/src/__tests__/session-boot-gate.test.ts +0 -70
- package/src/__tests__/session-wiring.test.ts +0 -16
- package/src/__tests__/try-hono-first.test.ts +0 -108
- package/src/__tests__/websocket-route.integration.test.ts +0 -695
- package/src/boot/__tests__/job-run-logger.integration.test.ts +0 -262
- package/src/boot/__tests__/job-run-logger.test.ts +0 -26
- package/src/boot/boot-crypto.ts +0 -67
- package/src/boot/job-run-logger.ts +0 -63
- package/src/boot/pii-event-backfill-on-boot.ts +0 -43
- package/src/build-prod-bundle.ts +0 -996
- package/src/bun-serve-options.ts +0 -110
- package/src/compose-features.ts +0 -197
- package/src/extra-routes-deps.ts +0 -38
- package/src/index.ts +0 -27
- package/src/pii-boot-gate.ts +0 -80
- package/src/resolve-tailwind-cli.ts +0 -45
- package/src/run-prod-app-boot-context.ts +0 -319
- package/src/run-prod-app-static-files.ts +0 -359
- package/src/run-prod-app.ts +0 -1420
- package/src/run-worker-app.ts +0 -366
- package/src/security-headers.ts +0 -67
- package/src/session-boot-gate.ts +0 -34
package/src/run-worker-app.ts
DELETED
|
@@ -1,366 +0,0 @@
|
|
|
1
|
-
// runWorkerApp — production-grade bootstrap wrapper for a dedicated
|
|
2
|
-
// Kumiko worker process. Symmetric to runProdApp, but without HTTP: no
|
|
3
|
-
// Hono app, no auth routes, no SSE broker, no seeds. Shares the boot core
|
|
4
|
-
// with runProdApp (env fail-fast, Temporal polyfill, composeFeatures/
|
|
5
|
-
// registry, PII invariants, KMS health gate, connections, schema-drift
|
|
6
|
-
// gate, boot-crypto, extraContext) — see fw#1725: before this function,
|
|
7
|
-
// every app deploying a worker rebuilt this boot by hand (solon#42), and
|
|
8
|
-
// every deviation was silent: without `ensureTemporalPolyfill`, every job
|
|
9
|
-
// in the worker fails with "Temporal is not defined" in a retry loop,
|
|
10
|
-
// with no boot-time signal that anything is wrong.
|
|
11
|
-
//
|
|
12
|
-
// App-author writes:
|
|
13
|
-
// await runWorkerApp({ features, wireComponents: async (deps) => {...} });
|
|
14
|
-
//
|
|
15
|
-
// Container/Coolify sets the same env vars as runProdApp:
|
|
16
|
-
// DATABASE_URL, REDIS_URL, JWT_SECRET — PORT is not needed.
|
|
17
|
-
|
|
18
|
-
import { loadJwtSecretOrKeyring, type ServerOptions } from "@cosmicdrift/kumiko-framework/api";
|
|
19
|
-
import {
|
|
20
|
-
configureBlindIndexKey,
|
|
21
|
-
configurePiiSubjectKms,
|
|
22
|
-
type KmsAdapter,
|
|
23
|
-
resolvePlatformKeks,
|
|
24
|
-
} from "@cosmicdrift/kumiko-framework/crypto";
|
|
25
|
-
import {
|
|
26
|
-
configureEntityFieldEncryption,
|
|
27
|
-
createDbConnection,
|
|
28
|
-
type DbConnection,
|
|
29
|
-
} from "@cosmicdrift/kumiko-framework/db";
|
|
30
|
-
import {
|
|
31
|
-
createRegistry,
|
|
32
|
-
type EffectiveFeaturesResolver,
|
|
33
|
-
type FeatureDefinition,
|
|
34
|
-
findTierResolverUsage,
|
|
35
|
-
type Registry,
|
|
36
|
-
type TierResolverPlugin,
|
|
37
|
-
type ValidateBootOptions,
|
|
38
|
-
validateBoot,
|
|
39
|
-
} from "@cosmicdrift/kumiko-framework/engine";
|
|
40
|
-
import {
|
|
41
|
-
createWorkerEntrypoint,
|
|
42
|
-
type WorkerEntrypoint,
|
|
43
|
-
} from "@cosmicdrift/kumiko-framework/entrypoint";
|
|
44
|
-
import type { JobRunnerOptions } from "@cosmicdrift/kumiko-framework/jobs";
|
|
45
|
-
import {
|
|
46
|
-
assertKumikoSchemaCurrent,
|
|
47
|
-
SchemaDriftError,
|
|
48
|
-
} from "@cosmicdrift/kumiko-framework/migrations";
|
|
49
|
-
import type {
|
|
50
|
-
ObservabilityOptions,
|
|
51
|
-
ObservabilityProvider,
|
|
52
|
-
} from "@cosmicdrift/kumiko-framework/observability";
|
|
53
|
-
import {
|
|
54
|
-
createEntityCache,
|
|
55
|
-
createEventDedup,
|
|
56
|
-
createIdempotencyGuard,
|
|
57
|
-
} from "@cosmicdrift/kumiko-framework/pipeline";
|
|
58
|
-
import type { MasterKeyProvider } from "@cosmicdrift/kumiko-framework/secrets";
|
|
59
|
-
import { warnIfNonUtcServerTimeZone } from "@cosmicdrift/kumiko-framework/time";
|
|
60
|
-
import Redis from "ioredis";
|
|
61
|
-
import { resolveBootCrypto } from "./boot/boot-crypto";
|
|
62
|
-
import { jobRunLoggerCallbacks } from "./boot/job-run-logger";
|
|
63
|
-
import { startPiiEventBackfillOnBoot } from "./boot/pii-event-backfill-on-boot";
|
|
64
|
-
import { composeFeatures } from "./compose-features";
|
|
65
|
-
import { makeDispatchSystemWrite, type SystemWireDeps } from "./extra-routes-deps";
|
|
66
|
-
import { assertPiiBootInvariants } from "./pii-boot-gate";
|
|
67
|
-
import { requireEnv } from "./run-prod-app";
|
|
68
|
-
import { addConfigAccessorFactory, buildBootExtraContext } from "./run-prod-app-boot-context";
|
|
69
|
-
|
|
70
|
-
export type WorkerContextOption =
|
|
71
|
-
| Record<string, unknown>
|
|
72
|
-
| ((deps: WorkerDeps) => Record<string, unknown>);
|
|
73
|
-
|
|
74
|
-
export type WorkerDeps = {
|
|
75
|
-
readonly db: DbConnection;
|
|
76
|
-
readonly redis: Redis;
|
|
77
|
-
readonly registry: Registry;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
/** Deps for the `wireComponents` hook — app-wired co-running components
|
|
81
|
-
* (an analysis runner, an IMAP supervisor, ...) that need the system-
|
|
82
|
-
* write dispatcher and register their own shutdown hooks on the worker
|
|
83
|
-
* lifecycle. Same shape as the `wire` hook's SystemWireDeps, plus
|
|
84
|
-
* `lifecycle` for `registerShutdownHook`. */
|
|
85
|
-
export type WorkerWireDeps = SystemWireDeps & {
|
|
86
|
-
readonly lifecycle: WorkerEntrypoint["lifecycle"];
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
export type RunWorkerAppOptions = {
|
|
90
|
-
/** App-specific features — same array as in the API/all-in-one process,
|
|
91
|
-
* so the registry + schema stay identical across processes. */
|
|
92
|
-
readonly features: readonly FeatureDefinition[];
|
|
93
|
-
/** Opt-in boot-validator warnings — see ValidateBootOptions. */
|
|
94
|
-
readonly validateBootOptions?: ValidateBootOptions;
|
|
95
|
-
/** Mount the auto-mixed config/user/tenant/auth-email-password features —
|
|
96
|
-
* MUST match the API process's `includeBundled` value, otherwise API
|
|
97
|
-
* and worker run with a diverging registry topology. Also controls
|
|
98
|
-
* whether buildBootExtraContext auto-wires `configResolver` (same
|
|
99
|
-
* auth-mode gate as runProdApp). */
|
|
100
|
-
readonly includeBundled?: boolean;
|
|
101
|
-
/** Path to kumiko/migrations for the boot gate. See RunProdAppOptions
|
|
102
|
-
* ["migrations"] — identical semantics. */
|
|
103
|
-
readonly migrations?: { readonly dir: string } | false;
|
|
104
|
-
/** Extra AppContext keys — same factory-union pattern as
|
|
105
|
-
* RunProdAppOptions["extraContext"], without sseBroker (the worker
|
|
106
|
-
* has none — see entrypoint/index.ts's documented SSE limitation). */
|
|
107
|
-
readonly extraContext?: WorkerContextOption;
|
|
108
|
-
/** MasterKeyProvider for ctx.secrets. Default: env-KEK (see
|
|
109
|
-
* RunProdAppOptions["masterKey"]). */
|
|
110
|
-
readonly masterKey?: MasterKeyProvider;
|
|
111
|
-
/** Subject-key adapter for crypto-shredding — boot checks health()
|
|
112
|
-
* before any connection (see RunProdAppOptions["kms"]). */
|
|
113
|
-
readonly kms?: KmsAdapter;
|
|
114
|
-
/** Blind-index key for lookupable fields (see
|
|
115
|
-
* RunProdAppOptions["blindIndexKey"]). */
|
|
116
|
-
readonly blindIndexKey?: string;
|
|
117
|
-
/** Explicit opt-out from the PII boot gate (see
|
|
118
|
-
* RunProdAppOptions["allowPlaintextPii"]). */
|
|
119
|
-
readonly allowPlaintextPii?: string;
|
|
120
|
-
readonly jobs?: {
|
|
121
|
-
readonly queueNamePrefix?: string;
|
|
122
|
-
/** Source of active tenant ids for `perTenant: true` jobs (see
|
|
123
|
-
* RunProdAppOptions["jobs"]["getActiveTenantIds"]) — identical
|
|
124
|
-
* semantics, framework falls back to the tenant feature's own
|
|
125
|
-
* active-tenant-ids query when omitted. */
|
|
126
|
-
readonly getActiveTenantIds?: JobRunnerOptions["getActiveTenantIds"];
|
|
127
|
-
};
|
|
128
|
-
/** Tuning knobs for the event-dispatcher loop (pollIntervalMs, pgClient
|
|
129
|
-
* for LISTEN/NOTIFY). */
|
|
130
|
-
readonly eventDispatcher?: ServerOptions["eventDispatcher"];
|
|
131
|
-
/** Hook for app-wired co-running components that need the system-write
|
|
132
|
-
* dispatcher (e.g. an analysis runner, an IMAP supervisor). Runs AFTER
|
|
133
|
-
* `entrypoint.start()` — the hook itself is responsible for starting
|
|
134
|
-
* its component and registering a shutdown hook on `lifecycle`. */
|
|
135
|
-
readonly wireComponents?: (deps: WorkerWireDeps) => Promise<void> | void;
|
|
136
|
-
/** Feature-toggle resolver (see RunProdAppOptions["effectiveFeatures"]). */
|
|
137
|
-
readonly effectiveFeatures?: EffectiveFeaturesResolver;
|
|
138
|
-
/** Override `process.env` for env-validation (see
|
|
139
|
-
* RunProdAppOptions["envSource"]). */
|
|
140
|
-
readonly envSource?: Record<string, string | undefined>;
|
|
141
|
-
/** Env names to decrypt from their `_CIPHERTEXT` twin at boot, typically
|
|
142
|
-
* `kmsSlotsOf(composedEnv.schema)`. The worker parses no env schema, so it
|
|
143
|
-
* cannot derive them; without this it reads the env as is. */
|
|
144
|
-
readonly kmsSlots?: readonly string[];
|
|
145
|
-
readonly observability?: ObservabilityProvider;
|
|
146
|
-
readonly observabilityOptions?: ObservabilityOptions;
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
export type WorkerAppHandle = {
|
|
150
|
-
/** In KUMIKO_DRY_RUN_ENV=boot mode with an injected envSource (test
|
|
151
|
-
* path), no boot ran — this slot is an undefined-cast, do not access. */
|
|
152
|
-
readonly entrypoint: WorkerEntrypoint;
|
|
153
|
-
readonly stop: () => Promise<void>;
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
function makeBootModeHandle(): WorkerAppHandle {
|
|
157
|
-
return {
|
|
158
|
-
// @cast-boundary boot-mode: no entrypoint exists because no boot ran —
|
|
159
|
-
// callers on this path never read this slot.
|
|
160
|
-
entrypoint: undefined as unknown as WorkerEntrypoint,
|
|
161
|
-
stop: async () => {},
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export async function runWorkerApp(options: RunWorkerAppOptions): Promise<WorkerAppHandle> {
|
|
166
|
-
const rawEnvSource = options.envSource ?? process.env;
|
|
167
|
-
const envSource: Record<string, string | undefined> = options.kmsSlots
|
|
168
|
-
? await resolvePlatformKeks(rawEnvSource, {
|
|
169
|
-
slots: options.kmsSlots,
|
|
170
|
-
logPrefix: "[runWorkerApp]",
|
|
171
|
-
})
|
|
172
|
-
: rawEnvSource;
|
|
173
|
-
|
|
174
|
-
// 1. Polyfill before anything else — exactly the bug fw#1725 reports:
|
|
175
|
-
// without it, every job in the worker fails with "Temporal is not
|
|
176
|
-
// defined" in a retry loop, with no boot-time signal.
|
|
177
|
-
const { ensureTemporalPolyfill } = await import("@cosmicdrift/kumiko-framework/time");
|
|
178
|
-
await ensureTemporalPolyfill();
|
|
179
|
-
|
|
180
|
-
// 2. Env vars: fail-fast, same requireEnv as runProdApp. JWT_SECRET is
|
|
181
|
-
// validated by loadJwtSecretOrKeyring itself (throws when neither
|
|
182
|
-
// JWT_SECRET nor JWT_SECRET_V<n> is set).
|
|
183
|
-
const databaseUrl = requireEnv("DATABASE_URL", envSource, "runWorkerApp");
|
|
184
|
-
const redisUrl = requireEnv("REDIS_URL", envSource, "runWorkerApp");
|
|
185
|
-
const jwtSecretOrKeyring = loadJwtSecretOrKeyring(envSource);
|
|
186
|
-
|
|
187
|
-
// biome-ignore lint/suspicious/noConsole: boot-time progress hint, no logger configured this early
|
|
188
|
-
console.log("[runWorkerApp] booting Kumiko worker…");
|
|
189
|
-
|
|
190
|
-
// 3. Feature registry — identical to runProdApp, MUST be built with the
|
|
191
|
-
// same `includeBundled` as the API process.
|
|
192
|
-
const features = composeFeatures(options.features, {
|
|
193
|
-
includeBundled: !!options.includeBundled,
|
|
194
|
-
});
|
|
195
|
-
validateBoot(features, { env: envSource, ...options.validateBootOptions });
|
|
196
|
-
warnIfNonUtcServerTimeZone();
|
|
197
|
-
assertPiiBootInvariants(features, {
|
|
198
|
-
kms: options.kms,
|
|
199
|
-
blindIndexKey: options.blindIndexKey,
|
|
200
|
-
allowPlaintextPii: options.allowPlaintextPii,
|
|
201
|
-
mode: "prod",
|
|
202
|
-
});
|
|
203
|
-
const registry = createRegistry(features);
|
|
204
|
-
|
|
205
|
-
// Boot-mode exit (parity with runProdApp's C1): validators ran and the
|
|
206
|
-
// registry is built, no DB/Redis client was constructed. Makes the boot
|
|
207
|
-
// testable without real Postgres/Redis infra.
|
|
208
|
-
if (envSource["KUMIKO_DRY_RUN_ENV"] === "boot") {
|
|
209
|
-
// biome-ignore lint/suspicious/noConsole: boot-mode output IS the deliverable
|
|
210
|
-
console.log(
|
|
211
|
-
`[runWorkerApp] boot validation OK (${features.length} features, ${registry.features.size} registry entries)`,
|
|
212
|
-
);
|
|
213
|
-
if (options.envSource === undefined) {
|
|
214
|
-
process.exit(0);
|
|
215
|
-
}
|
|
216
|
-
return makeBootModeHandle();
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// 4. KMS health gate — runs BEFORE the connections, so an abort leaks
|
|
220
|
-
// nothing (identical to runProdApp).
|
|
221
|
-
if (options.kms) {
|
|
222
|
-
const kmsHealth = await options.kms.health();
|
|
223
|
-
if (!kmsHealth.ok) {
|
|
224
|
-
throw new Error(
|
|
225
|
-
`[runWorkerApp] BOOT ABORTED — KMS health check failed (latency ${kmsHealth.latencyMs}ms)`,
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const { db, close: closeDb } = createDbConnection(databaseUrl);
|
|
231
|
-
const redis = new Redis(redisUrl, { maxRetriesPerRequest: null });
|
|
232
|
-
|
|
233
|
-
let resolvedEffectiveFeatures: EffectiveFeaturesResolver | undefined = options.effectiveFeatures;
|
|
234
|
-
if (resolvedEffectiveFeatures === undefined) {
|
|
235
|
-
const tierResolverUsage = findTierResolverUsage(features);
|
|
236
|
-
if (tierResolverUsage) {
|
|
237
|
-
const plugin = tierResolverUsage.options as TierResolverPlugin;
|
|
238
|
-
resolvedEffectiveFeatures = await plugin.build({ db, registry });
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// 5. Schema-drift gate — identical to runProdApp.
|
|
243
|
-
if (options.migrations !== false) {
|
|
244
|
-
const migrationsDir = options.migrations?.dir ?? "./kumiko/migrations";
|
|
245
|
-
// biome-ignore lint/suspicious/noConsole: boot-time progress hint
|
|
246
|
-
console.log(`[runWorkerApp] checking schema drift (${migrationsDir})…`);
|
|
247
|
-
try {
|
|
248
|
-
await assertKumikoSchemaCurrent(db, migrationsDir);
|
|
249
|
-
} catch (err) {
|
|
250
|
-
if (err instanceof SchemaDriftError) {
|
|
251
|
-
// biome-ignore lint/suspicious/noConsole: terminal error message
|
|
252
|
-
console.error(`\n[runWorkerApp] BOOT ABORTED — ${err.message}\n`);
|
|
253
|
-
}
|
|
254
|
-
throw err;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// 6. Pipeline pieces — same defaults as runProdApp.
|
|
259
|
-
const idempotency = createIdempotencyGuard(redis, { ttlSeconds: 60 });
|
|
260
|
-
const eventDedup = createEventDedup(redis, { ttlSeconds: 60 });
|
|
261
|
-
const entityCache = createEntityCache(redis, { ttlSeconds: 60 });
|
|
262
|
-
|
|
263
|
-
// 7. Boot-crypto + extraContext — the core of fw#1725: KMS wiring,
|
|
264
|
-
// blind-index, config-encryption must be wired in the worker exactly
|
|
265
|
-
// like in the API, otherwise the worker writes rows the API can no
|
|
266
|
-
// longer read (different cipher, missing blind index).
|
|
267
|
-
const deps: WorkerDeps = { db, redis, registry };
|
|
268
|
-
const resolvedExtraContext =
|
|
269
|
-
typeof options.extraContext === "function"
|
|
270
|
-
? options.extraContext(deps)
|
|
271
|
-
: (options.extraContext ?? {});
|
|
272
|
-
|
|
273
|
-
const bootCrypto = resolveBootCrypto(envSource, options.masterKey);
|
|
274
|
-
configureEntityFieldEncryption(bootCrypto.entityFieldCipher);
|
|
275
|
-
configurePiiSubjectKms(options.kms);
|
|
276
|
-
configureBlindIndexKey(options.blindIndexKey);
|
|
277
|
-
const autoExtraContext = buildBootExtraContext({
|
|
278
|
-
db,
|
|
279
|
-
features,
|
|
280
|
-
envSource,
|
|
281
|
-
registry,
|
|
282
|
-
hasAuth: !!options.includeBundled,
|
|
283
|
-
crypto: bootCrypto,
|
|
284
|
-
...(options.kms && { kms: options.kms }),
|
|
285
|
-
});
|
|
286
|
-
const extraContext = addConfigAccessorFactory(
|
|
287
|
-
{ ...autoExtraContext, ...resolvedExtraContext },
|
|
288
|
-
registry,
|
|
289
|
-
);
|
|
290
|
-
|
|
291
|
-
const jobLogger = jobRunLoggerCallbacks(registry, db);
|
|
292
|
-
const entrypoint = createWorkerEntrypoint({
|
|
293
|
-
registry,
|
|
294
|
-
context: { db, redis, entityCache, registry, ...extraContext },
|
|
295
|
-
jwtSecret: jwtSecretOrKeyring,
|
|
296
|
-
dispatcherOptions: {
|
|
297
|
-
idempotency,
|
|
298
|
-
...(resolvedEffectiveFeatures && { effectiveFeatures: resolvedEffectiveFeatures }),
|
|
299
|
-
},
|
|
300
|
-
eventDedup,
|
|
301
|
-
...(options.observability && { observability: options.observability }),
|
|
302
|
-
...(options.observabilityOptions && { observabilityOptions: options.observabilityOptions }),
|
|
303
|
-
redisUrl,
|
|
304
|
-
...jobLogger,
|
|
305
|
-
...(options.jobs?.queueNamePrefix !== undefined && {
|
|
306
|
-
queueNamePrefix: options.jobs.queueNamePrefix,
|
|
307
|
-
}),
|
|
308
|
-
...(options.jobs?.getActiveTenantIds !== undefined && {
|
|
309
|
-
getActiveTenantIds: options.jobs.getActiveTenantIds,
|
|
310
|
-
}),
|
|
311
|
-
...(options.eventDispatcher && { eventDispatcher: options.eventDispatcher }),
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
const handle: WorkerAppHandle = {
|
|
315
|
-
entrypoint,
|
|
316
|
-
stop: async () => {
|
|
317
|
-
await entrypoint.stop();
|
|
318
|
-
await closeDb();
|
|
319
|
-
redis.disconnect();
|
|
320
|
-
},
|
|
321
|
-
};
|
|
322
|
-
|
|
323
|
-
await entrypoint.start();
|
|
324
|
-
startPiiEventBackfillOnBoot({
|
|
325
|
-
db,
|
|
326
|
-
registry,
|
|
327
|
-
lifecycle: entrypoint.lifecycle,
|
|
328
|
-
envSource,
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
if (options.wireComponents) {
|
|
332
|
-
await options.wireComponents({
|
|
333
|
-
db,
|
|
334
|
-
redis,
|
|
335
|
-
registry,
|
|
336
|
-
dispatchSystemWrite: makeDispatchSystemWrite(entrypoint.dispatcher),
|
|
337
|
-
lifecycle: entrypoint.lifecycle,
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
let shuttingDown = false;
|
|
342
|
-
const shutdown = async (signal: string) => {
|
|
343
|
-
// skip: shutdown already in progress, avoid double-drain
|
|
344
|
-
if (shuttingDown) return;
|
|
345
|
-
shuttingDown = true;
|
|
346
|
-
// biome-ignore lint/suspicious/noConsole: boot-time progress hint, no logger configured this early
|
|
347
|
-
console.log(`[runWorkerApp] ${signal} received — draining…`);
|
|
348
|
-
try {
|
|
349
|
-
await handle.stop();
|
|
350
|
-
// biome-ignore lint/suspicious/noConsole: boot-time progress hint, no logger configured this early
|
|
351
|
-
console.log("[runWorkerApp] graceful shutdown complete.");
|
|
352
|
-
} catch (e) {
|
|
353
|
-
// biome-ignore lint/suspicious/noConsole: shutdown-time error, only path is stderr
|
|
354
|
-
console.error("[runWorkerApp] error during shutdown:", e);
|
|
355
|
-
} finally {
|
|
356
|
-
process.exit(0);
|
|
357
|
-
}
|
|
358
|
-
};
|
|
359
|
-
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
|
360
|
-
process.on("SIGINT", () => void shutdown("SIGINT"));
|
|
361
|
-
|
|
362
|
-
// biome-ignore lint/suspicious/noConsole: boot-time progress hint, no logger configured this early
|
|
363
|
-
console.log("[runWorkerApp] ready — worker running.");
|
|
364
|
-
|
|
365
|
-
return handle;
|
|
366
|
-
}
|
package/src/security-headers.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
export type SecurityHeadersOption =
|
|
2
|
-
| false
|
|
3
|
-
| {
|
|
4
|
-
/** `Strict-Transport-Security` value, or `false` to omit.
|
|
5
|
-
* Default: `max-age=31536000; includeSubDomains`. */
|
|
6
|
-
readonly hsts?: string | false;
|
|
7
|
-
/** `X-Frame-Options` value, or `false` to omit. Default: `DENY`.
|
|
8
|
-
* Apps that must be embeddable (iframe widgets) set `false` and
|
|
9
|
-
* scope framing via a `csp` frame-ancestors directive instead. */
|
|
10
|
-
readonly frameOptions?: string | false;
|
|
11
|
-
/** `false` omits `X-Content-Type-Options: nosniff`. */
|
|
12
|
-
readonly contentTypeOptions?: false;
|
|
13
|
-
/** `Referrer-Policy` value, or `false` to omit.
|
|
14
|
-
* Default: `strict-origin-when-cross-origin`. */
|
|
15
|
-
readonly referrerPolicy?: string | false;
|
|
16
|
-
/** `Content-Security-Policy` default for ALL responses. No built-in
|
|
17
|
-
* default — a wrong CSP breaks app assets, so it stays opt-in.
|
|
18
|
-
* A per-host CSP from `hostDispatch` wins over this value. */
|
|
19
|
-
readonly csp?: string;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const DEFAULT_HSTS = "max-age=31536000; includeSubDomains";
|
|
23
|
-
const DEFAULT_FRAME_OPTIONS = "DENY";
|
|
24
|
-
const DEFAULT_REFERRER_POLICY = "strict-origin-when-cross-origin";
|
|
25
|
-
|
|
26
|
-
export function resolveSecurityHeaders(
|
|
27
|
-
option: SecurityHeadersOption | undefined,
|
|
28
|
-
): ReadonlyArray<readonly [string, string]> {
|
|
29
|
-
if (option === false) return [];
|
|
30
|
-
const opt = option ?? {};
|
|
31
|
-
const headers: Array<readonly [string, string]> = [];
|
|
32
|
-
const hsts = opt.hsts ?? DEFAULT_HSTS;
|
|
33
|
-
if (hsts !== false) headers.push(["strict-transport-security", hsts]);
|
|
34
|
-
const frameOptions = opt.frameOptions ?? DEFAULT_FRAME_OPTIONS;
|
|
35
|
-
if (frameOptions !== false) headers.push(["x-frame-options", frameOptions]);
|
|
36
|
-
if (opt.contentTypeOptions !== false) headers.push(["x-content-type-options", "nosniff"]);
|
|
37
|
-
const referrerPolicy = opt.referrerPolicy ?? DEFAULT_REFERRER_POLICY;
|
|
38
|
-
if (referrerPolicy !== false) headers.push(["referrer-policy", referrerPolicy]);
|
|
39
|
-
if (opt.csp) headers.push(["content-security-policy", opt.csp]);
|
|
40
|
-
return headers;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Sets each header only when absent so per-response values (e.g. the
|
|
44
|
-
// per-host CSP from hostDispatch) always win over the runtime default.
|
|
45
|
-
export function withSecurityHeaders<TServeArg = string>(
|
|
46
|
-
handler: (req: Request, serveArg?: TServeArg) => Response | Promise<Response>,
|
|
47
|
-
option: SecurityHeadersOption | undefined,
|
|
48
|
-
): (req: Request, serveArg?: TServeArg) => Response | Promise<Response> {
|
|
49
|
-
const defaults = resolveSecurityHeaders(option);
|
|
50
|
-
if (defaults.length === 0) return handler;
|
|
51
|
-
return async (req: Request, serveArg?: TServeArg): Promise<Response> => {
|
|
52
|
-
const res = await handler(req, serveArg);
|
|
53
|
-
try {
|
|
54
|
-
for (const [name, value] of defaults) {
|
|
55
|
-
if (!res.headers.has(name)) res.headers.set(name, value);
|
|
56
|
-
}
|
|
57
|
-
return res;
|
|
58
|
-
} catch {
|
|
59
|
-
// Immutable-headers Response (e.g. proxied fetch) — re-wrap.
|
|
60
|
-
const headers = new Headers(res.headers);
|
|
61
|
-
for (const [name, value] of defaults) {
|
|
62
|
-
if (!headers.has(name)) headers.set(name, value);
|
|
63
|
-
}
|
|
64
|
-
return new Response(res.body, { status: res.status, statusText: res.statusText, headers });
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
}
|
package/src/session-boot-gate.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
export type SessionBootGateOptions = {
|
|
2
|
-
readonly hasAuth: boolean;
|
|
3
|
-
readonly sessionStoreProviderMounted: boolean;
|
|
4
|
-
/** prod fails hard on a missing sessionStore provider (stateless JWTs would
|
|
5
|
-
* silently skip server-side revocation and any session-list UI stays
|
|
6
|
-
* empty); dev only warns — a forgotten sessions mount in a local sample
|
|
7
|
-
* app shouldn't crash the dev-server boot, but it must not stay silent
|
|
8
|
-
* either (#2027 — dev previously had no gate here at all). */
|
|
9
|
-
readonly mode: "prod" | "dev";
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
// Catch a forgotten sessions mount at boot instead of silently degrading
|
|
13
|
-
// into stateless JWTs (#1372). Mount createSessionsFeature() for revocable
|
|
14
|
-
// sessions; there is no auth.sessions opt-out anymore.
|
|
15
|
-
export function assertSessionBootInvariants(opts: SessionBootGateOptions): void {
|
|
16
|
-
// skip: no auth mounted — nothing to gate.
|
|
17
|
-
if (!opts.hasAuth) return;
|
|
18
|
-
// skip: sessionStore provider is wired (sessions feature).
|
|
19
|
-
if (opts.sessionStoreProviderMounted) return;
|
|
20
|
-
|
|
21
|
-
const tag = opts.mode === "prod" ? "runProdApp" : "runDevApp";
|
|
22
|
-
const message =
|
|
23
|
-
"auth is mounted but no sessionStore provider is registered. JWTs would be stateless " +
|
|
24
|
-
"(no server-side revocation) and any session-list screen stays empty. Mount " +
|
|
25
|
-
"createSessionsFeature() (@cosmicdrift/kumiko-bundled-features/sessions) alongside auth-foundation.";
|
|
26
|
-
|
|
27
|
-
if (opts.mode === "dev") {
|
|
28
|
-
// biome-ignore lint/suspicious/noConsole: boot-time ops warning, no logger configured this early
|
|
29
|
-
console.warn(`[${tag}] ${message}`);
|
|
30
|
-
// skip: dev mode, warning already logged above — never abort dev boot.
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
throw new Error(`[${tag}] BOOT ABORTED — ${message}`);
|
|
34
|
-
}
|