From 493c0d556fbbb1251c7ce3bf3809ee13ae9004aa Mon Sep 17 00:00:00 2001 From: Elmehdi Aitbrahim Date: Mon, 17 Aug 2026 01:37:08 -0400 Subject: [PATCH 1/3] =?UTF-8?q?docs(fiqh):=20write=20the=20fiqh=20basis=20?= =?UTF-8?q?document=20=E2=80=94=20every=20encoded=20ruling,=20sourced=20(#?= =?UTF-8?q?288)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 3 +- README.md | 3 + docs/fiqh-basis.md | 236 ++++++++++++++++++++++++++++++ tests/test_fiqh_basis.py | 303 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 544 insertions(+), 1 deletion(-) create mode 100644 docs/fiqh-basis.md create mode 100644 tests/test_fiqh_basis.py 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..ce7f1554 100644 --- a/README.md +++ b/README.md @@ -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..b7340c51 --- /dev/null +++ b/docs/fiqh-basis.md @@ -0,0 +1,236 @@ +# 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'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, and the only ones a documented exception +(`keel assets exempt`) may ever waive. The Shariah criteria can never be waived: nothing in +the screen consults a waiver for them (`WAIVABLE_CRITERIA` is `{"history"}` and the code +says why: 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 the OIC Fiqh +Academy resolution holding 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 settled USDC; 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"; stakingrewards.com/asset/cosmos says: "ATOM has no supply cap. The + inflation rate is dynamic and adjusts algorithmically based on the staking ratio" (~12.66% + inflation, ~19.49% staking APY at the time 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 excludes staked positions on §29.2 conservatism, not on a ruling it holds — stop + implying staking is settled riba. +- **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 + market-fact criteria (history, liquidity) — never a Shariah criterion. +- **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` — 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..c7a4683b --- /dev/null +++ b/tests/test_fiqh_basis.py @@ -0,0 +1,303 @@ +"""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" + + +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 record that fetched it 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: {_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(): + """The doc must not let safety rails borrow religious authority -- and must say §65.6 said 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_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. + """ + open_questions = _doc().split("## How to disagree")[0] + assert "ATOM" in open_questions, ( + f"{_DOC} must state the ATOM dilution question among the open questions" + ) + assert "structurally diluted" in _unwrapped(open_questions), ( + f"{_DOC} must say a bare ATOM holder is structurally diluted -- the mechanism, not just " + "the worry" + ) + assert "pays_yield" in 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(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 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 (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_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" + ) From f912d8d89723a679e086ed085ee37c98b6f75c27 Mon Sep 17 00:00:00 2001 From: Elmehdi Aitbrahim Date: Mon, 17 Aug 2026 01:38:42 -0400 Subject: [PATCH 2/3] =?UTF-8?q?docs(readme):=20say=20eighteen=20rails,=20m?= =?UTF-8?q?atching=20guards.py=20=E2=80=94=20the=20fiqh=20basis=20doc=20co?= =?UTF-8?q?unts=20them,=20so=20the=20README=20cannot=20contradict=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ce7f1554..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 From 14fb13fec0f3394dcc9739960ddccc7612d4a54a Mon Sep 17 00:00:00 2001 From: Elmehdi Aitbrahim Date: Mon, 17 Aug 2026 02:09:22 -0400 Subject: [PATCH 3/3] =?UTF-8?q?docs(fiqh):=20review=20fixes=20=E2=80=94=20?= =?UTF-8?q?history-only=20waivers,=20source-67=20attribution,=20the=20?= =?UTF-8?q?=C2=A771.1=20non-ruling=20named,=20plus=20minor=20precision?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/fiqh-basis.md | 51 +++++++++++------ tests/test_fiqh_basis.py | 117 ++++++++++++++++++++++++++++++++------- 2 files changed, 131 insertions(+), 37 deletions(-) diff --git a/docs/fiqh-basis.md b/docs/fiqh-basis.md index b7340c51..aef39e5e 100644 --- a/docs/fiqh-basis.md +++ b/docs/fiqh-basis.md @@ -29,8 +29,8 @@ 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's opinion map is the worked example: prohibitions, - permissions, and the conditions each attaches, all recorded. + 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 @@ -77,10 +77,12 @@ not per-trade". The attested axes: `keel assets attest-instrument`. Unattested fails closed. The computed axes — history depth, liquidity, settlement quotability — are market facts -about our own cache, recomputed freely, and the only ones a documented exception -(`keel assets exempt`) may ever waive. The Shariah criteria can never be waived: nothing in -the screen consults a waiver for them (`WAIVABLE_CRITERIA` is `{"history"}` and the code -says why: expanding it is a deliberate future decision, not a default). +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`) @@ -99,8 +101,9 @@ asset we may not validly POSSESS — so acquiring more of it is the thing to sto 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 the OIC Fiqh -Academy resolution holding electronic constructive possession sufficient. +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`, @@ -149,7 +152,7 @@ religious claim: | 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 settled USDC; monthly allowance cap | 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 @@ -177,9 +180,10 @@ Stated, not hidden — each is a place where keel's encoded behaviour could be w - **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"; stakingrewards.com/asset/cosmos says: "ATOM has no supply cap. The - inflation rate is dynamic and adjusts algorithmically based on the staking ratio" (~12.66% - inflation, ~19.49% staking APY at the time of examination). Inflation is uncapped and + 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 @@ -188,8 +192,21 @@ Stated, not hidden — each is a place where keel's encoded behaviour could be w - **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 excludes staked positions on §29.2 conservatism, not on a ruling it holds — stop - implying staking is settled riba. + 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. @@ -209,7 +226,8 @@ The route the architecture already provides — record your own ruling locally: 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 - market-fact criteria (history, liquidity) — never a Shariah criterion. + 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. @@ -219,7 +237,8 @@ The route the architecture already provides — record your own ruling locally: - `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` — OIC Fiqh +- `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). diff --git a/tests/test_fiqh_basis.py b/tests/test_fiqh_basis.py index c7a4683b..0278c53b 100644 --- a/tests/test_fiqh_basis.py +++ b/tests/test_fiqh_basis.py @@ -58,6 +58,21 @@ #: 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.""" @@ -151,8 +166,8 @@ def test_the_fails_closed_posture_is_pinned_two_sided(): 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" + "keel/compliance/screen.py must still carry 'unknown is a rejection' -- the doc " + "quotes it, so the two must move together" ) @@ -171,14 +186,15 @@ def test_the_riba_failure_wording_is_pinned_two_sided(): def test_the_bare_holder_semantics_are_pinned_to_the_experiment_record(): - """'Bare holding earns nothing' is evidence, not vibes -- the record that fetched it is cited. + """'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: {_BARE_HOLDER!r}" + 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), ( @@ -197,13 +213,13 @@ def test_rail_17s_seven_day_ttl_is_pinned_to_the_executor(): "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" + "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(): - """The doc must not let safety rails borrow religious authority -- and must say §65.6 said so. + """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 @@ -224,6 +240,24 @@ def test_the_prudential_rails_are_separated_from_the_fiqh_rails_with_the_65_6_co ) +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. @@ -231,30 +265,32 @@ def test_the_atom_dilution_open_question_is_stated_not_hidden(): 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. """ - open_questions = _doc().split("## How to disagree")[0] - assert "ATOM" in open_questions, ( + 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(open_questions), ( - f"{_DOC} must say a bare ATOM holder is structurally diluted -- the mechanism, not just " - "the worry" + 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 open_questions, ( + 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(open_questions).lower(), ( + 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 worthless. + """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 (OIC), §71 (AAOIFI/IIFA) -- must - all appear, because rail 17's tri-sourcing stands on them. + 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)} @@ -266,11 +302,50 @@ def test_every_kb_citation_resolves_to_a_source_file_in_the_repo(): 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" + 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. @@ -287,8 +362,8 @@ def test_the_disagreement_section_names_the_local_attestation_route(): ) 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" + f"{_DOC}'s disagreement section must name the local route (`keel assets attest`) " + "-- it is the only dissent path the architecture provides" )