@ottochain/sdk 2.3.0 → 2.5.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 (287) hide show
  1. package/dist/cjs/apps/compute/index.js +33 -0
  2. package/dist/cjs/apps/compute/state-machines/compute-rule110.js +164 -0
  3. package/dist/cjs/apps/compute/state-machines/index.js +9 -0
  4. package/dist/cjs/apps/contracts/index.js +16 -9
  5. package/dist/cjs/apps/contracts/state-machines/contract-agreement.js +134 -161
  6. package/dist/cjs/apps/contracts/state-machines/contract-escrow.js +192 -170
  7. package/dist/cjs/apps/contracts/state-machines/contract-universal.js +46 -58
  8. package/dist/cjs/apps/corporate/index.js +2 -2
  9. package/dist/cjs/apps/corporate/state-machines/corp-board.js +500 -456
  10. package/dist/cjs/apps/corporate/state-machines/corp-entity.js +487 -361
  11. package/dist/cjs/apps/corporate/state-machines/corp-securities.js +769 -576
  12. package/dist/cjs/apps/corporate/state-machines/corp-shareholders.js +567 -557
  13. package/dist/cjs/apps/governance/index.js +21 -21
  14. package/dist/cjs/apps/governance/state-machines/dao-multisig.js +188 -209
  15. package/dist/cjs/apps/governance/state-machines/dao-reputation.js +181 -228
  16. package/dist/cjs/apps/governance/state-machines/dao-single.js +105 -88
  17. package/dist/cjs/apps/governance/state-machines/dao-token.js +199 -227
  18. package/dist/cjs/apps/governance/state-machines/governance-simple.js +231 -231
  19. package/dist/cjs/apps/governance/state-machines/governance-universal.js +64 -64
  20. package/dist/cjs/apps/identity/constants.js +55 -22
  21. package/dist/cjs/apps/identity/index.js +28 -12
  22. package/dist/cjs/apps/identity/state-machines/identity-agent.js +124 -158
  23. package/dist/cjs/apps/identity/state-machines/identity-oracle.js +158 -173
  24. package/dist/cjs/apps/identity/state-machines/identity-registry.js +262 -0
  25. package/dist/cjs/apps/identity/state-machines/identity-universal.js +51 -57
  26. package/dist/cjs/apps/identity/state-machines/index.js +3 -1
  27. package/dist/cjs/apps/index.js +22 -8
  28. package/dist/cjs/apps/lending/assets.js +247 -0
  29. package/dist/cjs/apps/lending/credit-scoring.js +87 -0
  30. package/dist/cjs/apps/lending/eligibility.js +105 -0
  31. package/dist/cjs/apps/lending/index.js +91 -0
  32. package/dist/cjs/apps/lending/state-machines/index.js +9 -0
  33. package/dist/cjs/apps/lending/state-machines/lending-zk-loan.js +433 -0
  34. package/dist/cjs/apps/markets/index.js +22 -12
  35. package/dist/cjs/apps/markets/state-machines/market-auction.js +116 -134
  36. package/dist/cjs/apps/markets/state-machines/market-crowdfund.js +131 -141
  37. package/dist/cjs/apps/markets/state-machines/market-group-buy.js +163 -184
  38. package/dist/cjs/apps/markets/state-machines/market-prediction.js +226 -233
  39. package/dist/cjs/apps/markets/state-machines/market-universal.js +65 -80
  40. package/dist/cjs/apps/oracles/index.js +3 -3
  41. package/dist/cjs/apps/privacy/index.js +67 -0
  42. package/dist/cjs/apps/privacy/state-machines/index.js +17 -0
  43. package/dist/cjs/apps/privacy/state-machines/mixer-ddhRing.js +286 -0
  44. package/dist/cjs/apps/privacy/state-machines/note-pool.js +476 -0
  45. package/dist/cjs/apps/staked-pool/assets.js +103 -0
  46. package/dist/cjs/apps/staked-pool/base.js +498 -0
  47. package/dist/cjs/apps/staked-pool/consumer.js +55 -0
  48. package/dist/cjs/apps/staked-pool/index.js +83 -0
  49. package/dist/cjs/apps/staked-pool/state-machines/index.js +12 -0
  50. package/dist/cjs/apps/staked-pool/state-machines/staked-pool-base.js +49 -0
  51. package/dist/cjs/apps/staked-pool/state-machines/staked-pool-oracle.js +131 -0
  52. package/dist/cjs/errors.js +3 -3
  53. package/dist/cjs/generated/google/protobuf/struct.js +5 -5
  54. package/dist/cjs/generated/google/protobuf/timestamp.js +1 -1
  55. package/dist/cjs/generated/index.js +11 -7
  56. package/dist/cjs/generated/openapi.js +6 -0
  57. package/dist/cjs/generated/ottochain/apps/contracts/v1/contract.js +51 -51
  58. package/dist/cjs/generated/ottochain/apps/corporate/v1/corporate.js +20 -21
  59. package/dist/cjs/generated/ottochain/apps/governance/v1/governance.js +14 -14
  60. package/dist/cjs/generated/ottochain/apps/identity/v1/attestation.js +5 -5
  61. package/dist/cjs/generated/ottochain/apps/identity/v1/identity.js +125 -131
  62. package/dist/cjs/generated/ottochain/apps/markets/v1/market.js +108 -108
  63. package/dist/cjs/generated/ottochain/v1/common.js +5 -5
  64. package/dist/cjs/generated/ottochain/v1/fiber.js +44 -6
  65. package/dist/cjs/generated/ottochain/v1/messages.js +1 -1
  66. package/dist/cjs/generated/ottochain/v1/records.js +1 -1
  67. package/dist/cjs/index.js +58 -7
  68. package/dist/cjs/openapi.js +2 -0
  69. package/dist/cjs/ottochain/drop-nulls.js +1 -2
  70. package/dist/cjs/ottochain/genesis-manifest.js +17 -11
  71. package/dist/cjs/ottochain/index.js +23 -13
  72. package/dist/cjs/ottochain/metagraph-client.js +81 -16
  73. package/dist/cjs/ottochain/snapshot.js +13 -16
  74. package/dist/cjs/ottochain/transaction.js +67 -14
  75. package/dist/cjs/ottochain/types.js +16 -2
  76. package/dist/cjs/privacy/index.js +14 -0
  77. package/dist/cjs/privacy/sealed-bid.js +213 -0
  78. package/dist/cjs/privacy/shield-app.js +131 -0
  79. package/dist/cjs/schema/effects.js +156 -0
  80. package/dist/cjs/schema/fiber-app.js +150 -15
  81. package/dist/cjs/schema/guard-lint.js +343 -0
  82. package/dist/cjs/schema/guards.js +246 -0
  83. package/dist/cjs/signing.js +80 -0
  84. package/dist/cjs/templates/asset-policy.js +190 -0
  85. package/dist/cjs/templates/index.js +86 -0
  86. package/dist/cjs/templates/machine.js +130 -0
  87. package/dist/cjs/templates/migration.js +39 -0
  88. package/dist/cjs/templates/state-shape.js +38 -0
  89. package/dist/cjs/types.js +2 -3
  90. package/dist/cjs/validation.js +9 -11
  91. package/dist/cjs/verify.js +9 -4
  92. package/dist/cjs/zk/commit.js +53 -0
  93. package/dist/cjs/zk/guard.js +107 -0
  94. package/dist/cjs/zk/index.js +48 -0
  95. package/dist/cjs/zk/preimage.js +37 -0
  96. package/dist/cjs/zk/prover.js +87 -0
  97. package/dist/cjs/zk/registry.js +62 -0
  98. package/dist/cjs/zk/types.js +28 -0
  99. package/dist/cjs/zk/witness.js +19 -0
  100. package/dist/esm/apps/compute/index.js +29 -0
  101. package/dist/esm/apps/compute/state-machines/compute-rule110.js +161 -0
  102. package/dist/esm/apps/compute/state-machines/index.js +5 -0
  103. package/dist/esm/apps/contracts/index.js +9 -5
  104. package/dist/esm/apps/contracts/state-machines/contract-agreement.js +135 -162
  105. package/dist/esm/apps/contracts/state-machines/contract-escrow.js +193 -171
  106. package/dist/esm/apps/contracts/state-machines/contract-universal.js +47 -59
  107. package/dist/esm/apps/corporate/index.js +2 -2
  108. package/dist/esm/apps/corporate/state-machines/corp-board.js +501 -457
  109. package/dist/esm/apps/corporate/state-machines/corp-entity.js +488 -362
  110. package/dist/esm/apps/corporate/state-machines/corp-securities.js +770 -577
  111. package/dist/esm/apps/corporate/state-machines/corp-shareholders.js +568 -558
  112. package/dist/esm/apps/governance/index.js +10 -10
  113. package/dist/esm/apps/governance/state-machines/dao-multisig.js +189 -210
  114. package/dist/esm/apps/governance/state-machines/dao-reputation.js +182 -229
  115. package/dist/esm/apps/governance/state-machines/dao-single.js +106 -89
  116. package/dist/esm/apps/governance/state-machines/dao-token.js +200 -228
  117. package/dist/esm/apps/governance/state-machines/governance-simple.js +232 -232
  118. package/dist/esm/apps/governance/state-machines/governance-universal.js +65 -65
  119. package/dist/esm/apps/identity/constants.js +52 -21
  120. package/dist/esm/apps/identity/index.js +14 -9
  121. package/dist/esm/apps/identity/state-machines/identity-agent.js +125 -159
  122. package/dist/esm/apps/identity/state-machines/identity-oracle.js +159 -174
  123. package/dist/esm/apps/identity/state-machines/identity-registry.js +259 -0
  124. package/dist/esm/apps/identity/state-machines/identity-universal.js +52 -58
  125. package/dist/esm/apps/identity/state-machines/index.js +1 -0
  126. package/dist/esm/apps/index.js +9 -5
  127. package/dist/esm/apps/lending/assets.js +231 -0
  128. package/dist/esm/apps/lending/credit-scoring.js +81 -0
  129. package/dist/esm/apps/lending/eligibility.js +99 -0
  130. package/dist/esm/apps/lending/index.js +69 -0
  131. package/dist/esm/apps/lending/state-machines/index.js +5 -0
  132. package/dist/esm/apps/lending/state-machines/lending-zk-loan.js +430 -0
  133. package/dist/esm/apps/markets/index.js +11 -7
  134. package/dist/esm/apps/markets/state-machines/market-auction.js +117 -135
  135. package/dist/esm/apps/markets/state-machines/market-crowdfund.js +132 -142
  136. package/dist/esm/apps/markets/state-machines/market-group-buy.js +164 -185
  137. package/dist/esm/apps/markets/state-machines/market-prediction.js +227 -234
  138. package/dist/esm/apps/markets/state-machines/market-universal.js +66 -81
  139. package/dist/esm/apps/oracles/index.js +3 -3
  140. package/dist/esm/apps/privacy/index.js +56 -0
  141. package/dist/esm/apps/privacy/state-machines/index.js +6 -0
  142. package/dist/esm/apps/privacy/state-machines/mixer-ddhRing.js +282 -0
  143. package/dist/esm/apps/privacy/state-machines/note-pool.js +470 -0
  144. package/dist/esm/apps/staked-pool/assets.js +91 -0
  145. package/dist/esm/apps/staked-pool/base.js +493 -0
  146. package/dist/esm/apps/staked-pool/consumer.js +49 -0
  147. package/dist/esm/apps/staked-pool/index.js +60 -0
  148. package/dist/esm/apps/staked-pool/state-machines/index.js +6 -0
  149. package/dist/esm/apps/staked-pool/state-machines/staked-pool-base.js +46 -0
  150. package/dist/esm/apps/staked-pool/state-machines/staked-pool-oracle.js +127 -0
  151. package/dist/esm/generated/google/protobuf/struct.js +1 -1
  152. package/dist/esm/generated/google/protobuf/timestamp.js +1 -1
  153. package/dist/esm/generated/index.js +9 -5
  154. package/dist/esm/generated/openapi.js +5 -0
  155. package/dist/esm/generated/ottochain/apps/contracts/v1/contract.js +47 -47
  156. package/dist/esm/generated/ottochain/apps/corporate/v1/corporate.js +1 -1
  157. package/dist/esm/generated/ottochain/apps/governance/v1/governance.js +1 -1
  158. package/dist/esm/generated/ottochain/apps/identity/v1/attestation.js +1 -1
  159. package/dist/esm/generated/ottochain/apps/identity/v1/identity.js +115 -121
  160. package/dist/esm/generated/ottochain/apps/markets/v1/market.js +101 -101
  161. package/dist/esm/generated/ottochain/v1/common.js +1 -1
  162. package/dist/esm/generated/ottochain/v1/fiber.js +40 -2
  163. package/dist/esm/generated/ottochain/v1/messages.js +1 -1
  164. package/dist/esm/generated/ottochain/v1/records.js +1 -1
  165. package/dist/esm/index.js +28 -3
  166. package/dist/esm/openapi.js +1 -0
  167. package/dist/esm/ottochain/genesis-manifest.js +15 -9
  168. package/dist/esm/ottochain/index.js +2 -3
  169. package/dist/esm/ottochain/metagraph-client.js +81 -16
  170. package/dist/esm/ottochain/snapshot.js +6 -8
  171. package/dist/esm/ottochain/transaction.js +51 -3
  172. package/dist/esm/ottochain/types.js +15 -1
  173. package/dist/esm/privacy/index.js +6 -0
  174. package/dist/esm/privacy/sealed-bid.js +209 -0
  175. package/dist/esm/privacy/shield-app.js +127 -0
  176. package/dist/esm/schema/effects.js +145 -0
  177. package/dist/esm/schema/fiber-app.js +139 -8
  178. package/dist/esm/schema/guard-lint.js +337 -0
  179. package/dist/esm/schema/guards.js +228 -0
  180. package/dist/esm/signing.js +74 -0
  181. package/dist/esm/templates/asset-policy.js +182 -0
  182. package/dist/esm/templates/index.js +41 -0
  183. package/dist/esm/templates/machine.js +125 -0
  184. package/dist/esm/templates/migration.js +34 -0
  185. package/dist/esm/templates/state-shape.js +34 -0
  186. package/dist/esm/validation.js +1 -3
  187. package/dist/esm/verify.js +8 -2
  188. package/dist/esm/zk/commit.js +44 -0
  189. package/dist/esm/zk/guard.js +99 -0
  190. package/dist/esm/zk/index.js +19 -0
  191. package/dist/esm/zk/preimage.js +30 -0
  192. package/dist/esm/zk/prover.js +82 -0
  193. package/dist/esm/zk/registry.js +55 -0
  194. package/dist/esm/zk/types.js +25 -0
  195. package/dist/esm/zk/witness.js +15 -0
  196. package/dist/types/apps/compute/index.d.ts +176 -0
  197. package/dist/types/apps/compute/state-machines/compute-rule110.d.ts +157 -0
  198. package/dist/types/apps/compute/state-machines/index.d.ts +5 -0
  199. package/dist/types/apps/contracts/index.d.ts +81 -221
  200. package/dist/types/apps/contracts/state-machines/contract-agreement.d.ts +23 -98
  201. package/dist/types/apps/contracts/state-machines/contract-escrow.d.ts +49 -115
  202. package/dist/types/apps/contracts/state-machines/contract-universal.d.ts +4 -4
  203. package/dist/types/apps/corporate/index.d.ts +1176 -440
  204. package/dist/types/apps/corporate/state-machines/corp-board.d.ts +271 -135
  205. package/dist/types/apps/corporate/state-machines/corp-entity.d.ts +279 -74
  206. package/dist/types/apps/corporate/state-machines/corp-securities.d.ts +373 -149
  207. package/dist/types/apps/corporate/state-machines/corp-shareholders.d.ts +252 -80
  208. package/dist/types/apps/governance/index.d.ts +368 -470
  209. package/dist/types/apps/governance/state-machines/dao-multisig.d.ts +92 -107
  210. package/dist/types/apps/governance/state-machines/dao-reputation.d.ts +61 -85
  211. package/dist/types/apps/governance/state-machines/dao-single.d.ts +25 -47
  212. package/dist/types/apps/governance/state-machines/dao-token.d.ts +79 -68
  213. package/dist/types/apps/governance/state-machines/governance-simple.d.ts +95 -125
  214. package/dist/types/apps/governance/state-machines/governance-universal.d.ts +6 -6
  215. package/dist/types/apps/identity/constants.d.ts +33 -5
  216. package/dist/types/apps/identity/index.d.ts +415 -131
  217. package/dist/types/apps/identity/state-machines/identity-agent.d.ts +23 -23
  218. package/dist/types/apps/identity/state-machines/identity-oracle.d.ts +27 -98
  219. package/dist/types/apps/identity/state-machines/identity-registry.d.ts +379 -0
  220. package/dist/types/apps/identity/state-machines/identity-universal.d.ts +4 -4
  221. package/dist/types/apps/identity/state-machines/index.d.ts +1 -0
  222. package/dist/types/apps/index.d.ts +9 -5
  223. package/dist/types/apps/lending/assets.d.ts +258 -0
  224. package/dist/types/apps/lending/credit-scoring.d.ts +80 -0
  225. package/dist/types/apps/lending/eligibility.d.ts +83 -0
  226. package/dist/types/apps/lending/index.d.ts +556 -0
  227. package/dist/types/apps/lending/state-machines/index.d.ts +5 -0
  228. package/dist/types/apps/lending/state-machines/lending-zk-loan.d.ts +536 -0
  229. package/dist/types/apps/markets/index.d.ts +181 -276
  230. package/dist/types/apps/markets/state-machines/market-auction.d.ts +25 -49
  231. package/dist/types/apps/markets/state-machines/market-crowdfund.d.ts +35 -53
  232. package/dist/types/apps/markets/state-machines/market-group-buy.d.ts +35 -61
  233. package/dist/types/apps/markets/state-machines/market-prediction.d.ts +65 -86
  234. package/dist/types/apps/markets/state-machines/market-universal.d.ts +12 -12
  235. package/dist/types/apps/oracles/index.d.ts +1 -1
  236. package/dist/types/apps/privacy/index.d.ts +328 -0
  237. package/dist/types/apps/privacy/state-machines/index.d.ts +6 -0
  238. package/dist/types/apps/privacy/state-machines/mixer-ddhRing.d.ts +337 -0
  239. package/dist/types/apps/privacy/state-machines/note-pool.d.ts +196 -0
  240. package/dist/types/apps/staked-pool/assets.d.ts +53 -0
  241. package/dist/types/apps/staked-pool/base.d.ts +80 -0
  242. package/dist/types/apps/staked-pool/consumer.d.ts +29 -0
  243. package/dist/types/apps/staked-pool/index.d.ts +51 -0
  244. package/dist/types/apps/staked-pool/state-machines/index.d.ts +6 -0
  245. package/dist/types/apps/staked-pool/state-machines/staked-pool-base.d.ts +8 -0
  246. package/dist/types/apps/staked-pool/state-machines/staked-pool-oracle.d.ts +34 -0
  247. package/dist/types/generated/index.d.ts +5 -5
  248. package/dist/types/generated/openapi.d.ts +1270 -0
  249. package/dist/types/generated/ottochain/apps/contracts/v1/contract.d.ts +5 -5
  250. package/dist/types/generated/ottochain/apps/identity/v1/identity.d.ts +12 -12
  251. package/dist/types/generated/ottochain/apps/markets/v1/market.d.ts +11 -11
  252. package/dist/types/generated/ottochain/v1/fiber.d.ts +7 -0
  253. package/dist/types/index.d.ts +7 -3
  254. package/dist/types/openapi.d.ts +24 -0
  255. package/dist/types/ottochain/genesis-manifest.d.ts +11 -5
  256. package/dist/types/ottochain/index.d.ts +5 -6
  257. package/dist/types/ottochain/metagraph-client.d.ts +49 -7
  258. package/dist/types/ottochain/snapshot.d.ts +6 -6
  259. package/dist/types/ottochain/transaction.d.ts +41 -1
  260. package/dist/types/ottochain/types.d.ts +336 -31
  261. package/dist/types/privacy/index.d.ts +6 -0
  262. package/dist/types/privacy/sealed-bid.d.ts +348 -0
  263. package/dist/types/privacy/shield-app.d.ts +48 -0
  264. package/dist/types/schema/effects.d.ts +141 -0
  265. package/dist/types/schema/fiber-app.d.ts +282 -2
  266. package/dist/types/schema/guard-lint.d.ts +111 -0
  267. package/dist/types/schema/guards.d.ts +146 -0
  268. package/dist/types/signing.d.ts +45 -0
  269. package/dist/types/templates/asset-policy.d.ts +119 -0
  270. package/dist/types/templates/index.d.ts +26 -0
  271. package/dist/types/templates/machine.d.ts +139 -0
  272. package/dist/types/templates/migration.d.ts +34 -0
  273. package/dist/types/templates/state-shape.d.ts +30 -0
  274. package/dist/types/validation.d.ts +22 -217
  275. package/dist/types/verify.d.ts +4 -0
  276. package/dist/types/zk/commit.d.ts +28 -0
  277. package/dist/types/zk/guard.d.ts +46 -0
  278. package/dist/types/zk/index.d.ts +19 -0
  279. package/dist/types/zk/preimage.d.ts +10 -0
  280. package/dist/types/zk/prover.d.ts +34 -0
  281. package/dist/types/zk/registry.d.ts +34 -0
  282. package/dist/types/zk/types.d.ts +33 -0
  283. package/dist/types/zk/witness.d.ts +40 -0
  284. package/package.json +47 -15
  285. package/dist/cjs/ottochain/normalize.js +0 -186
  286. package/dist/esm/ottochain/normalize.js +0 -179
  287. package/dist/types/ottochain/normalize.d.ts +0 -79
@@ -0,0 +1,337 @@
1
+ /**
2
+ * Regression-prevention lint for OttoChain std-app fiber definitions.
3
+ *
4
+ * Purpose
5
+ * -------
6
+ * Catch — at *authoring* time — the classes of SDK↔chain drift that the chain
7
+ * accepts structurally but then silently mis-parses, drops, or evaluates to a
8
+ * dead (always-true / always-false / always-throw) guard. Without this lint
9
+ * those mistakes ship as live, exploitable holes or as silent data corruption
10
+ * because nothing in `defineFiberApp` / `toProtoDefinition` rejects them.
11
+ *
12
+ * The drift classes (cross-referenced to docs/reviews/fiber-app-alignment-audit-2026-06.md):
13
+ *
14
+ * A1 — `$timestamp` (and any non-injected `$`-key). `ReservedKeys` injects ONLY
15
+ * `$ordinal`, `$lastSnapshotHash`, `$epochProgress`. `{"var":"$timestamp"}`
16
+ * resolves to null → `0` in numeric contexts → deadline guards die silently.
17
+ *
18
+ * A2 — Nonexistent JLVM opcodes used as operator tags: `size`, `getKey`,
19
+ * `setKey`, `deleteKey`. metakit decodes an unknown single-key object as a
20
+ * LITERAL Map, not an operator, so the guard is always-true/false and the
21
+ * effect writes a junk key. (Canonical good set: `KNOWN_OPERATORS`.)
22
+ *
23
+ * A3 — SDK directives the chain's `toProtoDefinition` silently drops:
24
+ * transition-level `emits` / `spawns`, and object-form `dependencies`
25
+ * entries `{machine,instanceRef,requiredState}` (chain parses `Set[UUID]`).
26
+ * Authors believe these gate/emit; on-chain they evaporate.
27
+ *
28
+ * per-context — `{"var":"witness.*"}` in a fiber TRANSITION. `witness` is only
29
+ * injected in ASSET-guard contexts; a transition gets `event` (the proof
30
+ * rides under `event.witness.*`). Reading `witness.*` resolves to null →
31
+ * fail-closed (zk gate un-passable) or silently bypassed.
32
+ *
33
+ * leading-dot — `{"var":".foo"}` resolves to null on chain.
34
+ *
35
+ * This is a STANDALONE validator. It is deliberately NOT wired into
36
+ * `defineFiberApp` / `toProtoDefinition`: doing so would break the build until
37
+ * every app is remediated. Run it via `scripts/lint-apps.mjs`.
38
+ */
39
+ import { KNOWN_OPERATORS as KNOWN_OPERATORS_TYPED } from '@constellation-network/metagraph-sdk-jlvm';
40
+ /**
41
+ * Canonical set of valid JLVM operator tags, imported from metakit (the same
42
+ * `KNOWN_OPERATORS` the on-chain evaluator is built from). Widened to
43
+ * `ReadonlySet<string>` so we can membership-test arbitrary object keys.
44
+ */
45
+ const KNOWN_OPERATORS = KNOWN_OPERATORS_TYPED;
46
+ /** Stable rule codes — referenced by tests and by report tooling. */
47
+ export const LINT_CODES = {
48
+ UNKNOWN_RESERVED_VAR: 'unknown-reserved-var', // rule 1 (A1)
49
+ UNKNOWN_OPERATOR: 'unknown-operator', // rule 2 (A2)
50
+ WITNESS_IN_TRANSITION: 'witness-in-transition', // rule 3
51
+ DROPPED_DIRECTIVE: 'dropped-directive', // rule 4 (A3)
52
+ LEADING_DOT_VAR: 'leading-dot-var', // rule 5
53
+ };
54
+ // =============================================================================
55
+ // Rule data
56
+ // =============================================================================
57
+ /**
58
+ * The ONLY `$`-prefixed keys the engine injects (`ReservedKeys`). Anything else
59
+ * (`$timestamp`, `$now`, ...) resolves to null on chain.
60
+ */
61
+ export const INJECTED_RESERVED_VARS = new Set(['$ordinal', '$lastSnapshotHash', '$epochProgress']);
62
+ /**
63
+ * Tags that LOOK like opcodes and appear in real definitions but are NOT valid
64
+ * JLVM operators — metakit mis-decodes each as a literal Map. These are always
65
+ * flagged as errors regardless of position (they are not plausible data fields).
66
+ * Mapped to the correct replacement opcode for the message.
67
+ */
68
+ export const KNOWN_BAD_OPERATORS = new Map([
69
+ ['size', 'length (collections) or count; for a Map use length[keys[...]]'],
70
+ ['getKey', 'get (value) / has (membership)'],
71
+ ['setKey', 'merge (or restructure the field as an array appended with cat/merge)'],
72
+ ['deleteKey', 'filter the collection (model the field as an array of records)'],
73
+ ]);
74
+ /**
75
+ * Structural keys that JSON-Logic uses as single-key objects but are NOT
76
+ * operator tags. Used to avoid false-positive "unknown operator" warnings.
77
+ * `var` is the data-access form; the rest are common rule scaffolding seen in
78
+ * std apps that are handled by the evaluator or are intentional literals.
79
+ */
80
+ const NON_OPERATOR_SINGLE_KEYS = new Set([
81
+ 'var', // data access — handled explicitly
82
+ ]);
83
+ /**
84
+ * Recursively lint a single JSON-Logic expression (a guard or an effect, or any
85
+ * sub-node). Walks every nested operand. Pushes a {@link LintViolation} for each
86
+ * rule-1 / rule-2 / rule-3 / rule-5 hit found at or below `node`.
87
+ *
88
+ * `inDataLiteral` tracks whether `node` sits in a position the evaluator treats
89
+ * as DATA rather than as an expression to evaluate (e.g. the 2nd operand of
90
+ * `merge`/`cat`, or a value nested inside such an operand). In a data-literal
91
+ * position an unknown single-key object is a legitimate field map, so the
92
+ * *generic* unknown-operator warning is suppressed there — but `$`-var keys,
93
+ * leading-dot vars, witness reads, and the known-BAD opcode tags are still
94
+ * flagged (those are bugs wherever they appear).
95
+ */
96
+ export function lintGuardExpression(node, ctx, path, inDataLiteral = false) {
97
+ const out = [];
98
+ // Arrays: walk each element, preserving the data-literal flag.
99
+ if (Array.isArray(node)) {
100
+ node.forEach((child, i) => {
101
+ out.push(...lintGuardExpression(child, ctx, `${path}[${i}]`, inDataLiteral));
102
+ });
103
+ return out;
104
+ }
105
+ // Primitives carry no structure to lint.
106
+ if (node === null || typeof node !== 'object') {
107
+ return out;
108
+ }
109
+ const obj = node;
110
+ const keys = Object.keys(obj);
111
+ // ---- `var` access: rules 1, 3, 5 -------------------------------------
112
+ if (keys.length === 1 && keys[0] === 'var') {
113
+ const ref = obj.var;
114
+ // The reference may be a string, or `[path, default]`, or (rarely) a
115
+ // nested expression; only string refs carry the patterns we check.
116
+ const refStr = typeof ref === 'string' ? ref : Array.isArray(ref) && typeof ref[0] === 'string' ? ref[0] : undefined;
117
+ if (refStr !== undefined) {
118
+ // rule 1 — unknown reserved `$`-key
119
+ if (refStr.startsWith('$') && !INJECTED_RESERVED_VARS.has(refStr)) {
120
+ out.push({
121
+ app: ctx.app,
122
+ transition: ctx.transition,
123
+ severity: 'error',
124
+ code: LINT_CODES.UNKNOWN_RESERVED_VAR,
125
+ message: `{"var":"${refStr}"} references a reserved key the engine never injects. ` +
126
+ `Only ${[...INJECTED_RESERVED_VARS].join(', ')} are injected; ` +
127
+ `"${refStr}" resolves to null (→ 0 in numeric contexts, defeating deadline guards). ` +
128
+ `Use $ordinal and model time fields as ordinal deltas/bounds.`,
129
+ path: `${path}.var`,
130
+ });
131
+ }
132
+ // rule 5 — leading-dot relative path
133
+ if (refStr.startsWith('.')) {
134
+ out.push({
135
+ app: ctx.app,
136
+ transition: ctx.transition,
137
+ severity: 'error',
138
+ code: LINT_CODES.LEADING_DOT_VAR,
139
+ message: `{"var":"${refStr}"} uses a leading-dot relative path, which resolves to null on chain. ` +
140
+ `Use the bare element field name ("${refStr.slice(1)}") inside map/filter/some scopes.`,
141
+ path: `${path}.var`,
142
+ });
143
+ }
144
+ // rule 3 — `witness.*` read inside a fiber transition
145
+ if (ctx.flagWitness && (refStr === 'witness' || refStr.startsWith('witness.'))) {
146
+ out.push({
147
+ app: ctx.app,
148
+ transition: ctx.transition,
149
+ severity: 'error',
150
+ code: LINT_CODES.WITNESS_IN_TRANSITION,
151
+ message: `{"var":"${refStr}"} reads the 'witness' context, which is injected ONLY in asset-guard ` +
152
+ `contexts — never in a fiber transition (the engine injects 'event'). It resolves to null ` +
153
+ `→ the zk gate is un-passable (fail-closed). Read 'event.${refStr}' instead, or move the ` +
154
+ `semi-private guard onto the asset mintPolicy/burnPolicy.`,
155
+ path: `${path}.var`,
156
+ });
157
+ }
158
+ }
159
+ // The `var` default operand (ref[1]) may itself be an expression; walk it.
160
+ if (Array.isArray(ref) && ref.length > 1) {
161
+ out.push(...lintGuardExpression(ref[1], ctx, `${path}.var[1]`, inDataLiteral));
162
+ }
163
+ return out;
164
+ }
165
+ // ---- single-key object: candidate operator (rule 2) ------------------
166
+ if (keys.length === 1) {
167
+ const tag = keys[0];
168
+ const operand = obj[tag];
169
+ // rule 2a — known-BAD opcode tag. Always an error (these are never plausible
170
+ // data field names, and metakit silently mis-decodes them as a literal Map).
171
+ if (KNOWN_BAD_OPERATORS.has(tag)) {
172
+ out.push({
173
+ app: ctx.app,
174
+ transition: ctx.transition,
175
+ severity: 'error',
176
+ code: LINT_CODES.UNKNOWN_OPERATOR,
177
+ message: `'${tag}' is not a JLVM operator — metakit decodes {"${tag}":...} as a literal Map, ` +
178
+ `so this guard is always-true/false and any effect writes a junk key. ` +
179
+ `Use ${KNOWN_BAD_OPERATORS.get(tag)}.`,
180
+ path: `${path}.${tag}`,
181
+ });
182
+ // Still descend into the operand (it may carry further violations).
183
+ out.push(...lintGuardExpression(operand, ctx, `${path}.${tag}`, inDataLiteral));
184
+ return out;
185
+ }
186
+ // Recognized operator: descend. `merge`/`cat` 2nd+ operands are DATA literals
187
+ // (field maps to write), so mark them to suppress the generic op warning below.
188
+ if (KNOWN_OPERATORS.has(tag)) {
189
+ const literalDataOp = tag === 'merge' || tag === 'cat';
190
+ if (literalDataOp && Array.isArray(operand)) {
191
+ operand.forEach((child, i) => {
192
+ // First operand is the base collection (an expression); the rest are
193
+ // the literal field maps merged/appended onto it.
194
+ out.push(...lintGuardExpression(child, ctx, `${path}.${tag}[${i}]`, i > 0 ? true : inDataLiteral));
195
+ });
196
+ }
197
+ else {
198
+ out.push(...lintGuardExpression(operand, ctx, `${path}.${tag}`, inDataLiteral));
199
+ }
200
+ return out;
201
+ }
202
+ // rule 2b (heuristic warn) — an unknown single-key tag that LOOKS like an
203
+ // operator (no spaces, not `var`, not a known data scaffold) and is NOT in a
204
+ // data-literal position. Keep this a WARNING to hold false positives low:
205
+ // in a data-literal position a one-key object is a legitimate `{field: ...}`.
206
+ if (!inDataLiteral &&
207
+ !NON_OPERATOR_SINGLE_KEYS.has(tag) &&
208
+ !tag.includes(' ') &&
209
+ !tag.startsWith('$') &&
210
+ !tag.startsWith('_') // reserved effect directives (_emit/_spawn/...) are handled structurally
211
+ ) {
212
+ out.push({
213
+ app: ctx.app,
214
+ transition: ctx.transition,
215
+ severity: 'warn',
216
+ code: LINT_CODES.UNKNOWN_OPERATOR,
217
+ message: `single-key object {"${tag}":...} in an expression position has a key that is not a known ` +
218
+ `JLVM operator. If '${tag}' was meant as an operator it will mis-decode as a literal Map; ` +
219
+ `if it is a literal field, ignore. Known operators: see KNOWN_OPERATORS.`,
220
+ path: `${path}.${tag}`,
221
+ });
222
+ }
223
+ out.push(...lintGuardExpression(operand, ctx, `${path}.${tag}`, inDataLiteral));
224
+ return out;
225
+ }
226
+ // ---- multi-key object: a DATA literal (a field map). Descend into values,
227
+ // marking children as data-literal so their inner one-key objects are not
228
+ // mistaken for operators. (`$`/leading-dot/witness `var`s inside are still
229
+ // flagged by the recursion.)
230
+ for (const k of keys) {
231
+ out.push(...lintGuardExpression(obj[k], ctx, `${path}.${k}`, true));
232
+ }
233
+ return out;
234
+ }
235
+ // =============================================================================
236
+ // Transition-level structural rules (rule 4 / A3)
237
+ // =============================================================================
238
+ function isObjectDependency(dep) {
239
+ return typeof dep === 'object' && dep !== null && !Array.isArray(dep);
240
+ }
241
+ function lintTransitionStructure(t, app, transition, path) {
242
+ const out = [];
243
+ const raw = t;
244
+ // rule 4a — transition-level `emits`
245
+ if (raw.emits !== undefined) {
246
+ out.push({
247
+ app,
248
+ transition,
249
+ severity: 'error',
250
+ code: LINT_CODES.DROPPED_DIRECTIVE,
251
+ message: `transition-level 'emits' is silently dropped by toProtoDefinition (the chain Transition has ` +
252
+ `no such field). Emit from INSIDE the effect result under the reserved key '_emit' ` +
253
+ `({name,data,destination}), or '_triggers' for a state-changing cross-machine call.`,
254
+ path: `${path}.emits`,
255
+ });
256
+ }
257
+ // rule 4b — transition-level `spawns`
258
+ if (raw.spawns !== undefined) {
259
+ out.push({
260
+ app,
261
+ transition,
262
+ severity: 'error',
263
+ code: LINT_CODES.DROPPED_DIRECTIVE,
264
+ message: `transition-level 'spawns' is silently dropped by toProtoDefinition — the child machine is ` +
265
+ `never created. Spawn from INSIDE the effect result under the reserved key '_spawn' ` +
266
+ `(with a full inline definition, a distinct childId, and initialData).`,
267
+ path: `${path}.spawns`,
268
+ });
269
+ }
270
+ // rule 4c — object-form `dependencies` entries (chain accepts Set[UUID] only)
271
+ if (Array.isArray(t.dependencies)) {
272
+ t.dependencies.forEach((dep, i) => {
273
+ if (isObjectDependency(dep)) {
274
+ out.push({
275
+ app,
276
+ transition,
277
+ severity: 'error',
278
+ code: LINT_CODES.DROPPED_DIRECTIVE,
279
+ message: `dependencies[${i}] is an object ({machine,instanceRef,requiredState}); the chain parses ` +
280
+ `'dependencies' as Set[UUID] and drops the object, so 'requiredState' gating NEVER happens. ` +
281
+ `Pass a bare fiber-UUID string and assert machines.<uuid>.currentStateId=="<state>" in the guard.`,
282
+ path: `${path}.dependencies[${i}]`,
283
+ });
284
+ }
285
+ });
286
+ }
287
+ return out;
288
+ }
289
+ // =============================================================================
290
+ // Top-level entry point
291
+ // =============================================================================
292
+ function appLabel(def) {
293
+ const m = def.metadata;
294
+ if (!m)
295
+ return undefined;
296
+ if (m.app && m.type)
297
+ return `${m.app}/${m.type}`;
298
+ return m.name ?? m.app ?? m.type ?? undefined;
299
+ }
300
+ /**
301
+ * Lint a complete fiber app definition. Walks every transition guard + effect
302
+ * (rules 1/2/3/5) and every transition's structural directives (rule 4), plus a
303
+ * light pass over `states` to catch stray reserved-key / operator drift embedded
304
+ * in state metadata. Returns ALL violations (errors and warnings); callers
305
+ * decide the exit policy (e.g. fail on any `error`).
306
+ */
307
+ export function lintFiberApp(def) {
308
+ const out = [];
309
+ const app = appLabel(def);
310
+ // Transitions.
311
+ (def.transitions ?? []).forEach((t, i) => {
312
+ const transition = t.eventName ?? `${t.from}->${t.to}`;
313
+ const tPath = `transitions[${i}]`;
314
+ const ctx = { app, transition, flagWitness: true };
315
+ if (t.guard !== undefined) {
316
+ out.push(...lintGuardExpression(t.guard, ctx, `${tPath}.guard`));
317
+ }
318
+ if (t.effect !== undefined) {
319
+ out.push(...lintGuardExpression(t.effect, ctx, `${tPath}.effect`));
320
+ }
321
+ out.push(...lintTransitionStructure(t, app, transition, tPath));
322
+ });
323
+ // States — usually inert metadata, but guard against reserved-key drift here
324
+ // too (states never run in an asset context, so witness reads would be bugs).
325
+ Object.entries(def.states ?? {}).forEach(([name, state]) => {
326
+ const ctx = { app, transition: `state:${name}`, flagWitness: true };
327
+ out.push(...lintGuardExpression(state, ctx, `states.${name}`));
328
+ });
329
+ return out;
330
+ }
331
+ /**
332
+ * Convenience: lint many apps at once, returning a flat list. Each app's
333
+ * violations already carry its `app` label.
334
+ */
335
+ export function lintFiberApps(defs) {
336
+ return defs.flatMap((d) => lintFiberApp(d));
337
+ }
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Canonical authorization-guard builders — bind "who may act" to the CHAIN-VERIFIED signer of the op,
3
+ * never to an attacker-supplied payload field.
4
+ *
5
+ * The forgeable anti-pattern (security audit class F1): `{"===":[{"var":"event.agent"}, {"var":"state.party"}]}`.
6
+ * The fiber engine injects `event` as the RAW transition payload (ContextProvider: `EVENT -> payload`),
7
+ * so `event.agent` is whatever the submitter wrote — a different party's address forges authorization.
8
+ * The chain ALSO injects `proofs` = `[{address, id, signature}]` where `address = id.toAddress` (the
9
+ * VERIFIED signer addresses). Authorization must bind to THAT. For ASSET ops (mint/burn/morphism) the
10
+ * context has no `event`/`proofs`; it injects `signers` (an array of verified address strings) instead.
11
+ *
12
+ * Use these so the safe pattern is consistent, greppable, and impossible to get subtly wrong.
13
+ */
14
+ /**
15
+ * FIBER-transition authorization: the pinned `partyVar` (a `state` path holding an address, e.g.
16
+ * `"state.borrower"`) MUST be among the op's verified signers (`proofs[].address`). The replay-safe
17
+ * replacement for `{"===":[{"var":"event.agent"}, {"var":partyVar}]}`.
18
+ */
19
+ export const signerIsParty = (partyVar) => ({
20
+ in: [{ var: partyVar }, { map: [{ var: 'proofs' }, { var: 'address' }] }],
21
+ });
22
+ /** FIBER-transition authorization where ANY of the pinned parties signed (e.g. borrower OR lender). */
23
+ export const signerIsAnyParty = (partyVars) => ({
24
+ or: partyVars.map(signerIsParty),
25
+ });
26
+ /**
27
+ * FIBER-transition authorization where the actor must be a MEMBER of a pinned set: at least one
28
+ * VERIFIED signer is in the `setVar` array (e.g. `"state.signers"`, `"state.members"`, `"state.oracles"`).
29
+ * The replay-safe replacement for `{"in":[{"var":"event.agent"}, {"var":setVar}]}`.
30
+ */
31
+ export const signerInSet = (setVar) => ({
32
+ some: [{ map: [{ var: 'proofs' }, { var: 'address' }] }, { in: [{ var: '' }, { var: setVar }] }],
33
+ });
34
+ /**
35
+ * FIBER-transition ANTI-SELF guard: NO verified signer is the pinned party (e.g. a proposal author may
36
+ * not vote on their own proposal). The replay-safe replacement for `{"!==":[{"var":"event.agent"}, {"var":partyVar}]}`.
37
+ */
38
+ export const signerIsNotParty = (partyVar) => ({
39
+ '!': [signerIsParty(partyVar)],
40
+ });
41
+ /**
42
+ * FIBER-transition authorization / dedup over a state MAP keyed by address: at least one VERIFIED signer
43
+ * is a key in `mapVar` (e.g. `"state.members"`, `"state.balances"`). The replay-safe replacement for the
44
+ * forgeable `{"getKey":[{"var":mapVar}, {"var":"event.agent"}]}` membership/presence check (note metakit
45
+ * has no `getKey`; use `get`/`has`). For a "no signer has acted yet" dedup, negate: `{"!":[signerHasEntry(...)]}`.
46
+ * For a per-actor VALUE threshold (e.g. balance >= N), build the `some` directly:
47
+ * `{"some":[{"map":[proofs,address]},{">=":[{"get":[mapVar,{"var":""}]}, N]}]}`.
48
+ */
49
+ export const signerHasEntry = (mapVar) => ({
50
+ some: [{ map: [{ var: 'proofs' }, { var: 'address' }] }, { has: [{ var: mapVar }, { var: '' }] }],
51
+ });
52
+ /**
53
+ * ASSET-op authorization (mintPolicy / burnPolicy / MorphismSpec.guard): the address at `addressVar`
54
+ * (e.g. `"holder.Wallet.address"`) MUST be among the op's verified `signers`. The asset context has no
55
+ * `event` or `proofs` — it injects `signers` directly.
56
+ */
57
+ export const assetSignerIs = (addressVar) => ({
58
+ in: [{ var: addressVar }, { var: 'signers' }],
59
+ });
60
+ /**
61
+ * EFFECT-KEY BINDING: prove the event's claimed actor (`actorVar`, default `event.agent`) is a
62
+ * CHAIN-VERIFIED signer, so that field is SAFE to use as a dynamic map key / array element in the
63
+ * EFFECT (`{"set":[map,{"var":"event.agent"},v]}`, `{"cat":[arr,[{"var":"event.agent"}]]}`).
64
+ *
65
+ * This is the coupling clause for the map-write remediation: a guard may authorize via membership /
66
+ * balance / reputation, but if the EFFECT writes under `event.agent` WITHOUT this clause, an attacker
67
+ * sets `event.agent` to a victim's address and writes under the victim's key (security class S1).
68
+ * Pair it with the authorization check expressed on the SAME `actorVar` — together they prove the actor
69
+ * both signed and is authorized, and the effect can only write under that one verified key.
70
+ * Structurally identical to {@link signerIsParty}; named for intent + greppability at write sites.
71
+ */
72
+ export const actorIsSigner = (actorVar = 'event.agent') => signerIsParty(actorVar);
73
+ /**
74
+ * EFFECT-KEY-BINDING membership: the event's claimed actor (`actorVar`, default `event.agent`) is BOTH
75
+ * a CHAIN-VERIFIED signer AND a member of the pinned set `setVar` (e.g. `"state.signers"`,
76
+ * `"state.members"`). Use when the EFFECT writes a map/array keyed by that actor and authorization is
77
+ * set-membership. It proves the EXACT actor whose key is written both signed and is authorized — closing
78
+ * the S1 forge AND the subtler gap {@link signerInSet} leaves open: `signerInSet` only proves SOME
79
+ * verified signer is a member, so an op co-signed by an authorized signer could still write a DIFFERENT
80
+ * verified-but-unauthorized address as the key (padding a signature/vote tally). `actorInSet` pins both
81
+ * to the same `actorVar`.
82
+ */
83
+ export const actorInSet = (setVar, actorVar = 'event.agent') => ({
84
+ and: [actorIsSigner(actorVar), { in: [{ var: actorVar }, { var: setVar }] }],
85
+ });
86
+ /**
87
+ * EFFECT-KEY-BINDING map membership: the claimed actor (`actorVar`, default `event.agent`) is BOTH a
88
+ * CHAIN-VERIFIED signer AND a key in the pinned state MAP `mapVar` (e.g. `"state.members"`,
89
+ * `"state.balances"`). The map analog of {@link actorInSet} — use it when the membership set is a dict
90
+ * keyed by address, where `in` does not apply and `has` checks key presence. Use when the EFFECT writes
91
+ * `mapVar` (or a per-actor tally) keyed by that actor: it proves the EXACT written key both signed and
92
+ * is an authorized member, closing the vote/signature-stuffing gap that bare {@link signerHasEntry}
93
+ * leaves open (which only proves SOME verified signer is a key, not that the written key is).
94
+ */
95
+ export const actorHasEntry = (mapVar, actorVar = 'event.agent') => ({
96
+ and: [actorIsSigner(actorVar), { has: [{ var: mapVar }, { var: actorVar }] }],
97
+ });
98
+ /**
99
+ * IDENTITY-REGISTRY reputation gate: at least one VERIFIED signer has a reputation in the registry map
100
+ * at `repMapVar` (a declared-dependency read, e.g. `"machines.<registryDep>.state.reputations"`, shaped
101
+ * `{ <address>: int }`) that is `>=` the bar read from `thresholdVar` (a state path, e.g.
102
+ * `"state.voteThreshold"`). A missing entry reads as null → 0 numerically, so unregistered signers
103
+ * fail-closed for any positive bar. The replay-safe replacement for the forgeable
104
+ * `{">=":[{"var":"event.agentReputation"}, bar]}` (security class S1). See
105
+ * docs/design/app-hardening-identity-integration.md §3–§4.1.
106
+ */
107
+ export const signerHasReputation = (repMapVar, thresholdVar) => ({
108
+ some: [
109
+ { map: [{ var: 'proofs' }, { var: 'address' }] },
110
+ {
111
+ '>=': [{ get: [{ var: repMapVar }, { var: '' }] }, { var: thresholdVar }],
112
+ },
113
+ ],
114
+ });
115
+ /**
116
+ * IDENTITY-REGISTRY role gate: at least one VERIFIED signer holds an active role attestation, i.e. is a
117
+ * key in the registry's flat per-role map at `roleMapVar` (a declared-dependency read, e.g.
118
+ * `"machines.<registryDep>.state.arbiters"` / `".slashers"` / `".issuers"` / `".boardMembers"`, each
119
+ * shaped `{ <address>: true }`). The replay-safe replacement for bare `event.judicialRuling` / role
120
+ * escapes and `{"==":[1,1]}` missing-auth (security class S2). Roles are FLAT per-role maps (not a
121
+ * nested `roles[addr][ROLE]`) because metakit `get`/`has` on a null inner map ERROR rather than
122
+ * returning null; a flat map keeps the read total + fail-closed. See app-hardening §4.2.
123
+ */
124
+ export const signerHasRole = (roleMapVar) => signerHasEntry(roleMapVar);
125
+ /**
126
+ * DYNAMIC identity-registry reputation gate — for when the registry instance is bound at RUNTIME via the
127
+ * fiber-engine `_addDependency` directive (#24) rather than hardcoded into the `machines.<uuid>` path.
128
+ * `registryIdVar` is a state/event path holding the registry fiber id (e.g. `"state.registryId"`); the
129
+ * read addresses `machines[<that id>].state.reputations[<signer>]` with dynamic `get`s. Guarded by a
130
+ * presence check so an UNBOUND registry yields a clean `false` (fail-closed) instead of an evaluation
131
+ * error. REQUIRES the registry dependency to have been added in a PRIOR transition (`_addDependency`),
132
+ * because the `machines` context is built before the effect runs (two-phase: bind, then read).
133
+ */
134
+ export const signerHasReputationVia = (registryIdVar, thresholdVar) => ({
135
+ if: [
136
+ { has: [{ var: 'machines' }, { var: registryIdVar }] },
137
+ {
138
+ some: [
139
+ { map: [{ var: 'proofs' }, { var: 'address' }] },
140
+ {
141
+ '>=': [
142
+ {
143
+ get: [
144
+ {
145
+ get: [
146
+ {
147
+ get: [{ get: [{ var: 'machines' }, { var: registryIdVar }] }, 'state'],
148
+ },
149
+ 'reputations',
150
+ ],
151
+ },
152
+ { var: '' },
153
+ ],
154
+ },
155
+ { var: thresholdVar },
156
+ ],
157
+ },
158
+ ],
159
+ },
160
+ false,
161
+ ],
162
+ });
163
+ /**
164
+ * DYNAMIC identity-registry role gate — the runtime-bound (`_addDependency`, #24) counterpart of
165
+ * {@link signerHasRole}. `registryIdVar` is a state/event path holding the registry fiber id;
166
+ * `roleField` is the registry's flat per-role state map name (`"arbiters"` / `"slashers"` / `"issuers"` /
167
+ * `"boardMembers"` — see REGISTRY_ROLE_MAP). Reads `machines[<id>].state.<roleField>[<signer>]`, guarded
168
+ * by a presence check so an UNBOUND registry yields a clean `false`. Same two-phase requirement as
169
+ * {@link signerHasReputationVia}.
170
+ */
171
+ export const signerHasRoleVia = (registryIdVar, roleField) => ({
172
+ if: [
173
+ { has: [{ var: 'machines' }, { var: registryIdVar }] },
174
+ {
175
+ some: [
176
+ { map: [{ var: 'proofs' }, { var: 'address' }] },
177
+ {
178
+ has: [
179
+ {
180
+ get: [
181
+ {
182
+ get: [{ get: [{ var: 'machines' }, { var: registryIdVar }] }, 'state'],
183
+ },
184
+ roleField,
185
+ ],
186
+ },
187
+ { var: '' },
188
+ ],
189
+ },
190
+ ],
191
+ },
192
+ false,
193
+ ],
194
+ });
195
+ /**
196
+ * CROSS-FIBER STATE GATE — assert a RUNTIME-bound dependency fiber is in a required lifecycle state. The
197
+ * replay-safe replacement for the dropped object-form dependency
198
+ * `{machine, instanceRef, requiredState}` (which the chain silently drops — `requiredState` gating never
199
+ * happens). `refVar` is a state/event path holding the dependency's fiber id; reads
200
+ * `machines[<refVar>].currentStateId == requiredState`, guarded by a presence check so an UNBOUND
201
+ * dependency yields a clean `false` (fail-closed). TWO-PHASE (#24): the dependency fiber must have been
202
+ * bound by an `_addDependency` in a PRIOR transition (the `machines` context is built before the effect),
203
+ * so a single gated transition is split into a bind step then this assert step. See
204
+ * docs/design/app-hardening-identity-integration.md §5–§6.
205
+ */
206
+ /**
207
+ * EFFECT-KEY-BINDING dedup over an ARRAY of records: no existing element of the array at `arrayVar`
208
+ * (e.g. `"state.submissions"`, shaped `[ { addr, value }, … ]`) has its `field` (default `"addr"`)
209
+ * equal to the actor at `actorVar` (default `"event.agent"`). The array analog of the map-keyed
210
+ * `{"!":[has(...)]}` dedup — used where the collection is an append-only array of `{addr,…}` records
211
+ * (there is no `(array,string)` key check, but `none` over the array with a per-element `===` is total).
212
+ *
213
+ * The bare-element callback context inside `none` sees each record map directly, so `{get:[{var:""},field]}`
214
+ * is a valid `(map,string)` read. Pair with an `actorIsSigner`/`actorHasEntry` clause on the same
215
+ * `actorVar` (the dedup proves "not yet present", not "is the verified signer").
216
+ */
217
+ export const actorNotInArray = (arrayVar, field = 'addr', actorVar = 'event.agent') => ({
218
+ none: [{ var: arrayVar }, { '===': [{ get: [{ var: '' }, field] }, { var: actorVar }] }],
219
+ });
220
+ export const depInState = (refVar, requiredState) => ({
221
+ if: [
222
+ { has: [{ var: 'machines' }, { var: refVar }] },
223
+ {
224
+ '==': [{ get: [{ get: [{ var: 'machines' }, { var: refVar }] }, 'currentStateId'] }, requiredState],
225
+ },
226
+ false,
227
+ ],
228
+ });
@@ -0,0 +1,74 @@
1
+ /**
2
+ * DataUpdate Signing Wrappers — dropNulls applied internally
3
+ *
4
+ * Metakit (>= 1.8) computes content hashes by recursively dropping
5
+ * null-valued OBJECT fields (array nulls are preserved) before RFC 8785
6
+ * canonicalization (`JsonBinaryCodec.dropNulls`). This makes
7
+ * `Option = None` indistinguishable from an absent field, so schema
8
+ * evolution never changes prior hashes.
9
+ *
10
+ * The upstream `@constellation-network/metagraph-sdk` signs over the
11
+ * payload as-is, so a payload containing explicit nulls would be signed
12
+ * over different bytes than the metagraph hashes — and the node rejects
13
+ * the signature (HTTP 400).
14
+ *
15
+ * These wrappers shadow the upstream dataUpdate signing surface and apply
16
+ * `dropNulls` internally, unconditionally. Standard-mode signing
17
+ * (currency transactions etc.) is passed through unchanged.
18
+ */
19
+ import { signDataUpdate as baseSignDataUpdate, createSignedObject as baseCreateSignedObject, addSignature as baseAddSignature, batchSign as baseBatchSign, } from '@constellation-network/metagraph-sdk';
20
+ import { dropNulls } from './ottochain/drop-nulls.js';
21
+ function resolveMode(options = {}) {
22
+ if (options.mode)
23
+ return options.mode;
24
+ if (options.isDataUpdate)
25
+ return 'dataUpdate';
26
+ return 'standard';
27
+ }
28
+ /**
29
+ * Sign data as a DataUpdate, hashing over null-dropped canonical bytes.
30
+ *
31
+ * Applies `dropNulls` internally so that explicit-null and absent optional
32
+ * fields produce identical signatures, matching metakit's content-hash rule
33
+ * (drop null object fields, preserve array nulls, then RFC 8785).
34
+ */
35
+ export function signDataUpdate(data, privateKey) {
36
+ return baseSignDataUpdate(dropNulls(data), privateKey);
37
+ }
38
+ /**
39
+ * Create a signed object. In `dataUpdate` mode the signature is computed
40
+ * over the null-dropped canonical bytes (the returned `value` is the
41
+ * caller's original payload, untouched).
42
+ */
43
+ export function createSignedObject(value, privateKey, options = {}) {
44
+ if (resolveMode(options) === 'dataUpdate') {
45
+ const signed = baseCreateSignedObject(dropNulls(value), privateKey, options);
46
+ return { ...signed, value };
47
+ }
48
+ return baseCreateSignedObject(value, privateKey, options);
49
+ }
50
+ /**
51
+ * Add an additional signature to an existing signed object. In
52
+ * `dataUpdate` mode the new signature is computed over the null-dropped
53
+ * canonical bytes of `signed.value`.
54
+ */
55
+ export function addSignature(signed, privateKey, options) {
56
+ const mode = options ? resolveMode(options) : (signed.mode ?? 'standard');
57
+ if (mode === 'dataUpdate') {
58
+ const result = baseAddSignature({ ...signed, value: dropNulls(signed.value) }, privateKey, options);
59
+ return { ...result, value: signed.value };
60
+ }
61
+ return baseAddSignature(signed, privateKey, options);
62
+ }
63
+ /**
64
+ * Create a signed object with multiple signatures at once. In
65
+ * `dataUpdate` mode all signatures are computed over the null-dropped
66
+ * canonical bytes (the returned `value` is the caller's original payload).
67
+ */
68
+ export function batchSign(value, privateKeys, options = {}) {
69
+ if (resolveMode(options) === 'dataUpdate') {
70
+ const signed = baseBatchSign(dropNulls(value), privateKeys, options);
71
+ return { ...signed, value };
72
+ }
73
+ return baseBatchSign(value, privateKeys, options);
74
+ }