@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
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
// Self-Populating Settings
|
|
1
|
+
// Self-Populating Settings Hub.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// scope)
|
|
6
|
-
// r.screen/r.nav
|
|
3
|
+
// Derives the Settings UI automatically from the config keys declared in the
|
|
4
|
+
// registry: one parent nav per audience (scope), and one configEdit screen plus
|
|
5
|
+
// child nav per (feature × scope). App authors do not need manual
|
|
6
|
+
// r.screen/r.nav declarations.
|
|
7
7
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// (mask.title,
|
|
11
|
-
// (ENV-
|
|
8
|
+
// Only a key WITH `mask` is visible (see ConfigKeyDefinition): mask is the
|
|
9
|
+
// per-key intent for a “user-facing setting” and also supplies its label
|
|
10
|
+
// (mask.title, an i18n key). Keys without mask are internal plumbing
|
|
11
|
+
// (ENV-provisioned/computed) and are omitted.
|
|
12
12
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
13
|
+
// buildAppSchema merges the generated screens/navs into the FeatureSchema of
|
|
14
|
+
// the config feature (featureName "config"). The renderer qualifies the short
|
|
15
|
+
// ids/refs with "config", so this code consistently uses SHORT ids/parent/screen
|
|
16
|
+
// refs (buildNavRegistrySliceForApp qualifies them itself).
|
|
17
17
|
import { isOptionsQueryFieldRef } from "@cosmicdrift/kumiko-types/fields";
|
|
18
18
|
import { CONFIG_EDIT_ENTITY, fieldLabelKey } from "../i18n/required-surface-keys.js";
|
|
19
19
|
import { isEncryptedAtRest } from "./config-helpers.js";
|
|
@@ -22,14 +22,15 @@ import { createBooleanField, createNumberField, createSelectField, createTextFie
|
|
|
22
22
|
import { isKebabSegment, toKebab } from "./qualified-name.js";
|
|
23
23
|
import { isFieldsEditSection, sectionFieldSpecs, } from "./screen-helpers.js";
|
|
24
24
|
import { isOpenToAllGranted } from "./types/handlers.js";
|
|
25
|
-
// Namespace
|
|
26
|
-
// (find-or-create FeatureSchema).
|
|
27
|
-
// bundled
|
|
28
|
-
// bundled-features → framework
|
|
25
|
+
// Namespace under which buildAppSchema attaches the generated screens/navs
|
|
26
|
+
// (find-or-create FeatureSchema). It MUST match CONFIG_FEATURE from the config
|
|
27
|
+
// bundled feature. The framework cannot import that constant because of the
|
|
28
|
+
// bundled-features → framework dependency direction, so it is pinned here and
|
|
29
|
+
// verified by a bundled-feature pin test.
|
|
29
30
|
export const SETTINGS_HUB_FEATURE = "config";
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
31
|
+
// A dedicated workspace for workspace-mode apps (see buildAppSchema): Settings
|
|
32
|
+
// appear as their own switcher entry instead of cluttering the curated app
|
|
33
|
+
// workspaces. Apps without workspaces expose these navs through the no-filter path.
|
|
33
34
|
export const SETTINGS_HUB_WORKSPACE = "settings";
|
|
34
35
|
// Audience-Reihenfolge im Sidebar: Plattform vor Tenant vor Benutzer.
|
|
35
36
|
const SCOPE_ORDER = { system: 10, tenant: 20, user: 30 };
|
|
@@ -54,21 +55,21 @@ const SECRETS_SECTION_DESCRIPTION_KEY = "config.secrets.description";
|
|
|
54
55
|
const audienceNavShortId = (scope) => `audience-${scope}`;
|
|
55
56
|
// Generated post-boot, never via r.nav() — the boot validator exempts exactly these QNs (an app references one to place the settings group inline).
|
|
56
57
|
export const SETTINGS_HUB_AUDIENCE_NAV_QNS = SCOPES_BROAD_TO_DEEP.map((scope) => `${SETTINGS_HUB_FEATURE}:nav:${audienceNavShortId(scope)}`);
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
58
|
+
// At a scope broader than a key's home scope, only a role privileged for THAT
|
|
59
|
+
// level may set the cascade default—SystemAdmin at system, TenantAdmin/Admin at
|
|
60
|
+
// tenant. The home scope retains the full write set (unchanged behavior). Thus a
|
|
61
|
+
// tenant-home key such as SMTP also gets a SystemAdmin-only platform screen; a
|
|
62
|
+
// key whose write set names none of these roles gets no broader screen (the write
|
|
63
|
+
// set is the opt-in).
|
|
63
64
|
const ELEVATED_ROLES = {
|
|
64
65
|
system: ["SystemAdmin"],
|
|
65
66
|
tenant: ["TenantAdmin", "Admin"],
|
|
66
67
|
user: [],
|
|
67
68
|
};
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
69
|
+
// The internal machine actor (access.system). A key writable ONLY by this role
|
|
70
|
+
// is provisioned-not-user-facing and must not enter the human hub; otherwise the
|
|
71
|
+
// UI would render a field that the visible user cannot save. `as const` narrows
|
|
72
|
+
// the literal for the comparison.
|
|
72
73
|
const MACHINE_WRITE_ROLE = "system";
|
|
73
74
|
const NO_SELECTOR_GATING = {
|
|
74
75
|
pluginsByOwnerGroup: new Map(),
|
|
@@ -442,7 +443,7 @@ function buildScopeResult(scope, masked, secretsEnabled, secretsWriteHandler, de
|
|
|
442
443
|
: NO_SELECTOR_GATING;
|
|
443
444
|
const screens = [];
|
|
444
445
|
const navs = [];
|
|
445
|
-
// Audience
|
|
446
|
+
// Audience parent: grouping node without a screen.
|
|
446
447
|
navs.push({
|
|
447
448
|
id: audienceNavShortId(scope),
|
|
448
449
|
label: `config.settings.${scope}`,
|
|
@@ -624,10 +625,10 @@ function effectiveWriteRoles(def, scope) {
|
|
|
624
625
|
const elevated = ELEVATED_ROLES[scope];
|
|
625
626
|
return def.access.write.filter((r) => elevated.includes(r));
|
|
626
627
|
}
|
|
627
|
-
// navMembers
|
|
628
|
-
// admin.navMembers === ["orders:nav:list", ...]).
|
|
629
|
-
//
|
|
630
|
-
//
|
|
628
|
+
// navMembers contain QUALIFIED nav QNs (see build-app-schema.test:
|
|
629
|
+
// admin.navMembers === ["orders:nav:list", ...]). The generated navs live under
|
|
630
|
+
// SETTINGS_HUB_FEATURE, namely `config:nav:<shortId>`. Sorting provides stable
|
|
631
|
+
// landing-screen selection (firstNavScreenId iterates navMembers in order).
|
|
631
632
|
function buildSettingsWorkspace(navs) {
|
|
632
633
|
const navMembers = navs.map((n) => `${SETTINGS_HUB_FEATURE}:nav:${n.id}`).sort();
|
|
633
634
|
return {
|
|
@@ -636,11 +637,10 @@ function buildSettingsWorkspace(navs) {
|
|
|
636
637
|
label: "config.settings.title",
|
|
637
638
|
icon: "settings",
|
|
638
639
|
order: 1000,
|
|
639
|
-
// Union
|
|
640
|
-
//
|
|
641
|
-
//
|
|
642
|
-
//
|
|
643
|
-
// Switcher-Gate leakt.
|
|
640
|
+
// Union of the access rules for the already generated (machine-filtered) Hub
|
|
641
|
+
// navs; otherwise an unprivileged user would see an empty "Settings"
|
|
642
|
+
// switcher. Use the navs rather than `masked` so the machine-only
|
|
643
|
+
// "system" role, which appears in no nav, cannot leak into the switcher gate.
|
|
644
644
|
access: unionAccessRules(navs.map((n) => n.access)),
|
|
645
645
|
},
|
|
646
646
|
navMembers,
|
|
@@ -830,14 +830,14 @@ function maskOrder(k) {
|
|
|
830
830
|
function minMaskOrder(keys) {
|
|
831
831
|
return Math.min(...keys.map(maskOrder));
|
|
832
832
|
}
|
|
833
|
-
//
|
|
834
|
-
//
|
|
835
|
-
//
|
|
836
|
-
//
|
|
837
|
-
//
|
|
838
|
-
// per
|
|
839
|
-
//
|
|
840
|
-
//
|
|
833
|
+
// The hub is for editing: anyone allowed to WRITE at least one key in the group
|
|
834
|
+
// sees the Settings entry (write, not read). This keeps system-scope (write
|
|
835
|
+
// default `["system"]`) hidden from humans until the author opts in with
|
|
836
|
+
// write: SystemAdmin, while user-scope (write `all`) is shown to everyone.
|
|
837
|
+
// `all` can only be represented as openToAll in AccessRule; writes remain
|
|
838
|
+
// gated per key on the server.
|
|
839
|
+
// Access rules for navs are unioned: an openToAll nav opens the whole gate;
|
|
840
|
+
// otherwise roles are combined. Navs without access rules contribute nothing.
|
|
841
841
|
function unionAccessRules(rules) {
|
|
842
842
|
const roles = [];
|
|
843
843
|
for (const rule of rules) {
|
|
@@ -852,8 +852,8 @@ function unionAccessRules(rules) {
|
|
|
852
852
|
}
|
|
853
853
|
return rolesToAccess(roles);
|
|
854
854
|
}
|
|
855
|
-
// `all`
|
|
856
|
-
//
|
|
855
|
+
// `all` can only be represented as openToAll in AccessRule; writes remain gated
|
|
856
|
+
// per key on the server.
|
|
857
857
|
function rolesToAccess(roles) {
|
|
858
858
|
if (roles.includes("all"))
|
|
859
859
|
return {
|
|
@@ -1,33 +1,30 @@
|
|
|
1
|
-
// buildTarget — typed
|
|
1
|
+
// buildTarget — typed builder for TargetRef.
|
|
2
2
|
//
|
|
3
|
-
// **
|
|
4
|
-
// compile
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// **What it does.** Creates a TargetRef whose action and args are
|
|
4
|
+
// validated at compile time against the target feature's treeActions map.
|
|
5
|
+
// Magic-string URIs are never typed by hand; this is the single source
|
|
6
|
+
// of truth for cross-feature linking in the visual tree.
|
|
7
7
|
//
|
|
8
|
-
// **
|
|
9
|
-
// (NoArgsAction)
|
|
10
|
-
// - action
|
|
11
|
-
// -
|
|
8
|
+
// **How it is typed.** Function overloads separate actions without args
|
|
9
|
+
// (NoArgsAction) from actions with args (WithArgsAction). TypeScript validates:
|
|
10
|
+
// - action is a string literal from the keys of treeActions
|
|
11
|
+
// - for WithArgsAction, args is required and matches
|
|
12
12
|
// treeActions[action]["args"]
|
|
13
|
-
// -
|
|
13
|
+
// - for NoArgsAction, args is not allowed
|
|
14
14
|
//
|
|
15
|
-
// **
|
|
16
|
-
// (FeatureWithTreeActions).
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
// Implementation-Signature sieht args als unknown (TArgs ist erased zur
|
|
24
|
-
// Laufzeit). Der Guard validiert die Plain-Object-Convention runtime
|
|
25
|
-
// statt einen `as`-Cast zu setzen — siehe coding-standards.md.
|
|
15
|
+
// **Feature handle.** It is currently a generic constraint
|
|
16
|
+
// (FeatureWithTreeActions). Once treeActions becomes a pattern type in the
|
|
17
|
+
// feature AST and defineFeature exposes a treeActions slot with the same
|
|
18
|
+
// shape, real features will fit without changing the builder.
|
|
19
|
+
// Type guard for args: overloads guarantee a plain object, but the
|
|
20
|
+
// implementation signature sees args as unknown (TArgs is erased at
|
|
21
|
+
// runtime). The guard validates the plain-object convention at runtime
|
|
22
|
+
// instead of using an `as` cast.
|
|
26
23
|
function isArgsObject(v) {
|
|
27
24
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
28
25
|
}
|
|
29
|
-
// Implementation — args
|
|
30
|
-
//
|
|
26
|
+
// Implementation — args is `unknown` because the overloads' generic TArgs
|
|
27
|
+
// is erased at runtime. The plain-object convention is enforced via the type guard.
|
|
31
28
|
export function buildTarget(spec) {
|
|
32
29
|
if (!isArgsObject(spec.args)) {
|
|
33
30
|
return Object.freeze({
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
// biome-ignore-all lint/suspicious/noConsole: CLI tool — console.log is the output
|
|
2
1
|
// biome-ignore-all lint/style/noNonNullAssertion: regex capture-group indexing — match-success implies non-null capture
|
|
3
2
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
3
|
import { join, relative } from "node:path";
|
|
5
4
|
import { Glob } from "bun";
|
|
6
|
-
import { Project, SyntaxKind } from "ts-morph";
|
|
5
|
+
import { Node, Project, SyntaxKind } from "ts-morph";
|
|
7
6
|
export function analyzeHandlerArrow(arrowText) {
|
|
8
7
|
const text = arrowText.trim();
|
|
9
8
|
let handlerBodyText;
|
|
@@ -53,24 +52,30 @@ export function analyzeHandlerArrow(arrowText) {
|
|
|
53
52
|
l.startsWith("for ") ||
|
|
54
53
|
l.startsWith("while ") ||
|
|
55
54
|
l.startsWith("} else"));
|
|
56
|
-
const
|
|
55
|
+
const [, createVar, createExecutor] = handlerBodyText.match(/const\s+(\w+)\s*=\s*await\s+(\w+)\.create\s*\(/) ?? [];
|
|
57
56
|
const updateMatch = handlerBodyText.match(/const\s+(\w+)\s*=\s*await\s+(\w+)\.update\s*\(/);
|
|
58
57
|
const hasSimpleReturn = /return\s*\{[^}]*isSuccess:\s*true/.test(handlerBodyText);
|
|
59
|
-
if (
|
|
58
|
+
if (createVar !== undefined &&
|
|
59
|
+
createExecutor !== undefined &&
|
|
60
|
+
hasSimpleReturn &&
|
|
61
|
+
!hasConditionalLogic) {
|
|
60
62
|
isSimpleExecutorCreate = true;
|
|
61
|
-
executorName =
|
|
62
|
-
executorCreateVar =
|
|
63
|
-
executorCreateArgs = extractArgs(handlerBodyText,
|
|
63
|
+
executorName = createExecutor;
|
|
64
|
+
executorCreateVar = createVar;
|
|
65
|
+
executorCreateArgs = extractArgs(handlerBodyText, createExecutor, "create");
|
|
64
66
|
}
|
|
65
|
-
else if (
|
|
67
|
+
else if (createVar !== undefined &&
|
|
68
|
+
createExecutor !== undefined &&
|
|
69
|
+
hasSimpleReturn &&
|
|
70
|
+
hasConditionalLogic) {
|
|
66
71
|
// Try to match the guard pattern: if (!<var>.isSuccess) { return fail } return success
|
|
67
|
-
const guard = extractGuardedPattern(handlerBodyText,
|
|
72
|
+
const guard = extractGuardedPattern(handlerBodyText, createVar);
|
|
68
73
|
if (guard) {
|
|
69
74
|
isGuardedCreate = true;
|
|
70
75
|
guardConfig = guard;
|
|
71
|
-
executorName =
|
|
72
|
-
executorCreateVar =
|
|
73
|
-
executorCreateArgs = extractArgs(handlerBodyText,
|
|
76
|
+
executorName = createExecutor;
|
|
77
|
+
executorCreateVar = createVar;
|
|
78
|
+
executorCreateArgs = extractArgs(handlerBodyText, createExecutor, "create");
|
|
74
79
|
}
|
|
75
80
|
}
|
|
76
81
|
if (updateMatch &&
|
|
@@ -312,6 +317,8 @@ function inferSchemaType(objLiteral) {
|
|
|
312
317
|
const schemaProp = objLiteral.getProperty("schema");
|
|
313
318
|
if (!schemaProp)
|
|
314
319
|
return "unknown";
|
|
320
|
+
if (!Node.isPropertyAssignment(schemaProp))
|
|
321
|
+
return "unknown";
|
|
315
322
|
const init = schemaProp.getInitializer();
|
|
316
323
|
if (!init)
|
|
317
324
|
return "unknown";
|
|
@@ -431,7 +438,7 @@ function ensurePipelineImport(content) {
|
|
|
431
438
|
const importRegex = /import\s*\{([^}]*)\}\s*from\s*["']@cosmicdrift\/kumiko-framework\/engine["']/;
|
|
432
439
|
const match = content.match(importRegex);
|
|
433
440
|
if (match) {
|
|
434
|
-
const existingImports = match[1].trim();
|
|
441
|
+
const existingImports = (match[1] ?? "").trim();
|
|
435
442
|
const newImports = existingImports ? `${existingImports}, stepsPipeline` : "stepsPipeline";
|
|
436
443
|
return content.replace(importRegex, `import { ${newImports} } from "@cosmicdrift/kumiko-framework/engine"`);
|
|
437
444
|
}
|
|
@@ -10,9 +10,9 @@ export function isEncryptedAtRest(def) {
|
|
|
10
10
|
// --- Access Presets ---
|
|
11
11
|
export const access = {
|
|
12
12
|
all: ["all"], // @cast-boundary schema-walk
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
13
|
+
// Bundled features use "TenantAdmin", while app repositories historically use
|
|
14
|
+
// "Admin"; including both prevents the feature reference's writeRole from
|
|
15
|
+
// drifting.
|
|
16
16
|
admin: ["TenantAdmin", "Admin", "SystemAdmin"], // @cast-boundary schema-walk
|
|
17
17
|
systemAdmin: ["SystemAdmin"], // @cast-boundary schema-walk
|
|
18
18
|
system: ["system"], // @cast-boundary schema-walk
|
|
@@ -25,7 +25,7 @@ export const access = {
|
|
|
25
25
|
// also set via ctx.systemWriteAs (roles = [SYSTEM_ROLE]). Stays human-writable
|
|
26
26
|
// because checkWriteAccess only collapses to system-only when system is the
|
|
27
27
|
// SOLE writer (humanWriters empty). Composes any preset, so apps with custom
|
|
28
|
-
// roles get the same provisioning path
|
|
28
|
+
// roles get the same provisioning path.
|
|
29
29
|
withSystem: (roles) => [SYSTEM_ROLE, ...roles],
|
|
30
30
|
};
|
|
31
31
|
// --- Scope Defaults ---
|
package/dist/engine/constants.js
CHANGED
|
@@ -31,7 +31,7 @@ export const LifecycleHookTypes = {
|
|
|
31
31
|
// --- Config Scopes ---
|
|
32
32
|
// Value object satisfies the canonical ConfigScope union from kumiko-types —
|
|
33
33
|
// a scope added here without a matching kumiko-types member is now a
|
|
34
|
-
// compile error instead of silent drift
|
|
34
|
+
// compile error instead of silent drift.
|
|
35
35
|
export const ConfigScopes = {
|
|
36
36
|
system: "system",
|
|
37
37
|
tenant: "tenant",
|
|
@@ -65,7 +65,7 @@ void _concurrencyModeExhaustive;
|
|
|
65
65
|
// --- Money Currency Sources ---
|
|
66
66
|
// Canonical definition lives in kumiko-types/fields (client-safe, next to
|
|
67
67
|
// MoneyCurrencySource) so renderer code can import it without pulling in
|
|
68
|
-
// runtime-only engine code
|
|
68
|
+
// runtime-only engine code. Re-exported here for server callers
|
|
69
69
|
// already importing framework/engine (bundled-features/src/tenant-settings/
|
|
70
70
|
// constants.ts TenantSettingsConfig.currency reuses this same constant, so
|
|
71
71
|
// the two stay in sync by construction).
|
|
@@ -11,7 +11,7 @@ function inlineFormFieldMaps(screen) {
|
|
|
11
11
|
return [];
|
|
12
12
|
return screen.confirm !== undefined ? [screen.fields, screen.confirm.fields] : [screen.fields];
|
|
13
13
|
}
|
|
14
|
-
// `currency: { kind: "literal", code }`
|
|
14
|
+
// `currency: { kind: "literal", code }` is only meaningful for a
|
|
15
15
|
// code the app knows — a typo would otherwise render and submit amounts in a
|
|
16
16
|
// currency no formatter or rate table covers.
|
|
17
17
|
function validateLiteralCurrencyCode(where, field, currencies) {
|
|
@@ -106,7 +106,7 @@ export function createApp(config) {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
// A money field on an entity-less form screen names its own currency
|
|
109
|
-
// source
|
|
109
|
+
// source — a literal code has to be one the app actually knows,
|
|
110
110
|
// same rule the entity `defaultCurrency` check above applies.
|
|
111
111
|
for (const [screenId, screen] of Object.entries(feature.screens ?? {})) {
|
|
112
112
|
const inlineFields = inlineFormFieldMaps(screen);
|
|
@@ -52,13 +52,14 @@ export function defineFeature(name, setup, settings) {
|
|
|
52
52
|
throwObjectArgumentRequired(name, "requires");
|
|
53
53
|
state.requires.push(...resolveFeatureNamesArgs(args));
|
|
54
54
|
};
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
return Object.assign(fn, {
|
|
56
|
+
projection: (tableName) => {
|
|
57
|
+
state.requiredProjections.add(tableName);
|
|
58
|
+
},
|
|
59
|
+
step: (stepKind) => {
|
|
60
|
+
state.requiredSteps.add(stepKind);
|
|
61
|
+
},
|
|
62
|
+
});
|
|
62
63
|
})(),
|
|
63
64
|
optionalRequires(...args) {
|
|
64
65
|
state.optionalRequires.push(...resolveFeatureNamesArgs(args));
|
|
@@ -106,10 +107,10 @@ export function defineFeature(name, setup, settings) {
|
|
|
106
107
|
postDelete: state.phasedLifecycleHooks.postDelete,
|
|
107
108
|
preQuery: state.lifecycleHooks["preQuery"] ?? {},
|
|
108
109
|
postQuery: state.lifecycleHooks["postQuery"] ?? {},
|
|
109
|
-
// @cast-boundary engine-bridge —
|
|
110
|
-
//
|
|
111
|
-
// addLifecycleHook)
|
|
112
|
-
//
|
|
110
|
+
// @cast-boundary engine-bridge — hook registration erases the per-slot
|
|
111
|
+
// signatures to LifecycleHookFn (a union; see the cast in
|
|
112
|
+
// addLifecycleHook). The branches there are the only producers and write
|
|
113
|
+
// the correct type for each slot.
|
|
113
114
|
},
|
|
114
115
|
entityHooks: {
|
|
115
116
|
postSave: state.entityPostSave,
|
|
@@ -7,6 +7,10 @@ function pickPreTransactionPhases(def) {
|
|
|
7
7
|
...(def.beforeTransaction && { beforeTransaction: def.beforeTransaction }),
|
|
8
8
|
};
|
|
9
9
|
}
|
|
10
|
+
// `in` keeps both branches of the discriminated union readable regardless of narrowing.
|
|
11
|
+
function hasHandlerAndPerform(def) {
|
|
12
|
+
return ("handler" in def && def.handler !== undefined && "perform" in def && def.perform !== undefined);
|
|
13
|
+
}
|
|
10
14
|
export function defineWriteHandler(def,
|
|
11
15
|
// R6: a phantom rest-param. When the inferred response `TData` carries a
|
|
12
16
|
// Secret<> anywhere, ContainsSecret<TData> is `true` and this resolves to a
|
|
@@ -32,13 +36,10 @@ export function defineWriteHandler(def,
|
|
|
32
36
|
// The discriminated-union type-error
|
|
33
37
|
// "Type 'PipelineDef<...>' is not assignable to type 'undefined'."
|
|
34
38
|
// is functional but cryptic for less TS-experienced users; this throws
|
|
35
|
-
// a name-and-explanation error message instead.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const probe = def;
|
|
40
|
-
if (probe.handler !== undefined && probe.perform !== undefined) {
|
|
41
|
-
throw new Error(`defineWriteHandler("${def.name}"): both \`handler\` and \`perform\` are set. ` +
|
|
39
|
+
// a name-and-explanation error message instead.
|
|
40
|
+
const handlerName = def.name;
|
|
41
|
+
if (hasHandlerAndPerform(def)) {
|
|
42
|
+
throw new Error(`defineWriteHandler("${handlerName}"): both \`handler\` and \`perform\` are set. ` +
|
|
42
43
|
`Pick one — \`handler\` for the free-form async function, ` +
|
|
43
44
|
`\`perform: stepsPipeline(...)\` for the step-pipeline form. ` +
|
|
44
45
|
`(See step-vocabulary.md for which form fits.)`);
|
|
@@ -103,10 +104,10 @@ export function isEntityConventionQueryHandler(def) {
|
|
|
103
104
|
}
|
|
104
105
|
// A projectionList screen's query must resolve to { rows, nextCursor, total? }
|
|
105
106
|
// (CursorResult<T>) — the renderer used to read rowsQuery.data?.rows and
|
|
106
|
-
// silently show an empty list otherwise
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
107
|
+
// silently show an empty list otherwise; the renderer now guards against a
|
|
108
|
+
// malformed shape at runtime instead. Use this instead of defineQueryHandler
|
|
109
|
+
// for any query wired to a projectionList screen's `query` so the contract is
|
|
110
|
+
// documented at the definition site.
|
|
110
111
|
//
|
|
111
112
|
// Deliberately does NOT merge cursor/limit/sort/search into the input
|
|
112
113
|
// schema — that would change sortable/paginated derivation for existing
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// (run-pipeline.ts) looks them up by `kind` to dispatch run-calls.
|
|
5
5
|
//
|
|
6
6
|
// The registry is process-global; Tier-2 step opt-in via
|
|
7
|
-
// `r.requires.step("…")`
|
|
7
|
+
// `r.requires.step("…")` is a future pass.
|
|
8
8
|
const stepRegistry = new Map();
|
|
9
9
|
export function defineStep(def) {
|
|
10
10
|
const existing = stepRegistry.get(def.kind);
|
|
@@ -22,9 +22,9 @@ export type WorkflowDefinition<TPayload = unknown, TData = unknown, TAwaits exte
|
|
|
22
22
|
readonly name: string;
|
|
23
23
|
readonly trigger: WorkflowTrigger;
|
|
24
24
|
/**
|
|
25
|
-
* Declared expected events (
|
|
25
|
+
* Declared expected events (workflow-resume-loop.md) — the sole route
|
|
26
26
|
* a waitForEvent step can reach an event type. Read at registration time
|
|
27
|
-
* to build the static apply-map
|
|
27
|
+
* to build the static apply-map; values are plain event-type
|
|
28
28
|
* strings, not the branded AwaitedEventType the pipeline closure sees.
|
|
29
29
|
*/
|
|
30
30
|
readonly awaits?: TAwaits;
|
|
@@ -70,17 +70,17 @@ export type WorkflowInput<TPayload = unknown, TData = unknown, TAwaits extends R
|
|
|
70
70
|
*/
|
|
71
71
|
export declare function defineWorkflow<TPayload = unknown, TData = unknown, const TAwaits extends Record<string, string> = Record<string, string>>(input: WorkflowInput<TPayload, TData, TAwaits>): WorkflowDefinition<TPayload, TData, TAwaits>;
|
|
72
72
|
/**
|
|
73
|
-
*
|
|
73
|
+
* Snapshot-at-Start fingerprint. SHA-256 over the workflow's stable
|
|
74
74
|
* identity (name + trigger + serialized pipeline-closure source). Persisted
|
|
75
75
|
* in `workflow.run.started` and re-checked at every resume so a library-
|
|
76
76
|
* upgrade that changes the closure source surfaces as a loud
|
|
77
77
|
* `workflow-definition-changed` failure on in-flight runs instead of a
|
|
78
78
|
* silent semantic drift.
|
|
79
79
|
*
|
|
80
|
-
* Limitations (will be tightened
|
|
80
|
+
* Limitations (will be tightened with the Designer/AST layer):
|
|
81
81
|
* - `build.toString()` captures the closure source but not bindings —
|
|
82
82
|
* two definitions that import different external helpers with the
|
|
83
|
-
* same source bytes would collide. Acceptable
|
|
83
|
+
* same source bytes would collide. Acceptable because the
|
|
84
84
|
* fingerprint is a *change-detector*, not a deep semantic identity.
|
|
85
85
|
* - Minifiers / source-maps will produce different fingerprints across
|
|
86
86
|
* environments. Run-the-fingerprint-in-the-same-environment is the
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// defineWorkflow — define a persistent, suspendable workflow.
|
|
2
2
|
// Tier-3 / Workflow-only mount-point. See step-vocabulary.md Sample 2 for
|
|
3
3
|
// the full lifecycle (run.started → wait → resume → run.completed) and
|
|
4
|
-
//
|
|
4
|
+
// Snapshot-at-Start for the in-flight upgrade story.
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
6
|
/**
|
|
7
7
|
* Define a suspendable workflow.
|
|
@@ -39,27 +39,27 @@ export function defineWorkflow(input) {
|
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
42
|
+
* Snapshot-at-Start fingerprint. SHA-256 over the workflow's stable
|
|
43
43
|
* identity (name + trigger + serialized pipeline-closure source). Persisted
|
|
44
44
|
* in `workflow.run.started` and re-checked at every resume so a library-
|
|
45
45
|
* upgrade that changes the closure source surfaces as a loud
|
|
46
46
|
* `workflow-definition-changed` failure on in-flight runs instead of a
|
|
47
47
|
* silent semantic drift.
|
|
48
48
|
*
|
|
49
|
-
* Limitations (will be tightened
|
|
49
|
+
* Limitations (will be tightened with the Designer/AST layer):
|
|
50
50
|
* - `build.toString()` captures the closure source but not bindings —
|
|
51
51
|
* two definitions that import different external helpers with the
|
|
52
|
-
* same source bytes would collide. Acceptable
|
|
52
|
+
* same source bytes would collide. Acceptable because the
|
|
53
53
|
* fingerprint is a *change-detector*, not a deep semantic identity.
|
|
54
54
|
* - Minifiers / source-maps will produce different fingerprints across
|
|
55
55
|
* environments. Run-the-fingerprint-in-the-same-environment is the
|
|
56
56
|
* contract; cross-env replay is out of scope.
|
|
57
57
|
*/
|
|
58
58
|
export function computeDefinitionFingerprint(workflow) {
|
|
59
|
-
// `awaits` is part of the routing contract (
|
|
59
|
+
// `awaits` is part of the routing contract (workflow-resume-loop.md):
|
|
60
60
|
// changing which events a run waits on changes the run's behavior just
|
|
61
61
|
// like changing the step source does, so a changed declaration must flip
|
|
62
|
-
// the fingerprint and trip the
|
|
62
|
+
// the fingerprint and trip the stale-definition check on resume.
|
|
63
63
|
const material = JSON.stringify({
|
|
64
64
|
name: workflow.name,
|
|
65
65
|
trigger: workflow.trigger,
|
|
@@ -90,14 +90,14 @@ export const entityListSchema = z.object({
|
|
|
90
90
|
.object({
|
|
91
91
|
field: z.string(),
|
|
92
92
|
op: z.enum(["eq", "ne", "lt", "gt", "lte", "gte", "in"]),
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
93
|
+
// `unknown` at compile time; the server checks while building the WHERE
|
|
94
|
+
// clause that the value's type matches the field. z.unknown() accepts
|
|
95
|
+
// everything, so the type check occurs in executor.list.
|
|
96
96
|
value: z.unknown(),
|
|
97
97
|
})
|
|
98
98
|
.optional(),
|
|
99
|
-
// User-
|
|
100
|
-
// `filter
|
|
99
|
+
// User-selected faceted filters are dynamic and additive to the static
|
|
100
|
+
// `filter`; executor.list combines all of them with AND.
|
|
101
101
|
filters: z
|
|
102
102
|
.array(z.object({
|
|
103
103
|
field: z.string(),
|
|
@@ -277,10 +277,10 @@ export function defineEntityWriteHandler(name, entity, options) {
|
|
|
277
277
|
changes: dropEmptyWriteOnlyValues(entity, submitted.changes),
|
|
278
278
|
};
|
|
279
279
|
const streamTenantId = await foreignRowTenantFor(event.user, payload.id, db, ctx);
|
|
280
|
-
// skipUnchanged
|
|
280
|
+
// skipUnchanged: API-driven updates diff against the stored
|
|
281
281
|
// row so a resubmitted-but-identical field doesn't force a fresh
|
|
282
282
|
// pii/encrypted ciphertext. Direct executor.update() callers (e.g.
|
|
283
|
-
// KEK-rotation, the user-data-rights
|
|
283
|
+
// KEK-rotation, the user-data-rights backfill) don't go through
|
|
284
284
|
// this handler and keep today's always-re-encrypt behavior, which
|
|
285
285
|
// they rely on to intentionally force a fresh event/ciphertext.
|
|
286
286
|
return executor.update(payload, event.user, db, {
|
|
@@ -350,7 +350,7 @@ export function defineEntityQueryHandler(name, entity, options) {
|
|
|
350
350
|
const executor = createEventStoreExecutor(table, entity, { entityName });
|
|
351
351
|
let schema;
|
|
352
352
|
let handler;
|
|
353
|
-
//
|
|
353
|
+
// Server-side eager loading: when the entity has reference fields, the
|
|
354
354
|
// handler resolves the UUIDs against the referenced entities after the
|
|
355
355
|
// main query. The `_refs` property lands on every row; the renderer-side
|
|
356
356
|
// useReferenceLookup stays as a fallback (for apps that write custom
|
|
@@ -384,17 +384,16 @@ export function defineEntityQueryHandler(name, entity, options) {
|
|
|
384
384
|
case "list":
|
|
385
385
|
schema = entityListSchema;
|
|
386
386
|
handler = async (query, ctx) => {
|
|
387
|
-
//
|
|
388
|
-
//
|
|
389
|
-
//
|
|
390
|
-
//
|
|
391
|
-
// Runtime-Override holt das.
|
|
387
|
+
// Pass the SearchAdapter from ctx through so payload.search is executed
|
|
388
|
+
// against Meilisearch/InMem at runtime (remote combobox search). The
|
|
389
|
+
// executor is built at definition time and cannot know the adapter, so the
|
|
390
|
+
// runtime override supplies it.
|
|
392
391
|
const listPayload = query.payload; // @cast-boundary engine-payload
|
|
393
392
|
const db = dbFor(ctx);
|
|
394
393
|
const result = await executor.list(listPayload, query.user, db, {
|
|
395
394
|
...(ctx.searchAdapter !== undefined && { searchAdapter: ctx.searchAdapter }),
|
|
396
395
|
...(ctx.includeDeleted === true && { includeDeleted: true }),
|
|
397
|
-
//
|
|
396
|
+
// Registry access only exists at request time, so the
|
|
398
397
|
// searchable-reference cache + entity resolver travel through the
|
|
399
398
|
// same runtime-override extension point as searchAdapter above.
|
|
400
399
|
referenceSearch: {
|
package/dist/engine/event-def.js
CHANGED
|
@@ -2,7 +2,7 @@ import { normalizeEventPiiSubject } from "@cosmicdrift/kumiko-types/handlers";
|
|
|
2
2
|
import { ZodObject } from "zod";
|
|
3
3
|
import { qn, toKebab } from "./qualified-name.js";
|
|
4
4
|
// A payload field that still holds null/undefined after parsing — the case
|
|
5
|
-
// where an event-PII owner field yields no subject at append time
|
|
5
|
+
// where an event-PII owner field yields no subject at append time.
|
|
6
6
|
// A `.default(...)` accepts undefined but parses to a value, so it is not
|
|
7
7
|
// absentable.
|
|
8
8
|
function isAbsentable(field) {
|
|
@@ -90,7 +90,7 @@ function validateEventPiiFields(featureName, eventName, schema, piiFields) {
|
|
|
90
90
|
*/
|
|
91
91
|
export function createEventDef(options) {
|
|
92
92
|
const { feature, name: eventName, schema } = options;
|
|
93
|
-
// Fail-closed PII stance gate
|
|
93
|
+
// Fail-closed PII stance gate — runs before anything else so
|
|
94
94
|
// an untyped JS consumer (no compiler to stop it) still can't register
|
|
95
95
|
// an event without declaring whether its payload carries personal data.
|
|
96
96
|
const missingStanceError = () => new Error(`[Feature ${feature}] defineEvent("${eventName}") must declare an explicit PII stance. Pass { piiFields: { <payloadField>: { personal: { of: "<userIdField>" } } } } for payload fields owned by a user (or { personal: "tenant" } / { personal: "self" } for tenant- or record-scoped subjects), or { piiFields: "none" } when the payload holds none.`);
|