morgana: row 193 design r2: trail and replies hide other users' unshared thread messages (sage-1354), 193b split into b1 and b2, coverage from FED r4.1 covers (gizmoduck-0018)

This commit is contained in:
jason.woltje committed 2026-10-20 14:14:10 -05:00
1 parent 5d15979e06
commit 19c015741d
1 file changed
+54 -24
+54 -24
View File
@@ -1,6 +1,6 @@
# Row 193 design: many humans per business, human roles, private and shared threads
Morgana (Opus 5.5), 2026-10-20. Gitea #1691, queue row 193, brief `docs/plans/2026-10-20_human-users.md`
Morgana (Opus 5.5), 2026-10-20. Revision 2: sage-1354 (5.7 private from the owner, 193b split) and gizmoduck-0018 (coverage and the R4 predicate in FED r4.1). Gitea #1691, queue row 193, brief `docs/plans/2026-10-20_human-users.md`
(blob f7472d1e47b1). Reviewer Duckworth, gate owner Sage. Base: `refactor` at 53bb59462.
Read for this design: COMMON.md (53bb59462), the brief, Gizmoduck's federation design r3.1 (a2840a6be on
@@ -252,7 +252,8 @@ and `thread.shares`. A new `#threadRead(s, id)` admits the owner or `#readable`,
| `thread.messages` | `#thread` | `#threadRead` |
| `thread.open`, `thread.send` | `#thread` | `#thread`, plus a collaborator grant for users |
| `project.list`, `names` | business-wide | per FED 3.5 for users, unchanged for the owner |
| `trail`, `replies`, `messages`, `inbox`, `agents`, `tasks`, `decisions`, `credentials` | human or reader | users refused (`access-refused`); owner unchanged |
| `trail`, `replies` | human, reader or tester; any message by id | users refused (`access-refused`); for the others, messages in other users' unshared threads hidden (5.7) |
| `messages`, `inbox`, `agents`, `tasks`, `decisions`, `credentials` | human or reader | users refused (`access-refused`); owner unchanged. A user-thread run can't send to the human (`user-thread-human`, FED 3.6), so `messages` holds none of a user's thread |
| `message.receive` by a run | its own thread (:1933) | unchanged: runs read their own thread, never a share |
| `threadsState` (launcher) | all threads | adds `revoked` per 5.6 |
@@ -267,22 +268,36 @@ Gizmoduck asked that a grant loss end access to threads shared with that user (g
- An unshare row ends access on the next request in the same way.
- A user who loses coverage of their own thread also loses its shares: rule 3 in 5.4.
One change to FED R4. FED 3.7 marks a user thread `revoked` when no live grant covers it. With viewer, that
is wrong: a viewer grant covers the thread but must not let a run continue. Row 193 changes the predicate
for `revoked`, claim end and the launch check to "no live **collaborator** grant covers the thread". A
downgrade to viewer then ends the run within one launcher tick, as a revocation does. This has its own test
and mutant (section 8).
The downgrade case. With viewer, "no live grant covers" would be wrong for runs: a viewer grant covers the
thread but must not let a run continue. FED r4.1 (73677b87d, gizmoduck-0018) puts this in R4: `revoked`,
claim end and the launch check call `covers` with level `collaborator`, so a downgrade to viewer ends the
run within one launcher tick. Row 193 doesn't carry that change. 193b1 still has a downgrade test and a
mutant (section 8), because viewer is new in this row.
### 5.7 What "private" means, stated plainly
Sage ruled (sage-1354): a user's thread is private from the owner on the bus too. Jason's words are "private
agent sessions per user", and in Case 2 the owner of Melanie's hub is not the user whose thread it is.
- A user's thread is private from every other user and viewer at the bus boundary. They reach the bus only
through the Console and the row 196 verifier, and every read is filtered.
- It is not private from the owner in a strong sense, and the design doesn't claim it is. The thread's
instance runs with the owner's role authority (F10), the owner holds the store as the instance's OS user,
and `trail` and `replies` read any message by id. Row 193 adds no owner verb that lists or reads other
humans' threads, and the Console doesn't show them. I chose to leave `trail` and `replies` unfiltered so
the owner can audit what their instance did on a user's behalf. Tradeoff: an owner who has a message id
can read it. Filtering those two verbs is a small change if Duckworth or Sage wants it.
- It is private from the owner at the bus boundary as well. Row 193 adds no owner verb that lists or reads
another human's threads, the Console doesn't show them, and the two board reads that return messages by
id, `trail` and `replies`, filter them (below).
- The filter. A message is hidden from a session when it is in a thread (`message_threads`) whose owner is a
`user:<id>` other than the caller and `#readable(s, t)` is false. Threads owned by the business human or
by `tester` are not affected, so row 177's behaviour is unchanged.
- `trail`: drops hidden messages, their `deliveries` rows, and `events` rows whose `subject` is a hidden
message id. A `subject` that is itself a hidden message returns `message-not-found`, as an unknown id
does, so the reply doesn't confirm the id exists.
- `replies {id}`: a hidden `id` fails `message-not-found`, and hidden messages are dropped from the
result.
- The filter applies to every session that can call these verbs: the owner, reader sessions and the
tester.
- What it does not do. The owner holds the store as the instance's OS user and can read it with sqlite, and
the thread's instance runs with the owner's role authority (F10). That is the same-uid limit in section 6,
not a bus path. An owner audit verb, explicit and logged, is a follow-up row only if Jason asks for one
(sage-1354).
## 6. The bus human boundary (brief point 5)
@@ -342,7 +357,7 @@ the instance's uid.
## 8. Tests and mutants
Bus (193b), each in `packages/bus/tests/`:
Bus, each in `packages/bus/tests/` (193b1: viewer, mixed grants, downgrade, board reads; 193b2: shares, migration, the 5.7 filters):
- Viewer: each allowed verb works, every other verb refuses `access-refused`, including `thread.create`,
`thread.open`, `thread.send` and `thread.share`.
@@ -360,6 +375,11 @@ Bus (193b), each in `packages/bus/tests/`:
- Non-owner `thread.share`, `thread.unshare`, `thread.shares` refuse. Tester refuses on all three.
- `thread.list` plain shape unchanged. `thread.list {shared: true}` returns only readable threads.
- Board reads refuse users and viewers.
- `trail`, for the owner, a reader session and the tester: a user's unshared thread message as `subject`
fails `message-not-found`; a decision trail that reaches such a message omits it, its deliveries and its
events. Once the thread is shared with the owner, the owner's trail shows it.
- `replies`, same three sessions: a hidden `id` fails `message-not-found`; hidden replies are dropped. The
owner's own and the tester's threads are unchanged.
- Migration: a 3i store with threads migrates to 3j, row counts unchanged, `thread_shares` empty and
append-only (update, delete, replace each abort). Restore-guard and `COUNTED` include it.
@@ -382,30 +402,40 @@ Mutants, each must be killed:
8. `share-not-covered` check removed.
9. `thread.share` owner check removed (sharee re-shares).
10. A user allowed `trail`.
11. The `trail` filter removed (the owner reads a user's unshared message by id).
12. The `replies` filter removed (the owner reads replies in a user's unshared thread).
## 9. Build rows and order
| Row | Work | Chains on | Schema |
|---|---|---|---|
| 193a | R1 level check widened to viewer | FED R1 candidate | none |
| 193b | Bus: viewer verb list, per-grant level checks, `revoked` predicate, `thread_shares`, share verbs, `#readable`, `#threadRead`, filters | FED R3, R4 candidates, row 196 verifier | 3j |
| 193b1 | Bus: viewer verb list, per-grant level checks, `revoked` predicate, users refused on board reads | FED R3, R4 candidates, row 196 verifier | none |
| 193b2 | Bus: `thread_shares`, share verbs, `#readable`, `#threadRead`, the 5.5 filters, the 5.7 `trail` and `replies` filters | 193b1 candidate | 3j |
| 193c | `mosaic access` user verbs and `--level viewer`, if R5 lacks them | FED R5 candidate | none |
| 193d | Console: shared list, share controls, viewer read-only | FED R6, row 192 candidates | none |
| 193d | Console: shared list, share controls, viewer read-only | FED R6, row 192 candidates, 193b2 for share controls | none |
193b can be split again if Sage wants the no-schema part live first: 193b1 viewer and level checks,
193b2 `thread_shares`. Both stay one design.
Sage ruled the split (sage-1354): 193b1 has no schema change and can go live first. 193b2 carries 3j, and
Sage takes its migrate window to Mos when 193b2 is near. The 5.7 filters go in 193b2 because they use
`#readable`. Until 193b2 lands no user thread exists live (FED R8 is later), so nothing is exposed in
between.
Dependency I need settled by R1 or R3 before 193b: how a grant's `projects` matches a thread's project.
FED 3.5 says `projects` is `["*"]` or "declared project ids". Threads point at bus project UUIDs
(`projects.id`, broker.mjs:2306), and a bus project may have no `repo` in the business file. Row 193 uses
whatever single `covers(grant, {project, instance})` R3 defines and adds no second one. I am raising this
with Gizmoduck.
Coverage, settled in FED r4.1 (73677b87d, gizmoduck-0018, still under Duckworth's review). A grant's
`projects` is `["*"]` or bus project UUIDs (`projects.id`, broker.mjs:2306; `threads.project` references it).
R1 exports one pure `covers(grant, {business, project, instance}, now, {level})`. Row 193 uses only that
function and adds no second one (Sage confirmed, sage-1354):
- share reads (5.4 rules 2 and 3) and `share-not-covered`: `covers(g, thread, now, {})`, any level;
- `thread.create`, `thread.open`, `thread.send`, `thread.share`: `covers(g, thread, now, {level: 'collaborator'})`;
- `project.list` and `names` for users: any level, as FED 3.5.
`covers` needs no further argument for row 193. If r4.1 changes in review, 193b1 follows it.
Follow-ups, not in this row:
- A read-only users and grants page for the owner in the Console.
- A second owner or admin level, if Jason wants one.
- Filtering `trail` and `replies` for user threads, if 5.7's tradeoff is ruled the other way.
- An explicit, logged owner audit verb for user threads, only if Jason asks for one (sage-1354).
## 10. Gate