Skip to content

[P1][Epic] Enhance Sites with artifact bundles, authenticated sharing, and the $site lifecycle #97

Description

@mightnent

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-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:

  1. resolve the owned site and project binding;
  2. confirm that the active workspace is the recorded source workspace;
  3. edit the retained source project;
  4. build and validate dist/;
  5. preview when useful; and
  6. 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.

The validator must reject:

  • symlinks, path traversal, absolute paths, duplicate normalized paths, and archive bombs;
  • missing root index.html;
  • hidden files, environment files, credentials, source maps, and unapproved executable/content types;
  • 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:

  1. validate the bridge principal and current workspace/agent generation;
  2. validate the archive and checksum;
  3. stage the object through ArtifactStore;
  4. create a tenant-scoped site version in staging state;
  5. finalize and verify the immutable artifact;
  6. mark the version ready; and
  7. 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.
  • 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:

  1. add the new tables/nullable pointers and deploy dual-read code;
  2. run a resumable, observable job that packages legacy content_html as artifact-backed versions;
  3. verify per-tenant site/version counts and checksums;
  4. enable new bundle publication only after all replicas understand it;
  5. retain legacy reads throughout the rollback window; and
  6. contract legacy payload columns only in a later issue with restore evidence.

Application startup continues to check schema compatibility and never migrates.

Explicit non-goals

  • Site-specific databases, MySQL/PostgreSQL, server functions, containers, workers, queues, or secrets.
  • Forms or workflows that persist shared business state.
  • Generated authentication or site-specific SSO configuration.
  • Anonymous/public links.
  • Custom domains, per-site subdomains, CDN delivery, or public object-store URLs.
  • Arbitrary outbound network/API access from generated code.
  • Fine-grained row/file permissions inside one site bundle.
  • Editor/contributor sharing roles or real-time collaboration.
  • Durable editable-source hosting outside the recorded workspace.
  • Reconstructing a source project from a compiled artifact after workspace deletion.

Security and tenancy invariants

  • Every metadata, invitation, grant, staging, artifact, cache, and authorization record is tenant-scoped in both deployment profiles.
  • The agent bridge derives tenant, owner, workspace, agent, and generation; caller-supplied identity fields are ignored or rejected.
  • Better Auth proves the account; LemmaComputer resolves membership and site authorization server-side.
  • Email/domain/provider claims do not directly grant organization or site authority.
  • S3 remains private and encrypted through the existing ArtifactStore configuration.
  • Bundle and manifest checksums are verified at publication and cache population.
  • Cross-tenant, revoked, expired, replayed, malformed, unavailable-storage, and partial-publication paths fail closed with secret-safe errors/logs.

Suggested execution slices

  1. Version/storage foundation: schema expansion, site bundle service, ArtifactStore lifecycle, legacy dual-read, migration/backfill contract.
  2. $site and publisher: rename/install the reviewed skill, project binding, multi-file packaging, validation, preview, publish/list/inspect, workspace-image qualification.
  3. Stable authenticated serving: opaque URL, viewer, sandboxed multi-file serving, bounded cache, headers and cross-tenant tests.
  4. Organization and selected-person sharing: visibility policy, grants, Share UI, revocation and administrator recovery.
  5. External site invitations: email delivery, Better Auth return flow, account binding, expiry/resend/revoke/audit without organization admission.
  6. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions