open() omits the
inClient field and the app renders in the sandboxed iframe. Nothing to switch
on, and no way into the host page except a stored approval pinning the current
content hash.
Two venues, one default
- Jail (default). Every app runs inside an iframe with
connect-src 'none', cannot reach the host page’s DOM, and cannot make network calls. This is the surface for every unreviewed or model-generated component. - In-client (approved). After a human review, an approved version of the app renders natively in the host DOM. It participates in the page’s origin, cookies, and CSP.
open(). A forged inClient field on a
stored, imported, or streamed tree is stripped before the runtime attaches its
own verdict. In-thread previews always render in the jail; the approved venue
is never used for conversation surfaces.
Approvals pin a version
An approval records that a named principal approved one exact content hash of one app. The hash covers all app content, so any edit (a pin change, a generated-component change, a tree edit) produces a new hash and invalidates every existing approval for that app. One exception to the jail fallback: a review-kind app — one whose pins were captured withreview: true — never drops to the jail while it waits.
Anything short of a grant becomes pending-review, the surface ships no
executable source at all, and the user sees the review standing.
If an approved component fails to compile or render, the surface drops back
to the jail with an error notice. Every verdict and how it renders is in the
Approval reference.
Reviewing what changed: ship-diff
Before minting an approval, a reviewer reads the ship-diff: approvable code diffed against the host baselinesvendo sync captured, hash-pinned to the
current version. The baseline is always the shipped product, so the diff
answers how far the app has drifted from what your users run. It is never a
changelog since the last approval. A pin whose captured baseline has since
moved is flagged drifted; the diff still returns, and approving is still
possible.
Net-new generated components appear as pure additions. The tree structure
itself is not in the diff; the reviewable code is.
Fetch the ship-diff over the wire:
vendo.apps.review.queue(ctx) hands the
ship-diffs of other users’ review-kind apps to a caller your composition’s
apps.review.reviewer(ctx) hook asserts. The response shape is in
Ship-diff shape.
Minting approvals
For local demos and development, Vendo exposes a documented injection seam that pins the app’s current version hash: you approve what is running, never a hand-crafted hash. For production hosts, Vendo Cloud provides the human review console that mints approvals.Server-side
approvedBy is required — the record names who reviewed. A review-kind app
refuses to be approved by its own owner unless your composition asserts a
reviewer through apps.review.reviewer(ctx); without that hook the call is
blocked with a pointer to it.
Development wire route
approvedBy is optional here and defaults to "local-dev". The route
returns the approval record, and follows the rules in
Dev route rules. It never mounts in production.
Approvals are audited through guard.report. The approved verdict reaches the
client on the open() payload, as inClient.
What runs in the host page
Approved content runs in a closed module space: sucrase-compiled code with a controlledrequire that resolves React and captured sub-sources only, the
same evaluation model the jail uses, in the venue the approved verdict
unlocks. Venue parity extends to props: a node with no live props falls back
to its captured sampleProps rehearsal stub, exactly as it would in the jail.
The $action dispatch continues to route through the tree chokepoint, so
host tools remain gated by the same policy, grants, and audit path as the
jailed venue.
Approval reference
Verdict states
open() attaches a payload.inClient field to the tree surface:
review is the standing: { status: "pending", versionHash } or
{ status: "rejected", versionHash, note, by, at }.
The approval record
An in-client approval is a small audit-trail record:vendo_inclient_approvals collection. The verdict is
simply “some stored approval pins the current version’s content hash”.
Corrupt or hand-crafted rows can never grant. Deleting an app clears its
approvals.
Ship-diff shape
Dev route rules
POST /api/vendo/dev/inclient-approval is the only /dev/* route Vendo
serves, and it is mounted in development compositions only (production
returns 404). It also refuses a request whose principal is marked
ephemeral: true (401) — approving a host-page mount is a host trust
decision, and the flag doesn’t clear it even in dev.
Related reading
- Generated UI and apps: the ladder and the default jail.
- Tools and safety: how host tool calls are gated regardless of venue.
- HTTP routes: wire endpoints for ship-diff and the dev injection seam.