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