From 3b42b005ba7b19a7dcb242beef89d563e965cd00 Mon Sep 17 00:00:00 2001 From: Elmehdi Aitbrahim Date: Tue, 1 Sep 2026 06:38:52 -0400 Subject: [PATCH] docs(fiqh): long-only is bay' ma la yamlik, and it is not closed at the venue boundary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `docs/fiqh-basis.md` had no section on long-only at all. The rails table covered the prudential rails, rails 18/19 covered the spot-only charter, and the one ruling that is neither -- keel never sells what it does not hold -- was left implicit in four places that enforce it and named in none. It is a fiqh-derivation, not a charter, and it descends from Ayub Ch 5.4.2 (§65.11) and Ch 6.5.1 (§65.4): *bay' ma la yamlik*. That anchor is more direct than riba for this class of failure, and the difference is load-bearing -- a sale of what the seller does not hold is impermissible with no interest anywhere in the transaction, so a riba-framed defence does not reach it at all. The section carries a qualification, because §65.4 is invoked here for nearly the opposite of what it was read for. §65.4's own point is that possession is CONSTRUCTIVE -- "what is meant by possession here is the inability to deliver the goods" -- which is exactly how exchange-held balances qualify as valid `qabd`. That lenient reading does not weaken the claim, it sharpens it: in every failure mode recorded here the asset is not there at all, so selling it fails the prohibition on the lenient reading and not merely the strict one. The hadith of Hakim ibn Hizam (Sunan an-Nasa'i 4613 / Abu Dawud 3503) is recorded as the operator's citation and explicitly marked as NOT extracted in this repo's knowledge base -- it carries no §N.x row and is not disguised as one. A test pins the disclaimer AND its truth: if a source file ever extracts the hadith, the disclaimer has become false and the test says so. And a new open question, because the ruling is sound where keel DECIDES and open where it SETTLES. A SELL is never clamped to the account's available base, so fee dust, an unrecorded partial fill (`filled_quantity` is NULL on every filled live order), or an out-of-band transfer can send an order for more than is held (#667). A resting stop leg can outlive the position that justified it, because no sweep cancels a SELL whose position is gone (#668). On a cash account each is a rejection; on a margin-enabled account the venue fills the difference as a short, and there is no cash-account posture check on Coinbase -- `verify_cash_account` exists only on the Alpaca adapter (#666). Named here rather than left to be found. Four pins, all two-sided and all mutation-verified (12 mutants, 12 killed). The base.py pin matches the FIELD LINE by regex rather than the file, because the `__post_init__` docstring quotes the annotation too and a prose mention is not a contract; it also pins the runtime check, since the annotation is a promise to mypy and a foreign rule never runs mypy. The venue-boundary pin is anchored to `_sell_base_size`'s own stated contract, so when #667 lands the test fails -- the doc gets updated with the code rather than after it. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01NzuKAe2RVrPt9acVAWjRyL --- docs/fiqh-basis.md | 55 ++++++++++++++++++++ tests/test_fiqh_basis.py | 106 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) diff --git a/docs/fiqh-basis.md b/docs/fiqh-basis.md index 02342395..dab6d755 100644 --- a/docs/fiqh-basis.md +++ b/docs/fiqh-basis.md @@ -123,6 +123,49 @@ The fiqh content — that derivatives and difference-settlement are impermissibl (§65.6: what makes speculation *maisir* is non-ownership, non-delivery, difference-settlement) but the RAILS are the charter enforcing it. +### Long-only — *bay' ma la yamlik*, §65.4/§65.6/§65.11 + +keel never sells what it does not hold, and four independent places enforce it: + +- **The rule contract.** `Setup` declares `direction: Literal["long"]`, and `__post_init__` + enforces at runtime the one thing that annotation only asserts statically — so a foreign + rule cannot propose a short, and cannot propose one wearing a long's label (#447). +- **The engine.** `keel/strategy/engine.py` builds every entry `Signal` with `side=Side.BUY`, + unconditionally. No branch produces a `Side.SELL` entry. +- **Rail 10.** Every SELL must cite a defined rule — no arbitrary liquidation. +- **Rails 18/19.** The spot-only charter: nothing that settles by price difference. + +The doctrine, from the same source the rest of this document rests on. §65.11 (Ayub Ch 5.4.2): +*"short-selling has been prohibited by almost all scholars"* — because the subject matter +*"must be existing/existable… capable of ownership/title, capable of delivery/possession… and +the seller must have its title and risk."* §65.6 draws the same boundary from the other side: +what converts speculation into *maisir* is **(a) not owning it, (b) not possessing it, (c) not +taking or making delivery, or (d) settling by price difference** — never frequency, never +volatility. + +**Unlike rails 18/19, this one is a fiqh-derivation and not a charter.** It descends from the +prohibition Ayub states at Ch 6.5.1 (§65.4) as *"do not sell what you do not possess"* — +*bay' ma la yamlik*. That anchor is more direct than riba for this class of failure, and the +difference matters: a sale of what the seller does not hold is impermissible with no interest +anywhere in the transaction, so a riba-framed defence does not reach it. + +⚠️ One qualification, because §65.4 is invoked here for nearly the opposite of what it was read +for. §65.4's own point is that the prohibition is **not** a physical-custody rule — *"what is +meant by possession here is the inability to deliver the goods"* — which is exactly how +exchange-held balances qualify as valid `qabd`. That lenient reading does not weaken the +long-only claim; it sharpens it. On the constructive-possession test the question is whether +anything prevents delivery, and in the failure modes recorded under "Known open questions" the +asset is not there at all. Selling it fails the prohibition on the lenient reading, not merely +the strict one. + +**On the primary text.** The doctrine is conventionally anchored to the hadith of Hakim ibn +Hizam — *"Do not sell what is not with you"* (لا تبع ما ليس عندك), Sunan an-Nasa'i 4613 / +Sunan Abu Dawud 3503. **That reference is not extracted in this repository's knowledge base**, +and is recorded here as the operator's citation: checkable against the collections, but +carrying no `§N.x` row and not treated as one. The in-repo authority for everything above is +§65.4/§65.6/§65.11, corroborated by §28.2's ownership principle and §33's gharar reading of +*"the sale of items not owned"*. + ### Purification (§65.9) and idle-balance rewards (§56.3) `keel/compliance/purification.py` implements Ayub §65.9: interest/reward credits are @@ -224,6 +267,18 @@ Stated, not hidden — each is a place where keel's encoded behaviour could be w Whether "no underlying purpose" is disqualifying "is exactly the kind of judgement the screen defers to a human" (`docs/experiments/2026-07-20-candidate-universe.md`) — deferred, not decided. +- **Long-only is enforced where keel DECIDES, not where it SETTLES.** Everything under + "Long-only" above governs what keel chooses. It does not govern the quantity that reaches + the venue. A SELL is never clamped to the account's available base (#667): the exit sells + what the ledger believes is held, and the ledger drifts from the account through fees taken + in the base asset, partial fills (`filled_quantity` is recorded nowhere on this + deployment), and out-of-band operator transfers. A resting stop leg can outlive the + position that justified it, because no sweep cancels a SELL whose position is gone (#668). + On a cash account each of these is a rejected order — bad, but not a fiqh failure. On a + margin-enabled account the venue fills the difference as a short, and keel has no + cash-account posture check on Coinbase (#666; the check exists only on the Alpaca adapter). + So the long-only ruling above is sound at the decision layer and **not yet closed at the + venue boundary**. Named here rather than left to be discovered. - **ZEC and the rest of the deferrals.** The candidate-universe record lists the open questions the attestation step has to answer and "which this agent must not answer". diff --git a/tests/test_fiqh_basis.py b/tests/test_fiqh_basis.py index 0278c53b..d0daaa0a 100644 --- a/tests/test_fiqh_basis.py +++ b/tests/test_fiqh_basis.py @@ -376,3 +376,109 @@ def test_the_readme_links_the_document(): assert _DOC in (_ROOT / "README.md").read_text(), ( f"README.md must link {_DOC} from its documentation map" ) + + +_LONG_ONLY_PROHIBITION = "short-selling has been prohibited by" + +_LONG_ONLY_SUBJECT_MATTER = "capable of ownership/title, capable of delivery/possession" + +_LONG_ONLY_TYPE = 'direction: Literal["long"]' + +_LONG_ONLY_SIDE = "side=Side.BUY" + +_HADITH_NOT_IN_KB = "That reference is not extracted in this repository's knowledge base" + +_SELL_SIZE_CONTRACT = "it can only ever make an exit MORE likely to be accepted" + + +def test_the_long_only_ruling_is_pinned_two_sided_to_the_code_that_enforces_it(): + """The doc names two enforcement points by their exact source text; both must still exist. + + Long-only is the one ruling in this document that is neither an attestation nor a rail: + it is structural, and it holds only because the rule contract cannot express a short and + the engine cannot construct one. Either half silently changing would leave the doc making + a fiqh claim about behaviour the code no longer has. + """ + doc = _unwrapped(_doc()) + assert _LONG_ONLY_TYPE in doc and _LONG_ONLY_SIDE in doc, ( + f"{_DOC} must name both long-only enforcement points exactly " + f"({_LONG_ONLY_TYPE!r} and {_LONG_ONLY_SIDE!r}) -- a claim that cannot be checked " + "against a line of code is the kind this document exists to refuse" + ) + base = (_ROOT / "keel/strategy/rules/base.py").read_text() + assert re.search(r'(?m)^\s+direction: Literal\["long"\]\s*$', base), ( + "keel/strategy/rules/base.py must still DECLARE the field as `Literal[\"long\"]`. " + "Matched on the field line, not anywhere in the file: the `__post_init__` docstring " + f"quotes the annotation too, and a prose mention is not a contract {_DOC} can cite" + ) + assert 'if self.direction != "long":' in _rel("keel/strategy/rules/base.py"), ( + "keel/strategy/rules/base.py must still enforce the direction at RUNTIME -- the " + "annotation is a promise to mypy, and a foreign rule never runs mypy (#447)" + ) + assert _LONG_ONLY_SIDE in _rel("keel/strategy/engine.py"), ( + "keel/strategy/engine.py must still build every entry Signal as a BUY; if an entry " + f"can be a SELL, {_DOC}'s long-only section is false" + ) + + +def test_the_long_only_doctrine_is_quoted_from_the_knowledge_base(): + """`bay' ma la yamlik` is cited, not asserted -- and the source must still say it. + + The document's whole method is that a ruling is checkable against an in-repo extract. + Long-only rests on §65.11's short-selling passage, so the doc's quotation and source-65's + text are pinned to each other in both directions. + """ + doc = _unwrapped(_doc()) + source = "docs/superpowers/references/trading-knowledge-base/sources/source-65.md" + text = _rel(source) + for quoted in (_LONG_ONLY_PROHIBITION, _LONG_ONLY_SUBJECT_MATTER): + assert quoted in doc, ( + f"{_DOC} must quote §65.11 verbatim ({quoted!r}) -- long-only is a fiqh " + "derivation here, not a charter, so it must carry its citation" + ) + assert quoted in text, ( + f"{source} must still carry the passage the doc quotes ({quoted!r})" + ) + + +def test_the_hadith_reference_is_marked_as_outside_the_knowledge_base(): + """A primary-text citation with no `§N.x` row must SAY it has none. + + Every other citation in this document resolves to an in-repo extract; the hadith of Hakim + ibn Hizam does not. Recording it without the disclaimer would let a reader assume the same + provenance the `§N.x` rows carry -- exactly the papering-over the KB's honesty rules + forbid. Two-sided: the disclaimer must be there, and it must still be TRUE. + """ + assert _HADITH_NOT_IN_KB in _unwrapped(_doc()), ( + f"{_DOC} must mark the hadith reference as outside the knowledge base " + f"({_HADITH_NOT_IN_KB!r}) -- an uncited primary text may be recorded, never disguised" + ) + sources = _ROOT / "docs/superpowers/references/trading-knowledge-base/sources" + carrying = [p.name for p in sorted(sources.glob("*.md")) if "an-Nasa'i" in p.read_text()] + assert not carrying, ( + f"{carrying} now extracts the hadith the doc says is absent -- the disclaimer has " + "become false; give the citation its §N.x row and drop the caveat" + ) + + +def test_the_venue_boundary_gap_is_stated_not_hidden(): + """Long-only holds where keel decides; the doc must admit it is open where keel settles. + + A SELL is never clamped to the account's available base, so fee dust, an unrecorded + partial fill, or an out-of-band transfer can send an order for more than is held (#667). + That is `bay' ma la yamlik` reached by arithmetic. Pinned to `_sell_base_size`'s own + contract: while that function's only job is to make an exit more likely to be accepted, it + reads no balance, and the open question must stay in the doc. When #667 lands this test + fails -- which is the point: the doc gets updated with the code, not after it. + """ + doc = _unwrapped(_doc()) + assert "not yet closed at the venue boundary" in doc, ( + f"{_DOC} must state that long-only is unclosed at the venue boundary -- the ruling is " + "sound at the decision layer and the operational gap is not the reader's to discover" + ) + for issue in ("#666", "#667", "#668"): + assert issue in doc, f"{_DOC} must name {issue} as the open venue-boundary work" + assert _SELL_SIZE_CONTRACT in _rel("keel/execution/executor.py"), ( + "keel/execution/executor.py's `_sell_base_size` must still carry its stated contract; " + f"if the exit now clamps to a real balance, {_DOC}'s open question is stale" + )