Web Device decides who may read a page at serve time, before it replays prerendered HTML. public must be written at some policy layer. An unmarked record with no collection, path, or hatch default is a 404, including for site admins.
Connect currently admits four roles: guest, member, admin, and owner. none is not a role; it is a hard deny.
Start with a public collection and a tighter record
The usual setup: the collection default is public, so unmarked records stay readable. Then mark the few restricted records.
# blocklet.yaml
collections:
articles:
access: public# content/articles/members-only/content.md
---
title: Members-only note
access: member
---This site's articles collection already has that default. collections.<type>.access is the default for details of that type. It does not set the /articles/ list page. Zero-config collection list routes are public unless you lock that route. A type-level member default therefore does not close the list; it closes unmarked details.
Leave preview unset. A signed-out reader gets the first screen of the body, then the gate. The rest of the tree never enters the HTML. Sitemap, llms.txt, and MCP omit a gated record unless you set preview: true. Unmarked records that inherit public from the collection stay on those faces (after arc#4086).
collections.<type>.readRole is a storage read grant, not this HTML gate.
A signed-out caller is guest. A signed-in DID Connect session may include member, admin, or owner. This page does not document how a site grants those roles.
Choose the record fields
| You write | Unsigned reader | Matching role, or admin/owner |
|---|---|---|
No access, collection access: public | Full page | Full page |
No access, and no collection / path / hatch default | 404 | 404 |
access: public | Full page | Full page |
access: member (or admin / owner), preview omitted, record is listable | First-screen teaser + gate | Full page |
access: member and preview: true | Teaser + gate. Declared excerpt is used only when there is no teaser HTML | Full page |
access: member and preview: false, record is listable | Gate only, no teaser | Full page |
access: member and preview: false, record is not listable | 404 | Full page |
access: none | 404 | 404 |
A record named role only tightens the scope above it. access: public on a record cannot open a locked path or collection. To open one slug under a lock, add a more specific accessPaths rule. See Content access reference.
A record is listable when the list route is public (zero-config /{locale}/{type}/ routes are), or when blocklet.yaml sets the hatch accessDefault: public. Collection access is not what makes the list public. On this site the article list is public, so a gated article without preview: false shows a teaser.
Prerender may write the full body into .web-cache. The gate runs on serve, before that file is replayed. Do not host .web-cache as static files.
What a denied reader sees
The detail stays in site chrome. Title, author, and date stay in post-hero. The cover is cleared. The body slot is a first-screen teaser (unless preview: false) and the access-gate stop.

Anonymous GET of a listed admin article on ARC 2.0.0-beta.35, 2026-08-15, http://arcblock.localhost:4922/. Live capture, not a mock. That fixture URL is no longer on this site.
Anonymous GET of a listed access: admin article on 2026-08-15, ARC 2.0.0-beta.35, http://arcblock.localhost:4922/:
curl -sI http://arcblock.localhost:4922/en/articles/access-fixture-team/HTTP/1.1 200 OK
cache-control: private, no-store
x-robots-tag: noindexThe HTML includes access-teaser, then:
<aside class="access-gate" data-kind="promo-shell" data-required-role="admin">
<p class="access-gate-message">The rest of this piece is for admins.</p>
<a class="access-gate-signin"
href="/.well-known/service/login?return_to=%2Fen%2Farticles%2Faccess-fixture-team%2F&cancel_to=%2Fen%2Farticles%2Faccess-fixture-team%2F">Sign in</a>
</aside>The protected body from that capture is absent. The same URL with a matching test caller returned the body and no gate. Sign-in is DID Connect.
The teaser is an SSR cut of the AUP tree (~480 visual units, at most two top-level blocks, last block always withheld). It is not a CSS fade over the full body.
Check a live site
Use a runtime that includes arc#4065 (arc --version on the capture above: 2.0.0-beta.35). Point the service at your blocklet and probe records you declared there. This site does not ship access-gate test articles.
# Hard deny — a record with access: none
curl -sI http://arcblock.localhost:4900/en/articles/<your-none-record>/
# HTTP/1.1 404 Not Found
# cache-control: private, no-store
# content-length: 0
# Explicit public
curl -sI http://arcblock.localhost:4900/en/articles/<your-public-record>/
# HTTP/1.1 200 OK
# cache-control: no-cacheLocal services started with AFS_TEST_ALLOW_CALLER_HEADER=1 accept a test caller. Both headers are required:
curl -s -H 'x-caller-did: z1admin' -H 'x-caller-role: admin' \
http://arcblock.localhost:4900/en/articles/<your-admin-record>/ \
| rg '<a string that exists only in the protected body>'A member caller opens a access: member record and still sees the gate on an access: admin record:
curl -s -H 'x-caller-did: z1member' -H 'x-caller-role: member' \
http://arcblock.localhost:4900/en/articles/<your-member-record>/ \
| rg '<member-only body marker>'
curl -s -H 'x-caller-did: z1member' -H 'x-caller-role: member' \
http://arcblock.localhost:4900/en/articles/<your-admin-record>/ \
| rg 'access-gate|<admin-only body marker>'x-caller-role alone still returns the gate. Do not send these headers on a public deployment.
Failures
| What you see | Usual cause |
|---|---|
404, empty body, Cache-Control: private, no-store | No scoped default, or access: none |
| Gate, but no first-screen teaser | You set preview: false |
| Teaser includes the last section of the article | Unexpected. File an upstream issue; the cut must withhold the last top-level block |
Record access: public still gated or 404 | A stricter accessPaths or collection default is the ceiling. If the list route is public you get a teaser/gate, not a 404 |
Signed-in member still sees an admin gate | Admit is AND across every named constraint. member does not satisfy admin |
Sitemap / llms.txt / MCP lists a gated article | That face only emits a gated record when preview: true (shell fields, no body). Omit preview to keep it off discovery |
Limits
- v1 admits
guest/member/admin/ownerfrom DID Connect. Other role names can be written and are shown in the gate copy; Connect does not currently issue them. - Verifiable-credential roles are not an admit path. Track arc#4056.
- Item-local media and collection RSS use the same admit rule. A teaser never keeps a media block (the file would 404).
- Shared public cache is only allowed for a full anonymous-open page.
index.access.jsonnext to prerendered HTML is not a public URL. A GET of that path is 404.
Resolution order, accessPaths syntax, HTTP headers, and discovery rules: Content access reference. Gate markup: access-gate.