diff --git a/.env.example b/.env.example
index 0068853..ec333e6 100644
--- a/.env.example
+++ b/.env.example
@@ -101,3 +101,19 @@ STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
STRIPE_PRO_PRICE_ID=
DROPS_TEAM_INVITE_SECRET=
+
+# Managed Platform V4. These values select external adapters; setting a name never
+# marks the adapter healthy by itself. Production UI waits for provider evidence.
+# DROPS_MANAGED_DATA_PROVIDER accepts only "d1" or "postgres" in adapter wiring.
+DROPS_MANAGED_DATA_PROVIDER=
+DATABASE_URL=
+DROPS_COLLABORATION_TRANSPORT_URL=
+
+# Generic enterprise OIDC. Values stay server-only and external login remains
+# disabled until discovery, callback, nonce, PKCE, domain and group checks pass.
+DROPS_ENTERPRISE_OIDC_ISSUER=
+DROPS_ENTERPRISE_OIDC_CLIENT_ID=
+DROPS_ENTERPRISE_OIDC_CLIENT_SECRET=
+
+# Separate 32+ byte material for a future durable append-only audit adapter.
+DROPS_ENTERPRISE_AUDIT_SIGNING_KEY=
diff --git a/app/api/platform/capabilities/route.ts b/app/api/platform/capabilities/route.ts
new file mode 100644
index 0000000..0b1fb7f
--- /dev/null
+++ b/app/api/platform/capabilities/route.ts
@@ -0,0 +1,16 @@
+import { NextResponse } from "next/server.js";
+
+import { platformCapabilitySnapshot } from "@/lib/platform-capabilities";
+
+export const runtime = "nodejs";
+export const dynamic = "force-dynamic";
+
+export async function GET() {
+ return NextResponse.json(platformCapabilitySnapshot(), {
+ status: 200,
+ headers: {
+ "cache-control": "private, no-store, max-age=0",
+ vary: "Cookie",
+ },
+ });
+}
diff --git a/app/backend/page.tsx b/app/backend/page.tsx
new file mode 100644
index 0000000..253c060
--- /dev/null
+++ b/app/backend/page.tsx
@@ -0,0 +1,9 @@
+import { Database, ShieldCheck } from "lucide-react";
+
+import { PlatformCapabilityConsole } from "@/components/platform/platform-capability-console";
+import { PlatformShell } from "@/components/platform/platform-shell";
+import { PageIntro, StatusBadge } from "@/components/platform/platform-ui";
+
+export default function BackendPage() {
+ return Data-plane readiness Live server capability receipt
Reference core verified
>} /> ;
+}
diff --git a/app/enterprise/page.tsx b/app/enterprise/page.tsx
new file mode 100644
index 0000000..ad2fc2f
--- /dev/null
+++ b/app/enterprise/page.tsx
@@ -0,0 +1,9 @@
+import { ShieldCheck, UsersRound } from "lucide-react";
+
+import { PlatformCapabilityConsole } from "@/components/platform/platform-capability-console";
+import { PlatformShell } from "@/components/platform/platform-shell";
+import { PageIntro, StatusBadge } from "@/components/platform/platform-ui";
+
+export default function EnterprisePage() {
+ return Control-plane evidence Tenant-safe reference runtime
Core verified locally
>} /> ;
+}
diff --git a/app/integrations/page.tsx b/app/integrations/page.tsx
new file mode 100644
index 0000000..8df4fc2
--- /dev/null
+++ b/app/integrations/page.tsx
@@ -0,0 +1,9 @@
+import { KeyRound, ShieldCheck } from "lucide-react";
+
+import { IntegrationCatalog } from "@/components/platform/integration-catalog";
+import { PlatformShell } from "@/components/platform/platform-shell";
+import { PageIntro, StatusBadge } from "@/components/platform/platform-ui";
+
+export default function IntegrationsPage() {
+ return No credential values rendered Only current-tab markers are inspected
Evidence required
>} /> ;
+}
diff --git a/app/layout.tsx b/app/layout.tsx
index d53e25d..dd91cb0 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -26,12 +26,12 @@ const productionUrl = /^https?:\/\//i.test(configuredProductionUrl)
export const metadata: Metadata = {
metadataBase: new URL(productionUrl),
- title: "Drops Studio — Build crypto products in minutes",
+ title: "Drops Studio — Build crypto apps 10x faster with AI",
description:
- "Build useful crypto apps with DropsTab intelligence, guided Drops Bot setup and the AI model you choose.",
+ "Plan, build, test and publish editable crypto applications with DropsTab intelligence, Drops Bot automation and the AI model you choose.",
openGraph: {
title: "Drops Studio",
- description: "Turn a crypto idea into a live project in five minutes.",
+ description: "Build editable crypto applications with AI, DropsTab intelligence and Drops Bot automation.",
type: "website",
images: [
{
@@ -45,7 +45,7 @@ export const metadata: Metadata = {
twitter: {
card: "summary_large_image",
title: "Drops Studio",
- description: "Turn a crypto idea into a live project in five minutes.",
+ description: "Build editable crypto applications with AI, DropsTab intelligence and Drops Bot automation.",
images: ["/og.png"],
},
};
diff --git a/app/organizations/page.tsx b/app/organizations/page.tsx
new file mode 100644
index 0000000..e1e506b
--- /dev/null
+++ b/app/organizations/page.tsx
@@ -0,0 +1,9 @@
+import { Building2, ShieldCheck } from "lucide-react";
+
+import { OrganizationConsole } from "@/components/platform/organization-console";
+import { PlatformShell } from "@/components/platform/platform-shell";
+import { PageIntro, StatusBadge } from "@/components/platform/platform-ui";
+
+export default function OrganizationsPage() {
+ return Tenant-aware control plane Account and storage status are read live
Account dependent
>} /> ;
+}
diff --git a/app/page.tsx b/app/page.tsx
index 8e87c83..af6fa8f 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -5,21 +5,22 @@ function LandingHero() {
return (
- BUILD IN 5 MINUTES
+ DROPS AI CRYPTO BUILDER
- Turn a crypto idea{" "}
+ Build crypto apps{" "}
- into a live project.
+ 10x faster with AI.
- Start from 12 extensible working foundations or describe any crypto
- product.
+ Describe a product or start from 12 extensible working foundations
+ for category-native crypto apps. Review the plan, then build an editable
+ multi-file application.
- Drops Studio assembles the data, triggers, AI brain and output around
- DropsTab + Drops Bot.
+ DropsTab supplies market intelligence. Drops Bot supplies monitoring,
+ alerts and approved Telegram delivery. You own the source.
diff --git a/app/platform/page.tsx b/app/platform/page.tsx
new file mode 100644
index 0000000..3e59095
--- /dev/null
+++ b/app/platform/page.tsx
@@ -0,0 +1,13 @@
+import { Boxes, ShieldCheck } from "lucide-react";
+
+import { PlatformOverview } from "@/components/platform/platform-overview";
+import { PlatformShell } from "@/components/platform/platform-shell";
+import { PageIntro, StatusBadge } from "@/components/platform/platform-ui";
+import { platformCapabilitySnapshot } from "@/lib/platform-capabilities";
+
+export const dynamic = "force-dynamic";
+
+export default function PlatformPage() {
+ const snapshot = platformCapabilitySnapshot();
+ return Capability-aware UI Working, local, and setup states stay distinct
Truthful states
>} /> ;
+}
diff --git a/app/projects/page.tsx b/app/projects/page.tsx
new file mode 100644
index 0000000..e974c51
--- /dev/null
+++ b/app/projects/page.tsx
@@ -0,0 +1,9 @@
+import { FolderKanban, HardDrive } from "lucide-react";
+
+import { PlatformShell } from "@/components/platform/platform-shell";
+import { PageIntro, StatusBadge } from "@/components/platform/platform-ui";
+import { ProjectLibrary } from "@/components/platform/project-library";
+
+export default function ProjectsPage() {
+ return Browser-first library Cloud sync remains account-dependent
Local source of truth
>} /> ;
+}
diff --git a/app/styles/drops-studio.responsive.css b/app/styles/drops-studio.responsive.css
index 41889c6..e664923 100644
--- a/app/styles/drops-studio.responsive.css
+++ b/app/styles/drops-studio.responsive.css
@@ -13,6 +13,7 @@
.studio-header nav.open { display: flex; align-items: stretch; }
.studio-header nav button, .studio-header nav a { border-radius: 8px; justify-content: space-between; padding: 11px; width: 100%; }
.studio-header nav button:hover, .studio-header nav a:hover { background: #f3f6fb; }
+ .studio-header nav .mobile-nav-connections { display: flex; }
.header-actions { min-width: 0; }
.mobile-menu { display: flex; }
.api-vault-button { display: none; }
diff --git a/app/styles/drops-studio.shell.css b/app/styles/drops-studio.shell.css
index d3df3dc..737c642 100644
--- a/app/styles/drops-studio.shell.css
+++ b/app/styles/drops-studio.shell.css
@@ -27,8 +27,8 @@
.studio-header nav button, .studio-header nav a { align-items: center; background: none; border: 0; color: #36435d; cursor: pointer; display: flex; font-size: 14px; font-weight: 560; gap: 6px; padding: 10px 0; text-decoration: none; }
.studio-header nav button:hover, .studio-header nav a:hover { color: var(--blue); }
.studio-header nav button span { align-items: center; background: #eaf0fb; border-radius: 99px; display: inline-flex; font-size: 12px; height: 18px; justify-content: center; min-width: 18px; }
+.studio-header nav .mobile-nav-connections { display: none; }
.header-actions { align-items: center; display: flex; justify-content: flex-end; min-width: 218px; }
.api-vault-button { align-items: center; background: #fff; border: 1px solid #dbe3ef; border-radius: 11px; cursor: pointer; display: flex; font-size: 13px; font-weight: 650; gap: 7px; padding: 10px 14px; box-shadow: 0 5px 16px rgba(40, 60, 100, .05); }
.api-vault-button:hover { border-color: #adc4ff; color: var(--blue); }
.mobile-menu { background: transparent; border: 0; cursor: pointer; display: none; padding: 7px; }
-
diff --git a/app/styles/platform-tailwind.css b/app/styles/platform-tailwind.css
new file mode 100644
index 0000000..ec105e3
--- /dev/null
+++ b/app/styles/platform-tailwind.css
@@ -0,0 +1,5 @@
+/* Public dashboard routes are isolated from the legacy landing cascade. Keep
+ these utilities unlayered so explicit component classes can override the
+ older unlayered h1-h6 reset without changing the approved builder shell. */
+@import "tailwindcss/theme.css";
+@import "tailwindcss/utilities.css";
diff --git a/app/templates/page.tsx b/app/templates/page.tsx
new file mode 100644
index 0000000..45bc2fd
--- /dev/null
+++ b/app/templates/page.tsx
@@ -0,0 +1,9 @@
+import { Layers3, ShieldCheck } from "lucide-react";
+
+import { PlatformShell } from "@/components/platform/platform-shell";
+import { PageIntro, StatusBadge } from "@/components/platform/platform-ui";
+import { TemplateCatalog } from "@/components/platform/template-catalog";
+
+export default function TemplatesPage() {
+ return Current recipe catalog Imported directly from lib/presets
12 registered No placeholder presets
>} /> ;
+}
diff --git a/components/drops-studio.tsx b/components/drops-studio.tsx
index 870e4ab..bb3fb00 100644
--- a/components/drops-studio.tsx
+++ b/components/drops-studio.tsx
@@ -347,10 +347,12 @@ function Brand() {
function useNearViewport() {
const [ready, setReady] = useState(false);
- const elementRef = useRef(null);
+ const [element, setElement] = useState(null);
+ const elementRef = useCallback((next: HTMLElement | null) => {
+ setElement(next);
+ }, []);
useEffect(() => {
- const element = elementRef.current;
if (!element) return;
const observer = new IntersectionObserver(
([entry]) => {
@@ -362,7 +364,7 @@ function useNearViewport() {
);
observer.observe(element);
return () => observer.disconnect();
- }, []);
+ }, [element]);
return { ready, elementRef };
}
@@ -476,6 +478,13 @@ export function DropsStudio({ hero }: { hero: ReactNode }) {
useEffect(() => {
const timer = window.setTimeout(async () => {
+ const params = new URLSearchParams(window.location.search);
+ const presetParam = params.get("preset");
+ const requestedCatalogPreset = presets.find(
+ (preset) => preset.id === presetParam,
+ );
+ if (requestedCatalogPreset) setSelectedId(requestedCatalogPreset.id);
+
let savedProjects = readProjectsFromStore();
try {
@@ -612,7 +621,6 @@ export function DropsStudio({ hero }: { hero: ReactNode }) {
} catch {
/* The local compiler remains available when access status is offline. */
}
- const params = new URLSearchParams(window.location.search);
const handoff = parseStudioConnectionHandoff(window.location.search);
if (handoff.connections) {
const requestedProvider = providerList.find(
@@ -1701,17 +1709,7 @@ export function DropsStudio({ hero }: { hero: ReactNode }) {
- {
- document
- .querySelector(".preset-section")
- ?.scrollIntoView({ behavior: "smooth" });
- setMenuOpen(false);
- }}
- >
- Templates
-
+ Templates
{
@@ -1721,7 +1719,10 @@ export function DropsStudio({ hero }: { hero: ReactNode }) {
>
My Projects {projects.length}
+ Integrations
+ Platform
{
setConnectionOpen(true);
@@ -1965,7 +1966,7 @@ export function DropsStudio({ hero }: { hero: ReactNode }) {
/>
- {previewSection.ready ? (
+ {previewSection.ready || selectedId !== defaultPresetId ? (
void) {
+ window.addEventListener("storage", onStoreChange);
+ return () => window.removeEventListener("storage", onStoreChange);
+}
+
+function getSessionConnectionSnapshot() {
+ try {
+ return integrations
+ .filter((integration) => !("fixed" in integration))
+ .filter((integration) => Boolean(window.sessionStorage.getItem(`drops-studio:${integration.id}`)))
+ .map((integration) => integration.id)
+ .join(",");
+ } catch {
+ return "";
+ }
+}
+
+export function IntegrationCatalog() {
+ const snapshot = useSyncExternalStore(subscribeToSessionConnections, getSessionConnectionSnapshot, () => "");
+ const sessionConnections = useMemo(() => new Set(snapshot ? snapshot.split(",") : []), [snapshot]);
+
+ return (
+
+ Session-only credential contract This page checks only whether the current tab has a configured marker. It never reads, displays, or persists credential values.
This tab only
+
+ {integrations.map((integration) => {
+ const Icon = integration.icon;
+ const sessionConfigured = sessionConnections.has(integration.id);
+ const fixed = "fixed" in integration ? integration.fixed : null;
+ const status: PlatformStatus = fixed === "built-in" ? "working" : sessionConfigured ? "configured" : "setup";
+ const label = fixed === "built-in" ? "Built in" : sessionConfigured ? "Session configured" : "Setup required";
+ return (
+
+ {label}
+ {integration.eyebrow}
+ {integration.name}
+ {integration.description}
+ } variant={status === "configured" || status === "working" ? "outline" : "default"} className="mt-5 w-full">
+ {status === "configured" ? "Manage in Connections" : status === "working" ? "Open builder" : integration.id === "github" || integration.id === "vercel" ? "Open a project" : "Set up connection"}
+
+
+ );
+ })}
+
+
+ );
+}
diff --git a/components/platform/organization-console.tsx b/components/platform/organization-console.tsx
new file mode 100644
index 0000000..fb648c0
--- /dev/null
+++ b/components/platform/organization-console.tsx
@@ -0,0 +1,137 @@
+"use client";
+
+import { Building2, Check, LoaderCircle, Plus, ShieldCheck, UserRound, UsersRound } from "lucide-react";
+import Link from "next/link";
+import { useCallback, useEffect, useState } from "react";
+
+import { Button } from "@/components/ui/button";
+import { Checkbox } from "@/components/ui/checkbox";
+import { Input } from "@/components/ui/input";
+
+import { StatusBadge } from "./platform-ui";
+
+interface WorkspaceMember {
+ identity: string;
+ role: "owner" | "editor" | "viewer";
+}
+
+interface TeamWorkspaceSummary {
+ id: string;
+ name: string;
+ revision: number;
+ ownerIdentity: string;
+ members: WorkspaceMember[];
+ projects: Array<{ projectId: string }>;
+ updatedAt: string;
+}
+
+type LoadState = "loading" | "ready" | "signed-out" | "setup" | "error";
+
+function safeMessage(value: unknown, fallback: string): string {
+ if (!value || typeof value !== "object") return fallback;
+ const message = (value as { error?: unknown }).error;
+ return typeof message === "string" && message.trim() ? message.slice(0, 240) : fallback;
+}
+
+export function OrganizationConsole() {
+ const [state, setState] = useState("loading");
+ const [workspaces, setWorkspaces] = useState([]);
+ const [message, setMessage] = useState("");
+ const [name, setName] = useState("");
+ const [consent, setConsent] = useState(false);
+ const [creating, setCreating] = useState(false);
+
+ const refresh = useCallback(async () => {
+ setState("loading");
+ setMessage("");
+ try {
+ const response = await fetch("/api/teams", {
+ credentials: "same-origin",
+ headers: { accept: "application/json" },
+ cache: "no-store",
+ });
+ const payload = await response.json().catch(() => ({})) as { workspaces?: TeamWorkspaceSummary[] };
+ if (response.status === 401) {
+ setState("signed-out");
+ return;
+ }
+ if (response.status === 503) {
+ setState("setup");
+ setMessage(safeMessage(payload, "Organization storage is not configured."));
+ return;
+ }
+ if (!response.ok) throw new Error(safeMessage(payload, "Organizations could not be loaded."));
+ setWorkspaces(Array.isArray(payload.workspaces) ? payload.workspaces : []);
+ setState("ready");
+ } catch (error) {
+ setState("error");
+ setMessage(error instanceof Error ? error.message : "Organizations could not be loaded.");
+ }
+ }, []);
+
+ useEffect(() => {
+ const timer = window.setTimeout(() => void refresh(), 0);
+ return () => window.clearTimeout(timer);
+ }, [refresh]);
+
+ async function createWorkspace() {
+ if (creating || !consent || name.trim().length < 2) return;
+ setCreating(true);
+ setMessage("");
+ try {
+ const response = await fetch("/api/teams", {
+ method: "POST",
+ credentials: "same-origin",
+ headers: { accept: "application/json", "content-type": "application/json" },
+ body: JSON.stringify({ name: name.trim(), consent: true }),
+ });
+ const payload = await response.json().catch(() => ({})) as { workspace?: TeamWorkspaceSummary; code?: string };
+ if (!response.ok || !payload.workspace) {
+ throw new Error(safeMessage(
+ payload,
+ payload.code === "PRO_REQUIRED"
+ ? "A verified Pro entitlement is required to create a team workspace."
+ : "Workspace could not be created.",
+ ));
+ }
+ setWorkspaces((current) => [payload.workspace!, ...current.filter((item) => item.id !== payload.workspace!.id)]);
+ setName("");
+ setConsent(false);
+ setState("ready");
+ setMessage("Workspace created with a verified server receipt.");
+ } catch (error) {
+ setMessage(error instanceof Error ? error.message : "Workspace could not be created.");
+ } finally {
+ setCreating(false);
+ }
+ }
+
+ return (
+
+
+
+
+
Current account
Organization workspaces
+
void refresh()} variant="outline" disabled={state === "loading"}>{state === "loading" ? : }Refresh
+
+
+ {state === "loading" ?
Loading organizations
: null}
+ {state === "signed-out" ?
Sign in required Connect a Studio member account Organization data is private and is never filled with sample members. Use the OpenRouter member flow from Connections, then return here.
} className="mt-5">Open Connections
: null}
+ {state === "setup" || state === "error" ?
{state === "setup" ? "Setup required" : "Unavailable"} Organization control plane is not ready in this environment {message || "Durable organization storage or authorization is unavailable."}
: null}
+ {state === "ready" && workspaces.length === 0 ?
No workspaces yet Create one below when the account has a verified team entitlement.
: null}
+ {state === "ready" && workspaces.length ?
{workspaces.map((workspace) =>
{workspace.name} Server revision {workspace.revision} Updated {new Date(workspace.updatedAt).toLocaleString()}
{workspace.members.length} {workspace.projects.length}
{workspace.members.map((member) => {member.role}{member.identity.slice(0, 8)} )}
)}
: null}
+
+
+
+ Create workspace Real server mutation with entitlement check
+ Workspace name
+ setName(event.target.value)} className="mt-2 h-12" maxLength={80} placeholder="Crypto Research" disabled={state !== "ready" || creating} />
+ setConsent(value === true)} disabled={state !== "ready" || creating} aria-label="Confirm workspace creation" />I approve creation of this team workspace and its first owner membership.
+ void createWorkspace()} disabled={state !== "ready" || creating || !consent || name.trim().length < 2} className="mt-4 w-full">{creating ? : }{creating ? "Creating…" : "Create workspace"}
+ {message ? {message}
: null}
+ Server authorization
Cross-origin requests, missing identity, unverified billing, stale revisions, invite replay, and disallowed roles are rejected by the existing team APIs.
+
+
+
+ );
+}
diff --git a/components/platform/platform-capability-console.tsx b/components/platform/platform-capability-console.tsx
new file mode 100644
index 0000000..8e3b557
--- /dev/null
+++ b/components/platform/platform-capability-console.tsx
@@ -0,0 +1,181 @@
+"use client";
+
+import {
+ Activity,
+ ArchiveRestore,
+ Braces,
+ Clock3,
+ Cloud,
+ Database,
+ Fingerprint,
+ FolderKey,
+ Gauge,
+ KeyRound,
+ ListChecks,
+ LoaderCircle,
+ LockKeyhole,
+ RadioTower,
+ ScrollText,
+ ServerCog,
+ ShieldCheck,
+ TableProperties,
+ UsersRound,
+ Webhook,
+ type LucideIcon,
+} from "lucide-react";
+import { useCallback, useEffect, useMemo, useState } from "react";
+
+import { Button } from "@/components/ui/button";
+import type {
+ PlatformCapabilityReceipt,
+ PlatformCapabilitySnapshot,
+ PlatformCapabilityState,
+} from "@/lib/platform-capabilities";
+
+import { StatusBadge, type PlatformStatus } from "./platform-ui";
+
+interface ConsoleSection {
+ id: string;
+ label: string;
+ icon: LucideIcon;
+ capabilityIds: string[];
+ description: string;
+ boundary: string;
+}
+
+const backendSections: ConsoleSection[] = [
+ { id: "overview", label: "Overview", icon: Gauge, capabilityIds: ["managed-backend", "project-data"], description: "Runtime readiness across the generated app data plane.", boundary: "Reference-core evidence and production-provider evidence are shown separately." },
+ { id: "data", label: "Data", icon: Database, capabilityIds: ["managed-backend", "project-data"], description: "Scoped collections, revisions, idempotency and bounded queries.", boundary: "Browser-local documents remain explicitly labelled when durable storage is unavailable." },
+ { id: "schema", label: "Schema", icon: TableProperties, capabilityIds: ["managed-backend"], description: "Versioned schemas, migration plans and production backup gates.", boundary: "A selected provider is not considered ready until its health receipt succeeds." },
+ { id: "auth", label: "Auth", icon: Fingerprint, capabilityIds: ["managed-backend", "enterprise-identity"], description: "Project-scoped users, sessions, CSRF and enterprise identity boundaries.", boundary: "Email and external OIDC delivery require configured adapters." },
+ { id: "storage", label: "Storage", icon: Cloud, capabilityIds: ["managed-backend", "project-data"], description: "Object metadata, quotas, scanning and short-lived signed capabilities.", boundary: "Secrets and provider bytes are never embedded in project source or checkpoints." },
+ { id: "functions", label: "Functions", icon: Braces, capabilityIds: ["managed-backend"], description: "Typed manifests, bounded timeouts and explicit network allowlists.", boundary: "No unrestricted host shell and no inherited production environment." },
+ { id: "jobs", label: "Jobs", icon: ListChecks, capabilityIds: ["managed-backend"], description: "Idempotent jobs, bounded retries and dead-letter records.", boundary: "External side effects remain approval-gated." },
+ { id: "cron", label: "Cron", icon: Clock3, capabilityIds: ["managed-backend"], description: "Validated schedules, time zones and production approval.", boundary: "A schedule declaration is not reported as running without provider evidence." },
+ { id: "webhooks", label: "Webhooks", icon: Webhook, capabilityIds: ["managed-backend"], description: "Signed ingestion, replay protection and event normalization.", boundary: "Remote registration is never presented as complete without a provider receipt." },
+ { id: "realtime", label: "Realtime", icon: RadioTower, capabilityIds: ["collaboration", "managed-backend"], description: "Presence, collaboration events and bounded subscriptions.", boundary: "The local deterministic runtime is not labelled as a production realtime transport." },
+ { id: "secrets", label: "Secrets", icon: FolderKey, capabilityIds: ["managed-backend"], description: "Encrypted values, metadata-only listings and scoped references.", boundary: "Values never appear in API responses, source archives, logs or checkpoints." },
+ { id: "logs", label: "Logs", icon: Activity, capabilityIds: ["managed-backend", "audit-backup"], description: "Structured, bounded and secret-sanitized runtime evidence.", boundary: "Empty log stores are not converted into fake successful terminal output." },
+ { id: "backups", label: "Backups", icon: ArchiveRestore, capabilityIds: ["audit-backup", "managed-backend"], description: "Checksummed snapshots and restore-to-new-environment defaults.", boundary: "Provider object bytes and secret values require separate approved recovery paths." },
+ { id: "settings", label: "Settings", icon: ServerCog, capabilityIds: ["managed-backend", "deployment"], description: "Provider, quota and deployment readiness without secret values.", boundary: "Configuration markers never substitute for a live adapter health check." },
+];
+
+const enterpriseSections: ConsoleSection[] = [
+ { id: "organizations", label: "Organizations", icon: UsersRound, capabilityIds: ["organizations"], description: "Tenant-scoped organizations, workspaces, membership and invitations.", boundary: "Every mutation is scoped to a verified actor and entitlement." },
+ { id: "roles", label: "Roles & RBAC", icon: ShieldCheck, capabilityIds: ["organizations", "enterprise-identity"], description: "Owner, admin, developer, designer, analyst, viewer, billing and security roles.", boundary: "Custom roles cannot grant permissions their creator does not hold." },
+ { id: "collaboration", label: "Collaboration", icon: RadioTower, capabilityIds: ["collaboration"], description: "Convergent text edits, presence expiry, comments and review workflows.", boundary: "A production transport is required before live cursors are activated." },
+ { id: "identity", label: "Identity", icon: Fingerprint, capabilityIds: ["enterprise-identity"], description: "OIDC state, nonce, PKCE, verified domains and group mapping.", boundary: "SAML and SCIM stay setup-required until dedicated adapters exist." },
+ { id: "credentials", label: "Service accounts", icon: KeyRound, capabilityIds: ["enterprise-identity"], description: "One-time scoped tokens with hashed storage, expiry and revocation.", boundary: "Raw tokens are returned once and never persisted in projects." },
+ { id: "policies", label: "Policies", icon: LockKeyhole, capabilityIds: ["enterprise-identity", "audit-backup"], description: "Deterministic precedence for provider, model, retention and action controls.", boundary: "Higher-priority policy can only tighten inherited constraints." },
+ { id: "audit", label: "Audit", icon: ScrollText, capabilityIds: ["audit-backup"], description: "Append-only, tenant-filtered and tamper-evident audit events.", boundary: "Secret-like metadata is rejected before it reaches the audit chain." },
+ { id: "lifecycle", label: "Lifecycle", icon: ArchiveRestore, capabilityIds: ["audit-backup"], description: "Retention, sanitized exports, deletion scheduling, backups and restore.", boundary: "Production restore is approval-gated and targets a separate environment by default." },
+];
+
+function badgeStatus(state: PlatformCapabilityState): PlatformStatus {
+ if (state === "working") return "working";
+ if (state === "working-local-test") return "local";
+ if (state === "unavailable") return "feature-gated";
+ return "setup";
+}
+
+function stateLabel(state: PlatformCapabilityState): string {
+ if (state === "working") return "Working";
+ if (state === "working-local-test") return "Core verified locally";
+ if (state === "unavailable") return "Health evidence required";
+ return "Setup required";
+}
+
+function CapabilityCard({ capability }: { capability: PlatformCapabilityReceipt }) {
+ return (
+
+
+
{capability.label} {capability.mode}
+
{stateLabel(capability.state)}
+
+ {capability.detail}
+ {capability.evidence.length ? {capability.evidence.map((item) => {item} )}
: null}
+ {capability.requiredEnvironment.length ? Required configuration names
{capability.requiredEnvironment.map((name) => {name})}
: null}
+
+ );
+}
+
+export function PlatformCapabilityConsole({ mode }: { mode: "backend" | "enterprise" }) {
+ const sections = mode === "backend" ? backendSections : enterpriseSections;
+ const [activeId, setActiveId] = useState(sections[0].id);
+ const [snapshot, setSnapshot] = useState(null);
+ const [error, setError] = useState("");
+ const [loading, setLoading] = useState(true);
+
+ const load = useCallback(async () => {
+ setLoading(true);
+ setError("");
+ const controller = new AbortController();
+ const timeoutId = window.setTimeout(() => controller.abort(), 10_000);
+ try {
+ const response = await fetch("/api/platform/capabilities", {
+ cache: "no-store",
+ credentials: "same-origin",
+ signal: controller.signal,
+ });
+ const payload = await response.json().catch(() => null) as PlatformCapabilitySnapshot | null;
+ if (!response.ok || !payload || !Array.isArray(payload.capabilities)) throw new Error("Capability evidence is unavailable.");
+ setSnapshot(payload);
+ } catch (cause) {
+ setSnapshot(null);
+ setError(cause instanceof DOMException && cause.name === "AbortError"
+ ? "Capability evidence request timed out."
+ : cause instanceof Error ? cause.message : "Capability evidence is unavailable.");
+ } finally {
+ window.clearTimeout(timeoutId);
+ setLoading(false);
+ }
+ }, []);
+
+ useEffect(() => {
+ const timer = window.setTimeout(() => void load(), 0);
+ return () => window.clearTimeout(timer);
+ }, [load]);
+
+ const active = sections.find((section) => section.id === activeId) ?? sections[0];
+ const receipts = useMemo(() => {
+ if (!snapshot) return [];
+ return active.capabilityIds
+ .map((id) => snapshot.capabilities.find((capability) => capability.id === id))
+ .filter((value): value is PlatformCapabilityReceipt => Boolean(value));
+ }, [active.capabilityIds, snapshot]);
+ const ActiveIcon = active.icon;
+
+ return (
+
+
+
+
+ {sections.map((section) => { const Icon = section.icon; return (
+ setActiveId(section.id)}
+ className={`flex min-h-11 shrink-0 items-center gap-3 rounded-xl border-0 px-3 text-left text-sm font-semibold transition-colors focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-[#316cff]/30 lg:w-full ${section.id === active.id ? "bg-white text-[#1e55e8] shadow-sm" : "text-[#596980] hover:bg-white/70 hover:text-[#07142f]"}`}
+ >
+ {section.label}
+
+ ); })}
+
+
+
+
+
+
{mode} / {active.label}
{active.description} {active.boundary}
+
void load()} disabled={loading}>{loading ? : }Refresh evidence
+
+
+ {loading ?
Reading server capability receipts…
: null}
+ {!loading && error ?
Evidence unavailable {error}
: null}
+ {!loading && snapshot ?
Environment: {snapshot.environment} Receipt time: {new Date(snapshot.generatedAt).toLocaleString()}
{receipts.map((receipt) => )}
: null}
+
+
+
+ );
+}
diff --git a/components/platform/platform-overview.tsx b/components/platform/platform-overview.tsx
new file mode 100644
index 0000000..7f2f279
--- /dev/null
+++ b/components/platform/platform-overview.tsx
@@ -0,0 +1,58 @@
+import { Boxes, Braces, CloudCog, DatabaseZap, GitPullRequest, KeyRound, RadioTower, ShieldCheck, UsersRound, Webhook } from "lucide-react";
+import Link from "next/link";
+
+import { Button } from "@/components/ui/button";
+import type {
+ PlatformCapabilitySnapshot,
+ PlatformCapabilityState,
+} from "@/lib/platform-capabilities";
+
+import { StatusBadge, SurfaceCard } from "./platform-ui";
+
+const capabilityGroups = [
+ { id: "project-v2", icon: Boxes, title: "Project runtime" },
+ { id: "sandbox", icon: CloudCog, title: "Vercel Sandbox" },
+ { id: "project-data", icon: DatabaseZap, title: "Project data" },
+ { id: "managed-backend", icon: Webhook, title: "Managed workflows" },
+ { id: "deployment", icon: GitPullRequest, title: "Delivery" },
+ { id: "audit-backup", icon: ShieldCheck, title: "Safety and recovery" },
+];
+
+const managedRoadmap = [
+ { href: "/organizations", icon: UsersRound, title: "Organizations and RBAC", text: "The current team control plane reads real member state; the V4 role engine adds tenant-safe default and custom role contracts." },
+ { href: "/backend", icon: Braces, title: "Managed backend", text: "The reference runtime covers schema through recovery. Production data still requires a healthy D1 or Postgres adapter; blob storage is never treated as relational storage." },
+ { href: "/enterprise", icon: RadioTower, title: "Realtime collaboration", text: "Concurrent edits, presence expiry, comments and AI branch conflicts are verified locally. Production transport remains setup-required." },
+ { href: "/enterprise", icon: KeyRound, title: "Enterprise identity", text: "OIDC, domains, service tokens, policies and audit have executable local contracts. External IdP health evidence remains required." },
+];
+
+function badgeStatus(state: PlatformCapabilityState) {
+ if (state === "working") return "working" as const;
+ if (state === "working-local-test") return "local" as const;
+ if (state === "unavailable") return "feature-gated" as const;
+ return "setup" as const;
+}
+
+function stateLabel(state: PlatformCapabilityState): string {
+ if (state === "working") return "Working";
+ if (state === "working-local-test") return "Core verified locally";
+ if (state === "unavailable") return "Health evidence required";
+ return "Setup required";
+}
+
+export function PlatformOverview({ snapshot }: { snapshot: PlatformCapabilitySnapshot }) {
+ return (
+
+
+ {capabilityGroups.map((item) => {
+ const receipt = snapshot.capabilities.find((capability) => capability.id === item.id);
+ return
{receipt ? <>{stateLabel(receipt.state)} {receipt.detail}
{receipt.evidence.length ? {receipt.evidence.join(" · ")}
: null}> : <>Evidence unavailable No server capability receipt was returned for this surface.
>} ;
+ })}
+
+
+
+
Managed platform V4
Executable cores, explicit provider boundaries. The data, collaboration and enterprise domains have tested reference implementations. The cards above come from the server capability snapshot, and configuration markers stay non-working until matching runtime health evidence exists.
} className="mt-6 no-underline">Inspect backend readiness
+
{managedRoadmap.map((item) => { const Icon = item.icon; return
{item.title} {item.text}
Core verified · provider-aware
; })}
+
+
+ );
+}
diff --git a/components/platform/platform-shell.tsx b/components/platform/platform-shell.tsx
new file mode 100644
index 0000000..59f7aa3
--- /dev/null
+++ b/components/platform/platform-shell.tsx
@@ -0,0 +1,82 @@
+import Image from "next/image";
+import Link from "next/link";
+import type { ReactNode } from "react";
+import { ArrowRight, Plus, ShieldCheck } from "lucide-react";
+
+import "@/app/styles/platform-tailwind.css";
+
+import { Button } from "@/components/ui/button";
+
+const navigation = [
+ { href: "/projects", label: "Projects" },
+ { href: "/templates", label: "Templates" },
+ { href: "/backend", label: "Backend" },
+ { href: "/integrations", label: "Integrations" },
+ { href: "/organizations", label: "Organizations" },
+ { href: "/enterprise", label: "Enterprise" },
+ { href: "/platform", label: "Platform" },
+] as const;
+
+export function PlatformShell({
+ active,
+ children,
+}: {
+ active: (typeof navigation)[number]["label"];
+ children: ReactNode;
+}) {
+ return (
+
+
+
+
+
+
+
+
Drops Studio
+
+
+
+ {navigation.map((item) => (
+
+ {item.label}
+
+ ))}
+
+
+
+
} variant="outline" className="hidden no-underline sm:inline-flex">
+ Connections
+
+
} className="no-underline">
+
+ New project
+
+
+
+
+
+ {children}
+
+
+
+
+
+
Truthful by design Provider, build, integration, and deployment states appear only when Drops Studio has matching evidence.
+
+
+ Explore the platform
+
+
+
+
+ );
+}
diff --git a/components/platform/platform-ui.tsx b/components/platform/platform-ui.tsx
new file mode 100644
index 0000000..e93d96d
--- /dev/null
+++ b/components/platform/platform-ui.tsx
@@ -0,0 +1,58 @@
+import type { LucideIcon } from "lucide-react";
+import type { ReactNode } from "react";
+
+export type PlatformStatus = "working" | "local" | "setup" | "configured" | "feature-gated";
+
+const statusStyle: Record = {
+ working: "border-[#bde7d2] bg-[#eefbf4] text-[#087449]",
+ local: "border-[#cfdcff] bg-[#f1f6ff] text-[#245fe5]",
+ setup: "border-[#ecd9bb] bg-[#fff9ef] text-[#8a5709]",
+ configured: "border-[#cfdcff] bg-[#eef4ff] text-[#1e55e8]",
+ "feature-gated": "border-[#dbe4f1] bg-[#f7f9fc] text-[#596980]",
+};
+
+export function StatusBadge({ status, children }: { status: PlatformStatus; children: ReactNode }) {
+ return {children} ;
+}
+
+export function PageIntro({
+ eyebrow,
+ title,
+ description,
+ receipt,
+}: {
+ eyebrow: string;
+ title: string;
+ description: string;
+ receipt?: ReactNode;
+}) {
+ return (
+
+
+
{eyebrow}
+
{title}
+
{description}
+
+ {receipt ? : null}
+
+ );
+}
+
+export function SurfaceCard({
+ icon: Icon,
+ title,
+ children,
+ className = "",
+}: {
+ icon: LucideIcon;
+ title: string;
+ children: ReactNode;
+ className?: string;
+}) {
+ return (
+
+
{title}
+ {children}
+
+ );
+}
diff --git a/components/platform/project-library.tsx b/components/platform/project-library.tsx
new file mode 100644
index 0000000..ec76bd6
--- /dev/null
+++ b/components/platform/project-library.tsx
@@ -0,0 +1,58 @@
+"use client";
+
+import { AppWindow, ArrowRight, Cloud, FolderKanban, Globe2, Plus, Save } from "lucide-react";
+import Link from "next/link";
+import { useEffect, useMemo, useState } from "react";
+
+import { Button } from "@/components/ui/button";
+import { getProjectPreset } from "@/lib/presets";
+import { readProjectsFromStore } from "@/lib/project-store";
+import type { GeneratedProject } from "@/lib/project-types";
+
+import { StatusBadge } from "./platform-ui";
+
+export function ProjectLibrary() {
+ const [projects, setProjects] = useState([]);
+ const [ready, setReady] = useState(false);
+
+ useEffect(() => {
+ const load = () => {
+ try { setProjects(readProjectsFromStore()); } catch { setProjects([]); }
+ setReady(true);
+ };
+ load();
+ window.addEventListener("storage", load);
+ return () => window.removeEventListener("storage", load);
+ }, []);
+
+ const published = useMemo(() => projects.filter((project) => Boolean(project.publishedUrl)).length, [projects]);
+
+ return (
+
+
+
Browser projects
{ready ? projects.length : "—"}
+
Published receipts
{ready ? published : "—"}
+
Storage mode
Browser-first Private cloud sync activates only in an eligible signed-in account.
+
+
+ Your workspace
Real saved projects }>
Build new project
+
+ {!ready ?
: projects.length ? (
+
+ {projects.map((project) => {
+ const preset = getProjectPreset(project.spec.presetId);
+ return (
+
+
+
{project.spec.name} {project.publishedUrl ? "Published receipt" : "Saved locally"} {preset.output} · updated {new Date(project.updatedAt).toLocaleDateString()}
+
Open Studio
+
+ );
+ })}
+
+ ) : (
+ No browser projects yet Start from one of the 12 real recipes or describe a custom crypto product. Nothing is fabricated to fill this library.
}>Browse templates } variant="outline">Open builder
+ )}
+
+ );
+}
diff --git a/components/platform/template-catalog.tsx b/components/platform/template-catalog.tsx
new file mode 100644
index 0000000..3487e1f
--- /dev/null
+++ b/components/platform/template-catalog.tsx
@@ -0,0 +1,95 @@
+"use client";
+
+import {
+ AudioLines,
+ Blocks,
+ ChartNoAxesCombined,
+ Gamepad2,
+ HeartPulse,
+ Megaphone,
+ Radio,
+ Rocket,
+ Search,
+ Sparkles,
+ Sun,
+ TableProperties,
+ UsersRound,
+ Zap,
+ ArrowRight,
+} from "lucide-react";
+import Link from "next/link";
+import { useMemo, useState } from "react";
+
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { presets } from "@/lib/presets";
+import type { Preset } from "@/lib/presets";
+
+import { StatusBadge } from "./platform-ui";
+
+const iconMap = {
+ AudioLines,
+ Blocks,
+ ChartNoAxesCombined,
+ Gamepad2,
+ HeartPulse,
+ Megaphone,
+ Radio,
+ Rocket,
+ Sparkles,
+ Sun,
+ TableProperties,
+ UsersRound,
+ Zap,
+} as const;
+
+function PresetCard({ preset }: { preset: Preset }) {
+ const Icon = iconMap[preset.icon as keyof typeof iconMap] ?? Blocks;
+ return (
+
+
+
+
{preset.category} {preset.output}
+
+
+
{preset.shortTitle} Recipe
+
{preset.tagline}
+
{preset.description}
+
+ {preset.tools.slice(0, 3).map((tool) => {tool} )}
+
+
} className="mt-5 w-full">
+ Open in builder
+
+
+
+ );
+}
+
+export function TemplateCatalog() {
+ const [query, setQuery] = useState("");
+ const [category, setCategory] = useState("All");
+ const categories = useMemo(() => ["All", ...new Set(presets.map((preset) => preset.category))], []);
+ const visible = useMemo(() => {
+ const needle = query.trim().toLowerCase();
+ return presets.filter((preset) => {
+ const categoryMatch = category === "All" || preset.category === category;
+ const searchMatch = !needle || [preset.title, preset.tagline, preset.description, ...preset.tools].join(" ").toLowerCase().includes(needle);
+ return categoryMatch && searchMatch;
+ });
+ }, [category, query]);
+
+ return (
+
+
+
setQuery(event.target.value)} className="h-12 pl-12" placeholder="Search all 12 crypto recipes" aria-label="Search templates" />
+
+ {categories.map((item) => setCategory(item)} aria-pressed={category === item} className={`min-h-11 shrink-0 rounded-xl border px-4 text-sm font-semibold transition-colors focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-[#316cff]/30 ${category === item ? "border-[#316cff] bg-[#316cff] text-white" : "border-[#dbe4f1] bg-white text-[#52617a] hover:bg-[#f1f6ff]"}`}>{item} )}
+
+
+
+ Category-native starters
{visible.length} proven foundations Every card comes from the current production recipe catalog. No generic placeholder templates are added.
+ {visible.length ? {visible.map((preset) =>
)}
: No matching recipe Try another search or open the blank-canvas builder.
} variant="outline" className="mt-5">Open custom builder
}
+
+ );
+}
diff --git a/docs/agent/skills/AUDIT_AND_COMPLIANCE.md b/docs/agent/skills/AUDIT_AND_COMPLIANCE.md
new file mode 100644
index 0000000..74fce2b
--- /dev/null
+++ b/docs/agent/skills/AUDIT_AND_COMPLIANCE.md
@@ -0,0 +1,7 @@
+# Runtime skill: audit-and-compliance
+
+- `id`: `audit-and-compliance`
+- `version`: `3.1.0`
+- `purpose`: Build tamper-evident audit, policy resolution, retention, export, deletion, backup, and restore flows.
+- `policy order`: System hard policy, organization, workspace, project, then user preference; lower layers cannot weaken stronger rules.
+- `security`: Events and exports exclude secret values. Completion, deletion, residency, and restore states require runtime evidence.
diff --git a/docs/agent/skills/COLLABORATION.md b/docs/agent/skills/COLLABORATION.md
new file mode 100644
index 0000000..3869a62
--- /dev/null
+++ b/docs/agent/skills/COLLABORATION.md
@@ -0,0 +1,7 @@
+# Runtime skill: collaboration
+
+- `id`: `collaboration`
+- `version`: `3.1.0`
+- `purpose`: Coordinate authorized rooms, concurrent text operations, file changes, comments, reviews, presence, and isolated AI task branches.
+- `truth boundary`: Presence appears only from a live transport receipt; degraded mode is labelled.
+- `safety`: AI work is revision/hash scoped, stale patches conflict instead of overwriting, and losing versions remain in history.
diff --git a/docs/agent/skills/DATA_MODELING.md b/docs/agent/skills/DATA_MODELING.md
new file mode 100644
index 0000000..b995acb
--- /dev/null
+++ b/docs/agent/skills/DATA_MODELING.md
@@ -0,0 +1,7 @@
+# Runtime skill: data-modeling
+
+- `id`: `data-modeling`
+- `version`: `3.1.0`
+- `purpose`: Generate typed collections, fields, references, indexes, row rules, and migration previews.
+- `truth boundary`: There is no unrestricted SQL endpoint and no automatic production data copy.
+- `safety`: Destructive migrations require a verified backup, a data-loss warning, explicit approval, and a runtime receipt.
diff --git a/docs/agent/skills/ENTERPRISE_RBAC.md b/docs/agent/skills/ENTERPRISE_RBAC.md
new file mode 100644
index 0000000..fb934fa
--- /dev/null
+++ b/docs/agent/skills/ENTERPRISE_RBAC.md
@@ -0,0 +1,7 @@
+# Runtime skill: enterprise-rbac
+
+- `id`: `enterprise-rbac`
+- `version`: `3.1.0`
+- `purpose`: Apply organization, workspace, project, environment, service-account, and API-token permissions.
+- `roles`: Owner, admin, developer, designer, analyst, viewer, billing, and security are defaults; custom roles cannot bypass system hard policy.
+- `security`: Server/tool handlers enforce authorization. One-time tokens are hashed after reveal and support expiry, rotation, and revocation.
diff --git a/docs/agent/skills/ENTERPRISE_SSO.md b/docs/agent/skills/ENTERPRISE_SSO.md
new file mode 100644
index 0000000..9d3beec
--- /dev/null
+++ b/docs/agent/skills/ENTERPRISE_SSO.md
@@ -0,0 +1,7 @@
+# Runtime skill: enterprise-sso
+
+- `id`: `enterprise-sso`
+- `version`: `3.1.0`
+- `purpose`: Configure generic organization OIDC with discovery, PKCE, state, nonce, verified domains, claim mapping, and owner recovery.
+- `truth boundary`: SAML and SCIM remain `Adapter not configured` unless complete configured implementations pass their contract tests.
+- `security`: SSO enforcement begins only after domain verification and never locks out the emergency owner recovery path.
diff --git a/docs/agent/skills/JOBS_AND_CRON.md b/docs/agent/skills/JOBS_AND_CRON.md
new file mode 100644
index 0000000..6c64fbd
--- /dev/null
+++ b/docs/agent/skills/JOBS_AND_CRON.md
@@ -0,0 +1,7 @@
+# Runtime skill: jobs-and-cron
+
+- `id`: `jobs-and-cron`
+- `version`: `3.1.0`
+- `purpose`: Create isolated jobs, delays, retries, cancellation, concurrency bounds, dead-letter handling, and validated schedules.
+- `truth boundary`: A production schedule is enabled only after approval and configured runtime evidence.
+- `acceptance`: Idempotency, overlap policy, quotas, next/last run, retry, cancellation, and dead-letter behavior are tested.
diff --git a/docs/agent/skills/MANAGED_AUTH.md b/docs/agent/skills/MANAGED_AUTH.md
new file mode 100644
index 0000000..d7b14d4
--- /dev/null
+++ b/docs/agent/skills/MANAGED_AUTH.md
@@ -0,0 +1,7 @@
+# Runtime skill: managed-auth
+
+- `id`: `managed-auth`
+- `version`: `3.1.0`
+- `purpose`: Build project-scoped passwordless users, roles, invitations, sessions, revocation, and audit events.
+- `truth boundary`: Test codes work only in the local/test adapter. Production without a configured delivery adapter displays `Auth setup required`.
+- `security boundary`: Studio member identity and generated-app user identity remain separate; CSRF, replay, fixation, rate-limit, and role checks are server-enforced.
diff --git a/docs/agent/skills/MANAGED_BACKEND.md b/docs/agent/skills/MANAGED_BACKEND.md
new file mode 100644
index 0000000..9edafe8
--- /dev/null
+++ b/docs/agent/skills/MANAGED_BACKEND.md
@@ -0,0 +1,8 @@
+# Runtime skill: managed-backend
+
+- `id`: `managed-backend`
+- `version`: `3.1.0`
+- `purpose`: Generate an environment-isolated backend manifest, migration plan, typed SDK, capability scopes, and verification tests.
+- `truth boundary`: An adapter is working only when its runtime returns evidence; otherwise generated apps and Studio show `Setup required`.
+- `security boundary`: Control-plane credentials, database URLs, provider keys, and vault values never enter Project V2, Sandbox, logs, checkpoints, or exports.
+- `acceptance`: Development, preview, and production remain isolated and production mutation stays approval-gated.
diff --git a/docs/agent/skills/OBJECT_STORAGE.md b/docs/agent/skills/OBJECT_STORAGE.md
new file mode 100644
index 0000000..7fc7675
--- /dev/null
+++ b/docs/agent/skills/OBJECT_STORAGE.md
@@ -0,0 +1,7 @@
+# Runtime skill: object-storage
+
+- `id`: `object-storage`
+- `version`: `3.1.0`
+- `purpose`: Add namespaced uploads, metadata, visibility, retention, deletion, and short-lived signed capabilities.
+- `truth boundary`: Vercel Blob or another adapter must be configured; malware scanning is never claimed without scanner evidence.
+- `security`: MIME, size, scope, expiry, and authorization are validated before access. Blob is not used as a relational database.
diff --git a/docs/agent/skills/REALTIME_DATA.md b/docs/agent/skills/REALTIME_DATA.md
new file mode 100644
index 0000000..f229fd2
--- /dev/null
+++ b/docs/agent/skills/REALTIME_DATA.md
@@ -0,0 +1,7 @@
+# Runtime skill: realtime-data
+
+- `id`: `realtime-data`
+- `version`: `3.1.0`
+- `purpose`: Add ordered, authorized collection subscriptions with reconnect, backpressure, and environment isolation.
+- `truth boundary`: Studio and generated SDK state the actual transport. Polling/SSE degradation is not presented as live collaborative cursors.
+- `acceptance`: Scope, filter validation, ordering, reconnect, expiry, limits, and cross-environment denial are tested.
diff --git a/docs/agent/skills/SERVER_FUNCTIONS.md b/docs/agent/skills/SERVER_FUNCTIONS.md
new file mode 100644
index 0000000..ec102f6
--- /dev/null
+++ b/docs/agent/skills/SERVER_FUNCTIONS.md
@@ -0,0 +1,7 @@
+# Runtime skill: server-functions
+
+- `id`: `server-functions`
+- `version`: `3.1.0`
+- `purpose`: Create versioned TypeScript functions with typed input/output, resource limits, network allowlists, retries, and structured logs.
+- `runtime`: Generated functions execute only in an approved server runtime or isolated Vercel Sandbox, never in the browser or host shell.
+- `acceptance`: Invocation, timeout, failure, redaction, version history, deployment state, and rollback are verified.
diff --git a/docs/agent/skills/WEBHOOKS.md b/docs/agent/skills/WEBHOOKS.md
new file mode 100644
index 0000000..1fa4e38
--- /dev/null
+++ b/docs/agent/skills/WEBHOOKS.md
@@ -0,0 +1,7 @@
+# Runtime skill: webhooks
+
+- `id`: `webhooks`
+- `version`: `3.1.0`
+- `purpose`: Build signed webhook ingestion, event schemas, idempotency, retries, dead-letter state, replay controls, and redacted delivery receipts.
+- `truth boundary`: Receipt is not signature evidence. Drops Bot events use only the documented normalization adapter.
+- `security`: Verify signature and timestamp before parsing; never store secrets or sensitive headers in logs.
diff --git a/docs/platform/AUDIT_RETENTION_EXPORT.md b/docs/platform/AUDIT_RETENTION_EXPORT.md
new file mode 100644
index 0000000..cbc4a35
--- /dev/null
+++ b/docs/platform/AUDIT_RETENTION_EXPORT.md
@@ -0,0 +1,7 @@
+# Audit, Retention and Export
+
+`ImmutableAuditLog` creates tenant-filtered, append-only events linked by integrity hashes. Secret-like metadata is rejected before append. Runtime logs use separate bounded sanitization.
+
+`EnterpriseLifecycleManager` validates retention from 1 through 3,650 days and manages sanitized export and deletion requests with scheduling and cancellation. Exports omit secrets and credential values.
+
+Production durability requires an append-only store and signed artifact receipt. The local integrity chain proves behavior but is never presented as durable provider evidence.
diff --git a/docs/platform/BACKUP_AND_RECOVERY.md b/docs/platform/BACKUP_AND_RECOVERY.md
new file mode 100644
index 0000000..3ed78ba
--- /dev/null
+++ b/docs/platform/BACKUP_AND_RECOVERY.md
@@ -0,0 +1,7 @@
+# Backup and Recovery
+
+Managed backups are scoped and checksummed. Production-destructive migrations require a matching verified backup. Restore targets a separate environment by default and requires approval.
+
+The reference backup includes managed data and safe metadata. It deliberately excludes secret values and external provider object bytes; those need provider-native recovery procedures.
+
+Recovery verification checks scope, checksum, approval, target isolation and audit evidence before reporting success.
diff --git a/docs/platform/COLLABORATION_ARCHITECTURE.md b/docs/platform/COLLABORATION_ARCHITECTURE.md
new file mode 100644
index 0000000..80478d6
--- /dev/null
+++ b/docs/platform/COLLABORATION_ARCHITECTURE.md
@@ -0,0 +1,7 @@
+# Collaboration Architecture
+
+`lib/enterprise-platform/collaboration.ts` provides deterministic concurrent text operations, authenticated bounded presence, comments, replies and resolve/reopen permissions. `AiBranchManager` isolates AI task branches, detects stale canonical revisions, returns explicit conflicts and creates checkpoints on successful merge.
+
+The two-actor tests prove convergence without lost edits, viewer mutation denial, presence expiry and stale AI work not overwriting canonical files.
+
+The shipped runtime is local/test. A durable collaboration release still needs an authorized realtime transport and shared append-only operation storage. The UI reports that boundary directly.
diff --git a/docs/platform/ENTERPRISE_IDENTITY.md b/docs/platform/ENTERPRISE_IDENTITY.md
new file mode 100644
index 0000000..b8d7770
--- /dev/null
+++ b/docs/platform/ENTERPRISE_IDENTITY.md
@@ -0,0 +1,7 @@
+# Enterprise Identity
+
+The generic OIDC contract enforces authorization state, nonce, PKCE, code replay protection, allowed domains and group mapping. Domain challenges are bound, expiring and rotating. SSO enforcement is policy-controlled.
+
+Service accounts and API tokens are scoped, expiring, revocable and stored only as hashes. Raw token material is returned once and never enters projects, ZIPs, checkpoints or logs.
+
+`LocalTestOidcAdapter` is standards-shaped test evidence, not an external identity provider. SAML and SCIM adapters intentionally return setup-required. External OIDC stays disabled until discovery and callback verification succeeds.
diff --git a/docs/platform/ENTERPRISE_POLICIES.md b/docs/platform/ENTERPRISE_POLICIES.md
new file mode 100644
index 0000000..5abe240
--- /dev/null
+++ b/docs/platform/ENTERPRISE_POLICIES.md
@@ -0,0 +1,7 @@
+# Enterprise Policies
+
+Policies can constrain provider/model allowlists, role actions, external mutations, retention and collaboration behavior. Resolution is deterministic and higher-priority policy may only tighten inherited constraints.
+
+Evaluation returns an allow/deny/approval decision plus a stable policy hash suitable for audit evidence. It never silently upgrades a missing provider or credential state.
+
+Private-key custody and automatic trading are permanently outside the platform policy surface.
diff --git a/docs/platform/FUNCTIONS_JOBS_CRON.md b/docs/platform/FUNCTIONS_JOBS_CRON.md
new file mode 100644
index 0000000..7cc0a64
--- /dev/null
+++ b/docs/platform/FUNCTIONS_JOBS_CRON.md
@@ -0,0 +1,7 @@
+# Functions, Jobs and Cron
+
+Functions use typed manifests with bounded input/output shapes, timeouts, secret references and network host allowlists. The default runtime is `setup-required`; the in-memory handler exists only for explicit tests.
+
+Jobs provide idempotency, bounded retries and dead-letter records. Cron expressions and time zones are validated, and production schedules require an approval receipt. Declarations are never shown as running until an external provider returns evidence.
+
+Trading, wallet custody, Telegram publication, deployment, webhook registration and external database writes remain approval-gated.
diff --git a/docs/platform/MANAGED_AUTH.md b/docs/platform/MANAGED_AUTH.md
new file mode 100644
index 0000000..f1bc27a
--- /dev/null
+++ b/docs/platform/MANAGED_AUTH.md
@@ -0,0 +1,7 @@
+# Managed Auth
+
+`ManagedAuthService` provides project-scoped users and sessions, hashed session tokens, CSRF binding, expiry and revocation. An email adapter is required for real one-time-code delivery; without it the capability reports `setup-required-email`.
+
+Enterprise workforce identity is separate from generated-app user auth. External OIDC credentials are server-only. BYO database and model credentials remain session-only during setup and are never compiled into generated source.
+
+Security tests cover scope isolation, CSRF, revocation and the absence of secret values in returned records and logs.
diff --git a/docs/platform/MANAGED_BACKEND.md b/docs/platform/MANAGED_BACKEND.md
new file mode 100644
index 0000000..59092d2
--- /dev/null
+++ b/docs/platform/MANAGED_BACKEND.md
@@ -0,0 +1,15 @@
+# Managed Backend
+
+## Shipped contract
+
+`lib/managed-platform/` implements environment-scoped schema versions, migration plans, CRUD/query, optimistic revisions, idempotency, row policies, auth, object metadata, functions, jobs, cron, webhooks, realtime events, secrets, logs and backups. `createInMemoryManagedPlatform()` is the executable reference/test adapter.
+
+Every scope includes organization, workspace, project and `development | preview | production`. Cross-scope principals are rejected before data access. Destructive production migrations require approval and a verified backup.
+
+## Generated projects
+
+Managed prompts add `backend/manifest.json`, `backend/schema.json`, `backend/policies.json`, a server-only capability client, `/api/backend/status` and a smoke test. The generated app remains runnable without cloud data and labels its browser-local fallback.
+
+## Production boundary
+
+The reference adapter is not durable production storage. Production requires a healthy `D1ManagedPlatformDriver` or `PostgresManagedPlatformDriver`. Blob remains reserved for snapshots/artifacts and is never described as relational storage. `/api/platform/capabilities` exposes state and configuration names, never values.
diff --git a/docs/platform/MANAGED_PLATFORM_COST_CONTROLS.md b/docs/platform/MANAGED_PLATFORM_COST_CONTROLS.md
new file mode 100644
index 0000000..4b9f6a4
--- /dev/null
+++ b/docs/platform/MANAGED_PLATFORM_COST_CONTROLS.md
@@ -0,0 +1,11 @@
+# Managed Platform Cost Controls
+
+- Keep browser-local demo persistence available when cloud storage is absent.
+- Scope quotas by organization, project and environment.
+- Bound rows, query complexity, object bytes, jobs, realtime subscriptions and events.
+- Use Sandbox idle shutdown, command timeouts and at most three repair rounds.
+- Separate development, preview and production to avoid accidental production load.
+- Require approval for external delivery, deployment and destructive database work.
+- Prefer user BYOK model spend; never persist provider keys.
+
+Reference defaults are 10,000 rows/environment, 12 query-complexity units, 10 MiB/object, 2,000 realtime events, 100 subscriptions and 1,000 jobs/environment. Provider pricing and limits must be reviewed before enabling a durable adapter.
diff --git a/docs/platform/MANAGED_PLATFORM_OPERATIONS.md b/docs/platform/MANAGED_PLATFORM_OPERATIONS.md
new file mode 100644
index 0000000..4bc2932
--- /dev/null
+++ b/docs/platform/MANAGED_PLATFORM_OPERATIONS.md
@@ -0,0 +1,10 @@
+# Managed Platform Operations
+
+1. Configure one durable provider adapter (`d1` or `postgres`) in the server runtime.
+2. Run its health check; do not activate production state from environment markers alone.
+3. Configure encrypted secret and signed capability issuers outside generated projects.
+4. Verify tenant isolation, migrations, backup and restore in preview.
+5. Configure realtime and external OIDC separately, with their own health evidence.
+6. Run release gates and browser E2E before production promotion.
+
+`/api/platform/capabilities` is the safe operational summary. `/backend` and `/enterprise` consume it without exposing secret values. If a provider degrades, return `unavailable` and preserve the labelled local/demo fallback where safe.
diff --git a/docs/platform/MANAGED_PLATFORM_SECURITY.md b/docs/platform/MANAGED_PLATFORM_SECURITY.md
new file mode 100644
index 0000000..428846d
--- /dev/null
+++ b/docs/platform/MANAGED_PLATFORM_SECURITY.md
@@ -0,0 +1,12 @@
+# Managed Platform Security
+
+- Every request is organization/workspace/project/environment scoped.
+- Public constructors reject secret-bearing source and unsafe project paths.
+- Tokens are hashed or encrypted; values are absent from API listings and logs.
+- Webhooks use signatures, timestamps and replay protection.
+- Mutations use revisions/idempotency and bounded payload/query limits.
+- Production migrations, restore, deployment and external delivery require approval.
+- Sandbox code receives no production environment or provider credentials.
+- D1/Postgres configuration is not readiness; health evidence is mandatory.
+
+Focused suites cover tenant isolation, role denial, invitation replay, OIDC replay, policy precedence, audit integrity, backup checksums and secret scanning.
diff --git a/docs/platform/ORGANIZATIONS_AND_RBAC.md b/docs/platform/ORGANIZATIONS_AND_RBAC.md
new file mode 100644
index 0000000..3a4c154
--- /dev/null
+++ b/docs/platform/ORGANIZATIONS_AND_RBAC.md
@@ -0,0 +1,7 @@
+# Organizations and RBAC
+
+The V4 domain defines organizations, workspaces, memberships, invitations, project directories and custom roles. Default roles are owner, admin, developer, designer, analyst, viewer, billing and security.
+
+Permissions are tenant-scoped. Custom roles cannot grant a permission the creating actor lacks. Invitations expire, rotate on resend, reject replay and cannot cross organizations. Project transfer validates both source and destination workspaces.
+
+The public `/organizations` page uses the existing signed team APIs. It shows sign-in/setup errors rather than sample members. Rich V4 records remain reference-only until a durable control-plane adapter is connected.
diff --git a/docs/platform/REALTIME_AND_WEBHOOKS.md b/docs/platform/REALTIME_AND_WEBHOOKS.md
new file mode 100644
index 0000000..98d1273
--- /dev/null
+++ b/docs/platform/REALTIME_AND_WEBHOOKS.md
@@ -0,0 +1,7 @@
+# Realtime and Webhooks
+
+`ManagedWebhookService` verifies signed payloads, timestamps and replay state before normalization. Remote registration is a separate external action and cannot become `completed` without a provider receipt.
+
+`ManagedRealtimeService` is a bounded in-memory reference event stream. The enterprise collaboration domain separately verifies deterministic edits, presence expiry and comments. Neither is labelled a production websocket service.
+
+Production activation requires an authenticated transport with tenant/project room authorization, connection limits, backpressure, disconnect cleanup and a health receipt.
diff --git a/e2e/contracts/managed-platform-v4.spec.ts b/e2e/contracts/managed-platform-v4.spec.ts
new file mode 100644
index 0000000..a2009d3
--- /dev/null
+++ b/e2e/contracts/managed-platform-v4.spec.ts
@@ -0,0 +1,81 @@
+import AxeBuilder from "@axe-core/playwright";
+
+import {
+ expect,
+ expectNoHorizontalOverflow,
+ installRuntimeGuards,
+ test,
+} from "../fixtures/ui-test";
+
+for (const surface of [
+ {
+ path: "/backend",
+ title: "A complete backend surface, with honest runtime evidence.",
+ tab: "Webhooks",
+ detail: "Signed ingestion, replay protection and event normalization.",
+ },
+ {
+ path: "/enterprise",
+ title: "Identity, collaboration and governance without theatre.",
+ tab: "Policies",
+ detail: "Deterministic precedence for provider, model, retention and action controls.",
+ },
+] as const) {
+ test(`${surface.path} renders real capability evidence and responsive controls`, async ({ page }) => {
+ const assertCleanRuntime = installRuntimeGuards(page);
+ const response = await page.goto(surface.path, { waitUntil: "domcontentloaded" });
+
+ expect(response?.status()).toBe(200);
+ await expect(page.getByRole("heading", { name: surface.title })).toBeVisible();
+ await expect(page.getByText("Live server capability receipt").or(page.getByText("Tenant-safe reference runtime"))).toBeVisible();
+ await expect(page.getByText("Environment:", { exact: false })).toBeVisible();
+
+ await page.getByRole("tab", { name: surface.tab, exact: true }).click();
+ await expect(page.getByText(surface.detail, { exact: true })).toBeVisible();
+ await expectNoHorizontalOverflow(page);
+
+ const shortTargets = await page.locator("a[href], button, [role='tab']").evaluateAll((elements) =>
+ elements
+ .filter((element) => {
+ const rect = element.getBoundingClientRect();
+ const style = getComputedStyle(element);
+ return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
+ })
+ .map((element) => {
+ const rect = element.getBoundingClientRect();
+ return { text: (element.textContent ?? "").trim(), width: rect.width, height: rect.height };
+ })
+ .filter(({ width, height }) => width < 44 || height < 44),
+ );
+ expect(shortTargets).toEqual([]);
+ await assertCleanRuntime();
+ });
+}
+
+test("managed platform capability API returns status metadata without secret values", async ({ request }) => {
+ const response = await request.get("/api/platform/capabilities");
+ expect(response.status()).toBe(200);
+ const payload = await response.json();
+ expect(payload.capabilities).toEqual(expect.arrayContaining([
+ expect.objectContaining({ id: "managed-backend" }),
+ expect.objectContaining({ id: "collaboration" }),
+ expect.objectContaining({ id: "enterprise-identity" }),
+ ]));
+ expect(JSON.stringify(payload)).not.toMatch(/(?:sk-|ghp_|github_pat_|xox[baprs]-|-----BEGIN [A-Z ]+PRIVATE KEY-----)/i);
+});
+
+test("managed platform surfaces pass WCAG A/AA checks", async ({ page }, testInfo) => {
+ test.skip(testInfo.project.name !== "chromium-1440");
+ await page.goto("/backend", { waitUntil: "domcontentloaded" });
+ await expect(page.getByText("Environment:", { exact: false })).toBeVisible();
+
+ const results = await new AxeBuilder({ page })
+ .withTags(["wcag2a", "wcag2aa", "wcag21aa"])
+ .analyze();
+
+ expect(results.violations.map((violation) => ({
+ id: violation.id,
+ impact: violation.impact,
+ targets: violation.nodes.flatMap((node) => node.target),
+ }))).toEqual([]);
+});
diff --git a/lib/agent/evals/dashboard-types.ts b/lib/agent/evals/dashboard-types.ts
index 47436c9..b6c4460 100644
--- a/lib/agent/evals/dashboard-types.ts
+++ b/lib/agent/evals/dashboard-types.ts
@@ -5,7 +5,7 @@ export const AGENT_V3_DASHBOARD_CONTRACT = Object.freeze({
benchmarkSlices: 8,
syntheticRepairRecords: 36,
promptRoles: 12,
- runtimeSkills: 17,
+ runtimeSkills: 29,
stabilizerFixers: 4,
requiredDesignViewports: 3,
productionDefaultChanged: false,
diff --git a/lib/agent/runtime/intelligent-builder.ts b/lib/agent/runtime/intelligent-builder.ts
index 3f05c46..b6f6f88 100644
--- a/lib/agent/runtime/intelligent-builder.ts
+++ b/lib/agent/runtime/intelligent-builder.ts
@@ -54,6 +54,7 @@ import {
type ImmutableVerificationEvidence,
type VerificationReport,
} from "../models/verifier.ts";
+import { loadRuntimeSkills } from "../skills/index.ts";
import {
composeRuntimeSystemPrompt,
loadRuntimeSystemPrompt,
@@ -163,11 +164,34 @@ function requestedIntegrations(prompt: string): string[] {
if (/dropstab|market cap|fdv|token unlock|funding round/i.test(prompt)) {
values.push("dropstab");
}
- if (/drops\s*bot|wallet monitor|tracked wallet|wallet event|webhook/i.test(prompt)) {
+ if (/drops\s*bot|wallet monitor|tracked wallet|wallet event/i.test(prompt)) {
values.push("drops-bot");
}
if (/telegram|channel delivery|send alert/i.test(prompt)) values.push("telegram");
- return values;
+ if (/managed backend|backend services|full[- ]stack|database|data model|schema migration/i.test(prompt)) values.push("managed-backend");
+ if (/managed auth|magic link|one[- ]time code|app users|user sessions/i.test(prompt)) values.push("managed-auth");
+ if (/attachment|file upload|object storage|signed url/i.test(prompt)) values.push("object-storage");
+ if (/server function|backend function|api handler/i.test(prompt)) values.push("managed-functions");
+ if (/background job|job queue|scheduled|cron/i.test(prompt)) values.push("managed-jobs");
+ if (/webhook|event inbox/i.test(prompt)) values.push("managed-webhooks");
+ if (/realtime|live updates|subscribe|websocket|sse/i.test(prompt)) values.push("managed-realtime");
+ if (/collaborative|collaboration|presence|comments?|review workflow|ai task branch/i.test(prompt)) values.push("collaboration");
+ if (/organization|workspace members|rbac|owner\/developer\/viewer|service account|api token/i.test(prompt)) values.push("organizations");
+ if (/enterprise sso|oidc|domain verification|group mapping|saml|scim/i.test(prompt)) values.push("oidc");
+ if (/audit|retention|export|deletion|backup|compliance/i.test(prompt)) values.push("audit");
+ return [...new Set(values)];
+}
+
+function runtimeSkillCapabilities(project: ProjectV2): string[] {
+ const capabilities = new Set(["project-v2", "vercel-sandbox"]);
+ for (const integration of project.integrations) {
+ capabilities.add(integration.kind);
+ if (integration.kind === "dropstab") capabilities.add("dropstab-proxy");
+ if (integration.kind === "drops-bot") capabilities.add("dropsbot-proxy");
+ if (integration.kind === "telegram") capabilities.add("telegram-proxy");
+ if (integration.kind === "project-data") capabilities.add("project-data");
+ }
+ return [...capabilities].sort();
}
function routeTask(request: BuilderAgentRequest, project: ProjectV2) {
@@ -472,8 +496,35 @@ function composeBuilderPrompt(input: {
safePrompt: string;
core: Awaited>;
}): ComposedRuntimePrompt {
+ const integrations = requestedIntegrations(input.safePrompt);
+ const skillSelection = loadRuntimeSkills({
+ role: input.route.primaryRole,
+ task: input.safePrompt,
+ project: {
+ framework: input.source.project.manifest.framework.name,
+ category: input.source.project.productSpec.presetId,
+ filePaths: Object.keys(input.source.project.files),
+ },
+ integrations: [
+ ...integrations,
+ ...input.source.project.integrations.map((integration) => integration.kind),
+ ],
+ availableCapabilities: runtimeSkillCapabilities(input.source.project),
+ maximumSkills: 12,
+ maximumEstimatedTokens: 4_800,
+ });
+ const selectedSkills = skillSelection.skills.map((skill) => ({
+ id: skill.id,
+ version: skill.version,
+ instructions: [
+ ...skill.instructions,
+ ...skill.acceptanceChecks.map((check) => `Acceptance: ${check}`),
+ ...skill.forbiddenClaims.map((claim) => `Boundary: ${claim}`),
+ ].join("\n"),
+ }));
const versions = createAgentRuntimeVersions({
projectRevision: safeProjectRevision(input.source.project),
+ selectedSkillVersions: selectedSkills,
});
for (const contextLimit of [5_500, 3_000, 1_500, 0]) {
const composed = composeRuntimeSystemPrompt({
@@ -495,7 +546,7 @@ function composeBuilderPrompt(input: {
"Use only the current Project V2 and registered tools.",
"Never expose credentials or claim unverified provider state.",
],
- requestedIntegrations: requestedIntegrations(input.safePrompt),
+ requestedIntegrations: integrations,
},
projectMemory: {
projectId: input.source.project.id,
@@ -503,7 +554,7 @@ function composeBuilderPrompt(input: {
framework: input.source.project.manifest.framework.name,
presetId: input.source.project.productSpec.presetId,
},
- selectedSkills: [],
+ selectedSkills,
retrievedContext: boundedRuntimeContext(
input.contextPackage,
contextLimit,
diff --git a/lib/agent/skills/registry.ts b/lib/agent/skills/registry.ts
index 52b93fb..5fd3242 100644
--- a/lib/agent/skills/registry.ts
+++ b/lib/agent/skills/registry.ts
@@ -261,6 +261,186 @@ const definitions: RuntimeSkillDefinition[] = [
priority: 75,
documentationPath: "docs/agent/skills/PROJECT_DATA.md",
},
+ {
+ id: "managed-backend",
+ version: "3.1.0",
+ description: "Design and generate an environment-isolated Drops managed backend manifest and typed SDK.",
+ activationSignals: ["managed backend", "backend services", "database and auth", "full stack saas"],
+ requiredCapabilities: [],
+ allowedRoles: ["planner", "coder", "autofix", "qa", "security"],
+ allowedTools: [...FILE_TOOLS, ...CHECK_TOOLS, "request_connection"],
+ requiredContextQueries: ["managed-backend-contract", "environment-isolation", "capability-token-policy"],
+ instructions: ["Declare development, preview, and production environments separately and generate only scoped client capabilities.", "Expose configured adapter evidence or an explicit setup-required state for every backend service."],
+ acceptanceChecks: ["Backend files include a validated manifest, schema, migration plan, typed SDK, and tests.", "No control-plane credential enters generated source."],
+ forbiddenClaims: ["Do not call an unconfigured backend service live or production-ready."],
+ priority: 96,
+ documentationPath: "docs/agent/skills/MANAGED_BACKEND.md",
+ },
+ {
+ id: "managed-auth",
+ version: "3.1.0",
+ description: "Generate project-scoped passwordless authentication with honest delivery configuration.",
+ activationSignals: ["managed auth", "magic link", "one-time code", "app users", "user sessions"],
+ requiredCapabilities: [],
+ allowedRoles: ["planner", "coder", "autofix", "qa", "security"],
+ allowedTools: [...FILE_TOOLS, ...CHECK_TOOLS, "request_connection"],
+ requiredContextQueries: ["managed-auth-contract", "session-security", "email-adapter-state"],
+ instructions: ["Separate generated-app users from Drops Studio identities and scope every session to one project environment.", "Use local test codes only in test mode and show Auth setup required when delivery is unconfigured."],
+ acceptanceChecks: ["Session creation, revocation, replay protection, CSRF, role checks, and rate limits are tested."],
+ forbiddenClaims: ["Do not imply that an email or OAuth provider delivered a login without provider evidence."],
+ priority: 94,
+ documentationPath: "docs/agent/skills/MANAGED_AUTH.md",
+ },
+ {
+ id: "data-modeling",
+ version: "3.1.0",
+ description: "Model bounded relational collections and approval-gated migrations for generated apps.",
+ activationSignals: ["data model", "schema migration", "collection schema", "relations", "row-level access"],
+ requiredCapabilities: [],
+ allowedRoles: ["planner", "coder", "qa", "security"],
+ allowedTools: [...FILE_TOOLS, ...CHECK_TOOLS],
+ requiredContextQueries: ["schema-registry", "migration-policy", "row-authorization"],
+ instructions: ["Generate typed schemas, indexes, references, validation, and reversible migration previews.", "Require a backup and approval before destructive or production schema changes."],
+ acceptanceChecks: ["Tenant isolation, optimistic concurrency, idempotency, query bounds, and migration safety are tested."],
+ forbiddenClaims: ["Do not expose unrestricted SQL or execute a destructive production migration automatically."],
+ priority: 92,
+ documentationPath: "docs/agent/skills/DATA_MODELING.md",
+ },
+ {
+ id: "object-storage",
+ version: "3.1.0",
+ description: "Add project-scoped object storage through configured Vercel Blob or compatible adapters.",
+ activationSignals: ["object storage", "file upload", "attachment", "signed url", "media storage"],
+ requiredCapabilities: [],
+ allowedRoles: ["planner", "coder", "autofix", "qa", "security"],
+ allowedTools: [...FILE_TOOLS, ...CHECK_TOOLS, "request_connection"],
+ requiredContextQueries: ["storage-adapter", "mime-policy", "signed-capability"],
+ instructions: ["Validate size, MIME type, visibility, retention, and authorization before issuing bounded signed capabilities.", "Keep object payloads and secret material out of source, logs, and checkpoints."],
+ acceptanceChecks: ["Upload, download, deletion, expiry, namespace isolation, and scanning hooks are tested."],
+ forbiddenClaims: ["Do not call Blob a relational database or claim malware scanning ran without a receipt."],
+ priority: 82,
+ documentationPath: "docs/agent/skills/OBJECT_STORAGE.md",
+ },
+ {
+ id: "server-functions",
+ version: "3.1.0",
+ description: "Create bounded versioned server functions with typed inputs and runtime policy.",
+ activationSignals: ["server function", "api handler", "backend function", "function invoke"],
+ requiredCapabilities: [],
+ allowedRoles: ["planner", "coder", "autofix", "qa", "security"],
+ allowedTools: [...FILE_TOOLS, ...CHECK_TOOLS, "start_preview"],
+ requiredContextQueries: ["function-runtime", "network-allowlist", "secret-reference-policy"],
+ instructions: ["Declare input and output schemas, timeouts, network allowlists, secret references, idempotency, and structured logs.", "Run generated server code only in approved server runtime or Vercel Sandbox."],
+ acceptanceChecks: ["Test invocation, failure, timeout, log redaction, and deployment state are verified."],
+ forbiddenClaims: ["Do not provide generated-app users with unrestricted shell execution."],
+ priority: 87,
+ documentationPath: "docs/agent/skills/SERVER_FUNCTIONS.md",
+ },
+ {
+ id: "jobs-and-cron",
+ version: "3.1.0",
+ description: "Create isolated idempotent jobs, retries, dead-letter handling, and validated schedules.",
+ activationSignals: ["background job", "job queue", "scheduled summary", "cron", "delayed task"],
+ requiredCapabilities: [],
+ allowedRoles: ["planner", "coder", "autofix", "qa", "security"],
+ allowedTools: [...FILE_TOOLS, ...CHECK_TOOLS],
+ requiredContextQueries: ["job-adapter", "cron-policy", "idempotency-contract"],
+ instructions: ["Bound attempts, delay, concurrency, overlap, cancellation, quotas, and dead-letter behavior per environment.", "Require explicit approval before enabling a production schedule."],
+ acceptanceChecks: ["Retry, cancellation, idempotency, dead-letter, next-run, and isolation behavior are tested."],
+ forbiddenClaims: ["Do not claim a schedule is enabled without configured runtime evidence."],
+ priority: 84,
+ documentationPath: "docs/agent/skills/JOBS_AND_CRON.md",
+ },
+ {
+ id: "webhooks",
+ version: "3.1.0",
+ description: "Implement signed idempotent webhook ingestion with replay protection and redacted receipts.",
+ activationSignals: ["signed webhook", "webhook inbox", "event delivery", "event replay", "dead letter"],
+ requiredCapabilities: [],
+ allowedRoles: ["planner", "coder", "autofix", "qa", "security"],
+ allowedTools: [...FILE_TOOLS, ...CHECK_TOOLS, "request_connection"],
+ requiredContextQueries: ["webhook-signature", "replay-window", "event-normalization"],
+ instructions: ["Verify signature and timestamp before parsing, then enforce idempotency, schema validation, retries, and redacted logging.", "Normalize Drops Bot events only through the documented adapter and keep Telegram delivery separately approval-gated."],
+ acceptanceChecks: ["Invalid signatures, replay, duplicate delivery, redaction, retry, and event isolation are tested."],
+ forbiddenClaims: ["Do not treat a received callback as verified provider evidence."],
+ priority: 93,
+ documentationPath: "docs/agent/skills/WEBHOOKS.md",
+ },
+ {
+ id: "realtime-data",
+ version: "3.1.0",
+ description: "Add authorized ordered project-data subscriptions with truthful transport status.",
+ activationSignals: ["realtime data", "live updates", "sse", "websocket", "subscribe"],
+ requiredCapabilities: [],
+ allowedRoles: ["planner", "coder", "autofix", "qa", "security"],
+ allowedTools: [...FILE_TOOLS, ...CHECK_TOOLS, "start_preview"],
+ requiredContextQueries: ["realtime-adapter", "subscription-authorization", "backpressure-policy"],
+ instructions: ["Scope subscriptions to project and environment, validate filters, preserve ordering, and bound reconnect and backpressure.", "Name the actual transport and label polling degradation instead of presenting it as live cursors."],
+ acceptanceChecks: ["Authorization, ordering, reconnect, expiry, limits, and cross-environment denial are tested."],
+ forbiddenClaims: ["Do not label polling or fixture updates as a live realtime connection."],
+ priority: 80,
+ documentationPath: "docs/agent/skills/REALTIME_DATA.md",
+ },
+ {
+ id: "collaboration",
+ version: "3.1.0",
+ description: "Implement conflict-safe collaborative edits, presence, review, and isolated AI branches.",
+ activationSignals: ["collaboration", "presence", "cursor", "comment review", "ai task branch"],
+ requiredCapabilities: [],
+ allowedRoles: ["planner", "coder", "autofix", "qa", "security"],
+ allowedTools: [...FILE_TOOLS, ...CHECK_TOOLS, "create_checkpoint"],
+ requiredContextQueries: ["collaboration-room", "crdt-document", "branch-merge-policy"],
+ instructions: ["Authorize every room and operation, preserve concurrent user edits, and bind AI work to a base revision and file hashes.", "Show presence only from live transport evidence and preserve losing versions on conflicts."],
+ acceptanceChecks: ["Two-user merge, reconnect, comments, viewer denial, stale AI patch, review, and checkpoint merge are tested."],
+ forbiddenClaims: ["Do not render fake collaborators or silently overwrite uncommitted user work."],
+ priority: 91,
+ documentationPath: "docs/agent/skills/COLLABORATION.md",
+ },
+ {
+ id: "enterprise-rbac",
+ version: "3.1.0",
+ description: "Apply organization, workspace, project, environment, and service-account authorization.",
+ activationSignals: ["organization roles", "rbac", "workspace members", "service account", "api token"],
+ requiredCapabilities: [],
+ allowedRoles: ["planner", "coder", "autofix", "qa", "security"],
+ allowedTools: [...FILE_TOOLS, ...CHECK_TOOLS],
+ requiredContextQueries: ["organization-model", "permission-matrix", "token-scope-policy"],
+ instructions: ["Enforce the resolved permission matrix in server and tool handlers, including tenant, workspace, project, environment, and action scope.", "Hash one-time tokens, support expiry and revocation, and record denied operations."],
+ acceptanceChecks: ["Default roles, custom-role bounds, invitation replay, cross-tenant denial, token scope, and ownership protection are tested."],
+ forbiddenClaims: ["Do not rely on hidden UI controls as authorization."],
+ priority: 95,
+ documentationPath: "docs/agent/skills/ENTERPRISE_RBAC.md",
+ },
+ {
+ id: "enterprise-sso",
+ version: "3.1.0",
+ description: "Configure generic organization OIDC with verified domains and strict session controls.",
+ activationSignals: ["enterprise sso", "oidc", "domain verification", "group mapping", "saml"],
+ requiredCapabilities: [],
+ allowedRoles: ["planner", "coder", "qa", "security"],
+ allowedTools: [...FILE_TOOLS, ...CHECK_TOOLS, "request_connection"],
+ requiredContextQueries: ["oidc-discovery", "domain-verification", "sso-enforcement"],
+ instructions: ["Validate discovery, issuer, PKCE, state, nonce, domains, group mapping, session scope, and owner recovery.", "Expose SAML and SCIM as disabled contracts unless their configured adapters pass complete tests."],
+ acceptanceChecks: ["OIDC login, replay denial, claim mapping, verified-domain enforcement, and owner recovery are tested with a standards-compliant test provider."],
+ forbiddenClaims: ["Do not claim SAML, SCIM, email recovery, or domain verification is active without adapter evidence."],
+ priority: 89,
+ documentationPath: "docs/agent/skills/ENTERPRISE_SSO.md",
+ },
+ {
+ id: "audit-and-compliance",
+ version: "3.1.0",
+ description: "Produce tamper-evident audit, policy, retention, export, deletion, and backup flows without secrets.",
+ activationSignals: ["audit log", "retention", "data export", "deletion request", "backup restore", "compliance"],
+ requiredCapabilities: [],
+ allowedRoles: ["planner", "coder", "autofix", "verifier", "qa", "security"],
+ allowedTools: [...READ_TOOLS, "write_file", "apply_patch", ...CHECK_TOOLS, "create_checkpoint"],
+ requiredContextQueries: ["audit-integrity", "policy-precedence", "retention-export-backup"],
+ instructions: ["Append privacy-safe integrity-chained audit events for identity, data, agent, integration, and release actions.", "Resolve policy from system through organization, workspace, project, and user without allowing a lower layer to weaken a stronger rule."],
+ acceptanceChecks: ["Audit integrity, role separation, secret-free export, deletion grace and cancel, backup checksum, and restore isolation are tested."],
+ forbiddenClaims: ["Do not claim residency, retention enforcement, export completion, deletion, or restore without runtime evidence."],
+ priority: 90,
+ documentationPath: "docs/agent/skills/AUDIT_AND_COMPLIANCE.md",
+ },
];
function duplicates(values: readonly string[]): boolean {
diff --git a/lib/agent/skills/selector.ts b/lib/agent/skills/selector.ts
index 81e7e7b..f72ae7f 100644
--- a/lib/agent/skills/selector.ts
+++ b/lib/agent/skills/selector.ts
@@ -24,6 +24,18 @@ function integrationSignal(skill: RuntimeSkill, integrations: Set): bool
"github-delivery": ["github"],
"vercel-deployment": ["vercel"],
"project-data": ["project-data", "database"],
+ "managed-backend": ["managed-backend", "project-data", "database"],
+ "data-modeling": ["managed-backend", "database"],
+ "managed-auth": ["managed-auth", "auth"],
+ "object-storage": ["object-storage", "blob", "r2"],
+ "server-functions": ["managed-functions", "functions"],
+ "jobs-and-cron": ["managed-jobs", "cron"],
+ "webhooks": ["managed-webhooks", "dropsbot"],
+ "realtime-data": ["managed-realtime", "realtime"],
+ "collaboration": ["collaboration"],
+ "enterprise-rbac": ["organizations", "rbac"],
+ "enterprise-sso": ["oidc", "sso"],
+ "audit-and-compliance": ["audit", "backups"],
};
return (map[skill.id] ?? []).some((entry) => integrations.has(entry));
}
diff --git a/lib/agent/skills/types.ts b/lib/agent/skills/types.ts
index b94f3c5..fa77c38 100644
--- a/lib/agent/skills/types.ts
+++ b/lib/agent/skills/types.ts
@@ -18,6 +18,18 @@ export const RUNTIME_SKILL_IDS = [
"vercel-deployment",
"crypto-game",
"project-data",
+ "managed-backend",
+ "managed-auth",
+ "data-modeling",
+ "object-storage",
+ "server-functions",
+ "jobs-and-cron",
+ "webhooks",
+ "realtime-data",
+ "collaboration",
+ "enterprise-rbac",
+ "enterprise-sso",
+ "audit-and-compliance",
] as const;
export type RuntimeSkillId = (typeof RUNTIME_SKILL_IDS)[number];
diff --git a/lib/enterprise-platform/audit.ts b/lib/enterprise-platform/audit.ts
new file mode 100644
index 0000000..9750af8
--- /dev/null
+++ b/lib/enterprise-platform/audit.ts
@@ -0,0 +1,126 @@
+import { enterpriseError } from "./errors.ts";
+import type { EnterprisePermission, EnterpriseRuntime } from "./types.ts";
+import { assertSafeId, boundedText, clone, containsSecretLikeValue, iso, sha256, stableJson } from "./utils.ts";
+
+export interface AuditEvent {
+ id: string;
+ organizationId: string;
+ workspaceId?: string;
+ projectId?: string;
+ environment?: string;
+ actorType: "user" | "service-account" | "system" | "agent";
+ actorId: string;
+ action: string;
+ targetType: string;
+ targetId?: string;
+ outcome: "success" | "failure" | "blocked";
+ reasonCode?: string;
+ requestId: string;
+ ipHash?: string;
+ userAgentSummary?: string;
+ metadata: Record;
+ createdAt: string;
+ previousIntegrityHash: string;
+ integrityHash: string;
+}
+
+export type AuditEventInput = Omit;
+
+export class ImmutableAuditLog {
+ readonly #runtime: EnterpriseRuntime;
+ readonly #events: AuditEvent[] = [];
+
+ constructor(runtime: EnterpriseRuntime) {
+ this.#runtime = runtime;
+ }
+
+ append(input: AuditEventInput): AuditEvent {
+ if (containsSecretLikeValue(input.metadata)) enterpriseError("AUDIT_SECRET_REJECTED", "Audit metadata contains a secret-like key or value.");
+ if (Buffer.byteLength(stableJson(input.metadata), "utf8") > 32_000) enterpriseError("INVALID_INPUT", "Audit metadata exceeds its bound.");
+ const payload = {
+ id: assertSafeId(this.#runtime.id("audit-event"), "Audit event id"),
+ organizationId: assertSafeId(input.organizationId, "Audit organization id"),
+ ...(input.workspaceId ? { workspaceId: assertSafeId(input.workspaceId, "Audit workspace id") } : {}),
+ ...(input.projectId ? { projectId: assertSafeId(input.projectId, "Audit project id") } : {}),
+ ...(input.environment ? { environment: boundedText(input.environment, "Audit environment", 80) } : {}),
+ actorType: input.actorType,
+ actorId: assertSafeId(input.actorId, "Audit actor id"),
+ action: boundedText(input.action, "Audit action", 160),
+ targetType: boundedText(input.targetType, "Audit target type", 120),
+ ...(input.targetId ? { targetId: assertSafeId(input.targetId, "Audit target id") } : {}),
+ outcome: input.outcome,
+ ...(input.reasonCode ? { reasonCode: boundedText(input.reasonCode, "Audit reason code", 120) } : {}),
+ requestId: assertSafeId(input.requestId, "Audit request id"),
+ ...(input.ipHash ? { ipHash: this.#hash(input.ipHash, "IP hash") } : {}),
+ ...(input.userAgentSummary ? { userAgentSummary: boundedText(input.userAgentSummary, "User agent summary", 240) } : {}),
+ metadata: clone(input.metadata),
+ createdAt: iso(this.#runtime.now()),
+ };
+ const previousIntegrityHash = this.#events.at(-1)?.integrityHash ?? "0".repeat(64);
+ const event: AuditEvent = {
+ ...payload,
+ previousIntegrityHash,
+ integrityHash: sha256(`${previousIntegrityHash}\0${stableJson(payload)}`),
+ };
+ this.#events.push(event);
+ return clone(event);
+ }
+
+ list(input: {
+ organizationId: string;
+ permissions: EnterprisePermission[];
+ workspaceId?: string;
+ projectId?: string;
+ action?: string;
+ outcome?: AuditEvent["outcome"];
+ cursor?: number;
+ limit: number;
+ }): { items: AuditEvent[]; nextCursor: number | null } {
+ if (!input.permissions.includes("audit.read")) enterpriseError("PERMISSION_DENIED", "Permission audit.read is required.");
+ if (!Number.isSafeInteger(input.limit) || input.limit < 1 || input.limit > 500) enterpriseError("INVALID_INPUT", "Audit page limit is invalid.");
+ const cursor = input.cursor ?? 0;
+ if (!Number.isSafeInteger(cursor) || cursor < 0) enterpriseError("INVALID_INPUT", "Audit cursor is invalid.");
+ const filtered = this.#events.filter((event) =>
+ event.organizationId === input.organizationId
+ && (!input.workspaceId || event.workspaceId === input.workspaceId)
+ && (!input.projectId || event.projectId === input.projectId)
+ && (!input.action || event.action === input.action)
+ && (!input.outcome || event.outcome === input.outcome));
+ const items = filtered.slice(cursor, cursor + input.limit).map(clone);
+ return { items, nextCursor: cursor + items.length < filtered.length ? cursor + items.length : null };
+ }
+
+ export(input: { organizationId: string; permissions: EnterprisePermission[] }): {
+ events: AuditEvent[];
+ eventCount: number;
+ chainRoot: string;
+ checksum: string;
+ } {
+ const events: AuditEvent[] = [];
+ let cursor: number | null = 0;
+ while (cursor !== null) {
+ const page = this.list({ organizationId: input.organizationId, permissions: input.permissions, cursor, limit: 500 });
+ events.push(...page.items);
+ cursor = page.nextCursor;
+ }
+ const serialized = stableJson(events);
+ return { events, eventCount: events.length, chainRoot: events.at(-1)?.integrityHash ?? "0".repeat(64), checksum: sha256(serialized) };
+ }
+
+ verifyIntegrity(): boolean {
+ let previousIntegrityHash = "0".repeat(64);
+ for (const event of this.#events) {
+ const { previousIntegrityHash: storedPrevious, integrityHash, ...payload } = event;
+ if (storedPrevious !== previousIntegrityHash) return false;
+ const expected = sha256(`${previousIntegrityHash}\0${stableJson(payload)}`);
+ if (expected !== integrityHash) return false;
+ previousIntegrityHash = integrityHash;
+ }
+ return true;
+ }
+
+ #hash(value: string, label: string): string {
+ if (!/^[a-f0-9]{64}$/i.test(value)) enterpriseError("INVALID_INPUT", `${label} must be a SHA-256 digest.`);
+ return value.toLowerCase();
+ }
+}
diff --git a/lib/enterprise-platform/branches.ts b/lib/enterprise-platform/branches.ts
new file mode 100644
index 0000000..783a8ce
--- /dev/null
+++ b/lib/enterprise-platform/branches.ts
@@ -0,0 +1,266 @@
+import { enterpriseError } from "./errors.ts";
+import type { EnterpriseRuntime } from "./types.ts";
+import { assertSafeId, clone, fileHash, iso, matchesScope, normalizeProjectPath } from "./utils.ts";
+
+export interface CanonicalProjectState {
+ projectId: string;
+ revision: number;
+ files: Record;
+ updatedAt: string;
+ updatedBy: string;
+}
+
+export interface AiTaskBranch {
+ id: string;
+ projectId: string;
+ taskOwnerId: string;
+ taskScope: string[];
+ baseRevision: number;
+ baseFiles: Record;
+ files: Record;
+ status: "open" | "conflict" | "merged" | "discarded";
+ createdAt: string;
+ updatedAt: string;
+}
+
+export interface ProjectCheckpoint {
+ id: string;
+ projectId: string;
+ sourceRevision: number;
+ files: Record;
+ createdAt: string;
+ createdBy: string;
+ reason: "pre-ai-merge" | "manual";
+}
+
+export interface BranchMergeConflict {
+ path: string;
+ baseHash: string | null;
+ canonicalHash: string | null;
+ branchHash: string | null;
+}
+
+export type BranchMergeResult =
+ | { status: "approval-required" }
+ | { status: "conflict"; conflicts: BranchMergeConflict[] }
+ | { status: "merged"; checkpointId: string; revision: number };
+
+export class AiBranchManager {
+ readonly #runtime: EnterpriseRuntime;
+ readonly #projects = new Map();
+ readonly #branches = new Map();
+ readonly #checkpoints = new Map();
+
+ constructor(runtime: EnterpriseRuntime) {
+ this.#runtime = runtime;
+ }
+
+ createProject(input: { projectId: string; files: Record }): CanonicalProjectState {
+ const projectId = assertSafeId(input.projectId, "Project id");
+ if (this.#projects.has(projectId)) enterpriseError("INVALID_INPUT", "Project already exists.");
+ const project: CanonicalProjectState = {
+ projectId,
+ revision: 1,
+ files: this.#files(input.files),
+ updatedAt: iso(this.#runtime.now()),
+ updatedBy: "system",
+ };
+ this.#projects.set(projectId, project);
+ return clone(project);
+ }
+
+ createBranch(input: { projectId: string; taskOwnerId: string; taskScope: string[] }): AiTaskBranch {
+ const project = this.#project(input.projectId);
+ const taskScope = [...new Set(input.taskScope.map((scope) => {
+ if (!scope.endsWith("/**")) return normalizeProjectPath(scope);
+ const prefix = normalizeProjectPath(`${scope.slice(0, -3)}/placeholder`).replace(/\/placeholder$/, "");
+ return `${prefix}/**`;
+ }))].sort();
+ if (!taskScope.length || taskScope.length > 32) enterpriseError("INVALID_INPUT", "AI task scope is invalid.");
+ const now = iso(this.#runtime.now());
+ const branch: AiTaskBranch = {
+ id: assertSafeId(this.#runtime.id("ai-branch"), "AI branch id"),
+ projectId: project.projectId,
+ taskOwnerId: assertSafeId(input.taskOwnerId, "Task owner id"),
+ taskScope,
+ baseRevision: project.revision,
+ baseFiles: clone(project.files),
+ files: clone(project.files),
+ status: "open",
+ createdAt: now,
+ updatedAt: now,
+ };
+ this.#branches.set(branch.id, branch);
+ return clone(branch);
+ }
+
+ writeBranchFile(input: { branchId: string; path: string; content: string }): AiTaskBranch {
+ const branch = this.#openBranch(input.branchId);
+ const path = normalizeProjectPath(input.path);
+ if (!branch.taskScope.some((scope) => matchesScope(path, scope))) enterpriseError("BRANCH_SCOPE_DENIED", "AI branch write is outside its assigned scope.");
+ if (input.content.length > 1_000_000 || input.content.includes("\0")) enterpriseError("INVALID_INPUT", "Branch file content is invalid.");
+ branch.files[path] = input.content;
+ branch.updatedAt = iso(this.#runtime.now());
+ return clone(branch);
+ }
+
+ deleteBranchFile(input: { branchId: string; path: string }): AiTaskBranch {
+ const branch = this.#openBranch(input.branchId);
+ const path = normalizeProjectPath(input.path);
+ if (!branch.taskScope.some((scope) => matchesScope(path, scope))) enterpriseError("BRANCH_SCOPE_DENIED", "AI branch delete is outside its assigned scope.");
+ delete branch.files[path];
+ branch.updatedAt = iso(this.#runtime.now());
+ return clone(branch);
+ }
+
+ updateCanonical(input: {
+ projectId: string;
+ actorUserId: string;
+ expectedRevision: number;
+ writes: Record;
+ }): CanonicalProjectState {
+ const project = this.#project(input.projectId);
+ if (project.revision !== input.expectedRevision) enterpriseError("REVISION_CONFLICT", "Canonical project revision is stale.");
+ const files = clone(project.files);
+ for (const [rawPath, content] of Object.entries(input.writes)) {
+ const path = normalizeProjectPath(rawPath);
+ if (content === null) delete files[path];
+ else {
+ if (content.length > 1_000_000 || content.includes("\0")) enterpriseError("INVALID_INPUT", "Canonical file content is invalid.");
+ files[path] = content;
+ }
+ }
+ project.files = files;
+ project.revision += 1;
+ project.updatedAt = iso(this.#runtime.now());
+ project.updatedBy = assertSafeId(input.actorUserId, "Actor user id");
+ return clone(project);
+ }
+
+ mergeBranch(input: { branchId: string; actorUserId: string; approved: boolean }): BranchMergeResult {
+ const branch = this.#openBranch(input.branchId, true);
+ if (!input.approved) return { status: "approval-required" };
+ const project = this.#project(branch.projectId);
+ const changedPaths = [...new Set([...Object.keys(branch.baseFiles), ...Object.keys(branch.files)])]
+ .filter((path) => branch.baseFiles[path] !== branch.files[path])
+ .sort();
+ const conflicts = changedPaths.filter((path) =>
+ project.files[path] !== branch.baseFiles[path] && project.files[path] !== branch.files[path])
+ .map((path) => ({
+ path,
+ baseHash: fileHash(branch.baseFiles[path]),
+ canonicalHash: fileHash(project.files[path]),
+ branchHash: fileHash(branch.files[path]),
+ }));
+ if (conflicts.length) {
+ branch.status = "conflict";
+ branch.updatedAt = iso(this.#runtime.now());
+ return { status: "conflict", conflicts };
+ }
+ const checkpoint = this.#checkpoint(project, input.actorUserId, "pre-ai-merge");
+ const files = clone(project.files);
+ for (const path of changedPaths) {
+ const content = branch.files[path];
+ if (content === undefined) delete files[path];
+ else files[path] = content;
+ }
+ project.files = files;
+ project.revision += 1;
+ project.updatedAt = iso(this.#runtime.now());
+ project.updatedBy = assertSafeId(input.actorUserId, "Merge actor id");
+ branch.status = "merged";
+ branch.updatedAt = project.updatedAt;
+ return { status: "merged", checkpointId: checkpoint.id, revision: project.revision };
+ }
+
+ rebaseBranch(input: { branchId: string }): { status: "rebased" } | { status: "conflict"; conflicts: BranchMergeConflict[] } {
+ const branch = this.#branch(input.branchId);
+ if (branch.status !== "open" && branch.status !== "conflict") enterpriseError("INVALID_INPUT", "Only an open or conflicting branch can be rebased.");
+ const project = this.#project(branch.projectId);
+ const changedPaths = [...new Set([...Object.keys(branch.baseFiles), ...Object.keys(branch.files)])]
+ .filter((path) => branch.baseFiles[path] !== branch.files[path]);
+ const conflicts = changedPaths.filter((path) =>
+ project.files[path] !== branch.baseFiles[path] && project.files[path] !== branch.files[path])
+ .map((path) => ({ path, baseHash: fileHash(branch.baseFiles[path]), canonicalHash: fileHash(project.files[path]), branchHash: fileHash(branch.files[path]) }));
+ if (conflicts.length) return { status: "conflict", conflicts };
+ const branchChanges = Object.fromEntries(changedPaths.map((path) => [path, branch.files[path]]));
+ branch.baseRevision = project.revision;
+ branch.baseFiles = clone(project.files);
+ branch.files = clone(project.files);
+ for (const [path, content] of Object.entries(branchChanges)) {
+ if (content === undefined) delete branch.files[path];
+ else branch.files[path] = content;
+ }
+ branch.status = "open";
+ branch.updatedAt = iso(this.#runtime.now());
+ return { status: "rebased" };
+ }
+
+ discardBranch(input: { branchId: string }): AiTaskBranch {
+ const branch = this.#openBranch(input.branchId, true);
+ branch.status = "discarded";
+ branch.updatedAt = iso(this.#runtime.now());
+ return clone(branch);
+ }
+
+ restoreCheckpoint(input: { projectId: string; checkpointId: string; actorUserId: string; expectedRevision: number }): CanonicalProjectState {
+ const project = this.#project(input.projectId);
+ if (project.revision !== input.expectedRevision) enterpriseError("REVISION_CONFLICT", "Restore revision is stale.");
+ const checkpoint = this.#checkpoints.get(input.checkpointId);
+ if (!checkpoint || checkpoint.projectId !== project.projectId) enterpriseError("NOT_FOUND", "Checkpoint was not found for this project.");
+ project.files = clone(checkpoint.files);
+ project.revision += 1;
+ project.updatedAt = iso(this.#runtime.now());
+ project.updatedBy = assertSafeId(input.actorUserId, "Restore actor id");
+ return clone(project);
+ }
+
+ project(id: string): CanonicalProjectState {
+ return clone(this.#project(id));
+ }
+
+ branch(id: string): AiTaskBranch {
+ return clone(this.#branch(id));
+ }
+
+ #checkpoint(project: CanonicalProjectState, actorId: string, reason: ProjectCheckpoint["reason"]): ProjectCheckpoint {
+ const checkpoint: ProjectCheckpoint = {
+ id: assertSafeId(this.#runtime.id("checkpoint"), "Checkpoint id"),
+ projectId: project.projectId,
+ sourceRevision: project.revision,
+ files: clone(project.files),
+ createdAt: iso(this.#runtime.now()),
+ createdBy: assertSafeId(actorId, "Checkpoint actor id"),
+ reason,
+ };
+ this.#checkpoints.set(checkpoint.id, checkpoint);
+ return checkpoint;
+ }
+
+ #files(input: Record): Record {
+ const entries = Object.entries(input);
+ if (entries.length > 5_000) enterpriseError("INVALID_INPUT", "Project file count exceeds the branch manager limit.");
+ return Object.fromEntries(entries.map(([path, content]) => {
+ if (content.length > 1_000_000 || content.includes("\0")) enterpriseError("INVALID_INPUT", "Project file content is invalid.");
+ return [normalizeProjectPath(path), content];
+ }));
+ }
+
+ #project(id: string): CanonicalProjectState {
+ const project = this.#projects.get(id);
+ if (!project) enterpriseError("NOT_FOUND", "Canonical project was not found.");
+ return project;
+ }
+
+ #branch(id: string): AiTaskBranch {
+ const branch = this.#branches.get(id);
+ if (!branch) enterpriseError("NOT_FOUND", "AI task branch was not found.");
+ return branch;
+ }
+
+ #openBranch(id: string, allowConflict = false): AiTaskBranch {
+ const branch = this.#branch(id);
+ if (branch.status !== "open" && !(allowConflict && branch.status === "conflict")) enterpriseError("INVALID_INPUT", "AI task branch is not open.");
+ return branch;
+ }
+}
diff --git a/lib/enterprise-platform/collaboration.ts b/lib/enterprise-platform/collaboration.ts
new file mode 100644
index 0000000..e4eba94
--- /dev/null
+++ b/lib/enterprise-platform/collaboration.ts
@@ -0,0 +1,361 @@
+import { enterpriseError } from "./errors.ts";
+import type { EnterprisePermission, EnterpriseRuntime } from "./types.ts";
+import { assertSafeId, boundedText, clone, iso, normalizeProjectPath, sha256, stableJson } from "./utils.ts";
+
+export interface TextInsertOperation {
+ kind: "insert";
+ operationId: string;
+ actorId: string;
+ lamport: number;
+ afterId: string | "ROOT";
+ value: string;
+}
+
+export interface TextDeleteOperation {
+ kind: "delete";
+ operationId: string;
+ actorId: string;
+ lamport: number;
+ targetId: string;
+}
+
+export type CollaborativeTextOperation = TextInsertOperation | TextDeleteOperation;
+
+export interface CollaborativeTextDocument {
+ id: string;
+ operations: CollaborativeTextOperation[];
+}
+
+interface RenderNode {
+ id: string;
+ value: string;
+}
+
+const MAX_DOCUMENT_CHARACTERS = 100_000;
+const MAX_DOCUMENT_OPERATIONS = 250_000;
+
+function operationOrder(left: TextInsertOperation, right: TextInsertOperation): number {
+ return right.lamport - left.lamport
+ || left.actorId.localeCompare(right.actorId)
+ || left.operationId.localeCompare(right.operationId);
+}
+
+function validateOperations(operations: readonly CollaborativeTextOperation[]): void {
+ if (operations.length > MAX_DOCUMENT_OPERATIONS) enterpriseError("COLLABORATION_OPERATION_INVALID", "Collaborative operation limit exceeded.");
+ const inserts = new Map();
+ const ids = new Map();
+ for (const operation of operations) {
+ assertSafeId(operation.operationId, "Operation id");
+ assertSafeId(operation.actorId, "Operation actor id");
+ if (!Number.isSafeInteger(operation.lamport) || operation.lamport < 0) enterpriseError("COLLABORATION_OPERATION_INVALID", "Lamport clock is invalid.");
+ const existing = ids.get(operation.operationId);
+ if (existing && stableJson(existing) !== stableJson(operation)) enterpriseError("COLLABORATION_OPERATION_INVALID", "Operation id has conflicting content.");
+ ids.set(operation.operationId, operation);
+ if (operation.kind === "insert") {
+ if (!operation.value || [...operation.value].length !== 1 || operation.value.includes("\0")) {
+ enterpriseError("COLLABORATION_OPERATION_INVALID", "Insert operations contain exactly one Unicode character.");
+ }
+ inserts.set(operation.operationId, operation);
+ }
+ }
+ for (const operation of ids.values()) {
+ if (operation.kind === "insert" && operation.afterId !== "ROOT" && !inserts.has(operation.afterId)) {
+ enterpriseError("COLLABORATION_OPERATION_INVALID", "Insert anchor does not exist.");
+ }
+ if (operation.kind === "delete" && !inserts.has(operation.targetId)) {
+ enterpriseError("COLLABORATION_OPERATION_INVALID", "Delete target does not exist.");
+ }
+ }
+}
+
+function renderNodes(document: CollaborativeTextDocument): RenderNode[] {
+ validateOperations(document.operations);
+ const inserts = document.operations.filter((operation): operation is TextInsertOperation => operation.kind === "insert");
+ const deleted = new Set(document.operations.filter((operation): operation is TextDeleteOperation => operation.kind === "delete").map((operation) => operation.targetId));
+ const children = new Map();
+ for (const insert of inserts) {
+ const entries = children.get(insert.afterId) ?? [];
+ entries.push(insert);
+ children.set(insert.afterId, entries);
+ }
+ for (const entries of children.values()) entries.sort(operationOrder);
+ const result: RenderNode[] = [];
+ const visiting = new Set();
+ const visited = new Set();
+ const stack: Array<{ parentId: string; children: TextInsertOperation[]; index: number }> = [
+ { parentId: "ROOT", children: children.get("ROOT") ?? [], index: 0 },
+ ];
+ while (stack.length) {
+ const frame = stack.at(-1)!;
+ if (frame.index >= frame.children.length) {
+ stack.pop();
+ if (frame.parentId !== "ROOT") visiting.delete(frame.parentId);
+ continue;
+ }
+ const child = frame.children[frame.index++];
+ if (visiting.has(child.operationId)) enterpriseError("COLLABORATION_OPERATION_INVALID", "Collaborative operation cycle detected.");
+ if (visited.has(child.operationId)) continue;
+ visiting.add(child.operationId);
+ visited.add(child.operationId);
+ if (!deleted.has(child.operationId)) result.push({ id: child.operationId, value: child.value });
+ stack.push({ parentId: child.operationId, children: children.get(child.operationId) ?? [], index: 0 });
+ }
+ if (visited.size !== inserts.length) enterpriseError("COLLABORATION_OPERATION_INVALID", "Collaborative document contains unreachable operations.");
+ if (result.length > MAX_DOCUMENT_CHARACTERS) enterpriseError("COLLABORATION_OPERATION_INVALID", "Collaborative document size limit exceeded.");
+ return result;
+}
+
+export function createCollaborativeTextDocument(id: string, initialText = ""): CollaborativeTextDocument {
+ assertSafeId(id, "Document id");
+ if ([...initialText].length > MAX_DOCUMENT_CHARACTERS || initialText.includes("\0")) enterpriseError("COLLABORATION_OPERATION_INVALID", "Initial document is invalid.");
+ let afterId: string | "ROOT" = "ROOT";
+ const operations: TextInsertOperation[] = [];
+ [...initialText].forEach((value, index) => {
+ const operationId = `${id}:base:${String(index).padStart(6, "0")}`;
+ operations.push({ kind: "insert", operationId, actorId: "system-base", lamport: 0, afterId, value });
+ afterId = operationId;
+ });
+ return { id, operations };
+}
+
+export function renderCollaborativeText(document: CollaborativeTextDocument): string {
+ return renderNodes(document).map((node) => node.value).join("");
+}
+
+export function createInsertOperations(
+ document: CollaborativeTextDocument,
+ input: { actorId: string; lamport: number; index: number; text: string },
+): TextInsertOperation[] {
+ assertSafeId(input.actorId, "Operation actor id");
+ if (!Number.isSafeInteger(input.lamport) || input.lamport < 1) enterpriseError("COLLABORATION_OPERATION_INVALID", "Lamport clock is invalid.");
+ const nodes = renderNodes(document);
+ if (!Number.isSafeInteger(input.index) || input.index < 0 || input.index > nodes.length) enterpriseError("COLLABORATION_OPERATION_INVALID", "Insert index is invalid.");
+ const characters = [...input.text];
+ if (!characters.length || characters.length > 4_096 || input.text.includes("\0")) enterpriseError("COLLABORATION_OPERATION_INVALID", "Insert text is invalid.");
+ let afterId: string | "ROOT" = input.index === 0 ? "ROOT" : nodes[input.index - 1].id;
+ return characters.map((value, index) => {
+ const lamport = input.lamport + index;
+ const operationId = `${input.actorId}:insert:${lamport}:${index}:${sha256(`${document.id}:${afterId}:${value}`).slice(0, 8)}`;
+ const operation: TextInsertOperation = { kind: "insert", operationId, actorId: input.actorId, lamport, afterId, value };
+ afterId = operationId;
+ return operation;
+ });
+}
+
+export function createDeleteOperations(
+ document: CollaborativeTextDocument,
+ input: { actorId: string; lamport: number; index: number; length: number },
+): TextDeleteOperation[] {
+ assertSafeId(input.actorId, "Operation actor id");
+ const nodes = renderNodes(document);
+ if (
+ !Number.isSafeInteger(input.lamport) || input.lamport < 1
+ || !Number.isSafeInteger(input.index) || input.index < 0
+ || !Number.isSafeInteger(input.length) || input.length < 1
+ || input.index + input.length > nodes.length
+ ) enterpriseError("COLLABORATION_OPERATION_INVALID", "Delete range is invalid.");
+ return nodes.slice(input.index, input.index + input.length).map((node, index) => ({
+ kind: "delete",
+ operationId: `${input.actorId}:delete:${input.lamport + index}:${sha256(node.id).slice(0, 8)}`,
+ actorId: input.actorId,
+ lamport: input.lamport + index,
+ targetId: node.id,
+ }));
+}
+
+export function applyTextOperations(
+ document: CollaborativeTextDocument,
+ incoming: readonly CollaborativeTextOperation[],
+): CollaborativeTextDocument {
+ const merged = new Map();
+ for (const operation of [...document.operations, ...incoming]) {
+ const existing = merged.get(operation.operationId);
+ if (existing && stableJson(existing) !== stableJson(operation)) enterpriseError("COLLABORATION_OPERATION_INVALID", "Operation id has conflicting content.");
+ merged.set(operation.operationId, clone(operation));
+ }
+ const next = { id: document.id, operations: [...merged.values()].sort((left, right) => left.operationId.localeCompare(right.operationId)) };
+ renderNodes(next);
+ return next;
+}
+
+export interface PresenceParticipant {
+ userId: string;
+ displayName: string;
+ activeFile: string;
+ state: "editing" | "viewing";
+ cursor?: { anchor: number; head: number };
+ connectedAt: number;
+ updatedAt: number;
+ expiresAt: number;
+}
+
+export class LocalPresenceRoom {
+ readonly #roomId: string;
+ readonly #ttlMs: number;
+ readonly #maximumParticipants: number;
+ readonly #authorized = new Map();
+ readonly #presence = new Map();
+
+ constructor(input: { roomId: string; ttlMs: number; maximumParticipants: number }) {
+ this.#roomId = assertSafeId(input.roomId, "Room id");
+ if (!Number.isSafeInteger(input.ttlMs) || input.ttlMs < 1_000 || input.ttlMs > 120_000) enterpriseError("INVALID_INPUT", "Presence TTL is invalid.");
+ if (!Number.isSafeInteger(input.maximumParticipants) || input.maximumParticipants < 1 || input.maximumParticipants > 100) enterpriseError("INVALID_INPUT", "Room capacity is invalid.");
+ this.#ttlMs = input.ttlMs;
+ this.#maximumParticipants = input.maximumParticipants;
+ }
+
+ authorize(input: { userId: string; displayName: string; canEdit: boolean }): void {
+ this.#authorized.set(assertSafeId(input.userId, "User id"), { displayName: boundedText(input.displayName, "Display name", 80), canEdit: input.canEdit });
+ }
+
+ revoke(userId: string): void {
+ this.#authorized.delete(userId);
+ this.#presence.delete(userId);
+ }
+
+ update(input: {
+ userId: string;
+ activeFile: string;
+ state: "editing" | "viewing";
+ cursor?: { anchor: number; head: number };
+ at: number;
+ }): PresenceParticipant {
+ const authorization = this.#authorized.get(input.userId);
+ if (!authorization) enterpriseError("ROOM_ACCESS_DENIED", `User is not authorized for room ${this.#roomId}.`);
+ if (input.state === "editing" && !authorization.canEdit) enterpriseError("PERMISSION_DENIED", "Viewer cannot publish editing presence.");
+ if (!Number.isSafeInteger(input.at) || input.at < 0) enterpriseError("INVALID_INPUT", "Presence timestamp is invalid.");
+ if (input.cursor && (!Number.isSafeInteger(input.cursor.anchor) || !Number.isSafeInteger(input.cursor.head) || input.cursor.anchor < 0 || input.cursor.head < 0)) {
+ enterpriseError("INVALID_INPUT", "Presence cursor is invalid.");
+ }
+ const existing = this.#presence.get(input.userId);
+ if (!existing && this.list(input.at).length >= this.#maximumParticipants) enterpriseError("ROOM_CAPACITY_EXCEEDED", "Presence room is full.");
+ const participant: PresenceParticipant = {
+ userId: input.userId,
+ displayName: authorization.displayName,
+ activeFile: normalizeProjectPath(input.activeFile),
+ state: input.state,
+ ...(input.cursor ? { cursor: clone(input.cursor) } : {}),
+ connectedAt: existing?.connectedAt ?? input.at,
+ updatedAt: input.at,
+ expiresAt: input.at + this.#ttlMs,
+ };
+ this.#presence.set(input.userId, participant);
+ return clone(participant);
+ }
+
+ list(at: number): PresenceParticipant[] {
+ for (const [userId, participant] of this.#presence) if (participant.expiresAt < at) this.#presence.delete(userId);
+ return [...this.#presence.values()].sort((left, right) => left.userId.localeCompare(right.userId)).map(clone);
+ }
+
+ state(): { status: "working-local-test"; mode: "deterministic-local-test"; providerEvidence: false } {
+ return { status: "working-local-test", mode: "deterministic-local-test", providerEvidence: false };
+ }
+}
+
+export interface CommentRecord {
+ id: string;
+ authorUserId: string;
+ body: string;
+ mentions: string[];
+ createdAt: string;
+}
+
+export interface CommentThreadRecord {
+ id: string;
+ projectId: string;
+ filePath: string;
+ range: { start: number; end: number };
+ status: "open" | "resolved";
+ comments: CommentRecord[];
+ resolvedByUserId: string | null;
+ resolvedAt: string | null;
+ createdAt: string;
+}
+
+export class CollaborationComments {
+ readonly #runtime: EnterpriseRuntime & { can(userId: string, permission: EnterprisePermission): boolean };
+ readonly #threads = new Map();
+
+ constructor(runtime: EnterpriseRuntime & { can(userId: string, permission: EnterprisePermission): boolean }) {
+ this.#runtime = runtime;
+ }
+
+ createThread(input: {
+ actorUserId: string;
+ projectId: string;
+ filePath: string;
+ range: { start: number; end: number };
+ body: string;
+ mentions?: string[];
+ }): CommentThreadRecord {
+ this.#assert(input.actorUserId, "collaboration.comment");
+ if (!Number.isSafeInteger(input.range.start) || !Number.isSafeInteger(input.range.end) || input.range.start < 0 || input.range.end < input.range.start) {
+ enterpriseError("INVALID_INPUT", "Comment range is invalid.");
+ }
+ const now = iso(this.#runtime.now());
+ const thread: CommentThreadRecord = {
+ id: assertSafeId(this.#runtime.id("comment-thread"), "Comment thread id"),
+ projectId: assertSafeId(input.projectId, "Project id"),
+ filePath: normalizeProjectPath(input.filePath),
+ range: clone(input.range),
+ status: "open",
+ comments: [this.#comment(input.actorUserId, input.body, input.mentions ?? [], now)],
+ resolvedByUserId: null,
+ resolvedAt: null,
+ createdAt: now,
+ };
+ this.#threads.set(thread.id, thread);
+ return clone(thread);
+ }
+
+ reply(input: { actorUserId: string; threadId: string; body: string; mentions?: string[] }): CommentRecord {
+ this.#assert(input.actorUserId, "collaboration.comment");
+ const thread = this.#thread(input.threadId);
+ const comment = this.#comment(input.actorUserId, input.body, input.mentions ?? [], iso(this.#runtime.now()));
+ thread.comments.push(comment);
+ return clone(comment);
+ }
+
+ resolve(input: { actorUserId: string; threadId: string }): CommentThreadRecord {
+ this.#assert(input.actorUserId, "collaboration.merge");
+ const thread = this.#thread(input.threadId);
+ thread.status = "resolved";
+ thread.resolvedByUserId = input.actorUserId;
+ thread.resolvedAt = iso(this.#runtime.now());
+ return clone(thread);
+ }
+
+ reopen(input: { actorUserId: string; threadId: string }): CommentThreadRecord {
+ this.#assert(input.actorUserId, "collaboration.merge");
+ const thread = this.#thread(input.threadId);
+ thread.status = "open";
+ thread.resolvedByUserId = null;
+ thread.resolvedAt = null;
+ return clone(thread);
+ }
+
+ thread(id: string): CommentThreadRecord {
+ return clone(this.#thread(id));
+ }
+
+ #comment(actorUserId: string, body: string, mentions: string[], createdAt: string): CommentRecord {
+ return {
+ id: assertSafeId(this.#runtime.id("comment"), "Comment id"),
+ authorUserId: assertSafeId(actorUserId, "Comment author id"),
+ body: boundedText(body, "Comment body", 4_000),
+ mentions: [...new Set(mentions.map((mention) => assertSafeId(mention, "Mention user id")))].sort(),
+ createdAt,
+ };
+ }
+
+ #thread(id: string): CommentThreadRecord {
+ const thread = this.#threads.get(id);
+ if (!thread) enterpriseError("NOT_FOUND", "Comment thread was not found.");
+ return thread;
+ }
+
+ #assert(userId: string, permission: EnterprisePermission): void {
+ if (!this.#runtime.can(userId, permission)) enterpriseError("PERMISSION_DENIED", `Permission ${permission} is required.`);
+ }
+}
diff --git a/lib/enterprise-platform/credentials.ts b/lib/enterprise-platform/credentials.ts
new file mode 100644
index 0000000..882f947
--- /dev/null
+++ b/lib/enterprise-platform/credentials.ts
@@ -0,0 +1,196 @@
+import { createHmac, timingSafeEqual } from "node:crypto";
+
+import { enterpriseError } from "./errors.ts";
+import type { EnterprisePermission, SecretRuntime } from "./types.ts";
+import { ENTERPRISE_PERMISSIONS } from "./types.ts";
+import { assertSafeId, boundedText, clone, iso, sha256 } from "./utils.ts";
+
+export interface ServiceAccountRecord {
+ id: string;
+ organizationId: string;
+ name: string;
+ description?: string;
+ permissions: EnterprisePermission[];
+ projectIds: string[];
+ environments: string[];
+ allowedIpHashes: string[];
+ expiresAt: string | null;
+ revokedAt: string | null;
+ createdAt: string;
+}
+
+export interface ApiTokenRecord {
+ id: string;
+ serviceAccountId: string;
+ prefix: string;
+ permissions: EnterprisePermission[];
+ expiresAt: string;
+ revokedAt: string | null;
+ rotatedFromTokenId: string | null;
+ lastUsedAt: string | null;
+ createdAt: string;
+}
+
+interface StoredApiToken extends ApiTokenRecord {
+ digest: string;
+}
+
+export class EnterpriseCredentialStore {
+ readonly #runtime: SecretRuntime;
+ readonly #pepper: string;
+ readonly #accounts = new Map();
+ readonly #tokens = new Map();
+
+ constructor(input: { runtime: SecretRuntime; tokenPepper: string }) {
+ if (input.tokenPepper.length < 32) enterpriseError("INVALID_INPUT", "API token pepper must contain at least 32 characters.");
+ this.#runtime = input.runtime;
+ this.#pepper = input.tokenPepper;
+ }
+
+ createServiceAccount(input: {
+ organizationId: string;
+ name: string;
+ description?: string;
+ permissions: EnterprisePermission[];
+ projectIds?: string[];
+ environments?: string[];
+ allowedIpAddresses?: string[];
+ expiresAt?: string;
+ }): ServiceAccountRecord {
+ const permissions = this.#permissions(input.permissions);
+ const expiresAt = input.expiresAt ? iso(new Date(input.expiresAt)) : null;
+ if (expiresAt && Date.parse(expiresAt) <= this.#runtime.now().getTime()) enterpriseError("INVALID_INPUT", "Service account expiry must be in the future.");
+ const account: ServiceAccountRecord = {
+ id: assertSafeId(this.#runtime.id("service-account"), "Service account id"),
+ organizationId: assertSafeId(input.organizationId, "Organization id"),
+ name: boundedText(input.name, "Service account name", 120),
+ ...(input.description ? { description: boundedText(input.description, "Service account description", 500) } : {}),
+ permissions,
+ projectIds: [...new Set((input.projectIds ?? []).map((id) => assertSafeId(id, "Project id")))].sort(),
+ environments: [...new Set((input.environments ?? []).map((environment) => boundedText(environment, "Environment", 80)))].sort(),
+ allowedIpHashes: [...new Set((input.allowedIpAddresses ?? []).map((address) => this.#digest(`ip:${address.trim()}`)))].sort(),
+ expiresAt,
+ revokedAt: null,
+ createdAt: iso(this.#runtime.now()),
+ };
+ this.#accounts.set(account.id, account);
+ return clone(account);
+ }
+
+ issueToken(input: { serviceAccountId: string; permissions: EnterprisePermission[]; expiresInMs: number }): {
+ token: string;
+ tokenRecord: ApiTokenRecord;
+ } {
+ const account = this.#account(input.serviceAccountId);
+ this.#assertAccountActive(account);
+ const permissions = this.#permissions(input.permissions);
+ if (permissions.some((permission) => !account.permissions.includes(permission))) enterpriseError("TOKEN_SCOPE_DENIED", "Token cannot exceed service-account permissions.");
+ if (!Number.isSafeInteger(input.expiresInMs) || input.expiresInMs < 1_000 || input.expiresInMs > 365 * 86_400_000) enterpriseError("INVALID_INPUT", "API token expiry is invalid.");
+ const entropy = this.#runtime.entropy("service_account_token");
+ if (entropy.length < 32) enterpriseError("INVALID_INPUT", "API token entropy is insufficient.");
+ const token = `dst_sa_${entropy}`;
+ const record: StoredApiToken = {
+ id: assertSafeId(this.#runtime.id("api-token"), "API token id"),
+ serviceAccountId: account.id,
+ prefix: "dst_sa_",
+ permissions,
+ expiresAt: iso(new Date(this.#runtime.now().getTime() + input.expiresInMs)),
+ revokedAt: null,
+ rotatedFromTokenId: null,
+ lastUsedAt: null,
+ createdAt: iso(this.#runtime.now()),
+ digest: this.#digest(token),
+ };
+ this.#tokens.set(record.id, record);
+ return { token, tokenRecord: this.#publicToken(record) };
+ }
+
+ rotateToken(input: { tokenId: string; expiresInMs: number }): { token: string; tokenRecord: ApiTokenRecord } {
+ const prior = this.#token(input.tokenId);
+ if (prior.revokedAt) enterpriseError("TOKEN_REVOKED", "API token is already revoked.");
+ const replacement = this.issueToken({ serviceAccountId: prior.serviceAccountId, permissions: prior.permissions, expiresInMs: input.expiresInMs });
+ prior.revokedAt = iso(this.#runtime.now());
+ const stored = this.#token(replacement.tokenRecord.id);
+ stored.rotatedFromTokenId = prior.id;
+ return { token: replacement.token, tokenRecord: this.#publicToken(stored) };
+ }
+
+ revokeToken(tokenId: string): ApiTokenRecord {
+ const token = this.#token(tokenId);
+ token.revokedAt = token.revokedAt ?? iso(this.#runtime.now());
+ return this.#publicToken(token);
+ }
+
+ revokeServiceAccount(serviceAccountId: string): ServiceAccountRecord {
+ const account = this.#account(serviceAccountId);
+ account.revokedAt = account.revokedAt ?? iso(this.#runtime.now());
+ for (const token of this.#tokens.values()) if (token.serviceAccountId === account.id && !token.revokedAt) token.revokedAt = account.revokedAt;
+ return clone(account);
+ }
+
+ authenticate(input: {
+ token: string;
+ permission: EnterprisePermission;
+ projectId?: string;
+ environment?: string;
+ ipAddress?: string;
+ }): { serviceAccountId: string; tokenId: string; organizationId: string; permission: EnterprisePermission } {
+ const digest = this.#digest(input.token);
+ const candidate = [...this.#tokens.values()].find((entry) => {
+ const left = Buffer.from(entry.digest, "hex");
+ const right = Buffer.from(digest, "hex");
+ return left.length === right.length && timingSafeEqual(left, right);
+ });
+ if (!candidate) enterpriseError("TOKEN_INVALID", "API token is invalid.");
+ if (candidate.revokedAt) enterpriseError("TOKEN_REVOKED", "API token is revoked.");
+ if (this.#runtime.now().getTime() > Date.parse(candidate.expiresAt)) enterpriseError("TOKEN_EXPIRED", "API token is expired.");
+ const account = this.#account(candidate.serviceAccountId);
+ this.#assertAccountActive(account);
+ if (!candidate.permissions.includes(input.permission)) enterpriseError("TOKEN_SCOPE_DENIED", "API token permission is out of scope.");
+ if (account.projectIds.length && (!input.projectId || !account.projectIds.includes(input.projectId))) enterpriseError("TOKEN_PROJECT_DENIED", "API token project is out of scope.");
+ if (account.environments.length && (!input.environment || !account.environments.includes(input.environment))) enterpriseError("TOKEN_ENVIRONMENT_DENIED", "API token environment is out of scope.");
+ if (account.allowedIpHashes.length && (!input.ipAddress || !account.allowedIpHashes.includes(this.#digest(`ip:${input.ipAddress.trim()}`)))) enterpriseError("TOKEN_IP_DENIED", "API token IP is out of scope.");
+ candidate.lastUsedAt = iso(this.#runtime.now());
+ return { serviceAccountId: account.id, tokenId: candidate.id, organizationId: account.organizationId, permission: input.permission };
+ }
+
+ snapshot(): { serviceAccounts: ServiceAccountRecord[]; tokens: ApiTokenRecord[]; storageIntegrityHash: string } {
+ const serviceAccounts = [...this.#accounts.values()].map(clone);
+ const tokens = [...this.#tokens.values()].map((entry) => this.#publicToken(entry));
+ const storageIntegrityHash = sha256(JSON.stringify([...this.#tokens.values()].map((entry) => ({ id: entry.id, digest: entry.digest })).sort((left, right) => left.id.localeCompare(right.id))));
+ return { serviceAccounts, tokens, storageIntegrityHash };
+ }
+
+ #assertAccountActive(account: ServiceAccountRecord): void {
+ if (account.revokedAt) enterpriseError("TOKEN_REVOKED", "Service account is revoked.");
+ if (account.expiresAt && this.#runtime.now().getTime() > Date.parse(account.expiresAt)) enterpriseError("TOKEN_EXPIRED", "Service account is expired.");
+ }
+
+ #permissions(input: EnterprisePermission[]): EnterprisePermission[] {
+ const permissions = [...new Set(input)].sort();
+ if (!permissions.length || permissions.some((permission) => !ENTERPRISE_PERMISSIONS.includes(permission))) enterpriseError("INVALID_INPUT", "Credential permissions are invalid.");
+ return permissions;
+ }
+
+ #digest(value: string): string {
+ return createHmac("sha256", this.#pepper).update(value, "utf8").digest("hex");
+ }
+
+ #account(id: string): ServiceAccountRecord {
+ const account = this.#accounts.get(id);
+ if (!account) enterpriseError("NOT_FOUND", "Service account was not found.");
+ return account;
+ }
+
+ #token(id: string): StoredApiToken {
+ const token = this.#tokens.get(id);
+ if (!token) enterpriseError("NOT_FOUND", "API token was not found.");
+ return token;
+ }
+
+ #publicToken(stored: StoredApiToken): ApiTokenRecord {
+ const record = clone(stored) as Partial;
+ delete record.digest;
+ return record as ApiTokenRecord;
+ }
+}
diff --git a/lib/enterprise-platform/errors.ts b/lib/enterprise-platform/errors.ts
new file mode 100644
index 0000000..2001df6
--- /dev/null
+++ b/lib/enterprise-platform/errors.ts
@@ -0,0 +1,54 @@
+export type EnterprisePlatformErrorCode =
+ | "INVALID_INPUT"
+ | "NOT_FOUND"
+ | "PERMISSION_DENIED"
+ | "TENANT_MISMATCH"
+ | "CONFIRMATION_REQUIRED"
+ | "INVITATION_EXPIRED"
+ | "INVITATION_REVOKED"
+ | "INVITATION_REPLAY"
+ | "INVITATION_EMAIL_MISMATCH"
+ | "COLLABORATION_OPERATION_INVALID"
+ | "ROOM_ACCESS_DENIED"
+ | "ROOM_CAPACITY_EXCEEDED"
+ | "REVISION_CONFLICT"
+ | "BRANCH_SCOPE_DENIED"
+ | "OIDC_SETUP_REQUIRED"
+ | "OIDC_STATE_INVALID"
+ | "OIDC_NONCE_INVALID"
+ | "OIDC_PKCE_INVALID"
+ | "OIDC_CODE_INVALID"
+ | "OIDC_REPLAY"
+ | "OIDC_DOMAIN_DENIED"
+ | "DOMAIN_CLAIMED"
+ | "DOMAIN_CHALLENGE_EXPIRED"
+ | "DOMAIN_VERIFICATION_FAILED"
+ | "TOKEN_INVALID"
+ | "TOKEN_REVOKED"
+ | "TOKEN_EXPIRED"
+ | "TOKEN_SCOPE_DENIED"
+ | "TOKEN_PROJECT_DENIED"
+ | "TOKEN_ENVIRONMENT_DENIED"
+ | "TOKEN_IP_DENIED"
+ | "AUDIT_SECRET_REJECTED"
+ | "RETENTION_INVALID"
+ | "EXPORT_NOT_PENDING"
+ | "DELETION_NOT_PENDING"
+ | "DELETION_DEPENDENCIES"
+ | "BACKUP_EVIDENCE_REQUIRED"
+ | "PRODUCTION_APPROVAL_REQUIRED"
+ | "RESTORE_EVIDENCE_REQUIRED";
+
+export class EnterprisePlatformError extends Error {
+ readonly code: EnterprisePlatformErrorCode;
+
+ constructor(code: EnterprisePlatformErrorCode, message: string) {
+ super(message);
+ this.name = "EnterprisePlatformError";
+ this.code = code;
+ }
+}
+
+export function enterpriseError(code: EnterprisePlatformErrorCode, message: string): never {
+ throw new EnterprisePlatformError(code, message);
+}
diff --git a/lib/enterprise-platform/feature-states.ts b/lib/enterprise-platform/feature-states.ts
new file mode 100644
index 0000000..5864cc8
--- /dev/null
+++ b/lib/enterprise-platform/feature-states.ts
@@ -0,0 +1,24 @@
+import type { EnterpriseFeatureState } from "./types.ts";
+
+export function enterpriseFeatureStates(input: {
+ organizations?: boolean;
+ localCollaboration?: boolean;
+ localTestOidc?: boolean;
+}): Record {
+ const local = (enabled: boolean, mode: string, reason: string): EnterpriseFeatureState => enabled
+ ? { status: "working-local-test", mode, providerEvidence: false, reason }
+ : { status: "disabled", mode: "disabled", providerEvidence: false, reason: "Feature flag is disabled." };
+ return {
+ organizations: local(Boolean(input.organizations), "in-memory-reference-adapter", "Organization domain is enabled only in the local/test reference adapter."),
+ realtimeCollaboration: input.localCollaboration
+ ? { status: "working-local-test", mode: "deterministic-local-test", providerEvidence: false, reason: "Deterministic operations and presence run in process; no network realtime transport is configured." }
+ : { status: "setup-required", mode: "transport-not-configured", providerEvidence: false, reason: "Realtime collaboration transport is not configured." },
+ enterpriseOidc: local(Boolean(input.localTestOidc), "standards-shaped-local-test-oidc", "OIDC is backed by the local test authorization-code adapter, not an external provider."),
+ enterpriseSaml: { status: "setup-required", mode: "not-configured", providerEvidence: false, reason: "SAML adapter not configured." },
+ scim: { status: "setup-required", mode: "not-configured", providerEvidence: false, reason: "SCIM adapter not configured." },
+ serviceAccounts: local(true, "in-memory-reference-adapter", "Scoped credentials are reference/test records until a durable store is wired."),
+ enterprisePolicies: local(true, "deterministic-policy-engine", "Policy resolution is deterministic and does not contact external providers."),
+ auditLog: local(true, "in-memory-integrity-chain", "Audit integrity is testable in process; durable append-only storage must be configured separately."),
+ backups: { status: "setup-required", mode: "metadata-only-reference-adapter", providerEvidence: false, reason: "External backup artifact storage is not configured." },
+ };
+}
diff --git a/lib/enterprise-platform/identity.ts b/lib/enterprise-platform/identity.ts
new file mode 100644
index 0000000..1841700
--- /dev/null
+++ b/lib/enterprise-platform/identity.ts
@@ -0,0 +1,267 @@
+import { createHash } from "node:crypto";
+
+import { enterpriseError } from "./errors.ts";
+import type { DefaultRoleId, EnterpriseFeatureState, SecretRuntime } from "./types.ts";
+import { DEFAULT_ROLE_IDS } from "./types.ts";
+import { assertSafeId, boundedText, clone, iso, normalizeDomain, normalizeEmail, sha256 } from "./utils.ts";
+
+export interface EnterpriseIdentity {
+ provider: "oidc-local-test";
+ organizationId: string;
+ subject: string;
+ email: string;
+ groups: string[];
+ roleId: DefaultRoleId;
+ authenticatedAt: string;
+ providerEvidence: false;
+}
+
+export interface EnterpriseIdentityAdapter {
+ state(): EnterpriseFeatureState;
+ begin(input: { organizationId: string; redirectUri: string }): {
+ authorizationUrl: string;
+ state: string;
+ nonce: string;
+ codeVerifier: string;
+ };
+}
+
+interface PendingOidcRequest {
+ organizationId: string;
+ redirectUri: string;
+ stateHash: string;
+ nonceHash: string;
+ verifierHash: string;
+ expiresAt: string;
+ codeHash?: string;
+ claims?: LocalTestOidcClaims;
+}
+
+export interface LocalTestOidcClaims {
+ subject: string;
+ email: string;
+ groups: string[];
+}
+
+function base64UrlSha256(value: string): string {
+ return createHash("sha256").update(value, "utf8").digest("base64url");
+}
+
+export class LocalTestOidcAdapter implements EnterpriseIdentityAdapter {
+ readonly #issuer: string;
+ readonly #clientId: string;
+ readonly #allowedDomains: Set;
+ readonly #groupRoleMappings: Readonly>;
+ readonly #runtime: SecretRuntime;
+ readonly #pending = new Map();
+ readonly #usedStates = new Set();
+
+ constructor(input: {
+ issuer: string;
+ clientId: string;
+ allowedDomains: string[];
+ groupRoleMappings: Record;
+ runtime: SecretRuntime;
+ }) {
+ const issuer = new URL(input.issuer);
+ if (issuer.protocol !== "https:" || issuer.hostname !== "oidc.test.local") {
+ enterpriseError("OIDC_SETUP_REQUIRED", "Local OIDC adapter accepts only the explicit oidc.test.local issuer.");
+ }
+ this.#issuer = issuer.origin;
+ this.#clientId = boundedText(input.clientId, "OIDC client id", 160);
+ this.#allowedDomains = new Set(input.allowedDomains.map(normalizeDomain));
+ if (!this.#allowedDomains.size) enterpriseError("INVALID_INPUT", "OIDC allowed domains are required.");
+ for (const role of Object.values(input.groupRoleMappings)) {
+ if (!(DEFAULT_ROLE_IDS as readonly string[]).includes(role)) enterpriseError("INVALID_INPUT", "OIDC group role mapping is invalid.");
+ }
+ this.#groupRoleMappings = clone(input.groupRoleMappings);
+ this.#runtime = input.runtime;
+ }
+
+ state(): EnterpriseFeatureState {
+ return {
+ status: "working-local-test",
+ mode: "standards-shaped-local-test-oidc",
+ providerEvidence: false,
+ reason: "Authorization codes are issued by the in-process test adapter; no external identity provider is contacted.",
+ };
+ }
+
+ begin(input: { organizationId: string; redirectUri: string }): {
+ authorizationUrl: string;
+ state: string;
+ nonce: string;
+ codeVerifier: string;
+ } {
+ const organizationId = assertSafeId(input.organizationId, "Organization id");
+ const redirect = new URL(input.redirectUri);
+ if (redirect.protocol !== "https:") enterpriseError("INVALID_INPUT", "OIDC redirect URI must use HTTPS.");
+ const state = this.#runtime.entropy("oidc_state");
+ const nonce = this.#runtime.entropy("oidc_nonce");
+ const codeVerifier = this.#runtime.entropy("oidc_verifier");
+ if ([state, nonce, codeVerifier].some((value) => value.length < 43)) enterpriseError("INVALID_INPUT", "OIDC entropy source is insufficient.");
+ const stateHash = sha256(state);
+ this.#pending.set(stateHash, {
+ organizationId,
+ redirectUri: redirect.toString(),
+ stateHash,
+ nonceHash: sha256(nonce),
+ verifierHash: sha256(codeVerifier),
+ expiresAt: iso(new Date(this.#runtime.now().getTime() + 10 * 60_000)),
+ });
+ const parameters = new URLSearchParams({
+ response_type: "code",
+ client_id: this.#clientId,
+ redirect_uri: redirect.toString(),
+ scope: "openid email profile groups",
+ state,
+ nonce,
+ code_challenge: base64UrlSha256(codeVerifier),
+ code_challenge_method: "S256",
+ });
+ return {
+ authorizationUrl: `${this.#issuer}/authorize?${parameters.toString()}`,
+ state,
+ nonce,
+ codeVerifier,
+ };
+ }
+
+ issueLocalTestCode(input: { state: string; nonce: string; claims: LocalTestOidcClaims }): string {
+ const pending = this.#pending.get(sha256(input.state));
+ if (!pending) enterpriseError("OIDC_STATE_INVALID", "OIDC state is invalid.");
+ this.#assertFresh(pending);
+ if (pending.nonceHash !== sha256(input.nonce)) enterpriseError("OIDC_NONCE_INVALID", "OIDC nonce is invalid.");
+ const code = this.#runtime.entropy("oidc_code");
+ if (code.length < 43) enterpriseError("INVALID_INPUT", "OIDC code entropy is insufficient.");
+ pending.codeHash = sha256(code);
+ pending.claims = {
+ subject: assertSafeId(input.claims.subject, "OIDC subject"),
+ email: normalizeEmail(input.claims.email),
+ groups: [...new Set(input.claims.groups.map((group) => boundedText(group, "OIDC group", 160)))].sort(),
+ };
+ return code;
+ }
+
+ complete(input: { state: string; code: string; codeVerifier: string }): EnterpriseIdentity {
+ const stateHash = sha256(input.state);
+ if (this.#usedStates.has(stateHash)) enterpriseError("OIDC_REPLAY", "OIDC authorization response has already been consumed.");
+ const pending = this.#pending.get(stateHash);
+ if (!pending) enterpriseError("OIDC_STATE_INVALID", "OIDC state is invalid.");
+ this.#assertFresh(pending);
+ if (pending.verifierHash !== sha256(input.codeVerifier)) enterpriseError("OIDC_PKCE_INVALID", "OIDC PKCE verifier is invalid.");
+ if (!pending.codeHash || pending.codeHash !== sha256(input.code) || !pending.claims) enterpriseError("OIDC_CODE_INVALID", "OIDC authorization code is invalid.");
+ const domain = normalizeDomain(pending.claims.email.split("@")[1]);
+ if (!this.#allowedDomains.has(domain)) enterpriseError("OIDC_DOMAIN_DENIED", "OIDC email domain is not allowed for this organization.");
+ const roleId = pending.claims.groups
+ .map((group) => this.#groupRoleMappings[group])
+ .find((role): role is DefaultRoleId => Boolean(role)) ?? "viewer";
+ this.#pending.delete(stateHash);
+ this.#usedStates.add(stateHash);
+ return {
+ provider: "oidc-local-test",
+ organizationId: pending.organizationId,
+ subject: pending.claims.subject,
+ email: pending.claims.email,
+ groups: [...pending.claims.groups],
+ roleId,
+ authenticatedAt: iso(this.#runtime.now()),
+ providerEvidence: false,
+ };
+ }
+
+ #assertFresh(pending: PendingOidcRequest): void {
+ if (this.#runtime.now().getTime() > Date.parse(pending.expiresAt)) enterpriseError("OIDC_STATE_INVALID", "OIDC authorization request has expired.");
+ }
+}
+
+export interface SamlAdapter {
+ state(): EnterpriseFeatureState;
+}
+
+export interface ScimAdapter {
+ state(): EnterpriseFeatureState;
+}
+
+export class SetupRequiredSamlAdapter implements SamlAdapter {
+ state(): EnterpriseFeatureState {
+ return { status: "setup-required", mode: "not-configured", providerEvidence: false, reason: "SAML adapter not configured." };
+ }
+}
+
+export class SetupRequiredScimAdapter implements ScimAdapter {
+ state(): EnterpriseFeatureState {
+ return { status: "setup-required", mode: "not-configured", providerEvidence: false, reason: "SCIM adapter not configured." };
+ }
+}
+
+interface DomainChallenge {
+ organizationId: string;
+ domain: string;
+ tokenHash: string;
+ expiresAt: string;
+ verifiedAt: string | null;
+}
+
+export class LocalTestDomainVerificationAdapter {
+ readonly #runtime: SecretRuntime;
+ readonly #challenges = new Map();
+
+ constructor(runtime: SecretRuntime) {
+ this.#runtime = runtime;
+ }
+
+ state(): EnterpriseFeatureState {
+ return {
+ status: "working-local-test",
+ mode: "supplied-txt-values-local-test",
+ providerEvidence: false,
+ reason: "TXT values are supplied by tests; no external DNS resolver is contacted.",
+ };
+ }
+
+ createChallenge(input: { organizationId: string; domain: string; expiresInMs: number }): { domain: string; txtName: string; txtValue: string; expiresAt: string } {
+ const organizationId = assertSafeId(input.organizationId, "Organization id");
+ const domain = normalizeDomain(input.domain);
+ const existing = this.#challenges.get(domain);
+ if (existing && existing.organizationId !== organizationId) enterpriseError("DOMAIN_CLAIMED", "Domain is already claimed by another organization.");
+ if (!Number.isSafeInteger(input.expiresInMs) || input.expiresInMs < 1_000 || input.expiresInMs > 86_400_000) enterpriseError("INVALID_INPUT", "Domain challenge expiry is invalid.");
+ const token = this.#runtime.entropy("domain_verification");
+ if (token.length < 32) enterpriseError("INVALID_INPUT", "Domain challenge entropy is insufficient.");
+ const expiresAt = iso(new Date(this.#runtime.now().getTime() + input.expiresInMs));
+ this.#challenges.set(domain, { organizationId, domain, tokenHash: sha256(token), expiresAt, verifiedAt: existing?.verifiedAt ?? null });
+ return { domain, txtName: `_drops-studio-verification.${domain}`, txtValue: `drops-studio=${token}`, expiresAt };
+ }
+
+ rotateChallenge(input: { organizationId: string; domain: string; expiresInMs: number }): { domain: string; txtName: string; txtValue: string; expiresAt: string } {
+ const domain = normalizeDomain(input.domain);
+ const existing = this.#challenges.get(domain);
+ if (existing && existing.organizationId !== input.organizationId) enterpriseError("DOMAIN_CLAIMED", "Domain is already claimed by another organization.");
+ return this.createChallenge(input);
+ }
+
+ verify(input: { organizationId: string; domain: string; observedTxtValues: string[] }): { verified: true; verifiedAt: string; providerEvidence: false } {
+ const domain = normalizeDomain(input.domain);
+ const challenge = this.#challenges.get(domain);
+ if (!challenge || challenge.organizationId !== input.organizationId) enterpriseError("DOMAIN_VERIFICATION_FAILED", "Domain challenge was not found for this organization.");
+ if (this.#runtime.now().getTime() > Date.parse(challenge.expiresAt)) enterpriseError("DOMAIN_CHALLENGE_EXPIRED", "Domain challenge has expired.");
+ const matched = input.observedTxtValues.some((value) => value.startsWith("drops-studio=") && sha256(value.slice("drops-studio=".length)) === challenge.tokenHash);
+ if (!matched) enterpriseError("DOMAIN_VERIFICATION_FAILED", "Expected domain verification TXT value was not observed.");
+ challenge.verifiedAt = iso(this.#runtime.now());
+ return { verified: true, verifiedAt: challenge.verifiedAt, providerEvidence: false };
+ }
+}
+
+export function enforceSso(input: {
+ required: boolean;
+ identityProvider: "oidc" | "openrouter" | "email";
+ isOwner: boolean;
+ emergencyOwnerRecoveryEnabled: boolean;
+ recoveryReason?: string;
+}): { allowed: boolean; recoveryUsed: boolean; reason: string } {
+ if (!input.required || input.identityProvider === "oidc") return { allowed: true, recoveryUsed: false, reason: "SSO policy satisfied." };
+ if (input.isOwner && input.emergencyOwnerRecoveryEnabled && input.recoveryReason?.trim()) {
+ return { allowed: true, recoveryUsed: true, reason: "Emergency owner recovery requires an audit event." };
+ }
+ return { allowed: false, recoveryUsed: false, reason: "Organization SSO policy requires OIDC." };
+}
diff --git a/lib/enterprise-platform/index.ts b/lib/enterprise-platform/index.ts
new file mode 100644
index 0000000..6471a56
--- /dev/null
+++ b/lib/enterprise-platform/index.ts
@@ -0,0 +1,11 @@
+export * from "./errors.ts";
+export * from "./types.ts";
+export * from "./organizations.ts";
+export * from "./collaboration.ts";
+export * from "./branches.ts";
+export * from "./identity.ts";
+export * from "./credentials.ts";
+export * from "./policies.ts";
+export * from "./audit.ts";
+export * from "./lifecycle.ts";
+export * from "./feature-states.ts";
diff --git a/lib/enterprise-platform/lifecycle.ts b/lib/enterprise-platform/lifecycle.ts
new file mode 100644
index 0000000..47c6aec
--- /dev/null
+++ b/lib/enterprise-platform/lifecycle.ts
@@ -0,0 +1,317 @@
+import { enterpriseError } from "./errors.ts";
+import type { EnterprisePermission, EnterpriseRuntime } from "./types.ts";
+import { assertSafeId, boundedText, clone, iso, secretFreeClone, sha256, stableJson } from "./utils.ts";
+
+export interface RetentionPolicyRecord {
+ organizationId: string;
+ revision: number;
+ traceDays: number;
+ auditDays: number;
+ logsDays: number;
+ presenceDays: number;
+ deletedProjectDays: number;
+ backupDays: number;
+ updatedAt: string;
+ updatedBy: string;
+}
+
+export interface ExportRequestRecord {
+ id: string;
+ organizationId: string;
+ requestedBy: string;
+ scope: { type: "organization" | "workspace" | "project"; id: string };
+ status: "pending" | "cancelled" | "completed-local-test";
+ manifest: unknown | null;
+ checksum: string | null;
+ createdAt: string;
+ completedAt: string | null;
+ cancelledAt: string | null;
+ temporaryArtifactExpiresAt: string | null;
+}
+
+export interface DeletionRequestRecord {
+ id: string;
+ organizationId: string;
+ requestedBy: string;
+ target: { type: "project" | "workspace" | "organization"; id: string };
+ status: "scheduled" | "cancelled" | "eligible" | "purged-local-metadata";
+ executeAfter: string;
+ dependencies: string[];
+ createdAt: string;
+ cancelledAt: string | null;
+ providerCleanup: "not-started" | "setup-required";
+}
+
+export interface BackupMetadataRecord {
+ id: string;
+ organizationId: string;
+ projectId: string;
+ environment: string;
+ sourceRevision: number;
+ artifactId: string;
+ artifactChecksum: string;
+ kind: "manual" | "pre-migration";
+ adapterMode: "local-test" | "external";
+ adapterVerified: boolean;
+ createdAt: string;
+}
+
+export interface RestoreOperationRecord {
+ id: string;
+ backupId: string;
+ projectId: string;
+ requestedBy: string;
+ targetEnvironment: string;
+ overwriteProduction: boolean;
+ approved: boolean;
+ status: "planned" | "completed-local-test" | "completed-external-verified";
+ adapterEvidenceId: string | null;
+ createdAt: string;
+ completedAt: string | null;
+}
+
+export class EnterpriseLifecycleManager {
+ readonly #runtime: EnterpriseRuntime;
+ readonly #retention = new Map();
+ readonly #exports = new Map();
+ readonly #deletions = new Map();
+ readonly #backups = new Map();
+ readonly #restores = new Map();
+
+ constructor(runtime: EnterpriseRuntime) {
+ this.#runtime = runtime;
+ }
+
+ setRetentionPolicy(input: {
+ organizationId: string;
+ actorUserId: string;
+ permissions: EnterprisePermission[];
+ values: Omit;
+ }): RetentionPolicyRecord {
+ this.#require(input.permissions, "security.manage");
+ for (const [key, value] of Object.entries(input.values)) {
+ if (!Number.isSafeInteger(value) || value < 1 || value > 3_650) enterpriseError("RETENTION_INVALID", `${key} retention is invalid.`);
+ }
+ const previous = this.#retention.get(input.organizationId);
+ const policy: RetentionPolicyRecord = {
+ organizationId: assertSafeId(input.organizationId, "Retention organization id"),
+ revision: (previous?.revision ?? 0) + 1,
+ ...input.values,
+ updatedAt: iso(this.#runtime.now()),
+ updatedBy: assertSafeId(input.actorUserId, "Retention actor id"),
+ };
+ this.#retention.set(policy.organizationId, policy);
+ return clone(policy);
+ }
+
+ scheduleExport(input: {
+ organizationId: string;
+ actorUserId: string;
+ permissions: EnterprisePermission[];
+ scope: ExportRequestRecord["scope"];
+ }): ExportRequestRecord {
+ this.#require(input.permissions, "project.export");
+ const now = iso(this.#runtime.now());
+ const request: ExportRequestRecord = {
+ id: assertSafeId(this.#runtime.id("data-export"), "Export request id"),
+ organizationId: assertSafeId(input.organizationId, "Export organization id"),
+ requestedBy: assertSafeId(input.actorUserId, "Export actor id"),
+ scope: { type: input.scope.type, id: assertSafeId(input.scope.id, "Export scope id") },
+ status: "pending",
+ manifest: null,
+ checksum: null,
+ createdAt: now,
+ completedAt: null,
+ cancelledAt: null,
+ temporaryArtifactExpiresAt: null,
+ };
+ this.#exports.set(request.id, request);
+ return clone(request);
+ }
+
+ completeExport(input: { exportId: string; data: unknown }): ExportRequestRecord {
+ const request = this.#export(input.exportId);
+ if (request.status !== "pending") enterpriseError("EXPORT_NOT_PENDING", "Only a pending export can complete.");
+ const manifest = secretFreeClone(input.data);
+ const serialized = stableJson(manifest);
+ if (Buffer.byteLength(serialized, "utf8") > 10_000_000) enterpriseError("INVALID_INPUT", "Local test export exceeds its size limit.");
+ request.manifest = manifest;
+ request.checksum = sha256(serialized);
+ request.status = "completed-local-test";
+ request.completedAt = iso(this.#runtime.now());
+ request.temporaryArtifactExpiresAt = iso(new Date(this.#runtime.now().getTime() + 60 * 60_000));
+ return clone(request);
+ }
+
+ cancelExport(input: { exportId: string; actorUserId: string }): ExportRequestRecord {
+ const request = this.#export(input.exportId);
+ if (request.status !== "pending") enterpriseError("EXPORT_NOT_PENDING", "Only a pending export can be cancelled.");
+ if (request.requestedBy !== input.actorUserId) enterpriseError("PERMISSION_DENIED", "Only the export requester can cancel this local test job.");
+ request.status = "cancelled";
+ request.cancelledAt = iso(this.#runtime.now());
+ return clone(request);
+ }
+
+ exportRequest(id: string): ExportRequestRecord {
+ return clone(this.#export(id));
+ }
+
+ scheduleDeletion(input: {
+ organizationId: string;
+ actorUserId: string;
+ permissions: EnterprisePermission[];
+ target: DeletionRequestRecord["target"];
+ gracePeriodMs: number;
+ confirmation: string;
+ dependencies: string[];
+ }): DeletionRequestRecord {
+ const required: EnterprisePermission = input.target.type === "organization" ? "organization.manage" : input.target.type === "workspace" ? "workspace.manage" : "project.delete";
+ this.#require(input.permissions, required);
+ if (input.confirmation !== `DELETE ${input.target.type}:${input.target.id}`) enterpriseError("CONFIRMATION_REQUIRED", "Explicit deletion confirmation is required.");
+ if (!Number.isSafeInteger(input.gracePeriodMs) || input.gracePeriodMs < 60_000 || input.gracePeriodMs > 30 * 86_400_000) enterpriseError("INVALID_INPUT", "Deletion grace period is invalid.");
+ const now = this.#runtime.now();
+ const request: DeletionRequestRecord = {
+ id: assertSafeId(this.#runtime.id("deletion-request"), "Deletion request id"),
+ organizationId: assertSafeId(input.organizationId, "Deletion organization id"),
+ requestedBy: assertSafeId(input.actorUserId, "Deletion actor id"),
+ target: { type: input.target.type, id: assertSafeId(input.target.id, "Deletion target id") },
+ status: "scheduled",
+ executeAfter: iso(new Date(now.getTime() + input.gracePeriodMs)),
+ dependencies: [...new Set(input.dependencies.map((dependency) => boundedText(dependency, "Deletion dependency", 160)))].sort(),
+ createdAt: iso(now),
+ cancelledAt: null,
+ providerCleanup: "not-started",
+ };
+ this.#deletions.set(request.id, request);
+ return clone(request);
+ }
+
+ cancelDeletion(input: { deletionId: string; actorUserId: string }): DeletionRequestRecord {
+ const request = this.#deletion(input.deletionId);
+ if (request.status !== "scheduled") enterpriseError("DELETION_NOT_PENDING", "Only scheduled deletion can be cancelled.");
+ if (request.requestedBy !== input.actorUserId) enterpriseError("PERMISSION_DENIED", "Only the deletion requester can cancel this local test request.");
+ request.status = "cancelled";
+ request.cancelledAt = iso(this.#runtime.now());
+ return clone(request);
+ }
+
+ markDeletionEligible(input: { deletionId: string }): DeletionRequestRecord {
+ const request = this.#deletion(input.deletionId);
+ if (request.status !== "scheduled") enterpriseError("DELETION_NOT_PENDING", "Deletion is not scheduled.");
+ if (this.#runtime.now().getTime() < Date.parse(request.executeAfter)) enterpriseError("DELETION_NOT_PENDING", "Deletion grace period has not elapsed.");
+ if (request.dependencies.length) enterpriseError("DELETION_DEPENDENCIES", "Deletion has unresolved dependencies.");
+ request.status = "eligible";
+ return clone(request);
+ }
+
+ purgeLocalMetadata(input: { deletionId: string }): DeletionRequestRecord {
+ const request = this.#deletion(input.deletionId);
+ if (request.status !== "eligible") enterpriseError("DELETION_NOT_PENDING", "Deletion is not eligible.");
+ request.status = "purged-local-metadata";
+ request.providerCleanup = "setup-required";
+ return clone(request);
+ }
+
+ deletionRequest(id: string): DeletionRequestRecord {
+ return clone(this.#deletion(id));
+ }
+
+ createBackupMetadata(input: {
+ organizationId: string;
+ projectId: string;
+ environment: string;
+ sourceRevision: number;
+ artifactId: string;
+ artifactChecksum: string;
+ kind: BackupMetadataRecord["kind"];
+ adapterEvidence: { mode: BackupMetadataRecord["adapterMode"]; verified: boolean };
+ }): BackupMetadataRecord {
+ if (!input.adapterEvidence.verified) enterpriseError("BACKUP_EVIDENCE_REQUIRED", "Backup metadata requires verified adapter evidence.");
+ if (!/^[a-f0-9]{64}$/i.test(input.artifactChecksum)) enterpriseError("INVALID_INPUT", "Backup checksum is invalid.");
+ if (!Number.isSafeInteger(input.sourceRevision) || input.sourceRevision < 0) enterpriseError("INVALID_INPUT", "Backup source revision is invalid.");
+ const backup: BackupMetadataRecord = {
+ id: assertSafeId(this.#runtime.id("backup"), "Backup id"),
+ organizationId: assertSafeId(input.organizationId, "Backup organization id"),
+ projectId: assertSafeId(input.projectId, "Backup project id"),
+ environment: boundedText(input.environment, "Backup environment", 80),
+ sourceRevision: input.sourceRevision,
+ artifactId: assertSafeId(input.artifactId, "Backup artifact id"),
+ artifactChecksum: input.artifactChecksum.toLowerCase(),
+ kind: input.kind,
+ adapterMode: input.adapterEvidence.mode,
+ adapterVerified: true,
+ createdAt: iso(this.#runtime.now()),
+ };
+ this.#backups.set(backup.id, backup);
+ return clone(backup);
+ }
+
+ planRestore(input: {
+ backupId: string;
+ actorUserId: string;
+ targetEnvironment: string;
+ overwriteProduction: boolean;
+ approved: boolean;
+ }): RestoreOperationRecord {
+ const backup = this.#backup(input.backupId);
+ const targetEnvironment = boundedText(input.targetEnvironment, "Restore target environment", 80);
+ if (targetEnvironment.toLowerCase() === "production" && (!input.overwriteProduction || !input.approved)) enterpriseError("PRODUCTION_APPROVAL_REQUIRED", "Production restore requires explicit overwrite approval.");
+ const operation: RestoreOperationRecord = {
+ id: assertSafeId(this.#runtime.id("restore"), "Restore operation id"),
+ backupId: backup.id,
+ projectId: backup.projectId,
+ requestedBy: assertSafeId(input.actorUserId, "Restore actor id"),
+ targetEnvironment,
+ overwriteProduction: input.overwriteProduction,
+ approved: input.approved,
+ status: "planned",
+ adapterEvidenceId: null,
+ createdAt: iso(this.#runtime.now()),
+ completedAt: null,
+ };
+ this.#restores.set(operation.id, operation);
+ return clone(operation);
+ }
+
+ completeRestore(input: { restoreId: string; checksumVerified: boolean; adapterEvidenceId: string }): RestoreOperationRecord {
+ const operation = this.#restore(input.restoreId);
+ if (operation.status !== "planned" || !input.checksumVerified) enterpriseError("RESTORE_EVIDENCE_REQUIRED", "Restore completion requires a verified checksum.");
+ const backup = this.#backup(operation.backupId);
+ operation.adapterEvidenceId = assertSafeId(input.adapterEvidenceId, "Restore evidence id");
+ operation.status = backup.adapterMode === "local-test" ? "completed-local-test" : "completed-external-verified";
+ operation.completedAt = iso(this.#runtime.now());
+ return clone(operation);
+ }
+
+ restoreOperation(id: string): RestoreOperationRecord {
+ return clone(this.#restore(id));
+ }
+
+ #require(permissions: EnterprisePermission[], permission: EnterprisePermission): void {
+ if (!permissions.includes(permission)) enterpriseError("PERMISSION_DENIED", `Permission ${permission} is required.`);
+ }
+
+ #export(id: string): ExportRequestRecord {
+ const request = this.#exports.get(id);
+ if (!request) enterpriseError("NOT_FOUND", "Export request was not found.");
+ return request;
+ }
+
+ #deletion(id: string): DeletionRequestRecord {
+ const request = this.#deletions.get(id);
+ if (!request) enterpriseError("NOT_FOUND", "Deletion request was not found.");
+ return request;
+ }
+
+ #backup(id: string): BackupMetadataRecord {
+ const backup = this.#backups.get(id);
+ if (!backup) enterpriseError("NOT_FOUND", "Backup metadata was not found.");
+ return backup;
+ }
+
+ #restore(id: string): RestoreOperationRecord {
+ const restore = this.#restores.get(id);
+ if (!restore) enterpriseError("NOT_FOUND", "Restore operation was not found.");
+ return restore;
+ }
+}
diff --git a/lib/enterprise-platform/organizations.ts b/lib/enterprise-platform/organizations.ts
new file mode 100644
index 0000000..7681c7b
--- /dev/null
+++ b/lib/enterprise-platform/organizations.ts
@@ -0,0 +1,468 @@
+import { enterpriseError } from "./errors.ts";
+import type { DefaultRoleId, EnterprisePermission, EnterpriseRuntime } from "./types.ts";
+import { DEFAULT_ROLE_IDS, ENTERPRISE_PERMISSIONS } from "./types.ts";
+import { assertSafeId, boundedText, clone, iso, normalizeEmail, sha256 } from "./utils.ts";
+
+const allPermissions = [...ENTERPRISE_PERMISSIONS];
+
+export const DEFAULT_ROLE_PERMISSIONS: Readonly> = Object.freeze({
+ owner: allPermissions,
+ admin: allPermissions.filter((permission) => permission !== "billing.manage"),
+ developer: [
+ "project.create", "project.read", "project.edit", "project.delete", "project.publish", "project.export",
+ "backend.schema.manage", "backend.data.read", "backend.data.write", "backend.functions.manage", "backend.logs.read",
+ "collaboration.comment", "collaboration.edit", "collaboration.merge", "integrations.manage", "github.manage", "deployment.manage",
+ ],
+ designer: ["project.read", "project.edit", "project.export", "collaboration.comment", "collaboration.edit"],
+ analyst: ["project.read", "project.export", "backend.data.read", "backend.logs.read", "collaboration.comment"],
+ viewer: ["project.read", "collaboration.comment"],
+ billing: ["billing.manage", "project.read"],
+ security: ["security.manage", "audit.read", "project.read", "backend.secrets.manage", "backend.logs.read"],
+});
+
+export interface OrganizationRecord {
+ id: string;
+ name: string;
+ kind: "personal" | "organization";
+ ownerUserId: string;
+ archivedAt: string | null;
+ createdAt: string;
+ updatedAt: string;
+}
+
+export interface WorkspaceRecord {
+ id: string;
+ organizationId: string;
+ name: string;
+ personal: boolean;
+ archivedAt: string | null;
+ createdAt: string;
+ updatedAt: string;
+}
+
+export interface MembershipRecord {
+ organizationId: string;
+ userId: string;
+ roleId: string;
+ status: "active" | "removed";
+ createdAt: string;
+ updatedAt: string;
+}
+
+export interface CustomRoleRecord {
+ id: string;
+ organizationId: string;
+ name: string;
+ permissions: EnterprisePermission[];
+ createdAt: string;
+}
+
+export interface InvitationRecord {
+ id: string;
+ organizationId: string;
+ workspaceId?: string;
+ email: string;
+ roleId: string;
+ tokenHash: string;
+ expiresAt: string;
+ revokedAt: string | null;
+ acceptedAt: string | null;
+ acceptedByUserId: string | null;
+ replacedInvitationId: string | null;
+ resendCount: number;
+ createdAt: string;
+}
+
+export interface ProjectDirectoryRecord {
+ id: string;
+ workspaceId: string;
+ organizationId: string;
+ name: string;
+ createdAt: string;
+ updatedAt: string;
+}
+
+interface DirectoryRuntime extends EnterpriseRuntime {
+ token(): string;
+}
+
+interface DirectorySnapshot {
+ organizations: OrganizationRecord[];
+ workspaces: WorkspaceRecord[];
+ memberships: MembershipRecord[];
+ roles: CustomRoleRecord[];
+ invitations: InvitationRecord[];
+ projects: ProjectDirectoryRecord[];
+}
+
+export class EnterpriseDirectory {
+ readonly #runtime: DirectoryRuntime;
+ readonly #organizations = new Map();
+ readonly #workspaces = new Map();
+ readonly #memberships = new Map();
+ readonly #workspaceMembers = new Map>();
+ readonly #roles = new Map();
+ readonly #invitations = new Map();
+ readonly #projects = new Map();
+
+ constructor(runtime: DirectoryRuntime) {
+ this.#runtime = runtime;
+ }
+
+ createOrganization(input: { ownerUserId: string; name: string; kind: "personal" | "organization" }): {
+ organization: OrganizationRecord;
+ workspace: WorkspaceRecord;
+ } {
+ const ownerUserId = assertSafeId(input.ownerUserId, "Owner user id");
+ const now = iso(this.#runtime.now());
+ const organization: OrganizationRecord = {
+ id: assertSafeId(this.#runtime.id("organization"), "Organization id"),
+ name: boundedText(input.name, "Organization name", 120),
+ kind: input.kind,
+ ownerUserId,
+ archivedAt: null,
+ createdAt: now,
+ updatedAt: now,
+ };
+ const workspace: WorkspaceRecord = {
+ id: assertSafeId(this.#runtime.id("workspace"), "Workspace id"),
+ organizationId: organization.id,
+ name: input.kind === "personal" ? "Personal" : "Default",
+ personal: input.kind === "personal",
+ archivedAt: null,
+ createdAt: now,
+ updatedAt: now,
+ };
+ this.#organizations.set(organization.id, organization);
+ this.#workspaces.set(workspace.id, workspace);
+ this.#memberships.set(this.#membershipKey(organization.id, ownerUserId), {
+ organizationId: organization.id,
+ userId: ownerUserId,
+ roleId: "owner",
+ status: "active",
+ createdAt: now,
+ updatedAt: now,
+ });
+ this.#workspaceMembers.set(workspace.id, new Set([ownerUserId]));
+ return { organization: clone(organization), workspace: clone(workspace) };
+ }
+
+ createWorkspace(input: { actorUserId: string; organizationId: string; name: string }): WorkspaceRecord {
+ this.#assertPermission(input.actorUserId, input.organizationId, "workspace.manage");
+ const organization = this.#organization(input.organizationId);
+ if (organization.archivedAt) enterpriseError("INVALID_INPUT", "Archived organization cannot create a workspace.");
+ const now = iso(this.#runtime.now());
+ const workspace: WorkspaceRecord = {
+ id: assertSafeId(this.#runtime.id("workspace"), "Workspace id"),
+ organizationId: organization.id,
+ name: boundedText(input.name, "Workspace name", 120),
+ personal: false,
+ archivedAt: null,
+ createdAt: now,
+ updatedAt: now,
+ };
+ this.#workspaces.set(workspace.id, workspace);
+ this.#workspaceMembers.set(workspace.id, new Set([organization.ownerUserId]));
+ return clone(workspace);
+ }
+
+ renameOrganization(input: { actorUserId: string; organizationId: string; name: string }): OrganizationRecord {
+ this.#assertPermission(input.actorUserId, input.organizationId, "organization.manage");
+ const organization = this.#organization(input.organizationId);
+ organization.name = boundedText(input.name, "Organization name", 120);
+ organization.updatedAt = iso(this.#runtime.now());
+ return clone(organization);
+ }
+
+ archiveOrganization(input: { actorUserId: string; organizationId: string; confirmation: string }): OrganizationRecord {
+ this.#assertPermission(input.actorUserId, input.organizationId, "organization.manage");
+ if (input.confirmation !== `ARCHIVE ${input.organizationId}`) enterpriseError("CONFIRMATION_REQUIRED", "Explicit organization archive confirmation is required.");
+ const organization = this.#organization(input.organizationId);
+ organization.archivedAt = iso(this.#runtime.now());
+ organization.updatedAt = organization.archivedAt;
+ return clone(organization);
+ }
+
+ inviteMember(input: {
+ actorUserId: string;
+ organizationId: string;
+ workspaceId?: string;
+ email: string;
+ roleId: string;
+ expiresInMs: number;
+ }): { invitation: InvitationRecord; token: string } {
+ this.#assertPermission(input.actorUserId, input.organizationId, "members.manage");
+ if (input.workspaceId && this.#workspace(input.workspaceId).organizationId !== input.organizationId) {
+ enterpriseError("TENANT_MISMATCH", "Invitation workspace belongs to another organization.");
+ }
+ this.#permissionsForRole(input.organizationId, input.roleId);
+ if (!Number.isSafeInteger(input.expiresInMs) || input.expiresInMs < 1_000 || input.expiresInMs > 30 * 86_400_000) {
+ enterpriseError("INVALID_INPUT", "Invitation expiry is invalid.");
+ }
+ const token = this.#runtime.token();
+ if (token.length < 32) enterpriseError("INVALID_INPUT", "Invitation token entropy is insufficient.");
+ const now = this.#runtime.now();
+ const invitation: InvitationRecord = {
+ id: assertSafeId(this.#runtime.id("invitation"), "Invitation id"),
+ organizationId: input.organizationId,
+ ...(input.workspaceId ? { workspaceId: input.workspaceId } : {}),
+ email: normalizeEmail(input.email),
+ roleId: input.roleId,
+ tokenHash: sha256(token),
+ expiresAt: iso(new Date(now.getTime() + input.expiresInMs)),
+ revokedAt: null,
+ acceptedAt: null,
+ acceptedByUserId: null,
+ replacedInvitationId: null,
+ resendCount: 0,
+ createdAt: iso(now),
+ };
+ this.#invitations.set(invitation.id, invitation);
+ return { invitation: clone(invitation), token };
+ }
+
+ acceptInvitation(input: { token: string; userId: string; email: string }): {
+ organizationId: string;
+ workspaceId?: string;
+ organizationRoleId: string;
+ } {
+ const tokenHash = sha256(input.token);
+ const invitation = [...this.#invitations.values()].find((entry) => entry.tokenHash === tokenHash);
+ if (!invitation) enterpriseError("INVITATION_REVOKED", "Invitation is invalid or has been rotated.");
+ if (invitation.acceptedAt) enterpriseError("INVITATION_REPLAY", "Invitation has already been accepted.");
+ if (invitation.revokedAt) enterpriseError("INVITATION_REVOKED", "Invitation has been revoked.");
+ if (this.#runtime.now().getTime() > Date.parse(invitation.expiresAt)) enterpriseError("INVITATION_EXPIRED", "Invitation has expired.");
+ if (normalizeEmail(input.email) !== invitation.email) enterpriseError("INVITATION_EMAIL_MISMATCH", "Invitation email does not match.");
+ const userId = assertSafeId(input.userId, "User id");
+ const existingMembership = this.#memberships.get(this.#membershipKey(invitation.organizationId, userId));
+ if (existingMembership?.status === "active") enterpriseError("INVALID_INPUT", "User is already an active member of this organization.");
+ const now = iso(this.#runtime.now());
+ invitation.acceptedAt = now;
+ invitation.acceptedByUserId = userId;
+ this.#memberships.set(this.#membershipKey(invitation.organizationId, userId), {
+ organizationId: invitation.organizationId,
+ userId,
+ roleId: invitation.roleId,
+ status: "active",
+ createdAt: now,
+ updatedAt: now,
+ });
+ if (invitation.workspaceId) {
+ const members = this.#workspaceMembers.get(invitation.workspaceId) ?? new Set();
+ members.add(userId);
+ this.#workspaceMembers.set(invitation.workspaceId, members);
+ }
+ return {
+ organizationId: invitation.organizationId,
+ ...(invitation.workspaceId ? { workspaceId: invitation.workspaceId } : {}),
+ organizationRoleId: invitation.roleId,
+ };
+ }
+
+ resendInvitation(input: { actorUserId: string; invitationId: string; expiresInMs: number }): { invitation: InvitationRecord; token: string } {
+ const prior = this.#invitation(input.invitationId);
+ this.#assertPermission(input.actorUserId, prior.organizationId, "members.manage");
+ if (prior.acceptedAt) enterpriseError("INVITATION_REPLAY", "Accepted invitation cannot be resent.");
+ const replacement = this.inviteMember({
+ actorUserId: input.actorUserId,
+ organizationId: prior.organizationId,
+ ...(prior.workspaceId ? { workspaceId: prior.workspaceId } : {}),
+ email: prior.email,
+ roleId: prior.roleId,
+ expiresInMs: input.expiresInMs,
+ });
+ prior.revokedAt = iso(this.#runtime.now());
+ const stored = this.#invitation(replacement.invitation.id);
+ stored.replacedInvitationId = prior.id;
+ stored.resendCount = prior.resendCount + 1;
+ return { invitation: clone(stored), token: replacement.token };
+ }
+
+ revokeInvitation(input: { actorUserId: string; invitationId: string }): InvitationRecord {
+ const invitation = this.#invitation(input.invitationId);
+ this.#assertPermission(input.actorUserId, invitation.organizationId, "members.manage");
+ if (invitation.acceptedAt) enterpriseError("INVITATION_REPLAY", "Accepted invitation cannot be revoked.");
+ invitation.revokedAt = iso(this.#runtime.now());
+ return clone(invitation);
+ }
+
+ transferOwnership(input: { actorUserId: string; organizationId: string; nextOwnerUserId: string; confirmation: string }): void {
+ const organization = this.#organization(input.organizationId);
+ if (organization.ownerUserId !== input.actorUserId) enterpriseError("PERMISSION_DENIED", "Only the current owner can transfer ownership.");
+ if (input.confirmation !== `TRANSFER ${input.organizationId}`) enterpriseError("CONFIRMATION_REQUIRED", "Explicit ownership transfer confirmation is required.");
+ const next = this.#membership(input.organizationId, input.nextOwnerUserId);
+ if (next.status !== "active") enterpriseError("NOT_FOUND", "Next owner must be an active organization member.");
+ const current = this.#membership(input.organizationId, input.actorUserId);
+ const now = iso(this.#runtime.now());
+ current.roleId = "admin";
+ current.updatedAt = now;
+ next.roleId = "owner";
+ next.updatedAt = now;
+ organization.ownerUserId = next.userId;
+ organization.updatedAt = now;
+ }
+
+ removeMember(input: { actorUserId: string; organizationId: string; userId: string }): void {
+ this.#assertPermission(input.actorUserId, input.organizationId, "members.manage");
+ const organization = this.#organization(input.organizationId);
+ if (organization.ownerUserId === input.userId) enterpriseError("CONFIRMATION_REQUIRED", "Transfer ownership before removing the owner.");
+ const membership = this.#membership(input.organizationId, input.userId);
+ membership.status = "removed";
+ membership.updatedAt = iso(this.#runtime.now());
+ for (const workspace of this.#workspaces.values()) {
+ if (workspace.organizationId === input.organizationId) this.#workspaceMembers.get(workspace.id)?.delete(input.userId);
+ }
+ }
+
+ createCustomRole(input: {
+ actorUserId: string;
+ organizationId: string;
+ name: string;
+ permissions: EnterprisePermission[];
+ }): CustomRoleRecord {
+ this.#assertPermission(input.actorUserId, input.organizationId, "security.manage");
+ const actorPermissions = new Set(this.#effectivePermissions(input.actorUserId, input.organizationId));
+ const permissions = [...new Set(input.permissions)].sort();
+ if (!permissions.length || permissions.some((permission) => !ENTERPRISE_PERMISSIONS.includes(permission))) {
+ enterpriseError("INVALID_INPUT", "Custom role permissions are invalid.");
+ }
+ if (permissions.some((permission) => !actorPermissions.has(permission))) {
+ enterpriseError("PERMISSION_DENIED", "Custom role cannot grant a permission the creator lacks.");
+ }
+ const role: CustomRoleRecord = {
+ id: assertSafeId(this.#runtime.id("role"), "Role id"),
+ organizationId: input.organizationId,
+ name: boundedText(input.name, "Role name", 80),
+ permissions,
+ createdAt: iso(this.#runtime.now()),
+ };
+ this.#roles.set(role.id, role);
+ return clone(role);
+ }
+
+ createProject(input: { actorUserId: string; workspaceId: string; name: string }): ProjectDirectoryRecord {
+ const workspace = this.#workspace(input.workspaceId);
+ this.#assertWorkspaceAccess(input.actorUserId, workspace);
+ this.#assertPermission(input.actorUserId, workspace.organizationId, "project.create");
+ const now = iso(this.#runtime.now());
+ const project: ProjectDirectoryRecord = {
+ id: assertSafeId(this.#runtime.id("project"), "Project id"),
+ workspaceId: workspace.id,
+ organizationId: workspace.organizationId,
+ name: boundedText(input.name, "Project name", 120),
+ createdAt: now,
+ updatedAt: now,
+ };
+ this.#projects.set(project.id, project);
+ return clone(project);
+ }
+
+ transferProject(input: { actorUserId: string; projectId: string; targetWorkspaceId: string }): ProjectDirectoryRecord {
+ const project = this.#project(input.projectId);
+ const source = this.#workspace(project.workspaceId);
+ const target = this.#workspace(input.targetWorkspaceId);
+ this.#assertWorkspaceAccess(input.actorUserId, source);
+ this.#assertWorkspaceAccess(input.actorUserId, target);
+ this.#assertPermission(input.actorUserId, source.organizationId, "project.edit");
+ this.#assertPermission(input.actorUserId, target.organizationId, "project.create");
+ if (source.organizationId !== target.organizationId) {
+ this.#assertPermission(input.actorUserId, source.organizationId, "organization.manage");
+ this.#assertPermission(input.actorUserId, target.organizationId, "organization.manage");
+ }
+ project.workspaceId = target.id;
+ project.organizationId = target.organizationId;
+ project.updatedAt = iso(this.#runtime.now());
+ return clone(project);
+ }
+
+ can(userId: string, organizationId: string, permission: EnterprisePermission): boolean {
+ try {
+ return this.#effectivePermissions(userId, organizationId).includes(permission);
+ } catch {
+ return false;
+ }
+ }
+
+ membership(organizationId: string, userId: string): MembershipRecord {
+ return clone(this.#membership(organizationId, userId));
+ }
+
+ project(projectId: string): ProjectDirectoryRecord {
+ return clone(this.#project(projectId));
+ }
+
+ snapshot(): DirectorySnapshot {
+ return clone({
+ organizations: [...this.#organizations.values()],
+ workspaces: [...this.#workspaces.values()],
+ memberships: [...this.#memberships.values()],
+ roles: [...this.#roles.values()],
+ invitations: [...this.#invitations.values()],
+ projects: [...this.#projects.values()],
+ });
+ }
+
+ #effectivePermissions(userId: string, organizationId: string): EnterprisePermission[] {
+ const organization = this.#organization(organizationId);
+ if (organization.archivedAt) return [];
+ const membership = this.#membership(organizationId, userId);
+ if (membership.status !== "active") return [];
+ return this.#permissionsForRole(organizationId, membership.roleId);
+ }
+
+ #permissionsForRole(organizationId: string, roleId: string): EnterprisePermission[] {
+ if ((DEFAULT_ROLE_IDS as readonly string[]).includes(roleId)) {
+ return [...DEFAULT_ROLE_PERMISSIONS[roleId as DefaultRoleId]];
+ }
+ const role = this.#roles.get(roleId);
+ if (!role || role.organizationId !== organizationId) enterpriseError("NOT_FOUND", "Role was not found in this organization.");
+ return [...role.permissions];
+ }
+
+ #assertPermission(userId: string, organizationId: string, permission: EnterprisePermission): void {
+ if (!this.can(userId, organizationId, permission)) enterpriseError("PERMISSION_DENIED", `Permission ${permission} is required.`);
+ }
+
+ #assertWorkspaceAccess(userId: string, workspace: WorkspaceRecord): void {
+ const membership = this.#membership(workspace.organizationId, userId);
+ if (membership.status !== "active") enterpriseError("PERMISSION_DENIED", "Active organization membership is required.");
+ const explicit = this.#workspaceMembers.get(workspace.id);
+ if (explicit && !explicit.has(userId) && membership.roleId !== "owner" && membership.roleId !== "admin") {
+ enterpriseError("PERMISSION_DENIED", "Workspace membership is required.");
+ }
+ }
+
+ #membershipKey(organizationId: string, userId: string): string {
+ return `${organizationId}:${userId}`;
+ }
+
+ #membership(organizationId: string, userId: string): MembershipRecord {
+ const membership = this.#memberships.get(this.#membershipKey(organizationId, userId));
+ if (!membership) enterpriseError("PERMISSION_DENIED", "Organization membership was not found.");
+ return membership;
+ }
+
+ #organization(id: string): OrganizationRecord {
+ const organization = this.#organizations.get(id);
+ if (!organization) enterpriseError("NOT_FOUND", "Organization was not found.");
+ return organization;
+ }
+
+ #workspace(id: string): WorkspaceRecord {
+ const workspace = this.#workspaces.get(id);
+ if (!workspace) enterpriseError("NOT_FOUND", "Workspace was not found.");
+ return workspace;
+ }
+
+ #project(id: string): ProjectDirectoryRecord {
+ const project = this.#projects.get(id);
+ if (!project) enterpriseError("NOT_FOUND", "Project was not found.");
+ return project;
+ }
+
+ #invitation(id: string): InvitationRecord {
+ const invitation = this.#invitations.get(id);
+ if (!invitation) enterpriseError("NOT_FOUND", "Invitation was not found.");
+ return invitation;
+ }
+}
diff --git a/lib/enterprise-platform/policies.ts b/lib/enterprise-platform/policies.ts
new file mode 100644
index 0000000..b45127e
--- /dev/null
+++ b/lib/enterprise-platform/policies.ts
@@ -0,0 +1,195 @@
+import { enterpriseError } from "./errors.ts";
+import { sha256, stableJson } from "./utils.ts";
+
+export interface EnterprisePolicy {
+ allowedModelProviders?: string[];
+ allowedModels?: string[];
+ byokRequired?: boolean;
+ platformModelsAllowed?: boolean;
+ maxAgentCostPerRun?: number;
+ maxSandboxDuration?: number;
+ maxParallelAgents?: number;
+ allowedDependencyRegistries?: string[];
+ allowedNetworkHosts?: string[];
+ productionPublishRequiresApproval?: boolean;
+ productionSchemaChangeRequiresApproval?: boolean;
+ githubPushRequiresApproval?: boolean;
+ telegramDeliveryRequiresApproval?: boolean;
+ secretAccessRoles?: string[];
+ retentionDays?: number;
+ exportAllowed?: boolean;
+ publicProjectLinksAllowed?: boolean;
+ ssoRequired?: boolean;
+ mfaRequiredWhenAvailable?: boolean;
+}
+
+export interface ResolvedEnterprisePolicy {
+ allowedModelProviders: string[] | null;
+ allowedModels: string[] | null;
+ byokRequired: boolean;
+ platformModelsAllowed: boolean;
+ maxAgentCostPerRun: number;
+ maxSandboxDuration: number;
+ maxParallelAgents: number;
+ allowedDependencyRegistries: string[] | null;
+ allowedNetworkHosts: string[] | null;
+ productionPublishRequiresApproval: boolean;
+ productionSchemaChangeRequiresApproval: boolean;
+ githubPushRequiresApproval: boolean;
+ telegramDeliveryRequiresApproval: boolean;
+ secretAccessRoles: string[] | null;
+ retentionDays: number;
+ exportAllowed: boolean;
+ publicProjectLinksAllowed: boolean;
+ ssoRequired: boolean;
+ mfaRequiredWhenAvailable: boolean;
+}
+
+export interface EnterprisePolicyResolution {
+ policy: ResolvedEnterprisePolicy;
+ policyHash: string;
+ appliedLayers: string[];
+}
+
+function intersect(current: string[] | null, next: string[] | undefined): string[] | null {
+ if (next === undefined) return current;
+ const values = [...new Set(next.map((value) => value.trim()).filter(Boolean))].sort();
+ if (current === null) return values;
+ return current.filter((value) => values.includes(value));
+}
+
+function minimum(current: number, next: number | undefined, lowerBound: number, upperBound: number, label: string): number {
+ if (next === undefined) return current;
+ if (!Number.isFinite(next) || next < lowerBound || next > upperBound) enterpriseError("INVALID_INPUT", `Enterprise policy ${label} must be between ${lowerBound} and ${upperBound}.`);
+ return Math.min(current, next);
+}
+
+export function resolveEnterprisePolicy(input: {
+ systemHard?: EnterprisePolicy;
+ organization?: EnterprisePolicy;
+ workspace?: EnterprisePolicy;
+ project?: EnterprisePolicy;
+ userPreference?: EnterprisePolicy;
+}): EnterprisePolicyResolution {
+ const policy: ResolvedEnterprisePolicy = {
+ allowedModelProviders: null,
+ allowedModels: null,
+ byokRequired: false,
+ platformModelsAllowed: true,
+ maxAgentCostPerRun: Number.MAX_SAFE_INTEGER,
+ maxSandboxDuration: Number.MAX_SAFE_INTEGER,
+ maxParallelAgents: Number.MAX_SAFE_INTEGER,
+ allowedDependencyRegistries: null,
+ allowedNetworkHosts: null,
+ productionPublishRequiresApproval: false,
+ productionSchemaChangeRequiresApproval: false,
+ githubPushRequiresApproval: false,
+ telegramDeliveryRequiresApproval: false,
+ secretAccessRoles: null,
+ retentionDays: 3_650,
+ exportAllowed: true,
+ publicProjectLinksAllowed: true,
+ ssoRequired: false,
+ mfaRequiredWhenAvailable: false,
+ };
+ const layers = [
+ ["system-hard", input.systemHard],
+ ["organization", input.organization],
+ ["workspace", input.workspace],
+ ["project", input.project],
+ ["user-preference", input.userPreference],
+ ] as const;
+ const appliedLayers: string[] = [];
+ for (const [name, layer] of layers) {
+ if (!layer) continue;
+ appliedLayers.push(name);
+ policy.allowedModelProviders = intersect(policy.allowedModelProviders, layer.allowedModelProviders);
+ policy.allowedModels = intersect(policy.allowedModels, layer.allowedModels);
+ policy.allowedDependencyRegistries = intersect(policy.allowedDependencyRegistries, layer.allowedDependencyRegistries);
+ policy.allowedNetworkHosts = intersect(policy.allowedNetworkHosts, layer.allowedNetworkHosts);
+ policy.secretAccessRoles = intersect(policy.secretAccessRoles, layer.secretAccessRoles);
+ policy.byokRequired ||= layer.byokRequired ?? false;
+ policy.platformModelsAllowed &&= layer.platformModelsAllowed ?? true;
+ policy.maxAgentCostPerRun = minimum(policy.maxAgentCostPerRun, layer.maxAgentCostPerRun, 0, 1_000_000, `${name}.maxAgentCostPerRun`);
+ policy.maxSandboxDuration = minimum(policy.maxSandboxDuration, layer.maxSandboxDuration, 1, 86_400, `${name}.maxSandboxDuration`);
+ policy.maxParallelAgents = minimum(policy.maxParallelAgents, layer.maxParallelAgents, 1, 100, `${name}.maxParallelAgents`);
+ policy.retentionDays = minimum(policy.retentionDays, layer.retentionDays, 1, 3_650, `${name}.retentionDays`);
+ policy.productionPublishRequiresApproval ||= layer.productionPublishRequiresApproval ?? false;
+ policy.productionSchemaChangeRequiresApproval ||= layer.productionSchemaChangeRequiresApproval ?? false;
+ policy.githubPushRequiresApproval ||= layer.githubPushRequiresApproval ?? false;
+ policy.telegramDeliveryRequiresApproval ||= layer.telegramDeliveryRequiresApproval ?? false;
+ policy.exportAllowed &&= layer.exportAllowed ?? true;
+ policy.publicProjectLinksAllowed &&= layer.publicProjectLinksAllowed ?? true;
+ policy.ssoRequired ||= layer.ssoRequired ?? false;
+ policy.mfaRequiredWhenAvailable ||= layer.mfaRequiredWhenAvailable ?? false;
+ }
+ return { policy, policyHash: sha256(stableJson(policy)), appliedLayers };
+}
+
+export type EnterprisePolicyAction =
+ | { action: "model.use"; provider: string; model?: string; byok?: boolean; estimatedCost?: number }
+ | { action: "network.connect"; host: string }
+ | { action: "dependency.install"; registry: string }
+ | { action: "sandbox.start"; durationSeconds: number }
+ | { action: "agents.parallel"; count: number }
+ | { action: "production.publish" }
+ | { action: "production.schema-change" }
+ | { action: "github.push" }
+ | { action: "telegram.deliver" }
+ | { action: "secret.access"; role: string }
+ | { action: "data.export" }
+ | { action: "project.public-link" };
+
+export function evaluateEnterprisePolicy(
+ resolution: EnterprisePolicyResolution,
+ action: EnterprisePolicyAction,
+): { allowed: boolean; requiresApproval: boolean; reason: string; policyHash: string } {
+ const policy = resolution.policy;
+ let allowed = true;
+ let requiresApproval = false;
+ let reason = "Policy allows this action.";
+ const deny = (message: string): void => { allowed = false; reason = message; };
+ switch (action.action) {
+ case "model.use":
+ if (policy.allowedModelProviders && !policy.allowedModelProviders.includes(action.provider)) deny("Model provider is not allowed.");
+ else if (action.model && policy.allowedModels && !policy.allowedModels.includes(action.model)) deny("Model is not allowed.");
+ else if (policy.byokRequired && !action.byok) deny("BYOK is required.");
+ else if (!policy.platformModelsAllowed && !action.byok) deny("Platform-funded models are disabled.");
+ else if ((action.estimatedCost ?? 0) > policy.maxAgentCostPerRun) deny("Agent cost exceeds policy.");
+ break;
+ case "network.connect":
+ if (policy.allowedNetworkHosts && !policy.allowedNetworkHosts.includes(action.host)) deny("Network host is not allowed.");
+ break;
+ case "dependency.install":
+ if (policy.allowedDependencyRegistries && !policy.allowedDependencyRegistries.includes(action.registry)) deny("Dependency registry is not allowed.");
+ break;
+ case "sandbox.start":
+ if (action.durationSeconds > policy.maxSandboxDuration) deny("Sandbox duration exceeds policy.");
+ break;
+ case "agents.parallel":
+ if (action.count > policy.maxParallelAgents) deny("Parallel agent count exceeds policy.");
+ break;
+ case "production.publish":
+ requiresApproval = policy.productionPublishRequiresApproval;
+ break;
+ case "production.schema-change":
+ requiresApproval = policy.productionSchemaChangeRequiresApproval;
+ break;
+ case "github.push":
+ requiresApproval = policy.githubPushRequiresApproval;
+ break;
+ case "telegram.deliver":
+ requiresApproval = policy.telegramDeliveryRequiresApproval;
+ break;
+ case "secret.access":
+ if (policy.secretAccessRoles && !policy.secretAccessRoles.includes(action.role)) deny("Role cannot access secret references.");
+ break;
+ case "data.export":
+ if (!policy.exportAllowed) deny("Data export is disabled.");
+ break;
+ case "project.public-link":
+ if (!policy.publicProjectLinksAllowed) deny("Public project links are disabled.");
+ break;
+ }
+ return { allowed, requiresApproval: allowed && requiresApproval, reason, policyHash: resolution.policyHash };
+}
diff --git a/lib/enterprise-platform/types.ts b/lib/enterprise-platform/types.ts
new file mode 100644
index 0000000..31ae913
--- /dev/null
+++ b/lib/enterprise-platform/types.ts
@@ -0,0 +1,64 @@
+export const ENTERPRISE_PERMISSIONS = [
+ "organization.manage",
+ "members.manage",
+ "billing.manage",
+ "security.manage",
+ "audit.read",
+ "workspace.manage",
+ "project.create",
+ "project.read",
+ "project.edit",
+ "project.delete",
+ "project.publish",
+ "project.export",
+ "backend.schema.manage",
+ "backend.data.read",
+ "backend.data.write",
+ "backend.functions.manage",
+ "backend.secrets.manage",
+ "backend.logs.read",
+ "collaboration.comment",
+ "collaboration.edit",
+ "collaboration.merge",
+ "integrations.manage",
+ "github.manage",
+ "deployment.manage",
+] as const;
+
+export type EnterprisePermission = (typeof ENTERPRISE_PERMISSIONS)[number];
+
+export const DEFAULT_ROLE_IDS = [
+ "owner",
+ "admin",
+ "developer",
+ "designer",
+ "analyst",
+ "viewer",
+ "billing",
+ "security",
+] as const;
+
+export type DefaultRoleId = (typeof DEFAULT_ROLE_IDS)[number];
+
+export interface EnterpriseRuntime {
+ now(): Date;
+ id(prefix: string): string;
+}
+
+export interface SecretRuntime extends EnterpriseRuntime {
+ entropy(label: string): string;
+}
+
+export type EnterpriseFeatureStatus =
+ | "working-local-test"
+ | "degraded"
+ | "setup-required"
+ | "disabled"
+ | "unavailable";
+
+export interface EnterpriseFeatureState {
+ status: EnterpriseFeatureStatus;
+ mode: string;
+ providerEvidence: boolean;
+ reason: string;
+}
diff --git a/lib/enterprise-platform/utils.ts b/lib/enterprise-platform/utils.ts
new file mode 100644
index 0000000..eda57ac
--- /dev/null
+++ b/lib/enterprise-platform/utils.ts
@@ -0,0 +1,116 @@
+import { createHash } from "node:crypto";
+
+import { enterpriseError } from "./errors.ts";
+
+const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9:._/-]{0,191}$/;
+const SAFE_PATH_SEGMENT = /^[A-Za-z0-9._@+-]+$/;
+const SECRET_VALUE = /(?:-----BEGIN [A-Z ]*PRIVATE KEY-----|\bBearer\s+[A-Za-z0-9._~+/-]{16,}|\b(?:ghp_|github_pat_|sk-proj-|dst_sa_)[A-Za-z0-9_-]{16,})/i;
+
+function isSecretKey(value: string): boolean {
+ const compact = value.replace(/[^a-z0-9]/gi, "").toLowerCase();
+ if (["apikey", "accesstoken", "refreshtoken", "token", "secret", "password", "authorization", "cookie", "privatekey"].includes(compact)) return true;
+ if (["tokenhash", "tokenprefix", "secretreference", "secretreferenceid"].some((suffix) => compact.endsWith(suffix))) return false;
+ return ["apikey", "accesstoken", "refreshtoken", "token", "secret", "password", "authorization", "cookie", "privatekey"]
+ .some((suffix) => compact.endsWith(suffix));
+}
+
+export function clone(value: T): T {
+ return structuredClone(value);
+}
+
+export function assertSafeId(value: string, label: string): string {
+ if (!SAFE_ID.test(value)) enterpriseError("INVALID_INPUT", `${label} is invalid.`);
+ return value;
+}
+
+export function boundedText(value: string, label: string, maximum = 240): string {
+ const normalized = value.trim().replace(/[\r\n\t]+/g, " ");
+ if (!normalized || normalized.length > maximum || normalized.includes("\0")) {
+ enterpriseError("INVALID_INPUT", `${label} is invalid.`);
+ }
+ return normalized;
+}
+
+export function normalizeEmail(value: string): string {
+ const normalized = value.trim().toLowerCase();
+ if (normalized.length > 254 || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalized)) {
+ enterpriseError("INVALID_INPUT", "Email is invalid.");
+ }
+ return normalized;
+}
+
+export function normalizeDomain(value: string): string {
+ const normalized = value.trim().toLowerCase().replace(/\.$/, "");
+ if (normalized.length > 253 || normalized.split(".").some((part) =>
+ !part || part.length > 63 || !/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(part))) {
+ enterpriseError("INVALID_INPUT", "Domain is invalid.");
+ }
+ return normalized;
+}
+
+export function normalizeProjectPath(value: string): string {
+ const normalized = value.replace(/\\/g, "/");
+ if (
+ !normalized
+ || normalized.length > 512
+ || normalized.startsWith("/")
+ || normalized.includes("\0")
+ || normalized.split("/").some((part) => !part || part === "." || part === ".." || !SAFE_PATH_SEGMENT.test(part))
+ ) enterpriseError("INVALID_INPUT", "Project path is invalid.");
+ return normalized;
+}
+
+function canonicalize(value: unknown): unknown {
+ if (Array.isArray(value)) return value.map(canonicalize);
+ if (!value || typeof value !== "object") return value;
+ return Object.fromEntries(
+ Object.entries(value as Record)
+ .filter(([, entry]) => entry !== undefined)
+ .sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
+ .map(([key, entry]) => [key, canonicalize(entry)]),
+ );
+}
+
+export function stableJson(value: unknown): string {
+ return JSON.stringify(canonicalize(value));
+}
+
+export function sha256(value: string): string {
+ return createHash("sha256").update(value, "utf8").digest("hex");
+}
+
+export function fileHash(value: string | undefined): string | null {
+ return value === undefined ? null : sha256(value);
+}
+
+export function matchesScope(path: string, scope: string): boolean {
+ const normalizedPath = normalizeProjectPath(path);
+ const normalizedScope = normalizeProjectPath(scope.replace(/\/\*\*$/, "/placeholder")).replace(/\/placeholder$/, "/**");
+ if (normalizedScope.endsWith("/**")) return normalizedPath.startsWith(normalizedScope.slice(0, -2));
+ return normalizedPath === normalizedScope;
+}
+
+export function containsSecretLikeValue(value: unknown): boolean {
+ if (typeof value === "string") return SECRET_VALUE.test(value);
+ if (Array.isArray(value)) return value.some(containsSecretLikeValue);
+ if (!value || typeof value !== "object") return false;
+ return Object.entries(value as Record).some(([key, entry]) =>
+ isSecretKey(key) || containsSecretLikeValue(entry));
+}
+
+export function secretFreeClone(value: unknown): unknown {
+ if (Array.isArray(value)) return value.map(secretFreeClone);
+ if (!value || typeof value !== "object") {
+ return typeof value === "string" && SECRET_VALUE.test(value) ? "[REDACTED]" : value;
+ }
+ return Object.fromEntries(
+ Object.entries(value as Record)
+ .filter(([key]) => !isSecretKey(key))
+ .map(([key, entry]) => [key, secretFreeClone(entry)]),
+ );
+}
+
+export function iso(date: Date): string {
+ if (!Number.isFinite(date.getTime())) enterpriseError("INVALID_INPUT", "Date is invalid.");
+ return date.toISOString();
+}
diff --git a/lib/managed-platform/auth.ts b/lib/managed-platform/auth.ts
new file mode 100644
index 0000000..2430aac
--- /dev/null
+++ b/lib/managed-platform/auth.ts
@@ -0,0 +1,184 @@
+import { randomBytes, randomInt, randomUUID } from "node:crypto";
+import type { ManagedEmailAdapter, ManagedPlatformLimits, ManagedPrincipal, ManagedScope } from "./contracts.ts";
+import { ManagedPlatformError, assertScope, clone, requirePermission, safeEqual, sha256 } from "./security.ts";
+import type { ManagedLogStore } from "./logs.ts";
+
+interface ManagedAppUser {
+ id: string;
+ scopeKey: string;
+ email: string | null;
+ anonymous: boolean;
+ roles: string[];
+ profile: Record;
+ status: "active" | "disabled" | "deleted";
+ createdAt: string;
+}
+
+interface StoredSession {
+ id: string;
+ scopeKey: string;
+ userId: string;
+ tokenHash: string;
+ csrfHash: string;
+ expiresAt: string;
+ revokedAt: string | null;
+ createdAt: string;
+}
+
+interface StoredEmailChallenge {
+ id: string;
+ scopeKey: string;
+ email: string;
+ codeHash: string;
+ evidenceId: string;
+ attempts: number;
+ expiresAt: string;
+ consumedAt: string | null;
+}
+
+const EMAIL = /^[^\s@]{1,128}@[^\s@]{1,190}\.[^\s@]{2,63}$/;
+
+export class ManagedAuthService {
+ private readonly users = new Map>();
+ private readonly sessions = new Map();
+ private readonly tokenIndex = new Map();
+ private readonly emailChallenges = new Map();
+
+ private readonly options: { now: () => Date; logs: ManagedLogStore; limits: ManagedPlatformLimits; emailAdapter?: ManagedEmailAdapter };
+ constructor(options: { now: () => Date; logs: ManagedLogStore; limits: ManagedPlatformLimits; emailAdapter?: ManagedEmailAdapter }) { this.options = options; }
+
+ async requestEmailCode(scope: ManagedScope, email: string, principal: ManagedPrincipal): Promise<{ status: "setup-required"; reasonCode: string } | { status: "sent"; challengeId: string; evidenceId: string; expiresAt: string }> {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.auth.manage");
+ const normalizedEmail = email.trim().toLowerCase();
+ if (!EMAIL.test(normalizedEmail)) throw new ManagedPlatformError("EMAIL_INVALID", "Email address is invalid.");
+ if (!this.options.emailAdapter) return { status: "setup-required", reasonCode: "EMAIL_ADAPTER_REQUIRED" };
+ const challengeId = `email_challenge_${randomUUID()}`;
+ const code = String(randomInt(0, 1_000_000)).padStart(6, "0");
+ const expiresAt = new Date(this.options.now().getTime() + 10 * 60_000).toISOString();
+ const { evidenceId } = await this.options.emailAdapter.deliverOneTimeCode({ scope, email: normalizedEmail, code, expiresAt });
+ this.emailChallenges.set(challengeId, {
+ id: challengeId,
+ scopeKey: scope.scopeKey,
+ email: normalizedEmail,
+ codeHash: sha256(`${challengeId}:${normalizedEmail}:${code}`),
+ evidenceId,
+ attempts: 0,
+ expiresAt,
+ consumedAt: null,
+ });
+ return { status: "sent", challengeId, evidenceId, expiresAt };
+ }
+
+ verifyEmailCode(scope: ManagedScope, input: { challengeId: string; email: string; code: string }, principal: ManagedPrincipal) {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.auth.manage");
+ const email = input.email.trim().toLowerCase();
+ const challenge = this.emailChallenges.get(input.challengeId);
+ if (!challenge || challenge.scopeKey !== scope.scopeKey || challenge.email !== email) throw new ManagedPlatformError("EMAIL_CHALLENGE_INVALID", "Email code challenge is invalid.");
+ if (challenge.consumedAt) throw new ManagedPlatformError("EMAIL_CHALLENGE_CONSUMED", "Email code challenge was already used.");
+ if (Date.parse(challenge.expiresAt) <= this.options.now().getTime()) throw new ManagedPlatformError("EMAIL_CHALLENGE_EXPIRED", "Email code challenge expired.");
+ if (challenge.attempts >= 5) throw new ManagedPlatformError("EMAIL_CHALLENGE_ATTEMPTS_EXCEEDED", "Email code challenge attempt limit was exceeded.");
+ const actual = sha256(`${challenge.id}:${email}:${input.code}`);
+ if (!safeEqual(actual, challenge.codeHash)) {
+ challenge.attempts += 1;
+ throw new ManagedPlatformError("EMAIL_CODE_INVALID", "Email verification code is invalid.");
+ }
+ challenge.consumedAt = this.options.now().toISOString();
+ this.options.logs.append(scope, { category: "auth", severity: "info", action: "auth.email.verify", actorId: principal.actorId, requestId: `req_${randomUUID()}`, metadata: { challengeId: challenge.id, emailHash: sha256(email), evidenceId: challenge.evidenceId } });
+ return { status: "verified" as const, email, challengeId: challenge.id, evidenceId: challenge.evidenceId, verifiedAt: challenge.consumedAt };
+ }
+
+ createUser(scope: ManagedScope, input: { email: string; roles: string[]; profile?: Record }, principal: ManagedPrincipal): ManagedAppUser {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.auth.manage");
+ const email = input.email.trim().toLowerCase();
+ if (!EMAIL.test(email)) throw new ManagedPlatformError("EMAIL_INVALID", "Email address is invalid.");
+ if (!input.roles.length || input.roles.length > 16) throw new ManagedPlatformError("ROLE_INVALID", "At least one bounded project role is required.");
+ const scoped = this.users.get(scope.scopeKey) ?? new Map();
+ if ([...scoped.values()].some((user) => user.email === email && user.status !== "deleted")) throw new ManagedPlatformError("USER_EXISTS", "Project user already exists.");
+ const user: ManagedAppUser = {
+ id: `app_user_${randomUUID()}`,
+ scopeKey: scope.scopeKey,
+ email,
+ anonymous: false,
+ roles: [...new Set(input.roles)].slice(0, 16),
+ profile: clone(input.profile ?? {}),
+ status: "active",
+ createdAt: this.options.now().toISOString(),
+ };
+ scoped.set(user.id, user);
+ this.users.set(scope.scopeKey, scoped);
+ this.options.logs.append(scope, { category: "auth", severity: "info", action: "auth.user.create", actorId: principal.actorId, requestId: `req_${randomUUID()}`, metadata: { userId: user.id, emailHash: sha256(email) } });
+ return clone(user);
+ }
+
+ createGuest(scope: ManagedScope, principal: ManagedPrincipal): ManagedAppUser {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.auth.manage");
+ const scoped = this.users.get(scope.scopeKey) ?? new Map();
+ const activeGuests = [...scoped.values()].filter((user) => user.anonymous && user.status === "active").length;
+ if (activeGuests >= this.options.limits.maxGuestUsersPerEnvironment) throw new ManagedPlatformError("GUEST_QUOTA_EXCEEDED", "Guest user quota exceeded for this environment.");
+ const user: ManagedAppUser = { id: `app_guest_${randomUUID()}`, scopeKey: scope.scopeKey, email: null, anonymous: true, roles: ["guest"], profile: {}, status: "active", createdAt: this.options.now().toISOString() };
+ scoped.set(user.id, user);
+ this.users.set(scope.scopeKey, scoped);
+ return clone(user);
+ }
+
+ createSession(scope: ManagedScope, userId: string, principal: ManagedPrincipal, options: { ttlSeconds?: number } = {}) {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.auth.manage");
+ const user = this.users.get(scope.scopeKey)?.get(userId);
+ if (!user || user.status !== "active") throw new ManagedPlatformError("USER_UNAVAILABLE", "Project user is unavailable.");
+ const ttl = options.ttlSeconds ?? 3_600;
+ if (!Number.isInteger(ttl) || ttl < 60 || ttl > 2_592_000) throw new ManagedPlatformError("SESSION_TTL_INVALID", "Session lifetime is invalid.");
+ const token = `ds_session_${randomBytes(32).toString("base64url")}`;
+ const csrfToken = `ds_csrf_${randomBytes(24).toString("base64url")}`;
+ const session: StoredSession = {
+ id: `session_${randomUUID()}`,
+ scopeKey: scope.scopeKey,
+ userId,
+ tokenHash: sha256(token),
+ csrfHash: sha256(csrfToken),
+ expiresAt: new Date(this.options.now().getTime() + ttl * 1_000).toISOString(),
+ revokedAt: null,
+ createdAt: this.options.now().toISOString(),
+ };
+ this.sessions.set(session.id, session);
+ this.tokenIndex.set(session.tokenHash, session.id);
+ return { id: session.id, token, csrfToken, expiresAt: session.expiresAt };
+ }
+
+ verifySession(scope: ManagedScope, token: string, options: { write?: boolean; csrfToken?: string } = {}) {
+ const id = this.tokenIndex.get(sha256(token));
+ const session = id ? this.sessions.get(id) : undefined;
+ if (!session) throw new ManagedPlatformError("SESSION_INVALID", "Managed auth session is invalid.");
+ if (session.scopeKey !== scope.scopeKey) throw new ManagedPlatformError("SESSION_SCOPE_MISMATCH", "Managed auth session scope or environment does not match.");
+ if (session.revokedAt) throw new ManagedPlatformError("SESSION_REVOKED", "Managed auth session was revoked.");
+ if (Date.parse(session.expiresAt) <= this.options.now().getTime()) throw new ManagedPlatformError("SESSION_EXPIRED", "Managed auth session expired.");
+ if (options.write && (!options.csrfToken || !safeEqual(sha256(options.csrfToken), session.csrfHash))) throw new ManagedPlatformError("CSRF_INVALID", "CSRF validation failed.");
+ const user = this.users.get(scope.scopeKey)?.get(session.userId);
+ if (!user || user.status !== "active") throw new ManagedPlatformError("USER_UNAVAILABLE", "Project user is unavailable.");
+ return { sessionId: session.id, userId: user.id, roles: [...user.roles], expiresAt: session.expiresAt };
+ }
+
+ revokeSession(scope: ManagedScope, sessionId: string, principal: ManagedPrincipal): void {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.auth.manage");
+ const session = this.sessions.get(sessionId);
+ if (!session || session.scopeKey !== scope.scopeKey) throw new ManagedPlatformError("SESSION_NOT_FOUND", "Managed auth session does not exist.");
+ session.revokedAt = this.options.now().toISOString();
+ }
+
+ exportMetadata(scope: ManagedScope, principal: ManagedPrincipal): { users: ManagedAppUser[] } {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.auth.manage");
+ return { users: clone([...(this.users.get(scope.scopeKey)?.values() ?? [])]) };
+ }
+
+ importMetadata(scope: ManagedScope, input: { users: ManagedAppUser[] }, principal: ManagedPrincipal): void {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.auth.manage");
+ this.users.set(scope.scopeKey, new Map(input.users.map((user) => [user.id, { ...clone(user), scopeKey: scope.scopeKey }])));
+ }
+}
diff --git a/lib/managed-platform/backups.ts b/lib/managed-platform/backups.ts
new file mode 100644
index 0000000..91f9faa
--- /dev/null
+++ b/lib/managed-platform/backups.ts
@@ -0,0 +1,123 @@
+import { randomUUID } from "node:crypto";
+import type { ManagedFunctionManifest, ManagedPrincipal, ManagedRecord, ManagedSchemaSnapshot, ManagedScope } from "./contracts.ts";
+import type { ManagedAuthService } from "./auth.ts";
+import type { InMemoryManagedData } from "./data.ts";
+import type { ManagedLogStore } from "./logs.ts";
+import type { ManagedFunctionService, ManagedJobService, ManagedWebhookService } from "./runtime-services.ts";
+import type { ManagedSecretVault } from "./secrets.ts";
+import type { ManagedObjectStorage } from "./storage.ts";
+import { ManagedPlatformError, assertScope, clone, requireApproval, requirePermission, sha256, stableJson } from "./security.ts";
+
+const OMITTED_RESTORE_COMPONENTS = ["auth-sessions", "function-manifests", "job-metadata", "object-bytes", "secret-values", "webhook-configuration"] as const;
+
+interface BackupPayload {
+ schemaVersion: 1;
+ sourceScope: Omit;
+ data: { schema: ManagedSchemaSnapshot; rows: Record };
+ auth: ReturnType;
+ storageMetadata: ReturnType;
+ functionManifests: ManagedFunctionManifest[];
+ webhookConfiguration: ReturnType;
+ jobMetadata: ReturnType;
+ secretReferences: ReturnType;
+ createdAt: string;
+}
+
+export interface ManagedBackup {
+ id: string;
+ scopeKey: string;
+ environment: ManagedScope["environment"];
+ checksum: string;
+ byteSize: number;
+ payload: BackupPayload;
+ createdAt: string;
+}
+
+export class ManagedBackupService {
+ private readonly backups = new Map();
+ private readonly options: {
+ now: () => Date;
+ data: InMemoryManagedData;
+ auth: ManagedAuthService;
+ storage: ManagedObjectStorage;
+ functions: ManagedFunctionService;
+ webhooks: ManagedWebhookService;
+ jobs: ManagedJobService;
+ secrets: ManagedSecretVault;
+ logs: ManagedLogStore;
+ };
+ constructor(options: ManagedBackupService["options"]) { this.options = options; }
+
+ create(scope: ManagedScope, principal: ManagedPrincipal): ManagedBackup {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.backups.manage");
+ const createdAt = this.options.now().toISOString();
+ const payload: BackupPayload = {
+ schemaVersion: 1,
+ sourceScope: { organizationId: scope.organizationId, workspaceId: scope.workspaceId, projectId: scope.projectId, environment: scope.environment },
+ data: this.options.data.exportState(scope),
+ auth: this.options.auth.exportMetadata(scope, principal),
+ storageMetadata: this.options.storage.exportMetadata(scope),
+ functionManifests: this.options.functions.exportManifests(scope),
+ webhookConfiguration: this.options.webhooks.exportConfiguration(scope),
+ jobMetadata: this.options.jobs.exportMetadata(scope),
+ secretReferences: this.options.secrets.exportReferences(scope),
+ createdAt,
+ };
+ const serialized = stableJson(payload);
+ const backup: ManagedBackup = { id: `backup_${randomUUID()}`, scopeKey: scope.scopeKey, environment: scope.environment, checksum: sha256(serialized), byteSize: Buffer.byteLength(serialized), payload, createdAt };
+ this.backups.set(backup.id, backup);
+ this.options.logs.append(scope, { category: "backup", severity: "info", action: "backup.create", actorId: principal.actorId, requestId: backup.id, metadata: { backupId: backup.id, checksum: backup.checksum, byteSize: backup.byteSize } });
+ return clone(backup);
+ }
+
+ private verified(backupId: string): ManagedBackup {
+ const backup = this.backups.get(backupId);
+ if (!backup) throw new ManagedPlatformError("BACKUP_NOT_FOUND", "Managed backend backup does not exist.");
+ if (sha256(stableJson(backup.payload)) !== backup.checksum) throw new ManagedPlatformError("BACKUP_INTEGRITY_FAILED", "Managed backend backup checksum is invalid.");
+ return backup;
+ }
+
+ verifyForScope(backupId: string, scope: ManagedScope): boolean {
+ const backup = this.verified(backupId);
+ return backup.scopeKey === scope.scopeKey;
+ }
+
+ previewRestore(backupId: string, target: ManagedScope, principal: ManagedPrincipal) {
+ assertScope(target, principal);
+ requirePermission(principal, "backend.backups.manage");
+ const backup = this.verified(backupId);
+ if (backup.payload.sourceScope.organizationId !== target.organizationId || backup.payload.sourceScope.workspaceId !== target.workspaceId || backup.payload.sourceScope.projectId !== target.projectId) {
+ throw new ManagedPlatformError("BACKUP_SCOPE_DENIED", "Backup may only restore inside its authorized project.");
+ }
+ return {
+ backupId,
+ sourceEnvironment: backup.environment,
+ targetEnvironment: target.environment,
+ collectionCount: Object.keys(backup.payload.data.schema.collections).length,
+ rowCount: Object.values(backup.payload.data.rows).reduce((sum, rows) => sum + rows.length, 0),
+ secretReferencesRequireRotation: backup.payload.secretReferences.length,
+ objectBytesRestored: false,
+ omittedComponents: [...OMITTED_RESTORE_COMPONENTS],
+ warnings: [
+ "Secret values are excluded and every restored reference requires rotation.",
+ "Object metadata is included, but provider object bytes require the configured storage recovery adapter and are not restored by this adapter.",
+ "Auth sessions, function manifests, webhook configuration, and job metadata are evidence-only in this backup adapter and are not restored.",
+ ],
+ checksum: backup.checksum,
+ };
+ }
+
+ restore(backupId: string, target: ManagedScope, principal: ManagedPrincipal, options: { approvalReceipt?: string; overwrite?: boolean } = {}) {
+ const preview = this.previewRestore(backupId, target, principal);
+ if (target.environment === "production") requireApproval(options.approvalReceipt);
+ if (this.options.data.hasEnvironment(target) && !options.overwrite) throw new ManagedPlatformError("RESTORE_TARGET_NOT_EMPTY", "Restore target already exists; explicit verified overwrite is required.");
+ if (options.overwrite) requireApproval(options.approvalReceipt);
+ const backup = this.verified(backupId);
+ this.options.data.importState(target, backup.payload.data);
+ this.options.auth.importMetadata(target, backup.payload.auth, principal);
+ this.options.secrets.importReferences(target, backup.payload.secretReferences);
+ this.options.logs.append(target, { category: "backup", severity: "info", action: "backup.restore", actorId: principal.actorId, requestId: `restore_${randomUUID()}`, metadata: { backupId, sourceEnvironment: backup.environment, targetEnvironment: target.environment, checksum: backup.checksum, secretReferencesRequireRotation: preview.secretReferencesRequireRotation } });
+ return { status: "restored" as const, ...preview };
+ }
+}
diff --git a/lib/managed-platform/contracts.ts b/lib/managed-platform/contracts.ts
new file mode 100644
index 0000000..2670ec3
--- /dev/null
+++ b/lib/managed-platform/contracts.ts
@@ -0,0 +1,190 @@
+export const MANAGED_ENVIRONMENTS = ["development", "preview", "production"] as const;
+export type ManagedEnvironment = (typeof MANAGED_ENVIRONMENTS)[number];
+
+export interface ManagedScope {
+ organizationId: string;
+ workspaceId: string;
+ projectId: string;
+ environment: ManagedEnvironment;
+ scopeKey: string;
+}
+
+export interface ManagedPrincipal {
+ actorId: string;
+ actorType: "user" | "service-account" | "system" | "agent";
+ scope: ManagedScope;
+ roles: string[];
+ permissions: string[];
+}
+
+export type ManagedFieldType =
+ | "string"
+ | "text"
+ | "integer"
+ | "float"
+ | "boolean"
+ | "datetime"
+ | "json"
+ | "enum"
+ | "reference"
+ | "user-reference"
+ | "file-reference";
+
+export interface ManagedFieldSchema {
+ type: ManagedFieldType;
+ required?: boolean;
+ default?: unknown;
+ enumValues?: string[];
+ referenceCollection?: string;
+ deprecated?: boolean;
+}
+
+export interface ManagedCollectionSchema {
+ name: string;
+ rowPolicy: "project" | "owner" | "roles";
+ allowedRoles?: string[];
+ fields: Record;
+ indexes: Array<{ name: string; fields: string[]; unique?: boolean }>;
+}
+
+export interface ManagedSchemaSnapshot {
+ version: number;
+ collections: Record;
+ hash: string;
+ updatedAt: string;
+}
+
+export type ManagedMigrationOperation =
+ | { kind: "create-collection"; collection: ManagedCollectionSchema }
+ | { kind: "add-field"; collection: string; field: string; definition: ManagedFieldSchema }
+ | { kind: "rename-field"; collection: string; from: string; to: string }
+ | { kind: "deprecate-field"; collection: string; field: string }
+ | { kind: "add-index"; collection: string; index: ManagedCollectionSchema["indexes"][number] };
+
+export interface ManagedMigrationPlan {
+ id: string;
+ scopeKey: string;
+ fromVersion: number;
+ toVersion: number;
+ operations: ManagedMigrationOperation[];
+ destructive: boolean;
+ warnings: string[];
+ requiresApproval: boolean;
+ checksum: string;
+ createdAt: string;
+}
+
+export interface ManagedRecord {
+ _id: string;
+ _revision: number;
+ _ownerId: string;
+ _createdAt: string;
+ _updatedAt: string;
+ [field: string]: unknown;
+}
+
+export type ManagedFilterOperator = "eq" | "ne" | "gt" | "gte" | "lt" | "lte" | "in";
+export interface ManagedQuery {
+ filters?: Array<{ field: string; operator: ManagedFilterOperator; value: unknown }>;
+ sort?: Array<{ field: string; direction: "asc" | "desc" }>;
+ limit?: number;
+ cursor?: string;
+}
+
+export interface ManagedProviderStatus {
+ kind: "d1-drizzle" | "postgres-drizzle";
+ status: "working" | "setup-required" | "unavailable";
+ reasonCode?: string;
+}
+
+export interface ManagedTransaction {
+ execute(statement: string, parameters: readonly unknown[]): Promise<{ rows: unknown[]; affectedRows: number }>;
+}
+
+/** Binding supplied by a Cloudflare-compatible D1/Drizzle deployment. */
+export interface D1ManagedPlatformDriver {
+ readonly kind: "d1-drizzle";
+ transaction(scope: ManagedScope, operation: (transaction: ManagedTransaction) => Promise): Promise;
+ health(): Promise<{ status: "working" | "degraded"; latencyMs: number }>;
+}
+
+/** Pool supplied by a configured Postgres/Drizzle deployment. */
+export interface PostgresManagedPlatformDriver {
+ readonly kind: "postgres-drizzle";
+ transaction(scope: ManagedScope, operation: (transaction: ManagedTransaction) => Promise): Promise;
+ health(): Promise<{ status: "working" | "degraded"; latencyMs: number }>;
+}
+
+export interface ControlPlaneStore {
+ readonly provider: ManagedProviderStatus;
+ ensureEnvironment(scope: ManagedScope): Promise;
+}
+
+export interface ProjectDataStore {
+ readonly provider: ManagedProviderStatus;
+ schema(scope: ManagedScope): Promise;
+}
+
+export interface ManagedEmailAdapter {
+ readonly mode: "configured" | "test";
+ deliverOneTimeCode(input: { scope: ManagedScope; email: string; code: string; expiresAt: string }): Promise<{ evidenceId: string }>;
+}
+
+export interface ObjectStorageAdapter {
+ readonly mode: "configured" | "test";
+ put(namespace: string, key: string, bytes: Uint8Array, metadata: Record): Promise<{ providerObjectId: string }>;
+ get(namespace: string, providerObjectId: string): Promise;
+ delete(namespace: string, providerObjectId: string): Promise;
+}
+
+export interface ManagedFunctionManifest {
+ name: string;
+ version: number;
+ timeoutMs: number;
+ input: Record;
+ output: Record;
+ allowedNetworkHosts: string[];
+ secretReferences: string[];
+}
+
+export interface FunctionRuntimeAdapter {
+ readonly mode: "configured" | "test" | "setup-required";
+ invoke(manifest: ManagedFunctionManifest, input: Record, context: { scope: ManagedScope; signal: AbortSignal }): Promise>;
+}
+
+export interface JobQueueAdapter {
+ readonly mode: "configured" | "test" | "setup-required";
+}
+export interface CronAdapter {
+ readonly mode: "configured" | "test" | "setup-required";
+}
+export interface RealtimeAdapter {
+ readonly mode: "websocket" | "sse" | "polling" | "in-memory-test" | "setup-required";
+}
+export interface SecretVaultAdapter {
+ readonly mode: "configured" | "in-memory-test" | "setup-required";
+}
+
+export interface ManagedLogEntry {
+ id: string;
+ scopeKey: string;
+ category: "auth" | "storage" | "function" | "webhook" | "job" | "cron" | "realtime" | "schema" | "data" | "backup" | "secret";
+ severity: "info" | "warning" | "error";
+ action: string;
+ actorId: string;
+ requestId: string;
+ metadata: Record;
+ createdAt: string;
+}
+
+export interface ManagedPlatformLimits {
+ maxRowsPerEnvironment: number;
+ maxQueryComplexity: number;
+ maxObjectBytes: number;
+ maxObjectsPerEnvironment: number;
+ maxObjectBytesPerEnvironment: number;
+ maxGuestUsersPerEnvironment: number;
+ maxRealtimeEvents: number;
+ maxRealtimeSubscriptions: number;
+ maxJobsPerEnvironment: number;
+}
diff --git a/lib/managed-platform/data.ts b/lib/managed-platform/data.ts
new file mode 100644
index 0000000..f627547
--- /dev/null
+++ b/lib/managed-platform/data.ts
@@ -0,0 +1,363 @@
+import { randomUUID } from "node:crypto";
+import type {
+ ManagedCollectionSchema,
+ ManagedFieldSchema,
+ ManagedMigrationOperation,
+ ManagedMigrationPlan,
+ ManagedPlatformLimits,
+ ManagedPrincipal,
+ ManagedQuery,
+ ManagedRecord,
+ ManagedSchemaSnapshot,
+ ManagedScope,
+} from "./contracts.ts";
+import {
+ ManagedPlatformError,
+ assertScope,
+ clone,
+ requireApproval,
+ requirePermission,
+ sha256,
+ stableJson,
+} from "./security.ts";
+
+interface EnvironmentState {
+ schema: ManagedSchemaSnapshot;
+ rows: Map>;
+ idempotency: Map;
+ migrations: Array<{ plan: ManagedMigrationPlan; before: ManagedSchemaSnapshot; after: ManagedSchemaSnapshot }>;
+}
+
+const NAME = /^[a-z][a-zA-Z0-9_]{1,63}$/;
+const INDEX_NAME = /^[a-z][a-z0-9_]{2,95}$/;
+const INTERNAL_FIELDS = new Set(["_id", "_revision", "_ownerId", "_createdAt", "_updatedAt"]);
+
+function emptySchema(now: Date): ManagedSchemaSnapshot {
+ const collections = {};
+ return { version: 0, collections, hash: sha256(stableJson(collections)), updatedAt: now.toISOString() };
+}
+
+function validateFieldValue(field: string, definition: ManagedFieldSchema, value: unknown): void {
+ if (value === undefined || value === null) {
+ if (definition.required && definition.default === undefined) throw new ManagedPlatformError("FIELD_REQUIRED", `Field ${field} is required.`);
+ return;
+ }
+ const invalid = () => { throw new ManagedPlatformError("FIELD_TYPE_INVALID", `Field ${field} must be ${definition.type}.`); };
+ switch (definition.type) {
+ case "string":
+ case "text":
+ case "reference":
+ case "user-reference":
+ case "file-reference":
+ if (typeof value !== "string" || value.length > (definition.type === "text" ? 100_000 : 4_096)) invalid();
+ break;
+ case "integer": if (!Number.isSafeInteger(value)) invalid(); break;
+ case "float": if (typeof value !== "number" || !Number.isFinite(value)) invalid(); break;
+ case "boolean": if (typeof value !== "boolean") invalid(); break;
+ case "datetime": if (typeof value !== "string" || Number.isNaN(Date.parse(value))) invalid(); break;
+ case "json":
+ try {
+ const serialized = JSON.stringify(value);
+ if (!serialized || Buffer.byteLength(serialized) > 256_000) invalid();
+ } catch { invalid(); }
+ break;
+ case "enum":
+ if (typeof value !== "string" || !definition.enumValues?.includes(value)) invalid();
+ break;
+ }
+}
+
+function validateCollection(collection: ManagedCollectionSchema): ManagedCollectionSchema {
+ if (!NAME.test(collection.name)) throw new ManagedPlatformError("COLLECTION_INVALID", "Collection name is invalid.");
+ if (collection.rowPolicy === "roles" && !collection.allowedRoles?.length) throw new ManagedPlatformError("ROW_POLICY_INVALID", "Role-scoped collection requires allowed roles.");
+ if (!Object.keys(collection.fields).length || Object.keys(collection.fields).length > 100) throw new ManagedPlatformError("SCHEMA_LIMIT", "Collection field count is invalid.");
+ for (const [field, definition] of Object.entries(collection.fields)) {
+ if (!NAME.test(field) || INTERNAL_FIELDS.has(field)) throw new ManagedPlatformError("FIELD_INVALID", `Field ${field} is invalid.`);
+ if (definition.enumValues && (definition.type !== "enum" || !definition.enumValues.length || new Set(definition.enumValues).size !== definition.enumValues.length)) {
+ throw new ManagedPlatformError("FIELD_INVALID", `Enum field ${field} is invalid.`);
+ }
+ if (definition.default !== undefined) validateFieldValue(field, definition, definition.default);
+ }
+ const indexNames = new Set();
+ for (const index of collection.indexes) {
+ if (!INDEX_NAME.test(index.name) || indexNames.has(index.name) || !index.fields.length || index.fields.length > 8) throw new ManagedPlatformError("INDEX_INVALID", "Index is invalid.");
+ indexNames.add(index.name);
+ for (const field of index.fields) if (!collection.fields[field] && !INTERNAL_FIELDS.has(field)) throw new ManagedPlatformError("INDEX_INVALID", `Index field ${field} does not exist.`);
+ }
+ return clone(collection);
+}
+
+function applyOperation(snapshot: ManagedSchemaSnapshot, operation: ManagedMigrationOperation): void {
+ if (operation.kind === "create-collection") {
+ const collection = validateCollection(operation.collection);
+ if (snapshot.collections[collection.name]) throw new ManagedPlatformError("COLLECTION_EXISTS", `Collection ${collection.name} already exists.`);
+ snapshot.collections[collection.name] = collection;
+ return;
+ }
+ const collection = snapshot.collections[operation.collection];
+ if (!collection) throw new ManagedPlatformError("COLLECTION_NOT_FOUND", `Collection ${operation.collection} does not exist.`);
+ if (operation.kind === "add-field") {
+ if (!NAME.test(operation.field) || collection.fields[operation.field]) throw new ManagedPlatformError("FIELD_EXISTS", `Field ${operation.field} already exists or is invalid.`);
+ validateFieldValue(operation.field, operation.definition, operation.definition.default);
+ collection.fields[operation.field] = clone(operation.definition);
+ } else if (operation.kind === "rename-field") {
+ if (!collection.fields[operation.from] || collection.fields[operation.to] || !NAME.test(operation.to)) throw new ManagedPlatformError("FIELD_RENAME_INVALID", "Field rename is invalid.");
+ collection.fields[operation.to] = collection.fields[operation.from];
+ delete collection.fields[operation.from];
+ for (const index of collection.indexes) index.fields = index.fields.map((field) => field === operation.from ? operation.to : field);
+ } else if (operation.kind === "deprecate-field") {
+ if (!collection.fields[operation.field]) throw new ManagedPlatformError("FIELD_NOT_FOUND", `Field ${operation.field} does not exist.`);
+ collection.fields[operation.field].deprecated = true;
+ } else {
+ if (collection.indexes.some((index) => index.name === operation.index.name)) throw new ManagedPlatformError("INDEX_EXISTS", "Index already exists.");
+ collection.indexes.push(validateCollection({ ...collection, indexes: [...collection.indexes, operation.index] }).indexes.at(-1)!);
+ }
+}
+
+export class InMemoryManagedData {
+ private readonly states = new Map();
+ private readonly options: { now: () => Date; limits: ManagedPlatformLimits };
+ constructor(options: { now: () => Date; limits: ManagedPlatformLimits }) { this.options = options; }
+
+ ensureEnvironment(scope: ManagedScope, principal: ManagedPrincipal): ManagedSchemaSnapshot {
+ assertScope(scope, principal);
+ if (!principal.roles.includes("owner") && !principal.permissions.some((permission) => permission.startsWith("backend."))) {
+ throw new ManagedPlatformError("PERMISSION_DENIED", "Backend access is required to create an environment.");
+ }
+ if (!this.states.has(scope.scopeKey)) {
+ this.states.set(scope.scopeKey, { schema: emptySchema(this.options.now()), rows: new Map(), idempotency: new Map(), migrations: [] });
+ }
+ return this.snapshot(scope, principal);
+ }
+
+ hasEnvironment(scope: ManagedScope): boolean { return this.states.has(scope.scopeKey); }
+
+ private state(scope: ManagedScope): EnvironmentState {
+ const state = this.states.get(scope.scopeKey);
+ if (!state) throw new ManagedPlatformError("ENVIRONMENT_NOT_FOUND", "Managed environment does not exist.");
+ return state;
+ }
+
+ snapshot(scope: ManagedScope, principal: ManagedPrincipal): ManagedSchemaSnapshot {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.schema.manage");
+ return clone(this.state(scope).schema);
+ }
+
+ plan(scope: ManagedScope, input: { baseVersion: number; operations: ManagedMigrationOperation[] }, principal: ManagedPrincipal): ManagedMigrationPlan {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.schema.manage");
+ const state = this.state(scope);
+ if (input.baseVersion !== state.schema.version) throw new ManagedPlatformError("SCHEMA_REVISION_CONFLICT", "Schema migration base version is stale.");
+ if (!input.operations.length || input.operations.length > 50) throw new ManagedPlatformError("MIGRATION_INVALID", "Migration operation count is invalid.");
+ const proposed = clone(state.schema);
+ for (const operation of input.operations) applyOperation(proposed, operation);
+ const destructive = input.operations.some((operation) => operation.kind === "rename-field" || operation.kind === "deprecate-field");
+ const createdAt = this.options.now().toISOString();
+ const base = {
+ scopeKey: scope.scopeKey,
+ fromVersion: state.schema.version,
+ toVersion: state.schema.version + 1,
+ operations: clone(input.operations),
+ destructive,
+ warnings: destructive ? ["Migration changes existing fields; create a verified backup before production promotion."] : [],
+ requiresApproval: scope.environment === "production" || destructive,
+ createdAt,
+ };
+ const checksum = sha256(stableJson(base));
+ return { id: `migration_${checksum.slice(0, 24)}`, ...base, checksum };
+ }
+
+ apply(scope: ManagedScope, plan: ManagedMigrationPlan, principal: ManagedPrincipal, options: { approvalReceipt?: string } = {}): ManagedSchemaSnapshot {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.schema.manage");
+ const state = this.state(scope);
+ if (plan.scopeKey !== scope.scopeKey || plan.fromVersion !== state.schema.version) throw new ManagedPlatformError("SCHEMA_REVISION_CONFLICT", "Schema migration plan is stale or belongs to another environment.");
+ const base = {
+ scopeKey: plan.scopeKey,
+ fromVersion: plan.fromVersion,
+ toVersion: plan.toVersion,
+ operations: plan.operations,
+ destructive: plan.destructive,
+ warnings: plan.warnings,
+ requiresApproval: plan.requiresApproval,
+ createdAt: plan.createdAt,
+ };
+ if (sha256(stableJson(base)) !== plan.checksum) throw new ManagedPlatformError("MIGRATION_TAMPERED", "Schema migration checksum is invalid.");
+ if (plan.requiresApproval) requireApproval(options.approvalReceipt);
+ const before = clone(state.schema);
+ const after = clone(state.schema);
+ for (const operation of plan.operations) applyOperation(after, operation);
+ after.version = plan.toVersion;
+ after.updatedAt = this.options.now().toISOString();
+ after.hash = sha256(stableJson(after.collections));
+ state.schema = after;
+ for (const name of Object.keys(after.collections)) if (!state.rows.has(name)) state.rows.set(name, new Map());
+ state.migrations.push({ plan: clone(plan), before, after: clone(after) });
+ return clone(after);
+ }
+
+ private collection(scope: ManagedScope, name: string): { state: EnvironmentState; schema: ManagedCollectionSchema; rows: Map } {
+ const state = this.state(scope);
+ const schema = state.schema.collections[name];
+ const rows = state.rows.get(name);
+ if (!schema || !rows) throw new ManagedPlatformError("COLLECTION_NOT_FOUND", `Collection ${name} does not exist in this environment.`);
+ return { state, schema, rows };
+ }
+
+ private authorizeRow(schema: ManagedCollectionSchema, record: ManagedRecord, principal: ManagedPrincipal): boolean {
+ if (principal.permissions.includes("backend.data.admin") || principal.roles.includes("owner")) return true;
+ if (schema.rowPolicy === "project") return true;
+ if (schema.rowPolicy === "owner") return record._ownerId === principal.actorId;
+ return schema.allowedRoles?.some((role) => principal.roles.includes(role)) ?? false;
+ }
+
+ private validateData(schema: ManagedCollectionSchema, input: Record, partial: boolean): Record {
+ for (const key of Object.keys(input)) if (!schema.fields[key]) throw new ManagedPlatformError("FIELD_UNKNOWN", `Unknown field ${key}.`);
+ const result: Record = {};
+ for (const [field, definition] of Object.entries(schema.fields)) {
+ const supplied = Object.hasOwn(input, field);
+ const value = supplied ? input[field] : partial ? undefined : definition.default;
+ if (!partial || supplied) {
+ validateFieldValue(field, definition, value);
+ if (value !== undefined) result[field] = clone(value);
+ }
+ }
+ return result;
+ }
+
+ private enforceUnique(schema: ManagedCollectionSchema, rows: Map, candidate: ManagedRecord, excludingId?: string): void {
+ for (const index of schema.indexes.filter((entry) => entry.unique)) {
+ if (index.fields.some((field) => candidate[field] === undefined || candidate[field] === null)) continue;
+ const collision = [...rows.values()].some((record) => record._id !== excludingId
+ && index.fields.every((field) => record[field] !== undefined && record[field] !== null && stableJson(record[field]) === stableJson(candidate[field])));
+ if (collision) throw new ManagedPlatformError("UNIQUE_CONSTRAINT", `Unique constraint ${index.name} was violated.`);
+ }
+ }
+
+ create(scope: ManagedScope, collectionName: string, input: Record, principal: ManagedPrincipal, options: { idempotencyKey?: string } = {}): ManagedRecord {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.data.write");
+ const { state, schema, rows } = this.collection(scope, collectionName);
+ const data = this.validateData(schema, input, false);
+ const fingerprint = sha256(stableJson({ action: "create", collectionName, data, actorId: principal.actorId }));
+ if (options.idempotencyKey) {
+ const key = `${principal.actorId}:create:${options.idempotencyKey}`;
+ const previous = state.idempotency.get(key);
+ if (previous) {
+ if (previous.fingerprint !== fingerprint) throw new ManagedPlatformError("IDEMPOTENCY_CONFLICT", "Idempotency key was reused with different input.");
+ return clone(previous.value);
+ }
+ }
+ const totalRows = [...state.rows.values()].reduce((sum, collection) => sum + collection.size, 0);
+ if (totalRows >= this.options.limits.maxRowsPerEnvironment) throw new ManagedPlatformError("ROW_QUOTA_EXCEEDED", "Managed data row quota exceeded.");
+ const timestamp = this.options.now().toISOString();
+ const record: ManagedRecord = { ...data, _id: `row_${randomUUID()}`, _revision: 1, _ownerId: principal.actorId, _createdAt: timestamp, _updatedAt: timestamp };
+ if (!this.authorizeRow(schema, record, principal)) throw new ManagedPlatformError("ROW_SCOPE_DENIED", "Managed row scope denied creation.");
+ this.enforceUnique(schema, rows, record);
+ rows.set(record._id, record);
+ if (options.idempotencyKey) state.idempotency.set(`${principal.actorId}:create:${options.idempotencyKey}`, { fingerprint, value: clone(record) });
+ return clone(record);
+ }
+
+ read(scope: ManagedScope, collectionName: string, id: string, principal: ManagedPrincipal): ManagedRecord {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.data.read");
+ const { schema, rows } = this.collection(scope, collectionName);
+ const record = rows.get(id);
+ if (!record) throw new ManagedPlatformError("ROW_NOT_FOUND", "Managed row does not exist.");
+ if (!this.authorizeRow(schema, record, principal)) throw new ManagedPlatformError("ROW_SCOPE_DENIED", "Managed row scope denied access.");
+ return clone(record);
+ }
+
+ query(scope: ManagedScope, collectionName: string, query: ManagedQuery, principal: ManagedPrincipal): { rows: ManagedRecord[]; nextCursor: string | null } {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.data.read");
+ const { schema, rows } = this.collection(scope, collectionName);
+ const filters = query.filters ?? [];
+ const sort = query.sort ?? [];
+ const complexity = filters.length + sort.length * 2;
+ if (complexity > this.options.limits.maxQueryComplexity || filters.some((filter) => filter.operator === "in" && (!Array.isArray(filter.value) || filter.value.length > 20))) {
+ throw new ManagedPlatformError("QUERY_COMPLEXITY", "Managed query complexity exceeds the bounded limit.");
+ }
+ const limit = query.limit ?? 50;
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw new ManagedPlatformError("QUERY_LIMIT", "Managed query limit is invalid.");
+ for (const entry of [...filters, ...sort]) if (!schema.fields[entry.field] && !INTERNAL_FIELDS.has(entry.field)) throw new ManagedPlatformError("QUERY_FIELD", `Query field ${entry.field} is invalid.`);
+ const compare = (left: unknown, operator: string, right: unknown) => {
+ if (operator === "eq") return stableJson(left) === stableJson(right);
+ if (operator === "ne") return stableJson(left) !== stableJson(right);
+ if (operator === "in") return (right as unknown[]).some((entry) => stableJson(left) === stableJson(entry));
+ if (operator === "gt") return (left as never) > (right as never);
+ if (operator === "gte") return (left as never) >= (right as never);
+ if (operator === "lt") return (left as never) < (right as never);
+ return (left as never) <= (right as never);
+ };
+ const selected = [...rows.values()].filter((record) => this.authorizeRow(schema, record, principal) && filters.every((filter) => compare(record[filter.field], filter.operator, filter.value)));
+ const compareSortValues = (left: unknown, right: unknown): number => {
+ if (Object.is(left, right)) return 0;
+ if (left === undefined || left === null) return -1;
+ if (right === undefined || right === null) return 1;
+ if (typeof left === "number" && typeof right === "number") return left < right ? -1 : 1;
+ if (typeof left === "string" && typeof right === "string") return left.localeCompare(right);
+ if (typeof left === "boolean" && typeof right === "boolean") return left === false ? -1 : 1;
+ return stableJson(left).localeCompare(stableJson(right));
+ };
+ selected.sort((left, right) => {
+ for (const entry of sort) {
+ const order = compareSortValues(left[entry.field], right[entry.field]);
+ if (order) return entry.direction === "asc" ? order : -order;
+ }
+ return left._id.localeCompare(right._id);
+ });
+ const offset = query.cursor ? Number(Buffer.from(query.cursor, "base64url").toString("utf8")) : 0;
+ if (!Number.isSafeInteger(offset) || offset < 0) throw new ManagedPlatformError("CURSOR_INVALID", "Query cursor is invalid.");
+ const page = selected.slice(offset, offset + limit);
+ const next = offset + limit < selected.length ? Buffer.from(String(offset + limit)).toString("base64url") : null;
+ return { rows: clone(page), nextCursor: next };
+ }
+
+ update(scope: ManagedScope, collectionName: string, id: string, patch: Record, principal: ManagedPrincipal, options: { expectedRevision: number; idempotencyKey?: string }): ManagedRecord {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.data.write");
+ const { state, schema, rows } = this.collection(scope, collectionName);
+ const record = rows.get(id);
+ if (!record) throw new ManagedPlatformError("ROW_NOT_FOUND", "Managed row does not exist.");
+ if (!this.authorizeRow(schema, record, principal)) throw new ManagedPlatformError("ROW_SCOPE_DENIED", "Managed row scope denied access.");
+ const data = this.validateData(schema, patch, true);
+ const fingerprint = sha256(stableJson({ action: "update", id, options: { expectedRevision: options.expectedRevision }, data }));
+ if (options.idempotencyKey) {
+ const previous = state.idempotency.get(`${principal.actorId}:update:${options.idempotencyKey}`);
+ if (previous) {
+ if (previous.fingerprint !== fingerprint) throw new ManagedPlatformError("IDEMPOTENCY_CONFLICT", "Idempotency key was reused with different input.");
+ return clone(previous.value);
+ }
+ }
+ if (record._revision !== options.expectedRevision) throw new ManagedPlatformError("REVISION_CONFLICT", "Managed row revision conflict.");
+ const updated: ManagedRecord = { ...record, ...data, _revision: record._revision + 1, _updatedAt: this.options.now().toISOString() };
+ this.enforceUnique(schema, rows, updated, id);
+ rows.set(id, updated);
+ if (options.idempotencyKey) state.idempotency.set(`${principal.actorId}:update:${options.idempotencyKey}`, { fingerprint, value: clone(updated) });
+ return clone(updated);
+ }
+
+ delete(scope: ManagedScope, collectionName: string, id: string, principal: ManagedPrincipal, options: { expectedRevision: number }): void {
+ const record = this.read(scope, collectionName, id, principal);
+ requirePermission(principal, "backend.data.write");
+ if (record._revision !== options.expectedRevision) throw new ManagedPlatformError("REVISION_CONFLICT", "Managed row revision conflict.");
+ this.collection(scope, collectionName).rows.delete(id);
+ }
+
+ exportState(scope: ManagedScope): { schema: ManagedSchemaSnapshot; rows: Record } {
+ const state = this.state(scope);
+ return {
+ schema: clone(state.schema),
+ rows: Object.fromEntries([...state.rows.entries()].map(([name, rows]) => [name, clone([...rows.values()])])),
+ };
+ }
+
+ importState(scope: ManagedScope, snapshot: { schema: ManagedSchemaSnapshot; rows: Record }): void {
+ const rows = new Map>();
+ for (const name of Object.keys(snapshot.schema.collections)) rows.set(name, new Map((snapshot.rows[name] ?? []).map((record) => [record._id, clone(record)])));
+ this.states.set(scope.scopeKey, { schema: clone(snapshot.schema), rows, idempotency: new Map(), migrations: [] });
+ }
+}
diff --git a/lib/managed-platform/index.ts b/lib/managed-platform/index.ts
new file mode 100644
index 0000000..5713018
--- /dev/null
+++ b/lib/managed-platform/index.ts
@@ -0,0 +1,6 @@
+export * from "./contracts.ts";
+export * from "./security.ts";
+export * from "./providers.ts";
+export * from "./runtime-services.ts";
+export * from "./platform.ts";
+export type { ManagedBackup } from "./backups.ts";
diff --git a/lib/managed-platform/logs.ts b/lib/managed-platform/logs.ts
new file mode 100644
index 0000000..aa326af
--- /dev/null
+++ b/lib/managed-platform/logs.ts
@@ -0,0 +1,39 @@
+import { randomUUID } from "node:crypto";
+import type { ManagedLogEntry, ManagedPrincipal, ManagedScope } from "./contracts.ts";
+import { assertScope, clone, requirePermission, sanitizeLogValue } from "./security.ts";
+
+export class ManagedLogStore {
+ private readonly entries = new Map();
+ private readonly now: () => Date;
+ private readonly maxEntriesPerEnvironment: number;
+ constructor(now: () => Date, maxEntriesPerEnvironment = 2_000) {
+ this.now = now;
+ this.maxEntriesPerEnvironment = maxEntriesPerEnvironment;
+ }
+
+ append(scope: ManagedScope, input: Omit): ManagedLogEntry {
+ const entry: ManagedLogEntry = {
+ ...input,
+ id: `log_${randomUUID()}`,
+ scopeKey: scope.scopeKey,
+ metadata: sanitizeLogValue(input.metadata) as Record,
+ createdAt: this.now().toISOString(),
+ };
+ const current = this.entries.get(scope.scopeKey) ?? [];
+ current.push(entry);
+ if (current.length > this.maxEntriesPerEnvironment) current.splice(0, current.length - this.maxEntriesPerEnvironment);
+ this.entries.set(scope.scopeKey, current);
+ return clone(entry);
+ }
+
+ list(scope: ManagedScope, principal: ManagedPrincipal, input: { category?: ManagedLogEntry["category"]; severity?: ManagedLogEntry["severity"]; limit?: number } = {}): ManagedLogEntry[] {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.logs.read");
+ const limit = input.limit ?? 100;
+ if (!Number.isInteger(limit) || limit < 1 || limit > 500) throw new Error("Log query limit is invalid.");
+ return clone((this.entries.get(scope.scopeKey) ?? [])
+ .filter((entry) => (!input.category || entry.category === input.category) && (!input.severity || entry.severity === input.severity))
+ .slice(-limit)
+ .reverse());
+ }
+}
diff --git a/lib/managed-platform/platform.ts b/lib/managed-platform/platform.ts
new file mode 100644
index 0000000..1665a3f
--- /dev/null
+++ b/lib/managed-platform/platform.ts
@@ -0,0 +1,124 @@
+import type { FunctionRuntimeAdapter, ManagedEmailAdapter, ManagedPlatformLimits, ManagedPrincipal, ManagedScope } from "./contracts.ts";
+import { ManagedAuthService } from "./auth.ts";
+import { ManagedBackupService } from "./backups.ts";
+import { InMemoryManagedData } from "./data.ts";
+import { ManagedLogStore } from "./logs.ts";
+import {
+ ManagedCronService,
+ ManagedFunctionService,
+ ManagedJobService,
+ ManagedRealtimeService,
+ ManagedWebhookService,
+ SetupRequiredFunctionRuntime,
+} from "./runtime-services.ts";
+import { ManagedPlatformError, assertScope, requirePermission } from "./security.ts";
+import { ManagedSecretVault } from "./secrets.ts";
+import { ManagedObjectStorage } from "./storage.ts";
+
+const DEFAULT_LIMITS: ManagedPlatformLimits = {
+ maxRowsPerEnvironment: 10_000,
+ maxQueryComplexity: 12,
+ maxObjectBytes: 10 * 1024 * 1024,
+ maxObjectsPerEnvironment: 1_000,
+ maxObjectBytesPerEnvironment: 512 * 1024 * 1024,
+ maxGuestUsersPerEnvironment: 1_000,
+ maxRealtimeEvents: 2_000,
+ maxRealtimeSubscriptions: 100,
+ maxJobsPerEnvironment: 1_000,
+};
+
+export interface InMemoryManagedPlatformOptions {
+ signingKey: Uint8Array;
+ encryptionKey: Uint8Array;
+ now?: () => Date;
+ limits?: Partial;
+ emailAdapter?: ManagedEmailAdapter;
+ functionRuntime?: FunctionRuntimeAdapter;
+ objectScanner?: (bytes: Uint8Array) => "clean" | "rejected";
+}
+
+export function createInMemoryManagedPlatform(options: InMemoryManagedPlatformOptions) {
+ if (options.signingKey.byteLength < 32) throw new ManagedPlatformError("SIGNING_KEY_INVALID", "Managed capability signing key must be at least 32 bytes.");
+ const now = options.now ?? (() => new Date());
+ const limits = { ...DEFAULT_LIMITS, ...options.limits };
+ const logs = new ManagedLogStore(now);
+ const dataCore = new InMemoryManagedData({ now, limits });
+ const secrets = new ManagedSecretVault({ key: options.encryptionKey, now, logs });
+ const storage = new ManagedObjectStorage({ signingKey: options.signingKey, now, limits, logs, scan: options.objectScanner });
+ const auth = new ManagedAuthService({ now, logs, limits, emailAdapter: options.emailAdapter });
+ const functions = new ManagedFunctionService({ runtime: options.functionRuntime ?? new SetupRequiredFunctionRuntime(), now, logs });
+ const webhooks = new ManagedWebhookService({ now, logs, secrets });
+ const jobs = new ManagedJobService({ now, logs, limits });
+ const cron = new ManagedCronService({ now, logs });
+ const realtime = new ManagedRealtimeService({ now, logs, limits });
+ const backups = new ManagedBackupService({ now, data: dataCore, auth, storage, functions, webhooks, jobs, secrets, logs });
+
+ const environments = {
+ ensure(scope: ManagedScope, principal: ManagedPrincipal) {
+ return dataCore.ensureEnvironment(scope, principal);
+ },
+ status(scope: ManagedScope, principal: ManagedPrincipal) {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.data.read");
+ return { environment: scope.environment, status: dataCore.hasEnvironment(scope) ? "working" as const : "setup-required" as const };
+ },
+ };
+
+ const schema = {
+ snapshot: dataCore.snapshot.bind(dataCore),
+ plan: dataCore.plan.bind(dataCore),
+ apply(scope: ManagedScope, plan: Parameters[1], principal: ManagedPrincipal, applyOptions: { approvalReceipt?: string; backupId?: string } = {}) {
+ if (scope.environment === "production" && plan.destructive) {
+ if (!applyOptions.backupId || !backups.verifyForScope(applyOptions.backupId, scope)) {
+ throw new ManagedPlatformError("MIGRATION_BACKUP_REQUIRED", "A verified production backup is required before destructive migration.");
+ }
+ }
+ return dataCore.apply(scope, plan, principal, applyOptions);
+ },
+ };
+
+ const data = {
+ create: dataCore.create.bind(dataCore),
+ read: dataCore.read.bind(dataCore),
+ query: dataCore.query.bind(dataCore),
+ update: dataCore.update.bind(dataCore),
+ delete: dataCore.delete.bind(dataCore),
+ };
+
+ const secretControls = Object.freeze({
+ mode: secrets.mode,
+ create: secrets.create.bind(secrets),
+ list: secrets.list.bind(secrets),
+ rotate: secrets.rotate.bind(secrets),
+ revoke: secrets.revoke.bind(secrets),
+ });
+
+ return Object.freeze({
+ mode: "in-memory-test" as const,
+ environments,
+ schema,
+ data,
+ auth,
+ storage,
+ functions,
+ webhooks,
+ jobs,
+ cron,
+ realtime,
+ secrets: secretControls,
+ logs: { list: logs.list.bind(logs) },
+ backups,
+ capabilities: Object.freeze({
+ data: "working-test-adapter",
+ auth: options.emailAdapter ? "working-configured-email" : "setup-required-email",
+ storage: "working-test-adapter",
+ functions: options.functionRuntime?.mode ?? "setup-required",
+ webhooks: "working-signed-inbox",
+ jobs: "working-test-adapter",
+ cron: "working-test-adapter",
+ realtime: "in-memory-test-not-live-cursors",
+ secrets: "encrypted-in-memory-test",
+ backups: "metadata-and-data-working-object-bytes-excluded",
+ }),
+ });
+}
diff --git a/lib/managed-platform/providers.ts b/lib/managed-platform/providers.ts
new file mode 100644
index 0000000..87d1f27
--- /dev/null
+++ b/lib/managed-platform/providers.ts
@@ -0,0 +1,48 @@
+import type { D1ManagedPlatformDriver, ManagedProviderStatus, PostgresManagedPlatformDriver } from "./contracts.ts";
+import { ManagedPlatformError } from "./security.ts";
+
+interface ProviderHealthOptions { timeoutMs?: number }
+
+async function boundedHealth(health: () => Promise, options: ProviderHealthOptions): Promise {
+ const timeoutMs = options.timeoutMs ?? 5_000;
+ if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 30_000) throw new ManagedPlatformError("PROVIDER_HEALTH_TIMEOUT_INVALID", "Provider health timeout is invalid.");
+ let timer: ReturnType | undefined;
+ try {
+ return await Promise.race([
+ health(),
+ new Promise((_resolve, reject) => {
+ timer = setTimeout(() => reject(new ManagedPlatformError("PROVIDER_HEALTH_TIMEOUT", "Managed provider health check timed out.")), timeoutMs);
+ }),
+ ]);
+ } finally {
+ if (timer) clearTimeout(timer);
+ }
+}
+
+export function describeD1ManagedProvider(driver?: D1ManagedPlatformDriver): ManagedProviderStatus {
+ if (!driver) return { kind: "d1-drizzle", status: "setup-required", reasonCode: "D1_BINDING_REQUIRED" };
+ return { kind: "d1-drizzle", status: "unavailable", reasonCode: "HEALTH_CHECK_REQUIRED" };
+}
+
+export function describePostgresManagedProvider(driver?: PostgresManagedPlatformDriver): ManagedProviderStatus {
+ if (!driver) return { kind: "postgres-drizzle", status: "setup-required", reasonCode: "DATABASE_URL_AND_DRIVER_REQUIRED" };
+ return { kind: "postgres-drizzle", status: "unavailable", reasonCode: "HEALTH_CHECK_REQUIRED" };
+}
+
+export async function verifyD1ManagedProvider(driver: D1ManagedPlatformDriver, options: ProviderHealthOptions = {}): Promise {
+ try {
+ const health = await boundedHealth(() => driver.health(), options);
+ return { kind: "d1-drizzle", status: health.status === "working" ? "working" : "unavailable", reasonCode: health.status === "degraded" ? "D1_DEGRADED" : undefined, latencyMs: health.latencyMs };
+ } catch (error) {
+ return { kind: "d1-drizzle", status: "unavailable", reasonCode: error instanceof ManagedPlatformError && error.code === "PROVIDER_HEALTH_TIMEOUT" ? "D1_HEALTH_TIMEOUT" : "D1_HEALTH_FAILED", latencyMs: 0 };
+ }
+}
+
+export async function verifyPostgresManagedProvider(driver: PostgresManagedPlatformDriver, options: ProviderHealthOptions = {}): Promise {
+ try {
+ const health = await boundedHealth(() => driver.health(), options);
+ return { kind: "postgres-drizzle", status: health.status === "working" ? "working" : "unavailable", reasonCode: health.status === "degraded" ? "POSTGRES_DEGRADED" : undefined, latencyMs: health.latencyMs };
+ } catch (error) {
+ return { kind: "postgres-drizzle", status: "unavailable", reasonCode: error instanceof ManagedPlatformError && error.code === "PROVIDER_HEALTH_TIMEOUT" ? "POSTGRES_HEALTH_TIMEOUT" : "POSTGRES_HEALTH_FAILED", latencyMs: 0 };
+ }
+}
diff --git a/lib/managed-platform/runtime-services.ts b/lib/managed-platform/runtime-services.ts
new file mode 100644
index 0000000..f42516a
--- /dev/null
+++ b/lib/managed-platform/runtime-services.ts
@@ -0,0 +1,396 @@
+import { createHmac, randomUUID } from "node:crypto";
+import type {
+ FunctionRuntimeAdapter,
+ ManagedFieldType,
+ ManagedFunctionManifest,
+ ManagedPlatformLimits,
+ ManagedPrincipal,
+ ManagedScope,
+} from "./contracts.ts";
+import { ManagedPlatformError, assertScope, clone, requireApproval, requirePermission, safeEqual, sanitizeLogValue, stableJson } from "./security.ts";
+import type { ManagedLogStore } from "./logs.ts";
+import type { ManagedSecretVault } from "./secrets.ts";
+
+type FunctionHandler = (input: Record, context: { scope: ManagedScope; signal: AbortSignal }) => Promise> | Record;
+
+export class InMemoryFunctionRuntime implements FunctionRuntimeAdapter {
+ readonly mode = "test" as const;
+ private readonly handlers: Record;
+ constructor(handlers: Record) { this.handlers = handlers; }
+ async invoke(manifest: ManagedFunctionManifest, input: Record, context: { scope: ManagedScope; signal: AbortSignal }): Promise> {
+ const handler = this.handlers[manifest.name];
+ if (!handler) throw new ManagedPlatformError("FUNCTION_HANDLER_MISSING", "Test function handler is not configured.");
+ return handler(clone(input), context);
+ }
+}
+
+export class SetupRequiredFunctionRuntime implements FunctionRuntimeAdapter {
+ readonly mode = "setup-required" as const;
+ async invoke(): Promise> {
+ throw new ManagedPlatformError("FUNCTION_RUNTIME_REQUIRED", "Function runtime setup is required.");
+ }
+}
+
+function validateShape(schema: Record, value: Record, label: string): void {
+ for (const [field, type] of Object.entries(schema)) {
+ const current = value[field];
+ const matches = type === "json"
+ ? current !== undefined
+ : type === "integer"
+ ? Number.isSafeInteger(current)
+ : type === "float"
+ ? typeof current === "number" && Number.isFinite(current)
+ : type === "boolean"
+ ? typeof current === "boolean"
+ : type === "datetime"
+ ? typeof current === "string" && !Number.isNaN(Date.parse(current))
+ : typeof current === "string";
+ if (!matches) throw new ManagedPlatformError("FUNCTION_SCHEMA_INVALID", `${label} field ${field} must be ${type}.`);
+ }
+ for (const field of Object.keys(value)) if (!schema[field]) throw new ManagedPlatformError("FUNCTION_SCHEMA_INVALID", `${label} contains unknown field ${field}.`);
+}
+
+export class ManagedFunctionService {
+ private readonly manifests = new Map();
+ private readonly options: { runtime: FunctionRuntimeAdapter; now: () => Date; logs: ManagedLogStore };
+ constructor(options: { runtime: FunctionRuntimeAdapter; now: () => Date; logs: ManagedLogStore }) { this.options = options; }
+
+ register(scope: ManagedScope, manifest: ManagedFunctionManifest, principal: ManagedPrincipal): ManagedFunctionManifest {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.functions.manage");
+ if (!/^[a-z][a-z0-9-]{1,63}$/.test(manifest.name) || !Number.isInteger(manifest.version) || manifest.version < 1 || manifest.timeoutMs < 100 || manifest.timeoutMs > 60_000) throw new ManagedPlatformError("FUNCTION_MANIFEST_INVALID", "Function manifest is invalid.");
+ for (const host of manifest.allowedNetworkHosts) {
+ if (!/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/i.test(host) || host === "localhost" || host.endsWith(".local")) throw new ManagedPlatformError("FUNCTION_NETWORK_INVALID", "Function network allowlist contains an invalid host.");
+ }
+ const key = `${scope.scopeKey}:${manifest.name}`;
+ const previous = this.manifests.get(key);
+ if (previous && manifest.version <= previous.version) throw new ManagedPlatformError("FUNCTION_VERSION_CONFLICT", "Function version must increase.");
+ this.manifests.set(key, clone(manifest));
+ return clone(manifest);
+ }
+
+ async invoke(scope: ManagedScope, name: string, input: Record, principal: ManagedPrincipal) {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.functions.invoke");
+ const manifest = this.manifests.get(`${scope.scopeKey}:${name}`);
+ if (!manifest) throw new ManagedPlatformError("FUNCTION_NOT_FOUND", "Managed function is not registered.");
+ if (this.options.runtime.mode === "setup-required") throw new ManagedPlatformError("FUNCTION_RUNTIME_REQUIRED", "Function runtime setup is required.");
+ validateShape(manifest.input, input, "Function input");
+ const controller = new AbortController();
+ let timer: ReturnType | undefined;
+ const timeout = new Promise((_resolve, reject) => {
+ timer = setTimeout(() => { controller.abort(); reject(new ManagedPlatformError("FUNCTION_TIMEOUT", "Managed function timed out.")); }, manifest.timeoutMs);
+ });
+ const requestId = `function_run_${randomUUID()}`;
+ try {
+ const output = await Promise.race([this.options.runtime.invoke(manifest, input, { scope, signal: controller.signal }), timeout]);
+ validateShape(manifest.output, output, "Function output");
+ this.options.logs.append(scope, { category: "function", severity: "info", action: "function.invoke.succeeded", actorId: principal.actorId, requestId, metadata: { function: name, version: manifest.version, output } });
+ return { id: requestId, status: "succeeded" as const, output: clone(output), runtimeMode: this.options.runtime.mode, finishedAt: this.options.now().toISOString() };
+ } catch (error) {
+ this.options.logs.append(scope, { category: "function", severity: "error", action: "function.invoke.failed", actorId: principal.actorId, requestId, metadata: { function: name, code: error instanceof ManagedPlatformError ? error.code : "FUNCTION_FAILED" } });
+ throw error;
+ } finally {
+ if (timer) clearTimeout(timer);
+ }
+ }
+
+ exportManifests(scope: ManagedScope): ManagedFunctionManifest[] {
+ return clone([...this.manifests.entries()].filter(([key]) => key.startsWith(`${scope.scopeKey}:`)).map(([, manifest]) => manifest));
+ }
+}
+
+interface WebhookEndpoint {
+ id: string;
+ scopeKey: string;
+ name: string;
+ signingSecretId: string;
+ eventType: string;
+ status: "active" | "disabled";
+ createdAt: string;
+}
+interface WebhookEvent {
+ id: string;
+ endpointId: string;
+ scopeKey: string;
+ eventType: string;
+ payload: unknown;
+ receivedAt: string;
+}
+
+export class ManagedWebhookService {
+ private readonly endpoints = new Map();
+ private readonly events = new Map();
+ private readonly replayKeys = new Map();
+ private readonly options: { now: () => Date; logs: ManagedLogStore; secrets: ManagedSecretVault };
+ constructor(options: { now: () => Date; logs: ManagedLogStore; secrets: ManagedSecretVault }) { this.options = options; }
+
+ register(scope: ManagedScope, input: { name: string; signingSecretId: string; eventType: string }, principal: ManagedPrincipal) {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.webhooks.manage");
+ if (!/^[a-z][a-z0-9-]{1,63}$/.test(input.name) || !/^[a-z][a-z0-9.]{2,95}$/.test(input.eventType)) throw new ManagedPlatformError("WEBHOOK_INVALID", "Webhook metadata is invalid.");
+ this.options.secrets.resolveForRuntime(scope, input.signingSecretId, "webhook");
+ const endpoint: WebhookEndpoint = { id: `webhook_${randomUUID()}`, scopeKey: scope.scopeKey, name: input.name, signingSecretId: input.signingSecretId, eventType: input.eventType, status: "active", createdAt: this.options.now().toISOString() };
+ this.endpoints.set(endpoint.id, endpoint);
+ return clone(endpoint);
+ }
+
+ receive(scope: ManagedScope, endpointId: string, input: { body: string; timestamp: number; nonce: string; signature: string }) {
+ const endpoint = this.endpoints.get(endpointId);
+ if (!endpoint || endpoint.scopeKey !== scope.scopeKey || endpoint.status !== "active") throw new ManagedPlatformError("WEBHOOK_NOT_FOUND", "Webhook endpoint does not exist.");
+ if (Buffer.byteLength(input.body) > 512_000 || !/^[A-Za-z0-9_-]{6,128}$/.test(input.nonce) || !/^[a-f0-9]{64}$/i.test(input.signature)) throw new ManagedPlatformError("WEBHOOK_REQUEST_INVALID", "Webhook request is invalid.");
+ const nowSeconds = Math.floor(this.options.now().getTime() / 1000);
+ for (const [key, expiresAt] of this.replayKeys) if (expiresAt <= nowSeconds) this.replayKeys.delete(key);
+ if (!Number.isSafeInteger(input.timestamp) || Math.abs(nowSeconds - input.timestamp) > 300) throw new ManagedPlatformError("WEBHOOK_TIMESTAMP_INVALID", "Webhook timestamp is outside the replay window.");
+ const replayKey = `${scope.scopeKey}:${endpointId}:${input.timestamp}:${input.nonce}`;
+ if (this.replayKeys.has(replayKey)) throw new ManagedPlatformError("WEBHOOK_REPLAY", "Webhook replay was rejected.");
+ const secret = this.options.secrets.resolveForRuntime(scope, endpoint.signingSecretId, "webhook");
+ const expected = createHmac("sha256", secret).update(`${input.timestamp}.${input.nonce}.${input.body}`).digest("hex");
+ if (!safeEqual(input.signature.toLowerCase(), expected)) throw new ManagedPlatformError("WEBHOOK_SIGNATURE_INVALID", "Webhook signature is invalid.");
+ let payload: unknown;
+ try { payload = JSON.parse(input.body); } catch { throw new ManagedPlatformError("WEBHOOK_JSON_INVALID", "Webhook payload must be valid JSON."); }
+ this.replayKeys.set(replayKey, input.timestamp + 300);
+ const event: WebhookEvent = { id: `webhook_event_${randomUUID()}`, endpointId, scopeKey: scope.scopeKey, eventType: endpoint.eventType, payload: sanitizeLogValue(payload), receivedAt: this.options.now().toISOString() };
+ this.events.set(event.id, event);
+ this.options.logs.append(scope, { category: "webhook", severity: "info", action: "webhook.accepted", actorId: "webhook-provider", requestId: event.id, metadata: { endpointId, eventType: event.eventType, payload } });
+ return { status: "accepted" as const, eventId: event.id, providerEvidence: { signatureVerified: true, replayProtected: true } };
+ }
+
+ replay(scope: ManagedScope, eventId: string, principal: ManagedPrincipal) {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.webhooks.replay");
+ const event = this.events.get(eventId);
+ if (!event || event.scopeKey !== scope.scopeKey) throw new ManagedPlatformError("WEBHOOK_EVENT_NOT_FOUND", "Webhook event does not exist.");
+ return clone({ ...event, replayedBy: principal.actorId, replayedAt: this.options.now().toISOString() });
+ }
+
+ exportConfiguration(scope: ManagedScope) {
+ return clone([...this.endpoints.values()].filter((entry) => entry.scopeKey === scope.scopeKey).map(({ signingSecretId, ...entry }) => ({ ...entry, signingSecretReference: signingSecretId })));
+ }
+}
+
+interface ManagedJob {
+ id: string;
+ scopeKey: string;
+ type: string;
+ payload: unknown;
+ idempotencyKey: string;
+ status: "queued" | "running" | "succeeded" | "failed" | "dead-letter" | "cancelled";
+ attempts: number;
+ maxAttempts: number;
+ runAt: string;
+ result?: unknown;
+ createdAt: string;
+ updatedAt: string;
+}
+
+export class ManagedJobService {
+ private readonly jobs = new Map();
+ private readonly idempotency = new Map();
+ private readonly options: { now: () => Date; logs: ManagedLogStore; limits: ManagedPlatformLimits };
+ constructor(options: { now: () => Date; logs: ManagedLogStore; limits: ManagedPlatformLimits }) { this.options = options; }
+
+ enqueue(scope: ManagedScope, input: { type: string; payload: unknown; idempotencyKey: string; maxAttempts?: number; delayMs?: number }, principal: ManagedPrincipal): ManagedJob {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.jobs.manage");
+ if (!/^[a-z][a-z0-9-]{1,63}$/.test(input.type) || !/^[A-Za-z0-9_-]{8,128}$/.test(input.idempotencyKey)) throw new ManagedPlatformError("JOB_INVALID", "Job metadata is invalid.");
+ const key = `${scope.scopeKey}:${input.type}:${input.idempotencyKey}`;
+ const existingId = this.idempotency.get(key);
+ if (existingId) {
+ const existing = this.jobs.get(existingId)!;
+ if (stableJson(existing.payload) !== stableJson(input.payload)) throw new ManagedPlatformError("JOB_IDEMPOTENCY_CONFLICT", "Job idempotency key was reused with different payload.");
+ return clone(existing);
+ }
+ if ([...this.jobs.values()].filter((job) => job.scopeKey === scope.scopeKey && ["queued", "running"].includes(job.status)).length >= this.options.limits.maxJobsPerEnvironment) throw new ManagedPlatformError("JOB_QUOTA_EXCEEDED", "Job queue quota exceeded.");
+ const maxAttempts = input.maxAttempts ?? 3;
+ if (!Number.isInteger(maxAttempts) || maxAttempts < 1 || maxAttempts > 10) throw new ManagedPlatformError("JOB_RETRY_INVALID", "Job retry policy is invalid.");
+ const timestamp = this.options.now().toISOString();
+ const job: ManagedJob = { id: `job_${randomUUID()}`, scopeKey: scope.scopeKey, type: input.type, payload: clone(input.payload), idempotencyKey: input.idempotencyKey, status: "queued", attempts: 0, maxAttempts, runAt: new Date(this.options.now().getTime() + (input.delayMs ?? 0)).toISOString(), createdAt: timestamp, updatedAt: timestamp };
+ this.jobs.set(job.id, job);
+ this.idempotency.set(key, job.id);
+ return clone(job);
+ }
+
+ async runNext(scope: ManagedScope, handlers: Record Promise | unknown>): Promise {
+ const job = [...this.jobs.values()].filter((entry) => entry.scopeKey === scope.scopeKey && entry.status === "queued" && Date.parse(entry.runAt) <= this.options.now().getTime()).sort((left, right) => left.runAt.localeCompare(right.runAt) || left.id.localeCompare(right.id))[0];
+ if (!job) throw new ManagedPlatformError("JOB_NOT_AVAILABLE", "No queued job is ready.");
+ const handler = handlers[job.type];
+ if (!handler) throw new ManagedPlatformError("JOB_HANDLER_MISSING", "Job handler is not configured.");
+ job.status = "running";
+ job.attempts += 1;
+ job.updatedAt = this.options.now().toISOString();
+ try {
+ job.result = sanitizeLogValue(await handler(clone(job.payload)));
+ job.status = "succeeded";
+ this.options.logs.append(scope, { category: "job", severity: "info", action: "job.succeeded", actorId: "job-runner", requestId: job.id, metadata: { jobId: job.id, type: job.type, attempt: job.attempts, result: job.result } });
+ } catch (error) {
+ job.status = job.attempts >= job.maxAttempts ? "dead-letter" : "queued";
+ job.runAt = new Date(this.options.now().getTime() + Math.min(60_000, 1_000 * 2 ** job.attempts)).toISOString();
+ this.options.logs.append(scope, { category: "job", severity: "error", action: "job.failed", actorId: "job-runner", requestId: job.id, metadata: { jobId: job.id, type: job.type, attempt: job.attempts, nextState: job.status, error: error instanceof Error ? error.message : "unknown" } });
+ }
+ job.updatedAt = this.options.now().toISOString();
+ return clone(job);
+ }
+
+ cancel(scope: ManagedScope, jobId: string, principal: ManagedPrincipal): ManagedJob {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.jobs.manage");
+ const job = this.jobs.get(jobId);
+ if (!job || job.scopeKey !== scope.scopeKey) throw new ManagedPlatformError("JOB_NOT_FOUND", "Job does not exist.");
+ if (job.status !== "queued") throw new ManagedPlatformError("JOB_NOT_CANCELLABLE", "Only queued jobs may be cancelled.");
+ job.status = "cancelled";
+ job.updatedAt = this.options.now().toISOString();
+ return clone(job);
+ }
+
+ exportMetadata(scope: ManagedScope): ManagedJob[] {
+ return clone([...this.jobs.values()].filter((job) => job.scopeKey === scope.scopeKey));
+ }
+}
+
+function zonedParts(date: Date, timezone: string): { minute: number; hour: number; day: number; month: number; weekday: number } {
+ let formatter: Intl.DateTimeFormat;
+ try {
+ formatter = new Intl.DateTimeFormat("en-US", { timeZone: timezone, minute: "2-digit", hour: "2-digit", hourCycle: "h23", day: "2-digit", month: "2-digit", weekday: "short" });
+ } catch { throw new ManagedPlatformError("CRON_TIMEZONE_INVALID", "Cron timezone is invalid."); }
+ const parts = Object.fromEntries(formatter.formatToParts(date).map((part) => [part.type, part.value]));
+ const weekdays: Record = { Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 };
+ return { minute: Number(parts.minute), hour: Number(parts.hour), day: Number(parts.day), month: Number(parts.month), weekday: weekdays[parts.weekday] };
+}
+
+function parseCronPart(value: string, min: number, max: number): Set {
+ const result = new Set();
+ for (const segment of value.split(",")) {
+ if (segment === "*") { for (let index = min; index <= max; index++) result.add(index); continue; }
+ const step = segment.match(/^\*\/(\d{1,2})$/);
+ if (step) {
+ const increment = Number(step[1]);
+ if (increment < 1 || increment > max - min + 1) throw new ManagedPlatformError("CRON_INVALID", "Cron step is invalid.");
+ for (let index = min; index <= max; index += increment) result.add(index);
+ continue;
+ }
+ const range = segment.match(/^(\d{1,2})-(\d{1,2})$/);
+ if (range) {
+ const start = Number(range[1]); const end = Number(range[2]);
+ if (start < min || end > max || start > end) throw new ManagedPlatformError("CRON_INVALID", "Cron range is invalid.");
+ for (let index = start; index <= end; index++) result.add(index);
+ continue;
+ }
+ const number = Number(segment);
+ if (!Number.isInteger(number) || number < min || number > max) throw new ManagedPlatformError("CRON_INVALID", "Cron value is invalid.");
+ result.add(number);
+ }
+ return result;
+}
+
+export function nextCronOccurrence(expression: string, timezone: string, from: Date): string {
+ const parts = expression.trim().split(/\s+/);
+ if (parts.length !== 5 || expression.length > 100) throw new ManagedPlatformError("CRON_INVALID", "Cron expression must contain five bounded fields.");
+ const allowed = [parseCronPart(parts[0], 0, 59), parseCronPart(parts[1], 0, 23), parseCronPart(parts[2], 1, 31), parseCronPart(parts[3], 1, 12), parseCronPart(parts[4], 0, 6)];
+ const dayOfMonthWildcard = parts[2] === "*";
+ const dayOfWeekWildcard = parts[4] === "*";
+ const cursor = new Date(Math.floor(from.getTime() / 60_000) * 60_000 + 60_000);
+ for (let iteration = 0; iteration < 527_040; iteration++, cursor.setUTCMinutes(cursor.getUTCMinutes() + 1)) {
+ const current = zonedParts(cursor, timezone);
+ const dayOfMonthMatches = allowed[2].has(current.day);
+ const dayOfWeekMatches = allowed[4].has(current.weekday);
+ const dayMatches = dayOfMonthWildcard
+ ? dayOfWeekMatches
+ : dayOfWeekWildcard
+ ? dayOfMonthMatches
+ : dayOfMonthMatches || dayOfWeekMatches;
+ if (allowed[0].has(current.minute) && allowed[1].has(current.hour) && dayMatches && allowed[3].has(current.month)) return cursor.toISOString();
+ }
+ throw new ManagedPlatformError("CRON_NEXT_RUN_UNAVAILABLE", "Cron expression has no run within one year.");
+}
+
+interface ManagedSchedule {
+ id: string;
+ scopeKey: string;
+ name: string;
+ expression: string;
+ timezone: string;
+ jobType: string;
+ enabled: boolean;
+ overlapPolicy: "skip" | "queue";
+ nextRunAt: string | null;
+ lastRunAt: string | null;
+ createdAt: string;
+}
+
+export class ManagedCronService {
+ private readonly schedules = new Map();
+ private readonly options: { now: () => Date; logs: ManagedLogStore };
+ constructor(options: { now: () => Date; logs: ManagedLogStore }) { this.options = options; }
+ create(scope: ManagedScope, input: Omit, principal: ManagedPrincipal, options: { approvalReceipt?: string } = {}): ManagedSchedule {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.cron.manage");
+ if (scope.environment === "production" && input.enabled) requireApproval(options.approvalReceipt);
+ if (!/^[a-z][a-z0-9-]{1,63}$/.test(input.name) || !/^[a-z][a-z0-9-]{1,63}$/.test(input.jobType)) throw new ManagedPlatformError("CRON_INVALID", "Cron metadata is invalid.");
+ if (!(["skip", "queue"] as const).includes(input.overlapPolicy)) throw new ManagedPlatformError("CRON_INVALID", "Cron overlap policy is invalid.");
+ const validatedNextRunAt = nextCronOccurrence(input.expression, input.timezone, this.options.now());
+ const nextRunAt = input.enabled ? validatedNextRunAt : null;
+ const schedule: ManagedSchedule = { ...clone(input), id: `cron_${randomUUID()}`, scopeKey: scope.scopeKey, nextRunAt, lastRunAt: null, createdAt: this.options.now().toISOString() };
+ this.schedules.set(schedule.id, schedule);
+ return clone(schedule);
+ }
+ due(scope: ManagedScope): ManagedSchedule[] {
+ return clone([...this.schedules.values()].filter((schedule) => schedule.scopeKey === scope.scopeKey && schedule.enabled && schedule.nextRunAt && Date.parse(schedule.nextRunAt) <= this.options.now().getTime()));
+ }
+}
+
+interface RealtimeEvent {
+ sequence: number;
+ collection: string;
+ operation: "created" | "updated" | "deleted";
+ data: unknown;
+ createdAt: string;
+}
+interface RealtimeSubscription {
+ id: string;
+ scopeKey: string;
+ collection: string;
+ cursor: number;
+ createdAt: string;
+}
+
+export class ManagedRealtimeService {
+ readonly mode = "in-memory-test" as const;
+ private readonly events = new Map();
+ private readonly subscriptions = new Map();
+ private readonly options: { now: () => Date; limits: ManagedPlatformLimits; logs: ManagedLogStore };
+ constructor(options: { now: () => Date; limits: ManagedPlatformLimits; logs: ManagedLogStore }) { this.options = options; }
+
+ subscribe(scope: ManagedScope, input: { collection: string }, principal: ManagedPrincipal) {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.realtime.read");
+ if (!/^[a-z][a-zA-Z0-9_]{1,63}$/.test(input.collection)) throw new ManagedPlatformError("REALTIME_COLLECTION_INVALID", "Realtime collection is invalid.");
+ if ([...this.subscriptions.values()].filter((entry) => entry.scopeKey === scope.scopeKey).length >= this.options.limits.maxRealtimeSubscriptions) throw new ManagedPlatformError("REALTIME_CONNECTION_LIMIT", "Realtime connection limit exceeded.");
+ const id = `subscription_${randomUUID()}`;
+ const subscription: RealtimeSubscription = { id, scopeKey: scope.scopeKey, collection: input.collection, cursor: 0, createdAt: this.options.now().toISOString() };
+ this.subscriptions.set(id, subscription);
+ return clone({ ...subscription, mode: this.mode });
+ }
+
+ publish(scope: ManagedScope, collection: string, operation: RealtimeEvent["operation"], data: unknown, principal: ManagedPrincipal): RealtimeEvent {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.realtime.publish");
+ if (!/^[a-z][a-zA-Z0-9_]{1,63}$/.test(collection) || !(["created", "updated", "deleted"] as const).includes(operation)) throw new ManagedPlatformError("REALTIME_EVENT_INVALID", "Realtime event metadata is invalid.");
+ const current = this.events.get(scope.scopeKey) ?? [];
+ const event: RealtimeEvent = { sequence: (current.at(-1)?.sequence ?? 0) + 1, collection, operation, data: sanitizeLogValue(data), createdAt: this.options.now().toISOString() };
+ current.push(event);
+ if (current.length > this.options.limits.maxRealtimeEvents) current.splice(0, current.length - this.options.limits.maxRealtimeEvents);
+ this.events.set(scope.scopeKey, current);
+ return clone(event);
+ }
+
+ poll(scope: ManagedScope, input: { id: string }, principal: ManagedPrincipal) {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.realtime.read");
+ const subscription = this.subscriptions.get(input.id);
+ if (!subscription || subscription.scopeKey !== scope.scopeKey) throw new ManagedPlatformError("REALTIME_SUBSCRIPTION_NOT_FOUND", "Realtime subscription does not exist in this scope.");
+ const events = (this.events.get(scope.scopeKey) ?? []).filter((event) => event.collection === subscription.collection && event.sequence > subscription.cursor).slice(0, 100);
+ if (events.length) subscription.cursor = events.at(-1)!.sequence;
+ return { events: clone(events), nextCursor: subscription.cursor, mode: this.mode };
+ }
+}
diff --git a/lib/managed-platform/secrets.ts b/lib/managed-platform/secrets.ts
new file mode 100644
index 0000000..9568997
--- /dev/null
+++ b/lib/managed-platform/secrets.ts
@@ -0,0 +1,117 @@
+import { createCipheriv, createDecipheriv, randomBytes, randomUUID } from "node:crypto";
+import type { ManagedPrincipal, ManagedScope } from "./contracts.ts";
+import { ManagedPlatformError, assertScope, clone, requirePermission } from "./security.ts";
+import type { ManagedLogStore } from "./logs.ts";
+
+type SecretPurpose = "function" | "webhook" | "job" | "cron";
+interface SecretVersion {
+ version: number;
+ iv: string;
+ tag: string;
+ ciphertext: string;
+ createdAt: string;
+ revokedAt: string | null;
+}
+interface StoredSecret {
+ id: string;
+ scopeKey: string;
+ name: string;
+ allowedPurposes: SecretPurpose[];
+ status: "active" | "revoked" | "rotation-required";
+ versions: SecretVersion[];
+ createdAt: string;
+}
+
+function metadata(secret: StoredSecret) {
+ return {
+ id: secret.id,
+ name: secret.name,
+ masked: "••••••••",
+ allowedPurposes: [...secret.allowedPurposes],
+ status: secret.status,
+ currentVersion: secret.versions.length,
+ versions: secret.versions.map(({ version, createdAt, revokedAt }) => ({ version, createdAt, revokedAt })),
+ createdAt: secret.createdAt,
+ };
+}
+
+export class ManagedSecretVault {
+ readonly mode = "in-memory-test" as const;
+ private readonly secrets = new Map();
+ private readonly options: { key: Uint8Array; now: () => Date; logs: ManagedLogStore };
+ constructor(options: { key: Uint8Array; now: () => Date; logs: ManagedLogStore }) {
+ this.options = options;
+ if (options.key.byteLength !== 32) throw new ManagedPlatformError("VAULT_KEY_INVALID", "Secret vault encryption key must be 32 bytes.");
+ }
+
+ private encrypt(value: string, scopeKey: string, version: number): SecretVersion {
+ if (!value || Buffer.byteLength(value) > 16_384 || /[\0]/.test(value)) throw new ManagedPlatformError("SECRET_INVALID", "Secret value is invalid.");
+ const iv = randomBytes(12);
+ const cipher = createCipheriv("aes-256-gcm", this.options.key, iv);
+ cipher.setAAD(Buffer.from(`${scopeKey}:${version}`));
+ const ciphertext = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
+ return { version, iv: iv.toString("base64url"), tag: cipher.getAuthTag().toString("base64url"), ciphertext: ciphertext.toString("base64url"), createdAt: this.options.now().toISOString(), revokedAt: null };
+ }
+
+ create(scope: ManagedScope, input: { name: string; value: string; allowedPurposes: SecretPurpose[] }, principal: ManagedPrincipal) {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.secrets.manage");
+ if (!/^[A-Z][A-Z0-9_]{1,95}$/.test(input.name) || !input.allowedPurposes.length) throw new ManagedPlatformError("SECRET_METADATA_INVALID", "Secret metadata is invalid.");
+ if ([...this.secrets.values()].some((secret) => secret.scopeKey === scope.scopeKey && secret.name === input.name && secret.status !== "revoked")) throw new ManagedPlatformError("SECRET_EXISTS", "Secret name already exists.");
+ const id = `secret_${randomUUID()}`;
+ const stored: StoredSecret = { id, scopeKey: scope.scopeKey, name: input.name, allowedPurposes: [...new Set(input.allowedPurposes)], status: "active", versions: [this.encrypt(input.value, scope.scopeKey, 1)], createdAt: this.options.now().toISOString() };
+ this.secrets.set(id, stored);
+ this.options.logs.append(scope, { category: "secret", severity: "info", action: "secret.create", actorId: principal.actorId, requestId: `req_${randomUUID()}`, metadata: { secretId: id, name: input.name } });
+ return metadata(stored);
+ }
+
+ list(scope: ManagedScope, principal: ManagedPrincipal) {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.secrets.manage");
+ return clone([...this.secrets.values()].filter((secret) => secret.scopeKey === scope.scopeKey).map(metadata));
+ }
+
+ resolveForRuntime(scope: ManagedScope, secretId: string, purpose: string): string {
+ const secret = this.secrets.get(secretId);
+ if (!secret || secret.scopeKey !== scope.scopeKey || secret.status !== "active") throw new ManagedPlatformError("SECRET_UNAVAILABLE", "Secret reference is unavailable.");
+ if (!secret.allowedPurposes.includes(purpose as SecretPurpose)) throw new ManagedPlatformError("SECRET_PURPOSE_DENIED", "Secret purpose is not authorized.");
+ const current = secret.versions.at(-1);
+ if (!current) throw new ManagedPlatformError("SECRET_UNAVAILABLE", "Secret value requires rotation before runtime use.");
+ const decipher = createDecipheriv("aes-256-gcm", this.options.key, Buffer.from(current.iv, "base64url"));
+ decipher.setAAD(Buffer.from(`${scope.scopeKey}:${current.version}`));
+ decipher.setAuthTag(Buffer.from(current.tag, "base64url"));
+ return Buffer.concat([decipher.update(Buffer.from(current.ciphertext, "base64url")), decipher.final()]).toString("utf8");
+ }
+
+ rotate(scope: ManagedScope, secretId: string, value: string, principal: ManagedPrincipal) {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.secrets.manage");
+ const secret = this.secrets.get(secretId);
+ if (!secret || secret.scopeKey !== scope.scopeKey || secret.status === "revoked") throw new ManagedPlatformError("SECRET_UNAVAILABLE", "Secret reference is unavailable.");
+ const current = secret.versions.at(-1);
+ const nextVersion = (current?.version ?? 0) + 1;
+ const replacement = this.encrypt(value, scope.scopeKey, nextVersion);
+ if (current) current.revokedAt = this.options.now().toISOString();
+ secret.versions.push(replacement);
+ secret.status = "active";
+ return metadata(secret);
+ }
+
+ revoke(scope: ManagedScope, secretId: string, principal: ManagedPrincipal): void {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.secrets.manage");
+ const secret = this.secrets.get(secretId);
+ if (!secret || secret.scopeKey !== scope.scopeKey) throw new ManagedPlatformError("SECRET_UNAVAILABLE", "Secret reference is unavailable.");
+ secret.status = "revoked";
+ const current = secret.versions.at(-1);
+ if (current) current.revokedAt = this.options.now().toISOString();
+ }
+
+ exportReferences(scope: ManagedScope) {
+ return clone([...this.secrets.values()].filter((secret) => secret.scopeKey === scope.scopeKey).map((secret) => ({ id: secret.id, name: secret.name, allowedPurposes: [...secret.allowedPurposes], createdAt: secret.createdAt })));
+ }
+
+ importReferences(scope: ManagedScope, references: Array<{ id: string; name: string; allowedPurposes: SecretPurpose[]; createdAt: string }>): void {
+ for (const reference of references) this.secrets.set(reference.id, { ...clone(reference), scopeKey: scope.scopeKey, status: "rotation-required", versions: [] });
+ }
+}
diff --git a/lib/managed-platform/security.ts b/lib/managed-platform/security.ts
new file mode 100644
index 0000000..7ea1baf
--- /dev/null
+++ b/lib/managed-platform/security.ts
@@ -0,0 +1,143 @@
+import { createHash, createHmac, timingSafeEqual } from "node:crypto";
+import type { ManagedPrincipal, ManagedScope } from "./contracts.ts";
+
+const ID_PATTERN = /^[a-z0-9][a-z0-9_-]{1,95}$/i;
+const APPROVAL_PATTERN = /^approval_[a-z0-9_-]{8,160}$/i;
+const SENSITIVE_KEY = /(?:authorization|cookie|token|secret|password|private.?key|api.?key|signature|csrf)/i;
+const SECRET_VALUE = /(?:Bearer\s+[A-Za-z0-9._~+\/-]{6,}|sk-[A-Za-z0-9_-]{12,}|gh[pousr]_[A-Za-z0-9]{12,}|\d{6,12}:[A-Za-z0-9_-]{20,}|(?:api[_-]?key|secret|token)\s*[:=]\s*[^\s,}]{6,})/gi;
+
+export class ManagedPlatformError extends Error {
+ readonly code: string;
+ constructor(code: string, message: string) {
+ super(message);
+ this.name = "ManagedPlatformError";
+ this.code = code;
+ }
+}
+
+function identifier(value: unknown, label: string): string {
+ const normalized = typeof value === "string" ? value.trim() : "";
+ if (!ID_PATTERN.test(normalized)) throw new ManagedPlatformError("INVALID_IDENTIFIER", `${label} is invalid.`);
+ return normalized;
+}
+
+export function managedScope(input: Omit & { scopeKey?: string }): ManagedScope {
+ const organizationId = identifier(input.organizationId, "Organization id");
+ const workspaceId = identifier(input.workspaceId, "Workspace id");
+ const projectId = identifier(input.projectId, "Project id");
+ if (!["development", "preview", "production"].includes(input.environment)) {
+ throw new ManagedPlatformError("INVALID_ENVIRONMENT", "Managed environment is invalid.");
+ }
+ const environment = input.environment;
+ return Object.freeze({
+ organizationId,
+ workspaceId,
+ projectId,
+ environment,
+ scopeKey: `${organizationId}/${workspaceId}/${projectId}/${environment}`,
+ });
+}
+
+export function managedPrincipal(input: Omit & { scope: ManagedScope }): ManagedPrincipal {
+ const scope = managedScope(input.scope);
+ const roles = [...new Set(input.roles.map((role) => identifier(role, "Role")))].sort();
+ const permissions = [...new Set(input.permissions.map((permission) => {
+ const value = permission.trim();
+ if (!/^[a-z][a-z0-9.-]{2,127}$/i.test(value)) throw new ManagedPlatformError("INVALID_PERMISSION", "Permission is invalid.");
+ return value;
+ }))].sort();
+ return Object.freeze({
+ actorId: identifier(input.actorId, "Actor id"),
+ actorType: input.actorType,
+ scope,
+ roles: Object.freeze(roles) as unknown as string[],
+ permissions: Object.freeze(permissions) as unknown as string[],
+ });
+}
+
+export function assertScope(scope: ManagedScope, principal: ManagedPrincipal): void {
+ if (scope.scopeKey !== principal.scope.scopeKey) {
+ throw new ManagedPlatformError("SCOPE_MISMATCH", "Principal scope or environment does not authorize this operation.");
+ }
+}
+
+export function requirePermission(principal: ManagedPrincipal, permission: string): void {
+ if (!principal.permissions.includes(permission) && !principal.roles.includes("owner")) {
+ throw new ManagedPlatformError("PERMISSION_DENIED", `Permission ${permission} is required.`);
+ }
+}
+
+export function requireApproval(receipt: string | undefined): void {
+ if (!receipt || !APPROVAL_PATTERN.test(receipt)) {
+ throw new ManagedPlatformError("APPROVAL_REQUIRED", "A valid explicit approval receipt is required.");
+ }
+}
+
+function canonicalize(value: unknown): unknown {
+ if (Array.isArray(value)) return value.map(canonicalize);
+ if (value && typeof value === "object") {
+ return Object.fromEntries(Object.entries(value as Record)
+ .sort(([left], [right]) => left.localeCompare(right))
+ .map(([key, nested]) => [key, canonicalize(nested)]));
+ }
+ return value;
+}
+
+export function stableJson(value: unknown): string {
+ return JSON.stringify(canonicalize(value));
+}
+
+export function sha256(value: string | Uint8Array): string {
+ return createHash("sha256").update(value).digest("hex");
+}
+
+export function safeEqual(left: string, right: string): boolean {
+ const a = Buffer.from(left);
+ const b = Buffer.from(right);
+ return a.byteLength === b.byteLength && timingSafeEqual(a, b);
+}
+
+export function signPayload(payload: Record, key: Uint8Array): string {
+ const encoded = Buffer.from(stableJson(payload)).toString("base64url");
+ const signature = createHmac("sha256", key).update(encoded).digest("base64url");
+ return `${encoded}.${signature}`;
+}
+
+export function verifySignedPayload(token: string, key: Uint8Array): Record {
+ if (token.length > 4096 || !/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(token)) {
+ throw new ManagedPlatformError("CAPABILITY_INVALID", "Signed capability is invalid.");
+ }
+ const [encoded, signature] = token.split(".");
+ const expected = createHmac("sha256", key).update(encoded).digest("base64url");
+ if (!safeEqual(signature, expected)) throw new ManagedPlatformError("CAPABILITY_INVALID", "Signed capability is invalid.");
+ try {
+ const parsed = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("invalid");
+ return parsed as Record;
+ } catch {
+ throw new ManagedPlatformError("CAPABILITY_INVALID", "Signed capability payload is invalid.");
+ }
+}
+
+function sanitizeString(value: string): string {
+ const bounded = value.length > 2_000 ? `${value.slice(0, 2_000)}…[TRUNCATED]` : value;
+ return bounded.replace(SECRET_VALUE, "[REDACTED]");
+}
+
+export function sanitizeLogValue(value: unknown, depth = 0): unknown {
+ if (depth > 6) return "[TRUNCATED]";
+ if (typeof value === "string") return sanitizeString(value);
+ if (typeof value === "number" || typeof value === "boolean" || value === null) return value;
+ if (Array.isArray(value)) return value.slice(0, 50).map((entry) => sanitizeLogValue(entry, depth + 1));
+ if (value && typeof value === "object") {
+ return Object.fromEntries(Object.entries(value as Record).slice(0, 80).map(([key, nested]) => [
+ key,
+ SENSITIVE_KEY.test(key) ? "[REDACTED]" : sanitizeLogValue(nested, depth + 1),
+ ]));
+ }
+ return String(value);
+}
+
+export function clone(value: T): T {
+ return structuredClone(value);
+}
diff --git a/lib/managed-platform/storage.ts b/lib/managed-platform/storage.ts
new file mode 100644
index 0000000..0596383
--- /dev/null
+++ b/lib/managed-platform/storage.ts
@@ -0,0 +1,82 @@
+import { randomUUID } from "node:crypto";
+import type { ManagedPlatformLimits, ManagedPrincipal, ManagedScope } from "./contracts.ts";
+import { ManagedPlatformError, assertScope, clone, requirePermission, sha256, signPayload, verifySignedPayload } from "./security.ts";
+import type { ManagedLogStore } from "./logs.ts";
+
+interface StoredObject {
+ id: string;
+ scopeKey: string;
+ key: string;
+ contentType: string;
+ visibility: "private" | "public";
+ size: number;
+ checksum: string;
+ bytes: Uint8Array;
+ status: "active" | "deleted";
+ createdAt: string;
+}
+
+const CONTENT_TYPES = new Set(["application/json", "application/pdf", "text/plain", "image/png", "image/jpeg", "image/webp"]);
+
+function publicMetadata(object: StoredObject) {
+ return clone({
+ id: object.id,
+ key: object.key,
+ contentType: object.contentType,
+ visibility: object.visibility,
+ size: object.size,
+ checksum: object.checksum,
+ status: object.status,
+ createdAt: object.createdAt,
+ });
+}
+
+export class ManagedObjectStorage {
+ private readonly objects = new Map();
+ private readonly options: { signingKey: Uint8Array; now: () => Date; limits: ManagedPlatformLimits; logs: ManagedLogStore; scan?: (bytes: Uint8Array) => "clean" | "rejected" };
+ constructor(options: { signingKey: Uint8Array; now: () => Date; limits: ManagedPlatformLimits; logs: ManagedLogStore; scan?: (bytes: Uint8Array) => "clean" | "rejected" }) { this.options = options; }
+
+ put(scope: ManagedScope, input: { key: string; contentType: string; visibility: "private" | "public"; bytes: Uint8Array }, principal: ManagedPrincipal) {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.storage.manage");
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9._/-]{0,254}$/.test(input.key) || input.key.startsWith("/") || input.key.split("/").includes("..") || input.key.includes("//")) throw new ManagedPlatformError("OBJECT_KEY_INVALID", "Object key is invalid.");
+ if (!CONTENT_TYPES.has(input.contentType)) throw new ManagedPlatformError("OBJECT_MIME_INVALID", "Object content type is not allowed.");
+ if (!input.bytes.byteLength || input.bytes.byteLength > this.options.limits.maxObjectBytes) throw new ManagedPlatformError("OBJECT_SIZE_INVALID", "Object size exceeds the bounded limit.");
+ const active = [...this.objects.values()].filter((object) => object.scopeKey === scope.scopeKey && object.status === "active");
+ if (active.length >= this.options.limits.maxObjectsPerEnvironment) throw new ManagedPlatformError("OBJECT_QUOTA_EXCEEDED", "Object count quota exceeded for this environment.");
+ if (active.reduce((total, object) => total + object.size, 0) + input.bytes.byteLength > this.options.limits.maxObjectBytesPerEnvironment) {
+ throw new ManagedPlatformError("OBJECT_QUOTA_EXCEEDED", "Aggregate object byte quota exceeded for this environment.");
+ }
+ if (this.options.scan?.(input.bytes) === "rejected") throw new ManagedPlatformError("OBJECT_SCAN_REJECTED", "Object was rejected by the configured scanning hook.");
+ const object: StoredObject = { id: `object_${randomUUID()}`, scopeKey: scope.scopeKey, key: input.key, contentType: input.contentType, visibility: input.visibility, size: input.bytes.byteLength, checksum: sha256(input.bytes), bytes: Uint8Array.from(input.bytes), status: "active", createdAt: this.options.now().toISOString() };
+ this.objects.set(object.id, object);
+ this.options.logs.append(scope, { category: "storage", severity: "info", action: "storage.put", actorId: principal.actorId, requestId: `req_${randomUUID()}`, metadata: { objectId: object.id, key: object.key, size: object.size, checksum: object.checksum } });
+ return publicMetadata(object);
+ }
+
+ signCapability(scope: ManagedScope, objectId: string, operation: "read" | "delete", principal: ManagedPrincipal, options: { ttlSeconds?: number } = {}): string {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.storage.manage");
+ const object = this.objects.get(objectId);
+ if (!object || object.scopeKey !== scope.scopeKey || object.status !== "active") throw new ManagedPlatformError("OBJECT_NOT_FOUND", "Stored object does not exist.");
+ const ttl = options.ttlSeconds ?? 300;
+ if (!Number.isInteger(ttl) || ttl < 10 || ttl > 3_600) throw new ManagedPlatformError("CAPABILITY_TTL_INVALID", "Object capability lifetime is invalid.");
+ return signPayload({ version: 1, scopeKey: scope.scopeKey, objectId, operation, exp: Math.floor(this.options.now().getTime() / 1000) + ttl, nonce: randomUUID() }, this.options.signingKey);
+ }
+
+ read(scope: ManagedScope, capability: string, principal: ManagedPrincipal) {
+ assertScope(scope, principal);
+ requirePermission(principal, "backend.storage.manage");
+ const payload = verifySignedPayload(capability, this.options.signingKey);
+ if (payload.scopeKey !== scope.scopeKey) throw new ManagedPlatformError("CAPABILITY_SCOPE_MISMATCH", "Object capability scope or environment does not match.");
+ if (payload.operation !== "read") throw new ManagedPlatformError("CAPABILITY_OPERATION_DENIED", "Object capability does not allow reading.");
+ if (typeof payload.exp !== "number" || payload.exp <= Math.floor(this.options.now().getTime() / 1000)) throw new ManagedPlatformError("CAPABILITY_EXPIRED", "Object capability expired.");
+ const object = this.objects.get(String(payload.objectId));
+ if (!object || object.scopeKey !== scope.scopeKey || object.status !== "active") throw new ManagedPlatformError("OBJECT_NOT_FOUND", "Stored object does not exist.");
+ return { metadata: publicMetadata(object), bytes: Buffer.from(object.bytes) };
+ }
+
+ exportMetadata(scope: ManagedScope) {
+ return clone([...this.objects.values()].filter((object) => object.scopeKey === scope.scopeKey && object.status === "active").map(publicMetadata));
+ }
+}
diff --git a/lib/platform-capabilities.ts b/lib/platform-capabilities.ts
new file mode 100644
index 0000000..8773dba
--- /dev/null
+++ b/lib/platform-capabilities.ts
@@ -0,0 +1,189 @@
+export type PlatformCapabilityState =
+ | "working"
+ | "working-local-test"
+ | "setup-required"
+ | "unavailable";
+
+export interface PlatformCapabilityReceipt {
+ id: string;
+ label: string;
+ state: PlatformCapabilityState;
+ mode: string;
+ detail: string;
+ evidence: string[];
+ requiredEnvironment: string[];
+}
+
+export interface PlatformCapabilitySnapshot {
+ generatedAt: string;
+ environment: "development" | "preview" | "production";
+ capabilities: PlatformCapabilityReceipt[];
+}
+
+type SafeEnvironment = Record;
+
+function configured(environment: SafeEnvironment, names: readonly string[]): boolean {
+ return names.every((name) => Boolean(environment[name]?.trim()));
+}
+
+function runtimeEnvironment(environment: SafeEnvironment): PlatformCapabilitySnapshot["environment"] {
+ if (environment.VERCEL_ENV === "production") return "production";
+ if (environment.VERCEL_ENV === "preview") return "preview";
+ return "development";
+}
+
+export function platformCapabilitySnapshot(
+ environment: SafeEnvironment = process.env,
+ now = new Date(),
+): PlatformCapabilitySnapshot {
+ const blob = Boolean(
+ environment.BLOB_READ_WRITE_TOKEN?.trim()
+ || configured(environment, ["BLOB_STORE_ID", "VERCEL_OIDC_TOKEN"]),
+ );
+ const sandbox = Boolean(
+ environment.VERCEL_OIDC_TOKEN?.trim()
+ || configured(environment, ["VERCEL_TOKEN", "VERCEL_TEAM_ID", "VERCEL_PROJECT_ID"]),
+ );
+ const projectData = blob && Boolean(environment.PROJECT_DATA_CAPABILITY_SECRET?.trim());
+ const localProjectData = runtimeEnvironment(environment) !== "production"
+ && environment.DROPS_STUDIO_LOCAL_PROJECT_DATA === "1";
+ const teamControlPlane = blob && Boolean(environment.DROPS_TEAM_INVITE_SECRET?.trim());
+ const deployment = Boolean(
+ environment.VERCEL_DEPLOY_TOKEN?.trim()
+ && environment.VERCEL_GENERATED_PROJECT_ID?.trim(),
+ );
+ const github = configured(environment, [
+ "GITHUB_APP_ID",
+ "GITHUB_APP_PRIVATE_KEY",
+ "GITHUB_APP_INSTALLATION_ID",
+ "GITHUB_APP_ALLOWED_REPOSITORIES",
+ ]);
+ const managedRelational = environment.DROPS_MANAGED_DATA_PROVIDER === "d1"
+ || environment.DROPS_MANAGED_DATA_PROVIDER === "postgres";
+ const realtime = Boolean(environment.DROPS_COLLABORATION_TRANSPORT_URL?.trim());
+ const oidc = configured(environment, [
+ "DROPS_ENTERPRISE_OIDC_ISSUER",
+ "DROPS_ENTERPRISE_OIDC_CLIENT_ID",
+ "DROPS_ENTERPRISE_OIDC_CLIENT_SECRET",
+ ]);
+ const audit = managedRelational && Boolean(environment.DROPS_ENTERPRISE_AUDIT_SIGNING_KEY?.trim());
+
+ const capabilities: PlatformCapabilityReceipt[] = [
+ {
+ id: "project-v2",
+ label: "Multi-file Project V2",
+ state: "working",
+ mode: "canonical-filesystem",
+ detail: "Validated files, revisions, diffs, checkpoints and runnable source export are active.",
+ evidence: ["project-v2-validator", "checkpoint-engine", "artifact-secret-scan"],
+ requiredEnvironment: [],
+ },
+ {
+ id: "sandbox",
+ label: "Vercel Sandbox",
+ state: sandbox ? "unavailable" : "setup-required",
+ mode: sandbox ? "authorization-present-health-required" : "provider-not-authorized",
+ detail: sandbox
+ ? "Vercel authorization is present, but Node 24 build, preview and cleanup stay unverified until a live Sandbox health receipt succeeds."
+ : "Sandbox operations stay disabled until Vercel OIDC or the bounded local credential trio is available.",
+ evidence: sandbox ? ["authorization-marker-only"] : [],
+ requiredEnvironment: sandbox ? [] : ["VERCEL_OIDC_TOKEN"],
+ },
+ {
+ id: "project-data",
+ label: "Built-in project data",
+ state: localProjectData ? "working-local-test" : projectData ? "unavailable" : "setup-required",
+ mode: localProjectData ? "process-memory-local-test" : projectData ? "credentials-present-backend-health-required" : "browser-local-fallback",
+ detail: localProjectData
+ ? "Scoped JSON documents are running in explicit non-production process-memory proof mode."
+ : projectData
+ ? "Blob and signing markers are present, but the transactional project-data backend has not returned a health receipt."
+ : "Generated apps remain runnable with labelled browser-local persistence; cloud writes are disabled.",
+ evidence: localProjectData ? ["explicit-local-test-flag"] : projectData ? ["storage-marker-only", "capability-signing-marker-only"] : [],
+ requiredEnvironment: projectData || localProjectData ? [] : ["PROJECT_DATA_CAPABILITY_SECRET", "BLOB_READ_WRITE_TOKEN or Vercel Blob OIDC"],
+ },
+ {
+ id: "managed-backend",
+ label: "Managed relational backend",
+ state: managedRelational ? "unavailable" : "working-local-test",
+ mode: managedRelational ? "adapter-health-check-required" : "reference-core-only",
+ detail: managedRelational
+ ? "A provider was selected, but the UI will not claim readiness without a live adapter health receipt."
+ : "Schema, migrations, CRUD, auth, storage, functions, jobs, webhooks and backups have a verified local reference core; a durable D1 or Postgres adapter is still required for production data.",
+ evidence: ["managed-platform-contract-tests"],
+ requiredEnvironment: managedRelational ? [] : ["DROPS_MANAGED_DATA_PROVIDER", "provider-specific binding"],
+ },
+ {
+ id: "organizations",
+ label: "Organizations and workspaces",
+ state: teamControlPlane ? "unavailable" : "setup-required",
+ mode: teamControlPlane ? "configuration-present-health-required" : "storage-or-signing-missing",
+ detail: teamControlPlane
+ ? "Durable storage and invite signing are configured, but team mutations are not marked working until the control-plane health check succeeds."
+ : "No sample members are shown; team mutations remain disabled until durable storage and invite signing are configured.",
+ evidence: teamControlPlane ? ["team-storage-marker-only", "invite-signing-marker-only"] : [],
+ requiredEnvironment: teamControlPlane ? [] : ["DROPS_TEAM_INVITE_SECRET", "BLOB_READ_WRITE_TOKEN or Vercel Blob OIDC"],
+ },
+ {
+ id: "collaboration",
+ label: "Realtime collaboration",
+ state: realtime ? "unavailable" : "working-local-test",
+ mode: realtime ? "transport-health-check-required" : "deterministic-reference-runtime",
+ detail: realtime
+ ? "A transport URL exists, but room authorization and health evidence are still required before activation."
+ : "Deterministic concurrent edits, presence expiry, comments and conflict-safe AI branches are verified locally; no production realtime transport is claimed.",
+ evidence: ["collaboration-convergence-tests", "branch-conflict-tests"],
+ requiredEnvironment: realtime ? [] : ["DROPS_COLLABORATION_TRANSPORT_URL"],
+ },
+ {
+ id: "enterprise-identity",
+ label: "Enterprise identity and policy",
+ state: oidc ? "unavailable" : "working-local-test",
+ mode: oidc ? "oidc-health-check-required" : "standards-shaped-reference-runtime",
+ detail: oidc
+ ? "OIDC values are configured, but sign-in stays disabled until discovery and callback verification succeeds."
+ : "OIDC state/nonce/PKCE, domain mapping, RBAC, policy precedence and scoped service tokens are covered by local reference tests; external SSO is setup-required.",
+ evidence: ["oidc-replay-tests", "rbac-isolation-tests", "policy-resolution-tests"],
+ requiredEnvironment: oidc ? [] : ["DROPS_ENTERPRISE_OIDC_ISSUER", "DROPS_ENTERPRISE_OIDC_CLIENT_ID", "DROPS_ENTERPRISE_OIDC_CLIENT_SECRET"],
+ },
+ {
+ id: "audit-backup",
+ label: "Audit, retention and recovery",
+ state: audit ? "unavailable" : "working-local-test",
+ mode: audit ? "durable-health-check-required" : "integrity-chain-reference-runtime",
+ detail: audit
+ ? "Durable settings exist, but append-only storage and restore receipts must pass before production activation."
+ : "Secret-safe audit chaining, retention, export/deletion workflows and checksummed restore-to-new-environment behavior are verified locally.",
+ evidence: ["audit-integrity-tests", "backup-checksum-tests", "restore-isolation-tests"],
+ requiredEnvironment: audit ? [] : ["DROPS_ENTERPRISE_AUDIT_SIGNING_KEY", "durable managed provider"],
+ },
+ {
+ id: "github",
+ label: "GitHub delivery",
+ state: github ? "unavailable" : "setup-required",
+ mode: github ? "github-app-health-required" : "visitor-token-or-app-required",
+ detail: github
+ ? "GitHub App markers are complete, but branch and PR actions remain unverified until an authenticated repository health receipt succeeds."
+ : "Import remains available with a request-only visitor token; server-side branch, commit and PR actions need GitHub App configuration.",
+ evidence: github ? ["github-app-configuration-markers-only"] : [],
+ requiredEnvironment: github ? [] : ["GITHUB_APP_ID", "GITHUB_APP_PRIVATE_KEY", "GITHUB_APP_INSTALLATION_ID", "GITHUB_APP_ALLOWED_REPOSITORIES"],
+ },
+ {
+ id: "deployment",
+ label: "Generated-app deployment",
+ state: deployment ? "unavailable" : "setup-required",
+ mode: deployment ? "vercel-provider-health-required" : "visitor-token-or-platform-token-required",
+ detail: deployment
+ ? "Deployment credentials are configured, but no generated-app deploy is marked ready until Vercel confirms a provider receipt."
+ : "Runnable ZIP and legacy publish remain available; Vercel preview deployment needs a request-only visitor token or platform configuration.",
+ evidence: deployment ? ["vercel-deployment-configuration-markers-only"] : [],
+ requiredEnvironment: deployment ? [] : ["VERCEL_DEPLOY_TOKEN", "VERCEL_GENERATED_PROJECT_ID"],
+ },
+ ];
+
+ return {
+ generatedAt: now.toISOString(),
+ environment: runtimeEnvironment(environment),
+ capabilities,
+ };
+}
diff --git a/lib/project-template-managed.ts b/lib/project-template-managed.ts
new file mode 100644
index 0000000..691d9aa
--- /dev/null
+++ b/lib/project-template-managed.ts
@@ -0,0 +1,343 @@
+import type {
+ ProjectEnvironmentDefinitionV2,
+ ProjectFileLanguageV2,
+ ProjectFileRoleV2,
+ ProjectIntegrationManifestV2,
+} from "./project-v2-types.ts";
+import type { GeneratedProjectSpec } from "./project-types.ts";
+
+export interface ManagedProjectTemplateFile {
+ path: string;
+ content: string;
+ language?: ProjectFileLanguageV2;
+ role?: ProjectFileRoleV2;
+}
+
+export interface ManagedProjectTemplate {
+ enabled: boolean;
+ capabilities: string[];
+ integration?: ProjectIntegrationManifestV2;
+ environment: ProjectEnvironmentDefinitionV2[];
+ files: ManagedProjectTemplateFile[];
+ readme: string;
+}
+
+function safeJson(value: unknown): string {
+ return JSON.stringify(value, null, 2).replace(/ pattern.test(corpus);
+ const enabled = spec.presetId === "custom-product"
+ || requested(/backend|database|data model|saas|webhook|collaborat|organization|workspace|auth|storage|cron|job|realtime|multi-user/);
+ if (!enabled) return [];
+ const capabilities = ["data", "schema"];
+ if (requested(/auth|login|sign[ -]?in|user|member|organization|workspace|multi-user|collaborat/)) capabilities.push("auth");
+ if (requested(/storage|upload|file|asset|image/)) capabilities.push("storage");
+ if (requested(/function|server action|api route|workflow|enrich|score|summary/)) capabilities.push("functions");
+ if (requested(/job|queue|retry|dead.?letter|background/)) capabilities.push("jobs");
+ if (requested(/cron|schedule|daily|hourly|morning/)) capabilities.push("cron");
+ if (requested(/webhook|wallet event|drops bot|dropsbot/)) capabilities.push("webhooks");
+ if (requested(/realtime|presence|cursor|collaborat|live update/)) capabilities.push("realtime", "collaboration");
+ if (requested(/organization|rbac|role|permission|oidc|sso|enterprise|audit|retention/)) capabilities.push("enterprise-policy");
+ return [...new Set(capabilities)];
+}
+
+const MANAGED_SERVER_SOURCE = `import "server-only";
+
+const DEFAULT_ORIGIN = "https://drops-studio.vercel.app";
+
+function managedOrigin(): string {
+ const candidate = process.env.DROPS_MANAGED_API_ORIGIN?.trim() || DEFAULT_ORIGIN;
+ const url = new URL(candidate);
+ if (url.protocol !== "https:") throw new Error("Managed API origin must use HTTPS.");
+ return url.origin;
+}
+
+export function managedBackendStatus() {
+ return {
+ state: process.env.DROPS_MANAGED_PROJECT_CAPABILITY?.trim() ? "configured" : "setup-required",
+ mode: process.env.DROPS_MANAGED_PROJECT_CAPABILITY?.trim() ? "server-capability" : "labelled-browser-local-fallback",
+ } as const;
+}
+
+export async function managedBackendRequest(path: string, init: RequestInit = {}) {
+ const capability = process.env.DROPS_MANAGED_PROJECT_CAPABILITY?.trim();
+ if (!capability) throw new Error("Managed project capability is not configured.");
+ if (!path.startsWith("/") || path.includes("..")) throw new Error("Managed API path is invalid.");
+ const origin = managedOrigin();
+ const target = new URL(path, origin);
+ if (target.origin !== origin) throw new Error("Managed API path escaped its configured origin.");
+ const headers = new Headers(init.headers);
+ headers.set("authorization", "Bearer " + capability);
+ return fetch(target, {
+ ...init,
+ headers,
+ cache: "no-store",
+ redirect: "error",
+ signal: init.signal ?? AbortSignal.timeout(10_000),
+ });
+}
+`;
+
+const MANAGED_STATUS_ROUTE = `import { NextResponse } from "next/server";
+
+import { managedBackendStatus } from "../../../../lib/drops-managed-server";
+
+export const dynamic = "force-dynamic";
+
+export function GET() {
+ return NextResponse.json(managedBackendStatus(), { headers: { "cache-control": "private, no-store" } });
+}
+`;
+
+const MANAGED_COLLECTION_ROUTE = `import { managedBackendRequest } from "../../../../../lib/drops-managed-server";
+
+export const dynamic = "force-dynamic";
+
+const ALLOWED_COLLECTIONS = new Set(["workflow_items", "wallet_events", "alerts", "comments"]);
+const BODY_LIMIT_BYTES = 128 * 1024;
+
+async function collection(params: Promise<{ collection: string }>) {
+ const value = (await params).collection;
+ if (!ALLOWED_COLLECTIONS.has(value)) throw new Error("Managed collection is not declared by this project.");
+ return value;
+}
+
+function sameOrigin(request: Request) {
+ if (request.headers.get("sec-fetch-site")?.toLowerCase() === "cross-site") return false;
+ const origin = request.headers.get("origin");
+ return !origin || new URL(origin).origin === new URL(request.url).origin;
+}
+
+async function forward(request: Request, params: Promise<{ collection: string }>, method: "GET" | "POST") {
+ try {
+ if (method === "POST" && !sameOrigin(request)) return Response.json({ state: "permission-denied" }, { status: 403 });
+ const name = await collection(params);
+ const body = method === "POST" ? await request.text() : undefined;
+ if (body && new TextEncoder().encode(body).byteLength > BODY_LIMIT_BYTES) return Response.json({ state: "quota-exceeded" }, { status: 413 });
+ const upstream = await managedBackendRequest("/v1/collections/" + encodeURIComponent(name), {
+ method,
+ headers: body ? { "content-type": "application/json" } : undefined,
+ body,
+ });
+ const payload = await upstream.text();
+ return new Response(payload, {
+ status: upstream.status,
+ headers: { "cache-control": "private, no-store", "content-type": upstream.headers.get("content-type") || "application/json" },
+ });
+ } catch {
+ return Response.json({ state: "setup-required", message: "Managed backend is not configured; use the labelled browser-local fallback." }, { status: 503, headers: { "cache-control": "private, no-store" } });
+ }
+}
+
+export function GET(request: Request, context: { params: Promise<{ collection: string }> }) {
+ return forward(request, context.params, "GET");
+}
+
+export function POST(request: Request, context: { params: Promise<{ collection: string }> }) {
+ return forward(request, context.params, "POST");
+}
+`;
+
+const MANAGED_COLLECTION_CLIENT = `"use client";
+
+import { useCallback, useEffect, useState } from "react";
+
+export type ManagedCollectionMode = "loading" | "managed" | "browser-local";
+
+interface WorkflowItem { id: string; title: string }
+
+function localKey(collection: string) { return "drops-managed-demo:" + collection; }
+function localItems(collection: string): WorkflowItem[] {
+ try {
+ const value = JSON.parse(localStorage.getItem(localKey(collection)) || "[]");
+ return Array.isArray(value) ? value.filter((item): item is WorkflowItem => Boolean(item && typeof item.id === "string" && typeof item.title === "string")).slice(0, 100) : [];
+ } catch { return []; }
+}
+
+function normalizeRows(payload: unknown): WorkflowItem[] {
+ const record = payload && typeof payload === "object" ? payload as Record : {};
+ const rows = Array.isArray(record.rows) ? record.rows : Array.isArray(record.documents) ? record.documents : [];
+ return rows.map((row) => {
+ const value = row && typeof row === "object" ? row as Record : {};
+ const data = value.data && typeof value.data === "object" ? value.data as Record : value;
+ return { id: String(value._id || value.id || crypto.randomUUID()), title: String(data.title || "Untitled item").slice(0, 160) };
+ }).slice(0, 100);
+}
+
+export function useManagedCollection(collection: string) {
+ const [items, setItems] = useState([]);
+ const [mode, setMode] = useState("loading");
+
+ useEffect(() => {
+ let cancelled = false;
+ void fetch("/api/backend/collections/" + encodeURIComponent(collection), { cache: "no-store", credentials: "same-origin" })
+ .then(async (response) => {
+ if (!response.ok) throw new Error("managed backend unavailable");
+ const rows = normalizeRows(await response.json());
+ if (!cancelled) { setItems(rows); setMode("managed"); }
+ })
+ .catch(() => {
+ if (!cancelled) { setItems(localItems(collection)); setMode("browser-local"); }
+ });
+ return () => { cancelled = true; };
+ }, [collection]);
+
+ const addItem = useCallback(async (title: string) => {
+ const normalized = title.trim().slice(0, 160);
+ if (!normalized) return false;
+ if (mode === "loading") throw new Error("Managed backend mode is still loading.");
+ if (mode === "managed") {
+ const response = await fetch("/api/backend/collections/" + encodeURIComponent(collection), {
+ method: "POST",
+ credentials: "same-origin",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify({ data: { title: normalized, done: false } }),
+ }).catch(() => null);
+ if (response?.ok) {
+ const payload = await response.json().catch(() => ({})) as Record;
+ const created = normalizeRows({ rows: [payload.record || payload.document || payload] })[0];
+ if (created) setItems((current) => [created, ...current].slice(0, 100));
+ return true;
+ }
+ setMode("browser-local");
+ }
+ const next = [{ id: crypto.randomUUID(), title: normalized }, ...items].slice(0, 100);
+ setItems(next);
+ localStorage.setItem(localKey(collection), JSON.stringify(next));
+ return true;
+ }, [collection, items, mode]);
+
+ return {
+ items,
+ mode,
+ addItem,
+ status: mode === "managed" ? "Managed write confirmed" : mode === "loading" ? "Checking managed backend" : "Browser-local demo · cloud setup required",
+ };
+}
+`;
+
+const MANAGED_TEST_SOURCE = `import assert from "node:assert/strict";
+import { readFile } from "node:fs/promises";
+
+const [manifest, schema, policies, server] = await Promise.all([
+ readFile(new URL("../backend/manifest.json", import.meta.url), "utf8").then(JSON.parse),
+ readFile(new URL("../backend/schema.json", import.meta.url), "utf8").then(JSON.parse),
+ readFile(new URL("../backend/policies.json", import.meta.url), "utf8").then(JSON.parse),
+ readFile(new URL("../lib/drops-managed-server.ts", import.meta.url), "utf8"),
+]);
+assert.equal(manifest.productionProvider, "setup-required-until-health-receipt");
+assert.ok(Object.keys(schema.collections).length >= 3);
+assert.ok(policies.approvals.includes("telegram.publish"));
+ assert.match(server, /server-only/);
+ assert.match(server, /target\.origin !== origin/);
+ assert.match(server, /redirect: "error"/);
+assert.doesNotMatch(JSON.stringify({ manifest, schema, policies }), /sk-|ghp_|xox[baprs]-|BEGIN PRIVATE KEY/);
+console.log("Managed backend manifest passed");
+`;
+
+export function projectManagedTemplate(spec: GeneratedProjectSpec): ManagedProjectTemplate {
+ const capabilities = requestedCapabilities(spec);
+ if (!capabilities.length) return { enabled: false, capabilities, environment: [], files: [], readme: "" };
+ const manifest = {
+ schemaVersion: 1,
+ mode: "managed-with-labelled-local-fallback",
+ environments: ["development", "preview", "production"],
+ capabilities,
+ serverProxy: "/api/backend/status",
+ productionProvider: "setup-required-until-health-receipt",
+ boundaries: {
+ secrets: "server-environment-only",
+ externalMutations: "explicit-approval-required",
+ trading: "denied",
+ localPersistence: "demo-only-and-labelled",
+ },
+ };
+ const schema = {
+ schemaVersion: 1,
+ collections: {
+ wallet_events: {
+ rowPolicy: "project",
+ fields: {
+ wallet: { type: "string", required: true },
+ chain: { type: "string", required: true },
+ eventType: { type: "string", required: true },
+ occurredAt: { type: "datetime", required: true },
+ enrichment: { type: "json", required: false },
+ },
+ indexes: [{ name: "wallet_events_time", fields: ["occurredAt"] }],
+ },
+ alerts: {
+ rowPolicy: "owner",
+ fields: {
+ status: { type: "enum", required: true, enumValues: ["draft", "approved", "delivered", "failed"] },
+ score: { type: "float", required: true },
+ evidence: { type: "json", required: true },
+ approvedAt: { type: "datetime", required: false },
+ },
+ indexes: [{ name: "alerts_status", fields: ["status"] }],
+ },
+ comments: {
+ rowPolicy: "roles",
+ allowedRoles: ["owner", "admin", "developer", "designer", "analyst", "viewer"],
+ fields: {
+ targetPath: { type: "string", required: true },
+ body: { type: "text", required: true },
+ resolved: { type: "boolean", required: true, default: false },
+ },
+ indexes: [{ name: "comments_target", fields: ["targetPath"] }],
+ },
+ workflow_items: {
+ rowPolicy: "roles",
+ allowedRoles: ["owner", "admin", "developer", "designer", "analyst", "viewer"],
+ fields: {
+ title: { type: "string", required: true },
+ done: { type: "boolean", required: true, default: false },
+ },
+ indexes: [{ name: "workflow_items_title", fields: ["title"] }],
+ },
+ },
+ };
+ const policies = {
+ schemaVersion: 1,
+ approvals: ["telegram.publish", "webhook.register", "deployment.create", "github.push", "external-database.write"],
+ denied: ["wallet.private-key.read", "wallet.trade.execute", "production-environment.inherit"],
+ retention: { demoDays: 7, production: "organization-policy" },
+ };
+ return {
+ enabled: true,
+ capabilities,
+ integration: {
+ id: "managed-backend",
+ kind: "custom",
+ status: "setup-required",
+ capabilities,
+ proxyPath: "/api/backend/status",
+ providerEvidenceRequired: true,
+ },
+ environment: [
+ { name: "DROPS_MANAGED_API_ORIGIN", description: "Approved HTTPS origin for the Drops Studio managed data plane.", required: false, secret: false, scope: "runtime" },
+ { name: "DROPS_MANAGED_PROJECT_CAPABILITY", description: "Server-only project capability issued after explicit backend setup.", required: false, secret: true, scope: "runtime" },
+ ],
+ files: [
+ { path: "backend/manifest.json", content: safeJson(manifest), language: "json", role: "manifest" },
+ { path: "backend/schema.json", content: safeJson(schema), language: "json", role: "config" },
+ { path: "backend/policies.json", content: safeJson(policies), language: "json", role: "config" },
+ { path: "lib/drops-managed-server.ts", content: MANAGED_SERVER_SOURCE, language: "typescript", role: "integration" },
+ { path: "lib/use-managed-collection.ts", content: MANAGED_COLLECTION_CLIENT, language: "typescript", role: "integration" },
+ { path: "app/api/backend/status/route.ts", content: MANAGED_STATUS_ROUTE, language: "typescript", role: "integration" },
+ { path: "app/api/backend/collections/[collection]/route.ts", content: MANAGED_COLLECTION_ROUTE, language: "typescript", role: "integration" },
+ { path: "tests/managed-backend-manifest.test.mjs", content: MANAGED_TEST_SOURCE, language: "javascript", role: "test" },
+ ],
+ readme: " The `backend/` directory declares the managed data model, policies and production boundaries. `/api/backend/status` reports only real server configuration; without `DROPS_MANAGED_PROJECT_CAPABILITY` the app stays runnable with its labelled browser-local fallback.",
+ };
+}
diff --git a/lib/project-template-materializer.ts b/lib/project-template-materializer.ts
index 44f1c9e..8f716b4 100644
--- a/lib/project-template-materializer.ts
+++ b/lib/project-template-materializer.ts
@@ -23,6 +23,7 @@ import {
PROJECT_TEMPLATE_GLOBAL_CSS,
projectTemplateComponentSource,
} from "./project-template-ui.ts";
+import { projectManagedTemplate } from "./project-template-managed.ts";
interface CategoryTemplate {
eyebrow: string;
@@ -192,15 +193,18 @@ function sourceFiles(spec: GeneratedProjectSpec): Array<{
role?: ProjectFileRoleV2;
}> {
const category = categories[spec.presetId];
+ const managed = projectManagedTemplate(spec);
const integrationManifest: ProjectIntegrationManifestV2[] = [
{ id: "dropstab", kind: "dropstab", status: "demo", capabilities: ["coins"], proxyPath: "/api/capabilities/dropstab", providerEvidenceRequired: true },
{ id: "drops-bot", kind: "drops-bot", status: "setup-required", capabilities: ["wallet-events", "alerts", "webhooks"], proxyPath: "/api/capabilities/drops-bot", providerEvidenceRequired: true },
{ id: "telegram", kind: "telegram", status: "setup-required", capabilities: ["approved-delivery"], proxyPath: "/api/capabilities/telegram", providerEvidenceRequired: true },
{ id: "project-data", kind: "project-data", status: "setup-required", capabilities: ["demo-documents", "event-inbox"], proxyPath: "/api/project-data", providerEvidenceRequired: true },
+ ...(managed.integration ? [managed.integration] : []),
];
const environment = [
{ name: "DROPSTAB_API_KEY", description: "Optional server-side DropsTab credential.", required: false, secret: true, scope: "runtime" },
{ name: "DROPS_BOT_WEBHOOK_SECRET", description: "Optional server-side webhook verification secret.", required: false, secret: true, scope: "runtime" },
+ ...managed.environment,
];
return [
{ path: "package.json", content: json({ ...packageManifest, name: spec.slug }), language: "json", role: "manifest" },
@@ -225,7 +229,8 @@ function sourceFiles(spec: GeneratedProjectSpec): Array<{
{ path: "tsconfig.json", content: json({ compilerOptions: { target: "ES2017", lib: ["dom", "dom.iterable", "esnext"], allowJs: true, skipLibCheck: true, strict: true, noEmit: true, esModuleInterop: true, module: "esnext", moduleResolution: "bundler", resolveJsonModule: true, isolatedModules: true, jsx: "react-jsx", incremental: true, plugins: [{ name: "next" }] }, include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts"], exclude: ["node_modules"] }), language: "json", role: "config" },
{ path: "tests/smoke.mjs", content: `import assert from "node:assert/strict";\nimport { readFile } from "node:fs/promises";\n\nconst [page, component, config] = await Promise.all([readFile(new URL("../app/page.tsx", import.meta.url), "utf8"), readFile(new URL("../components/crypto-product.tsx", import.meta.url), "utf8"), readFile(new URL("../drops.config.json", import.meta.url), "utf8").then(JSON.parse)]);\nassert.match(page, /CryptoProduct/);\nassert.match(component, /${spec.presetId}/);\nassert.equal(config.truthfulFallback, "Demo data is never labelled live.");\nconsole.log("Project V2 smoke passed");\n`, language: "javascript", role: "test" },
{ path: "tests/dropstab-capability.test.mjs", content: PROJECT_TEMPLATE_DROPSTAB_TEST, language: "javascript", role: "test" },
- { path: "README.md", content: `# ${spec.name}\n\nA category-native Drops Studio Project V2 built with Next.js, React, TypeScript and Tailwind CSS.\n\n## Run\n\n- \`npm install --ignore-scripts\`\n- \`npm run typecheck\`\n- \`npm run lint\`\n- \`npm test\`\n- \`npm run build\`\n- \`npm run dev\`\n\nThe same-origin \`/api/capabilities/dropstab\` route reads only the documented DropsTab \`/coins\` endpoint. \`DROPSTAB_API_KEY\` is read only by its server-only module; the browser receives normalized rows and explicit provider evidence. Without a configured key or a valid upstream response the route returns an embedded snapshot labelled \`demo\`, never live DropsTab data. Drops Bot and Telegram remain setup-required until confirmed by their providers.\n`, language: "markdown", role: "documentation" },
+ ...managed.files,
+ { path: "README.md", content: `# ${spec.name}\n\nA category-native Drops Studio Project V2 built with Next.js, React, TypeScript and Tailwind CSS.\n\n## Run\n\n- \`npm install --ignore-scripts\`\n- \`npm run typecheck\`\n- \`npm run lint\`\n- \`npm test\`\n- \`npm run build\`\n- \`npm run dev\`\n\nThe same-origin \`/api/capabilities/dropstab\` route reads only the documented DropsTab \`/coins\` endpoint. \`DROPSTAB_API_KEY\` is read only by its server-only module; the browser receives normalized rows and explicit provider evidence. Without a configured key or a valid upstream response the route returns an embedded snapshot labelled \`demo\`, never live DropsTab data. Drops Bot and Telegram remain setup-required until confirmed by their providers.${managed.readme}\n`, language: "markdown", role: "documentation" },
];
}
diff --git a/lib/project-template-ui.ts b/lib/project-template-ui.ts
index 11a3da4..fb788d5 100644
--- a/lib/project-template-ui.ts
+++ b/lib/project-template-ui.ts
@@ -17,6 +17,7 @@ const COMPONENT_TEMPLATE = String.raw`"use client";
import { createContext, useContext, useState, type ReactNode } from "react";
import { useDropsTabCoins, type DropsTabCapabilityState } from "../lib/use-dropstab-coins";
+__MANAGED_IMPORT__
const product = __PRODUCT_MODEL__ as const;
@@ -163,10 +164,11 @@ function CryptoAssistant() {
function CustomProduct() {
const modules: readonly string[] = product.modules.length ? product.modules : ["Primary workflow", "Sourced data", "Local persistence"];
const [active, setActive] = useState(modules[0]);
- const [items, setItems] = useState([]);
+ const managed = useManagedCollection("workflow_items");
const [draft, setDraft] = useState("");
- const add = () => { if (!draft.trim()) return; setItems((current) => [draft.trim(), ...current]); setDraft(""); };
- return
;
+ const [saveError, setSaveError] = useState("");
+ const add = async () => { if (!draft.trim()) return; try { await managed.addItem(draft); setDraft(""); setSaveError(""); } catch { setSaveError("Wait for the managed backend check, then try again."); } };
+ return {modules.map((module) => setActive(module)}>{module} )}
Add a workflow item setDraft(event.target.value)} placeholder="Describe an item" /> void add()}>Add item
{saveError || managed.status}
{managed.items.length ? item.title)} /> : {product.emptyState} }
;
}
const products = {
@@ -322,8 +324,13 @@ export function projectTemplateComponentSource(
modules: spec.blueprint.modules,
emptyState: spec.blueprint.content.emptyState,
});
+ const managedImport = spec.presetId === "custom-product"
+ ? 'import { useManagedCollection } from "../lib/use-managed-collection";'
+ : "";
return selectCategoryComponent(
- COMPONENT_TEMPLATE.replace("__PRODUCT_MODEL__", model),
+ COMPONENT_TEMPLATE
+ .replace("__PRODUCT_MODEL__", () => model)
+ .replace("__MANAGED_IMPORT__", () => managedImport),
COMPONENT_NAME_BY_PRESET[spec.presetId],
);
}
diff --git a/outputs/agent-evals/v3/compact-core-metrics.json b/outputs/agent-evals/v3/compact-core-metrics.json
index dc3af4d..a1dc673 100644
--- a/outputs/agent-evals/v3/compact-core-metrics.json
+++ b/outputs/agent-evals/v3/compact-core-metrics.json
@@ -118,8 +118,8 @@
]
},
"runtimeSkills": {
- "aggregateHash": "bec6a706c391ba9e42534b2ec0ac4260f11044cb83bf6ac0066bec9af5c513cb",
- "count": 17
+ "aggregateHash": "319f989236c629a4b172d70cc018f75473c631aa5e118426af01c891c080a135",
+ "count": 29
},
"schemaVersion": 1
}
diff --git a/outputs/agent-evals/v3/manifest.json b/outputs/agent-evals/v3/manifest.json
index 5f70a15..3336b63 100644
--- a/outputs/agent-evals/v3/manifest.json
+++ b/outputs/agent-evals/v3/manifest.json
@@ -4,7 +4,7 @@
{
"byteCount": 4150,
"name": "compact-core-metrics.json",
- "sha256": "e4989d92f940b9b846324ceb1217c60cff691363a9c2c7b0b0c7a38c34a55367"
+ "sha256": "425a42cb74b5f47c3e0aeaf0f9558fe393a80b9e618117b97f6cd45b55e6427a"
},
{
"byteCount": 234485,
diff --git a/tests/agent-intelligence-runtime.test.mjs b/tests/agent-intelligence-runtime.test.mjs
index fca1384..b514ebb 100644
--- a/tests/agent-intelligence-runtime.test.mjs
+++ b/tests/agent-intelligence-runtime.test.mjs
@@ -247,6 +247,7 @@ test("composite runtime resolves once, compiles redacted provenance, delegates,
assert.match(prompts[0], /# Drops Studio Agent/);
assert.match(prompts[0], //);
assert.match(prompts[0], /GET \/coins/);
+ assert.match(prompts[0], /dropstab-integration/);
assert.doesNotMatch(prompts[0], new RegExp(secret));
assert.equal(output.verification.verdict, "PASS_WITH_SETUP_REQUIRED");
assert.equal(output.trace.verification.deterministicGatePassed, true);
diff --git a/tests/agent-prompt-skills-v3.test.mjs b/tests/agent-prompt-skills-v3.test.mjs
index 5f0dcda..1c8d729 100644
--- a/tests/agent-prompt-skills-v3.test.mjs
+++ b/tests/agent-prompt-skills-v3.test.mjs
@@ -105,6 +105,45 @@ test("skill selection is relevant, deterministic, budgeted, and excludes unrelat
assert.deepEqual(irrelevant.skills, []);
});
+test("managed collaborative SaaS requests load only the relevant V4 runtime skills", () => {
+ const selected = skills.selectRuntimeSkills({
+ role: "planner",
+ task: "Build a collaborative crypto research SaaS with organization roles, managed auth, schema migrations, file attachments, a signed webhook inbox, scheduled cron summaries, realtime updates, audit history, backups, and approval-based delivery.",
+ integrations: [
+ "managed-backend",
+ "managed-auth",
+ "object-storage",
+ "managed-jobs",
+ "managed-webhooks",
+ "managed-realtime",
+ "collaboration",
+ "organizations",
+ "audit",
+ ],
+ availableCapabilities: ["project-v2", "vercel-sandbox", "project-data"],
+ maximumSkills: 12,
+ maximumEstimatedTokens: 4_800,
+ });
+ const ids = selected.skills.map((skill) => skill.id);
+ for (const expected of [
+ "managed-backend",
+ "managed-auth",
+ "data-modeling",
+ "object-storage",
+ "jobs-and-cron",
+ "webhooks",
+ "realtime-data",
+ "collaboration",
+ "enterprise-rbac",
+ "audit-and-compliance",
+ ]) assert.ok(ids.includes(expected), `${expected} should be selected`);
+ for (const irrelevant of ["crypto-game", "github-delivery", "vercel-deployment"]) {
+ assert.equal(ids.includes(irrelevant), false, `${irrelevant} should be absent`);
+ }
+ assert.ok(selected.skills.length <= 12);
+ assert.ok(selected.estimatedTokens <= 4_800);
+});
+
test("prompt assembly is deterministic and records a complete content-addressed manifest", async () => {
const core = await prompts.loadCompactCorePrompt();
const rolePrompt = await prompts.loadRolePrompt("planner");
diff --git a/tests/enterprise-platform-collaboration.test.mjs b/tests/enterprise-platform-collaboration.test.mjs
new file mode 100644
index 0000000..d2d5951
--- /dev/null
+++ b/tests/enterprise-platform-collaboration.test.mjs
@@ -0,0 +1,102 @@
+import assert from "node:assert/strict";
+import test from "node:test";
+
+const {
+ AiBranchManager,
+ CollaborationComments,
+ EnterprisePlatformError,
+ LocalPresenceRoom,
+ applyTextOperations,
+ createCollaborativeTextDocument,
+ createDeleteOperations,
+ createInsertOperations,
+ renderCollaborativeText,
+} = await import("../lib/enterprise-platform/index.ts");
+
+function hasCode(code) {
+ return (error) => error instanceof EnterprisePlatformError && error.code === code;
+}
+
+test("concurrent deterministic text operations converge without losing either actor", () => {
+ const base = createCollaborativeTextDocument("doc-1", "AB");
+ const left = createInsertOperations(base, { actorId: "alice", lamport: 10, index: 1, text: "x" });
+ const right = createInsertOperations(base, { actorId: "bob", lamport: 10, index: 1, text: "y" });
+ const first = applyTextOperations(base, [...left, ...right]);
+ const second = applyTextOperations(base, [...right, ...left]);
+ assert.equal(renderCollaborativeText(first), renderCollaborativeText(second));
+ assert.match(renderCollaborativeText(first), /^A(?:xy|yx)B$/);
+
+ const deletion = createDeleteOperations(first, { actorId: "alice", lamport: 20, index: 1, length: 1 });
+ const deleted = applyTextOperations(first, deletion);
+ assert.equal(renderCollaborativeText(deleted).length, 3);
+ assert.throws(() => applyTextOperations(base, [{
+ kind: "insert", operationId: "bad", actorId: "alice", lamport: 1, afterId: "missing", value: "z",
+ }]), hasCode("COLLABORATION_OPERATION_INVALID"));
+});
+
+test("deep collaborative documents render without recursive stack overflow", () => {
+ const source = "x".repeat(20_000);
+ const document = createCollaborativeTextDocument("deep-document", source);
+ assert.equal(renderCollaborativeText(document), source);
+});
+
+test("presence is authenticated, real, bounded and expires", () => {
+ const room = new LocalPresenceRoom({ roomId: "room-1", ttlMs: 5_000, maximumParticipants: 2 });
+ room.authorize({ userId: "alice", displayName: "Alice", canEdit: true });
+ room.authorize({ userId: "viewer", displayName: "Viewer", canEdit: false });
+ room.update({ userId: "alice", activeFile: "app/page.tsx", state: "editing", cursor: { anchor: 2, head: 4 }, at: 1_000 });
+ room.update({ userId: "viewer", activeFile: "app/page.tsx", state: "viewing", at: 1_000 });
+ assert.equal(room.list(5_999).length, 2);
+ assert.equal(room.list(6_001).length, 0);
+ assert.throws(() => room.update({ userId: "viewer", activeFile: "app/page.tsx", state: "editing", at: 7_000 }), hasCode("PERMISSION_DENIED"));
+ assert.throws(() => room.update({ userId: "unknown", activeFile: "app/page.tsx", state: "viewing", at: 7_000 }), hasCode("ROOM_ACCESS_DENIED"));
+});
+
+test("comments support replies and explicit resolve/reopen permissions", () => {
+ const permissions = new Map([
+ ["owner", new Set(["collaboration.comment", "collaboration.merge"])],
+ ["dev", new Set(["collaboration.comment"])],
+ ["viewer", new Set()],
+ ]);
+ let id = 0;
+ const comments = new CollaborationComments({
+ now: () => new Date("2026-07-30T12:00:00.000Z"),
+ id: (prefix) => `${prefix}-${++id}`,
+ can: (userId, permission) => permissions.get(userId)?.has(permission) ?? false,
+ });
+ const thread = comments.createThread({ actorUserId: "owner", projectId: "project-1", filePath: "app/page.tsx", range: { start: 4, end: 10 }, body: "Check this card", mentions: ["dev"] });
+ comments.reply({ actorUserId: "dev", threadId: thread.id, body: "Fixed in my branch" });
+ comments.resolve({ actorUserId: "owner", threadId: thread.id });
+ assert.equal(comments.thread(thread.id).status, "resolved");
+ comments.reopen({ actorUserId: "owner", threadId: thread.id });
+ assert.equal(comments.thread(thread.id).status, "open");
+ assert.equal(comments.thread(thread.id).comments.length, 2);
+ assert.throws(() => comments.reply({ actorUserId: "viewer", threadId: thread.id, body: "No access" }), hasCode("PERMISSION_DENIED"));
+});
+
+test("AI task branches never overwrite stale canonical work and successful merge checkpoints", () => {
+ let id = 0;
+ const manager = new AiBranchManager({
+ now: () => new Date("2026-07-30T12:00:00.000Z"),
+ id: (prefix) => `${prefix}-${++id}`,
+ });
+ manager.createProject({ projectId: "project-1", files: { "app/page.tsx": "base", "lib/data.ts": "data-v1" } });
+ const stale = manager.createBranch({ projectId: "project-1", taskOwnerId: "agent-owner", taskScope: ["app/**"] });
+ manager.writeBranchFile({ branchId: stale.id, path: "app/page.tsx", content: "agent edit" });
+ manager.updateCanonical({ projectId: "project-1", actorUserId: "developer", expectedRevision: 1, writes: { "app/page.tsx": "human edit" } });
+ const conflict = manager.mergeBranch({ branchId: stale.id, actorUserId: "reviewer", approved: true });
+ assert.equal(conflict.status, "conflict");
+ assert.deepEqual(conflict.conflicts.map((entry) => entry.path), ["app/page.tsx"]);
+ assert.equal(manager.project("project-1").files["app/page.tsx"], "human edit");
+ assert.equal(manager.branch(stale.id).status, "conflict");
+
+ const clean = manager.createBranch({ projectId: "project-1", taskOwnerId: "agent-owner", taskScope: ["lib/**"] });
+ manager.writeBranchFile({ branchId: clean.id, path: "lib/data.ts", content: "data-v2" });
+ assert.equal(manager.mergeBranch({ branchId: clean.id, actorUserId: "reviewer", approved: false }).status, "approval-required");
+ const merged = manager.mergeBranch({ branchId: clean.id, actorUserId: "reviewer", approved: true });
+ assert.equal(merged.status, "merged");
+ assert.match(merged.checkpointId, /^checkpoint-/);
+ assert.equal(manager.project("project-1").files["lib/data.ts"], "data-v2");
+ manager.restoreCheckpoint({ projectId: "project-1", checkpointId: merged.checkpointId, actorUserId: "owner", expectedRevision: 3 });
+ assert.equal(manager.project("project-1").files["lib/data.ts"], "data-v1");
+});
diff --git a/tests/enterprise-platform-identity-policy.test.mjs b/tests/enterprise-platform-identity-policy.test.mjs
new file mode 100644
index 0000000..3dac998
--- /dev/null
+++ b/tests/enterprise-platform-identity-policy.test.mjs
@@ -0,0 +1,155 @@
+import assert from "node:assert/strict";
+import test from "node:test";
+
+const {
+ EnterpriseCredentialStore,
+ EnterprisePlatformError,
+ LocalTestDomainVerificationAdapter,
+ LocalTestOidcAdapter,
+ enterpriseFeatureStates,
+ evaluateEnterprisePolicy,
+ resolveEnterprisePolicy,
+} = await import("../lib/enterprise-platform/index.ts");
+
+function hasCode(code) {
+ return (error) => error instanceof EnterprisePlatformError && error.code === code;
+}
+
+function deterministicRuntime() {
+ let sequence = 0;
+ let now = new Date("2026-07-30T12:00:00.000Z");
+ return {
+ now: () => new Date(now),
+ id: (prefix) => `${prefix}-${++sequence}`,
+ entropy: (label) => `${label}_${++sequence}_${"e".repeat(48)}`,
+ advance: (milliseconds) => { now = new Date(now.getTime() + milliseconds); },
+ };
+}
+
+test("local test OIDC enforces state, nonce, PKCE, domain, group mapping and replay", () => {
+ const runtime = deterministicRuntime();
+ const oidc = new LocalTestOidcAdapter({
+ issuer: "https://oidc.test.local",
+ clientId: "drops-studio-test",
+ allowedDomains: ["example.com"],
+ groupRoleMappings: { developers: "developer", auditors: "security" },
+ runtime,
+ });
+ assert.equal(oidc.state().status, "working-local-test");
+ const request = oidc.begin({ organizationId: "org-1", redirectUri: "https://studio.test/callback" });
+ assert.match(request.authorizationUrl, /code_challenge_method=S256/);
+ const code = oidc.issueLocalTestCode({
+ state: request.state,
+ nonce: request.nonce,
+ claims: { subject: "subject-1", email: "dev@example.com", groups: ["developers"] },
+ });
+ const identity = oidc.complete({ state: request.state, code, codeVerifier: request.codeVerifier });
+ assert.equal(identity.roleId, "developer");
+ assert.equal(identity.email, "dev@example.com");
+ assert.throws(() => oidc.complete({ state: request.state, code, codeVerifier: request.codeVerifier }), hasCode("OIDC_REPLAY"));
+
+ const second = oidc.begin({ organizationId: "org-1", redirectUri: "https://studio.test/callback" });
+ const badDomainCode = oidc.issueLocalTestCode({
+ state: second.state,
+ nonce: second.nonce,
+ claims: { subject: "subject-2", email: "dev@outside.test", groups: ["developers"] },
+ });
+ assert.throws(() => oidc.complete({ state: second.state, code: badDomainCode, codeVerifier: second.codeVerifier }), hasCode("OIDC_DOMAIN_DENIED"));
+});
+
+test("local domain challenges are domain bound, expiring, rotating and conflict safe", () => {
+ const runtime = deterministicRuntime();
+ const domains = new LocalTestDomainVerificationAdapter(runtime);
+ const challenge = domains.createChallenge({ organizationId: "org-1", domain: "Example.com", expiresInMs: 10_000 });
+ assert.equal(challenge.txtName, "_drops-studio-verification.example.com");
+ assert.equal(domains.verify({ organizationId: "org-1", domain: "example.com", observedTxtValues: [challenge.txtValue] }).verified, true);
+ assert.throws(() => domains.createChallenge({ organizationId: "org-2", domain: "example.com", expiresInMs: 10_000 }), hasCode("DOMAIN_CLAIMED"));
+ const verifiedRotation = domains.rotateChallenge({ organizationId: "org-1", domain: "example.com", expiresInMs: 10_000 });
+ assert.throws(() => domains.verify({ organizationId: "org-1", domain: "example.com", observedTxtValues: [challenge.txtValue] }), hasCode("DOMAIN_VERIFICATION_FAILED"));
+ assert.equal(domains.verify({ organizationId: "org-1", domain: "example.com", observedTxtValues: [verifiedRotation.txtValue] }).verified, true);
+ assert.throws(() => domains.rotateChallenge({ organizationId: "org-2", domain: "example.com", expiresInMs: 10_000 }), hasCode("DOMAIN_CLAIMED"));
+
+ const expiring = domains.createChallenge({ organizationId: "org-1", domain: "other.example.com", expiresInMs: 1_000 });
+ runtime.advance(1_001);
+ assert.throws(() => domains.verify({ organizationId: "org-1", domain: "other.example.com", observedTxtValues: [expiring.txtValue] }), hasCode("DOMAIN_CHALLENGE_EXPIRED"));
+ const rotated = domains.rotateChallenge({ organizationId: "org-1", domain: "other.example.com", expiresInMs: 10_000 });
+ assert.notEqual(rotated.txtValue, expiring.txtValue);
+});
+
+test("service-account tokens are one-time, hashed, scoped, expiring and revocable", () => {
+ const runtime = deterministicRuntime();
+ const credentials = new EnterpriseCredentialStore({ runtime, tokenPepper: "p".repeat(48) });
+ const account = credentials.createServiceAccount({
+ organizationId: "org-1",
+ name: "Preview automation",
+ permissions: ["project.read", "project.edit"],
+ projectIds: ["project-1"],
+ environments: ["preview"],
+ });
+ const issued = credentials.issueToken({ serviceAccountId: account.id, permissions: ["project.read"], expiresInMs: 60_000 });
+ assert.match(issued.token, /^dst_sa_/);
+ assert.equal(issued.tokenRecord.prefix, "dst_sa_");
+ assert.equal(JSON.stringify(credentials.snapshot()).includes(issued.token), false);
+ assert.equal(credentials.authenticate({ token: issued.token, permission: "project.read", projectId: "project-1", environment: "preview" }).serviceAccountId, account.id);
+ assert.throws(() => credentials.authenticate({ token: issued.token, permission: "project.edit", projectId: "project-1", environment: "preview" }), hasCode("TOKEN_SCOPE_DENIED"));
+ assert.throws(() => credentials.authenticate({ token: issued.token, permission: "project.read", projectId: "project-2", environment: "preview" }), hasCode("TOKEN_PROJECT_DENIED"));
+ const rotated = credentials.rotateToken({ tokenId: issued.tokenRecord.id, expiresInMs: 60_000 });
+ assert.throws(() => credentials.authenticate({ token: issued.token, permission: "project.read", projectId: "project-1", environment: "preview" }), hasCode("TOKEN_REVOKED"));
+ credentials.revokeToken(rotated.tokenRecord.id);
+ assert.throws(() => credentials.authenticate({ token: rotated.token, permission: "project.read", projectId: "project-1", environment: "preview" }), hasCode("TOKEN_REVOKED"));
+});
+
+test("failed service-account token rotation leaves the previous token active", () => {
+ const runtime = deterministicRuntime();
+ const credentials = new EnterpriseCredentialStore({ runtime, tokenPepper: "p".repeat(48) });
+ const account = credentials.createServiceAccount({
+ organizationId: "org-1",
+ name: "Safe rotation",
+ permissions: ["project.read"],
+ });
+ const issued = credentials.issueToken({ serviceAccountId: account.id, permissions: ["project.read"], expiresInMs: 60_000 });
+ runtime.entropy = () => "too-short";
+ assert.throws(() => credentials.rotateToken({ tokenId: issued.tokenRecord.id, expiresInMs: 60_000 }), hasCode("INVALID_INPUT"));
+ assert.equal(credentials.authenticate({ token: issued.token, permission: "project.read" }).tokenId, issued.tokenRecord.id);
+});
+
+test("policy precedence only tightens higher-priority constraints and records a stable hash", () => {
+ const resolved = resolveEnterprisePolicy({
+ systemHard: {
+ allowedModelProviders: ["openai", "anthropic"],
+ platformModelsAllowed: false,
+ maxAgentCostPerRun: 10,
+ allowedNetworkHosts: ["api.dropstab.com", "studio.example"],
+ productionPublishRequiresApproval: true,
+ exportAllowed: true,
+ },
+ organization: {
+ allowedModelProviders: ["openai", "custom"],
+ platformModelsAllowed: true,
+ maxAgentCostPerRun: 5,
+ allowedNetworkHosts: ["api.dropstab.com"],
+ productionPublishRequiresApproval: false,
+ },
+ project: { maxAgentCostPerRun: 8, exportAllowed: false },
+ userPreference: { allowedModelProviders: ["openai", "anthropic"] },
+ });
+ assert.deepEqual(resolved.policy.allowedModelProviders, ["openai"]);
+ assert.equal(resolved.policy.platformModelsAllowed, false);
+ assert.equal(resolved.policy.maxAgentCostPerRun, 5);
+ assert.equal(resolved.policy.productionPublishRequiresApproval, true);
+ assert.equal(resolved.policy.exportAllowed, false);
+ assert.match(resolved.policyHash, /^[a-f0-9]{64}$/);
+ assert.equal(evaluateEnterprisePolicy(resolved, { action: "model.use", provider: "anthropic" }).allowed, false);
+ assert.equal(evaluateEnterprisePolicy(resolved, { action: "network.connect", host: "api.dropstab.com" }).allowed, true);
+ assert.equal(evaluateEnterprisePolicy(resolved, { action: "production.publish" }).requiresApproval, true);
+});
+
+test("feature states never claim unconfigured SAML, SCIM or external OIDC success", () => {
+ const states = enterpriseFeatureStates({ organizations: true, localCollaboration: true, localTestOidc: true });
+ assert.equal(states.organizations.status, "working-local-test");
+ assert.equal(states.realtimeCollaboration.mode, "deterministic-local-test");
+ assert.equal(states.enterpriseOidc.status, "working-local-test");
+ assert.equal(states.enterpriseSaml.status, "setup-required");
+ assert.equal(states.scim.status, "setup-required");
+ assert.equal(states.enterpriseSaml.providerEvidence, false);
+});
diff --git a/tests/enterprise-platform-lifecycle.test.mjs b/tests/enterprise-platform-lifecycle.test.mjs
new file mode 100644
index 0000000..064d578
--- /dev/null
+++ b/tests/enterprise-platform-lifecycle.test.mjs
@@ -0,0 +1,134 @@
+import assert from "node:assert/strict";
+import test from "node:test";
+
+const {
+ EnterpriseLifecycleManager,
+ EnterprisePlatformError,
+ ImmutableAuditLog,
+} = await import("../lib/enterprise-platform/index.ts");
+
+function hasCode(code) {
+ return (error) => error instanceof EnterprisePlatformError && error.code === code;
+}
+
+function runtime() {
+ let sequence = 0;
+ let now = new Date("2026-07-30T12:00:00.000Z");
+ return {
+ now: () => new Date(now),
+ id: (prefix) => `${prefix}-${++sequence}`,
+ advance: (milliseconds) => { now = new Date(now.getTime() + milliseconds); },
+ };
+}
+
+test("audit events are append-only, secret-safe, tenant-filtered and tamper evident", () => {
+ const clock = runtime();
+ const audit = new ImmutableAuditLog(clock);
+ audit.append({
+ organizationId: "org-1", actorType: "user", actorId: "owner", action: "organization.create", targetType: "organization", targetId: "org-1", outcome: "success", requestId: "request-1", metadata: { name: "Alpha" },
+ });
+ audit.append({
+ organizationId: "org-1", workspaceId: "workspace-1", actorType: "agent", actorId: "agent-1", action: "branch.merge", targetType: "project", targetId: "project-1", outcome: "blocked", reasonCode: "conflict", requestId: "request-2", metadata: { conflictCount: 1 },
+ });
+ const listed = audit.list({ organizationId: "org-1", permissions: ["audit.read"], limit: 10 });
+ assert.equal(listed.items.length, 2);
+ assert.equal(audit.verifyIntegrity(), true);
+ listed.items[0].metadata.name = "tampered clone";
+ assert.equal(audit.verifyIntegrity(), true);
+ assert.equal(audit.list({ organizationId: "org-1", permissions: ["audit.read"], limit: 10 }).items[0].metadata.name, "Alpha");
+ assert.throws(() => audit.append({
+ organizationId: "org-1", actorType: "user", actorId: "owner", action: "secret.update", targetType: "secret", outcome: "success", requestId: "request-3", metadata: { apiToken: `secret_${"x".repeat(32)}` },
+ }), hasCode("AUDIT_SECRET_REJECTED"));
+ assert.throws(() => audit.list({ organizationId: "org-1", permissions: [], limit: 10 }), hasCode("PERMISSION_DENIED"));
+});
+
+test("audit export paginates beyond the public 500-event page limit", () => {
+ const clock = runtime();
+ const audit = new ImmutableAuditLog(clock);
+ for (let index = 0; index < 501; index += 1) {
+ audit.append({
+ organizationId: "org-1",
+ actorType: "system",
+ actorId: "audit-system",
+ action: "project.verify",
+ targetType: "project",
+ targetId: "project-1",
+ outcome: "success",
+ requestId: `request-${index}`,
+ metadata: { index },
+ });
+ }
+ audit.append({
+ organizationId: "org-2",
+ actorType: "system",
+ actorId: "audit-system",
+ action: "project.verify",
+ targetType: "project",
+ targetId: "project-2",
+ outcome: "success",
+ requestId: "request-other-tenant",
+ metadata: {},
+ });
+ const exported = audit.export({ organizationId: "org-1", permissions: ["audit.read"] });
+ assert.equal(exported.eventCount, 501);
+ assert.equal(exported.events.length, 501);
+ assert.equal(exported.events.at(-1).requestId, "request-500");
+ assert.equal(exported.chainRoot, exported.events.at(-1).integrityHash);
+ assert.match(exported.checksum, /^[a-f0-9]{64}$/);
+});
+
+test("retention, exports and deletions are scheduled, cancellable and secret free", () => {
+ const clock = runtime();
+ const lifecycle = new EnterpriseLifecycleManager(clock);
+ const retention = lifecycle.setRetentionPolicy({
+ organizationId: "org-1", actorUserId: "security", permissions: ["security.manage"],
+ values: { auditDays: 365, logsDays: 30, traceDays: 30, presenceDays: 1, deletedProjectDays: 14, backupDays: 90 },
+ });
+ assert.equal(retention.revision, 1);
+
+ const exportJob = lifecycle.scheduleExport({
+ organizationId: "org-1", actorUserId: "owner", permissions: ["project.export"], scope: { type: "organization", id: "org-1" },
+ });
+ const completed = lifecycle.completeExport({
+ exportId: exportJob.id,
+ data: { projects: [{ id: "project-1" }], apiToken: `dst_${"x".repeat(40)}`, configuration: { secretReferenceId: "secret-ref-1" } },
+ });
+ assert.equal(completed.status, "completed-local-test");
+ assert.equal(JSON.stringify(completed.manifest).includes("dst_"), false);
+ assert.match(completed.checksum, /^[a-f0-9]{64}$/);
+
+ const pending = lifecycle.scheduleExport({
+ organizationId: "org-1", actorUserId: "owner", permissions: ["project.export"], scope: { type: "project", id: "project-1" },
+ });
+ lifecycle.cancelExport({ exportId: pending.id, actorUserId: "owner" });
+ assert.equal(lifecycle.exportRequest(pending.id).status, "cancelled");
+
+ const deletion = lifecycle.scheduleDeletion({
+ organizationId: "org-1", actorUserId: "owner", permissions: ["organization.manage"], target: { type: "organization", id: "org-1" }, gracePeriodMs: 86_400_000, confirmation: "DELETE organization:org-1", dependencies: [],
+ });
+ lifecycle.cancelDeletion({ deletionId: deletion.id, actorUserId: "owner" });
+ assert.equal(lifecycle.deletionRequest(deletion.id).status, "cancelled");
+});
+
+test("backup metadata is checksummed and restore defaults to a new environment", () => {
+ const clock = runtime();
+ const lifecycle = new EnterpriseLifecycleManager(clock);
+ const backup = lifecycle.createBackupMetadata({
+ organizationId: "org-1", projectId: "project-1", environment: "development", sourceRevision: 7, artifactId: "artifact-1", artifactChecksum: "a".repeat(64), kind: "manual", adapterEvidence: { mode: "local-test", verified: true },
+ });
+ const restore = lifecycle.planRestore({ backupId: backup.id, actorUserId: "owner", targetEnvironment: "restore-preview", overwriteProduction: false, approved: false });
+ assert.equal(restore.status, "planned");
+ assert.equal(restore.targetEnvironment, "restore-preview");
+ lifecycle.completeRestore({ restoreId: restore.id, checksumVerified: true, adapterEvidenceId: "local-test-restore-1" });
+ assert.equal(lifecycle.restoreOperation(restore.id).status, "completed-local-test");
+
+ assert.throws(() => lifecycle.planRestore({
+ backupId: backup.id, actorUserId: "owner", targetEnvironment: "production", overwriteProduction: true, approved: false,
+ }), hasCode("PRODUCTION_APPROVAL_REQUIRED"));
+ assert.throws(() => lifecycle.planRestore({
+ backupId: backup.id, actorUserId: "owner", targetEnvironment: "PrOdUcTiOn", overwriteProduction: false, approved: false,
+ }), hasCode("PRODUCTION_APPROVAL_REQUIRED"));
+ assert.throws(() => lifecycle.createBackupMetadata({
+ organizationId: "org-1", projectId: "project-1", environment: "development", sourceRevision: 7, artifactId: "artifact-unverified", artifactChecksum: "b".repeat(64), kind: "manual", adapterEvidence: { mode: "external", verified: false },
+ }), hasCode("BACKUP_EVIDENCE_REQUIRED"));
+});
diff --git a/tests/enterprise-platform-organizations.test.mjs b/tests/enterprise-platform-organizations.test.mjs
new file mode 100644
index 0000000..166f680
--- /dev/null
+++ b/tests/enterprise-platform-organizations.test.mjs
@@ -0,0 +1,180 @@
+import assert from "node:assert/strict";
+import test from "node:test";
+
+const {
+ DEFAULT_ROLE_PERMISSIONS,
+ EnterpriseDirectory,
+ EnterprisePlatformError,
+} = await import("../lib/enterprise-platform/index.ts");
+
+function runtime(at = "2026-07-30T12:00:00.000Z") {
+ let sequence = 0;
+ let current = new Date(at);
+ return {
+ now: () => new Date(current),
+ id: (prefix) => `${prefix}-${String(++sequence).padStart(4, "0")}`,
+ token: () => `invite_${String(++sequence).padStart(4, "0")}_${"x".repeat(32)}`,
+ advance: (milliseconds) => { current = new Date(current.getTime() + milliseconds); },
+ };
+}
+
+function hasCode(code) {
+ return (error) => error instanceof EnterprisePlatformError && error.code === code;
+}
+
+test("default roles are bounded and viewers cannot mutate projects", () => {
+ assert.ok(DEFAULT_ROLE_PERMISSIONS.owner.includes("organization.manage"));
+ assert.ok(DEFAULT_ROLE_PERMISSIONS.developer.includes("project.edit"));
+ assert.ok(DEFAULT_ROLE_PERMISSIONS.security.includes("audit.read"));
+ assert.equal(DEFAULT_ROLE_PERMISSIONS.viewer.includes("project.edit"), false);
+ assert.equal(DEFAULT_ROLE_PERMISSIONS.billing.includes("security.manage"), false);
+});
+
+test("organizations, workspaces, invitations and ownership remain tenant safe", () => {
+ const clock = runtime();
+ const directory = new EnterpriseDirectory(clock);
+ const created = directory.createOrganization({
+ ownerUserId: "user-owner",
+ name: "Alpha Research",
+ kind: "organization",
+ });
+ const secondWorkspace = directory.createWorkspace({
+ actorUserId: "user-owner",
+ organizationId: created.organization.id,
+ name: "Trading Lab",
+ });
+ const invited = directory.inviteMember({
+ actorUserId: "user-owner",
+ organizationId: created.organization.id,
+ workspaceId: secondWorkspace.id,
+ email: "Dev@Example.com",
+ roleId: "developer",
+ expiresInMs: 60_000,
+ });
+ assert.match(invited.token, /^invite_/);
+ assert.equal(JSON.stringify(directory.snapshot()).includes(invited.token), false);
+
+ const accepted = directory.acceptInvitation({
+ token: invited.token,
+ userId: "user-dev",
+ email: "dev@example.com",
+ });
+ assert.equal(accepted.organizationRoleId, "developer");
+ assert.equal(directory.can("user-dev", created.organization.id, "project.edit"), true);
+ assert.equal(directory.can("user-dev", created.organization.id, "billing.manage"), false);
+ assert.equal(directory.createProject({ actorUserId: "user-dev", workspaceId: secondWorkspace.id, name: "Scoped project" }).workspaceId, secondWorkspace.id);
+ assert.throws(() => directory.createProject({ actorUserId: "user-dev", workspaceId: created.workspace.id, name: "Unscoped project" }), hasCode("PERMISSION_DENIED"));
+ assert.throws(() => directory.acceptInvitation({
+ token: invited.token,
+ userId: "user-dev-2",
+ email: "dev@example.com",
+ }), hasCode("INVITATION_REPLAY"));
+
+ const other = directory.createOrganization({
+ ownerUserId: "other-owner",
+ name: "Other Tenant",
+ kind: "organization",
+ });
+ assert.equal(directory.can("user-dev", other.organization.id, "project.read"), false);
+ assert.throws(() => directory.createWorkspace({
+ actorUserId: "user-dev",
+ organizationId: other.organization.id,
+ name: "Cross tenant",
+ }), hasCode("PERMISSION_DENIED"));
+
+ assert.throws(() => directory.transferOwnership({
+ actorUserId: "user-owner",
+ organizationId: created.organization.id,
+ nextOwnerUserId: "user-dev",
+ confirmation: "wrong",
+ }), hasCode("CONFIRMATION_REQUIRED"));
+ directory.transferOwnership({
+ actorUserId: "user-owner",
+ organizationId: created.organization.id,
+ nextOwnerUserId: "user-dev",
+ confirmation: `TRANSFER ${created.organization.id}`,
+ });
+ assert.equal(directory.membership(created.organization.id, "user-dev").roleId, "owner");
+ assert.equal(directory.membership(created.organization.id, "user-owner").roleId, "admin");
+});
+
+test("expired and revoked invitations cannot be replayed and resend rotates the token", () => {
+ const clock = runtime();
+ const directory = new EnterpriseDirectory(clock);
+ const { organization } = directory.createOrganization({ ownerUserId: "owner", name: "Alpha", kind: "organization" });
+ const expired = directory.inviteMember({
+ actorUserId: "owner", organizationId: organization.id, email: "old@example.com", roleId: "viewer", expiresInMs: 1_000,
+ });
+ clock.advance(1_001);
+ assert.throws(() => directory.acceptInvitation({ token: expired.token, userId: "old", email: "old@example.com" }), hasCode("INVITATION_EXPIRED"));
+
+ const pending = directory.inviteMember({
+ actorUserId: "owner", organizationId: organization.id, email: "new@example.com", roleId: "viewer", expiresInMs: 10_000,
+ });
+ const resent = directory.resendInvitation({ actorUserId: "owner", invitationId: pending.invitation.id, expiresInMs: 20_000 });
+ assert.notEqual(resent.token, pending.token);
+ assert.throws(() => directory.acceptInvitation({ token: pending.token, userId: "new", email: "new@example.com" }), hasCode("INVITATION_REVOKED"));
+ directory.revokeInvitation({ actorUserId: "owner", invitationId: resent.invitation.id });
+ assert.throws(() => directory.acceptInvitation({ token: resent.token, userId: "new", email: "new@example.com" }), hasCode("INVITATION_REVOKED"));
+});
+
+test("failed invitation resend keeps the original invitation usable", () => {
+ const clock = runtime();
+ const directory = new EnterpriseDirectory(clock);
+ const { organization } = directory.createOrganization({ ownerUserId: "owner", name: "Alpha", kind: "organization" });
+ const pending = directory.inviteMember({
+ actorUserId: "owner", organizationId: organization.id, email: "safe@example.com", roleId: "viewer", expiresInMs: 10_000,
+ });
+ clock.token = () => "too-short";
+ assert.throws(() => directory.resendInvitation({ actorUserId: "owner", invitationId: pending.invitation.id, expiresInMs: 20_000 }), hasCode("INVALID_INPUT"));
+ assert.equal(directory.acceptInvitation({ token: pending.token, userId: "safe-user", email: "safe@example.com" }).organizationRoleId, "viewer");
+});
+
+test("an active member cannot consume a second invitation", () => {
+ const clock = runtime();
+ const directory = new EnterpriseDirectory(clock);
+ const { organization } = directory.createOrganization({ ownerUserId: "owner", name: "Alpha", kind: "organization" });
+ const first = directory.inviteMember({
+ actorUserId: "owner", organizationId: organization.id, email: "member@example.com", roleId: "viewer", expiresInMs: 10_000,
+ });
+ directory.acceptInvitation({ token: first.token, userId: "member", email: "member@example.com" });
+ const second = directory.inviteMember({
+ actorUserId: "owner", organizationId: organization.id, email: "member+second@example.com", roleId: "developer", expiresInMs: 10_000,
+ });
+ assert.throws(() => directory.acceptInvitation({ token: second.token, userId: "member", email: "member+second@example.com" }), hasCode("INVALID_INPUT"));
+ const storedSecond = directory.snapshot().invitations.find((invitation) => invitation.id === second.invitation.id);
+ assert.equal(storedSecond.acceptedAt, null);
+ assert.equal(directory.membership(organization.id, "member").roleId, "viewer");
+});
+
+test("custom roles cannot grant permissions the creator lacks and project transfer checks both workspaces", () => {
+ const clock = runtime();
+ const directory = new EnterpriseDirectory(clock);
+ const { organization, workspace } = directory.createOrganization({ ownerUserId: "owner", name: "Alpha", kind: "organization" });
+ const target = directory.createWorkspace({ actorUserId: "owner", organizationId: organization.id, name: "Target" });
+ const project = directory.createProject({ actorUserId: "owner", workspaceId: workspace.id, name: "Whale Lab" });
+ directory.transferProject({ actorUserId: "owner", projectId: project.id, targetWorkspaceId: target.id });
+ assert.equal(directory.project(project.id).workspaceId, target.id);
+
+ const custom = directory.createCustomRole({
+ actorUserId: "owner",
+ organizationId: organization.id,
+ name: "Reviewer",
+ permissions: ["project.read", "collaboration.comment"],
+ });
+ assert.deepEqual(custom.permissions, ["collaboration.comment", "project.read"]);
+ const reviewerInvitation = directory.inviteMember({
+ actorUserId: "owner",
+ organizationId: organization.id,
+ email: "reviewer@example.com",
+ roleId: "developer",
+ expiresInMs: 60_000,
+ });
+ directory.acceptInvitation({ token: reviewerInvitation.token, userId: "reviewer", email: "reviewer@example.com" });
+ assert.throws(() => directory.createCustomRole({
+ actorUserId: "reviewer",
+ organizationId: organization.id,
+ name: "Escalated",
+ permissions: ["organization.manage"],
+ }), hasCode("PERMISSION_DENIED"));
+});
diff --git a/tests/managed-platform-backups.test.mjs b/tests/managed-platform-backups.test.mjs
new file mode 100644
index 0000000..c6c7c83
--- /dev/null
+++ b/tests/managed-platform-backups.test.mjs
@@ -0,0 +1,60 @@
+import assert from "node:assert/strict";
+import { registerHooks } from "node:module";
+import test from "node:test";
+
+registerHooks({
+ resolve(specifier, context, nextResolve) {
+ if (!specifier.startsWith("@/")) return nextResolve(specifier, context);
+ const path = specifier.slice(2);
+ return { shortCircuit: true, url: new URL(path.endsWith(".ts") ? path : `${path}.ts`, new URL("../", import.meta.url)).href };
+ },
+});
+
+const managed = await import("../lib/managed-platform/index.ts");
+const SOURCE = managed.managedScope({ organizationId: "org", workspaceId: "workspace", projectId: "project", environment: "development" });
+const RESTORE = managed.managedScope({ ...SOURCE, environment: "preview" });
+
+function owner(scope) {
+ return managed.managedPrincipal({
+ actorId: "owner",
+ actorType: "user",
+ scope,
+ roles: ["owner"],
+ permissions: ["backend.schema.manage", "backend.data.read", "backend.data.write", "backend.data.admin", "backend.secrets.manage", "backend.backups.manage"],
+ });
+}
+
+test("backup snapshots are checksummed, omit secret values, and restore to a separate environment", () => {
+ const platform = managed.createInMemoryManagedPlatform({ signingKey: Buffer.alloc(32, 1), encryptionKey: Buffer.alloc(32, 2) });
+ platform.environments.ensure(SOURCE, owner(SOURCE));
+ const migration = platform.schema.plan(SOURCE, {
+ baseVersion: 0,
+ operations: [{ kind: "create-collection", collection: {
+ name: "alerts",
+ rowPolicy: "project",
+ fields: { title: { type: "string", required: true } },
+ indexes: [],
+ } }],
+ }, owner(SOURCE));
+ platform.schema.apply(SOURCE, migration, owner(SOURCE));
+ platform.data.create(SOURCE, "alerts", { title: "Whale swap" }, owner(SOURCE));
+ platform.secrets.create(SOURCE, { name: "PRIVATE_KEY", value: "never-export-this-value", allowedPurposes: ["function"] }, owner(SOURCE));
+
+ const backup = platform.backups.create(SOURCE, owner(SOURCE));
+ assert.match(backup.checksum, /^[a-f0-9]{64}$/);
+ assert.equal(JSON.stringify(backup).includes("never-export-this-value"), false);
+ const preview = platform.backups.previewRestore(backup.id, RESTORE, owner(RESTORE));
+ assert.equal(preview.targetEnvironment, "preview");
+ assert.equal(preview.secretReferencesRequireRotation, 1);
+ assert.deepEqual(preview.omittedComponents, ["auth-sessions", "function-manifests", "job-metadata", "object-bytes", "secret-values", "webhook-configuration"]);
+ assert.ok(preview.warnings.every((warning) => typeof warning === "string" && warning.length > 0));
+ platform.backups.restore(backup.id, RESTORE, owner(RESTORE));
+ assert.equal(platform.data.query(RESTORE, "alerts", { filters: [], limit: 10 }, owner(RESTORE)).rows[0].title, "Whale swap");
+ assert.equal(platform.secrets.list(RESTORE, owner(RESTORE))[0].status, "rotation-required");
+ const restoredSecret = platform.secrets.list(RESTORE, owner(RESTORE))[0];
+ assert.equal(platform.secrets.rotate(RESTORE, restoredSecret.id, "rotated-secret-value", owner(RESTORE)).currentVersion, 1);
+ platform.secrets.revoke(RESTORE, restoredSecret.id, owner(RESTORE));
+ assert.equal(platform.secrets.list(RESTORE, owner(RESTORE))[0].status, "revoked");
+
+ assert.throws(() => platform.backups.restore(backup.id, managed.managedScope({ ...SOURCE, environment: "production" }), owner(managed.managedScope({ ...SOURCE, environment: "production" }))), /approval/i);
+});
diff --git a/tests/managed-platform-data.test.mjs b/tests/managed-platform-data.test.mjs
new file mode 100644
index 0000000..3f971b1
--- /dev/null
+++ b/tests/managed-platform-data.test.mjs
@@ -0,0 +1,215 @@
+import assert from "node:assert/strict";
+import { registerHooks } from "node:module";
+import test from "node:test";
+
+registerHooks({
+ resolve(specifier, context, nextResolve) {
+ if (!specifier.startsWith("@/")) return nextResolve(specifier, context);
+ const path = specifier.slice(2);
+ return { shortCircuit: true, url: new URL(path.endsWith(".ts") ? path : `${path}.ts`, new URL("../", import.meta.url)).href };
+ },
+});
+
+const managed = await import("../lib/managed-platform/index.ts");
+
+const DEV = managed.managedScope({
+ organizationId: "org-alpha",
+ workspaceId: "workspace-research",
+ projectId: "team-whale-intelligence",
+ environment: "development",
+});
+const PREVIEW = managed.managedScope({ ...DEV, environment: "preview" });
+const PROD = managed.managedScope({ ...DEV, environment: "production" });
+
+function owner(scope = DEV, actorId = "owner-1") {
+ return managed.managedPrincipal({
+ actorId,
+ actorType: "user",
+ scope,
+ roles: ["owner"],
+ permissions: ["backend.schema.manage", "backend.data.read", "backend.data.write", "backend.data.admin", "backend.backups.manage"],
+ });
+}
+
+function member(scope = DEV, actorId = "member-1") {
+ return managed.managedPrincipal({
+ actorId,
+ actorType: "user",
+ scope,
+ roles: ["developer"],
+ permissions: ["backend.data.read", "backend.data.write"],
+ });
+}
+
+function watchlistMigration(baseVersion = 0) {
+ return {
+ baseVersion,
+ operations: [{
+ kind: "create-collection",
+ collection: {
+ name: "watchlists",
+ rowPolicy: "owner",
+ fields: {
+ name: { type: "string", required: true },
+ enabled: { type: "boolean", required: true, default: true },
+ tags: { type: "json", required: false },
+ },
+ indexes: [{ name: "watchlists_owner_name", fields: ["_ownerId", "name"], unique: true }],
+ },
+ }],
+ };
+}
+
+test("schema migrations are versioned, environment-isolated, and production protected", () => {
+ const platform = managed.createInMemoryManagedPlatform({
+ signingKey: Buffer.alloc(32, 7),
+ encryptionKey: Buffer.alloc(32, 9),
+ });
+ platform.environments.ensure(DEV, owner());
+ platform.environments.ensure(PREVIEW, owner(PREVIEW));
+ platform.environments.ensure(PROD, owner(PROD));
+
+ const plan = platform.schema.plan(DEV, watchlistMigration(), owner());
+ assert.equal(plan.fromVersion, 0);
+ assert.equal(plan.toVersion, 1);
+ assert.equal(plan.requiresApproval, false);
+ const applied = platform.schema.apply(DEV, plan, owner());
+ assert.equal(applied.version, 1);
+ assert.ok(applied.collections.watchlists);
+ assert.equal(platform.schema.snapshot(PREVIEW, owner(PREVIEW)).version, 0);
+
+ const productionPlan = platform.schema.plan(PROD, watchlistMigration(), owner(PROD));
+ assert.equal(productionPlan.requiresApproval, true);
+ assert.throws(() => platform.schema.apply(PROD, productionPlan, owner(PROD)), /approval/i);
+ assert.equal(platform.schema.apply(PROD, productionPlan, owner(PROD), { approvalReceipt: "approval_prod_20260730" }).version, 1);
+
+ assert.throws(() => platform.schema.apply(DEV, plan, owner()), /stale/i);
+});
+
+test("CRUD enforces schema, row scope, idempotency, revisions, and bounded queries", () => {
+ const platform = managed.createInMemoryManagedPlatform({
+ signingKey: Buffer.alloc(32, 7),
+ encryptionKey: Buffer.alloc(32, 9),
+ limits: { maxRowsPerEnvironment: 10, maxQueryComplexity: 8 },
+ });
+ platform.environments.ensure(DEV, owner());
+ platform.schema.apply(DEV, platform.schema.plan(DEV, watchlistMigration(), owner()), owner());
+
+ const alice = member(DEV, "alice");
+ const bob = member(DEV, "bob");
+ const created = platform.data.create(DEV, "watchlists", { name: "Whales", enabled: true }, alice, { idempotencyKey: "idem_watchlist_alice_1" });
+ const duplicate = platform.data.create(DEV, "watchlists", { name: "Whales", enabled: true }, alice, { idempotencyKey: "idem_watchlist_alice_1" });
+ assert.deepEqual(duplicate, created);
+ assert.equal(created._revision, 1);
+ assert.equal(created._ownerId, "alice");
+
+ assert.throws(() => platform.data.read(DEV, "watchlists", created._id, bob), /row scope/i);
+ assert.equal(platform.data.query(DEV, "watchlists", { filters: [], limit: 20 }, bob).rows.length, 0);
+ assert.equal(platform.data.read(DEV, "watchlists", created._id, owner()).name, "Whales");
+
+ const updated = platform.data.update(DEV, "watchlists", created._id, { name: "Smart money" }, alice, { expectedRevision: 1, idempotencyKey: "idem_watchlist_update_1" });
+ assert.equal(updated._revision, 2);
+ assert.equal(updated.name, "Smart money");
+ assert.throws(() => platform.data.update(DEV, "watchlists", created._id, { name: "Stale" }, alice, { expectedRevision: 1 }), /revision conflict/i);
+ assert.throws(() => platform.data.create(DEV, "watchlists", { name: "Smart money", enabled: true }, alice), /unique/i);
+ assert.throws(() => platform.data.create(DEV, "watchlists", { name: "Bad", enabled: "yes" }, alice), /boolean/i);
+ assert.throws(() => platform.data.query(DEV, "watchlists", {
+ filters: Array.from({ length: 9 }, (_, index) => ({ field: "name", operator: "eq", value: `x-${index}` })),
+ limit: 20,
+ }, alice), /complexity/i);
+
+ assert.throws(() => platform.data.read(PREVIEW, "watchlists", created._id, member(PREVIEW, "alice")), /environment|collection/i);
+});
+
+test("provider descriptors never claim D1 or Postgres is working without a configured driver", () => {
+ assert.deepEqual(managed.describeD1ManagedProvider(), {
+ kind: "d1-drizzle",
+ status: "setup-required",
+ reasonCode: "D1_BINDING_REQUIRED",
+ });
+ assert.deepEqual(managed.describePostgresManagedProvider(), {
+ kind: "postgres-drizzle",
+ status: "setup-required",
+ reasonCode: "DATABASE_URL_AND_DRIVER_REQUIRED",
+ });
+});
+
+test("create enforces roles row policy before persisting a record", () => {
+ const platform = managed.createInMemoryManagedPlatform({ signingKey: Buffer.alloc(32, 7), encryptionKey: Buffer.alloc(32, 9) });
+ platform.environments.ensure(DEV, owner());
+ const plan = platform.schema.plan(DEV, {
+ baseVersion: 0,
+ operations: [{ kind: "create-collection", collection: {
+ name: "operatorNotes",
+ rowPolicy: "roles",
+ allowedRoles: ["analyst"],
+ fields: { body: { type: "text", required: true } },
+ indexes: [],
+ } }],
+ }, owner());
+ platform.schema.apply(DEV, plan, owner());
+
+ assert.throws(
+ () => platform.data.create(DEV, "operatorNotes", { body: "must not persist" }, member()),
+ /row scope|denied/i,
+ );
+ assert.equal(platform.data.query(DEV, "operatorNotes", { limit: 10 }, owner()).rows.length, 0);
+});
+
+test("sparse unique indexes allow missing values but reject equal defined values", () => {
+ const platform = managed.createInMemoryManagedPlatform({ signingKey: Buffer.alloc(32, 7), encryptionKey: Buffer.alloc(32, 9) });
+ platform.environments.ensure(DEV, owner());
+ const plan = platform.schema.plan(DEV, {
+ baseVersion: 0,
+ operations: [{ kind: "create-collection", collection: {
+ name: "walletAliases",
+ rowPolicy: "project",
+ fields: {
+ label: { type: "string", required: true },
+ address: { type: "string" },
+ },
+ indexes: [{ name: "wallet_alias_address", fields: ["address"], unique: true }],
+ } }],
+ }, owner());
+ platform.schema.apply(DEV, plan, owner());
+
+ platform.data.create(DEV, "walletAliases", { label: "First" }, member());
+ platform.data.create(DEV, "walletAliases", { label: "Second" }, member());
+ platform.data.create(DEV, "walletAliases", { label: "Tracked", address: "0xabc" }, member());
+ assert.throws(() => platform.data.create(DEV, "walletAliases", { label: "Duplicate", address: "0xabc" }, member()), /unique/i);
+});
+
+test("numeric fields sort numerically instead of lexicographically", () => {
+ const platform = managed.createInMemoryManagedPlatform({ signingKey: Buffer.alloc(32, 7), encryptionKey: Buffer.alloc(32, 9) });
+ platform.environments.ensure(DEV, owner());
+ const plan = platform.schema.plan(DEV, {
+ baseVersion: 0,
+ operations: [{ kind: "create-collection", collection: {
+ name: "rankedSignals",
+ rowPolicy: "project",
+ fields: { score: { type: "float", required: true } },
+ indexes: [],
+ } }],
+ }, owner());
+ platform.schema.apply(DEV, plan, owner());
+ for (const score of [10, 2, 1]) platform.data.create(DEV, "rankedSignals", { score }, member());
+
+ assert.deepEqual(
+ platform.data.query(DEV, "rankedSignals", { sort: [{ field: "score", direction: "asc" }], limit: 10 }, member()).rows.map((row) => row.score),
+ [1, 2, 10],
+ );
+});
+
+test("provider health verification returns a bounded timeout status", async () => {
+ const never = new Promise(() => {});
+ const driver = {
+ kind: "d1-drizzle",
+ transaction: async (_scope, operation) => operation({ execute: async () => ({ rows: [], affectedRows: 0 }) }),
+ health: () => never,
+ };
+ const status = await Promise.race([
+ managed.verifyD1ManagedProvider(driver, { timeoutMs: 5 }),
+ new Promise((_resolve, reject) => setTimeout(() => reject(new Error("provider health check did not time out")), 30)),
+ ]);
+ assert.deepEqual(status, { kind: "d1-drizzle", status: "unavailable", reasonCode: "D1_HEALTH_TIMEOUT", latencyMs: 0 });
+});
diff --git a/tests/managed-platform-e2e-v4.test.mjs b/tests/managed-platform-e2e-v4.test.mjs
new file mode 100644
index 0000000..233a96e
--- /dev/null
+++ b/tests/managed-platform-e2e-v4.test.mjs
@@ -0,0 +1,128 @@
+import assert from "node:assert/strict";
+import { registerHooks } from "node:module";
+import test from "node:test";
+
+registerHooks({
+ resolve(specifier, context, nextResolve) {
+ if (!specifier.startsWith("@/")) return nextResolve(specifier, context);
+ const path = specifier.slice(2);
+ return { shortCircuit: true, url: new URL(path.endsWith(".ts") ? path : `${path}.ts`, new URL("../", import.meta.url)).href };
+ },
+});
+
+const { createProjectSpec } = await import("../lib/project-factory.ts");
+const { materializeProjectV2Template } = await import("../lib/project-template-materializer.ts");
+const { selectRuntimeSkills } = await import("../lib/agent/skills/index.ts");
+const managed = await import("../lib/managed-platform/index.ts");
+const enterprise = await import("../lib/enterprise-platform/index.ts");
+
+function deterministicRuntime() {
+ let sequence = 0;
+ return {
+ now: () => new Date("2026-07-30T12:00:00.000Z"),
+ id: (prefix) => `${prefix}-${String(++sequence).padStart(4, "0")}`,
+ token: () => `invite_${String(++sequence).padStart(4, "0")}_${"x".repeat(32)}`,
+ entropy: (label) => `${label}_${String(++sequence).padStart(4, "0")}_${"e".repeat(48)}`,
+ };
+}
+
+test("agent-generated collaborative crypto SaaS crosses V4 data, identity, collaboration, policy, audit and recovery", async () => {
+ const prompt = "Build a multi-user whale intelligence SaaS with organizations, RBAC, managed auth, wallet webhooks, scheduled enrichment jobs, realtime collaborative comments, audit history, backups and approved Telegram alerts.";
+ const spec = createProjectSpec({
+ presetId: "custom-product",
+ values: {},
+ prompt,
+ tools: ["DropsTab API", "Drops Bot", "Telegram"],
+ provider: "free",
+ model: "Free compiler",
+ market: [],
+ prediction: { title: "No prediction", probability: null, change: null },
+ origin: "https://drops-studio.example",
+ });
+ const project = await materializeProjectV2Template({ id: "e2e-whale-saas", spec, now: "2026-07-30T12:00:00.000Z" });
+ assert.ok(project.files["backend/schema.json"]);
+ assert.equal(project.integrations.find((item) => item.id === "managed-backend")?.status, "setup-required");
+
+ const selection = selectRuntimeSkills({
+ role: "planner",
+ task: prompt,
+ integrations: ["managed-backend", "managed-auth", "managed-jobs", "managed-webhooks", "managed-realtime", "collaboration", "organizations", "audit"],
+ availableCapabilities: ["project-v2", "project-data", "vercel-sandbox"],
+ maximumSkills: 12,
+ maximumEstimatedTokens: 4_800,
+ });
+ for (const skillId of ["managed-backend", "managed-auth", "webhooks", "jobs-and-cron", "collaboration", "enterprise-rbac", "audit-and-compliance"]) {
+ assert.ok(selection.skills.some((skill) => skill.id === skillId), `missing ${skillId}`);
+ }
+
+ const runtime = deterministicRuntime();
+ const directory = new enterprise.EnterpriseDirectory(runtime);
+ const tenant = directory.createOrganization({ ownerUserId: "owner", name: "Whale Research", kind: "organization" });
+ const invitation = directory.inviteMember({ actorUserId: "owner", organizationId: tenant.organization.id, workspaceId: tenant.workspace.id, email: "dev@example.com", roleId: "developer", expiresInMs: 60_000 });
+ directory.acceptInvitation({ token: invitation.token, userId: "developer", email: "dev@example.com" });
+ assert.equal(directory.can("developer", tenant.organization.id, "project.edit"), true);
+ assert.equal(directory.can("developer", tenant.organization.id, "billing.manage"), false);
+
+ const scope = managed.managedScope({ organizationId: tenant.organization.id, workspaceId: tenant.workspace.id, projectId: project.id, environment: "development" });
+ const restoreScope = managed.managedScope({ ...scope, environment: "preview" });
+ const principal = (actorId, targetScope = scope) => managed.managedPrincipal({
+ actorId,
+ actorType: "user",
+ scope: targetScope,
+ roles: actorId === "owner" ? ["owner"] : ["developer"],
+ permissions: actorId === "owner"
+ ? ["backend.schema.manage", "backend.data.read", "backend.data.write", "backend.data.admin", "backend.backups.manage"]
+ : ["backend.data.read", "backend.data.write"],
+ });
+ const dataPlane = managed.createInMemoryManagedPlatform({ signingKey: Buffer.alloc(32, 4), encryptionKey: Buffer.alloc(32, 8) });
+ dataPlane.environments.ensure(scope, principal("owner"));
+ const generatedSchema = JSON.parse(project.files["backend/schema.json"].content);
+ const migration = dataPlane.schema.plan(scope, {
+ baseVersion: 0,
+ operations: [{ kind: "create-collection", collection: { name: "alerts", ...generatedSchema.collections.alerts } }],
+ }, principal("owner"));
+ dataPlane.schema.apply(scope, migration, principal("owner"));
+ const alert = dataPlane.data.create(scope, "alerts", { status: "draft", score: 91.5, evidence: { dropstab: "demo-labelled", walletEventId: "evt-1" } }, principal("developer"), { idempotencyKey: "alert-evt-1" });
+ assert.equal(alert._revision, 1);
+ assert.equal(dataPlane.data.query(scope, "alerts", { filters: [{ field: "status", operator: "eq", value: "draft" }], limit: 10 }, principal("developer")).rows.length, 1);
+
+ const document = enterprise.createCollaborativeTextDocument("alert-copy", "Whale alert");
+ const alice = enterprise.createInsertOperations(document, { actorId: "owner", lamport: 2, index: 5, text: " verified" });
+ const bob = enterprise.createInsertOperations(document, { actorId: "developer", lamport: 2, index: 5, text: " sourced" });
+ const firstOrder = enterprise.renderCollaborativeText(enterprise.applyTextOperations(document, [...alice, ...bob]));
+ const secondOrder = enterprise.renderCollaborativeText(enterprise.applyTextOperations(document, [...bob, ...alice]));
+ assert.equal(firstOrder, secondOrder);
+ assert.match(firstOrder, /verified/);
+ assert.match(firstOrder, /sourced/);
+
+ const branches = new enterprise.AiBranchManager(runtime);
+ branches.createProject({ projectId: project.id, files: { "app/page.tsx": project.files["app/page.tsx"].content, "backend/schema.json": project.files["backend/schema.json"].content } });
+ const aiBranch = branches.createBranch({ projectId: project.id, taskOwnerId: "agent", taskScope: ["backend/**"] });
+ branches.writeBranchFile({ branchId: aiBranch.id, path: "backend/schema.json", content: project.files["backend/schema.json"].content.replace("draft", "queued") });
+ const approvalRequired = branches.mergeBranch({ branchId: aiBranch.id, actorUserId: "owner", approved: false });
+ assert.equal(approvalRequired.status, "approval-required");
+ const merged = branches.mergeBranch({ branchId: aiBranch.id, actorUserId: "owner", approved: true });
+ assert.equal(merged.status, "merged");
+
+ const oidc = new enterprise.LocalTestOidcAdapter({ issuer: "https://oidc.test.local", clientId: "drops-studio-test", allowedDomains: ["example.com"], groupRoleMappings: { developers: "developer" }, runtime });
+ const authorization = oidc.begin({ organizationId: tenant.organization.id, redirectUri: "https://studio.test/callback" });
+ const code = oidc.issueLocalTestCode({ state: authorization.state, nonce: authorization.nonce, claims: { subject: "developer", email: "dev@example.com", groups: ["developers"] } });
+ assert.equal(oidc.complete({ state: authorization.state, code, codeVerifier: authorization.codeVerifier }).roleId, "developer");
+
+ const policy = enterprise.resolveEnterprisePolicy({
+ systemHard: { allowedModelProviders: ["openai", "anthropic"], platformModelsAllowed: false, maxAgentCostPerRun: 10, allowedNetworkHosts: ["api.dropstab.com"], productionPublishRequiresApproval: true, exportAllowed: true },
+ organization: { allowedModelProviders: ["openai"], maxAgentCostPerRun: 3 },
+ });
+ assert.equal(enterprise.evaluateEnterprisePolicy(policy, { action: "production.publish" }).requiresApproval, true);
+ assert.equal(enterprise.evaluateEnterprisePolicy(policy, { action: "model.use", provider: "anthropic" }).allowed, false);
+
+ const audit = new enterprise.ImmutableAuditLog(runtime);
+ audit.append({ organizationId: tenant.organization.id, workspaceId: tenant.workspace.id, actorType: "agent", actorId: "agent", action: "branch.merge", targetType: "project", targetId: project.id, outcome: "success", requestId: "e2e-v4", metadata: { checkpointId: merged.checkpointId, policyHash: policy.policyHash } });
+ assert.equal(audit.verifyIntegrity(), true);
+
+ dataPlane.secrets.create(scope, { name: "BACKUP_SCAN", value: "verified-backup-secret-plaintext", allowedPurposes: ["function"] }, principal("owner"));
+ const backup = dataPlane.backups.create(scope, principal("owner"));
+ assert.equal(JSON.stringify(backup).includes("verified-backup-secret-plaintext"), false);
+ dataPlane.backups.restore(backup.id, restoreScope, principal("owner", restoreScope));
+ assert.equal(dataPlane.data.query(restoreScope, "alerts", { filters: [], limit: 10 }, principal("owner", restoreScope)).rows[0].score, 91.5);
+});
diff --git a/tests/managed-platform-services.test.mjs b/tests/managed-platform-services.test.mjs
new file mode 100644
index 0000000..ab0047a
--- /dev/null
+++ b/tests/managed-platform-services.test.mjs
@@ -0,0 +1,243 @@
+import assert from "node:assert/strict";
+import { createHmac } from "node:crypto";
+import { registerHooks } from "node:module";
+import test from "node:test";
+
+registerHooks({
+ resolve(specifier, context, nextResolve) {
+ if (!specifier.startsWith("@/")) return nextResolve(specifier, context);
+ const path = specifier.slice(2);
+ return { shortCircuit: true, url: new URL(path.endsWith(".ts") ? path : `${path}.ts`, new URL("../", import.meta.url)).href };
+ },
+});
+
+const managed = await import("../lib/managed-platform/index.ts");
+
+const SCOPE = managed.managedScope({ organizationId: "org", workspaceId: "workspace", projectId: "project", environment: "development" });
+const OTHER_ENV = managed.managedScope({ ...SCOPE, environment: "preview" });
+
+function principal(scope = SCOPE, actorId = "owner") {
+ return managed.managedPrincipal({
+ actorId,
+ actorType: "user",
+ scope,
+ roles: ["owner"],
+ permissions: [
+ "backend.auth.manage", "backend.storage.manage", "backend.functions.manage", "backend.functions.invoke",
+ "backend.webhooks.manage", "backend.webhooks.replay", "backend.jobs.manage", "backend.cron.manage",
+ "backend.realtime.read", "backend.realtime.publish", "backend.secrets.manage", "backend.logs.read", "backend.backups.manage",
+ "backend.schema.manage", "backend.data.read", "backend.data.write", "backend.data.admin",
+ ],
+ });
+}
+
+function platformAt(clock) {
+ return managed.createInMemoryManagedPlatform({
+ signingKey: Buffer.alloc(32, 3),
+ encryptionKey: Buffer.alloc(32, 4),
+ now: () => new Date(clock.value),
+ });
+}
+
+test("managed auth is project-scoped, CSRF-bound, revocable, and honest without email delivery", async () => {
+ const clock = { value: Date.parse("2026-07-30T10:00:00.000Z") };
+ const platform = platformAt(clock);
+ platform.environments.ensure(SCOPE, principal());
+ assert.deepEqual(await platform.auth.requestEmailCode(SCOPE, "user@example.com", principal()), { status: "setup-required", reasonCode: "EMAIL_ADAPTER_REQUIRED" });
+
+ const user = platform.auth.createUser(SCOPE, { email: "user@example.com", roles: ["developer"], profile: { name: "User" } }, principal());
+ const session = platform.auth.createSession(SCOPE, user.id, principal(), { ttlSeconds: 600 });
+ assert.ok(session.token.startsWith("ds_session_"));
+ assert.ok(session.csrfToken.startsWith("ds_csrf_"));
+ assert.equal(platform.auth.verifySession(SCOPE, session.token, { write: true, csrfToken: session.csrfToken }).userId, user.id);
+ assert.throws(() => platform.auth.verifySession(SCOPE, session.token, { write: true, csrfToken: "wrong" }), /CSRF/i);
+ assert.throws(() => platform.auth.verifySession(OTHER_ENV, session.token), /scope|environment/i);
+ platform.auth.revokeSession(SCOPE, session.id, principal());
+ assert.throws(() => platform.auth.verifySession(SCOPE, session.token), /revoked/i);
+});
+
+test("secret vault exposes only metadata and signed object capabilities cannot cross environments", () => {
+ const clock = { value: Date.parse("2026-07-30T10:00:00.000Z") };
+ const platform = platformAt(clock);
+ platform.environments.ensure(SCOPE, principal());
+ platform.environments.ensure(OTHER_ENV, principal(OTHER_ENV));
+ const secret = platform.secrets.create(SCOPE, { name: "DROPSTAB_KEY", value: "synthetic-test-secret-value", allowedPurposes: ["function", "webhook"] }, principal());
+ assert.equal(secret.masked, "••••••••");
+ assert.equal(JSON.stringify(secret).includes("synthetic-test-secret-value"), false);
+ assert.equal(platform.secrets.resolveForRuntime, undefined);
+
+ const object = platform.storage.put(SCOPE, {
+ key: "attachments/report.json",
+ contentType: "application/json",
+ visibility: "private",
+ bytes: Buffer.from("{\"ok\":true}"),
+ }, principal());
+ const capability = platform.storage.signCapability(SCOPE, object.id, "read", principal(), { ttlSeconds: 60 });
+ assert.deepEqual(platform.storage.read(SCOPE, capability, principal()).bytes, Buffer.from("{\"ok\":true}"));
+ assert.throws(() => platform.storage.read(OTHER_ENV, capability, principal(OTHER_ENV)), /scope|environment/i);
+ clock.value += 61_000;
+ assert.throws(() => platform.storage.read(SCOPE, capability, principal()), /expired/i);
+});
+
+test("functions, signed webhooks, jobs, cron, realtime, and logs use real in-memory evidence without leaking secrets", async () => {
+ const clock = { value: Date.parse("2026-07-30T10:00:00.000Z") };
+ const runtime = new managed.InMemoryFunctionRuntime({
+ summarize: async (input) => ({ count: input.events.length, authorization: "must-redact" }),
+ });
+ const platform = managed.createInMemoryManagedPlatform({
+ signingKey: Buffer.alloc(32, 3),
+ encryptionKey: Buffer.alloc(32, 4),
+ now: () => new Date(clock.value),
+ functionRuntime: runtime,
+ });
+ platform.environments.ensure(SCOPE, principal());
+ platform.functions.register(SCOPE, {
+ name: "summarize",
+ version: 1,
+ timeoutMs: 1_000,
+ input: { events: "json" },
+ output: { count: "integer", authorization: "string" },
+ allowedNetworkHosts: [],
+ secretReferences: [],
+ }, principal());
+ const invocation = await platform.functions.invoke(SCOPE, "summarize", { events: [{ id: 1 }] }, principal());
+ assert.equal(invocation.status, "succeeded");
+ assert.equal(invocation.output.count, 1);
+
+ const webhookSecret = platform.secrets.create(SCOPE, { name: "WEBHOOK_SIGNING", value: "webhook-secret-value", allowedPurposes: ["webhook"] }, principal());
+ const endpoint = platform.webhooks.register(SCOPE, { name: "dropsbot", signingSecretId: webhookSecret.id, eventType: "wallet.event" }, principal());
+ const body = JSON.stringify({ wallet: "0xabc", authorization: "Bearer should-redact" });
+ const timestamp = Math.floor(clock.value / 1000);
+ const signature = createHmac("sha256", "webhook-secret-value").update(`${timestamp}.nonce-1.${body}`).digest("hex");
+ const accepted = platform.webhooks.receive(SCOPE, endpoint.id, { body, timestamp, nonce: "nonce-1", signature });
+ assert.equal(accepted.status, "accepted");
+ assert.throws(() => platform.webhooks.receive(SCOPE, endpoint.id, { body, timestamp, nonce: "nonce-1", signature }), /replay/i);
+
+ const firstJob = platform.jobs.enqueue(SCOPE, { type: "morning-summary", payload: { eventId: accepted.eventId }, idempotencyKey: "summary-event-1", maxAttempts: 2 }, principal());
+ const sameJob = platform.jobs.enqueue(SCOPE, { type: "morning-summary", payload: { eventId: accepted.eventId }, idempotencyKey: "summary-event-1", maxAttempts: 2 }, principal());
+ assert.equal(sameJob.id, firstJob.id);
+ const ran = await platform.jobs.runNext(SCOPE, { "morning-summary": async () => ({ delivered: false, setupRequired: "Telegram approval required" }) });
+ assert.equal(ran.status, "succeeded");
+
+ const schedule = platform.cron.create(SCOPE, { name: "morning", expression: "0 8 * * *", timezone: "UTC", jobType: "morning-summary", enabled: true, overlapPolicy: "skip" }, principal());
+ assert.equal(schedule.nextRunAt, "2026-07-31T08:00:00.000Z");
+ assert.throws(() => platform.cron.create(managed.managedScope({ ...SCOPE, environment: "production" }), { name: "unsafe", expression: "* * * * *", timezone: "UTC", jobType: "x", enabled: true, overlapPolicy: "skip" }, principal(managed.managedScope({ ...SCOPE, environment: "production" }))), /approval/i);
+
+ const subscription = platform.realtime.subscribe(SCOPE, { collection: "walletEvents" }, principal());
+ platform.realtime.publish(SCOPE, "walletEvents", "created", { id: "event-1" }, principal());
+ assert.equal(platform.realtime.poll(SCOPE, subscription, principal()).events[0].sequence, 1);
+ assert.throws(() => platform.realtime.poll(OTHER_ENV, subscription, principal(OTHER_ENV)), /scope|environment/i);
+
+ const logs = platform.logs.list(SCOPE, principal());
+ const serialized = JSON.stringify(logs);
+ assert.equal(serialized.includes("webhook-secret-value"), false);
+ assert.equal(serialized.includes("Bearer should-redact"), false);
+ assert.equal(serialized.includes("must-redact"), false);
+ assert.ok(serialized.includes("[REDACTED]"));
+});
+
+test("email challenges are hashed, expiring, attempt-bounded, and single use", async () => {
+ const clock = { value: Date.parse("2026-07-30T10:00:00.000Z") };
+ let deliveredCode = "";
+ const platform = managed.createInMemoryManagedPlatform({
+ signingKey: Buffer.alloc(32, 3),
+ encryptionKey: Buffer.alloc(32, 4),
+ now: () => new Date(clock.value),
+ emailAdapter: {
+ mode: "test",
+ async deliverOneTimeCode(input) {
+ deliveredCode = input.code;
+ return { evidenceId: "email_evidence_1" };
+ },
+ },
+ });
+ platform.environments.ensure(SCOPE, principal());
+ const sent = await platform.auth.requestEmailCode(SCOPE, "USER@example.com", principal());
+ assert.equal(sent.status, "sent");
+ assert.match(sent.challengeId, /^email_challenge_/);
+ assert.equal(JSON.stringify(sent).includes(deliveredCode), false);
+ assert.throws(() => platform.auth.verifyEmailCode(SCOPE, { challengeId: sent.challengeId, email: "user@example.com", code: "000000" }, principal()), /code/i);
+ assert.equal(platform.auth.verifyEmailCode(SCOPE, { challengeId: sent.challengeId, email: "user@example.com", code: deliveredCode }, principal()).status, "verified");
+ assert.throws(() => platform.auth.verifyEmailCode(SCOPE, { challengeId: sent.challengeId, email: "user@example.com", code: deliveredCode }, principal()), /used|consumed/i);
+
+ const expiring = await platform.auth.requestEmailCode(SCOPE, "other@example.com", principal());
+ clock.value += 10 * 60_000 + 1;
+ assert.throws(() => platform.auth.verifyEmailCode(SCOPE, { challengeId: expiring.challengeId, email: "other@example.com", code: deliveredCode }, principal()), /expired/i);
+});
+
+test("guest creation and auth metadata require scoped permission and enforce quotas", () => {
+ const platform = managed.createInMemoryManagedPlatform({
+ signingKey: Buffer.alloc(32, 3),
+ encryptionKey: Buffer.alloc(32, 4),
+ limits: { maxGuestUsersPerEnvironment: 1 },
+ });
+ platform.environments.ensure(SCOPE, principal());
+ const reader = managed.managedPrincipal({ actorId: "reader", actorType: "user", scope: SCOPE, roles: ["viewer"], permissions: ["backend.data.read"] });
+ assert.throws(() => platform.auth.createGuest(SCOPE, reader), /permission/i);
+ platform.auth.createGuest(SCOPE, principal());
+ assert.throws(() => platform.auth.createGuest(SCOPE, principal()), /quota/i);
+ assert.throws(() => platform.auth.exportMetadata(SCOPE, reader), /permission/i);
+ assert.throws(() => platform.auth.importMetadata(SCOPE, { users: [] }, reader), /permission/i);
+});
+
+test("storage enforces object count and aggregate byte quotas per environment", () => {
+ const platform = managed.createInMemoryManagedPlatform({
+ signingKey: Buffer.alloc(32, 3),
+ encryptionKey: Buffer.alloc(32, 4),
+ limits: { maxObjectBytes: 8, maxObjectsPerEnvironment: 2, maxObjectBytesPerEnvironment: 10 },
+ });
+ platform.environments.ensure(SCOPE, principal());
+ const put = (key, bytes) => platform.storage.put(SCOPE, { key, contentType: "text/plain", visibility: "private", bytes: Buffer.from(bytes) }, principal());
+ put("one.txt", "12345");
+ put("two.txt", "12345");
+ assert.throws(() => put("three.txt", "1"), /quota/i);
+
+ const aggregate = managed.createInMemoryManagedPlatform({
+ signingKey: Buffer.alloc(32, 5),
+ encryptionKey: Buffer.alloc(32, 6),
+ limits: { maxObjectBytes: 8, maxObjectsPerEnvironment: 10, maxObjectBytesPerEnvironment: 9 },
+ });
+ aggregate.environments.ensure(SCOPE, principal());
+ aggregate.storage.put(SCOPE, { key: "one.txt", contentType: "text/plain", visibility: "private", bytes: Buffer.from("12345") }, principal());
+ assert.throws(() => aggregate.storage.put(SCOPE, { key: "two.txt", contentType: "text/plain", visibility: "private", bytes: Buffer.from("12345") }, principal()), /quota/i);
+});
+
+test("webhook replay cache prunes entries outside the replay window", () => {
+ const clock = { value: Date.parse("2026-07-30T10:00:00.000Z") };
+ const platform = platformAt(clock);
+ platform.environments.ensure(SCOPE, principal());
+ const secret = platform.secrets.create(SCOPE, { name: "WEBHOOK_CACHE", value: "cache-secret-value", allowedPurposes: ["webhook"] }, principal());
+ const endpoint = platform.webhooks.register(SCOPE, { name: "cache", signingSecretId: secret.id, eventType: "cache.event" }, principal());
+ const send = (nonce) => {
+ const body = JSON.stringify({ nonce });
+ const timestamp = Math.floor(clock.value / 1000);
+ const signature = createHmac("sha256", "cache-secret-value").update(`${timestamp}.${nonce}.${body}`).digest("hex");
+ return platform.webhooks.receive(SCOPE, endpoint.id, { body, timestamp, nonce, signature });
+ };
+ send("nonce-old");
+ assert.equal(platform.webhooks.replayKeys.size, 1);
+ clock.value += 301_000;
+ send("nonce-new");
+ assert.equal(platform.webhooks.replayKeys.size, 1);
+});
+
+test("realtime publish requires scoped write permission and polling hides cross-scope subscriptions", () => {
+ const platform = platformAt({ value: Date.parse("2026-07-30T10:00:00.000Z") });
+ platform.environments.ensure(SCOPE, principal());
+ const reader = managed.managedPrincipal({ actorId: "reader", actorType: "user", scope: SCOPE, roles: ["viewer"], permissions: ["backend.realtime.read"] });
+ const subscription = platform.realtime.subscribe(SCOPE, { collection: "signals" }, reader);
+ assert.throws(() => platform.realtime.publish(SCOPE, "signals", "created", { id: "denied" }, reader), /permission/i);
+ platform.realtime.publish(SCOPE, "signals", "created", { id: "allowed" }, principal());
+ assert.equal(platform.realtime.poll(SCOPE, subscription, reader).events.length, 1);
+ assert.throws(() => platform.realtime.poll(OTHER_ENV, subscription, principal(OTHER_ENV)), /does not exist|scope/i);
+});
+
+test("cron follows Vixie day-of-month/day-of-week semantics and validates disabled schedules", () => {
+ assert.equal(
+ managed.nextCronOccurrence("0 0 1 * 1", "UTC", new Date("2026-08-01T00:01:00.000Z")),
+ "2026-08-03T00:00:00.000Z",
+ );
+ const platform = platformAt({ value: Date.parse("2026-07-30T10:00:00.000Z") });
+ assert.throws(() => platform.cron.create(SCOPE, { name: "disabled", expression: "invalid", timezone: "UTC", jobType: "noop", enabled: false, overlapPolicy: "skip" }, principal()), /cron/i);
+ assert.throws(() => platform.cron.create(SCOPE, { name: "disabled", expression: "0 0 * * *", timezone: "Mars/Olympus", jobType: "noop", enabled: false, overlapPolicy: "skip" }, principal()), /timezone/i);
+});
diff --git a/tests/platform-capabilities.test.mjs b/tests/platform-capabilities.test.mjs
new file mode 100644
index 0000000..1d6ae50
--- /dev/null
+++ b/tests/platform-capabilities.test.mjs
@@ -0,0 +1,42 @@
+import assert from "node:assert/strict";
+import test from "node:test";
+
+import { platformCapabilitySnapshot } from "../lib/platform-capabilities.ts";
+
+test("capability snapshot never upgrades reference adapters to production", () => {
+ const snapshot = platformCapabilitySnapshot({ VERCEL_ENV: "production" }, new Date("2026-07-30T12:00:00.000Z"));
+ assert.equal(snapshot.environment, "production");
+ assert.equal(snapshot.capabilities.find((item) => item.id === "managed-backend")?.state, "working-local-test");
+ assert.equal(snapshot.capabilities.find((item) => item.id === "collaboration")?.state, "working-local-test");
+ assert.equal(snapshot.capabilities.find((item) => item.id === "enterprise-identity")?.state, "working-local-test");
+ assert.equal(snapshot.capabilities.find((item) => item.id === "sandbox")?.state, "setup-required");
+});
+
+test("server configuration markers never become working without provider health evidence", () => {
+ const secret = "this-value-must-never-be-returned";
+ const snapshot = platformCapabilitySnapshot({
+ VERCEL_ENV: "preview",
+ VERCEL_OIDC_TOKEN: secret,
+ BLOB_STORE_ID: "store-id",
+ PROJECT_DATA_CAPABILITY_SECRET: secret,
+ DROPS_TEAM_INVITE_SECRET: secret,
+ VERCEL_DEPLOY_TOKEN: secret,
+ VERCEL_GENERATED_PROJECT_ID: "project-id",
+ }, new Date("2026-07-30T12:00:00.000Z"));
+ const serialized = JSON.stringify(snapshot);
+ assert.equal(snapshot.capabilities.find((item) => item.id === "sandbox")?.state, "unavailable");
+ assert.equal(snapshot.capabilities.find((item) => item.id === "project-data")?.state, "unavailable");
+ assert.equal(snapshot.capabilities.find((item) => item.id === "organizations")?.state, "unavailable");
+ assert.equal(snapshot.capabilities.find((item) => item.id === "deployment")?.state, "unavailable");
+ assert.doesNotMatch(serialized, new RegExp(secret));
+});
+
+test("explicit non-production project-data proof mode remains local-test only", () => {
+ const snapshot = platformCapabilitySnapshot({
+ VERCEL_ENV: "development",
+ DROPS_STUDIO_LOCAL_PROJECT_DATA: "1",
+ }, new Date("2026-07-30T12:00:00.000Z"));
+ const projectData = snapshot.capabilities.find((item) => item.id === "project-data");
+ assert.equal(projectData?.state, "working-local-test");
+ assert.equal(projectData?.mode, "process-memory-local-test");
+});
diff --git a/tests/platform-public-surfaces.test.mjs b/tests/platform-public-surfaces.test.mjs
new file mode 100644
index 0000000..f58327b
--- /dev/null
+++ b/tests/platform-public-surfaces.test.mjs
@@ -0,0 +1,84 @@
+import assert from "node:assert/strict";
+import { readFile } from "node:fs/promises";
+import test from "node:test";
+
+import { presets } from "../lib/presets.ts";
+
+const paths = ["templates", "projects", "backend", "integrations", "organizations", "enterprise", "platform"];
+
+test("public platform routes use the shared shell and remain explicit product surfaces", async () => {
+ const pages = await Promise.all(paths.map((path) => readFile(new URL(`../app/${path}/page.tsx`, import.meta.url), "utf8")));
+ for (const [index, source] of pages.entries()) {
+ assert.match(source, /PlatformShell/);
+ assert.match(source, /PageIntro/);
+ assert.doesNotMatch(source, /(? {
+ const source = await readFile(new URL("../components/platform/organization-console.tsx", import.meta.url), "utf8");
+ assert.match(source, /fetch\("\/api\/teams"/);
+ assert.match(source, /method: "POST"/);
+ assert.match(source, /Sign in required/);
+ assert.match(source, /Setup required/);
+ assert.doesNotMatch(source, /sampleMembers|fakePresence|mockOrganization/i);
+});
+
+test("backend and enterprise surfaces read the server capability API", async () => {
+ const [consoleSource, routeSource] = await Promise.all([
+ readFile(new URL("../components/platform/platform-capability-console.tsx", import.meta.url), "utf8"),
+ readFile(new URL("../app/api/platform/capabilities/route.ts", import.meta.url), "utf8"),
+ ]);
+ assert.match(consoleSource, /fetch\("\/api\/platform\/capabilities"/);
+ assert.match(consoleSource, /Data.*Schema.*Auth.*Storage.*Functions.*Jobs.*Cron.*Webhooks.*Realtime.*Secrets.*Logs.*Backups.*Settings/s);
+ assert.match(consoleSource, /Organizations.*Roles & RBAC.*Collaboration.*Identity.*Service accounts.*Policies.*Audit.*Lifecycle/s);
+ assert.match(routeSource, /platformCapabilitySnapshot/);
+ assert.doesNotMatch(consoleSource, /mockCapability|fakeReceipt|sampleOrganization/i);
+});
+
+test("platform overview renders the server snapshot instead of hardcoded readiness", async () => {
+ const [pageSource, overviewSource] = await Promise.all([
+ readFile(new URL("../app/platform/page.tsx", import.meta.url), "utf8"),
+ readFile(new URL("../components/platform/platform-overview.tsx", import.meta.url), "utf8"),
+ ]);
+ assert.match(pageSource, /platformCapabilitySnapshot\(\)/);
+ assert.match(pageSource, /PlatformOverview snapshot=\{snapshot\}/);
+ assert.match(overviewSource, /snapshot\.capabilities\.find/);
+ assert.doesNotMatch(overviewSource, /title: "Vercel Sandbox", status:/);
+});
+
+test("template catalog is wired to the canonical twelve recipes", async () => {
+ assert.equal(presets.length, 12);
+ const source = await readFile(new URL("../components/platform/template-catalog.tsx", import.meta.url), "utf8");
+ assert.match(source, /import \{ presets \} from "@\/lib\/presets"/);
+ assert.match(source, /presets\.filter/);
+ assert.doesNotMatch(source, /sampleTemplates|mockTemplates|placeholderTemplates/i);
+});
+
+test("project and integration surfaces read real browser state without exposing values", async () => {
+ const [projects, integrations] = await Promise.all([
+ readFile(new URL("../components/platform/project-library.tsx", import.meta.url), "utf8"),
+ readFile(new URL("../components/platform/integration-catalog.tsx", import.meta.url), "utf8"),
+ ]);
+ assert.match(projects, /readProjectsFromStore/);
+ assert.match(projects, /\/studio\/\$\{encodeURIComponent\(project\.id\)\}/);
+ assert.match(integrations, /sessionStorage\.getItem/);
+ assert.match(integrations, /Setup required/);
+ assert.doesNotMatch(integrations, /setSessionConnections\([^)]*getItem/);
+});
+
+test("shared public surfaces preserve target and typography contracts", async () => {
+ const sources = await Promise.all([
+ "platform-shell.tsx",
+ "platform-ui.tsx",
+ "template-catalog.tsx",
+ "project-library.tsx",
+ "integration-catalog.tsx",
+ "platform-overview.tsx",
+ "platform-capability-console.tsx",
+ ].map((file) => readFile(new URL(`../components/platform/${file}`, import.meta.url), "utf8")));
+ const source = sources.join("\n");
+ assert.match(source, /min-h-11/);
+ assert.doesNotMatch(source, /text-\[(?:[0-9]|1[01])px\]/);
+ assert.doesNotMatch(source, /text-\[0\.[0-9]+rem\]/);
+});
diff --git a/tests/project-template-materializer.test.mjs b/tests/project-template-materializer.test.mjs
index 406de0e..9bd12b7 100644
--- a/tests/project-template-materializer.test.mjs
+++ b/tests/project-template-materializer.test.mjs
@@ -11,8 +11,10 @@ registerHooks({
});
const { projectPresetIds } = await import("../lib/presets.ts");
+const { findArtifactSecrets } = await import("../lib/artifact-security.ts");
const { createProjectSpec } = await import("../lib/project-factory.ts");
const { materializeProjectV2Template } = await import("../lib/project-template-materializer.ts");
+const { projectTemplateComponentSource } = await import("../lib/project-template-ui.ts");
const { validateProjectV2 } = await import("../lib/project-v2-validator.ts");
const categoryExpectations = {
@@ -145,3 +147,66 @@ test("mandatory vertical demos contain their real category interactions and hone
for (const text of evidence) assert.match(source, new RegExp(text, "i"), `${presetId}: ${text}`);
}
});
+
+test("custom collaborative SaaS prompts materialize a secret-free managed backend contract", async () => {
+ const spec = createProjectSpec({
+ presetId: "custom-product",
+ values: {},
+ prompt: "Build a multi-user whale intelligence SaaS with organizations, RBAC, auth, collaborative comments, wallet webhooks, jobs, realtime updates, audit and approved Telegram alerts.",
+ tools: ["DropsTab API", "Drops Bot", "Telegram"],
+ provider: "free",
+ model: "Free compiler",
+ market: [],
+ prediction: { title: "No prediction", probability: null, change: null },
+ origin: "https://drops-studio.example",
+ });
+ const project = await materializeProjectV2Template({ id: "managed-collaborative-saas", spec, now: "2026-07-30T12:00:00.000Z" });
+ const manifest = JSON.parse(project.files["backend/manifest.json"].content);
+ const schema = JSON.parse(project.files["backend/schema.json"].content);
+ const policies = JSON.parse(project.files["backend/policies.json"].content);
+ const integration = project.integrations.find((item) => item.id === "managed-backend");
+ const environment = project.environment.find((item) => item.name === "DROPS_MANAGED_PROJECT_CAPABILITY");
+
+ assert.equal(manifest.productionProvider, "setup-required-until-health-receipt");
+ assert.deepEqual(Object.keys(schema.collections), ["wallet_events", "alerts", "comments", "workflow_items"]);
+ assert.ok(policies.approvals.includes("telegram.publish"));
+ assert.equal(integration?.status, "setup-required");
+ assert.ok(integration?.capabilities.includes("collaboration"));
+ assert.ok(integration?.capabilities.includes("enterprise-policy"));
+ assert.equal(environment?.secret, true);
+ assert.match(project.files["app/api/backend/status/route.ts"].content, /\.\.\/\.\.\/\.\.\/\.\.\/lib\/drops-managed-server/);
+ assert.match(project.files["lib/drops-managed-server.ts"].content, /target\.origin !== origin/);
+ assert.match(project.files["lib/drops-managed-server.ts"].content, /redirect: "error"/);
+ assert.match(project.files["app/api/backend/collections/[collection]/route.ts"].content, /ALLOWED_COLLECTIONS/);
+ assert.match(project.files["lib/use-managed-collection.ts"].content, /Browser-local demo · cloud setup required/);
+ assert.match(project.files["components/crypto-product.tsx"].content, /useManagedCollection\("workflow_items"\)/);
+ assert.ok(project.files["tests/managed-backend-manifest.test.mjs"]);
+ const generatedSource = Object.values(project.files).map((file) => file.content).join("\n");
+ assert.deepEqual(findArtifactSecrets(generatedSource, "managed collaborative template"), []);
+ assert.equal((await validateProjectV2(project)).contentHash, project.contentHash);
+});
+
+test("dynamic template values are inserted verbatim without replacement-token expansion", () => {
+ const spec = createProjectSpec({
+ presetId: "custom-product",
+ values: {},
+ prompt: "Build a custom product",
+ tools: [],
+ provider: "free",
+ model: "Free compiler",
+ market: [],
+ prediction: { title: "No prediction", probability: null, change: null },
+ origin: "https://drops-studio.example",
+ });
+ const source = projectTemplateComponentSource(spec, {
+ eyebrow: "VALUE $&",
+ headline: "Literal $` and $'",
+ description: "Replacement tokens stay data",
+ primaryAction: "Create",
+ metrics: ["One", "Two", "Three"],
+ blocks: ["A", "B", "C"],
+ });
+ assert.match(source, /VALUE \$&/);
+ assert.match(source, /Literal \$` and \$'/);
+ assert.doesNotMatch(source, /__PRODUCT_MODEL__|__MANAGED_IMPORT__/);
+});
diff --git a/tests/rendered-html.test.mjs b/tests/rendered-html.test.mjs
index 932cf39..3f77bd9 100644
--- a/tests/rendered-html.test.mjs
+++ b/tests/rendered-html.test.mjs
@@ -11,7 +11,7 @@ test("Next production build emits the complete Drops Studio builder HTML", async
"utf8",
);
assert.match(html, /Drops Studio/);
- assert.match(html, /Turn a crypto idea/);
+ assert.match(html, /Build crypto apps 10x faster with AI/);
assert.match(html, /AI Morning Alpha/);
assert.match(html, /Action Engine/);
assert.match(html, /Crypto Aggregator/);