Skip to content

[feature] Core: define how Capabilities declare that no current closed-set RiskLabel applies #164

Description

@rainypilgrimage

Problem

Moss requires every Capability to declare a risk field, but Registry rejects an empty list:

if (meta.spec.risk.length === 0) {
  throw new Error(`capability "${config.name}.${name}" must declare a risk label`);
}

The current closed set is:

[
  "fundOut",
  "approval",
  "priceImpact",
  "debt",
  "leverage",
  "liquidation",
]

The semantics accepted in #114 and implemented in #119 define:

  • fundOut: assets leave the account in the current transaction;
  • debt: the Capability increases the account?s repayment obligations, even when no asset leaves in the current transaction.

Those definitions resolved debt-increasing borrow, but they also made a residual authoring gap explicit.

Core currently has no valid authored state for:

The Capability author has reviewed the operation, but none of the current closed-set RiskLabels accurately applies.

Omitting risk is invalid, an explicit empty list is rejected, and a non-empty list may force the author to select a label known to be inaccurate.

Confirmed cases

aPriori claim

During requestRedeem, the owner?s aprMON shares are transferred into vault custody. The later claim settles that request and pays matured MON to the receiver.

The exposed claim transaction does not move an asset out of the account or create a repayment obligation. fundOut, debt, approval, and priceImpact therefore do not describe this Capability.

The adapter currently retains:

risk: ["fundOut"]

with a comment recording that the operation is inflow-only and that the label is a Registry-compatible placeholder. Although #119 has since added debt, that label does not fit claim because completion does not increase a repayment obligation.

Source:

@Capability<AprioriProtocol, typeof claimParams>({
intent: "Claim MON for withdrawal request {requestId} to {receiver}",
verb: "claim",
params: claimParams,
receipt: "claimReceipt",
// claim is inflow-only, but Registry rejects an empty risk list and the
// closed RISK_LABELS set has no inflow/obligation label yet (#114).
// Drop fundOut here as soon as #114 lands a correct minimal set.
risk: ["fundOut"],
tags: ["staking", "liquid-staking", "withdrawal-queue"],
})
async claim(params: InferParams<typeof claimParams>) {
const transaction = this.aprMon.redeem([[BigInt(params.requestId)], params.receiver]);
return [transaction];

FastLane completeUnstake

FastLane has the same transaction shape. requestUnstake commits the shMON in an earlier transaction; after the epoch completes, completeUnstake only pays MON from FastLane to the owner.

The integration-branch comment says that fundOut ?does not fit,? notes that Registry rejects an empty risk set, calls the declaration the ?least-wrong placeholder,? and points to #12 and #104 for the pending ruling.

The declaration nevertheless remains:

risk: ["fundOut"]

The payout direction is supported by a real Monad-mainnet completion transaction documented in #158.

Source:

@Capability<FastLane, typeof completeUnstakeParams>({
intent: "Complete a delayed unstaking request after epoch completion",
verb: "unstake",
params: completeUnstakeParams,
receipt: "completeUnstakeReceipt",
// This step is inflow-only: requestUnstake already committed the shMON, and
// completion only pays MON back to the owner. `fundOut` as settled in #114
// means assets leave the account in this transaction, so it does not fit,
// but Registry rejects an empty risk set and the closed set has no
// inflow-only entry. Kept as the least-wrong placeholder pending the ruling
// asked for in #12 and #104.
risk: ["fundOut"],
tags: ["staking", "unstake", "delayed"],
})
async completeUnstake() {
return [this.staking.completeUnstake([])];

Why this needs a Core decision

load() exposes risk separately from free-form tags. A placeholder therefore becomes part of the structured contract an Agent reads rather than remaining an internal adapter comment.

Registry currently enforces a non-empty invariant: every registered Capability must provide at least one Core classification. This can prevent omitted classification, but the confirmed cases show that it can also force a known-inaccurate label when the closed set contains no applicable category.

?No current Core RiskLabel applies? is not a safety, trust, or execution-policy decision. Mandatory simulation, Receipt and Warning handling, pre-signing presentation, wallet review, and user approval remain unchanged.

It means only that none of the current Core-defined static danger categories accurately describes the operation.

Proposed direction

Keep the risk field required, but allow an explicitly authored empty list:

risk: []

For a Capability, define this as:

The author has reviewed the operation and determined that none of the current closed-set Core RiskLabels accurately applies.

The field remains required, missing or malformed metadata remains invalid, every non-empty member remains closed-set validated, and load() projects the authored empty list unchanged.

The public shape is already RiskLabel[], and Query stubs already project an empty list. This would not require a new TypeScript, JSON, or MCP data shape.

It would nevertheless be an Agent-facing semantic compatibility change. Consumers that rely on Registry?s runtime guarantee that every Capability has at least one RiskLabel would need to stop making that assumption, so the change should be documented and released accordingly.

The strongest objection is that an empty list could become an easy opt-out if contributors use it instead of analysing the operation or proposing a missing reusable danger semantic. The authoring rule below is intended to prevent that use.

Authoring decision rule

risk: [] must not be used merely because the existing labels are inconvenient or because the operation has not been fully analysed.

When no current RiskLabel accurately applies:

  1. If the Capability exposes a recurring static danger that the current closed set cannot represent, contributors should raise a focused Core vocabulary issue.

    The issue should provide concrete Capability evidence, define the proposed semantic boundary, compare existing labels and alternatives, and wait for a maintainer decision before implementation?the sequence demonstrated by [feature] Clarify RiskLabel semantics for debt-increasing lending capabilities #114/feat(core): add debt risk label #119.

  2. If no current label applies and, after reviewing the operation?s static dangers, no distinct reusable danger semantic has been identified from the evidence, an explicitly authored risk: [] may represent that result.

  3. If the classification remains uncertain, the contributor should request Core or maintainer review rather than use an inaccurate placeholder or treat risk: [] as a substitute for analysis.

If Core later defines an applicable RiskLabel, an existing risk: [] declaration should be replaced with that label.

Alternatives considered

Add fundIn

This preserves the non-empty invariant, and the FastLane author explicitly preferred this direction over risk: [] in #158 for that reason.

Its advantage is that every Capability continues to have at least one Core-defined classification. The tradeoff is that receiving funds is not itself a category of danger. fundIn would broaden RiskLabel toward a general asset-direction taxonomy, while the representation question may recur for operations that have neither an applicable danger label nor a pure-inflow shape.

Add a sentinel such as noApplicableRisk

This preserves a non-empty list and explicitly represents the author?s conclusion.

However, the sentinel is not a danger category and would require combination rules to reject contradictory states such as:

risk: ["noApplicableRisk", "approval"]

It would mix classification status with the RiskLabel taxonomy.

Other alternatives

Maintainer decisions requested

  1. Should Core allow an explicitly authored risk: [] when none of the current closed-set RiskLabels applies, while keeping the field required?

    If Core should preserve the non-empty invariant, an alternative accurate public representation would resolve the same adapter pressure without relying on known-inaccurate placeholders.

  2. Should the canonical meaning and authoring guidance distinguish:

  3. Should the focused Core implementation establish the public representation, validation, coverage, documentation, authoring rule, and changeset, while leaving adapter adoption to coordinated follow-up work?

Suggested acceptance coverage

For the maintainer-approved representation, the focused Core implementation should:

  • implement the representation while keeping risk required;
  • reject missing, malformed, and unknown metadata;
  • cover Registry registration, validation, and exact load() projection;
  • preserve compile-time required-field and closed-set guarantees;
  • update the canonical glossary, MCP documentation, and short Protocol-onboarding rule;
  • include the Core changeset and pass the full pnpm lint ? build ? typecheck ? test verification chain.

The focused Core PR may close this issue once the approved public contract and coverage land.

Adapter adoption is downstream work and should not block closure of the Core issue. aPriori adoption can be handled in a separately coordinated follow-up without expanding #159, while FastLane adoption should be coordinated with the existing #12/#158 contributors. Each affected adapter package can carry its own appropriate changeset.

Scope

This issue is limited to the Core authoring representation for a Capability when none of the current closed-set RiskLabels accurately applies.

It does not:

Related work and attribution

This issue extracts the shared residual Core question made explicit by the accepted #114/#119 semantics. It does not supersede the concrete adapter findings, implementation work, or attribution above.

This issue does not claim implementation ownership. A focused Core PR can follow the maintainer decision.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions