@crediolabs/policy-synth 1.1.1 → 1.2.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.
- package/dist/run/index.d.ts +14 -0
- package/dist/run/index.js +69 -1
- package/dist/run/schemas.d.ts +35 -0
- package/dist/run/schemas.js +28 -0
- package/dist-cjs/run/index.d.ts +14 -0
- package/dist-cjs/run/index.js +70 -1
- package/dist-cjs/run/schemas.d.ts +35 -0
- package/dist-cjs/run/schemas.js +28 -0
- package/package.json +1 -1
- package/src/run/index.ts +73 -1
- package/src/run/schemas.ts +28 -0
package/dist/run/index.d.ts
CHANGED
|
@@ -38,6 +38,20 @@ export declare function runSynthesizePolicy(raw: unknown): Promise<ToolResponse<
|
|
|
38
38
|
predicateTree: PredicateNode | null;
|
|
39
39
|
};
|
|
40
40
|
}>;
|
|
41
|
+
/** The refusal message for an install the cross-rule scan proves cannot bind,
|
|
42
|
+
* or `undefined` when the install may proceed.
|
|
43
|
+
*
|
|
44
|
+
* Only `bypass` refuses. That class means the neighbouring rule carries NO
|
|
45
|
+
* policy, so a shared signer names it and the new predicate never runs - a
|
|
46
|
+
* proof, from data already in hand, that the rule constrains nothing.
|
|
47
|
+
* `unknown` (a neighbour policed by a contract this tool cannot decode) stays
|
|
48
|
+
* advisory: it may well be tighter, and refusing on "cannot decode" would
|
|
49
|
+
* block installs on a guess. A `null` scan is NOT CHECKED, which is not
|
|
50
|
+
* evidence of a bypass and must not refuse on its own.
|
|
51
|
+
*
|
|
52
|
+
* Separated from the tool body so the decision can be tested without a
|
|
53
|
+
* network: the install it guards cannot be built without one. */
|
|
54
|
+
export declare function authorityBypassRefusal(scan: AuthorityOverlap[] | null, allowAuthorityOverlap: boolean | undefined): string | undefined;
|
|
41
55
|
export declare function runInstallPolicy(raw: unknown): Promise<ToolResponse<BuildInstallPolicyResult & {
|
|
42
56
|
authorityScan: AuthorityOverlap[] | null;
|
|
43
57
|
}>>;
|
package/dist/run/index.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
// No business logic. No retries. No session state. The same call shape can
|
|
18
18
|
// drive the CLI (which calls into the same core directly without MCP).
|
|
19
19
|
import { createHash } from 'node:crypto';
|
|
20
|
+
import { readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
20
21
|
import { rpc } from '@stellar/stellar-sdk';
|
|
21
22
|
import { PLACEHOLDER_INTERPRETER_ADDRESS } from "../adapters/interpreter/adapter.js";
|
|
22
23
|
import { declarePredicate, encodePredicate, recordTransaction, synthesizeFromRecording, } from "../index.js";
|
|
@@ -128,6 +129,28 @@ export async function runSynthesizePolicy(raw) {
|
|
|
128
129
|
return toolFailure('synthesize_policy', e);
|
|
129
130
|
}
|
|
130
131
|
}
|
|
132
|
+
/** The refusal message for an install the cross-rule scan proves cannot bind,
|
|
133
|
+
* or `undefined` when the install may proceed.
|
|
134
|
+
*
|
|
135
|
+
* Only `bypass` refuses. That class means the neighbouring rule carries NO
|
|
136
|
+
* policy, so a shared signer names it and the new predicate never runs - a
|
|
137
|
+
* proof, from data already in hand, that the rule constrains nothing.
|
|
138
|
+
* `unknown` (a neighbour policed by a contract this tool cannot decode) stays
|
|
139
|
+
* advisory: it may well be tighter, and refusing on "cannot decode" would
|
|
140
|
+
* block installs on a guess. A `null` scan is NOT CHECKED, which is not
|
|
141
|
+
* evidence of a bypass and must not refuse on its own.
|
|
142
|
+
*
|
|
143
|
+
* Separated from the tool body so the decision can be tested without a
|
|
144
|
+
* network: the install it guards cannot be built without one. */
|
|
145
|
+
export function authorityBypassRefusal(scan, allowAuthorityOverlap) {
|
|
146
|
+
if (scan === null || allowAuthorityOverlap === true)
|
|
147
|
+
return undefined;
|
|
148
|
+
const proven = scan.filter((o) => o.severity === 'bypass');
|
|
149
|
+
if (proven.length === 0)
|
|
150
|
+
return undefined;
|
|
151
|
+
const ids = proven.map((o) => o.ruleId).join(', ');
|
|
152
|
+
return `install_policy: ${proven[0]?.advice ?? ''} This rule would install cleanly and constrain nothing, so it is refused (rule ${ids}); remove the shared signer from that rule, attach a policy to it, or set \`allowAuthorityOverlap: true\` to install anyway.`;
|
|
153
|
+
}
|
|
131
154
|
export async function runInstallPolicy(raw) {
|
|
132
155
|
const parsed = InstallPolicyInputSchema.safeParse(raw);
|
|
133
156
|
if (!parsed.success) {
|
|
@@ -330,7 +353,52 @@ export async function runInstallPolicy(raw) {
|
|
|
330
353
|
},
|
|
331
354
|
existing: observed,
|
|
332
355
|
});
|
|
333
|
-
|
|
356
|
+
// A `bypass` overlap is not a warning, it is a proof that this rule cannot
|
|
357
|
+
// bind the key it names: the neighbour carries NO policy, so the signer
|
|
358
|
+
// names that rule instead and the predicate never runs. Returning `ok` with
|
|
359
|
+
// the finding buried in `authorityScan` puts the whole protection on the
|
|
360
|
+
// caller reading a field, and the caller here is usually an agent that
|
|
361
|
+
// checks whether the call succeeded. Refuse, and let the caller opt in.
|
|
362
|
+
//
|
|
363
|
+
// Only the provable class. `unknown` - a neighbour whose policy this tool
|
|
364
|
+
// cannot decode - stays advisory: it may well be tighter, and refusing on
|
|
365
|
+
// "cannot decode" would block installs on a guess.
|
|
366
|
+
const bypassRefusal = authorityBypassRefusal(authorityScan, input.allowAuthorityOverlap);
|
|
367
|
+
if (bypassRefusal !== undefined) {
|
|
368
|
+
return {
|
|
369
|
+
ok: false,
|
|
370
|
+
error: {
|
|
371
|
+
code: 'INSTALL_BUILD_FAILED',
|
|
372
|
+
message: bypassRefusal,
|
|
373
|
+
severity: 'error',
|
|
374
|
+
retryable: false,
|
|
375
|
+
remediation: { toolCall: { name: 'install_policy', args: {} } },
|
|
376
|
+
},
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
// Write the envelope here when asked, so it never travels through the
|
|
380
|
+
// caller. `writtenTo` is what the caller should hand to a signer.
|
|
381
|
+
let writtenTo;
|
|
382
|
+
if (input.outPath !== undefined) {
|
|
383
|
+
// Write beside the target and rename, which is atomic within a
|
|
384
|
+
// filesystem. A plain write truncates first, so anything watching the
|
|
385
|
+
// directory - a signer picking up envelopes is the obvious case - can
|
|
386
|
+
// read a half-written file and report a malformed TRANSACTION. With a
|
|
387
|
+
// rename the path either does not exist or holds the whole envelope.
|
|
388
|
+
const staging = `${input.outPath}.partial`;
|
|
389
|
+
await writeFile(staging, result.unsignedXdr, 'utf8');
|
|
390
|
+
const readBack = await readFile(staging, 'utf8');
|
|
391
|
+
if (readBack !== result.unsignedXdr) {
|
|
392
|
+
await rm(staging, { force: true });
|
|
393
|
+
throw new Error(`outPath: wrote ${result.unsignedXdr.length} characters to ${input.outPath} but read back ${readBack.length}; the file was not persisted intact`);
|
|
394
|
+
}
|
|
395
|
+
await rename(staging, input.outPath);
|
|
396
|
+
writtenTo = input.outPath;
|
|
397
|
+
}
|
|
398
|
+
return {
|
|
399
|
+
ok: true,
|
|
400
|
+
data: { ...result, authorityScan, ...(writtenTo !== undefined ? { writtenTo } : {}) },
|
|
401
|
+
};
|
|
334
402
|
}
|
|
335
403
|
catch (e) {
|
|
336
404
|
return toolFailure('install_policy', e);
|
package/dist/run/schemas.d.ts
CHANGED
|
@@ -3366,6 +3366,17 @@ export declare const InstallPolicyInputSchema: z.ZodEffects<z.ZodEffects<z.ZodOb
|
|
|
3366
3366
|
* knows. Supply it only to re-install over an existing rule, where the
|
|
3367
3367
|
* interpreter wants `stored_nonce + 1`. */
|
|
3368
3368
|
installNonce: z.ZodOptional<z.ZodNumber>;
|
|
3369
|
+
/** Absolute path to write the unsigned envelope to.
|
|
3370
|
+
*
|
|
3371
|
+
* The envelope runs to several thousand characters, and without this the
|
|
3372
|
+
* only route onto disk is the CALLER re-emitting it - through a model, a
|
|
3373
|
+
* shell argument, or both. That transport mangles it: observed in practice
|
|
3374
|
+
* as a file of the right length whose bytes no longer parse
|
|
3375
|
+
* ("xdr padding contains non-zero bytes"), and as silent truncation.
|
|
3376
|
+
* Writing it here takes the caller out of the transport entirely.
|
|
3377
|
+
*
|
|
3378
|
+
* Opt-in: omitted, nothing is written and behaviour is unchanged. */
|
|
3379
|
+
outPath: z.ZodOptional<z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>>;
|
|
3369
3380
|
/** Optional RPC URL override. Defaults to the pinned RPC for the
|
|
3370
3381
|
* selected `network` (testnet by default, mainnet when
|
|
3371
3382
|
* `network: 'mainnet'`); the override is refused unless
|
|
@@ -3411,6 +3422,18 @@ export declare const InstallPolicyInputSchema: z.ZodEffects<z.ZodEffects<z.ZodOb
|
|
|
3411
3422
|
* unaffected; only the case the synthesizer explicitly flagged is
|
|
3412
3423
|
* refused. */
|
|
3413
3424
|
allowUnboundedAmount: z.ZodOptional<z.ZodBoolean>;
|
|
3425
|
+
/** Opt-in to installing a rule that the cross-rule scan proves cannot bind.
|
|
3426
|
+
*
|
|
3427
|
+
* An OZ account resolves a call against the rule the caller NAMES, so a
|
|
3428
|
+
* key on several rules gets the MAXIMUM authority over them, never the
|
|
3429
|
+
* intersection. A key that also sits on a rule with no policy is therefore
|
|
3430
|
+
* unconstrained: it names that rule and the new predicate never runs.
|
|
3431
|
+
*
|
|
3432
|
+
* Default-deny, and only for the case the scan can PROVE - an unpoliced
|
|
3433
|
+
* neighbour sharing signers and selectors. A neighbour carrying a policy
|
|
3434
|
+
* this tool cannot decode stays advisory, because "cannot decode" is not
|
|
3435
|
+
* "proved unsafe" and refusing it would block installs on a guess. */
|
|
3436
|
+
allowAuthorityOverlap: z.ZodOptional<z.ZodBoolean>;
|
|
3414
3437
|
/** Opt-in to pointing the rule's interpreter policy at any address
|
|
3415
3438
|
* other than the pinned interpreter for the selected network.
|
|
3416
3439
|
* Default-deny: a caller that controls the interpreter can permit
|
|
@@ -3549,9 +3572,11 @@ export declare const InstallPolicyInputSchema: z.ZodEffects<z.ZodEffects<z.ZodOb
|
|
|
3549
3572
|
validUntilLedger?: number | undefined;
|
|
3550
3573
|
name?: string | undefined;
|
|
3551
3574
|
} | undefined;
|
|
3575
|
+
outPath?: string | undefined;
|
|
3552
3576
|
rpcUrl?: string | undefined;
|
|
3553
3577
|
allowUnpinnedRpcUrl?: boolean | undefined;
|
|
3554
3578
|
allowUnboundedAmount?: boolean | undefined;
|
|
3579
|
+
allowAuthorityOverlap?: boolean | undefined;
|
|
3555
3580
|
allowUnpinnedInterpreter?: boolean | undefined;
|
|
3556
3581
|
baseFee?: number | undefined;
|
|
3557
3582
|
}, {
|
|
@@ -3684,9 +3709,11 @@ export declare const InstallPolicyInputSchema: z.ZodEffects<z.ZodEffects<z.ZodOb
|
|
|
3684
3709
|
validUntilLedger?: number | undefined;
|
|
3685
3710
|
name?: string | undefined;
|
|
3686
3711
|
} | undefined;
|
|
3712
|
+
outPath?: string | undefined;
|
|
3687
3713
|
rpcUrl?: string | undefined;
|
|
3688
3714
|
allowUnpinnedRpcUrl?: boolean | undefined;
|
|
3689
3715
|
allowUnboundedAmount?: boolean | undefined;
|
|
3716
|
+
allowAuthorityOverlap?: boolean | undefined;
|
|
3690
3717
|
allowUnpinnedInterpreter?: boolean | undefined;
|
|
3691
3718
|
baseFee?: number | undefined;
|
|
3692
3719
|
}>, {
|
|
@@ -3819,9 +3846,11 @@ export declare const InstallPolicyInputSchema: z.ZodEffects<z.ZodEffects<z.ZodOb
|
|
|
3819
3846
|
validUntilLedger?: number | undefined;
|
|
3820
3847
|
name?: string | undefined;
|
|
3821
3848
|
} | undefined;
|
|
3849
|
+
outPath?: string | undefined;
|
|
3822
3850
|
rpcUrl?: string | undefined;
|
|
3823
3851
|
allowUnpinnedRpcUrl?: boolean | undefined;
|
|
3824
3852
|
allowUnboundedAmount?: boolean | undefined;
|
|
3853
|
+
allowAuthorityOverlap?: boolean | undefined;
|
|
3825
3854
|
allowUnpinnedInterpreter?: boolean | undefined;
|
|
3826
3855
|
baseFee?: number | undefined;
|
|
3827
3856
|
}, {
|
|
@@ -3954,9 +3983,11 @@ export declare const InstallPolicyInputSchema: z.ZodEffects<z.ZodEffects<z.ZodOb
|
|
|
3954
3983
|
validUntilLedger?: number | undefined;
|
|
3955
3984
|
name?: string | undefined;
|
|
3956
3985
|
} | undefined;
|
|
3986
|
+
outPath?: string | undefined;
|
|
3957
3987
|
rpcUrl?: string | undefined;
|
|
3958
3988
|
allowUnpinnedRpcUrl?: boolean | undefined;
|
|
3959
3989
|
allowUnboundedAmount?: boolean | undefined;
|
|
3990
|
+
allowAuthorityOverlap?: boolean | undefined;
|
|
3960
3991
|
allowUnpinnedInterpreter?: boolean | undefined;
|
|
3961
3992
|
baseFee?: number | undefined;
|
|
3962
3993
|
}>, {
|
|
@@ -4089,9 +4120,11 @@ export declare const InstallPolicyInputSchema: z.ZodEffects<z.ZodEffects<z.ZodOb
|
|
|
4089
4120
|
validUntilLedger?: number | undefined;
|
|
4090
4121
|
name?: string | undefined;
|
|
4091
4122
|
} | undefined;
|
|
4123
|
+
outPath?: string | undefined;
|
|
4092
4124
|
rpcUrl?: string | undefined;
|
|
4093
4125
|
allowUnpinnedRpcUrl?: boolean | undefined;
|
|
4094
4126
|
allowUnboundedAmount?: boolean | undefined;
|
|
4127
|
+
allowAuthorityOverlap?: boolean | undefined;
|
|
4095
4128
|
allowUnpinnedInterpreter?: boolean | undefined;
|
|
4096
4129
|
baseFee?: number | undefined;
|
|
4097
4130
|
}, {
|
|
@@ -4224,9 +4257,11 @@ export declare const InstallPolicyInputSchema: z.ZodEffects<z.ZodEffects<z.ZodOb
|
|
|
4224
4257
|
validUntilLedger?: number | undefined;
|
|
4225
4258
|
name?: string | undefined;
|
|
4226
4259
|
} | undefined;
|
|
4260
|
+
outPath?: string | undefined;
|
|
4227
4261
|
rpcUrl?: string | undefined;
|
|
4228
4262
|
allowUnpinnedRpcUrl?: boolean | undefined;
|
|
4229
4263
|
allowUnboundedAmount?: boolean | undefined;
|
|
4264
|
+
allowAuthorityOverlap?: boolean | undefined;
|
|
4230
4265
|
allowUnpinnedInterpreter?: boolean | undefined;
|
|
4231
4266
|
baseFee?: number | undefined;
|
|
4232
4267
|
}>;
|
package/dist/run/schemas.js
CHANGED
|
@@ -623,6 +623,22 @@ export const InstallPolicyInputSchema = z
|
|
|
623
623
|
* knows. Supply it only to re-install over an existing rule, where the
|
|
624
624
|
* interpreter wants `stored_nonce + 1`. */
|
|
625
625
|
installNonce: z.number().int().positive().optional(),
|
|
626
|
+
/** Absolute path to write the unsigned envelope to.
|
|
627
|
+
*
|
|
628
|
+
* The envelope runs to several thousand characters, and without this the
|
|
629
|
+
* only route onto disk is the CALLER re-emitting it - through a model, a
|
|
630
|
+
* shell argument, or both. That transport mangles it: observed in practice
|
|
631
|
+
* as a file of the right length whose bytes no longer parse
|
|
632
|
+
* ("xdr padding contains non-zero bytes"), and as silent truncation.
|
|
633
|
+
* Writing it here takes the caller out of the transport entirely.
|
|
634
|
+
*
|
|
635
|
+
* Opt-in: omitted, nothing is written and behaviour is unchanged. */
|
|
636
|
+
outPath: z
|
|
637
|
+
.string()
|
|
638
|
+
.min(1)
|
|
639
|
+
.refine((p) => p.startsWith('/'), 'outPath must be an absolute path')
|
|
640
|
+
.refine((p) => !p.includes('\0'), 'outPath must not contain a null byte')
|
|
641
|
+
.optional(),
|
|
626
642
|
/** Optional RPC URL override. Defaults to the pinned RPC for the
|
|
627
643
|
* selected `network` (testnet by default, mainnet when
|
|
628
644
|
* `network: 'mainnet'`); the override is refused unless
|
|
@@ -666,6 +682,18 @@ export const InstallPolicyInputSchema = z
|
|
|
666
682
|
* unaffected; only the case the synthesizer explicitly flagged is
|
|
667
683
|
* refused. */
|
|
668
684
|
allowUnboundedAmount: z.boolean().optional(),
|
|
685
|
+
/** Opt-in to installing a rule that the cross-rule scan proves cannot bind.
|
|
686
|
+
*
|
|
687
|
+
* An OZ account resolves a call against the rule the caller NAMES, so a
|
|
688
|
+
* key on several rules gets the MAXIMUM authority over them, never the
|
|
689
|
+
* intersection. A key that also sits on a rule with no policy is therefore
|
|
690
|
+
* unconstrained: it names that rule and the new predicate never runs.
|
|
691
|
+
*
|
|
692
|
+
* Default-deny, and only for the case the scan can PROVE - an unpoliced
|
|
693
|
+
* neighbour sharing signers and selectors. A neighbour carrying a policy
|
|
694
|
+
* this tool cannot decode stays advisory, because "cannot decode" is not
|
|
695
|
+
* "proved unsafe" and refusing it would block installs on a guess. */
|
|
696
|
+
allowAuthorityOverlap: z.boolean().optional(),
|
|
669
697
|
/** Opt-in to pointing the rule's interpreter policy at any address
|
|
670
698
|
* other than the pinned interpreter for the selected network.
|
|
671
699
|
* Default-deny: a caller that controls the interpreter can permit
|
package/dist-cjs/run/index.d.ts
CHANGED
|
@@ -38,6 +38,20 @@ export declare function runSynthesizePolicy(raw: unknown): Promise<ToolResponse<
|
|
|
38
38
|
predicateTree: PredicateNode | null;
|
|
39
39
|
};
|
|
40
40
|
}>;
|
|
41
|
+
/** The refusal message for an install the cross-rule scan proves cannot bind,
|
|
42
|
+
* or `undefined` when the install may proceed.
|
|
43
|
+
*
|
|
44
|
+
* Only `bypass` refuses. That class means the neighbouring rule carries NO
|
|
45
|
+
* policy, so a shared signer names it and the new predicate never runs - a
|
|
46
|
+
* proof, from data already in hand, that the rule constrains nothing.
|
|
47
|
+
* `unknown` (a neighbour policed by a contract this tool cannot decode) stays
|
|
48
|
+
* advisory: it may well be tighter, and refusing on "cannot decode" would
|
|
49
|
+
* block installs on a guess. A `null` scan is NOT CHECKED, which is not
|
|
50
|
+
* evidence of a bypass and must not refuse on its own.
|
|
51
|
+
*
|
|
52
|
+
* Separated from the tool body so the decision can be tested without a
|
|
53
|
+
* network: the install it guards cannot be built without one. */
|
|
54
|
+
export declare function authorityBypassRefusal(scan: AuthorityOverlap[] | null, allowAuthorityOverlap: boolean | undefined): string | undefined;
|
|
41
55
|
export declare function runInstallPolicy(raw: unknown): Promise<ToolResponse<BuildInstallPolicyResult & {
|
|
42
56
|
authorityScan: AuthorityOverlap[] | null;
|
|
43
57
|
}>>;
|
package/dist-cjs/run/index.js
CHANGED
|
@@ -21,6 +21,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
21
21
|
exports.ToolErrorSchema = exports.TESTNET_RPC_URL = exports.SynthesizePolicyInputSchema = exports.RPC_URL_BY_NETWORK = exports.RevokePolicyInputSchema = exports.RecordTransactionInputSchema = exports.RecordedTransactionSchema = exports.PredicateNodeSchema = exports.PredicateLeafSchema = exports.PINNED_OZ_POLICY_WASM_SHA256 = exports.PINNED_OZ_POLICY_ADDRESS_BY_NETWORK = exports.PINNED_INTERPRETER_WASM_SHA256 = exports.PINNED_INTERPRETER_TESTNET_ADDRESS = exports.PINNED_INTERPRETER_MAINNET_ADDRESS = exports.PINNED_INTERPRETER_GRAMMAR_VERSION = exports.PINNED_INTERPRETER_ADDRESS_BY_NETWORK = exports.NetworkSchema = exports.MAINNET_RPC_URL = exports.InterpreterOptionsSchema = exports.InstallPolicyInputSchema = exports.GetInterpreterInfoInputSchema = exports.DeclarePolicyInputSchema = exports.ComposeUserResponsesSchema = void 0;
|
|
22
22
|
exports.runRecordTransaction = runRecordTransaction;
|
|
23
23
|
exports.runSynthesizePolicy = runSynthesizePolicy;
|
|
24
|
+
exports.authorityBypassRefusal = authorityBypassRefusal;
|
|
24
25
|
exports.runInstallPolicy = runInstallPolicy;
|
|
25
26
|
exports.runRevokePolicy = runRevokePolicy;
|
|
26
27
|
exports.contextTypeForPredicate = contextTypeForPredicate;
|
|
@@ -30,6 +31,7 @@ exports.runVerifyPolicy = runVerifyPolicy;
|
|
|
30
31
|
exports.runGetInterpreterInfo = runGetInterpreterInfo;
|
|
31
32
|
exports.caughtError = caughtError;
|
|
32
33
|
const node_crypto_1 = require("node:crypto");
|
|
34
|
+
const promises_1 = require("node:fs/promises");
|
|
33
35
|
const stellar_sdk_1 = require("@stellar/stellar-sdk");
|
|
34
36
|
const adapter_ts_1 = require("../adapters/interpreter/adapter.js");
|
|
35
37
|
const index_ts_1 = require("../index.js");
|
|
@@ -164,6 +166,28 @@ async function runSynthesizePolicy(raw) {
|
|
|
164
166
|
return toolFailure('synthesize_policy', e);
|
|
165
167
|
}
|
|
166
168
|
}
|
|
169
|
+
/** The refusal message for an install the cross-rule scan proves cannot bind,
|
|
170
|
+
* or `undefined` when the install may proceed.
|
|
171
|
+
*
|
|
172
|
+
* Only `bypass` refuses. That class means the neighbouring rule carries NO
|
|
173
|
+
* policy, so a shared signer names it and the new predicate never runs - a
|
|
174
|
+
* proof, from data already in hand, that the rule constrains nothing.
|
|
175
|
+
* `unknown` (a neighbour policed by a contract this tool cannot decode) stays
|
|
176
|
+
* advisory: it may well be tighter, and refusing on "cannot decode" would
|
|
177
|
+
* block installs on a guess. A `null` scan is NOT CHECKED, which is not
|
|
178
|
+
* evidence of a bypass and must not refuse on its own.
|
|
179
|
+
*
|
|
180
|
+
* Separated from the tool body so the decision can be tested without a
|
|
181
|
+
* network: the install it guards cannot be built without one. */
|
|
182
|
+
function authorityBypassRefusal(scan, allowAuthorityOverlap) {
|
|
183
|
+
if (scan === null || allowAuthorityOverlap === true)
|
|
184
|
+
return undefined;
|
|
185
|
+
const proven = scan.filter((o) => o.severity === 'bypass');
|
|
186
|
+
if (proven.length === 0)
|
|
187
|
+
return undefined;
|
|
188
|
+
const ids = proven.map((o) => o.ruleId).join(', ');
|
|
189
|
+
return `install_policy: ${proven[0]?.advice ?? ''} This rule would install cleanly and constrain nothing, so it is refused (rule ${ids}); remove the shared signer from that rule, attach a policy to it, or set \`allowAuthorityOverlap: true\` to install anyway.`;
|
|
190
|
+
}
|
|
167
191
|
async function runInstallPolicy(raw) {
|
|
168
192
|
const parsed = schemas_ts_1.InstallPolicyInputSchema.safeParse(raw);
|
|
169
193
|
if (!parsed.success) {
|
|
@@ -366,7 +390,52 @@ async function runInstallPolicy(raw) {
|
|
|
366
390
|
},
|
|
367
391
|
existing: observed,
|
|
368
392
|
});
|
|
369
|
-
|
|
393
|
+
// A `bypass` overlap is not a warning, it is a proof that this rule cannot
|
|
394
|
+
// bind the key it names: the neighbour carries NO policy, so the signer
|
|
395
|
+
// names that rule instead and the predicate never runs. Returning `ok` with
|
|
396
|
+
// the finding buried in `authorityScan` puts the whole protection on the
|
|
397
|
+
// caller reading a field, and the caller here is usually an agent that
|
|
398
|
+
// checks whether the call succeeded. Refuse, and let the caller opt in.
|
|
399
|
+
//
|
|
400
|
+
// Only the provable class. `unknown` - a neighbour whose policy this tool
|
|
401
|
+
// cannot decode - stays advisory: it may well be tighter, and refusing on
|
|
402
|
+
// "cannot decode" would block installs on a guess.
|
|
403
|
+
const bypassRefusal = authorityBypassRefusal(authorityScan, input.allowAuthorityOverlap);
|
|
404
|
+
if (bypassRefusal !== undefined) {
|
|
405
|
+
return {
|
|
406
|
+
ok: false,
|
|
407
|
+
error: {
|
|
408
|
+
code: 'INSTALL_BUILD_FAILED',
|
|
409
|
+
message: bypassRefusal,
|
|
410
|
+
severity: 'error',
|
|
411
|
+
retryable: false,
|
|
412
|
+
remediation: { toolCall: { name: 'install_policy', args: {} } },
|
|
413
|
+
},
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
// Write the envelope here when asked, so it never travels through the
|
|
417
|
+
// caller. `writtenTo` is what the caller should hand to a signer.
|
|
418
|
+
let writtenTo;
|
|
419
|
+
if (input.outPath !== undefined) {
|
|
420
|
+
// Write beside the target and rename, which is atomic within a
|
|
421
|
+
// filesystem. A plain write truncates first, so anything watching the
|
|
422
|
+
// directory - a signer picking up envelopes is the obvious case - can
|
|
423
|
+
// read a half-written file and report a malformed TRANSACTION. With a
|
|
424
|
+
// rename the path either does not exist or holds the whole envelope.
|
|
425
|
+
const staging = `${input.outPath}.partial`;
|
|
426
|
+
await (0, promises_1.writeFile)(staging, result.unsignedXdr, 'utf8');
|
|
427
|
+
const readBack = await (0, promises_1.readFile)(staging, 'utf8');
|
|
428
|
+
if (readBack !== result.unsignedXdr) {
|
|
429
|
+
await (0, promises_1.rm)(staging, { force: true });
|
|
430
|
+
throw new Error(`outPath: wrote ${result.unsignedXdr.length} characters to ${input.outPath} but read back ${readBack.length}; the file was not persisted intact`);
|
|
431
|
+
}
|
|
432
|
+
await (0, promises_1.rename)(staging, input.outPath);
|
|
433
|
+
writtenTo = input.outPath;
|
|
434
|
+
}
|
|
435
|
+
return {
|
|
436
|
+
ok: true,
|
|
437
|
+
data: { ...result, authorityScan, ...(writtenTo !== undefined ? { writtenTo } : {}) },
|
|
438
|
+
};
|
|
370
439
|
}
|
|
371
440
|
catch (e) {
|
|
372
441
|
return toolFailure('install_policy', e);
|
|
@@ -3366,6 +3366,17 @@ export declare const InstallPolicyInputSchema: z.ZodEffects<z.ZodEffects<z.ZodOb
|
|
|
3366
3366
|
* knows. Supply it only to re-install over an existing rule, where the
|
|
3367
3367
|
* interpreter wants `stored_nonce + 1`. */
|
|
3368
3368
|
installNonce: z.ZodOptional<z.ZodNumber>;
|
|
3369
|
+
/** Absolute path to write the unsigned envelope to.
|
|
3370
|
+
*
|
|
3371
|
+
* The envelope runs to several thousand characters, and without this the
|
|
3372
|
+
* only route onto disk is the CALLER re-emitting it - through a model, a
|
|
3373
|
+
* shell argument, or both. That transport mangles it: observed in practice
|
|
3374
|
+
* as a file of the right length whose bytes no longer parse
|
|
3375
|
+
* ("xdr padding contains non-zero bytes"), and as silent truncation.
|
|
3376
|
+
* Writing it here takes the caller out of the transport entirely.
|
|
3377
|
+
*
|
|
3378
|
+
* Opt-in: omitted, nothing is written and behaviour is unchanged. */
|
|
3379
|
+
outPath: z.ZodOptional<z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>>;
|
|
3369
3380
|
/** Optional RPC URL override. Defaults to the pinned RPC for the
|
|
3370
3381
|
* selected `network` (testnet by default, mainnet when
|
|
3371
3382
|
* `network: 'mainnet'`); the override is refused unless
|
|
@@ -3411,6 +3422,18 @@ export declare const InstallPolicyInputSchema: z.ZodEffects<z.ZodEffects<z.ZodOb
|
|
|
3411
3422
|
* unaffected; only the case the synthesizer explicitly flagged is
|
|
3412
3423
|
* refused. */
|
|
3413
3424
|
allowUnboundedAmount: z.ZodOptional<z.ZodBoolean>;
|
|
3425
|
+
/** Opt-in to installing a rule that the cross-rule scan proves cannot bind.
|
|
3426
|
+
*
|
|
3427
|
+
* An OZ account resolves a call against the rule the caller NAMES, so a
|
|
3428
|
+
* key on several rules gets the MAXIMUM authority over them, never the
|
|
3429
|
+
* intersection. A key that also sits on a rule with no policy is therefore
|
|
3430
|
+
* unconstrained: it names that rule and the new predicate never runs.
|
|
3431
|
+
*
|
|
3432
|
+
* Default-deny, and only for the case the scan can PROVE - an unpoliced
|
|
3433
|
+
* neighbour sharing signers and selectors. A neighbour carrying a policy
|
|
3434
|
+
* this tool cannot decode stays advisory, because "cannot decode" is not
|
|
3435
|
+
* "proved unsafe" and refusing it would block installs on a guess. */
|
|
3436
|
+
allowAuthorityOverlap: z.ZodOptional<z.ZodBoolean>;
|
|
3414
3437
|
/** Opt-in to pointing the rule's interpreter policy at any address
|
|
3415
3438
|
* other than the pinned interpreter for the selected network.
|
|
3416
3439
|
* Default-deny: a caller that controls the interpreter can permit
|
|
@@ -3549,9 +3572,11 @@ export declare const InstallPolicyInputSchema: z.ZodEffects<z.ZodEffects<z.ZodOb
|
|
|
3549
3572
|
validUntilLedger?: number | undefined;
|
|
3550
3573
|
name?: string | undefined;
|
|
3551
3574
|
} | undefined;
|
|
3575
|
+
outPath?: string | undefined;
|
|
3552
3576
|
rpcUrl?: string | undefined;
|
|
3553
3577
|
allowUnpinnedRpcUrl?: boolean | undefined;
|
|
3554
3578
|
allowUnboundedAmount?: boolean | undefined;
|
|
3579
|
+
allowAuthorityOverlap?: boolean | undefined;
|
|
3555
3580
|
allowUnpinnedInterpreter?: boolean | undefined;
|
|
3556
3581
|
baseFee?: number | undefined;
|
|
3557
3582
|
}, {
|
|
@@ -3684,9 +3709,11 @@ export declare const InstallPolicyInputSchema: z.ZodEffects<z.ZodEffects<z.ZodOb
|
|
|
3684
3709
|
validUntilLedger?: number | undefined;
|
|
3685
3710
|
name?: string | undefined;
|
|
3686
3711
|
} | undefined;
|
|
3712
|
+
outPath?: string | undefined;
|
|
3687
3713
|
rpcUrl?: string | undefined;
|
|
3688
3714
|
allowUnpinnedRpcUrl?: boolean | undefined;
|
|
3689
3715
|
allowUnboundedAmount?: boolean | undefined;
|
|
3716
|
+
allowAuthorityOverlap?: boolean | undefined;
|
|
3690
3717
|
allowUnpinnedInterpreter?: boolean | undefined;
|
|
3691
3718
|
baseFee?: number | undefined;
|
|
3692
3719
|
}>, {
|
|
@@ -3819,9 +3846,11 @@ export declare const InstallPolicyInputSchema: z.ZodEffects<z.ZodEffects<z.ZodOb
|
|
|
3819
3846
|
validUntilLedger?: number | undefined;
|
|
3820
3847
|
name?: string | undefined;
|
|
3821
3848
|
} | undefined;
|
|
3849
|
+
outPath?: string | undefined;
|
|
3822
3850
|
rpcUrl?: string | undefined;
|
|
3823
3851
|
allowUnpinnedRpcUrl?: boolean | undefined;
|
|
3824
3852
|
allowUnboundedAmount?: boolean | undefined;
|
|
3853
|
+
allowAuthorityOverlap?: boolean | undefined;
|
|
3825
3854
|
allowUnpinnedInterpreter?: boolean | undefined;
|
|
3826
3855
|
baseFee?: number | undefined;
|
|
3827
3856
|
}, {
|
|
@@ -3954,9 +3983,11 @@ export declare const InstallPolicyInputSchema: z.ZodEffects<z.ZodEffects<z.ZodOb
|
|
|
3954
3983
|
validUntilLedger?: number | undefined;
|
|
3955
3984
|
name?: string | undefined;
|
|
3956
3985
|
} | undefined;
|
|
3986
|
+
outPath?: string | undefined;
|
|
3957
3987
|
rpcUrl?: string | undefined;
|
|
3958
3988
|
allowUnpinnedRpcUrl?: boolean | undefined;
|
|
3959
3989
|
allowUnboundedAmount?: boolean | undefined;
|
|
3990
|
+
allowAuthorityOverlap?: boolean | undefined;
|
|
3960
3991
|
allowUnpinnedInterpreter?: boolean | undefined;
|
|
3961
3992
|
baseFee?: number | undefined;
|
|
3962
3993
|
}>, {
|
|
@@ -4089,9 +4120,11 @@ export declare const InstallPolicyInputSchema: z.ZodEffects<z.ZodEffects<z.ZodOb
|
|
|
4089
4120
|
validUntilLedger?: number | undefined;
|
|
4090
4121
|
name?: string | undefined;
|
|
4091
4122
|
} | undefined;
|
|
4123
|
+
outPath?: string | undefined;
|
|
4092
4124
|
rpcUrl?: string | undefined;
|
|
4093
4125
|
allowUnpinnedRpcUrl?: boolean | undefined;
|
|
4094
4126
|
allowUnboundedAmount?: boolean | undefined;
|
|
4127
|
+
allowAuthorityOverlap?: boolean | undefined;
|
|
4095
4128
|
allowUnpinnedInterpreter?: boolean | undefined;
|
|
4096
4129
|
baseFee?: number | undefined;
|
|
4097
4130
|
}, {
|
|
@@ -4224,9 +4257,11 @@ export declare const InstallPolicyInputSchema: z.ZodEffects<z.ZodEffects<z.ZodOb
|
|
|
4224
4257
|
validUntilLedger?: number | undefined;
|
|
4225
4258
|
name?: string | undefined;
|
|
4226
4259
|
} | undefined;
|
|
4260
|
+
outPath?: string | undefined;
|
|
4227
4261
|
rpcUrl?: string | undefined;
|
|
4228
4262
|
allowUnpinnedRpcUrl?: boolean | undefined;
|
|
4229
4263
|
allowUnboundedAmount?: boolean | undefined;
|
|
4264
|
+
allowAuthorityOverlap?: boolean | undefined;
|
|
4230
4265
|
allowUnpinnedInterpreter?: boolean | undefined;
|
|
4231
4266
|
baseFee?: number | undefined;
|
|
4232
4267
|
}>;
|
package/dist-cjs/run/schemas.js
CHANGED
|
@@ -626,6 +626,22 @@ exports.InstallPolicyInputSchema = zod_1.z
|
|
|
626
626
|
* knows. Supply it only to re-install over an existing rule, where the
|
|
627
627
|
* interpreter wants `stored_nonce + 1`. */
|
|
628
628
|
installNonce: zod_1.z.number().int().positive().optional(),
|
|
629
|
+
/** Absolute path to write the unsigned envelope to.
|
|
630
|
+
*
|
|
631
|
+
* The envelope runs to several thousand characters, and without this the
|
|
632
|
+
* only route onto disk is the CALLER re-emitting it - through a model, a
|
|
633
|
+
* shell argument, or both. That transport mangles it: observed in practice
|
|
634
|
+
* as a file of the right length whose bytes no longer parse
|
|
635
|
+
* ("xdr padding contains non-zero bytes"), and as silent truncation.
|
|
636
|
+
* Writing it here takes the caller out of the transport entirely.
|
|
637
|
+
*
|
|
638
|
+
* Opt-in: omitted, nothing is written and behaviour is unchanged. */
|
|
639
|
+
outPath: zod_1.z
|
|
640
|
+
.string()
|
|
641
|
+
.min(1)
|
|
642
|
+
.refine((p) => p.startsWith('/'), 'outPath must be an absolute path')
|
|
643
|
+
.refine((p) => !p.includes('\0'), 'outPath must not contain a null byte')
|
|
644
|
+
.optional(),
|
|
629
645
|
/** Optional RPC URL override. Defaults to the pinned RPC for the
|
|
630
646
|
* selected `network` (testnet by default, mainnet when
|
|
631
647
|
* `network: 'mainnet'`); the override is refused unless
|
|
@@ -669,6 +685,18 @@ exports.InstallPolicyInputSchema = zod_1.z
|
|
|
669
685
|
* unaffected; only the case the synthesizer explicitly flagged is
|
|
670
686
|
* refused. */
|
|
671
687
|
allowUnboundedAmount: zod_1.z.boolean().optional(),
|
|
688
|
+
/** Opt-in to installing a rule that the cross-rule scan proves cannot bind.
|
|
689
|
+
*
|
|
690
|
+
* An OZ account resolves a call against the rule the caller NAMES, so a
|
|
691
|
+
* key on several rules gets the MAXIMUM authority over them, never the
|
|
692
|
+
* intersection. A key that also sits on a rule with no policy is therefore
|
|
693
|
+
* unconstrained: it names that rule and the new predicate never runs.
|
|
694
|
+
*
|
|
695
|
+
* Default-deny, and only for the case the scan can PROVE - an unpoliced
|
|
696
|
+
* neighbour sharing signers and selectors. A neighbour carrying a policy
|
|
697
|
+
* this tool cannot decode stays advisory, because "cannot decode" is not
|
|
698
|
+
* "proved unsafe" and refusing it would block installs on a guess. */
|
|
699
|
+
allowAuthorityOverlap: zod_1.z.boolean().optional(),
|
|
672
700
|
/** Opt-in to pointing the rule's interpreter policy at any address
|
|
673
701
|
* other than the pinned interpreter for the selected network.
|
|
674
702
|
* Default-deny: a caller that controls the interpreter can permit
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crediolabs/policy-synth",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Off-chain TypeScript synthesis core for the OZ Accounts Policy Builder. Records Soroban transactions, synthesises the minimal policy that permits exactly that flow, verifies it, and returns an unsigned install transaction.",
|
|
6
6
|
"type": "module",
|
package/src/run/index.ts
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
// drive the CLI (which calls into the same core directly without MCP).
|
|
19
19
|
|
|
20
20
|
import { createHash } from 'node:crypto'
|
|
21
|
+
import { readFile, rename, rm, writeFile } from 'node:fs/promises'
|
|
21
22
|
import { rpc } from '@stellar/stellar-sdk'
|
|
22
23
|
import { PLACEHOLDER_INTERPRETER_ADDRESS } from '../adapters/interpreter/adapter.ts'
|
|
23
24
|
import {
|
|
@@ -236,6 +237,30 @@ export async function runSynthesizePolicy(raw: unknown): Promise<
|
|
|
236
237
|
}
|
|
237
238
|
}
|
|
238
239
|
|
|
240
|
+
/** The refusal message for an install the cross-rule scan proves cannot bind,
|
|
241
|
+
* or `undefined` when the install may proceed.
|
|
242
|
+
*
|
|
243
|
+
* Only `bypass` refuses. That class means the neighbouring rule carries NO
|
|
244
|
+
* policy, so a shared signer names it and the new predicate never runs - a
|
|
245
|
+
* proof, from data already in hand, that the rule constrains nothing.
|
|
246
|
+
* `unknown` (a neighbour policed by a contract this tool cannot decode) stays
|
|
247
|
+
* advisory: it may well be tighter, and refusing on "cannot decode" would
|
|
248
|
+
* block installs on a guess. A `null` scan is NOT CHECKED, which is not
|
|
249
|
+
* evidence of a bypass and must not refuse on its own.
|
|
250
|
+
*
|
|
251
|
+
* Separated from the tool body so the decision can be tested without a
|
|
252
|
+
* network: the install it guards cannot be built without one. */
|
|
253
|
+
export function authorityBypassRefusal(
|
|
254
|
+
scan: AuthorityOverlap[] | null,
|
|
255
|
+
allowAuthorityOverlap: boolean | undefined
|
|
256
|
+
): string | undefined {
|
|
257
|
+
if (scan === null || allowAuthorityOverlap === true) return undefined
|
|
258
|
+
const proven = scan.filter((o) => o.severity === 'bypass')
|
|
259
|
+
if (proven.length === 0) return undefined
|
|
260
|
+
const ids = proven.map((o) => o.ruleId).join(', ')
|
|
261
|
+
return `install_policy: ${proven[0]?.advice ?? ''} This rule would install cleanly and constrain nothing, so it is refused (rule ${ids}); remove the shared signer from that rule, attach a policy to it, or set \`allowAuthorityOverlap: true\` to install anyway.`
|
|
262
|
+
}
|
|
263
|
+
|
|
239
264
|
export async function runInstallPolicy(
|
|
240
265
|
raw: unknown
|
|
241
266
|
): Promise<ToolResponse<BuildInstallPolicyResult & { authorityScan: AuthorityOverlap[] | null }>> {
|
|
@@ -453,7 +478,54 @@ export async function runInstallPolicy(
|
|
|
453
478
|
},
|
|
454
479
|
existing: observed,
|
|
455
480
|
})
|
|
456
|
-
|
|
481
|
+
// A `bypass` overlap is not a warning, it is a proof that this rule cannot
|
|
482
|
+
// bind the key it names: the neighbour carries NO policy, so the signer
|
|
483
|
+
// names that rule instead and the predicate never runs. Returning `ok` with
|
|
484
|
+
// the finding buried in `authorityScan` puts the whole protection on the
|
|
485
|
+
// caller reading a field, and the caller here is usually an agent that
|
|
486
|
+
// checks whether the call succeeded. Refuse, and let the caller opt in.
|
|
487
|
+
//
|
|
488
|
+
// Only the provable class. `unknown` - a neighbour whose policy this tool
|
|
489
|
+
// cannot decode - stays advisory: it may well be tighter, and refusing on
|
|
490
|
+
// "cannot decode" would block installs on a guess.
|
|
491
|
+
const bypassRefusal = authorityBypassRefusal(authorityScan, input.allowAuthorityOverlap)
|
|
492
|
+
if (bypassRefusal !== undefined) {
|
|
493
|
+
return {
|
|
494
|
+
ok: false,
|
|
495
|
+
error: {
|
|
496
|
+
code: 'INSTALL_BUILD_FAILED',
|
|
497
|
+
message: bypassRefusal,
|
|
498
|
+
severity: 'error',
|
|
499
|
+
retryable: false,
|
|
500
|
+
remediation: { toolCall: { name: 'install_policy', args: {} } },
|
|
501
|
+
},
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
// Write the envelope here when asked, so it never travels through the
|
|
505
|
+
// caller. `writtenTo` is what the caller should hand to a signer.
|
|
506
|
+
let writtenTo: string | undefined
|
|
507
|
+
if (input.outPath !== undefined) {
|
|
508
|
+
// Write beside the target and rename, which is atomic within a
|
|
509
|
+
// filesystem. A plain write truncates first, so anything watching the
|
|
510
|
+
// directory - a signer picking up envelopes is the obvious case - can
|
|
511
|
+
// read a half-written file and report a malformed TRANSACTION. With a
|
|
512
|
+
// rename the path either does not exist or holds the whole envelope.
|
|
513
|
+
const staging = `${input.outPath}.partial`
|
|
514
|
+
await writeFile(staging, result.unsignedXdr, 'utf8')
|
|
515
|
+
const readBack = await readFile(staging, 'utf8')
|
|
516
|
+
if (readBack !== result.unsignedXdr) {
|
|
517
|
+
await rm(staging, { force: true })
|
|
518
|
+
throw new Error(
|
|
519
|
+
`outPath: wrote ${result.unsignedXdr.length} characters to ${input.outPath} but read back ${readBack.length}; the file was not persisted intact`
|
|
520
|
+
)
|
|
521
|
+
}
|
|
522
|
+
await rename(staging, input.outPath)
|
|
523
|
+
writtenTo = input.outPath
|
|
524
|
+
}
|
|
525
|
+
return {
|
|
526
|
+
ok: true,
|
|
527
|
+
data: { ...result, authorityScan, ...(writtenTo !== undefined ? { writtenTo } : {}) },
|
|
528
|
+
}
|
|
457
529
|
} catch (e) {
|
|
458
530
|
return toolFailure('install_policy', e)
|
|
459
531
|
}
|
package/src/run/schemas.ts
CHANGED
|
@@ -713,6 +713,22 @@ export const InstallPolicyInputSchema = z
|
|
|
713
713
|
* knows. Supply it only to re-install over an existing rule, where the
|
|
714
714
|
* interpreter wants `stored_nonce + 1`. */
|
|
715
715
|
installNonce: z.number().int().positive().optional(),
|
|
716
|
+
/** Absolute path to write the unsigned envelope to.
|
|
717
|
+
*
|
|
718
|
+
* The envelope runs to several thousand characters, and without this the
|
|
719
|
+
* only route onto disk is the CALLER re-emitting it - through a model, a
|
|
720
|
+
* shell argument, or both. That transport mangles it: observed in practice
|
|
721
|
+
* as a file of the right length whose bytes no longer parse
|
|
722
|
+
* ("xdr padding contains non-zero bytes"), and as silent truncation.
|
|
723
|
+
* Writing it here takes the caller out of the transport entirely.
|
|
724
|
+
*
|
|
725
|
+
* Opt-in: omitted, nothing is written and behaviour is unchanged. */
|
|
726
|
+
outPath: z
|
|
727
|
+
.string()
|
|
728
|
+
.min(1)
|
|
729
|
+
.refine((p) => p.startsWith('/'), 'outPath must be an absolute path')
|
|
730
|
+
.refine((p) => !p.includes('\0'), 'outPath must not contain a null byte')
|
|
731
|
+
.optional(),
|
|
716
732
|
/** Optional RPC URL override. Defaults to the pinned RPC for the
|
|
717
733
|
* selected `network` (testnet by default, mainnet when
|
|
718
734
|
* `network: 'mainnet'`); the override is refused unless
|
|
@@ -756,6 +772,18 @@ export const InstallPolicyInputSchema = z
|
|
|
756
772
|
* unaffected; only the case the synthesizer explicitly flagged is
|
|
757
773
|
* refused. */
|
|
758
774
|
allowUnboundedAmount: z.boolean().optional(),
|
|
775
|
+
/** Opt-in to installing a rule that the cross-rule scan proves cannot bind.
|
|
776
|
+
*
|
|
777
|
+
* An OZ account resolves a call against the rule the caller NAMES, so a
|
|
778
|
+
* key on several rules gets the MAXIMUM authority over them, never the
|
|
779
|
+
* intersection. A key that also sits on a rule with no policy is therefore
|
|
780
|
+
* unconstrained: it names that rule and the new predicate never runs.
|
|
781
|
+
*
|
|
782
|
+
* Default-deny, and only for the case the scan can PROVE - an unpoliced
|
|
783
|
+
* neighbour sharing signers and selectors. A neighbour carrying a policy
|
|
784
|
+
* this tool cannot decode stays advisory, because "cannot decode" is not
|
|
785
|
+
* "proved unsafe" and refusing it would block installs on a guess. */
|
|
786
|
+
allowAuthorityOverlap: z.boolean().optional(),
|
|
759
787
|
/** Opt-in to pointing the rule's interpreter policy at any address
|
|
760
788
|
* other than the pinned interpreter for the selected network.
|
|
761
789
|
* Default-deny: a caller that controls the interpreter can permit
|