@crediolabs/policy-synth 0.1.3 → 0.1.5

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 (194) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +193 -5
  3. package/dist/adapters/interpreter/adapter.d.ts +38 -0
  4. package/dist/adapters/interpreter/adapter.js +522 -0
  5. package/dist/adapters/interpreter/index.d.ts +1 -0
  6. package/dist/adapters/interpreter/index.js +2 -0
  7. package/dist/adapters/oz/adapter.js +2 -0
  8. package/dist/codegen/compile-gate.d.ts +33 -0
  9. package/dist/codegen/compile-gate.js +119 -0
  10. package/dist/codegen/index.d.ts +2 -0
  11. package/dist/codegen/index.js +8 -0
  12. package/dist/codegen/template.d.ts +18 -0
  13. package/dist/codegen/template.js +131 -0
  14. package/dist/errors.d.ts +1 -1
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +2 -0
  17. package/dist/ir/types.d.ts +13 -2
  18. package/dist/predicate/encode.d.ts +10 -0
  19. package/dist/predicate/encode.js +249 -0
  20. package/dist/predicate/index.d.ts +1 -0
  21. package/dist/predicate/index.js +2 -0
  22. package/dist/record/decode.d.ts +6 -3
  23. package/dist/record/decode.js +13 -3
  24. package/dist/review-card/builder.d.ts +14 -0
  25. package/dist/review-card/builder.js +261 -0
  26. package/dist/review-card/conflict.d.ts +40 -0
  27. package/dist/review-card/conflict.js +111 -0
  28. package/dist/review-card/cross-check.d.ts +11 -0
  29. package/dist/review-card/cross-check.js +148 -0
  30. package/dist/review-card/index.d.ts +3 -0
  31. package/dist/review-card/index.js +4 -0
  32. package/dist/run/index.d.ts +31 -0
  33. package/dist/run/index.js +164 -0
  34. package/dist/run/schemas.d.ts +2003 -0
  35. package/dist/run/schemas.js +215 -0
  36. package/dist/synth/compose-from-recording.d.ts +35 -7
  37. package/dist/synth/compose-from-recording.js +225 -34
  38. package/dist/synth/deny-cases.d.ts +12 -0
  39. package/dist/synth/deny-cases.js +377 -0
  40. package/dist/synth/evaluate.d.ts +39 -0
  41. package/dist/synth/evaluate.js +425 -0
  42. package/dist/synth/harness.d.ts +16 -0
  43. package/dist/synth/harness.js +26 -0
  44. package/dist/synth/index.d.ts +4 -0
  45. package/dist/synth/index.js +4 -0
  46. package/dist/synth/minimize.d.ts +4 -0
  47. package/dist/synth/minimize.js +38 -0
  48. package/dist/synth/predicate-literals.d.ts +5 -0
  49. package/dist/synth/predicate-literals.js +25 -0
  50. package/dist/synth/synthesize-from-recording.d.ts +37 -5
  51. package/dist/synth/synthesize-from-recording.js +550 -18
  52. package/dist/types.d.ts +40 -1
  53. package/dist/types.js +17 -0
  54. package/dist/verify/envelope.d.ts +15 -0
  55. package/dist/verify/envelope.js +22 -0
  56. package/dist/verify/index.d.ts +3 -0
  57. package/dist/verify/index.js +3 -0
  58. package/dist/verify/simulate.d.ts +31 -0
  59. package/dist/verify/simulate.js +258 -0
  60. package/dist/verify/verify.d.ts +21 -0
  61. package/dist/verify/verify.js +189 -0
  62. package/dist-cjs/adapters/interpreter/adapter.d.ts +38 -0
  63. package/dist-cjs/adapters/interpreter/adapter.js +527 -0
  64. package/dist-cjs/adapters/interpreter/index.d.ts +1 -0
  65. package/dist-cjs/adapters/interpreter/index.js +8 -0
  66. package/dist-cjs/adapters/oz/adapter.d.ts +20 -0
  67. package/dist-cjs/adapters/oz/adapter.js +289 -0
  68. package/dist-cjs/adapters/oz/index.d.ts +1 -0
  69. package/dist-cjs/adapters/oz/index.js +8 -0
  70. package/dist-cjs/codegen/compile-gate.d.ts +33 -0
  71. package/dist-cjs/codegen/compile-gate.js +123 -0
  72. package/dist-cjs/codegen/index.d.ts +2 -0
  73. package/dist-cjs/codegen/index.js +14 -0
  74. package/dist-cjs/codegen/template.d.ts +18 -0
  75. package/dist-cjs/codegen/template.js +134 -0
  76. package/dist-cjs/errors.d.ts +37 -0
  77. package/dist-cjs/errors.js +3 -0
  78. package/dist-cjs/index.d.ts +11 -0
  79. package/dist-cjs/index.js +27 -0
  80. package/dist-cjs/ir/index.d.ts +1 -0
  81. package/dist-cjs/ir/index.js +3 -0
  82. package/dist-cjs/ir/types.d.ts +108 -0
  83. package/dist-cjs/ir/types.js +12 -0
  84. package/dist-cjs/mandate/index.d.ts +2 -0
  85. package/dist-cjs/mandate/index.js +6 -0
  86. package/dist-cjs/mandate/to-ir.d.ts +3 -0
  87. package/dist-cjs/mandate/to-ir.js +63 -0
  88. package/dist-cjs/mandate/types.d.ts +20 -0
  89. package/dist-cjs/mandate/types.js +9 -0
  90. package/dist-cjs/package.json +3 -0
  91. package/dist-cjs/predicate/encode.d.ts +10 -0
  92. package/dist-cjs/predicate/encode.js +252 -0
  93. package/dist-cjs/predicate/index.d.ts +1 -0
  94. package/dist-cjs/predicate/index.js +6 -0
  95. package/dist-cjs/record/decode.d.ts +79 -0
  96. package/dist-cjs/record/decode.js +398 -0
  97. package/dist-cjs/record/freshness.d.ts +17 -0
  98. package/dist-cjs/record/freshness.js +55 -0
  99. package/dist-cjs/record/index.d.ts +21 -0
  100. package/dist-cjs/record/index.js +166 -0
  101. package/dist-cjs/record/movements.d.ts +20 -0
  102. package/dist-cjs/record/movements.js +192 -0
  103. package/dist-cjs/record/rpc.d.ts +22 -0
  104. package/dist-cjs/record/rpc.js +74 -0
  105. package/dist-cjs/record/validate.d.ts +22 -0
  106. package/dist-cjs/record/validate.js +63 -0
  107. package/dist-cjs/registry/identify.d.ts +11 -0
  108. package/dist-cjs/registry/identify.js +87 -0
  109. package/dist-cjs/registry/index.d.ts +3 -0
  110. package/dist-cjs/registry/index.js +15 -0
  111. package/dist-cjs/registry/known-addresses.d.ts +16 -0
  112. package/dist-cjs/registry/known-addresses.js +53 -0
  113. package/dist-cjs/registry/protocols.d.ts +38 -0
  114. package/dist-cjs/registry/protocols.js +153 -0
  115. package/dist-cjs/review-card/builder.d.ts +14 -0
  116. package/dist-cjs/review-card/builder.js +264 -0
  117. package/dist-cjs/review-card/conflict.d.ts +40 -0
  118. package/dist-cjs/review-card/conflict.js +114 -0
  119. package/dist-cjs/review-card/cross-check.d.ts +11 -0
  120. package/dist-cjs/review-card/cross-check.js +151 -0
  121. package/dist-cjs/review-card/index.d.ts +3 -0
  122. package/dist-cjs/review-card/index.js +10 -0
  123. package/dist-cjs/run/index.d.ts +31 -0
  124. package/dist-cjs/run/index.js +178 -0
  125. package/dist-cjs/run/schemas.d.ts +2003 -0
  126. package/dist-cjs/run/schemas.js +218 -0
  127. package/dist-cjs/seams/index.d.ts +1 -0
  128. package/dist-cjs/seams/index.js +3 -0
  129. package/dist-cjs/seams/types.d.ts +66 -0
  130. package/dist-cjs/seams/types.js +12 -0
  131. package/dist-cjs/synth/compose-from-recording.d.ts +64 -0
  132. package/dist-cjs/synth/compose-from-recording.js +356 -0
  133. package/dist-cjs/synth/deny-cases.d.ts +12 -0
  134. package/dist-cjs/synth/deny-cases.js +380 -0
  135. package/dist-cjs/synth/evaluate.d.ts +39 -0
  136. package/dist-cjs/synth/evaluate.js +428 -0
  137. package/dist-cjs/synth/harness.d.ts +16 -0
  138. package/dist-cjs/synth/harness.js +29 -0
  139. package/dist-cjs/synth/index.d.ts +9 -0
  140. package/dist-cjs/synth/index.js +23 -0
  141. package/dist-cjs/synth/lower.d.ts +23 -0
  142. package/dist-cjs/synth/lower.js +119 -0
  143. package/dist-cjs/synth/minimize.d.ts +4 -0
  144. package/dist-cjs/synth/minimize.js +41 -0
  145. package/dist-cjs/synth/predicate-literals.d.ts +5 -0
  146. package/dist-cjs/synth/predicate-literals.js +28 -0
  147. package/dist-cjs/synth/scope.d.ts +26 -0
  148. package/dist-cjs/synth/scope.js +81 -0
  149. package/dist-cjs/synth/synthesize-from-mandate.d.ts +5 -0
  150. package/dist-cjs/synth/synthesize-from-mandate.js +37 -0
  151. package/dist-cjs/synth/synthesize-from-recording.d.ts +49 -0
  152. package/dist-cjs/synth/synthesize-from-recording.js +711 -0
  153. package/dist-cjs/types.d.ts +288 -0
  154. package/dist-cjs/types.js +55 -0
  155. package/dist-cjs/verify/envelope.d.ts +15 -0
  156. package/dist-cjs/verify/envelope.js +23 -0
  157. package/dist-cjs/verify/index.d.ts +3 -0
  158. package/dist-cjs/verify/index.js +8 -0
  159. package/dist-cjs/verify/simulate.d.ts +31 -0
  160. package/dist-cjs/verify/simulate.js +261 -0
  161. package/dist-cjs/verify/verify.d.ts +21 -0
  162. package/dist-cjs/verify/verify.js +192 -0
  163. package/package.json +43 -3
  164. package/src/adapters/interpreter/adapter.ts +642 -0
  165. package/src/adapters/interpreter/index.ts +8 -0
  166. package/src/adapters/oz/adapter.ts +2 -0
  167. package/src/codegen/compile-gate.ts +162 -0
  168. package/src/codegen/index.ts +17 -0
  169. package/src/codegen/template.ts +148 -0
  170. package/src/errors.ts +2 -0
  171. package/src/index.ts +2 -0
  172. package/src/ir/types.ts +9 -2
  173. package/src/predicate/encode.ts +307 -0
  174. package/src/predicate/index.ts +3 -0
  175. package/src/record/decode.ts +21 -10
  176. package/src/review-card/builder.ts +303 -0
  177. package/src/review-card/conflict.ts +143 -0
  178. package/src/review-card/cross-check.ts +158 -0
  179. package/src/review-card/index.ts +12 -0
  180. package/src/run/index.ts +213 -0
  181. package/src/run/schemas.ts +242 -0
  182. package/src/synth/compose-from-recording.ts +277 -43
  183. package/src/synth/deny-cases.ts +466 -0
  184. package/src/synth/evaluate.ts +493 -0
  185. package/src/synth/harness.ts +40 -0
  186. package/src/synth/index.ts +12 -0
  187. package/src/synth/minimize.ts +44 -0
  188. package/src/synth/predicate-literals.ts +27 -0
  189. package/src/synth/synthesize-from-recording.ts +655 -19
  190. package/src/types.ts +38 -2
  191. package/src/verify/envelope.ts +28 -0
  192. package/src/verify/index.ts +5 -0
  193. package/src/verify/simulate.ts +311 -0
  194. package/src/verify/verify.ts +243 -0
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // src/errors.ts
3
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,11 @@
1
+ export * from './adapters/oz/index.ts';
2
+ export * from './errors.ts';
3
+ export * from './ir/index.ts';
4
+ export * from './mandate/index.ts';
5
+ export * from './predicate/index.ts';
6
+ export * from './record/index.ts';
7
+ export * from './registry/index.ts';
8
+ export * from './review-card/index.ts';
9
+ export * from './seams/index.ts';
10
+ export * from './synth/index.ts';
11
+ export * from './types.ts';
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./adapters/oz/index.js"), exports);
18
+ __exportStar(require("./errors.js"), exports);
19
+ __exportStar(require("./ir/index.js"), exports);
20
+ __exportStar(require("./mandate/index.js"), exports);
21
+ __exportStar(require("./predicate/index.js"), exports);
22
+ __exportStar(require("./record/index.js"), exports);
23
+ __exportStar(require("./registry/index.js"), exports);
24
+ __exportStar(require("./review-card/index.js"), exports);
25
+ __exportStar(require("./seams/index.js"), exports);
26
+ __exportStar(require("./synth/index.js"), exports);
27
+ __exportStar(require("./types.js"), exports);
@@ -0,0 +1 @@
1
+ export type { IRCompare, IRCompOp, IRCondition, IRLogic, IRPolicyRule, IRScalarType, IRSelector, IRVecMode, PolicyIR, } from './types.ts';
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // src/ir/index.ts - re-export the PolicyIR ("Policy Tree") types.
3
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,108 @@
1
+ /** Comparison operator. Mirrors NEAR-V2 `CompOp`. */
2
+ export type IRCompOp = 'eq' | 'lt' | 'lte' | 'gt' | 'gte';
3
+ /** Boolean combinator. Mirrors NEAR-V2 `LogicOp`. */
4
+ export type IRLogic = 'and' | 'or';
5
+ /** Vector match mode for a repeated arg. Mirrors NEAR-V2 `VecMode`. */
6
+ export type IRVecMode = 'all' | 'any';
7
+ /** Scalar value type. Mirrors NEAR-V2 `ScValType`, superset for OZ. */
8
+ export type IRScalarType = 'address' | 'i128' | 'u128' | 'u32' | 'u64' | 'i64' | 'symbol' | 'bytes' | 'bool';
9
+ /** WHERE in the authorized call a value is read. NEAR-V2 selectors first, OZ
10
+ * extensions after (an adapter that cannot express an extension flags it). */
11
+ export type IRSelector = {
12
+ kind: 'arg';
13
+ argIndex: number;
14
+ fieldIndex?: number;
15
+ vecMode?: IRVecMode;
16
+ scalarType: IRScalarType;
17
+ } | {
18
+ kind: 'calldata';
19
+ offset: number;
20
+ length: number;
21
+ } | {
22
+ kind: 'value';
23
+ } | {
24
+ kind: 'amount';
25
+ token: string;
26
+ } | {
27
+ kind: 'window_spent';
28
+ token: string;
29
+ windowSeconds: number;
30
+ } | {
31
+ kind: 'invocation_count';
32
+ windowSeconds: number;
33
+ } | {
34
+ kind: 'now';
35
+ } | {
36
+ kind: 'valid_until';
37
+ } | {
38
+ kind: 'oracle_price';
39
+ asset: string;
40
+ };
41
+ /** A single comparison leaf. `value` is a decimal/hex string (i128-safe;
42
+ * never a JS number). */
43
+ export interface IRCompare {
44
+ selector: IRSelector;
45
+ operator: IRCompOp;
46
+ value: string;
47
+ }
48
+ /** Condition tree. NEAR-V2 guard/constraint are flat And/Or; the IR allows
49
+ * nesting + `not` + `in` + `eq_seq` so the same IR can later lower to the OZ
50
+ * predicate DSL. Week-1 adapters only lower the flat supported subset and flag
51
+ * the rest. */
52
+ export type IRCondition = {
53
+ op: 'and' | 'or';
54
+ children: IRCondition[];
55
+ } | {
56
+ op: 'not';
57
+ child: IRCondition;
58
+ } | {
59
+ op: 'compare';
60
+ compare: IRCompare;
61
+ } | {
62
+ op: 'in';
63
+ selector: IRSelector;
64
+ values: string[];
65
+ }
66
+ /** Exact ordered sequence equality. The IR-level selector value MUST EQUAL
67
+ * `values` as an ORDERED sequence (e.g. a swap hop `path`). `in` is pure
68
+ * set membership and cannot express order; `eq_seq` is the construct that
69
+ * does. Adapters that cannot express an exact ordered vector (OZ built-ins)
70
+ * flag this as `uncovered` rather than silently dropping it. */
71
+ | {
72
+ op: 'eq_seq';
73
+ selector: IRSelector;
74
+ values: string[];
75
+ };
76
+ export interface IRPolicyRule {
77
+ /** NEAR-V2 roles whitelist (empty = any; owner exempt). */
78
+ roles: string[];
79
+ /** NEAR-V2 scope filter; each field optional (absent = wildcard). */
80
+ scope: {
81
+ chainId?: number;
82
+ contract?: string;
83
+ method?: string;
84
+ };
85
+ /** NEAR-V2 guard (applicability; skip the rule when it fails). */
86
+ guard?: IRCondition;
87
+ /** NEAR-V2 constraint (AND; reject the transaction when any fails). */
88
+ constraints: IRCondition[];
89
+ /** M-of-N approval (OZ threshold primitives). */
90
+ approval?: {
91
+ kind: 'threshold';
92
+ threshold: number;
93
+ weights?: Record<string, number>;
94
+ };
95
+ /** OZ context-rule expiry. */
96
+ expiry?: {
97
+ validUntilLedger?: number;
98
+ validUntilUnixSeconds?: number;
99
+ };
100
+ }
101
+ export interface PolicyIR {
102
+ /** NEAR-V2 PolicyChain. */
103
+ chain: 'stellar' | 'evm';
104
+ /** NEAR-V2 DefaultBehavior. OZ context rules are deny-by-default, so
105
+ * `deny_all` is the OZ default fallback when no rule matches. */
106
+ defaultBehavior: 'allow_all' | 'deny_all';
107
+ rules: IRPolicyRule[];
108
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ // src/ir/types.ts - the PolicyIR ("Policy Tree").
3
+ //
4
+ // `PolicyIR` is the custody-agnostic "Policy Tree" hub (the diagram's Policy
5
+ // Tree) that every CustodyAdapter compiles FROM. It generalizes the NEAR-V2
6
+ // policy schema (roles / scope filter / guard / constraint / comparison leaves /
7
+ // default behaviour) into one chain-neutral shape and extends it with the
8
+ // OZ-only selectors (spend window, oracle price, invocation count, time) so a
9
+ // single IR can serve every backend. NEAR-V2 has no stateful spend window,
10
+ // oracle, time/expiry, or invocation-count; those are marked as OZ extensions
11
+ // below and are only lowered by adapters that declare support for them.
12
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ export { mandateToPolicyIR } from './to-ir.ts';
2
+ export type { MandateSpec } from './types.ts';
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ // src/mandate/index.ts - re-export the Mandate source.
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.mandateToPolicyIR = void 0;
5
+ var to_ir_ts_1 = require("./to-ir.js");
6
+ Object.defineProperty(exports, "mandateToPolicyIR", { enumerable: true, get: function () { return to_ir_ts_1.mandateToPolicyIR; } });
@@ -0,0 +1,3 @@
1
+ import type { PolicyIR } from '../ir/types.ts';
2
+ import type { MandateSpec } from './types.ts';
3
+ export declare function mandateToPolicyIR(spec: MandateSpec): PolicyIR;
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ // src/mandate/to-ir.ts - deterministic lowering of a MandateSpec to a PolicyIR.
3
+ //
4
+ // `mandateToPolicyIR` is pure and total: the same spec always lowers to a
5
+ // byte-identical PolicyIR. No decoding, no inference, no clock. Each mandate
6
+ // field maps to exactly one IR construct:
7
+ // contract/method -> rule.scope
8
+ // spendingLimit -> a `window_spent(token,window) <= limit` compare
9
+ // approvalThreshold-> rule.approval.threshold
10
+ // recipients -> an `in` condition on the recipient arg (flagged as not
11
+ // covered by the OZ adapter; that is expected)
12
+ // expiry -> rule.expiry
13
+ // The top-level default is `deny_all` (OZ context rules are deny-by-default).
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.mandateToPolicyIR = mandateToPolicyIR;
16
+ /** Arg index the recipient allowlist constrains. Pinned to the SEP-41
17
+ * `transfer(from, to, amount)` convention where `to` is arg 1. This condition
18
+ * is flagged as not covered by the OZ adapter (no built-in primitive expresses
19
+ * an arg allowlist), so the index only needs to be deterministic in week-1. */
20
+ const RECIPIENT_ARG_INDEX = 1;
21
+ function mandateToPolicyIR(spec) {
22
+ const scope = { contract: spec.contract };
23
+ if (spec.method !== undefined)
24
+ scope.method = spec.method;
25
+ const constraints = [];
26
+ if (spec.spendingLimit) {
27
+ constraints.push({
28
+ op: 'compare',
29
+ compare: {
30
+ selector: {
31
+ kind: 'window_spent',
32
+ token: spec.spendingLimit.token,
33
+ windowSeconds: spec.spendingLimit.windowSeconds,
34
+ },
35
+ operator: 'lte',
36
+ value: spec.spendingLimit.limit,
37
+ },
38
+ });
39
+ }
40
+ if (spec.recipients && spec.recipients.length > 0) {
41
+ const selector = {
42
+ kind: 'arg',
43
+ argIndex: RECIPIENT_ARG_INDEX,
44
+ scalarType: 'address',
45
+ };
46
+ constraints.push({ op: 'in', selector, values: [...spec.recipients] });
47
+ }
48
+ const rule = { roles: [], scope, constraints };
49
+ if (spec.approvalThreshold !== undefined) {
50
+ rule.approval = { kind: 'threshold', threshold: spec.approvalThreshold };
51
+ }
52
+ if (spec.expiry) {
53
+ const expiry = {};
54
+ if (spec.expiry.validUntilLedger !== undefined) {
55
+ expiry.validUntilLedger = spec.expiry.validUntilLedger;
56
+ }
57
+ if (spec.expiry.validUntilUnixSeconds !== undefined) {
58
+ expiry.validUntilUnixSeconds = spec.expiry.validUntilUnixSeconds;
59
+ }
60
+ rule.expiry = expiry;
61
+ }
62
+ return { chain: spec.chain, defaultBehavior: 'deny_all', rules: [rule] };
63
+ }
@@ -0,0 +1,20 @@
1
+ export interface MandateSpec {
2
+ chain: 'stellar';
3
+ contract: string;
4
+ method?: string;
5
+ /** -> OZ `spending_limit` primitive. `limit` is an i128 decimal string. */
6
+ spendingLimit?: {
7
+ token: string;
8
+ limit: string;
9
+ windowSeconds: number;
10
+ };
11
+ /** -> OZ `simple_threshold` / `weighted_threshold` primitive. */
12
+ approvalThreshold?: number;
13
+ /** Recipient allowlist -> an `in` arg condition (not covered by an OZ built-in). */
14
+ recipients?: string[];
15
+ /** -> OZ context-rule expiry. */
16
+ expiry?: {
17
+ validUntilLedger?: number;
18
+ validUntilUnixSeconds?: number;
19
+ };
20
+ }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ // src/mandate/types.ts - the deterministic Mandate source.
3
+ //
4
+ // A MandateSpec is a declarative policy statement: "this account may call this
5
+ // contract, spending at most L per window, requiring M approvals, to these
6
+ // recipients, until this expiry". It carries NO transaction to decode, NO
7
+ // parseConfidence, and NO inference - it lowers deterministically to a
8
+ // PolicyIR. It is the clean end-to-end demo path, co-equal with the recorder.
9
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
@@ -0,0 +1,10 @@
1
+ import { type PredicateNode } from '../types.ts';
2
+ export interface EncodedPredicate {
3
+ /** base64 of the canonical ScVal XDR of the predicate root. */
4
+ encodedPredicate: string;
5
+ /** sha256 hex digest of the raw XDR bytes (post-canonicalisation). */
6
+ predicateHash: string;
7
+ }
8
+ /** Encode a `PredicateNode` to the canonical ScVal wire format and hash it.
9
+ * Pure function: same input -> byte-identical output every run. */
10
+ export declare function encodePredicate(node: PredicateNode): EncodedPredicate;
@@ -0,0 +1,252 @@
1
+ "use strict";
2
+ // src/predicate/encode.ts - canonical predicate encoder.
3
+ //
4
+ // Pure function. Maps a `PredicateNode` AST to the canonical ScVal wire format
5
+ // described in `packages/policy-interpreter/INTERPRETER_INSTALL_PARAMS.md`:
6
+ // - every node is a `ScVal::Vec` whose head element is the tag `ScVal::Symbol`
7
+ // - children of `and` / `or` are sorted ascending by their canonical XDR bytes
8
+ // - `in` haystacks are ALWAYS sorted by canonical XDR bytes (pure set
9
+ // membership); an EXACT ordered sequence is expressed as
10
+ // `eq(selector, literal_vec)` where the `literal_vec` element order is
11
+ // preserved verbatim (the order IS the semantic)
12
+ // - `literal_vec` encodes to a bare `ScVal::Vec` of its element encodings;
13
+ // order is preserved, NOT sorted
14
+ // - i128 uses `Int128Parts{hi: Int64 (signed), lo: Uint64 (unsigned)}`,
15
+ // value = hi*2^64 + lo (NOT signed-magnitude)
16
+ // - no `ScMap` anywhere in the predicate
17
+ // - `encodedPredicate = base64(root.toXDR())`,
18
+ // `predicateHash = sha256(raw XDR bytes)` as hex
19
+ //
20
+ // Caps from `PREDICATE_CAPS` are enforced BEFORE returning; a cap breach throws
21
+ // a `ToolError` with the matching error code and `severity: 'error'`.
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.encodePredicate = encodePredicate;
24
+ const node_crypto_1 = require("node:crypto");
25
+ const stellar_sdk_1 = require("@stellar/stellar-sdk");
26
+ const types_ts_1 = require("../types.js");
27
+ const INT64_MAX = (1n << 63n) - 1n;
28
+ const INT64_MIN = -(1n << 63n);
29
+ const UINT64_MAX = (1n << 64n) - 1n;
30
+ /** Encode a `PredicateNode` to the canonical ScVal wire format and hash it.
31
+ * Pure function: same input -> byte-identical output every run. */
32
+ function encodePredicate(node) {
33
+ // --- pass 1: structural cap checks (no encoding needed) ---
34
+ const stats = computeStats(node);
35
+ if (stats.depth > types_ts_1.PREDICATE_CAPS.MAX_DEPTH) {
36
+ throw capError('PREDICATE_TOO_DEEP', `predicate depth ${stats.depth} exceeds MAX_DEPTH ${types_ts_1.PREDICATE_CAPS.MAX_DEPTH}`);
37
+ }
38
+ if (stats.leaves > types_ts_1.PREDICATE_CAPS.MAX_LEAVES) {
39
+ throw capError('TOO_MANY_LEAVES', `predicate leaf count ${stats.leaves} exceeds MAX_LEAVES ${types_ts_1.PREDICATE_CAPS.MAX_LEAVES}`);
40
+ }
41
+ for (const c of stats.inCounts) {
42
+ if (c.count > types_ts_1.PREDICATE_CAPS.MAX_IN_OPERAND_COUNT) {
43
+ throw capError('IN_OPERAND_LIMIT', `\`in\` operand count ${c.count} exceeds MAX_IN_OPERAND_COUNT ${types_ts_1.PREDICATE_CAPS.MAX_IN_OPERAND_COUNT}`);
44
+ }
45
+ }
46
+ // Two-round confirmation costs 2 oracle reads per UNIQUE asset referenced.
47
+ const oracleReads = stats.oracleAssets.size * 2;
48
+ if (oracleReads > types_ts_1.PREDICATE_CAPS.MAX_ORACLE_READS) {
49
+ throw capError('PREDICATE_ORACLE_OVER_LIMIT', `oracle reads ${oracleReads} exceed MAX_ORACLE_READS ${types_ts_1.PREDICATE_CAPS.MAX_ORACLE_READS}`);
50
+ }
51
+ // --- pass 2: build + canonicalise the ScVal ---
52
+ const root = encodeNode(node);
53
+ const rawBytes = root.toXDR();
54
+ if (rawBytes.length > types_ts_1.PREDICATE_CAPS.MAX_PREDICATE_BYTES) {
55
+ throw capError('PREDICATE_TOO_LARGE', `predicate bytes ${rawBytes.length} exceed MAX_PREDICATE_BYTES ${types_ts_1.PREDICATE_CAPS.MAX_PREDICATE_BYTES}`);
56
+ }
57
+ const encodedPredicate = rawBytes.toString('base64');
58
+ const predicateHash = (0, node_crypto_1.createHash)('sha256').update(rawBytes).digest('hex');
59
+ return { encodedPredicate, predicateHash };
60
+ }
61
+ function computeStats(node) {
62
+ const inCounts = [];
63
+ const oracleAssets = new Set();
64
+ const { depth, leaves } = walk(node, inCounts, oracleAssets);
65
+ return { depth, leaves, inCounts, oracleAssets };
66
+ }
67
+ function walk(node, inCounts, oracleAssets) {
68
+ switch (node.op) {
69
+ case 'and':
70
+ case 'or': {
71
+ if (node.children.length === 0)
72
+ return { depth: 1, leaves: 0 };
73
+ let maxChildDepth = 0;
74
+ let totalLeaves = 0;
75
+ for (const c of node.children) {
76
+ const child = walk(c, inCounts, oracleAssets);
77
+ if (child.depth > maxChildDepth)
78
+ maxChildDepth = child.depth;
79
+ totalLeaves += child.leaves;
80
+ }
81
+ return { depth: maxChildDepth + 1, leaves: totalLeaves };
82
+ }
83
+ case 'not': {
84
+ const child = walk(node.child, inCounts, oracleAssets);
85
+ return { depth: child.depth + 1, leaves: child.leaves };
86
+ }
87
+ case 'eq':
88
+ case 'lt':
89
+ case 'lte':
90
+ case 'gt':
91
+ case 'gte': {
92
+ collectOracle(node.left, oracleAssets);
93
+ collectOracle(node.right, oracleAssets);
94
+ return { depth: 1, leaves: leafCount(node.left) + leafCount(node.right) };
95
+ }
96
+ case 'in': {
97
+ inCounts.push({ count: node.haystack.length });
98
+ collectOracle(node.needle, oracleAssets);
99
+ let haystackLeaves = 0;
100
+ for (const h of node.haystack) {
101
+ collectOracle(h, oracleAssets);
102
+ haystackLeaves += leafCount(h);
103
+ }
104
+ return {
105
+ depth: 1,
106
+ leaves: leafCount(node.needle) + haystackLeaves,
107
+ };
108
+ }
109
+ }
110
+ }
111
+ /** Leaf-count contribution of one PredicateLeaf: 1 for any flat leaf, and the
112
+ * sum of element leaf counts for `literal_vec` so MAX_LEAVES caps see nested
113
+ * vector elements. */
114
+ function leafCount(leaf) {
115
+ if (leaf.kind === 'literal_vec') {
116
+ let total = 1; // the literal_vec itself counts as one leaf node
117
+ for (const el of leaf.elements)
118
+ total += leafCount(el);
119
+ return total;
120
+ }
121
+ return 1;
122
+ }
123
+ function collectOracle(leaf, oracleAssets) {
124
+ // literal_vec is the only nested leaf; recurse so an oracle_price buried in a
125
+ // vector literal (which the lowering would forbid, but the cap-walker must
126
+ // still see) is counted toward the oracle-read budget.
127
+ if (leaf.kind === 'oracle_price') {
128
+ oracleAssets.add(leaf.asset);
129
+ }
130
+ else if (leaf.kind === 'literal_vec') {
131
+ for (const el of leaf.elements)
132
+ collectOracle(el, oracleAssets);
133
+ }
134
+ }
135
+ function encodeNode(node) {
136
+ switch (node.op) {
137
+ case 'and':
138
+ case 'or': {
139
+ const encoded = node.children.map(encodeNode);
140
+ // sort children by their canonical XDR bytes ascending.
141
+ const sorted = sortByCanonicalBytes(encoded);
142
+ return stellar_sdk_1.xdr.ScVal.scvVec([symbol(node.op), stellar_sdk_1.xdr.ScVal.scvVec(sorted)]);
143
+ }
144
+ case 'not': {
145
+ return stellar_sdk_1.xdr.ScVal.scvVec([symbol('not'), encodeNode(node.child)]);
146
+ }
147
+ case 'eq':
148
+ case 'lt':
149
+ case 'lte':
150
+ case 'gt':
151
+ case 'gte': {
152
+ return stellar_sdk_1.xdr.ScVal.scvVec([symbol(node.op), encodeLeaf(node.left), encodeLeaf(node.right)]);
153
+ }
154
+ case 'in': {
155
+ const needle = encodeLeaf(node.needle);
156
+ // `in` is PURE set membership: the haystack is ALWAYS sorted by canonical
157
+ // XDR bytes ascending. An exact ordered sequence (e.g. a swap hop path)
158
+ // is expressed as `eq(selector, literal_vec)` where the vec's element
159
+ // order is preserved verbatim (handled in `encodeLeaf(literal_vec)`).
160
+ const haystack = sortByCanonicalBytes(node.haystack.map(encodeLeaf));
161
+ return stellar_sdk_1.xdr.ScVal.scvVec([symbol('in'), needle, stellar_sdk_1.xdr.ScVal.scvVec(haystack)]);
162
+ }
163
+ }
164
+ }
165
+ function encodeLeaf(leaf) {
166
+ switch (leaf.kind) {
167
+ case 'call_contract':
168
+ return stellar_sdk_1.xdr.ScVal.scvVec([symbol('call_contract')]);
169
+ case 'call_fn':
170
+ return stellar_sdk_1.xdr.ScVal.scvVec([symbol('call_fn')]);
171
+ case 'call_arg':
172
+ return stellar_sdk_1.xdr.ScVal.scvVec([symbol('call_arg'), stellar_sdk_1.xdr.ScVal.scvU32(leaf.index)]);
173
+ case 'amount':
174
+ return stellar_sdk_1.xdr.ScVal.scvVec([symbol('amount'), scvAddressFromStrkey(leaf.token)]);
175
+ case 'window_spent':
176
+ return stellar_sdk_1.xdr.ScVal.scvVec([
177
+ symbol('window_spent'),
178
+ scvAddressFromStrkey(leaf.token),
179
+ scvU64FromValue(leaf.windowSeconds),
180
+ ]);
181
+ case 'now':
182
+ return stellar_sdk_1.xdr.ScVal.scvVec([symbol('now')]);
183
+ case 'valid_until':
184
+ return stellar_sdk_1.xdr.ScVal.scvVec([symbol('valid_until')]);
185
+ case 'invocation_count_in_window':
186
+ return stellar_sdk_1.xdr.ScVal.scvVec([symbol('invocation_count'), scvU64FromValue(leaf.windowSecs)]);
187
+ case 'oracle_price':
188
+ return stellar_sdk_1.xdr.ScVal.scvVec([symbol('oracle_price'), scvAddressFromStrkey(leaf.asset)]);
189
+ case 'literal_address':
190
+ return scvAddressFromStrkey(leaf.value);
191
+ case 'literal_i128':
192
+ return scvI128FromDecimal(leaf.value);
193
+ case 'literal_symbol':
194
+ return stellar_sdk_1.xdr.ScVal.scvSymbol(leaf.value);
195
+ case 'literal_u32':
196
+ return stellar_sdk_1.xdr.ScVal.scvU32(leaf.value);
197
+ case 'literal_u64':
198
+ return scvU64FromValue(leaf.value);
199
+ case 'literal_bytes':
200
+ return stellar_sdk_1.xdr.ScVal.scvBytes(Buffer.from(leaf.value, 'hex'));
201
+ case 'literal_vec':
202
+ // Bare ScVal::Vec of element encodings - order is preserved verbatim
203
+ // because the order IS the semantic (exact ordered sequence equality).
204
+ return stellar_sdk_1.xdr.ScVal.scvVec(leaf.elements.map(encodeLeaf));
205
+ }
206
+ }
207
+ function symbol(s) {
208
+ return stellar_sdk_1.xdr.ScVal.scvSymbol(s);
209
+ }
210
+ function sortByCanonicalBytes(values) {
211
+ const pairs = values.map((v) => ({ v, bytes: v.toXDR() }));
212
+ pairs.sort((a, b) => Buffer.compare(a.bytes, b.bytes));
213
+ return pairs.map((p) => p.v);
214
+ }
215
+ function scvAddressFromStrkey(strkey) {
216
+ return stellar_sdk_1.xdr.ScVal.scvAddress(stellar_sdk_1.Address.fromString(strkey).toScAddress());
217
+ }
218
+ function scvU64FromValue(value) {
219
+ // The Uint64 (UnsignedHyper) constructor accepts string | bigint | number;
220
+ // string is safest for values > 2^53.
221
+ return stellar_sdk_1.xdr.ScVal.scvU64(new stellar_sdk_1.xdr.Uint64(String(value)));
222
+ }
223
+ /** Build `ScVal::I128(Int128Parts{hi, lo})` from a signed decimal string.
224
+ * `Int128Parts` encodes the value as `(hi << 64) + lo` with `hi` a SIGNED
225
+ * 64-bit int and `lo` an UNSIGNED 64-bit int (this is NOT signed-magnitude).
226
+ * The inverse split is `hi = v >> 64n` (arithmetic right shift) and
227
+ * `lo = v & 0xFFFF...`. The SDK's `Int64` constructor takes a signed
228
+ * bigint/string/number. */
229
+ function scvI128FromDecimal(decimal) {
230
+ const v = BigInt(decimal);
231
+ const hi = v >> 64n;
232
+ const lo = v & UINT64_MAX;
233
+ // Guard against accidental over-range. Int128Parts.hi must be a valid Int64
234
+ // (-2^63 .. 2^63-1); values outside this range are invalid i128 entirely.
235
+ if (hi < INT64_MIN || hi > INT64_MAX) {
236
+ throw capError('MALFORMED_PREDICATE', `literal_i128 value ${decimal} is outside the Int128 range`);
237
+ }
238
+ if (lo < 0n || lo > UINT64_MAX) {
239
+ throw capError('MALFORMED_PREDICATE', `literal_i128 value ${decimal} has an invalid Int128Parts.lo`);
240
+ }
241
+ return stellar_sdk_1.xdr.ScVal.scvI128(new stellar_sdk_1.xdr.Int128Parts({
242
+ hi: new stellar_sdk_1.xdr.Int64(hi),
243
+ lo: new stellar_sdk_1.xdr.Uint64(lo),
244
+ }));
245
+ }
246
+ function capError(code, message) {
247
+ const err = new Error(message);
248
+ err.code = code;
249
+ err.severity = 'error';
250
+ err.retryable = false;
251
+ throw err;
252
+ }
@@ -0,0 +1 @@
1
+ export { type EncodedPredicate, encodePredicate } from './encode.ts';
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ // src/predicate/index.ts - re-export the canonical predicate encoder.
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.encodePredicate = void 0;
5
+ var encode_ts_1 = require("./encode.js");
6
+ Object.defineProperty(exports, "encodePredicate", { enumerable: true, get: function () { return encode_ts_1.encodePredicate; } });
@@ -0,0 +1,79 @@
1
+ import { xdr } from '@stellar/stellar-sdk';
2
+ import { type ContractInvocation, type Network, type OnChainEvent, type ParseConfidence, type ScVal, type TokenMovement } from '../types.ts';
3
+ /** Result of decoding an envelope XDR plus the surrounding metadata from
4
+ * `getTransaction` (events + auth entries). All fields are plain JSON; the
5
+ * caller decides how to interpret them. */
6
+ export interface SerializableAuthEntry {
7
+ authorizingAddress: string | null;
8
+ contract: string;
9
+ fn: string;
10
+ }
11
+ export interface DecodedTransaction {
12
+ sourceAccount: string;
13
+ /** Authorising signers from the envelope signatures (hex-encoded ed25519
14
+ * pubkey hints expanded to G... strkeys). The recorder cannot recover the
15
+ * strkey from the 4-byte hint alone; we record the hint bytes hex instead
16
+ * when no full strkey is available. */
17
+ signers: string[];
18
+ invocations: ContractInvocation[];
19
+ /** Raw on-chain events (from getTransaction.events). Used by validate.ts to
20
+ * cross-check the parsed TokenMovement[] and ContractInvocation[]. */
21
+ events: OnChainEvent[];
22
+ /** Plain JSON projection of InvokeHostFunctionOp.auth(). */
23
+ authEntries: SerializableAuthEntry[];
24
+ /** Records that include at least one `ContractInvocation` whose decoded args
25
+ * vector or whose auth-tree contained an opaque ScVal. Empty list = full
26
+ * parseConfidence = 1.0 (modulo unknown contracts). */
27
+ opaqueScVals: ParseConfidence['opaqueScVals'];
28
+ /** Contracts referenced in invocations whose (fn, args) did not match any
29
+ * known protocol ABI. Each unrecognised invocation is recorded separately
30
+ * so the freshness gate stays fail-closed regardless of operation order. */
31
+ unknownContracts: ParseConfidence['unknownContracts'];
32
+ knownContracts: string[];
33
+ ledgerSequence: number;
34
+ }
35
+ /** Decode a TransactionEnvelope XDR (base64) into a fully parsed invocation tree. */
36
+ export declare function decodeEnvelopeXdr(envelopeXdrB64: string, events?: OnChainEvent[], _authEntries?: unknown[], ledgerSequence?: number, knownContracts?: ReadonlySet<string>, network?: Network | null): DecodedTransaction;
37
+ /** Same as `decodeEnvelopeXdr` but accepts an already-decoded envelope. Useful
38
+ * for tests that build the envelope via SDK helpers (no XDR round-trip). */
39
+ export declare function decodeEnvelope(envelope: xdr.TransactionEnvelope, events?: OnChainEvent[], _authEntries?: unknown[], ledgerSequence?: number, knownContracts?: ReadonlySet<string>, network?: Network | null): DecodedTransaction;
40
+ /** Decode an ScAddress (either account or contract) to its strkey form. */
41
+ export declare function decodeScAddressToAnyStrkey(scAddr: xdr.ScAddress): string | null;
42
+ /** Decode an ScAddress to a C... contract strkey. */
43
+ export declare function decodeScAddressToC(scAddr: xdr.ScAddress): string;
44
+ /** Map an arbitrary ScVal into the normalised `ScVal` subset defined in
45
+ * types.ts. Anything outside the subset is wrapped as `{type:'other', value}`
46
+ * AND recorded in `opaqueScVals` so the freshness module can reduce
47
+ * parseConfidence. Recursion is bounded by `MAX_SCVAL_DEPTH` so a
48
+ * hand-crafted nested-vec payload cannot RangeError the JS stack; the
49
+ * over-depth branch is collapsed to a single opaque `depth-exceeded` token
50
+ * (the recorder's parseConfidence gate then fails the recording closed). */
51
+ export declare function scValToSubset(val: xdr.ScVal, path: string, opaqueScVals: ParseConfidence['opaqueScVals'], depth?: number): ScVal;
52
+ /** Convert Int128Parts (hi: Int64, lo: Uint64) to a BigInt.
53
+ * hi is a signed 64-bit value; lo is unsigned. We use the SDK-provided
54
+ * `toString()` form which gives the signed decimal representation of the 64-bit
55
+ * Int64 directly. */
56
+ export declare function i128PartsToBigInt(parts: xdr.Int128Parts): bigint;
57
+ /** Convert UInt128Parts (hi: Uint64, lo: Uint64) to an unsigned BigInt. */
58
+ export declare function u128PartsToBigInt(parts: xdr.UInt128Parts): bigint;
59
+ export declare function u64PartsToBigInt(h: xdr.UnsignedHyper | xdr.Hyper): bigint;
60
+ /** Convert a ScVal to a friendly string for use in OnChainEvent.topics
61
+ * (always-string scalar representation; vectors/bytes are stringified as JSON
62
+ * to keep the field string-typed). */
63
+ export declare function scValToTopicString(val: xdr.ScVal): string;
64
+ /** Build the OnChainEvent[] view from raw contract events. Diagnostic-only:
65
+ * captures the topics + data + emitter contract for the downstream
66
+ * validator. */
67
+ export declare function contractEventsToOnChainEvents(contractEvents: xdr.ContractEvent[][]): OnChainEvent[];
68
+ /** Build the OnChainEvent[] view from TransactionEvent[] (which wraps
69
+ * ContractEvent with a stage). Same shape as above. */
70
+ export declare function transactionEventsToOnChainEvents(txEvents: xdr.TransactionEvent[]): OnChainEvent[];
71
+ /** Tiny typed error so callers can `instanceof DecodeError` without leaking
72
+ * SDK internals. */
73
+ export declare class DecodeError extends Error {
74
+ readonly name = "DecodeError";
75
+ }
76
+ /** Helper used by validate.ts to map a TokenMovement back to a search key
77
+ * without re-parsing (the validate module must cross-check against the raw
78
+ * events, NOT against the parse). */
79
+ export declare function tokenMovementKey(m: TokenMovement): string;