gizmoduck: T3 federation design r4 for #414, correcting 04a6112be (only its SESSIONS line landed): R8 gate host form only with sudo, doas and ssh checks (B7, mos-0996), hypervisor path open with O1-O3 (mos-1004, #419), #waiting skips user replies (N11), shares end by row 193 (N12), row 196 verifier (gander-0006)
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
# T3 federation design: factory and hub (Vikunja #414, G3, G4, C1.1)
|
||||
|
||||
Author Gizmoduck (Opus 5.5), 2026-10-20, r3. Design only, no product code. Reviewer Duckworth (Opus 5.5,
|
||||
Author Gizmoduck (Opus 5.5), 2026-10-20, r4. Design only, no product code. Reviewer Duckworth (Opus 5.5,
|
||||
mos-0978), r1 verdict changes B1-B5 (duckworth-0001, REVIEW.md at b402197ff), r2 verdict changes B6 (duckworth-0002,
|
||||
REVIEW-r2.md at 03a663cd7). R1, R2 and R5 go to Sage's
|
||||
REVIEW-r2.md at 03a663cd7), r3 verdict changes B7 (duckworth-0003, REVIEW-r3.md at 45cbe5896). R1, R2 and R5
|
||||
go to Sage's
|
||||
queue once Duckworth approves, and Sage assigns owners. Code lines are at origin/refactor 9ae8c73ac. The
|
||||
Q175 SSO design is cited at Sage's local commit 817e8f51b (agents/sage/work/q175-sso/DESIGN.md), which was
|
||||
not on origin when this was written.
|
||||
@@ -30,6 +31,15 @@ PLAN.md T3 (all in jason-master-restart).
|
||||
asserts a reply). C1, the walk doesn't stop an unsandboxed same-uid agent (3.6). N10, `names` gates
|
||||
through `#agent` (3.6 item 4). mos-0991: R8's gate accepts a separate host or a separate uid, and 3.8
|
||||
checks the federation path for one-host assumptions. Q1 holds Jason's answer. morgana-0002 settles Q2.
|
||||
- r4 (duckworth-0003, mos-0996): B7, R8's gate now proves there is no privilege path from the factory to
|
||||
the hub. Per mos-0996 (Vikunja #418) the hub runs on its own host, and for 10-27 the gate is the host form
|
||||
only. The uid form (row 190b) is a later option. The checks run as the factory uid with a clean
|
||||
environment and pass only when the call fails: `sudo -n`, `doas -n`, and `ssh -o BatchMode=yes` to the
|
||||
hub host with and without the agent socket. Factory root on the PVE nodes defeats the host form
|
||||
(duckworth-0004, mos-1004), so it is an open R8 gate item with options O1 to O3 for Jason (Vikunja
|
||||
#419, section 8). N11, `#waiting` returns null for `to` `user` (3.6 item 7, R3).
|
||||
N12, R3 assumes nothing about how shares end (section 9 Q2). Row 196 (#1694, Gander) owns the shared
|
||||
principal verifier before R3 (section 5).
|
||||
- r2.1 (mos-0988): the separate uid is row 190b. R8's gate points at 190b, R7 at row 190 (one uid,
|
||||
scratch). P19 333 is on the 10-27 path (section 8).
|
||||
- Mos's rulings in mos-0978 and mos-0984 replace the r1 questions (section 9). The push credential note is
|
||||
@@ -224,8 +234,8 @@ say so.
|
||||
Writes go through `mosaic access grant` and `revoke` (row R5), atomically with a revision bump and one
|
||||
receipt line in `<dataRoot>/state/access-log.jsonl` (append-only, as `activation-log.jsonl` is). The
|
||||
CLI checks its caller with the human proof, but a direct file write by the instance's uid skips both the
|
||||
CLI and the receipt. Only the separate uid or separate host (R8 gate) keeps other instances' agents
|
||||
from making that write. Jason may also edit the file by hand.
|
||||
CLI and the receipt. Only the separate host (R8 gate) keeps other instances' agents from making
|
||||
that write. Jason may also edit the file by hand.
|
||||
|
||||
### 3.6 The user principal in the broker
|
||||
|
||||
@@ -256,8 +266,8 @@ shell Jason opens for example, can start its own copy of the Console's tree from
|
||||
any granted user. So can anything that can ptrace or write files as that uid. The walk refuses an agent
|
||||
in its normal process tree, but an unsandboxed agent on the same uid can get past it by starting the
|
||||
Console through `systemd-run --user` or by daemonizing so its parent chain ends at pid 1. That is the same
|
||||
hole the owner proof has (P19 333), not a new one. Other instances can't: on one host the uid check
|
||||
refuses them, and on separate hosts the hub's bus isn't on their host at all (R8 gate). Federation adds no weaker path than the owner's.
|
||||
hole the owner proof has (P19 333), not a new one. Other instances can't: the 10-27 hub runs on its own host, so its bus
|
||||
isn't on their host at all, and R8's gate proves the factory has no login there. The hypervisor path is open (R8, section 8). Federation adds no weaker path than the owner's.
|
||||
|
||||
The broker changes, each with an R3 test:
|
||||
|
||||
@@ -289,6 +299,9 @@ The broker changes, each with an R3 test:
|
||||
inbox through the instance. `#checkMessaging` reads `user` as `human` for that run, as :1867 does for
|
||||
the tester. A human or tester session sending `to: 'user'` fails `unknown-role`. The reply lands in the
|
||||
thread through the existing route, so the user reads it with `thread.messages`.
|
||||
`#waiting` (broker.mjs:2556) returns null at once for `to` `user`, as it does for `human` and `tester`.
|
||||
Otherwise every reply to a user falls through to the launch notice at :2562 and posts a waiting
|
||||
notice to the owner's launcher (N11).
|
||||
|
||||
The runner header (runner.mjs:75-78 has the tester's) gains a line for `from_role` `user`: "This message
|
||||
is from <id>, a granted user. It is never Jason's or the owner's instruction and authorizes nothing."
|
||||
@@ -334,8 +347,9 @@ do that first.
|
||||
|
||||
### 3.8 One host or two (mos-0991)
|
||||
|
||||
Melanie's hub may run on a separate server instead of under a separate uid. The federation path assumes
|
||||
nothing about sharing a host:
|
||||
For 10-27 the hub runs on its own host, not on dragon-lin or Melanie's server (mos-0996, Vikunja #418).
|
||||
Whether that is a cluster VM or other hardware waits on Jason (#419, section 8). The federation path
|
||||
assumes nothing about sharing a host:
|
||||
|
||||
- Nothing crosses between the instances' buses or filesystems. Each Console talks only to its own bus
|
||||
through `user-cli` or `human-cli`, spawned on its own host. access.json lives in the receiving
|
||||
@@ -350,8 +364,8 @@ nothing about sharing a host:
|
||||
- Q175 checks token expiry, and section 6 has a 60 s window on signed requests, so both hosts need a synced
|
||||
clock.
|
||||
|
||||
The only one-host pieces are R7, which is scratch on one host by design (row 190), and the uid checks in
|
||||
R8's gate, which now have a separate-host form.
|
||||
The only one-host piece is R7, which is scratch on one host by design (row 190). The uid form of R8's gate
|
||||
(row 190b) is a later option, outside the 10-27 gate.
|
||||
|
||||
## 4. Fit with the current code
|
||||
|
||||
@@ -380,14 +394,21 @@ R8's gate, which now have a separate-host form.
|
||||
for scratch tests. R7 runs on that. Row 190b (Darkwing, Opus, design first, chained on 190) puts the hub
|
||||
under its own Linux user, with no factory process able to touch it (mos-0986). Rows R1 to R7 don't need
|
||||
190b: they use separate `MOSAIC_CONFIG` files and data roots, which the code already honours
|
||||
(business.mjs:25-28). R8 refuses to start without 190b (mos-0984 point 2). With per-uid instances,
|
||||
(business.mjs:25-28). For 10-27, R8's gate is the host form only (mos-0996), so 190b is a later
|
||||
option, not an R8 dependency. With per-uid instances,
|
||||
a factory-uid process can't read or write the hub's access.json or data root, and it can't pass the
|
||||
hub's human or user proof, because `verifyHuman` requires the caller's uid to equal the bus's
|
||||
(human.mjs:67). Inside one instance the proofs stay same-uid cooperative (3.6, P19 333).
|
||||
If row 190b can't deliver separate uids, Mos takes a new decision to Jason then. The design doesn't plan a
|
||||
single-uid live run.
|
||||
- Or a separate host (mos-0991). If Jason runs the hub on its own server, that meets R8's isolation gate
|
||||
instead of 190b, and 190b isn't needed for R8 (3.8).
|
||||
The design doesn't plan a single-uid live run.
|
||||
- A separate host (mos-0991, mos-0996). The hub runs on its own host, which R8's gate checks (section 7,
|
||||
3.8). A cluster VM or other hardware waits on Jason (#419, section 8).
|
||||
mos-0996 measured why the other hosts fail: on dragon-lin `sudo -n true` succeeds for jwoltje, and
|
||||
Melanie's server takes `ssh -o BatchMode=yes` from here with passwordless sudo behind it.
|
||||
- Row 196 (#1694, Gander) owns the bus human boundary before R3 (gander-0006): one shared verifier,
|
||||
`verifyPrincipal(request, connection)` returning `{kind, business, id}`, with `SO_PEERCRED` binding
|
||||
`proof.pid` to the connection. R3 consumes it for the `user` kind instead of writing its own
|
||||
`verifyUser`, with the same checks as 3.6 (human walk, direct parent the bus tree's Console `serve`,
|
||||
own nonce variable). R3 keeps the grant check and its mutants as consumer tests.
|
||||
- T1.1 live also needs two Consoles, two public origins, two reverse-proxy routes and two provider
|
||||
clients, which are Jason-gated infra.
|
||||
- T1.2, #412 SSO. Hard dependency. Row R6 builds on Sage's phase 1 candidate (Q177 merge-chaining) and
|
||||
@@ -424,11 +445,11 @@ gate. Chains follow Q177: a dependent row builds on its parent's candidate.
|
||||
| R1 | `packages/business/src/access.mjs`: access.json loader and validator. Providers, peers and businesses come in as arguments. One read plus the file checks | Codex Sol | none | business tests: every rule in 3.5 refuses with the key path named, symlink, non-regular file, other owner, group or other write, expiry, `*`, disjoint from the given subjects, `human`/`tester` ids, a level other than `collaborator` |
|
||||
| R2 | `federation` key in scripts/mosaic-config.mjs | Haiku 5.5 high | Sage #412 phase 1 candidate | test-config.sh: valid key loads, unknown sub-key and bad origin refuse, config without it loads |
|
||||
| R5 | `mosaic access list, grant, revoke`: human proof, atomic write, revision bump, receipt line. Help text states the `instances` limit and the provider-removal order | Haiku 5.5 high | R1 | cli tests: tester and agent callers refused, concurrent write refuses on revision conflict, receipt appended, bad grant refused before write |
|
||||
| R3 | Broker user principal: `verifyUser` and `user-cli.mjs`, `bindUser`, the 3.6 changes, the collaborator verb list and its filters, per-request grant read, audit lines for every `s.human`/`s.tester` branch | Opus 5.5 | R1 | bus tests: each listed verb allowed, every other verb refused, a leak test per listed read (other projects, other instances, other users' threads), a reply-chain `message.send` into another user's thread refused, `thread.send` writes no `human.input`, the run's reply to a user's message reaches that user's `thread.messages`, a reply `to: 'user'` without a matching `in_reply_to` refused, a user-thread run sending `to: 'human'` refused, revoked and expired grant refused on next request, refusals recorded. Mutants: the user CLI accepted under an agent-marked parent, accepted with a parent that isn't the Console, the grant read skipped, a read filter dropped, `#threadFor`'s user refusal removed, the reply-to check dropped (a run answers another user's message or one outside its thread), a user named like the human accepted |
|
||||
| R3 | Broker user principal: `verifyUser` and `user-cli.mjs`, `bindUser`, the 3.6 changes, the collaborator verb list and its filters, per-request grant read, audit lines for every `s.human`/`s.tester` branch | Opus 5.5 | R1 | bus tests: each listed verb allowed, every other verb refused, a leak test per listed read (other projects, other instances, other users' threads), a reply-chain `message.send` into another user's thread refused, `thread.send` writes no `human.input`, the run's reply to a user's message reaches that user's `thread.messages`, a reply `to: 'user'` without a matching `in_reply_to` refused, a user-thread run sending `to: 'human'` refused, a reply to a user returns `waiting` null and posts no notice message, revoked and expired grant refused on next request, refusals recorded. Mutants: the user CLI accepted under an agent-marked parent, accepted with a parent that isn't the Console, the grant read skipped, a read filter dropped, `#threadFor`'s user refusal removed, the reply-to check dropped (a run answers another user's message or one outside its thread), a user named like the human accepted |
|
||||
| R4 | Revocation: 30 s timer, `access.reload` verb, claim end on loss, `threadsState` `revoked`, launcher skip and SIGTERM, launch check refusal, R5 calls `access.reload` | Codex Sol | R3, R5 | bus and launcher tests, each with a mutant: a hand edit removing the grant ends the run within one timer period, an expiry with no request ends it, a pending message followed by a launcher restart starts no run, a running revoked thread gets SIGTERM, another user's runs stay |
|
||||
| R6 | Console: granted-user sign-in, `user-cli` transport per session, per-request grant re-read, owner-only controls hidden, peer links | Opus 5.5 | Sage #412 phase 1, R1, R2, R3 | webui tests on Sage's fake OP: granted user signs in, ungranted `sub` refused, revoked mid-session gets 401 on next call, a granted user's owner-verb call refused by the bus even with the UI check removed (mutant) |
|
||||
| R7 | Scratch two-instance acceptance: two configs, two data roots, two Consoles, one fake OP. The Consoles start outside agent ancestry (a transient user unit), as the user proof requires. They never use the live stack's units or ports | Haiku 5.5 high | R1-R6, row 190 (several instances on one uid) | script test: Jason granted collaborator on one hub project signs in to the hub, makes a thread, sends to the granted instance, gets the instance's reply in the thread, is refused on another project and on board reads, is revoked and gets refused with the run ended. The factory shows nothing of the hub |
|
||||
| R8 | Live window, not a code row: hub provider client, hub proxy route, access.json written by Jason, re-pins | Jason, Mos | row 190b live (hub under its own Linux user) or the hub on its own host, #412 phase 2, R7 | Entry gate, R8 refuses to start unless one isolation form holds. Separate uid: the factory and hub buses run under different uids, a factory-uid human-cli and user-cli call to the hub socket is refused (human.mjs:67), and the factory uid can't read the hub data root or config dir (both 0700). Separate host: the hub's bus socket, data root and config dir don't exist on the factory host, the factory host mounts nothing of the hub's, and Jason confirms no factory agent holds a login or key for the hub host. Both forms also check: the two provider client ids differ, each origin answers over HTTPS from the other host, each Console reaches its issuer's discovery URL, and both clocks are synced. Then Jason signs in to the hub from his account and sends one message, Melanie revokes, and his next request is refused |
|
||||
| R8 | Live window, not a code row: hub provider client, hub proxy route, access.json written by Jason, re-pins | Jason, Mos | the hub host live (Vikunja #418), Jason's #419 answer, #412 phase 2, R7 | Entry gate, host form only for 10-27 (mos-0996): the hub runs on its own host (#418), not on dragon-lin or Melanie's server. R8 refuses to start unless every check passes. The privilege checks run on the factory host as the factory uid under `env -i`, and each passes only when the call fails: `sudo -n true`, `doas -n true`, and `ssh -o BatchMode=yes <hub-host> true` as the hub user, as Jason's user and as root, each with and without the session's `SSH_AUTH_SOCK`. If `sudo -n` succeeds, as it does for jwoltje on dragon-lin (mos-0996 point 1), the ssh checks also run as root through it and must fail, and the hub host's `authorized_keys` files hold no key whose private half is on the factory host. The hub host's bus socket, data root and config dir don't exist on the factory host, the factory host mounts nothing of the hub's, and the hub host runs no other instance or agent. Also: the two provider client ids differ, each origin answers over HTTPS from the other host, each Console reaches its issuer's discovery URL, and both clocks are synced. Jason's attestation confirms no factory agent holds a login or key for the hub host, but the failed calls are the gate. Open gate item (Vikunja #419, Jason's decision): factory agents hold root ssh to the PVE nodes (Q172), and root on a node defeats the host form (duckworth-0004). R8 doesn't start until Jason picks one of O1 to O3 (section 8). The uid form (row 190b) is a later option, outside this gate. Then Jason signs in to the hub from his account and sends one message, Melanie revokes, and his next request is refused |
|
||||
|
||||
R1, R2 and R5 can start now and run in parallel. R3 starts when R1 has a candidate, R4 when R3 and R5 do.
|
||||
R6 waits on Sage's phase 1 candidate.
|
||||
@@ -436,13 +457,28 @@ R6 waits on Sage's phase 1 candidate.
|
||||
## 8. Risk to 10-27
|
||||
|
||||
The slice lands only if three things hold. #412 phase 1 has a reviewed candidate by about 10-23. P19 333,
|
||||
the path-bound human proof R3 depends on, is settled in time for R3 (Mos told Sage, mos-0988). Row 190b
|
||||
or a separate hub host is live by about 10-25. R6 and R7 chain on #412 phase 1. R8 needs all three, plus
|
||||
the path-bound human proof R3 depends on, is settled in time for R3 (Mos told Sage, mos-0988). The hub
|
||||
host is live by about 10-25 (Vikunja #418), with Jason's #419 answer. R6 and R7 chain on #412 phase 1. R8 needs all three, plus
|
||||
two Jason-gated windows (#412 phase 2 and the federation re-pin), plus infra work only Jason does: a
|
||||
second public origin, a proxy route and a provider client, and on a separate host that host itself. If
|
||||
neither isolation form is ready, R8 doesn't run and Mos takes it to Jason.
|
||||
second public origin, a proxy route and a provider client, and the hub host itself. If the host fails the
|
||||
gate, R8 doesn't run and Mos takes it to Jason.
|
||||
|
||||
## 9. Rulings (mos-0978, mos-0984, mos-0991)
|
||||
Open gate item, the hypervisor path (Vikunja #419, mos-1004, Jason's decision). Factory agents hold root
|
||||
ssh to the PVE nodes (Q172). Root on a node can read a running VM's memory and disk, open its console,
|
||||
and change its config or boot. So it defeats the host form completely when the hub is a cluster VM
|
||||
(duckworth-0004). LUKS inside the VM doesn't close it alone: the key sits in the running VM's memory,
|
||||
which the node can read, and a node that can change the boot can capture the passphrase. Options:
|
||||
|
||||
- O1. The hub runs on hardware outside the PVE cluster, where the factory has no root.
|
||||
- O2. A cluster VM, with factory root ssh removed on every node, no factory PVE token with console,
|
||||
monitor, config, backup or snapshot rights on the VM, VM storage the factory can't read, and PBS
|
||||
backups the factory can't read or that are encrypted with a key it doesn't hold.
|
||||
- O3. Jason accepts it as a residual. Then this section and R8 say plainly that the host form doesn't
|
||||
stop a factory agent using PVE root, and Melanie is told.
|
||||
|
||||
Mos adds Jason's answer. Until then R8 doesn't start.
|
||||
|
||||
## 9. Rulings (mos-0978, mos-0984, mos-0991, mos-0996, mos-1004)
|
||||
|
||||
1. Q1: human cross-access is the 10-27 slice (mos-0978). Duckworth's N8 put agent-to-agent's timing to
|
||||
Jason through Mos. Jason answered "P2P federation is acceptable for 10-27" (mos-0991). Mos is checking
|
||||
@@ -452,10 +488,11 @@ neither isolation form is ready, R8 doesn't run and Mos takes it to Jason.
|
||||
collaborator. The owner stays outside the file. Thread shares go in a new append-only bus table,
|
||||
`thread_shares`, not in access.json, and a share counts only while the named user's live grant covers
|
||||
the thread's project and instance. The 10-27 slice ships collaborator only (W2), so when row 193 adds
|
||||
viewer, R1's level check widens with it.
|
||||
3. Q3 and B5: a separate uid per instance is a hard precondition of R8 (mos-0984 point 2). Mos won't ask
|
||||
Jason to accept a single uid, and takes a new decision to him if row 190b can't deliver. A separate
|
||||
host meets the same gate (mos-0991, 3.8).
|
||||
viewer, R1's level check widens with it. `thread_shares` is append-only, so an unshare is an end row in
|
||||
row 193's design. R3 assumes nothing about shares (N12).
|
||||
3. Q3 and B5: R8 never runs on a single uid (mos-0984 point 2). For 10-27 its isolation is the hub's own host,
|
||||
the host form only (mos-0996, Vikunja #418). The uid form (row 190b) is a later option. The hypervisor
|
||||
path is open with Jason (#419, section 8).
|
||||
|
||||
## 10. Notes
|
||||
|
||||
|
||||
@@ -1363,3 +1363,4 @@ are never rewritten or removed; corrections are new entries.
|
||||
2026-10-20T19:00:18Z | Gizmoduck (Opus 5.5) | T3 federation design r3 for Vikunja #414, answering duckworth-0002 B6 (user reply path), C1 and N10 | committed and pushed on gizmoduck/federation-design; sent to Duckworth for approval on the diff
|
||||
2026-10-20T19:01:43Z | Gizmoduck (Opus 5.5) | T3 federation design r3.1 for Vikunja #414, mos-0991 (R8 gate accepts a separate host or uid, one-host audit 3.8, Q1 answer) and morgana-0002 (Q2 settled, access.json is the human store) | committed and pushed on gizmoduck/federation-design; sent to Duckworth with r3
|
||||
2026-10-20T19:04:18Z | Gizmoduck (Opus 5.5) | T3 federation design r4 for Vikunja #414, answering duckworth-0003 B7 (privilege paths in R8's gate), N11 (#waiting for user replies), N12 | committed and pushed on gizmoduck/federation-design; sent to Duckworth
|
||||
2026-10-20T19:08:20Z | Gizmoduck (Opus 5.5) | Correction to the 19:04:18Z line: commit 04a6112be carried only that SESSIONS line, because the DESIGN.md edit script failed on an anchor and the chain still committed and pushed, and nothing went to Duckworth then. r4 proper: B7 per mos-0996 and mos-1004 (host form only, sudo, doas and ssh checks, hypervisor path an open R8 item with O1 to O3 on Vikunja #419), N11, N12, row 196 verifier noted per gander-0006 | committed and pushed on gizmoduck/federation-design; sent to Duckworth and Mos, reply to Gander
|
||||
|
||||
Reference in New Issue
Block a user