Publishing a deck — public read, and handing out what it demos

Making a talk readable by the world is two decisions, not one: who may read the deck, and how the audience reaches the thing the deck demonstrates. They have different answers, and the second one is where the damage is.

Worked on Event/SavGeneralversammlung2026/Talk (the SAV Generalversammlung 2026 talk), 2026-09-09. Every claim below was measured against the live mesh or the source, not inferred.

1. A public surface needs BOTH grants, or half your readers are refused

Permissions inherit top-down, so one AccessAssignment at the deck root covers every slide — 22 of them here. The node is ordinary:

Event/…/Talk/_Access/Anonymous
  nodeType : AccessAssignment
  mainNode : Event/…/Talk          ← MUST equal the scope the path encodes (AccessAssignmentGuard)
  content  : { accessObject: "Anonymous", roles: [ { role: "Viewer" } ] }

🚨 Anonymous alone is a trap. Authenticated users inherit Public, never Anonymous — so a grant to Anonymous alone gives you a deck that a logged-out visitor can read and a logged-in one cannot. Seed both, which is what Doc already does (core's Doc/Architecture/AccessControl says so under Anonymous and Public access; it is easy to read the Anonymous example there and stop).

Siblings are untouched by inheritance, which is the point: the Mail node beside this deck — cover letter, bio, CV — stays private without a single deny.

2. What Viewer actually permits

Viewer => Permissions = Permission.Read | Permission.Execute | Permission.Api

No Create, Update or Delete. But Execute is in there, and it is the one to think about before granting a surface that contains anything runnable. On a deck it is inert — a Publish/Deck plus Publish/Slide nodes and nothing else; verify with a descendants query excluding slides and expect zero rows. On a package it is not inert: ReinsuranceDemo and Underwriting hold 15 and 30 Code nodes, including a one-click importer (ReinsuranceDemo/Setup). A viewer's own writes are denied, but a script that elevates to system is not.

The composer does not write where you are standing

A signed-in reader's chat composer is a per-user singleton in their own home{user}/_Thread/ThreadComposer (Chat.razor) — so composing from a read-only deck never attempts a write in the deck's namespace. For an anonymous reader ResolveUserId is empty and the control resolves to null: no composer is rendered at all.

⚠️ Unverified, and worth checking before you rely on it. MeshExtensions documents a second chain — Node.MainNode → NavigationContext.PrimaryPath → NavigationService.CurrentNamespace → the chat composer's StartThread namespace. Whether a composer opened as a side panel from a content page takes the current namespace rather than the home path was not traced. If it does, a submit from a read-only deck is a denied write, not a redirect home.

SlideLayoutAreas.BuildStage attaches .WithClickAction(…) to the entire stage whenever there is a next slide, and every view passes a real nextHref except the speaker-notes preview (BuildStage(slide, nextHref: null)). The rendered area confirms it: the stage comes back with isClickable: true and cursor: pointer.

So an <a href> in slide content competes with a redirect for the same click. Put URLs and codes on a slide as readable text, not links. That is the right answer anyway — the audience is reading a projection, or a photo of one, and will type what they see. Real controls belong outside the stage, which is where the presenter bar's Prev/Next/Deck/Present buttons live and why they work.

4. Reach the demo with a COUPON, not a public grant

The instinct is to make the demo public too. Resist it: the tour's steps target other partitions entirely, and opening them is not a small act.

The tour's step Its target
1 · the desk Underwriting/Desk
2–7 ReinsuranceDemo/Submissions/MTPL2027

Both partitions exceed 200 nodes and hold 26 and 30 GitHubIssue nodes — an internal issue tracker mirrored into the mesh. Granting the tour node alone is worse than useless: the walkthrough renders its cards and every step is denied.

A coupon does the whole job with one node, because acquisition cascades. DependencyInstall.EnsureEntitledTo grants an entitlement on every dependency at amount 0, attributed dependency of {package} — and DispositionOf auto-grants only unpriced ones. ReinsuranceDemo and Underwriting both carry no price, so redeeming on ReinsuranceDemo yields Claims, Ifrs17, LossModelling, Pricing, Reinsurance, Training and Underwriting with it. That is exactly the tour's surface — capped, windowed, revocable and audited, and with the issue trackers still private.

Admin/Coupons/SAVDEMO
  price 0 · plugins ["ReinsuranceDemo"] · maxRedemptions 200 · validUntil 2026-12-31

What a redeemer can and cannot do

Enrollment writes the plugin-root Viewer grant, so a redeemer gets the same read-only shape as above. On this demo that means sign-off, sending the enquiry and taking over a claim are denied — steps 1–4, 6 and 7 read fine and step 5's "reword it live, send" is refused.

That is the correct default, and it is also a product limit worth stating out loud: an interactive demo for a room full of people needs per-learner copies, because two hundred readers acting on one shared MTPL2027 submission would trample each other regardless of permissions.

5. Do not publish a code the mesh will refuse

A slide that prints a coupon is a promise. Check the coupon can actually be redeemed on the deployment the readers will hit, not on main:

Both were true at once on 2026-09-09: #1553 was merged while Store/Coupon/Source/CouponContent on memex still read lastModified 2026-09-02. Merged is not deployed — read that timestamp before arming a coupon or making the deck that advertises it public.

Reconnecting…
The server was updated. Reloading the page to pick up the latest version.