Skip to content

feat(assets): the user's broker holdings as a candidate source - #120

Merged
eaitbrahim merged 4 commits into
mainfrom
feat/holdings-candidate-source
Jul 22, 2026
Merged

feat(assets): the user's broker holdings as a candidate source#120
eaitbrahim merged 4 commits into
mainfrom
feat/holdings-candidate-source

Conversation

@eaitbrahim

Copy link
Copy Markdown
Contributor

Adds the missing source for allowlist candidates: the assets you actually hold at the broker.

keel assets holdings [--min-balance N] [--screen]

It answers "what do I already own that this system might trade?", where keel assets discover answers "what could anyone trade?".

A source, not a gate

Holding an asset is not a reason to trade it. This command admits nothing and writes nothing — no attestation, no allowlist change, no DB write. It is a read-only report, in the same family as assets discover.

The vetting itself is unchanged. screen_asset and its policy are untouched: if this work had needed to weaken the gate to let the user's own holdings through, that would have been evidence against the holdings, not against the gate. An unattested asset you hold is still REJECT — sector and backing cannot be derived from a balance any more than from a price.

2 holding(s) above 0, excluding USDC and fiat:

  BTC      balance=0.5      on-allowlist     UNATTESTED
      REJECT  (2000 daily bars cached)
      x attestation: MISSING. ...
  SOL      balance=12       not-on-allowlist UNATTESTED
      REJECT  (0 daily bars cached)
      ! no local history -- run `keel fetch --products SOL-USD` first, then re-screen.
        This is a MISSING-DATA verdict, not a verdict about the asset.

One gate, shared by construction

assets screen inlined the attestation lookup and the screen_asset call. That is extracted to _screen_product, and both commands now route through it — so a proposer cannot drift onto a laxer path. A test asserts the two commands reach the same verdict for the same asset.

This is also the seam the future LLM proposer must use. Recorded in the spec: per the project's asymmetry principle an LLM may propose and may veto, but may never admit. Admission still requires a human keel assets attest with a source, a passing screen, and a deliberate config.allowlist edit. Nothing here grants a proposer new authority.

Two things found while building it

  • Screening {asset}-{quote_currency} would have made the feature useless. Daily history is keyed -USD throughout this codebase (_default_sim_products, keel fetch, keel simulate), while quote_currency is USDC. Screening the settlement product would have found zero cached bars for every asset and reported "no local history" forever — which is worse than useless, because it reads as a verdict about the asset. The convention is now single-sourced in _history_product, and my own test caught this by asserting the two commands agree.
  • "No local history" is not "bad asset". The likeliest misreading of the whole feature, so it is handled in the output rather than left to the operator, and it names the keel fetch that fixes it.

Verification

1300 tests pass, ruff clean. Nine new tests: fiat/settlement-currency exclusion, dust filtering, held-but-unattested is still rejected, both commands agree on one asset, the missing-data message, the command writes nothing (asserted, not assumed), and a broker failure is an error rather than an empty result that would falsely read as "you hold nothing".

Design: docs/superpowers/specs/2026-07-21-holdings-as-candidate-source-design.md.

eaitbrahim and others added 2 commits July 21, 2026 19:47
A new SOURCE, not a new gate: the user's Coinbase balances feed the existing
fail-closed screen. Extracts one _screen_product helper so discover, holdings
and a future LLM proposer cannot diverge onto a laxer path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
'keel assets holdings [--min-balance N] [--screen]' answers 'what do I already
own that this system might trade?', where 'assets discover' answers 'what could
anyone trade?'.

A SOURCE, not a gate. Holding an asset is not a reason to trade it, so this
admits nothing and writes nothing -- no attestation, no allowlist change, no DB
write. Extracts _screen_product so 'assets screen', 'assets holdings --screen'
and any future proposer (an LLM shortlist) share ONE call site of screen_asset;
a test pins that the two commands agree on the same asset, which is what makes
'the same vetting process' a property of the code rather than an intention.

Two things found while building it:
- Screening '{asset}-{quote_currency}' would have found zero cached bars for
  every asset forever, because history is keyed '-USD' throughout the codebase.
  The convention is now single-sourced in _history_product.
- A rejection for zero cached bars is a statement about our DATA, not the asset
  -- the likeliest misreading of the whole feature, so it is called out in the
  output and names the 'keel fetch' that fixes it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@eaitbrahim eaitbrahim added feature New capability (groups under Features) compliance Shariah/compliance screening or policy (Compliance & rails) labels Jul 21, 2026
eaitbrahim and others added 2 commits July 21, 2026 19:58
…tifacts

Independent review of this PR found two blocking defects:

1. --screen printed result.failures but never result.warnings, so a PAXG holder
   saw a bare ADMIT and never learned about §65.5's bay' al-sarf regime that
   'assets screen' reports for the identical asset. The 'one gate' claim was
   true only for the admitted boolean; the compliance-relevant half of
   ScreenResult was silently discarded.

2. With zero cached bars, the liquidity and settlement failures report on our
   DATA, not the asset -- median volume is 0 because there are no bars, and
   quotable_in_settlement_currency degenerates to bool(candles). Printing them
   as findings asserted exactly what the missing-data message exists to deny.
   They now render as '· not assessable without history'; the real history
   failure stays.

Also: lowercase currency codes are uppercased before exclusion (a 'usdc'
balance was being offered as tradable); --min-balance rejects garbage/NaN/
negative like the rest of this CLI; broker CONSTRUCTION is inside the guarded
block so a credentials problem is not reported as a venue problem; the fiat
list is broader; and two tests were vacuous -- 'not-on-allowlist' contains
'on-allowlist', and the agreement test passed when BOTH sides were False.

Records, but does NOT fix, a pre-existing weakness this surfaced: because every
screened product is -USD while quote_currency is USDC,
require_settlement_quote currently re-checks 'do we have bars'. That is a
compliance rail and deserves its own deliberate change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ilure tags

Third review round, all non-blocking:
- A lowercase 'btc' balance was screened raw: it read 'on-allowlist' and
  'UNATTESTED' for the same asset simultaneously, and handed the operator
  'keel fetch --products btc-USD', an id that never resolves. Fail-closed
  (over-rejects, never over-admits) but self-contradictory.
- get_accounts defaults a missing currency to None, so .upper() could crash the
  command outright. Tolerated now.
- The zero-bars suppression matches failure tags by string, so renaming a tag in
  screen_asset would silently make it inert and reintroduce 'data artifacts
  printed as verdicts' with a green suite. A test now pins the tags -- and I
  mutation-checked it: renaming 'liquidity' to 'depth' makes it fail, which is
  the point.
- Spec: _screen_product returns (MarketFacts, ScreenResult), not ScreenResult.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@eaitbrahim
eaitbrahim merged commit 81210bb into main Jul 22, 2026
1 check passed
@eaitbrahim
eaitbrahim deleted the feat/holdings-candidate-source branch July 22, 2026 00:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

compliance Shariah/compliance screening or policy (Compliance & rails) feature New capability (groups under Features)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant