feat(assets): the user's broker holdings as a candidate source - #120
Merged
Conversation
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>
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the missing source for allowlist candidates: the assets you actually hold at the broker.
It answers "what do I already own that this system might trade?", where
keel assets discoveranswers "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_assetand 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 stillREJECT— sector and backing cannot be derived from a balance any more than from a price.One gate, shared by construction
assets screeninlined the attestation lookup and thescreen_assetcall. 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 attestwith a source, a passing screen, and a deliberateconfig.allowlistedit. Nothing here grants a proposer new authority.Two things found while building it
{asset}-{quote_currency}would have made the feature useless. Daily history is keyed-USDthroughout this codebase (_default_sim_products,keel fetch,keel simulate), whilequote_currencyisUSDC. 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.keel fetchthat 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.