RFC 0036 — Two rights, and : fields are paths under their vertex¶
Note
Status: accepted. This page is an accepted change proposal, kept as the record of why the specification reads as it does. RFCs are proposals and history, not the standard. The normative specification is Protocol v1 and the annexes its §3 incorporates; where an RFC and the specification differ, the specification wins. All RFCs, with their status, are listed in the ADR and RFC index.
Field |
Value |
|---|---|
RFC |
0036 |
Title |
Two rights, and |
Status |
accepted (2026-10-10, maintainer approval; proposed the same day). The maintainer set the direction on 2026-10-10 and ruled “all rec” on every §12 question the same day. The rulings are folded into the text, and §12 records them. Comment window waived by the sole maintainer. |
Author(s) |
AvatarSD (maintainer), with AI drafting |
Created |
2026-10-10 |
Comment window |
Waived by the maintainer, as is the default while the project is solo-maintained (GOVERNANCE.md §”Errata, amendments, and the comment window”). Invoke it explicitly if outside input is wanted. |
Instrument |
Amendment. It changes the |
Tracking issue |
#2088. It blocks #2019 (wire spelling for a suspended subscriber slot), whose RFC-0035 has an open question that this RFC answers (§8). |
Target spec version |
v1 itself. |
Amends |
reference/05 § |
Relates to |
RFC-0034, the subscription selector’s fields; RFC-0035, |
Numbering note. RFC-0035 is in an open pull request and is not yet on
main, so this record leaves a one-number gap onmainuntil it merges. The gap is not a skipped number. Numbering gaps and why they are not reused are recorded in the ADR and RFC index.
1. Summary¶
The access_mask keeps two rights, READ (0x01) and WRITE (0x02). The other six
rights are retired: SUBSCRIBE, CREATE, DELETE, READ_ACL, WRITE_ACL and WRITE_OWNER. Each : field
is a path under its vertex and is governed by the vertex’s own ACL:
Reading any
:field needs READ on the vertex.:identitykeeps its exemption (RFC-0011).Writing a
:field needs WRITE on the vertex. Two fields have their own rule:v:aclis written by the owner only. The owner is the empty local caller. No ACE can grant a remote peer write on:acl.v:subscribers[]: a caller may create, toggle or clear an entry that it holds itself if it has READ onv. Only the owner can change an entry held by someone else.
v:children[]creation, write-creates and a creator endpoint’sSPECare all WRITE.
An ACE that carries any bit other than READ or WRITE is refused when it is installed, with
tr::schema::type_mismatch. It is not silently ignored.
Remote administration goes through a vertex the owner’s application registers. Its handler applies ACEs locally, under the owner’s channel. The protocol keeps no second kind of authority.
2. Motivation¶
2.1 Eight rights, six gates, and a : plane that is not uniform¶
ADR-0020 adopted the NFSv4 mask so that creation and administration could be enforced. In the reference core today:
Right |
Bit |
What gates on it today |
|---|---|---|
READ |
|
data reads, |
WRITE |
|
data writes, fan-in at a delivery target, |
SUBSCRIBE |
|
the |
CREATE |
|
|
DELETE |
|
nothing. RFC-0009 §A.2 records it as reserved and unused |
READ_ACL |
|
a whole |
WRITE_ACL |
|
an |
WRITE_OWNER |
|
nothing. There is no owner identity to transfer |
So the : plane, the vertex’s ioctl (CONTEXT.md §Field-write), has a separate right for nearly
every field. A deployer who wants “peer P may use this vertex” has to know that this means READ,
WRITE and SUBSCRIBE, that :acl reads need a fourth right, and that creation needs a fifth. Two
bits gate nothing, and a mask that carries them is accepted and stored anyway.
2.2 Administration held remotely is a root right¶
A peer that holds WRITE_ACL can rewrite the ACL, so it can grant itself every other right. Under the default ALLOW-only profile, an inherited WRITE_ACL cannot be taken back at a descendant either. WRITE_ACL is therefore not one right among eight. It is the whole authority, held by a remote subject that the node can identify only by the opaque token its resolver returns. ADR-0086 places identity at the application level and keeps permissions in the receiver’s ACL. It leaves delegation chains out of scope. A remote admin bit is a one-step delegation chain, and the protocol is the wrong place to hold it.
2.3 Owner semantics have been open since #1033¶
ADR-0020’s 2026-08-10 erratum withdrew OWNER@ and recorded that “real owner semantics remain
open, and are an amendment”. The core has had an owner since #905 all the same: the empty
local caller, which graph_t::acl_allows (core/src/graph.cpp:graph_t::acl_allows) admits
before any resolver runs, and which no remote operation can spell. write_ctx_t::is_local_owner
(core/include/libtracer/vertex.hpp:write_ctx_t) is how a door asks for it, and that header
explains why there is no OWNER@ sentinel. This RFC makes that owner the normative one.
2.4 RFC-0035 needs a rule for other parties’ subscriptions¶
RFC-0035 §12 Q2 asks who may suspend or clear a subscription the caller did not make. It weighs
WRITE_ACL against a new MANAGE_SUBSCRIBERS bit. Both answers add authority to the ACL. This RFC
answers by taking authority out of it (§8).
3. The rule¶
Normative. “MUST” and “MUST NOT” are used as in RFC 2119.
3.1 Two rights¶
The access_mask registry of reference/05 §0x0A becomes:
READ=0x01 WRITE=0x02 (0x04 and above reserved)
The canonical wire width stays u32 (RFC-0026), and so
does the acceptance rule for narrower spellings. The six retired bits, 0x04 to 0x80, go back to
the reserved range together with 0x100 and above.
Install-time refusal. An :acl write MUST be refused with tr::schema::type_mismatch
(0x0030) when any ACE’s access_mask has a bit set outside 0x03. The whole write is refused and
nothing is stored. The rule covers every bit outside 0x03: the six retired bits and 0x100 and above alike, so a right defined later can never be stored and left inert by an older core (§12 Q3). This is the same family of refusal as a DENY ACE under the ALLOW-only profile,
or a flag bit beyond INHERIT: an ACE whose meaning the evaluator would silently weaken is not
stored (#906). A mask of 0 stays legal. It grants nothing, as it does today.
3.2 A : field is a path under its vertex¶
A : field has no ACL of its own. It is evaluated against the effective ACL of the vertex it is
under: the vertex’s own ACEs plus the INHERIT ACEs of its ancestors (ADR-0020, unchanged). In
this sense the fields form the vertex’s ACL subtree.
Operation |
Right demanded at |
|---|---|
read of any |
READ |
write of |
WRITE (unchanged; RFC-0010 §A.3’s declaration gate still follows it) |
|
WRITE |
write of |
the owner only (§3.3) |
|
READ (§3.4) |
write of |
the holder with READ, or the owner (§3.4) |
v:schema, v:stats and v:identity have no write surface, and that does not change.
:identity stays readable above the READ gate (RFC-0011; reference/05 §0x0B). A shape that names
nothing is still resolved before the gate wherever it is today, so the #869 divergences pinned by
field_shape_matrix are not changed by this RFC. One of them goes away, though: a :acl read no
longer has two rights to choose between, so its shape test no longer selects which right is
demanded (§4, row 3).
3.3 v:acl is written by the owner only¶
The owner is the node’s local host: an operation whose caller context is empty. The empty context is the trusted channel that ACL evaluation short-circuits (#905). A remote operation always carries a non-empty context (the inbound link’s name), so it cannot be the owner. Every network write path must carry its caller for this to hold, which #974 enforced.
A write of
v:aclfrom any caller that is not the owner MUST be refused withtr::access::denied(0x0050). No ACE can grant it, so no ACE is evaluated.This rule, and the owner-only rule of §3.4, hold on every node, with or without ACL enforcement. They are identity tests and need no resolver. On a node with no resolver, reads and writes stay open by default, but a remote peer still cannot plant ACEs that would take effect once a resolver is installed later (§12 Q2).
A read of
v:aclneeds READ onv, like every other field. A peer that can read the vertex can read who else may read it. A node that wants to hide its policy withholds READ.There is no owner subject token.
OWNER@stays what ADR-0020’s erratum made it: an ordinary opaque token with no meaning to the core.
Ownership is node-scoped. There is one owner per node and no per-vertex owner identity, so there is nothing to transfer and WRITE_OWNER has nothing to gate.
Remote administration. A deployment that wants a remote peer to manage ACLs registers an
owner-app vertex. That is an ordinary vertex whose write handler, or whose settings.app field
admission (RFC-0010 §A.3), receives the request together with the writer’s subject
(write_ctx_t::subject), decides by the application’s own policy whether to honor it, and then
writes the target’s :acl through the host API as the owner. The peer needs WRITE on the owner-app
vertex and nothing else. The policy for who may grant what to whom, how far delegation goes, and
what gets audited lives in the application, where ADR-0086 puts identity. The protocol defines no
admin vertex type. Reference/13 gains an informative recipe for one when the documentation for
this RFC lands (§12 Q7, §13).
3.4 v:subscribers[]: your own entry needs READ, everyone else’s is the owner’s¶
An entry’s holder is the subject that its admission was made under. That is the writer of the
append (RFC-0021 §E, “the gate is the writer’s”), and not necessarily the target. A replace makes
the replacer the holder. An edge admitted by a local host call is held by the owner. The core
already stores the context per edge (subscriber_remote_t::caller,
core/include/libtracer/subscriber.hpp:subscriber_remote_t), so the holder costs no new state.
The holder test. This RFC is the normative home of the rule. RFC-0035 §7.1 cites it. A caller
is an entry’s holder if and only if all of the following hold:
The entry’s stored context is non-empty. An entry with an empty stored context was made by the owner, and it is held by the owner alone. It MUST NOT match any remote caller, whatever the resolver maps either side to. The subject lookup MUST NOT be run on an empty context, just as ACL evaluation never runs it there (#905).
The caller’s context is non-empty. The owner needs no holder test, because it passes as the owner.
With a resolver installed, both contexts resolve, and to the same token. The lookup runs at check time on both stored contexts. If the lookup fails on either side, or either side returns a reserved token such as
EVERYONE@, the caller is not the holder. So an entry whose stored context no longer resolves is owner-only. Two callers that resolve to the same token are the same holder. This is what keeps a peer’s entry its own when it comes back over a new link under the same key (§12 Q1).With no resolver installed, the two stored contexts are equal byte for byte (§12 Q1).
Rule 1 is what keeps READ from turning into authority over the owner’s subscriptions. Take a
resolver that maps every unauthenticated link to one token, guest. If the owner’s empty context
were resolved too, it could come out as guest. A peer holding only READ would then count as the
holder of every edge the owner wired, and could clear, retarget or suspend them. Rule 1 refuses
that, and so does rule 3 for any edge whose creator is gone.
A shared token does make its callers one holder. That is intended: a deployment that maps many links to one token has said they are one principal. They can manage each other’s entries, never the owner’s.
Write |
The caller holds the entry |
The caller does not |
|---|---|---|
|
READ. The new entry is the caller’s own |
— |
|
READ |
owner only |
|
READ |
owner only |
|
READ. The new edge is the caller’s own too |
owner only |
Why READ is enough for the caller’s own entry. A subscription delivers what a read of
vwould return, when it changes. Requiring READ means a caller cannot subscribe to data it may not read. Clearing or toggling one’s own entry gives no power beyond what the caller already had, because it could let the edge lapse by leaving.Why everyone else’s is the owner’s. Clearing, suspending or retargeting another party’s subscription decides what that party receives. No right on
vshould carry that decision. The owner can still do it, and through an owner-app vertex (§3.3) it can let a remote peer do it under the application’s policy. The RFC-0034 selector is exactly such a vertex (§9).The target’s fan-in gate does not change. A delivery into a local target still needs WRITE on the target under the holder’s context (ADR-0026’s second gate).
Order. The shape checks stay ahead of every gate. A non-owner without READ on
vMUST be refusedtr::access::deniedbefore slotNis looked up. A caller with READ then learns whether slotNexists and whether it holds it. A READ of the list tells it the same, as RFC-0035 §7.2 already argues.
3.5 In-tree access is the owner’s¶
The application’s own writes, and any in-tree module that acts for the application, call the graph
with the empty caller and are allowed natively. Examples are a mux vertex, a bridge, a selector and
the creation hooks. This is today’s rule (#905). This RFC adds one duty, which the reference
already keeps: a module acting for a remote caller MUST pass that caller’s context, never the
empty one. The router does this at every terminus (fwd_router_t::deliver_local takes its caller
as a required parameter). A module that wants the owner’s authority on purpose is an owner-app
vertex, and its policy is the gate (§3.3).
3.6 Remotely created children¶
A child created by a remote peer, through a creator endpoint’s SPEC, a :children[] append or a
creation hook, is owned by the node like every other vertex. Its creator gets nothing for
having created it.
It starts with no ACEs of its own. Its effective ACL is the INHERIT ACEs of its ancestors,
and if there are none it is open by default, which is today’s rule for any vertex. The core stamps
no creator ACE (§12 Q5, ruled). A creation hook or a transport factory runs locally with the creator’s
caller context in hand. If the application wants its creators to manage what they create, it can
install such an ACE as the owner. That is policy, and it costs the core nothing.
For a creator endpoint, the deployer therefore decides what a new connection admits through the
inheritable ACEs on /net and /net/<module>, as it does today.
4. Every core gate that tests a retired right, and what it becomes¶
Found with git grep on origin/main (f3001401) for acl_right_t::SUBSCRIBE, CREATE,
DELETE, READ_ACL, WRITE_ACL and WRITE_OWNER, and for the ALLOW-only profile
(allow_only_policy_t, kAcceptsDeny). Outside core/src the hits are tests and examples
(core/tests/acl_test.cpp, mount_hop_acl_test.cpp, payload_right_table_test.cpp,
read_back_backend_test.cpp, security_acl_test.cpp, core/examples/acl_right_bits.cpp), the
CHANGELOG, and one ESP-IDF README sentence. Four more places change with row 7 or row 5 without
naming a retired right:
core/tests/subtree_test.cpp: its WRITE-without-CREATE case expects creation to be denied, and that expectation inverts.core/tests/graph_seam_refusal_test.cpp: it usespayload_right_t.core/tests/CMakeLists.txt: it listspayload_right_table_test.tools/no_heap_baseline.json: it lists the deleted functions’ symbols.
# |
Site |
Today |
After |
|---|---|---|---|
1 |
|
SUBSCRIBE on the producer |
READ on the producer |
2 |
|
WRITE on |
the owner passes; otherwise the caller must hold slot |
3 |
|
READ_ACL for a whole read, READ for other shapes |
READ for every shape. The ternary goes |
4 |
|
WRITE_ACL |
|
5 |
|
CREATE on |
WRITE on |
6 |
|
CREATE on the hooked parent |
WRITE on the hooked parent |
7 |
|
a vertex may map the written TLV’s type to a right: |
WRITE for every value write. |
8 |
|
stores any u32 mask |
refuses a mask with a bit outside |
— |
DELETE, WRITE_OWNER |
declared, gate nothing |
the enumerators are removed |
— |
the mount-hop gate, |
READ or WRITE by the frame’s op |
unchanged. A subscribe through a mount is a |
— |
|
the owner, or WRITE |
unchanged. It already has the shape this RFC gives every field |
The ALLOW-only MCU profile (allow_only_policy_t, the default_config_t::acl_policy_t
binding) and the full policy (full_acl_policy_t) evaluate one bit through the merged list, and
that does not change. What changes is that the two rules of §3.3 and §3.4 that no ACE can express
are identity tests, not ACE evaluations. Both profiles enforce them identically, and the
ALLOW-only limit (an inherited right cannot be taken back at a descendant) no longer reaches
administration, because there is no administration right left to inherit.
5. Perf, RAM, code size and branches across NARROW, MID and WIDE¶
Effect |
|
|---|---|
RAM per vertex, per edge, per ACE |
none. The holder is the context each edge already stores, and the mask stays u32 in memory. Narrowing |
RAM per creator endpoint |
one table-source draw fewer (the two |
value-write hot path ( |
one relaxed flag-bit test fewer on every value write, on every class. That test is the |
fan-out and delivery |
unchanged |
subscribe admission |
unchanged: one |
|
cheaper: an empty-context test instead of an ACE walk and a resolver call |
|
one holder comparison on a cold control-plane path: up to two subject lookups with a resolver installed, a byte compare without one (§12 Q1) |
code size |
an indicative figure from a RelWithDebInfo x86-64 build of a 2026-10-09 branch ( |
gate decision points |
counted by reading |
NARROW (MCU, ALLOW-only): no RAM, a smaller image, and one test fewer on every write. The two owner rules need no resolver and no ACE storage.
MID: the same, and a deployer has two rights to reason about instead of eight.
WIDE (host, thousands of edges): a non-owner
[N]write costs at most two subject lookups. It is O(1) in the edge count and runs off the delivery path. Nothing on the fan-out path changes.
6. Compatibility¶
This is a breaking change before 1.0. No backward compatibility is owed (the RFC-0028 ruling, restated as RFC-0029 ruling 10). But every deployed ACL has to be looked at, so it gets the upgrade note in §7.
An
:aclwrite carrying a retired bit is refused where it used to be stored. An application that installs its ACLs at boot with SUBSCRIBE, CREATE, READ_ACL or WRITE_ACL set getstype_mismatch, and nothing is installed, so the vertex stays as open or as closed as it was. The refusal is loud on purpose: an ACE that silently lost bits would change what it grants.A remote
:aclwrite is refused even for a peer that held WRITE_ACL, and also on a node with no resolver installed, where it used to succeed (§3.3).A peer with READ can now subscribe where it used to need SUBSCRIBE too. The cost of the edges it can hold is bounded by the node’s
retainedseam (reference/09), which is sized against the subscription population, and its exhaustion refuses the admission asbackpressure. A READ-only “poll but never subscribe” grant is no longer expressible. That was accepted (§12 Q6). An owner-declared “no remote subscribers” flag is added only if a deployment asks for one.A peer with WRITE can no longer clear or replace another party’s slot. A holder with READ can now clear its own slot without WRITE.
Creating through a creator endpoint needs WRITE on the endpoint, and WRITE now also removes. Create-only and remove-only are no longer separately grantable through the ACL. A deployment that needs create-only refuses
NAMEfrom non-owners in the endpoint’s admission filter, which sees the payload type and the writer’s subject (§12 Q4).Vectors (
tests/conformance/vectors/v1/acl/):acl/acl-acesis unchanged. Its masks are0x03and0x01.acl/ace-duplicate-keyis repaired in place. The secondaccess_maskvalue changes fromu16=0xFFFFtou16=0x0003(READ|WRITE). The vector stays 84 bytes, and only its last two bytes change,FF FFto03 00. It still pins the same refusal: the repeated key is rejected, and last-wins would have widened READ to READ|WRITE. Without the repair, the vector would be refused for two reasons, and it would no longer isolate the one it is about. The new bytes are0A4050000A404C0002000400747970650100010000020007007375626A65637401000600706565722D6102000B006163636573735F6D61736B01000200010002000B006163636573735F6D61736B010002000300.acl/ace-retired-bitis new. It is one ALLOW ACE forpeer-awhoseaccess_maskisu32=0x00000004(the old SUBSCRIBE), and every reader must reject it. It is 65 bytes:0A403D000A40390002000400747970650100010000020007007375626A65637401000600706565722D6102000B006163636573735F6D61736B0100040004000000. The codec round-trips it. The refusal belongs to the reader and is bound per core (tests/conformance/HARNESS.md), as forace-duplicate-key.conn/remove-via-namekeeps its bytes. Its description stops saying “notDELETE(0x10)”, because there is no DELETE right left to contrast with.
C++ API.
acl_right_tkeepsREADandWRITEonly.payload_right_tand everyrightsparameter of the registration calls are removed. This goes incore/CHANGELOG.md.Rust binding.
structured::acl’s reader refuses a mask outside0x03, and theAcedoc comment lists two rights. This goes inbindings/rust/CHANGELOG.md.
7. Upgrading¶
Informative. This text goes into reference/05 §0x0A and the core CHANGELOG on acceptance.
Rewrite each ACE the application installs:
Old right |
New |
|---|---|
SUBSCRIBE |
READ. Subscribing needs READ on the producer |
CREATE |
WRITE on the parent, or on the creator endpoint |
READ_ACL |
READ |
WRITE_ACL |
drop it. Administer through an owner-app vertex (§3.3) |
DELETE, WRITE_OWNER |
drop them. Neither gated anything |
Then check three things:
Who clears subscriptions. A remote tool that cleared or retargeted other parties’ subscriptions with WRITE is now refused. Move that function into an owner-app vertex, or into a selector (RFC-0034).
The 0.18.1 connection-vertex layout becomes: an inheritable READ on
/net; READ and WRITE on each connection vertex, written once the connection exists; and no WRITE on/net/<module>/conn, which is what stops a remote peer from opening connections. As before, under the ALLOW-only profile WRITE stays off/netand/net/<module>.mount_hop_acl_testpins the layout.Remote formation. An orchestrator (reference/13) used to be granted WRITE_ACL. It now needs WRITE where it creates and binds, READ where it subscribes, and an owner-app vertex for any ACL it has to set.
8. How RFC-0035 changes¶
RFC-0035 (PR #2086) is revised to cite this RFC, and it lands after it (#2088 blocks #2019):
§7.1 renames the edge’s “owner” to holder, the term of §3.4, so that “owner” means only the node’s owner. Its comparison rule is replaced by a citation of §3.4’s holder test, which is normative here. “Only the local caller and
WRITE_ACLholders can manage it” becomes “only the owner can manage it”.§7.2’s table becomes §3.4’s table. The create and the caller’s own
[N]clear, toggle and replace need READ, where RFC-0035 proposed SUBSCRIBE. Another party’s slot is owner-only, where RFC-0035 proposed WRITE_ACL. The sentence “a node built without ACL enforcement admits every caller to every operation, ownership included” is replaced: per §12 Q2, other parties’ slots stay owner-only on such a node too, and the holder comparison falls back to bytes (§12 Q1).§7.2’s order paragraph reads “a non-owner without READ” in place of “a caller that holds neither SUBSCRIBE nor WRITE_ACL”. The disclosure argument is unchanged.
§7.3: the holder keeps READ, where RFC-0035 says SUBSCRIBE, so it can resume or re-append. A node that wants a party to stop receiving revokes its READ and clears the edge.
§12 Q2 is answered: neither WRITE_ACL nor a
MANAGE_SUBSCRIBERSbit. Other parties’ slots are the owner’s. Remote management goes through an owner-app vertex, and the selector is the first one.§15’s breaking gate reads “a peer that is not the owner” in place of “holding WRITE but not WRITE_ACL”, and “a holder with READ gains the clear” in place of “an owner with SUBSCRIBE and no WRITE”.
§8 (the selector) is unchanged, and this RFC relies on it.
9. How RFC-0034 fits¶
The RFC-0034 selector writes
as the owner. A switch suspends and resumes refs on their producers through the host API under
the empty local caller, so it can toggle any entry it lists, whoever holds it. A remote controller
needs WRITE on the selector’s own vertex, where settings.app.active is an RFC-0010 field, and no
right at all on the producers.
That makes the selector the reference instance of §3.3’s owner-app vertex. The ACL on the selector’s vertex is the delegation: granting a peer WRITE there lets it switch exactly the refs the owner listed, and nothing else. RFC-0034 §2.3, under which the selector owns the suspend bit of every subscription it lists and a peer’s subscribe is admitted delivering, stands unchanged.
10. What this overrides, explicitly¶
ADR-0020: “
adminis preciselyWRITE_ACL” and its eight-bit mask. The ADR’s other decisions stand: NFSv4-style ACEs, ALLOW and DENY,INHERIT, the MCU subset andEVERYONE@. Its status key carriessuperseded-in-part-by: RFC-0036. The owner-semantics item its #1033 erratum left open is closed without a reserved token, by §3.3.RFC-0014 §5 and Amendment 2: the create-but-not-remove split and the payload-right table. “The create right is delegable on the endpoint’s own ACL without any right on the parent transport” stays true, with WRITE in the place of CREATE.
RFC-0009 §D.1 and RFC-0005 §D: WRITE for any
[N]write, and CREATE for write-creates.ADR-0026: the SUBSCRIBE fan-out gate becomes READ. The two-ACL shape (the producer gates subscribe, the target gates fan-in) is unchanged.
CONTEXT.md: the entries for Write-creates, Network formation, ACL entry and SUBSCRIBER direction.
11. Alternatives considered¶
An ADMIN right (one bit for
:acland for other parties’ subscriptions). Rejected. A remote holder of a bit that can rewrite the ACL holds every right, so the bit is the whole authority under a narrower name. It is also a delegation chain, which ADR-0086 keeps out of the protocol, and it is impersonable by whatever a pass-through resolver returns. Under the ALLOW-only profile an inherited ADMIN cannot be taken back at a descendant. And it is a third right, which the ruling excludes. An owner-app vertex expresses everything ADMIN would, plus the policy ADMIN cannot express.Keeping WRITE_ACL. Rejected for the same reasons. It would also leave
:aclas the one:field not governed by its vertex’s READ and WRITE, which is the non-uniformity this RFC exists to remove. Keeping READ_ACL beside it would keep a second read right for one field, to hide a policy from peers that may already read everything the policy protects.An
OWNER@sentinel. Rejected. ADR-0020’s erratum (#1033) withdrew it for two reasons. AnOWNER@ACE matched nobody and locked the vertex it was meant to delegate. And the name is impersonable through a resolver that passes caller-supplied identity through. Bringing it back would mean reserving it as #908 reservedEVERYONE@, storing a per-vertex owner identity that the graph does not hold (RAM on every vertex), and changing how existing stored ACEs evaluate. The empty local caller is a stronger owner than any token: no ACE can spell it, and no remote operation can carry it.Keeping SUBSCRIBE separate from READ. Rejected by the ruling. A subscription discloses nothing a read does not. Its cost to the producer is bounded by the
retainedseam, not by a right. Keeping the bit would keep the poll-only grant at the price of a third right on every core.Keeping CREATE separate from WRITE. Rejected. Creating is a write to the parent or to the endpoint. The one thing CREATE bought, create-without-remove on a creator endpoint, can be had from the endpoint’s admission filter, which sees the payload type and the writer’s subject (§12 Q4).
Silently masking retired bits on install. Rejected. An ACE that loses bits without a word grants something other than what its writer wrote, which is the failure #906 removed from the parser.
12. Rulings¶
The maintainer ruled “all rec” on 2026-10-10: each question took its recommended option. The text above already states each ruling, and this section records the question and its answer.
How two holders are compared. Ruled: through the subject lookup at check time, falling back to a byte compare of the stored contexts when no resolver is installed (§3.4). Rejected: a byte compare always, which would make a peer that reconnects lose its own edge.
Whether the owner-only rules hold without enforcement. Ruled: always (§3.3). They cost one branch and need no resolver. Rejected: only once a resolver is installed.
What install refuses. Ruled: every bit outside
0x03, withtype_mismatch(§3.1). Rejected: refusing only the six retired bits, and a new error identity.Create versus remove on a creator endpoint. Ruled: WRITE allows both, and the endpoint’s admission filter can refuse removals from non-owners (§6). The payload-right table is deleted. Rejected: a table holding only READ and WRITE, and splitting the endpoint into two vertices.
The initial ACL of a remotely created child. Ruled: inheritance only. The core stamps nothing, and a hook may install a creator ACE as the owner (§3.6). Rejected: a core-stamped creator ACE on every created child.
The lost poll-only grant. Ruled: accepted. Edge cost stays bounded by the
retainedseam (§6). An owner-declared “no remote subscribers” flag is filed only if a deployment asks.A standard admin vertex type. Ruled: none now. Reference/13 gains an informative recipe for an owner-app vertex that applies ACEs (§3.3). It lands with the documentation slice of §13, together with the rest of reference/13’s formation text, which still describes WRITE_ACL until the core change ships. A typed admin vertex gets its own RFC once a second management tool needs one.
13. Implementation (follows acceptance)¶
Core. Apply the gates of §4, the install refusal of §3.1, the deletion of the payload-right table, and the holder test. Rewrite
acl_right_bitsand the affected tests. Add a CHANGELOG entry with the upgrade table. Build the CI matrix, includingacl_fullON and OFF, and record the ESP32 size delta. Add a host test for each holder-test rule of §3.4, run under a resolver that maps every link to one token. Rule 1: an owner-wired edge refuses a READ-only remote clear, replace and toggle. Rule 3: an edge whose stored context no longer resolves is owner-only. The same token is the same holder. This is a behavioural clause with no new bytes, so the conformance instrument is a bound host test (tests/conformance/HARNESS.md) rather than a codec vector.Vectors and Rust. Repair
acl/ace-duplicate-keyand addacl/ace-retired-bit, both bound bycore/tests/acl_test.cppandbindings/rust/tests/conformance_vectors.rs. Change the Rust reader and its CHANGELOG.Docs. Reference/05 §
0x0A(the registry, the enforcement paragraph and the upgrade note), reference/02 (write-creates and the[N]table), reference/13 (formation, and the admin-vertex recipe of §12 Q7), reference/18 and reference/19 (the CREATE gate), CONTEXT.md, a forward note on RFC-0014 Amendment 2, and the ESP-IDF README sentence.RFC-0035 is revised per §8, and its §7.1 cites §3.4 as the holder rule.
Discussion¶
The maintainer’s direction of 2026-10-10 is “two rights, and : fields are paths under their
vertex”. This document turned it into normative text and mapped it onto every gate in the
reference core. The maintainer accepted it on 2026-10-10 and ruled “all rec” on the seven
questions in §12.