diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 01a2fd06..4eb687c4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,7 +35,8 @@ adjudicate fiqh and will not become a court with a merge button. Attest your own locally — `keel assets attest` writes to *your* database, with your source and your name on it — and run the enforcement engine under it. The disagreement then costs nobody anything: upstream stays neutral, your deployment follows your ruling, and the audit trail records -exactly who said what. +exactly who said what. The Shariah reasoning the codebase encodes, ruling by ruling with its +source, is written up in [`docs/fiqh-basis.md`](docs/fiqh-basis.md). ## Development setup and the gates a PR must pass diff --git a/README.md b/README.md index 143e2620..9f1be737 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ applies the confirm/autonomy gate, then places and logs. There is deliberately n promotion clears a two-part gate: performance floors *and* an overfitting check (PBO/CSCV). A rule that clears four floors on one in-sample parameter set is exactly what the second gate exists to be suspicious of. -- **The rails** (`keel/execution/guards.py`) — nineteen deterministic checks no order can +- **The rails** (`keel/execution/guards.py`) — eighteen deterministic checks no order can skip and nothing can override, not even autonomy: the halal allowlist, per-order and per-day spend caps, exposure and concentration caps, correlation-aware sizing, a minimum-move floor, no-martingale/no-stop-widening, a fails-closed kill-switch, total and @@ -96,7 +96,7 @@ the stop holds, not what you spend. ``` keel/ the agent and CLI ├── agent.py the loop and RULE_REGISTRY (where rules live) -├── execution/guards.py the 19 rails (where enforcement lives) +├── execution/guards.py the 18 rails (where enforcement lives) ├── execution/sizing.py position sizing ├── compliance/screen.py attested allowlist admission (fails closed) └── commands/ CLI command implementations @@ -118,6 +118,9 @@ adapter, deliberately divergent, that the conformance suite runs against. ## Documentation map +- [`docs/fiqh-basis.md`](docs/fiqh-basis.md) — the Shariah reasoning keel encodes, ruling by + ruling, each with its in-repo source: what is attested vs computed, the open questions, + and how to disagree. - [`docs/operator-runbook.md`](docs/operator-runbook.md) — operating a deployment: the account-level compliance obligations no rail can enforce, deploying/upgrading releases, and the paper-vs-live distinctions (two accounts that share nothing). diff --git a/docs/fiqh-basis.md b/docs/fiqh-basis.md new file mode 100644 index 00000000..aef39e5e --- /dev/null +++ b/docs/fiqh-basis.md @@ -0,0 +1,255 @@ +# The fiqh basis of keel's encoded rulings + +## What this document is and is not + +This document is written for a Muslim developer deciding whether to trust keel with money. It +states, ruling by ruling, what Shariah reasoning is encoded in this repository, and where in +the repository each ruling's source lives — so the basis is auditable by someone who does not +already know where to look. It is scholarship by reference: every ruling below carries a +citation to an in-repo source, and the enforcement code carries the same citations in its +comments. + +What this document is not: a fatwa, or a claim that keel can produce one. + +> keel is not a fatwa engine. It is an enforcement engine for a ruling you supply. + +No scholar has reviewed this document; whether such a review happens is deliberately a +separate, still-open question (#289), and nothing here should be read as one having occurred. + +## How to read the citations + +`§N.x` means source N, section x → the file +`docs/superpowers/references/trading-knowledge-base/sources/source-NN.md`, section `N.x` — +the knowledge base's own convention, stated in its README index. The sources are extracts of +real books, papers, and council resolutions, kept in-repo; the README row for each source +records what was fetched and from where, so a claim is checkable against its origin. + +Two honesty rules the knowledge base holds, and this document inherits: + +- Where a source is silent on something, we say "not stated" — a gap is never papered over + with a paraphrase that sounds like a ruling. +- Where schools and councils disagree, the disagreement is named, with both sides — never + flattened into "scholars say". §71.7/§71.8's opinion map is the worked example: + prohibitions, permissions, and the conditions each attaches, all recorded. + +## What is attested versus what is computed + +The core claim of keel's compliance design, in the screen's own words +(`keel/compliance/screen.py`): market facts are computed, Shariah classifications are +**ATTESTED, never inferred**. Whether a token's core purpose is a haram sector (§28.4), +whether it is asset-backed `'ayn` or a claim `dayn` (§65.5/§67.2), and whether it pays a +riba-like yield are questions of fact-plus-scholarship about the world. No module in this +repository derives them from candles, and none pretends to. A human records them, with a +source and a name, via `keel assets attest`. + +And when the attestation is absent: **unknown is a rejection**. An unattested asset is not +"probably fine" — it is unknown, and the screen fails closed on unknown. The same posture +runs through the rails: rail 17 fails closed on a missing attestation because "silence is not +evidence of possession" (`keel/execution/guards.py`). + +## The rulings encoded, and their sources + +### The curation screen (`keel/compliance/screen.py`) + +A CURATION gate — admission to the allowlist, checked once, not per-trade. §28.4 is explicit +that sector and backing are "a listing criterion, checked once when curating the allowlist, +not per-trade". The attested axes: + +- **Sector (§28.4).** A token whose core business is a haram line — gambling, alcohol, + riba-based lending, and the rest of `HARAM_SECTORS` — is rejected. Aave/Compound-class + lending tokens fail here (§41.1's readings, confirmed at §65.10). +- **Backing (§65.5/§67.2).** `'ayn` (an owned thing) passes; `dayn` (a debt claim on an + issuer) is refused — trading a pure claim is a different contract under different rules. + An `'ayn` asset backed by gold or silver draws a warning that §65.5's stricter + `bay' al-sarf` regime applies: no deferment, and a 72-hour settlement bound. +- **`pays_yield` (§28.4, the riba screen).** Rejects with the screen's exact failure wording: + "the asset carries a guaranteed/expected return for holding it, which is riba-like + (§28.4); holding it is not a bare spot position". The field's semantics are BARE HOLDER, + not "staking exists": it asserts what holding the asset *without* staking or lending earns. + Established by fetching the docs, not by assumption — Solana's staking documentation says + rewards require delegation ("In order to earn staking rewards … the tokens in a stake + account must be delegated to a validator"), with no rebasing, so "Bare holding earns + nothing, which is exactly what the field asserts." + (`docs/experiments/2026-08-07-unvalidated-skip-set-reassessment.md`). +- **Wrapper/instrument (§71.4a).** The allowlist is not juristically homogeneous, so admission + names the CONTRACT, not just the underlying. Only `spot` is admitted; CFD, future, + perpetual, option, and leveraged-token listings are refused, recorded via + `keel assets attest-instrument`. Unattested fails closed. + +The computed axes — history depth, liquidity, settlement quotability — are market facts +about our own cache, recomputed freely. Of everything the screen checks, a documented +exception (`keel assets exempt`) may waive only ONE criterion today: `history` +(`WAIVABLE_CRITERIA` is `frozenset({"history"})`). Liquidity, settlement, and the spot +instrument shape can NEVER be waived, and neither can any Shariah criterion — nothing in +the screen consults a waiver for them, and the CLI's `--criterion` choice is restricted to +that set. Expanding it is a deliberate future decision, not a default. + +### Rail 1 — allowlist enforcement (`keel/execution/guards.py`) + +Per-trade and un-overridable: every intent, DCA included, must be for an allowlisted asset. +This rail enforces the attested rulings above mechanically on every order; the ruling itself +lives in the attestation, never in the rail. + +### Rail 17 — withdrawal capability, `qabd` §65.4 + +The one rail that encodes fiqh as an executable check. Ayub's constructive-possession test +holds that possession is completed when the vendor sets the asset aside and "there is nothing +to prevent the buyer from taking physical possession from the vendor whenever he desires"; +the two-part test is "(i) the buyer bears the risk and reward, and (ii) nothing prevents the +buyer from taking delivery whenever he wishes" (§65.4). An asset we cannot withdraw is an +asset we may not validly POSSESS — so acquiring more of it is the thing to stop. + +The operative test is tri-sourced: "Three sources now converge on the identical operative +test: possession is the ability to dispose, not physical custody (§65.4 Ayub · §67.1 OIC +53/4-6 · §71.5 AAOIFI SS 18 3/5 via SRB)" — §71.5's own summary; §67.1 is Al-Jarhi, +Abuzaid & Oweida's *Handbook of Islamic Finance* (2022) quoting the OIC Fiqh Academy +resolution (Res. 53/4-6) that holds electronic constructive possession sufficient. + +Mechanics: the operator attests with `keel withdrawals attest`; the attestation is live-read +on every intent and expires after 7 days (`WITHDRAWAL_ATTESTATION_TTL_SEC`, +`keel/execution/executor.py`) — "a stale attestation is no better than none". ENTRIES ONLY, +like rails 11/16: existing holdings are already ours, and forcing a sale to "fix" a +withdrawal freeze would be strictly worse than holding through it. Unknown fails closed. + +### Rails 18/19 — settlement currency and spot-instrument shape + +Charter, not fiqh-derivation: "Spot-only is this agent's CHARTER, not an operator preference" +(`guards.py`, rail 19's comment). Rail 18 confines settlement to the operator's configured +currencies (default USD/USDC — a config field, the escape hatch); rail 19 requires the +product id to be a well-formed spot pair. Both are justified by measurement, not doctrine: +the feasibility study `docs/experiments/2026-08-05-coinbase-asset-class-feasibility.md` +verified by execution which instrument classes exist on the venue and what each rail closes. +The fiqh content — that derivatives and difference-settlement are impermissible — is real +(§65.6: what makes speculation *maisir* is non-ownership, non-delivery, difference-settlement) +but the RAILS are the charter enforcing it. + +### Purification (§65.9) and idle-balance rewards (§56.3) + +`keel/compliance/purification.py` implements Ayub §65.9: interest/reward credits are +segregated from realised P&L and the equity base, reported as owed to charity, never +recognised as profit — and zakat is computed on purified wealth (§33.1). It is REPORT-ONLY: +the agent never disposes of funds. The record of what it found in this project's own imported +history is `docs/experiments/2026-07-20-income-purification.md`. + +The preventive half is §56.3: Coinbase pays USDC rewards on idle balances, that interest is +riba, and it accrues with no order placed — so no rail can catch it. Disabling rewards at the +account level is the operator's obligation, listed first in +`docs/operator-runbook.md`. + +### The remaining rails — prudential, not fiqh + +Eighteen rails exist (1–14, 16, 17, 18, 19 — there is no rail 15). Of these, only rail 17 +encodes a fiqh ruling, and rails 1/18/19 enforce what the screen and the charter admit. The +rest are PRUDENTIAL — risk and discipline, justified by trading evidence, carrying no +religious claim: + +| rail | what it does | basis | +| --- | --- | --- | +| 2, 3 | per-order and per-day spend caps | risk discipline | +| 4, 5, 6 | exposure cap, correlation-aware sizing, concentration cap | risk discipline | +| 7 | min-move / anti-scalping floor | trading justification only — see below | +| 8, 9 | no averaging into losers, no stop-widening | risk discipline | +| 10 | sells must cite a defined rule | audit discipline | +| 11, 16 | drawdown and consecutive-loss breakers | risk discipline | +| 12 | stale-feed + kill-switch, fails closed | operational safety | +| 13, 14 | spend only the settled quote currency; monthly allowance cap | operational safety | + +Rail 7 carries a correction this repository records prominently: §65.6 holds that +"speculation per se, which means sale/purchase keeping in mind possible change in prices in +the future, is not prohibited" — what makes speculation *maisir* is non-ownership, +non-delivery, or difference-settlement, **not frequency**. So the anti-scalping rail "keeps +its trading justification and LOSES its shariah claim" (the KB's words for §65.6). It stays +because churn costs taker fees, not because churn is haram. + +## What keel deliberately does not decide + +- **Whose ruling is right.** The ruling lives in your attestation, not in the code, so two + operators following different schools get different answers from the same code, by design + (see `CONTRIBUTING.md`, "Governance: rulings vs. machinery"). +- **Whether a given token qualifies as *Māl*.** That is a judgement of fact-plus-scholarship + the screen defers to the human attestor — the DOGE question below is the live example. +- **School differences.** Where sources diverge (§66.6 records identical retail FX ruled + haram by one jurisdiction and halal by another), keel records both and enforces whichever + ruling the operator supplies; it does not adjudicate. +- **Anything about an asset nobody has attested.** Unknown is a rejection, not a default + pass — refusing to decide is the decision. + +## Known open questions + +Stated, not hidden — each is a place where keel's encoded behaviour could be wrong: + +- **ATOM dilution.** Cosmos Hub's own documentation (docs.cosmos.network) says: "Delegate + your ATOM to one or more of the validators on the Cosmos Hub blockchain to earn more ATOM + through Proof-of-Stake"; and, per stakingrewards.com/asset/cosmos as examined at the + time, ATOM has no supply cap, and its dynamic inflation rate adjusts algorithmically to + target the staking ratio (~12.66% inflation, ~19.49% staking APY as of 2026-08-14, the + date of examination). Inflation is uncapped and + dynamic, and newly minted ATOM accrues only to bonded delegators — so a bare holder is + structurally diluted, at an algorithmically maintained rate: value transfers from + non-stakers to stakers, and the transfer does not fade. `pays_yield=NO` remains correct on + the screen's own axis (bare holding pays nothing) and is a separate question from this one. + There is no settled answer in this repository. +- **Staking generally (§65.14).** Contested, not settled: the honest position is "the + question is genuinely contested, we have no scholarly determination in hand, our mandate has + no need of it, and §29.2 directs us to the conservative branch where scholars diverge." + keel stakes nothing — no module in this repository can stake, so no code excludes staked + positions — which is why the §29.2 conservatism belongs to the premise question below, + not to an exclusion keel performs. Stop implying staking is settled riba. +- **The foundational premise itself (§71.1/§29.2).** Every ruling above presupposes that + crypto is Shariah-recognised tradable property, and on that the highest available + authority has declined to rule. IIFA Resolution 237 (§71.1) convened a dedicated symposium + on electronic currencies, debated the matter at its 24th session (Nov 2019), and ISSUED + NO RULING — it identified as unresolved exactly this question ("Is cryptocurrency + considered by Shariah a real-valued property and a tradable item?"), noted the + significant risks and the instability of their transactions, and referred the matter back + for further research. A withheld ruling is not a prohibition; it is also not a + permission. keel's premise that BTC/ETH-class assets are tradable property is a + well-supported INTERPRETIVE POSITION held on §29.2's conservative branch, not a settled + ruling — and keel does not get to cite the same Academy's Res. 53/4-6 as authoritative + on `qabd` (§67.1) while treating it as silent here. +- **DOGE (§86.4).** "A token that has no genuine use or benefit and survives only because + people hope to sell it to someone else at a higher price may FAIL to qualify as *Māl*" — + and the source's own lean is "Strong lean: EXCLUDE DOGE." DOGE also has no supply cap. + 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. +- **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". + +## How to disagree + +The route the architecture already provides — record your own ruling locally: + +- **Attest your own classification.** `keel assets attest --asset --sector --backing + --pays-yield --source --attested-by` writes to *your* database; `--source` and + `--attested-by` are required, because an unsourced claim is not evidence. Your deployment + then follows your ruling, upstream stays neutral, and the audit trail records exactly who + said what. +- **Document exceptions where the screen allows them.** `keel assets exempt` may waive only + one criterion today — `history`: never a Shariah criterion, and never liquidity, + settlement, or the spot instrument shape. +- **To change a classification for everyone**, that is a PR of a different kind: + `CONTRIBUTING.md` requires a cited source and discussion before merge — a classification + with no source behind it is not mergeable, however confident the author. + +## Sources index + +- `docs/superpowers/references/trading-knowledge-base/sources/source-65.md` — Muhammad Ayub, + *Understanding Islamic Finance* (Wiley 2007): the foundation source (§65.4 `qabd`, §65.5 + backing, §65.6 speculation, §65.9 purification, §65.14 staking). +- `docs/superpowers/references/trading-knowledge-base/sources/source-67.md` — Al-Jarhi, + Abuzaid & Oweida, *Handbook of Islamic Finance* (ASBÜ Yayınları, 2022), quoting OIC Fiqh + Academy Res. 53/4-6 on electronic constructive possession (§67.1), gold/`sarf` (§67.2). +- `docs/superpowers/references/trading-knowledge-base/sources/source-71.md` — IIFA Res. 237, + SRB (AAOIFI SS 18 3/5), SC Malaysia's `ribawi` classifier (§71.4a), digital `qabd` (§71.5). +- `docs/superpowers/references/trading-knowledge-base/sources/source-85.md` — Mufti Faraz + Adam, *Bitcoin: Shariah Compliant?* — the keystone for the BTC/ETH premise. +- `docs/superpowers/references/trading-knowledge-base/sources/source-86.md` — Mufti Faraz + Adam, *Is Crypto Halal?* — *Māl* qualification and the DOGE reading (§86.4). +- `docs/superpowers/references/trading-knowledge-base/README.md` — the index: per-source + rows, the citation convention, and the opinion maps. +- Experiment records cited above, under `docs/experiments/`: + `2026-08-07-unvalidated-skip-set-reassessment.md` (bare-holder semantics), + `2026-07-20-candidate-universe.md` (deferred questions), + `2026-07-20-income-purification.md` (what purification found), + `2026-08-05-coinbase-asset-class-feasibility.md` (rails 18/19). diff --git a/tests/test_fiqh_basis.py b/tests/test_fiqh_basis.py new file mode 100644 index 00000000..0278c53b --- /dev/null +++ b/tests/test_fiqh_basis.py @@ -0,0 +1,378 @@ +"""The fiqh basis document: every encoded ruling, stated with its source (#288). + +A Muslim developer deciding whether to trust keel with money is asking a question the code +cannot answer by being read: WHAT fiqh does this machine enforce, and where did each ruling +come from? The codebase carries those answers scattered across guard comments, a KB the size of +a bookshelf, and a handful of experiment records -- auditable in principle, auditable by nobody +who does not already know where to look. #288 asks for one document that states, ruling by +ruling, what Shariah reasoning is encoded, each with its in-repo source, plus what is attested +versus computed, what keel deliberately does not decide, the open questions, and how to +disagree. This file pins that document's acceptance. + +The pins are deliberately TWO-SIDED (the house pattern from `test_contributing.py`): every +verbatim quote the document takes from `guards.py`, `screen.py`, or a KB source is asserted in +the document AND in the file it quotes, so the document cannot drift from the code it explains. +The §65.4 qabd condition is pinned THREE-sided -- document, guard, and source -- because it is +the single ruling most likely to be re-litigated. Nothing here asserts that any scholar has +reviewed the document; whether that happens is #289's question, not this one's. +""" + +from __future__ import annotations + +import re +from pathlib import Path + +_ROOT = Path(__file__).resolve().parents[1] + +#: The document under test (#288). +_DOC = "docs/fiqh-basis.md" + +#: The one-sentence boundary, quoted exactly -- the same constant `test_governance.py` pins in +#: the README and CONTRIBUTING.md. Declared here (not imported) so each file reads standalone, +#: which is also why the doc must quote it verbatim: three copies, one sentence. +_BOUNDARY = "keel is not a fatwa engine. It is an enforcement engine for a ruling you supply." + +#: §65.4's live condition on constructive possession (`qabd`), quoted exactly. Pinned in the +#: document AND in rail 17's comment AND in the KB source -- three-sided, because a sentence +#: this load-bearing must not drift in any of the three places it lives. +_QABD_CONDITION = "there is nothing to prevent the buyer from taking physical possession" + +#: The screen's core epistemic split, from `keel/compliance/screen.py`'s docstring: what is +#: attested versus what is computed. Two-sided with the module it describes. +_ATTESTED_NEVER_INFERRED = "ATTESTED, never inferred" + +#: The screen's fail-closed posture, same docstring. Two-sided. +_UNKNOWN_IS_A_REJECTION = "unknown is a rejection" + +#: The riba screen's failure wording, from `screen_asset`'s `pays_yield` branch. Two-sided. +_RIBA_WORDING = ( + "the asset carries a guaranteed/expected return for holding it, which is riba-like" +) + +#: The bare-holder semantics of `pays_yield`, established by fetching the staking docs. Pinned +#: two-sided with the experiment record that did the fetching, so the document's account of +#: what the field asserts cannot outlive the evidence for it. +_BARE_HOLDER = "Bare holding earns nothing, which is exactly what the field asserts." + +#: §65.6's correction of the anti-scalping rail, quoted from the KB README's source-65 row. +#: Two-sided, so the document cannot soften the correction the KB is blunt about. +_PRUDENTIAL_CORRECTION = "keeps its trading justification and LOSES its shariah claim" + +#: The exact waiver set the doc may claim, two-sided with `screen.py`'s definition: only +#: `history` is waivable today, and the doc must neither widen the hatch nor miss a change +#: to it. `keel assets exempt`'s `--criterion` Choice is built from this very set. +_HISTORY_ONLY_WAIVABLE = 'frozenset({"history"})' + +#: Source-67's true author, in the source file's own title: the Handbook QUOTES OIC Fiqh +#: Academy Res. 53/4-6, it is not the resolution itself. Two-sided, so the doc cannot +#: promote a second-hand quotation into a primary resolution again. +_SOURCE_67_OWNER = "Handbook of Islamic Finance" + +#: §71.1's finding, in the KB's own capital-letter formulation. The premise caveat's +#: load-bearing phrase: IIFA Res. 237 issued no ruling on whether crypto is tradable +#: property, which makes keel's premise an interpretive position under §29.2, not settled. +_ISSUED_NO_RULING = "ISSUED NO RULING" + + +def _doc() -> str: + """The document's text; empty until it exists, so a red run FAILS rather than errors.""" + path = _ROOT / _DOC + return path.read_text() if path.is_file() else "" + + +def _unwrapped(text: str) -> str: + """Join markdown wrapping: drop blockquote markers, then collapse all whitespace.""" + return " ".join(re.sub(r"(?m)^\s*>\s?", "", text).split()) + + +def _rel(relative: str) -> str: + """Read a repo file, wrap-normalised; Python sources also join adjacent string literals. + + A failure message split across two literals (`"...which is " "riba-like..."`) is ONE + sentence to the reader but two quoted fragments after a naive whitespace collapse, so + implicit concatenation is stitched back before matching. + """ + text = re.sub(r"(?m)^\s*>\s?", "", (_ROOT / relative).read_text()) + return " ".join(re.sub(r'"\s+"', " ", text).split()) + + +def test_the_document_exists(): + """#288's deliverable is a file, not a section of some other file. + + The basis must be findable by someone who has never read the code -- a path is a promise + that it stays where it was put. + """ + assert (_ROOT / _DOC).is_file(), ( + f"{_DOC} must exist: the fiqh basis has to be one findable document" + ) + + +def test_the_boundary_sentence_is_stated_verbatim(): + """The doc opens with the boundary, word for word, because everything after depends on it. + + A reader deciding whether to trust keel must first learn whose rulings these are -- and a + paraphrased boundary is a different boundary. + """ + assert _BOUNDARY in _unwrapped(_doc()), ( + f"{_DOC} must state the governance boundary verbatim: {_BOUNDARY!r}" + ) + + +def test_the_qabd_condition_is_pinned_three_sided(): + """§65.4's live condition appears in the doc, in rail 17's comment, and in the KB source. + + Rail 17 is the one place keel encodes fiqh as an executable check, and its entire logic is + this sentence. If any of the three copies is rewritten, this fails rather than letting the + doc explain a guard that no longer exists (or cite a source that no longer says it). + """ + texts = { + _DOC: _unwrapped(_doc()), + "keel/execution/guards.py": _rel("keel/execution/guards.py"), + "docs/superpowers/references/trading-knowledge-base/sources/source-65.md": _rel( + "docs/superpowers/references/trading-knowledge-base/sources/source-65.md" + ), + } + for relative, text in texts.items(): + assert _QABD_CONDITION in text, ( + f"{relative} must carry the §65.4 qabd condition verbatim ({_QABD_CONDITION!r}): " + "the doc, the rail, and the source must say the same thing" + ) + + +def test_the_attested_never_inferred_split_is_pinned_two_sided(): + """The doc's core claim -- classifications are attested, never inferred -- is the screen's. + + This is the sentence that separates keel from a fatwa engine, and it is the screen's own + docstring that says it. The doc must quote it, and the screen must still carry it. + """ + assert _ATTESTED_NEVER_INFERRED in _unwrapped(_doc()), ( + f"{_DOC} must state that Shariah classifications are {_ATTESTED_NEVER_INFERRED!r}" + ) + assert _ATTESTED_NEVER_INFERRED in _rel("keel/compliance/screen.py"), ( + "keel/compliance/screen.py must still carry the attested-never-inferred claim the doc " + "quotes -- if the docstring is rewritten, the doc must be updated to match it" + ) + + +def test_the_fails_closed_posture_is_pinned_two_sided(): + """'Unknown is a rejection' is quoted from the screen, not invented for the doc. + + An unattested asset failing closed is the screen's single most consequential behaviour; a + doc that describes it in softer words than the code would be claiming a kindness the code + does not have. + """ + assert _UNKNOWN_IS_A_REJECTION in _unwrapped(_doc()), ( + f"{_DOC} must state the fail-closed posture in the screen's own words: " + f"{_UNKNOWN_IS_A_REJECTION!r}" + ) + assert _UNKNOWN_IS_A_REJECTION in _rel("keel/compliance/screen.py"), ( + "keel/compliance/screen.py must still carry 'unknown is a rejection' -- the doc " + "quotes it, so the two must move together" + ) + + +def test_the_riba_failure_wording_is_pinned_two_sided(): + """The doc quotes the exact message a riba-yield asset receives, and screen.py keeps it. + + Operators meet this ruling as a CLI failure line long before they meet it as doctrine; the + doc's account and the operator's experience must be the same words. + """ + assert _RIBA_WORDING in _unwrapped(_doc()), ( + f"{_DOC} must quote the riba failure wording verbatim ({_RIBA_WORDING!r})" + ) + assert _RIBA_WORDING in _rel("keel/compliance/screen.py"), ( + "keel/compliance/screen.py must still carry the riba failure wording the doc quotes" + ) + + +def test_the_bare_holder_semantics_are_pinned_to_the_experiment_record(): + """'Bare holding earns nothing' is evidence, not vibes -- the fetching record is cited. + + `pays_yield` asserts what holding WITHOUT staking earns; that semantics was established by + fetching the staking docs (2026-08-07). The doc must quote the finding and the record must + still show it. + """ + assert _BARE_HOLDER in _unwrapped(_doc()), ( + f"{_DOC} must state the bare-holder semantics in the record's own words: " + f"{_BARE_HOLDER!r}" + ) + record = "docs/experiments/2026-08-07-unvalidated-skip-set-reassessment.md" + assert _BARE_HOLDER in _rel(record), ( + f"{record} must still carry the finding the doc quotes -- the semantics stand on it" + ) + + +def test_rail_17s_seven_day_ttl_is_pinned_to_the_executor(): + """The doc names the TTL by its constant, two-sided with the executor that defines it. + + 'Fresh attestation' is a rubbery phrase; `WITHDRAWAL_ATTESTATION_TTL_SEC` is 7 days, and a + reader auditing the rail needs the number and the symbol that owns it. + """ + assert "WITHDRAWAL_ATTESTATION_TTL_SEC" in _doc(), ( + f"{_DOC} must name rail 17's TTL as WITHDRAWAL_ATTESTATION_TTL_SEC (7 days), so the " + "number is traceable to the constant that enforces it" + ) + assert "WITHDRAWAL_ATTESTATION_TTL_SEC" in _rel("keel/execution/executor.py"), ( + "keel/execution/executor.py must still define WITHDRAWAL_ATTESTATION_TTL_SEC -- " + "the doc cites it, so a rename must fail here rather than orphan the citation" + ) + + +def test_the_prudential_rails_are_separated_from_the_fiqh_rails_with_the_65_6_correction(): + """Safety rails must not borrow religious authority -- and §65.6 must be quoted saying so. + + Only rail 17 (and the screen behind rail 1) encode fiqh; the rest are prudential. §65.6 + stripped the anti-scalping rail of a shariah claim it never had, and the KB's wording is + the citable form of that correction. Two-sided with the KB README's source-65 row. + """ + doc = _unwrapped(_doc()) + assert "PRUDENTIAL" in doc, ( + f"{_DOC} must mark the non-fiqh rails as prudential -- a safety rail wearing fiqh " + "clothing is exactly the confusion the document exists to prevent" + ) + assert _PRUDENTIAL_CORRECTION in doc, ( + f"{_DOC} must state the §65.6 anti-scalping correction in the KB's words: " + f"{_PRUDENTIAL_CORRECTION!r}" + ) + kb = "docs/superpowers/references/trading-knowledge-base/README.md" + assert _PRUDENTIAL_CORRECTION in _rel(kb), ( + f"{kb} must still carry the §65.6 correction the doc quotes" + ) + + +def test_the_waivable_criteria_claim_is_pinned_two_sided(): + """The doc says only `history` may be waived, and `screen.py` must still mean it. + + `keel assets exempt` is the one escape hatch in the curation screen. The doc's account of + what it can waive must match the set the code enforces, in both directions: a doc that + widens the hatch claims an authority the code does not have, and a code change the doc + misses describes a hatch that no longer exists. + """ + assert _HISTORY_ONLY_WAIVABLE in _unwrapped(_doc()), ( + f"{_DOC} must state the waiver set exactly ({_HISTORY_ONLY_WAIVABLE!r}): only " + "`history` is waivable today -- never a Shariah criterion, settlement, or liquidity" + ) + assert _HISTORY_ONLY_WAIVABLE in _rel("keel/compliance/screen.py"), ( + "keel/compliance/screen.py must still define WAIVABLE_CRITERIA as " + f"{_HISTORY_ONLY_WAIVABLE!r} -- if the set grows, the doc must be updated to match" + ) + + +def test_the_atom_dilution_open_question_is_stated_not_hidden(): + """The hardest open question stays in the doc, phrased on the screen's own axis. + + ATOM's uncapped, dynamic inflation pays only bonded delegators, so a bare holder is + structurally diluted -- yet `pays_yield=NO` remains correct on the axis the field asserts. + The doc must hold both halves at once and say plainly that this repo has no settled answer. + """ + known = _doc().split("## Known open questions", 1)[1] + known_open_questions = known.split("## How to disagree", 1)[0] + assert "ATOM" in known_open_questions, ( + f"{_DOC} must state the ATOM dilution question among the open questions" + ) + assert "structurally diluted" in _unwrapped(known_open_questions), ( + f"{_DOC} must say a bare ATOM holder is structurally diluted -- the mechanism, not " + "just the worry" + ) + assert "pays_yield" in known_open_questions, ( + f"{_DOC} must name the pays_yield axis alongside the dilution question, so the screen " + "field and the open question cannot be conflated" + ) + assert "no settled answer" in _unwrapped(known_open_questions).lower(), ( + f"{_DOC} must say plainly that the ATOM question has no settled answer in this repo" + ) + + +def test_every_kb_citation_resolves_to_a_source_file_in_the_repo(): + """Every §N.x the doc cites must be a real in-repo file -- citations are checkable + or they are worthless. + + The KB's convention is that §N.x means sources/source-NN.md; a citation of a source that + does not exist (there is no source-53 or source-77) is a dead reference wearing the costume + of scholarship. The load-bearing trio -- §65 (Ayub), §67 (the Handbook quoting the OIC), + §71 (AAOIFI/IIFA) -- must all appear, because rail 17's tri-sourcing stands on them. + """ + text = _doc() + cited = {int(match) for match in re.findall(r"§(\d+)", text)} + assert cited, f"{_DOC} must cite the KB with §N.x section references" + for source in (65, 67, 71): + assert source in cited, ( + f"{_DOC} must cite §{source}: the qabd ruling and the screen stand on it" + ) + sources_dir = _ROOT / "docs/superpowers/references/trading-knowledge-base/sources" + for source in sorted(cited): + assert (sources_dir / f"source-{source:02d}.md").is_file(), ( + f"{_DOC} cites §{source} but sources/source-{source:02d}.md does not exist -- " + "every citation must resolve to a real in-repo source file" + ) + + +def test_the_foundational_premise_non_ruling_is_pinned_two_sided(): + """The premise caveat names IIFA's withheld ruling, and source-71.md must still carry it. + + Everything the doc encodes presupposes crypto is tradable property; the honest caveat -- + that IIFA Res. 237 issued no ruling on exactly that question, leaving keel's premise a + well-supported interpretive position under §29.2 -- must sit among the open questions and + stay anchored to the KB section that established it. + """ + known = _doc().split("## Known open questions", 1)[1] + known_open_questions = known.split("## How to disagree", 1)[0] + assert _ISSUED_NO_RULING in _unwrapped(known_open_questions), ( + f"{_DOC} must state the §71.1 non-ruling ({_ISSUED_NO_RULING!r}) among the open " + "questions: keel's premise is an interpretive position under §29.2, not a settled " + "ruling" + ) + source = "docs/superpowers/references/trading-knowledge-base/sources/source-71.md" + assert _ISSUED_NO_RULING in _rel(source), ( + f"{source} must still carry the non-ruling the doc cites -- the caveat stands on it" + ) + + +def test_source_67_is_attributed_to_its_true_author(): + """§67 is the Handbook quoting OIC Fiqh Academy Res. 53/4-6, not the resolution itself. + + The doc cites §67.1 for the `qabd` tri-sourcing; that passage is a 2022 handbook's + second-hand reproduction of the resolution. Attributing the quote to the Academy directly + would dress a quotation as a primary ruling -- the doc and the source file must agree on + whose document it is. + """ + source = "docs/superpowers/references/trading-knowledge-base/sources/source-67.md" + assert _SOURCE_67_OWNER in _unwrapped(_doc()), ( + f"{_DOC} must attribute source-67 to the {_SOURCE_67_OWNER!r} -- it quotes the OIC " + "resolution, it is not the resolution" + ) + assert _SOURCE_67_OWNER in _rel(source), ( + f"{source} must still name itself the {_SOURCE_67_OWNER!r} the doc cites" + ) + + +def test_the_disagreement_section_names_the_local_attestation_route(): + """How to disagree must route through `keel assets attest`, not through a fiqh court. + + The document tells a reader how to dissent; the only route the architecture offers is + recording your own attestation locally. A disagreement section without that command is an + invitation to litigate upstream, which the project has forsworn. + """ + text = _doc() + assert "## What keel deliberately does not decide" in text, ( + f"{_DOC} must have a section naming what keel deliberately does not decide" + ) + assert "## How to disagree" in text, ( + f"{_DOC} must have a 'How to disagree' section" + ) + disagreement = text.split("## How to disagree", 1)[1] + assert "keel assets attest" in disagreement, ( + f"{_DOC}'s disagreement section must name the local route (`keel assets attest`) " + "-- it is the only dissent path the architecture provides" + ) + + +def test_the_readme_links_the_document(): + """The README's documentation map points at the basis, so a stranger can find it. + + A document about findability that cannot be found from the front door would be a joke in + exactly the register this project tries to avoid. + """ + assert _DOC in (_ROOT / "README.md").read_text(), ( + f"README.md must link {_DOC} from its documentation map" + )