Repository navigation
feat: the authority decides, this node executes - #112
Merged
Merged
Conversation
Owner ruling, 2026-09-30: anything may propose, the authority in force
authorizes, and after a decide/3 allow this node's Elixir runs the tool. The
adapter does not run the tool and does not change the arguments it was asked
about.
The membrane
Trinity.Effects used to hand the effect to authority.execute/3 and treat what
came back as the result, so the effect happened in another repository, past every
check the membrane had just made. It now calls the tool's own execute/2 itself,
in this VM, after the admission receipt is in the chain. run_tool/2 is that call
and the census test holds it as the only one on the effect path.
stage/2 could also change what ran. The fingerprint was verified before the
authority was handed the Staged, and whatever came back was used unexamined. Two
checks now run after stage/2, in this order: the fingerprint is re-derived over
the returned arguments and compared to the one the gate bound, not to the one the
returned struct carries, so rewriting args and fingerprint to agree does not
pass; then every remaining field is compared, because the fingerprint covers
session, tool, args and cwd and does not cover module at all. An authority may
set staged_at and basis and nothing else.
Both are tested against adapters that attempt them, with a control adapter that
changes nothing and whose effect runs, so the two denials cannot be satisfied by
a membrane that refuses everything under any adapter. The module-swap adapter
first swapped to Trinity.TestTools.WriteNote, which is what write_note already
resolves to in the test registry: the identity, and it passed while proving
nothing. It swaps to Trinity.TestTools.Echo.
Boot
An unloadable TRINITY_AUTHORITY under :regulated stopped the boot with a
FunctionClauseError in Trinity.Profile instead of naming the module. Application
resolved the authority through Selection.select/1 and flattened every refusal to
nil, and nil matched no clause of check_authority/2. The caller now reports the
selection's own reason as {:regulated_authority_unselectable, reason}, and
check_authority/2 is total.
Selection ran as {Task, :start_link, ...}, which returns as soon as the task is
spawned, so it ran concurrently with every child started after it. In that window
persistent_term held nothing and Authority.impl/0 answered Local, which under
:regulated is the one module the profile exists to refuse. Selection is
synchronous: start/0 finishes the work and answers :ignore. impl/0 additionally
raises under :regulated rather than falling back, because "should be unreachable"
is not a guarantee and this failure is silent.
Documents
ADR-0008 point 5 said an external adapter meant Trinity kept no executor, and the
census asserted that absence. Amended by append, with the old text struck rather
than deleted, and a table of where each new claim is enforced. The README
executor sentences say the same thing. docs/authority-adapter.md is the contract:
four callbacks plus forward_receipt/2, decide returns allow or deny and never
falls back to a local decision, execute/3 runs no tool, stage/2 changes nothing
but staged_at and basis, Trinity writes its own receipts and the adapter
acknowledges envelopes byte for byte, act_hash is opaque with act_hash_alg
sha256+rfc8785-v1, and the wrapper release lives outside this repository. Every
claim in it is a path in this tree or says NOT IN TREE.
No adapter is added. README mentions of the authority layer go from three to two;
none is added anywhere.
Signed-off-by: Ayla Croft <aylacroft@proton.me>
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.
Owner ruling, 2026-09-30:
The adapter decides. It does not run the tool, and it does not change the arguments it was asked about. No adapter is added here, and none is implemented.
1. The effect path
Trinity.Effectshanded the effect toauthority.execute/3and treated what came back as the result, so the effect happened in another repository, past every check the membrane had just made. It now calls the tool's ownexecute/2itself, in this VM, after the admission receipt is in the chain.result = authority.execute(staged, :allow, ctx)result = run_tool(restaged, ctx)lib/trinity/authority/local.exlib/trinity/effects.exTrinity.Authority.Local.execute/3now answers{:error, :not_the_effect_path}. The callback stays in the behaviour so an adapter written against the older contract, where returning a result from it was how the effect happened, is not silently accepted.2.
stage/2could still change what ranThe fingerprint was verified before the authority was handed the
Staged, and whatever came back was used unexamined. Two checks now run afterstage/2, in this order:argsandfingerprintto agree with each other does not pass;moduleat all.An authority may set
staged_atandbasis. Nothing else.Tested against adapters that actually attempt it, plus a control that changes nothing and whose effect runs, so the two denials cannot be satisfied by a membrane that simply refuses everything under any adapter:
The module-swap adapter first swapped to
Trinity.TestTools.WriteNote, which is whatwrite_notealready resolves to in the test registry. That is the identity, and it passed while proving nothing. It swaps toTrinity.TestTools.Echo.3. Two boot failures under
:regulatedAn unloadable
TRINITY_AUTHORITYcrashed instead of refusing by name. Reproduced before fixing:Trinity.Applicationflattened every selection refusal tonil, andnilmatched no clause. The caller now reports the selection's own reason as{:regulated_authority_unselectable, reason}, andcheck_authority/2is total.Selection exposed
Localwhile it was still in flight. It ran as{Task, :start_link, [fn -> boot!() end]}, which returns as soon as the task is spawned, so it ran concurrently with every child started after it. In that window:persistent_termheld nothing andAuthority.impl/0answeredLocal, which under:regulatedis the one module the profile exists to refuse. Selection is now synchronous:start/0finishes the work and answers:ignore.impl/0additionally raises under:regulatedrather than falling back, because "should be unreachable" is not a guarantee and this failure is silent.4. Documents
docs/authority-adapter.md, new: four callbacks plusforward_receipt/2;decide/3returns allow or deny and never falls back to a local decision;execute/3runs no tool;stage/2changes nothing butstaged_atandbasis; Trinity writes its own receipts and the adapter acknowledges envelopes byte for byte;act_hashis opaque, carried withact_hash_algofsha256+rfc8785-v1, and NOT IN TREE; the wrapper release lives outside this repository. Every claim in it is a path in this tree or says NOT IN TREE.The one test that proves
Localis refused under:regulatedis untouched and still passes, intest/trinity/profile_test.exsand at a real boot intest/trinity/regulated_boot_node_test.exs.Evidence
The one failure is
Trinity.Skills.RegistryTest"AC3 ... within 2 s", which is load-sensitive by construction and fails on this machine with these changes entirely excluded. It is green on CI.No new slice id, no ROADMAP row, no adapter. Mentions of the authority layer by name in the README go from three to two; none is added anywhere in the tree.
Not merged.