One table, two instances
Partly superseded by authority-is-belnap-evidence:
the precedence and binary-verdict parts (rank, Verdict, meet_insert,
Carrier as a ranked scope, respelled as the deepest fold, widening by
evicts) are replaced by evidence and exclusion-wins. The shared table, object
identity and the dynamic stack stand.
The fs and exec dimensions of a grant are one structure, Table<K: Scope>,
whose meet is defined by the pointwise law ⟦a ∧ b⟧ = ⟦a⟧ ∧ ⟦b⟧; the fs
region and the exec rules are its two instances. The mathematics, the
instances side by side and every platform corner case are
authority-tables. Supersedes the mechanics of
exec-rules and of
denies-hold-under-every-spelling,
whose laws stand; supersedes the Namespace tag that rode along with
guest-namespace-prefixes, whose
fold stands.
Context
The two dimensions answered the same question — which rule speaks to this subject, and which of the speakers decides — with two unrelated mechanisms.
- fs was a pair of prefix sets,
PrefixSet<Allow>andPrefixSet<Deny>, withmeetfor allows,unionfor denies,outsidedropping an allow a deny covered,coveringfinding a deny’s match, andmeet_prefixesthe atom the kernel and the deputy fold shared.allow_regionanddeny_regionfolded each across a stack. - exec was a compiled table of four maps (files, tools, dirs, vetoes)
with a hand-written
met,dirs_over,dir_verdictandkernel_verdict; the authoredExecGrantwas itself three maps,names,pathsanddirs, the last keyed to abool. - A namespace tag. Every
FrozenPathcarried aNamespace,HostorGuest, and fs containment keyed on(namespace, resolved), so a guest prefix never overlapped a host one.
Once denies came to hold under every spelling, exec’s meet needed a page-long
proof that writing no default as a deny preserved
verdict(a ∧ b, p) = verdict(a, p) ∧ verdict(b, p), argued case by case over
dirs, files, tools and vetoes; fs held the same law by an argument nobody had
written down, through different code. Two proofs of one law over two
representations is one too many.
The tag was a bug. It named the machine relative to whoever minted the prefix
— Guest from the host’s side — but was compared as though absolute. Inside
synod’s guest, synod’s own prefixes arrived tagged Guest and a nested
grant minted its prefixes there tagged Host, so every nested grant met
synod’s base to nothing. The fix that had mattered was the fold:
from_guest’s POSIX fold_dots_posix.
Decision
- One table.
Table<K: Scope>is aBTreeMap<K, Verdict>entered throughmeet_insert. AScopesupplies a rank,holds::<P: Polarity>, andown().verdict,live,denies,respelledand the meet are written once, generically; nothing intable.rsnames anIdentity. - The generic meet law. Denies join; an allow scope survives at
⟦a⟧(k̂) ∧ ⟦b⟧(k̂)where that is not Deny; a key met to Deny that no layer wrote is left absent. The pointwise law is the meet’s definition, and the lattice laws are property-tested once, over a universe of case and NFC/NFD pairs. - fs is
Region = Table<FrozenPath>, subject a plain&Path, rank(is a deny, depth), one region perFsOpcarrying the layer’sdeny_paths, re-frozen against the caller’sResolver(refreeze), folded byregion. The projection renderslive()anddenies()as surface strings; the deputy folds aRegionof allows per dimension and readslive(). - exec is
ExecRules = Table<ExecScope>,ExecScope { Dir, Carrier, File, Tool, Name }overSubject { Tool, File, Under }, rankDir(depth) < Carrier < Exact < Name, folded byrules. own()replaces scope-against-scope containment. The meet asks a rule’s survival at the rule’s own subject, so the only relation a scope must define isholds, between a scope and a subject.Carrieris a scope.kernel()adds the carriers to a copy of the table as(Carrier(c), Allow)and asksverdictfor each file once, so an authored exact deny or a veto beats a carrier by rank alone, where a hand-writtenkernel_verdictdecided it before. A carrier holds a file by mutual containment, as aFilerule does, where it was set membership, so firmlink spellings agree.- The authored grant is one map too.
ExecGrant(BTreeMap<ExecKey, Verdict>),ExecKey { Name, Path, Dir }, the written twin ofExecScope;Displayspells a key as a grant does, a dir with its trailing/, which is what exarch’s prompt prints. Exec now has three languages, one map each:ExecKeyas written,ExecScopecompiled against this host,ExecRuleas the kernel reads it. - No namespace.
Namespaceis deleted; a prefix issurfaceandresolved, and the guest’s spelling is the door’s business (from_guest).
Rejected
- A three-state dir key (allowed, authored deny, unset). “Unset” is a key that must never speak and never render, which is what absence already is, and it needs a third arm in every reader. The meet manufactures the default, so the meet stops it.
- A trait with scope-vs-scope containment for the meet. It duplicates
holdswith a second relation that must agree with it under both polarities;own()reduces the one to the other. - One fs table with op-tagged scopes. A deny binds both ops, so it would be written twice or need an op-polymorphic scope, and read and write are never asked together. Two regions, one per op, are each a plain table, zipped where the projection needs both.
- A
Place { namespace: Option<Namespace>, path }subject that kept the tag, the guard asking with none and the meet with one. It keeps the bug’s carrier alive and makes the subject richer than the question.
Consequences
- One meet, one law, one set of property tests:
the_meet_of_two_tables_judges_as_the_meet_of_their_verdicts,the_meet_is_idempotent_commutative_and_associative,the_kernel_rules_judge_as_the_table_and_its_carriers. respelledis one function for fs and exec, naming the most specific folded deny (the deepest for fs, a file deny over a dir deny for exec), so the two refusals say the same sentence by construction.- A nested
grantinside synod’s guest narrows synod’s base as it does anywhere else. - Widening is untouched:
--extend-basestill unions authored layers (ExecGrant::widen,FsPolicy::widen), withevictsthe sweep, since widening is a single-layer operation on what an author wrote, not a meet of tables. - The fs projection no longer re-mints a frozen surface through the host’s
fold: a no-op on a host prefix, and on a Windows host the one step that
would have turned a guest’s
/workback into\work. A guest prefix is now re-frozen only byrefreezein the guard that matches it, inside the machine.
Where
core/src/capability/table.rs (Table, Scope, verdict, live,
denies, respelled, Meet for Table), core/src/capability/fs.rs
(Region, region, impl Scope for FrozenPath),
core/src/capability/exec.rs (ExecScope, Subject, Rank, ExecRules,
compile, for_kernel, precedence, allowed_files, rules),
core/src/sandbox/projection.rs (surface), core/src/capability/deputy.rs
(deputy_prefixes), core/src/path/forms.rs (contains, refreeze,
from_guest), core/src/path/lex.rs (Polarity, Allow, Deny),
core/src/capability/lattice.rs (ExecKey, ExecGrant, meet_insert).