@crediolabs/policy-synth 0.3.1 → 0.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 (100) hide show
  1. package/dist/adapters/interpreter/adapter.d.ts +2 -2
  2. package/dist/adapters/interpreter/adapter.js +11 -3
  3. package/dist/errors.d.ts +6 -1
  4. package/dist/install/authority-overlap.d.ts +101 -0
  5. package/dist/install/authority-overlap.js +239 -0
  6. package/dist/install/build-add-context-rule.d.ts +1 -1
  7. package/dist/install/index.d.ts +1 -0
  8. package/dist/install/index.js +4 -0
  9. package/dist/install/read-account-rules.d.ts +79 -0
  10. package/dist/install/read-account-rules.js +241 -0
  11. package/dist/predicate/decode.js +22 -1
  12. package/dist/predicate/encode.js +52 -5
  13. package/dist/predicate/from-json.js +14 -1
  14. package/dist/record/index.d.ts +10 -0
  15. package/dist/record/index.js +32 -1
  16. package/dist/record/rpc.d.ts +4 -0
  17. package/dist/record/rpc.js +4 -1
  18. package/dist/registry/identify.d.ts +10 -1
  19. package/dist/registry/identify.js +4 -1
  20. package/dist/registry/on-chain-spec.d.ts +37 -0
  21. package/dist/registry/on-chain-spec.js +152 -0
  22. package/dist/review-card/builder.js +40 -0
  23. package/dist/review-card/cross-check.js +34 -0
  24. package/dist/review-card/render-leaf.d.ts +1 -1
  25. package/dist/review-card/render-leaf.js +7 -0
  26. package/dist/run/index.d.ts +25 -4
  27. package/dist/run/index.js +108 -4
  28. package/dist/run/schemas.d.ts +350 -6
  29. package/dist/run/schemas.js +95 -6
  30. package/dist/simulate/deny-cases.js +11 -0
  31. package/dist/simulate/evaluate.js +86 -5
  32. package/dist/synth/declare.d.ts +43 -0
  33. package/dist/synth/declare.js +127 -0
  34. package/dist/synth/index.d.ts +1 -0
  35. package/dist/synth/index.js +1 -0
  36. package/dist/synth/synthesize-from-recording.js +1 -1
  37. package/dist/types.d.ts +21 -1
  38. package/dist/types.js +1 -1
  39. package/dist-cjs/adapters/interpreter/adapter.d.ts +2 -2
  40. package/dist-cjs/adapters/interpreter/adapter.js +11 -3
  41. package/dist-cjs/errors.d.ts +6 -1
  42. package/dist-cjs/install/authority-overlap.d.ts +101 -0
  43. package/dist-cjs/install/authority-overlap.js +248 -0
  44. package/dist-cjs/install/build-add-context-rule.d.ts +1 -1
  45. package/dist-cjs/install/index.d.ts +1 -0
  46. package/dist-cjs/install/index.js +13 -2
  47. package/dist-cjs/install/read-account-rules.d.ts +79 -0
  48. package/dist-cjs/install/read-account-rules.js +252 -0
  49. package/dist-cjs/predicate/decode.js +22 -1
  50. package/dist-cjs/predicate/encode.js +52 -5
  51. package/dist-cjs/predicate/from-json.js +14 -1
  52. package/dist-cjs/record/index.d.ts +10 -0
  53. package/dist-cjs/record/index.js +31 -0
  54. package/dist-cjs/record/rpc.d.ts +4 -0
  55. package/dist-cjs/record/rpc.js +7 -3
  56. package/dist-cjs/registry/identify.d.ts +10 -1
  57. package/dist-cjs/registry/identify.js +4 -0
  58. package/dist-cjs/registry/on-chain-spec.d.ts +37 -0
  59. package/dist-cjs/registry/on-chain-spec.js +159 -0
  60. package/dist-cjs/review-card/builder.js +40 -0
  61. package/dist-cjs/review-card/cross-check.js +34 -0
  62. package/dist-cjs/review-card/render-leaf.d.ts +1 -1
  63. package/dist-cjs/review-card/render-leaf.js +7 -0
  64. package/dist-cjs/run/index.d.ts +25 -4
  65. package/dist-cjs/run/index.js +108 -2
  66. package/dist-cjs/run/schemas.d.ts +350 -6
  67. package/dist-cjs/run/schemas.js +96 -7
  68. package/dist-cjs/simulate/deny-cases.js +11 -0
  69. package/dist-cjs/simulate/evaluate.js +86 -5
  70. package/dist-cjs/synth/declare.d.ts +43 -0
  71. package/dist-cjs/synth/declare.js +130 -0
  72. package/dist-cjs/synth/index.d.ts +1 -0
  73. package/dist-cjs/synth/index.js +3 -1
  74. package/dist-cjs/synth/synthesize-from-recording.js +1 -1
  75. package/dist-cjs/types.d.ts +21 -1
  76. package/dist-cjs/types.js +1 -1
  77. package/package.json +1 -1
  78. package/src/adapters/interpreter/adapter.ts +13 -5
  79. package/src/errors.ts +5 -0
  80. package/src/install/authority-overlap.ts +323 -0
  81. package/src/install/index.ts +20 -0
  82. package/src/install/read-account-rules.ts +313 -0
  83. package/src/predicate/decode.ts +22 -1
  84. package/src/predicate/encode.ts +55 -5
  85. package/src/predicate/from-json.ts +14 -1
  86. package/src/record/index.ts +59 -2
  87. package/src/record/rpc.ts +4 -1
  88. package/src/registry/identify.ts +4 -1
  89. package/src/registry/on-chain-spec.ts +168 -0
  90. package/src/review-card/builder.ts +45 -2
  91. package/src/review-card/cross-check.ts +35 -1
  92. package/src/review-card/render-leaf.ts +8 -1
  93. package/src/run/index.ts +125 -2
  94. package/src/run/schemas.ts +100 -6
  95. package/src/simulate/deny-cases.ts +12 -1
  96. package/src/simulate/evaluate.ts +101 -9
  97. package/src/synth/declare.ts +206 -0
  98. package/src/synth/index.ts +5 -0
  99. package/src/synth/synthesize-from-recording.ts +1 -1
  100. package/src/types.ts +16 -1
@@ -1494,25 +1494,123 @@ export declare const VerifyPolicyInputSchema: z.ZodObject<{
1494
1494
  predicate?: unknown;
1495
1495
  }>;
1496
1496
  export type VerifyPolicyInput = z.infer<typeof VerifyPolicyInputSchema>;
1497
+ /** A rule ALREADY on the account, as the caller observed it. Supplying these
1498
+ * turns on the cross-rule authority scan: a signer belonging to several rules
1499
+ * picks which one applies, so a predicate only constrains a key when the
1500
+ * policed rule is the only rule that key is on. */
1501
+ export declare const ObservedRuleSchema: z.ZodObject<{
1502
+ id: z.ZodNumber;
1503
+ contextType: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
1504
+ kind: z.ZodLiteral<"default">;
1505
+ }, "strip", z.ZodTypeAny, {
1506
+ kind: "default";
1507
+ }, {
1508
+ kind: "default";
1509
+ }>, z.ZodObject<{
1510
+ kind: z.ZodLiteral<"call_contract">;
1511
+ contract: z.ZodString;
1512
+ }, "strip", z.ZodTypeAny, {
1513
+ kind: "call_contract";
1514
+ contract: string;
1515
+ }, {
1516
+ kind: "call_contract";
1517
+ contract: string;
1518
+ }>, z.ZodObject<{
1519
+ kind: z.ZodLiteral<"create_contract">;
1520
+ wasmHash: z.ZodString;
1521
+ }, "strip", z.ZodTypeAny, {
1522
+ kind: "create_contract";
1523
+ wasmHash: string;
1524
+ }, {
1525
+ kind: "create_contract";
1526
+ wasmHash: string;
1527
+ }>]>;
1528
+ signers: z.ZodArray<z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
1529
+ kind: z.ZodLiteral<"delegated">;
1530
+ address: z.ZodString;
1531
+ }, "strip", z.ZodTypeAny, {
1532
+ address: string;
1533
+ kind: "delegated";
1534
+ }, {
1535
+ address: string;
1536
+ kind: "delegated";
1537
+ }>, z.ZodObject<{
1538
+ kind: z.ZodLiteral<"external">;
1539
+ verifier: z.ZodString;
1540
+ keyBytes: z.ZodString;
1541
+ }, "strip", z.ZodTypeAny, {
1542
+ kind: "external";
1543
+ verifier: string;
1544
+ keyBytes: string;
1545
+ }, {
1546
+ kind: "external";
1547
+ verifier: string;
1548
+ keyBytes: string;
1549
+ }>]>, "many">;
1550
+ policyAddresses: z.ZodArray<z.ZodString, "many">;
1551
+ predicate: z.ZodOptional<z.ZodType<unknown, z.ZodTypeDef, unknown>>;
1552
+ }, "strip", z.ZodTypeAny, {
1553
+ signers: ({
1554
+ address: string;
1555
+ kind: "delegated";
1556
+ } | {
1557
+ kind: "external";
1558
+ verifier: string;
1559
+ keyBytes: string;
1560
+ })[];
1561
+ id: number;
1562
+ policyAddresses: string[];
1563
+ contextType: {
1564
+ kind: "default";
1565
+ } | {
1566
+ kind: "call_contract";
1567
+ contract: string;
1568
+ } | {
1569
+ kind: "create_contract";
1570
+ wasmHash: string;
1571
+ };
1572
+ predicate?: unknown;
1573
+ }, {
1574
+ signers: ({
1575
+ address: string;
1576
+ kind: "delegated";
1577
+ } | {
1578
+ kind: "external";
1579
+ verifier: string;
1580
+ keyBytes: string;
1581
+ })[];
1582
+ id: number;
1583
+ policyAddresses: string[];
1584
+ contextType: {
1585
+ kind: "default";
1586
+ } | {
1587
+ kind: "call_contract";
1588
+ contract: string;
1589
+ } | {
1590
+ kind: "create_contract";
1591
+ wasmHash: string;
1592
+ };
1593
+ predicate?: unknown;
1594
+ }>;
1497
1595
  /** Pinned interpreter address (testnet).
1498
1596
  * Single source for the MCP layer; do not embed elsewhere. */
1499
- export declare const PINNED_INTERPRETER_TESTNET_ADDRESS = "CCL336TCK2Y5OFNRCMN2M3HVPBCEX4PW5H6EQ5VW5NPMXOCP4ESB5XR4";
1500
- /** Pinned interpreter address (mainnet), redeployed 2026-08-22 from a reproducible build. The mainnet
1597
+ export declare const PINNED_INTERPRETER_TESTNET_ADDRESS = "CCBHVZ6HGGV7C4SNHCZ3S5665Z2WEMHTMBAEPO4XW6PKON464BEBANU5";
1598
+ /** Pinned interpreter address (mainnet), redeployed 2026-08-22 for grammar 4, from a reproducible build. The mainnet
1501
1599
  * interpreter IS the binary exercised on testnet - both instances were created
1502
1600
  * from the same uploaded wasm hash (see PINNED_INTERPRETER_WASM_SHA256), and
1503
- * both were read back with `grammar_version()` returning 3. The address differs
1601
+ * both were read back with `grammar_version()` returning 4. The address differs
1504
1602
  * because instance ids are network-scoped. UNAUDITED at the time of writing.
1505
1603
  *
1506
1604
  * These four constants move together or not at all. The grammar version and
1507
1605
  * wasm hash are single values covering BOTH networks, so re-pinning one network
1508
1606
  * alone would have the builder emit a version the other network refuses - with
1509
1607
  * a green test run, since `grammar-version-parity.test.ts` would then pass. */
1510
- export declare const PINNED_INTERPRETER_MAINNET_ADDRESS = "CBZXLSTQUITBFZHQH6XRXF3XIVRQR4RHRI64Q5WELS5KGY3ZKJPFWDPF";
1608
+ export declare const PINNED_INTERPRETER_MAINNET_ADDRESS = "CDN755TDYZM3ZQ5OXTJ6TIBUBWZV2KRI2BYJPBXD2MVWED4STT3VBN52";
1511
1609
  /** Pinned interpreter wasm sha256 (hex). */
1512
- export declare const PINNED_INTERPRETER_WASM_SHA256 = "a2b36e8ac5a61caf3757af26aa79e83f2995b451099f44772383806a55fe3414";
1610
+ export declare const PINNED_INTERPRETER_WASM_SHA256 = "b5ba1e35ccf20cd8c13c3a2c3098bf337033a92bcaf475d63c03ddc0cba0fcae";
1513
1611
  /** The grammar version the interpreter enforces (matches SELF_VERSION in
1514
1612
  * contracts/policy-interpreter/src/version.rs). */
1515
- export declare const PINNED_INTERPRETER_GRAMMAR_VERSION = 3;
1613
+ export declare const PINNED_INTERPRETER_GRAMMAR_VERSION = 4;
1516
1614
  /** Default Soroban RPC for the install / revoke / info tools. The recorder
1517
1615
  * keeps its own copy in record/rpc.ts because it hands back a fetcher rather
1518
1616
  * than a Server; the two are deliberately different surfaces, so this is
@@ -1533,7 +1631,165 @@ export declare const RPC_URL_BY_NETWORK: Record<Network, string>;
1533
1631
  * matching passphrase when the wallet signs (a mismatch yields invalid
1534
1632
  * hashes). */
1535
1633
  export declare const NETWORK_PASSPHRASES: Record<Network, string>;
1634
+ export declare const DeclarePolicyInputSchema: z.ZodObject<{
1635
+ fn: z.ZodString;
1636
+ contract: z.ZodOptional<z.ZodString>;
1637
+ /** Smallest unit, unsigned decimal STRING - an i128 is wider than
1638
+ * Number.MAX_SAFE_INTEGER, so a number here would silently round. */
1639
+ maxAmount: z.ZodOptional<z.ZodString>;
1640
+ amountArgIndex: z.ZodOptional<z.ZodNumber>;
1641
+ recipients: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
1642
+ recipientArgIndex: z.ZodOptional<z.ZodNumber>;
1643
+ allowZeroCap: z.ZodOptional<z.ZodBoolean>;
1644
+ /** Minimum output as a ratio of the call's own input. num/den are decimal
1645
+ * STRINGS for the same reason maxAmount is: an i128 ratio does not
1646
+ * survive a JS number. */
1647
+ minOutputRatio: z.ZodOptional<z.ZodObject<{
1648
+ num: z.ZodString;
1649
+ den: z.ZodString;
1650
+ inputArgIndex: z.ZodNumber;
1651
+ outputArgIndex: z.ZodNumber;
1652
+ }, "strict", z.ZodTypeAny, {
1653
+ num: string;
1654
+ den: string;
1655
+ inputArgIndex: number;
1656
+ outputArgIndex: number;
1657
+ }, {
1658
+ num: string;
1659
+ den: string;
1660
+ inputArgIndex: number;
1661
+ outputArgIndex: number;
1662
+ }>>;
1663
+ }, "strict", z.ZodTypeAny, {
1664
+ fn: string;
1665
+ contract?: string | undefined;
1666
+ maxAmount?: string | undefined;
1667
+ amountArgIndex?: number | undefined;
1668
+ recipients?: string[] | undefined;
1669
+ recipientArgIndex?: number | undefined;
1670
+ allowZeroCap?: boolean | undefined;
1671
+ minOutputRatio?: {
1672
+ num: string;
1673
+ den: string;
1674
+ inputArgIndex: number;
1675
+ outputArgIndex: number;
1676
+ } | undefined;
1677
+ }, {
1678
+ fn: string;
1679
+ contract?: string | undefined;
1680
+ maxAmount?: string | undefined;
1681
+ amountArgIndex?: number | undefined;
1682
+ recipients?: string[] | undefined;
1683
+ recipientArgIndex?: number | undefined;
1684
+ allowZeroCap?: boolean | undefined;
1685
+ minOutputRatio?: {
1686
+ num: string;
1687
+ den: string;
1688
+ inputArgIndex: number;
1689
+ outputArgIndex: number;
1690
+ } | undefined;
1691
+ }>;
1692
+ export type DeclarePolicyInput = z.infer<typeof DeclarePolicyInputSchema>;
1536
1693
  export declare const InstallPolicyInputSchema: z.ZodEffects<z.ZodObject<{
1694
+ /** Rules already on the account. Supplying them turns on the cross-rule
1695
+ * authority scan, which reports every existing rule a signer of this
1696
+ * install could name INSTEAD - including an unpoliced one, against which
1697
+ * the predicate never runs. Absent means the scan is skipped, and the
1698
+ * result says so rather than reporting "no overlaps found". */
1699
+ existingRules: z.ZodOptional<z.ZodArray<z.ZodObject<{
1700
+ id: z.ZodNumber;
1701
+ contextType: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
1702
+ kind: z.ZodLiteral<"default">;
1703
+ }, "strip", z.ZodTypeAny, {
1704
+ kind: "default";
1705
+ }, {
1706
+ kind: "default";
1707
+ }>, z.ZodObject<{
1708
+ kind: z.ZodLiteral<"call_contract">;
1709
+ contract: z.ZodString;
1710
+ }, "strip", z.ZodTypeAny, {
1711
+ kind: "call_contract";
1712
+ contract: string;
1713
+ }, {
1714
+ kind: "call_contract";
1715
+ contract: string;
1716
+ }>, z.ZodObject<{
1717
+ kind: z.ZodLiteral<"create_contract">;
1718
+ wasmHash: z.ZodString;
1719
+ }, "strip", z.ZodTypeAny, {
1720
+ kind: "create_contract";
1721
+ wasmHash: string;
1722
+ }, {
1723
+ kind: "create_contract";
1724
+ wasmHash: string;
1725
+ }>]>;
1726
+ signers: z.ZodArray<z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
1727
+ kind: z.ZodLiteral<"delegated">;
1728
+ address: z.ZodString;
1729
+ }, "strip", z.ZodTypeAny, {
1730
+ address: string;
1731
+ kind: "delegated";
1732
+ }, {
1733
+ address: string;
1734
+ kind: "delegated";
1735
+ }>, z.ZodObject<{
1736
+ kind: z.ZodLiteral<"external">;
1737
+ verifier: z.ZodString;
1738
+ keyBytes: z.ZodString;
1739
+ }, "strip", z.ZodTypeAny, {
1740
+ kind: "external";
1741
+ verifier: string;
1742
+ keyBytes: string;
1743
+ }, {
1744
+ kind: "external";
1745
+ verifier: string;
1746
+ keyBytes: string;
1747
+ }>]>, "many">;
1748
+ policyAddresses: z.ZodArray<z.ZodString, "many">;
1749
+ predicate: z.ZodOptional<z.ZodType<unknown, z.ZodTypeDef, unknown>>;
1750
+ }, "strip", z.ZodTypeAny, {
1751
+ signers: ({
1752
+ address: string;
1753
+ kind: "delegated";
1754
+ } | {
1755
+ kind: "external";
1756
+ verifier: string;
1757
+ keyBytes: string;
1758
+ })[];
1759
+ id: number;
1760
+ policyAddresses: string[];
1761
+ contextType: {
1762
+ kind: "default";
1763
+ } | {
1764
+ kind: "call_contract";
1765
+ contract: string;
1766
+ } | {
1767
+ kind: "create_contract";
1768
+ wasmHash: string;
1769
+ };
1770
+ predicate?: unknown;
1771
+ }, {
1772
+ signers: ({
1773
+ address: string;
1774
+ kind: "delegated";
1775
+ } | {
1776
+ kind: "external";
1777
+ verifier: string;
1778
+ keyBytes: string;
1779
+ })[];
1780
+ id: number;
1781
+ policyAddresses: string[];
1782
+ contextType: {
1783
+ kind: "default";
1784
+ } | {
1785
+ kind: "call_contract";
1786
+ contract: string;
1787
+ } | {
1788
+ kind: "create_contract";
1789
+ wasmHash: string;
1790
+ };
1791
+ predicate?: unknown;
1792
+ }>, "many">>;
1537
1793
  /** The smart account contract address (C...) that will receive the rule. */
1538
1794
  smartAccount: z.ZodString;
1539
1795
  /** The signer that authorises the install (G... wallet). Used only for
@@ -1918,6 +2174,28 @@ export declare const InstallPolicyInputSchema: z.ZodEffects<z.ZodObject<{
1918
2174
  [k: string]: unknown;
1919
2175
  };
1920
2176
  network?: "mainnet" | "testnet" | undefined;
2177
+ existingRules?: {
2178
+ signers: ({
2179
+ address: string;
2180
+ kind: "delegated";
2181
+ } | {
2182
+ kind: "external";
2183
+ verifier: string;
2184
+ keyBytes: string;
2185
+ })[];
2186
+ id: number;
2187
+ policyAddresses: string[];
2188
+ contextType: {
2189
+ kind: "default";
2190
+ } | {
2191
+ kind: "call_contract";
2192
+ contract: string;
2193
+ } | {
2194
+ kind: "create_contract";
2195
+ wasmHash: string;
2196
+ };
2197
+ predicate?: unknown;
2198
+ }[] | undefined;
1921
2199
  rpcUrl?: string | undefined;
1922
2200
  allowUnpinnedRpcUrl?: boolean | undefined;
1923
2201
  allowUnpinnedInterpreter?: boolean | undefined;
@@ -1955,6 +2233,28 @@ export declare const InstallPolicyInputSchema: z.ZodEffects<z.ZodObject<{
1955
2233
  [k: string]: unknown;
1956
2234
  };
1957
2235
  network?: "mainnet" | "testnet" | undefined;
2236
+ existingRules?: {
2237
+ signers: ({
2238
+ address: string;
2239
+ kind: "delegated";
2240
+ } | {
2241
+ kind: "external";
2242
+ verifier: string;
2243
+ keyBytes: string;
2244
+ })[];
2245
+ id: number;
2246
+ policyAddresses: string[];
2247
+ contextType: {
2248
+ kind: "default";
2249
+ } | {
2250
+ kind: "call_contract";
2251
+ contract: string;
2252
+ } | {
2253
+ kind: "create_contract";
2254
+ wasmHash: string;
2255
+ };
2256
+ predicate?: unknown;
2257
+ }[] | undefined;
1958
2258
  rpcUrl?: string | undefined;
1959
2259
  allowUnpinnedRpcUrl?: boolean | undefined;
1960
2260
  allowUnpinnedInterpreter?: boolean | undefined;
@@ -1992,6 +2292,28 @@ export declare const InstallPolicyInputSchema: z.ZodEffects<z.ZodObject<{
1992
2292
  [k: string]: unknown;
1993
2293
  };
1994
2294
  network?: "mainnet" | "testnet" | undefined;
2295
+ existingRules?: {
2296
+ signers: ({
2297
+ address: string;
2298
+ kind: "delegated";
2299
+ } | {
2300
+ kind: "external";
2301
+ verifier: string;
2302
+ keyBytes: string;
2303
+ })[];
2304
+ id: number;
2305
+ policyAddresses: string[];
2306
+ contextType: {
2307
+ kind: "default";
2308
+ } | {
2309
+ kind: "call_contract";
2310
+ contract: string;
2311
+ } | {
2312
+ kind: "create_contract";
2313
+ wasmHash: string;
2314
+ };
2315
+ predicate?: unknown;
2316
+ }[] | undefined;
1995
2317
  rpcUrl?: string | undefined;
1996
2318
  allowUnpinnedRpcUrl?: boolean | undefined;
1997
2319
  allowUnpinnedInterpreter?: boolean | undefined;
@@ -2029,6 +2351,28 @@ export declare const InstallPolicyInputSchema: z.ZodEffects<z.ZodObject<{
2029
2351
  [k: string]: unknown;
2030
2352
  };
2031
2353
  network?: "mainnet" | "testnet" | undefined;
2354
+ existingRules?: {
2355
+ signers: ({
2356
+ address: string;
2357
+ kind: "delegated";
2358
+ } | {
2359
+ kind: "external";
2360
+ verifier: string;
2361
+ keyBytes: string;
2362
+ })[];
2363
+ id: number;
2364
+ policyAddresses: string[];
2365
+ contextType: {
2366
+ kind: "default";
2367
+ } | {
2368
+ kind: "call_contract";
2369
+ contract: string;
2370
+ } | {
2371
+ kind: "create_contract";
2372
+ wasmHash: string;
2373
+ };
2374
+ predicate?: unknown;
2375
+ }[] | undefined;
2032
2376
  rpcUrl?: string | undefined;
2033
2377
  allowUnpinnedRpcUrl?: boolean | undefined;
2034
2378
  allowUnpinnedInterpreter?: boolean | undefined;
@@ -177,6 +177,15 @@ export const PredicateLeafSchema = z.lazy(() => z.union([
177
177
  element: z.number().int().nonnegative(),
178
178
  field: z.string(),
179
179
  }),
180
+ // num/den are i128 decimal strings, matching `literal_i128`. The regex
181
+ // is the boundary guard; the ratio's SIGN is checked at encode, where
182
+ // the message can explain that a negative ratio inverts the comparison.
183
+ z.object({
184
+ kind: z.literal('call_arg_scaled'),
185
+ index: z.number().int().nonnegative(),
186
+ num: z.string().regex(/^-?[0-9]+$/),
187
+ den: z.string().regex(/^-?[0-9]+$/),
188
+ }),
180
189
  z.object({ kind: z.literal('literal_address'), value: z.string() }),
181
190
  z.object({ kind: z.literal('literal_i128'), value: z.string().regex(/^-?[0-9]+$/) }),
182
191
  z.object({ kind: z.literal('literal_symbol'), value: z.string() }),
@@ -195,16 +204,32 @@ export const PredicateLeafSchema = z.lazy(() => z.union([
195
204
  * `and`; the lazy + annotation pattern keeps the recursion type-safe. */
196
205
  export const PredicateNodeSchema = z.lazy(() => z.union([
197
206
  z.object({ op: z.literal('and'), children: z.array(PredicateNodeSchema) }),
207
+ z.object({ op: z.literal('or'), children: z.array(PredicateNodeSchema) }),
198
208
  z.object({
199
209
  op: z.literal('eq'),
200
210
  left: PredicateLeafSchema,
201
211
  right: PredicateLeafSchema,
202
212
  }),
213
+ z.object({
214
+ op: z.literal('lt'),
215
+ left: PredicateLeafSchema,
216
+ right: PredicateLeafSchema,
217
+ }),
203
218
  z.object({
204
219
  op: z.literal('lte'),
205
220
  left: PredicateLeafSchema,
206
221
  right: PredicateLeafSchema,
207
222
  }),
223
+ z.object({
224
+ op: z.literal('gt'),
225
+ left: PredicateLeafSchema,
226
+ right: PredicateLeafSchema,
227
+ }),
228
+ z.object({
229
+ op: z.literal('gte'),
230
+ left: PredicateLeafSchema,
231
+ right: PredicateLeafSchema,
232
+ }),
208
233
  z.object({
209
234
  op: z.literal('in'),
210
235
  needle: PredicateLeafSchema,
@@ -246,6 +271,26 @@ export const VerifyPolicyInputSchema = SimulatePolicyInputSchema;
246
271
  * the temporal dead zone (no JS hoisting for `const`). */
247
272
  const MAX_SIGNERS_PER_RULE = 15;
248
273
  const MAX_POLICIES_PER_RULE = 5;
274
+ const SignerDraftSchema = z.discriminatedUnion('kind', [
275
+ z.object({ kind: z.literal('delegated'), address: z.string() }),
276
+ z.object({ kind: z.literal('external'), verifier: z.string(), keyBytes: z.string() }),
277
+ ]);
278
+ const ContextTypeSchema = z.discriminatedUnion('kind', [
279
+ z.object({ kind: z.literal('default') }),
280
+ z.object({ kind: z.literal('call_contract'), contract: z.string() }),
281
+ z.object({ kind: z.literal('create_contract'), wasmHash: z.string() }),
282
+ ]);
283
+ /** A rule ALREADY on the account, as the caller observed it. Supplying these
284
+ * turns on the cross-rule authority scan: a signer belonging to several rules
285
+ * picks which one applies, so a predicate only constrains a key when the
286
+ * policed rule is the only rule that key is on. */
287
+ export const ObservedRuleSchema = z.object({
288
+ id: z.number().int().nonnegative(),
289
+ contextType: ContextTypeSchema,
290
+ signers: z.array(SignerDraftSchema),
291
+ policyAddresses: z.array(z.string()),
292
+ predicate: PredicateNodeSchema.optional(),
293
+ });
249
294
  const ContextRuleDraftSchema = z
250
295
  .object({
251
296
  contextRuleType: z.discriminatedUnion('kind', [
@@ -280,23 +325,23 @@ const ContextRuleDraftSchema = z
280
325
  // existing four.
281
326
  /** Pinned interpreter address (testnet).
282
327
  * Single source for the MCP layer; do not embed elsewhere. */
283
- export const PINNED_INTERPRETER_TESTNET_ADDRESS = 'CCL336TCK2Y5OFNRCMN2M3HVPBCEX4PW5H6EQ5VW5NPMXOCP4ESB5XR4';
284
- /** Pinned interpreter address (mainnet), redeployed 2026-08-22 from a reproducible build. The mainnet
328
+ export const PINNED_INTERPRETER_TESTNET_ADDRESS = 'CCBHVZ6HGGV7C4SNHCZ3S5665Z2WEMHTMBAEPO4XW6PKON464BEBANU5';
329
+ /** Pinned interpreter address (mainnet), redeployed 2026-08-22 for grammar 4, from a reproducible build. The mainnet
285
330
  * interpreter IS the binary exercised on testnet - both instances were created
286
331
  * from the same uploaded wasm hash (see PINNED_INTERPRETER_WASM_SHA256), and
287
- * both were read back with `grammar_version()` returning 3. The address differs
332
+ * both were read back with `grammar_version()` returning 4. The address differs
288
333
  * because instance ids are network-scoped. UNAUDITED at the time of writing.
289
334
  *
290
335
  * These four constants move together or not at all. The grammar version and
291
336
  * wasm hash are single values covering BOTH networks, so re-pinning one network
292
337
  * alone would have the builder emit a version the other network refuses - with
293
338
  * a green test run, since `grammar-version-parity.test.ts` would then pass. */
294
- export const PINNED_INTERPRETER_MAINNET_ADDRESS = 'CBZXLSTQUITBFZHQH6XRXF3XIVRQR4RHRI64Q5WELS5KGY3ZKJPFWDPF';
339
+ export const PINNED_INTERPRETER_MAINNET_ADDRESS = 'CDN755TDYZM3ZQ5OXTJ6TIBUBWZV2KRI2BYJPBXD2MVWED4STT3VBN52';
295
340
  /** Pinned interpreter wasm sha256 (hex). */
296
- export const PINNED_INTERPRETER_WASM_SHA256 = 'a2b36e8ac5a61caf3757af26aa79e83f2995b451099f44772383806a55fe3414';
341
+ export const PINNED_INTERPRETER_WASM_SHA256 = 'b5ba1e35ccf20cd8c13c3a2c3098bf337033a92bcaf475d63c03ddc0cba0fcae';
297
342
  /** The grammar version the interpreter enforces (matches SELF_VERSION in
298
343
  * contracts/policy-interpreter/src/version.rs). */
299
- export const PINNED_INTERPRETER_GRAMMAR_VERSION = 3;
344
+ export const PINNED_INTERPRETER_GRAMMAR_VERSION = 4;
300
345
  /** Default Soroban RPC for the install / revoke / info tools. The recorder
301
346
  * keeps its own copy in record/rpc.ts because it hands back a fetcher rather
302
347
  * than a Server; the two are deliberately different surfaces, so this is
@@ -335,8 +380,52 @@ export const NETWORK_PASSPHRASES = {
335
380
  // `sourceAccount` is the signing wallet (G...).
336
381
  const STELLAR_CONTRACT_ADDRESS = /^C[2-7A-Z]{55}$/;
337
382
  const STELLAR_ACCOUNT_ADDRESS = /^G[2-7A-Z]{55}$/;
383
+ // ===== declare_policy =====
384
+ //
385
+ // The declarative front-end: the constraint stated outright, with no
386
+ // transaction to decode. Deliberately NOT a revival of the removed
387
+ // `MandateSpec` - that carried a rolling `spendingLimit` the interpreter
388
+ // cannot evaluate and an `approvalThreshold` needing OZ primitives nobody
389
+ // deployed. Only fields grammar 3 can actually enforce appear here.
390
+ export const DeclarePolicyInputSchema = z
391
+ .object({
392
+ fn: z.string().min(1, 'fn must name the method to pin'),
393
+ contract: z
394
+ .string()
395
+ .regex(STELLAR_CONTRACT_ADDRESS, 'contract must be a Stellar contract address (C...)')
396
+ .optional(),
397
+ /** Smallest unit, unsigned decimal STRING - an i128 is wider than
398
+ * Number.MAX_SAFE_INTEGER, so a number here would silently round. */
399
+ maxAmount: z
400
+ .string()
401
+ .regex(/^[0-9]+$/, 'maxAmount must be an unsigned integer in the smallest unit')
402
+ .optional(),
403
+ amountArgIndex: z.number().int().nonnegative().max(U32_MAX).optional(),
404
+ recipients: z.array(z.string()).min(1, 'recipients must not be empty').optional(),
405
+ recipientArgIndex: z.number().int().nonnegative().max(U32_MAX).optional(),
406
+ allowZeroCap: z.boolean().optional(),
407
+ /** Minimum output as a ratio of the call's own input. num/den are decimal
408
+ * STRINGS for the same reason maxAmount is: an i128 ratio does not
409
+ * survive a JS number. */
410
+ minOutputRatio: z
411
+ .object({
412
+ num: z.string().regex(/^[0-9]+$/, 'num must be an unsigned integer'),
413
+ den: z.string().regex(/^[0-9]+$/, 'den must be an unsigned integer'),
414
+ inputArgIndex: z.number().int().nonnegative().max(U32_MAX),
415
+ outputArgIndex: z.number().int().nonnegative().max(U32_MAX),
416
+ })
417
+ .strict()
418
+ .optional(),
419
+ })
420
+ .strict();
338
421
  export const InstallPolicyInputSchema = z
339
422
  .object({
423
+ /** Rules already on the account. Supplying them turns on the cross-rule
424
+ * authority scan, which reports every existing rule a signer of this
425
+ * install could name INSTEAD - including an unpoliced one, against which
426
+ * the predicate never runs. Absent means the scan is skipped, and the
427
+ * result says so rather than reporting "no overlaps found". */
428
+ existingRules: z.array(ObservedRuleSchema).optional(),
340
429
  /** The smart account contract address (C...) that will receive the rule. */
341
430
  smartAccount: z
342
431
  .string()
@@ -201,11 +201,22 @@ function visit(node, facts) {
201
201
  for (const child of node.children)
202
202
  visit(child, facts);
203
203
  return;
204
+ // NOT descended into. A deny case works by violating ONE constraint and
205
+ // asserting the predicate refuses the call. Violating one branch of an
206
+ // `or` proves nothing, because another branch can still permit, so the
207
+ // generated case would either fail or pass for the wrong reason. A sound
208
+ // deny case for a disjunction must violate EVERY branch at once, which
209
+ // this generator does not construct - so it emits none.
210
+ case 'or':
211
+ return;
204
212
  case 'in':
205
213
  facts.memberships.push(node);
206
214
  return;
207
215
  case 'eq':
216
+ case 'lt':
208
217
  case 'lte':
218
+ case 'gt':
219
+ case 'gte':
209
220
  facts.comparisons.push(node);
210
221
  }
211
222
  }