The README says it applies to any domain, and shows how - #122
Merged
Merged
Conversation
At first glance the README read as a payments library. It was not the headings — only one names money — it was that every concrete thing under them was a Stripe refund: 67 occurrences of "refund", 23 of "stripe", 34 euro signs, against 4 mentions of a namespace and 3 of IAM. Six of the seven runnable blocks were refunds, and the only multi-domain policy in the file sat collapsed inside a <details> where a skimming reader never reaches it. The breadth was there; nothing above the fold showed it. The fix is variety in the concrete, not a retreat into philosophy. The refund story keeps its place: AMBIGUOUS versus FAILED is abstract until money moves twice, and it is the sharpest instance we have. What changes is everything after it. - One line under the demo GIF says the refund is an instance. It is the first prose a stranger reads and it is domain-free. - The quick start moves to IAM, and its policy carries three domains and all three decisions in twelve lines: crm.update_record allows, iam.grant_role asks a human for anything above read-only, audit.log.delete denies. The approval-binding lesson is unchanged and now demonstrates role_in rather than a third amount band, so the policy language no longer looks like it only understands money. The closing paragraph says in one sentence that money is the same shape with amount_gte/amount_lte, and keeps the negative-amount warning. - The gateway example becomes delete_document / search_documents. Also fixes a defect: the output block after approved.py was the wrong command's output. It repeated `ctrlrun approve`'s two lines with a stray "D" appended, where the script prints three lines of its own. Every output block in the section is now the real output of the block above it, captured from a run. refund 67 -> 35, stripe 23 -> 11, payment 18 -> 3. What remains is the demo transcript and the verify report, both generated from real output and both a larger change.
The previous change stopped the README reading as payments-only. It did not say what the general case is, so a reader in healthcare or devops still had to infer it from a quick start about IAM. A new section says it and shows it. The mechanism first, because that is the part a reader can check: nothing in the kernel knows what a refund is, an action is a name, canonical arguments, an effect key and a resource, and the three questions asked of it do not vary by domain. Two things carry the domain and the operator writes both -- the effect key, which is the only domain knowledge in the system, and the conditions, which are `<argument>_<op>` and not amounts. `replicas_lte: 10` and `host_count_lte: 1` are in the shipped templates and are the evidence that a band is available to a domain that has never issued an invoice. Then a table, one row per template under examples/policies/, naming an action that is autonomous, one a human decides and one that is never allowed. The nine files were mentioned in half a sentence downpage; they are the best evidence of breadth in the repository and nothing showed them. The security row is called out in the prose because it is the one that argues against the amount-shaped reading of the whole idea: adding a deny rule to a firewall is autonomous and adding an allow rule is not, and no threshold would have told you that. The table is a claim about files that change, so it is tested. test_examples.py reads the rows back out of the README, resolves each to the template its own cell links to, and asserts the cited action exists there and can reach the decision it is cited for -- reachable, not exclusive, since three rows cite a banded action that is honestly in two columns. A row that drifts fails there. The header line under the GIF now links to the section.
arpanghoshal
force-pushed
the
readme-domain-neutral
branch
from
September 7, 2026 12:15
69fdd40 to
52fe779
Compare
arpanghoshal
added a commit
that referenced
this pull request
Sep 13, 2026
…hree steps The README is the GitHub and PyPI surface and ctrlrun.dev is the marketing one, and until now they led with different sentences. The header now opens with the homepage's H1 and lede verbatim, the first section carries its extra-zero example, "Three ways to use it" carries its line about agents you can and can't modify, "How it works" walks the seven steps of its diagram in the same order, the tiers are the homepage's three, and the file closes on its footer line. tests/test_readme_assets.py pins the strings here; the docs repository reads them out of index.mdx on a branch of the same name, so a drift on either side fails CI on the other. The animation was `ctrlrun demo`'s first two scenarios, piped through sed and a pacing loop. It was a true recording and a hard one to read: a stranger saw a transcript with two pipes in the command line and no code, and could not tell from it what CTRLRun is. It is now a recording of the README's own "Use it in three steps" section: the policy file, the agent that runs one refund and is stopped on the next, the human answering from the shell, and the same approval refused for a different amount. The three files it records are committed under docs/assets/recording/ and asserted byte for byte equal to the README's runnable blocks; the lines it ends on are committed as before, and the test now runs those files against the library instead of the demo. Both programs exit non-zero on the path where the refusal did not happen, so a recording of a broken build fails instead of looking fine. The terminal is 65 columns at 20px rather than 120 at 17px, so the text is legible at the width GitHub renders it. The quick start moves from IAM back to a refund so that the README, the animation, the homepage and the quickstart tell one story; the multi-domain point #122 made is kept by the sentence after it and by the nine-domain table. "How it works" gains a paragraph on budgets, scope and tasks, which 0.9 shipped and the README did not mention. Signed-off-by: arpan <contact@arpanghoshal.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
At first glance the README read as a payments library, and it never said otherwise. It was not the headings — only one of ten named money — it was that every concrete thing under them was a Stripe refund.
refundstripe€paymentSix of the seven runnable blocks were refunds, and the one multi-domain policy in the file sat collapsed inside a
<details>where a skimming reader never reaches it. The nine domain templates underexamples/policies/— the best evidence of breadth in the repository — were named in half a sentence downpage.The approach
Variety in the concrete, not a retreat into philosophy. The hero already states the thesis without a domain — "Autonomy belongs to the action, not the agent" — and replacing the refund story with an abstraction would buy generality at the price of anyone knowing what the library does. The refund keeps its place:
AMBIGUOUSversusFAILEDis abstract until money moves twice. Everything after it changes, and a new section says outright what the general case is.Commit 1 — the refund is the example, not the scope
crm.update_recordallows,iam.grant_roleasks a human for anything above read-only,audit.log.deletedenies. The approval-binding lesson is unchanged — a human approvesadminforu_412, the agent triesowner, and it is refused — and it now demonstratesrole_inrather than a third amount band, so the policy language stops looking like it only understands money. The closing paragraph says in one sentence that money is the same shape withamount_gte/amount_lte, and keeps the negative-amount warning.delete_document/search_documents.approved.pywas the wrong command's output — it repeatedctrlrun approve's two lines with a strayDappended, where the script prints three lines of its own. Every output block in the section is now the real output of the block above it, captured from a run.Commit 2 — say it applies to any domain, and show how
A new section, "The same shape in nine domains", placed before the policy file.
The mechanism first, because that is the part a reader can check: nothing in the kernel knows what a refund is. An action is a name, canonical arguments, an effect key and a resource, and the three questions asked of it do not vary by domain. Two things carry the domain and the operator writes both:
refund:{payment_id},namespace:{cluster}:{name},grant:{user_id}:{role},prescription:{patient_id}:{drug}.<argument>_<op>, so the same operators readreplicas_lte: 10andhost_count_lte: 1— both already in the shipped templates, and the evidence that a band is available to a domain that has never issued an invoice.Then a table, one row per template under
examples/policies/: an action that is autonomous, one a human decides, one that is never allowed, each linked to the file it came from. The security row is called out in the prose because it argues against the amount-shaped reading of the whole idea — adding a deny rule to a firewall is autonomous and adding an allow rule is not, and no threshold would have told you that.The table is tested
It is a claim about files that change, so
tests/test_examples.pyreads the rows back out of the README, resolves each to the template its own cell links to, and asserts the cited action exists there and can reach the decision it is cited for — reachable, not exclusive, since three rows cite a banded action that is honestly in two columns.Mutation-tested:
firewall.add_allow_rulemoved into the autonomous column...carries_the_decision_it_is_cited_for[security]fails...carries_the_decision_it_is_cited_for[healthcare]failstest_the_readme_domain_table_has_a_row_per_templatefailsWhat is left
The demo transcript (five refund scenarios) and the
ctrlrun verifyreport (stripe.refundthroughout) are still money. Both are generated from real output with tests that hold them byte-exact, so diversifying them means changing the demo scenarios and re-recording the GIF — a separate change. Scenario 3 is worth noting: the prose describes it as "two workers running onekubectl delete namespace" and the code implements a refund.ctrlrun verifyagainstexamples/authority/devops.yamlwas considered for the verify block and rejected — it reports 1/1 with 10 N/A, which is a weaker showcase than payments' 11/11.Checks
tools/docs_audit/snippets.py README.md— 7 runnable blocks, 0 failed, offlinetools/docs_audit/lint.pyandlinks.py— 0 findings, 0 brokenruff checkclean