@cosmicdrift/kumiko-bundled-features 0.352.0 → 0.353.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.
Files changed (105) hide show
  1. package/dist/agent-tools/agent-manifest.js +8 -2
  2. package/dist/agent-tools/manifest-deny-qns.d.ts +3 -0
  3. package/dist/agent-tools/manifest-deny-qns.js +17 -0
  4. package/dist/agent-tools/tool-catalog.d.ts +2 -1
  5. package/dist/agent-tools/tool-catalog.js +4 -1
  6. package/dist/audit/handlers/list.query.js +13 -8
  7. package/dist/auth-email-password/handlers/signup-confirm.write.js +2 -3
  8. package/dist/auth-mfa/schema/user-mfa.d.ts +6 -0
  9. package/dist/auth-mfa/screens.js +2 -2
  10. package/dist/billing-foundation/checkout-core.d.ts +7 -0
  11. package/dist/billing-foundation/checkout-core.js +7 -2
  12. package/dist/billing-foundation/entities.d.ts +8 -0
  13. package/dist/billing-foundation/handlers/start-plan-checkout.write.js +7 -1
  14. package/dist/billing-foundation/projection.d.ts +8 -0
  15. package/dist/cap-counter/entity.d.ts +1 -0
  16. package/dist/cap-overview/access.js +2 -9
  17. package/dist/cap-overview/handlers/tenant-caps-list.query.js +2 -2
  18. package/dist/cap-overview/handlers/tenant-options.query.js +4 -5
  19. package/dist/compliance-profiles/schema/profile-selection.d.ts +8 -8
  20. package/dist/compliance-profiles-ops/handlers/tenants-missing-profile.query.js +2 -4
  21. package/dist/config/handlers/values.query.d.ts +3 -1
  22. package/dist/config/handlers/values.query.js +7 -2
  23. package/dist/config/table.d.ts +6 -0
  24. package/dist/custom-fields/entity.d.ts +4 -0
  25. package/dist/data-retention/schema/tenant-retention-override.d.ts +4 -0
  26. package/dist/delivery/public-names.js +1 -1
  27. package/dist/delivery/tables.d.ts +6 -0
  28. package/dist/document-ingest-foundation/entity.d.ts +4 -0
  29. package/dist/files-tenant-data/hooks.js +3 -2
  30. package/dist/folders/entity.d.ts +8 -0
  31. package/dist/folders/feature.js +2 -10
  32. package/dist/form-draft/db/queries/cleanup.d.ts +1 -1
  33. package/dist/form-draft/db/queries/cleanup.js +4 -3
  34. package/dist/form-draft/entity.d.ts +2 -0
  35. package/dist/form-draft/handlers/cleanup.job.d.ts +5 -1
  36. package/dist/form-draft/handlers/cleanup.job.js +11 -5
  37. package/dist/inbound-mail-foundation/entities.d.ts +25 -0
  38. package/dist/inbound-mail-foundation/projection.d.ts +20 -0
  39. package/dist/jobs/handlers/list.query.d.ts +2 -1
  40. package/dist/jobs/handlers/list.query.js +39 -14
  41. package/dist/jobs/i18n.js +1 -0
  42. package/dist/jobs/job-run-table.d.ts +6 -0
  43. package/dist/ledger/entity.d.ts +21 -8
  44. package/dist/ledger/entity.js +4 -0
  45. package/dist/ledger/schemas.js +8 -2
  46. package/dist/managed-pages/feature.js +28 -10
  47. package/dist/managed-pages/table.d.ts +12 -0
  48. package/dist/notes-history/entity.d.ts +12 -0
  49. package/dist/notes-history/feature.js +2 -10
  50. package/dist/personal-access-tokens/schema/api-token.d.ts +6 -0
  51. package/dist/personal-access-tokens/screens.js +14 -3
  52. package/dist/secrets/table.d.ts +1 -0
  53. package/dist/seo/feature.js +28 -6
  54. package/dist/sessions/schema/user-session.d.ts +4 -0
  55. package/dist/shared/assert-no-where-rule-in-ownership-write.d.ts +2 -0
  56. package/dist/shared/assert-no-where-rule-in-ownership-write.js +18 -0
  57. package/dist/shared/index.d.ts +2 -1
  58. package/dist/shared/index.js +2 -1
  59. package/dist/shared/rate-limited-text-response.d.ts +2 -0
  60. package/dist/shared/rate-limited-text-response.js +9 -0
  61. package/dist/tags/entity.d.ts +9 -0
  62. package/dist/tags/feature.js +2 -10
  63. package/dist/template-resolver/table.d.ts +40 -30
  64. package/dist/template-resolver/user-content-table.d.ts +30 -20
  65. package/dist/tenant/decrypt-tenant-names.d.ts +3 -0
  66. package/dist/tenant/decrypt-tenant-names.js +10 -0
  67. package/dist/tenant/handlers/me.query.js +5 -1
  68. package/dist/tenant/handlers/memberships.query.js +3 -2
  69. package/dist/tenant/handlers/tenant-directory.query.js +9 -3
  70. package/dist/tenant/index.d.ts +1 -0
  71. package/dist/tenant/index.js +1 -0
  72. package/dist/tenant/invitation-table.d.ts +14 -8
  73. package/dist/tenant/membership-table.d.ts +4 -0
  74. package/dist/tenant/schema/tenant.d.ts +16 -10
  75. package/dist/tenant/schema/tenant.js +4 -6
  76. package/dist/tenant/screens.js +1 -1
  77. package/dist/tenant-handover/move-entity-graph.js +11 -18
  78. package/dist/tenant-lifecycle/feature.js +1 -0
  79. package/dist/tenant-lifecycle/run-tenant-destroy.d.ts +3 -0
  80. package/dist/tenant-lifecycle/run-tenant-destroy.js +2 -0
  81. package/dist/tenant-lifecycle/stages.d.ts +2 -0
  82. package/dist/tenant-lifecycle/stages.js +7 -1
  83. package/dist/tier-engine/entity.d.ts +2 -0
  84. package/dist/tier-engine/tier-resolver.d.ts +2 -0
  85. package/dist/user/handlers/list.query.js +2 -2
  86. package/dist/user/schema/user.d.ts +20 -8
  87. package/dist/user-data-rights/boot-checks.js +5 -0
  88. package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.js +2 -2
  89. package/dist/user-data-rights/schema/download-attempt.d.ts +14 -0
  90. package/dist/user-data-rights/schema/download-token.d.ts +8 -0
  91. package/dist/user-data-rights/schema/export-job.d.ts +14 -8
  92. package/dist/waitlist/entity.d.ts +20 -6
  93. package/dist/workflow-runner/event-trigger.js +6 -3
  94. package/dist/workflow-runner/handlers/resume-run.write.js +3 -0
  95. package/package.json +9 -9
  96. package/src/agent-tools/changes.json +12 -0
  97. package/src/audit/changes.json +6 -0
  98. package/src/form-draft/changes.json +7 -1
  99. package/src/managed-pages/changes.json +6 -0
  100. package/src/seo/changes.json +5 -0
  101. package/src/tenant/changes.json +7 -0
  102. package/src/tenant-handover/changes.json +6 -0
  103. package/src/workflow-runner/changes.json +5 -0
  104. package/dist/shared/has-where-rule.d.ts +0 -2
  105. package/dist/shared/has-where-rule.js +0 -10
@@ -4,6 +4,7 @@ export declare function createNoteEntryEntity(access?: EntityDefinition["access"
4
4
  readonly type: "text";
5
5
  readonly description?: string;
6
6
  readonly maxLength?: number;
7
+ readonly minLength?: number;
7
8
  readonly required?: boolean;
8
9
  readonly searchable?: boolean;
9
10
  readonly sortable?: boolean;
@@ -25,6 +26,7 @@ export declare function createNoteEntryEntity(access?: EntityDefinition["access"
25
26
  readonly type: "text";
26
27
  readonly description?: string;
27
28
  readonly maxLength?: number;
29
+ readonly minLength?: number;
28
30
  readonly required?: boolean;
29
31
  readonly searchable?: boolean;
30
32
  readonly sortable?: boolean;
@@ -46,6 +48,7 @@ export declare function createNoteEntryEntity(access?: EntityDefinition["access"
46
48
  readonly type: "text";
47
49
  readonly description?: string;
48
50
  readonly maxLength?: number;
51
+ readonly minLength?: number;
49
52
  readonly required?: boolean;
50
53
  readonly searchable?: boolean;
51
54
  readonly sortable?: boolean;
@@ -67,6 +70,7 @@ export declare function createNoteEntryEntity(access?: EntityDefinition["access"
67
70
  readonly type: "text";
68
71
  readonly description?: string;
69
72
  readonly maxLength?: number;
73
+ readonly minLength?: number;
70
74
  readonly required?: boolean;
71
75
  readonly searchable?: boolean;
72
76
  readonly sortable?: boolean;
@@ -106,6 +110,7 @@ export declare const noteEntryEntity: EntityDefinition<{
106
110
  readonly type: "text";
107
111
  readonly description?: string;
108
112
  readonly maxLength?: number;
113
+ readonly minLength?: number;
109
114
  readonly required?: boolean;
110
115
  readonly searchable?: boolean;
111
116
  readonly sortable?: boolean;
@@ -127,6 +132,7 @@ export declare const noteEntryEntity: EntityDefinition<{
127
132
  readonly type: "text";
128
133
  readonly description?: string;
129
134
  readonly maxLength?: number;
135
+ readonly minLength?: number;
130
136
  readonly required?: boolean;
131
137
  readonly searchable?: boolean;
132
138
  readonly sortable?: boolean;
@@ -148,6 +154,7 @@ export declare const noteEntryEntity: EntityDefinition<{
148
154
  readonly type: "text";
149
155
  readonly description?: string;
150
156
  readonly maxLength?: number;
157
+ readonly minLength?: number;
151
158
  readonly required?: boolean;
152
159
  readonly searchable?: boolean;
153
160
  readonly sortable?: boolean;
@@ -169,6 +176,7 @@ export declare const noteEntryEntity: EntityDefinition<{
169
176
  readonly type: "text";
170
177
  readonly description?: string;
171
178
  readonly maxLength?: number;
179
+ readonly minLength?: number;
172
180
  readonly required?: boolean;
173
181
  readonly searchable?: boolean;
174
182
  readonly sortable?: boolean;
@@ -208,6 +216,7 @@ export declare function createNoteMentionEntity(): EntityDefinition<{
208
216
  readonly type: "text";
209
217
  readonly description?: string;
210
218
  readonly maxLength?: number;
219
+ readonly minLength?: number;
211
220
  readonly required?: boolean;
212
221
  readonly searchable?: boolean;
213
222
  readonly sortable?: boolean;
@@ -229,6 +238,7 @@ export declare function createNoteMentionEntity(): EntityDefinition<{
229
238
  readonly type: "text";
230
239
  readonly description?: string;
231
240
  readonly maxLength?: number;
241
+ readonly minLength?: number;
232
242
  readonly required?: boolean;
233
243
  readonly searchable?: boolean;
234
244
  readonly sortable?: boolean;
@@ -252,6 +262,7 @@ export declare const noteMentionEntity: EntityDefinition<{
252
262
  readonly type: "text";
253
263
  readonly description?: string;
254
264
  readonly maxLength?: number;
265
+ readonly minLength?: number;
255
266
  readonly required?: boolean;
256
267
  readonly searchable?: boolean;
257
268
  readonly sortable?: boolean;
@@ -273,6 +284,7 @@ export declare const noteMentionEntity: EntityDefinition<{
273
284
  readonly type: "text";
274
285
  readonly description?: string;
275
286
  readonly maxLength?: number;
287
+ readonly minLength?: number;
276
288
  readonly required?: boolean;
277
289
  readonly searchable?: boolean;
278
290
  readonly sortable?: boolean;
@@ -13,7 +13,7 @@
13
13
  // a new entry, not an edit; see entity.ts for why that still satisfies GDPR
14
14
  // erasure without a delete path.
15
15
  import { defineEntityListHandler, defineFeature, } from "@cosmicdrift/kumiko-framework/engine";
16
- import { hasWhereRule } from "../shared/index.js";
16
+ import { assertNoWhereRuleInOwnershipWrite } from "../shared/index.js";
17
17
  import { DEFAULT_NOTES_HISTORY_ACCESS, NOTES_HISTORY_FEATURE_NAME } from "./constants.js";
18
18
  import { createNoteEntryEntity, noteMentionEntity } from "./entity.js";
19
19
  import { createNoteEntryExecutor } from "./executor.js";
@@ -58,15 +58,7 @@ export function createNotesHistoryFeature(opts = {}) {
58
58
  opts.parents === undefined) {
59
59
  return notesHistoryFeature;
60
60
  }
61
- if (hasWhereRule(opts.ownership?.write)) {
62
- throw new Error("createNotesHistoryFeature({ ownership }): ownership.write must not contain a " +
63
- '`{ kind: "where" }` rule — where-rules are evaluated only at the SQL ' +
64
- "layer (the read path, via buildOwnershipClause). Write paths that " +
65
- "consult access.write (userCanCreateFieldRow/userCanWriteFieldRow) can't " +
66
- "evaluate them, so such a rule can only ever deny — boot validation " +
67
- "rejects it too (fw#2626). Use a `from()` rule for ownership.write, or " +
68
- "leave it unset.");
69
- }
61
+ assertNoWhereRuleInOwnershipWrite("createNotesHistoryFeature", opts.ownership?.write);
70
62
  // A mount that accepts no parents at all can never take a note write —
71
63
  // that's a config mistake, not a valid allowlist. Omit `parents` instead
72
64
  // of passing an empty array.
@@ -3,6 +3,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
3
3
  readonly type: "text";
4
4
  readonly description?: string;
5
5
  readonly maxLength?: number;
6
+ readonly minLength?: number;
6
7
  readonly required?: boolean;
7
8
  readonly searchable?: boolean;
8
9
  readonly sortable?: boolean;
@@ -24,6 +25,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
24
25
  readonly type: "text";
25
26
  readonly description?: string;
26
27
  readonly maxLength?: number;
28
+ readonly minLength?: number;
27
29
  readonly required?: boolean;
28
30
  readonly searchable?: boolean;
29
31
  readonly sortable?: boolean;
@@ -45,6 +47,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
45
47
  readonly type: "text";
46
48
  readonly description?: string;
47
49
  readonly maxLength?: number;
50
+ readonly minLength?: number;
48
51
  readonly required?: boolean;
49
52
  readonly searchable?: boolean;
50
53
  readonly sortable?: boolean;
@@ -66,6 +69,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
66
69
  readonly type: "text";
67
70
  readonly description?: string;
68
71
  readonly maxLength?: number;
72
+ readonly minLength?: number;
69
73
  readonly required?: boolean;
70
74
  readonly searchable?: boolean;
71
75
  readonly sortable?: boolean;
@@ -87,6 +91,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
87
91
  readonly type: "text";
88
92
  readonly description?: string;
89
93
  readonly maxLength?: number;
94
+ readonly minLength?: number;
90
95
  readonly required?: boolean;
91
96
  readonly searchable?: boolean;
92
97
  readonly sortable?: boolean;
@@ -108,6 +113,7 @@ export declare const apiTokenEntity: import("@cosmicdrift/kumiko-types/fields").
108
113
  readonly type: "text";
109
114
  readonly description?: string;
110
115
  readonly maxLength?: number;
116
+ readonly minLength?: number;
111
117
  readonly required?: boolean;
112
118
  readonly searchable?: boolean;
113
119
  readonly sortable?: boolean;
@@ -1,6 +1,11 @@
1
1
  import { i18nKey, MAX_LIST_LIMIT, } from "@cosmicdrift/kumiko-framework/engine";
2
- import { PAT_MINT_SCREEN_ID, PAT_SCREEN_ID, PatHandlers, PatQueries } from "./constants.js";
2
+ import { ACTION_FORM_ENTITY } from "@cosmicdrift/kumiko-framework/ui-types";
3
+ import { fieldOptionLabelKeyPrefix } from "@cosmicdrift/kumiko-headless";
4
+ import { PAT_FEATURE, PAT_MINT_SCREEN_ID, PAT_SCREEN_ID, PatHandlers, PatQueries, } from "./constants.js";
3
5
  const PAT_STATUS_OPTION_KEY_PREFIX = "pat.list.status.";
6
+ // Same keys the mint form's `scopes` multiSelect options resolve through
7
+ // (see patScopeOptionTranslations), so list and form show identical labels.
8
+ const PAT_SCOPE_OPTION_KEY_PREFIX = fieldOptionLabelKeyPrefix(PAT_FEATURE, ACTION_FORM_ENTITY, "scopes");
4
9
  // Both screens render only for tenants whose tier includes the feature; the
5
10
  // availability query is rejected by the feature gate otherwise.
6
11
  const PAT_SCREEN_VISIBLE_WHEN = {
@@ -20,11 +25,17 @@ export const patListScreen = {
20
25
  defaultSort: { field: "createdAt", dir: "desc" },
21
26
  columns: [
22
27
  { field: "name", label: i18nKey("pat.list.col.name") },
23
- { field: "prefix", label: i18nKey("pat.list.col.prefix") },
24
- { field: "scopes", label: i18nKey("pat.list.col.scopes") },
28
+ { field: "prefix", label: i18nKey("pat.list.col.prefix"), sortable: false },
29
+ {
30
+ field: "scopes",
31
+ label: i18nKey("pat.list.col.scopes"),
32
+ sortable: false,
33
+ renderer: { format: "enumOption", keyPrefix: PAT_SCOPE_OPTION_KEY_PREFIX },
34
+ },
25
35
  {
26
36
  field: "status",
27
37
  label: i18nKey("pat.list.col.status"),
38
+ sortable: false,
28
39
  renderer: { format: "enumOption", keyPrefix: PAT_STATUS_OPTION_KEY_PREFIX },
29
40
  },
30
41
  {
@@ -8,6 +8,7 @@ export declare const tenantSecretEntity: import("@cosmicdrift/kumiko-types/field
8
8
  readonly type: "text";
9
9
  readonly description?: string;
10
10
  readonly maxLength?: number;
11
+ readonly minLength?: number;
11
12
  readonly required?: boolean;
12
13
  readonly searchable?: boolean;
13
14
  readonly sortable?: boolean;
@@ -1,8 +1,10 @@
1
1
  // kumiko-lint-ignore app-feature-structure Phase-3 conversion tracked in #2312 (feature.ts > 300 lines)
2
2
  import { computeRevisionEtag } from "@cosmicdrift/kumiko-framework/api";
3
3
  import { defineFeature, SYSTEM_TENANT_ID, } from "@cosmicdrift/kumiko-framework/engine";
4
+ import { RateLimitError } from "@cosmicdrift/kumiko-framework/errors";
4
5
  import { LEGAL_ROUTES } from "../legal-pages/index.js";
5
6
  import { cachedSecurePageResponse } from "../page-render/index.js";
7
+ import { rateLimitedTextResponse } from "../shared/index.js";
6
8
  import { SEO_CONFIG_KEYS, SEO_DEFAULT_PATHS } from "./constants.js";
7
9
  import { seoConfigQuery } from "./handlers/seo-config.query.js";
8
10
  import { buildLlmsTxt } from "./llms-txt.js";
@@ -62,7 +64,10 @@ async function gatherEntries(opts, systemQuery, origin, host) {
62
64
  });
63
65
  }
64
66
  }
65
- catch {
67
+ catch (error) {
68
+ // A rate limit must not look like "pages removed" to a crawler.
69
+ if (error instanceof RateLimitError)
70
+ throw error;
66
71
  // managed-pages unreachable/not mounted — degrade to callback-only entries.
67
72
  }
68
73
  }
@@ -113,7 +118,15 @@ export function createSeoFeature(opts) {
113
118
  anonymous: true,
114
119
  handler: async (c, { systemQuery }) => {
115
120
  const { origin, host } = requestHost(c);
116
- const entries = await gatherEntries(opts, systemQuery, origin, host);
121
+ let entries;
122
+ try {
123
+ entries = await gatherEntries(opts, systemQuery, origin, host);
124
+ }
125
+ catch (error) {
126
+ if (error instanceof RateLimitError)
127
+ return rateLimitedTextResponse(error);
128
+ throw error;
129
+ }
117
130
  const xml = buildSitemapXml(entries);
118
131
  const etag = computeRevisionEtag([host, xml]);
119
132
  return cachedSecurePageResponse(c.req.raw, {
@@ -133,10 +146,19 @@ export function createSeoFeature(opts) {
133
146
  const tenantId = opts.managedPages
134
147
  ? ((await opts.managedPages.resolveApexTenant(host)) ?? SYSTEM_TENANT_ID)
135
148
  : SYSTEM_TENANT_ID;
136
- const [entries, seoConfig] = await Promise.all([
137
- gatherEntries(opts, systemQuery, origin, host),
138
- readSeoConfig(systemQuery, tenantId),
139
- ]);
149
+ let entries;
150
+ let seoConfig;
151
+ try {
152
+ [entries, seoConfig] = await Promise.all([
153
+ gatherEntries(opts, systemQuery, origin, host),
154
+ readSeoConfig(systemQuery, tenantId),
155
+ ]);
156
+ }
157
+ catch (error) {
158
+ if (error instanceof RateLimitError)
159
+ return rateLimitedTextResponse(error);
160
+ throw error;
161
+ }
140
162
  const sections = entries.length > 0
141
163
  ? [
142
164
  {
@@ -3,6 +3,7 @@ export declare const userSessionEntity: import("@cosmicdrift/kumiko-types/fields
3
3
  readonly type: "text";
4
4
  readonly description?: string;
5
5
  readonly maxLength?: number;
6
+ readonly minLength?: number;
6
7
  readonly required?: boolean;
7
8
  readonly searchable?: boolean;
8
9
  readonly sortable?: boolean;
@@ -24,6 +25,7 @@ export declare const userSessionEntity: import("@cosmicdrift/kumiko-types/fields
24
25
  readonly type: "text";
25
26
  readonly description?: string;
26
27
  readonly maxLength?: number;
28
+ readonly minLength?: number;
27
29
  readonly required?: boolean;
28
30
  readonly searchable?: boolean;
29
31
  readonly sortable?: boolean;
@@ -105,6 +107,7 @@ export declare const userSessionEntity: import("@cosmicdrift/kumiko-types/fields
105
107
  readonly type: "text";
106
108
  readonly description?: string;
107
109
  readonly maxLength?: number;
110
+ readonly minLength?: number;
108
111
  readonly required?: boolean;
109
112
  readonly searchable?: boolean;
110
113
  readonly sortable?: boolean;
@@ -126,6 +129,7 @@ export declare const userSessionEntity: import("@cosmicdrift/kumiko-types/fields
126
129
  readonly type: "text";
127
130
  readonly description?: string;
128
131
  readonly maxLength?: number;
132
+ readonly minLength?: number;
129
133
  readonly required?: boolean;
130
134
  readonly searchable?: boolean;
131
135
  readonly sortable?: boolean;
@@ -0,0 +1,2 @@
1
+ import type { OwnershipMap } from "@cosmicdrift/kumiko-framework/engine";
2
+ export declare function assertNoWhereRuleInOwnershipWrite(factoryName: string, writeOwnership: OwnershipMap | undefined): void;
@@ -0,0 +1,18 @@
1
+ // Boot validation rejects `{ kind: "where" }` in access.write too (fw#2626);
2
+ // failing at factory time names the option instead of an entity scope.
3
+ export function assertNoWhereRuleInOwnershipWrite(factoryName, writeOwnership) {
4
+ // skip: no write ownership declared, nothing to validate
5
+ if (!writeOwnership)
6
+ return;
7
+ const hasWhereRule = Object.values(writeOwnership).some((rule) => rule !== "all" && rule.kind === "where");
8
+ // skip: no where-rule present, the option is valid
9
+ if (!hasWhereRule)
10
+ return;
11
+ throw new Error(`${factoryName}({ ownership }): ownership.write must not contain a ` +
12
+ '`{ kind: "where" }` rule — where-rules are evaluated only at the SQL ' +
13
+ "layer (the read path, via buildOwnershipClause). Write paths that " +
14
+ "consult access.write (userCanCreateFieldRow/userCanWriteFieldRow) can't " +
15
+ "evaluate them, so such a rule can only ever deny — boot validation " +
16
+ "rejects it too (fw#2626). Use a `from()` rule for ownership.write, or " +
17
+ "leave it unset.");
18
+ }
@@ -1,17 +1,18 @@
1
1
  export { collectErasureFailure, throwIfErasureFailed } from "./assert-erased.js";
2
+ export { assertNoWhereRuleInOwnershipWrite } from "./assert-no-where-rule-in-ownership-write.js";
2
3
  export { type ChunkedMigrationOptions, type ChunkedMigrationResult, type ChunkedMigrationStopReason, type MigrationRowOutcome, runChunkedMigration, } from "./chunked-entity-migration.js";
3
4
  export { classifyStoredEnvelope, type StoredEnvelopeClassification, } from "./classify-stored-envelope.js";
4
5
  export { decryptStoredPii } from "./decrypt-stored-pii.js";
5
6
  export { encryptForDirectWrite } from "./encrypt-for-direct-write.js";
6
7
  export { entitiesOf } from "./entities-of.js";
7
8
  export { isWithinGracePeriod } from "./grace-period.js";
8
- export { hasWhereRule } from "./has-where-rule.js";
9
9
  export { isIdentityV3Hash, verifyIdentityV3Hash } from "./identity-v3-hash.js";
10
10
  export { isTenantDb } from "./is-tenant-db.js";
11
11
  export { createLockoutCounter, type LockoutCounterState } from "./lockout-counter.js";
12
12
  export { mapWithConcurrency } from "./map-with-concurrency.js";
13
13
  export { denyUnlessJoinRowParentVisible, joinRowParentIsVisible, parentRowIsVisible, } from "./parent-visibility.js";
14
14
  export { hashPassword, verifyDummyPassword, verifyPassword } from "./password-hashing.js";
15
+ export { rateLimitedTextResponse } from "./rate-limited-text-response.js";
15
16
  export { redactBotTokens, redactEmailAddresses, redactErrorText, redactUrls, } from "./redact.js";
16
17
  export { type RowBoundGrantResult, redeemRowBoundGrant, signRowBoundGrant, } from "./row-bound-grant.js";
17
18
  export { runInSubTransaction } from "./run-in-sub-transaction.js";
@@ -1,17 +1,18 @@
1
1
  export { collectErasureFailure, throwIfErasureFailed } from "./assert-erased.js";
2
+ export { assertNoWhereRuleInOwnershipWrite } from "./assert-no-where-rule-in-ownership-write.js";
2
3
  export { runChunkedMigration, } from "./chunked-entity-migration.js";
3
4
  export { classifyStoredEnvelope, } from "./classify-stored-envelope.js";
4
5
  export { decryptStoredPii } from "./decrypt-stored-pii.js";
5
6
  export { encryptForDirectWrite } from "./encrypt-for-direct-write.js";
6
7
  export { entitiesOf } from "./entities-of.js";
7
8
  export { isWithinGracePeriod } from "./grace-period.js";
8
- export { hasWhereRule } from "./has-where-rule.js";
9
9
  export { isIdentityV3Hash, verifyIdentityV3Hash } from "./identity-v3-hash.js";
10
10
  export { isTenantDb } from "./is-tenant-db.js";
11
11
  export { createLockoutCounter } from "./lockout-counter.js";
12
12
  export { mapWithConcurrency } from "./map-with-concurrency.js";
13
13
  export { denyUnlessJoinRowParentVisible, joinRowParentIsVisible, parentRowIsVisible, } from "./parent-visibility.js";
14
14
  export { hashPassword, verifyDummyPassword, verifyPassword } from "./password-hashing.js";
15
+ export { rateLimitedTextResponse } from "./rate-limited-text-response.js";
15
16
  export { redactBotTokens, redactEmailAddresses, redactErrorText, redactUrls, } from "./redact.js";
16
17
  export { redeemRowBoundGrant, signRowBoundGrant, } from "./row-bound-grant.js";
17
18
  export { runInSubTransaction } from "./run-in-sub-transaction.js";
@@ -0,0 +1,2 @@
1
+ import type { RateLimitError } from "@cosmicdrift/kumiko-framework/errors";
2
+ export declare function rateLimitedTextResponse(error: RateLimitError): Response;
@@ -0,0 +1,9 @@
1
+ export function rateLimitedTextResponse(error) {
2
+ return new Response("rate limited", {
3
+ status: 429,
4
+ headers: {
5
+ "content-type": "text/plain; charset=utf-8",
6
+ "retry-after": String(error.details.retryAfterSeconds),
7
+ },
8
+ });
9
+ }
@@ -4,6 +4,7 @@ export declare const tagEntity: EntityDefinition<{
4
4
  readonly type: "text";
5
5
  readonly description?: string;
6
6
  readonly maxLength?: number;
7
+ readonly minLength?: number;
7
8
  readonly required?: boolean;
8
9
  readonly searchable?: boolean;
9
10
  readonly sortable?: boolean;
@@ -25,6 +26,7 @@ export declare const tagEntity: EntityDefinition<{
25
26
  readonly type: "text";
26
27
  readonly description?: string;
27
28
  readonly maxLength?: number;
29
+ readonly minLength?: number;
28
30
  readonly required?: boolean;
29
31
  readonly searchable?: boolean;
30
32
  readonly sortable?: boolean;
@@ -46,6 +48,7 @@ export declare const tagEntity: EntityDefinition<{
46
48
  readonly type: "text";
47
49
  readonly description?: string;
48
50
  readonly maxLength?: number;
51
+ readonly minLength?: number;
49
52
  readonly required?: boolean;
50
53
  readonly searchable?: boolean;
51
54
  readonly sortable?: boolean;
@@ -69,6 +72,7 @@ export declare function createTagAssignmentEntity(access?: EntityDefinition["acc
69
72
  readonly type: "text";
70
73
  readonly description?: string;
71
74
  readonly maxLength?: number;
75
+ readonly minLength?: number;
72
76
  readonly required?: boolean;
73
77
  readonly searchable?: boolean;
74
78
  readonly sortable?: boolean;
@@ -90,6 +94,7 @@ export declare function createTagAssignmentEntity(access?: EntityDefinition["acc
90
94
  readonly type: "text";
91
95
  readonly description?: string;
92
96
  readonly maxLength?: number;
97
+ readonly minLength?: number;
93
98
  readonly required?: boolean;
94
99
  readonly searchable?: boolean;
95
100
  readonly sortable?: boolean;
@@ -111,6 +116,7 @@ export declare function createTagAssignmentEntity(access?: EntityDefinition["acc
111
116
  readonly type: "text";
112
117
  readonly description?: string;
113
118
  readonly maxLength?: number;
119
+ readonly minLength?: number;
114
120
  readonly required?: boolean;
115
121
  readonly searchable?: boolean;
116
122
  readonly sortable?: boolean;
@@ -134,6 +140,7 @@ export declare const tagAssignmentEntity: EntityDefinition<{
134
140
  readonly type: "text";
135
141
  readonly description?: string;
136
142
  readonly maxLength?: number;
143
+ readonly minLength?: number;
137
144
  readonly required?: boolean;
138
145
  readonly searchable?: boolean;
139
146
  readonly sortable?: boolean;
@@ -155,6 +162,7 @@ export declare const tagAssignmentEntity: EntityDefinition<{
155
162
  readonly type: "text";
156
163
  readonly description?: string;
157
164
  readonly maxLength?: number;
165
+ readonly minLength?: number;
158
166
  readonly required?: boolean;
159
167
  readonly searchable?: boolean;
160
168
  readonly sortable?: boolean;
@@ -176,6 +184,7 @@ export declare const tagAssignmentEntity: EntityDefinition<{
176
184
  readonly type: "text";
177
185
  readonly description?: string;
178
186
  readonly maxLength?: number;
187
+ readonly minLength?: number;
179
188
  readonly required?: boolean;
180
189
  readonly searchable?: boolean;
181
190
  readonly sortable?: boolean;
@@ -18,7 +18,7 @@
18
18
  // entityList/entityEdit catalog screens (legacy QNs stay for TagManager).
19
19
  // Deferred: optional host-projection decoration (`wireTagsFor`), search indexing.
20
20
  import { defineEntityDetailHandler, defineEntityListHandler, defineEntityUpdateHandler, defineFeature, } from "@cosmicdrift/kumiko-framework/engine";
21
- import { hasWhereRule } from "../shared/index.js";
21
+ import { assertNoWhereRuleInOwnershipWrite } from "../shared/index.js";
22
22
  import { DEFAULT_TAG_ACCESS, TAGS_FEATURE_NAME } from "./constants.js";
23
23
  import { createTagAssignmentEntity, tagEntity } from "./entity.js";
24
24
  import { createTagAssignmentExecutor } from "./executor.js";
@@ -109,15 +109,7 @@ export function createTagsFeature(opts = {}) {
109
109
  "assignment row. Omit `parents` to keep every registered entity " +
110
110
  "admissible as a host instead.");
111
111
  }
112
- if (hasWhereRule(opts.ownership?.write)) {
113
- throw new Error("createTagsFeature({ ownership }): ownership.write must not contain a " +
114
- '`{ kind: "where" }` rule — where-rules are evaluated only at the SQL ' +
115
- "layer (the read path, via buildOwnershipClause). Write paths that " +
116
- "consult access.write (userCanCreateFieldRow/userCanWriteFieldRow) can't " +
117
- "evaluate them, so such a rule can only ever deny — boot validation " +
118
- "rejects it too (fw#2626). Use a `from()` rule for ownership.write, or " +
119
- "leave it unset.");
120
- }
112
+ assertNoWhereRuleInOwnershipWrite("createTagsFeature", opts.ownership?.write);
121
113
  const access = resolveAccess(opts);
122
114
  return defineFeature(TAGS_FEATURE_NAME, (r) => registerTags(r, access, opts.toggleable, opts.ownership, opts.parents));
123
115
  }