@crediolabs/policy-builder-cli 0.1.18 → 0.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/README.md CHANGED
@@ -58,9 +58,11 @@ small: the two commands that fit a pipe.
58
58
 
59
59
  ## Security model
60
60
 
61
- The CLI holds no key material and signs nothing. The on-chain interpreter the
62
- policies target is **unaudited**; see the
63
- [architecture document](https://github.com/untangledfinance/octogate/blob/main/docs/architecture.md).
61
+ The CLI holds no key material and signs nothing. See the
62
+ [architecture document](https://github.com/untangledfinance/octogate/blob/main/docs/architecture.md)
63
+ for what the on-chain interpreter does and does not enforce, and the
64
+ [repository README](https://github.com/untangledfinance/octogate#readme) for
65
+ the contracts' audit status.
64
66
 
65
67
  ## License
66
68
 
@@ -26,6 +26,7 @@
26
26
  // `command + subcommand-flags + global-flags` and dispatches to the matching
27
27
  // command body. Bad args -> CliError -> non-zero exit (JSON envelope under
28
28
  // --json).
29
+ import { runMergeCommand } from "../src/commands/merge.js";
29
30
  import { runRecordCommand } from "../src/commands/record.js";
30
31
  import { runSynthesizeCommand } from "../src/commands/synthesize.js";
31
32
  import { emitCliError, parseFlags } from "../src/output.js";
@@ -47,6 +48,10 @@ async function main() {
47
48
  await runSynthesizeCommand(subcommandArgs, flags);
48
49
  return;
49
50
  }
51
+ case 'merge': {
52
+ await runMergeCommand(subcommandArgs, flags);
53
+ return;
54
+ }
50
55
  case 'help':
51
56
  case '--help':
52
57
  case '-h':
@@ -68,6 +73,10 @@ function printHelp() {
68
73
 
69
74
  Usage:
70
75
  policy-builder record --network <mainnet|testnet> --hash <tx> | --xdr <b64>
76
+ policy-builder merge --smart-account <C...> --source-account <G...>
77
+ --rule-id <n> --predicate <b64> --step <detach|reinstall>
78
+ [--network <mainnet|testnet>] [--rpc-url <url>]
79
+ [--json] [--quiet] [--out <path>]
71
80
  [--json] [--quiet] [--out <path>]
72
81
  policy-builder synthesize --mandate <path.json>
73
82
  [--oz-config <path.json>] [--confidence <0..1>]
@@ -0,0 +1,2 @@
1
+ import { type CliFlags } from '../output.ts';
2
+ export declare function runMergeCommand(argv: ReadonlyArray<string>, flags: CliFlags): Promise<import("@crediolabs/policy-synth/run").MergePolicyResult>;
@@ -0,0 +1,58 @@
1
+ // packages/policy-builder-cli/src/commands/merge.ts
2
+ //
3
+ // `policy-builder merge` subcommand. The tightening remedy for a cross-rule
4
+ // authority overlap.
5
+ //
6
+ // OpenZeppelin enforces only the policies of the context rule a signer NAMES,
7
+ // so a signer in several rules picks which one applies and their authority is
8
+ // the maximum over the matching rules. Adding a second, tighter rule therefore
9
+ // restricts nothing. To actually tighten, the restriction has to become ONE
10
+ // predicate: the conjunction of the installed one and the new one.
11
+ //
12
+ // Two transactions, in order, because `add_policy` refuses a policy already
13
+ // attached to the rule:
14
+ //
15
+ // policy-builder merge --step detach ... sign and submit, wait
16
+ // policy-builder merge --step reinstall ... sign and submit
17
+ //
18
+ // The detach uninstalls the policy, which resets every counter on the rule and
19
+ // leaves it unpoliced until the reinstall confirms. Both are reported as
20
+ // warnings rather than left for the operator to discover afterwards.
21
+ import { runMergePolicy } from '@crediolabs/policy-synth/run';
22
+ import { CliError, formatToolResponse, parsePairs } from "../output.js";
23
+ function missing(message) {
24
+ return new CliError({
25
+ code: 'CLI_MISSING_ARG',
26
+ message,
27
+ severity: 'error',
28
+ retryable: false,
29
+ });
30
+ }
31
+ function required(pairs, name) {
32
+ const value = pairs[name];
33
+ if (!value)
34
+ throw missing(`merge: --${name} is required`);
35
+ return value;
36
+ }
37
+ export async function runMergeCommand(argv, flags) {
38
+ const pairs = parsePairs(argv);
39
+ const step = required(pairs, 'step');
40
+ if (step !== 'detach' && step !== 'reinstall') {
41
+ throw missing("merge: --step must be 'detach' or 'reinstall'");
42
+ }
43
+ const ruleId = Number(required(pairs, 'rule-id'));
44
+ if (!Number.isInteger(ruleId) || ruleId < 0) {
45
+ throw missing('merge: --rule-id must be a non-negative integer');
46
+ }
47
+ const res = await runMergePolicy({
48
+ smartAccount: required(pairs, 'smart-account'),
49
+ sourceAccount: required(pairs, 'source-account'),
50
+ ruleId,
51
+ incomingPredicateBlobBase64: required(pairs, 'predicate'),
52
+ step,
53
+ ...(pairs.network ? { network: pairs.network } : {}),
54
+ ...(pairs['rpc-url'] ? { rpcUrl: pairs['rpc-url'] } : {}),
55
+ ...(pairs['allow-unpinned-rpc-url'] === 'true' ? { allowUnpinnedRpcUrl: true } : {}),
56
+ });
57
+ return formatToolResponse(res, flags);
58
+ }
@@ -1,3 +1,4 @@
1
+ export { runMergeCommand } from './commands/merge.ts';
1
2
  export { runRecordCommand } from './commands/record.ts';
2
3
  export { runSynthesizeCommand } from './commands/synthesize.ts';
3
4
  export { formatToolResponse, readJsonFile, writeJsonFile } from './output.ts';
package/dist/src/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // packages/policy-builder-cli/src/index.ts - public re-exports for the CLI package.
2
+ export { runMergeCommand } from "./commands/merge.js";
2
3
  export { runRecordCommand } from "./commands/record.js";
3
4
  export { runSynthesizeCommand } from "./commands/synthesize.js";
4
5
  export { formatToolResponse, readJsonFile, writeJsonFile } from "./output.js";
@@ -0,0 +1,2 @@
1
+ import { type CliFlags } from '../output.ts';
2
+ export declare function runMergeCommand(argv: ReadonlyArray<string>, flags: CliFlags): Promise<import("@crediolabs/policy-synth/run").MergePolicyResult>;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ // packages/policy-builder-cli/src/commands/merge.ts
3
+ //
4
+ // `policy-builder merge` subcommand. The tightening remedy for a cross-rule
5
+ // authority overlap.
6
+ //
7
+ // OpenZeppelin enforces only the policies of the context rule a signer NAMES,
8
+ // so a signer in several rules picks which one applies and their authority is
9
+ // the maximum over the matching rules. Adding a second, tighter rule therefore
10
+ // restricts nothing. To actually tighten, the restriction has to become ONE
11
+ // predicate: the conjunction of the installed one and the new one.
12
+ //
13
+ // Two transactions, in order, because `add_policy` refuses a policy already
14
+ // attached to the rule:
15
+ //
16
+ // policy-builder merge --step detach ... sign and submit, wait
17
+ // policy-builder merge --step reinstall ... sign and submit
18
+ //
19
+ // The detach uninstalls the policy, which resets every counter on the rule and
20
+ // leaves it unpoliced until the reinstall confirms. Both are reported as
21
+ // warnings rather than left for the operator to discover afterwards.
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.runMergeCommand = runMergeCommand;
24
+ const run_1 = require("@crediolabs/policy-synth/run");
25
+ const output_ts_1 = require("../output.js");
26
+ function missing(message) {
27
+ return new output_ts_1.CliError({
28
+ code: 'CLI_MISSING_ARG',
29
+ message,
30
+ severity: 'error',
31
+ retryable: false,
32
+ });
33
+ }
34
+ function required(pairs, name) {
35
+ const value = pairs[name];
36
+ if (!value)
37
+ throw missing(`merge: --${name} is required`);
38
+ return value;
39
+ }
40
+ async function runMergeCommand(argv, flags) {
41
+ const pairs = (0, output_ts_1.parsePairs)(argv);
42
+ const step = required(pairs, 'step');
43
+ if (step !== 'detach' && step !== 'reinstall') {
44
+ throw missing("merge: --step must be 'detach' or 'reinstall'");
45
+ }
46
+ const ruleId = Number(required(pairs, 'rule-id'));
47
+ if (!Number.isInteger(ruleId) || ruleId < 0) {
48
+ throw missing('merge: --rule-id must be a non-negative integer');
49
+ }
50
+ const res = await (0, run_1.runMergePolicy)({
51
+ smartAccount: required(pairs, 'smart-account'),
52
+ sourceAccount: required(pairs, 'source-account'),
53
+ ruleId,
54
+ incomingPredicateBlobBase64: required(pairs, 'predicate'),
55
+ step,
56
+ ...(pairs.network ? { network: pairs.network } : {}),
57
+ ...(pairs['rpc-url'] ? { rpcUrl: pairs['rpc-url'] } : {}),
58
+ ...(pairs['allow-unpinned-rpc-url'] === 'true' ? { allowUnpinnedRpcUrl: true } : {}),
59
+ });
60
+ return (0, output_ts_1.formatToolResponse)(res, flags);
61
+ }
@@ -1,3 +1,4 @@
1
+ export { runMergeCommand } from './commands/merge.ts';
1
2
  export { runRecordCommand } from './commands/record.ts';
2
3
  export { runSynthesizeCommand } from './commands/synthesize.ts';
3
4
  export { formatToolResponse, readJsonFile, writeJsonFile } from './output.ts';
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  // packages/policy-builder-cli/src/index.ts - public re-exports for the CLI package.
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.writeJsonFile = exports.readJsonFile = exports.formatToolResponse = exports.runSynthesizeCommand = exports.runRecordCommand = void 0;
4
+ exports.writeJsonFile = exports.readJsonFile = exports.formatToolResponse = exports.runSynthesizeCommand = exports.runRecordCommand = exports.runMergeCommand = void 0;
5
+ var merge_ts_1 = require("./commands/merge.js");
6
+ Object.defineProperty(exports, "runMergeCommand", { enumerable: true, get: function () { return merge_ts_1.runMergeCommand; } });
5
7
  var record_ts_1 = require("./commands/record.js");
6
8
  Object.defineProperty(exports, "runRecordCommand", { enumerable: true, get: function () { return record_ts_1.runRecordCommand; } });
7
9
  var synthesize_ts_1 = require("./commands/synthesize.js");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crediolabs/policy-builder-cli",
3
- "version": "0.1.18",
3
+ "version": "0.2.0",
4
4
  "license": "MIT",
5
5
  "description": "CLI wrapper around the OZ policy-synth core (record + synthesize) for solo-dev and CI workflows.",
6
6
  "type": "module",
@@ -63,7 +63,7 @@
63
63
  "prepack": "bun run build"
64
64
  },
65
65
  "dependencies": {
66
- "@crediolabs/policy-synth": "0.1.18",
66
+ "@crediolabs/policy-synth": "0.2.0",
67
67
  "@stellar/stellar-sdk": "14.4.0",
68
68
  "zod": "3.25.76"
69
69
  },
@@ -0,0 +1,65 @@
1
+ // packages/policy-builder-cli/src/commands/merge.ts
2
+ //
3
+ // `policy-builder merge` subcommand. The tightening remedy for a cross-rule
4
+ // authority overlap.
5
+ //
6
+ // OpenZeppelin enforces only the policies of the context rule a signer NAMES,
7
+ // so a signer in several rules picks which one applies and their authority is
8
+ // the maximum over the matching rules. Adding a second, tighter rule therefore
9
+ // restricts nothing. To actually tighten, the restriction has to become ONE
10
+ // predicate: the conjunction of the installed one and the new one.
11
+ //
12
+ // Two transactions, in order, because `add_policy` refuses a policy already
13
+ // attached to the rule:
14
+ //
15
+ // policy-builder merge --step detach ... sign and submit, wait
16
+ // policy-builder merge --step reinstall ... sign and submit
17
+ //
18
+ // The detach uninstalls the policy, which resets every counter on the rule and
19
+ // leaves it unpoliced until the reinstall confirms. Both are reported as
20
+ // warnings rather than left for the operator to discover afterwards.
21
+
22
+ import { runMergePolicy } from '@crediolabs/policy-synth/run'
23
+ import { CliError, type CliFlags, formatToolResponse, parsePairs } from '../output.ts'
24
+
25
+ function missing(message: string): CliError {
26
+ return new CliError({
27
+ code: 'CLI_MISSING_ARG',
28
+ message,
29
+ severity: 'error',
30
+ retryable: false,
31
+ })
32
+ }
33
+
34
+ function required(pairs: Record<string, string>, name: string): string {
35
+ const value = pairs[name]
36
+ if (!value) throw missing(`merge: --${name} is required`)
37
+ return value
38
+ }
39
+
40
+ export async function runMergeCommand(argv: ReadonlyArray<string>, flags: CliFlags) {
41
+ const pairs = parsePairs(argv)
42
+
43
+ const step = required(pairs, 'step')
44
+ if (step !== 'detach' && step !== 'reinstall') {
45
+ throw missing("merge: --step must be 'detach' or 'reinstall'")
46
+ }
47
+
48
+ const ruleId = Number(required(pairs, 'rule-id'))
49
+ if (!Number.isInteger(ruleId) || ruleId < 0) {
50
+ throw missing('merge: --rule-id must be a non-negative integer')
51
+ }
52
+
53
+ const res = await runMergePolicy({
54
+ smartAccount: required(pairs, 'smart-account'),
55
+ sourceAccount: required(pairs, 'source-account'),
56
+ ruleId,
57
+ incomingPredicateBlobBase64: required(pairs, 'predicate'),
58
+ step,
59
+ ...(pairs.network ? { network: pairs.network } : {}),
60
+ ...(pairs['rpc-url'] ? { rpcUrl: pairs['rpc-url'] } : {}),
61
+ ...(pairs['allow-unpinned-rpc-url'] === 'true' ? { allowUnpinnedRpcUrl: true } : {}),
62
+ })
63
+
64
+ return formatToolResponse(res, flags)
65
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  // packages/policy-builder-cli/src/index.ts - public re-exports for the CLI package.
2
2
 
3
+ export { runMergeCommand } from './commands/merge.ts'
3
4
  export { runRecordCommand } from './commands/record.ts'
4
5
  export { runSynthesizeCommand } from './commands/synthesize.ts'
5
6
  export { formatToolResponse, readJsonFile, writeJsonFile } from './output.ts'