v0.1.0-alpha.3
Pre-releaseAdded
-
counterparty_allowlist'sKNOWN_ISSUERkind gains an opt-ingate_inflows
flag (defaultfalse, so existing policy files parse and behave unchanged).
Whentrue,KNOWN_ISSUERevaluates every leg of the descriptor — debit
and inflow alike — instead of debit legs only, so tokens received from an
un-allowlisted issuer (Blend withdraw/borrow proceeds, vault withdrawals)
are gated too. An inflow leg whose asset is unresolvable denies fail-closed,
the same posture as the existing debit handling. The other counterparty
kinds (G_ACCOUNT/C_ACCOUNT/HOME_DOMAIN) are unaffected. (#39) -
profile enroll-owner-keyenrols the policy-file owner ed25519 PUBLIC key
from an operator-held seed, andprofile sign-policysigns a V1 policy file
with that seed so the engine accepts it. Together they make
policy.engine = "v1"usable end to end: no shipped command previously
produced the[signature]table the engine requires, so selectingv1
failed closed. (#30) -
stellar_agent_core::policy::v1::signature::sign, the owner-signature
primitive that is the exact inverse ofverify. (#30) -
Value-moving verbs now write a hash-chained, HMAC-signed
value_action_submittedaudit row after a confirmed on-chain submit,
recording the SAME value legs the policy gate sized (single-derivation
invariant), the redacted transaction hash, and the ledger. This covers the MCP
stellar_pay/stellar_create_account/stellar_claim/stellar_trustline
commit tools, the Blend / DEX / DeFindex adapters, the opaque
stellar_sep43_sign_and_submit_transactionpath, and the CLIpay/
claim/accounts create(sponsored) /trustlineverbs. The x402
payment authorizers write their ownx402_payment_authorizedrow at
authorization signing (there is no on-chain submit on that path), carrying
the gate-sized legs plus the settle network and scheme. A
DeFi adapter that fails on submit records asa_raw_invocationrow instead.
Emission is non-fatal post-submit: a row-write failure logs a warning and
never changes the result. (#21) -
PolicyEnginegainsevaluate_full/evaluate_with_value_full, which return
anEvaluation { decision, value_effects }surfacing the value descriptor the
gate sized on the allow path; the decision-onlyevaluate/
evaluate_with_valueremain as thin views. Value-verb dispatch uses the
_fullmethods so the post-submit audit row records exactly the legs the gate
evaluated rather than re-deriving them. (#21) -
The six key-writing profile commands —
enroll-signer,enroll-owner-key,
rotate-nonce-key,rotate-attestation-key,rotate-counterparty-key, and
rotate-audit-key— now write akeyring_key_writtenaudit row recording the
key purpose and, where applicable, the redacted public address. (#34) -
profile rotate-audit-keyrotates the audit chain-root HMAC key and re-signs
every per-file chain-root sidecar with the new key soaudit verifystays
green across the rotation; the new key is persisted before any sidecar is
re-signed. (#34) -
Offline envelope-shape regression coverage for the
nonce.mint_failed
business error on the four two-phase simulate handlers (stellar_pay,
stellar_create_account,stellar_claim,stellar_trustline) and for the
RPC-dependentsep48.spec_fetch_failed/sep48.render_failed/
sep47.discovery_failedarms ofstellar_sep48_preview_invocation/
stellar_sep47_discover, each asserting the full normalised envelope
(ok:false, the documented wire code, a non-emptyrequest_id,
is_error == Some(true)). (#36) -
Testnet acceptance coverage for a sponsored
stellar_create_account/
stellar_create_account_committwo-phase call: the destination account
exists on-chain afterward with the sponsored starting balance, and the
commit recorded avalue_action_submittedaudit row. (#43) -
Testnet acceptance coverage for a classic
stellar_trustline/
stellar_trustline_committwo-phase call against the pinned testnet USDC
issuer, run under aminimum_reservepolicy rule the funded source account
satisfies: the simulate and commit steps both reachingok:true(rather
thanpolicy.criterion_evaluation_failed) is on-chain proof that both
dispatch points supply a genuinely populatedaccount_view(#47). Asserts
the on-chain trustline limit and the commit'svalue_action_submitted
audit row. (#43) -
profile rotate-audit-keygained therun_with_dependenciesseam already
used by the other key-writing profile commands, so its unit coverage now
drives the actual persist → re-sign → emit sequence rather than a parallel
reimplementation of it; reordering the three steps turns the test red. A
V1-engine testnet acceptance variant of thestellar_pay_commitflow now
asserts the confirmed commit'svalue_action_submittedaudit row's leg
content (action,amount,asset, redacteddestination) equals exactly
the values submitted on-chain, not merely that a row of the right kind
exists. (#44)
Changed
- CLI
pay --sign-only/--submit-onlyandclaim --sign-only/
--submit-onlynow evaluate operator policy on the supplied envelope before
signing or broadcasting, instead of running unconditionally under
policy.engine = "v1". Each stage decodes the envelope through the same
decoder the MCPstellar_pay_commit/stellar_claim_commitpath uses and
evaluates the decoded amount/asset/destination — sizing comes from the
envelope, not caller-supplied args.--submit-onlygates even though the
envelope arrives pre-signed, because broadcasting still spends funds. An
envelope the decoder cannot classify into a sized shape follows the
opaque-signing posture: deniespolicy.deny.unsizable_value_effectunder a
matched value rule unless it setsallow_opaque_signing = true, mirroring
thestellar_sep43_*tools' posture.policy.engine = "noop"is unaffected
— the staged flows remain ungated there, as before. The staged flows
match policy rules under thestellar_pay_commit/stellar_claim_commit
tool names (the same names the MCP commit phase matches), notstellar_pay
/stellar_claim: a ruleset that names only the base tools default-denies
the staged flows, so operators cover both names, or usetool = "*", for
uniform behavior across invocation modes. (#40) - The per-period rolling-window accumulator (
PolicyStateStore) is now
i128-width: cumulative recorded spend within a rolling window is exact
across the fulli128range, superseding the previousi64-width
accounting and its fail-closed refusal abovei64::MAX(#20). The
accumulator is in-process state only (no persistence across restarts, as
before), so there is no legacy on-disk form to migrate. (#42) - Documented that
minimum_reserveand identity-class criteria
(home_domain_resolved) are inapplicable to the smart-account verbs
(stellar_blend_lend,stellar_dex_trade,stellar_defindex_vault_deposit,
stellar_defindex_vault_withdraw, and the CLIlend/trade/vault
equivalents): the acting account is a smart-account contract with no classic
AccountEntry, soaccount_viewandidentity_viewstay unset permanently
on these tools, by design. A rule configuring either criterion on one of
these verbs fails closed on every call. (#38) - Value criteria (
per_tx_cap,per_period_cap,minimum_reserve,
counterparty_allowlist) now size a call through a typed value descriptor
derived at the dispatch gate, instead of matching hard-coded tool names. A
rule that matches a value-moving tool constrains every debit leg it carries
(classic pay/create, Blend supply/repay, DEX trades, vault deposits, x402
payments), and per-asset caps aggregate across the legs of a multi-leg call.
A value rule that matches a call whose value cannot be sized — a tool that
reached the gate without resolved effects, or a raw signing tool
(stellar_sep43_*) — now denies fail-closed with
policy.deny.unsizable_value_effectrather than passing silently. A rule may
opt a signing tool back in withallow_opaque_signing = true.
minimum_reservenow counts only native-XLM outflow legs; a token-only move
no longer reduces the native reserve. Operators with existing value rules
should expect previously-unconstrained value tools to be gated. (#18, #19,
#20) - CLI
pay,claim, andaccounts create(sponsored mode) now evaluate
operator policy before signing, through the samePolicyEngine::evaluate
path thetrade/lend/vault/trustlineCLI verbs already use and with
value descriptors identical to theirstellar_pay/stellar_claim/
stellar_create_accountMCP twins. Previously these three verbs signed and
submitted unconditionally, bypassing the engine entirely. All three verbs
gain a--profileflag (default"default"). With no persisted profile
file, an in-memoryNoop-engine testnet profile is synthesized, so the verbs
keep working without an authored profile andpolicy.engine = "noop"
behavior on testnet is unchanged. The gate only bites when--profile
resolves to a
persisted profile withpolicy.engine = "v1".accounts createFriendbot
mode is not gated (it debits no wallet funds). (#19) - CLI
trade,lend, andvaultnow size their policy gate with the same
value descriptor theirstellar_dex_trade/stellar_blend_lend/
stellar_defindex_vault_deposit/stellar_defindex_vault_withdrawMCP
twins use: each verb builds its value legs from the same parsed inputs it
submits and evaluates them throughPolicyEngine::evaluate_with_value, so
per_tx_cap/per_period_cap/minimum_reserveconstrain CLI DeFi debits
exactly as they constrain the MCP calls. Previously these verbs gated on the
tool name alone — withtradeclassified read-only — leaving the traded,
lent, and deposited amounts unconstrained. CLItrustlinegates through the
shared args-path descriptor builder; its refusals now carry the shared
policy.deny.<code>/policy.approval_required/policy.unexpected_decision
/policy.engine_requiredwire codes instead of the previous
trustline.policy_denied.<code>/trustline.policy_*codes (a
wire-observable parity change). Operators withpolicy.engine = "v1"value
rules should expect CLI DeFi debits to be gated. (#20) - Value caps (
per_tx_cap,per_period_cap,minimum_reserve, and their
bundle_*variants) and the amount fields of their deny reasons
(max_stroops,attempted_stroops,period_used_stroops,
reserve_required_stroops,balance_stroops) arei128: the comparison
path and the emitted deny-reason amounts are exact across the fulli128
range and are no longer clamped toi64::MAX, so a cap or an attempted
single-transaction debit abovei64::MAXis represented exactly instead of
saturating. These amounts cross the MCP wire as decimal strings
(JSON-number-unsafe beyond 2^53); consumers must parse them asi128/
decimal strings rather thani64. (The per-period window accumulator's own
width is covered separately above, (#42).) (#20) - Breaking (policy file behavior):
counterparty_allowlist'sHOME_DOMAIN
kind now requires the destination's on-chainhome_domainto be
independently VERIFIED through the operator's counterparty cache before the
allowlist is even consulted — a resolved cache entry for that domain, whose
cachedstellar.tomlACCOUNTSlist names the counterparty account.
Previously a bare self-assertedhome_domainmatch sufficed: any account
could sethome_domainto an allowlisted string viaSetOptionsat zero
cost and pass. ExistingHOME_DOMAINrules now deny until the operator
populates the cache for the domains they allowlist —stellar-agent counterparty warm-uprefreshes every domain already in the policy file's
HOME_DOMAINallowlists in one pass;stellar-agent counterparty refresh <domain>refreshes one domain.G_ACCOUNT/C_ACCOUNT/KNOWN_ISSUER
are unaffected.CounterpartyCacheViewgainsis_account_listed
(defaultfalse, fail-closed) andStellarTomlBindinggains anaccounts
field carrying the cachedstellar.toml'sACCOUNTSG-strkeys. (#49)
Removed
- Breaking (policy file): the
soroban_resource_fee_capcriterion. It gated on
astellar_invoke*tool-name prefix that no registered tool matches, so it
never constrained a real call. A policy file that references
soroban_resource_fee_capnow fails to load with the unknown-criterion
error. A future contract-invocation tool should reintroduce a
descriptor-based resource criterion sized againstContractInvokevalue
legs. (#22) - The remaining hard-coded per-tool arms inside the value criteria. A criterion
now sizes a call solely from its typed value legs, never from the tool name.
(#22)
Fixed
- MCP
stellar_pay_commit,stellar_claim_commit, and
stellar_create_account_commitnow supply the source account (and, for
stellar_pay_commit, the destination) as the policy gate's
account_view/identity_view— mirroringstellar_trustline_commit— so a
minimum_reservecriterion configured on these verbs is actually evaluated
at commit instead of failing closed on every call, even when the same rule
passed at simulate. The account fetch each commit path already made for the
sequence number is reused; no second fetch.identity_viewstaysNonefor
stellar_claim_commit/stellar_create_account_commit, matching their
simulate phases. (#48) ContextRuleManager::check_divergence_for_auth_rule_ids,
deploy_smart_account(and its five sibling deploy flows:
deploy_ed25519_verifier,deploy_webauthn_verifier,deploy_policy,
deploy_spending_limit_policy,deploy_timelock_controller), and
retry_with_backoffeach now enforce a collective wall-clock budget across
their fixed-count multi-stage RPC sequence, instead of leaving each stage
bounded only by the transport's own per-call timeout. ASignersManager
divergence check across up to 50auth_rule_ids, a deploy flow's
fetch/simulate/submit/verify sequence, and a blind-backoff retry loop could
previously run for up to (stage count) × (transport timeout) with no total
cap; each now refuses with a "collective budget elapsed" error once its
budget (the manager's/flow's existing configured timeout) is exhausted.
retry_with_backoffadditionally races each attempt against the shared
deadline, so one hung attempt cannot overshoot the deadline by the
transport's own bound; a deadline cutoff surfaces as the SAME
TransactionSubmissionTimeoutvariant the existing poll-timeout path
returns and is never retried. (#46)- MCP
stellar_trustline/stellar_trustline_commitand CLItrustlinenow
supply the source account as the policy gate'saccount_view(previously
None), so aminimum_reservecriterion configured onstellar_trustline
is actually evaluated instead of failing closed on every call. The source
fetch was already made by the existing ordered gate (for the sequence
number); the policy gate now runs after it.identity_viewstaysNoneon
this verb: the only counterparty account is the asset issuer, whose on-chain
home_domainis self-asserted — supplying it tocounterparty_allowlist
HOME_DOMAIN matching would let an issuer alias an allowlisted domain, so
identity-class criteria configured onstellar_trustlinefail closed by
design. (#47) approve --idwrites the human-readable approval summary and the y/n prompt
to stderr; stdout carries exactly one JSON envelope, so
approve --id <ID> --yes > out.jsonyields parseable JSON with the
approval_attestation, as the output contract documents. Summary field
lines are consistently indented. (#32)audit verifyno longer doubles the wire-code prefix in error details, and
a missing primary log file is classified as the actionable
audit.log_not_foundvalidation error instead of an internal error. (#29)- CLI
pay,claim, andaccounts createnow initialize the platform
keyring store before reading the owner key on thepolicy.engine = "v1"
path, so v1 policy evaluation works on a real install (previously failed
policy.engine_unavailablewithNoDefaultStore). (#41) - A rule carrying any value-summing bundle cap (
bundle_aggregate_cap,
bundle_per_tx_cap, orbundle_per_period_cap) now implicitly enforces the
restrict_bundle_to_recognised_kindsGeneric-rejection check at evaluation
time, regardless of whether that criterion is configured on the rule or its
enabledvalue. These caps sum onlyTokenTransferinners, so a multicall
bundle containing aGenericinner now denies under a cap-only rule instead
of bypassing the cap. (#23) ContextRuleManager::list_active_context_rules, Blend's
query_oracle_lastprice_timestamps, and the timelocklist_pendingscan
each now enforce a collective wall-clock budget across their per-item RPC
loop, instead of only bounding iteration COUNT. A large scan bound, request
batch, or scheduling history against a slow RPC endpoint previously had no
total time cap; each of the three now refuses with a "collective ... budget
elapsed" message once its budget (the manager's configuredtimeoutfor
the rule scan; a fixed constant for the other two) is exhausted, rather
than continuing to probe for up to iteration-count times the transport's
60s per-call bound. (#33)cargo build --workspace --tests(and any barecargo test/cargo build --testsinvocation omitting--features test-helpers) no longer fails to
compilestellar-agent-approval-remote: itstest_helpersmodule was
gated oncfg(any(test, feature = "test-helpers")), which letcfg(test)
alone compile the module'sp256imports without the optionalp256
dependency they need (gated solely on thetest-helpersfeature). The
module is now gated on the feature alone. (#37)- CLI
pay,claim, andaccounts create(sponsored) now supply the same
account_view/identity_viewtheir MCP twins supply —paya source
account_viewplus a destination-derivedidentity_view;claimand
accounts createa source/sponsoraccount_viewonly — so aminimum_reserve
or identity-class criterion configured on these verbs is actually evaluated
instead of failing closed on every call.trustlineis unchanged: its MCP
twin supplies no views at all, so the CLI mirrors that exactly. The
AccountReservesView/AccountIdentityViewbridge adapter
(AccountViewAdapter) moved fromstellar-agent-mcp::policy_adapterto
stellar-agent-network::policy_view(re-exported from its former path for
compatibility) so the CLI can use it without a new dependency on the MCP
crate. (#45) per_period_capandrate_limit(and their bundle counterparts,
bundle_per_period_cap/bundle_rate_limit) now actually accumulate
across calls: a new HMAC-protected, single-writer, atomically-written
per-profile window-state store (<state>/stellar-agent/policy/<profile>.window,
keyed by the newpolicy_window_state_key_idprofile coordinate) persists
the rolling-window history that was previously reconstructed empty on every
invocation, so these criteria evaluated every call against zero history and
never actually capped anything across calls.profile rotate-policy-state-key
rotates the HMAC key (re-signing the store so history is preserved, not
invalidated);profile reset-window-staterecovers from an unreadable,
tampered, or unparseable store by re-initialising it to empty (audited via
a newPolicyWindowStateResetaudit row). The multicall bundle path's
per-invocation throwaway state store is replaced with the persisted one.
(#50)stellar_pay/stellar_pay_commitpath-payment envelopes
(PathPaymentStrictReceive/PathPaymentStrictSend) now size the policy
gate's debit leg from the SEND side (send_max/send_amount), not the
destination side (dest_amount) — the wallet's actual spendable-balance
debit.PathPaymentStrictSendadditionally now usessend_asset(not
dest_asset) for the debit's asset. The destination side is still
surfaced, as a separate non-debit informational leg, so counterparty checks
continue to see the recipient. (#51)
Changed
- Breaking (MCP wire): tool business errors now use one uniform result envelope
{ ok: false, error: { code, message }, request_id }withis_errorset, in
place of the previous mix of JSON-RPCErrorData, bare{ error, detail }
(SEP-53), and{ code: "x402.error" }shapes. Branch onerror.code. x402
errors carry per-variant codes (x402.<reason>); SEP-53 failures use
sep53.keyring_load_failed/sep53.sign_failed/sep53.verify_failed; a
keyring-unavailable nonce mint at simulate time returnsnonce.mint_failed;
and a trustline to a clawback-enabled issuer returns the
trustline.clawback_opt_in_requiredbusiness error instead of anokresult.
Genuine protocol faults (malformed arguments, internal invariants) remain
JSON-RPC errors. The sixstellar_sep43_*tools keep the SEP-43 v1.2.1
{ code, message }object (numeric codes) for signing results and their
protocol, mainnet, and keyring-unlock errors to preserve wire compatibility;
the one case those tools use the standard envelope is a policy
RequireApprovalverdict, refused aspolicy.approval_required_unsupported.
The SEP-43 sign-and-submit submit-layer mainnet backstop now reports the
unifiedMainnetSigningForbidden(SEP-43 code -3) instead of the generic
rpc-error code (-2). (#35) - Breaking: removed
profile rotate-owner-key. The policy owner keyring entry
now holds the owner PUBLIC key that the always-online engine verifies
against, not the private seed. Enrol the public key with
profile enroll-owner-keyand sign policy files withprofile sign-policy,
keeping the owner seed offline. Profiles that relied onrotate-owner-key
must re-enrol the owner public key and re-sign their policy files. (#30)
Changed
- Testnet acceptance CI now provisions a headless Linux Secret Service
(gnome-keyring under a private D-Bus session) for the CLI'spayv1-policy
acceptance suite, which registers the platform keyring store before its
policy gate; the suite's self-skip on missing keyring is removed — keyring
init failure now fails the suite instead of silently skipping it. (#52) - Acceptance-suite environmental-flake hardening, none of it weakening any
assertion: the shared test-support Friendbot funding helper re-requests
funding once and re-confirms if the account is still absent after the
confirm wait; the MCP high-value independent-RPC cross-check retries a
rebuild FAILURE (not a byte mismatch) up to 3 times over a bounded window
before treating it as divergence, distinguishing "the independent RPC
hasn't caught up yet" from "the two RPCs disagree"; and browser-driven
acceptance suites (WebAuthn, remote-approval, rule-proposal, operator
enrollment) get one additional retry with a longer cooldown in the
testnet-acceptance driver script, on top of the universal retry-once
default. (#53)
Fixed
fund_with_friendbot(the CLIfriendbotcommand, the MCP
stellar_friendbottool, andaccounts create --fund-with-friendbot) now
polls the RPC endpoint until the funded account is actually queryable
before reporting success, instead of returning as soon as Friendbot's HTTP
response arrives; a funded account that never becomes visible within the
bounded window returns the newnetwork.friendbot_funding_not_confirmed
error instead of a premature success.FriendbotResultgains
funding_confirmed_after_ms. The MCP server now tracks, per source
account, the highest sequence number a confirmed submit in this process
consumed; when a build-time account fetch observes a sequence below that
floor, it re-polls within a bounded window before proceeding, removing
avoidable read-after-write propagation lag on thestellar_pay_commit/
stellar_claim_commit/stellar_trustline_commit/
stellar_create_account_commitbuild paths (and their simulate-phase
twins). Neither mitigation invents a sequence number or blocks
indefinitely: a genuinely stale build still fails typed
submission.sequence_number_staleexactly as before. (#54)