@cosmicdrift/kumiko-framework 0.341.0 → 0.343.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/dist/api/auth-routes.d.ts +2 -0
- package/dist/api/auth-routes.js +35 -2
- package/dist/api/request-context.d.ts +2 -0
- package/dist/api/request-id-middleware.d.ts +2 -0
- package/dist/api/request-id-middleware.js +4 -1
- package/dist/api/routes.js +5 -5
- package/dist/api/server.d.ts +3 -0
- package/dist/api/server.js +4 -1
- package/dist/compliance/duration-spec.d.ts +2 -1
- package/dist/compliance/duration-spec.js +11 -0
- package/dist/compliance/index.d.ts +2 -2
- package/dist/compliance/index.js +1 -1
- package/dist/compliance/profiles.d.ts +6 -5
- package/dist/crypto/kek-source.d.ts +3 -4
- package/dist/crypto/kek-source.js +2 -5
- package/dist/crypto/kms-wiring.d.ts +2 -2
- package/dist/crypto/kms-wiring.js +2 -2
- package/dist/db/entity-field-encryption.d.ts +1 -1
- package/dist/db/entity-field-encryption.js +5 -5
- package/dist/db/event-store-executor-write.js +38 -11
- package/dist/db/index.d.ts +2 -1
- package/dist/db/index.js +2 -1
- package/dist/db/migrate-generator.d.ts +6 -0
- package/dist/db/migrate-generator.js +15 -1
- package/dist/db/migrate-runner.d.ts +1 -0
- package/dist/db/migrate-runner.js +11 -1
- package/dist/db/projection-registry-binding.d.ts +3 -0
- package/dist/db/projection-registry-binding.js +12 -0
- package/dist/db/retired-framework-tables.d.ts +10 -0
- package/dist/db/retired-framework-tables.js +72 -0
- package/dist/db/tenant-db.d.ts +2 -0
- package/dist/db/tenant-db.js +22 -7
- package/dist/engine/boot-validator/index.d.ts +11 -0
- package/dist/engine/boot-validator/index.js +45 -1
- package/dist/engine/boot-validator/query-output-columns.js +8 -7
- package/dist/engine/boot-validator/query-refs.js +3 -0
- package/dist/engine/boot-validator/screens.js +28 -0
- package/dist/engine/index.d.ts +2 -2
- package/dist/engine/index.js +1 -1
- package/dist/engine/soft-delete-cleanup.d.ts +2 -0
- package/dist/engine/soft-delete-cleanup.js +28 -9
- package/dist/engine/types/index.d.ts +2 -2
- package/dist/engine/types/index.js +1 -1
- package/dist/env/index.d.ts +3 -0
- package/dist/env/index.js +12 -0
- package/dist/errors/docs-url.d.ts +7 -0
- package/dist/errors/docs-url.js +36 -0
- package/dist/errors/index.d.ts +2 -0
- package/dist/errors/index.js +1 -0
- package/dist/errors/kumiko-error.d.ts +0 -2
- package/dist/errors/kumiko-error.js +0 -24
- package/dist/errors/serialize.d.ts +3 -2
- package/dist/errors/serialize.js +4 -2
- package/dist/jobs/job-runner.js +37 -2
- package/dist/observability/escape-hatch-report.d.ts +10 -1
- package/dist/observability/escape-hatch-report.js +19 -1
- package/dist/observability/index.d.ts +2 -2
- package/dist/observability/index.js +2 -2
- package/dist/observability/standard-metrics.d.ts +1 -0
- package/dist/observability/standard-metrics.js +14 -0
- package/dist/pipeline/dispatch-shared.js +3 -1
- package/dist/pipeline/event-retention.d.ts +1 -0
- package/dist/pipeline/event-retention.js +6 -0
- package/dist/pipeline/index.d.ts +2 -0
- package/dist/pipeline/index.js +1 -0
- package/dist/pipeline/projections-runner.d.ts +1 -0
- package/dist/pipeline/projections-runner.js +23 -14
- package/dist/rate-limit/middleware.js +1 -1
- package/dist/schema-cli.d.ts +8 -6
- package/dist/schema-cli.js +37 -4
- package/dist/stack/test-stack.d.ts +7 -0
- package/dist/stack/test-stack.js +6 -0
- package/package.json +4 -4
- package/src/changes.json +60 -0
package/dist/schema-cli.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
|
|
11
11
|
import { join, resolve as resolvePath } from "node:path";
|
|
12
12
|
import { configureBlindIndexKey, configurePiiSubjectKms, resolveKmsWiringAsync, } from "./crypto/index.js";
|
|
13
|
-
import { assertValidMigrationName, baselineMigrations, createDbConnection, diffReplayAgainstSnapshot, fetchAppliedMigrations, generateMigration, loadMigrationsFromDir, loadSnapshotJson, rebuildTablesFromDiff, replayMigrationsDir, runMigrationsFromDir, tableExists, writeRebuildMarker, writeSnapshotJson, } from "./db/index.js";
|
|
13
|
+
import { assertValidMigrationName, baselineMigrations, createDbConnection, diffReplayAgainstSnapshot, fetchAppliedMigrations, findCommentedDropTables, generateMigration, isRetiredFrameworkTable, loadMigrationsFromDir, loadSnapshotJson, readMigrationSqlTexts, rebuildTablesFromDiff, replayMigrationsDir, runMigrationsFromDir, tableExists, writeRebuildMarker, writeSnapshotJson, } from "./db/index.js";
|
|
14
14
|
import { validateBoot } from "./engine/boot-validator.js";
|
|
15
15
|
import { createRegistry } from "./engine/registry.js";
|
|
16
16
|
import { createEventsTable } from "./event-store/index.js";
|
|
@@ -49,12 +49,40 @@ function nextSequenceNumber(migrationsDir) {
|
|
|
49
49
|
}
|
|
50
50
|
return max + 1;
|
|
51
51
|
}
|
|
52
|
+
// A table that a migration only drops as a commented DESTRUCTIVE marker and
|
|
53
|
+
// that the snapshot no longer has is gone on paper but still in the live DB.
|
|
54
|
+
async function reportLeftoverDroppedTables(db, migrationsDir, out) {
|
|
55
|
+
const snapshotPath = join(migrationsDir, SNAPSHOT_FILENAME);
|
|
56
|
+
const snapshot = existsSync(snapshotPath) ? loadSnapshotJson(snapshotPath) : null;
|
|
57
|
+
const leftovers = [];
|
|
58
|
+
for (const tableName of findCommentedDropTables(readMigrationSqlTexts(migrationsDir), snapshot)) {
|
|
59
|
+
if (await tableExists(db, tableName))
|
|
60
|
+
leftovers.push(tableName);
|
|
61
|
+
}
|
|
62
|
+
// skip: nothing left over, nothing to report
|
|
63
|
+
if (leftovers.length === 0)
|
|
64
|
+
return;
|
|
65
|
+
out.log(" Tables dropped only as a commented DESTRUCTIVE marker but still in the database:");
|
|
66
|
+
for (const tableName of leftovers) {
|
|
67
|
+
out.log(isRetiredFrameworkTable(tableName)
|
|
68
|
+
? ` ! ${tableName} — retired framework table: the next \`schema generate <name>\` creates the drop`
|
|
69
|
+
: ` ! ${tableName} — app table: after a backup, write a new migration with the drop`);
|
|
70
|
+
}
|
|
71
|
+
out.log("");
|
|
72
|
+
}
|
|
52
73
|
const SCHEMA_SUBCOMMANDS = ["generate", "validate", "apply", "baseline", "status"];
|
|
53
74
|
/**
|
|
54
75
|
* Runs a schema-CLI subcommand. `appCwd` is the app workspace root (where
|
|
55
76
|
* `kumiko/schema.ts` + `kumiko/migrations/` live). Returns a process exit code.
|
|
56
77
|
*/
|
|
57
78
|
export async function runSchemaCli(argv, appCwd, out, options = {}) {
|
|
79
|
+
// Runtime guard for untyped callers: the rebuild after `apply` needs the
|
|
80
|
+
// slots, and failing there would leave migrations applied but unrebuilt.
|
|
81
|
+
if (options.features && !options.kmsSlots) {
|
|
82
|
+
out.err("runSchemaCli: `features` requires `kmsSlots` — pass kmsSlots: kmsSlotsOf(<composedEnv>.schema), " +
|
|
83
|
+
"or [] when the app wires no Key Manager slots.");
|
|
84
|
+
return 1;
|
|
85
|
+
}
|
|
58
86
|
// runProdApp/runDevApp install this at boot; the standalone CLI (the
|
|
59
87
|
// migrate-db initContainer, `bun kumiko.js schema apply`) never goes through
|
|
60
88
|
// that boot path, so a projection rebuild's tz/timestamp coercion throws
|
|
@@ -114,10 +142,12 @@ export async function runSchemaCli(argv, appCwd, out, options = {}) {
|
|
|
114
142
|
prevSnapshot,
|
|
115
143
|
name,
|
|
116
144
|
sequenceNumber: nextSequenceNumber(migrationsDir),
|
|
145
|
+
priorMigrationsSql: readMigrationSqlTexts(migrationsDir),
|
|
117
146
|
});
|
|
118
147
|
const isEmpty = result.diff.newTables.length === 0 &&
|
|
119
148
|
result.diff.changedTables.length === 0 &&
|
|
120
|
-
result.diff.droppedTables.length === 0
|
|
149
|
+
result.diff.droppedTables.length === 0 &&
|
|
150
|
+
result.retiredDrops.length === 0;
|
|
121
151
|
if (isEmpty) {
|
|
122
152
|
out.log(" No schema changes detected — kein neues Migration-File geschrieben.");
|
|
123
153
|
return 0;
|
|
@@ -133,7 +163,7 @@ export async function runSchemaCli(argv, appCwd, out, options = {}) {
|
|
|
133
163
|
writeRebuildMarker(migrationsDir, result.filename, rebuildTables);
|
|
134
164
|
out.log("");
|
|
135
165
|
out.log(` ✓ ${result.filename}`);
|
|
136
|
-
out.log(` new tables: ${result.diff.newTables.length}, changed: ${result.diff.changedTables.length}, dropped: ${result.diff.droppedTables.length}`);
|
|
166
|
+
out.log(` new tables: ${result.diff.newTables.length}, changed: ${result.diff.changedTables.length}, dropped: ${result.diff.droppedTables.length}, retired framework tables dropped: ${result.retiredDrops.length}`);
|
|
137
167
|
if (rebuildTables.length > 0) {
|
|
138
168
|
out.log(` rebuild-marker: ${result.filename.replace(/\.sql$/, ".rebuild.json")} (${rebuildTables.length} table(s))`);
|
|
139
169
|
}
|
|
@@ -182,11 +212,13 @@ export async function runSchemaCli(argv, appCwd, out, options = {}) {
|
|
|
182
212
|
prevSnapshot,
|
|
183
213
|
name: "validate",
|
|
184
214
|
sequenceNumber: nextSequenceNumber(migrationsDir),
|
|
215
|
+
priorMigrationsSql: readMigrationSqlTexts(migrationsDir),
|
|
185
216
|
});
|
|
186
217
|
const pendingTables = [
|
|
187
218
|
...drift.diff.newTables.map((t) => t.tableName),
|
|
188
219
|
...drift.diff.changedTables.map((t) => t.tableName),
|
|
189
220
|
...drift.diff.droppedTables,
|
|
221
|
+
...drift.retiredDrops.map((t) => t.tableName),
|
|
190
222
|
];
|
|
191
223
|
if (pendingTables.length === 0) {
|
|
192
224
|
out.log(" ✓ schema: migrations match the entity definitions");
|
|
@@ -308,7 +340,7 @@ export async function runSchemaCli(argv, appCwd, out, options = {}) {
|
|
|
308
340
|
wiring = await resolveKmsWiringAsync(process.env, {
|
|
309
341
|
logPrefix: "[kumiko schema apply]",
|
|
310
342
|
log: out.log,
|
|
311
|
-
|
|
343
|
+
slots: options.kmsSlots,
|
|
312
344
|
});
|
|
313
345
|
if ("kms" in wiring) {
|
|
314
346
|
configurePiiSubjectKms(wiring.kms);
|
|
@@ -398,6 +430,7 @@ export async function runSchemaCli(argv, appCwd, out, options = {}) {
|
|
|
398
430
|
out.log("");
|
|
399
431
|
out.log(` ${applied.size} applied, ${pending} pending.`);
|
|
400
432
|
out.log("");
|
|
433
|
+
await reportLeftoverDroppedTables(db, migrationsDir, out);
|
|
401
434
|
return pending === 0 ? 0 : 1;
|
|
402
435
|
}
|
|
403
436
|
finally {
|
|
@@ -3,6 +3,7 @@ import type { AuthRoutesConfig } from "../api/auth-routes.js";
|
|
|
3
3
|
import type { JwtHelper } from "../api/jwt.js";
|
|
4
4
|
import { type SseBroker } from "../api/sse-broker.js";
|
|
5
5
|
import type { AppContext, FeatureDefinition, JobRunIn, Registry, TenantId } from "../engine/types/index.js";
|
|
6
|
+
import type { ErrorDocsConfig } from "../errors/docs-url.js";
|
|
6
7
|
import { type JobRunner, type JobRunnerOptions } from "../jobs/index.js";
|
|
7
8
|
import type { Lifecycle } from "../lifecycle/index.js";
|
|
8
9
|
import { type ObservabilityProvider } from "../observability/index.js";
|
|
@@ -159,6 +160,8 @@ export type TestStackOptions = {
|
|
|
159
160
|
/** Forwarded to buildServer's top-level `ServerOptions.trustedProxyHops`
|
|
160
161
|
* — see there. Default 0. */
|
|
161
162
|
trustedProxyHops?: number;
|
|
163
|
+
/** Links the app's own error reasons to its own docs (`docsUrl` in error responses); framework reasons keep the framework docs link. Passed to buildServer's `ServerOptions.errorDocs`. */
|
|
164
|
+
errorDocs?: ErrorDocsConfig;
|
|
162
165
|
/** Second stack on the Redis namespace of `owner`: own connection, same
|
|
163
166
|
* keyPrefix, so rate-limit buckets, locks, idempotency keys and the
|
|
164
167
|
* cache-sync channel are shared across both. The derived
|
|
@@ -172,5 +175,9 @@ export type TestStackOptions = {
|
|
|
172
175
|
* Implied by `sharedRedisWith`; set it on the owner stack too, or its writes
|
|
173
176
|
* never reach the borrower. Without it the stack uses a process-local bus. */
|
|
174
177
|
cacheSync?: boolean;
|
|
178
|
+
/** "full" runs the prod-boot validateBoot over `features` (screens and ref
|
|
179
|
+
* entities across features included). Use it with the app's complete feature
|
|
180
|
+
* composition; the default only checks what is safe on a feature subset. */
|
|
181
|
+
validateBoot?: "full";
|
|
175
182
|
};
|
|
176
183
|
export declare function setupTestStack(options: TestStackOptions): Promise<TestStack>;
|
package/dist/stack/test-stack.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildServer } from "../api/server.js";
|
|
2
2
|
import { createSseBroker } from "../api/sse-broker.js";
|
|
3
|
+
import { validateBoot, validateNavBootSubset } from "../engine/boot-validator/index.js";
|
|
3
4
|
import { validateOwnershipBoot } from "../engine/boot-validator/ownership.js";
|
|
4
5
|
import { createRegistry } from "../engine/registry.js";
|
|
5
6
|
import { createArchivedStreamsTable, createEventsTable } from "../event-store/index.js";
|
|
@@ -31,6 +32,10 @@ export async function setupTestStack(options) {
|
|
|
31
32
|
// columns) and before the ephemeral DB/Redis exist, so a throw can't leak
|
|
32
33
|
// a database that nothing will clean up.
|
|
33
34
|
validateOwnershipBoot(options.features);
|
|
35
|
+
// Nav/workspace refs fail the prod boot too; the subset check tolerates refs into unmounted features.
|
|
36
|
+
validateNavBootSubset(options.features);
|
|
37
|
+
if (options.validateBoot === "full")
|
|
38
|
+
validateBoot(options.features);
|
|
34
39
|
// Forward db-name/persistent-flag through to createTestDb. The
|
|
35
40
|
// defaults (undefined dbName, persistent:false) keep the legacy
|
|
36
41
|
// test contract: fresh kumiko_test_<random> DB per setup, dropped
|
|
@@ -280,6 +285,7 @@ export async function setupTestStack(options) {
|
|
|
280
285
|
eventDedup,
|
|
281
286
|
sseBroker,
|
|
282
287
|
...(options.trustedProxyHops !== undefined && { trustedProxyHops: options.trustedProxyHops }),
|
|
288
|
+
...(options.errorDocs && { errorDocs: options.errorDocs }),
|
|
283
289
|
...(options.extraRoutes && { extraRoutes: options.extraRoutes }),
|
|
284
290
|
...(options.metrics && { metrics: options.metrics }),
|
|
285
291
|
// Tests drive the dispatcher via stack.eventDispatcher.runOnce() for
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.343.0",
|
|
4
4
|
"description": "Framework core — engine, pipeline, API, DB, and every other bit that makes Kumiko go.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -206,8 +206,8 @@
|
|
|
206
206
|
"./package.json": "./package.json"
|
|
207
207
|
},
|
|
208
208
|
"dependencies": {
|
|
209
|
-
"@cosmicdrift/kumiko-http": "0.
|
|
210
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
209
|
+
"@cosmicdrift/kumiko-http": "0.343.0",
|
|
210
|
+
"@cosmicdrift/kumiko-types": "0.343.0",
|
|
211
211
|
"bullmq": "^5.76.7",
|
|
212
212
|
"bun-types": "^1.3.13",
|
|
213
213
|
"hono": "^4.13.1",
|
|
@@ -223,7 +223,7 @@
|
|
|
223
223
|
"zod": "^4.4.3"
|
|
224
224
|
},
|
|
225
225
|
"devDependencies": {
|
|
226
|
-
"@cosmicdrift/kumiko-dispatcher-live": "0.
|
|
226
|
+
"@cosmicdrift/kumiko-dispatcher-live": "0.343.0",
|
|
227
227
|
"bun-types": "^1.3.13",
|
|
228
228
|
"pino-pretty": "^13.1.3"
|
|
229
229
|
},
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,64 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.343.0",
|
|
4
|
+
"type": "breaking",
|
|
5
|
+
"title": "App error reasons only carry a docsUrl when errorDocs covers them",
|
|
6
|
+
"detail": "`docsUrl` in error responses is now resolved at serialization by `resolveErrorDocsUrl`. Framework reasons (and errors without a reason) link to the framework docs as before. An app's own reasons no longer link to the framework docs, where no page exists for them: they get a `docsUrl` only if the new option `errorDocs: { baseUrl, reasons: string[] | \"all\" }` (on `runProdApp`, `buildServer`, `createKumikoServer`, `setupTestStack`) covers them.\nMigration: `KumikoError.docsUrl` (the getter) is removed; use `resolveErrorDocsUrl(err, errorDocs?)`. `ErrorResponseBody.docsUrl` is now optional. Apps whose clients read `docsUrl` for app reasons pass `errorDocs` or handle the missing field.",
|
|
7
|
+
"migration": "KumikoError.docsUrl (the getter) is removed; call resolveErrorDocsUrl(err, errorDocs?) instead. ErrorResponseBody.docsUrl is optional. Apps whose clients read docsUrl for app reasons pass errorDocs or handle the missing field."
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"version": "0.343.0",
|
|
11
|
+
"type": "fix",
|
|
12
|
+
"title": "Executor writes outside the dispatcher no longer skip custom projections",
|
|
13
|
+
"detail": "Executor writes run custom projections\n`EventStoreExecutor` create, update, delete, forget and restore now run the custom projections registered for the written event, in the same savepoint as the write. The registry is bound to the `TenantDb` by the dispatcher and the job runner; a `TenantDb` built elsewhere skips custom projections. Projection runs are idempotent per event object, so the dispatcher's own pass after the handler does not run them twice.",
|
|
14
|
+
"migration": "Remove manual `runProjections` calls after executor writes in hooks or jobs together with the bump. A call is a no-op only when it passes the exact event object the executor returned; a spread or rebuilt event projects twice."
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"version": "0.343.0",
|
|
18
|
+
"type": "improvement",
|
|
19
|
+
"title": "isFailedWriteResult is exported from the pipeline entry",
|
|
20
|
+
"detail": "`isFailedWriteResult` and `FailedWriteResult` are exported from the pipeline entry\nConsumers can narrow a `WriteResult` to its failure branch with `import { isFailedWriteResult } from \"@cosmicdrift/kumiko-framework/pipeline\"` instead of re-implementing the check."
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"version": "0.343.0",
|
|
24
|
+
"type": "improvement",
|
|
25
|
+
"title": "retiredCookieDomains clears auth cookies left on a former cookieDomain",
|
|
26
|
+
"detail": "`AuthRoutesConfig.retiredCookieDomains` lists domains that used to carry the auth cookies. Login and logout also send delete headers for `kumiko_auth` and `kumiko_csrf` with `Domain=<entry>`, so a stale cookie from an earlier `cookieDomain` no longer shadows the new session after the value changes. Login sends these deletes before the new `Set-Cookie` headers. `createAuthRoutes` throws on an empty entry, on `;`, comma, whitespace, CR or LF, and on an entry equal to the current `cookieDomain`. Without the option nothing changes. Remove an entry once the session TTL has passed since the switch."
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"version": "0.343.0",
|
|
30
|
+
"type": "improvement",
|
|
31
|
+
"title": "Test stacks validate nav references and can run the full boot validation",
|
|
32
|
+
"detail": "`setupTestStack` now validates nav, workspace and tree-action references on the mounted features, with the same error messages as the prod boot. References into features that are not mounted are skipped. The new option `validateBoot: \"full\"` runs the complete `validateBoot`, including the screen and ref-entity checks that span features; give apps one boot test with their prod feature composition and this option."
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"version": "0.342.0",
|
|
36
|
+
"type": "improvement",
|
|
37
|
+
"title": "System cron escape hatches are audited once per process, with a use counter and audit retention",
|
|
38
|
+
"detail": "System crons audit their declared escape hatch once per process; audit events get a retention\nA cron job with a declared `escapeHatch` that uses `unsafeRaw()` or `db.global()` writes one `audit:event:escape-hatch-used` per process, handler and tenant instead of one per run. Manual runs by a user, identity switches, `acknowledge-cross-tenant` and `unsafe-all-tenants` keep the 60-second dedup window. Every use, audited or not, counts in the new metric `kumiko_escape_hatch_uses_total{handler,kind}`; `createEscapeHatchReporter` takes optional `processDedup`, `meter` and `now` for this. The audit feature gains a daily job `audit:job:escape-hatch-retention` that removes `escapeHatchUse` events through `pruneEvents`, with the period in the system config key `audit:config:escape-hatch-retention-days` (default 90). A new runbook, `docs/runbooks/kumiko-events-bloat.md`, shows how to measure `kumiko_events` bloat and reclaim the space.",
|
|
39
|
+
"migration": "The first run of `audit:job:escape-hatch-retention` deletes `escapeHatchUse` events older than 90 days. To keep them longer, set the system config key `audit:config:escape-hatch-retention-days` (for example through an app override) before the bump rolls out. The audit feature now also requires the `config` feature, which every app with `tenant` already mounts."
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"version": "0.342.0",
|
|
43
|
+
"type": "breaking",
|
|
44
|
+
"title": "Key Manager slots are required, LEGACY_SLOTS default removed",
|
|
45
|
+
"detail": "Key Manager slots are required: no more LEGACY_SLOTS default\n`resolvePlatformKeks`, `resolveKmsWiringAsync` and `requireKmsWiringAsync` no longer fall back to the three platform slots; `slots` is a required option. `runSchemaCli` called with `features` now requires `kmsSlots` and exits 1 with a hint before any DB connect or migration apply when it is missing. `kmsSlots: []` stays valid for apps that wire no Key Manager slots. The scaffolded `bin/kumiko.ts` passes `kmsSlots` derived from the composed env schema.",
|
|
46
|
+
"migration": "Pass `slots` (for example `kmsSlotsOf(<app>ComposedEnv.schema)`) to `resolvePlatformKeks`, `resolveKmsWiringAsync` and `requireKmsWiringAsync`. In `bin/kumiko.ts`, `runSchemaCli(..., { features })` needs `kmsSlots: kmsSlotsOf(<app>ComposedEnv.schema)` (or `[]` when the app wires no Key Manager slots. With `[]` every `*_CIPHERTEXT` env var is ignored, so an app that ships a ciphertext must pass its slots); without it the CLI exits 1 before applying migrations.\nWorkspace state at release: show-pony `bin/kumiko.ts` and its docs copy `kumiko-platform/apps/docs/_samples/show-pony/bin/kumiko.ts` still call `runSchemaCli` with `features` only. publicstatus, solon, offlot-app, money-horse, phronexsis and kumiko-studio already pass `kmsSlots` / `slots`."
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"version": "0.342.0",
|
|
50
|
+
"type": "fix",
|
|
51
|
+
"title": "Retired framework tables (read_job_runs) are dropped by the next schema generate",
|
|
52
|
+
"detail": "`kumiko schema generate` now writes an executable `DROP TABLE IF EXISTS \"read_job_runs\";` for retired framework tables (new `retiredFrameworkTables` list next to the migration code). Until now the drop stayed a commented `-- DESTRUCTIVE:` marker in every app, so the table lived on forever. A retired table is dropped only if an earlier migration created it or carries the commented drop, no earlier migration dropped it for real, and the schema no longer declares it. App tables stay commented out. `generate` writes a migration even when the retired drop is its only content, and `schema validate` reports it as pending. `kumiko schema status` now lists tables that a migration only drops as a commented marker but that still exist in the database, with a hint per case.",
|
|
53
|
+
"migration": "Run `kumiko schema generate <name>` in publicstatus, solon, money-horse, phronexsis, show-pony and kumiko-studio, review and commit the drop of read_job_runs (old job run history is not preserved), then apply."
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"version": "0.342.0",
|
|
57
|
+
"type": "fix",
|
|
58
|
+
"title": "Soft-delete cleanup works for global tables such as user",
|
|
59
|
+
"detail": "Soft-delete cleanup no longer aborts on `tenancy: \"global\"` tables\n`soft-delete:job:cleanup` aborted for a tenant as soon as an entity with `tenancy: \"global\"` and a `tenantId` column (such as `user`) had softDelete enabled, so the tenant's other entities were not cleaned either. Both cleanup jobs now decide by the entity's tenancy declaration. The per-tenant job skips global tables; `soft-delete:job:cleanup-system` hard-deletes their expired rows once, system-wide, through `db.global()`. Jobs can now receive the `globalWrites` grant from their `escapeHatch` declaration.\nThe first run of `soft-delete:job:cleanup-system` after the update hard-deletes soft-deleted users older than the fixed 30-day grace period in every app with the user feature. Earlier versions never removed them.",
|
|
60
|
+
"migration": "None required. The first nightly run of `soft-delete:job:cleanup-system` after the bump hard-deletes soft-deleted users older than 30 days; restore any you still need before it runs."
|
|
61
|
+
},
|
|
2
62
|
{
|
|
3
63
|
"version": "0.341.0",
|
|
4
64
|
"type": "improvement",
|