@cosmicdrift/kumiko-framework 0.336.0 → 0.336.1
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/routes.js +1 -1
- package/dist/crypto/event-pii.js +3 -3
- package/dist/engine/boot-validator/access-declarations.js +2 -1
- package/dist/engine/build-app-schema.js +2 -1
- package/dist/engine/define-handler.js +3 -5
- package/dist/migrations/pending-rebuilds.js +4 -3
- package/dist/pipeline/dispatch-batch.js +2 -2
- package/dist/pipeline/dispatcher.js +1 -1
- package/package.json +4 -4
- package/src/changes.json +6 -0
- package/src/scripts/codemod/pii-personal-migration.ts +11 -21
package/dist/api/routes.js
CHANGED
|
@@ -301,7 +301,7 @@ function clientFaultLoggingEnabled() {
|
|
|
301
301
|
return !FAULT_LOG_SILENCED_LEVELS.has(process.env["LOG_LEVEL"] ?? "");
|
|
302
302
|
}
|
|
303
303
|
// A failing request must leave a trace even when it ends in 4xx — a paid
|
|
304
|
-
// external call that 422s was invisible before
|
|
304
|
+
// external call that 422s was invisible before. Status, error
|
|
305
305
|
// code and duration only: message/details/stack can carry submitted values.
|
|
306
306
|
function logClientWarn(message, requestId, type, fields) {
|
|
307
307
|
if (!clientFaultLoggingEnabled()) {
|
package/dist/crypto/event-pii.js
CHANGED
|
@@ -45,9 +45,9 @@ function resolvePiiStance(eventType) {
|
|
|
45
45
|
// (user/tenant/self — resolveEventSubject, fw#2801). No-op when the event
|
|
46
46
|
// type is uncatalogued or no subject KMS is configured (plaintext rollout
|
|
47
47
|
// mode — the hard boot gate governs whether that is acceptable). A
|
|
48
|
-
// user-subject field with
|
|
49
|
-
//
|
|
50
|
-
//
|
|
48
|
+
// user-subject field with an empty owner follows its declared `whenAbsent`
|
|
49
|
+
// (tenant key or acknowledged plaintext) and aborts the append when none is
|
|
50
|
+
// declared. This is the ONLY live-write encrypt path — the backfill
|
|
51
51
|
// catalog branch (`backfill-pii.ts`) resolves through the same
|
|
52
52
|
// resolveEventSubject so a field never ends up encrypted under different
|
|
53
53
|
// subjects depending on which path wrote it.
|
|
@@ -53,7 +53,8 @@ function validateEscapeHatchReason(feature, kind, handlerName, escapeHatch) {
|
|
|
53
53
|
return;
|
|
54
54
|
throw new Error(`[Feature ${feature.name}] ${kind} handler "${handlerName}" declares ` +
|
|
55
55
|
`{ escapeHatch: { reason: "" } } — the reason must be a non-empty string ` +
|
|
56
|
-
"explaining why this handler needs db.global() write access
|
|
56
|
+
"explaining why this handler needs db.global() write access, a SYSTEM identity switch " +
|
|
57
|
+
"or a cross-tenant ctx.queryProjection({ unsafeAllTenants: true }) read.");
|
|
57
58
|
}
|
|
58
59
|
function validatePersonalDataOnlyOnWrite(feature, kind, handlerName, access) {
|
|
59
60
|
// skip: write handlers may declare personalData, or it wasn't declared here
|
|
@@ -432,7 +432,8 @@ function projectField(fieldDef) {
|
|
|
432
432
|
out["multiple"] = def["multiple"];
|
|
433
433
|
// MultiSelect: display picks checkboxes vs. combobox in the renderer,
|
|
434
434
|
// columns/maxRows size the checkbox grid — without these the renderer
|
|
435
|
-
// always falls back to the combobox (fw#2494).
|
|
435
|
+
// always falls back to the combobox (fw#2494). Select: display picks
|
|
436
|
+
// "radio" | "dropdown" the same way.
|
|
436
437
|
if (typeof def["display"] === "string")
|
|
437
438
|
out["display"] = def["display"];
|
|
438
439
|
if (typeof def["columns"] === "number")
|
|
@@ -70,11 +70,9 @@ export function defineQueryHandler(def,
|
|
|
70
70
|
return def;
|
|
71
71
|
}
|
|
72
72
|
// Runtime marker set only by definePagedQueryHandler. A plain
|
|
73
|
-
// defineQueryHandler-built object never carries it.
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
// PagedRows" from "doesn't" without running the handler; the actual
|
|
77
|
-
// enforcement is a runtime shape guard in the renderer (kumiko-screen.tsx).
|
|
73
|
+
// defineQueryHandler-built object never carries it. query-output-columns.ts
|
|
74
|
+
// reads it to gate the paged envelope check on outputSchema; the renderer's
|
|
75
|
+
// runtime shape guard (kumiko-screen.tsx) remains as a second line.
|
|
78
76
|
//
|
|
79
77
|
// A string key, not a Symbol: bundled-features imports this module through
|
|
80
78
|
// the package's "@cosmicdrift/kumiko-framework/engine" subpath (symlinked
|
|
@@ -65,9 +65,10 @@ export async function clearPendingRebuilds(db, rows) {
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
// buildProjectionTableIndex maps both single-stream and multi-stream projection
|
|
68
|
-
// tables to their name, so the rebuild has to pick the matching path.
|
|
69
|
-
// drains
|
|
70
|
-
//
|
|
68
|
+
// tables to their name, so the rebuild has to pick the matching path. Callers
|
|
69
|
+
// (queue drains, inline enqueue, the manual rebuild job) all rethrow the error,
|
|
70
|
+
// so it is recorded there; a failed MSP replay rolls back and must not park
|
|
71
|
+
// the still-intact live consumer as dead.
|
|
71
72
|
export async function rebuildProjectionOrMultiStream(name, deps) {
|
|
72
73
|
const { db, registry, signal, errorPolicy } = deps;
|
|
73
74
|
if (registry.getAllMultiStreamProjections().has(name)) {
|
|
@@ -24,8 +24,8 @@ export async function runBatch(ctx, commands, user, requestId, inheritedOrigin)
|
|
|
24
24
|
if (!current?.signal) {
|
|
25
25
|
return runBatchBody(ctx, commands, user, requestId, inheritedOrigin);
|
|
26
26
|
}
|
|
27
|
-
// Strip the signal: a disconnect
|
|
28
|
-
//
|
|
27
|
+
// Strip the signal: a client disconnect must not roll back a write whose
|
|
28
|
+
// afterCommit effects would then be lost.
|
|
29
29
|
const { signal: _signal, ...withoutSignal } = current;
|
|
30
30
|
return requestContext.run(withoutSignal, () => runBatchBody(ctx, commands, user, requestId, inheritedOrigin));
|
|
31
31
|
}
|
|
@@ -67,7 +67,7 @@ export function createDispatcher(registry, context, options = {}) {
|
|
|
67
67
|
if (isDeprecatedCrossTenantHandler(def.handler)) {
|
|
68
68
|
context.log?.warn(DEPRECATED_CROSS_TENANT_SIGNAL, {
|
|
69
69
|
handler: def.name,
|
|
70
|
-
migration: 'replace crossTenant: true with escapeHatch: { reason: "<why this operator handler reads/writes every tenant>" } (bun scripts/codemod/migrate-cross-tenant.ts)',
|
|
70
|
+
migration: 'replace crossTenant: true with escapeHatch: { reason: "<why this operator handler reads/writes every tenant>" } (bun node_modules/@cosmicdrift/kumiko-framework/src/scripts/codemod/migrate-cross-tenant.ts)',
|
|
71
71
|
});
|
|
72
72
|
}
|
|
73
73
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-framework",
|
|
3
|
-
"version": "0.336.
|
|
3
|
+
"version": "0.336.1",
|
|
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.336.
|
|
210
|
-
"@cosmicdrift/kumiko-types": "0.336.
|
|
209
|
+
"@cosmicdrift/kumiko-http": "0.336.1",
|
|
210
|
+
"@cosmicdrift/kumiko-types": "0.336.1",
|
|
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.336.
|
|
226
|
+
"@cosmicdrift/kumiko-dispatcher-live": "0.336.1",
|
|
227
227
|
"bun-types": "^1.3.13",
|
|
228
228
|
"pino-pretty": "^13.1.3"
|
|
229
229
|
},
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.336.1",
|
|
4
|
+
"type": "fix",
|
|
5
|
+
"title": "Cross-tenant deprecation warning names a runnable codemod path",
|
|
6
|
+
"detail": "The `deprecation:entity-handler-cross-tenant` boot warning now names the codemod path that runs from a consumer repo. The `reason` strings on the `passwordHash`, PAT `tokenHash` and PAT `prefix` fields no longer carry an issue-number suffix. The `pii-personal-migration` codemod now skips `.d.ts` files in `--report-stance` mode as well."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.336.0",
|
|
4
10
|
"type": "fix",
|
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
// packages/types/src/fields.ts). Never guesses: anything outside this
|
|
22
22
|
// table is reported (file:line) instead of transformed.
|
|
23
23
|
//
|
|
24
|
-
// Usage: bun scripts/codemod/pii-personal-migration.ts <targetDir> [--dry-run]
|
|
24
|
+
// Usage: bun scripts/codemod/pii-personal-migration.ts <targetDir> [--dry-run | --report-stance]
|
|
25
|
+
// --report-stance is read-only: it writes nothing and classifies fields
|
|
26
|
+
// that carry no personal stance.
|
|
25
27
|
|
|
26
28
|
import { readFileSync } from "node:fs";
|
|
27
29
|
import { relative, resolve } from "node:path";
|
|
@@ -423,14 +425,12 @@ function applyTransform(
|
|
|
423
425
|
obj.insertPropertyAssignments(insertIndex, newProps);
|
|
424
426
|
}
|
|
425
427
|
|
|
426
|
-
function
|
|
428
|
+
function findSourceFiles(rootDir: string, extraExcludes: readonly string[] = []): string[] {
|
|
427
429
|
const glob = new Glob("**/*.{ts,tsx}");
|
|
428
|
-
|
|
429
|
-
// *implementation* of expandPersonalAnnotations (personal -> flags) —
|
|
430
|
-
// not an authored override, so it's not a migration target.
|
|
431
|
-
const EXCLUDE = ["/node_modules/", "/dist/", "/build/", "/engine/factories.ts"];
|
|
430
|
+
const EXCLUDE = ["/node_modules/", "/dist/", "/build/", ...extraExcludes];
|
|
432
431
|
const files: string[] = [];
|
|
433
432
|
for (const file of glob.scanSync({ cwd: rootDir, dot: false })) {
|
|
433
|
+
if (file.endsWith(".d.ts")) continue;
|
|
434
434
|
const abs = resolve(rootDir, file);
|
|
435
435
|
if (EXCLUDE.some((p) => abs.includes(p))) continue;
|
|
436
436
|
files.push(abs);
|
|
@@ -666,19 +666,6 @@ function collectStanceSites(sourceFile: SourceFile): StanceSite[] {
|
|
|
666
666
|
return sites;
|
|
667
667
|
}
|
|
668
668
|
|
|
669
|
-
function findReportStanceFiles(rootDir: string): string[] {
|
|
670
|
-
const glob = new Glob("**/*.{ts,tsx}");
|
|
671
|
-
const EXCLUDE = ["/node_modules/", "/dist/", "/build/"];
|
|
672
|
-
const files: string[] = [];
|
|
673
|
-
for (const file of glob.scanSync({ cwd: rootDir, dot: false })) {
|
|
674
|
-
if (file.endsWith(".d.ts")) continue;
|
|
675
|
-
const abs = resolve(rootDir, file);
|
|
676
|
-
if (EXCLUDE.some((p) => abs.includes(p))) continue;
|
|
677
|
-
files.push(abs);
|
|
678
|
-
}
|
|
679
|
-
return files.sort();
|
|
680
|
-
}
|
|
681
|
-
|
|
682
669
|
function formatHintSuffix(site: StanceSite): string {
|
|
683
670
|
if (!site.hint) return "";
|
|
684
671
|
return site.nearMissOf
|
|
@@ -701,7 +688,7 @@ function reportStance(rootDir: string): void {
|
|
|
701
688
|
let total = 0;
|
|
702
689
|
|
|
703
690
|
const project = createReportStanceProject();
|
|
704
|
-
for (const file of
|
|
691
|
+
for (const file of findSourceFiles(rootDir)) {
|
|
705
692
|
const sites = reportStanceForSource(readFileSync(file, "utf8"), file, project);
|
|
706
693
|
if (sites.length === 0) continue;
|
|
707
694
|
|
|
@@ -741,7 +728,10 @@ async function main(): Promise<void> {
|
|
|
741
728
|
}
|
|
742
729
|
|
|
743
730
|
const dryRun = process.argv.includes("--dry-run");
|
|
744
|
-
|
|
731
|
+
// engine/factories.ts constructs raw ResolvedPiiFlags literals as the
|
|
732
|
+
// *implementation* of expandPersonalAnnotations (personal -> flags) —
|
|
733
|
+
// not an authored override, so it's not a migration target.
|
|
734
|
+
const files = findSourceFiles(rootDir, ["/engine/factories.ts"]);
|
|
745
735
|
const project = new Project({
|
|
746
736
|
skipAddingFilesFromTsConfig: true,
|
|
747
737
|
skipFileDependencyResolution: true,
|