@cosmicdrift/kumiko-framework 0.385.0 → 0.387.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/LICENSE +21 -57
- package/README.md +2 -2
- package/agents/widgets.md +39 -39
- package/dist/api/api-constants.js +2 -2
- package/dist/api/auth-middleware.js +7 -7
- package/dist/api/auth-rate-limiters.js +7 -3
- package/dist/api/auth-routes-mfa.js +1 -1
- package/dist/api/auth-routes-session.js +1 -1
- package/dist/api/auth-routes-shared.js +7 -3
- package/dist/api/auth-routes-signup.js +1 -1
- package/dist/api/client-ip.js +3 -1
- package/dist/api/extra-route.js +1 -1
- package/dist/api/pii-leak-guard.js +4 -2
- package/dist/api/readiness.js +5 -5
- package/dist/api/redis-sse-broker.js +8 -6
- package/dist/api/request-context.d.ts +2 -0
- package/dist/api/request-context.js +16 -7
- package/dist/api/routes.js +2 -2
- package/dist/api/server.js +34 -33
- package/dist/api/sse-broker.js +1 -1
- package/dist/api/sse-route.d.ts +13 -13
- package/dist/api/sse-route.js +15 -15
- package/dist/auth/index.js +4 -4
- package/dist/auth/roles.d.ts +4 -4
- package/dist/auth/roles.js +24 -24
- package/dist/compliance/duration-spec.js +18 -20
- package/dist/compliance/index.js +2 -3
- package/dist/compliance/override-schema.d.ts +4 -4
- package/dist/compliance/override-schema.js +19 -22
- package/dist/compliance/profiles.d.ts +26 -29
- package/dist/compliance/profiles.js +62 -69
- package/dist/compliance/sub-processors.d.ts +28 -30
- package/dist/compliance/sub-processors.js +17 -21
- package/dist/consumer-cli.js +4 -4
- package/dist/crypto/blind-index.js +18 -17
- package/dist/crypto/ciphertext-pattern.js +1 -1
- package/dist/crypto/event-pii.js +3 -3
- package/dist/crypto/kek-source.d.ts +1 -1
- package/dist/crypto/kek-source.js +3 -2
- package/dist/crypto/kms-adapter.d.ts +1 -1
- package/dist/crypto/kms-adapter.js +4 -6
- package/dist/crypto/kms-wiring.d.ts +1 -2
- package/dist/crypto/kms-wiring.js +2 -2
- package/dist/crypto/pg-kms-adapter.js +2 -6
- package/dist/crypto/pii-field-encryption.js +4 -4
- package/dist/crypto/subject-resolver.d.ts +1 -1
- package/dist/crypto/subject-resolver.js +2 -2
- package/dist/db/apply-entity-event.d.ts +6 -6
- package/dist/db/apply-entity-event.js +55 -55
- package/dist/db/blind-index-cleanup.js +7 -7
- package/dist/db/collect-table-metas.js +10 -11
- package/dist/db/compound-types.d.ts +1 -1
- package/dist/db/compound-types.js +12 -12
- package/dist/db/cross-tenant-reads.d.ts +8 -0
- package/dist/db/cross-tenant-reads.js +23 -0
- package/dist/db/cursor.js +3 -4
- package/dist/db/dialect.d.ts +2 -2
- package/dist/db/dialect.js +2 -2
- package/dist/db/eagerload.d.ts +3 -3
- package/dist/db/eagerload.js +21 -12
- package/dist/db/entity-table-meta.d.ts +2 -2
- package/dist/db/entity-table-meta.js +4 -4
- package/dist/db/event-store-executor-context.d.ts +1 -1
- package/dist/db/event-store-executor-context.js +13 -13
- package/dist/db/event-store-executor-read.js +27 -28
- package/dist/db/event-store-executor-write.js +65 -70
- package/dist/db/located-timestamp.d.ts +11 -11
- package/dist/db/located-timestamp.js +29 -30
- package/dist/db/migrate-generator.d.ts +1 -1
- package/dist/db/migrate-generator.js +26 -19
- package/dist/db/migrate-runner.js +25 -25
- package/dist/db/money.d.ts +6 -6
- package/dist/db/money.js +29 -27
- package/dist/db/queries/backfill-pii.js +5 -5
- package/dist/db/queries/ddl.js +1 -1
- package/dist/db/queries/es-ops.d.ts +1 -1
- package/dist/db/queries/es-ops.js +1 -1
- package/dist/db/queries/event-consumer.js +3 -3
- package/dist/db/queries/event-store.js +4 -2
- package/dist/db/queries/projection-rebuild.js +3 -3
- package/dist/db/queries/raw-sql.d.ts +2 -2
- package/dist/db/queries/raw-sql.js +2 -2
- package/dist/db/queries/shadow-swap.js +19 -19
- package/dist/db/queries/test-stack.d.ts +4 -1
- package/dist/db/queries/test-stack.js +15 -2
- package/dist/db/query.d.ts +1 -1
- package/dist/db/query.js +6 -6
- package/dist/db/rebuild-marker.js +20 -21
- package/dist/db/replay-migration-sql.js +2 -2
- package/dist/db/schema-inspection.js +4 -5
- package/dist/db/table-builder.d.ts +1 -2
- package/dist/db/table-builder.js +82 -82
- package/dist/db/tenant-db.d.ts +2 -2
- package/dist/db/tenant-db.js +9 -2
- package/dist/derivatives/delete-stored-file.js +5 -2
- package/dist/derivatives/derivatives-context.js +1 -1
- package/dist/engine/boot-validator/access-roles.js +4 -3
- package/dist/engine/boot-validator/api-ext.js +14 -14
- package/dist/engine/boot-validator/config-deps.js +7 -6
- package/dist/engine/boot-validator/detail-screens.js +3 -3
- package/dist/engine/boot-validator/entity-handler.js +60 -61
- package/dist/engine/boot-validator/gdpr-storage.js +4 -4
- package/dist/engine/boot-validator/i18n-keys.js +1 -1
- package/dist/engine/boot-validator/index.d.ts +2 -2
- package/dist/engine/boot-validator/index.js +20 -20
- package/dist/engine/boot-validator/nav.js +5 -5
- package/dist/engine/boot-validator/ownership.js +12 -9
- package/dist/engine/boot-validator/parent-ref.js +1 -1
- package/dist/engine/boot-validator/pii-retention.js +50 -57
- package/dist/engine/boot-validator/projection-list-screens.js +7 -7
- package/dist/engine/boot-validator/query-refs.js +1 -1
- package/dist/engine/boot-validator/screens-actions.js +15 -16
- package/dist/engine/boot-validator/screens-forms.js +10 -11
- package/dist/engine/boot-validator/screens-lists.js +14 -15
- package/dist/engine/boot-validator/screens.js +34 -40
- package/dist/engine/boot-validator/security-baseline.js +3 -2
- package/dist/engine/boot-validator/transfer-graph.js +2 -2
- package/dist/engine/boot-validator/workspaces.js +3 -3
- package/dist/engine/bridge-stub.js +3 -4
- package/dist/engine/build-app-schema.js +75 -75
- package/dist/engine/build-config-feature-schema.js +50 -50
- package/dist/engine/build-target.js +20 -23
- package/dist/engine/codemod/pipeline-codemod.js +20 -13
- package/dist/engine/config-helpers.js +4 -4
- package/dist/engine/constants.js +2 -2
- package/dist/engine/create-app.js +2 -2
- package/dist/engine/define-feature.js +12 -11
- package/dist/engine/define-handler.js +12 -11
- package/dist/engine/define-step.js +1 -1
- package/dist/engine/define-workflow.d.ts +5 -5
- package/dist/engine/define-workflow.js +6 -6
- package/dist/engine/entity-handlers.js +13 -14
- package/dist/engine/event-def.js +2 -2
- package/dist/engine/extension-names.d.ts +16 -17
- package/dist/engine/extension-names.js +37 -38
- package/dist/engine/extensions/escape-hatch-usage.js +1 -1
- package/dist/engine/extensions/tenant-data.js +2 -2
- package/dist/engine/extensions/user-data.d.ts +23 -24
- package/dist/engine/factories.d.ts +14 -13
- package/dist/engine/factories.js +38 -37
- package/dist/engine/feature-ast/entity-field-types.js +1 -1
- package/dist/engine/feature-ast/extractors/handlers.js +1 -1
- package/dist/engine/feature-ast/extractors/hooks.js +1 -1
- package/dist/engine/feature-ast/extractors/shared.d.ts +2 -3
- package/dist/engine/feature-ast/extractors/shared.js +2 -3
- package/dist/engine/feature-ast/parse.js +13 -13
- package/dist/engine/feature-ast/patch.js +1 -1
- package/dist/engine/feature-ast/pattern-change-schema.d.ts +8 -0
- package/dist/engine/feature-ast/pattern-change-schema.js +1 -1
- package/dist/engine/feature-ast/patterns.d.ts +2 -2
- package/dist/engine/feature-ast/render.js +2 -2
- package/dist/engine/feature-config-events-jobs.js +15 -12
- package/dist/engine/feature-entity-handlers.js +9 -5
- package/dist/engine/feature-manifest.d.ts +9 -9
- package/dist/engine/feature-manifest.js +6 -7
- package/dist/engine/feature-ui-extensions.d.ts +3 -3
- package/dist/engine/feature-ui-extensions.js +30 -22
- package/dist/engine/field-access.js +5 -6
- package/dist/engine/index.d.ts +1 -0
- package/dist/engine/index.js +4 -4
- package/dist/engine/object-form.d.ts +1 -0
- package/dist/engine/object-form.js +14 -4
- package/dist/engine/ownership.js +3 -3
- package/dist/engine/parse-ref-target.js +5 -7
- package/dist/engine/pattern-library/index.js +2 -2
- package/dist/engine/pattern-library/library.js +2 -2
- package/dist/engine/pattern-library/types.js +3 -3
- package/dist/engine/pipeline.js +2 -2
- package/dist/engine/project-app-schema-for-roles.js +1 -1
- package/dist/engine/projection-helpers.js +9 -9
- package/dist/engine/qualified-name.js +2 -2
- package/dist/engine/registry-facade.js +1 -1
- package/dist/engine/registry-ingest.js +12 -12
- package/dist/engine/registry-state.js +17 -19
- package/dist/engine/registry-validate.js +12 -12
- package/dist/engine/run-in.js +2 -2
- package/dist/engine/run-pipeline.js +7 -0
- package/dist/engine/schema-builder.js +40 -38
- package/dist/engine/screen-filter-ops.js +17 -20
- package/dist/engine/screen-helpers.js +4 -3
- package/dist/engine/state-machine.d.ts +12 -12
- package/dist/engine/state-machine.js +7 -7
- package/dist/engine/steps/_resolver-utils.js +4 -3
- package/dist/engine/steps/for-each.js +4 -4
- package/dist/engine/steps/unsafe-projection-delete.js +1 -1
- package/dist/engine/steps/unsafe-projection-upsert.js +4 -4
- package/dist/engine/steps/wait-for-event.js +4 -4
- package/dist/engine/steps/workflow-system-events.js +3 -3
- package/dist/engine/tier-resolver-extension.d.ts +29 -27
- package/dist/engine/tier-resolver-extension.js +26 -26
- package/dist/engine/transfer-adjacency.js +2 -2
- package/dist/engine/validate-projection-allowlist.js +7 -27
- package/dist/engine/walk-step-instances.d.ts +2 -0
- package/dist/engine/walk-step-instances.js +38 -0
- package/dist/engine/where-rule-lint.js +1 -1
- package/dist/engine/write-handler-qn-extract.js +1 -1
- package/dist/entrypoint/index.js +3 -3
- package/dist/env/index.d.ts +7 -6
- package/dist/env/index.js +10 -9
- package/dist/errors/transition-details.js +7 -7
- package/dist/errors/write-error-info.d.ts +6 -6
- package/dist/errors/write-error-info.js +6 -6
- package/dist/es-ops/context.d.ts +9 -9
- package/dist/es-ops/context.js +28 -28
- package/dist/es-ops/index.js +7 -9
- package/dist/es-ops/operations-schema.js +4 -10
- package/dist/es-ops/runner.d.ts +11 -11
- package/dist/es-ops/runner.js +49 -53
- package/dist/es-ops/types.d.ts +55 -55
- package/dist/es-ops/types.js +11 -11
- package/dist/event-store/admin-api.js +6 -7
- package/dist/event-store/errors.js +1 -1
- package/dist/event-store/event-store.js +2 -2
- package/dist/event-store/rebuild-dead-letter.js +1 -1
- package/dist/files/feature.js +9 -10
- package/dist/files/file-handle.js +5 -6
- package/dist/files/file-ref-entity.js +15 -15
- package/dist/files/file-ref-table.js +7 -8
- package/dist/files/file-routes.d.ts +7 -0
- package/dist/files/file-routes.js +11 -5
- package/dist/files/in-memory-provider.js +8 -9
- package/dist/files/local-provider.js +18 -25
- package/dist/files/provider-resolver.d.ts +7 -7
- package/dist/files/storage-tracking.js +2 -2
- package/dist/files/zip-stream.d.ts +12 -19
- package/dist/files/zip-stream.js +44 -63
- package/dist/i18n/required-surface-keys.d.ts +1 -1
- package/dist/i18n/required-surface-keys.js +8 -8
- package/dist/internal/stack-support.d.ts +1 -1
- package/dist/internal/stack-support.js +1 -1
- package/dist/jobs/job-runner-helpers.d.ts +1 -2
- package/dist/jobs/job-runner-helpers.js +8 -18
- package/dist/jobs/job-runner-schedulers.d.ts +1 -1
- package/dist/jobs/job-runner-schedulers.js +3 -4
- package/dist/jobs/job-runner.js +17 -18
- package/dist/lifecycle/lifecycle.js +3 -5
- package/dist/lifecycle/signal-handlers.js +5 -4
- package/dist/logging/utils.d.ts +2 -1
- package/dist/logging/utils.js +38 -10
- package/dist/migrations/index.js +1 -1
- package/dist/migrations/kumiko-drift.js +14 -13
- package/dist/migrations/pending-rebuilds.d.ts +21 -20
- package/dist/migrations/pending-rebuilds.js +27 -27
- package/dist/migrations/projection-table-index.d.ts +3 -3
- package/dist/migrations/projection-table-index.js +8 -8
- package/dist/observability/metric-validator.js +1 -1
- package/dist/observability/metrics-handle.js +3 -3
- package/dist/pipeline/append-event-core.js +11 -2
- package/dist/pipeline/cascade-handler.d.ts +1 -1
- package/dist/pipeline/dispatch-batch.js +2 -2
- package/dist/pipeline/dispatch-query.js +10 -10
- package/dist/pipeline/dispatch-shared.d.ts +1 -0
- package/dist/pipeline/dispatch-shared.js +42 -19
- package/dist/pipeline/dispatch-stream.js +1 -1
- package/dist/pipeline/dispatch-write.js +3 -3
- package/dist/pipeline/dispatcher-utils.d.ts +1 -1
- package/dist/pipeline/dispatcher-utils.js +1 -1
- package/dist/pipeline/event-consumer-state.js +2 -2
- package/dist/pipeline/event-dispatcher-delivery.js +1 -1
- package/dist/pipeline/event-dispatcher.js +17 -28
- package/dist/pipeline/irreversible-operation-gate.js +1 -1
- package/dist/pipeline/lifecycle-pipeline.js +9 -9
- package/dist/pipeline/member-read-only-transaction.js +1 -3
- package/dist/pipeline/msp-rebuild.js +1 -1
- package/dist/pipeline/projection-rebuild.js +13 -12
- package/dist/pipeline/system-hooks.js +39 -41
- package/dist/pipeline/system-identity-switch.js +1 -3
- package/dist/pipeline/tenant-timezone-cache.js +1 -1
- package/dist/random/generate.d.ts +29 -29
- package/dist/random/generate.js +30 -30
- package/dist/random/words.js +20 -20
- package/dist/rate-limit/middleware.js +9 -5
- package/dist/redis/client.js +2 -2
- package/dist/redis/pubsub-signal.js +14 -17
- package/dist/schema-cli.js +116 -22
- package/dist/search/index.js +4 -4
- package/dist/search/purge-subject.js +4 -5
- package/dist/search/reindex-entity.js +6 -9
- package/dist/time/boot-tz-warning.d.ts +2 -2
- package/dist/time/boot-tz-warning.js +8 -8
- package/dist/time/iana.js +10 -10
- package/dist/time/index.js +9 -9
- package/dist/time/legacy-date.d.ts +2 -2
- package/dist/time/legacy-date.js +6 -8
- package/dist/time/polyfill.js +1 -1
- package/dist/time/tz-context.d.ts +1 -1
- package/dist/time/tz-context.js +1 -1
- package/dist/ui-types/app-schema.js +7 -7
- package/dist/ui-types/list-row-meta.js +4 -4
- package/dist/upgrade-cli.js +10 -9
- package/dist/utils/compare.js +1 -1
- package/package.json +4 -4
- package/src/changes.json +54 -0
- package/src/scripts/codemod/crypto-shredding-testing-move.ts +2 -2
- package/src/scripts/codemod/migrate-cross-tenant.ts +1 -1
- package/src/scripts/codemod/migrate-open-to-all.ts +1 -1
- package/src/scripts/codemod/pii-personal-migration.ts +17 -8
- package/src/scripts/codemod/unprocessable-error-details-reason.ts +2 -2
|
@@ -61,7 +61,7 @@ function parentRefEdgesFrom(entityName, entity, allEntityNames) {
|
|
|
61
61
|
//
|
|
62
62
|
// A type can legitimately be reached by more than one edge — `note.vehicleId`
|
|
63
63
|
// and `note.campaignId` are distinct, and collapsing them by entity name would
|
|
64
|
-
// leave one edge's rows behind,
|
|
64
|
+
// leave one edge's rows behind, causing a silent partial move.
|
|
65
65
|
export function resolveTransferAdjacency(entities, rootEntityType) {
|
|
66
66
|
const allEntityNames = [...entities.keys()];
|
|
67
67
|
const byParent = new Map();
|
|
@@ -96,7 +96,7 @@ export function findOverDeepTransferChain(adjacency, onPath) {
|
|
|
96
96
|
// skip: a cycle revisits a type already on this path, and schema depth
|
|
97
97
|
// cannot measure how far it actually runs — that depends on the rows, not
|
|
98
98
|
// the declaration. The mover carries this one instead, failing with
|
|
99
|
-
// `transfer_graph_too_deep` when its rounds run out
|
|
99
|
+
// `transfer_graph_too_deep` when its rounds run out.
|
|
100
100
|
if (onPath.includes(edge.entityName))
|
|
101
101
|
continue;
|
|
102
102
|
const deeper = findOverDeepTransferChain(adjacency, [...onPath, edge.entityName]);
|
|
@@ -23,35 +23,16 @@
|
|
|
23
23
|
// caught by this validator. A future lint-rule will enforce the contract
|
|
24
24
|
// statically; today it lives in this comment + the StepBuilder doc.
|
|
25
25
|
import { KUMIKO_META_SYMBOL } from "@cosmicdrift/kumiko-types/schema-table-types";
|
|
26
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
26
27
|
import { schemaTableName } from "../db/table-introspection.js";
|
|
27
28
|
import { getStep } from "./define-step.js";
|
|
28
29
|
import { buildPipelineSteps } from "./pipeline.js";
|
|
30
|
+
import { walkStepInstances } from "./walk-step-instances.js";
|
|
29
31
|
// Listed step-kinds whose `args.table` must be in the owning feature's
|
|
30
32
|
// r.requires.projection allowlist. Extend as further unsafeProjection.*
|
|
31
33
|
// steps land — don't pre-list hypothetical kinds (CLAUDE.md: don't design
|
|
32
34
|
// for scenarios that can't happen).
|
|
33
35
|
const UNSAFE_PROJECTION_KINDS = new Set(["unsafeProjectionUpsert", "unsafeProjectionDelete"]);
|
|
34
|
-
// Sub-pipeline arg-paths come from `defineStep({ subPaths: [...] })` —
|
|
35
|
-
// each builder declares its own (e.g. branch's onTrue/onFalse, forEach's
|
|
36
|
-
// do). Walking via the registry means nested unsafeProjection-* in NEW
|
|
37
|
-
// sub-step-builders is automatically caught the moment the builder
|
|
38
|
-
// registers itself; no central map to keep in sync. Followup #15.
|
|
39
|
-
function* walkAllSteps(steps) {
|
|
40
|
-
for (const step of steps) {
|
|
41
|
-
yield step;
|
|
42
|
-
const def = getStep(step.kind);
|
|
43
|
-
const subPaths = def?.subPaths;
|
|
44
|
-
if (!subPaths || subPaths.length === 0)
|
|
45
|
-
continue;
|
|
46
|
-
const args = step.args;
|
|
47
|
-
for (const path of subPaths) {
|
|
48
|
-
const subSteps = args[path];
|
|
49
|
-
if (Array.isArray(subSteps)) {
|
|
50
|
-
yield* walkAllSteps(subSteps);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
36
|
// table()/buildEntityTable spread column handles as enumerable props, so an
|
|
56
37
|
// entity field named `tableName`/`source` would shadow the matching meta key —
|
|
57
38
|
// the canonical meta under KUMIKO_META_SYMBOL is the only collision-safe source.
|
|
@@ -95,7 +76,6 @@ export function validateProjectionAllowlist(features) {
|
|
|
95
76
|
// the first feature's projections (and vice-versa). Detect and surface
|
|
96
77
|
// here so the error names BOTH owners; without this, the silent-set
|
|
97
78
|
// would point any later unsafeProjection-error at the wrong feature.
|
|
98
|
-
// Followup #8.
|
|
99
79
|
const aggregateTables = new Map();
|
|
100
80
|
for (const f of features) {
|
|
101
81
|
for (const [entityName, entity] of Object.entries(f.entities ?? {})) {
|
|
@@ -129,8 +109,8 @@ export function validateProjectionAllowlist(features) {
|
|
|
129
109
|
`Closure body must produce the step list without reading event-payload fields ` +
|
|
130
110
|
`(those belong inside step resolvers).`);
|
|
131
111
|
}
|
|
132
|
-
for (const step of
|
|
133
|
-
// Tier-2 step-discovery
|
|
112
|
+
for (const step of walkStepInstances(steps, `[Feature ${f.name}] writeHandler "${handlerName}"`)) {
|
|
113
|
+
// Tier-2 step-discovery: require explicit opt-in via
|
|
134
114
|
// r.requires.step("<kind>") for any tier-2+ step. Tier-1 is implicit.
|
|
135
115
|
const def = getStep(step.kind);
|
|
136
116
|
if (def?.tier === 2 && !f.requiredSteps.has(step.kind)) {
|
|
@@ -140,12 +120,12 @@ export function validateProjectionAllowlist(features) {
|
|
|
140
120
|
}
|
|
141
121
|
if (!UNSAFE_PROJECTION_KINDS.has(step.kind))
|
|
142
122
|
continue;
|
|
143
|
-
const
|
|
144
|
-
if (!
|
|
123
|
+
const stepTable = isPlainObject(step.args) ? step.args["table"] : undefined;
|
|
124
|
+
if (!stepTable) {
|
|
145
125
|
throw new Error(`[Feature ${f.name}] writeHandler "${handlerName}" has a ${step.kind} step ` +
|
|
146
126
|
`without a \`table\` argument.`);
|
|
147
127
|
}
|
|
148
|
-
const tableName = resolveTableNameFromStep(
|
|
128
|
+
const tableName = resolveTableNameFromStep(stepTable);
|
|
149
129
|
const aggregateOwner = aggregateTables.get(tableName);
|
|
150
130
|
if (aggregateOwner) {
|
|
151
131
|
throw new Error(`[Feature ${f.name}] writeHandler "${handlerName}" uses ${step.kind} on table "${tableName}", ` +
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
2
|
+
import { getStep } from "./define-step.js";
|
|
3
|
+
function isStepInstance(value) {
|
|
4
|
+
return isPlainObject(value) && typeof value["kind"] === "string";
|
|
5
|
+
}
|
|
6
|
+
// Sub-pipeline arg-paths come from `defineStep({ subPaths })`, so nested steps of new
|
|
7
|
+
// sub-step builders are walked without a central map.
|
|
8
|
+
// Fail-closed: an entry the walker cannot read would hide a step from the caller's check
|
|
9
|
+
// (projection allowlist, event-trigger support), so malformed input throws instead of being skipped.
|
|
10
|
+
export function* walkStepInstances(steps, where) {
|
|
11
|
+
for (const [stepIndex, step] of steps.entries()) {
|
|
12
|
+
yield step;
|
|
13
|
+
const subPaths = getStep(step.kind)?.subPaths;
|
|
14
|
+
if (!subPaths || subPaths.length === 0)
|
|
15
|
+
continue;
|
|
16
|
+
const stepWhere = `${where} step[${stepIndex}] "${step.kind}"`;
|
|
17
|
+
if (!isPlainObject(step.args)) {
|
|
18
|
+
throw new Error(`${stepWhere} declares sub-paths (${subPaths.join(", ")}) but its args is not an object.`);
|
|
19
|
+
}
|
|
20
|
+
for (const path of subPaths) {
|
|
21
|
+
const subSteps = step.args[path];
|
|
22
|
+
// skip: optional sub-path not provided (branch.run treats onFalse: null like absent)
|
|
23
|
+
if (subSteps === undefined || subSteps === null)
|
|
24
|
+
continue;
|
|
25
|
+
if (!Array.isArray(subSteps)) {
|
|
26
|
+
throw new Error(`${stepWhere} sub-path "${path}" must be a list of steps.`);
|
|
27
|
+
}
|
|
28
|
+
const checked = [];
|
|
29
|
+
for (const [entryIndex, entry] of subSteps.entries()) {
|
|
30
|
+
if (!isStepInstance(entry)) {
|
|
31
|
+
throw new Error(`${stepWhere} sub-path "${path}"[${entryIndex}] is not a step (expected an object with a string "kind").`);
|
|
32
|
+
}
|
|
33
|
+
checked.push(entry);
|
|
34
|
+
}
|
|
35
|
+
yield* walkStepInstances(checked, `${stepWhere} ${path}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -6,7 +6,7 @@ import { toKebab } from "./qualified-name.js";
|
|
|
6
6
|
export const WRITE_HANDLER_QN_FORMAT_RE = /^[a-zA-Z][a-zA-Z0-9-]*:write:[a-zA-Z][a-zA-Z0-9-]*(:[a-zA-Z][a-zA-Z0-9-]*)*$/;
|
|
7
7
|
// Matches `dispatcher.write("qn", ...)` and `<expr>.write("qn", ...)` —
|
|
8
8
|
// same surface the CI guard scans. Dynamic QNs (variables, templates) are
|
|
9
|
-
// intentionally skipped (known limitation
|
|
9
|
+
// intentionally skipped (known limitation).
|
|
10
10
|
const DISPATCHER_WRITE_LITERAL_RE = /\.write\s*\(\s*["']([^"']+)["']/g;
|
|
11
11
|
/** Extract string-literal write-handler QNs from TS/TSX source text. */
|
|
12
12
|
export function extractDispatcherWriteQnsFromSource(source) {
|
package/dist/entrypoint/index.js
CHANGED
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
//
|
|
37
37
|
// SSE fanout across processes (split api/worker deploy, replicas > 1):
|
|
38
38
|
// buildServer's default sseBroker is Redis-backed whenever REDIS_URL is
|
|
39
|
-
// set
|
|
39
|
+
// set — a push on the worker's dispatcher reaches clients
|
|
40
40
|
// connected to any API instance via Redis Pub/Sub, no per-instance
|
|
41
41
|
// cursor needed. Without REDIS_URL, the in-memory broker still only
|
|
42
42
|
// reaches clients on the same process.
|
|
@@ -55,7 +55,7 @@ import { resolveCacheSyncBus } from "../redis/cache-sync-bus.js";
|
|
|
55
55
|
// so the command-dispatcher fires handleEvent as an afterCommit-hook for
|
|
56
56
|
// event-triggered jobs (dispatcher.ts:997). Without this plumbing,
|
|
57
57
|
// `r.job({ trigger: { on: … } })` silently drops on every write — that
|
|
58
|
-
// was the hidden
|
|
58
|
+
// was the hidden gap.
|
|
59
59
|
//
|
|
60
60
|
// Caller-supplied `dispatcherOptions.jobRunner` wins (tests sometimes
|
|
61
61
|
// inject a mock runner directly).
|
|
@@ -198,7 +198,7 @@ export function createApiEntrypoint(options) {
|
|
|
198
198
|
const lifecycle = options.lifecycle ?? createLifecycle({ startReady: true });
|
|
199
199
|
const observability = resolveObservability(options.observability);
|
|
200
200
|
const { options: busOptions, context } = resolveEntrypointCacheSync(options, lifecycle);
|
|
201
|
-
// Boot-validation
|
|
201
|
+
// Boot-validation — fail loud before traffic arrives:
|
|
202
202
|
// (a) Any jobs declared + no jobs-block → command-dispatcher would
|
|
203
203
|
// silently drop every event-triggered enqueue. Fix: add jobs:
|
|
204
204
|
// { redisUrl } so the API holds lane-queue-clients.
|
package/dist/env/index.d.ts
CHANGED
|
@@ -116,12 +116,13 @@ export type ParseEnvOptions = {
|
|
|
116
116
|
* is `undefined` in the result despite its `string` type — it is only set once
|
|
117
117
|
* the boot-time decrypt (resolvePlatformKeks) has run. */
|
|
118
118
|
export declare function parseEnv<S extends z.ZodObject<z.ZodRawShape>>(schema: S, env: Record<string, string | undefined>, options?: ParseEnvOptions): z.infer<S>;
|
|
119
|
-
/** Dry-
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
119
|
+
/** Dry-run counterpart to parseEnv (KUMIKO_DRY_RUN_ENV path): parses each
|
|
120
|
+
* field individually and returns an honest Partial — present values pass
|
|
121
|
+
* through with their types (coerced), while missing or invalid values are
|
|
122
|
+
* simply omitted. Never throws: the dry run renders the inventory instead
|
|
123
|
+
* of failing on required fields. Replaces the `({} as Shape)` pattern, where
|
|
124
|
+
* every access silently returned undefined and the compiler could not detect
|
|
125
|
+
* it. */
|
|
125
126
|
export declare function parseEnvDryRun<S extends z.ZodObject<z.ZodRawShape>>(schema: S, env: Record<string, string | undefined>): Partial<z.infer<S>>;
|
|
126
127
|
export type EnvFieldStatus = "set" | "unset" | "missing" | "invalid";
|
|
127
128
|
export type EnvFieldInspection = {
|
package/dist/env/index.js
CHANGED
|
@@ -209,7 +209,7 @@ export function composeEnvSchema(options) {
|
|
|
209
209
|
// rather than silently overwriting a deploy-critical default. The
|
|
210
210
|
// conflict-detection makes the order safe in both directions — both
|
|
211
211
|
// throw — but downstream tools that consume `sources` (e.g. the
|
|
212
|
-
// upcoming
|
|
212
|
+
// upcoming lint-guard) may rely on this insertion order.
|
|
213
213
|
if (options.core) {
|
|
214
214
|
for (const [key, field] of Object.entries(zodShape(options.core))) {
|
|
215
215
|
merged[key] = field;
|
|
@@ -356,12 +356,13 @@ export function parseEnv(schema, env, options = {}) {
|
|
|
356
356
|
function ucfirst(s) {
|
|
357
357
|
return s.length === 0 ? s : s.charAt(0).toUpperCase() + s.slice(1);
|
|
358
358
|
}
|
|
359
|
-
/** Dry-
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
359
|
+
/** Dry-run counterpart to parseEnv (KUMIKO_DRY_RUN_ENV path): parses each
|
|
360
|
+
* field individually and returns an honest Partial — present values pass
|
|
361
|
+
* through with their types (coerced), while missing or invalid values are
|
|
362
|
+
* simply omitted. Never throws: the dry run renders the inventory instead
|
|
363
|
+
* of failing on required fields. Replaces the `({} as Shape)` pattern, where
|
|
364
|
+
* every access silently returned undefined and the compiler could not detect
|
|
365
|
+
* it. */
|
|
365
366
|
export function parseEnvDryRun(schema, env) {
|
|
366
367
|
const out = {};
|
|
367
368
|
for (const [name, field] of Object.entries(zodShape(schema))) {
|
|
@@ -372,8 +373,8 @@ export function parseEnvDryRun(schema, env) {
|
|
|
372
373
|
if (result.success)
|
|
373
374
|
out[name] = result.data;
|
|
374
375
|
}
|
|
375
|
-
// @cast-boundary schema-walk — Partial<z.infer<S>> erasure
|
|
376
|
-
// per-
|
|
376
|
+
// @cast-boundary schema-walk — Partial<z.infer<S>> erasure through the
|
|
377
|
+
// per-field safeParse; every included value passed its field-level parse.
|
|
377
378
|
return out;
|
|
378
379
|
}
|
|
379
380
|
/** Checks every field of `schema` on its own against `env`, for the boot dry
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
//
|
|
2
|
-
// (state-machine.assertTransition
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
1
|
+
// Shared detail shape for invalid_transition errors. Both paths
|
|
2
|
+
// (`state-machine.assertTransition` and `failTransition`) must provide
|
|
3
|
+
// identically structured details so HTTP clients can parse the 422 body uniformly:
|
|
4
|
+
// `from`/`to`/`allowed` are the structured fields, while `message` is the
|
|
5
|
+
// human-readable form.
|
|
6
6
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
7
|
+
// `validTargets` is not re-exported as a CSV string; callers that need CSV can use
|
|
8
|
+
// `allowed.join(", ")`. One shape defines the contract.
|
|
9
9
|
export function buildInvalidTransitionDetails(from, to, allowed) {
|
|
10
10
|
return {
|
|
11
11
|
from,
|
|
@@ -7,13 +7,13 @@ export declare function failUnprocessable(reason: string, details?: Readonly<Rec
|
|
|
7
7
|
readonly reason?: never;
|
|
8
8
|
}): WriteFailure;
|
|
9
9
|
/**
|
|
10
|
-
* Convenience
|
|
11
|
-
* reason="invalid_transition"
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
10
|
+
* Convenience for state-transition rejects: produces a WriteFailure with
|
|
11
|
+
* reason="invalid_transition", a structured `from`/`to`/`allowed` detail
|
|
12
|
+
* block, and a readable message. This pattern recurs in publicstatus
|
|
13
|
+
* maintenance and existing state-machine helpers, so the helper collects it
|
|
14
|
+
* in one call instead of requiring three manual detail fields.
|
|
15
15
|
*
|
|
16
|
-
* `allowed`
|
|
16
|
+
* `allowed` is typically `MAINTENANCE_TRANSITIONS.allowedFrom(from)`.
|
|
17
17
|
*/
|
|
18
18
|
export declare function failTransition(from: string, to: string, allowed: readonly string[]): WriteFailure;
|
|
19
19
|
export declare function toWriteErrorInfo(err: KumikoError): WriteErrorInfo;
|
|
@@ -24,13 +24,13 @@ export function failUnprocessable(reason, details) {
|
|
|
24
24
|
return writeFailure(new UnprocessableError(reason, details ? { details } : undefined));
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
-
* Convenience
|
|
28
|
-
* reason="invalid_transition"
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
27
|
+
* Convenience for state-transition rejects: produces a WriteFailure with
|
|
28
|
+
* reason="invalid_transition", a structured `from`/`to`/`allowed` detail
|
|
29
|
+
* block, and a readable message. This pattern recurs in publicstatus
|
|
30
|
+
* maintenance and existing state-machine helpers, so the helper collects it
|
|
31
|
+
* in one call instead of requiring three manual detail fields.
|
|
32
32
|
*
|
|
33
|
-
* `allowed`
|
|
33
|
+
* `allowed` is typically `MAINTENANCE_TRANSITIONS.allowedFrom(from)`.
|
|
34
34
|
*/
|
|
35
35
|
export function failTransition(from, to, allowed) {
|
|
36
36
|
return writeFailure(new UnprocessableError(FrameworkReasons.invalidTransition, {
|
package/dist/es-ops/context.d.ts
CHANGED
|
@@ -5,15 +5,15 @@ export type CreateSeedMigrationContextArgs = {
|
|
|
5
5
|
readonly dispatcher: Dispatcher;
|
|
6
6
|
readonly dbRunner: DbRunner;
|
|
7
7
|
};
|
|
8
|
-
/** Builder
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
/** Builder that returns a factory function called by the runner for each
|
|
9
|
+
* migration. dbRunner may be a top-level connection or a transaction—read
|
|
10
|
+
* helpers use it directly, while systemWriteAs delegates to the dispatcher.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* runner.integration.test.ts
|
|
12
|
+
* dispatcher.write does not receive the runner transaction: runBatch opens
|
|
13
|
+
* a separate transaction on context.db (using a separate pool connection).
|
|
14
|
+
* Writes therefore commit independently: a runner failure rolls back only
|
|
15
|
+
* the marker insert and dbRunner reads; dispatcher writes remain committed.
|
|
16
|
+
* Seeds must therefore be idempotent (verified in
|
|
17
|
+
* runner.integration.test.ts “dispatcher-writes vor throw bleiben committed”). */
|
|
18
18
|
export declare function createSeedMigrationContext(args: CreateSeedMigrationContextArgs): SeedMigrationContext;
|
|
19
19
|
export type { SeedMigrationContext } from "./types.ts";
|
package/dist/es-ops/context.js
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
// SeedMigrationContext
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// SeedMigrationContext builder. The caller (runProdApp/CLI) supplies a
|
|
2
|
+
// preconfigured dispatcher; the builder creates a transaction-scoped context
|
|
3
|
+
// for each migration that uses the existing handler path through
|
|
4
|
+
// dispatcher.write—the same pattern as a user UI click.
|
|
5
5
|
//
|
|
6
|
-
//
|
|
7
|
-
// config-seed.ts:40). Events
|
|
8
|
-
//
|
|
6
|
+
// The system user bypasses access checks (the standard seed pattern; see
|
|
7
|
+
// config-seed.ts:40). Events have createdBy = SYSTEM_TENANT_ID user,
|
|
8
|
+
// making them auditable.
|
|
9
9
|
import { configuredPiiSubjectKms, decryptPiiFieldValues } from "../crypto/index.js";
|
|
10
10
|
import { selectAllTenants, selectMembershipsOfUser, selectTemplateResources, selectUserByEmail, } from "../db/queries/seed-context.js";
|
|
11
11
|
import { createSystemUser, SYSTEM_TENANT_ID } from "../engine/index.js";
|
|
12
12
|
import { parseStringArrayJson } from "../utils/parse-string-array-json.js";
|
|
13
|
-
/** Builder
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
/** Builder that returns a factory function called by the runner for each
|
|
14
|
+
* migration. dbRunner may be a top-level connection or a transaction—read
|
|
15
|
+
* helpers use it directly, while systemWriteAs delegates to the dispatcher.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* runner.integration.test.ts
|
|
17
|
+
* dispatcher.write does not receive the runner transaction: runBatch opens
|
|
18
|
+
* a separate transaction on context.db (using a separate pool connection).
|
|
19
|
+
* Writes therefore commit independently: a runner failure rolls back only
|
|
20
|
+
* the marker insert and dbRunner reads; dispatcher writes remain committed.
|
|
21
|
+
* Seeds must therefore be idempotent (verified in
|
|
22
|
+
* runner.integration.test.ts “dispatcher-writes vor throw bleiben committed”). */
|
|
23
23
|
export function createSeedMigrationContext(args) {
|
|
24
|
-
// Default
|
|
25
|
-
// text-content, etc.).
|
|
26
|
-
//
|
|
24
|
+
// Default executor for system-scope aggregates (config-values, system
|
|
25
|
+
// text-content, etc.). For tenant-scope aggregates, the caller must
|
|
26
|
+
// explicitly provide `tenantIdOverride`—see the documentation in types.ts.
|
|
27
27
|
const defaultSystemUser = createSystemUser(SYSTEM_TENANT_ID);
|
|
28
28
|
return {
|
|
29
29
|
systemWriteAs: async (handlerQualifiedName, payload, tenantIdOverride, extraRoles) => {
|
|
30
|
-
// tenantIdOverride
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
// feedback_event_store_tenant_consistency.md).
|
|
30
|
+
// tenantIdOverride creates a system user with the stream tenant ID so that
|
|
31
|
+
// the event-store executor finds the aggregate in the correct stream.
|
|
32
|
+
// This prevents the version_conflict failure.
|
|
34
33
|
const executor = tenantIdOverride !== undefined || (extraRoles && extraRoles.length > 0)
|
|
35
34
|
? createSystemUser(tenantIdOverride ?? SYSTEM_TENANT_ID, extraRoles)
|
|
36
35
|
: defaultSystemUser;
|
|
37
36
|
const result = await args.dispatcher.write(handlerQualifiedName, payload, executor);
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
37
|
+
// Without this check, WriteResult{isSuccess: false} would pass silently,
|
|
38
|
+
// causing the marker to be written despite the failed write and the
|
|
39
|
+
// migration to be incorrectly marked as "applied". Throw so the
|
|
40
|
+
// runner's outer transaction rolls back and the marker is not written.
|
|
41
|
+
// Seed authors can use try/catch for custom handling when a soft failure
|
|
42
|
+
// is expected.
|
|
43
43
|
if (!result.isSuccess) {
|
|
44
44
|
const code = result.error?.code ?? "unknown";
|
|
45
45
|
const message = result.error?.message ?? "(no message)";
|
package/dist/es-ops/index.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
// es-ops — ES
|
|
1
|
+
// es-ops — ES operations pattern for Kumiko apps.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
// (kumiko_es_operations
|
|
3
|
+
// Seed migrations use the same infrastructure as later operation types
|
|
4
|
+
// (the `kumiko_es_operations` table with an `operation_type` discriminator).
|
|
5
5
|
//
|
|
6
|
-
// App
|
|
7
|
-
// - SeedMigration
|
|
8
|
-
// - runProdApp({ seedsDir })
|
|
9
|
-
// - bunx kumiko ops seed:new|status|apply (CLI)
|
|
10
|
-
//
|
|
11
|
-
// Plan-Doc: kumiko-platform/docs/plans/features/es-ops.md
|
|
6
|
+
// App author API:
|
|
7
|
+
// - `SeedMigration`: default export type of a seed file
|
|
8
|
+
// - `runProdApp({ seedsDir })`: the framework runs pending migrations at boot
|
|
9
|
+
// - `bunx kumiko ops seed:new|status|apply` (CLI)
|
|
12
10
|
export { createSeedMigrationContext, } from "./context.js";
|
|
13
11
|
export { createEsOperationsTable, esOperationsTable, } from "./operations-schema.js";
|
|
14
12
|
export { runPendingSeedMigrations, } from "./runner.js";
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// File-ID-Tracking analog drizzle-kit: filename ist die ID, applied-set
|
|
6
|
-
// liegt in dieser Tabelle. Pending = files-on-disk MINUS applied-set.
|
|
7
|
-
//
|
|
8
|
-
// operation_type-Discriminator lässt Phase 2+ dieselbe Tabelle nutzen,
|
|
9
|
-
// kein Schema-Sprawl. CLI-Status filtert per type:
|
|
1
|
+
// Tracks ES operations. The filename is the ID, so pending operations are files on disk minus the
|
|
2
|
+
// applied set. The operation_type discriminator lets different operation types share one table
|
|
3
|
+
// without schema sprawl; CLI status commands filter by type:
|
|
10
4
|
// bunx kumiko ops seed:status → operation_type = "seed-migration"
|
|
11
5
|
// bunx kumiko ops projection:status → operation_type = "projection-rebuild"
|
|
12
6
|
import { index, integer, table as pgTable, sql, text, timestamp } from "../db/dialect.js";
|
|
@@ -24,7 +18,7 @@ export const esOperationsTable = pgTable("kumiko_es_operations", {
|
|
|
24
18
|
.default(sql `now()`),
|
|
25
19
|
durationMs: integer("duration_ms").notNull(),
|
|
26
20
|
// Trace: came from boot-time auto-apply, explicit CLI, or CI step.
|
|
27
|
-
//
|
|
21
|
+
// Supports forensic questions about who initiated the operation and when.
|
|
28
22
|
appliedBy: text("applied_by").notNull().$type(),
|
|
29
23
|
// Optional human-readable annotation — surfaced in `ops <op>:status`.
|
|
30
24
|
notes: text("notes"),
|
package/dist/es-ops/runner.d.ts
CHANGED
|
@@ -3,23 +3,23 @@ import type { Registry } from "../engine/index.ts";
|
|
|
3
3
|
import type { EsOperationAppliedBy, SeedMigrationContext } from "./types.ts";
|
|
4
4
|
export type RunPendingSeedMigrationsArgs = {
|
|
5
5
|
readonly db: DbConnection;
|
|
6
|
-
/**
|
|
6
|
+
/** Absolute path to the seeds directory (typically <appRoot>/seeds). */
|
|
7
7
|
readonly seedsDir: string;
|
|
8
|
-
/** Factory
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
/** Factory that creates the context for each seed migration.
|
|
9
|
+
* The caller receives a DbRunner (connection OR active transaction); the
|
|
10
|
+
* runner invokes this once per migration within the transaction scope. */
|
|
11
11
|
readonly createContext: (dbRunner: DbRunner) => SeedMigrationContext;
|
|
12
12
|
/** Trace-marker: boot | cli | ci-pipeline. Landet in applied_by. */
|
|
13
13
|
readonly appliedBy: EsOperationAppliedBy;
|
|
14
|
-
/** Optional registry
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
/** Optional registry for dry-run validation: parses each seed file and
|
|
15
|
+
* checks that all referenced handler QNs exist in the registry
|
|
16
|
+
* BEFORE the migration runs. This catches camelCase typos and other QN
|
|
17
|
+
* drift at boot rather than in the middle of a write cycle.
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* If omitted, no dry run is performed (backward compatibility for tests
|
|
20
|
+
* that do not provide a registry). runProdApp supplies the correct one. */
|
|
21
21
|
readonly registry?: Registry;
|
|
22
|
-
/** Optional
|
|
22
|
+
/** Optional line sink; the default logs under the "es-ops/seed-migration" namespace. */
|
|
23
23
|
readonly logger?: (line: string) => void;
|
|
24
24
|
};
|
|
25
25
|
export type RunPendingSeedMigrationsResult = {
|