The view: one read-only bus user for a page in a browser, composed like every other (hq research 036) #203
Closed
mesh-admin
wants to merge 2 commits from
feat/bus-view-principal into main
pull from: feat/bus-view-principal
merge into: :main
:main
:fix/sweep-2026-10-30
:fix/checker-full-failure
:feat/1945-user-list-switch
:fix/1945-mesh-grants-publishes-memberships
:fix/issued-states-follow-the-module
:fix/1945-credential-on-assignment
:fix/r6-mirror-on-change
:fix/store-forge-address
:fix/r6-look-again-after-unlock
:fix/r6-key-waits-said
:fix/1931-reconnects-verb
:fix/forge-requires-no-status
:fix/906-lease-renewal
:feat/r4e-controller
:fix/1106-sdk-member-merges
:fix/1399-person-word-test
:fix/1652-checks-wait
:feat/files-through-the-store-files-verbs
:fix/1649-held-ask-handed-out-twice
:fix/1658-sdk-items
:fix/1334-a-bus-reload-fails-a-tier
:feat/chain-on-the-bus
:fix/701-a-name-nothing-answers-is-not-used
:fix/270-a-dead-bus-watch-is-heard
:fix/630-config-values-from-four-sources
:fix/613-node-smb-server
:feat/328-libraries-and-module-users
:feat/325-identities-and-kept-folders
:feat/bus-view-principal
:test/plan-diff-left-out-line
:fix/the-registry-trust-test-reads-the-runtimes-module
:fix/the-runtime-file-is-the-runtimes
:feat/build-edges-are-recorded
:fix/several-contributions-collide-in-grants-v2
No Reviewers
No labels
Milestone
No items
No Milestone
Assignees
agent-ace
agent-g14
agent-novox
agent-shanks
jschoubben
mesh-tools
miel.truyen
nox-agent
Clear assignees
No Assignees
No due date set.
Dependencies
No dependencies set.
Reference: novox/mesh-controller#203
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
hq research 036 names the gap (G1): no way for a browser to reach the bus. mesh-catalog #181 gives the bus module a WebSocket listener on the overlay; this is who connects there: the read-only user the issue tracker's local view page holds. No hq record by the operator's word; code only. Goes with #181 (merge order free: the user is composed only once minted, and the listener is live only after a person's
bus upgrade).What changes
internal/broker:KindViewis one fixed principal, userview, composed into the same file as every user (ADR 0234) once its credential is minted (Records.View) and left out once forgotten. Its whole authority is two lists beside it.ViewHears):mesh.mod.mesh-issues.event.{opened,moved,noted,linked},mesh.seat.mesh-controller.event.{plan-moved,condition-raised,condition-changed,condition-cleared},mesh.mod.mesh-delivery.event.{transition,group}, and its own inbox_INBOX.view.>.ViewReads), three read requests on the tracker's streamKV_mesh-issues_issuesand no other:$JS.API.STREAM.INFO.KV_mesh-issues_issues(bind),$JS.API.DIRECT.GET.KV_mesh-issues_issues(the batch read,multi_last, which lists the newest value of every key) and$JS.API.DIRECT.GET.KV_mesh-issues_issues.>(one key). There is noallow_responses, no deny, no$KV.*write, and no consumer.deliver_subjectwasmesh.mod.mesh-issues.event.opened. A module subscribed there received the bucket's entry as the tracker's event. The server does not check a deliver subject against its creator's permissions, so a consumer grant is a publish to any subject. CONSUMER.DELETE would also let the view delete a module's consumer. The page therefore lists with the batch read and re-reads the key a tracker event names (every event carriesnumber).mesh-issues_issues(the bus's name for mesh-issues's stateissues, read live withnats_buckets), notissues.internal/inventory: a bus-user kindview;BusRecordssetsViewwhen the rowviewof that kind exists. No schema change: the row is the record.cmd/mesh-controller:bus view-credentialmints the credential and prints it once (the hash is kept, as withoperator issue). It prints the WebSocket URL (ws://<bus host>:4223; the port is mirrored from the nats module's opening, see the note below), the bus URL and fingerprint, user, password, the inbox prefix the client must use (_INBOX.view), what it hears, what it reads and how to read it.bus view-revokeforgets the row and is revoked at the next composition, said asoperator revokesays it. The credential works after the next push to the bus's machine; while a new bus build waits there (after #181), that push is held and the step isbus upgrade, and the printed text says so. These are terminal lines only; thebusseat verb is unchanged.Tests
view_test.go: the composed user's subscribe and publish lists are exactly the two lists, and every publish grant is one of the three reads. Mutation: puttingCONSUMER.CREATE.<stream>.>back fails three tests (the exact-list check, a named watch subject, and the live watch). It names 22 refused publishes (put, delete, other buckets, STREAM.CREATE/PURGE/DELETE/MSG.DELETE, a watch, an unnamed consumer, a module consumer's delete, flow control, events, tools, seat verbs) and 7 refused subscribes. It also checks the view is composed only once minted, and is named as missing without a hash.view_live_test.go: the composed file is read by nats-server itself, with a WebSocket listener as the bus module declares it (no TLS, compression). The controller defines the bucket and the tracker writes it. The view connects overws://with its credential, binds the bucket, reads a key, and lists every key with one batch read. It hears amovedevent and re-reads the key it names. It is refused a consumer delivering ontoevent.openedand a watch, and nothing reaches a subscriber there. It is refused a put, a delete and an event (permission violations from the server), and the bucket is unchanged.testdata/composed.conf, +4 lines; read it).Reviewers' checklist (an independent review was run on the first head)
ViewReads, the mutation, and the live refusals.bus view-revoke,ForgetBusUser).recordpolicy.Notes, not blockers
busWebSocketPort = 4223is mirrored by hand from the nats manifest, and no test ties the two repositories. #181's test holds the manifest's three places to one port.exposesetting (ADR 0046) could widen afrom: meshopening toanywhere, and nothing refuses that for a listener without TLS. This was there before this change.For the page: use nats.js v3 or nats.ws and connect to
ws://novox.internal:4223(the control-node's overlay name; its address is10.10.0.1). Use userview, the printed password, andinboxPrefix: "_INBOX.view". Open the bucket withkvm.open("mesh-issues_issues", {allow_direct: true}), nevercreate, and use onlyget. Do not callwatch,keysorhistory: each makes a consumer, which is refused. List with a request to$JS.API.DIRECT.GET.KV_mesh-issues_issuescarrying{"multi_last":["$KV.mesh-issues_issues.>"]}; replies arrive in the inbox until aStatus: 204header. Subscribe to the events andgetthe key each event'snumbernames. Open the page fromfile://orhttp://: an https page cannot openws://.Checks.
go vet, andgo testof./internal/broker/ ./cmd/mesh-controller/ ./internal/inventory/, pass locally;merge-check.shagainst a throwaway store passed on the first head.Do not merge without review.
Delivery
novox/mesh-controller@dc5a8208a213— closed since 2026-10-25T19:33:26ZTransitions
The commit's note under
refs/notes/mesh-plankeeps every transition of this change up to its merge:git log --notes=mesh-plan.Merge check at
a5a355aemesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: build-agent, mesh-controller, route-proxy)mesh/repo-check: PASS — its merge-check.sh passedDelivery plan — builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step
Run by the build seat on g14 as
build-1791640925568845903(builds --log build-1791640925568845903).Merge check at
dc5a8208mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: build-agent, mesh-controller, route-proxy)mesh/repo-check: PASS — its merge-check.sh passedDelivery plan — builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step
Run by the build seat on novox as
build-1791641749753159970(builds --log build-1791641749753159970).Merge check at
dc5a8208Judged: head
dc5a8208a213merged into trunk7c9c187ca6e2mesh/merge-gate: FAIL — headdc5a8208a2does not merge cleanly into trunk7c9c187ca6: in conflict internal/broker/nats.go, internal/inventory/bususers.go (modules: build-agent, mesh-controller, route-proxy)mesh/repo-check: FAIL — headdc5a8208a2does not merge cleanly into trunk7c9c187ca6: in conflict internal/broker/nats.go, internal/inventory/bususers.goDelivery plan — builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step
Run by the build seat on ace as
build-1792268602106987822(builds --log build-1792268602106987822).Merge check at
dc5a8208Judged: head
dc5a8208a213merged into trunk47f9290b37aamesh/merge-gate: FAIL — headdc5a8208a2does not merge cleanly into trunk47f9290b37: in conflict internal/broker/nats.go, internal/inventory/bususers.go (modules: mesh-controller)mesh/repo-check: FAIL — headdc5a8208a2does not merge cleanly into trunk47f9290b37: in conflict internal/broker/nats.go, internal/inventory/bususers.goDelivery plan — builds mesh-controller → novox; no bus step
Run by the build seat on shanks as
build-1792275635760693285(builds --log build-1792275635760693285).Merge check at
dc5a8208Judged: head
dc5a8208a213merged into trunk54070f5bc32bmesh/merge-gate: FAIL — headdc5a8208a2does not merge cleanly into trunk54070f5bc3: in conflict internal/broker/nats.go, internal/inventory/bususers.go (modules: build-agent, mesh-controller, route-proxy)mesh/repo-check: FAIL — headdc5a8208a2does not merge cleanly into trunk54070f5bc3: in conflict internal/broker/nats.go, internal/inventory/bususers.goDelivery plan — builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step
Run by the build seat on shanks as
build-1792282621077532731(builds --log build-1792282621077532731).mesh-delivery: the operator's Approve of this pull request's merge into main is ended: its head
dc5a8208a2is no longer the pull request's head, or the pull request closed. Nothing is merged on it. The operator is asked again once the checks pass at the new head.