@czap/core 0.1.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 (372) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +19 -0
  3. package/dist/addressed-digest.d.ts +15 -0
  4. package/dist/addressed-digest.d.ts.map +1 -0
  5. package/dist/addressed-digest.js +35 -0
  6. package/dist/addressed-digest.js.map +1 -0
  7. package/dist/animation.d.ts +46 -0
  8. package/dist/animation.d.ts.map +1 -0
  9. package/dist/animation.js +70 -0
  10. package/dist/animation.js.map +1 -0
  11. package/dist/assembly.d.ts +25 -0
  12. package/dist/assembly.d.ts.map +1 -0
  13. package/dist/assembly.js +58 -0
  14. package/dist/assembly.js.map +1 -0
  15. package/dist/av-bridge.d.ts +74 -0
  16. package/dist/av-bridge.d.ts.map +1 -0
  17. package/dist/av-bridge.js +107 -0
  18. package/dist/av-bridge.js.map +1 -0
  19. package/dist/av-renderer.d.ts +56 -0
  20. package/dist/av-renderer.d.ts.map +1 -0
  21. package/dist/av-renderer.js +65 -0
  22. package/dist/av-renderer.js.map +1 -0
  23. package/dist/blend.d.ts +61 -0
  24. package/dist/blend.d.ts.map +1 -0
  25. package/dist/blend.js +100 -0
  26. package/dist/blend.js.map +1 -0
  27. package/dist/boundary.d.ts +154 -0
  28. package/dist/boundary.d.ts.map +1 -0
  29. package/dist/boundary.js +269 -0
  30. package/dist/boundary.js.map +1 -0
  31. package/dist/brands.d.ts +63 -0
  32. package/dist/brands.d.ts.map +1 -0
  33. package/dist/brands.js +31 -0
  34. package/dist/brands.js.map +1 -0
  35. package/dist/caps.d.ts +49 -0
  36. package/dist/caps.d.ts.map +1 -0
  37. package/dist/caps.js +73 -0
  38. package/dist/caps.js.map +1 -0
  39. package/dist/capsule.d.ts +77 -0
  40. package/dist/capsule.d.ts.map +1 -0
  41. package/dist/capsule.js +18 -0
  42. package/dist/capsule.js.map +1 -0
  43. package/dist/capsules/boundary-evaluate.d.ts +28 -0
  44. package/dist/capsules/boundary-evaluate.d.ts.map +1 -0
  45. package/dist/capsules/boundary-evaluate.js +117 -0
  46. package/dist/capsules/boundary-evaluate.js.map +1 -0
  47. package/dist/capsules/canonical-cbor.d.ts +13 -0
  48. package/dist/capsules/canonical-cbor.d.ts.map +1 -0
  49. package/dist/capsules/canonical-cbor.js +60 -0
  50. package/dist/capsules/canonical-cbor.js.map +1 -0
  51. package/dist/capsules/token-buffer.d.ts +24 -0
  52. package/dist/capsules/token-buffer.d.ts.map +1 -0
  53. package/dist/capsules/token-buffer.js +53 -0
  54. package/dist/capsules/token-buffer.js.map +1 -0
  55. package/dist/capture.d.ts +40 -0
  56. package/dist/capture.d.ts.map +1 -0
  57. package/dist/capture.js +10 -0
  58. package/dist/capture.js.map +1 -0
  59. package/dist/cbor.d.ts +33 -0
  60. package/dist/cbor.d.ts.map +1 -0
  61. package/dist/cbor.js +179 -0
  62. package/dist/cbor.js.map +1 -0
  63. package/dist/cell.d.ts +53 -0
  64. package/dist/cell.d.ts.map +1 -0
  65. package/dist/cell.js +83 -0
  66. package/dist/cell.js.map +1 -0
  67. package/dist/codec.d.ts +30 -0
  68. package/dist/codec.d.ts.map +1 -0
  69. package/dist/codec.js +25 -0
  70. package/dist/codec.js.map +1 -0
  71. package/dist/component.d.ts +52 -0
  72. package/dist/component.d.ts.map +1 -0
  73. package/dist/component.js +44 -0
  74. package/dist/component.js.map +1 -0
  75. package/dist/composable.d.ts +76 -0
  76. package/dist/composable.d.ts.map +1 -0
  77. package/dist/composable.js +221 -0
  78. package/dist/composable.js.map +1 -0
  79. package/dist/compositor-pool.d.ts +74 -0
  80. package/dist/compositor-pool.d.ts.map +1 -0
  81. package/dist/compositor-pool.js +119 -0
  82. package/dist/compositor-pool.js.map +1 -0
  83. package/dist/compositor.d.ts +90 -0
  84. package/dist/compositor.d.ts.map +1 -0
  85. package/dist/compositor.js +278 -0
  86. package/dist/compositor.js.map +1 -0
  87. package/dist/config.d.ts +72 -0
  88. package/dist/config.d.ts.map +1 -0
  89. package/dist/config.js +97 -0
  90. package/dist/config.js.map +1 -0
  91. package/dist/dag.d.ts +251 -0
  92. package/dist/dag.d.ts.map +1 -0
  93. package/dist/dag.js +450 -0
  94. package/dist/dag.js.map +1 -0
  95. package/dist/defaults.d.ts +45 -0
  96. package/dist/defaults.d.ts.map +1 -0
  97. package/dist/defaults.js +45 -0
  98. package/dist/defaults.js.map +1 -0
  99. package/dist/derived.d.ts +34 -0
  100. package/dist/derived.d.ts.map +1 -0
  101. package/dist/derived.js +101 -0
  102. package/dist/derived.js.map +1 -0
  103. package/dist/diagnostics.d.ts +77 -0
  104. package/dist/diagnostics.d.ts.map +1 -0
  105. package/dist/diagnostics.js +122 -0
  106. package/dist/diagnostics.js.map +1 -0
  107. package/dist/dirty.d.ts +55 -0
  108. package/dist/dirty.d.ts.map +1 -0
  109. package/dist/dirty.js +80 -0
  110. package/dist/dirty.js.map +1 -0
  111. package/dist/easing.d.ts +55 -0
  112. package/dist/easing.d.ts.map +1 -0
  113. package/dist/easing.js +291 -0
  114. package/dist/easing.js.map +1 -0
  115. package/dist/ecs.d.ts +105 -0
  116. package/dist/ecs.d.ts.map +1 -0
  117. package/dist/ecs.js +245 -0
  118. package/dist/ecs.js.map +1 -0
  119. package/dist/fnv.d.ts +14 -0
  120. package/dist/fnv.d.ts.map +1 -0
  121. package/dist/fnv.js +28 -0
  122. package/dist/fnv.js.map +1 -0
  123. package/dist/frame-budget.d.ts +73 -0
  124. package/dist/frame-budget.d.ts.map +1 -0
  125. package/dist/frame-budget.js +114 -0
  126. package/dist/frame-budget.js.map +1 -0
  127. package/dist/gen-frame.d.ts +102 -0
  128. package/dist/gen-frame.d.ts.map +1 -0
  129. package/dist/gen-frame.js +121 -0
  130. package/dist/gen-frame.js.map +1 -0
  131. package/dist/harness/arbitrary-from-schema.d.ts +28 -0
  132. package/dist/harness/arbitrary-from-schema.d.ts.map +1 -0
  133. package/dist/harness/arbitrary-from-schema.js +262 -0
  134. package/dist/harness/arbitrary-from-schema.js.map +1 -0
  135. package/dist/harness/cached-projection.d.ts +19 -0
  136. package/dist/harness/cached-projection.d.ts.map +1 -0
  137. package/dist/harness/cached-projection.js +39 -0
  138. package/dist/harness/cached-projection.js.map +1 -0
  139. package/dist/harness/index.d.ts +16 -0
  140. package/dist/harness/index.d.ts.map +1 -0
  141. package/dist/harness/index.js +15 -0
  142. package/dist/harness/index.js.map +1 -0
  143. package/dist/harness/policy-gate.d.ts +18 -0
  144. package/dist/harness/policy-gate.d.ts.map +1 -0
  145. package/dist/harness/policy-gate.js +46 -0
  146. package/dist/harness/policy-gate.js.map +1 -0
  147. package/dist/harness/pure-transform.d.ts +42 -0
  148. package/dist/harness/pure-transform.d.ts.map +1 -0
  149. package/dist/harness/pure-transform.js +76 -0
  150. package/dist/harness/pure-transform.js.map +1 -0
  151. package/dist/harness/receipted-mutation.d.ts +23 -0
  152. package/dist/harness/receipted-mutation.d.ts.map +1 -0
  153. package/dist/harness/receipted-mutation.js +52 -0
  154. package/dist/harness/receipted-mutation.js.map +1 -0
  155. package/dist/harness/scene-composition.d.ts +19 -0
  156. package/dist/harness/scene-composition.d.ts.map +1 -0
  157. package/dist/harness/scene-composition.js +47 -0
  158. package/dist/harness/scene-composition.js.map +1 -0
  159. package/dist/harness/site-adapter.d.ts +18 -0
  160. package/dist/harness/site-adapter.d.ts.map +1 -0
  161. package/dist/harness/site-adapter.js +38 -0
  162. package/dist/harness/site-adapter.js.map +1 -0
  163. package/dist/harness/state-machine.d.ts +19 -0
  164. package/dist/harness/state-machine.d.ts.map +1 -0
  165. package/dist/harness/state-machine.js +44 -0
  166. package/dist/harness/state-machine.js.map +1 -0
  167. package/dist/hlc.d.ts +99 -0
  168. package/dist/hlc.d.ts.map +1 -0
  169. package/dist/hlc.js +219 -0
  170. package/dist/hlc.js.map +1 -0
  171. package/dist/index.d.ts +104 -0
  172. package/dist/index.d.ts.map +1 -0
  173. package/dist/index.js +137 -0
  174. package/dist/index.js.map +1 -0
  175. package/dist/interpolate.d.ts +14 -0
  176. package/dist/interpolate.d.ts.map +1 -0
  177. package/dist/interpolate.js +31 -0
  178. package/dist/interpolate.js.map +1 -0
  179. package/dist/live-cell.d.ts +46 -0
  180. package/dist/live-cell.d.ts.map +1 -0
  181. package/dist/live-cell.js +154 -0
  182. package/dist/live-cell.js.map +1 -0
  183. package/dist/op.d.ts +58 -0
  184. package/dist/op.d.ts.map +1 -0
  185. package/dist/op.js +171 -0
  186. package/dist/op.js.map +1 -0
  187. package/dist/plan.d.ts +195 -0
  188. package/dist/plan.d.ts.map +1 -0
  189. package/dist/plan.js +211 -0
  190. package/dist/plan.js.map +1 -0
  191. package/dist/protocol.d.ts +33 -0
  192. package/dist/protocol.d.ts.map +1 -0
  193. package/dist/protocol.js +10 -0
  194. package/dist/protocol.js.map +1 -0
  195. package/dist/quantizer-types.d.ts +28 -0
  196. package/dist/quantizer-types.d.ts.map +1 -0
  197. package/dist/quantizer-types.js +9 -0
  198. package/dist/quantizer-types.js.map +1 -0
  199. package/dist/receipt.d.ts +294 -0
  200. package/dist/receipt.d.ts.map +1 -0
  201. package/dist/receipt.js +352 -0
  202. package/dist/receipt.js.map +1 -0
  203. package/dist/runtime-coordinator.d.ts +75 -0
  204. package/dist/runtime-coordinator.d.ts.map +1 -0
  205. package/dist/runtime-coordinator.js +149 -0
  206. package/dist/runtime-coordinator.js.map +1 -0
  207. package/dist/scheduler.d.ts +58 -0
  208. package/dist/scheduler.d.ts.map +1 -0
  209. package/dist/scheduler.js +109 -0
  210. package/dist/scheduler.js.map +1 -0
  211. package/dist/ship-capsule.d.ts +54 -0
  212. package/dist/ship-capsule.d.ts.map +1 -0
  213. package/dist/ship-capsule.js +142 -0
  214. package/dist/ship-capsule.js.map +1 -0
  215. package/dist/ship-manifest.d.ts +45 -0
  216. package/dist/ship-manifest.d.ts.map +1 -0
  217. package/dist/ship-manifest.js +175 -0
  218. package/dist/ship-manifest.js.map +1 -0
  219. package/dist/signal.d.ts +149 -0
  220. package/dist/signal.d.ts.map +1 -0
  221. package/dist/signal.js +277 -0
  222. package/dist/signal.js.map +1 -0
  223. package/dist/speculative.d.ts +67 -0
  224. package/dist/speculative.d.ts.map +1 -0
  225. package/dist/speculative.js +139 -0
  226. package/dist/speculative.js.map +1 -0
  227. package/dist/store.d.ts +39 -0
  228. package/dist/store.d.ts.map +1 -0
  229. package/dist/store.js +42 -0
  230. package/dist/store.js.map +1 -0
  231. package/dist/style.d.ts +119 -0
  232. package/dist/style.d.ts.map +1 -0
  233. package/dist/style.js +168 -0
  234. package/dist/style.js.map +1 -0
  235. package/dist/testing.d.ts +14 -0
  236. package/dist/testing.d.ts.map +1 -0
  237. package/dist/testing.js +14 -0
  238. package/dist/testing.js.map +1 -0
  239. package/dist/theme.d.ts +78 -0
  240. package/dist/theme.d.ts.map +1 -0
  241. package/dist/theme.js +109 -0
  242. package/dist/theme.js.map +1 -0
  243. package/dist/timeline.d.ts +45 -0
  244. package/dist/timeline.d.ts.map +1 -0
  245. package/dist/timeline.js +101 -0
  246. package/dist/timeline.js.map +1 -0
  247. package/dist/token-buffer.d.ts +43 -0
  248. package/dist/token-buffer.d.ts.map +1 -0
  249. package/dist/token-buffer.js +112 -0
  250. package/dist/token-buffer.js.map +1 -0
  251. package/dist/token.d.ts +107 -0
  252. package/dist/token.d.ts.map +1 -0
  253. package/dist/token.js +143 -0
  254. package/dist/token.js.map +1 -0
  255. package/dist/tuple.d.ts +16 -0
  256. package/dist/tuple.d.ts.map +1 -0
  257. package/dist/tuple.js +16 -0
  258. package/dist/tuple.js.map +1 -0
  259. package/dist/type-utils.d.ts +41 -0
  260. package/dist/type-utils.d.ts.map +1 -0
  261. package/dist/type-utils.js +10 -0
  262. package/dist/type-utils.js.map +1 -0
  263. package/dist/typed-ref.d.ts +50 -0
  264. package/dist/typed-ref.d.ts.map +1 -0
  265. package/dist/typed-ref.js +59 -0
  266. package/dist/typed-ref.js.map +1 -0
  267. package/dist/ui-quality.d.ts +50 -0
  268. package/dist/ui-quality.d.ts.map +1 -0
  269. package/dist/ui-quality.js +64 -0
  270. package/dist/ui-quality.js.map +1 -0
  271. package/dist/validation-error.d.ts +25 -0
  272. package/dist/validation-error.d.ts.map +1 -0
  273. package/dist/validation-error.js +32 -0
  274. package/dist/validation-error.js.map +1 -0
  275. package/dist/vector-clock.d.ts +46 -0
  276. package/dist/vector-clock.d.ts.map +1 -0
  277. package/dist/vector-clock.js +91 -0
  278. package/dist/vector-clock.js.map +1 -0
  279. package/dist/video.d.ts +62 -0
  280. package/dist/video.d.ts.map +1 -0
  281. package/dist/video.js +59 -0
  282. package/dist/video.js.map +1 -0
  283. package/dist/wasm-dispatch.d.ts +52 -0
  284. package/dist/wasm-dispatch.d.ts.map +1 -0
  285. package/dist/wasm-dispatch.js +204 -0
  286. package/dist/wasm-dispatch.js.map +1 -0
  287. package/dist/wasm-fallback.d.ts +19 -0
  288. package/dist/wasm-fallback.d.ts.map +1 -0
  289. package/dist/wasm-fallback.js +93 -0
  290. package/dist/wasm-fallback.js.map +1 -0
  291. package/dist/wire.d.ts +49 -0
  292. package/dist/wire.d.ts.map +1 -0
  293. package/dist/wire.js +201 -0
  294. package/dist/wire.js.map +1 -0
  295. package/dist/zap.d.ts +42 -0
  296. package/dist/zap.d.ts.map +1 -0
  297. package/dist/zap.js +172 -0
  298. package/dist/zap.js.map +1 -0
  299. package/package.json +71 -0
  300. package/src/addressed-digest.ts +48 -0
  301. package/src/animation.ts +103 -0
  302. package/src/assembly.ts +76 -0
  303. package/src/av-bridge.ts +161 -0
  304. package/src/av-renderer.ts +118 -0
  305. package/src/blend.ts +135 -0
  306. package/src/boundary.ts +363 -0
  307. package/src/brands.ts +86 -0
  308. package/src/caps.ts +100 -0
  309. package/src/capsule.ts +95 -0
  310. package/src/capsules/boundary-evaluate.ts +128 -0
  311. package/src/capsules/canonical-cbor.ts +60 -0
  312. package/src/capsules/token-buffer.ts +57 -0
  313. package/src/capture.ts +48 -0
  314. package/src/cbor.ts +199 -0
  315. package/src/cell.ts +130 -0
  316. package/src/codec.ts +39 -0
  317. package/src/component.ts +102 -0
  318. package/src/composable.ts +328 -0
  319. package/src/compositor-pool.ts +162 -0
  320. package/src/compositor.ts +387 -0
  321. package/src/config.ts +157 -0
  322. package/src/dag.ts +527 -0
  323. package/src/defaults.ts +60 -0
  324. package/src/derived.ts +164 -0
  325. package/src/diagnostics.ts +186 -0
  326. package/src/dirty.ts +101 -0
  327. package/src/easing.ts +334 -0
  328. package/src/ecs.ts +382 -0
  329. package/src/fnv.ts +31 -0
  330. package/src/frame-budget.ts +149 -0
  331. package/src/gen-frame.ts +229 -0
  332. package/src/harness/arbitrary-from-schema.ts +270 -0
  333. package/src/harness/cached-projection.ts +46 -0
  334. package/src/harness/index.ts +16 -0
  335. package/src/harness/policy-gate.ts +51 -0
  336. package/src/harness/pure-transform.ts +121 -0
  337. package/src/harness/receipted-mutation.ts +59 -0
  338. package/src/harness/scene-composition.ts +54 -0
  339. package/src/harness/site-adapter.ts +43 -0
  340. package/src/harness/state-machine.ts +49 -0
  341. package/src/hlc.ts +238 -0
  342. package/src/index.ts +274 -0
  343. package/src/interpolate.ts +37 -0
  344. package/src/live-cell.ts +199 -0
  345. package/src/op.ts +233 -0
  346. package/src/plan.ts +317 -0
  347. package/src/protocol.ts +49 -0
  348. package/src/quantizer-types.ts +29 -0
  349. package/src/receipt.ts +444 -0
  350. package/src/runtime-coordinator.ts +230 -0
  351. package/src/scheduler.ts +161 -0
  352. package/src/ship-capsule.ts +191 -0
  353. package/src/signal.ts +345 -0
  354. package/src/speculative.ts +186 -0
  355. package/src/store.ts +77 -0
  356. package/src/style.ts +249 -0
  357. package/src/testing.ts +14 -0
  358. package/src/theme.ts +153 -0
  359. package/src/timeline.ts +146 -0
  360. package/src/token-buffer.ts +151 -0
  361. package/src/token.ts +197 -0
  362. package/src/tuple.ts +19 -0
  363. package/src/type-utils.ts +48 -0
  364. package/src/typed-ref.ts +79 -0
  365. package/src/ui-quality.ts +105 -0
  366. package/src/validation-error.ts +34 -0
  367. package/src/vector-clock.ts +111 -0
  368. package/src/video.ts +106 -0
  369. package/src/wasm-dispatch.ts +300 -0
  370. package/src/wasm-fallback.ts +102 -0
  371. package/src/wire.ts +274 -0
  372. package/src/zap.ts +241 -0
package/src/receipt.ts ADDED
@@ -0,0 +1,444 @@
1
+ /**
2
+ * Receipt -- chain validation and envelope construction.
3
+ *
4
+ * Salvaged from `@kit/core`.
5
+ *
6
+ * @module
7
+ */
8
+
9
+ import { Effect } from 'effect';
10
+ import type { HLC } from './brands.js';
11
+ import { TypedRef as TypedRefModule, type TypedRef } from './typed-ref.js';
12
+ import { HLC as HLCOps } from './hlc.js';
13
+
14
+ /** The logical entity a receipt describes: an effect, a run, an artifact, or an intent. */
15
+ export interface ReceiptSubject {
16
+ readonly type: 'effect' | 'run' | 'artifact' | 'intent';
17
+ readonly id: string;
18
+ }
19
+
20
+ /**
21
+ * Single link in a receipt chain: timestamped, content-addressed, and linked
22
+ * to its predecessor(s). Merge envelopes carry an array of `previous` hashes;
23
+ * optionally MAC-signed via `Receipt.macEnvelope`.
24
+ */
25
+ export interface ReceiptEnvelope {
26
+ readonly kind: string;
27
+ readonly timestamp: HLC;
28
+ readonly subject: ReceiptSubject;
29
+ readonly payload: TypedRef.Shape;
30
+ readonly hash: string;
31
+ readonly previous: string | readonly string[];
32
+ readonly signature?: string;
33
+ }
34
+
35
+ /** Structured failure returned by `Receipt.validateChainDetailed`. */
36
+ export type ChainValidationError =
37
+ | { readonly type: 'not_genesis'; readonly index: 0 }
38
+ | { readonly type: 'hash_mismatch'; readonly index: number; readonly computed: string; readonly stored: string }
39
+ | { readonly type: 'chain_break'; readonly index: number; readonly expected: string; readonly actual: string }
40
+ | { readonly type: 'hlc_not_increasing'; readonly index: number };
41
+
42
+ /** Sentinel `previous` value marking the root of a receipt chain. */
43
+ export const GENESIS: string = 'genesis';
44
+
45
+ /**
46
+ * Compute the content hash of a receipt envelope.
47
+ *
48
+ * Normalizes the `previous` field (sorts array form), canonicalizes the
49
+ * payload, and hashes with SHA-256 via TypedRef.
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * import { Effect } from 'effect';
54
+ *
55
+ * const hash = yield* Receipt.hashEnvelope(envelope);
56
+ * // hash === envelope.hash (if envelope is valid)
57
+ * ```
58
+ */
59
+ export const hashEnvelope = (envelope: ReceiptEnvelope): Effect.Effect<string> => {
60
+ const previousNormalized = Array.isArray(envelope.previous)
61
+ ? [...(envelope.previous as readonly string[])].sort()
62
+ : envelope.previous;
63
+ const hashInput = TypedRefModule.canonicalize({
64
+ kind: envelope.kind,
65
+ timestamp: envelope.timestamp,
66
+ subject: envelope.subject,
67
+ payload: envelope.payload,
68
+ previous: previousNormalized,
69
+ });
70
+ return TypedRefModule.hash(hashInput);
71
+ };
72
+
73
+ /**
74
+ * Create a new receipt envelope with an auto-computed content hash.
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * const envelope = yield* Receipt.createEnvelope(
79
+ * 'state-change',
80
+ * { type: 'effect', id: 'actor-1' },
81
+ * { _tag: 'TypedRef', mediaType: 'application/json', data: { key: 'value' } },
82
+ * hlcTimestamp,
83
+ * Receipt.GENESIS,
84
+ * );
85
+ * // envelope.hash is the computed SHA-256 content address
86
+ * ```
87
+ */
88
+ export const createEnvelope = (
89
+ kind: string,
90
+ subject: ReceiptSubject,
91
+ payload: TypedRef.Shape,
92
+ timestamp: HLC,
93
+ previousHash: string | readonly string[],
94
+ ): Effect.Effect<ReceiptEnvelope> =>
95
+ Effect.gen(function* () {
96
+ const previousNormalized = Array.isArray(previousHash)
97
+ ? [...(previousHash as readonly string[])].sort()
98
+ : previousHash;
99
+ const partial = { kind, timestamp, subject, payload, previous: previousNormalized };
100
+ const h = yield* TypedRefModule.hash(TypedRefModule.canonicalize(partial));
101
+ return { kind, timestamp, subject, payload, hash: h, previous: previousNormalized };
102
+ });
103
+
104
+ /**
105
+ * Build a linear chain of receipt envelopes from an array of entries.
106
+ *
107
+ * Each envelope's `previous` points to the prior envelope's hash,
108
+ * starting from GENESIS.
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * const chain = yield* Receipt.buildChain([
113
+ * { kind: 'init', subject: { type: 'effect', id: 'a' }, payload, timestamp: ts1 },
114
+ * { kind: 'update', subject: { type: 'effect', id: 'a' }, payload, timestamp: ts2 },
115
+ * ]);
116
+ * // chain.length === 2
117
+ * // chain[1].previous === chain[0].hash
118
+ * ```
119
+ */
120
+ export const buildChain = (
121
+ entries: ReadonlyArray<{
122
+ kind: string;
123
+ subject: ReceiptSubject;
124
+ payload: TypedRef.Shape;
125
+ timestamp: HLC;
126
+ }>,
127
+ ): Effect.Effect<ReceiptEnvelope[]> =>
128
+ Effect.gen(function* () {
129
+ const chain: ReceiptEnvelope[] = [];
130
+ let previousHash = GENESIS;
131
+ for (const entry of entries) {
132
+ const envelope = yield* createEnvelope(entry.kind, entry.subject, entry.payload, entry.timestamp, previousHash);
133
+ chain.push(envelope);
134
+ previousHash = envelope.hash;
135
+ }
136
+ return chain;
137
+ });
138
+
139
+ /**
140
+ * Validate a receipt chain: genesis link, hash integrity, chain continuity, HLC ordering.
141
+ *
142
+ * Returns true on success or fails with an Error describing the violation.
143
+ *
144
+ * @example
145
+ * ```ts
146
+ * const chain = yield* Receipt.buildChain(entries);
147
+ * const valid = yield* Receipt.validateChain(chain);
148
+ * // valid === true
149
+ * ```
150
+ */
151
+ export const validateChain = (chain: ReadonlyArray<ReceiptEnvelope>): Effect.Effect<boolean, Error> =>
152
+ Effect.gen(function* () {
153
+ if (chain.length === 0) return true;
154
+ const first = chain[0]!;
155
+ const firstPrev = first.previous;
156
+ if (firstPrev !== GENESIS && !(Array.isArray(firstPrev) && (firstPrev as readonly string[]).includes(GENESIS))) {
157
+ return yield* Effect.fail(new Error('First envelope must have previous=genesis'));
158
+ }
159
+ for (let i = 0; i < chain.length; i++) {
160
+ const envelope = chain[i]!;
161
+ const computedHash = yield* hashEnvelope(envelope);
162
+ if (computedHash !== envelope.hash) {
163
+ return yield* Effect.fail(
164
+ new Error(`Envelope ${i}: hash mismatch (expected "${envelope.hash}", computed "${computedHash}")`),
165
+ );
166
+ }
167
+ const isMerge = Array.isArray(envelope.previous);
168
+ if (!isMerge && i > 0 && envelope.previous !== chain[i - 1]!.hash) {
169
+ return yield* Effect.fail(new Error(`Envelope ${i}: chain break`));
170
+ }
171
+ if (!isMerge && i > 0 && HLCOps.compare(chain[i - 1]!.timestamp, envelope.timestamp) >= 0) {
172
+ return yield* Effect.fail(new Error(`Envelope ${i}: HLC not monotonically increasing`));
173
+ }
174
+ }
175
+ return true;
176
+ });
177
+
178
+ /**
179
+ * Validate a receipt chain with detailed, structured error reporting.
180
+ *
181
+ * Returns `true` on success or fails with a typed `ChainValidationError`
182
+ * discriminated union (not_genesis | hash_mismatch | chain_break | hlc_not_increasing).
183
+ *
184
+ * @example
185
+ * ```ts
186
+ * import { Effect } from 'effect';
187
+ *
188
+ * const result = yield* Effect.either(Receipt.validateChainDetailed(chain));
189
+ * // result._tag === 'Right' on success
190
+ * // result._tag === 'Left' with .left.type on failure
191
+ * ```
192
+ */
193
+ export const validateChainDetailed = (
194
+ chain: ReadonlyArray<ReceiptEnvelope>,
195
+ ): Effect.Effect<true, ChainValidationError> =>
196
+ Effect.gen(function* () {
197
+ if (chain.length === 0) return true as const;
198
+
199
+ const first = chain[0]!;
200
+ const firstPrev = first.previous;
201
+ const firstIsGenesis =
202
+ firstPrev === GENESIS || (Array.isArray(firstPrev) && (firstPrev as readonly string[]).includes(GENESIS));
203
+ if (!firstIsGenesis) {
204
+ return yield* Effect.fail({ type: 'not_genesis' as const, index: 0 as const });
205
+ }
206
+
207
+ for (let i = 0; i < chain.length; i++) {
208
+ const envelope = chain[i]!;
209
+ const isMerge = Array.isArray(envelope.previous);
210
+
211
+ const computedHash = yield* hashEnvelope(envelope);
212
+ if (computedHash !== envelope.hash) {
213
+ return yield* Effect.fail({
214
+ type: 'hash_mismatch' as const,
215
+ index: i,
216
+ computed: computedHash,
217
+ stored: envelope.hash,
218
+ });
219
+ }
220
+
221
+ if (!isMerge && i > 0 && envelope.previous !== chain[i - 1]!.hash) {
222
+ return yield* Effect.fail({
223
+ type: 'chain_break' as const,
224
+ index: i,
225
+ expected: chain[i - 1]!.hash,
226
+ actual: envelope.previous as string,
227
+ });
228
+ }
229
+
230
+ if (!isMerge && i > 0 && HLCOps.compare(chain[i - 1]!.timestamp, envelope.timestamp) >= 0) {
231
+ return yield* Effect.fail({
232
+ type: 'hlc_not_increasing' as const,
233
+ index: i,
234
+ });
235
+ }
236
+ }
237
+
238
+ return true as const;
239
+ });
240
+
241
+ /**
242
+ * Check whether a receipt envelope is a genesis (root) envelope.
243
+ *
244
+ * @example
245
+ * ```ts
246
+ * const chain = yield* Receipt.buildChain(entries);
247
+ * Receipt.isGenesis(chain[0]); // true
248
+ * Receipt.isGenesis(chain[1]); // false
249
+ * ```
250
+ */
251
+ export const isGenesis = (receipt: ReceiptEnvelope): boolean =>
252
+ receipt.previous === GENESIS ||
253
+ (Array.isArray(receipt.previous) && (receipt.previous as readonly string[]).includes(GENESIS));
254
+
255
+ /**
256
+ * Get the last (most recent) envelope in a chain.
257
+ *
258
+ * @example
259
+ * ```ts
260
+ * const latest = Receipt.head(chain);
261
+ * // latest === chain[chain.length - 1]
262
+ * ```
263
+ */
264
+ export const head = (chain: ReadonlyArray<ReceiptEnvelope>): ReceiptEnvelope | undefined =>
265
+ chain.length > 0 ? chain[chain.length - 1] : undefined;
266
+
267
+ /**
268
+ * Get the first (genesis) envelope in a chain.
269
+ *
270
+ * @example
271
+ * ```ts
272
+ * const first = Receipt.tail(chain);
273
+ * // first === chain[0]
274
+ * ```
275
+ */
276
+ export const tail = (chain: ReadonlyArray<ReceiptEnvelope>): ReceiptEnvelope | undefined =>
277
+ chain.length > 0 ? chain[0] : undefined;
278
+
279
+ /**
280
+ * Append a new entry to an existing chain, auto-linking to the previous hash.
281
+ *
282
+ * Optionally accepts explicit previous hashes for merge envelopes.
283
+ *
284
+ * @example
285
+ * ```ts
286
+ * const chain = yield* Receipt.buildChain([entry1]);
287
+ * const extended = yield* Receipt.append(chain, {
288
+ * kind: 'update', subject: { type: 'effect', id: 'a' }, payload, timestamp: ts2,
289
+ * });
290
+ * // extended.length === 2
291
+ * ```
292
+ */
293
+ export const append = (
294
+ chain: ReadonlyArray<ReceiptEnvelope>,
295
+ entry: { kind: string; subject: ReceiptSubject; payload: TypedRef.Shape; timestamp: HLC },
296
+ previousHashes?: readonly string[],
297
+ ): Effect.Effect<ReceiptEnvelope[]> =>
298
+ Effect.gen(function* () {
299
+ const previousHash: string | readonly string[] = previousHashes
300
+ ? previousHashes
301
+ : chain.length > 0
302
+ ? chain[chain.length - 1]!.hash
303
+ : GENESIS;
304
+ const envelope = yield* createEnvelope(entry.kind, entry.subject, entry.payload, entry.timestamp, previousHash);
305
+ return [...chain, envelope];
306
+ });
307
+
308
+ /**
309
+ * Find an envelope in a chain by its content hash.
310
+ *
311
+ * @example
312
+ * ```ts
313
+ * const found = Receipt.findByHash(chain, targetHash);
314
+ * // found?.hash === targetHash
315
+ * ```
316
+ */
317
+ export const findByHash = (chain: ReadonlyArray<ReceiptEnvelope>, hash: string): ReceiptEnvelope | undefined =>
318
+ chain.find((e) => e.hash === hash);
319
+
320
+ /**
321
+ * Find all envelopes in a chain matching a given kind.
322
+ *
323
+ * @example
324
+ * ```ts
325
+ * const updates = Receipt.findByKind(chain, 'update');
326
+ * // updates contains all envelopes with kind === 'update'
327
+ * ```
328
+ */
329
+ export const findByKind = (chain: ReadonlyArray<ReceiptEnvelope>, kind: string): ReceiptEnvelope[] =>
330
+ chain.filter((e) => e.kind === kind);
331
+
332
+ /**
333
+ * Generate an HMAC-SHA-256 key for signing receipt envelopes.
334
+ *
335
+ * @example
336
+ * ```ts
337
+ * const key = yield* Receipt.generateMACKey();
338
+ * const signed = yield* Receipt.macEnvelope(envelope, key);
339
+ * // signed.signature is a hex string
340
+ * ```
341
+ */
342
+ export const generateMACKey = (): Effect.Effect<CryptoKey, Error> =>
343
+ Effect.tryPromise({
344
+ try: () => crypto.subtle.generateKey({ name: 'HMAC', hash: { name: 'SHA-256' } }, true, ['sign', 'verify']),
345
+ catch: (error) => new Error(`Failed to generate MAC key: ${error}`),
346
+ });
347
+
348
+ /**
349
+ * Sign a receipt envelope with an HMAC key, adding a `signature` field.
350
+ *
351
+ * @example
352
+ * ```ts
353
+ * const key = yield* Receipt.generateMACKey();
354
+ * const signed = yield* Receipt.macEnvelope(envelope, key);
355
+ * // signed.signature !== undefined
356
+ * ```
357
+ */
358
+ export const macEnvelope = (envelope: ReceiptEnvelope, key: CryptoKey): Effect.Effect<ReceiptEnvelope, Error> =>
359
+ Effect.gen(function* () {
360
+ const data = new TextEncoder().encode(envelope.hash);
361
+ const signatureBuffer = yield* Effect.tryPromise({
362
+ try: () => crypto.subtle.sign('HMAC', key, data),
363
+ catch: (error) => new Error(`Failed to MAC envelope: ${error}`),
364
+ });
365
+ const signatureArray = Array.from(new Uint8Array(signatureBuffer));
366
+ const signature = signatureArray.map((b) => b.toString(16).padStart(2, '0')).join('');
367
+ return { ...envelope, signature };
368
+ });
369
+
370
+ /**
371
+ * Verify an envelope's HMAC signature against a key.
372
+ *
373
+ * Returns false if the envelope has no signature.
374
+ *
375
+ * @example
376
+ * ```ts
377
+ * const valid = yield* Receipt.verifyMAC(signedEnvelope, key);
378
+ * // valid === true if signature matches
379
+ * ```
380
+ */
381
+ export const verifyMAC = (envelope: ReceiptEnvelope, key: CryptoKey): Effect.Effect<boolean, Error> =>
382
+ Effect.gen(function* () {
383
+ if (!envelope.signature) return false;
384
+ const signatureHex = envelope.signature;
385
+ if (!/^[0-9a-fA-F]+$/.test(signatureHex) || signatureHex.length % 2 !== 0) {
386
+ return yield* Effect.fail(new Error('Invalid signature hex: expected even-length hex string'));
387
+ }
388
+ const signatureArray = new Uint8Array(signatureHex.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16)));
389
+ const data = new TextEncoder().encode(envelope.hash);
390
+ const valid = yield* Effect.tryPromise({
391
+ try: () => crypto.subtle.verify('HMAC', key, signatureArray, data),
392
+ catch: (error) => new Error(`Failed to verify signature: ${error}`),
393
+ });
394
+ return valid;
395
+ });
396
+
397
+ /**
398
+ * Receipt namespace -- chain validation and envelope construction.
399
+ *
400
+ * Build, validate, append, query, and sign linear receipt chains.
401
+ * Each envelope is content-addressed and linked to its predecessor.
402
+ * Supports HMAC signing/verification for tamper detection.
403
+ *
404
+ * @example
405
+ * ```ts
406
+ * import { Effect } from 'effect';
407
+ * import { Receipt, HLC } from '@czap/core';
408
+ *
409
+ * const program = Effect.gen(function* () {
410
+ * const ts = HLC.increment(HLC.create('node-1'), Date.now());
411
+ * const chain = yield* Receipt.buildChain([
412
+ * { kind: 'init', subject: { type: 'effect', id: 'a' }, payload, timestamp: ts },
413
+ * ]);
414
+ * const valid = yield* Receipt.validateChain(chain);
415
+ * const latest = Receipt.head(chain);
416
+ * });
417
+ * ```
418
+ */
419
+ export const Receipt = {
420
+ GENESIS,
421
+ createEnvelope,
422
+ buildChain,
423
+ validateChain,
424
+ validateChainDetailed,
425
+ hashEnvelope,
426
+ isGenesis,
427
+ head,
428
+ tail,
429
+ append,
430
+ findByHash,
431
+ findByKind,
432
+ generateMACKey,
433
+ macEnvelope,
434
+ verifyMAC,
435
+ };
436
+
437
+ export declare namespace Receipt {
438
+ /** Alias for {@link ReceiptSubject}. */
439
+ export type Subject = ReceiptSubject;
440
+ /** Alias for {@link ReceiptEnvelope}. */
441
+ export type Envelope = ReceiptEnvelope;
442
+ /** Alias for {@link ChainValidationError}. */
443
+ export type ChainError = ChainValidationError;
444
+ }
@@ -0,0 +1,230 @@
1
+ /**
2
+ * RuntimeCoordinator -- shared host/runtime coordination surface.
3
+ *
4
+ * Bridges Plan and ECS into the live host path by:
5
+ * - defining the execution graph for runtime passes
6
+ * - backing quantizer state indices and dirty epochs with dense stores
7
+ *
8
+ * The coordinator is intentionally light-weight on the hot path: the plan is
9
+ * constructed once up front, and dense stores are used for numeric state that
10
+ * is read repeatedly during runtime work.
11
+ *
12
+ * @module
13
+ */
14
+
15
+ import { Part, EntityId } from './ecs.js';
16
+ import type { DenseStore } from './ecs.js';
17
+ import { Plan } from './plan.js';
18
+
19
+ /**
20
+ * Named stages of the runtime frame pass, in canonical topological order:
21
+ * discrete quantization first, then blend weights, then target emitters.
22
+ */
23
+ export type RuntimePhase = 'compute-discrete' | 'compute-blend' | 'emit-css' | 'emit-glsl' | 'emit-aria';
24
+
25
+ /** Options accepted by {@link RuntimeCoordinator.create}: entity capacity and plan name. */
26
+ export interface RuntimeCoordinatorConfig {
27
+ readonly capacity?: number;
28
+ readonly name?: string;
29
+ }
30
+
31
+ /**
32
+ * Live coordinator surface: the immutable runtime {@link Plan}, ordered phase
33
+ * list, dense stores for state index + dirty epoch, and registration/mutation
34
+ * APIs used by the compositor on the hot path.
35
+ */
36
+ export interface RuntimeCoordinatorShape {
37
+ readonly plan: Plan.IR;
38
+ readonly phases: readonly RuntimePhase[];
39
+ readonly stores: {
40
+ readonly stateIndex: DenseStore;
41
+ readonly dirtyEpoch: DenseStore;
42
+ };
43
+ reset(
44
+ registrations?: readonly {
45
+ readonly name: string;
46
+ readonly states: readonly string[];
47
+ }[],
48
+ ): void;
49
+ registerQuantizer(name: string, states: readonly string[]): EntityId;
50
+ removeQuantizer(name: string): void;
51
+ hasQuantizer(name: string): boolean;
52
+ setState(name: string, state: string): void;
53
+ applyState(name: string, state: string): number;
54
+ getStateIndex(name: string): number;
55
+ markDirty(name: string): void;
56
+ getDirtyEpoch(name: string): number;
57
+ registeredNames(): readonly string[];
58
+ }
59
+
60
+ interface RegisteredQuantizer {
61
+ readonly entityId: EntityId;
62
+ readonly stateLookup: Readonly<Record<string, number>>;
63
+ }
64
+
65
+ const DEFAULT_RUNTIME_CAPACITY = 128;
66
+
67
+ function makeRuntimePlan(name: string): Plan.IR {
68
+ return Plan.make(name)
69
+ .step('compute-discrete', { type: 'noop' }, { phase: 'compute-discrete' })
70
+ .step('compute-blend', { type: 'noop' }, { phase: 'compute-blend' })
71
+ .step('emit-css', { type: 'noop' }, { phase: 'emit-css' })
72
+ .step('emit-glsl', { type: 'noop' }, { phase: 'emit-glsl' })
73
+ .step('emit-aria', { type: 'noop' }, { phase: 'emit-aria' })
74
+ .seq('step-1', 'step-2')
75
+ .par('step-2', 'step-3')
76
+ .par('step-2', 'step-4')
77
+ .par('step-2', 'step-5')
78
+ .build();
79
+ }
80
+
81
+ function orderedPhases(plan: Plan.IR): readonly RuntimePhase[] {
82
+ const sorted = Plan.topoSort(plan).sorted;
83
+ const stepsById = new Map(plan.steps.map((step) => [step.id, step]));
84
+ return sorted
85
+ .map((id) => stepsById.get(id)?.metadata?.['phase'])
86
+ .filter((phase): phase is RuntimePhase => typeof phase === 'string');
87
+ }
88
+
89
+ const RUNTIME_PLAN_TEMPLATE = makeRuntimePlan('czap-runtime');
90
+ const RUNTIME_PHASES = orderedPhases(RUNTIME_PLAN_TEMPLATE);
91
+
92
+ /**
93
+ * Build a fresh {@link RuntimeCoordinator} with dense backing stores and the
94
+ * canonical runtime plan. Prefer {@link RuntimeCoordinator.create}, which is
95
+ * the exported entry point.
96
+ */
97
+ export function createRuntimeCoordinator(config?: RuntimeCoordinatorConfig): RuntimeCoordinatorShape {
98
+ const name = config?.name ?? 'czap-runtime';
99
+ const plan = name === RUNTIME_PLAN_TEMPLATE.name ? RUNTIME_PLAN_TEMPLATE : { ...RUNTIME_PLAN_TEMPLATE, name };
100
+ const phases = RUNTIME_PHASES;
101
+ const stateIndex = Part.dense('state-index', config?.capacity ?? DEFAULT_RUNTIME_CAPACITY);
102
+ const dirtyEpoch = Part.dense('dirty-epoch', config?.capacity ?? DEFAULT_RUNTIME_CAPACITY);
103
+ const quantizerByName = new Map<string, RegisteredQuantizer>();
104
+ let nextEntity = 0;
105
+
106
+ const ensureQuantizer = (name: string): RegisteredQuantizer | undefined => quantizerByName.get(name);
107
+ return {
108
+ plan,
109
+ phases,
110
+ stores: {
111
+ stateIndex,
112
+ dirtyEpoch,
113
+ },
114
+
115
+ reset(registrations) {
116
+ quantizerByName.clear();
117
+ nextEntity = 0;
118
+ stateIndex.reset();
119
+ dirtyEpoch.reset();
120
+
121
+ for (const registration of registrations ?? []) {
122
+ this.registerQuantizer(registration.name, registration.states);
123
+ }
124
+ },
125
+
126
+ registerQuantizer(name, states) {
127
+ const existing = ensureQuantizer(name);
128
+ if (existing) {
129
+ return existing.entityId;
130
+ }
131
+
132
+ const entityId = EntityId(`runtime-${++nextEntity}`);
133
+ const stateLookup: Record<string, number> = Object.create(null);
134
+ for (let index = 0; index < states.length; index++) {
135
+ stateLookup[states[index]!] = index;
136
+ }
137
+ quantizerByName.set(name, {
138
+ entityId,
139
+ stateLookup,
140
+ });
141
+ stateIndex.set(entityId, 0);
142
+ dirtyEpoch.set(entityId, 1);
143
+ return entityId;
144
+ },
145
+
146
+ removeQuantizer(name) {
147
+ const quantizer = ensureQuantizer(name);
148
+ if (!quantizer) {
149
+ return;
150
+ }
151
+
152
+ quantizerByName.delete(name);
153
+ stateIndex.delete(quantizer.entityId);
154
+ dirtyEpoch.delete(quantizer.entityId);
155
+ },
156
+
157
+ hasQuantizer(name) {
158
+ return quantizerByName.has(name);
159
+ },
160
+
161
+ setState(name, state) {
162
+ const quantizer = ensureQuantizer(name);
163
+ if (!quantizer) {
164
+ return;
165
+ }
166
+
167
+ stateIndex.set(quantizer.entityId, quantizer.stateLookup[state] ?? 0);
168
+ },
169
+
170
+ applyState(name, state) {
171
+ const quantizer = ensureQuantizer(name);
172
+ if (!quantizer) {
173
+ return 0;
174
+ }
175
+
176
+ const nextIndex = quantizer.stateLookup[state] ?? 0;
177
+ stateIndex.set(quantizer.entityId, nextIndex);
178
+ return nextIndex;
179
+ },
180
+
181
+ getStateIndex(name) {
182
+ const quantizer = ensureQuantizer(name);
183
+ if (!quantizer) {
184
+ return 0;
185
+ }
186
+
187
+ return stateIndex.get(quantizer.entityId)!;
188
+ },
189
+
190
+ markDirty(name) {
191
+ const quantizer = ensureQuantizer(name);
192
+ if (!quantizer) {
193
+ return;
194
+ }
195
+
196
+ dirtyEpoch.set(quantizer.entityId, dirtyEpoch.get(quantizer.entityId)! + 1);
197
+ },
198
+
199
+ getDirtyEpoch(name) {
200
+ const quantizer = ensureQuantizer(name);
201
+ if (!quantizer) {
202
+ return 0;
203
+ }
204
+
205
+ return dirtyEpoch.get(quantizer.entityId)!;
206
+ },
207
+
208
+ registeredNames() {
209
+ return Array.from(quantizerByName.keys());
210
+ },
211
+ };
212
+ }
213
+
214
+ /**
215
+ * Runtime coordinator namespace — single entry point for building the shared
216
+ * {@link Plan} + ECS store bundle consumed by every host adapter.
217
+ */
218
+ export const RuntimeCoordinator = {
219
+ /** Create a fresh coordinator. See {@link createRuntimeCoordinator}. */
220
+ create: createRuntimeCoordinator,
221
+ } as const;
222
+
223
+ export declare namespace RuntimeCoordinator {
224
+ /** Alias for `RuntimeCoordinatorShape`. */
225
+ export type Shape = RuntimeCoordinatorShape;
226
+ /** Alias for `RuntimeCoordinatorConfig`. */
227
+ export type Config = RuntimeCoordinatorConfig;
228
+ /** Alias for `RuntimePhase`. */
229
+ export type Phase = RuntimePhase;
230
+ }