@cosmicdrift/kumiko-bundled-features 0.236.1 → 0.237.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +9 -9
- package/src/admin-shell/feature.ts +4 -0
- package/src/agent-tools/__tests__/agent-doc-lint.test.ts +53 -1
- package/src/audit/feature.ts +4 -0
- package/src/audit/handlers/details.query.ts +2 -0
- package/src/audit/handlers/list.query.ts +2 -0
- package/src/auth-email-password/handlers/change-password.write.ts +2 -0
- package/src/auth-email-password/handlers/confirm-account-unlock.write.ts +1 -0
- package/src/auth-email-password/handlers/invite-accept-with-login.write.ts +1 -0
- package/src/auth-email-password/handlers/invite-accept.write.ts +1 -0
- package/src/auth-email-password/handlers/invite-create.write.ts +2 -0
- package/src/auth-email-password/handlers/invite-signup-complete.write.ts +1 -0
- package/src/auth-email-password/handlers/login.write.ts +2 -0
- package/src/auth-email-password/handlers/logout.write.ts +2 -0
- package/src/auth-email-password/handlers/request-account-unlock.write.ts +2 -0
- package/src/auth-email-password/handlers/request-email-verification.write.ts +2 -0
- package/src/auth-email-password/handlers/request-password-reset.write.ts +2 -0
- package/src/auth-email-password/handlers/reset-password.write.ts +1 -0
- package/src/auth-email-password/handlers/self-registration-status.query.ts +2 -0
- package/src/auth-email-password/handlers/signup-confirm.write.ts +1 -0
- package/src/auth-email-password/handlers/signup-request.write.ts +2 -0
- package/src/auth-email-password/handlers/token-request-handler.ts +4 -0
- package/src/auth-email-password/handlers/verify-email.write.ts +1 -0
- package/src/auth-mfa/feature.ts +2 -0
- package/src/auth-mfa/handlers/disable.write.ts +3 -0
- package/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +2 -0
- package/src/auth-mfa/handlers/enable-confirm.write.ts +2 -0
- package/src/auth-mfa/handlers/enable-start-preauth.write.ts +2 -0
- package/src/auth-mfa/handlers/enable-start.write.ts +2 -0
- package/src/auth-mfa/handlers/regenerate-recovery.write.ts +3 -0
- package/src/auth-mfa/handlers/status.query.ts +2 -0
- package/src/auth-mfa/handlers/verify.write.ts +2 -0
- package/src/auth-mfa/schema/user-mfa.ts +2 -0
- package/src/billing-foundation/handlers/create-checkout-session.write.ts +2 -0
- package/src/billing-foundation/handlers/create-portal-session.write.ts +2 -0
- package/src/billing-foundation/handlers/list-subscriptions.query.ts +2 -0
- package/src/billing-foundation/handlers/process-event.write.ts +1 -0
- package/src/cap-counter/entity.ts +2 -0
- package/src/cap-counter/feature.ts +7 -1
- package/src/cap-counter/handlers/get-counter.query.ts +2 -0
- package/src/cap-counter/handlers/increment-rolling.write.ts +1 -0
- package/src/cap-counter/handlers/increment.write.ts +1 -0
- package/src/cap-counter/handlers/mark-soft-warned.write.ts +1 -0
- package/src/cap-overview/handlers/caps-usage.query.ts +2 -0
- package/src/cap-overview/handlers/tenant-caps-list.query.ts +2 -0
- package/src/cap-overview/handlers/tenant-options.query.ts +2 -0
- package/src/channel-in-app/handlers/inbox.query.ts +2 -0
- package/src/channel-in-app/handlers/mark-all-read.write.ts +2 -0
- package/src/channel-in-app/handlers/mark-read.write.ts +2 -0
- package/src/channel-in-app/handlers/unread-count.query.ts +2 -0
- package/src/compliance-profiles/handlers/for-tenant.query.ts +2 -0
- package/src/compliance-profiles/handlers/list-profiles.query.ts +2 -0
- package/src/compliance-profiles/handlers/needs-profile.query.ts +2 -0
- package/src/compliance-profiles/handlers/set-profile.write.ts +2 -0
- package/src/compliance-profiles/handlers/sub-processors.query.ts +2 -0
- package/src/compliance-profiles-ops/handlers/tenants-missing-profile.query.ts +2 -0
- package/src/config/handlers/cascade.query.ts +2 -0
- package/src/config/handlers/readiness.query.ts +2 -0
- package/src/config/handlers/reset.write.ts +3 -0
- package/src/config/handlers/schema.query.ts +2 -0
- package/src/config/handlers/set.write.ts +2 -0
- package/src/config/handlers/values.query.ts +2 -0
- package/src/crypto-shredding/handlers/forget-subject.write.ts +3 -0
- package/src/custom-fields/entity.ts +2 -0
- package/src/custom-fields/feature.ts +2 -0
- package/src/custom-fields/handlers/clear-custom-field.write.ts +2 -0
- package/src/custom-fields/handlers/define-system-field.write.ts +2 -0
- package/src/custom-fields/handlers/define-tenant-field.write.ts +2 -0
- package/src/custom-fields/handlers/delete-system-field.write.ts +3 -0
- package/src/custom-fields/handlers/delete-tenant-field.write.ts +3 -0
- package/src/custom-fields/handlers/set-custom-field.write.ts +2 -0
- package/src/custom-fields/handlers/update-tenant-field.write.ts +2 -0
- package/src/data-retention/handlers/policy-for.query.ts +2 -0
- package/src/delivery/handlers/log.query.ts +2 -0
- package/src/delivery/handlers/preferences.query.ts +2 -0
- package/src/delivery/handlers/set-preference.write.ts +2 -0
- package/src/feature-toggles/handlers/list.query.ts +2 -0
- package/src/feature-toggles/handlers/registered.query.ts +2 -0
- package/src/feature-toggles/handlers/set.write.ts +2 -0
- package/src/file-derivatives/handlers/public-variant.query.ts +1 -0
- package/src/folders/entity.ts +4 -0
- package/src/folders/feature.ts +35 -5
- package/src/folders/handlers/clear-folder.write.ts +2 -0
- package/src/folders/handlers/delete-folder.write.ts +3 -0
- package/src/folders/handlers/set-folder.write.ts +2 -0
- package/src/form-draft/handlers/discard.write.ts +3 -0
- package/src/form-draft/handlers/get.query.ts +2 -0
- package/src/form-draft/handlers/list.query.ts +2 -0
- package/src/form-draft/handlers/save.write.ts +2 -0
- package/src/inbound-mail-foundation/handlers/connect-account.write.ts +2 -0
- package/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +3 -0
- package/src/inbound-mail-foundation/handlers/ingest-message.write.ts +1 -0
- package/src/inbound-mail-foundation/handlers/list-accounts.query.ts +2 -0
- package/src/inbound-mail-foundation/handlers/list-messages.query.ts +2 -0
- package/src/inbound-mail-foundation/handlers/update-account.write.ts +2 -0
- package/src/jobs/feature.ts +4 -0
- package/src/jobs/handlers/catalog.query.ts +2 -0
- package/src/jobs/handlers/detail.query.ts +2 -0
- package/src/jobs/handlers/list.query.ts +2 -0
- package/src/jobs/handlers/retry.write.ts +2 -0
- package/src/jobs/handlers/trigger.write.ts +2 -0
- package/src/ledger/entity.ts +6 -0
- package/src/ledger/feature.ts +70 -10
- package/src/ledger/handlers/confirm-schedule-period.write.ts +2 -0
- package/src/ledger/handlers/create-transaction.write.ts +2 -0
- package/src/ledger/handlers/reports.query.ts +6 -0
- package/src/ledger/handlers/reverse-transaction.write.ts +2 -0
- package/src/managed-pages/feature.ts +4 -23
- package/src/managed-pages/handlers/branding.query.ts +2 -0
- package/src/managed-pages/handlers/by-slug.query.ts +2 -0
- package/src/managed-pages/handlers/by-tenant-published.query.ts +2 -0
- package/src/managed-pages/handlers/page-crud.ts +49 -0
- package/src/managed-pages/handlers/set.write.ts +2 -0
- package/src/managed-pages/table.ts +2 -0
- package/src/notes-history/entity.ts +2 -0
- package/src/notes-history/feature.ts +7 -1
- package/src/notes-history/handlers/add-note.write.ts +2 -0
- package/src/personal-access-tokens/feature.ts +2 -0
- package/src/personal-access-tokens/handlers/available-scopes.query.ts +2 -0
- package/src/personal-access-tokens/handlers/create.write.ts +2 -0
- package/src/personal-access-tokens/handlers/list.query.ts +2 -0
- package/src/personal-access-tokens/handlers/revoke.write.ts +3 -0
- package/src/rate-limiting/handlers/status.query.ts +2 -0
- package/src/readiness/handlers/status.query.ts +2 -0
- package/src/secrets/handlers/delete.write.ts +3 -0
- package/src/secrets/handlers/list.query.ts +2 -0
- package/src/secrets/handlers/set.write.ts +2 -0
- package/src/secrets/table.ts +2 -0
- package/src/seo/handlers/seo-config.query.ts +2 -0
- package/src/sessions/handlers/detail.query.ts +2 -0
- package/src/sessions/handlers/list.query.ts +2 -0
- package/src/sessions/handlers/mine.query.ts +2 -0
- package/src/sessions/handlers/revoke-all-for-user.write.ts +3 -0
- package/src/sessions/handlers/revoke-all-others.write.ts +3 -0
- package/src/sessions/handlers/revoke.write.ts +3 -0
- package/src/tags/entity.ts +4 -0
- package/src/tags/feature.ts +39 -6
- package/src/tags/handlers/assign-tag.write.ts +2 -0
- package/src/tags/handlers/create-tag.write.ts +2 -0
- package/src/tags/handlers/delete-tag.write.ts +3 -0
- package/src/tags/handlers/remove-tag.write.ts +2 -0
- package/src/tags/handlers/update-tag.write.ts +2 -0
- package/src/template-resolver/handlers/by-slug.query.ts +2 -0
- package/src/template-resolver/handlers/by-tenant.query.ts +2 -0
- package/src/template-resolver/handlers/collection-item.query.ts +4 -0
- package/src/template-resolver/handlers/collection-list.query.ts +4 -0
- package/src/template-resolver/handlers/collection-set.write.ts +4 -0
- package/src/template-resolver/handlers/find-by-id.query.ts +2 -0
- package/src/template-resolver/handlers/list.query.ts +2 -0
- package/src/template-resolver/handlers/set.write.ts +2 -0
- package/src/template-resolver/handlers/toggle-status.write.ts +12 -3
- package/src/template-resolver/handlers/upsert-system.write.ts +2 -0
- package/src/template-resolver/handlers/upsert-tenant.write.ts +2 -0
- package/src/tenant/feature.ts +15 -3
- package/src/tenant/handlers/active-tenant-ids.query.ts +2 -0
- package/src/tenant/handlers/add-member.write.ts +2 -0
- package/src/tenant/handlers/cancel-invitation.write.ts +2 -0
- package/src/tenant/handlers/create.write.ts +2 -0
- package/src/tenant/handlers/invitations.query.ts +2 -0
- package/src/tenant/handlers/list.query.ts +2 -0
- package/src/tenant/handlers/me.query.ts +2 -0
- package/src/tenant/handlers/members.query.ts +2 -0
- package/src/tenant/handlers/memberships.query.ts +2 -0
- package/src/tenant/handlers/remove-member.write.ts +3 -0
- package/src/tenant/handlers/resolve-user-ids.query.ts +1 -0
- package/src/tenant/handlers/team-list.query.ts +2 -0
- package/src/tenant/handlers/toggle-enabled.write.ts +3 -0
- package/src/tenant/handlers/update-member-roles.write.ts +2 -0
- package/src/tenant/handlers/update.write.ts +2 -0
- package/src/tenant/schema/tenant.ts +2 -0
- package/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +2 -0
- package/src/tenant-lifecycle/handlers/request-destruction.write.ts +3 -0
- package/src/tier-engine/entity.ts +2 -0
- package/src/tier-engine/feature.ts +25 -3
- package/src/tier-engine/handlers/active-tier.query.ts +2 -0
- package/src/tier-engine/handlers/get-tenant-tier.query.ts +2 -0
- package/src/tier-engine/handlers/set-tenant-tier.write.ts +2 -0
- package/src/user/handlers/create.write.ts +2 -0
- package/src/user/handlers/detail.query.ts +2 -0
- package/src/user/handlers/find-for-auth.query.ts +1 -0
- package/src/user/handlers/list.query.ts +2 -0
- package/src/user/handlers/me.query.ts +2 -0
- package/src/user/handlers/update.write.ts +2 -0
- package/src/user/schema/user.ts +2 -0
- package/src/user-data-rights/feature.ts +2 -0
- package/src/user-data-rights/handlers/cancel-deletion.write.ts +2 -0
- package/src/user-data-rights/handlers/confirm-deletion-by-token.write.ts +1 -0
- package/src/user-data-rights/handlers/download-attempt-list.query.ts +6 -1
- package/src/user-data-rights/handlers/download-by-job.query.ts +2 -0
- package/src/user-data-rights/handlers/download-by-token.query.ts +1 -0
- package/src/user-data-rights/handlers/export-job-detail.query.ts +2 -0
- package/src/user-data-rights/handlers/export-job-list.query.ts +2 -0
- package/src/user-data-rights/handlers/export-status.query.ts +2 -0
- package/src/user-data-rights/handlers/lift-restriction.write.ts +2 -0
- package/src/user-data-rights/handlers/list-download-attempts.query.ts +2 -0
- package/src/user-data-rights/handlers/my-audit-log.query.ts +2 -0
- package/src/user-data-rights/handlers/request-deletion-by-email.write.ts +2 -0
- package/src/user-data-rights/handlers/request-deletion.write.ts +3 -0
- package/src/user-data-rights/handlers/request-export.write.ts +2 -0
- package/src/user-data-rights/handlers/restrict-account.write.ts +3 -0
- package/src/user-data-rights/handlers/run-forget-cleanup.write.ts +1 -0
- package/src/user-data-rights/schema/download-attempt.ts +2 -0
- package/src/user-data-rights/schema/export-job.ts +2 -0
- package/src/user-profile/handlers/change-email.write.ts +2 -0
- package/src/workflow-runner/handlers/resume-run.write.ts +1 -0
package/src/ledger/feature.ts
CHANGED
|
@@ -60,26 +60,86 @@ function registerLedger(
|
|
|
60
60
|
// Chart of accounts — plain CRUD, no custom logic. No delete in v1: removing an
|
|
61
61
|
// account that has postings would orphan them; a posting-aware guard lands with
|
|
62
62
|
// the postings projection (Phase 1).
|
|
63
|
-
r.writeHandler(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
r.writeHandler(
|
|
64
|
+
defineEntityCreateHandler("account", accountEntity, {
|
|
65
|
+
access,
|
|
66
|
+
description:
|
|
67
|
+
"Adds an account to the tenant's chart of accounts with a name, a type of asset, liability, equity, income or expense, an optional code and an optional parent account; use it before booking anything against that account.",
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
70
|
+
r.writeHandler(
|
|
71
|
+
defineEntityUpdateHandler("account", accountEntity, {
|
|
72
|
+
access,
|
|
73
|
+
description:
|
|
74
|
+
"Changes an existing chart-of-accounts entry's name, type, code or parent; use it to rename or re-file an account, its balance is derived from postings and never edited here.",
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
77
|
+
r.queryHandler(
|
|
78
|
+
defineEntityListHandler("account", accountEntity, {
|
|
79
|
+
access,
|
|
80
|
+
description:
|
|
81
|
+
"Lists the tenant's chart of accounts with name, type, code and parent; use it to browse the account tree or to look up the account id a booking needs.",
|
|
82
|
+
}),
|
|
83
|
+
);
|
|
84
|
+
r.queryHandler(
|
|
85
|
+
defineEntityDetailHandler("account", accountEntity, {
|
|
86
|
+
access,
|
|
87
|
+
description:
|
|
88
|
+
"Returns one chart-of-accounts entry by id with its name, type, code and parent; use it to inspect a single account.",
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
67
91
|
|
|
68
92
|
// Journal entries — immutable. Only create (balanced) + reverse (Storno). No
|
|
69
93
|
// update/delete handler is registered, so a posted entry cannot be mutated.
|
|
70
94
|
r.writeHandler(createCreateTransactionHandler(access));
|
|
71
95
|
r.writeHandler(createReverseTransactionHandler(access));
|
|
72
|
-
r.queryHandler(
|
|
73
|
-
|
|
96
|
+
r.queryHandler(
|
|
97
|
+
defineEntityListHandler("transaction", transactionEntity, {
|
|
98
|
+
access,
|
|
99
|
+
description:
|
|
100
|
+
"Lists the tenant's journal entries with date, description, reference, status and their embedded posting lines; use it to browse the books or to find the entry a reversal should target.",
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
r.queryHandler(
|
|
104
|
+
defineEntityDetailHandler("transaction", transactionEntity, {
|
|
105
|
+
access,
|
|
106
|
+
description:
|
|
107
|
+
"Returns one journal entry by id including every posting line; use it to inspect a single booking.",
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
74
110
|
|
|
75
111
|
// Recurring schedules — CRUD catalog plus confirm-schedule-period, which books
|
|
76
112
|
// one projected period as a balanced, idempotent + reversal-aware entry. The
|
|
77
113
|
// Soll (forecast) is a pure projection (projectSchedule); only confirming writes.
|
|
78
|
-
r.writeHandler(
|
|
79
|
-
|
|
114
|
+
r.writeHandler(
|
|
115
|
+
defineEntityCreateHandler("schedule", scheduleEntity, {
|
|
116
|
+
access,
|
|
117
|
+
description:
|
|
118
|
+
"Creates a recurring booking template that names a debit and a credit account, a positive amount, an interval and a start plus optional end date; use it to set up a standing order, it books nothing on its own.",
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
121
|
+
r.writeHandler(
|
|
122
|
+
defineEntityUpdateHandler("schedule", scheduleEntity, {
|
|
123
|
+
access,
|
|
124
|
+
description:
|
|
125
|
+
"Changes a recurring booking template's description, accounts, amount, interval or date window; use it when a standing order's terms change, already confirmed periods stay booked as they were.",
|
|
126
|
+
}),
|
|
127
|
+
);
|
|
80
128
|
r.writeHandler(createConfirmSchedulePeriodHandler(access));
|
|
81
|
-
r.queryHandler(
|
|
82
|
-
|
|
129
|
+
r.queryHandler(
|
|
130
|
+
defineEntityListHandler("schedule", scheduleEntity, {
|
|
131
|
+
access,
|
|
132
|
+
description:
|
|
133
|
+
"Lists the tenant's recurring booking templates with their accounts, amount, interval and date window; use it to see which standing orders exist and forecast upcoming periods.",
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
r.queryHandler(
|
|
137
|
+
defineEntityDetailHandler("schedule", scheduleEntity, {
|
|
138
|
+
access,
|
|
139
|
+
description:
|
|
140
|
+
"Returns one recurring booking template by id with its accounts, amount, interval and date window; use it to inspect a single standing order before confirming a period.",
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
83
143
|
|
|
84
144
|
// Reports — pure aggregations over the posted entries (account balances,
|
|
85
145
|
// GuV, Bilanz with the current result folded into equity).
|
|
@@ -32,6 +32,8 @@ export function createConfirmSchedulePeriodHandler(
|
|
|
32
32
|
name: "confirm-schedule-period",
|
|
33
33
|
schema: confirmSchedulePeriodPayloadSchema,
|
|
34
34
|
access,
|
|
35
|
+
description:
|
|
36
|
+
"Turns one month of a recurring schedule into a posted balanced entry between its debit and credit account, returning the existing booking instead when that period is already booked and not reversed; use it to move a forecast period into the actual books.",
|
|
35
37
|
handler: async (event, ctx) => {
|
|
36
38
|
const payload = event.payload as ConfirmSchedulePeriodPayload; // @cast-boundary engine-payload
|
|
37
39
|
|
|
@@ -17,6 +17,8 @@ export function createCreateTransactionHandler(
|
|
|
17
17
|
name: "create-transaction",
|
|
18
18
|
schema: createTransactionPayloadSchema,
|
|
19
19
|
access,
|
|
20
|
+
description:
|
|
21
|
+
"Books one balanced journal entry from a date, description and at least two posting lines summing to zero, after verifying every named account exists; use it to record any financial movement in the tenant's books.",
|
|
20
22
|
handler: async (event, ctx) => {
|
|
21
23
|
const payload = event.payload as CreateTransactionPayload; // @cast-boundary engine-payload
|
|
22
24
|
|
|
@@ -38,6 +38,8 @@ export function createBalancesReportHandler(
|
|
|
38
38
|
name: "report:balances",
|
|
39
39
|
schema: periodSchema,
|
|
40
40
|
access,
|
|
41
|
+
description:
|
|
42
|
+
"Returns the per-account balance in its natural sign plus the trial balance over an optional from/to period; use it for a trial balance or to read one account's standing.",
|
|
41
43
|
handler: async (query, ctx) => {
|
|
42
44
|
const period = periodSchema.parse(query);
|
|
43
45
|
const { accounts, entries } = await loadBooks(ctx);
|
|
@@ -54,6 +56,8 @@ export function createIncomeStatementHandler(
|
|
|
54
56
|
name: "report:income-statement",
|
|
55
57
|
schema: periodSchema,
|
|
56
58
|
access,
|
|
59
|
+
description:
|
|
60
|
+
"Returns income minus expense over an optional from/to period; use it for a profit-and-loss statement.",
|
|
57
61
|
handler: async (query, ctx) => {
|
|
58
62
|
const period = periodSchema.parse(query);
|
|
59
63
|
const { accounts, entries } = await loadBooks(ctx);
|
|
@@ -70,6 +74,8 @@ export function createBalanceSheetHandler(
|
|
|
70
74
|
name: "report:balance-sheet",
|
|
71
75
|
schema: periodSchema,
|
|
72
76
|
access,
|
|
77
|
+
description:
|
|
78
|
+
"Returns assets, liabilities and equity as of the optional `to` date with the period result folded into equity so the sheet balances; use it for a balance sheet at a point in time.",
|
|
73
79
|
handler: async (query, ctx) => {
|
|
74
80
|
const period = periodSchema.parse(query);
|
|
75
81
|
const { accounts, entries } = await loadBooks(ctx);
|
|
@@ -24,6 +24,8 @@ export function createReverseTransactionHandler(
|
|
|
24
24
|
name: "reverse-transaction",
|
|
25
25
|
schema: reverseTransactionPayloadSchema,
|
|
26
26
|
access,
|
|
27
|
+
description:
|
|
28
|
+
"Books the mirror image of a posted journal entry as a new entry referencing the original, refusing entries that are not posted or already reversed; use it as the only way to correct a booking, since entries themselves are immutable.",
|
|
27
29
|
handler: async (event, ctx) => {
|
|
28
30
|
const payload = event.payload as ReverseTransactionPayload; // @cast-boundary engine-payload
|
|
29
31
|
|
|
@@ -1,13 +1,5 @@
|
|
|
1
1
|
import { computeRevisionEtag, etagMatches } from "@cosmicdrift/kumiko-framework/api";
|
|
2
|
-
import {
|
|
3
|
-
defineEntityCreateHandler,
|
|
4
|
-
defineEntityDeleteHandler,
|
|
5
|
-
defineEntityDetailHandler,
|
|
6
|
-
defineEntityListHandler,
|
|
7
|
-
defineEntityUpdateHandler,
|
|
8
|
-
defineFeature,
|
|
9
|
-
type FeatureDefinition,
|
|
10
|
-
} from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { defineFeature, type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
11
3
|
import {
|
|
12
4
|
type BrandingTokens,
|
|
13
5
|
cachedSecurePageResponse,
|
|
@@ -20,17 +12,13 @@ import { BRANDING_KEYS, BRANDING_QUERY_QN, CUSTOM_CSS_KEY, coerceBranding } from
|
|
|
20
12
|
import { createBrandingQuery } from "./handlers/branding.query";
|
|
21
13
|
import { bySlugQuery } from "./handlers/by-slug.query";
|
|
22
14
|
import { byTenantPublishedQuery } from "./handlers/by-tenant-published.query";
|
|
15
|
+
import { pageCrudQueries, pageCrudWrites } from "./handlers/page-crud";
|
|
23
16
|
import { setWrite } from "./handlers/set.write";
|
|
24
17
|
import { MANAGED_PAGES_I18N } from "./i18n";
|
|
25
18
|
import { createBrandingSettingsScreen } from "./screens/branding-screen";
|
|
26
19
|
import { pageEditScreen, pageListScreen } from "./screens/page-screens";
|
|
27
20
|
import { pageEntity } from "./table";
|
|
28
21
|
|
|
29
|
-
// Admin-Authoring läuft als TenantAdmin (self-service) oder SystemAdmin
|
|
30
|
-
// (app-weite Pages). Spiegelt set.write's ACL — Apps mit eigenem Rollen-
|
|
31
|
-
// Alias (publicstatus = "Admin") müssen TenantAdmin granten/mappen.
|
|
32
|
-
const ADMIN_ACCESS = { roles: ["TenantAdmin", "SystemAdmin"] } as const;
|
|
33
|
-
|
|
34
22
|
// 60s-shared-cache saves the origin-revalidate roundtrip; CMS edits are live within 60s.
|
|
35
23
|
const PUBLIC_PAGE_CACHE = { kind: "revalidate", maxAgeSeconds: 60 } as const;
|
|
36
24
|
|
|
@@ -186,15 +174,8 @@ export function createManagedPagesFeature(opts: ManagedPagesOptions): FeatureDef
|
|
|
186
174
|
branding: r.queryHandler(createBrandingQuery({ allowCustomCss })),
|
|
187
175
|
};
|
|
188
176
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
// delete}` + `managed-pages:query:page:{list,detail}`. `set` (oben)
|
|
192
|
-
// wird davon NICHT genutzt und bleibt als Provisioning-API erhalten.
|
|
193
|
-
r.writeHandler(defineEntityCreateHandler("page", pageEntity, { access: ADMIN_ACCESS }));
|
|
194
|
-
r.writeHandler(defineEntityUpdateHandler("page", pageEntity, { access: ADMIN_ACCESS }));
|
|
195
|
-
r.writeHandler(defineEntityDeleteHandler("page", pageEntity, { access: ADMIN_ACCESS }));
|
|
196
|
-
r.queryHandler(defineEntityListHandler("page", pageEntity, { access: ADMIN_ACCESS }));
|
|
197
|
-
r.queryHandler(defineEntityDetailHandler("page", pageEntity, { access: ADMIN_ACCESS }));
|
|
177
|
+
for (const def of pageCrudWrites) r.writeHandler(def);
|
|
178
|
+
for (const def of pageCrudQueries) r.queryHandler(def);
|
|
198
179
|
|
|
199
180
|
r.screen(pageListScreen);
|
|
200
181
|
r.screen(pageEditScreen);
|
|
@@ -19,6 +19,8 @@ export function createBrandingQuery(opts: { readonly allowCustomCss: boolean })
|
|
|
19
19
|
name: "branding",
|
|
20
20
|
schema: z.object({}),
|
|
21
21
|
access: { roles: ["anonymous", "User", "TenantAdmin", "SystemAdmin"] },
|
|
22
|
+
description:
|
|
23
|
+
"Reads the tenant's public branding values for the rendered page shell, adding the raw custom CSS only when the app opted in and the per-tenant CSS toggle is on; anonymous callers may read it because it dresses public pages.",
|
|
22
24
|
handler: async (_query, ctx) => {
|
|
23
25
|
const base = await readBranding(ctx.config);
|
|
24
26
|
if (
|
|
@@ -16,6 +16,8 @@ export const bySlugQuery = defineQueryHandler({
|
|
|
16
16
|
lang: z.string().min(2).max(8),
|
|
17
17
|
}),
|
|
18
18
|
access: { roles: ["anonymous", "User", "TenantAdmin", "SystemAdmin"] },
|
|
19
|
+
description:
|
|
20
|
+
"Reads the body and metadata of one published managed page of the caller's tenant by slug and language, returning null for drafts and bodyless pages; use it to render a public page, not to load one for editing.",
|
|
19
21
|
handler: async (query, ctx) => {
|
|
20
22
|
const row = await fetchOne<PageRow>(ctx.db, pagesTable, {
|
|
21
23
|
tenantId: query.user.tenantId,
|
|
@@ -28,6 +28,8 @@ export const byTenantPublishedQuery = defineQueryHandler({
|
|
|
28
28
|
tenantIdOverride: z.string().min(1).optional(),
|
|
29
29
|
}),
|
|
30
30
|
access: { roles: ["anonymous", "User", "TenantAdmin", "SystemAdmin"] },
|
|
31
|
+
description:
|
|
32
|
+
"Lists every published page of a tenant with slug, language, title and last-change time but no body; use it to enumerate the public pages for sitemap.xml or llms.txt, rather than by-slug which fetches one page's content.",
|
|
31
33
|
handler: async (query, ctx) => {
|
|
32
34
|
const override = query.payload.tenantIdOverride;
|
|
33
35
|
const overrideDenied = crossTenantOverrideDenied(
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineEntityCreateHandler,
|
|
3
|
+
defineEntityDeleteHandler,
|
|
4
|
+
defineEntityDetailHandler,
|
|
5
|
+
defineEntityListHandler,
|
|
6
|
+
defineEntityUpdateHandler,
|
|
7
|
+
} from "@cosmicdrift/kumiko-framework/engine";
|
|
8
|
+
import { pageEntity } from "../table";
|
|
9
|
+
|
|
10
|
+
// Admin authoring runs as TenantAdmin (self-service) or SystemAdmin (app-wide
|
|
11
|
+
// pages). Mirrors set.write's ACL — an app with its own role alias
|
|
12
|
+
// (publicstatus = "Admin") has to grant or map TenantAdmin.
|
|
13
|
+
const ADMIN_ACCESS = { roles: ["TenantAdmin", "SystemAdmin"] } as const;
|
|
14
|
+
|
|
15
|
+
// Convention CRUD behind the admin screens: entityEdit/entityList dispatch
|
|
16
|
+
// `managed-pages:write:page:{create,update,delete}` +
|
|
17
|
+
// `managed-pages:query:page:{list,detail}` by convention. The `set` write
|
|
18
|
+
// handler in feature.ts is NOT used by them and stays the provisioning API.
|
|
19
|
+
export const pageCrudWrites = [
|
|
20
|
+
defineEntityCreateHandler("page", pageEntity, {
|
|
21
|
+
access: ADMIN_ACCESS,
|
|
22
|
+
description:
|
|
23
|
+
"Creates a managed page from the admin form's field values, failing if that slug and language already exist; use it from the page catalog screen, whereas managed-pages:write:set upserts a page addressed by slug and language.",
|
|
24
|
+
}),
|
|
25
|
+
defineEntityUpdateHandler("page", pageEntity, {
|
|
26
|
+
access: ADMIN_ACCESS,
|
|
27
|
+
description:
|
|
28
|
+
"Updates one managed page addressed by row id from the admin form's `{ id, version, changes }` envelope; use it from the page edit screen, whereas managed-pages:write:set addresses a page by slug and language instead.",
|
|
29
|
+
}),
|
|
30
|
+
defineEntityDeleteHandler("page", pageEntity, {
|
|
31
|
+
access: ADMIN_ACCESS,
|
|
32
|
+
description:
|
|
33
|
+
"Deletes one managed page by row id so its URL stops resolving entirely; use it to retire a page for good, not to take it offline temporarily — that is a published:false write through managed-pages:write:set.",
|
|
34
|
+
agent: { risk: "high" },
|
|
35
|
+
}),
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
export const pageCrudQueries = [
|
|
39
|
+
defineEntityListHandler("page", pageEntity, {
|
|
40
|
+
access: ADMIN_ACCESS,
|
|
41
|
+
description:
|
|
42
|
+
"Lists the tenant's managed pages for the admin catalog, drafts included; use it to browse or search pages for editing, unlike by-tenant-published which only enumerates the publicly visible ones.",
|
|
43
|
+
}),
|
|
44
|
+
defineEntityDetailHandler("page", pageEntity, {
|
|
45
|
+
access: ADMIN_ACCESS,
|
|
46
|
+
description:
|
|
47
|
+
"Reads one managed page by row id including its body and draft state; use it to load a page into the admin edit screen, whereas by-slug serves the public render path.",
|
|
48
|
+
}),
|
|
49
|
+
];
|
|
@@ -43,6 +43,8 @@ export const setWrite = defineWriteHandler({
|
|
|
43
43
|
tenantIdOverride: z.string().min(1).optional(),
|
|
44
44
|
}),
|
|
45
45
|
access: { roles: ["TenantAdmin", "SystemAdmin"] },
|
|
46
|
+
description:
|
|
47
|
+
"Creates or overwrites one managed page addressed by slug and language, keeping the existing published flag, description and OG image when the payload omits them; use it for content edits and publish toggles, and as SystemAdmin to write another tenant's pages.",
|
|
46
48
|
handler: async (event, ctx) => {
|
|
47
49
|
const db = ctx.db;
|
|
48
50
|
const override = event.payload.tenantIdOverride;
|
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
// Tenant-eigene Pages (Host → tenantId via resolveApexTenant am Render-Pfad).
|
|
14
14
|
export const pageEntity = createEntity({
|
|
15
15
|
table: "read_pages",
|
|
16
|
+
description:
|
|
17
|
+
"One tenant-editable public web page, unique per slug and language, holding a markdown body plus the title, SEO description and OG image, with a published flag that decides whether anonymous visitors are served it or get a 404.",
|
|
16
18
|
fields: {
|
|
17
19
|
slug: createTextField({ required: true, maxLength: 64, sortable: true, searchable: true }),
|
|
18
20
|
lang: createTextField({ required: true, maxLength: 8, sortable: true }),
|
|
@@ -32,6 +32,8 @@ import {
|
|
|
32
32
|
export function createNoteEntryEntity(access?: EntityDefinition["access"]) {
|
|
33
33
|
return createEntity({
|
|
34
34
|
table: "read_note_entries",
|
|
35
|
+
description:
|
|
36
|
+
"One note attached to a host entity by entityType and entityId, holding the note body plus the id and the display name of the author as it stood when the note was written. Rows are append-only: a correction is a further note, never an edit of this one.",
|
|
35
37
|
access,
|
|
36
38
|
fields: {
|
|
37
39
|
entityType: createTextField({ required: true, maxLength: 64 }),
|
|
@@ -44,7 +44,13 @@ function registerNotesHistory(
|
|
|
44
44
|
r.entity("note-entry", entity);
|
|
45
45
|
|
|
46
46
|
r.writeHandler(createAddNoteHandler(access));
|
|
47
|
-
r.queryHandler(
|
|
47
|
+
r.queryHandler(
|
|
48
|
+
defineEntityListHandler("note-entry", entity, {
|
|
49
|
+
access,
|
|
50
|
+
description:
|
|
51
|
+
"Lists note-history entries of the caller's tenant with their text, author and timestamp; filter on entityId to read the full note trail of one record.",
|
|
52
|
+
}),
|
|
53
|
+
);
|
|
48
54
|
|
|
49
55
|
r.translations({ keys: NOTES_HISTORY_FEATURE_I18N });
|
|
50
56
|
}
|
|
@@ -18,6 +18,8 @@ export function createAddNoteHandler(
|
|
|
18
18
|
name: "add-note",
|
|
19
19
|
schema: addNotePayloadSchema,
|
|
20
20
|
access,
|
|
21
|
+
description:
|
|
22
|
+
"Appends a note to one host entity's history, stamping the author from the authenticated caller rather than the payload; use it for every remark and correction alike, because entries can never be edited or removed afterwards.",
|
|
21
23
|
handler: async (event, ctx) => {
|
|
22
24
|
const payload = event.payload as AddNotePayload; // @cast-boundary engine-payload
|
|
23
25
|
|
|
@@ -156,6 +156,8 @@ export function createPersonalAccessTokensFeature(
|
|
|
156
156
|
renderer: { react: { __component: "PatTokensScreen" } },
|
|
157
157
|
access: { openToAll: true },
|
|
158
158
|
dormant: true,
|
|
159
|
+
description:
|
|
160
|
+
"Self-service screen where a signed-in user reviews their personal access tokens, mints a new one by picking a permission level per scope domain and an expiry, and revokes tokens they no longer need.",
|
|
159
161
|
});
|
|
160
162
|
r.translations({ keys: PAT_FEATURE_I18N });
|
|
161
163
|
|
|
@@ -11,6 +11,8 @@ export function buildAvailableScopesQuery(scopes: PatScopeConfig) {
|
|
|
11
11
|
name: "available-scopes",
|
|
12
12
|
schema: z.object({}),
|
|
13
13
|
access: { openToAll: true },
|
|
14
|
+
description:
|
|
15
|
+
"Lists the API scope domains this deployment declares, each with its display label and whether it offers write access, for choosing what a new personal access token may do.",
|
|
14
16
|
handler: async () =>
|
|
15
17
|
Object.entries(scopes).map(([name, def]) => ({
|
|
16
18
|
name,
|
|
@@ -58,6 +58,8 @@ export function createPatCreateHandler(opts: CreatePatOptions = {}) {
|
|
|
58
58
|
mfaCode: z.string().optional(),
|
|
59
59
|
}),
|
|
60
60
|
access: { openToAll: true },
|
|
61
|
+
description:
|
|
62
|
+
"Mints a personal access token for the calling user after re-verifying their password (and MFA code when enrolled) and returns the plaintext token exactly once; use it when a user needs a long-lived API credential.",
|
|
61
63
|
handler: async (event, ctx) => {
|
|
62
64
|
const systemUser = createSystemUser(event.user.tenantId);
|
|
63
65
|
const me = (await ctx.queryAs(systemUser, UserQueries.findForAuth, {
|
|
@@ -11,6 +11,8 @@ export const listPatQuery = defineQueryHandler({
|
|
|
11
11
|
name: "mine",
|
|
12
12
|
schema: z.object({}),
|
|
13
13
|
access: { openToAll: true },
|
|
14
|
+
description:
|
|
15
|
+
"Lists the calling user's personal access tokens with metadata only (name, key prefix, scopes, created/expiry/revoked timestamps, including revoked ones) and never the token secret.",
|
|
14
16
|
handler: async (query, ctx) => {
|
|
15
17
|
const rows = await selectMany<{
|
|
16
18
|
id: string;
|
|
@@ -14,6 +14,9 @@ export const revokePatWrite = defineWriteHandler({
|
|
|
14
14
|
name: "revoke",
|
|
15
15
|
schema: z.object({ id: z.uuid() }),
|
|
16
16
|
access: { openToAll: true },
|
|
17
|
+
description:
|
|
18
|
+
"Permanently revokes one of the caller's own personal access tokens so it stops authenticating; use it when a token leaked or is no longer needed.",
|
|
19
|
+
agent: { risk: "high" },
|
|
17
20
|
handler: async (event, ctx) => {
|
|
18
21
|
const updated = await updateMany(
|
|
19
22
|
ctx.db,
|
|
@@ -22,6 +22,8 @@ export const rateLimitStatus = defineQueryHandler({
|
|
|
22
22
|
// when the feature is registered. Passing the qualified form here would
|
|
23
23
|
// double-prefix it and the handler wouldn't be reachable.
|
|
24
24
|
name: "status",
|
|
25
|
+
description:
|
|
26
|
+
"Peeks at one rate-limit bucket and returns its remaining tokens, window and next reset without consuming a token; use it to explain why a caller is being throttled.",
|
|
25
27
|
schema: z.object({
|
|
26
28
|
bucket: z.string().min(1),
|
|
27
29
|
limit: z.number().int().positive(),
|
|
@@ -13,6 +13,8 @@ export type ReadinessMissingSecret = { readonly key: string };
|
|
|
13
13
|
// query can't see secrets, this feature requires both — so it may verdict.
|
|
14
14
|
export const statusQuery = defineQueryHandler({
|
|
15
15
|
name: "status",
|
|
16
|
+
description:
|
|
17
|
+
"Reports whether the caller's tenant is fully configured, naming every required config key and every required secret that is still unset; use it to answer what is missing before go-live.",
|
|
16
18
|
schema: z.object({}),
|
|
17
19
|
// Same gate as secrets:query:list — the response names missing secrets.
|
|
18
20
|
access: { roles: ["TenantAdmin"] },
|
|
@@ -7,6 +7,9 @@ import { requireSecretsContext } from "../feature";
|
|
|
7
7
|
export function createDeleteHandler(access: AccessRule = DEFAULT_SECRETS_ACCESS) {
|
|
8
8
|
return defineWriteHandler({
|
|
9
9
|
name: "delete",
|
|
10
|
+
description:
|
|
11
|
+
"Permanently removes the stored secret under the given key for the caller's tenant, breaking every feature that depends on that credential.",
|
|
12
|
+
agent: { risk: "high" },
|
|
10
13
|
schema: z.object({
|
|
11
14
|
key: z.string().min(1).max(100),
|
|
12
15
|
}),
|
|
@@ -10,6 +10,8 @@ import { tenantSecretsTable } from "../table";
|
|
|
10
10
|
export function createListHandler(access: AccessRule = DEFAULT_SECRETS_ACCESS) {
|
|
11
11
|
return defineQueryHandler({
|
|
12
12
|
name: "list",
|
|
13
|
+
description:
|
|
14
|
+
"Lists the secrets stored for the caller's tenant by key with a redacted preview, hint, key version and rotation date — never the plaintext; use it to check which credentials are set.",
|
|
13
15
|
schema: z.object({}),
|
|
14
16
|
access,
|
|
15
17
|
handler: async (event, ctx) => {
|
|
@@ -6,6 +6,8 @@ import { requireSecretsContext } from "../feature";
|
|
|
6
6
|
export function createSetHandler(access: AccessRule = DEFAULT_SECRETS_ACCESS) {
|
|
7
7
|
return defineWriteHandler({
|
|
8
8
|
name: "set",
|
|
9
|
+
description:
|
|
10
|
+
"Stores or replaces one encrypted secret for the caller's tenant under the given key, together with a redacted preview and optional hint; use it to enter or rotate a credential.",
|
|
9
11
|
schema: z.object({
|
|
10
12
|
key: z.string().min(1).max(100),
|
|
11
13
|
value: z.string(),
|
package/src/secrets/table.ts
CHANGED
|
@@ -47,6 +47,8 @@ export type StoredMetadata = {
|
|
|
47
47
|
// doesn't weaken the threat model vs. the pre-ES tenant_secrets column.
|
|
48
48
|
export const tenantSecretEntity = createEntity({
|
|
49
49
|
table: "read_tenant_secrets",
|
|
50
|
+
description:
|
|
51
|
+
"One encrypted secret per tenant and key, holding the sealed envelope, the key-encryption-key version it was sealed under and its last rotation time; plaintext values are never stored or returned.",
|
|
50
52
|
fields: {
|
|
51
53
|
key: createTextField({ required: true }),
|
|
52
54
|
kekVersion: createNumberField({ required: true, integer: true }),
|
|
@@ -18,6 +18,8 @@ export const seoConfigQuery = defineQueryHandler({
|
|
|
18
18
|
name: "config",
|
|
19
19
|
schema: z.object({}),
|
|
20
20
|
access: { roles: ["anonymous", "User", "TenantAdmin", "SystemAdmin"] },
|
|
21
|
+
description:
|
|
22
|
+
"Reads the tenant's SEO metadata settings — organization name and logo URL, Twitter site handle, LLMs summary and default OG image — as plain strings; use it to inspect what the sitemap, llms.txt and page meta tags will publish.",
|
|
21
23
|
handler: async (_query, ctx) => {
|
|
22
24
|
const read = async (qualifiedKey: string): Promise<string> => {
|
|
23
25
|
if (!ctx.config) return "";
|
|
@@ -20,6 +20,8 @@ export const detailQuery = defineQueryHandler({
|
|
|
20
20
|
name: "user-session:detail",
|
|
21
21
|
schema: z.object({ id: z.uuid() }),
|
|
22
22
|
access: { roles: access.admin },
|
|
23
|
+
description:
|
|
24
|
+
"Loads one session of the active tenant by its id with owner, timestamps and decrypted IP/user-agent; use it when an admin investigates a specific session found through the session list.",
|
|
23
25
|
outputSchema: sessionRowSchema.nullable(),
|
|
24
26
|
handler: async (query, ctx) => {
|
|
25
27
|
const row = await fetchOne<{
|
|
@@ -33,6 +33,8 @@ export const listQuery = definePagedQueryHandler({
|
|
|
33
33
|
sortDirection: z.enum(["asc", "desc"]).optional(),
|
|
34
34
|
}),
|
|
35
35
|
access: { roles: access.admin },
|
|
36
|
+
description:
|
|
37
|
+
"Lists every session in the active tenant, live and revoked, sortable by id, user, creation, expiry or revocation, for an admin auditing who is signed in.",
|
|
36
38
|
outputSchema: z.object({
|
|
37
39
|
rows: z.array(
|
|
38
40
|
z.object({
|
|
@@ -11,6 +11,8 @@ export const mineQuery = defineQueryHandler({
|
|
|
11
11
|
name: "user-session:mine",
|
|
12
12
|
schema: z.object({}),
|
|
13
13
|
access: { openToAll: true },
|
|
14
|
+
description:
|
|
15
|
+
"Lists the calling user's own still-live sessions, newest first, each flagged whether it is the one making the request; use it to show a user their signed-in devices.",
|
|
14
16
|
handler: async (query, ctx) => {
|
|
15
17
|
const rows = await selectMany<{
|
|
16
18
|
id: string;
|
|
@@ -34,6 +34,9 @@ export const revokeAllForUserWrite = defineWriteHandler({
|
|
|
34
34
|
tenantId: z.string().min(1).optional(),
|
|
35
35
|
}),
|
|
36
36
|
access: { roles: access.privileged },
|
|
37
|
+
description:
|
|
38
|
+
"Irreversibly signs a named user out of all their live sessions, across every tenant unless a tenantId narrows it; use it for operator actions such as freezing or banning an account.",
|
|
39
|
+
agent: { risk: "high" },
|
|
37
40
|
handler: async (event, ctx) => {
|
|
38
41
|
const updated = await updateMany<{ id: string }>(
|
|
39
42
|
ctx.db.raw,
|
|
@@ -16,6 +16,9 @@ export const revokeAllOthersWrite = defineWriteHandler({
|
|
|
16
16
|
name: "user-session:revoke-all-others",
|
|
17
17
|
schema: z.object({}),
|
|
18
18
|
access: { openToAll: true },
|
|
19
|
+
description:
|
|
20
|
+
'Irreversibly signs the calling user out of every session except the one making the request and reports how many were dropped; use it for a "sign out everywhere else" action after a suspected compromise.',
|
|
21
|
+
agent: { risk: "high" },
|
|
19
22
|
handler: async (event, ctx) => {
|
|
20
23
|
const keepSid = event.user.sid;
|
|
21
24
|
if (!keepSid) {
|
|
@@ -29,6 +29,9 @@ export const revokeWrite = defineWriteHandler({
|
|
|
29
29
|
id: z.uuid(),
|
|
30
30
|
}),
|
|
31
31
|
access: { openToAll: true },
|
|
32
|
+
description:
|
|
33
|
+
"Irreversibly signs one of the caller's own sessions out by id; use it when a user wants to drop a single device, and note that revoking their current session logs them out.",
|
|
34
|
+
agent: { risk: "high" },
|
|
32
35
|
handler: async (event, ctx) => {
|
|
33
36
|
const updated = await updateMany(
|
|
34
37
|
ctx.db,
|
package/src/tags/entity.ts
CHANGED
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
// events. tenantId is a base column set by the framework → tenant-scoped.
|
|
10
10
|
export const tagEntity = createEntity({
|
|
11
11
|
table: "read_tags",
|
|
12
|
+
description:
|
|
13
|
+
"One entry of a tenant's tag catalog: a name plus an optional colour hint for rendering and an optional entityType scope that limits which entities a picker offers the tag on. Names are not unique, and a tag carries no link to the entities it is attached to.",
|
|
12
14
|
fields: {
|
|
13
15
|
// Catalog labels ("urgent", "billing"), not user-identifying content —
|
|
14
16
|
// `personal: false` silences the user-content heuristic (456/5). A tag
|
|
@@ -56,6 +58,8 @@ export const tagEntity = createEntity({
|
|
|
56
58
|
export function createTagAssignmentEntity(access?: EntityDefinition["access"]) {
|
|
57
59
|
return createEntity({
|
|
58
60
|
table: "read_tag_assignments",
|
|
61
|
+
description:
|
|
62
|
+
"The join row recording that one catalog tag is attached to one host entity, addressed by tagId, entityType and entityId, with exactly one row per pair. An entity may carry many tags, unlike the single-folder membership rows.",
|
|
59
63
|
softDelete: true,
|
|
60
64
|
access,
|
|
61
65
|
fields: {
|
package/src/tags/feature.ts
CHANGED
|
@@ -79,13 +79,46 @@ function registerTags(
|
|
|
79
79
|
// (payloadMode=values); update must accept the {id,version,changes} envelope
|
|
80
80
|
// entityEdit sends (legacy update-tag stays for TagManager). delete stays
|
|
81
81
|
// legacy so assignment cascade is preserved (convention delete would orphan).
|
|
82
|
-
r.writeHandler(
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
r.writeHandler({
|
|
83
|
+
...createCreateTagHandler(access, "tag:create"),
|
|
84
|
+
description:
|
|
85
|
+
"Adds a tag to the caller's tenant tag catalog and mints its id; this is the convention name the declarative tag catalog screens dispatch, with the same effect as the legacy create-tag.",
|
|
86
|
+
});
|
|
87
|
+
r.writeHandler(
|
|
88
|
+
defineEntityUpdateHandler("tag", tagEntity, {
|
|
89
|
+
access,
|
|
90
|
+
description:
|
|
91
|
+
"Renames, recolours or re-scopes a catalog tag from the entity-edit `{ id, version, changes }` envelope; this is the convention name the tag edit screen submits, unlike the flat-payload legacy update-tag.",
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
94
|
+
r.writeHandler({
|
|
95
|
+
...createDeleteTagHandler(access, "tag:delete"),
|
|
96
|
+
description:
|
|
97
|
+
"Deletes a catalog tag and detaches it from every entity carrying it; this is the convention name the tag catalog list screen's delete row-action dispatches, with the same cascade as the legacy delete-tag.",
|
|
98
|
+
agent: { risk: "high" },
|
|
99
|
+
});
|
|
85
100
|
|
|
86
|
-
r.queryHandler(
|
|
87
|
-
|
|
88
|
-
|
|
101
|
+
r.queryHandler(
|
|
102
|
+
defineEntityListHandler("tag", tagEntity, {
|
|
103
|
+
access,
|
|
104
|
+
description:
|
|
105
|
+
"Lists the caller's tenant tag catalog with each tag's name, colour and scope; use it to render the catalog or to offer a user the tags they can attach.",
|
|
106
|
+
}),
|
|
107
|
+
);
|
|
108
|
+
r.queryHandler(
|
|
109
|
+
defineEntityListHandler("tag-assignment", tagAssignmentEntity, {
|
|
110
|
+
access,
|
|
111
|
+
description:
|
|
112
|
+
"Lists the tag-to-entity assignment rows of the caller's tenant; filter on entityId to get one record's tags, or on tagId to get every record carrying a tag.",
|
|
113
|
+
}),
|
|
114
|
+
);
|
|
115
|
+
r.queryHandler(
|
|
116
|
+
defineEntityDetailHandler("tag", tagEntity, {
|
|
117
|
+
access,
|
|
118
|
+
description:
|
|
119
|
+
"Reads one catalog tag by id with its name, colour and scope; use it to load a single tag into an edit form rather than to find which entities carry it.",
|
|
120
|
+
}),
|
|
121
|
+
);
|
|
89
122
|
|
|
90
123
|
// Standalone catalog: entityList + entityEdit. App navs via
|
|
91
124
|
// r.nav("tags:screen:tag-list"). TagManager stays for picker/section only.
|
|
@@ -26,6 +26,8 @@ export function createAssignTagHandler(access: AccessRule = DEFAULT_TAG_ACCESS):
|
|
|
26
26
|
name: "assign-tag",
|
|
27
27
|
schema: assignTagPayloadSchema,
|
|
28
28
|
access,
|
|
29
|
+
description:
|
|
30
|
+
"Attaches an existing catalog tag to one host entity addressed by its type and id, reporting success when the tag was already attached; use it to tag a record, not to create the tag.",
|
|
29
31
|
handler: async (event, ctx) => {
|
|
30
32
|
const payload = event.payload as AssignTagPayload; // @cast-boundary engine-payload
|
|
31
33
|
const id = tagAssignmentAggregateId(
|
|
@@ -15,6 +15,8 @@ export function createCreateTagHandler(
|
|
|
15
15
|
name,
|
|
16
16
|
schema: createTagPayloadSchema,
|
|
17
17
|
access,
|
|
18
|
+
description:
|
|
19
|
+
"Adds a tag to the caller's tenant tag catalog from a flat payload and mints its id; this is the legacy name the TagManager and TagPicker widgets dispatch, and it does not attach the new tag to anything.",
|
|
18
20
|
handler: async (event, ctx) => {
|
|
19
21
|
const payload = event.payload as CreateTagPayload; // @cast-boundary engine-payload
|
|
20
22
|
return tagExecutor.create(payload, event.user, ctx.db);
|