CBA side illustration

CommSec Markets

Reports holdings and quotes, and accepts an order only where the customer has separately authorised trading. Reading a portfolio and trading it are two different authorities.

This service is an illustration built on the CBA side of the boundary. It is not a Raidiam product. It exists to show what a resource server can demand of an agent, and to show that a refusal can always be explained.

What an agent has to discover

Resourcehttps://rs-markets.demo.cba.raidiam.io
Authorization serverhttps://commsec.demo.cba.raidiam.io
Trust anchorhttps://authority.directory.cba.raidiam.io/authority/5a27c88b-97ed-4d37-a3c8-59c66b19aa25
Detail typesmarkets_read, markets_trade
Scopesopenid, markets.read
Sender constrainingDPoP verified when presented, required for bound tokens
Mutabilityaccepts instructions

Authorization detail types

markets_read

Authority to read holdings and quotes. It places no orders.

Required members: type purpose

Optional members: none

{
  "type": "markets_read",
  "purpose": "Report the customer's portfolio position"
}

markets_trade

The type the state changing tools name. It is published so a refusal is checkable against a declared requirement rather than looking arbitrary. Holding the reading authority never yields this one.

Required members: type purpose

Optional members: none

{
  "type": "markets_trade",
  "purpose": "Granted separately by the customer."
}

Token claims this resource decides on

Tools

ToolPurposeRequiresEffect
list_holdings The customer's holdings with unit counts and the last traded price. markets_read read only
quote The last traded price for a listed code. markets_read read only
place_order Place an order. Held for a named human to release; never executed by an agent alone. markets_trade changes state

Guardrails, published in advance

Every call carries a token from the named authorization server

Calls are accepted only with an access token issued by https://commsec.demo.cba.raidiam.io and addressed to this resource as its audience. A token minted for a different resource is refused even when it is otherwise valid.

Refusal reason missing_access_token, invalid_token · decided at authorization · policy id rs.authenticated_caller

What clears it: Read this metadata document, then request a token from the authorization server it names, with this resource as the audience.

Authority is the RFC 9396 detail type, not a scope

Each tool names one authorization_details type. The token must carry that type, or an umbrella type that narrows to it. Holding a scope, or holding authority for a neighbouring resource, does not admit the call.

Refusal reason insufficient_authority · decided at authorization · policy id rs.authority_gate

What clears it: Obtain a token carrying the detail type the tool names. Delegation only ever narrows, so the delegating envelope must already contain it.

A revoked delegation stops working before its tokens expire

Revocation arrives as a Shared Signals event and is applied to the delegation, not to a single token. Every token issued under a revoked delegation is refused from that moment, whatever its expiry says.

Refusal reason delegation_revoked · decided at authorization · policy id rs.revocation_honoured

What clears it: The customer must grant a fresh delegation. There is no way to appeal a revocation at the resource.

Sender constrained tokens are bound to the key that holds them

A DPoP proof is verified whenever one is presented, and is required whenever the access token names a key in its cnf.jkt claim. Each proof is accepted once, so a captured proof cannot be replayed.

Refusal reason dpop_proof_required, invalid_dpop_proof, dpop_key_mismatch, dpop_proof_replayed, access_token_not_dpop_bound · decided at authorization · policy id rs.dpop_binding

What clears it: Request the access token with a DPoP proof so the authorization server binds it to your key, then send a fresh proof with every call.

A malformed request is refused with the reason it was malformed

Arguments are validated before any business rule runs, and the refusal names the argument at fault rather than returning a bare failure.

Refusal reason tool_error, invalid_amount, unknown_tool · decided at execution · policy id rs.request_validity

What clears it: Correct the named argument. The tool schemas are published in this document.

Reading a portfolio is not trading it

markets_read admits the reporting tools and nothing else. Placing an order names markets_trade, a separate capability type rather than a wider scope on the same one, so an agent holding read authority cannot reach trading by asking differently.

Refusal reason capability_denied · decided at authorization · policy id markets.reading_is_not_trading

What clears it: Trading authority is granted separately by the customer and is not derivable from reporting authority.

An order is held for a second human

An order accepted here is held rather than executed, and a named human releases it. An agent may prepare a trade and may not complete one on its own, whatever authority it holds.

Refusal reason approval_required · decided at execution · policy id markets.orders_are_held_for_a_human

What clears it: The customer releases the order in CommSec.

Observability

Every admission decision, allowed and refused, is recorded with the policy that decided it and the values it turned on. Read them at /decisions.