Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/sql-host-storage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@caplets/core": minor
"caplets": minor
---

Add authoritative SQLite and PostgreSQL host storage, relational Caplet Records with Markdown import/export, and SQL-backed host administration across CLI, HTTP, and remote-control surfaces.
14 changes: 11 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,17 @@

## Agent skills

- Issues and PRDs live in GitHub Issues for `spiritledsoftware/caplets`; use `gh` with `--repo spiritledsoftware/caplets`. Details: `docs/agents/issue-tracker.md`.
- Triage labels are documented in `docs/agents/triage-labels.md`; current live labels include `question` for `needs-info` and `wontfix` for `wontfix`.
- This is a single-context repo. Start with `CONTEXT.md`, relevant ADRs in `docs/adr/`, and `docs/agents/domain.md`; use `STRATEGY.md`, `CONCEPTS.md`, and `docs/solutions/` when the task touches product direction, vocabulary, or documented patterns.
### Issue tracker

Issues and PRDs live in GitHub Issues for `spiritledsoftware/caplets`; use `gh` with `--repo spiritledsoftware/caplets`. See `docs/agents/issue-tracker.md`.

### Triage labels

Use the five canonical triage labels without overrides. See `docs/agents/triage-labels.md`.

### Domain docs

This is a single-context repo: start with root `CONTEXT.md` and relevant ADRs in `docs/adr/`. See `docs/agents/domain.md`.

## Test Quality Bar

Expand Down
4 changes: 2 additions & 2 deletions CONCEPTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ In v1, Catalog Presentation Metadata is limited to `catalog.icon`, which may ide

### Caplets Lockfile

A `caplets.lock.json` file that records installed catalog Caplets, their source repository, source path, destination, tracked source channel, resolved revision when available, content hash, and portability status.
A project-local `.caplets.lock.json` file that records installed project Caplets, their source repository, source path, destination, tracked source channel, resolved revision when available, content hash, and portability status.

Caplets Lockfiles let `caplets install`, no-argument install restore, and `caplets update` manage installed Caplets from recorded provenance rather than from copied files alone. Project installs use `./.caplets.lock.json`; global installs use the target machine's Caplets state directory.
Caplets Lockfiles let project `caplets install`, no-argument restore, and `caplets update` manage filesystem Caplets from recorded provenance rather than from copied files alone. Global and remote SQL-backed Caplets use Caplet Installations instead.

Caplets Lockfiles are share-safe and integrity-aware. They strip credential-bearing source URLs, prefer project-relative paths where possible, verify recorded content before restore, and fail closed when local-source entries are unavailable or marked non-portable.

Expand Down
32 changes: 32 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,38 @@ _Avoid_: Caplets utility namespace, custom helper surface
The first compatibility-global set for Code Mode: URL and query handling, text encoding, base64 and Buffer-compatible byte handling, web binary payload objects, safe crypto primitives, timing, scheduling, and structured cloning.
_Avoid_: Minimal encoding helpers, full Node compatibility

**Caplet Record**:
The canonical structured form of a Caplet persisted in runtime-owned storage. A Caplet File is its portable Markdown import/export projection, not the stored record itself.
_Avoid_: SQL Caplet File, stored Caplet File

**Caplet Revision**:
An immutable saved version of a Caplet Record's structured content and Caplet Bundle Snapshot; the record's current Caplet ID is stable metadata outside content history. A record selects one current revision; removing it promotes the newest remaining revision, while removing the sole revision removes the record.
_Avoid_: Mutable draft, audit-log entry

**Caplet Installation**:
A provenance-bearing lifecycle that connects a Caplet Record to an external source and update channel. Detaching ends update tracking without erasing that lifecycle; replacing a detached install starts a new Caplet Installation.
_Avoid_: Global lockfile entry, permanent source ownership

**Caplet Bundle Snapshot**:
A portable normalized snapshot of every regular auxiliary file in a Caplet bundle, including its relative path, content identity, and executable intent; `CAPLET.md` is represented by the Caplet Record instead. Safe in-bundle symlinks become regular files; escaping or broken links and special files are invalid.
_Avoid_: Bundle archive blob, mounted bundle path

**Caplet File Layer**:
A live filesystem-backed source whose Caplet Files can shadow lower-precedence Caplets without modifying them. Project Caplet Files outrank global Caplet Files, which outrank SQL-backed Caplet Records.
_Avoid_: One-time bootstrap import, filesystem synchronization

**Effective Caplet**:
The Caplet selected for an ID after all configured Caplet sources are resolved. Ordinary runtime and dashboard views show only the Effective Caplet; explicit storage operations may still address a shadowed Caplet Record.
_Avoid_: Active record, merged Caplet

**Authoritative Host State**:
The correctness-bearing state owned by a Caplets host that must present one coherent view across all of that host's server nodes. It excludes node-local caches and artifacts, client-owned credentials, and native service files.
_Avoid_: All runtime files, shared cache

**Host Node**:
A running server instance participating in one logical Caplets host. Host Nodes share Authoritative Host State but own their live Code Mode heap, bound workspace copy, and any node-local artifacts.
_Avoid_: Independent host, tenant

**Caplets exposure projection**:
A shared adapter-neutral runtime view of which local and remote Caplets are exposed as Code Mode handles, progressive tools, direct downstream operations, or direct MCP surfaces, including non-callable diagnostic breadcrumbs for hidden Caplets. MCP, native, and attach host adapters render it differently; they do not own exposure policy or execution behavior.
_Avoid_: Tool registration list, exposure wrapper map, transport-specific surface
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ Long-lived docs:
- [Benchmark methodology](https://github.com/spiritledsoftware/caplets/blob/main/docs/benchmarks/coding-agent.md)
- [Native integrations](https://github.com/spiritledsoftware/caplets/blob/main/docs/native-integrations.md)
- [Project Binding](https://github.com/spiritledsoftware/caplets/blob/main/docs/project-binding.md)
- [SQL Authoritative Host State operations](https://github.com/spiritledsoftware/caplets/blob/main/docs/operations/sql-authoritative-host-state.md)

## License

Expand Down
8 changes: 7 additions & 1 deletion apps/dashboard/src/components/DashboardApp.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ vi.mock("@/lib/api", () => ({
vi.mock("@/components/ui/sonner", () => ({ Toaster: () => null }));
vi.mock("sonner", () => ({ toast }));

import { DashboardApp, catalogMutationLabel } from "./DashboardApp";
import { DashboardApp, catalogMutationLabel, routeFromPath } from "./DashboardApp";

type Deferred<T> = {
promise: Promise<T>;
Expand Down Expand Up @@ -176,6 +176,12 @@ describe("catalog update presentation", () => {
});
});

describe("dashboard routing", () => {
it("recognizes the Stored Caplets route", () => {
expect(routeFromPath("/dashboard/stored-caplets")).toBe("stored-caplets");
});
});

describe("Vault reveal races", () => {
it("dismisses a parent-owned reveal confirmation when Vault unmounts", async () => {
await mountVault();
Expand Down
28 changes: 27 additions & 1 deletion apps/dashboard/src/components/DashboardApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ import { EPHEMERAL_REVEAL_TTL_MS, createEphemeralRevealExpiry } from "@/lib/ephe
import { dashboardBasePath, dashboardPath } from "@/lib/paths";

import { CatalogPage } from "@/components/catalog/CatalogPage";
import { StoredCapletsPage } from "@/components/StoredCapletsPage";
const REVEAL_DURATION_SECONDS = EPHEMERAL_REVEAL_TTL_MS / 1_000;
const ACTION_DISCARDED = Symbol("dashboard-action-discarded");

Expand Down Expand Up @@ -131,6 +132,7 @@ type RouteKey =
| "overview"
| "access"
| "caplets"
| "stored-caplets"
| "catalog"
| "vault"
| "runtime"
Expand Down Expand Up @@ -191,6 +193,12 @@ const routes: Array<{
{ key: "overview", label: "Overview", href: dashboardPath(), icon: HomeIcon },
{ key: "access", label: "Access", href: dashboardPath("access"), icon: ShieldCheckIcon },
{ key: "caplets", label: "Caplets", href: dashboardPath("caplets"), icon: BoxesIcon },
{
key: "stored-caplets",
label: "Stored Caplets",
href: dashboardPath("stored-caplets"),
icon: DatabaseIcon,
},
{ key: "catalog", label: "Catalog", href: dashboardPath("catalog"), icon: BoxIcon },
{ key: "vault", label: "Vault", href: dashboardPath("vault"), icon: KeyRoundIcon },
{ key: "runtime", label: "Runtime", href: dashboardPath("runtime"), icon: TerminalIcon },
Expand Down Expand Up @@ -255,6 +263,15 @@ function useActionConfirm() {
return {
confirmAction: async (title: string, description: string) =>
Boolean(await confirm({ title, description, confirmLabel: "Continue" })),
confirmDestructive: async (title: string, description: string, confirmLabel = "Delete") =>
Boolean(
await confirm({
title,
description,
confirmLabel,
destructive: true,
}),
),
confirmTyped: async (title: string, description: string, expectedPhrase: string) =>
Boolean(
await confirm({
Expand Down Expand Up @@ -783,9 +800,18 @@ function Page({
session: DashboardSession;
action: DashboardAction;
}) {
const { confirmTyped } = useActionConfirm();
const { confirmAction, confirmDestructive, confirmTyped } = useActionConfirm();
if (route === "access") return <AccessPage data={data} loading={loading} action={action} />;
if (route === "caplets") return <CapletsPage data={data} loading={loading} action={action} />;
if (route === "stored-caplets")
return (
<StoredCapletsPage
action={action}
confirmAction={confirmAction}
confirmDestructive={confirmDestructive}
confirmTyped={confirmTyped}
/>
);
if (route === "catalog")
return <CatalogPage data={data} action={action} confirmTyped={confirmTyped} />;
if (route === "vault") return <VaultPage data={data} loading={loading} action={action} />;
Expand Down
Loading
Loading