diff --git a/CONTEXT.md b/CONTEXT.md
index af551d99..0333cbdf 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -241,7 +241,7 @@ _Avoid_: Token format, credential shape, credential prefix, bearer credential fo
**Access Link Signed URL**:
-The shareable URL form of an **Access Link**, shaped `https://app.agent-paste.sh/al/{publicId}#{blob}` where `blob` is a base64url-encoded binary payload containing the signing-key generation, expiration, allowed scopes, and HMAC signature. The payload is carried in the URL fragment so it never reaches any server-side log, and the signature is the credential — the `access_links` row holds no secret material. An authorized **Workspace Member** or **Agent Credential** with read and share **Scopes** mints a fresh URL on demand; re-minting produces a new URL with a new expiration.
+The shareable URL form of an **Access Link**, shaped `https://app.agent-paste.sh/al/{publicId}#{blob}` where `blob` is a base64url-encoded binary payload containing the signing-key generation, expiration, allowed scopes, and HMAC signature. The payload is carried in the URL fragment so it never reaches any server-side log, and the signature is the credential — the `access_links` row holds no secret material. An authorized **Workspace Member** or **Agent Credential** with read and publish **Scopes** mints a fresh URL on demand; re-minting produces a new URL with a new expiration.
_Avoid_: link token, access link secret, credential
@@ -254,15 +254,15 @@ _Avoid_: Owner, author
A named permission that authorizes an actor to perform a class of action within a **Workspace**. There is exactly one **Scope** vocabulary, shared verbatim by the API and the MCP surface (no per-surface names, no translation layer to keep in sync). The three **Scopes** are:
- `read` — view your own **Artifacts**, **Revisions**, and **Access Links**.
-- `publish` — change your own content: create, revise, and delete **Artifacts**, and manage public access to your own **Artifact** (make it public, list, and revoke its **Access Links**). This is the full agent write surface.
-- `admin` — a **Member-Only Scope** for account/workspace management only (**Agent Credential** lifecycle, **Workspace** settings, **Audit Event** reads, billing). It does **not** grant publishing or public-access actions.
+- `publish` — change your own content: create, revise, and delete **Artifacts**, and manage unauthenticated access to your own **Artifact** (share, list, and revoke its **Access Links**; in the planned **Public Artifact** model, select or clear its **Public Version**). This is the full agent write surface.
+- `admin` — a **Member-Only Scope** for account/workspace management only (**Agent Credential** lifecycle, **Workspace** settings, **Audit Event** reads, billing). It does **not** grant content or unauthenticated access actions.
A **Workspace Member** authenticated for direct workspace control (the dashboard) is implicitly granted every **Scope**, including `admin`. An **Agent Credential** (created by `agent-paste login`) and an MCP member's delegated set are capped at `publish` and `read` (never `admin`), so every agent surface is structurally below the dashboard ceiling. A member's MCP **Scopes** are their stored API **Scopes** verbatim, derived in `api`, never carried in the OAuth token (ADR 0079).
_Avoid_: Role, capability, write/share (old MCP-only names — unified into `publish`)
**Member-Only Scope**:
-A **Scope** that only a **Workspace Member** can hold via direct workspace authentication (the dashboard); it cannot be granted to an **Agent Credential** and cannot be carried by tokens issued for delegated agent surfaces such as the CLI or MCP. The only **Member-Only Scope** is `admin`: it authorizes **Agent Credential** lifecycle management, **Audit Event** reads, **Workspace** settings, and billing. It is distinct from `publish`, which covers all content and public-access actions on your own **Artifacts** and is available to agents.
+A **Scope** that only a **Workspace Member** can hold via direct workspace authentication (the dashboard); it cannot be granted to an **Agent Credential** and cannot be carried by tokens issued for delegated agent surfaces such as the CLI or MCP. The only **Member-Only Scope** is `admin`: it authorizes **Agent Credential** lifecycle management, **Audit Event** reads, **Workspace** settings, and billing. It is distinct from `publish`, which covers content changes and unauthenticated sharing actions on your own **Artifacts** and is available to agents.
_Avoid_: Admin scope, restricted scope
@@ -327,13 +327,13 @@ _Avoid_: Remote localStorage, app database, permanent storage
**Private Link**:
-The login-walled clean viewer (`/v/`) for a **Workspace Member**, returned by every **Publish** as `private_url`. No management chrome. It is **permanent and stable**: the URL is built only from the **Artifact** id with no token, signature, or **Expiration** baked in, and `add_revision` republishes into the same id, so the same link keeps working and live-updates to the latest **Published Revision** — it never changes when content is revised. It is **member-only and always private**: there is no public mode and **Publish** never grants public access. It stops resolving only when the **Artifact** itself is gone (deleted or swept by **Auto Deletion**), which is a property of the **Artifact**'s lifetime, not the link. To hand the same content to someone without a login, the **Member** mints a separate, revocable **Share Link**.
+The login-walled clean viewer (`/v/`) for a **Workspace Member**, returned by every **Publish** as `private_url`. No management chrome. It is **permanent and stable**: the URL is built only from the **Artifact** id with no token, signature, or **Expiration** baked in, and `add_revision` republishes into the same id, so the same link keeps working and live-updates to the latest **Published Revision** — it never changes when content is revised. It is **member-only and always private**: there is no public mode and **Publish** never grants unauthenticated access. It stops resolving only when the **Artifact** itself is gone (deleted or swept by **Auto Deletion**), which is a property of the **Artifact**'s lifetime, not the link. To hand the same content to someone without a login, the **Member** mints a separate, revocable **Share Link**.
_Avoid_: Artifact URL, console link, /artifacts page, dashboard link, permalink (it is stable, but say "permanent member link" not "permalink", which we reserve against for public links)
**Access Link**:
A revocable, unlisted, high-entropy grant for reading an **Artifact** without tenant authentication. **Share Links** and **Revision Links** are Access Link types. An Access Link is the durable grant; an **Access Link Signed URL** is the URL string minted from that grant.
-_Avoid_: Artifact URL, content URL, dashboard URL
+_Avoid_: Public Link, Artifact URL, content URL, dashboard URL
**Access Link Lockdown**:
@@ -342,13 +342,48 @@ _Avoid_: Disable sharing, private mode, emergency revoke
**Platform Lockdown**:
-A platform-initiated state that blocks all link resolution for either a single **Artifact** or an entire **Workspace**, applied by the operator to respond to abuse reports, takedown requests, or external safety flags. A **Workspace**-scoped **Platform Lockdown** also suspends every **Agent Credential** in the **Workspace**.
+A platform-initiated state that blocks all platform-controlled link resolution, public resolution, and public asset access for either a single **Artifact** or an entire **Workspace**, applied by the operator to respond to abuse reports, takedown requests, or external safety flags. It is the hard public takedown path for **Public Artifacts**: unlike **Public Offline**, it blocks the **Public Resolver** and **Public Version Assets** and uses cache purge or deny controls where available. A **Workspace**-scoped **Platform Lockdown** also suspends every **Agent Credential** in the **Workspace**.
_Avoid_: Suspension, ban, freeze, admin lock
**Share Link**:
-A type of **Access Link** that resolves to the latest **Published Revision** of an **Artifact**. It opens the **Artifact Viewer** and can receive **Publish Updates**. It is the **public** counterpart to the **Private Link**: a no-login URL, **off by default**, created only by the explicit make-public step (`make_public` on MCP, `agent-paste make-public` on the CLI), which mints or reuses the **one** Share Link an **Artifact** has and returns its **Access Link Signed URL**. **Publish** never creates one. It is **revocable at any time** (`revoke_access_link`) and may expire, so avoid calling it a permalink; revoking it kills public access without touching the **Artifact**, its data, its **Revisions**, or its **Private Link**.
-_Avoid_: Artifact Console, public app link, permalink, Revision Content URL
+A type of **Access Link** that resolves to the latest **Published Revision** of an **Artifact**. It opens the **Artifact Viewer** and can receive **Publish Updates**. It is the unlisted counterpart to the **Private Link**: a no-login URL, **off by default**, created only by an explicit sharing step, which mints or reuses the **one** Share Link an **Artifact** has and returns its **Access Link Signed URL**. **Publish** never creates one. It is the control-oriented unauthenticated path: it does not create a **Public URL** or **Public Version Assets** and is not the aggressive edge-cache surface. It is **revocable at any time** (`revoke_access_link`) and may expire, so avoid calling it public or a permalink; revoking it kills unlisted access without touching the **Artifact**, its data, its **Revisions**, or its **Private Link**.
+_Avoid_: Public Link, Artifact Console, public app link, permalink, Revision Content URL
+
+
+**Public Artifact**:
+An **Artifact** intentionally configured for broad unauthenticated viewing through a **Public URL**, not merely shared through a high-entropy **Access Link Signed URL**. This is planned domain language from ADR 0087, not the shipped Share Link behavior described by ADR 0086. It is the distribution-oriented unauthenticated path: the first public action atomically allocates its **Public ID** and selects its initial **Public Version**. When online, it resolves through a selected **Public Version** and its **Public Version Assets** are designed for broad edge caching. When **Public Offline**, the permalink is retained but broad public viewing is unavailable.
+_Avoid_: Share Link, Access Link, Unlisted Link, secret link
+
+
+**Public Version**:
+The **Published Revision** currently selected for broad unauthenticated viewing of a **Public Artifact**.
+_Avoid_: Latest Revision, Live Version, public pointer
+
+
+**Public Offline**:
+The reversible soft-control state where a **Public Artifact** keeps its **Public URL** and **Public ID** but has no selected **Public Version**, so broad unauthenticated public resolution is temporarily unavailable. Selecting a **Public Version** brings it back online without changing the permalink. It is not a hard takedown guarantee for already-cached **Public Version Assets**.
+_Avoid_: Delete, revoke, Access Link Lockdown, Platform Lockdown, rotate Public ID
+
+
+**Public URL**:
+The stable browser URL for broad unauthenticated viewing of a **Public Artifact**, shaped `/p/{publicId}`. It remains reserved while the **Public Artifact** is **Public Offline** and resolves through the **Public Resolver**.
+_Avoid_: Public Link, Share Link, Access Link Signed URL, Revision Content URL
+
+
+**Public ID**:
+The high-entropy identifier carried by a **Public URL**, distinct from the **Artifact** id.
+_Avoid_: Artifact id, slug, title, human-readable name
+
+
+**Public Resolver**:
+The small mutable resolution layer behind a **Public URL**. It maps a **Public ID** to either the selected **Public Version** or **Public Offline** and must change quickly when the public pointer changes. It is not the long-lived cache boundary for **Untrusted Content**.
+_Avoid_: CDN asset, static page, public file, Public Version Asset
+
+
+**Public Version Asset**:
+An immutable **Untrusted Content** response for one file path in the **Published Revision** selected by a **Public Version**. It can be cached aggressively because selecting a different **Public Version** or going **Public Offline** changes the **Public Resolver** instead of mutating the asset.
+_Avoid_: live asset, latest asset, mutable public file
**Expiration**:
@@ -479,9 +514,9 @@ _Avoid_: tenant filter, RLS shim, scoped map
- Only one finalized **Draft Revision** can wait for **Publish** on an **Artifact**
- Finalizing an **Upload Session** fails when another **Draft Revision** is already waiting for **Publish**
- **Draft Revisions** are visible to **Workspace Members** in management surfaces
-- **Draft Revisions** are visible to **Agent Credentials** with write **Scope**
+- **Draft Revisions** are visible to **Agent Credentials** with publish **Scope**
- A read **Scope** does not grant access to **Draft Revisions**
-- **Agent Credentials** with write **Scope** can discard **Draft Revisions**
+- **Agent Credentials** with publish **Scope** can discard **Draft Revisions**
- Discarding a **Draft Revision** does not affect the **Published Revision**
- **Private Links** and **Access Links** never resolve to **Draft Revisions**
- **Private Links** and **Access Links** do not resolve for an **Unpublished Artifact**
@@ -527,15 +562,16 @@ _Avoid_: tenant filter, RLS shim, scoped map
- **Access Link Lockdown** does not prevent changing **Access Link** **Expiration**
- A **Platform Lockdown** is operator-initiated and cannot be set or lifted by a **Workspace Member** or an **Agent Credential**
- A **Platform Lockdown** has scope `Artifact` or `Workspace`
-- A **Platform Lockdown** at `Artifact` scope blocks the **Private Link** and every **Access Link** for one **Artifact**
-- A **Platform Lockdown** at `Workspace` scope blocks the **Private Link** and **Access Link** resolution for every **Artifact** in the **Workspace**
+- A **Platform Lockdown** at `Artifact` scope blocks the **Private Link**, every **Access Link**, the **Public Resolver**, and **Public Version Assets** for one **Artifact**
+- A **Platform Lockdown** at `Workspace` scope blocks **Private Link**, **Access Link**, **Public Resolver**, and **Public Version Asset** access for every **Artifact** in the **Workspace**
- A **Platform Lockdown** at `Workspace` scope suspends every **Agent Credential** in the **Workspace**
- A **Platform Lockdown** is reversible by the operator
- A **Platform Lockdown** does not delete bytes or **Revisions**
+- A **Platform Lockdown** uses cache purge and deny controls for **Public Version Assets** where available
- A **Platform Lockdown** does not auto-expire in the MVP
- A **Platform Lockdown** creates an **Audit Event** visible to **Workspace Members**
- A **Platform Lockdown** is not exposed through the public API, SDK, or CLI
-- A **Platform Lockdown** is distinct from **Access Link Lockdown**: it is operator-initiated, also blocks the **Private Link**, and at `Workspace` scope suspends **Agent Credentials**
+- A **Platform Lockdown** is distinct from **Access Link Lockdown**: it is operator-initiated, also blocks the **Private Link**, **Public Resolver**, and **Public Version Assets**, and at `Workspace` scope suspends **Agent Credentials**
- An **Operator** is a **Workspace Member** whose authenticated email is on the platform operator allowlist
- An **Operator** acts with platform-wide authority only on operator-only routes; on every other route the identity is a normal **Workspace Member**
- An **Operator** identity cannot be assumed by an **Agent Credential**: operator-only routes reject **Agent Credential** authentication
@@ -544,23 +580,53 @@ _Avoid_: tenant filter, RLS shim, scoped map
- A **Platform Lockdown** can only be set or lifted by an **Operator**
- A **Share Link** resolves to the latest **Published Revision** of an **Artifact**
- A **Share Link** is an **Access Link** type, not a synonym for **Access Link**
-- The signed URL minted from a **Share Link** is the public, no-login handoff produced by the make-public step
+- The signed URL minted from a **Share Link** is an unlisted, no-login handoff produced by an explicit sharing step
- A **Revision Link** resolves to exactly one **Revision**
- A **Revision Link** can continue resolving to an older **Revision** after a newer **Revision** is published
- A **Revision Link** can be revoked without deleting its **Revision**
- **Retention** can make a **Revision Link** stop resolving without revoking it
- The base REST/CLI **Publish Result** includes the **Artifact** id, **Revision** id, authenticated **Private Link** (`private_url`), direct signed **Revision Content URL**, public **Agent View** URL, expiration, and **Bundle Availability**
- **Publish** is content-only and private-first on every surface (CLI, MCP, REST): it accepts no visibility input and returns exactly one link, the **Private Link** as `private_url`. There is no `share`/`--share` input and no `shared` output bit
-- A **Share Link** is created only by the explicit make-public step (`make_public` on MCP, `agent-paste make-public` on the CLI), never by **Publish**
+- A **Share Link** is created only by an explicit sharing step, never by **Publish**
- MCP publish tools do not create a **Revision Link** unless the agent explicitly calls **Create Revision Link**
- **Access Link Lockdown** blocks creating or resolving **Access Links**; **Publish** is content-only and never an **Access Link** operation
- **Revision Links** are not created for **Draft Revisions**
- **Share Links** always resolve to the latest **Published Revision**
- Additional **Revision Links** can be created for an already published **Revision**
- Additional **Revision Links** can target any retained published **Revision**
-- **Publish** never creates a **Share Link**; making an **Artifact** public is the separate `make_public` step, which mints or reuses the one revocable **Share Link** and returns its **Access Link Signed URL**
-- The make-public step fails if the **Share Link** cannot be created (for example under **Access Link Lockdown**) without affecting the **Published Revision**
-- A public **Artifact** keeps one stable **Share Link**: `make_public` reuses an active **Share Link** before creating one, so the public URL stays the same across revisions and live-updates to the latest **Published Revision**
+- **Publish** never creates a **Share Link**; sharing an **Artifact** without login is a separate explicit step, which mints or reuses the one revocable **Share Link** and returns its **Access Link Signed URL**
+- The sharing step fails if the **Share Link** cannot be created (for example under **Access Link Lockdown**) without affecting the **Published Revision**
+- An unlisted shared **Artifact** keeps one stable **Share Link**: the sharing step reuses an active **Share Link** before creating one, so the unlisted URL stays the same across revisions and live-updates to the latest **Published Revision**
+- A **Share Link** is the unauthenticated path to choose when revocation and takedown control matter more than broad distribution
+- A **Share Link** does not create a **Public URL** or **Public Version Assets**
+- A **Share Link** is not the aggressive edge-cache surface
+- **Public Artifact**, **Public Version**, **Public URL**, **Public ID**, **Public Resolver**, **Public Version Asset**, and **Public Offline** are planned public-distribution terms from ADR 0087; shipped CLI/MCP behavior still creates **Share Links** for no-login latest-moving handoff until the implementation specs and routes change
+- A **Public Artifact** has zero or one **Public Version** at a time
+- A **Public Artifact** has one stable **Public URL**
+- A **Public Artifact** is created by the first public action on an **Artifact**
+- The first public action atomically allocates the **Public ID**, creates the **Public URL**, and selects the initial **Public Version**
+- There is no reserved **Public URL** state before the first **Public Version** is selected
+- A **Public Artifact** is the unauthenticated path to choose when broad distribution and traffic spikes matter more than strict cache-level revocation
+- A **Public URL** carries a **Public ID**, not the **Artifact** id
+- A **Public URL** has no slug
+- A **Public URL** resolves through the **Public Resolver**
+- The **Public Resolver** resolves through the selected **Public Version**
+- A **Public Version** resolves to exactly one **Published Revision**
+- **Public Version Assets** are immutable for one **Published Revision**
+- **Public Version Assets** are the aggressive edge-cache surface for broad public traffic
+- Selecting a **Public Version** is the action that makes that **Published Revision** eligible for **Public Version Assets**
+- **Publish**, **Share Link** creation, and **Revision Link** creation do not make **Untrusted Content** eligible for **Public Version Assets**
+- The **Public Resolver** is not cached aggressively; it must use short cache lifetime or explicit purge on **Public Version** and **Public Offline** changes
+- A **Public Artifact** with no selected **Public Version** is **Public Offline**
+- **Public Offline** preserves the **Public URL** and **Public ID**
+- **Public Offline** prevents the **Public URL** from resolving broad public content without affecting the **Artifact**, **Revisions**, **Private Link**, **Share Link**, or other **Access Links**
+- **Public Offline** is a soft public-distribution control, not a hard takedown guarantee for already-cached **Public Version Assets**
+- Selecting a **Public Version** brings a **Public Offline** **Public Artifact** back online without changing the **Public URL**
+- **Publish Updates** do not advance a **Public Version**
+- Selecting a new **Public Version** is an explicit action, not an automatic side effect of **Publish**
+- An **Agent Credential** requires publish **Scope** to select a **Public Version**
+- An **Agent Credential** requires publish **Scope** to put a **Public Artifact** **Public Offline**
+- Moving a **Public Artifact** to a new **Public Version** does not change its **Public URL**, **Private Link**, or **Share Link**
- The CLI and MCP **Publish Result** both surface one `private_url`; the CLI still carries the full **Publish Result** (IDs, `private_url`, exact **Revision Content URL**, **Agent View** URL, **Bundle** status) in its JSON for automation
- A **Publish Result** includes separate human-view links and agent-view links
- A **Publish Result** includes **Bundle Availability** even when the **Bundle** is not ready
@@ -636,7 +702,8 @@ _Avoid_: tenant filter, RLS shim, scoped map
- An **Audit Event** has exactly one **Change Summary**
- **Audit Retention** is separate from **Usage Policy**
- **Audit Events** are visible only to **Workspace Members** in the MVP
-- **Unpublished Artifact** creation, **Publish**, **Deletion**, **Draft Revision** discard, **Retention** removals, **Display Metadata** changes, **Safety Warnings**, durable **Usage Policy** enforcement, **Agent Credential** changes, **Agent Credential Revocation**, **Access Link** changes, and **Access Link Lockdown** changes create **Audit Events**
+- **Unpublished Artifact** creation, **Publish**, **Deletion**, **Draft Revision** discard, **Retention** removals, **Display Metadata** changes, **Safety Warnings**, durable **Usage Policy** enforcement, **Agent Credential** changes, **Agent Credential Revocation**, **Access Link** changes, **Access Link Lockdown** changes, **Public Version** changes, and **Public Offline** changes create **Audit Events**
+- **Public Version** and **Public Offline** **Audit Events** include a redacted **Change Summary** with the **Public ID**, previous **Published Revision** id or null, new **Published Revision** id or null, actor, and calling surface
- Routine **Upload Cleanup** does not create **Audit Events**
- **Upload Cleanup** creates **Audit Events** when it removes stale **Unpublished Artifact** management state
- A **Workspace** can have zero or more **Agent Credentials**
@@ -657,15 +724,14 @@ _Avoid_: tenant filter, RLS shim, scoped map
- **Agent Credential Revocation** stops future use of the **Agent Credential**
- **Agent Credential Revocation** does not revoke **Artifacts** or **Access Links** created with it
- An **Agent Credential** requires a read **Scope** to read private **Artifacts**
-- An **Agent Credential** requires a share **Scope** to manage **Access Links**
-- An **Agent Credential** requires read and share **Scopes** to create **Access Links**
-- An **Agent Credential** requires read and share **Scopes** to mint **Access Link Signed URLs**
-- An **Agent Credential** requires a share **Scope** to change **Access Link Lockdown**
-- A share **Scope** does not imply a read **Scope**
-- A write **Scope** does not imply a share **Scope**
-- **Publish** requires write and read **Scopes**; requested **Access Link** creation additionally requires share **Scope**
-- **Upload Sessions** require a write **Scope**, not a share **Scope**
-- A write-only **Agent Credential** can prepare a **Draft Revision** for another actor to **Publish**
+- An **Agent Credential** requires publish **Scope** to manage **Access Links**
+- An **Agent Credential** requires read and publish **Scopes** to create **Access Links**
+- An **Agent Credential** requires read and publish **Scopes** to mint **Access Link Signed URLs**
+- An **Agent Credential** requires publish **Scope** to change **Access Link Lockdown**
+- A publish **Scope** does not imply a read **Scope**
+- The **Publish** action requires publish and read **Scopes**
+- **Upload Sessions** require publish **Scope**, not read **Scope**
+- A publish-only **Agent Credential** can prepare a **Draft Revision** for another actor to **Publish**
- A **Creator** is recorded for an **Artifact** but does not own it
- A **Creator** is recorded before first **Publish** when an **Unpublished Artifact** is created
- A **Creator** remains recorded after **Agent Credential Revocation** or **Agent Credential** **Expiration**
@@ -691,7 +757,7 @@ _Avoid_: tenant filter, RLS shim, scoped map
- Any **Agent Credential** with the right **Scope** in the owning **Workspace** can update **Display Metadata**
- Updating a known **Artifact** does not require a read **Scope**
- Updating **Display Metadata** for a known **Artifact** does not require a read **Scope**
-- **Publish** requires write and read **Scopes**; share **Scope** is required only when the actor requests a **Share Link**
+- The **Publish** action requires publish and read **Scopes**
- An **Artifact** contains **Untrusted Content**
- An **Artifact** can have zero or more **Safety Warnings**
- A **Revision** can have zero or more **Safety Warnings**
@@ -793,6 +859,34 @@ _Avoid_: tenant filter, RLS shim, scoped map
> **Domain expert:** "Yes — JavaScript is allowed but remains **Untrusted Content**."
> **Dev:** "When an **Artifact** is updated, should existing links change?"
> **Domain expert:** "No — **Private Links** and **Share Links** stay stable and show the latest **Published Revision**."
+> **Dev:** "Is a **Share Link** a public URL?"
+> **Domain expert:** "No — a **Share Link** is unlisted access; a **Public Artifact** is intentionally published for broad viewing."
+> **Dev:** "Does **Publish** automatically update a **Public Artifact**?"
+> **Domain expert:** "No — a **Public Artifact** changes only when a new **Public Version** is selected."
+> **Dev:** "Does selecting a new **Public Version** change the **Public URL**?"
+> **Domain expert:** "No — the **Public URL** is stable; it resolves through whichever **Public Version** is currently selected."
+> **Dev:** "Does the **Public URL** expose the **Artifact** id?"
+> **Domain expert:** "No — it carries a separate **Public ID**."
+> **Dev:** "Should a **Public URL** include a title slug?"
+> **Domain expert:** "No — the **Public ID** is the canonical URL segment."
+> **Dev:** "Can an agent reserve a **Public URL** before choosing a **Public Version**?"
+> **Domain expert:** "No — the first public action atomically creates the **Public URL** and selects the initial **Public Version**."
+> **Dev:** "Can an agent move a **Public Artifact** to the latest **Published Revision** on every publish?"
+> **Domain expert:** "Only when it takes the explicit action to select a new **Public Version**; ordinary **Publish** does not move public viewing."
+> **Dev:** "Is selecting a **Public Version** human-only?"
+> **Domain expert:** "No — an **Agent Credential** can select a **Public Version** with publish **Scope**."
+> **Dev:** "If a public page needs to come down briefly, do we delete or rotate the **Public URL**?"
+> **Domain expert:** "No — put the **Public Artifact** **Public Offline**. The **Public URL** and **Public ID** stay reserved, and selecting a **Public Version** brings it back online."
+> **Dev:** "Does **Public Offline** affect private or unlisted access?"
+> **Domain expert:** "No — it only stops the **Public URL** from serving broad public content. **Private Links**, **Share Links**, and other **Access Links** are separate controls."
+> **Dev:** "Do we cache the stable **Public URL** hard?"
+> **Domain expert:** "No — cache immutable **Public Version Assets** aggressively. Keep the **Public Resolver** short-lived or explicitly purged so pointer changes and **Public Offline** take effect quickly."
+> **Dev:** "Should I make something **Public** if I might need strict takedown later?"
+> **Domain expert:** "No — use a **Share Link** when revocation and takedown control matter. Use **Public** when broad distribution and traffic-spike handling are the priority."
+> **Dev:** "Does **Public Offline** mean every cached public asset disappears immediately?"
+> **Domain expert:** "No — it is a soft public-distribution control for the **Public Resolver**, not a hard takedown guarantee for already-cached **Public Version Assets**."
+> **Dev:** "What is the hard takedown path for a **Public Artifact**?"
+> **Domain expert:** "**Platform Lockdown**. It is operator-only and blocks the **Public Resolver** and **Public Version Assets**, using cache purge and deny controls where available."
> **Dev:** "Can a **Private Link** be pinned to an older **Revision**?"
> **Domain expert:** "No — a **Private Link** always follows the latest **Published Revision**."
> **Dev:** "Can viewers see files while an update is still uploading?"
@@ -804,9 +898,9 @@ _Avoid_: tenant filter, RLS shim, scoped map
> **Dev:** "Can **Workspace Members** see a **Draft Revision**?"
> **Domain expert:** "Yes — management surfaces can show drafts, but viewing links remain published-only."
> **Dev:** "Can a read-only **Agent Credential** see **Draft Revisions**?"
-> **Domain expert:** "No — draft access is a management capability tied to write **Scope**."
+> **Domain expert:** "No — draft access is a management capability tied to publish **Scope**."
> **Dev:** "Can an agent discard a **Draft Revision**?"
-> **Domain expert:** "Yes — an **Agent Credential** with write **Scope** can discard it without affecting the **Published Revision**."
+> **Domain expert:** "Yes — an **Agent Credential** with publish **Scope** can discard it without affecting the **Published Revision**."
> **Dev:** "Does discarding a **Draft Revision** create an **Audit Event**?"
> **Domain expert:** "Yes — finalized draft state is durable management state."
> **Dev:** "Can only the original **Creator** update an **Artifact**?"
@@ -840,7 +934,7 @@ _Avoid_: tenant filter, RLS shim, scoped map
> **Dev:** "Can an agent change **Access Link** **Expiration** during **Access Link Lockdown**?"
> **Domain expert:** "Yes — expiration can change, but lockdown still prevents access."
> **Dev:** "Can an agent publish a new **Revision** while **Access Link Lockdown** is active?"
-> **Domain expert:** "Yes — **Publish** is content-only and can create a new **Revision**. Lockdown blocks creating new **Access Links**, so the separate make-public step (the **Share Link**) and explicit **Revision Links** fail while it is active."
+> **Domain expert:** "Yes — **Publish** is content-only and can create a new **Revision**. Lockdown blocks creating new **Access Links**, so the separate sharing step for a **Share Link** and explicit **Revision Links** fail while it is active."
> **Dev:** "Can another agent use a **Share Link** without an **Agent Credential**?"
> **Domain expert:** "Yes — a **Share Link** grants read-only access to the **Agent View** and published files."
> **Dev:** "Can another agent use a **Revision Link** to inspect an exact **Revision**?"
@@ -894,21 +988,21 @@ _Avoid_: tenant filter, RLS shim, scoped map
> **Dev:** "Do **Unpublished Artifacts** count against creation limits?"
> **Domain expert:** "Yes — **Usage Policy** controls their creation too."
> **Dev:** "What is the simplest way for an agent to share a folder?"
-> **Domain expert:** "Two steps. **Publish** the folder — it is content-only and private, and returns the **Private Link** as `private_url`. Then, only if the user wants a public no-login link, call the make-public step (`make_public` on MCP, `agent-paste make-public` on the CLI). That mints or reuses the **Artifact**'s one **Share Link** and returns its **Access Link Signed URL**."
+> **Domain expert:** "Two steps. **Publish** the folder — it is content-only and private, and returns the **Private Link** as `private_url`. Then, only if the user wants an unlisted no-login link, run the explicit sharing step. That mints or reuses the **Artifact**'s one **Share Link** and returns its **Access Link Signed URL**."
> **Dev:** "What does an agent get back after **Publish**?"
> **Domain expert:** "One `private_url` — the login-walled `/v/` viewer. The CLI also carries the full **Publish Result** in its JSON — IDs, `private_url`, direct **Revision Content URL**, **Agent View** URL, **Bundle** status, and any **Safety Warnings** — for automation. There is no `shared` bit and no `share` input."
> **Dev:** "Does **Publish** make an **Artifact** shareable by default?"
-> **Domain expert:** "No — **Publish** is content-only and private on every surface; nothing is reachable without login. Going public is the separate `make_public` step, and `private_url` is always the authenticated **Private Link**."
-> **Dev:** "What if the **Share Link** cannot be created during the make-public step?"
-> **Domain expert:** "`make_public` fails without touching the **Published Revision** or its **Private Link**."
+> **Domain expert:** "No — **Publish** is content-only and private on every surface; nothing is reachable without login. Unlisted sharing is a separate explicit step, and `private_url` is always the authenticated **Private Link**."
+> **Dev:** "What if the **Share Link** cannot be created during the sharing step?"
+> **Domain expert:** "The sharing step fails without touching the **Published Revision** or its **Private Link**."
> **Dev:** "Can a **Share Link** be pinned to the current **Published Revision**?"
> **Domain expert:** "No — **Share Links** always follow the latest **Published Revision**; use a **Revision Link** to pin one."
> **Dev:** "Can an agent create a **Share Link** without publishing again?"
-> **Domain expert:** "Yes — the make-public step works on any already-published **Artifact**; it never needs a re-publish."
+> **Domain expert:** "Yes — the sharing step works on any already-published **Artifact**; it never needs a re-publish."
> **Dev:** "Can an agent create a **Share Link** while **Access Link Lockdown** is active?"
> **Domain expert:** "No — lockdown prevents creating new **Access Links**."
> **Dev:** "Does **Publish** create a pinned link for the exact **Revision**?"
-> **Domain expert:** "No — **Publish** returns only the latest-following **Private Link**. For a public latest-moving link an agent runs the make-public step to mint the **Share Link**; for a pinned URL of the exact **Revision** it calls **Create Revision Link**."
+> **Domain expert:** "No — **Publish** returns only the latest-following **Private Link**. For an unlisted latest-moving link an agent runs the sharing step to mint the **Share Link**; for a pinned URL of the exact **Revision** it calls **Create Revision Link**."
> **Dev:** "Can an agent create another **Revision Link** for the same **Revision**?"
> **Domain expert:** "Yes — additional **Revision Links** can be created for separate audiences."
> **Dev:** "Can an agent create an additional **Revision Link** during **Access Link Lockdown**?"
@@ -943,22 +1037,20 @@ _Avoid_: tenant filter, RLS shim, scoped map
> **Domain expert:** "Yes — credential lifecycle changes are security-relevant."
> **Dev:** "Can a publishing **Agent Credential** read private **Artifacts**?"
> **Domain expert:** "Only if it has a read **Scope**."
-> **Dev:** "Can any write-capable **Agent Credential** manage **Access Links**?"
-> **Domain expert:** "No — managing **Access Links** requires a share **Scope**."
-> **Dev:** "Can a share-only **Agent Credential** create **Access Links**?"
-> **Domain expert:** "No — minting **Access Link Signed URLs** requires both read and share **Scopes**."
-> **Dev:** "Can a share-only **Agent Credential** mint **Access Link Signed URLs**?"
-> **Domain expert:** "No — minting **Access Link Signed URLs** requires both read and share **Scopes**."
-> **Dev:** "Does share **Scope** include read **Scope**?"
+> **Dev:** "Can any publishing **Agent Credential** manage **Access Links**?"
+> **Domain expert:** "Yes — publish **Scope** manages **Access Links**."
+> **Dev:** "Can a publish-only **Agent Credential** create **Access Links**?"
+> **Domain expert:** "No — creating **Access Links** requires both read and publish **Scopes**."
+> **Dev:** "Can a publish-only **Agent Credential** mint **Access Link Signed URLs**?"
+> **Domain expert:** "No — minting **Access Link Signed URLs** requires both read and publish **Scopes**."
+> **Dev:** "Does publish **Scope** include read **Scope**?"
> **Domain expert:** "No — **Scopes** are independent."
-> **Dev:** "Does write **Scope** include share **Scope**?"
-> **Domain expert:** "No — **Publish** requires write and read. Share is a separate power required only for creating or minting **Access Links**."
-> **Dev:** "Can an **Agent Credential** publish with write **Scope** but no read or share **Scope**?"
-> **Domain expert:** "No — **Publish** requires write and read **Scopes**. It requires share **Scope** only when a **Share Link** is requested."
-> **Dev:** "Does creating an **Upload Session** require a share **Scope**?"
-> **Domain expert:** "No — **Upload Sessions** create drafts, so write **Scope** is enough."
+> **Dev:** "Can an **Agent Credential** publish with publish **Scope** but no read **Scope**?"
+> **Domain expert:** "No — the **Publish** action requires publish and read **Scopes**."
+> **Dev:** "Does creating an **Upload Session** require read **Scope**?"
+> **Domain expert:** "No — **Upload Sessions** create drafts, so publish **Scope** is enough."
> **Dev:** "Can one agent upload a draft and another publish it?"
-> **Domain expert:** "Yes — a write-only **Agent Credential** can prepare a **Draft Revision** for another actor to **Publish**."
+> **Domain expert:** "Yes — a publish-only **Agent Credential** can prepare a **Draft Revision** for another actor to **Publish**."
> **Dev:** "Can an **Upload Session** expire?"
> **Domain expert:** "Yes — after **Expiration**, it can no longer be used."
> **Dev:** "Does **Retention** clean up expired **Upload Sessions**?"
@@ -970,11 +1062,11 @@ _Avoid_: tenant filter, RLS shim, scoped map
> **Dev:** "What if **Upload Cleanup** removes a stale **Unpublished Artifact**?"
> **Domain expert:** "That creates an **Audit Event** because management state changed."
> **Dev:** "Can an **Agent Credential** update a known **Artifact** without reading it first?"
-> **Domain expert:** "Yes — update authority comes from the write **Scope**, not the read **Scope**."
+> **Domain expert:** "Yes — update authority comes from the publish **Scope**, not the read **Scope**."
> **Dev:** "Can an **Agent Credential** publish without read **Scope**?"
> **Domain expert:** "No — **Publish** returns a **Revision Content URL** and **Agent View** URL, so read **Scope** is required."
> **Dev:** "Does updating **Display Metadata** require a read **Scope**?"
-> **Domain expert:** "No — write **Scope** is enough for a known **Artifact**."
+> **Domain expert:** "No — publish **Scope** is enough for a known **Artifact**."
> **Dev:** "Can uploaded JavaScript call arbitrary external APIs?"
> **Domain expert:** "Not by default — the **Execution Policy** restricts external network access."
> **Dev:** "Does **Execution Policy** only apply to HTML?"
@@ -1013,6 +1105,8 @@ _Avoid_: tenant filter, RLS shim, scoped map
> **Domain expert:** "No — **Audit Event** reads require a **Member-Only Scope**, which only a dashboard-authenticated **Workspace Member** carries. An **Agent Credential** cannot hold it; CLI and MCP tokens cannot carry it."
> **Dev:** "Do **Access Link** changes create **Audit Events**?"
> **Domain expert:** "Yes — they are unauthenticated access grants, so lifecycle changes are security-relevant."
+> **Dev:** "Do **Public Version** changes and **Public Offline** changes create **Audit Events**?"
+> **Domain expert:** "Yes — they are important public access events. The **Change Summary** records the **Public ID**, old and new **Published Revision** ids or null, actor, and calling surface."
> **Dev:** "Do **Audit Events** store raw uploaded content or secrets?"
> **Domain expert:** "No — they store redacted **Change Summaries**."
> **Dev:** "Does **Publish** wait for deep content scanning?"
diff --git a/docs/adr/0052-agent-view-discovery-from-access-link-signed-urls.md b/docs/adr/0052-agent-view-discovery-from-access-link-signed-urls.md
index 41fa7ade..28b97644 100644
--- a/docs/adr/0052-agent-view-discovery-from-access-link-signed-urls.md
+++ b/docs/adr/0052-agent-view-discovery-from-access-link-signed-urls.md
@@ -2,14 +2,14 @@
Status: Accepted. Renumbered and rewritten from the duplicate ADR 0043 after [ADR 0047](./0047-access-link-signed-url-with-fragment-encoded-payload.md) replaced code-bearing Access Links with fragment-encoded signed URLs. Amended 2026-06-11: Access Link Signed URLs remain the unauthenticated recipient discovery model, but publish surfaces no longer create or return Share Links by default.
-The **Access Link Signed URL** minted from a **Share Link** is the unauthenticated human handoff URL for live-updating Artifact Viewers when a caller explicitly creates a public/shareable link. A receiving agent discovers the **Agent View** from the same **Access Link Signed URL** that a human opens: parse `https://app.agent-paste.sh/al/{publicId}#{blob}`, preserve the fragment, and call `POST /v1/access-links/resolve` with `{ public_id, blob }`. The response is the **Agent View** plus short-lived content-gateway URLs from [ADR 0028](./0028-signed-url-tokens-for-content-gateway-authorization.md). There are no code-scoped bearer routes and no `Link: rel="agent-view"` header on `content` responses, because `content` sees only the derived content token and cannot reconstruct the fragment credential.
+The **Access Link Signed URL** minted from a **Share Link** is the unauthenticated human handoff URL for live-updating Artifact Viewers when a caller explicitly creates an unlisted/shareable link. A receiving agent discovers the **Agent View** from the same **Access Link Signed URL** that a human opens: parse `https://app.agent-paste.sh/al/{publicId}#{blob}`, preserve the fragment, and call `POST /v1/access-links/resolve` with `{ public_id, blob }`. The response is the **Agent View** plus short-lived content-gateway URLs from [ADR 0028](./0028-signed-url-tokens-for-content-gateway-authorization.md). There are no code-scoped bearer routes and no `Link: rel="agent-view"` header on `content` responses, because `content` sees only the derived content token and cannot reconstruct the fragment credential.
## Consequences
- The distributed handoff string is the full **Access Link Signed URL** from ADR 0047, including its fragment. Agents must not drop the fragment when normalizing or logging URLs.
- `POST /v1/access-links/resolve` is the unauthenticated Agent View discovery endpoint. It accepts the `public_id` path segment and fragment `blob`; every invalid, expired, revoked, locked, retained, or deleted case returns the generic `not_found` envelope from [ADR 0036](./0036-error-envelope-and-generic-404-boundary.md).
- `GET /v1/artifacts/{id}/agent-view` remains the authenticated member-or-key-scoped surface and is the publisher's follow-up handle. It is never returned through an unauthenticated surface, so the artifact id stays out of distributed link strings.
-- User-facing **Publish Result** surfaces should return the authenticated **Artifact URL** by default. They return `access_link_url`, the **Access Link Signed URL** minted from a **Share Link**, only after explicit public/shareable link creation. `agent_view_url` is for the publishing actor, not for unauthenticated recipients. Recipients derive their **Agent View** through the resolve endpoint above when they receive an **Access Link Signed URL**.
+- User-facing **Publish Result** surfaces should return the authenticated **Artifact URL** by default. They return `access_link_url`, the **Access Link Signed URL** minted from a **Share Link**, only after explicit unlisted/shareable link creation. `agent_view_url` is for the publishing actor, not for unauthenticated recipients. Recipients derive their **Agent View** through the resolve endpoint above when they receive an **Access Link Signed URL**.
- The CLI and internal `api-client` accept an Access Link Signed URL wherever an artifact read URL is expected. They parse `{ publicId, blob }`, call resolve, and then follow the returned content-gateway URLs.
- The **Content Origin** does not emit `Link: rel="agent-view"` for Access Link responses. A `Link` header would either omit the fragment credential and fail, or reintroduce a server-visible bearer URL, which ADR 0047 explicitly removed.
- No `GET /v1/r/{code}/agent-view` or `GET /v1/s/{code}/agent-view` endpoints are created. The `publicId` alone is log-safe but not a credential.
diff --git a/docs/adr/0079-mcp-scopes-derived-from-member-role-not-workos-token.md b/docs/adr/0079-mcp-scopes-derived-from-member-role-not-workos-token.md
index 2c90febc..ad1b5fc1 100644
--- a/docs/adr/0079-mcp-scopes-derived-from-member-role-not-workos-token.md
+++ b/docs/adr/0079-mcp-scopes-derived-from-member-role-not-workos-token.md
@@ -2,7 +2,7 @@
Status: Accepted. Supersedes the scope-granting mechanism of [ADR 0061](./0061-mcp-worker-with-oauth-only-via-auth0-dcr.md). The MCP transport, OAuth-via-AuthKit, CIMD/DCR registration, resource-indicator audience binding, the twelve-tool surface, and the `write`/`read`/`share` scope vocabulary are all retained from ADR 0061 unchanged. Only the **source** of a caller's granted scopes changes: from a WorkOS-issued `scope` token claim (which AuthKit does not and cannot issue) to a set derived from the caller's **Workspace Member** role inside `api`.
-> **Amendment (private-first publish, [ADR 0086](./0086-private-first-publish.md)):** The `write`/`read`/`share` MCP scope vocabulary referenced throughout this ADR has since been **unified with the API scope vocabulary** (`read`/`publish`/`admin`). There is now **one** scope set shared by `api` and MCP: a member's MCP scopes **are** their stored API scopes verbatim. The translation layer described below (`apiScopesToMcpScopes` / `mcpScopesToApiScopes` in `packages/contracts/src/mcp/scopes.ts`) has been **removed** — there is nothing to map. Scope meaning is now canonical: `read` = view your stuff; `publish` = change your stuff, **including managing an Artifact's own public access** (make_public, list and revoke that Artifact's links — these are `publish`-scope actions, **not** `admin`); `admin` = account/workspace management (API keys, settings, audit, billing). The decision below (scopes are derived from the member, not the WorkOS token; the Worker verifies issuer + audience only and pre-flight-gates against `mcp.whoami`) **still holds** — only the vocabulary and the now-deleted translation step changed. Read references to `write`/`share` below as `publish`/`publish` respectively, and `mcp.whoami` now returns the member's API scopes verbatim.
+> **Amendment (private-first publish, [ADR 0086](./0086-private-first-publish.md)):** The `write`/`read`/`share` MCP scope vocabulary referenced throughout this ADR has since been **unified with the API scope vocabulary** (`read`/`publish`/`admin`). There is now **one** scope set shared by `api` and MCP: a member's MCP scopes **are** their stored API scopes verbatim. The translation layer described below (`apiScopesToMcpScopes` / `mcpScopesToApiScopes` in `packages/contracts/src/mcp/scopes.ts`) has been **removed** — there is nothing to map. Scope meaning is now canonical: `read` = view your stuff; `publish` = change your stuff, **including managing an Artifact's own unauthenticated access** (make_public, list and revoke that Artifact's links — these are `publish`-scope actions, **not** `admin`); `admin` = account/workspace management (API keys, settings, audit, billing). The decision below (scopes are derived from the member, not the WorkOS token; the Worker verifies issuer + audience only and pre-flight-gates against `mcp.whoami`) **still holds** — only the vocabulary and the now-deleted translation step changed. Read references to `write`/`share` below as `publish`/`publish` respectively, and `mcp.whoami` now returns the member's API scopes verbatim.
ADR 0061 assumed the MCP consent screen would request from `{write, read, share}` and that WorkOS would mint those into the access token's `scope` claim, which both `apps/mcp` and `api` would read. That assumption is wrong for how WorkOS AuthKit actually works, and the entire authenticated MCP surface is non-functional as a result.
diff --git a/docs/adr/0086-publish-is-content-only-private-first.md b/docs/adr/0086-publish-is-content-only-private-first.md
index dce5cb9c..51c2243d 100644
--- a/docs/adr/0086-publish-is-content-only-private-first.md
+++ b/docs/adr/0086-publish-is-content-only-private-first.md
@@ -1,5 +1,11 @@
# Publish Is Content-Only and Private-First; Going Public Is a Separate Step
+> **Planned terminology amendment:** [ADR 0087](./0087-public-artifacts-and-unlisted-share-links.md)
+> reserves **Public Artifact** for a future CDN-backed public distribution model
+> and reclassifies the current Share Link handoff as unlisted. Until that model is
+> implemented, this ADR still describes shipped CLI/MCP behavior: `make_public`
+> and `agent-paste make-public` mint or reuse the Artifact's one Share Link.
+
agent-paste is private-first: an **Artifact** is for its owner until they decide
otherwise. The publish surfaces must honor that by default and never expose anything
by URL that the caller did not explicitly ask to expose.
@@ -35,15 +41,15 @@ Link** (when private) and the public **Share Link** (when shared), surfaced thro
only from the Artifact id — no token, signature, or expiry — and `add_revision`
republishes into the same id, so the link never changes across revisions and
live-updates to the latest Published Revision. It is member-only (publish never
- grants public access) and stops resolving only when the Artifact itself is deleted
- or swept by Auto Deletion. The `expires_at` in the publish response is the
- Artifact's content lifetime, not a link expiry. The mental model: a permanent,
- private, internal link that is always there; making it public is a separate,
- revocable Share Link.
-- **Going public is a separate, explicit verb.** `make_public` (MCP) and
- `agent-paste make-public` (CLI), replacing `create_share_link`, mint or reuse the
- one revocable **Share Link** (`access_links.type='share'`) and return its public,
- no-login **Access Link Signed URL**. This is the only way an Artifact becomes
+ grants unauthenticated access) and stops resolving only when the Artifact itself
+ is deleted or swept by Auto Deletion. The `expires_at` in the publish response
+ is the Artifact's content lifetime, not a link expiry. The mental model: a
+ permanent, private, internal link that is always there; unauthenticated sharing
+ is a separate, revocable Share Link.
+- **Creating unauthenticated access is a separate, explicit verb.** `make_public`
+ (MCP) and `agent-paste make-public` (CLI), replacing `create_share_link`, mint or
+ reuse the one revocable **Share Link** (`access_links.type='share'`) and return
+ its no-login **Access Link Signed URL**. This is the only way an Artifact becomes
reachable without login.
- **Revocation is independent of content.** `revoke_access_link` kills a Share Link
(or Revision Link) without touching the Artifact, its data, its revisions, or its
diff --git a/docs/adr/0087-public-artifacts-and-unlisted-share-links.md b/docs/adr/0087-public-artifacts-and-unlisted-share-links.md
new file mode 100644
index 00000000..e992ca84
--- /dev/null
+++ b/docs/adr/0087-public-artifacts-and-unlisted-share-links.md
@@ -0,0 +1,93 @@
+# Public Artifacts and Unlisted Share Links
+
+Status: Planned. Current shipped CLI/MCP behavior still treats `make_public` /
+`agent-paste make-public` as Share Link minting until the implementation specs
+and routes are updated.
+
+## Context
+
+ADR 0086 made publish private-first and moved unauthenticated handoff into a
+separate `make_public` step that mints the Artifact's one revocable Share Link.
+That fixed accidental public-by-flag publishing, but it reused "public" for two
+different jobs:
+
+- unlisted, revocable handoff to a specific audience
+- broad public distribution that should survive traffic spikes and benefit from
+ aggressive edge caching
+
+Those jobs have different control and caching expectations. A user iterating on
+an Artifact may need a no-login URL that can be revoked cleanly. A user sharing a
+finished Artifact with many people needs a stable permalink and CDN-shaped cost
+profile. Treating both as "public" makes the product and implementation lie.
+
+## Decision
+
+- **Share Links are unlisted.** A Share Link remains an Access Link that follows
+ the latest Published Revision, opens the Artifact Viewer, and can receive Live
+ Updates. It is the control-oriented unauthenticated path: revocable,
+ expirable, not a permalink, and not the aggressive edge-cache surface.
+- **Public Artifacts are a separate planned distribution model.** A Public
+ Artifact has a stable ID-only Public URL shaped `/p/{publicId}`. The Public ID
+ is separate from the Artifact id and has no slug.
+- **The first public action is atomic.** It allocates the Public ID, creates the
+ Public URL, and selects the initial Public Version in one durable action.
+ There is no reserved Public URL state before the first Public Version is
+ selected.
+- **Public Versions are frozen.** A Public Version points at one Published
+ Revision. Ordinary Publish Updates do not move it. Moving the public pointer is
+ an explicit action available to Agent Credentials with publish Scope.
+- **Public Offline is soft.** Clearing the selected Public Version keeps the
+ Public URL and Public ID reserved while stopping the Public Resolver from
+ serving broad public content. It is for owner/agent control, not hard abuse or
+ legal takedown.
+- **Public Resolver and Public Version Assets are separate cache surfaces.** The
+ Public Resolver is mutable and must change quickly through short cache lifetime
+ or explicit purge. Public Version Assets are immutable for one Published
+ Revision and are the aggressive edge-cache surface for broad traffic.
+- **Platform Lockdown is the hard public takedown path.** Operator-only Platform
+ Lockdown blocks the Public Resolver and Public Version Assets, using cache
+ purge and deny controls where available. It remains distinct from Access Link
+ Lockdown and Public Offline.
+- **Public pointer changes are audit-worthy.** Public Version changes and Public
+ Offline changes create Audit Events with a redacted Change Summary containing
+ the Public ID, old and new Published Revision ids or null, actor, and calling
+ surface.
+
+## Consequences
+
+- The current `make_public` / `agent-paste make-public` name becomes misleading:
+ it creates an unlisted Share Link today, not the future Public Artifact model.
+ A follow-up implementation should choose explicit verbs before shipping true
+ public distribution, for example `share` / `create_share_link` for unlisted and
+ `make_public` / `select_public_version` for true public.
+- Existing shipped specs and user docs remain current until that implementation
+ lands: publish is private-first, Share Link creation is explicit, and Access
+ Link Signed URLs remain the only shipped no-login latest-moving handoff.
+- The Public Artifact model needs schema, API, CLI, MCP, cache, audit, and
+ operator-lockdown work before it can be described as shipped behavior in
+ `docs/specs/`.
+- Public should be chosen for broad distribution and traffic spikes. Unlisted
+ Share Links should be chosen when revocation and takedown control matter more
+ than cache-level distribution.
+
+## Considered Options
+
+- **Keep calling Share Links public.** Rejected. It hides the cache and
+ revocation tradeoff and makes CDN-backed distribution sound safer to revoke
+ than it is.
+- **Use signed URLs for public distribution.** Rejected. Signed URLs are the
+ right shape for unlisted grants, but broad public distribution needs a stable
+ permalink and immutable cacheable assets.
+- **Let Publish move the public pointer automatically.** Rejected. Public
+ versions should be frozen so the public page does not live-update while an
+ agent iterates.
+- **Add slugs to Public URLs.** Rejected for the canonical URL. Slugs create
+ uniqueness and rename concerns without adding enough product value. The Public
+ ID is the canonical segment.
+
+## What this ADR is not
+
+- Not an implementation of Public Artifacts.
+- Not a change to the current Access Link Signed URL model from ADR 0047.
+- Not a change to the current shipped `make_public` command until a follow-up
+ spec and implementation PR changes it.
diff --git a/docs/adr/README.md b/docs/adr/README.md
index 476fad33..1df77fd0 100644
--- a/docs/adr/README.md
+++ b/docs/adr/README.md
@@ -39,7 +39,8 @@ This directory is the decision log for agent-paste: it records _why_ choices wer
- [ADR 0083](./0083-local-repository-backend-enforces-run-scope.md) records that the local in-memory repository backend now enforces the **Run Scope** ([ADR 0070](./0070-repository-core-ports-and-adapters.md)) through a **Scoped View**, as a deliberate test-surface bug detector rather than a faithful RLS emulator. Under a workspace Run Scope a foreign read returns nothing (RLS-faithful), a foreign `insert` throws (loud and self-labeling, the one `set()` path), and every other foreign mutation no-ops; the platform Run Scope is unfiltered. It only closes a gap in the local backend's enforcement and does not change the production isolation model ([ADR 0044](./0044-workspace-isolation-via-postgres-rls.md) Postgres RLS is unchanged).
- [ADR 0084](./0084-cli-and-mcp-share-one-publish-path.md) records that the **CLI** and **MCP** are two transports over one publish path: both call `runPublish` in `@agent-paste/api-client`, differing only behind a four-method `PublishTransport` seam (CLI over the HTTPS `ApiClient`, MCP over Worker service bindings). It forbids reintroducing a surface-specific publish implementation — the divergence that shipped the no-link-on-MCP and draft-`list_artifacts`-500 bugs. The shared module is exposed on the Worker-safe `@agent-paste/api-client/publish` subpath so the MCP bundle never pulls the Node-only `ApiClient`. The publish output is `{title, private_url, expires_at, upload_stats?}` with no `shared` field, per [ADR 0086](./0086-publish-is-content-only-private-first.md). It does not merge the two binaries; login/logout/upgrade, ephemeral provisioning, idempotency-key derivation, and output rendering stay caller-specific.
- [ADR 0085](./0085-publish-returns-one-viewer-url.md) — **Status: Superseded by [ADR 0086](./0086-publish-is-content-only-private-first.md).** It recorded that publish (both surfaces, through [ADR 0084](./0084-cli-and-mcp-share-one-publish-path.md)) returns one `viewer_url` plus a `shared` boolean, private by default, where `viewer_url` switched between the authenticated **Private Link** and the public **Share Link**'s signed URL. ADR 0086 retired that switching field and the `share`/`shared` convention: the switching link lied on revise (it reported `shared:false` while a live Share Link still served the page) and the `share` flag put public-by-flag on the content-publish call. **Viewer URL** is removed from [`CONTEXT.md`](../../CONTEXT.md).
-- [ADR 0086](./0086-publish-is-content-only-private-first.md) supersedes [ADR 0085](./0085-publish-returns-one-viewer-url.md): publish is content-only and private-first. `publish_artifact`, `add_revision`, and `agent-paste publish` accept no visibility input and return exactly one link — the **Private Link**, surfaced as `private_url`, a login-walled clean viewer at `/v/` for the owning **Workspace Member** (never the **Artifact Console** at `/artifacts/`). The `share`/`--share` inputs and the `shared` output bit are removed from every surface (CLI, MCP, the REST `PublishRevisionRequest` body, and `runPublish`); the server `PublishResult` renames `viewer_url`/`artifact_url` to `private_url` and drops `access_link_url`. Going public is a separate explicit verb: `make_public` (MCP) and `agent-paste make-public` (CLI), replacing `create_share_link`, mint or reuse the one revocable **Share Link** and return its no-login **Access Link Signed URL**. `revoke_access_link`, `list_access_links`, and `create_revision_link` are unchanged; the [ADR 0047](./0047-access-link-signed-url-with-fragment-encoded-payload.md) Access Link grant model is untouched. [`CONTEXT.md`](../../CONTEXT.md) deletes **Viewer URL**, renames **Artifact URL** to **Artifact Console**, and retargets **Private Link** at the `/v` viewer. Amends [ADR 0084](./0084-cli-and-mcp-share-one-publish-path.md)'s output-shape note.
+- [ADR 0086](./0086-publish-is-content-only-private-first.md) supersedes [ADR 0085](./0085-publish-returns-one-viewer-url.md): publish is content-only and private-first. `publish_artifact`, `add_revision`, and `agent-paste publish` accept no visibility input and return exactly one link — the **Private Link**, surfaced as `private_url`, a login-walled clean viewer at `/v/` for the owning **Workspace Member** (never the **Artifact Console** at `/artifacts/`). The `share`/`--share` inputs and the `shared` output bit are removed from every surface (CLI, MCP, the REST `PublishRevisionRequest` body, and `runPublish`); the server `PublishResult` renames `viewer_url`/`artifact_url` to `private_url` and drops `access_link_url`. Creating unauthenticated Share Link access is a separate explicit verb: `make_public` (MCP) and `agent-paste make-public` (CLI), replacing `create_share_link`, mint or reuse the one revocable **Share Link** and return its no-login **Access Link Signed URL**. `revoke_access_link`, `list_access_links`, and `create_revision_link` are unchanged; the [ADR 0047](./0047-access-link-signed-url-with-fragment-encoded-payload.md) Access Link grant model is untouched. [`CONTEXT.md`](../../CONTEXT.md) deletes **Viewer URL**, renames **Artifact URL** to **Artifact Console**, and retargets **Private Link** at the `/v` viewer. Amends [ADR 0084](./0084-cli-and-mcp-share-one-publish-path.md)'s output-shape note.
+- [ADR 0087](./0087-public-artifacts-and-unlisted-share-links.md) records the planned split between unlisted Share Links and true Public Artifacts. Current shipped behavior is still ADR 0086: `make_public` / `agent-paste make-public` mint or reuse a Share Link. The future Public Artifact model gets a stable ID-only `/p/{publicId}` Public URL, frozen Public Version, soft Public Offline control, cacheable Public Version Assets, and operator-only Platform Lockdown for hard takedown.
- [ADR 0021](./0021-id-based-r2-object-key-layout.md) is amended for revision file keys. The ADR originally described env-scoped file keys; shipped revision files and upload PUT targets use the legacy `artifacts/{artifactId}/revisions/{revisionId}/files/{path}` prefix. Derived bundles and env-scoped purge prefixes remain env-scoped. Current shapes are in [`docs/specs/data-model.md`](../specs/data-model.md#r2-object-key-layout).
- [ADR 0062](./0062-two-layer-cache-for-hot-path-auth-lookups.md) is amended for the L2 synthetic cache URL. The ADR originally used `https://cache.agent-paste.internal/{namespace}/{key}`; the shipped helper uses `https://agent-paste.internal/cache/{namespace}/{key}`. Current behavior is in [`docs/specs/architecture.md`](../specs/architecture.md#auth-lookup-cache).
- [`packages/contracts`](../../packages/contracts) and [`docs/specs/contracts.md`](../specs/contracts.md) are the canonical MVP implementation contract for Zod schemas, ID formats, and the route registry. ADRs provide rationale; contracts provide field-level implementation shape.
diff --git a/docs/ops/project-status.md b/docs/ops/project-status.md
index 73a7e079..86da64ae 100644
--- a/docs/ops/project-status.md
+++ b/docs/ops/project-status.md
@@ -210,11 +210,17 @@ Highest-signal gaps:
`agent-paste publish` take no visibility input and return one link,
`private_url` — the login-walled `/v/` clean viewer (the server
`PublishResult` renamed `artifact_url`→`private_url` and dropped
- `access_link_url`/`shared`). Going public is the separate explicit verb
- `make_public` (MCP) / `agent-paste make-public` (CLI), renamed from
- `create_share_link`, which mints or reuses the one revocable Share Link and
- returns its no-login Access Link Signed URL. ADR 0085 (one switching
+ `access_link_url`/`shared`). Unlisted no-login sharing is the separate
+ explicit verb `make_public` (MCP) / `agent-paste make-public` (CLI), renamed
+ from `create_share_link`, which mints or reuses the one revocable Share Link
+ and returns its no-login Access Link Signed URL. ADR 0085 (one switching
`viewer_url` + `shared`) is superseded.
+- True Public Artifacts are planned, not shipped. ADR 0087 / AP-330 reserves
+ **Public Artifact** for the future CDN-backed distribution model with a stable
+ ID-only `/p/{publicId}` URL, frozen Public Version, soft Public Offline control,
+ and hard Platform Lockdown path. Until that implementation lands, shipped
+ unauthenticated latest-moving handoff remains the explicit Share Link created by
+ `make_public` / `agent-paste make-public`.
- File-bytes hash-reputation malware scanner: cancelled/removed. Llama Guard
and Cloudflare URL Scanner still support the ephemeral advisory/abuse path
when configured, alongside built-in warning metadata. Containment is the trust
diff --git a/docs/specs/api.md b/docs/specs/api.md
index 6a31759d..796a41f2 100644
--- a/docs/specs/api.md
+++ b/docs/specs/api.md
@@ -200,7 +200,7 @@ link publish returns. It is **permanent and stable**: the URL is derived only
from the Artifact id with no token, signature, or expiry, and `add_revision`
republishes into the same id, so the link never changes across revisions and
live-updates to the latest Published Revision. It is **always private** (member
-only; publish never grants public access) and stops resolving only when the
+only; publish never grants unauthenticated access) and stops resolving only when the
Artifact itself is deleted or swept by Auto Deletion — a property of the
Artifact's lifetime, not the link. The `expires_at` in `PublishResult` is the
Artifact's content lifetime, not a link expiry. The dashboard-only **Artifact
@@ -210,10 +210,11 @@ response, expires with its signed token, and does not Live Update. Direct
`usercontent` HTML is inert raw byte delivery unless it is loaded through the
controlled Artifact Viewer iframe. MCP publish tools (`publish_artifact`,
`add_revision`) and CLI `publish` run the same publish path and return the same
-shape: `private_url`, title, expiry, and upload stats. Making an Artifact public
-is a separate explicit step — `make_public` (MCP) and `agent-paste make-public`
-(CLI) — which mints or reuses the one revocable **Share Link** and returns its
-public, no-login **Access Link Signed URL**. Creating a `share` Access Link is
+shape: `private_url`, title, expiry, and upload stats. Creating an unlisted
+no-login handoff is a separate explicit step — `make_public` (MCP) and
+`agent-paste make-public` (CLI) — which currently mints or reuses the one
+revocable **Share Link** and returns its no-login **Access Link Signed URL**.
+Creating a `share` Access Link is
idempotent on the Artifact, not just on the request key: if the Artifact already
has an active (non-revoked, unexpired) Share Link, create returns that same link
instead of minting a duplicate, so an Artifact has at most one live Share Link.
@@ -257,10 +258,10 @@ Human operators and rotation agents use WorkOS operator auth or Cloudflare Acces
Publishing without `--artifact-id` creates a new Artifact. Publishing with an
existing `artifact_id` creates and publishes a new Revision for that Artifact.
The previous `revision_content_url` continues to point at the older Revision.
-Publish never makes the Artifact public; a Share Link is created only by the
-separate `make_public` / `agent-paste make-public` step. Its Access Link Signed
-URL is the user-facing public URL. The `private_url` remains the authenticated
-clean-viewer link for Workspace members.
+Publish never creates unauthenticated access; a Share Link is created only by
+the separate `make_public` / `agent-paste make-public` step. Its Access Link
+Signed URL is the user-facing unlisted no-login URL. The `private_url` remains
+the authenticated clean-viewer link for Workspace members.
Workspace-wide publish deduplication starts only for new hash-aware uploads after
the digest-manifest contract shipped. There is no historical backfill of legacy
diff --git a/docs/specs/architecture.md b/docs/specs/architecture.md
index 3701dab6..7ba24eab 100644
--- a/docs/specs/architecture.md
+++ b/docs/specs/architecture.md
@@ -126,8 +126,8 @@ Key invariants:
## Read And Share Flow
Default human handoff is the authenticated Private Link (`private_url`, the
-`/v/` clean viewer); publish is content-only and never makes an
-Artifact public. Public or shareable handoff requires an explicit, separate
+`/v/` clean viewer); publish is content-only and never creates
+unauthenticated access. Unlisted no-login handoff requires an explicit, separate
make-public step that mints a revocable Share Link; its Access Link Signed URL
opens the Artifact Viewer for the latest Published Revision. Direct signed
content URLs are delivery URLs for one exact Revision.
@@ -154,7 +154,7 @@ Access Link rules:
- The shareable credential lives in the URL fragment, not in the path or query
string.
-- An Access Link Signed URL minted from a Share Link grants public access to the Artifact Viewer.
+- An Access Link Signed URL minted from a Share Link grants unlisted no-login access to the Artifact Viewer.
- A Share Link resolves to the latest Published Revision and can Live Update
through that viewer.
- A Revision Link resolves to exactly one Revision and does not Live Update.
diff --git a/docs/specs/cli.md b/docs/specs/cli.md
index 9508728e..3b6cc0b4 100644
--- a/docs/specs/cli.md
+++ b/docs/specs/cli.md
@@ -43,9 +43,9 @@ automatic; flags override detection.
- `publish` is content-only and private: it emits one handoff link, `private_url`
(the login-walled clean viewer at `/v/` for a Workspace Member) —
the same field the MCP server returns. There is no `--share` input and no
- `shared` output bit. Making an Artifact public is the separate `make-public`
- command, which mints or reuses the one Share Link and prints its no-login
- Access Link Signed URL.
+ `shared` output bit. Creating an unlisted no-login handoff is the separate
+ `make-public` command, which currently mints or reuses the one Share Link and
+ prints its no-login Access Link Signed URL.
- Errors in `json` mode are emitted on **stderr** as
`{ "error": { "code", "message", "docs?" } }` (no `schema_version` — it is an
error envelope, not a result).
diff --git a/docs/specs/local-dev.md b/docs/specs/local-dev.md
index ffa240a7..89f2184a 100644
--- a/docs/specs/local-dev.md
+++ b/docs/specs/local-dev.md
@@ -208,7 +208,7 @@ The first local vertical slice is complete when:
1. A Workspace and local CLI credential can be created locally.
2. `agent-paste whoami` succeeds after `pnpm cli:dev login`.
3. CLI can publish a folder with `index.html`.
-4. Publish is content-only and private: it prints the `private_url` (`/v/` clean viewer) as `View`. Making an Artifact public is the separate `make-public` step (MCP `make_public`).
+4. Publish is content-only and private: it prints the `private_url` (`/v/` clean viewer) as `View`. Unlisted no-login sharing is the separate `make-public` step (MCP `make_public`).
5. CLI JSON output includes `artifact_id`, `revision_id`, `private_url`, `revision_content_url`, `agent_view_url`, and `expires_at` for automation. There is no `share` input and no `shared` output.
6. `private_url` opens the authenticated `/v/` clean viewer in the local harness, while `revision_content_url` serves raw Revision bytes under the content origin with direct HTML scripts disabled.
7. `agent_view_url` returns Agent View JSON with full per-file URLs.
diff --git a/docs/specs/mvp.md b/docs/specs/mvp.md
index db2b7b4b..a39c6430 100644
--- a/docs/specs/mvp.md
+++ b/docs/specs/mvp.md
@@ -100,12 +100,12 @@ Publish returns:
Publish is content-only and private. `private_url` is the login-walled clean
viewer at `/v/` for the owning Workspace Member and is the only
handoff link publish returns; there is no `share` input and no `shared` output.
-Making an Artifact public is the separate `make_public` / `agent-paste
-make-public` step, which mints or reuses the one Share Link and returns its
-no-login Access Link Signed URL. `revision_content_url` remains a direct signed
-content URL for the exact Revision. Direct `usercontent` HTML is inert raw byte
-delivery unless it is loaded through the controlled Artifact Viewer iframe. The
-content token lives in the path.
+Creating an unlisted no-login handoff is the separate `make_public` /
+`agent-paste make-public` step, which currently mints or reuses the one Share
+Link and returns its no-login Access Link Signed URL. `revision_content_url`
+remains a direct signed content URL for the exact Revision. Direct `usercontent`
+HTML is inert raw byte delivery unless it is loaded through the controlled
+Artifact Viewer iframe. The content token lives in the path.
`agent_view_url` is public and signed. It returns a JSON manifest for the same revision.
@@ -247,5 +247,5 @@ The MVP is buildable when the API-key publish loop works end to end. Phase 3 mem
- `agent-paste publish ./demo.html` uploads a single HTML file.
- Human-facing publish output returns the `private_url` (`/v/` clean viewer) as `View`.
- JSON/REST publish output also carries `artifact_id`, `revision_id`, `private_url`, `revision_content_url`, `agent_view_url`, and `expires_at` for automation. There is no `share` input and no `shared` output.
-- `private_url` is the authenticated `/v/` clean viewer; making an Artifact public is the separate `make-public` step (which mints or reuses the one Share Link's no-login Access Link Signed URL); `revision_content_url` is raw byte delivery for one Revision; and `agent_view_url` returns JSON with full per-file URLs.
+- `private_url` is the authenticated `/v/` clean viewer; unlisted no-login sharing is the separate `make-public` step (which currently mints or reuses the one Share Link's no-login Access Link Signed URL); `revision_content_url` is raw byte delivery for one Revision; and `agent_view_url` returns JSON with full per-file URLs.
- Expired artifacts stop resolving and their bytes are cleaned up.
diff --git a/docs/specs/phases.md b/docs/specs/phases.md
index c3ed56b8..628d56fe 100644
--- a/docs/specs/phases.md
+++ b/docs/specs/phases.md
@@ -27,7 +27,7 @@ Goal: prove the artifact handoff loop.
- Storage: private R2.
- Metadata: Postgres through Cloudflare Hyperdrive using Drizzle.
- Publish: single HTML file or folder with `index.html`.
-- Output: publish is content-only and private. Human-facing publish prints the `private_url` (`/v/` clean viewer) as `View`; CLI JSON output includes `artifact_id`, `revision_id`, `private_url`, direct signed `revision_content_url`, public signed `agent_view_url`, and `expires_at`. There is no `share` input and no `shared` output; making an Artifact public is the separate `make-public` step.
+- Output: publish is content-only and private. Human-facing publish prints the `private_url` (`/v/` clean viewer) as `View`; CLI JSON output includes `artifact_id`, `revision_id`, `private_url`, direct signed `revision_content_url`, public signed `agent_view_url`, and `expires_at`. There is no `share` input and no `shared` output; unlisted no-login sharing is the separate `make-public` step.
- Agent View: simple JSON with full per-file URLs.
- Retention: default `30d`, max `90d`, scheduled cleanup.
- Operator: WorkOS `/v1/web/admin/*` lockdown routes; non-production smokes use the harness secret.
diff --git a/docs/specs/use-cases.md b/docs/specs/use-cases.md
index bb5bf8c9..22735d59 100644
--- a/docs/specs/use-cases.md
+++ b/docs/specs/use-cases.md
@@ -31,13 +31,14 @@ committing temporary files, creating a gist, or deploying to a preview host.
| Govern agent output | A team needs to know what agents published, when it expires, and how to revoke access if needed. | Attach Artifacts to Workspaces, Access Links, Audit Events, Auto Deletion, and lockdown controls. |
| Embed artifact handoff | A product needs artifact storage and a manifest protocol without building the whole platform itself. | Expose CLI, MCP, Agent View, and documented contracts that can be built on by another platform. |
-For the iteration use case, public/shareable browser URLs must come from an
-explicit Share Link, minted by the separate make-public step (`agent-paste
-make-public`, MCP `make_public`); publish itself is content-only and private and
-never makes an Artifact public. The direct `revision_content_url` is exact
-Revision content; it is useful for one-shot inspection but it does not advance
-when the agent publishes a later Revision. The `private_url` (`/v/`
-clean viewer) is the default authenticated Workspace view publish returns.
+For the iteration use case, no-login shareable browser URLs must come from an
+explicit unlisted Share Link, minted by the separate make-public step
+(`agent-paste make-public`, MCP `make_public`); publish itself is content-only
+and private and never creates unauthenticated access. The direct
+`revision_content_url` is exact Revision content; it is useful for one-shot
+inspection but it does not advance when the agent publishes a later Revision.
+The `private_url` (`/v/` clean viewer) is the default authenticated
+Workspace view publish returns.
## Primary Audiences
diff --git a/docs/specs/web.md b/docs/specs/web.md
index e547a3d7..26d2c5ab 100644
--- a/docs/specs/web.md
+++ b/docs/specs/web.md
@@ -51,12 +51,12 @@ On every authed request, `api` verifies the forwarded WorkOS access token and re
Read-only Workspace Members can list Access Links workspace-wide and per
Artifact. Access Link management mutations on your own Artifact - create
-(make public), mint, and revoke - require the `publish` scope: making an
-Artifact public and revoking that public access are part of the same content
-authority that creates and revises it. Workspace-wide Access Link Lockdown set
-and Lockdown lift remain `admin` (an account/workspace management action, not a
-per-Artifact one). Member `scopes` are always resolved from the database, not
-WorkOS token text.
+(create unlisted Share Link), mint, and revoke - require the `publish` scope:
+creating unlisted no-login access and revoking that access are part of the same
+content authority that creates and revises it. Workspace-wide Access Link
+Lockdown set and Lockdown lift remain `admin` (an account/workspace management
+action, not a per-Artifact one). Member `scopes` are always resolved from the
+database, not WorkOS token text.
After first provisioning, `POST /v1/auth/web/callback` receives the default credential plaintext once. The dashboard stores it only in client memory for the first-run card. The secret is never persisted, never written to logs, and never retrievable from `api`.