@crediolabs/policy-synth 0.1.17 → 0.1.18

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 (91) hide show
  1. package/README.md +43 -246
  2. package/dist/adapters/interpreter/adapter.d.ts +6 -7
  3. package/dist/adapters/interpreter/adapter.js +28 -48
  4. package/dist/adapters/oz/adapter.js +12 -13
  5. package/dist/codegen/compile-gate.js +5 -0
  6. package/dist/codegen/template.js +17 -0
  7. package/dist/install/build-add-context-rule.js +16 -45
  8. package/dist/install/build-install-policy.d.ts +12 -5
  9. package/dist/install/build-install-policy.js +32 -23
  10. package/dist/install/get-interpreter-info.js +2 -2
  11. package/dist/install/index.d.ts +1 -1
  12. package/dist/install/index.js +1 -1
  13. package/dist/install/oz-auth.js +5 -3
  14. package/dist/predicate/decode.js +1 -1
  15. package/dist/predicate/encode.js +171 -10
  16. package/dist/record/decode.js +5 -6
  17. package/dist/registry/protocols.d.ts +1 -1
  18. package/dist/registry/protocols.js +1 -1
  19. package/dist/review-card/builder.d.ts +6 -0
  20. package/dist/review-card/builder.js +9 -1
  21. package/dist/run/index.d.ts +14 -13
  22. package/dist/run/index.js +80 -67
  23. package/dist/run/schemas.d.ts +193 -11
  24. package/dist/run/schemas.js +59 -17
  25. package/dist/synth/compose-from-recording.d.ts +10 -15
  26. package/dist/synth/compose-from-recording.js +79 -124
  27. package/dist/synth/deny-cases.d.ts +5 -0
  28. package/dist/synth/deny-cases.js +52 -13
  29. package/dist/synth/evaluate.js +69 -119
  30. package/dist/synth/harness.d.ts +13 -1
  31. package/dist/synth/harness.js +22 -1
  32. package/dist/synth/index.d.ts +1 -1
  33. package/dist/synth/synthesize-from-recording.d.ts +31 -21
  34. package/dist/synth/synthesize-from-recording.js +129 -155
  35. package/dist-cjs/adapters/interpreter/adapter.d.ts +6 -7
  36. package/dist-cjs/adapters/interpreter/adapter.js +28 -48
  37. package/dist-cjs/adapters/oz/adapter.js +12 -13
  38. package/dist-cjs/codegen/compile-gate.js +5 -0
  39. package/dist-cjs/codegen/template.js +17 -0
  40. package/dist-cjs/install/build-add-context-rule.js +16 -45
  41. package/dist-cjs/install/build-install-policy.d.ts +12 -5
  42. package/dist-cjs/install/build-install-policy.js +32 -23
  43. package/dist-cjs/install/get-interpreter-info.js +2 -2
  44. package/dist-cjs/install/index.d.ts +1 -1
  45. package/dist-cjs/install/index.js +2 -2
  46. package/dist-cjs/install/oz-auth.js +5 -3
  47. package/dist-cjs/predicate/decode.js +1 -1
  48. package/dist-cjs/predicate/encode.js +171 -10
  49. package/dist-cjs/record/decode.js +5 -6
  50. package/dist-cjs/registry/protocols.d.ts +1 -1
  51. package/dist-cjs/registry/protocols.js +1 -1
  52. package/dist-cjs/review-card/builder.d.ts +6 -0
  53. package/dist-cjs/review-card/builder.js +9 -1
  54. package/dist-cjs/run/index.d.ts +14 -13
  55. package/dist-cjs/run/index.js +80 -67
  56. package/dist-cjs/run/schemas.d.ts +193 -11
  57. package/dist-cjs/run/schemas.js +59 -17
  58. package/dist-cjs/synth/compose-from-recording.d.ts +10 -15
  59. package/dist-cjs/synth/compose-from-recording.js +79 -124
  60. package/dist-cjs/synth/deny-cases.d.ts +5 -0
  61. package/dist-cjs/synth/deny-cases.js +52 -13
  62. package/dist-cjs/synth/evaluate.js +69 -119
  63. package/dist-cjs/synth/harness.d.ts +13 -1
  64. package/dist-cjs/synth/harness.js +22 -1
  65. package/dist-cjs/synth/index.d.ts +1 -1
  66. package/dist-cjs/synth/synthesize-from-recording.d.ts +31 -21
  67. package/dist-cjs/synth/synthesize-from-recording.js +129 -153
  68. package/package.json +1 -1
  69. package/src/adapters/interpreter/adapter.ts +28 -48
  70. package/src/adapters/oz/adapter.ts +12 -13
  71. package/src/codegen/compile-gate.ts +5 -0
  72. package/src/codegen/template.ts +17 -0
  73. package/src/install/build-add-context-rule.ts +17 -55
  74. package/src/install/build-install-policy.ts +41 -23
  75. package/src/install/get-interpreter-info.ts +2 -2
  76. package/src/install/index.ts +2 -2
  77. package/src/install/oz-auth.ts +5 -3
  78. package/src/predicate/decode.ts +1 -1
  79. package/src/predicate/encode.ts +176 -10
  80. package/src/record/decode.ts +5 -6
  81. package/src/registry/protocols.ts +1 -1
  82. package/src/review-card/builder.ts +17 -1
  83. package/src/run/index.ts +117 -91
  84. package/src/run/schemas.ts +64 -16
  85. package/src/synth/compose-from-recording.ts +87 -132
  86. package/src/synth/deny-cases.ts +59 -13
  87. package/src/synth/evaluate.ts +70 -118
  88. package/src/synth/harness.ts +29 -1
  89. package/src/synth/index.ts +1 -0
  90. package/src/synth/synthesize-from-recording.ts +209 -192
  91. package/src/contracts/policy-template/OZ_POLICY_TRAIT.md +0 -196
@@ -352,7 +352,7 @@ export declare const RecordedTransactionSchema: z.ZodObject<{
352
352
  /** MandateSpec mirrors the core MandateSpec. The deterministic Mandate
353
353
  * front-end needs no parseConfidence; the tool adapter injects the full
354
354
  * confidence after synthesis so the orchestrator can compare. */
355
- export declare const MandateSpecSchema: z.ZodObject<{
355
+ export declare const MandateSpecSchema: z.ZodEffects<z.ZodObject<{
356
356
  chain: z.ZodLiteral<"stellar">;
357
357
  contract: z.ZodString;
358
358
  method: z.ZodOptional<z.ZodString>;
@@ -439,6 +439,64 @@ export declare const MandateSpecSchema: z.ZodObject<{
439
439
  validUntilLedger?: number | undefined;
440
440
  validUntilUnixSeconds?: number | undefined;
441
441
  }>>;
442
+ }, z.ZodTypeAny, "passthrough">>, z.objectOutputType<{
443
+ chain: z.ZodLiteral<"stellar">;
444
+ contract: z.ZodString;
445
+ method: z.ZodOptional<z.ZodString>;
446
+ spendingLimit: z.ZodOptional<z.ZodObject<{
447
+ token: z.ZodString;
448
+ limit: z.ZodString;
449
+ windowSeconds: z.ZodNumber;
450
+ }, "strip", z.ZodTypeAny, {
451
+ token: string;
452
+ limit: string;
453
+ windowSeconds: number;
454
+ }, {
455
+ token: string;
456
+ limit: string;
457
+ windowSeconds: number;
458
+ }>>;
459
+ approvalThreshold: z.ZodOptional<z.ZodNumber>;
460
+ recipients: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
461
+ expiry: z.ZodOptional<z.ZodObject<{
462
+ validUntilLedger: z.ZodOptional<z.ZodNumber>;
463
+ validUntilUnixSeconds: z.ZodOptional<z.ZodNumber>;
464
+ }, "strip", z.ZodTypeAny, {
465
+ validUntilLedger?: number | undefined;
466
+ validUntilUnixSeconds?: number | undefined;
467
+ }, {
468
+ validUntilLedger?: number | undefined;
469
+ validUntilUnixSeconds?: number | undefined;
470
+ }>>;
471
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
472
+ chain: z.ZodLiteral<"stellar">;
473
+ contract: z.ZodString;
474
+ method: z.ZodOptional<z.ZodString>;
475
+ spendingLimit: z.ZodOptional<z.ZodObject<{
476
+ token: z.ZodString;
477
+ limit: z.ZodString;
478
+ windowSeconds: z.ZodNumber;
479
+ }, "strip", z.ZodTypeAny, {
480
+ token: string;
481
+ limit: string;
482
+ windowSeconds: number;
483
+ }, {
484
+ token: string;
485
+ limit: string;
486
+ windowSeconds: number;
487
+ }>>;
488
+ approvalThreshold: z.ZodOptional<z.ZodNumber>;
489
+ recipients: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
490
+ expiry: z.ZodOptional<z.ZodObject<{
491
+ validUntilLedger: z.ZodOptional<z.ZodNumber>;
492
+ validUntilUnixSeconds: z.ZodOptional<z.ZodNumber>;
493
+ }, "strip", z.ZodTypeAny, {
494
+ validUntilLedger?: number | undefined;
495
+ validUntilUnixSeconds?: number | undefined;
496
+ }, {
497
+ validUntilLedger?: number | undefined;
498
+ validUntilUnixSeconds?: number | undefined;
499
+ }>>;
442
500
  }, z.ZodTypeAny, "passthrough">>;
443
501
  /** ComposeUserResponses mirrors the core. */
444
502
  export declare const ComposeUserResponsesSchema: z.ZodObject<{
@@ -530,7 +588,7 @@ export declare const RecordTransactionInputSchema: z.ZodEffects<z.ZodEffects<z.Z
530
588
  export type RecordTransactionInput = z.infer<typeof RecordTransactionInputSchema>;
531
589
  export declare const SynthesizePolicyMandateInputSchema: z.ZodObject<{
532
590
  source: z.ZodLiteral<"mandate">;
533
- mandate: z.ZodObject<{
591
+ mandate: z.ZodEffects<z.ZodObject<{
534
592
  chain: z.ZodLiteral<"stellar">;
535
593
  contract: z.ZodString;
536
594
  method: z.ZodOptional<z.ZodString>;
@@ -617,6 +675,64 @@ export declare const SynthesizePolicyMandateInputSchema: z.ZodObject<{
617
675
  validUntilLedger?: number | undefined;
618
676
  validUntilUnixSeconds?: number | undefined;
619
677
  }>>;
678
+ }, z.ZodTypeAny, "passthrough">>, z.objectOutputType<{
679
+ chain: z.ZodLiteral<"stellar">;
680
+ contract: z.ZodString;
681
+ method: z.ZodOptional<z.ZodString>;
682
+ spendingLimit: z.ZodOptional<z.ZodObject<{
683
+ token: z.ZodString;
684
+ limit: z.ZodString;
685
+ windowSeconds: z.ZodNumber;
686
+ }, "strip", z.ZodTypeAny, {
687
+ token: string;
688
+ limit: string;
689
+ windowSeconds: number;
690
+ }, {
691
+ token: string;
692
+ limit: string;
693
+ windowSeconds: number;
694
+ }>>;
695
+ approvalThreshold: z.ZodOptional<z.ZodNumber>;
696
+ recipients: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
697
+ expiry: z.ZodOptional<z.ZodObject<{
698
+ validUntilLedger: z.ZodOptional<z.ZodNumber>;
699
+ validUntilUnixSeconds: z.ZodOptional<z.ZodNumber>;
700
+ }, "strip", z.ZodTypeAny, {
701
+ validUntilLedger?: number | undefined;
702
+ validUntilUnixSeconds?: number | undefined;
703
+ }, {
704
+ validUntilLedger?: number | undefined;
705
+ validUntilUnixSeconds?: number | undefined;
706
+ }>>;
707
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
708
+ chain: z.ZodLiteral<"stellar">;
709
+ contract: z.ZodString;
710
+ method: z.ZodOptional<z.ZodString>;
711
+ spendingLimit: z.ZodOptional<z.ZodObject<{
712
+ token: z.ZodString;
713
+ limit: z.ZodString;
714
+ windowSeconds: z.ZodNumber;
715
+ }, "strip", z.ZodTypeAny, {
716
+ token: string;
717
+ limit: string;
718
+ windowSeconds: number;
719
+ }, {
720
+ token: string;
721
+ limit: string;
722
+ windowSeconds: number;
723
+ }>>;
724
+ approvalThreshold: z.ZodOptional<z.ZodNumber>;
725
+ recipients: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
726
+ expiry: z.ZodOptional<z.ZodObject<{
727
+ validUntilLedger: z.ZodOptional<z.ZodNumber>;
728
+ validUntilUnixSeconds: z.ZodOptional<z.ZodNumber>;
729
+ }, "strip", z.ZodTypeAny, {
730
+ validUntilLedger?: number | undefined;
731
+ validUntilUnixSeconds?: number | undefined;
732
+ }, {
733
+ validUntilLedger?: number | undefined;
734
+ validUntilUnixSeconds?: number | undefined;
735
+ }>>;
620
736
  }, z.ZodTypeAny, "passthrough">>;
621
737
  ozConfig: z.ZodOptional<z.ZodObject<{
622
738
  network: z.ZodEnum<["mainnet", "testnet"]>;
@@ -1222,7 +1338,7 @@ export declare const SynthesizePolicyRecordingInputSchema: z.ZodObject<{
1222
1338
  }>;
1223
1339
  export declare const SynthesizePolicyInputSchema: z.ZodDiscriminatedUnion<"source", [z.ZodObject<{
1224
1340
  source: z.ZodLiteral<"mandate">;
1225
- mandate: z.ZodObject<{
1341
+ mandate: z.ZodEffects<z.ZodObject<{
1226
1342
  chain: z.ZodLiteral<"stellar">;
1227
1343
  contract: z.ZodString;
1228
1344
  method: z.ZodOptional<z.ZodString>;
@@ -1309,6 +1425,64 @@ export declare const SynthesizePolicyInputSchema: z.ZodDiscriminatedUnion<"sourc
1309
1425
  validUntilLedger?: number | undefined;
1310
1426
  validUntilUnixSeconds?: number | undefined;
1311
1427
  }>>;
1428
+ }, z.ZodTypeAny, "passthrough">>, z.objectOutputType<{
1429
+ chain: z.ZodLiteral<"stellar">;
1430
+ contract: z.ZodString;
1431
+ method: z.ZodOptional<z.ZodString>;
1432
+ spendingLimit: z.ZodOptional<z.ZodObject<{
1433
+ token: z.ZodString;
1434
+ limit: z.ZodString;
1435
+ windowSeconds: z.ZodNumber;
1436
+ }, "strip", z.ZodTypeAny, {
1437
+ token: string;
1438
+ limit: string;
1439
+ windowSeconds: number;
1440
+ }, {
1441
+ token: string;
1442
+ limit: string;
1443
+ windowSeconds: number;
1444
+ }>>;
1445
+ approvalThreshold: z.ZodOptional<z.ZodNumber>;
1446
+ recipients: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
1447
+ expiry: z.ZodOptional<z.ZodObject<{
1448
+ validUntilLedger: z.ZodOptional<z.ZodNumber>;
1449
+ validUntilUnixSeconds: z.ZodOptional<z.ZodNumber>;
1450
+ }, "strip", z.ZodTypeAny, {
1451
+ validUntilLedger?: number | undefined;
1452
+ validUntilUnixSeconds?: number | undefined;
1453
+ }, {
1454
+ validUntilLedger?: number | undefined;
1455
+ validUntilUnixSeconds?: number | undefined;
1456
+ }>>;
1457
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
1458
+ chain: z.ZodLiteral<"stellar">;
1459
+ contract: z.ZodString;
1460
+ method: z.ZodOptional<z.ZodString>;
1461
+ spendingLimit: z.ZodOptional<z.ZodObject<{
1462
+ token: z.ZodString;
1463
+ limit: z.ZodString;
1464
+ windowSeconds: z.ZodNumber;
1465
+ }, "strip", z.ZodTypeAny, {
1466
+ token: string;
1467
+ limit: string;
1468
+ windowSeconds: number;
1469
+ }, {
1470
+ token: string;
1471
+ limit: string;
1472
+ windowSeconds: number;
1473
+ }>>;
1474
+ approvalThreshold: z.ZodOptional<z.ZodNumber>;
1475
+ recipients: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
1476
+ expiry: z.ZodOptional<z.ZodObject<{
1477
+ validUntilLedger: z.ZodOptional<z.ZodNumber>;
1478
+ validUntilUnixSeconds: z.ZodOptional<z.ZodNumber>;
1479
+ }, "strip", z.ZodTypeAny, {
1480
+ validUntilLedger?: number | undefined;
1481
+ validUntilUnixSeconds?: number | undefined;
1482
+ }, {
1483
+ validUntilLedger?: number | undefined;
1484
+ validUntilUnixSeconds?: number | undefined;
1485
+ }>>;
1312
1486
  }, z.ZodTypeAny, "passthrough">>;
1313
1487
  ozConfig: z.ZodOptional<z.ZodObject<{
1314
1488
  network: z.ZodEnum<["mainnet", "testnet"]>;
@@ -2641,20 +2815,19 @@ export declare const VerifyPolicyInputSchema: z.ZodObject<{
2641
2815
  }> | undefined;
2642
2816
  }>;
2643
2817
  export type VerifyPolicyInput = z.infer<typeof VerifyPolicyInputSchema>;
2644
- /** Pinned interpreter address (testnet). Mirrors DEPLOYMENTS.md:128.
2818
+ /** Pinned interpreter address (testnet).
2645
2819
  * Single source for the MCP layer; do not embed elsewhere. */
2646
2820
  export declare const PINNED_INTERPRETER_TESTNET_ADDRESS = "CDR4NLV22STCXFGZPNKDQTEANWLF7LZ6AJLY6B7CLJXKHDZGYJWIOKGP";
2647
- /** Pinned interpreter address (mainnet). Mirrors DEPLOYMENTS.md:18. Mainnet
2821
+ /** Pinned interpreter address (mainnet). Mainnet
2648
2822
  * has now been deployed (2026-08-04); the mainnet interpreter IS the binary
2649
2823
  * that was exercised on testnet (same wasm sha256, see
2650
2824
  * PINNED_INTERPRETER_WASM_SHA256). The address differs because instance
2651
- * ids are network-scoped. UNAUDITED at the time of writing - see
2652
- * DEPLOYMENTS.md:29-32 for the recorded decision and what is still pending. */
2825
+ * ids are network-scoped. UNAUDITED at the time of writing. */
2653
2826
  export declare const PINNED_INTERPRETER_MAINNET_ADDRESS = "CALZAMUPREIRY4TULBEXIK77AUTOEJG63XLCPUWEHHQDOVK6ZVVS7VQ2";
2654
- /** Pinned interpreter wasm sha256 (hex). Mirrors DEPLOYMENTS.md:156-157. */
2827
+ /** Pinned interpreter wasm sha256 (hex). */
2655
2828
  export declare const PINNED_INTERPRETER_WASM_SHA256 = "6e6c13d93e197aa380303a42cd120f5ddb080dd36ef2a343ee1dbd04ca52a443";
2656
2829
  /** The grammar version the interpreter enforces (matches SELF_VERSION in
2657
- * packages/policy-interpreter/src/version.rs). */
2830
+ * contracts/policy-interpreter/src/version.rs). */
2658
2831
  export declare const PINNED_INTERPRETER_GRAMMAR_VERSION = 1;
2659
2832
  /** Default Soroban RPC for the install / revoke / info tools. The recorder
2660
2833
  * keeps its own copy in record/rpc.ts because it hands back a fetcher rather
@@ -2668,7 +2841,7 @@ export declare const TESTNET_RPC_URL = "https://soroban-testnet.stellar.org";
2668
2841
  export declare const MAINNET_RPC_URL = "https://mainnet.sorobanrpc.com";
2669
2842
  /** Pin + RPC lookup for the gate enforcement. The interpreters' wasm sha256
2670
2843
  * is identical across both networks (the same binary was uploaded both
2671
- * places; DEPLOYMENTS.md:25-27), so `PINNED_INTERPRETER_WASM_SHA256` stays
2844
+ * places), so `PINNED_INTERPRETER_WASM_SHA256` stays
2672
2845
  * a single constant - only the addresses and RPCs are network-scoped. */
2673
2846
  export declare const PINNED_INTERPRETER_ADDRESS_BY_NETWORK: Record<Network, string>;
2674
2847
  export declare const RPC_URL_BY_NETWORK: Record<Network, string>;
@@ -3403,15 +3576,24 @@ export declare const GetInterpreterInfoInputSchema: z.ZodObject<{
3403
3576
  /** When true, perform an optional live `grammar_version()` RPC call to
3404
3577
  * verify the deployed contract matches the pin. */
3405
3578
  verifyLive: z.ZodOptional<z.ZodBoolean>;
3406
- /** Optional RPC URL override. */
3579
+ /** Optional RPC URL override. When `verifyLive` is true, the URL must
3580
+ * equal the pinned RPC for the selected network unless
3581
+ * `allowUnpinnedRpcUrl: true` is also set (same opt-in shape as
3582
+ * install/revoke - the live grammar_version() answer binds to whichever
3583
+ * RPC returned it). */
3407
3584
  rpcUrl: z.ZodOptional<z.ZodString>;
3585
+ /** Opt-in to using a non-pinned RPC URL when `verifyLive` is true. Same
3586
+ * semantics as install/revoke: the caller accepts the trust shift. */
3587
+ allowUnpinnedRpcUrl: z.ZodOptional<z.ZodBoolean>;
3408
3588
  }, "strip", z.ZodTypeAny, {
3409
3589
  network?: "mainnet" | "testnet" | undefined;
3410
3590
  rpcUrl?: string | undefined;
3591
+ allowUnpinnedRpcUrl?: boolean | undefined;
3411
3592
  verifyLive?: boolean | undefined;
3412
3593
  }, {
3413
3594
  network?: "mainnet" | "testnet" | undefined;
3414
3595
  rpcUrl?: string | undefined;
3596
+ allowUnpinnedRpcUrl?: boolean | undefined;
3415
3597
  verifyLive?: boolean | undefined;
3416
3598
  }>;
3417
3599
  export type GetInterpreterInfoInput = z.infer<typeof GetInterpreterInfoInputSchema>;
@@ -8,9 +8,9 @@
8
8
  // step with the TS source of truth.
9
9
  //
10
10
  // i128 amounts and other large integers are carried as base-10 decimal strings
11
- // end-to-end (no JS number coercion). Networks are pinned to the same closed
12
- // set the core defines. The discriminated union on `source` exposes BOTH
13
- // synthesize_policy front-ends through a single tool input.
11
+ // end-to-end. Networks are pinned to the same closed set the core defines.
12
+ // The discriminated union on `source` exposes BOTH synthesize_policy
13
+ // front-ends through a single tool input.
14
14
  //
15
15
  // This module is the SINGLE source of truth for these shapes. The MCP package
16
16
  // imports them here so its tool-shape bindings stay in step; the CLI imports
@@ -126,7 +126,21 @@ exports.MandateSpecSchema = zod_1.z
126
126
  })
127
127
  .optional(),
128
128
  })
129
- .passthrough();
129
+ .passthrough()
130
+ // TS-F4/F6: a `recipients` allowlist is only meaningful against a SEP-41
131
+ // method whose arg 1 IS the recipient (SAC/SEP-41 `transfer(from, to,
132
+ // amount)` and SEP-41 `mint(to, amount)`). Without this gate the
133
+ // `to-ir.ts` lowering pins the allowlist to `RECIPIENT_ARG_INDEX = 1`
134
+ // for any contract+method, which would let a non-SEP-41 method's
135
+ // arg[1] (e.g. an amount, an op type, an arbitrary address payload)
136
+ // be silently constrained as if it were a recipient. Refusing
137
+ // non-SEP-41 methods at the boundary is the fail-closed shape.
138
+ .refine((v) => v.recipients === undefined ||
139
+ v.recipients.length === 0 ||
140
+ v.method === 'transfer' ||
141
+ v.method === 'mint', {
142
+ message: 'recipients is only valid when method is a SEP-41 method (transfer or mint); other methods do not have a recipient at arg[1]',
143
+ });
130
144
  /** ComposeUserResponses mirrors the core. */
131
145
  exports.ComposeUserResponsesSchema = zod_1.z
132
146
  .object({
@@ -267,7 +281,12 @@ exports.PredicateLeafSchema = zod_1.z.lazy(() => zod_1.z.union([
267
281
  windowSeconds: zod_1.z.number().int().positive(),
268
282
  }),
269
283
  zod_1.z.object({ kind: zod_1.z.literal('now') }),
270
- zod_1.z.object({ kind: zod_1.z.literal('valid_until') }),
284
+ // `valid_until` is NOT in the public predicate grammar: the encoder +
285
+ // decoder already throw on it, so accepting it here would let a
286
+ // hand-crafted payload through the schema and only surface a 200+
287
+ // character internal-commentary error at encode time. Drop it - the
288
+ // policy's expiry is carried at the install layer (MandateSpec +
289
+ // validUntilLedger) instead.
271
290
  zod_1.z.object({
272
291
  kind: zod_1.z.literal('invocation_count_in_window'),
273
292
  windowSecs: zod_1.z.number().int().positive(),
@@ -427,20 +446,19 @@ const MandateSpecSchemaForRule = zod_1.z
427
446
  // install/revoke/get_info schemas live HERE (after the helper) so the const
428
447
  // reference resolves at module init. They are the new tools on top of the
429
448
  // existing four.
430
- /** Pinned interpreter address (testnet). Mirrors DEPLOYMENTS.md:128.
449
+ /** Pinned interpreter address (testnet).
431
450
  * Single source for the MCP layer; do not embed elsewhere. */
432
451
  exports.PINNED_INTERPRETER_TESTNET_ADDRESS = 'CDR4NLV22STCXFGZPNKDQTEANWLF7LZ6AJLY6B7CLJXKHDZGYJWIOKGP';
433
- /** Pinned interpreter address (mainnet). Mirrors DEPLOYMENTS.md:18. Mainnet
452
+ /** Pinned interpreter address (mainnet). Mainnet
434
453
  * has now been deployed (2026-08-04); the mainnet interpreter IS the binary
435
454
  * that was exercised on testnet (same wasm sha256, see
436
455
  * PINNED_INTERPRETER_WASM_SHA256). The address differs because instance
437
- * ids are network-scoped. UNAUDITED at the time of writing - see
438
- * DEPLOYMENTS.md:29-32 for the recorded decision and what is still pending. */
456
+ * ids are network-scoped. UNAUDITED at the time of writing. */
439
457
  exports.PINNED_INTERPRETER_MAINNET_ADDRESS = 'CALZAMUPREIRY4TULBEXIK77AUTOEJG63XLCPUWEHHQDOVK6ZVVS7VQ2';
440
- /** Pinned interpreter wasm sha256 (hex). Mirrors DEPLOYMENTS.md:156-157. */
458
+ /** Pinned interpreter wasm sha256 (hex). */
441
459
  exports.PINNED_INTERPRETER_WASM_SHA256 = '6e6c13d93e197aa380303a42cd120f5ddb080dd36ef2a343ee1dbd04ca52a443';
442
460
  /** The grammar version the interpreter enforces (matches SELF_VERSION in
443
- * packages/policy-interpreter/src/version.rs). */
461
+ * contracts/policy-interpreter/src/version.rs). */
444
462
  exports.PINNED_INTERPRETER_GRAMMAR_VERSION = 1;
445
463
  /** Default Soroban RPC for the install / revoke / info tools. The recorder
446
464
  * keeps its own copy in record/rpc.ts because it hands back a fetcher rather
@@ -454,7 +472,7 @@ exports.TESTNET_RPC_URL = 'https://soroban-testnet.stellar.org';
454
472
  exports.MAINNET_RPC_URL = 'https://mainnet.sorobanrpc.com';
455
473
  /** Pin + RPC lookup for the gate enforcement. The interpreters' wasm sha256
456
474
  * is identical across both networks (the same binary was uploaded both
457
- * places; DEPLOYMENTS.md:25-27), so `PINNED_INTERPRETER_WASM_SHA256` stays
475
+ * places), so `PINNED_INTERPRETER_WASM_SHA256` stays
458
476
  * a single constant - only the addresses and RPCs are network-scoped. */
459
477
  exports.PINNED_INTERPRETER_ADDRESS_BY_NETWORK = {
460
478
  testnet: exports.PINNED_INTERPRETER_TESTNET_ADDRESS,
@@ -474,13 +492,26 @@ exports.NETWORK_PASSPHRASES = {
474
492
  testnet: 'Test SDF Network ; September 2015',
475
493
  mainnet: 'Public Global Stellar Network ; September 2015',
476
494
  };
495
+ // Stellar strkey shapes (StrKey base32, no checksum byte, version byte
496
+ // 0x30/0x40 prefixed by the decoder, then 32 base32 chars giving a 56-char
497
+ // total). The shared `isStellarAddress` helper accepts BOTH wallet (G...) and
498
+ // contract (C...) addresses; the install schema has to distinguish them
499
+ // because the smart account is a contract (C...) - a wallet address would
500
+ // mean the rule is being installed against the WRONG account kind entirely.
501
+ // `sourceAccount` is the signing wallet (G...).
502
+ const STELLAR_CONTRACT_ADDRESS = /^C[2-7A-Z]{55}$/;
503
+ const STELLAR_ACCOUNT_ADDRESS = /^G[2-7A-Z]{55}$/;
477
504
  exports.InstallPolicyInputSchema = zod_1.z
478
505
  .object({
479
506
  /** The smart account contract address (C...) that will receive the rule. */
480
- smartAccount: zod_1.z.string(),
507
+ smartAccount: zod_1.z
508
+ .string()
509
+ .regex(STELLAR_CONTRACT_ADDRESS, 'smartAccount must be a Stellar contract address (C...)'),
481
510
  /** The signer that authorises the install (G... wallet). Used only for
482
511
  * sequence number + auth nonce simulation; never persisted, never signed. */
483
- sourceAccount: zod_1.z.string(),
512
+ sourceAccount: zod_1.z
513
+ .string()
514
+ .regex(STELLAR_ACCOUNT_ADDRESS, 'sourceAccount must be a Stellar account address (G...)'),
484
515
  /** Target network for the install. Selects which interpreter pin and
485
516
  * which RPC URL are valid by default. Defaults to `testnet` so the
486
517
  * pre-mainnet callers keep working: they were always pointing at
@@ -519,12 +550,16 @@ exports.InstallPolicyInputSchema = zod_1.z
519
550
  exports.RevokePolicyInputSchema = zod_1.z
520
551
  .object({
521
552
  /** The smart account contract address (C...). */
522
- smartAccount: zod_1.z.string(),
553
+ smartAccount: zod_1.z
554
+ .string()
555
+ .regex(STELLAR_CONTRACT_ADDRESS, 'smartAccount must be a Stellar contract address (C...)'),
523
556
  /** The wallet that will sign the removal. The ACCOUNT decides whether it
524
557
  * accepts that signer; this schema does not assert a rule it cannot
525
558
  * verify, since the account's source is not in this repo. Proven on
526
559
  * testnet: the account's deployer can revoke. */
527
- sourceAccount: zod_1.z.string(),
560
+ sourceAccount: zod_1.z
561
+ .string()
562
+ .regex(STELLAR_ACCOUNT_ADDRESS, 'sourceAccount must be a Stellar account address (G...)'),
528
563
  /** Target network for the revoke. Same `testnet`-default as install,
529
564
  * so pre-mainnet callers keep working without an explicit flag. */
530
565
  network: exports.NetworkSchema.optional(),
@@ -546,8 +581,15 @@ exports.GetInterpreterInfoInputSchema = zod_1.z.object({
546
581
  /** When true, perform an optional live `grammar_version()` RPC call to
547
582
  * verify the deployed contract matches the pin. */
548
583
  verifyLive: zod_1.z.boolean().optional(),
549
- /** Optional RPC URL override. */
584
+ /** Optional RPC URL override. When `verifyLive` is true, the URL must
585
+ * equal the pinned RPC for the selected network unless
586
+ * `allowUnpinnedRpcUrl: true` is also set (same opt-in shape as
587
+ * install/revoke - the live grammar_version() answer binds to whichever
588
+ * RPC returned it). */
550
589
  rpcUrl: zod_1.z.string().url().optional(),
590
+ /** Opt-in to using a non-pinned RPC URL when `verifyLive` is true. Same
591
+ * semantics as install/revoke: the caller accepts the trust shift. */
592
+ allowUnpinnedRpcUrl: zod_1.z.boolean().optional(),
551
593
  });
552
594
  // ===== Error envelope (canonical) =====
553
595
  //
@@ -28,28 +28,23 @@ export interface ComposeUserResponses {
28
28
  * invocation_count bound; absent -> FREQUENCY_BOUND_MISSING. */
29
29
  invocationLimit?: number;
30
30
  /** Per-asset oracle-price bound(s). Each entry lowers to a single
31
- * `oracle_price(asset) OP value` compare in the interpreter IR. Multiple
32
- * entries on the same asset emit multiple leaves. */
31
+ * `oracle_price(asset) OP value` compare in the interpreter IR. */
33
32
  oraclePriceBound?: OraclePriceBound[];
34
33
  /** Minimum acceptable swap output per unit of input, as `num/den` (e.g.
35
- * `{num:'95',den:'100'}` = accept losing at most 5%). Emitted as a
36
- * slippage floor bounding the output arg against the input arg of the same
37
- * call.
38
- *
39
- * REQUIRED to be supplied by the caller: it is never derived from the
40
- * recording. The recorded in/out pair is a price at one moment, and
41
- * freezing it as policy would deny ordinary trades as soon as the rate
42
- * moves. Absent, no floor is emitted and the existing unbounded-output
43
- * warning stands. */
34
+ * `{num:'95',den:'100'}` = accept losing at most 5%). REQUIRED to be
35
+ * supplied by the caller: never derived from the recording (the recorded
36
+ * in/out pair is a price at one moment, and freezing it as policy would
37
+ * deny ordinary trades as soon as the rate moves). Absent, no floor is
38
+ * emitted and the existing unbounded-output warning stands. */
44
39
  swapMinOutRatio?: {
45
40
  num: string;
46
41
  den: string;
47
42
  };
48
43
  /** Recipient allowlist for a swap (call_arg[3] on SoroSwap's
49
- * swap_exact_tokens_for_tokens). When supplied, it REPLACES the default
50
- * pin. Absent -> the recipient is pinned to the recorded value (mirroring
51
- * SEP-41) and RECIPIENT_ALLOWLIST_EMPTY is surfaced as informational, never
52
- * a silent free pass. */
44
+ * swap_exact_tokens_for_tokens). When supplied, REPLACES the default pin.
45
+ * Absent -> recipient is pinned to the recorded value (mirroring SEP-41)
46
+ * and RECIPIENT_ALLOWLIST_EMPTY surfaces as informational, never a silent
47
+ * free pass. */
53
48
  swapRecipientAllowlist?: string[];
54
49
  }
55
50
  /** Composition options. */