Access & sharing
The unified visibility model for published entities — public, workspace, and private tiers plus the per-user sharedWith ACL, and the /p/{type}/{slug} reader URL.
Every publishable entity in Nova — documents, threads, skills, flows, agents, composio-presets, and app artifacts — shares one access model. A single pure function, resolveEntityAccess() (lib/library/access.ts), decides "who may read this", and both the public reader route and the app-artifact API delegate to it so they can never drift apart.
The shareable URL
Published entities are read at:
/p/{type}/{slug}{type} is a one-letter publishable-type code:
| Code | Entity |
|---|---|
d | document |
t | thread |
s | skill |
f | flow |
a | agent |
c | composio-preset |
r | artifact |
The slug is only a locator — it resolves the record, but access is then decided from the entity's current stored visibility. Revoking access is a visibility change, not a slug change. Document URLs additionally accept ?v=<n> to pin to a previously-published snapshot. All reader pages are marked noindex.
Visibility tiers
Visibility is a strict ladder, most-open first: public ⊇ workspace ⊇ private. The model is default-deny — any unrecognized tier denies.
| Tier | Anonymous (no session) | Authenticated member | Rule |
|---|---|---|---|
public | ✅ can view | ✅ can view | Readable by anyone, including logged-out visitors. |
workspace | ❌ | ✅ can view | Any valid session. Nova is a single-workspace product, so "has a verified session" means "is a workspace member". |
private | ❌ | Owner or shared email only | Readable only by the owner (uid === ownerId) or an email in sharedWith. |
The sharedWith ACL
For private entities, access can be extended to specific people via sharedWith — a list of entries keyed by email:
sharedWith: [{ email: "teammate@example.com", role: "viewer" }]For the read decision only email matters; emails are compared case-insensitively. The role field (viewer/editor) is irrelevant to read access. ownerId and sharedWith are consulted only for the private tier — they are ignored for public and workspace.
How a request is gated
- The reader resolves the slug to an entity and reads its current visibility.
resolveEntityAccess({ visibility, ownerId, sharedWith }, session)returns{ allowed }.- If allowed, the per-type renderer produces a read-only public view; otherwise the reader returns not-found / denied.
Because the gate is a single pure function shared by every surface, a new publishable type or a new reader inherits the exact same rules with no extra wiring.
App artifacts & prototypes
How Nova composes, renders, publishes, and shares interactive HTML app artifacts — the chrome-at-render-time model and the sandboxed inline-JS runtime.
Browser automation & QA (Browserbase)
How any Nova agent drives a real remote browser via the browser_* tools — navigate, observe, act, extract, screenshot — plus the captcha + residential-proxy setup, live-view, session reuse across HITL pauses, and how to point this at QA/verification (nova-qa) not just directory submissions.