@cogenta/schema 0.2.0 → 0.4.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.
Files changed (193) hide show
  1. package/dist/define-collection.d.ts.map +1 -1
  2. package/dist/define-collection.js +53 -3
  3. package/dist/define-collection.js.map +1 -1
  4. package/dist/generate-types.d.ts.map +1 -1
  5. package/dist/generate-types.js +6 -1
  6. package/dist/generate-types.js.map +1 -1
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +1 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/media-usage.d.ts +56 -0
  12. package/dist/media-usage.d.ts.map +1 -0
  13. package/dist/media-usage.js +89 -0
  14. package/dist/media-usage.js.map +1 -0
  15. package/dist/rich-text.d.ts +28 -2
  16. package/dist/rich-text.d.ts.map +1 -1
  17. package/dist/rich-text.js +26 -3
  18. package/dist/rich-text.js.map +1 -1
  19. package/dist/routing/index.d.ts +5 -1
  20. package/dist/routing/index.d.ts.map +1 -1
  21. package/dist/routing/index.js +2 -0
  22. package/dist/routing/index.js.map +1 -1
  23. package/dist/routing/not-found-log.d.ts +78 -0
  24. package/dist/routing/not-found-log.d.ts.map +1 -0
  25. package/dist/routing/not-found-log.js +153 -0
  26. package/dist/routing/not-found-log.js.map +1 -0
  27. package/dist/routing/redirect-patterns.d.ts +61 -0
  28. package/dist/routing/redirect-patterns.d.ts.map +1 -0
  29. package/dist/routing/redirect-patterns.js +173 -0
  30. package/dist/routing/redirect-patterns.js.map +1 -0
  31. package/dist/routing/redirects.d.ts +27 -4
  32. package/dist/routing/redirects.d.ts.map +1 -1
  33. package/dist/routing/redirects.js +160 -48
  34. package/dist/routing/redirects.js.map +1 -1
  35. package/dist/scheduling/index.d.ts +2 -0
  36. package/dist/scheduling/index.d.ts.map +1 -1
  37. package/dist/scheduling/index.js +1 -0
  38. package/dist/scheduling/index.js.map +1 -1
  39. package/dist/scheduling/registry.d.ts +147 -0
  40. package/dist/scheduling/registry.d.ts.map +1 -0
  41. package/dist/scheduling/registry.js +318 -0
  42. package/dist/scheduling/registry.js.map +1 -0
  43. package/dist/search/extract.d.ts +24 -0
  44. package/dist/search/extract.d.ts.map +1 -1
  45. package/dist/search/extract.js +63 -8
  46. package/dist/search/extract.js.map +1 -1
  47. package/dist/search/index.d.ts +2 -1
  48. package/dist/search/index.d.ts.map +1 -1
  49. package/dist/search/index.js +1 -9
  50. package/dist/search/index.js.map +1 -1
  51. package/dist/store/admin-theme-store.d.ts +26 -0
  52. package/dist/store/admin-theme-store.d.ts.map +1 -0
  53. package/dist/store/admin-theme-store.js +81 -0
  54. package/dist/store/admin-theme-store.js.map +1 -0
  55. package/dist/store/admin-theme-tables.d.ts +24 -0
  56. package/dist/store/admin-theme-tables.d.ts.map +1 -0
  57. package/dist/store/admin-theme-tables.js +41 -0
  58. package/dist/store/admin-theme-tables.js.map +1 -0
  59. package/dist/store/admin-theme-templates.d.ts +135 -0
  60. package/dist/store/admin-theme-templates.d.ts.map +1 -0
  61. package/dist/store/admin-theme-templates.js +217 -0
  62. package/dist/store/admin-theme-templates.js.map +1 -0
  63. package/dist/store/columns.d.ts.map +1 -1
  64. package/dist/store/columns.js +8 -2
  65. package/dist/store/columns.js.map +1 -1
  66. package/dist/store/diff.d.ts +40 -0
  67. package/dist/store/diff.d.ts.map +1 -1
  68. package/dist/store/diff.js +148 -0
  69. package/dist/store/diff.js.map +1 -1
  70. package/dist/store/index.d.ts +47 -4
  71. package/dist/store/index.d.ts.map +1 -1
  72. package/dist/store/index.js +26 -2
  73. package/dist/store/index.js.map +1 -1
  74. package/dist/store/maintenance-store.d.ts +34 -0
  75. package/dist/store/maintenance-store.d.ts.map +1 -0
  76. package/dist/store/maintenance-store.js +92 -0
  77. package/dist/store/maintenance-store.js.map +1 -0
  78. package/dist/store/menu-store.d.ts +146 -0
  79. package/dist/store/menu-store.d.ts.map +1 -0
  80. package/dist/store/menu-store.js +565 -0
  81. package/dist/store/menu-store.js.map +1 -0
  82. package/dist/store/menu-tables.d.ts +38 -0
  83. package/dist/store/menu-tables.d.ts.map +1 -0
  84. package/dist/store/menu-tables.js +117 -0
  85. package/dist/store/menu-tables.js.map +1 -0
  86. package/dist/store/naming.d.ts +1 -1
  87. package/dist/store/naming.d.ts.map +1 -1
  88. package/dist/store/naming.js +2 -0
  89. package/dist/store/naming.js.map +1 -1
  90. package/dist/store/pattern-store.d.ts +75 -0
  91. package/dist/store/pattern-store.d.ts.map +1 -0
  92. package/dist/store/pattern-store.js +118 -0
  93. package/dist/store/pattern-store.js.map +1 -0
  94. package/dist/store/pattern-tables.d.ts +29 -0
  95. package/dist/store/pattern-tables.d.ts.map +1 -0
  96. package/dist/store/pattern-tables.js +53 -0
  97. package/dist/store/pattern-tables.js.map +1 -0
  98. package/dist/store/read-only.d.ts +3 -3
  99. package/dist/store/read-only.d.ts.map +1 -1
  100. package/dist/store/read-only.js +4 -3
  101. package/dist/store/read-only.js.map +1 -1
  102. package/dist/store/redirect-tracking.d.ts +50 -0
  103. package/dist/store/redirect-tracking.d.ts.map +1 -0
  104. package/dist/store/redirect-tracking.js +91 -0
  105. package/dist/store/redirect-tracking.js.map +1 -0
  106. package/dist/store/review-transitions.d.ts +34 -0
  107. package/dist/store/review-transitions.d.ts.map +1 -0
  108. package/dist/store/review-transitions.js +51 -0
  109. package/dist/store/review-transitions.js.map +1 -0
  110. package/dist/store/role-permission-export.d.ts +22 -0
  111. package/dist/store/role-permission-export.d.ts.map +1 -0
  112. package/dist/store/role-permission-export.js +0 -0
  113. package/dist/store/role-permission-export.js.map +1 -0
  114. package/dist/store/role-permission-overlay.d.ts +27 -0
  115. package/dist/store/role-permission-overlay.d.ts.map +1 -0
  116. package/dist/store/role-permission-overlay.js +0 -0
  117. package/dist/store/role-permission-overlay.js.map +1 -0
  118. package/dist/store/role-permission-store.d.ts +72 -0
  119. package/dist/store/role-permission-store.d.ts.map +1 -0
  120. package/dist/store/role-permission-store.js +148 -0
  121. package/dist/store/role-permission-store.js.map +1 -0
  122. package/dist/store/role-permission-tables.d.ts +27 -0
  123. package/dist/store/role-permission-tables.d.ts.map +1 -0
  124. package/dist/store/role-permission-tables.js +52 -0
  125. package/dist/store/role-permission-tables.js.map +1 -0
  126. package/dist/store/scheduled-publish-enqueue.d.ts +37 -0
  127. package/dist/store/scheduled-publish-enqueue.d.ts.map +1 -0
  128. package/dist/store/scheduled-publish-enqueue.js +51 -0
  129. package/dist/store/scheduled-publish-enqueue.js.map +1 -0
  130. package/dist/store/scheduled-publish-failures.d.ts +36 -0
  131. package/dist/store/scheduled-publish-failures.d.ts.map +1 -0
  132. package/dist/store/scheduled-publish-failures.js +67 -0
  133. package/dist/store/scheduled-publish-failures.js.map +1 -0
  134. package/dist/store/schema-2-1-migration.d.ts +29 -0
  135. package/dist/store/schema-2-1-migration.d.ts.map +1 -0
  136. package/dist/store/schema-2-1-migration.js +71 -0
  137. package/dist/store/schema-2-1-migration.js.map +1 -0
  138. package/dist/store/search-console-store.d.ts +44 -0
  139. package/dist/store/search-console-store.d.ts.map +1 -0
  140. package/dist/store/search-console-store.js +90 -0
  141. package/dist/store/search-console-store.js.map +1 -0
  142. package/dist/store/search-console-tables.d.ts +14 -0
  143. package/dist/store/search-console-tables.d.ts.map +1 -0
  144. package/dist/store/search-console-tables.js +29 -0
  145. package/dist/store/search-console-tables.js.map +1 -0
  146. package/dist/store/search-indexing.d.ts +36 -0
  147. package/dist/store/search-indexing.d.ts.map +1 -1
  148. package/dist/store/search-indexing.js +33 -0
  149. package/dist/store/search-indexing.js.map +1 -1
  150. package/dist/store/site-settings-registry.d.ts +164 -0
  151. package/dist/store/site-settings-registry.d.ts.map +1 -0
  152. package/dist/store/site-settings-registry.js +1160 -0
  153. package/dist/store/site-settings-registry.js.map +1 -0
  154. package/dist/store/site-settings-store.d.ts +42 -0
  155. package/dist/store/site-settings-store.d.ts.map +1 -0
  156. package/dist/store/site-settings-store.js +97 -0
  157. package/dist/store/site-settings-store.js.map +1 -0
  158. package/dist/store/site-settings-tables.d.ts +29 -0
  159. package/dist/store/site-settings-tables.d.ts.map +1 -0
  160. package/dist/store/site-settings-tables.js +48 -0
  161. package/dist/store/site-settings-tables.js.map +1 -0
  162. package/dist/store/store.d.ts +57 -2
  163. package/dist/store/store.d.ts.map +1 -1
  164. package/dist/store/store.js +278 -37
  165. package/dist/store/store.js.map +1 -1
  166. package/dist/store/tables.d.ts.map +1 -1
  167. package/dist/store/tables.js +11 -0
  168. package/dist/store/tables.js.map +1 -1
  169. package/dist/store/taxonomy-store.d.ts.map +1 -1
  170. package/dist/store/taxonomy-store.js +90 -39
  171. package/dist/store/taxonomy-store.js.map +1 -1
  172. package/dist/store/taxonomy-usage.d.ts +47 -0
  173. package/dist/store/taxonomy-usage.d.ts.map +1 -0
  174. package/dist/store/taxonomy-usage.js +101 -0
  175. package/dist/store/taxonomy-usage.js.map +1 -0
  176. package/dist/store/theme-store.d.ts +73 -0
  177. package/dist/store/theme-store.d.ts.map +1 -0
  178. package/dist/store/theme-store.js +193 -0
  179. package/dist/store/theme-store.js.map +1 -0
  180. package/dist/store/types.d.ts +36 -1
  181. package/dist/store/types.d.ts.map +1 -1
  182. package/dist/store/values.d.ts.map +1 -1
  183. package/dist/store/values.js +28 -1
  184. package/dist/store/values.js.map +1 -1
  185. package/dist/system-fields.d.ts +8 -1
  186. package/dist/system-fields.d.ts.map +1 -1
  187. package/dist/system-fields.js +14 -1
  188. package/dist/system-fields.js.map +1 -1
  189. package/dist/types.d.ts +54 -1
  190. package/dist/types.d.ts.map +1 -1
  191. package/dist/types.js +21 -0
  192. package/dist/types.js.map +1 -1
  193. package/package.json +2 -2
@@ -0,0 +1,91 @@
1
+ import { recordSlugChange } from '../routing/index.js';
2
+ /** The name of the collection's `f.slug(...)` field, or null when it has none. */
3
+ function slugFieldOf(collection) {
4
+ const found = Object.entries(collection.fields).find(([, field]) => field.kind === 'slug');
5
+ return found === undefined ? null : found[0];
6
+ }
7
+ /** The `:name` segments of a route pattern, in order. */
8
+ function routeParamNames(pattern) {
9
+ return pattern
10
+ .split('/')
11
+ .filter((segment) => segment.startsWith(':'))
12
+ .map((segment) => segment.slice(1));
13
+ }
14
+ async function trackRename(store, options, slugField, id, before) {
15
+ if (before === null)
16
+ return;
17
+ try {
18
+ const after = await store.read(id, { state: 'published' });
19
+ if (after === null)
20
+ return;
21
+ const previousSlug = before.values[slugField];
22
+ const nextSlug = after.values[slugField];
23
+ if (typeof previousSlug !== 'string' || typeof nextSlug !== 'string')
24
+ return;
25
+ if (previousSlug === nextSlug)
26
+ return;
27
+ const pattern = options.collection.routing?.pattern;
28
+ const otherParams = {};
29
+ if (pattern !== undefined) {
30
+ for (const name of routeParamNames(pattern)) {
31
+ if (name === slugField)
32
+ continue;
33
+ const value = after.values[name];
34
+ if (typeof value === 'string')
35
+ otherParams[name] = value;
36
+ }
37
+ }
38
+ await recordSlugChange(options.redirects, {
39
+ collection: options.collection,
40
+ entryId: id,
41
+ status: 'published',
42
+ previousSlug,
43
+ nextSlug,
44
+ slugParam: slugField,
45
+ locale: after.locale,
46
+ ...(Object.keys(otherParams).length === 0 ? {} : { params: otherParams }),
47
+ });
48
+ }
49
+ catch (error) {
50
+ options.onError?.(error);
51
+ }
52
+ }
53
+ /**
54
+ * Wraps a `ContentStore` so that renaming the slug of a **published** entry
55
+ * writes a 301 from the old path to the new one, with no caller having to
56
+ * remember to.
57
+ *
58
+ * A collection with no `f.slug(...)` field is returned unwrapped — there is
59
+ * nothing this decorator could ever do for it, and wrapping it anyway would
60
+ * only cost two extra reads per write for nothing.
61
+ */
62
+ export function withRedirectTracking(store, options) {
63
+ const slugField = slugFieldOf(options.collection);
64
+ if (slugField === null)
65
+ return store;
66
+ async function before(id) {
67
+ return store.read(id, { state: 'published' });
68
+ }
69
+ return {
70
+ ...store,
71
+ update: async (id, input) => {
72
+ const previous = await before(id);
73
+ const result = await store.update(id, input);
74
+ await trackRename(store, options, slugField, id, previous);
75
+ return result;
76
+ },
77
+ publish: async (id, input) => {
78
+ const previous = await before(id);
79
+ const result = await store.publish(id, input);
80
+ await trackRename(store, options, slugField, id, previous);
81
+ return result;
82
+ },
83
+ restore: async (id, version, input) => {
84
+ const previous = await before(id);
85
+ const result = await store.restore(id, version, input);
86
+ await trackRename(store, options, slugField, id, previous);
87
+ return result;
88
+ },
89
+ };
90
+ }
91
+ //# sourceMappingURL=redirect-tracking.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirect-tracking.js","sourceRoot":"","sources":["../../src/store/redirect-tracking.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import type { ContentAction, ReviewState, ReviewTransition } from '../types.js';
2
+ /**
3
+ * The editorial workflow's transition table (`schema@2.1`, ADR-0027).
4
+ *
5
+ * A closed set of three transitions, each with the `from` states it accepts,
6
+ * the `to` state it produces, and the contract A action a caller needs to
7
+ * make it — the same shape the contract E order state machine already
8
+ * proved out. It lives on the server and nowhere else: an admin screen reads
9
+ * it to grey out a button, it never re-derives what is legal.
10
+ *
11
+ * `approve` does **not** produce `status: 'published'`. Approving and
12
+ * publishing stay two different actions, guarded by two different
13
+ * permissions (`publish` either way) — conflating them would publish by
14
+ * surprise the moment an editor clicks "Approve".
15
+ */
16
+ export interface ReviewTransitionRule {
17
+ readonly from: readonly ReviewState[];
18
+ readonly to: ReviewState;
19
+ /** The contract A action a caller needs, checked by the caller — never by this table. */
20
+ readonly action: ContentAction;
21
+ }
22
+ export declare const REVIEW_TRANSITION_TABLE: Readonly<Record<ReviewTransition, ReviewTransitionRule>>;
23
+ /**
24
+ * Checks a transition against the table, throwing `CONTENT_REVIEW_TRANSITION_INVALID`
25
+ * for an illegal jump. Returns the state the entry should move to.
26
+ *
27
+ * Permission is not this function's business: the caller already asserted
28
+ * `REVIEW_TRANSITION_TABLE[transition].action` before reaching here.
29
+ */
30
+ export declare function nextReviewState(transition: ReviewTransition, current: ReviewState, context: {
31
+ readonly collection: string;
32
+ readonly id: string;
33
+ }): ReviewState;
34
+ //# sourceMappingURL=review-transitions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-transitions.d.ts","sourceRoot":"","sources":["../../src/store/review-transitions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAE/E;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,SAAS,WAAW,EAAE,CAAA;IACrC,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAA;IACxB,yFAAyF;IACzF,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;CAC/B;AAED,eAAO,MAAM,uBAAuB,EAAE,QAAQ,CAAC,MAAM,CAAC,gBAAgB,EAAE,oBAAoB,CAAC,CAS5F,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,gBAAgB,EAC5B,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE;IAAE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC5D,WAAW,CAWb"}
@@ -0,0 +1,51 @@
1
+ import { CogentaError } from '@cogenta/core';
2
+ export const REVIEW_TRANSITION_TABLE = {
3
+ // A contributor sends a fresh draft — or one sent back for changes — into
4
+ // the queue. Re-submitting after `changes-requested` is expected, not an
5
+ // error: that is the whole point of asking for changes.
6
+ submit: { from: ['none', 'changes-requested'], to: 'pending', action: 'update' },
7
+ // Only a pending entry can be approved or sent back — an entry nobody
8
+ // submitted has nothing for a reviewer to have looked at yet.
9
+ approve: { from: ['pending'], to: 'approved', action: 'publish' },
10
+ requestChanges: { from: ['pending'], to: 'changes-requested', action: 'publish' },
11
+ };
12
+ /**
13
+ * Checks a transition against the table, throwing `CONTENT_REVIEW_TRANSITION_INVALID`
14
+ * for an illegal jump. Returns the state the entry should move to.
15
+ *
16
+ * Permission is not this function's business: the caller already asserted
17
+ * `REVIEW_TRANSITION_TABLE[transition].action` before reaching here.
18
+ */
19
+ export function nextReviewState(transition, current, context) {
20
+ const rule = REVIEW_TRANSITION_TABLE[transition];
21
+ if (!rule.from.includes(current)) {
22
+ throw new CogentaError({
23
+ code: 'CONTENT_REVIEW_TRANSITION_INVALID',
24
+ message: `"${context.id}" of "${context.collection}" cannot ${transitionVerb(transition)}: its review state is "${current}", not ${rule.from.map((state) => `"${state}"`).join(' or ')}.`,
25
+ hint: transitionHint(transition, current),
26
+ details: { collection: context.collection, id: context.id, transition, from: current },
27
+ });
28
+ }
29
+ return rule.to;
30
+ }
31
+ function transitionVerb(transition) {
32
+ switch (transition) {
33
+ case 'submit':
34
+ return 'be submitted for review';
35
+ case 'approve':
36
+ return 'be approved';
37
+ case 'requestChanges':
38
+ return 'have changes requested';
39
+ }
40
+ }
41
+ function transitionHint(transition, current) {
42
+ if (transition === 'submit') {
43
+ return current === 'pending'
44
+ ? 'It is already waiting for a reviewer.'
45
+ : 'Only a fresh entry or one sent back with requested changes can be submitted.';
46
+ }
47
+ return current === 'none'
48
+ ? 'Nobody submitted this entry yet — ask its author to submit it first.'
49
+ : 'Only an entry waiting for review (pending) can be approved or sent back.';
50
+ }
51
+ //# sourceMappingURL=review-transitions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-transitions.js","sourceRoot":"","sources":["../../src/store/review-transitions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAwB5C,MAAM,CAAC,MAAM,uBAAuB,GAA6D;IAC/F,0EAA0E;IAC1E,yEAAyE;IACzE,wDAAwD;IACxD,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,mBAAmB,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE;IAChF,sEAAsE;IACtE,8DAA8D;IAC9D,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE;IACjE,cAAc,EAAE,EAAE,IAAI,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,mBAAmB,EAAE,MAAM,EAAE,SAAS,EAAE;CAClF,CAAA;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAC7B,UAA4B,EAC5B,OAAoB,EACpB,OAA6D;IAE7D,MAAM,IAAI,GAAG,uBAAuB,CAAC,UAAU,CAAC,CAAA;IAChD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,YAAY,CAAC;YACrB,IAAI,EAAE,mCAAmC;YACzC,OAAO,EAAE,IAAI,OAAO,CAAC,EAAE,SAAS,OAAO,CAAC,UAAU,YAAY,cAAc,CAAC,UAAU,CAAC,0BAA0B,OAAO,UAAU,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG;YACzL,IAAI,EAAE,cAAc,CAAC,UAAU,EAAE,OAAO,CAAC;YACzC,OAAO,EAAE,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE;SACvF,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,IAAI,CAAC,EAAE,CAAA;AAChB,CAAC;AAED,SAAS,cAAc,CAAC,UAA4B;IAClD,QAAQ,UAAU,EAAE,CAAC;QACnB,KAAK,QAAQ;YACX,OAAO,yBAAyB,CAAA;QAClC,KAAK,SAAS;YACZ,OAAO,aAAa,CAAA;QACtB,KAAK,gBAAgB;YACnB,OAAO,wBAAwB,CAAA;IACnC,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,UAA4B,EAAE,OAAoB;IACxE,IAAI,UAAU,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,OAAO,KAAK,SAAS;YAC1B,CAAC,CAAC,uCAAuC;YACzC,CAAC,CAAC,8EAA8E,CAAA;IACpF,CAAC;IACD,OAAO,OAAO,KAAK,MAAM;QACvB,CAAC,CAAC,sEAAsE;QACxE,CAAC,CAAC,0EAA0E,CAAA;AAChF,CAAC"}
@@ -0,0 +1,22 @@
1
+ import { type ContentAction } from '../types.js';
2
+ import type { RolePermissionOverrideRecord, RolePermissionTargetType } from './role-permission-store.js';
3
+ export declare const ROLE_PERMISSION_EXPORT_VERSION = 1;
4
+ export interface RolePermissionExportRow {
5
+ readonly targetType: RolePermissionTargetType;
6
+ readonly targetName: string;
7
+ readonly action: ContentAction;
8
+ readonly roles: readonly string[];
9
+ readonly own: boolean;
10
+ readonly updatedAt: string;
11
+ readonly updatedBy: string | null;
12
+ }
13
+ export interface RolePermissionExportFile {
14
+ readonly version: typeof ROLE_PERMISSION_EXPORT_VERSION;
15
+ readonly exportedAt: string;
16
+ readonly overrides: readonly RolePermissionExportRow[];
17
+ }
18
+ /** Sorted for a clean, stable git diff between two exports — never insertion order. */
19
+ export declare function serialiseRolePermissionExport(records: readonly RolePermissionOverrideRecord[], now?: () => Date): RolePermissionExportFile;
20
+ /** The round trip's other half: `JSON.parse` a file `serialiseRolePermissionExport` wrote, checked shape by shape. */
21
+ export declare function parseRolePermissionExport(value: unknown): RolePermissionExportFile;
22
+ //# sourceMappingURL=role-permission-export.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role-permission-export.d.ts","sourceRoot":"","sources":["../../src/store/role-permission-export.ts"],"names":[],"mappings":"AACA,OAAO,EAAmB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAA;AACjE,OAAO,KAAK,EACV,4BAA4B,EAC5B,wBAAwB,EACzB,MAAM,4BAA4B,CAAA;AAmBnC,eAAO,MAAM,8BAA8B,IAAI,CAAA;AAE/C,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,UAAU,EAAE,wBAAwB,CAAA;IAC7C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAA;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAClC;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,OAAO,EAAE,OAAO,8BAA8B,CAAA;IACvD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,uBAAuB,EAAE,CAAA;CACvD;AAMD,uFAAuF;AACvF,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,SAAS,4BAA4B,EAAE,EAChD,GAAG,GAAE,MAAM,IAAuB,GACjC,wBAAwB,CAG1B;AAyDD,sHAAsH;AACtH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,wBAAwB,CAqBlF"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role-permission-export.js","sourceRoot":"","sources":["../../src/store/role-permission-export.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { ContentAction, NormalisedPermissionRule } from '../types.js';
2
+ import type { RolePermissionStore } from './role-permission-store.js';
3
+ /**
4
+ * The synchronous face `PermissionLayer` (`@cogenta/api`) actually reads.
5
+ *
6
+ * `can()`/`canTerm()` run on every request, in a tight loop, and must stay
7
+ * synchronous — turning them `async` would ripple through every REST route,
8
+ * the GraphQL gateway and the page-builder preview for one feature. So the
9
+ * database read happens once, here, ahead of time; the request path only
10
+ * ever does a `Map.get`.
11
+ */
12
+ export interface RolePermissionOverrides {
13
+ /** `undefined` means no override exists — the caller falls back to the file. Never the reverse. */
14
+ getCollectionRule(collection: string, action: ContentAction): NormalisedPermissionRule | undefined;
15
+ getTaxonomyRule(taxonomy: string, action: ContentAction): NormalisedPermissionRule | undefined;
16
+ }
17
+ export interface RolePermissionOverlay extends RolePermissionOverrides {
18
+ /**
19
+ * Re-reads every row from the store. Call after a write completes so the
20
+ * very next request already sees it — no restart, the same promise the
21
+ * appearance screen's live theme swap already makes.
22
+ */
23
+ refresh(): Promise<void>;
24
+ }
25
+ /** Built once at startup (`assembleSite`), awaited before `createPermissionLayer` runs. */
26
+ export declare function createRolePermissionOverlay(store: RolePermissionStore): Promise<RolePermissionOverlay>;
27
+ //# sourceMappingURL=role-permission-overlay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role-permission-overlay.d.ts","sourceRoot":"","sources":["../../src/store/role-permission-overlay.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAA;AAC1E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAErE;;;;;;;;GAQG;AACH,MAAM,WAAW,uBAAuB;IACtC,mGAAmG;IACnG,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,wBAAwB,GAAG,SAAS,CAAA;IAClG,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,wBAAwB,GAAG,SAAS,CAAA;CAC/F;AAED,MAAM,WAAW,qBAAsB,SAAQ,uBAAuB;IACpE;;;;OAIG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACzB;AAMD,2FAA2F;AAC3F,wBAAsB,2BAA2B,CAC/C,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,qBAAqB,CAAC,CA0BhC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role-permission-overlay.js","sourceRoot":"","sources":["../../src/store/role-permission-overlay.ts"],"names":[],"mappings":"AA2BA,SAAS,GAAG,CAAC,IAAY,EAAE,MAAqB;IAC9C,OAAO,GAAG,IAAI,IAAI,MAAM,EAAE,CAAA;AAC5B,CAAC;AAED,2FAA2F;AAC3F,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAC/C,KAA0B;IAE1B,IAAI,YAAY,GAAG,IAAI,GAAG,EAAoC,CAAA;IAC9D,IAAI,UAAU,GAAG,IAAI,GAAG,EAAoC,CAAA;IAE5D,KAAK,UAAU,MAAM;QACnB,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,CAAA;QAClC,MAAM,cAAc,GAAG,IAAI,GAAG,EAAoC,CAAA;QAClE,MAAM,YAAY,GAAG,IAAI,GAAG,EAAoC,CAAA;QAEhE,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,MAAM,IAAI,GAA6B,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,CAAA;YAC/E,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,KAAK,YAAY,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,YAAY,CAAA;YACjF,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAA;QACzD,CAAC;QAED,YAAY,GAAG,cAAc,CAAA;QAC7B,UAAU,GAAG,YAAY,CAAA;IAC3B,CAAC;IAED,MAAM,MAAM,EAAE,CAAA;IAEd,OAAO;QACL,iBAAiB,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QACpF,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC5E,OAAO,EAAE,MAAM;KAChB,CAAA;AACH,CAAC"}
@@ -0,0 +1,72 @@
1
+ import { type DatabaseHandle } from '@cogenta/core';
2
+ import type { CollectionDefinition, ContentAction, TaxonomyDefinition } from '../types.js';
3
+ /**
4
+ * The database-backed override layer of a role's permissions (fiche 63,
5
+ * ADR-0028).
6
+ *
7
+ * A row here **replaces** a `(targetType, targetName, action)` rule from
8
+ * `cogenta.schema.*` entirely; it never merges with it. `PermissionLayer`
9
+ * (`@cogenta/api`) is the only reader that turns this into an access
10
+ * decision — this store's job stops at persisting a validated row.
11
+ *
12
+ * **Validation reuses `defineCollection`/`defineTaxonomy` — task 4 of the
13
+ * fiche, "ne pas dupliquer une seconde logique de validation."** A candidate
14
+ * write is folded into the real, already-loaded `CollectionDefinition` or
15
+ * `TaxonomyDefinition` (the same objects `PermissionLayer` was built from) in
16
+ * place of the one action being changed, and the result is handed to the
17
+ * same function that validates a collection or taxonomy loaded from the
18
+ * schema file. An invalid role name, an unknown action, or `own` on a
19
+ * taxonomy (which has no author — `defineTaxonomy`'s own `checkPermissions`
20
+ * only accepts a plain role list) is refused by that single door, not a
21
+ * second copy of its rules.
22
+ */
23
+ export type RolePermissionTargetType = 'collection' | 'taxonomy';
24
+ export interface RolePermissionOverrideRecord {
25
+ readonly targetType: RolePermissionTargetType;
26
+ readonly targetName: string;
27
+ readonly action: ContentAction;
28
+ readonly roles: readonly string[];
29
+ /** Always `false` for a taxonomy target — see the module comment. */
30
+ readonly own: boolean;
31
+ readonly updatedAt: string;
32
+ readonly updatedBy: string | null;
33
+ }
34
+ export interface SetRolePermissionInput {
35
+ readonly targetType: RolePermissionTargetType;
36
+ readonly targetName: string;
37
+ readonly action: ContentAction;
38
+ readonly roles: readonly string[];
39
+ readonly own?: boolean;
40
+ readonly updatedBy?: string | null;
41
+ }
42
+ export interface RolePermissionStoreOptions {
43
+ readonly db: DatabaseHandle;
44
+ /**
45
+ * The file-declared collections/taxonomies to validate an override
46
+ * against — the same arrays `createPermissionLayer` was built from at
47
+ * startup. A target absent from both is `ROLE_PERMISSION_TARGET_UNKNOWN`:
48
+ * an override can narrow or widen an existing collection or taxonomy's
49
+ * rule, never invent a collection that is not declared in code.
50
+ */
51
+ readonly collections: readonly CollectionDefinition[];
52
+ readonly taxonomies: readonly TaxonomyDefinition[];
53
+ readonly now?: () => Date;
54
+ }
55
+ export interface RolePermissionStore {
56
+ /** Creates the table if it is missing. Called for you by every other method. */
57
+ ensureTable(): Promise<void>;
58
+ list(): Promise<readonly RolePermissionOverrideRecord[]>;
59
+ /**
60
+ * Validates the candidate (see the module comment) and writes it,
61
+ * replacing any existing row for the same `(targetType, targetName,
62
+ * action)` triple.
63
+ */
64
+ set(input: SetRolePermissionInput): Promise<RolePermissionOverrideRecord>;
65
+ /**
66
+ * Deletes the override, if any — the target's action falls back to
67
+ * `cogenta.schema.*` on the very next check. Returns whether a row existed.
68
+ */
69
+ remove(targetType: RolePermissionTargetType, targetName: string, action: ContentAction): Promise<boolean>;
70
+ }
71
+ export declare function createRolePermissionStore(options: RolePermissionStoreOptions): RolePermissionStore;
72
+ //# sourceMappingURL=role-permission-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role-permission-store.d.ts","sourceRoot":"","sources":["../../src/store/role-permission-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,cAAc,EAAmB,MAAM,eAAe,CAAA;AAGlF,OAAO,KAAK,EACV,oBAAoB,EAEpB,aAAa,EACb,kBAAkB,EACnB,MAAM,aAAa,CAAA;AAGpB;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG,UAAU,CAAA;AAEhE,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,UAAU,EAAE,wBAAwB,CAAA;IAC7C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,qEAAqE;IACrE,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAA;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAClC;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,UAAU,EAAE,wBAAwB,CAAA;IAC7C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACnC;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,EAAE,EAAE,cAAc,CAAA;IAC3B;;;;;;OAMG;IACH,QAAQ,CAAC,WAAW,EAAE,SAAS,oBAAoB,EAAE,CAAA;IACrD,QAAQ,CAAC,UAAU,EAAE,SAAS,kBAAkB,EAAE,CAAA;IAClD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;CAC1B;AAED,MAAM,WAAW,mBAAmB;IAClC,gFAAgF;IAChF,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,IAAI,IAAI,OAAO,CAAC,SAAS,4BAA4B,EAAE,CAAC,CAAA;IACxD;;;;OAIG;IACH,GAAG,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,4BAA4B,CAAC,CAAA;IACzE;;;OAGG;IACH,MAAM,CACJ,UAAU,EAAE,wBAAwB,EACpC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,OAAO,CAAC,CAAA;CACpB;AAgFD,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,0BAA0B,GAClC,mBAAmB,CAqGrB"}
@@ -0,0 +1,148 @@
1
+ import { CogentaError, identifier, sql } from '@cogenta/core';
2
+ import { defineCollection } from '../define-collection.js';
3
+ import { defineTaxonomy } from '../define-taxonomy.js';
4
+ import { ensureRolePermissionTable, ROLE_PERMISSIONS_TABLE } from './role-permission-tables.js';
5
+ function text(value) {
6
+ return typeof value === 'string' ? value : String(value);
7
+ }
8
+ function nullableText(value) {
9
+ return value === null || value === undefined ? null : text(value);
10
+ }
11
+ function toBool(value) {
12
+ return value === true || value === 'true' || value === 1 || value === '1';
13
+ }
14
+ function toRecord(row) {
15
+ return {
16
+ targetType: text(row['target_type']),
17
+ targetName: text(row['target_name']),
18
+ action: text(row['action']),
19
+ roles: JSON.parse(text(row['roles'])),
20
+ own: toBool(row['own']),
21
+ updatedAt: text(row['updated_at']),
22
+ updatedBy: nullableText(row['updated_by']),
23
+ };
24
+ }
25
+ function unknownTarget(targetType, targetName) {
26
+ return new CogentaError({
27
+ code: 'ROLE_PERMISSION_TARGET_UNKNOWN',
28
+ message: `No ${targetType} named "${targetName}" is declared in the site's schema.`,
29
+ hint: targetType === 'collection'
30
+ ? 'Overriding a role permission needs a real defineCollection() — check the spelling against cogenta.schema.*.'
31
+ : 'Overriding a role permission needs a real defineTaxonomy() — check the spelling against cogenta.schema.*.',
32
+ details: { targetType, targetName },
33
+ });
34
+ }
35
+ function ownNotSupported(targetName) {
36
+ return new CogentaError({
37
+ code: 'ROLE_PERMISSION_INVALID',
38
+ message: `Taxonomy "${targetName}" has no author, so "own" has no meaning here.`,
39
+ hint: 'Drop "own" — it only applies to a collection action (schema@2.1, ADR-0027).',
40
+ details: { targetName },
41
+ });
42
+ }
43
+ /**
44
+ * Folds one action's candidate rule into the real, already-validated
45
+ * definition and re-validates the whole thing through `defineCollection` —
46
+ * the single door contract A's permission shape ever passes through. Any
47
+ * other action already on the collection is carried over unchanged.
48
+ */
49
+ function validateCollectionCandidate(target, action, roles, own) {
50
+ const permissions = {
51
+ ...target.permissions,
52
+ [action]: own ? { roles, own: true } : [...roles],
53
+ };
54
+ // Throws SCHEMA_INVALID (via `schemaError`) naming every issue, exactly as
55
+ // it would for a hand-edited `cogenta.schema.*` — the only difference here
56
+ // is *where* the candidate came from.
57
+ defineCollection({ ...target, permissions });
58
+ }
59
+ function validateTaxonomyCandidate(target, action, roles) {
60
+ const permissions = { ...target.permissions, [action]: [...roles] };
61
+ defineTaxonomy({ ...target, permissions });
62
+ }
63
+ export function createRolePermissionStore(options) {
64
+ const { db, collections, taxonomies } = options;
65
+ const dialect = db.dialect;
66
+ const now = options.now ?? (() => new Date());
67
+ const table = identifier(ROLE_PERMISSIONS_TABLE, dialect);
68
+ let ready = false;
69
+ async function ensureTable() {
70
+ if (ready)
71
+ return;
72
+ await ensureRolePermissionTable(db);
73
+ ready = true;
74
+ }
75
+ function findCollection(name) {
76
+ return collections.find((collection) => collection.name === name);
77
+ }
78
+ function findTaxonomy(name) {
79
+ return taxonomies.find((taxonomy) => taxonomy.name === name);
80
+ }
81
+ return {
82
+ ensureTable,
83
+ list: async () => {
84
+ await ensureTable();
85
+ const found = await db.query(sql `select * from ${table} order by ${identifier('target_type', dialect)} asc, ${identifier('target_name', dialect)} asc, ${identifier('action', dialect)} asc`);
86
+ return found.rows.map(toRecord);
87
+ },
88
+ set: async (input) => {
89
+ await ensureTable();
90
+ const own = input.own === true;
91
+ if (input.targetType === 'collection') {
92
+ const target = findCollection(input.targetName);
93
+ if (target === undefined)
94
+ throw unknownTarget('collection', input.targetName);
95
+ validateCollectionCandidate(target, input.action, input.roles, own);
96
+ }
97
+ else {
98
+ if (own)
99
+ throw ownNotSupported(input.targetName);
100
+ const target = findTaxonomy(input.targetName);
101
+ if (target === undefined)
102
+ throw unknownTarget('taxonomy', input.targetName);
103
+ validateTaxonomyCandidate(target, input.action, input.roles);
104
+ }
105
+ const at = now().toISOString();
106
+ const updatedBy = input.updatedBy ?? null;
107
+ const rolesJson = JSON.stringify(input.roles);
108
+ const ownValue = own ? 'true' : 'false';
109
+ await db.transaction(async (tx) => {
110
+ // Delete-then-insert rather than an upsert: `ON CONFLICT`, `ON
111
+ // DUPLICATE KEY` and `INSERT OR REPLACE` are three different
112
+ // statements across the three dialects — the same reasoning
113
+ // `redirects.ts`'s `performAdd` already gives for its own writes.
114
+ await tx.query(sql `delete from ${table}
115
+ where ${identifier('target_type', dialect)} = ${input.targetType}
116
+ and ${identifier('target_name', dialect)} = ${input.targetName}
117
+ and ${identifier('action', dialect)} = ${input.action}`);
118
+ await tx.query(sql `insert into ${table} (
119
+ ${identifier('target_type', dialect)}, ${identifier('target_name', dialect)},
120
+ ${identifier('action', dialect)}, ${identifier('roles', dialect)},
121
+ ${identifier('own', dialect)}, ${identifier('updated_at', dialect)},
122
+ ${identifier('updated_by', dialect)}
123
+ ) values (
124
+ ${input.targetType}, ${input.targetName}, ${input.action}, ${rolesJson},
125
+ ${ownValue}, ${at}, ${updatedBy}
126
+ )`);
127
+ }, { immediate: true });
128
+ return {
129
+ targetType: input.targetType,
130
+ targetName: input.targetName,
131
+ action: input.action,
132
+ roles: input.roles,
133
+ own,
134
+ updatedAt: at,
135
+ updatedBy,
136
+ };
137
+ },
138
+ remove: async (targetType, targetName, action) => {
139
+ await ensureTable();
140
+ const result = await db.query(sql `delete from ${table}
141
+ where ${identifier('target_type', dialect)} = ${targetType}
142
+ and ${identifier('target_name', dialect)} = ${targetName}
143
+ and ${identifier('action', dialect)} = ${action}`);
144
+ return result.rowsAffected > 0;
145
+ },
146
+ };
147
+ }
148
+ //# sourceMappingURL=role-permission-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role-permission-store.js","sourceRoot":"","sources":["../../src/store/role-permission-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import { type DatabaseHandle } from '@cogenta/core';
2
+ /**
3
+ * Role permission overrides (fiche 63, ADR-0028).
4
+ *
5
+ * `cogenta.schema.*` (contract A's `permissions` block) is the file the site
6
+ * was deployed with — code, reviewed and versioned like any other (ADR-0010).
7
+ * This table is the deliberate exception that decision names: a permission
8
+ * change an admin makes in production, applied without a deploy cycle. One
9
+ * fixed table, exactly `menu-tables.ts`'s and `site-settings-tables.ts`'s
10
+ * pattern — a role's grant on a collection or taxonomy action is not
11
+ * schema-declared content, it is runtime, admin-editable state.
12
+ *
13
+ * One row per `(targetType, targetName, action)` triple **overrides** that
14
+ * action's rule entirely — it does not merge with the file. `PermissionLayer`
15
+ * (`@cogenta/api`) checks this table first and falls back to the file only
16
+ * when no row exists for that exact triple, never the other way around: a
17
+ * deployment that regressed the file must not silently widen what the table
18
+ * had already narrowed.
19
+ *
20
+ * `own` only ever applies to a collection (`schema@2.1`, ADR-0027) — a
21
+ * taxonomy term has no author, and `createRolePermissionStore`'s own
22
+ * validation (reusing `defineTaxonomy`'s `checkPermissions`) refuses `own` on
23
+ * a taxonomy target before a row is ever written.
24
+ */
25
+ export declare const ROLE_PERMISSIONS_TABLE = "cogenta_role_permissions";
26
+ export declare function ensureRolePermissionTable(db: DatabaseHandle): Promise<void>;
27
+ //# sourceMappingURL=role-permission-tables.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role-permission-tables.d.ts","sourceRoot":"","sources":["../../src/store/role-permission-tables.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAqC,MAAM,eAAe,CAAA;AAGtF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,eAAO,MAAM,sBAAsB,6BAA6B,CAAA;AAShE,wBAAsB,yBAAyB,CAAC,EAAE,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBjF"}
@@ -0,0 +1,52 @@
1
+ import { identifier, sql } from '@cogenta/core';
2
+ import { booleanColumn, textColumn, timestampColumn } from './columns.js';
3
+ /**
4
+ * Role permission overrides (fiche 63, ADR-0028).
5
+ *
6
+ * `cogenta.schema.*` (contract A's `permissions` block) is the file the site
7
+ * was deployed with — code, reviewed and versioned like any other (ADR-0010).
8
+ * This table is the deliberate exception that decision names: a permission
9
+ * change an admin makes in production, applied without a deploy cycle. One
10
+ * fixed table, exactly `menu-tables.ts`'s and `site-settings-tables.ts`'s
11
+ * pattern — a role's grant on a collection or taxonomy action is not
12
+ * schema-declared content, it is runtime, admin-editable state.
13
+ *
14
+ * One row per `(targetType, targetName, action)` triple **overrides** that
15
+ * action's rule entirely — it does not merge with the file. `PermissionLayer`
16
+ * (`@cogenta/api`) checks this table first and falls back to the file only
17
+ * when no row exists for that exact triple, never the other way around: a
18
+ * deployment that regressed the file must not silently widen what the table
19
+ * had already narrowed.
20
+ *
21
+ * `own` only ever applies to a collection (`schema@2.1`, ADR-0027) — a
22
+ * taxonomy term has no author, and `createRolePermissionStore`'s own
23
+ * validation (reusing `defineTaxonomy`'s `checkPermissions`) refuses `own` on
24
+ * a taxonomy target before a row is ever written.
25
+ */
26
+ export const ROLE_PERMISSIONS_TABLE = 'cogenta_role_permissions';
27
+ /** `collection` or `taxonomy` — matches `RolePermissionTargetType`. */
28
+ const TARGET_TYPE_LENGTH = 16;
29
+ /** Same bound `naming.ts`'s `checked()` enforces on a collection/taxonomy name. */
30
+ const TARGET_NAME_LENGTH = 64;
31
+ /** `read`, `create`, `update`, `delete`, `publish` — contract A's fixed set. */
32
+ const ACTION_LENGTH = 16;
33
+ export async function ensureRolePermissionTable(db) {
34
+ const dialect = db.dialect;
35
+ const table = identifier(ROLE_PERMISSIONS_TABLE, dialect);
36
+ const statements = [
37
+ sql `create table if not exists ${table} (
38
+ ${identifier('target_type', dialect)} ${textColumn(dialect, TARGET_TYPE_LENGTH)} not null,
39
+ ${identifier('target_name', dialect)} ${textColumn(dialect, TARGET_NAME_LENGTH)} not null,
40
+ ${identifier('action', dialect)} ${textColumn(dialect, ACTION_LENGTH)} not null,
41
+ ${identifier('roles', dialect)} ${textColumn(dialect, 2000)} not null,
42
+ ${identifier('own', dialect)} ${booleanColumn(dialect)} not null,
43
+ ${identifier('updated_at', dialect)} ${timestampColumn(dialect)} not null,
44
+ ${identifier('updated_by', dialect)} ${textColumn(dialect, 36)},
45
+ primary key (${identifier('target_type', dialect)}, ${identifier('target_name', dialect)}, ${identifier('action', dialect)})
46
+ )`,
47
+ ];
48
+ for (const statement of statements) {
49
+ await db.query(statement);
50
+ }
51
+ }
52
+ //# sourceMappingURL=role-permission-tables.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"role-permission-tables.js","sourceRoot":"","sources":["../../src/store/role-permission-tables.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,UAAU,EAAoB,GAAG,EAAE,MAAM,eAAe,CAAA;AACtF,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAEzE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,CAAC,MAAM,sBAAsB,GAAG,0BAA0B,CAAA;AAEhE,uEAAuE;AACvE,MAAM,kBAAkB,GAAG,EAAE,CAAA;AAC7B,mFAAmF;AACnF,MAAM,kBAAkB,GAAG,EAAE,CAAA;AAC7B,gFAAgF;AAChF,MAAM,aAAa,GAAG,EAAE,CAAA;AAExB,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAAC,EAAkB;IAChE,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,CAAA;IAC1B,MAAM,KAAK,GAAG,UAAU,CAAC,sBAAsB,EAAE,OAAO,CAAC,CAAA;IAEzD,MAAM,UAAU,GAAkB;QAChC,GAAG,CAAA,8BAA8B,KAAK;QAClC,UAAU,CAAC,aAAa,EAAE,OAAO,CAAC,IAAI,UAAU,CAAC,OAAO,EAAE,kBAAkB,CAAC;QAC7E,UAAU,CAAC,aAAa,EAAE,OAAO,CAAC,IAAI,UAAU,CAAC,OAAO,EAAE,kBAAkB,CAAC;QAC7E,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,UAAU,CAAC,OAAO,EAAE,aAAa,CAAC;QACnE,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC;QACzD,UAAU,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,aAAa,CAAC,OAAO,CAAC;QACpD,UAAU,CAAC,YAAY,EAAE,OAAO,CAAC,IAAI,eAAe,CAAC,OAAO,CAAC;QAC7D,UAAU,CAAC,YAAY,EAAE,OAAO,CAAC,IAAI,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC;qBAC/C,UAAU,CAAC,aAAa,EAAE,OAAO,CAAC,KAAK,UAAU,CAAC,aAAa,EAAE,OAAO,CAAC,KAAK,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC;MAC1H;KACH,CAAA;IAED,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;IAC3B,CAAC;AACH,CAAC"}