@crediolabs/policy-synth 0.3.0 → 0.4.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/install/authority-overlap.d.ts +101 -0
- package/dist/install/authority-overlap.js +227 -0
- package/dist/install/index.d.ts +1 -0
- package/dist/install/index.js +4 -0
- package/dist/record/freshness.d.ts +17 -11
- package/dist/record/freshness.js +28 -18
- package/dist/record/index.d.ts +10 -0
- package/dist/record/index.js +32 -1
- package/dist/record/rpc.d.ts +4 -0
- package/dist/record/rpc.js +4 -1
- package/dist/registry/identify.d.ts +10 -1
- package/dist/registry/identify.js +4 -1
- package/dist/registry/on-chain-spec.d.ts +37 -0
- package/dist/registry/on-chain-spec.js +152 -0
- package/dist/run/index.d.ts +25 -4
- package/dist/run/index.js +65 -4
- package/dist/run/schemas.d.ts +313 -0
- package/dist/run/schemas.js +52 -0
- package/dist/synth/declare.d.ts +30 -0
- package/dist/synth/declare.js +98 -0
- package/dist/synth/index.d.ts +1 -0
- package/dist/synth/index.js +1 -0
- package/dist-cjs/install/authority-overlap.d.ts +101 -0
- package/dist-cjs/install/authority-overlap.js +236 -0
- package/dist-cjs/install/index.d.ts +1 -0
- package/dist-cjs/install/index.js +13 -2
- package/dist-cjs/record/freshness.d.ts +17 -11
- package/dist-cjs/record/freshness.js +28 -18
- package/dist-cjs/record/index.d.ts +10 -0
- package/dist-cjs/record/index.js +31 -0
- package/dist-cjs/record/rpc.d.ts +4 -0
- package/dist-cjs/record/rpc.js +7 -3
- package/dist-cjs/registry/identify.d.ts +10 -1
- package/dist-cjs/registry/identify.js +4 -0
- package/dist-cjs/registry/on-chain-spec.d.ts +37 -0
- package/dist-cjs/registry/on-chain-spec.js +159 -0
- package/dist-cjs/run/index.d.ts +25 -4
- package/dist-cjs/run/index.js +65 -2
- package/dist-cjs/run/schemas.d.ts +313 -0
- package/dist-cjs/run/schemas.js +53 -1
- package/dist-cjs/synth/declare.d.ts +30 -0
- package/dist-cjs/synth/declare.js +101 -0
- package/dist-cjs/synth/index.d.ts +1 -0
- package/dist-cjs/synth/index.js +3 -1
- package/package.json +2 -2
- package/src/install/authority-overlap.ts +312 -0
- package/src/install/index.ts +20 -0
- package/src/record/freshness.ts +29 -18
- package/src/record/index.ts +59 -2
- package/src/record/rpc.ts +4 -1
- package/src/registry/identify.ts +4 -1
- package/src/registry/on-chain-spec.ts +168 -0
- package/src/run/index.ts +79 -2
- package/src/run/schemas.ts +57 -0
- package/src/synth/declare.ts +157 -0
- package/src/synth/index.ts +5 -0
- package/dist/adapters/interpreter/index.d.ts +0 -1
- package/dist/adapters/interpreter/index.js +0 -2
- package/dist/adapters/oz/adapter.d.ts +0 -20
- package/dist/adapters/oz/adapter.js +0 -281
- package/dist/adapters/oz/index.d.ts +0 -1
- package/dist/adapters/oz/index.js +0 -2
- package/dist/codegen/compile-gate.d.ts +0 -33
- package/dist/codegen/compile-gate.js +0 -124
- package/dist/codegen/index.d.ts +0 -2
- package/dist/codegen/index.js +0 -8
- package/dist/codegen/template.d.ts +0 -18
- package/dist/codegen/template.js +0 -148
- package/dist/ir/index.d.ts +0 -1
- package/dist/ir/index.js +0 -2
- package/dist/ir/types.d.ts +0 -71
- package/dist/ir/types.js +0 -9
- package/dist/mandate/index.d.ts +0 -2
- package/dist/mandate/index.js +0 -2
- package/dist/mandate/to-ir.d.ts +0 -3
- package/dist/mandate/to-ir.js +0 -60
- package/dist/mandate/types.d.ts +0 -20
- package/dist/mandate/types.js +0 -8
- package/dist/seams/index.d.ts +0 -1
- package/dist/seams/index.js +0 -2
- package/dist/seams/types.d.ts +0 -64
- package/dist/seams/types.js +0 -11
- package/dist/synth/deny-cases.d.ts +0 -28
- package/dist/synth/deny-cases.js +0 -212
- package/dist/synth/evaluate.d.ts +0 -18
- package/dist/synth/evaluate.js +0 -271
- package/dist/synth/harness.d.ts +0 -28
- package/dist/synth/harness.js +0 -47
- package/dist/synth/minimize.d.ts +0 -4
- package/dist/synth/minimize.js +0 -38
- package/dist/synth/permit-context.d.ts +0 -8
- package/dist/synth/permit-context.js +0 -35
- package/dist/synth/predicate-literals.d.ts +0 -5
- package/dist/synth/predicate-literals.js +0 -25
- package/dist/synth/synthesize-from-mandate.d.ts +0 -20
- package/dist/synth/synthesize-from-mandate.js +0 -59
- package/dist/verify/envelope.d.ts +0 -15
- package/dist/verify/envelope.js +0 -22
- package/dist/verify/index.d.ts +0 -3
- package/dist/verify/index.js +0 -3
- package/dist/verify/simulate.d.ts +0 -18
- package/dist/verify/simulate.js +0 -172
- package/dist/verify/verify.d.ts +0 -15
- package/dist/verify/verify.js +0 -181
- package/dist-cjs/adapters/interpreter/index.d.ts +0 -1
- package/dist-cjs/adapters/interpreter/index.js +0 -7
- package/dist-cjs/adapters/oz/adapter.d.ts +0 -20
- package/dist-cjs/adapters/oz/adapter.js +0 -286
- package/dist-cjs/adapters/oz/index.d.ts +0 -1
- package/dist-cjs/adapters/oz/index.js +0 -8
- package/dist-cjs/codegen/compile-gate.d.ts +0 -33
- package/dist-cjs/codegen/compile-gate.js +0 -128
- package/dist-cjs/codegen/index.d.ts +0 -2
- package/dist-cjs/codegen/index.js +0 -14
- package/dist-cjs/codegen/template.d.ts +0 -18
- package/dist-cjs/codegen/template.js +0 -151
- package/dist-cjs/ir/index.d.ts +0 -1
- package/dist-cjs/ir/index.js +0 -3
- package/dist-cjs/ir/types.d.ts +0 -71
- package/dist-cjs/ir/types.js +0 -10
- package/dist-cjs/mandate/index.d.ts +0 -2
- package/dist-cjs/mandate/index.js +0 -6
- package/dist-cjs/mandate/to-ir.d.ts +0 -3
- package/dist-cjs/mandate/to-ir.js +0 -63
- package/dist-cjs/mandate/types.d.ts +0 -20
- package/dist-cjs/mandate/types.js +0 -9
- package/dist-cjs/seams/index.d.ts +0 -1
- package/dist-cjs/seams/index.js +0 -3
- package/dist-cjs/seams/types.d.ts +0 -64
- package/dist-cjs/seams/types.js +0 -12
- package/dist-cjs/synth/deny-cases.d.ts +0 -28
- package/dist-cjs/synth/deny-cases.js +0 -217
- package/dist-cjs/synth/evaluate.d.ts +0 -18
- package/dist-cjs/synth/evaluate.js +0 -274
- package/dist-cjs/synth/harness.d.ts +0 -28
- package/dist-cjs/synth/harness.js +0 -50
- package/dist-cjs/synth/minimize.d.ts +0 -4
- package/dist-cjs/synth/minimize.js +0 -41
- package/dist-cjs/synth/permit-context.d.ts +0 -8
- package/dist-cjs/synth/permit-context.js +0 -38
- package/dist-cjs/synth/predicate-literals.d.ts +0 -5
- package/dist-cjs/synth/predicate-literals.js +0 -28
- package/dist-cjs/synth/synthesize-from-mandate.d.ts +0 -20
- package/dist-cjs/synth/synthesize-from-mandate.js +0 -62
- package/dist-cjs/verify/envelope.d.ts +0 -15
- package/dist-cjs/verify/envelope.js +0 -23
- package/dist-cjs/verify/index.d.ts +0 -3
- package/dist-cjs/verify/index.js +0 -8
- package/dist-cjs/verify/simulate.d.ts +0 -18
- package/dist-cjs/verify/simulate.js +0 -175
- package/dist-cjs/verify/verify.d.ts +0 -15
- package/dist-cjs/verify/verify.js +0 -184
package/dist/run/schemas.js
CHANGED
|
@@ -246,6 +246,26 @@ export const VerifyPolicyInputSchema = SimulatePolicyInputSchema;
|
|
|
246
246
|
* the temporal dead zone (no JS hoisting for `const`). */
|
|
247
247
|
const MAX_SIGNERS_PER_RULE = 15;
|
|
248
248
|
const MAX_POLICIES_PER_RULE = 5;
|
|
249
|
+
const SignerDraftSchema = z.discriminatedUnion('kind', [
|
|
250
|
+
z.object({ kind: z.literal('delegated'), address: z.string() }),
|
|
251
|
+
z.object({ kind: z.literal('external'), verifier: z.string(), keyBytes: z.string() }),
|
|
252
|
+
]);
|
|
253
|
+
const ContextTypeSchema = z.discriminatedUnion('kind', [
|
|
254
|
+
z.object({ kind: z.literal('default') }),
|
|
255
|
+
z.object({ kind: z.literal('call_contract'), contract: z.string() }),
|
|
256
|
+
z.object({ kind: z.literal('create_contract'), wasmHash: z.string() }),
|
|
257
|
+
]);
|
|
258
|
+
/** A rule ALREADY on the account, as the caller observed it. Supplying these
|
|
259
|
+
* turns on the cross-rule authority scan: a signer belonging to several rules
|
|
260
|
+
* picks which one applies, so a predicate only constrains a key when the
|
|
261
|
+
* policed rule is the only rule that key is on. */
|
|
262
|
+
export const ObservedRuleSchema = z.object({
|
|
263
|
+
id: z.number().int().nonnegative(),
|
|
264
|
+
contextType: ContextTypeSchema,
|
|
265
|
+
signers: z.array(SignerDraftSchema),
|
|
266
|
+
policyAddresses: z.array(z.string()),
|
|
267
|
+
predicate: PredicateNodeSchema.optional(),
|
|
268
|
+
});
|
|
249
269
|
const ContextRuleDraftSchema = z
|
|
250
270
|
.object({
|
|
251
271
|
contextRuleType: z.discriminatedUnion('kind', [
|
|
@@ -335,8 +355,40 @@ export const NETWORK_PASSPHRASES = {
|
|
|
335
355
|
// `sourceAccount` is the signing wallet (G...).
|
|
336
356
|
const STELLAR_CONTRACT_ADDRESS = /^C[2-7A-Z]{55}$/;
|
|
337
357
|
const STELLAR_ACCOUNT_ADDRESS = /^G[2-7A-Z]{55}$/;
|
|
358
|
+
// ===== declare_policy =====
|
|
359
|
+
//
|
|
360
|
+
// The declarative front-end: the constraint stated outright, with no
|
|
361
|
+
// transaction to decode. Deliberately NOT a revival of the removed
|
|
362
|
+
// `MandateSpec` - that carried a rolling `spendingLimit` the interpreter
|
|
363
|
+
// cannot evaluate and an `approvalThreshold` needing OZ primitives nobody
|
|
364
|
+
// deployed. Only fields grammar 3 can actually enforce appear here.
|
|
365
|
+
export const DeclarePolicyInputSchema = z
|
|
366
|
+
.object({
|
|
367
|
+
fn: z.string().min(1, 'fn must name the method to pin'),
|
|
368
|
+
contract: z
|
|
369
|
+
.string()
|
|
370
|
+
.regex(STELLAR_CONTRACT_ADDRESS, 'contract must be a Stellar contract address (C...)')
|
|
371
|
+
.optional(),
|
|
372
|
+
/** Smallest unit, unsigned decimal STRING - an i128 is wider than
|
|
373
|
+
* Number.MAX_SAFE_INTEGER, so a number here would silently round. */
|
|
374
|
+
maxAmount: z
|
|
375
|
+
.string()
|
|
376
|
+
.regex(/^[0-9]+$/, 'maxAmount must be an unsigned integer in the smallest unit')
|
|
377
|
+
.optional(),
|
|
378
|
+
amountArgIndex: z.number().int().nonnegative().max(U32_MAX).optional(),
|
|
379
|
+
recipients: z.array(z.string()).min(1, 'recipients must not be empty').optional(),
|
|
380
|
+
recipientArgIndex: z.number().int().nonnegative().max(U32_MAX).optional(),
|
|
381
|
+
allowZeroCap: z.boolean().optional(),
|
|
382
|
+
})
|
|
383
|
+
.strict();
|
|
338
384
|
export const InstallPolicyInputSchema = z
|
|
339
385
|
.object({
|
|
386
|
+
/** Rules already on the account. Supplying them turns on the cross-rule
|
|
387
|
+
* authority scan, which reports every existing rule a signer of this
|
|
388
|
+
* install could name INSTEAD - including an unpoliced one, against which
|
|
389
|
+
* the predicate never runs. Absent means the scan is skipped, and the
|
|
390
|
+
* result says so rather than reporting "no overlaps found". */
|
|
391
|
+
existingRules: z.array(ObservedRuleSchema).optional(),
|
|
340
392
|
/** The smart account contract address (C...) that will receive the rule. */
|
|
341
393
|
smartAccount: z
|
|
342
394
|
.string()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { PredicateNode } from '../types.ts';
|
|
2
|
+
export interface PolicyDeclaration {
|
|
3
|
+
/** Method to pin. Required: a predicate with no selector leaf constrains
|
|
4
|
+
* nothing and the contract refuses it at install. */
|
|
5
|
+
fn: string;
|
|
6
|
+
/** Contract to pin, already resolved to a `C...` address. */
|
|
7
|
+
contract?: string;
|
|
8
|
+
/** Upper bound on the call's amount argument, in the token's SMALLEST
|
|
9
|
+
* unit as an unsigned decimal string (25 XLM = "250000000"). */
|
|
10
|
+
maxAmount?: string;
|
|
11
|
+
/** Which argument carries the amount. Defaults to the SEP-41 position. */
|
|
12
|
+
amountArgIndex?: number;
|
|
13
|
+
/** Recipient allowlist. */
|
|
14
|
+
recipients?: string[];
|
|
15
|
+
/** Which argument carries the recipient. Defaults to the SEP-41 position. */
|
|
16
|
+
recipientArgIndex?: number;
|
|
17
|
+
/** A cap of "0" denies every call, so it is refused unless asked for
|
|
18
|
+
* explicitly. A rule that permits nothing is a plausible thing to want and
|
|
19
|
+
* an implausible thing to want by accident. */
|
|
20
|
+
allowZeroCap?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface DeclaredPredicate {
|
|
23
|
+
predicate: PredicateNode;
|
|
24
|
+
/** Assumptions the caller should check. Never empty when an argument index
|
|
25
|
+
* was defaulted rather than supplied. */
|
|
26
|
+
warnings: string[];
|
|
27
|
+
}
|
|
28
|
+
/** Lower a declared constraint to a grammar-3 predicate. Pure and total:
|
|
29
|
+
* the same declaration always produces the same predicate. */
|
|
30
|
+
export declare function declarePredicate(d: PolicyDeclaration): DeclaredPredicate;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// src/synth/declare.ts - build a predicate from a DECLARED constraint.
|
|
2
|
+
//
|
|
3
|
+
// The second synthesis front-end, beside `synthesizeFromRecording`. That one
|
|
4
|
+
// infers a predicate from a transaction that happened; this one takes the
|
|
5
|
+
// constraint the user states outright. No transaction, no decoding, no
|
|
6
|
+
// inference, no RPC: the same declaration always lowers to a byte-identical
|
|
7
|
+
// predicate.
|
|
8
|
+
//
|
|
9
|
+
// This is the surviving half of the removed `MandateSpec`. Two of that type's
|
|
10
|
+
// five fields lowered to things that do not exist - `spendingLimit` became a
|
|
11
|
+
// `window_spent` compare the interpreter cannot evaluate (it is handed one
|
|
12
|
+
// call and keeps no state), and `approvalThreshold` needed OZ built-in policy
|
|
13
|
+
// contracts that were never deployed. Both are deliberately absent here, and
|
|
14
|
+
// the per-call `maxAmount` bound below is the honest replacement for the
|
|
15
|
+
// first: it constrains the amount in THIS call rather than implying a rolling
|
|
16
|
+
// total nothing tracks.
|
|
17
|
+
//
|
|
18
|
+
// What a declaration can say maps one-to-one onto grammar 3:
|
|
19
|
+
// fn -> eq(call_fn, literal_symbol)
|
|
20
|
+
// contract -> eq(call_contract, literal_address)
|
|
21
|
+
// maxAmount -> lte(call_arg(i), literal_i128)
|
|
22
|
+
// recipients -> in(call_arg(j), [literal_address, ...])
|
|
23
|
+
import { isStellarAddress } from "./address.js";
|
|
24
|
+
/** Argument positions of the SEP-41 `transfer(from, to, amount)` shape. A
|
|
25
|
+
* declaration that names a different method almost certainly has different
|
|
26
|
+
* positions, which is why using either default emits a warning naming the
|
|
27
|
+
* index it assumed - a bound on the wrong argument constrains something the
|
|
28
|
+
* user did not mean and fails silently. */
|
|
29
|
+
const SEP41_RECIPIENT_ARG = 1;
|
|
30
|
+
const SEP41_AMOUNT_ARG = 2;
|
|
31
|
+
/** Lower a declared constraint to a grammar-3 predicate. Pure and total:
|
|
32
|
+
* the same declaration always produces the same predicate. */
|
|
33
|
+
export function declarePredicate(d) {
|
|
34
|
+
if (!d.fn || d.fn.trim() === '') {
|
|
35
|
+
throw declareError('SYNTHESIS_ERROR', 'a declaration needs `fn`: the method to pin');
|
|
36
|
+
}
|
|
37
|
+
const warnings = [];
|
|
38
|
+
const children = [
|
|
39
|
+
{ op: 'eq', left: { kind: 'call_fn' }, right: { kind: 'literal_symbol', value: d.fn } },
|
|
40
|
+
];
|
|
41
|
+
if (d.contract !== undefined) {
|
|
42
|
+
if (!isStellarAddress(d.contract) || !d.contract.startsWith('C')) {
|
|
43
|
+
throw declareError('SYNTHESIS_ERROR', `contract must be a Stellar contract address (C...), got ${d.contract}`);
|
|
44
|
+
}
|
|
45
|
+
children.push({
|
|
46
|
+
op: 'eq',
|
|
47
|
+
left: { kind: 'call_contract' },
|
|
48
|
+
right: { kind: 'literal_address', value: d.contract },
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (d.recipients !== undefined) {
|
|
52
|
+
if (d.recipients.length === 0) {
|
|
53
|
+
throw declareError('SYNTHESIS_ERROR', 'recipients was supplied but empty; an empty `in` haystack is refused at decode. Omit it to leave recipients unconstrained.');
|
|
54
|
+
}
|
|
55
|
+
for (const r of d.recipients) {
|
|
56
|
+
if (!isStellarAddress(r)) {
|
|
57
|
+
throw declareError('SYNTHESIS_ERROR', `recipient is not a Stellar address: ${r}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const idx = d.recipientArgIndex ?? SEP41_RECIPIENT_ARG;
|
|
61
|
+
if (d.recipientArgIndex === undefined) {
|
|
62
|
+
warnings.push(`recipient allowlist bound to call_arg(${idx}), the SEP-41 \`transfer\` position. If \`${d.fn}\` carries the recipient elsewhere this constrains the wrong argument - pass recipientArgIndex.`);
|
|
63
|
+
}
|
|
64
|
+
children.push({
|
|
65
|
+
op: 'in',
|
|
66
|
+
needle: { kind: 'call_arg', index: idx },
|
|
67
|
+
haystack: d.recipients.map((value) => ({ kind: 'literal_address', value })),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
if (d.maxAmount !== undefined) {
|
|
71
|
+
if (!/^[0-9]+$/.test(d.maxAmount)) {
|
|
72
|
+
throw declareError('SYNTHESIS_ERROR', `maxAmount must be an unsigned integer in the token's smallest unit, got "${d.maxAmount}" (25 XLM = "250000000")`);
|
|
73
|
+
}
|
|
74
|
+
if (d.maxAmount === '0' && d.allowZeroCap !== true) {
|
|
75
|
+
throw declareError('SYNTHESIS_ERROR', 'maxAmount "0" denies every call: no amount satisfies the bound. Set allowZeroCap to declare that deliberately.');
|
|
76
|
+
}
|
|
77
|
+
const idx = d.amountArgIndex ?? SEP41_AMOUNT_ARG;
|
|
78
|
+
if (d.amountArgIndex === undefined) {
|
|
79
|
+
warnings.push(`amount cap bound to call_arg(${idx}), the SEP-41 \`transfer\` position. If \`${d.fn}\` carries the amount elsewhere this caps the wrong argument - pass amountArgIndex.`);
|
|
80
|
+
}
|
|
81
|
+
children.push({
|
|
82
|
+
op: 'lte',
|
|
83
|
+
left: { kind: 'call_arg', index: idx },
|
|
84
|
+
right: { kind: 'literal_i128', value: d.maxAmount },
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
// A single conjunct is emitted bare. `and` with one child encodes to
|
|
88
|
+
// different bytes than the child alone, and the extra node buys nothing.
|
|
89
|
+
const predicate = children.length === 1 ? children[0] : { op: 'and', children };
|
|
90
|
+
return { predicate, warnings };
|
|
91
|
+
}
|
|
92
|
+
function declareError(code, message) {
|
|
93
|
+
const err = new Error(message);
|
|
94
|
+
err.code = code;
|
|
95
|
+
err.severity = 'error';
|
|
96
|
+
err.retryable = false;
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
package/dist/synth/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { isStellarAddress } from './address.ts';
|
|
2
2
|
export { type ComposeOptions, type ComposeResult, composeFromRecording, } from './compose-from-recording.ts';
|
|
3
|
+
export { type DeclaredPredicate, declarePredicate, type PolicyDeclaration, } from './declare.ts';
|
|
3
4
|
export { type IntentFacts, lower } from './lower.ts';
|
|
4
5
|
export { type DecideScopeOptions, decideScope, type ScopeDecision, scopeToContextRuleType, } from './scope.ts';
|
|
5
6
|
export { type __TestInterpreterAdapterOptions, type SynthesizeFromRecordingOptions, synthesizeFromRecording, } from './synthesize-from-recording.ts';
|
package/dist/synth/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// and re-deriving it elsewhere means a second address check that can drift.
|
|
5
5
|
export { isStellarAddress } from "./address.js";
|
|
6
6
|
export { composeFromRecording, } from "./compose-from-recording.js";
|
|
7
|
+
export { declarePredicate, } from "./declare.js";
|
|
7
8
|
export { lower } from "./lower.js";
|
|
8
9
|
export { decideScope, scopeToContextRuleType, } from "./scope.js";
|
|
9
10
|
export { synthesizeFromRecording, } from "./synthesize-from-recording.js";
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { PredicateNode, SignerDraft } from '../types.ts';
|
|
2
|
+
/** Wildcard component of a `Selector`: the predicate does not pin this half. */
|
|
3
|
+
export declare const ANY = "*";
|
|
4
|
+
/** A (contract, function) pair a predicate may permit. `ANY` in either half
|
|
5
|
+
* means unconstrained, so `{contract: ANY, fn: ANY}` is "any call at all". */
|
|
6
|
+
export interface Selector {
|
|
7
|
+
contract: string;
|
|
8
|
+
fn: string;
|
|
9
|
+
}
|
|
10
|
+
export type ContextType = {
|
|
11
|
+
kind: 'default';
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'call_contract';
|
|
14
|
+
contract: string;
|
|
15
|
+
} | {
|
|
16
|
+
kind: 'create_contract';
|
|
17
|
+
wasmHash: string;
|
|
18
|
+
};
|
|
19
|
+
/** How much can be said about a neighbouring rule.
|
|
20
|
+
* - `interpreter`: policed by our interpreter and the predicate was readable,
|
|
21
|
+
* so its authority is known exactly.
|
|
22
|
+
* - `foreign`: policed by some other contract. The address is visible, the
|
|
23
|
+
* semantics are not, so it needs review by hand.
|
|
24
|
+
* - `unpoliced`: no policy at all. Whatever its context type allows, its
|
|
25
|
+
* signers may do without constraint. */
|
|
26
|
+
export type RuleClass = 'interpreter' | 'foreign' | 'unpoliced';
|
|
27
|
+
export interface ObservedRule {
|
|
28
|
+
id: number;
|
|
29
|
+
contextType: ContextType;
|
|
30
|
+
signers: SignerDraft[];
|
|
31
|
+
/** Policy contract addresses attached to the rule, in OZ's order. */
|
|
32
|
+
policyAddresses: string[];
|
|
33
|
+
/** Decoded predicate. Present only when the rule is policed by OUR
|
|
34
|
+
* interpreter and the stored document was readable. */
|
|
35
|
+
predicate?: PredicateNode;
|
|
36
|
+
}
|
|
37
|
+
export interface IntendedInstall {
|
|
38
|
+
/** Rule the predicate is being installed onto. A re-install onto the same
|
|
39
|
+
* id REPLACES its predicate rather than adding a second source of
|
|
40
|
+
* authority, so that id is skipped. */
|
|
41
|
+
ruleId: number;
|
|
42
|
+
contextType: ContextType;
|
|
43
|
+
signers: SignerDraft[];
|
|
44
|
+
predicate: PredicateNode;
|
|
45
|
+
}
|
|
46
|
+
export type OverlapSeverity =
|
|
47
|
+
/** A neighbouring rule imposes no constraint at all on the shared calls. */
|
|
48
|
+
'bypass'
|
|
49
|
+
/** A neighbouring policy exists but what it permits cannot be read. */
|
|
50
|
+
| 'unknown'
|
|
51
|
+
/** Both rules are ours. The new rule will not restrict the shared calls,
|
|
52
|
+
* because the signer names whichever is more permissive. */
|
|
53
|
+
| 'not-restricting';
|
|
54
|
+
export interface AuthorityOverlap {
|
|
55
|
+
ruleId: number;
|
|
56
|
+
ruleClass: RuleClass;
|
|
57
|
+
severity: OverlapSeverity;
|
|
58
|
+
/** Signers present in BOTH rules. An overlap is only reachable by a signer
|
|
59
|
+
* who can name both, so a rule sharing no signer is not a collision. */
|
|
60
|
+
sharedSigners: SignerDraft[];
|
|
61
|
+
/** The selectors both rules can serve. Non-empty by construction. */
|
|
62
|
+
sharedSelectors: Selector[];
|
|
63
|
+
advice: string;
|
|
64
|
+
}
|
|
65
|
+
/** Canonical key for signer equality. Mirrors OZ's `Signer` enum: a delegated
|
|
66
|
+
* signer is its address, an external signer is the verifier plus the key
|
|
67
|
+
* bytes, since one verifier may hold many keys. */
|
|
68
|
+
export declare function signerKey(s: SignerDraft): string;
|
|
69
|
+
/** Intersection of two selector SETS: every compatible pairing survives. */
|
|
70
|
+
export declare function intersectSelectors(a: Selector[], b: Selector[]): Selector[];
|
|
71
|
+
/**
|
|
72
|
+
* The set of `(contract, fn)` selectors a predicate may permit.
|
|
73
|
+
*
|
|
74
|
+
* A deliberate OVER-approximation: every call the predicate actually permits is
|
|
75
|
+
* covered by some returned selector, and unrecognised structure widens to the
|
|
76
|
+
* wildcard rather than narrowing. That direction is what makes the emptiness
|
|
77
|
+
* test sound. A call carries exactly one `(contract, fn)`, so if two
|
|
78
|
+
* predicates' over-approximations do not intersect, no single call can be
|
|
79
|
+
* routed to either and the rules provably cannot collide.
|
|
80
|
+
*
|
|
81
|
+
* Narrowing instead would be the fail-OPEN direction: it would let this report
|
|
82
|
+
* "no overlap" for rules that do collide.
|
|
83
|
+
*/
|
|
84
|
+
export declare function permittedSelectors(node: PredicateNode): Selector[];
|
|
85
|
+
/** Selectors a context type admits, before the predicate narrows them. */
|
|
86
|
+
export declare function selectorsForContextType(ct: ContextType): Selector[];
|
|
87
|
+
/** What a rule can actually authorise: its context type narrowed by its
|
|
88
|
+
* predicate. An unpoliced or unreadable rule contributes no narrowing. */
|
|
89
|
+
export declare function effectiveSelectors(rule: ObservedRule): Selector[];
|
|
90
|
+
/**
|
|
91
|
+
* Every existing rule a signer of the intended install could name instead.
|
|
92
|
+
*
|
|
93
|
+
* A rule collides when it shares at least one signer AND at least one selector.
|
|
94
|
+
* Both are needed for the signer to have a choice: same signer but disjoint
|
|
95
|
+
* calls means no call can be rerouted, and same calls but no shared signer
|
|
96
|
+
* means nobody can reroute them.
|
|
97
|
+
*/
|
|
98
|
+
export declare function findAuthorityOverlaps(args: {
|
|
99
|
+
intended: IntendedInstall;
|
|
100
|
+
existing: ObservedRule[];
|
|
101
|
+
}): AuthorityOverlap[];
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/install/authority-overlap.ts - cross-rule authority analysis.
|
|
3
|
+
//
|
|
4
|
+
// An OZ smart account selects a context rule by CALLER DECLARATION and enforces
|
|
5
|
+
// only the policies of the rule that was named. A signer belonging to several
|
|
6
|
+
// rules therefore picks which one applies, so for any given call their
|
|
7
|
+
// authority is the MAXIMUM over the matching rules, never the intersection.
|
|
8
|
+
//
|
|
9
|
+
// The consequence is the one that catches people: installing a second, tighter
|
|
10
|
+
// rule restricts nothing. A key that also sits on an unpoliced rule is not
|
|
11
|
+
// constrained at all - it names that rule and the predicate never runs. This
|
|
12
|
+
// module detects that at install time, before the caller acts on a policy that
|
|
13
|
+
// looks binding and is not.
|
|
14
|
+
//
|
|
15
|
+
// Not theoretical. Proven on chain 2026-08-22: the same key, the same account
|
|
16
|
+
// and the same forbidden call was denied `#100` naming the policed rule and
|
|
17
|
+
// PERMITTED naming an unpoliced one. It happened in this project's own end-to-
|
|
18
|
+
// end harness, written by the author of the grammar, and was caught by review
|
|
19
|
+
// rather than by tooling - which is why the tooling now exists.
|
|
20
|
+
//
|
|
21
|
+
// Adapted to grammar 3 from the version published in `@crediolabs/policy-synth`
|
|
22
|
+
// 0.2.0, which came from the `octogate` repository and was lost when the npm
|
|
23
|
+
// lineage moved here. `or` and `not` are gone from the grammar, so the cases
|
|
24
|
+
// handling them are gone too; oracle bounds are gone from the stored document.
|
|
25
|
+
//
|
|
26
|
+
// Pure: no network. The caller supplies the account's rules.
|
|
27
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
|
+
exports.ANY = void 0;
|
|
29
|
+
exports.signerKey = signerKey;
|
|
30
|
+
exports.intersectSelectors = intersectSelectors;
|
|
31
|
+
exports.permittedSelectors = permittedSelectors;
|
|
32
|
+
exports.selectorsForContextType = selectorsForContextType;
|
|
33
|
+
exports.effectiveSelectors = effectiveSelectors;
|
|
34
|
+
exports.findAuthorityOverlaps = findAuthorityOverlaps;
|
|
35
|
+
/** Wildcard component of a `Selector`: the predicate does not pin this half. */
|
|
36
|
+
exports.ANY = '*';
|
|
37
|
+
// ---- signer identity ----
|
|
38
|
+
/** Canonical key for signer equality. Mirrors OZ's `Signer` enum: a delegated
|
|
39
|
+
* signer is its address, an external signer is the verifier plus the key
|
|
40
|
+
* bytes, since one verifier may hold many keys. */
|
|
41
|
+
function signerKey(s) {
|
|
42
|
+
return s.kind === 'delegated' ? `delegated:${s.address}` : `external:${s.verifier}:${s.keyBytes}`;
|
|
43
|
+
}
|
|
44
|
+
function sharedSigners(a, b) {
|
|
45
|
+
const bKeys = new Set(b.map(signerKey));
|
|
46
|
+
return a.filter((s) => bKeys.has(signerKey(s)));
|
|
47
|
+
}
|
|
48
|
+
// ---- selector extraction ----
|
|
49
|
+
const WILDCARD = { contract: exports.ANY, fn: exports.ANY };
|
|
50
|
+
function selectorKey(s) {
|
|
51
|
+
return `${s.contract} ${s.fn}`;
|
|
52
|
+
}
|
|
53
|
+
function dedupe(sels) {
|
|
54
|
+
const seen = new Map();
|
|
55
|
+
for (const s of sels)
|
|
56
|
+
seen.set(selectorKey(s), s);
|
|
57
|
+
return [...seen.values()];
|
|
58
|
+
}
|
|
59
|
+
/** Intersect one pair. `ANY` absorbs, equal literals survive, and two
|
|
60
|
+
* different literals cannot both hold for a single call. */
|
|
61
|
+
function intersectOne(a, b) {
|
|
62
|
+
const contract = a.contract === exports.ANY
|
|
63
|
+
? b.contract
|
|
64
|
+
: b.contract === exports.ANY
|
|
65
|
+
? a.contract
|
|
66
|
+
: a.contract === b.contract
|
|
67
|
+
? a.contract
|
|
68
|
+
: null;
|
|
69
|
+
if (contract === null)
|
|
70
|
+
return null;
|
|
71
|
+
const fn = a.fn === exports.ANY ? b.fn : b.fn === exports.ANY ? a.fn : a.fn === b.fn ? a.fn : null;
|
|
72
|
+
if (fn === null)
|
|
73
|
+
return null;
|
|
74
|
+
return { contract, fn };
|
|
75
|
+
}
|
|
76
|
+
/** Intersection of two selector SETS: every compatible pairing survives. */
|
|
77
|
+
function intersectSelectors(a, b) {
|
|
78
|
+
const out = [];
|
|
79
|
+
for (const x of a) {
|
|
80
|
+
for (const y of b) {
|
|
81
|
+
const hit = intersectOne(x, y);
|
|
82
|
+
if (hit)
|
|
83
|
+
out.push(hit);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return dedupe(out);
|
|
87
|
+
}
|
|
88
|
+
function literalAddress(leaf) {
|
|
89
|
+
return leaf.kind === 'literal_address' ? leaf.value : null;
|
|
90
|
+
}
|
|
91
|
+
function literalSymbol(leaf) {
|
|
92
|
+
return leaf.kind === 'literal_symbol' ? leaf.value : null;
|
|
93
|
+
}
|
|
94
|
+
/** Selector pinned by a single `eq`, whichever side the literal sits on. */
|
|
95
|
+
function selectorFromEq(left, right) {
|
|
96
|
+
if (left.kind === 'call_contract') {
|
|
97
|
+
const addr = literalAddress(right);
|
|
98
|
+
return addr === null ? null : { contract: addr, fn: exports.ANY };
|
|
99
|
+
}
|
|
100
|
+
if (right.kind === 'call_contract') {
|
|
101
|
+
const addr = literalAddress(left);
|
|
102
|
+
return addr === null ? null : { contract: addr, fn: exports.ANY };
|
|
103
|
+
}
|
|
104
|
+
if (left.kind === 'call_fn') {
|
|
105
|
+
const sym = literalSymbol(right);
|
|
106
|
+
return sym === null ? null : { contract: exports.ANY, fn: sym };
|
|
107
|
+
}
|
|
108
|
+
if (right.kind === 'call_fn') {
|
|
109
|
+
const sym = literalSymbol(left);
|
|
110
|
+
return sym === null ? null : { contract: exports.ANY, fn: sym };
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The set of `(contract, fn)` selectors a predicate may permit.
|
|
116
|
+
*
|
|
117
|
+
* A deliberate OVER-approximation: every call the predicate actually permits is
|
|
118
|
+
* covered by some returned selector, and unrecognised structure widens to the
|
|
119
|
+
* wildcard rather than narrowing. That direction is what makes the emptiness
|
|
120
|
+
* test sound. A call carries exactly one `(contract, fn)`, so if two
|
|
121
|
+
* predicates' over-approximations do not intersect, no single call can be
|
|
122
|
+
* routed to either and the rules provably cannot collide.
|
|
123
|
+
*
|
|
124
|
+
* Narrowing instead would be the fail-OPEN direction: it would let this report
|
|
125
|
+
* "no overlap" for rules that do collide.
|
|
126
|
+
*/
|
|
127
|
+
function permittedSelectors(node) {
|
|
128
|
+
switch (node.op) {
|
|
129
|
+
case 'and': {
|
|
130
|
+
// Every conjunct must hold at once, so the permitted set is the
|
|
131
|
+
// intersection. Intersecting over-approximations stays one.
|
|
132
|
+
let acc = [WILDCARD];
|
|
133
|
+
for (const child of node.children)
|
|
134
|
+
acc = intersectSelectors(acc, permittedSelectors(child));
|
|
135
|
+
return acc;
|
|
136
|
+
}
|
|
137
|
+
case 'eq': {
|
|
138
|
+
const sel = selectorFromEq(node.left, node.right);
|
|
139
|
+
return sel === null ? [WILDCARD] : [sel];
|
|
140
|
+
}
|
|
141
|
+
case 'in': {
|
|
142
|
+
// Set membership over the selector halves: `call_fn in {a, b}` permits
|
|
143
|
+
// both. A haystack element that is not the matching literal kind makes
|
|
144
|
+
// the node uninformative rather than narrower.
|
|
145
|
+
if (node.needle.kind === 'call_contract') {
|
|
146
|
+
const addrs = node.haystack.map(literalAddress);
|
|
147
|
+
if (addrs.some((a) => a === null))
|
|
148
|
+
return [WILDCARD];
|
|
149
|
+
return dedupe(addrs.map((a) => ({ contract: a, fn: exports.ANY })));
|
|
150
|
+
}
|
|
151
|
+
if (node.needle.kind === 'call_fn') {
|
|
152
|
+
const syms = node.haystack.map(literalSymbol);
|
|
153
|
+
if (syms.some((s) => s === null))
|
|
154
|
+
return [WILDCARD];
|
|
155
|
+
return dedupe(syms.map((s) => ({ contract: exports.ANY, fn: s })));
|
|
156
|
+
}
|
|
157
|
+
return [WILDCARD];
|
|
158
|
+
}
|
|
159
|
+
default:
|
|
160
|
+
// `lte` binds an amount, never the selector.
|
|
161
|
+
return [WILDCARD];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/** Selectors a context type admits, before the predicate narrows them. */
|
|
165
|
+
function selectorsForContextType(ct) {
|
|
166
|
+
switch (ct.kind) {
|
|
167
|
+
case 'default':
|
|
168
|
+
return [WILDCARD];
|
|
169
|
+
case 'call_contract':
|
|
170
|
+
return [{ contract: ct.contract, fn: exports.ANY }];
|
|
171
|
+
case 'create_contract':
|
|
172
|
+
// A contract-creation context is a different `Context` shape. The
|
|
173
|
+
// interpreter refuses anything that is not `Context::Contract`, and a
|
|
174
|
+
// creation rule can never serve a call, so it shares no selector.
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/** What a rule can actually authorise: its context type narrowed by its
|
|
179
|
+
* predicate. An unpoliced or unreadable rule contributes no narrowing. */
|
|
180
|
+
function effectiveSelectors(rule) {
|
|
181
|
+
const fromType = selectorsForContextType(rule.contextType);
|
|
182
|
+
if (!rule.predicate)
|
|
183
|
+
return fromType;
|
|
184
|
+
return intersectSelectors(fromType, permittedSelectors(rule.predicate));
|
|
185
|
+
}
|
|
186
|
+
function classifyRule(rule) {
|
|
187
|
+
if (rule.policyAddresses.length === 0)
|
|
188
|
+
return 'unpoliced';
|
|
189
|
+
return rule.predicate ? 'interpreter' : 'foreign';
|
|
190
|
+
}
|
|
191
|
+
function adviceFor(cls, ruleId) {
|
|
192
|
+
switch (cls) {
|
|
193
|
+
case 'unpoliced':
|
|
194
|
+
return `rule ${ruleId} has no policy attached, so a shared signer may make these calls with no constraint at all - the predicate you are installing will never run for them. Remove the shared signer from rule ${ruleId}, or attach a policy to it.`;
|
|
195
|
+
case 'foreign':
|
|
196
|
+
return `rule ${ruleId} is policed by a contract this tool cannot decode, so its authority over these calls is unknown. Review it by hand before relying on the new rule.`;
|
|
197
|
+
case 'interpreter':
|
|
198
|
+
return `a shared signer may name rule ${ruleId} instead, so the new rule will not restrict these calls. To TIGHTEN, edit rule ${ruleId} itself rather than adding a second rule. To ADD a separate capability, keep both and expect neither to constrain the other.`;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Every existing rule a signer of the intended install could name instead.
|
|
203
|
+
*
|
|
204
|
+
* A rule collides when it shares at least one signer AND at least one selector.
|
|
205
|
+
* Both are needed for the signer to have a choice: same signer but disjoint
|
|
206
|
+
* calls means no call can be rerouted, and same calls but no shared signer
|
|
207
|
+
* means nobody can reroute them.
|
|
208
|
+
*/
|
|
209
|
+
function findAuthorityOverlaps(args) {
|
|
210
|
+
const intendedSelectors = intersectSelectors(selectorsForContextType(args.intended.contextType), permittedSelectors(args.intended.predicate));
|
|
211
|
+
const out = [];
|
|
212
|
+
for (const rule of args.existing) {
|
|
213
|
+
if (rule.id === args.intended.ruleId)
|
|
214
|
+
continue;
|
|
215
|
+
const shared = sharedSigners(args.intended.signers, rule.signers);
|
|
216
|
+
if (shared.length === 0)
|
|
217
|
+
continue;
|
|
218
|
+
const sharedSelectors = intersectSelectors(intendedSelectors, effectiveSelectors(rule));
|
|
219
|
+
if (sharedSelectors.length === 0)
|
|
220
|
+
continue;
|
|
221
|
+
const ruleClass = classifyRule(rule);
|
|
222
|
+
out.push({
|
|
223
|
+
ruleId: rule.id,
|
|
224
|
+
ruleClass,
|
|
225
|
+
severity: ruleClass === 'unpoliced'
|
|
226
|
+
? 'bypass'
|
|
227
|
+
: ruleClass === 'foreign'
|
|
228
|
+
? 'unknown'
|
|
229
|
+
: 'not-restricting',
|
|
230
|
+
sharedSigners: shared,
|
|
231
|
+
sharedSelectors,
|
|
232
|
+
advice: adviceFor(ruleClass, rule.id),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
return out;
|
|
236
|
+
}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
+
export { ANY, type AuthorityOverlap, type ContextType, effectiveSelectors, findAuthorityOverlaps, type IntendedInstall, intersectSelectors, type ObservedRule, type OverlapSeverity, permittedSelectors, type RuleClass, type Selector, selectorsForContextType, signerKey, } from './authority-overlap.ts';
|
|
1
2
|
export { ADD_CONTEXT_RULE_SYMBOL, type AddContextRuleArgs, type BuildAddContextRuleArgs, buildAddContextRuleArgs, DEFAULT_GRAMMAR_VERSION, } from './build-add-context-rule.ts';
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DEFAULT_GRAMMAR_VERSION = exports.buildAddContextRuleArgs = exports.ADD_CONTEXT_RULE_SYMBOL = void 0;
|
|
4
2
|
// Public entry for the install-argument builders.
|
|
5
3
|
//
|
|
6
4
|
// `buildAddContextRuleArgs` is the encoder `runInstallPolicy` uses to turn a
|
|
@@ -15,6 +13,19 @@ exports.DEFAULT_GRAMMAR_VERSION = exports.buildAddContextRuleArgs = exports.ADD_
|
|
|
15
13
|
// Exported here rather than from the package root to keep the root surface
|
|
16
14
|
// about synthesis, and because these are transaction-building primitives whose
|
|
17
15
|
// callers should know they are reaching for them.
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.DEFAULT_GRAMMAR_VERSION = exports.buildAddContextRuleArgs = exports.ADD_CONTEXT_RULE_SYMBOL = exports.signerKey = exports.selectorsForContextType = exports.permittedSelectors = exports.intersectSelectors = exports.findAuthorityOverlaps = exports.effectiveSelectors = exports.ANY = void 0;
|
|
18
|
+
// Cross-rule authority analysis. Exported because the check has to happen
|
|
19
|
+
// wherever an install is BUILT, and a client that assembles its own
|
|
20
|
+
// `add_context_rule` call never reaches `runInstallPolicy`.
|
|
21
|
+
var authority_overlap_ts_1 = require("./authority-overlap.js");
|
|
22
|
+
Object.defineProperty(exports, "ANY", { enumerable: true, get: function () { return authority_overlap_ts_1.ANY; } });
|
|
23
|
+
Object.defineProperty(exports, "effectiveSelectors", { enumerable: true, get: function () { return authority_overlap_ts_1.effectiveSelectors; } });
|
|
24
|
+
Object.defineProperty(exports, "findAuthorityOverlaps", { enumerable: true, get: function () { return authority_overlap_ts_1.findAuthorityOverlaps; } });
|
|
25
|
+
Object.defineProperty(exports, "intersectSelectors", { enumerable: true, get: function () { return authority_overlap_ts_1.intersectSelectors; } });
|
|
26
|
+
Object.defineProperty(exports, "permittedSelectors", { enumerable: true, get: function () { return authority_overlap_ts_1.permittedSelectors; } });
|
|
27
|
+
Object.defineProperty(exports, "selectorsForContextType", { enumerable: true, get: function () { return authority_overlap_ts_1.selectorsForContextType; } });
|
|
28
|
+
Object.defineProperty(exports, "signerKey", { enumerable: true, get: function () { return authority_overlap_ts_1.signerKey; } });
|
|
18
29
|
var build_add_context_rule_ts_1 = require("./build-add-context-rule.js");
|
|
19
30
|
Object.defineProperty(exports, "ADD_CONTEXT_RULE_SYMBOL", { enumerable: true, get: function () { return build_add_context_rule_ts_1.ADD_CONTEXT_RULE_SYMBOL; } });
|
|
20
31
|
Object.defineProperty(exports, "buildAddContextRuleArgs", { enumerable: true, get: function () { return build_add_context_rule_ts_1.buildAddContextRuleArgs; } });
|
|
@@ -16,15 +16,21 @@ export declare function isBelowThreshold(c: ParseConfidence): boolean;
|
|
|
16
16
|
* under-confidence recording.
|
|
17
17
|
*
|
|
18
18
|
* The remediation text branches on which diagnostic bucket is non-empty:
|
|
19
|
-
* - unknownContracts only ->
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* - neither (the "denom === 0" path) ->
|
|
29
|
-
*
|
|
19
|
+
* - unknownContracts only -> the contract is not in the compiled-in
|
|
20
|
+
* registry. NOT "the contract has no interface": most Soroban
|
|
21
|
+
* contracts publish a typed spec on chain, this package just never
|
|
22
|
+
* reads one. The reason code stays `no-abi` because it is a published
|
|
23
|
+
* enum value, so the TEXT has to carry the correction.
|
|
24
|
+
* - opaqueScVals only -> the recorder met a value shape it should
|
|
25
|
+
* support but did not decode. That is a decoder bug, and the guidance
|
|
26
|
+
* says so rather than implying the caller can fix it.
|
|
27
|
+
* - both -> list both diagnostics and chain the right remediation.
|
|
28
|
+
* - neither (the "denom === 0" path) -> the caller did not actually hit
|
|
29
|
+
* a code-level barrier.
|
|
30
|
+
*
|
|
31
|
+
* Do NOT tell the caller to supply an ABI. There is no input for one:
|
|
32
|
+
* recognition runs off the compiled-in registry plus a known-contract set
|
|
33
|
+
* that no public tool boundary exposes. The old text named an action
|
|
34
|
+
* nobody could take and sent users looking for a file they do not have and
|
|
35
|
+
* would not need. */
|
|
30
36
|
export declare function buildLowConfidenceQuestion(c: ParseConfidence): string;
|