otto-intel-mcp 0.1.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/LICENSE +21 -0
- package/README.md +102 -0
- package/dist/adapter/cdp-signer.d.ts +133 -0
- package/dist/adapter/cdp-signer.js +356 -0
- package/dist/adapter/cdp-signer.js.map +1 -0
- package/dist/adapter/chain.d.ts +36 -0
- package/dist/adapter/chain.js +65 -0
- package/dist/adapter/chain.js.map +1 -0
- package/dist/adapter/erc20.d.ts +39 -0
- package/dist/adapter/erc20.js +17 -0
- package/dist/adapter/erc20.js.map +1 -0
- package/dist/adapter/lifi-decode.d.ts +52 -0
- package/dist/adapter/lifi-decode.js +149 -0
- package/dist/adapter/lifi-decode.js.map +1 -0
- package/dist/adapter/refusal.d.ts +21 -0
- package/dist/adapter/refusal.js +55 -0
- package/dist/adapter/refusal.js.map +1 -0
- package/dist/adapter/sent-step.d.ts +15 -0
- package/dist/adapter/sent-step.js +6 -0
- package/dist/adapter/sent-step.js.map +1 -0
- package/dist/adapter/verify.d.ts +149 -0
- package/dist/adapter/verify.js +432 -0
- package/dist/adapter/verify.js.map +1 -0
- package/dist/adapter-cdp-index.d.ts +6 -0
- package/dist/adapter-cdp-index.js +7 -0
- package/dist/adapter-index.d.ts +15 -0
- package/dist/adapter-index.js +15 -0
- package/dist/artifact-id.d.ts +16 -0
- package/dist/artifact-id.js +60 -0
- package/dist/boot-redaction.d.ts +16 -0
- package/dist/boot-redaction.js +54 -0
- package/dist/data-source.d.ts +7 -0
- package/dist/data-source.js +2 -0
- package/dist/errors.d.ts +70 -0
- package/dist/errors.js +221 -0
- package/dist/execution-config.d.ts +232 -0
- package/dist/execution-config.js +443 -0
- package/dist/execution-delegated-definition.d.ts +165 -0
- package/dist/execution-delegated-definition.js +116 -0
- package/dist/execution-delegation-admin-definition.d.ts +208 -0
- package/dist/execution-delegation-admin-definition.js +170 -0
- package/dist/execution-delegation-admin.d.ts +74 -0
- package/dist/execution-delegation-admin.js +290 -0
- package/dist/execution-delegation-policy.d.ts +257 -0
- package/dist/execution-delegation-policy.js +279 -0
- package/dist/execution-delegation.d.ts +190 -0
- package/dist/execution-delegation.js +545 -0
- package/dist/execution-errors.d.ts +9 -0
- package/dist/execution-errors.js +128 -0
- package/dist/execution-index.d.ts +17 -0
- package/dist/execution-index.js +16 -0
- package/dist/execution-intent.d.ts +14 -0
- package/dist/execution-intent.js +36 -0
- package/dist/execution-registration.d.ts +113 -0
- package/dist/execution-registration.js +161 -0
- package/dist/execution-tool-definitions.d.ts +1103 -0
- package/dist/execution-tool-definitions.js +1051 -0
- package/dist/execution-tools.d.ts +85 -0
- package/dist/execution-tools.js +690 -0
- package/dist/execution-types.d.ts +274 -0
- package/dist/execution-types.js +157 -0
- package/dist/hyperliquid-info-client.d.ts +31 -0
- package/dist/hyperliquid-info-client.js +121 -0
- package/dist/hyperliquid-order-assertions.d.ts +89 -0
- package/dist/hyperliquid-order-assertions.js +325 -0
- package/dist/hyperliquid-order-builder.d.ts +47 -0
- package/dist/hyperliquid-order-builder.js +55 -0
- package/dist/lifi-bridge-assertions.d.ts +49 -0
- package/dist/lifi-bridge-assertions.js +303 -0
- package/dist/lifi-execution-client.d.ts +74 -0
- package/dist/lifi-execution-client.js +165 -0
- package/dist/lifi-fee-assertions.d.ts +32 -0
- package/dist/lifi-fee-assertions.js +103 -0
- package/dist/lifi-swap-assertions.d.ts +32 -0
- package/dist/lifi-swap-assertions.js +214 -0
- package/dist/local-config.d.ts +9 -0
- package/dist/local-config.js +45 -0
- package/dist/mcp-server.d.ts +4 -0
- package/dist/mcp-server.js +44 -0
- package/dist/polymarket-clob-client.d.ts +73 -0
- package/dist/polymarket-clob-client.js +186 -0
- package/dist/polymarket-order-assertions.d.ts +60 -0
- package/dist/polymarket-order-assertions.js +273 -0
- package/dist/polymarket-order-builder.d.ts +43 -0
- package/dist/polymarket-order-builder.js +74 -0
- package/dist/prepared-artifacts.d.ts +45 -0
- package/dist/prepared-artifacts.js +92 -0
- package/dist/stdio-main.d.ts +2 -0
- package/dist/stdio-main.js +20 -0
- package/dist/tool-definitions.d.ts +20 -0
- package/dist/tool-definitions.js +162 -0
- package/dist/x402-read-source.d.ts +11 -0
- package/dist/x402-read-source.js +77 -0
- package/package.json +162 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* execution-delegated-definition.ts — the ONE execution tool that submits: `otto_submit_under_delegation`.
|
|
3
|
+
* Kept apart from the prepare-only `EXECUTION_TOOL_DEFINITIONS` on purpose: it registers only when the
|
|
4
|
+
* hosted service holds Otto's CDP developer credentials, it is not read-only, and its caller is
|
|
5
|
+
* authenticated per request by a header the arguments never carry.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { EXECUTION_ENVELOPE_SCHEMA, PREPARE_SWAP_INPUT_SCHEMA, } from './execution-tool-definitions.js';
|
|
9
|
+
import { MODEL_B_PER_SWAP_CAP_USD } from './execution-delegation-policy.js';
|
|
10
|
+
export const DELEGATED_EXECUTION_TOOL_NAME = 'otto_submit_under_delegation';
|
|
11
|
+
/** Per-request caller authentication rides THIS header — never a tool argument (arguments are logged). */
|
|
12
|
+
export const DELEGATION_AUTH_HEADER = 'x-otto-delegation-auth';
|
|
13
|
+
const END_USER_ID_PATTERN = /^[a-zA-Z0-9-]{1,100}$/;
|
|
14
|
+
const EVM_ADDRESS_PATTERN = /^0x[0-9a-fA-F]{40}$/;
|
|
15
|
+
const HEX_PATTERN = /^0x(?:[0-9a-fA-F]{2})*$/;
|
|
16
|
+
export const SUBMIT_UNDER_DELEGATION_INPUT_SCHEMA = z
|
|
17
|
+
.object({
|
|
18
|
+
userId: z
|
|
19
|
+
.string()
|
|
20
|
+
.regex(END_USER_ID_PATTERN, 'must be a CDP end-user id')
|
|
21
|
+
.describe('The CDP end user whose delegation submits the plan. The server reads the user and its accounts from CDP; the caller never binds an address on its own.'),
|
|
22
|
+
envelope: EXECUTION_ENVELOPE_SCHEMA.describe('The otto_prepare_swap envelope, exactly as returned.'),
|
|
23
|
+
prepareInput: PREPARE_SWAP_INPUT_SCHEMA.describe("The EXACT otto_prepare_swap request that produced the envelope (binds intent_digest). slippage.minAmountOut is REQUIRED here: it is the user's own floor, enforced on-chain by the router and re-checked by the verifier."),
|
|
24
|
+
})
|
|
25
|
+
.strict()
|
|
26
|
+
.superRefine((value, context) => {
|
|
27
|
+
if (value.prepareInput.slippage.minAmountOut === undefined) {
|
|
28
|
+
context.addIssue({
|
|
29
|
+
code: 'custom',
|
|
30
|
+
path: ['prepareInput', 'slippage', 'minAmountOut'],
|
|
31
|
+
message: 'required for a delegated send: the caller must state the minimum acceptable output',
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
if (value.prepareInput.accountProfile.kind !== 'eoa') {
|
|
35
|
+
context.addIssue({
|
|
36
|
+
code: 'custom',
|
|
37
|
+
path: ['prepareInput', 'accountProfile', 'kind'],
|
|
38
|
+
message: 'a CDP end-user account is an EOA',
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (value.prepareInput.chainId !== 8453 || value.envelope.payload.chain_id !== 8453) {
|
|
42
|
+
context.addIssue({
|
|
43
|
+
code: 'custom',
|
|
44
|
+
path: ['prepareInput', 'chainId'],
|
|
45
|
+
message: 'delegated sends are Base mainnet (8453) only',
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
/**
|
|
50
|
+
* What the tool REGISTERS as its input schema: permissive on purpose. The MCP SDK validates the registered
|
|
51
|
+
* schema BEFORE the handler runs, which would answer an unauthenticated caller with the exact validation
|
|
52
|
+
* failure — a schema oracle in front of the caller check. Registering a passthrough object moves ALL
|
|
53
|
+
* validation behind the header check: the handler parses `SUBMIT_UNDER_DELEGATION_INPUT_SCHEMA` itself,
|
|
54
|
+
* strictly, after the caller is accepted. The strict schema stays exported for consumers.
|
|
55
|
+
*/
|
|
56
|
+
export const DELEGATED_TOOL_TRANSPORT_SCHEMA = z.object({}).passthrough();
|
|
57
|
+
const SENT_STEP_SCHEMA = z
|
|
58
|
+
.object({
|
|
59
|
+
index: z.number().int().min(0).max(2),
|
|
60
|
+
kind: z.enum(['approve_reset', 'approve', 'call']),
|
|
61
|
+
to: z.string().regex(EVM_ADDRESS_PATTERN),
|
|
62
|
+
transaction_hash: z.string().regex(/^0x[0-9a-fA-F]{64}$/),
|
|
63
|
+
status: z.literal('success'),
|
|
64
|
+
block_number: z.string().regex(/^\d+$/),
|
|
65
|
+
gas_used: z.string().regex(/^\d+$/),
|
|
66
|
+
})
|
|
67
|
+
.strict();
|
|
68
|
+
export const DELEGATED_SEND_RESULT_SCHEMA = z
|
|
69
|
+
.object({
|
|
70
|
+
status: z.literal('sent'),
|
|
71
|
+
user_id: z.string().regex(END_USER_ID_PATTERN),
|
|
72
|
+
address: z.string().regex(EVM_ADDRESS_PATTERN),
|
|
73
|
+
chain_id: z.literal(8453),
|
|
74
|
+
artifact_id: z.string().regex(/^[0-9A-HJKMNP-TV-Z]{26}$/),
|
|
75
|
+
steps: z.array(SENT_STEP_SCHEMA).length(3),
|
|
76
|
+
fence: z
|
|
77
|
+
.object({
|
|
78
|
+
policy_id: z.string(),
|
|
79
|
+
policy_name: z.string(),
|
|
80
|
+
rules_digest: z.string().regex(/^[0-9a-f]{64}$/),
|
|
81
|
+
per_swap_cap_usd: z.literal(MODEL_B_PER_SWAP_CAP_USD),
|
|
82
|
+
})
|
|
83
|
+
.strict(),
|
|
84
|
+
delegation: z.object({ expires_at: z.string() }).strict(),
|
|
85
|
+
/** True when this response is the stored result of an EARLIER submission of the same artifact (nothing was re-sent). */
|
|
86
|
+
replayed: z.boolean().optional(),
|
|
87
|
+
verification: z
|
|
88
|
+
.object({
|
|
89
|
+
checks: z.array(z.string()).min(12),
|
|
90
|
+
gross: z.string().regex(/^\d+$/),
|
|
91
|
+
min_amount_out: z.string().regex(/^\d+$/),
|
|
92
|
+
otto_fee_atomic: z.string().regex(/^\d+$/),
|
|
93
|
+
otto_fee_recipient: z.string().regex(EVM_ADDRESS_PATTERN),
|
|
94
|
+
fee_config_version: z.string(),
|
|
95
|
+
receiver: z.string().regex(EVM_ADDRESS_PATTERN),
|
|
96
|
+
call_data_digest: z.string().regex(HEX_PATTERN).optional(),
|
|
97
|
+
})
|
|
98
|
+
.strict(),
|
|
99
|
+
})
|
|
100
|
+
.strict();
|
|
101
|
+
/** Not read-only, not idempotent: every accepted call moves the end user's funds through the delegation. */
|
|
102
|
+
export const DELEGATED_TOOL_ANNOTATIONS = Object.freeze({
|
|
103
|
+
readOnlyHint: false,
|
|
104
|
+
destructiveHint: true,
|
|
105
|
+
idempotentHint: false,
|
|
106
|
+
openWorldHint: true,
|
|
107
|
+
});
|
|
108
|
+
export const DELEGATED_EXECUTION_TOOL_DEFINITION = Object.freeze({
|
|
109
|
+
name: DELEGATED_EXECUTION_TOOL_NAME,
|
|
110
|
+
title: 'Submit an Otto-prepared swap under a CDP end-user delegation',
|
|
111
|
+
description: `Submit a verified otto_prepare_swap envelope on Base under the end user's CDP delegation. Arguments: { userId, envelope (the otto_prepare_swap result), prepareInput (the exact otto_prepare_swap request, with slippage.minAmountOut) } — validated strictly AFTER the caller is authenticated, so the listed input schema is intentionally permissive. Server-to-server only: the caller presents Otto's delegation secret in the \`${DELEGATION_AUTH_HEADER}\` request header (never in arguments); a call without it is refused before anything is read. Before any send the server confirms, in this order: the Model-B project policy is installed and reads back by name and rules digest; the user's grant is active past the envelope's valid_until; the account is one of that end user's own CDP accounts as CDP reports them; the envelope passes the 13-check verifier for exactly the request that produced it; the plan is inside the fence (allow-listed input token, reviewed spender, gross at or below the ${MODEL_B_PER_SWAP_CAP_USD} USD per-swap cap). Then the three steps (allowance reset, approval, swap) are submitted in order under the delegation, each waited to a sealed canonical block; a halt clears the allowance and reports the read-back. One delegated plan at a time per user. Coinbase's engine bounds the delegated key to Model-B swap shapes through LI.FI up to the per-swap cap; the receiver binding is Otto's server-side check, and the user can revoke at any time.`,
|
|
112
|
+
inputSchema: DELEGATED_TOOL_TRANSPORT_SCHEMA,
|
|
113
|
+
strictInputSchema: SUBMIT_UNDER_DELEGATION_INPUT_SCHEMA,
|
|
114
|
+
outputSchema: DELEGATED_SEND_RESULT_SCHEMA,
|
|
115
|
+
});
|
|
116
|
+
//# sourceMappingURL=execution-delegated-definition.js.map
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* execution-delegation-admin-definition.ts — the three header-authed delegation ADMIN tools (PR-1b, seat ruling
|
|
3
|
+
* D10 = (a), 2026-08-29): `otto_delegation_fence`, `otto_delegation_status`, `otto_delegation_revoke`.
|
|
4
|
+
*
|
|
5
|
+
* Same seam and admission as `otto_submit_under_delegation`: they register only with a rig, the caller presents
|
|
6
|
+
* Otto's server secret in the `x-otto-delegation-auth` request header (never in arguments), the registered
|
|
7
|
+
* transport schema is permissive and the strict schema is parsed only AFTER the header is accepted (no schema
|
|
8
|
+
* oracle in front of the caller check — `execution-delegated-definition.ts` states why).
|
|
9
|
+
*
|
|
10
|
+
* IDENTITY INVARIANT (seat ruling, 21:4x CEST): `status` and the user path of `revoke` never take a client-sent
|
|
11
|
+
* id — they take the end user's CDP ACCESS TOKEN and the server resolves the userId itself through
|
|
12
|
+
* `cdp.endUser.validateAccessToken`. A bare `userId` on `status` is refused by the strict schema. The SUPPORT path
|
|
13
|
+
* of `revoke` (`{ userId, support_reason }`) exists for an operator holding the server secret; the dApp's public
|
|
14
|
+
* proxy exposes only the token variant (asserted in PR-2).
|
|
15
|
+
*/
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { DELEGATED_TOOL_TRANSPORT_SCHEMA } from './execution-delegated-definition.js';
|
|
18
|
+
export declare const DELEGATION_FENCE_TOOL_NAME: "otto_delegation_fence";
|
|
19
|
+
export declare const DELEGATION_STATUS_TOOL_NAME: "otto_delegation_status";
|
|
20
|
+
export declare const DELEGATION_REVOKE_TOOL_NAME: "otto_delegation_revoke";
|
|
21
|
+
export declare const DELEGATION_ADMIN_TOOL_NAMES: readonly ["otto_delegation_fence", "otto_delegation_status", "otto_delegation_revoke"];
|
|
22
|
+
/** The one PUBLIC delegation tool: the fence's public promise (present / name / digest / cap) — never user or grant data. */
|
|
23
|
+
export declare const DELEGATION_FENCE_STATUS_TOOL_NAME: "otto_delegation_fence_status";
|
|
24
|
+
export declare const DELEGATION_FENCE_INPUT_SCHEMA: z.ZodObject<{}, z.core.$strict>;
|
|
25
|
+
export declare const DELEGATION_FENCE_RESULT_SCHEMA: z.ZodObject<{
|
|
26
|
+
status: z.ZodLiteral<"present">;
|
|
27
|
+
action: z.ZodEnum<{
|
|
28
|
+
unchanged: "unchanged";
|
|
29
|
+
created: "created";
|
|
30
|
+
updated: "updated";
|
|
31
|
+
}>;
|
|
32
|
+
policy_id: z.ZodString;
|
|
33
|
+
policy_name: z.ZodString;
|
|
34
|
+
rules_digest: z.ZodString;
|
|
35
|
+
per_swap_cap_usd: z.ZodLiteral<5000>;
|
|
36
|
+
}, z.core.$strict>;
|
|
37
|
+
export declare const DELEGATION_STATUS_INPUT_SCHEMA: z.ZodObject<{
|
|
38
|
+
accessToken: z.ZodString;
|
|
39
|
+
}, z.core.$strict>;
|
|
40
|
+
export declare const DELEGATION_STATUS_RESULT_SCHEMA: z.ZodObject<{
|
|
41
|
+
user_id: z.ZodString;
|
|
42
|
+
addresses: z.ZodArray<z.ZodString>;
|
|
43
|
+
fence: z.ZodObject<{
|
|
44
|
+
present: z.ZodBoolean;
|
|
45
|
+
policy_name: z.ZodString;
|
|
46
|
+
per_swap_cap_usd: z.ZodLiteral<5000>;
|
|
47
|
+
rules_digest: z.ZodOptional<z.ZodString>;
|
|
48
|
+
}, z.core.$strict>;
|
|
49
|
+
delegation: z.ZodObject<{
|
|
50
|
+
active: z.ZodBoolean;
|
|
51
|
+
expires_at: z.ZodOptional<z.ZodString>;
|
|
52
|
+
}, z.core.$strict>;
|
|
53
|
+
revocation_requested_here: z.ZodBoolean;
|
|
54
|
+
}, z.core.$strict>;
|
|
55
|
+
export declare const DELEGATION_REVOKE_INPUT_SCHEMA: z.ZodUnion<readonly [z.ZodObject<{
|
|
56
|
+
accessToken: z.ZodString;
|
|
57
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
58
|
+
userId: z.ZodString;
|
|
59
|
+
support_reason: z.ZodString;
|
|
60
|
+
}, z.core.$strict>]>;
|
|
61
|
+
export declare const DELEGATION_REVOKE_RESULT_SCHEMA: z.ZodObject<{
|
|
62
|
+
user_id: z.ZodString;
|
|
63
|
+
revoked: z.ZodLiteral<true>;
|
|
64
|
+
reads_back: z.ZodLiteral<false>;
|
|
65
|
+
path: z.ZodEnum<{
|
|
66
|
+
user: "user";
|
|
67
|
+
support: "support";
|
|
68
|
+
}>;
|
|
69
|
+
}, z.core.$strict>;
|
|
70
|
+
export type DelegationFenceResult = z.infer<typeof DELEGATION_FENCE_RESULT_SCHEMA>;
|
|
71
|
+
export declare const DELEGATION_FENCE_STATUS_INPUT_SCHEMA: z.ZodObject<{}, z.core.$strict>;
|
|
72
|
+
export declare const DELEGATION_FENCE_STATUS_RESULT_SCHEMA: z.ZodObject<{
|
|
73
|
+
present: z.ZodBoolean;
|
|
74
|
+
policy_name: z.ZodString;
|
|
75
|
+
per_swap_cap_usd: z.ZodLiteral<5000>;
|
|
76
|
+
project_id: z.ZodOptional<z.ZodString>;
|
|
77
|
+
rules_digest: z.ZodOptional<z.ZodString>;
|
|
78
|
+
reason: z.ZodOptional<z.ZodEnum<{
|
|
79
|
+
absent: "absent";
|
|
80
|
+
ambiguous: "ambiguous";
|
|
81
|
+
name_mismatch: "name_mismatch";
|
|
82
|
+
digest_mismatch: "digest_mismatch";
|
|
83
|
+
}>>;
|
|
84
|
+
}, z.core.$strict>;
|
|
85
|
+
export type DelegationFenceStatusResult = z.infer<typeof DELEGATION_FENCE_STATUS_RESULT_SCHEMA>;
|
|
86
|
+
export declare const DELEGATION_FENCE_STATUS_TOOL_DEFINITION: Readonly<{
|
|
87
|
+
name: "otto_delegation_fence_status";
|
|
88
|
+
title: "Read the Model-B delegation fence (public promise: present, name, rules digest, per-swap cap)";
|
|
89
|
+
description: "PUBLIC and read-only, no header: whether the Model-B project policy that fences every delegated send is installed and v-current on this server, with its versioned name, rules digest, the per-swap cap in USD, and Otto's PUBLIC CDP project id (a UUID — the authority an agent binds its mint to). Exposes ONLY the fence's public promise — never an end user, a grant, an address or a policy's contents. An agent about to mint a delegation (otto-execute delegate) reads this first, mints to the published project_id and no other, and refuses to mint when the fence is not present; the server enforces fence-before-authority on every delegated send regardless. Arguments: {}.";
|
|
90
|
+
inputSchema: z.ZodObject<{}, z.core.$strict>;
|
|
91
|
+
outputSchema: z.ZodObject<{
|
|
92
|
+
present: z.ZodBoolean;
|
|
93
|
+
policy_name: z.ZodString;
|
|
94
|
+
per_swap_cap_usd: z.ZodLiteral<5000>;
|
|
95
|
+
project_id: z.ZodOptional<z.ZodString>;
|
|
96
|
+
rules_digest: z.ZodOptional<z.ZodString>;
|
|
97
|
+
reason: z.ZodOptional<z.ZodEnum<{
|
|
98
|
+
absent: "absent";
|
|
99
|
+
ambiguous: "ambiguous";
|
|
100
|
+
name_mismatch: "name_mismatch";
|
|
101
|
+
digest_mismatch: "digest_mismatch";
|
|
102
|
+
}>>;
|
|
103
|
+
}, z.core.$strict>;
|
|
104
|
+
annotations: Readonly<{
|
|
105
|
+
readOnlyHint: true;
|
|
106
|
+
destructiveHint: false;
|
|
107
|
+
idempotentHint: true;
|
|
108
|
+
openWorldHint: true;
|
|
109
|
+
}>;
|
|
110
|
+
}>;
|
|
111
|
+
export type DelegationStatusResult = z.infer<typeof DELEGATION_STATUS_RESULT_SCHEMA>;
|
|
112
|
+
export type DelegationRevokeResult = z.infer<typeof DELEGATION_REVOKE_RESULT_SCHEMA>;
|
|
113
|
+
export interface DelegationAdminToolDefinition<TStrict extends z.ZodTypeAny, TOut extends z.ZodTypeAny> {
|
|
114
|
+
readonly name: (typeof DELEGATION_ADMIN_TOOL_NAMES)[number];
|
|
115
|
+
readonly title: string;
|
|
116
|
+
readonly description: string;
|
|
117
|
+
readonly inputSchema: typeof DELEGATED_TOOL_TRANSPORT_SCHEMA;
|
|
118
|
+
readonly strictInputSchema: TStrict;
|
|
119
|
+
readonly outputSchema: TOut;
|
|
120
|
+
readonly annotations: {
|
|
121
|
+
readonly readOnlyHint: boolean;
|
|
122
|
+
readonly destructiveHint: boolean;
|
|
123
|
+
readonly idempotentHint: boolean;
|
|
124
|
+
readonly openWorldHint: boolean;
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
export declare const DELEGATION_FENCE_TOOL_DEFINITION: Readonly<{
|
|
128
|
+
name: "otto_delegation_fence";
|
|
129
|
+
title: "Ensure the Model-B project policy (the delegation fence) is installed and reads back as v-current";
|
|
130
|
+
description: `Idempotent ensure-by-digest of the ONE CDP project-scope policy that fences every delegated send, then a read-back. Writes only when no project policy exists (creates it) or when the existing one is Otto's own lineage with stale rules (updates it in place). REFUSES BY NAME if a different project-scope policy is installed \u2014 it is never overwritten or deleted. Never downgrades a newer fence. An OPS action: at most one ensure runs project-wide at a time, never during a rolling deploy, never from the dApp (the mint gate reads fence.present from otto_delegation_status). Arguments: {}. Output: the policy id, versioned name, rules digest and the per-swap cap; the tool fails unless the fence reads back present under the written id. ${string}`;
|
|
131
|
+
inputSchema: z.ZodObject<{}, z.core.$loose>;
|
|
132
|
+
strictInputSchema: z.ZodObject<{}, z.core.$strict>;
|
|
133
|
+
outputSchema: z.ZodObject<{
|
|
134
|
+
status: z.ZodLiteral<"present">;
|
|
135
|
+
action: z.ZodEnum<{
|
|
136
|
+
unchanged: "unchanged";
|
|
137
|
+
created: "created";
|
|
138
|
+
updated: "updated";
|
|
139
|
+
}>;
|
|
140
|
+
policy_id: z.ZodString;
|
|
141
|
+
policy_name: z.ZodString;
|
|
142
|
+
rules_digest: z.ZodString;
|
|
143
|
+
per_swap_cap_usd: z.ZodLiteral<5000>;
|
|
144
|
+
}, z.core.$strict>;
|
|
145
|
+
annotations: Readonly<{
|
|
146
|
+
readOnlyHint: false;
|
|
147
|
+
destructiveHint: false;
|
|
148
|
+
idempotentHint: true;
|
|
149
|
+
openWorldHint: true;
|
|
150
|
+
}>;
|
|
151
|
+
}>;
|
|
152
|
+
export declare const DELEGATION_STATUS_TOOL_DEFINITION: Readonly<{
|
|
153
|
+
name: "otto_delegation_status";
|
|
154
|
+
title: "Read one end user's delegation status (token-bound)";
|
|
155
|
+
description: `Read back, for the end user identified by a CDP access token, their CDP accounts, whether the Model-B fence is present, and whether an active delegation reads back (with its expiry). Arguments: { accessToken } \u2014 the server resolves the user from the token; a client-sent user id is refused. Output is bound to that user; nothing about any other user is returned. ${string}`;
|
|
156
|
+
inputSchema: z.ZodObject<{}, z.core.$loose>;
|
|
157
|
+
strictInputSchema: z.ZodObject<{
|
|
158
|
+
accessToken: z.ZodString;
|
|
159
|
+
}, z.core.$strict>;
|
|
160
|
+
outputSchema: z.ZodObject<{
|
|
161
|
+
user_id: z.ZodString;
|
|
162
|
+
addresses: z.ZodArray<z.ZodString>;
|
|
163
|
+
fence: z.ZodObject<{
|
|
164
|
+
present: z.ZodBoolean;
|
|
165
|
+
policy_name: z.ZodString;
|
|
166
|
+
per_swap_cap_usd: z.ZodLiteral<5000>;
|
|
167
|
+
rules_digest: z.ZodOptional<z.ZodString>;
|
|
168
|
+
}, z.core.$strict>;
|
|
169
|
+
delegation: z.ZodObject<{
|
|
170
|
+
active: z.ZodBoolean;
|
|
171
|
+
expires_at: z.ZodOptional<z.ZodString>;
|
|
172
|
+
}, z.core.$strict>;
|
|
173
|
+
revocation_requested_here: z.ZodBoolean;
|
|
174
|
+
}, z.core.$strict>;
|
|
175
|
+
annotations: Readonly<{
|
|
176
|
+
readOnlyHint: true;
|
|
177
|
+
destructiveHint: false;
|
|
178
|
+
idempotentHint: true;
|
|
179
|
+
openWorldHint: true;
|
|
180
|
+
}>;
|
|
181
|
+
}>;
|
|
182
|
+
export declare const DELEGATION_REVOKE_TOOL_DEFINITION: Readonly<{
|
|
183
|
+
name: "otto_delegation_revoke";
|
|
184
|
+
title: "Revoke one end user's delegation, developer-side, confirmed by read-back";
|
|
185
|
+
description: `Revoke the end user's delegation from Otto's side and confirm by read-back that it no longer exists; from then on this server submits nothing for that user (fail-closed, even if the read-back had failed). Arguments: EITHER { accessToken } (the user path \u2014 the server resolves the user from the token) OR { userId, support_reason } (the support path, for an operator holding the server secret; the reason is logged, the token never is). ${string}`;
|
|
186
|
+
inputSchema: z.ZodObject<{}, z.core.$loose>;
|
|
187
|
+
strictInputSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
188
|
+
accessToken: z.ZodString;
|
|
189
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
190
|
+
userId: z.ZodString;
|
|
191
|
+
support_reason: z.ZodString;
|
|
192
|
+
}, z.core.$strict>]>;
|
|
193
|
+
outputSchema: z.ZodObject<{
|
|
194
|
+
user_id: z.ZodString;
|
|
195
|
+
revoked: z.ZodLiteral<true>;
|
|
196
|
+
reads_back: z.ZodLiteral<false>;
|
|
197
|
+
path: z.ZodEnum<{
|
|
198
|
+
user: "user";
|
|
199
|
+
support: "support";
|
|
200
|
+
}>;
|
|
201
|
+
}, z.core.$strict>;
|
|
202
|
+
annotations: Readonly<{
|
|
203
|
+
readOnlyHint: false;
|
|
204
|
+
destructiveHint: true;
|
|
205
|
+
idempotentHint: true;
|
|
206
|
+
openWorldHint: true;
|
|
207
|
+
}>;
|
|
208
|
+
}>;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* execution-delegation-admin-definition.ts — the three header-authed delegation ADMIN tools (PR-1b, seat ruling
|
|
3
|
+
* D10 = (a), 2026-08-29): `otto_delegation_fence`, `otto_delegation_status`, `otto_delegation_revoke`.
|
|
4
|
+
*
|
|
5
|
+
* Same seam and admission as `otto_submit_under_delegation`: they register only with a rig, the caller presents
|
|
6
|
+
* Otto's server secret in the `x-otto-delegation-auth` request header (never in arguments), the registered
|
|
7
|
+
* transport schema is permissive and the strict schema is parsed only AFTER the header is accepted (no schema
|
|
8
|
+
* oracle in front of the caller check — `execution-delegated-definition.ts` states why).
|
|
9
|
+
*
|
|
10
|
+
* IDENTITY INVARIANT (seat ruling, 21:4x CEST): `status` and the user path of `revoke` never take a client-sent
|
|
11
|
+
* id — they take the end user's CDP ACCESS TOKEN and the server resolves the userId itself through
|
|
12
|
+
* `cdp.endUser.validateAccessToken`. A bare `userId` on `status` is refused by the strict schema. The SUPPORT path
|
|
13
|
+
* of `revoke` (`{ userId, support_reason }`) exists for an operator holding the server secret; the dApp's public
|
|
14
|
+
* proxy exposes only the token variant (asserted in PR-2).
|
|
15
|
+
*/
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { MODEL_B_PER_SWAP_CAP_USD } from './execution-delegation-policy.js';
|
|
18
|
+
import { DELEGATION_AUTH_HEADER, DELEGATED_TOOL_TRANSPORT_SCHEMA, } from './execution-delegated-definition.js';
|
|
19
|
+
export const DELEGATION_FENCE_TOOL_NAME = 'otto_delegation_fence';
|
|
20
|
+
export const DELEGATION_STATUS_TOOL_NAME = 'otto_delegation_status';
|
|
21
|
+
export const DELEGATION_REVOKE_TOOL_NAME = 'otto_delegation_revoke';
|
|
22
|
+
export const DELEGATION_ADMIN_TOOL_NAMES = Object.freeze([
|
|
23
|
+
DELEGATION_FENCE_TOOL_NAME,
|
|
24
|
+
DELEGATION_STATUS_TOOL_NAME,
|
|
25
|
+
DELEGATION_REVOKE_TOOL_NAME,
|
|
26
|
+
]);
|
|
27
|
+
/** The one PUBLIC delegation tool: the fence's public promise (present / name / digest / cap) — never user or grant data. */
|
|
28
|
+
export const DELEGATION_FENCE_STATUS_TOOL_NAME = 'otto_delegation_fence_status';
|
|
29
|
+
const END_USER_ID_PATTERN = /^[a-zA-Z0-9-]{1,100}$/;
|
|
30
|
+
const EVM_ADDRESS_PATTERN = /^0x[0-9a-fA-F]{40}$/;
|
|
31
|
+
/** A CDP end-user access token: opaque, bounded, never logged, never echoed. */
|
|
32
|
+
const ACCESS_TOKEN_SCHEMA = z.string().min(16).max(8192);
|
|
33
|
+
/* ── fence ── */
|
|
34
|
+
export const DELEGATION_FENCE_INPUT_SCHEMA = z.object({}).strict();
|
|
35
|
+
export const DELEGATION_FENCE_RESULT_SCHEMA = z
|
|
36
|
+
.object({
|
|
37
|
+
status: z.literal('present'),
|
|
38
|
+
/** What this call did: nothing (already v-current), created it, or updated our own stale lineage in place. */
|
|
39
|
+
action: z.enum(['unchanged', 'created', 'updated']),
|
|
40
|
+
policy_id: z.string(),
|
|
41
|
+
policy_name: z.string(),
|
|
42
|
+
rules_digest: z.string().regex(/^[0-9a-f]{64}$/),
|
|
43
|
+
per_swap_cap_usd: z.literal(MODEL_B_PER_SWAP_CAP_USD),
|
|
44
|
+
})
|
|
45
|
+
.strict();
|
|
46
|
+
/* ── status ── */
|
|
47
|
+
export const DELEGATION_STATUS_INPUT_SCHEMA = z
|
|
48
|
+
.object({ accessToken: ACCESS_TOKEN_SCHEMA })
|
|
49
|
+
.strict();
|
|
50
|
+
export const DELEGATION_STATUS_RESULT_SCHEMA = z
|
|
51
|
+
.object({
|
|
52
|
+
user_id: z.string().regex(END_USER_ID_PATTERN),
|
|
53
|
+
addresses: z.array(z.string().regex(EVM_ADDRESS_PATTERN)),
|
|
54
|
+
fence: z
|
|
55
|
+
.object({
|
|
56
|
+
present: z.boolean(),
|
|
57
|
+
policy_name: z.string(),
|
|
58
|
+
per_swap_cap_usd: z.literal(MODEL_B_PER_SWAP_CAP_USD),
|
|
59
|
+
rules_digest: z
|
|
60
|
+
.string()
|
|
61
|
+
.regex(/^[0-9a-f]{64}$/)
|
|
62
|
+
.optional(),
|
|
63
|
+
})
|
|
64
|
+
.strict(),
|
|
65
|
+
delegation: z
|
|
66
|
+
.object({
|
|
67
|
+
/** Active = reads back from CDP, not expired, and no revocation was requested on this server. */
|
|
68
|
+
active: z.boolean(),
|
|
69
|
+
expires_at: z.string().optional(),
|
|
70
|
+
})
|
|
71
|
+
.strict(),
|
|
72
|
+
revocation_requested_here: z.boolean(),
|
|
73
|
+
})
|
|
74
|
+
.strict();
|
|
75
|
+
/* ── revoke ── */
|
|
76
|
+
export const DELEGATION_REVOKE_INPUT_SCHEMA = z.union([
|
|
77
|
+
z.object({ accessToken: ACCESS_TOKEN_SCHEMA }).strict(),
|
|
78
|
+
z
|
|
79
|
+
.object({
|
|
80
|
+
userId: z.string().regex(END_USER_ID_PATTERN),
|
|
81
|
+
support_reason: z.string().min(8).max(200),
|
|
82
|
+
})
|
|
83
|
+
.strict(),
|
|
84
|
+
]);
|
|
85
|
+
export const DELEGATION_REVOKE_RESULT_SCHEMA = z
|
|
86
|
+
.object({
|
|
87
|
+
user_id: z.string().regex(END_USER_ID_PATTERN),
|
|
88
|
+
revoked: z.literal(true),
|
|
89
|
+
/** The grant no longer reads back from CDP (the revoke is confirmed by read-back, never inferred). */
|
|
90
|
+
reads_back: z.literal(false),
|
|
91
|
+
path: z.enum(['user', 'support']),
|
|
92
|
+
})
|
|
93
|
+
.strict();
|
|
94
|
+
/* ── fence status (public, read-only, no header) ── */
|
|
95
|
+
export const DELEGATION_FENCE_STATUS_INPUT_SCHEMA = z.object({}).strict();
|
|
96
|
+
export const DELEGATION_FENCE_STATUS_RESULT_SCHEMA = z
|
|
97
|
+
.object({
|
|
98
|
+
present: z.boolean(),
|
|
99
|
+
policy_name: z.string(),
|
|
100
|
+
per_swap_cap_usd: z.literal(MODEL_B_PER_SWAP_CAP_USD),
|
|
101
|
+
/** Otto's PUBLIC CDP project id (a UUID): the authority the agent CLI binds its mint to. */
|
|
102
|
+
project_id: z
|
|
103
|
+
.string()
|
|
104
|
+
.regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)
|
|
105
|
+
.optional(),
|
|
106
|
+
rules_digest: z
|
|
107
|
+
.string()
|
|
108
|
+
.regex(/^[0-9a-f]{64}$/)
|
|
109
|
+
.optional(),
|
|
110
|
+
/** Why it is not present — a class, never a policy's contents. */
|
|
111
|
+
reason: z.enum(['absent', 'ambiguous', 'name_mismatch', 'digest_mismatch']).optional(),
|
|
112
|
+
})
|
|
113
|
+
.strict();
|
|
114
|
+
export const DELEGATION_FENCE_STATUS_TOOL_DEFINITION = Object.freeze({
|
|
115
|
+
name: DELEGATION_FENCE_STATUS_TOOL_NAME,
|
|
116
|
+
title: 'Read the Model-B delegation fence (public promise: present, name, rules digest, per-swap cap)',
|
|
117
|
+
description: `PUBLIC and read-only, no header: whether the Model-B project policy that fences every delegated send is installed and v-current on this server, with its versioned name, rules digest, the per-swap cap in USD, and Otto's PUBLIC CDP project id (a UUID — the authority an agent binds its mint to). Exposes ONLY the fence's public promise — never an end user, a grant, an address or a policy's contents. An agent about to mint a delegation (otto-execute delegate) reads this first, mints to the published project_id and no other, and refuses to mint when the fence is not present; the server enforces fence-before-authority on every delegated send regardless. Arguments: {}.`,
|
|
118
|
+
inputSchema: DELEGATION_FENCE_STATUS_INPUT_SCHEMA,
|
|
119
|
+
outputSchema: DELEGATION_FENCE_STATUS_RESULT_SCHEMA,
|
|
120
|
+
annotations: Object.freeze({
|
|
121
|
+
readOnlyHint: true,
|
|
122
|
+
destructiveHint: false,
|
|
123
|
+
idempotentHint: true,
|
|
124
|
+
openWorldHint: true,
|
|
125
|
+
}),
|
|
126
|
+
});
|
|
127
|
+
const AUTH_SENTENCE = `Server-to-server only: the caller presents Otto's delegation secret in the \`${DELEGATION_AUTH_HEADER}\` request header (never in arguments); a call without it is refused before anything is read. The listed input schema is intentionally permissive — the strict schema is validated after the caller is authenticated.`;
|
|
128
|
+
export const DELEGATION_FENCE_TOOL_DEFINITION = Object.freeze({
|
|
129
|
+
name: DELEGATION_FENCE_TOOL_NAME,
|
|
130
|
+
title: 'Ensure the Model-B project policy (the delegation fence) is installed and reads back as v-current',
|
|
131
|
+
description: `Idempotent ensure-by-digest of the ONE CDP project-scope policy that fences every delegated send, then a read-back. Writes only when no project policy exists (creates it) or when the existing one is Otto's own lineage with stale rules (updates it in place). REFUSES BY NAME if a different project-scope policy is installed — it is never overwritten or deleted. Never downgrades a newer fence. An OPS action: at most one ensure runs project-wide at a time, never during a rolling deploy, never from the dApp (the mint gate reads fence.present from otto_delegation_status). Arguments: {}. Output: the policy id, versioned name, rules digest and the per-swap cap; the tool fails unless the fence reads back present under the written id. ${AUTH_SENTENCE}`,
|
|
132
|
+
inputSchema: DELEGATED_TOOL_TRANSPORT_SCHEMA,
|
|
133
|
+
strictInputSchema: DELEGATION_FENCE_INPUT_SCHEMA,
|
|
134
|
+
outputSchema: DELEGATION_FENCE_RESULT_SCHEMA,
|
|
135
|
+
annotations: Object.freeze({
|
|
136
|
+
readOnlyHint: false,
|
|
137
|
+
destructiveHint: false,
|
|
138
|
+
idempotentHint: true,
|
|
139
|
+
openWorldHint: true,
|
|
140
|
+
}),
|
|
141
|
+
});
|
|
142
|
+
export const DELEGATION_STATUS_TOOL_DEFINITION = Object.freeze({
|
|
143
|
+
name: DELEGATION_STATUS_TOOL_NAME,
|
|
144
|
+
title: "Read one end user's delegation status (token-bound)",
|
|
145
|
+
description: `Read back, for the end user identified by a CDP access token, their CDP accounts, whether the Model-B fence is present, and whether an active delegation reads back (with its expiry). Arguments: { accessToken } — the server resolves the user from the token; a client-sent user id is refused. Output is bound to that user; nothing about any other user is returned. ${AUTH_SENTENCE}`,
|
|
146
|
+
inputSchema: DELEGATED_TOOL_TRANSPORT_SCHEMA,
|
|
147
|
+
strictInputSchema: DELEGATION_STATUS_INPUT_SCHEMA,
|
|
148
|
+
outputSchema: DELEGATION_STATUS_RESULT_SCHEMA,
|
|
149
|
+
annotations: Object.freeze({
|
|
150
|
+
readOnlyHint: true,
|
|
151
|
+
destructiveHint: false,
|
|
152
|
+
idempotentHint: true,
|
|
153
|
+
openWorldHint: true,
|
|
154
|
+
}),
|
|
155
|
+
});
|
|
156
|
+
export const DELEGATION_REVOKE_TOOL_DEFINITION = Object.freeze({
|
|
157
|
+
name: DELEGATION_REVOKE_TOOL_NAME,
|
|
158
|
+
title: "Revoke one end user's delegation, developer-side, confirmed by read-back",
|
|
159
|
+
description: `Revoke the end user's delegation from Otto's side and confirm by read-back that it no longer exists; from then on this server submits nothing for that user (fail-closed, even if the read-back had failed). Arguments: EITHER { accessToken } (the user path — the server resolves the user from the token) OR { userId, support_reason } (the support path, for an operator holding the server secret; the reason is logged, the token never is). ${AUTH_SENTENCE}`,
|
|
160
|
+
inputSchema: DELEGATED_TOOL_TRANSPORT_SCHEMA,
|
|
161
|
+
strictInputSchema: DELEGATION_REVOKE_INPUT_SCHEMA,
|
|
162
|
+
outputSchema: DELEGATION_REVOKE_RESULT_SCHEMA,
|
|
163
|
+
annotations: Object.freeze({
|
|
164
|
+
readOnlyHint: false,
|
|
165
|
+
destructiveHint: true,
|
|
166
|
+
idempotentHint: true,
|
|
167
|
+
openWorldHint: true,
|
|
168
|
+
}),
|
|
169
|
+
});
|
|
170
|
+
//# sourceMappingURL=execution-delegation-admin-definition.js.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* execution-delegation-admin.ts — the three delegation ADMIN tools on the hosted MCP (PR-1b, seat ruling D10 = (a)).
|
|
3
|
+
*
|
|
4
|
+
* otto_delegation_fence — the ONLY writer of the Model-B project policy: ensure-by-digest + read-back.
|
|
5
|
+
* otto_delegation_status — one user's grant/fence read-back, bound to the user a CDP access token resolves to.
|
|
6
|
+
* otto_delegation_revoke — developer-side revoke with read-back, fail-closed under the rig lock.
|
|
7
|
+
*
|
|
8
|
+
* Every tool: the request header FIRST (constant-time, before parsing or any CDP read — the same seam as
|
|
9
|
+
* `otto_submit_under_delegation`), then the strict schema, then the reads. Nothing here logs a token, the
|
|
10
|
+
* header, or a credential; refusals are machine-readable codes.
|
|
11
|
+
*
|
|
12
|
+
* FENCE RULES (gate r1 of this PR sharpened them; each is mutation-proved in the tests):
|
|
13
|
+
* CARDINALITY — Coinbase allows exactly one project-scope policy. A read-back with MORE than one is an
|
|
14
|
+
* anomalous snapshot and refuses (`DELEGATION_FENCE_AMBIGUOUS`) before any other judgement — nothing is
|
|
15
|
+
* written, nothing is blessed as present on the strength of "one of them matches".
|
|
16
|
+
* LINEAGE — a project policy is OURS only when its description matches the ANCHORED pattern
|
|
17
|
+
* `^OTTO model b swap fence v<N>$` (`modelBLineageVersion`). Ours with N == this binary's version and the
|
|
18
|
+
* v-current rules → `unchanged`. Ours with N ≤ this version and stale rules → updated IN PLACE. Ours with
|
|
19
|
+
* N > this version → refused (`DELEGATION_FENCE_NEWER`): a running older binary never downgrades a newer
|
|
20
|
+
* fence (rollback is a governed manual act). Anything else — the proof kit's `OTTO PROBE …`, a hand-installed
|
|
21
|
+
* policy, no description, the prefix in the middle, a suffix — is FOREIGN: refused by name
|
|
22
|
+
* (`DELEGATION_FENCE_FOREIGN`, naming the description + rules-digest prefix); never overwritten, never deleted.
|
|
23
|
+
* WRITE ONCE — a create that Coinbase refuses as a duplicate (`already_exists` / 409: someone created a policy
|
|
24
|
+
* between the snapshot and the write) is never retried and never turned into an update from this path: the
|
|
25
|
+
* tool re-reads once and judges what is there.
|
|
26
|
+
* BEFORE AN UPDATE — the policy is re-read BY ID immediately before the write and must still be exactly the
|
|
27
|
+
* snapshot (same description, same rules digest); any change → refused, nothing written. `@coinbase/cdp-sdk`
|
|
28
|
+
* 1.54.0 offers no conditional update (`UpdatePolicyOptions` = { id, policy, idempotencyKey } — no revision,
|
|
29
|
+
* no ETag), so the window between that re-read and the write cannot be closed by code. It is closed by the
|
|
30
|
+
* OPERATIONAL PREMISE, stated here, in the tool description and in the README: AT MOST ONE ENSURE WRITES
|
|
31
|
+
* PROJECT-WIDE AT A TIME, ACROSS VERSIONS — the ensure is an OPS action run by one operator, never during a
|
|
32
|
+
* rolling deploy (two versions of this tool running at once are two writers, and the older one would win a
|
|
33
|
+
* race and downgrade); the dApp never invokes it (PR-2's mint gate reads `fence.present` from
|
|
34
|
+
* `otto_delegation_status`); nobody edits the policy in the CDP portal while an ensure runs. Under that
|
|
35
|
+
* premise the re-read is exact; without it, "never overwrite foreign" and "never downgrade" narrow to
|
|
36
|
+
* "…what was foreign / newer one round-trip ago".
|
|
37
|
+
* READ-BACK BOUND TO THE WRITE — after a write, the proof is a read-back of exactly one project policy whose
|
|
38
|
+
* id IS the id that was written (the created id, or the updated id) and which reads back as v-current. A
|
|
39
|
+
* matching record under another id (a stale cache, a ghost) is not proof; a bounded poll covers the engine's
|
|
40
|
+
* asynchronous propagation, then the tool fails closed (`DELEGATION_FENCE_ABSENT`).
|
|
41
|
+
*
|
|
42
|
+
* IDENTITY. `status` and the user path of `revoke` resolve the userId from `cdp.endUser.validateAccessToken`;
|
|
43
|
+
* a client-sent id is refused by the strict schema. The support path of `revoke` takes `{ userId,
|
|
44
|
+
* support_reason }` from an operator holding the server secret; the reason is written to the structured log
|
|
45
|
+
* through the boot redactor (URLs and configured secret values never land in the log), the token never is.
|
|
46
|
+
*/
|
|
47
|
+
import { type DelegationFenceResult, type DelegationFenceStatusResult, type DelegationRevokeResult, type DelegationStatusResult } from './execution-delegation-admin-definition.js';
|
|
48
|
+
import { type DelegationCaller } from './execution-delegation.js';
|
|
49
|
+
import type { ExecutionToolRuntime } from './execution-tools.js';
|
|
50
|
+
/** Read-back after a write: the engine propagates asynchronously (the proof kit waited 3 s flat). */
|
|
51
|
+
export interface FenceReadBackWait {
|
|
52
|
+
readonly pollMs?: number;
|
|
53
|
+
readonly maxPolls?: number;
|
|
54
|
+
}
|
|
55
|
+
/** `otto_delegation_fence` — ensure-by-digest + read-back bound to the write; the only writer of the project policy. */
|
|
56
|
+
export declare function ensureDelegationFence(runtime: ExecutionToolRuntime, rawInput: unknown, caller: DelegationCaller, wait?: FenceReadBackWait): Promise<DelegationFenceResult>;
|
|
57
|
+
/**
|
|
58
|
+
* `otto_delegation_fence_status` — PUBLIC, read-only, no caller check: the fence's public promise. Nothing here
|
|
59
|
+
* names a user, a grant, an address or a policy's contents; `reason` is a class from `readBackModelBFence`.
|
|
60
|
+
*/
|
|
61
|
+
export declare function readDelegationFenceStatus(runtime: ExecutionToolRuntime): Promise<DelegationFenceStatusResult>;
|
|
62
|
+
/** `otto_delegation_status` — one user's read-back, bound to the token's user. */
|
|
63
|
+
export declare function readDelegationStatus(runtime: ExecutionToolRuntime, rawInput: unknown, caller: DelegationCaller): Promise<DelegationStatusResult>;
|
|
64
|
+
/** What the support path writes to the structured log: the user id and the REDACTED reason — never a token. */
|
|
65
|
+
export interface SupportActionSink {
|
|
66
|
+
write(entry: {
|
|
67
|
+
readonly action: 'delegation_revoke';
|
|
68
|
+
readonly userId: string;
|
|
69
|
+
readonly reason: string;
|
|
70
|
+
readonly ts: string;
|
|
71
|
+
}): void;
|
|
72
|
+
}
|
|
73
|
+
/** `otto_delegation_revoke` — developer-side revoke, read-back inside the rig lock, fail-closed afterwards. */
|
|
74
|
+
export declare function revokeDelegation(runtime: ExecutionToolRuntime, rawInput: unknown, caller: DelegationCaller, supportSink?: SupportActionSink): Promise<DelegationRevokeResult>;
|