You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Turn the current owner-only Sites demo into a small, durable dashboard-publishing product for management users without turning LemmaComputer into a general application platform.
An employee should be able to create or edit a dashboard with an agent, publish it to a stable URL, authenticate through the normal LemmaComputer login, and share it with their organization, selected people, or an explicitly invited external viewer.
This epic applies to both supported deployment profiles from the same codebase and migration stream:
hosted: multi-organization LemmaComputer service using the existing private S3-backed ArtifactStore.
customer-managed: single-tenant installation using the existing filesystem or configured S3 ArtifactStore backend.
Current state
As inspected on origin/main at c1403bd:
skills/make-a-site/SKILL.md creates and publishes exactly one dist/index.html file, limited to 512 KB.
The workspace publisher sends base64 HTML through the loopback broker and authenticated agent bridge.
Control stores the complete HTML document in site_revisions.content_html in PostgreSQL.
Publishing the same owner/slug creates an immutable revision and updates current_revision.
Sites are owner-scoped and opened through /api/v1/sites/:siteId/content.
The existing ArtifactStore already supports filesystem and S3 backends; hosted startup requires S3.
The agent-to-broker-to-Control authority boundary and immutable revision concept are useful. The single-file database payload, owner-only authorization, creation-only skill name, and lack of a durable sharing URL are the parts to replace.
Outcome
A site is a versioned, read-only, interactive static dashboard.
A normal multi-file dist/ bundle is published as one immutable archive through ArtifactStore.
PostgreSQL stores tenant-scoped site/version metadata, permissions, invitations, hashes, and opaque artifact locators, not new site payloads.
Each site has one stable authenticated URL whose current-version pointer moves atomically after a successful publish.
The URL survives workspace stop, restart, or deletion.
The normal LemmaComputer authentication session proves identity; Sites performs the resource authorization decision.
Owners can share with their organization, selected accounts, and external people through site-specific email invitations.
New versions can be published and earlier ready versions can be restored without changing the URL.
Product decisions
1. Rename the reviewed skill to $site
Replace the creation-only make-a-site skill identifier and folder with the reviewed $site skill. Its discovery description must cover:
create a site;
edit an existing site;
preview a site;
publish or republish;
inspect publication status; and
restore an earlier published version where the caller is authorized.
Update every owned integration point together: reviewed skill catalog, UI prompt/action, Docker workspace image copy, workspace startup installation, Claude/Codex/Hermes profile projection, Hermes reviewed defaults, launcher/runtime tests, fixtures, and documentation. Do not leave make-a-site as a second independently installed skill.
Keep SKILL.md concise and outcome-focused. Put the detailed bundle contract in skills/site/references/publishing-contract.md, and enforce mechanical constraints with the lemmacomputer-sites CLI rather than prose alone.
The skill must never claim success until the server returns published: true, the stable URL, site ID, and published version.
2. Define reliable create and edit behavior
On first publication, bind the source project to the returned site ID using a small non-secret project file such as .lemmacomputer/site.json. Subsequent edits and republishes use the immutable site ID rather than relying only on a mutable name or slug.
Persist the source workspace ID and a validated workspace-relative project path as metadata. An edit should:
resolve the owned site and project binding;
confirm that the active workspace is the recorded source workspace;
edit the retained source project;
build and validate dist/;
preview when useful; and
publish a new immutable version.
If the source workspace/project no longer exists, the skill must explain that the published dashboard remains viewable but cannot be safely source-edited. It may offer to create a replacement only with clear user intent. Reconstructing editable source from a compiled bundle and durable source-repository hosting are non-goals for this epic.
3. Publish a bounded multi-file bundle
Add CLI operations for at least validate, preview, publish, list, and inspect while preserving the loopback broker and server-derived tenant/workspace/agent identity.
The publisher should create a deterministic ZIP containing one root index.html, local scripts/styles/images/fonts, and optional snapshot data such as data/*.json or data/*.csv.
remote scripts, remote styles, direct database connections, and known secret-bearing configuration;
file-count, per-file, compressed-size, or extracted-size limit violations; and
content whose declared size, manifest, or SHA-256 does not match.
Use bounded initial limits suitable for dashboards, proposed as 20 MB compressed, 50 MB extracted, and 500 files. Keep the limits configurable and covered by contract tests.
4. Store bundles through the existing ArtifactStore
Use one immutable ZIP as the canonical site-version artifact so the current single-blob ArtifactStore interface remains useful. Do not expose S3 credentials or generic object-store access to workspaces, generated JavaScript, or browsers.
Publication must be staged and idempotent:
validate the bridge principal and current workspace/agent generation;
validate the archive and checksum;
stage the object through ArtifactStore;
create a tenant-scoped site version in staging state;
finalize and verify the immutable artifact;
mark the version ready; and
atomically move the site's published-version pointer.
Failure must leave the previously published version live. Record bounded failure state and clean abandoned staging/final objects without treating workspace storage as authority.
Do not create a Sites-only S3 integration. Reuse FilesystemArtifactStore and S3ArtifactStore; reuse the configured artifact bucket unless retention/compliance requirements later justify a separate bucket.
5. Serve the published bundle through an authenticated Sites gateway
Expose a stable, opaque handle URL such as /s/{siteHandle}. The handle is an identifier, not a bearer credential.
The Lemma-owned top-level viewer must:
redirect unauthenticated visitors through the normal customer login and return them to the site URL;
authorize the requested site after authentication;
resolve the current ready version; and
open the generated document in a full-screen sandboxed iframe/new-tab viewer.
Serve index.html and relative bundle paths by version through Control/Sites. On a bounded cache miss, read the ZIP from ArtifactStore, verify its checksum and manifest, and safely cache extracted bytes keyed by immutable version ID. The stable viewer is no-store; immutable versioned assets may use private immutable caching with exact MIME types and ETags.
Generated content must receive no Lemma session token, database credential, generic artifact locator, or Control API authority. Enforce a restrictive CSP/sandbox, no forms, no top-level navigation, no popups, no objects, and no network connection except owned bundle-resource delivery. Unauthorized and nonexistent handles must remain indistinguishable.
An independently deployed Sites service, public S3 objects, CloudFront/CDN delivery, custom domains, and per-site subdomains are deferred. The boundary should permit those later without changing the site/version contract.
6. Add resource-level sharing
Support three visibility modes:
private: creator plus authorized organization administration/recovery.
organization: active members of the owning organization.
restricted: creator plus explicit site grants.
Add explicit viewer grants to stable account_user_id values. Do not add editor/contributor roles in this epic.
External email sharing must use a separate site invitation, not an organization invitation: access to one site must not grant organization membership. Reuse the existing transactional-email and Better Auth login/verification machinery, but persist tenant-scoped site invitations with hashed single-use tokens, expiry, resend generation, revocation, acceptance account ID, and audit timestamps.
Invitation possession plus a verified authentication identity may bind the invitation to an account_user_id; all later access decisions use that stable account ID, never ongoing email matching. Revocation must take effect immediately.
7. Support snapshot data, not a site database
The $site skill may generate data/*.json or data/*.csv inside the immutable bundle. The dashboard may read those files with relative URLs for charts, filters, and drill-downs. Republishing updates UI and data atomically; scheduled regeneration may be added after the core flow.
Every viewer of a site is authorized to receive every file in that site bundle. Do not represent bundled data as row-secured or secret.
MySQL, PostgreSQL, direct database credentials, writable shared state, arbitrary backend APIs, and browser-to-database connections are excluded. A future separately governed read-only Dataset API may be designed only after snapshot dashboards demonstrate a concrete freshness limitation.
Persistence and migration
Add tenant-scoped structures equivalent to:
sites: owner/creator, opaque handle, name/slug, visibility, published version pointer, lifecycle timestamps.
site_versions: immutable version number, state, ArtifactStore backend/locator, archive and manifest hashes/sizes, source workspace/agent/project metadata, creator and timestamps.
site_grants: site, grantee account, viewer permission, grantor and revocation state.
Organization and selected-person sharing: visibility policy, grants, Share UI, revocation and administrator recovery.
External site invitations: email delivery, Better Auth return flow, account binding, expiry/resend/revoke/audit without organization admission.
Versions and operations: version list, rollback, cleanup, metrics, migration completion and legacy contraction follow-up.
Each implementation slice owns one branch/worktree. Native sub-issues may be created from these slices before implementation so migrations, workspace-image work, Web/auth flows, and storage changes can be sequenced without overlapping worktrees.
Required evidence
Focused unit/contract tests for bundle construction and rejection, idempotent publishing, atomic version switching, ACL matrices, invitations, cache behavior, and legacy reads.
PostgreSQL feature tests and npm run verify:db for every persistence slice.
Filesystem plus mocked/qualified S3 ArtifactStore behavior.
Agent-bridge negative tests for stale generation, spoofed source identity, altered bytes, replay, and dependency failure.
Sites API tests for owner, same-organization member, selected account, external guest, revoked account/grant, foreign tenant, and indistinguishable 404 behavior.
Focused Playwright coverage for create, edit/republish, stable URL/login return, sharing, invitation acceptance, revocation, rollback, desktop, and narrow mobile viewports.
Workspace-image/runtime qualification proving $site reaches Claude, Codex, and Hermes and the old make-a-site installation is absent.
npm run verify:quick, npm run verify:db, npm run qualify:artifact-store, npm run qualify:deployment-profiles, the relevant customer-authentication E2E, and the complete Sites browser flow.
Definition of success
$site, not make-a-site, is the one reviewed lifecycle skill across every supported agent surface.
The skill can create and edit/republish a bound source project, and reports the stable URL and version only after confirmed publication.
A normal bounded multi-file bundle with optional JSON/CSV snapshot data is stored as an immutable ArtifactStore artifact.
New site payloads are not stored in PostgreSQL.
A failed publication never displaces the current working version.
The stable URL survives republishing and workspace deletion.
Existing Lemma login methods authenticate site viewers without generated auth code.
Private, organization, selected-person, and external-invitation access behave as specified and revoke immediately.
An external site viewer receives no organization membership or broader tenant authority.
Generated content cannot access Lemma credentials, Control APIs, external networks, or another site's files.
Existing single-file sites remain available through the migration and verified backfill.
Hosted and customer-managed profiles use the same contracts, code, migrations, and tests with only the configured ArtifactStore backend differing.
Coordination
Blocked by
None identified. The required authentication, transactional-email, organization membership, migrations, and ArtifactStore foundations already exist.
Can run in parallel with
Unrelated work only in separate worktrees. Within this epic, complete the additive schema/storage contract before enabling the new publisher, serving, or external-grant writes.
Product intent
Turn the current owner-only Sites demo into a small, durable dashboard-publishing product for management users without turning LemmaComputer into a general application platform.
An employee should be able to create or edit a dashboard with an agent, publish it to a stable URL, authenticate through the normal LemmaComputer login, and share it with their organization, selected people, or an explicitly invited external viewer.
This epic applies to both supported deployment profiles from the same codebase and migration stream:
hosted: multi-organization LemmaComputer service using the existing private S3-backedArtifactStore.customer-managed: single-tenant installation using the existing filesystem or configured S3ArtifactStorebackend.Current state
As inspected on
origin/mainatc1403bd:skills/make-a-site/SKILL.mdcreates and publishes exactly onedist/index.htmlfile, limited to 512 KB.site_revisions.content_htmlin PostgreSQL.current_revision./api/v1/sites/:siteId/content.ArtifactStorealready supports filesystem and S3 backends; hosted startup requires S3.The agent-to-broker-to-Control authority boundary and immutable revision concept are useful. The single-file database payload, owner-only authorization, creation-only skill name, and lack of a durable sharing URL are the parts to replace.
Outcome
dist/bundle is published as one immutable archive throughArtifactStore.Product decisions
1. Rename the reviewed skill to
$siteReplace the creation-only
make-a-siteskill identifier and folder with the reviewed$siteskill. Its discovery description must cover:Update every owned integration point together: reviewed skill catalog, UI prompt/action, Docker workspace image copy, workspace startup installation, Claude/Codex/Hermes profile projection, Hermes reviewed defaults, launcher/runtime tests, fixtures, and documentation. Do not leave
make-a-siteas a second independently installed skill.Keep
SKILL.mdconcise and outcome-focused. Put the detailed bundle contract inskills/site/references/publishing-contract.md, and enforce mechanical constraints with thelemmacomputer-sitesCLI rather than prose alone.The skill must never claim success until the server returns
published: true, the stable URL, site ID, and published version.2. Define reliable create and edit behavior
On first publication, bind the source project to the returned site ID using a small non-secret project file such as
.lemmacomputer/site.json. Subsequent edits and republishes use the immutable site ID rather than relying only on a mutable name or slug.Persist the source workspace ID and a validated workspace-relative project path as metadata. An edit should:
dist/;If the source workspace/project no longer exists, the skill must explain that the published dashboard remains viewable but cannot be safely source-edited. It may offer to create a replacement only with clear user intent. Reconstructing editable source from a compiled bundle and durable source-repository hosting are non-goals for this epic.
3. Publish a bounded multi-file bundle
Add CLI operations for at least
validate,preview,publish,list, andinspectwhile preserving the loopback broker and server-derived tenant/workspace/agent identity.The publisher should create a deterministic ZIP containing one root
index.html, local scripts/styles/images/fonts, and optional snapshot data such asdata/*.jsonordata/*.csv.The validator must reject:
index.html;Use bounded initial limits suitable for dashboards, proposed as 20 MB compressed, 50 MB extracted, and 500 files. Keep the limits configurable and covered by contract tests.
4. Store bundles through the existing ArtifactStore
Use one immutable ZIP as the canonical site-version artifact so the current single-blob
ArtifactStoreinterface remains useful. Do not expose S3 credentials or generic object-store access to workspaces, generated JavaScript, or browsers.Publication must be staged and idempotent:
ArtifactStore;stagingstate;ready; andFailure must leave the previously published version live. Record bounded failure state and clean abandoned staging/final objects without treating workspace storage as authority.
Do not create a Sites-only S3 integration. Reuse
FilesystemArtifactStoreandS3ArtifactStore; reuse the configured artifact bucket unless retention/compliance requirements later justify a separate bucket.5. Serve the published bundle through an authenticated Sites gateway
Expose a stable, opaque handle URL such as
/s/{siteHandle}. The handle is an identifier, not a bearer credential.The Lemma-owned top-level viewer must:
Serve
index.htmland relative bundle paths by version through Control/Sites. On a bounded cache miss, read the ZIP from ArtifactStore, verify its checksum and manifest, and safely cache extracted bytes keyed by immutable version ID. The stable viewer isno-store; immutable versioned assets may use private immutable caching with exact MIME types and ETags.Generated content must receive no Lemma session token, database credential, generic artifact locator, or Control API authority. Enforce a restrictive CSP/sandbox, no forms, no top-level navigation, no popups, no objects, and no network connection except owned bundle-resource delivery. Unauthorized and nonexistent handles must remain indistinguishable.
An independently deployed Sites service, public S3 objects, CloudFront/CDN delivery, custom domains, and per-site subdomains are deferred. The boundary should permit those later without changing the site/version contract.
6. Add resource-level sharing
Support three visibility modes:
private: creator plus authorized organization administration/recovery.organization: active members of the owning organization.restricted: creator plus explicit site grants.Add explicit
viewergrants to stableaccount_user_idvalues. Do not add editor/contributor roles in this epic.External email sharing must use a separate site invitation, not an organization invitation: access to one site must not grant organization membership. Reuse the existing transactional-email and Better Auth login/verification machinery, but persist tenant-scoped site invitations with hashed single-use tokens, expiry, resend generation, revocation, acceptance account ID, and audit timestamps.
Invitation possession plus a verified authentication identity may bind the invitation to an
account_user_id; all later access decisions use that stable account ID, never ongoing email matching. Revocation must take effect immediately.7. Support snapshot data, not a site database
The
$siteskill may generatedata/*.jsonordata/*.csvinside the immutable bundle. The dashboard may read those files with relative URLs for charts, filters, and drill-downs. Republishing updates UI and data atomically; scheduled regeneration may be added after the core flow.Every viewer of a site is authorized to receive every file in that site bundle. Do not represent bundled data as row-secured or secret.
MySQL, PostgreSQL, direct database credentials, writable shared state, arbitrary backend APIs, and browser-to-database connections are excluded. A future separately governed read-only Dataset API may be designed only after snapshot dashboards demonstrate a concrete freshness limitation.
Persistence and migration
Add tenant-scoped structures equivalent to:
sites: owner/creator, opaque handle, name/slug, visibility, published version pointer, lifecycle timestamps.site_versions: immutable version number, state, ArtifactStore backend/locator, archive and manifest hashes/sizes, source workspace/agent/project metadata, creator and timestamps.site_grants: site, grantee account, viewer permission, grantor and revocation state.site_invitations: site, normalized email, token hash, delivery generation, expiry/status, accepted account and audit fields.Do not edit the released static-Sites migration. Use generated, forward-only migrations and an expand/migrate/contract rollout:
content_htmlas artifact-backed versions;Application startup continues to check schema compatibility and never migrates.
Explicit non-goals
Security and tenancy invariants
Suggested execution slices
$siteand publisher: rename/install the reviewed skill, project binding, multi-file packaging, validation, preview, publish/list/inspect, workspace-image qualification.Each implementation slice owns one branch/worktree. Native sub-issues may be created from these slices before implementation so migrations, workspace-image work, Web/auth flows, and storage changes can be sequenced without overlapping worktrees.
Required evidence
npm run verify:dbfor every persistence slice.$sitereaches Claude, Codex, and Hermes and the oldmake-a-siteinstallation is absent.npm run verify:quick,npm run verify:db,npm run qualify:artifact-store,npm run qualify:deployment-profiles, the relevant customer-authentication E2E, and the complete Sites browser flow.Definition of success
$site, notmake-a-site, is the one reviewed lifecycle skill across every supported agent surface.Coordination
Blocked by
Can run in parallel with