cursedbelt-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (307) hide show
  1. package/LICENSE +15 -0
  2. package/dist/client/wsWire.d.ts +49 -0
  3. package/dist/client/wsWire.js +57 -0
  4. package/dist/config/appConfig.d.ts +361 -0
  5. package/dist/config/appConfig.js +308 -0
  6. package/dist/core/activity/index.d.ts +8 -0
  7. package/dist/core/activity/index.js +7 -0
  8. package/dist/core/activity/model.d.ts +115 -0
  9. package/dist/core/activity/model.js +63 -0
  10. package/dist/core/analytics/index.d.ts +18 -0
  11. package/dist/core/analytics/index.js +18 -0
  12. package/dist/core/analytics/range.d.ts +186 -0
  13. package/dist/core/analytics/range.js +271 -0
  14. package/dist/core/analytics/schedule.d.ts +301 -0
  15. package/dist/core/analytics/schedule.js +372 -0
  16. package/dist/core/category/index.d.ts +3 -0
  17. package/dist/core/category/index.js +6 -0
  18. package/dist/core/category/memoryStore.d.ts +21 -0
  19. package/dist/core/category/memoryStore.js +97 -0
  20. package/dist/core/category/tree.d.ts +26 -0
  21. package/dist/core/category/tree.js +113 -0
  22. package/dist/core/category/types.d.ts +48 -0
  23. package/dist/core/category/types.js +8 -0
  24. package/dist/core/color.d.ts +14 -0
  25. package/dist/core/color.js +32 -0
  26. package/dist/core/colorScheme/colorSchemeStore.d.ts +70 -0
  27. package/dist/core/colorScheme/colorSchemeStore.js +123 -0
  28. package/dist/core/colorScheme/index.d.ts +7 -0
  29. package/dist/core/colorScheme/index.js +7 -0
  30. package/dist/core/domainOwners.d.ts +19 -0
  31. package/dist/core/domainOwners.js +15 -0
  32. package/dist/core/download.d.ts +14 -0
  33. package/dist/core/download.js +37 -0
  34. package/dist/core/events/ccEventBus.d.ts +44 -0
  35. package/dist/core/events/ccEventBus.js +70 -0
  36. package/dist/core/file-tree/fileTreeModel.d.ts +276 -0
  37. package/dist/core/file-tree/fileTreeModel.js +312 -0
  38. package/dist/core/folder-tree/actions.d.ts +84 -0
  39. package/dist/core/folder-tree/actions.js +119 -0
  40. package/dist/core/folder-tree/index.d.ts +3 -0
  41. package/dist/core/folder-tree/index.js +20 -0
  42. package/dist/core/folder-tree/path.d.ts +76 -0
  43. package/dist/core/folder-tree/path.js +146 -0
  44. package/dist/core/folder-tree/tree.d.ts +86 -0
  45. package/dist/core/folder-tree/tree.js +184 -0
  46. package/dist/core/layout/index.d.ts +1 -0
  47. package/dist/core/layout/index.js +7 -0
  48. package/dist/core/master-lock/index.d.ts +14 -0
  49. package/dist/core/master-lock/index.js +14 -0
  50. package/dist/core/master-lock/kdf.d.ts +63 -0
  51. package/dist/core/master-lock/kdf.js +117 -0
  52. package/dist/core/master-lock/policy.d.ts +32 -0
  53. package/dist/core/master-lock/policy.js +52 -0
  54. package/dist/core/master-lock/presence.d.ts +52 -0
  55. package/dist/core/master-lock/presence.js +61 -0
  56. package/dist/core/master-lock/wire.d.ts +139 -0
  57. package/dist/core/master-lock/wire.js +81 -0
  58. package/dist/core/media/bgRemoval.d.ts +27 -0
  59. package/dist/core/media/bgRemoval.js +101 -0
  60. package/dist/core/media/ffmpeg.d.ts +57 -0
  61. package/dist/core/media/ffmpeg.js +126 -0
  62. package/dist/core/media/format.d.ts +20 -0
  63. package/dist/core/media/format.js +54 -0
  64. package/dist/core/media/index.d.ts +12 -0
  65. package/dist/core/media/index.js +18 -0
  66. package/dist/core/media/mediaItem.d.ts +15 -0
  67. package/dist/core/media/mediaItem.js +34 -0
  68. package/dist/core/media/renderPlan.d.ts +63 -0
  69. package/dist/core/media/renderPlan.js +232 -0
  70. package/dist/core/media/segment.d.ts +79 -0
  71. package/dist/core/media/segment.js +247 -0
  72. package/dist/core/media/size.d.ts +1 -0
  73. package/dist/core/media/size.js +3 -0
  74. package/dist/core/media/trim.d.ts +21 -0
  75. package/dist/core/media/trim.js +40 -0
  76. package/dist/core/media/types.d.ts +148 -0
  77. package/dist/core/media/types.js +11 -0
  78. package/dist/core/media/uploadLimits.d.ts +7 -0
  79. package/dist/core/media/uploadLimits.js +16 -0
  80. package/dist/core/media/validate.d.ts +9 -0
  81. package/dist/core/media/validate.js +121 -0
  82. package/dist/core/media/videoCuts.d.ts +152 -0
  83. package/dist/core/media/videoCuts.js +284 -0
  84. package/dist/core/navigation/breadcrumbCompat.d.ts +24 -0
  85. package/dist/core/navigation/breadcrumbCompat.js +21 -0
  86. package/dist/core/navigation/coverageTest.d.ts +24 -0
  87. package/dist/core/navigation/coverageTest.js +48 -0
  88. package/dist/core/navigation/index.d.ts +6 -0
  89. package/dist/core/navigation/index.js +10 -0
  90. package/dist/core/navigation/migrateNavPreferences.d.ts +55 -0
  91. package/dist/core/navigation/migrateNavPreferences.js +63 -0
  92. package/dist/core/navigation/navigationModel.d.ts +165 -0
  93. package/dist/core/navigation/navigationModel.js +14 -0
  94. package/dist/core/navigation/navigationSelectors.d.ts +63 -0
  95. package/dist/core/navigation/navigationSelectors.js +167 -0
  96. package/dist/core/navigation/searchCatalogue.d.ts +23 -0
  97. package/dist/core/navigation/searchCatalogue.js +76 -0
  98. package/dist/core/net/reconnectSchedule.d.ts +46 -0
  99. package/dist/core/net/reconnectSchedule.js +38 -0
  100. package/dist/core/palette/ShortcutRegistry.d.ts +33 -0
  101. package/dist/core/palette/ShortcutRegistry.js +52 -0
  102. package/dist/core/palette/fuzzyRank.d.ts +18 -0
  103. package/dist/core/palette/fuzzyRank.js +73 -0
  104. package/dist/core/palette/hotkeys.d.ts +28 -0
  105. package/dist/core/palette/hotkeys.js +118 -0
  106. package/dist/core/palette/index.d.ts +3 -0
  107. package/dist/core/palette/index.js +8 -0
  108. package/dist/core/schema/projection.d.ts +84 -0
  109. package/dist/core/schema/projection.js +184 -0
  110. package/dist/core/session/claims.d.ts +145 -0
  111. package/dist/core/session/claims.js +157 -0
  112. package/dist/core/sharing/index.d.ts +5 -0
  113. package/dist/core/sharing/index.js +4 -0
  114. package/dist/core/sharing/model.d.ts +195 -0
  115. package/dist/core/sharing/model.js +150 -0
  116. package/dist/core/site/siteCopy.d.ts +92 -0
  117. package/dist/core/site/siteCopy.js +91 -0
  118. package/dist/core/slots.d.ts +22 -0
  119. package/dist/core/slots.js +31 -0
  120. package/dist/core/styles/constructableSheet.d.ts +51 -0
  121. package/dist/core/styles/constructableSheet.js +80 -0
  122. package/dist/core/tokens/registry.d.ts +119 -0
  123. package/dist/core/tokens/registry.js +447 -0
  124. package/dist/core/uiScale/index.d.ts +8 -0
  125. package/dist/core/uiScale/index.js +8 -0
  126. package/dist/core/uiScale/scale.d.ts +36 -0
  127. package/dist/core/uiScale/scale.js +47 -0
  128. package/dist/core/uiScale/uiScaleStore.d.ts +47 -0
  129. package/dist/core/uiScale/uiScaleStore.js +63 -0
  130. package/dist/core/wire/__fixtures__/rowsMarshalWorker.d.ts +1 -0
  131. package/dist/core/wire/__fixtures__/rowsMarshalWorker.js +23 -0
  132. package/dist/core/wire/index.d.ts +17 -0
  133. package/dist/core/wire/index.js +17 -0
  134. package/dist/core/wire/ingress.d.ts +44 -0
  135. package/dist/core/wire/ingress.js +134 -0
  136. package/dist/core/wire/marshalWorker.d.ts +70 -0
  137. package/dist/core/wire/marshalWorker.js +273 -0
  138. package/dist/core/wire/marshalling.d.ts +122 -0
  139. package/dist/core/wire/marshalling.js +126 -0
  140. package/dist/core/wire/wireHub.d.ts +80 -0
  141. package/dist/core/wire/wireHub.js +116 -0
  142. package/dist/ctgr/base64url.d.ts +8 -0
  143. package/dist/ctgr/base64url.js +39 -0
  144. package/dist/ctgr/chunkAccumulator.d.ts +214 -0
  145. package/dist/ctgr/chunkAccumulator.js +307 -0
  146. package/dist/ctgr/chunkStore.d.ts +24 -0
  147. package/dist/ctgr/chunkStore.js +125 -0
  148. package/dist/ctgr/codec.d.ts +44 -0
  149. package/dist/ctgr/codec.js +153 -0
  150. package/dist/ctgr/compress.d.ts +2 -0
  151. package/dist/ctgr/compress.js +20 -0
  152. package/dist/ctgr/crc32.d.ts +2 -0
  153. package/dist/ctgr/crc32.js +23 -0
  154. package/dist/ctgr/digest.d.ts +1 -0
  155. package/dist/ctgr/digest.js +15 -0
  156. package/dist/ctgr/index.d.ts +9 -0
  157. package/dist/ctgr/index.js +17 -0
  158. package/dist/ctgr/types.d.ts +64 -0
  159. package/dist/ctgr/types.js +21 -0
  160. package/dist/ctgr/v0compat.d.ts +27 -0
  161. package/dist/ctgr/v0compat.js +128 -0
  162. package/dist/index.d.ts +15 -0
  163. package/dist/index.js +17 -0
  164. package/dist/shared/errors.d.ts +101 -0
  165. package/dist/shared/errors.js +99 -0
  166. package/dist/testing/bytes.d.ts +32 -0
  167. package/dist/testing/bytes.js +33 -0
  168. package/dist/testing/domInspect.d.ts +59 -0
  169. package/dist/testing/domInspect.js +115 -0
  170. package/dist/testing/fakeFetch.d.ts +23 -0
  171. package/dist/testing/fakeFetch.js +21 -0
  172. package/dist/testing/index.d.ts +3 -0
  173. package/dist/testing/index.js +11 -0
  174. package/package.json +373 -0
  175. package/src/client/wsWire.ts +83 -0
  176. package/src/config/appConfig.spec.ts +158 -0
  177. package/src/config/appConfig.ts +372 -0
  178. package/src/core/activity/index.ts +21 -0
  179. package/src/core/activity/model.ts +160 -0
  180. package/src/core/analytics/index.ts +18 -0
  181. package/src/core/analytics/range.spec.ts +280 -0
  182. package/src/core/analytics/range.ts +383 -0
  183. package/src/core/analytics/schedule.spec.ts +311 -0
  184. package/src/core/analytics/schedule.ts +624 -0
  185. package/src/core/category/category.spec.ts +144 -0
  186. package/src/core/category/index.ts +19 -0
  187. package/src/core/category/memoryStore.ts +104 -0
  188. package/src/core/category/tree.ts +118 -0
  189. package/src/core/category/types.ts +59 -0
  190. package/src/core/color.spec.ts +35 -0
  191. package/src/core/color.ts +35 -0
  192. package/src/core/colorScheme/colorScheme.spec.ts +127 -0
  193. package/src/core/colorScheme/colorSchemeStore.ts +184 -0
  194. package/src/core/colorScheme/index.ts +23 -0
  195. package/src/core/domainOwners.ts +32 -0
  196. package/src/core/download.spec.ts +38 -0
  197. package/src/core/download.ts +35 -0
  198. package/src/core/events/ccEventBus.spec.ts +90 -0
  199. package/src/core/events/ccEventBus.ts +120 -0
  200. package/src/core/file-tree/fileTreeModel.spec.ts +462 -0
  201. package/src/core/file-tree/fileTreeModel.ts +511 -0
  202. package/src/core/folder-tree/actions.spec.ts +103 -0
  203. package/src/core/folder-tree/actions.ts +153 -0
  204. package/src/core/folder-tree/index.ts +46 -0
  205. package/src/core/folder-tree/path.spec.ts +113 -0
  206. package/src/core/folder-tree/path.ts +148 -0
  207. package/src/core/folder-tree/tree.spec.ts +157 -0
  208. package/src/core/folder-tree/tree.ts +244 -0
  209. package/src/core/layout/index.ts +7 -0
  210. package/src/core/master-lock/index.ts +50 -0
  211. package/src/core/master-lock/kdf.spec.ts +95 -0
  212. package/src/core/master-lock/kdf.ts +145 -0
  213. package/src/core/master-lock/policy.ts +55 -0
  214. package/src/core/master-lock/presence.spec.ts +73 -0
  215. package/src/core/master-lock/presence.ts +71 -0
  216. package/src/core/master-lock/wire.ts +155 -0
  217. package/src/core/media/bgRemoval.spec.ts +120 -0
  218. package/src/core/media/bgRemoval.ts +123 -0
  219. package/src/core/media/ffmpeg.spec.ts +135 -0
  220. package/src/core/media/ffmpeg.ts +162 -0
  221. package/src/core/media/format.spec.ts +41 -0
  222. package/src/core/media/format.ts +58 -0
  223. package/src/core/media/index.ts +105 -0
  224. package/src/core/media/mediaItem.spec.ts +46 -0
  225. package/src/core/media/mediaItem.ts +40 -0
  226. package/src/core/media/renderPlan.spec.ts +140 -0
  227. package/src/core/media/renderPlan.ts +318 -0
  228. package/src/core/media/segment.spec.ts +159 -0
  229. package/src/core/media/segment.ts +352 -0
  230. package/src/core/media/size.spec.ts +19 -0
  231. package/src/core/media/size.ts +3 -0
  232. package/src/core/media/trim.spec.ts +75 -0
  233. package/src/core/media/trim.ts +60 -0
  234. package/src/core/media/types.ts +147 -0
  235. package/src/core/media/uploadLimits.ts +18 -0
  236. package/src/core/media/validate.spec.ts +106 -0
  237. package/src/core/media/validate.ts +131 -0
  238. package/src/core/media/videoCuts.spec.ts +327 -0
  239. package/src/core/media/videoCuts.ts +355 -0
  240. package/src/core/navigation/breadcrumbCompat.ts +44 -0
  241. package/src/core/navigation/coverageTest.ts +54 -0
  242. package/src/core/navigation/index.ts +50 -0
  243. package/src/core/navigation/migrateNavPreferences.ts +119 -0
  244. package/src/core/navigation/navigation.spec.ts +388 -0
  245. package/src/core/navigation/navigationModel.ts +185 -0
  246. package/src/core/navigation/navigationSelectors.ts +206 -0
  247. package/src/core/navigation/searchCatalogue.ts +89 -0
  248. package/src/core/net/reconnectSchedule.spec.ts +49 -0
  249. package/src/core/net/reconnectSchedule.ts +78 -0
  250. package/src/core/palette/ShortcutRegistry.ts +69 -0
  251. package/src/core/palette/fuzzyRank.ts +78 -0
  252. package/src/core/palette/hotkeys.ts +132 -0
  253. package/src/core/palette/index.ts +22 -0
  254. package/src/core/palette/palette.spec.ts +160 -0
  255. package/src/core/schema/projection.spec.ts +122 -0
  256. package/src/core/schema/projection.ts +225 -0
  257. package/src/core/session/claims.ts +181 -0
  258. package/src/core/sharing/index.ts +33 -0
  259. package/src/core/sharing/model.spec.ts +104 -0
  260. package/src/core/sharing/model.ts +296 -0
  261. package/src/core/site/siteCopy.ts +115 -0
  262. package/src/core/slots.ts +38 -0
  263. package/src/core/styles/constructableSheet.ts +86 -0
  264. package/src/core/tokens/registry.spec.ts +160 -0
  265. package/src/core/tokens/registry.ts +536 -0
  266. package/src/core/uiScale/index.ts +32 -0
  267. package/src/core/uiScale/scale.ts +63 -0
  268. package/src/core/uiScale/uiScale.spec.ts +77 -0
  269. package/src/core/uiScale/uiScaleStore.ts +100 -0
  270. package/src/core/wire/__fixtures__/rowsMarshalWorker.ts +24 -0
  271. package/src/core/wire/index.ts +56 -0
  272. package/src/core/wire/ingress.spec.ts +276 -0
  273. package/src/core/wire/ingress.ts +182 -0
  274. package/src/core/wire/marshalWorker.spec.ts +196 -0
  275. package/src/core/wire/marshalWorker.ts +361 -0
  276. package/src/core/wire/marshalling.spec.ts +139 -0
  277. package/src/core/wire/marshalling.ts +238 -0
  278. package/src/core/wire/wireHub.spec.ts +137 -0
  279. package/src/core/wire/wireHub.ts +213 -0
  280. package/src/ctgr/base64url.ts +44 -0
  281. package/src/ctgr/chunkAccumulator.ts +403 -0
  282. package/src/ctgr/chunkStore.spec.ts +101 -0
  283. package/src/ctgr/chunkStore.ts +168 -0
  284. package/src/ctgr/codec.spec.ts +208 -0
  285. package/src/ctgr/codec.ts +202 -0
  286. package/src/ctgr/compress.ts +21 -0
  287. package/src/ctgr/crc32.ts +23 -0
  288. package/src/ctgr/digest.ts +14 -0
  289. package/src/ctgr/index.ts +39 -0
  290. package/src/ctgr/types.ts +80 -0
  291. package/src/ctgr/v0compat.spec.ts +73 -0
  292. package/src/ctgr/v0compat.ts +156 -0
  293. package/src/declaredDepsAreImported.spec.ts +87 -0
  294. package/src/index.ts +25 -0
  295. package/src/leafSubpathsImportNothing.spec.ts +287 -0
  296. package/src/lockfileIsTracked.spec.ts +50 -0
  297. package/src/namedSubpathsResolve.spec.ts +122 -0
  298. package/src/noPathDeps.spec.ts +24 -0
  299. package/src/publishShape.spec.ts +89 -0
  300. package/src/shared/errors.ts +153 -0
  301. package/src/skippedTestsAreAnswered.spec.ts +221 -0
  302. package/src/testTmpRoot.spec.ts +102 -0
  303. package/src/testing/bytes.ts +38 -0
  304. package/src/testing/domInspect.spec.ts +79 -0
  305. package/src/testing/domInspect.ts +127 -0
  306. package/src/testing/fakeFetch.ts +26 -0
  307. package/src/testing/index.ts +11 -0
@@ -0,0 +1,308 @@
1
+ /**
2
+ * `app-config.json` — the single schema the compiler AND the runtime import
3
+ * (02-MASTER-ENGINES §2). Nothing else may re-declare this shape: a second copy
4
+ * is how `privateMedia` without `guard` parsed as valid for a year (J2-1).
5
+ *
6
+ * Two laws are encoded here rather than in prose:
7
+ *
8
+ * 1. `.strict()` end to end. An unknown key — including inside a per-engine
9
+ * `features` block — is a parse ERROR (`unknown-config-key`), never silently
10
+ * stripped. A typo'd flag that gets dropped reads as "the feature is off" at
11
+ * runtime, which is indistinguishable from a bug in the feature.
12
+ * 2. `.superRefine()` cross-engine dependencies. `privateMedia` needs `guard`,
13
+ * `oralHistory` needs `asset`, `auth.enabled` needs `guard`, and the saas
14
+ * profile needs `guard` + `rbac:true`. These are structural: the config
15
+ * references something that will not exist at runtime, so it dies at V0 —
16
+ * fail-closed on structure (04 §0).
17
+ *
18
+ * Zod v4 notes: `z.record` requires an explicit key schema, and `addIssue` takes
19
+ * the literal `'custom'` code (the `ZodIssueCode` enum is gone).
20
+ */
21
+ import { z } from 'zod';
22
+ export const ENGINE_IDS = ['asset', 'knowledge', 'dev', 'guard', 'system'];
23
+ export const EngineIdSchema = z.enum(ENGINE_IDS);
24
+ /* ---------- per-engine feature schemas (STRICT: unknown key = V0 error) ---------- */
25
+ export const AssetFeatures = z
26
+ .object({
27
+ gallery: z.boolean().default(true),
28
+ galleryLayouts: z.array(z.enum(['masonry', 'rows', 'columns', 'dense'])).min(1).default(['rows']),
29
+ video: z.boolean().default(true), // false ⇒ video ports REMOVED (fail-closed, V2)
30
+ hls: z.boolean().default(true), // false ⇒ ports stay, progressive resolver forced (V2 degrade)
31
+ editor: z.boolean().default(false), // lazy chunk; joins bundleKey
32
+ fileBrowser: z.boolean().default(false),
33
+ taggedFiles: z.boolean().default(false),
34
+ privateMedia: z.boolean().default(false), // requires guard engine — enforced in superRefine
35
+ vaultSource: z.boolean().default(false),
36
+ ocr: z.boolean().default(false),
37
+ imageStudio: z.boolean().default(false),
38
+ })
39
+ .strict();
40
+ export const KnowledgeFeatures = z
41
+ .object({
42
+ notes: z.boolean().default(true),
43
+ todos: z.boolean().default(true),
44
+ airtableStyleLists: z.boolean().default(false),
45
+ kanban: z.boolean().default(false),
46
+ contacts: z.boolean().default(false),
47
+ oralHistory: z.boolean().default(false), // requires asset engine — enforced in superRefine
48
+ textCompare: z.boolean().default(false),
49
+ ragChat: z.boolean().default(false),
50
+ ragDiskIndexing: z.boolean().default(false),
51
+ aiOrganizer: z.boolean().default(false),
52
+ bookmarks: z.boolean().default(false),
53
+ worklog: z.boolean().default(false),
54
+ family: z.boolean().default(false),
55
+ habits: z.boolean().default(false),
56
+ articles: z.boolean().default(false),
57
+ })
58
+ .strict();
59
+ export const DevFeatures = z
60
+ .object({
61
+ appRegistry: z.boolean().default(true),
62
+ configEditor: z.boolean().default(false), // PRIVILEGED: requires 'infra:write' claim (02 §4.4)
63
+ deploy: z.boolean().default(false),
64
+ fleet: z.boolean().default(false),
65
+ pipelines: z.boolean().default(false),
66
+ docs: z.boolean().default(true),
67
+ screenshots: z.boolean().default(false),
68
+ diskUsage: z.boolean().default(false),
69
+ dbAdmin: z.boolean().default(false), // PRIVILEGED: requires 'infra:write' claim (02 §4.4)
70
+ orchestration: z.boolean().default(false),
71
+ })
72
+ .strict();
73
+ /**
74
+ * Who may create an account (D33 — the commercial-launch gate). Ordered from
75
+ * most open to most closed; `effectiveSignupMode` picks the STRICTEST configured
76
+ * value, so two guard instances can never disagree in the open direction.
77
+ *
78
+ * - `open` — anyone signs up (saas: minting their own tenant as OWNER).
79
+ * - `invite` — a valid invitation token is required; token-less signup is a
80
+ * 403. This is the "coming soon" launch posture: the operator invites
81
+ * trusted testers while the public sees a closed door.
82
+ * - `closed` — no signups at all, invitation or not.
83
+ */
84
+ export const SIGNUP_MODES = ['open', 'invite', 'closed'];
85
+ export const GuardFeatures = z
86
+ .object({
87
+ privateMode: z.boolean().default(true),
88
+ secretsPanel: z.boolean().default(false), // station-style credential management UI
89
+ accounts: z.boolean().default(true), // username/password change (ex-Admin)
90
+ rbac: z.boolean().default(false), // SaaS profile forces true — superRefine (e)
91
+ sessionTtlMinutes: z.number().int().min(1).max(720).default(30),
92
+ signupMode: z.enum(SIGNUP_MODES).default('open'), // D33 launch gate — see SIGNUP_MODES
93
+ // D36: db-stored marketing-copy overrides + the owner-only editor panel.
94
+ // OFF by default: an app that is not THE commercial deployment has no
95
+ // marketing page to feed, and a public copy endpoint on it would be noise.
96
+ siteCopy: z.boolean().default(false),
97
+ })
98
+ .strict();
99
+ export const SystemFeatures = z
100
+ .object({
101
+ dashboard: z.boolean().default(false),
102
+ calendar: z.boolean().default(false),
103
+ notifications: z.boolean().default(true),
104
+ stopwatch: z.boolean().default(false),
105
+ timeOff: z.boolean().default(false),
106
+ formatLab: z.boolean().default(false),
107
+ lineFilter: z.boolean().default(false),
108
+ httpClient: z.boolean().default(false),
109
+ budgets: z.boolean().default(false),
110
+ subscriptions: z.boolean().default(false),
111
+ })
112
+ .strict();
113
+ export const FeaturesByEngine = {
114
+ asset: AssetFeatures,
115
+ knowledge: KnowledgeFeatures,
116
+ dev: DevFeatures,
117
+ guard: GuardFeatures,
118
+ system: SystemFeatures,
119
+ };
120
+ /**
121
+ * The signup gate a whole app enforces (D33). The STRICTEST configured value
122
+ * wins across guard instances — with two instances, `open` beside `invite`
123
+ * must mean invite-only, or adding a second guard instance would silently
124
+ * reopen a closed door. Apps with no guard instance have no signup surface at
125
+ * all, so the returned default is never enforced anywhere.
126
+ */
127
+ export const effectiveSignupMode = (instances) => {
128
+ let strictest = 'open';
129
+ for (const instance of instances) {
130
+ if (instance.engine !== 'guard')
131
+ continue;
132
+ const raw = instance.effective.signupMode;
133
+ const mode = raw === 'invite' || raw === 'closed' ? raw : 'open';
134
+ if (SIGNUP_MODES.indexOf(mode) > SIGNUP_MODES.indexOf(strictest))
135
+ strictest = mode;
136
+ }
137
+ return strictest;
138
+ };
139
+ /* ---------- instances, wires, routes, fills ---------- */
140
+ /**
141
+ * Client SPA path (03 §3): kebab-case literal segments, camelCase `:params`.
142
+ * `/chat/:conversationId` is the canonical example, so param names must admit
143
+ * capitals while literal segments stay lowercase — a `/Chat` vs `/chat` pair is
144
+ * two routes on a case-sensitive router and one route on a case-insensitive one.
145
+ */
146
+ export const ROUTE_PATH_RE = /^\/(?:[a-z0-9-]+|:[a-zA-Z][a-zA-Z0-9]*)?(?:\/(?:[a-z0-9-]+|:[a-zA-Z][a-zA-Z0-9]*))*$/;
147
+ export const RouteDeclSchema = z
148
+ .object({
149
+ path: z.string().regex(ROUTE_PATH_RE),
150
+ view: z.string().min(1), // engine view id; checked against the export index at V1
151
+ aliases: z.array(z.string()).default([]),
152
+ deepLinkEntity: z.string().optional(), // token id whose payloads resolve here (03 §4)
153
+ })
154
+ .strict();
155
+ export const FillDeclSchema = z
156
+ .object({
157
+ slot: z.string().min(1), // must exist in the SlotRegistry — V1 `slot-unknown`
158
+ component: z.string().min(1), // exported fill id of this instance's engine
159
+ order: z.number().int().default(0),
160
+ requiredFeatures: z.array(z.string()).default([]), // J2-3: pure boolean lookup, no predicates
161
+ })
162
+ .strict();
163
+ export const InstanceSchema = z
164
+ .object({
165
+ engine: EngineIdSchema,
166
+ id: z.string().regex(/^[a-z][a-z0-9-]*$/), // unique per app — V1 `duplicate-instance-id`
167
+ title: z.string().optional(),
168
+ features: z.record(z.string(), z.unknown()).default({}), // validated per-engine in superRefine
169
+ options: z.record(z.string(), z.unknown()).default({}),
170
+ routes: z.array(RouteDeclSchema).default([]),
171
+ fills: z.array(FillDeclSchema).default([]),
172
+ cssIsolation: z.enum(['none', 'scope', 'shadow']).default('none'), // 02 §5.2
173
+ })
174
+ .strict();
175
+ /** '<instanceId>:<portId>' or 'data:<sourceId>'. */
176
+ export const ENDPOINT_RE = /^[a-z][a-z0-9-]*:[a-z0-9\-:]+$/;
177
+ export const ConnectionSchema = z
178
+ .object({
179
+ from: z.string().regex(ENDPOINT_RE),
180
+ to: z.string().regex(ENDPOINT_RE),
181
+ cyclic: z.boolean().default(false),
182
+ })
183
+ .strict();
184
+ /* ---------- the app config ---------- */
185
+ export const AppConfigSchema = z
186
+ .object({
187
+ configVersion: z.literal(1), // unknown version ⇒ V0 `config-version-unknown`
188
+ app: z
189
+ .object({
190
+ id: z.string().regex(/^[a-z0-9-]+$/),
191
+ title: z.string().min(1),
192
+ port: z.number().int().min(1024).max(65535).optional(), // registry precedence (01 §3.2)
193
+ kind: z.enum(['app', 'test', 'demo']).default('app'),
194
+ })
195
+ .strict(),
196
+ profile: z.enum(['local', 'saas']).default('local'),
197
+ deployTarget: z
198
+ .object({
199
+ // context-aware capability branching input (04 V2 / J4-B)
200
+ capabilities: z.record(z.string(), z.boolean()).default({}), // e.g. { ffmpeg: true, mps: true }
201
+ })
202
+ .strict()
203
+ .default({ capabilities: {} }),
204
+ engines: z.array(EngineIdSchema).min(1), // ACTIVE MASTER ENGINE ARRAY — drives server mixins
205
+ instances: z.array(InstanceSchema).min(1),
206
+ connections: z.array(ConnectionSchema).default([]),
207
+ auth: z
208
+ .object({
209
+ enabled: z.boolean().default(false),
210
+ authUI: z
211
+ .object({
212
+ showLogout: z.boolean().default(true),
213
+ placement: z.enum(['header-right', 'header-left', 'sidebar-footer']).default('header-right'),
214
+ icon: z.string().default('LogOut'),
215
+ variant: z.enum(['ghost', 'outline', 'default']).default('ghost'),
216
+ })
217
+ .strict()
218
+ .default({ showLogout: true, placement: 'header-right', icon: 'LogOut', variant: 'ghost' }),
219
+ })
220
+ .strict()
221
+ .default({
222
+ enabled: false,
223
+ authUI: { showLogout: true, placement: 'header-right', icon: 'LogOut', variant: 'ghost' },
224
+ }),
225
+ theme: z
226
+ .object({
227
+ preset: z.string().default('cc-default'),
228
+ /**
229
+ * CSS custom-property map (02 §5.1), emitted into the generated app's
230
+ * `client/styles.css` as a `:root` block by `emitStylesCss`.
231
+ *
232
+ * **Both halves are validated because both are written verbatim into a
233
+ * stylesheet.** A property name is restricted to `--` plus
234
+ * word/dash characters, and a value may not contain the characters that
235
+ * would end the declaration or the rule (`;` `{` `}`) or open a comment.
236
+ * Without that, an override value of `red; } body { background: url(…)`
237
+ * escapes its own declaration and appends arbitrary rules to the app's
238
+ * first-party stylesheet — CSS is not inert (it fetches URLs and can
239
+ * hide or overlay UI). This is `.strict()`-doctrine fail-closed on
240
+ * STRUCTURE: an unusable custom property is a malformed config, not a
241
+ * capability to degrade.
242
+ */
243
+ overrides: z
244
+ .record(z
245
+ .string()
246
+ .regex(/^--[A-Za-z0-9_-]+$/, 'a theme override key must be a CSS custom property, e.g. "--accent"'), z
247
+ .string()
248
+ .min(1)
249
+ .refine((value) => !/[;{}]|\/\*|\*\//.test(value), {
250
+ message: 'a theme override value may not contain ";", "{", "}" or a CSS comment',
251
+ }))
252
+ .default({}),
253
+ })
254
+ .strict()
255
+ .default({ preset: 'cc-default', overrides: {} }),
256
+ ignoreExistingData: z.boolean().default(false), // the ONLY registry bypass; always logs
257
+ })
258
+ .strict()
259
+ .superRefine((config, ctx) => {
260
+ const engineSet = new Set(config.engines);
261
+ // `ccCode` rides along in params so V0 can map each cross-engine issue onto
262
+ // its diagnostic code without pattern-matching on English message text.
263
+ const issue = (path, message, ccCode = 'engine-dependency-missing') => ctx.addIssue({ code: 'custom', path, message, params: { ccCode } });
264
+ config.instances.forEach((inst, i) => {
265
+ // (a) instance engine must be activated
266
+ if (!engineSet.has(inst.engine))
267
+ issue(['instances', i, 'engine'], `Instance '${inst.id}' uses engine '${inst.engine}' which is absent from engines[].`);
268
+ // (b) features parse against the FULL per-engine schema — typos die here, not at boot
269
+ const parsed = FeaturesByEngine[inst.engine].safeParse(inst.features);
270
+ if (!parsed.success) {
271
+ for (const e of parsed.error.issues) {
272
+ const keys = e.code === 'unrecognized_keys' ? e.keys : [];
273
+ const suffix = keys.length > 0 ? keys.map(String) : e.path.map(String);
274
+ issue(['instances', i, 'features', ...suffix], e.message, e.code === 'unrecognized_keys' ? 'unknown-config-key' : 'config-invalid-value');
275
+ }
276
+ return;
277
+ }
278
+ const f = parsed.data;
279
+ // (c) cross-engine dependencies — hard blocks, not comments (J2-1)
280
+ if (inst.engine === 'asset' && f.privateMedia === true && !engineSet.has('guard'))
281
+ issue(['instances', i, 'features', 'privateMedia'], `'privateMedia' requires the 'guard' engine in engines[] (engine-dependency-missing).`);
282
+ if (inst.engine === 'knowledge' && f.oralHistory === true && !engineSet.has('asset'))
283
+ issue(['instances', i, 'features', 'oralHistory'], `'oralHistory' requires the 'asset' engine for its media player tier (engine-dependency-missing).`);
284
+ });
285
+ // (d) auth requires guard
286
+ if (config.auth.enabled && !engineSet.has('guard'))
287
+ issue(['auth', 'enabled'], `auth.enabled requires the 'guard' engine (engine-dependency-missing).`);
288
+ // (e) SaaS guardrails: guard engine present, rbac on
289
+ if (config.profile === 'saas') {
290
+ if (!engineSet.has('guard'))
291
+ issue(['engines'], `profile 'saas' structurally requires the 'guard' engine (engine-dependency-missing).`);
292
+ const guardInst = config.instances.find((x) => x.engine === 'guard');
293
+ if (guardInst && guardInst.features.rbac !== true)
294
+ issue(['instances', config.instances.indexOf(guardInst), 'features', 'rbac'], `profile 'saas' structurally requires 'features.rbac: true' on the guard instance.`);
295
+ }
296
+ });
297
+ /**
298
+ * Resolve one instance's declared features through the FULL (non-partial) engine
299
+ * schema — the "full-resolution preview" of V0.3. Defaults land here, so a
300
+ * missing required option surfaces at design time instead of at first boot.
301
+ */
302
+ export function resolveInstanceFeatures(engine, declared) {
303
+ return FeaturesByEngine[engine].parse(declared);
304
+ }
305
+ /** The declared feature key set of an engine — used by V1 `unknown-required-feature`. */
306
+ export function featureKeysOf(engine) {
307
+ return Object.keys(FeaturesByEngine[engine].shape);
308
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `cursedbelt-core/activity` — the fleet's activity/audit vocabulary, pure.
3
+ *
4
+ * The storage + query half is `cursedbelt-server/activity`; this entry is what a
5
+ * browser, a spec or a wire type can import without pulling `bun:sqlite` in.
6
+ */
7
+ export { ACTIVITY_OUTCOMES, activityFromShareEvent, activitySubject, describeActivityEvent, describeActivityResource, isActivityAction, isActivityOutcome, } from "./model";
8
+ export type { ActivityEvent, ActivityEventInput, ActivityOutcome, ActivityResource, } from "./model";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * `cursedbelt-core/activity` — the fleet's activity/audit vocabulary, pure.
3
+ *
4
+ * The storage + query half is `cursedbelt-server/activity`; this entry is what a
5
+ * browser, a spec or a wire type can import without pulling `bun:sqlite` in.
6
+ */
7
+ export { ACTIVITY_OUTCOMES, activityFromShareEvent, activitySubject, describeActivityEvent, describeActivityResource, isActivityAction, isActivityOutcome, } from "./model";
@@ -0,0 +1,115 @@
1
+ /**
2
+ * The fleet's ACTIVITY vocabulary — who did what, to which record, when, and how
3
+ * it went. One shape, so twelve apps' event streams can be read as one.
4
+ *
5
+ * ── Why this is not a second audit shape ────────────────────────────────────
6
+ * `cursedbelt-core/sharing` already had to answer this question for grants, and its
7
+ * `ShareEvent` is the shape that survived review: an ISO-8601 `at`, an `actor`
8
+ * that is always an account and never a session, a `(app, kind, id)` resource
9
+ * triple, a dotted `action`, and a free `note` for whoever reads it six months
10
+ * later. This vocabulary is deliberately the SAME five fields plus an outcome —
11
+ * `activityFromShareEvent` below is the proof, and it is a total function.
12
+ *
13
+ * So a consumer never has to choose between two audit trails:
14
+ * - a sharing grant is `share.grant` in the stream, projected out of
15
+ * `share_events` by `cursedbelt-server/activity`'s `shareEventsSource` — the
16
+ * same rows, never a copy, so the two can never disagree;
17
+ * - everything else an app wants remembered is `record()`d into
18
+ * `activity_events`.
19
+ *
20
+ * ── The rules the shape enforces ────────────────────────────────────────────
21
+ * - **`at` is an ISO-8601 string**, matching `share_events` and the fleet's
22
+ * audit vocabulary. An audit trail is read by humans far more often than it
23
+ * is sorted by a machine, and a sortable string is both.
24
+ * - **`actor` is required and is never optional.** An event that cannot say who
25
+ * is a log line, not an audit trail. Apps with no session pass a label that
26
+ * says so (`"curt@… (dev-open)"`), which is honest rather than empty.
27
+ * - **`action` is `subject.verb`**, lowercase and dotted — `note.create`,
28
+ * `backup.delete`, `share.grant`. The prefix is what makes "everything that
29
+ * happened to backups" a filter rather than a regex.
30
+ * - **`id` is source-qualified** (`log:41`, `share:12`), because the stream is a
31
+ * UNION over more than one table and two tables' autoincrements collide at 1.
32
+ */
33
+ import type { ShareEvent } from "../sharing/model";
34
+ /** How it went. `denied` is its own outcome rather than an `error`: a refused
35
+ * action is the single most interesting row in an audit trail, and one that
36
+ * reads as a crash gets triaged as one. */
37
+ export type ActivityOutcome = "ok" | "denied" | "error";
38
+ export declare const ACTIVITY_OUTCOMES: readonly ActivityOutcome[];
39
+ export declare const isActivityOutcome: (value: unknown) => value is ActivityOutcome;
40
+ /**
41
+ * What was acted on: `(app, kind, id)` — deliberately identical to
42
+ * {@link import("../sharing/model").ShareResource}, so a resource can be handed
43
+ * from one primitive to the other without a translation layer.
44
+ */
45
+ export interface ActivityResource {
46
+ app: string;
47
+ kind: string;
48
+ id: string;
49
+ }
50
+ /** One line of the stream, as read. */
51
+ export interface ActivityEvent {
52
+ /** `"<source>:<row id>"`. Stable, and unique across the union. */
53
+ id: string;
54
+ /** Which table it came from — `log`, `share`, or a source an app registered. */
55
+ source: string;
56
+ /** ISO-8601, UTC. */
57
+ at: string;
58
+ /** The satellite. Present on every row so a fleet-wide merge stays labeled. */
59
+ app: string;
60
+ actor: string;
61
+ /** `subject.verb`, lowercase. */
62
+ action: string;
63
+ /** Null when the action is about no single record (a sign-in, a sweep). */
64
+ resource: ActivityResource | null;
65
+ outcome: ActivityOutcome;
66
+ /** Why, in the words of whoever did it. */
67
+ note: string;
68
+ /** Anything else worth keeping, as JSON. Never indexed, never queried on. */
69
+ data: Record<string, unknown> | null;
70
+ }
71
+ /** One line of the stream, as written. The store supplies `app`, `id` and `source`. */
72
+ export interface ActivityEventInput {
73
+ actor: string;
74
+ action: string;
75
+ /** The `app` half is supplied by the store, so a caller cannot write an event
76
+ * into another app's name. */
77
+ resource?: {
78
+ kind: string;
79
+ id: string;
80
+ } | null;
81
+ outcome?: ActivityOutcome;
82
+ note?: string;
83
+ data?: Record<string, unknown> | null;
84
+ /** ISO-8601. Defaults to now — passed explicitly only by tests and importers. */
85
+ at?: string;
86
+ }
87
+ /** `note.create` → `note`. The half a filter groups on. */
88
+ export declare const activitySubject: (action: string) => string;
89
+ export declare const isActivityAction: (value: unknown) => value is string;
90
+ /** `family · person 3f2` — the resource, for a row that has one. */
91
+ export declare const describeActivityResource: (resource: ActivityResource | null) => string;
92
+ /**
93
+ * One sentence for a person. Deliberately generic — an app that wants its own
94
+ * wording for its own actions renders it itself; this is what the fleet-wide
95
+ * board shows for an action it has never seen before, and it must never be
96
+ * empty.
97
+ */
98
+ export declare function describeActivityEvent(event: ActivityEvent): string;
99
+ /**
100
+ * 🔴 THE coordination point with `cursedbelt-core/sharing`.
101
+ *
102
+ * A sharing grant is already audited, in `share_events`, by a store that
103
+ * guarantees no grant can be made without one. This maps that row into the
104
+ * activity vocabulary so the fleet's event stream can INCLUDE it rather than
105
+ * ask every consumer to write a second copy — the "coordinate the shape rather
106
+ * than invent a second one" rule, as code.
107
+ *
108
+ * `cursedbelt-server/activity`'s `shareEventsSource` is the SQL form of exactly
109
+ * this mapping, and `store.spec.ts` asserts the two agree field for field. This
110
+ * function is the one to use from a `ShareStore`'s `onEvent` hook when an app
111
+ * genuinely wants a copy (a different database, say); an app whose share events
112
+ * live beside its activity table should register the SQL source instead and
113
+ * keep one row.
114
+ */
115
+ export declare function activityFromShareEvent(event: ShareEvent): ActivityEventInput;
@@ -0,0 +1,63 @@
1
+ export const ACTIVITY_OUTCOMES = ["ok", "denied", "error"];
2
+ export const isActivityOutcome = (value) => value === "ok" || value === "denied" || value === "error";
3
+ /** `note.create` → `note`. The half a filter groups on. */
4
+ export const activitySubject = (action) => action.split(".")[0] ?? action;
5
+ /**
6
+ * A well-formed action: `subject.verb`, optionally deeper (`share.group.create`),
7
+ * lowercase, dots and dashes only.
8
+ *
9
+ * Enforced at the WRITE (see the store), not merely documented — an action
10
+ * vocabulary that drifts to `Note Created` on one screen and `note.create` on
11
+ * another cannot be filtered, and a filter is the whole point of the column.
12
+ */
13
+ const ACTION_RE = /^[a-z0-9-]+(\.[a-z0-9-]+)+$/;
14
+ export const isActivityAction = (value) => typeof value === "string" && value.length <= 64 && ACTION_RE.test(value);
15
+ /** `family · person 3f2` — the resource, for a row that has one. */
16
+ export const describeActivityResource = (resource) => resource ? `${resource.kind} ${resource.id}` : "—";
17
+ /**
18
+ * One sentence for a person. Deliberately generic — an app that wants its own
19
+ * wording for its own actions renders it itself; this is what the fleet-wide
20
+ * board shows for an action it has never seen before, and it must never be
21
+ * empty.
22
+ */
23
+ export function describeActivityEvent(event) {
24
+ const what = event.resource
25
+ ? `${event.resource.kind} ${event.resource.id}`
26
+ : `${event.app}`;
27
+ const verb = event.action.replace(/\./g, " ");
28
+ const how = event.outcome === "ok" ? "" : event.outcome === "denied" ? " — refused" : " — failed";
29
+ const why = event.note ? ` (${event.note})` : "";
30
+ return `${event.actor} ${verb} ${what}${how}${why}`;
31
+ }
32
+ /**
33
+ * 🔴 THE coordination point with `cursedbelt-core/sharing`.
34
+ *
35
+ * A sharing grant is already audited, in `share_events`, by a store that
36
+ * guarantees no grant can be made without one. This maps that row into the
37
+ * activity vocabulary so the fleet's event stream can INCLUDE it rather than
38
+ * ask every consumer to write a second copy — the "coordinate the shape rather
39
+ * than invent a second one" rule, as code.
40
+ *
41
+ * `cursedbelt-server/activity`'s `shareEventsSource` is the SQL form of exactly
42
+ * this mapping, and `store.spec.ts` asserts the two agree field for field. This
43
+ * function is the one to use from a `ShareStore`'s `onEvent` hook when an app
44
+ * genuinely wants a copy (a different database, say); an app whose share events
45
+ * live beside its activity table should register the SQL source instead and
46
+ * keep one row.
47
+ */
48
+ export function activityFromShareEvent(event) {
49
+ return {
50
+ at: event.at,
51
+ actor: event.actor,
52
+ action: `share.${event.action}`,
53
+ resource: event.resource ? { kind: event.resource.kind, id: event.resource.id } : null,
54
+ outcome: "ok",
55
+ note: event.note,
56
+ data: {
57
+ subjectType: event.subject.type,
58
+ subjectId: event.subject.id,
59
+ level: event.level,
60
+ previousLevel: event.previousLevel,
61
+ },
62
+ };
63
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * `cursedbelt-core/analytics` — the framework-free half of an analytics surface.
3
+ *
4
+ * Framework-free because BOTH sides need it: a server buckets a series before it
5
+ * ships it, and the browser renders the same buckets. They must agree exactly or
6
+ * a chart's x-axis disagrees with its own tooltip — so this half must not pull
7
+ * React in.
8
+ *
9
+ * - `range` — the time vocabulary (today/week/…/custom), local-day bucketing,
10
+ * and the baseline window every comparison is measured against.
11
+ * - `schedule` — list scheduling with resource exclusion and a contention term:
12
+ * how long a backlog takes to drain, whether another worker helps, and when a
13
+ * budget runs out at each fleet size.
14
+ *
15
+ * The React furniture that renders it lives in `cursedbelt/react/analytics`.
16
+ */
17
+ export * from './range';
18
+ export * from './schedule';
@@ -0,0 +1,18 @@
1
+ /**
2
+ * `cursedbelt-core/analytics` — the framework-free half of an analytics surface.
3
+ *
4
+ * Framework-free because BOTH sides need it: a server buckets a series before it
5
+ * ships it, and the browser renders the same buckets. They must agree exactly or
6
+ * a chart's x-axis disagrees with its own tooltip — so this half must not pull
7
+ * React in.
8
+ *
9
+ * - `range` — the time vocabulary (today/week/…/custom), local-day bucketing,
10
+ * and the baseline window every comparison is measured against.
11
+ * - `schedule` — list scheduling with resource exclusion and a contention term:
12
+ * how long a backlog takes to drain, whether another worker helps, and when a
13
+ * budget runs out at each fleet size.
14
+ *
15
+ * The React furniture that renders it lives in `cursedbelt/react/analytics`.
16
+ */
17
+ export * from './range';
18
+ export * from './schedule';