carlos / platform Public

plan: CARLOS framework design — event-driven writes, crypto core, manifest UI (#2)

* plan: CARLOS framework design — reconciling with a recovered prior spec

Synthesizes today's brainstorm (event-driven writes, crypto core, TOML
manifests, grounded in a fresh survey of all six source apps) with an
earlier session's codegen/override-by-existence spec recovered from a
gist. Manifests resolve as typed Go (canonical) with TOML as sugar for
the pure-data case, per user direction.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* plan: stress-test the framework design against the six apps; name it rastrillo

Checked every section against the six source apps' real code and fixed
what didn't hold up: manifests are optional per route (Eleven/Woodstar
are SPA-shaped), crypto core needed Sign/Verify (keymail's dual
keypair), multi-tenant scope needed an explicit ctx.Scope pattern
(titogo), Advanced-tab forms must generate as separate POST actions
(titogo's stated safety invariant), Money must be integer cents, and
a second extraction candidate (WebAuthn, duplicated in kass+slopbox)
gets its own section. Flagged localization and blob storage as open
questions rather than guessing.

Renamed the framework from carlos.* to rastrillo.* (carlos is reserved
for the platform CLI) after flint, chassis, shasi, and dibber each
turned out to collide with existing Go projects.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* plan: promote localization and blob storage to first-class design

Per direction: i18n should be first-class since default components
ship with English labels ("Sign in" etc.) that need translation, and
file storage should always offload to object storage from day one
since the platform's minimal definition already guarantees a bucket
is present.

Adds §9 Localization (TOML catalogs per locale, generated translation
keys for manifest labels, app-wide locale declaration matching
titogo's real templatesFor(code) shape, carlos vet catalog-
completeness check) and a first-class Blob field kind in §5
(content-addressed like release artifacts, presigned direct-to-bucket
transfer, composes with the crypto core so E2EE apps seal blobs before
they leave the process). Removes both from open questions; renumbers
sections 9-14 to 10-15 accordingly and fixes cross-references.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* plan: preload scaffolded apps with CLAUDE.md + carlosframework/skills; create the repo

Adds §11: rastrillo new writes a starter CLAUDE.md (a generated "shape"
table kept truthful by rastrillo generate, plus a hand-written one-rule/
settled-decisions section it writes once and never touches again) and
installs the carlosframework/skills plugin, both opt-out. Read that
skills repo directly rather than assuming its contents -- its
building-carlos-apps skill already states in prose a good chunk of what
this framework now enforces as generated code, which the doc calls out
explicitly rather than let the two silently duplicate.

Renumbers sections 11-15 to 12-16 and fixes cross-references, including
one open-question reference that had gone stale after an earlier
revision removed an item from that list.

Also creates github.com/carlosframework/rastrillo (public, matching
website/skills -- platform alone is private) and seeds it with a
README pointing back to this design doc, resolving what had been open
question 5.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* plan: add an agents system to the framework design

Grounded in reading Eleven's internal/bot and internal/integrations
directly rather than the earlier summary-level survey -- found real
tool-calling infrastructure (a declarative op registry rendered into
LLM tool schemas, re-validated before execution, writes gated behind a
human tap except a named EXECUTOR allowlist), not a simple webhook
forward. That changes this from a from-scratch design into another
extraction, same as everything else in this document.

Adds §8: actions opt into being LLM tools via the same Resource field
declarations that already generate UI and SQL (a third rendering of
one source of truth, generated at rastrillo generate time per the
"never interpret at runtime" principle); agent-proposed writes reuse
the existing confirm-page mechanism rather than growing a second
consent path; ctx.Actor records human-vs-agent for the audit trail;
sidecar members (Eleven's bot shape) compose the crypto core and the
platform's sidecar wake sources rather than needing anything new. Adds
a matching design principle (§2), a mechanical write-bypass guard rail
(§13), a non-goal ruling out an agent bypass of the confirm gate
(§14), and three open questions (EXECUTOR declaration mechanism, LLM
provider scope, bounded-task defaults).

Renumbers sections 8-16 to 9-17 and fixes all cross-references,
verified by grep this time before committing rather than after.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* plan: resolve 6 of 9 open questions from spec review

Direct answers at spec review, folded into the design rather than left
as a separate decision log:

- sqlc/SQLite portability: a lint (rastrillo generate --check) flagging
  non-portable SQL, not full dual-engine generation.
- CLI verb naming: confirmed, not just assumed -- carlos is the
  platform's CLI, rastrillo is the framework's.
- Go line-cap: 400 lines, backstop only. The real mechanism is
  directory structure -- internal/'s "new concern, new package" rule,
  already stated in carlosframework/skills but under-enforced (titogo's
  own shop.go runs past 3,000 lines uncapped). Added as a guard rail
  alongside the existing JS cap.
- Skill versioning: no pin in v1: rastrillo new installs whatever the
  marketplace currently serves.
- Agent LLM client: imports an SDK, reversing the earlier hand-rolled
  decision -- an explicit, named exception to the framework's
  hand-roll convention, since an LLM API's surface moves faster than
  what that convention was written for (S3 SigV4 etc).
- Bounded-task budget: app-declared per sidecar, not a framework
  default -- Eleven's 6/16 is one app's choice, not evidence of a
  universal number.

Also added a portability lint and a generate-idempotency check to the
testing guard rails, both motivated by the above.

Three questions came back "don't know"/"maybe" and stay genuinely
open: TOML library choice, the mergeable ordering default, and the
EXECUTOR declaration mechanism. Renumbered the open-questions list
down to those three.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Paul Campbell 4d15d76696e10f3a90f26c4f031c63eb30b8d0f6 parent 324617b
1 file changed, +869 −0
  • docs/superpowers/specs/2026-08-01-carlos-framework-design.md +869 −0
diff --git a/docs/superpowers/specs/2026-08-01-carlos-framework-design.md b/docs/superpowers/specs/2026-08-01-carlos-framework-design.md
new file mode 100644
index 000000000..dba29cc28
--- /dev/null
+++ b/docs/superpowers/specs/2026-08-01-carlos-framework-design.md
@@ -0,0 +1,869 @@
+# 🤖 Rastrillo — the CARLOS Web Framework
+
+> 🤖 This document was written by an LLM (Claude), on the ideas, instruction,
+> and editing of humans, per [factor X](https://11factor.org/#x). It
+> synthesizes two brainstorming sessions: this one (2026-08-01, grounded in a
+> fresh code survey of all six source apps, then stress-tested against that
+> same survey) and an earlier session (recovered from a gist, ~mid-2026) that
+> established the codegen/override-by-existence model. Where they conflicted,
+> resolutions are noted inline.
+
+**Status:** Draft, pending user review. Revised 2026-08-01 after (1) a
+stress-test pass checking every section against the six source apps'
+actual code, (2) naming the framework `rastrillo` ("rake," Spanish) after
+`carlos` (reserved for the platform CLI), `flint`, `chassis`, and `dibber`
+each turned out to collide with existing Go projects (§16), (3) promoting
+localization (§10) and blob storage (§5) from open questions to
+first-class design, (4) adding a preloaded CLAUDE.md and skill for
+scaffolded apps (§12), (5) adding an agents system (§8), grounded in
+reading Eleven's actual bot/tool-calling code rather than designed from
+scratch, and (6) resolving six of the nine open questions raised at that
+point (§15). The code will live in
+[`carlosframework/rastrillo`](https://github.com/carlosframework/rastrillo)
+(created 2026-08-01, currently just a README pointing back here).
+
+**Relationship to the platform:** the [platform design](2026-08-01-carlos-platform-design.md)
+defines the substrate apps run on; this document defines **the shape of an
+app** — the way the twelve factors stood beside Heroku. An app built on this
+framework declares a manifest the platform already understands (storage
+ownership, serving class, service needs); the two vocabularies are meant to
+read as one system, not two.
+
+## 1. Purpose
+
+A Go web framework, extracted — not designed on a whiteboard — from six apps
+(Eleven, Keymail, Woodstar, Slopbox, Kass, Tito) that kept independently
+reinventing the same shapes. Concretely, this document's job is to turn
+eight duplicated things into one shared thing:
+
+| Duplicated today (found by repo survey) | Becomes |
+|---|---|
+| `internal/carlos` reimplemented from scratch in titogo, kass, slopbox | *(platform's job — activator/host agent, already specced)* |
+| `requireAuth`/`requirePerson`/`requireApp` handler-wrapping decorator, same shape in 4 repos | Framework middleware convention |
+| `sql.Open` + WAL + `busy_timeout`-before-`journal_mode` + `SetMaxOpenConns(1)`, copied file-to-file | Framework's `rastrillo.Serve` bootstrap |
+| ECDH-P256→HKDF→AES-GCM envelope, byte-identical Go+JS in eleven and keymail, pinned only by hand-copied test vectors | `rastrillo/crypto`, one implementation each side, shared golden vectors |
+| titogo's `admin_partials.html` component vocabulary — the only app that built one, unique to it | Framework's default component library |
+| `internal/webauthn`, independently written in kass and slopbox (ES256/COSE alg -7, no attestation) | `rastrillo/webauthn` (§7) |
+| titogo's per-locale `html/template` cloning (`templatesFor(code)`, 6 locales) — the only app with real i18n, unique to it | Framework's localization system (§10) |
+| Eleven's `internal/integrations` tool registry + `internal/bot` — the only app with real agent/tool-calling, unique to it | Framework's agent system (§8) |
+
+Three things the survey found **no existing app doing at all**: a
+first-class event-sourced write model (Eleven's append-only shape is real
+in production but hand-rolled; kass's `derive.go` is the closest precedent
+— `next(spec, events) → session`, a pure reducer), a manifest-driven
+page-composition system, and object-storage-backed blob fields (§5). The
+first two are explicit in the original brief; blobs came from a follow-up
+direction rather than inspected code — Slopbox's own file-storage
+mechanism wasn't in the surveyed scope, so that piece is motivated by its
+stated product purpose and the platform's architecture, not extraction.
+All three are designed fresh here.
+
+## 2. Design principles
+
+Carried from the recovered spec, still load-bearing:
+
+- **Generate and recompile; never interpret at runtime.** The canonical
+ manifest is typed Go, not YAML/JSON. A typo'd field is a compile error.
+- **Generated code is committed and readable.** Ejecting a file — copying it
+ from `gen/` into the hand-written tree — is the escape hatch for
+ everything. No plugin system, no hooks, ever.
+- **stdlib first.** Go 1.22+ `net/http` mux. No router dependency.
+- **Small vocabulary, cheap "no."** The Django-admin discipline: the
+ manifest doesn't grow to chase one screen's use case. Outside the
+ vocabulary, eject and write plain code.
+- **Prefer duplication over the wrong abstraction.** Single-file actions
+ push toward duplication on purpose; anything used twice moves to
+ `internal/`.
+
+New, from this session's brief and the platform spec:
+
+- **SQLite-forward.** Every app starts embedded; graduation to a server
+ engine is the platform's job (§5 of the platform spec), not the
+ framework's default.
+- **Event-driven by encouragement, not by force.** Commands append events;
+ state derives by replay. Apps that don't need it (titogo's shape) never
+ touch the event log.
+- **E2EE is opt-in per app**, never structural. A non-E2EE app (titogo,
+ Tito's dedicated-tier case) imports nothing from the crypto core.
+- **One override mechanism for everything.** Actions, templates, and
+ queries all eject the same way: a hand-written file at the generated
+ path wins, silently, by existence.
+- **Object storage is assumed present, always.** The platform's own
+ minimal definition already guarantees a bucket — "one object-store
+ bucket + N hosts + DNS" (platform spec §2) — so the framework designs
+ directly against it (§5) rather than treating it as an optional extra.
+- **Convention preload is opt-out, never mandatory.** A scaffolded app
+ starts with the family's documented conventions in reach (§12) — never
+ silently required, always deletable.
+- **An agent is never a second consent mechanism.** Whatever gate a human
+ write already goes through (§9's confirm-page), an agent-proposed write
+ goes through the same one (§8) — never a bypass, never a parallel path.
+
+## 3. Manifests: typed Go, TOML as sugar
+
+This was the fork between the two sessions — resolved as a combination,
+per your direction:
+
+**The canonical manifest is a typed Go struct**, satisfying the recovered
+spec's hard constraint (no runtime interpretation, compile-time safety):
+
+```go
+// app/manifest/ticket_types.go
+var TicketTypes = rastrillo.Resource{
+ Name: "ticket_types",
+ Route: "/{slug}/admin/ticket_types",
+ Store: rastrillo.Exclusive, // or rastrillo.Mergeable — §5
+ List: rastrillo.List{
+ Columns: []rastrillo.Column{
+ {Field: "Name", Kind: rastrillo.Text},
+ {Field: "Price", Kind: rastrillo.Money},
+ {Field: "Sold", Kind: rastrillo.Meter, Render: soldMeter}, // a Go func — only expressible here
+ },
+ Search: true,
+ Filter: []string{"Status"},
+ },
+ Form: rastrillo.Form{
+ Basics: []rastrillo.Field{{Name: "Name"}, {Name: "Price", Kind: rastrillo.Money}},
+ Advanced: []rastrillo.Field{{Name: "MaxPerOrder"}},
+ },
+}
+```
+
+**TOML is an optional serialization of the pure-data subset of the same
+struct**, for the common case that needs no custom Go values:
+
+```toml
+# app/manifest/ticket_types.toml
+name = "ticket_types"
+route = "/{slug}/admin/ticket_types"
+store = "exclusive"
+
+[list]
+columns = [{ field = "Name", kind = "text" }, { field = "Price", kind = "money" }]
+search = true
+filter = ["Status"]
+
+[form]
+basics = [{ name = "Name" }, { name = "Price", kind = "money" }]
+advanced = [{ name = "MaxPerOrder" }]
+```
+
+The generator unmarshals TOML into the identical `rastrillo.Resource` Go
+value — same validation, same downstream codegen, same collision check. The
+two forms are interchangeable inputs to one pipeline, not two systems:
+**write TOML for a plain screen; drop to a `.go` manifest the moment you
+need a function value** (a custom `ColumnRenderer`, a computed field, a
+validator) — TOML simply cannot express a Go closure, so the need for code
+is the natural signal to switch files, not a framework rule you have to
+remember.
+
+**Manifests are optional, per route.** The survey found two of the six apps
+(Eleven, Woodstar) with essentially no server-rendered UI at all — Eleven's
+pages are static HTML with client-side ES modules, Woodstar is a pure
+client SPA over Go services. Neither app would define more than a handful
+of `Resource` manifests, if any. `Resource`/`List`/`Form` generation is
+sugar for the CRUD/admin-screen case (titogo's and kass's shape); a plain
+`actions/*.go` file that just writes JSON and returns is a completely
+normal, first-class way to use the framework — the routing, action
+signature, data, and crypto layers (§4–§6) don't assume a manifest exists.
+An app can define zero `Resource`s and pair its JSON actions with its own
+hand-written SPA, the way Woodstar already does.
+
+**Basics and Advanced generate as two separate POST actions.** Titogo's
+production system enforces a specific, named safety property here: "each
+tab is its own form posting only its own fields, and the handler scopes
+the update accordingly — a basics save can never clobber an advanced
+setting" (`docs/design-system.md`). A `Form{Basics, Advanced}` manifest
+generates two independent actions (`edit-basics.POST.go`,
+`edit-advanced.POST.go`), each scoped to only its own fields — never one
+combined save — so this invariant holds by construction, not by
+convention.
+
+`rastrillo.Money` is backed by an integer number of cents, never a float —
+CARLOS's own non-negotiable, stated site-wide: "a float never touches a
+value a person will be held to." The generator rejects a `Money` field
+typed as anything but an integer at the SQL/Go boundary.
+
+**Every `Name` and label is generated as a translation key, not a baked
+string** — see §10 for the mechanism. This applies uniformly whether the
+manifest is written as Go or TOML; a single-locale app never has to know.
+
+**The same field declarations that generate UI and SQL also generate an
+LLM tool schema** when an action opts in — see §8. A `Resource`'s typed
+`Column`/`Field` list is already the one source of truth; a tool
+description is a third rendering of it, not a fourth thing to maintain.
+
+**Generation is scaffolding-with-skip**, exactly as the recovered spec
+describes: the generator emits real files into `gen/` (actions, `sqlc`
+glue, `html/template` component wiring); if a hand-written file already
+exists at the same computed path in `actions/` or `templates/`, generation
+of that one file is skipped. Ejecting is copying the generated file out and
+deleting the manifest's claim to it.
+
+## 4. Routing and the action signature
+
+**Filesystem convention**, from the recovered spec, generalized: file path
+= URL path, filename suffix = verb, Next.js-style bracket dirs for path
+params.
+
+```
+actions/orders/[id]/cancel.POST.go → POST /orders/{id}/cancel
+actions/orders/[id]/edit.GET.go → GET /orders/{id}/edit
+```
+
+The generator walks `actions/` plus everything manifests emit into
+`gen/actions/`, and builds one `http.ServeMux` (Go 1.22 method+pattern
+routes) into `gen/router.go` — mechanically the same mux every one of the
+six apps already hand-builds (`mux.HandleFunc("GET /{slug}/orders/{token}",
+...)`), just wired by directory walk instead of by hand. **Build fails
+loudly on any collision** between a hand-written and a generated path.
+
+**Action signature**, fixed early per the recovered spec's own advice
+("decide once, painful to change"):
+
+```go
+func Handle(ctx *rastrillo.Ctx, w http.ResponseWriter, r *http.Request)
+```
+
+`rastrillo.Ctx` carries the DB handle, logger, the identity the platform's
+blind router already resolved (§3 of the platform spec — the framework
+never re-implements auth, it *receives* identity), an event appender for
+mergeable resources (§5 below), the resolved locale (§10), **who's
+calling** (`ctx.Actor` — a human or a named agent, §8), and flash.
+
+**Multi-tenant scope.** Titogo's real handlers take a resolved scope
+beyond the fixed three arguments — `func (s *Server) handleX(w, r, ev
+*Event)`, an event/account resolved before the handler runs. Rather than
+letting per-app needs pressure the signature open, `_middleware.go`
+resolves scope onto `ctx.Scope any` (an untyped slot), and the handler
+type-asserts it: `ev := ctx.Scope.(*Event)`. The signature stays fixed;
+what varies is what middleware puts in the one extension point, which is
+exactly what `_middleware.go` cascading is for.
+
+**Middleware** is per-directory `_middleware.go`, cascading down the
+`actions/` tree — this directly generalizes the `requireAuth` /
+`requirePerson` / `requireApp` decorator every app independently
+hand-wrote at its mux call sites, so existing code translates almost
+mechanically.
+
+## 5. Data layer
+
+**Bootstrap** (`rastrillo.Serve`) bakes in the fixes the survey found
+hand-propagated repo-to-repo, so a new app starts from the corrected
+version instead of rediscovering them: `modernc.org/sqlite`,
+`busy_timeout` set *before* `journal_mode=WAL` (titogo's fix — reverse
+order crashes under concurrent opens), `SetMaxOpenConns(1)`, additive-only
+numbered migrations applied at boot.
+
+**Queries** are `sqlc`, colocated: `cancel.sql` next to `cancel.POST.go`,
+generated code landing in `gen/`. SQLite is the only target v1 generates
+against — Postgres/MySQL portability is the **platform's** concern at
+`carlos graduate` time (§5 of the platform spec already puts the burden on
+the app author, gated by `carlos vet`). The framework doesn't generate
+against both engines, but it doesn't leave the app author to find out at
+graduation time either: **a portability lint**, run via `rastrillo
+generate --check` (§13), flags SQL constructs known not to travel between
+sqlc's supported engines — SQLite-only pragmas and functions, dialect-
+specific syntax — narrowing what `carlos graduate` can still catch by
+surprise, short of taking on full dual-engine generation.
+
+**Two store shapes**, and a manifest declares which one a `Resource` uses
+— deliberately the same word the platform manifest already uses for
+storage ownership, so the two vocabularies are one:
+
+- **`rastrillo.Exclusive`** — plain sqlc CRUD. An action validates, writes,
+ returns. This is the Django-admin shape and covers most screens (Tito's
+ case: not E2EE, not append-only, dedicated-tier).
+- **`rastrillo.Mergeable`** — the Eleven shape, designed fresh here since
+ no app has extracted it yet. A command doesn't `UPDATE`; it appends an
+ immutable event to the resource's stream (`ctx.Append(stream, event)`).
+ A generated, pure `Derive(events) → State` function — literally kass's
+ `next(spec, events) → session` pattern, generalized — recomputes the
+ read model. Because it's pure and total over the event history: **replay
+ works for free** (rebuild state at any point by truncating the event
+ list), and **multi-edge works for free** — merging several instances'
+ independent streams is exactly the platform's `mergeable` contract
+ (many single-writer streams, one deterministic merge), so an app that
+ chooses this shape gets write-anywhere replication without writing
+ replication code. An app that never needs multi-edge still benefits:
+ replay-from-events is a good default even with one writer.
+
+A `Resource` picks one shape; nothing stops an app from mixing shapes
+across resources in the same binary (Kass-style: mostly exclusive, with
+one mergeable log for the append-only parts of a training programme).
+
+**Blobs are a first-class field type, from day one.** Neither store shape
+above fits file content well — sqlc rows aren't the right shape for large
+binaries, which matters most for Slopbox's core case (a file-storage app)
+— so unlike an earlier draft of this document, this isn't deferred. The
+framework can assume object storage is always available (§2), so it
+designs directly against it:
+
+- `rastrillo.Blob` is a `Column`/`Field` `Kind`, sibling to
+ `Money`/`Text`/`Meter`. The SQLite row holds only metadata — a content
+ hash, size, content-type; the bytes themselves live in the bucket,
+ **content-addressed by hash** — the same scheme the platform already
+ uses for release artifacts (platform spec §4), so blobs get
+ deduplication and integrity checking for free, for the same reason
+ releases do.
+- **Upload and download go straight to the bucket** via presigned PUT/GET
+ URLs — the standard object-storage pattern — bypassing the app process
+ for bandwidth. The app only ever sees the hash/metadata row, never
+ proxies the bytes itself.
+- **No new infrastructure concept.** This reuses the same bucket the
+ platform already provisions per app for litestream's WAL streaming and
+ database parking (§5 of the platform spec); blobs live under a
+ different key prefix (`<app>/<instance>/blobs/<hash>` vs. the
+ database's own prefix) within it.
+- **E2EE apps seal blobs before they leave the process.** The platform's
+ object store is a *blind* service — "may hold data, but only data it
+ cannot read" (platform spec §7). For an E2EE app (Slopbox's stated
+ purpose: "an open, end-to-end-encrypted alternative to Dropbox"),
+ `rastrillo.Blob` composes with the crypto core (§6): bytes are sealed
+ via `crypto.Seal` before upload and opened via `crypto.Open` after
+ download, so the bucket holds ciphertext for blobs exactly as it
+ already does for everything else in an E2EE app. A non-E2EE app stores
+ blobs as plaintext directly — the same opt-in-per-app posture as the
+ rest of the crypto core (§6).
+
+## 6. Crypto core
+
+A new shared package, `rastrillo/crypto` (Go) with a JS twin, replacing
+what the survey found **already byte-identical but independently
+duplicated** in eleven and keymail: ECDH P-256 ephemeral → HKDF-SHA256 →
+AES-GCM, wire shape `ephPub(65) ‖ iv(12) ‖ ciphertext`.
+
+```go
+Seal(pub PublicKey, plaintext []byte) (Envelope, error)
+Open(priv PrivateKey, env Envelope) ([]byte, error)
+WrapKey(pub PublicKey, key []byte) ([]byte, error) // thread/group keys
+UnwrapKey(priv PrivateKey, wrapped []byte) ([]byte, error)
+DeriveInvite(secret []byte) (id, wrapKey, claimSecret []byte) // eleven's setup_invite.go shape
+
+Sign(priv SigningKey, msg []byte, context string) ([]byte, error)
+Verify(pub SigningKey, msg, sig []byte, context string) bool
+```
+
+`Sign`/`Verify` are new relative to the recovered spec's silence on
+crypto: keymail's real usage is a **dual keypair**
+(`Keypair{SignPriv, BoxPriv}`) — ECDSA P-256 for signing (raw `r‖s`,
+WebCrypto-compatible format, verified via `parseECDSAPub`/`verifySig`) is
+how an app proves it's authorized to send through a mailbox in keymail's
+OAuth flow, alongside the ECDH box keypair for sealing. A crypto core
+that only covered `Seal`/`Open` would leave this half of keymail's actual
+usage unextracted. The `context` string parameter carries forward
+keymail's own convention of domain-separating signatures
+(`keymail-attest-v1`, `keymail-env-v1`) so two different signature
+purposes in the same app can never be confused for one another.
+
+Golden test vectors are shared between the Go and JS implementations and
+checked in both sides' CI — the mechanism keymail already uses internally
+for its own Go/JS pair, promoted to the framework level so it protects
+five apps instead of one, not zero (the two implementations literally
+cannot drift silently once there's one shared fixture instead of five
+private ones).
+
+**Invite links** carry a one-time secret in the URL `#fragment`
+(`/join/setup#<T>`), which browsers never send to the server — the pattern
+is identical across eleven and keymail today and is lifted verbatim.
+
+**Opt-in, structurally.** An app that isn't E2EE (titogo) imports nothing
+from this package. An app that is E2EE keeps private keys client-side by
+default; server-side use of the Go half is only for apps hosting bot/
+sidecar members that hold real keys (eleven's case — see §8), matching
+the platform's sidecar serving class (§5.4 of the platform spec) exactly
+— sidecars already have their own trust domain and sealed key material,
+the framework just gives them the primitives to use it.
+
+## 7. Passkeys (WebAuthn)
+
+A second, smaller extraction candidate the survey found: kass and slopbox
+each independently wrote their own `internal/webauthn` package, both
+verify-only (ES256 / COSE algorithm -7), both explicitly **not** checking
+attestation. Same shape, duplicated twice already with zero apps sharing
+it — the same pattern that justified the crypto core in §6, at smaller
+scale. `rastrillo/webauthn` extracts it: registration-ceremony and
+assertion-verification helpers, no attestation checking (matching both
+source implementations), used for passkey sign-in.
+
+This is deliberately a separate package from `rastrillo/crypto`, not a
+subpackage of it: WebAuthn proves **identity** (is this the browser that
+holds the passkey), while the crypto core seals **content** (E2EE
+message/thread data). An app can use one without the other — titogo could
+adopt passkey sign-in without ever touching E2EE; kass already pairs
+passkeys with client-side-only key handling and no server-side crypto
+core usage at all.
+
+## 8. Agents
+
+Two shapes, both extending pieces already designed rather than inventing
+new ones — and both grounded in Eleven's actual bot architecture
+(`internal/bot/`, `internal/integrations/`), which turns out to be the
+most sophisticated tool-calling system among the six apps: real
+LLM/tool-calling, not a fixed webhook forward, and the reason
+server-side crypto exists in §6 at all.
+
+**Actions as tools, generated from the same manifest.** Eleven's
+`internal/integrations` registry already proves the shape rastrillo needs
+for a feature like "describe your event in words, set up ticket types":
+a declarative manifest of ops (id, access `read`/`write`, args, a
+`Confirm` sentence for writes) rendered directly into LLM tool-call
+schemas, with every model-proposed call **re-validated against the same
+registry before it executes** — "its description IS the complete list of
+what each service can do." A `rastrillo.Resource`'s `List`/`Form` field
+declarations are already exactly this shape (typed fields, already the
+one source of truth for UI and SQL — §3); the generator emits a tool
+schema from them the same way it emits `html/template` and `sqlc` glue —
+at `rastrillo generate` time, not interpreted at runtime (§2). An action
+opts in with `Tool: true` (or `rastrillo.Tool{Description: "..."}` on a
+hand-written action outside a `Resource`); everything else about it —
+validation, `ctx.Scope`, the SQL it runs — is unchanged, because a tool
+call and an HTTP POST reach the identical `Handle` function (§4). This is
+what makes an in-app assistant a rastrillo-shaped feature rather than a
+bolt-on: it proposes calls against the same actions a human's form
+already posts to.
+
+**Writes stay consent-gated, reusing the confirm-page (§9), not a new
+mechanism.** Eleven's own rule, stated plainly in the bot code: every
+write-capable action is consent-gated behind a member's tap; the bot
+itself can never execute a write op — chip-proposals only, with a small
+named allowlist of exceptions Eleven calls EXECUTORS. Rastrillo already
+has the UI for exactly this: every destructive/write action already
+routes through its own confirm-page URL (§9). An agent-proposed tool call
+that writes renders that same confirm page instead of executing — the
+human taps the same button a direct user flow would show them, so the
+framework doesn't grow a second consent mechanism for AI specifically
+(§2); it reuses the one it already has. `ctx.Actor` (§4) — a small
+addition alongside `ctx.Scope`/`ctx.Locale` — records whether the caller
+was a human or a named agent, so the audit trail (kass/eleven mergeable
+event provenance, titogo's audit rows) can say so honestly, matching the
+family's own factor X: "AI is a guest with a name tag — opt-in, never
+required, always disclosed." An app can name specific actions as
+EXECUTOR-equivalent (skip the confirm-page, run immediately) but that is
+always an explicit per-action declaration, never a framework default —
+open question 3 below covers the mechanism.
+
+**Sidecar members compose existing primitives rather than needing new
+ones.** Eleven's bot is a real E2EE member: it self-provisions with a
+genuine keypair via the same invite-claim flow a human's browser uses
+(`e2ee.GenerateUserKeypair`, `POST /api/pubkey`), gets invited to threads
+exactly like a human, and its replies go through the identical
+`POST /api/messages`/WebSocket surface — "the server has no bot-specific
+surface, and only ever sees ciphertext." Every piece of that composes
+from what §6 and the platform's sidecar class (§5.4 of the platform
+spec) already provide: `rastrillo/crypto`'s invite/seal/wrap primitives
+for identity and content, and the platform's sidecar wake sources
+(**delivery** and **schedule** — Eleven's charter bots use the server's
+own scheduled-message delivery to wake themselves, not a bot-side cron:
+encrypt a tick to itself, schedule its delivery, react to it arriving
+like any other message). Nothing new is required here beyond a thin
+harness — `rastrillo.RunSidecar(...)`, generalizing the wake → read new
+events since a bookmark → decide → act loop Eleven's `Runtime.Run`
+already implements — because a sidecar's decision loop is structurally
+the same shape as a `Derive` function (§5): read events since a point,
+produce a result, the only difference being side effects (calling an
+LLM, sending a reply) instead of pure reduction.
+
+**The LLM client imports an SDK — an explicit, named exception to §2's
+hand-roll convention.** Eleven's `internal/bot/anthropic.go` talks to the
+Anthropic Messages API directly over `net/http`, no SDK, a reasonable
+choice for one hand-picked provider's stable surface. At spec review the
+call went the other way for the framework: an LLM API's surface moves
+faster and is more involved than something like S3 SigV4 — the case the
+family's "hand-roll small clients" rule was written for
+(`carlosframework/skills`) — so here the SDK's maintenance cost is worth
+paying. Which provider(s) `rastrillo/agent` ships against by default
+stays case-by-case rather than hard-committed to Anthropic-only, decided
+as real providers get wired in rather than guessed at now.
+
+**Two usage shapes, one underlying mechanism:**
+
+- **Synchronous, in-request** (an in-app assistant like Tito's "describe
+ your event in words"): a tool-calling loop scoped to the current
+ request's `ctx.Scope`/identity, proposing calls against the declared
+ tools, writes gated by the confirm-page as above. No new identity, no
+ wake mechanism — it runs inside the request that asked for it.
+- **Persistent, sidecar** (Eleven's bot): its own E2EE identity, wakes on
+ delivery/schedule, the same tool/confirm mechanism, plus a narrower
+ **bounded task mode** for unattended scheduled runs — Eleven's own
+ precedent (`internal/bot/task.go`): a closed tool set and a hard
+ call-count budget, no chat context, "there is no one to converse
+ with." **The budget is app-declared, not a framework default** —
+ Eleven's own 6/16 default-and-cap is one app's choice, not evidence of
+ a universal number; `rastrillo.RunSidecar` takes it as a required
+ option, never an implicit default nobody set. This is the shape for
+ the platform's own "digest senders, indexers, schedulers" sidecar
+ description (§5.4 of the platform spec).
+
+## 9. Component vocabulary and UI generation
+
+The survey found exactly one app with a real component system: titogo's
+`admin_partials.html` plus its `render`/`adminRender` entry points and
+`docs/design-system.md`. It is the only proven vocabulary among the six
+apps, has been through several rounds of production feedback, and becomes
+the framework's default — not because it's Tito's, but because it's the
+only one that exists and works. (As §3 notes, this whole section is
+sugar an app opts into per route — Eleven and Woodstar would use little
+or none of it.)
+
+**Default component library** (subset; full list carries over from
+`design-system.md`): `page-header`, `box`, `list-bar`, `list-row`,
+`person`, `status-pill` / `badge` / `meter` / `mono`, `detail-list`,
+`alert`, `field` family, `toggle-block`, `seg-tabs`, `form-foot`, `menu`,
+`confirm`, `modal-route`, `empty-state`, `bulk-select`, `pagination`,
+`help-link`. Each is an `{{define "name"}}` partial the generator's output
+composes by name — the same mechanism titogo already uses, just shipped as
+a library instead of grown in place. A `Resource` manifest maps onto
+titogo's own four canonical states (**Blank → List → Show → Edit/New** —
+"a screen that wants a fifth state is a design-system change"), so a
+`rastrillo.Resource{List: ..., Form: ...}` generates all four without the
+author naming them individually. Every piece of copy these components
+render — button labels, empty-state teaching text, confirm-page phrasing
+— comes from the localization catalog (§10), not a baked-in string, so an
+app gets correctly-worded default components in every locale it declares,
+without writing its own microcopy for the built-in vocabulary.
+
+**Visual skin** is token-based CSS (custom properties, light/dark via
+`prefers-color-scheme` plus `data-theme` override, WCAG AA) in the same
+structural spirit as `design-system.md` — but framework-neutral, not
+Tito's cornflower branding. An app reskins by overriding tokens; component
+*structure* is not meant to be forked per app, only its colours and type.
+
+**Zero-JS baseline is a framework non-negotiable**, promoted from a
+titogo convention to a hard rule: every generated screen works with
+JavaScript disabled (search/filter/sort as GET round-trips, destructive
+actions as their own confirm-page URL, modals as their own routes); JS is
+progressive enhancement only (spinners, drawers, live dirty-state).
+
+**Override-by-existence extends to templates**, uniformly with actions: a
+generated page or component can be ejected into the hand-written
+`templates/` tree, and the generator stops regenerating it. One mental
+model, one mechanism, for code and markup alike.
+
+## 10. Localization
+
+Titogo already runs real i18n in production: six locales
+(en/ga/fr/fr-informal/de/de-informal), one `html/template` set cloned per
+locale at boot (`templatesFor(code)`), each clone bound via `Funcs` to a
+locale-specific translation function. No other surveyed app has this —
+same situation as the component vocabulary in §9 — so per your direction
+it becomes first-class framework design rather than an app-level open
+question.
+
+**Catalogs.** One TOML file per locale (`locales/en.toml`,
+`locales/fr.toml`, …), flat key → string, consistent with TOML already
+being the manifest sugar format (§3). The framework's default component
+library ships its own base English catalog covering every string its
+built-in components render — "Sign in," "Save," "Cancel," "New …,"
+empty-state teach copy, confirm-page phrasing, the help tooltip label —
+so a single-locale app gets correctly-worded components for free, with no
+catalog of its own required. An app adding a locale supplies that
+locale's catalog; missing keys fall back to the declared default locale
+during development, but `carlos vet` (§13) fails the build on a key
+present in the default locale's catalog and silently missing from a
+declared non-default one — silent fallback while iterating, loud failure
+before ship.
+
+**Manifest labels are translation keys by generation, not by
+authorship.** A `Resource`'s `Name`, and every `Column`/`Field`'s label,
+generates a default catalog key (`resource.ticket_types.name`,
+`resource.ticket_types.field.price`) whose value falls back to a
+title-cased version of the Go identifier when no catalog entry exists. A
+single-locale app never touches a catalog; a multi-locale app fills in
+the keys the generator already emitted. Same "cheap no" discipline as the
+rest of the manifest system (§2): translation is opt-in effort, not
+opt-in architecture.
+
+**Resolution is app-wide, not per-`Resource`** — matching titogo's actual
+shape, where the whole app is cloned per locale rather than individual
+screens:
+
+```go
+rastrillo.Serve(rastrillo.Options{
+ Locales: []string{"en", "fr", "de"},
+ DefaultLocale: "en",
+})
+```
+
+Per-request locale is resolved by framework-level middleware — URL path
+prefix, then `Accept-Language`, then a stored preference, in that
+precedence — onto `ctx.Locale string` (§4), generalizing titogo's own
+`Lang` field threading through `render`/`adminRender`. Templates call
+`{{T "key"}}` / `{{Tf "key" .Args}}` for lookup with placeholder
+interpolation, bound per-clone exactly as titogo already does it.
+
+**Locale switching stays zero-JS**, consistent with §9's baseline: a
+plain link to the same path under a different locale prefix works with no
+client-side language picker required.
+
+## 11. Dev loop and CLI
+
+`rastrillo new <name>` scaffolds a fresh app: `go.mod`,
+`cmd/<name>/main.go` calling `rastrillo.Serve(...)`, empty
+`app/manifest/` + `actions/`, one starter TOML resource, `gen/` populated
+by running the generator once, and a starter `CLAUDE.md` plus preloaded
+skill (§12) — so `go build` works immediately, with no separate toolchain
+step (the shipped artifact stays a single static binary, the generation
+step is a dev-time/CI-time concern whose output is committed, never a
+deploy-time one).
+
+`rastrillo dev` watches `app/`, `actions/`, `manifest/`; on any change:
+regenerate → rebuild → restart. Target save-to-serving under ~2 seconds,
+carried directly from the recovered spec. Both `rastrillo dev` and CI call
+the same one-shot `rastrillo generate` underneath — one code path, not two.
+
+**Deploying reuses the platform's existing CLI as-is** — `carlos ship`,
+`carlos canary`, `carlos promote`, `carlos graduate`, `carlos vet` are
+already specced and built in the platform repo; this document does not
+propose new deploy verbs. **Confirmed at spec review:** `carlos` is the
+platform's CLI; `rastrillo` is the framework building the app. Today's
+brief's `carlos app create`/`carlos deploy` phrasing was intent, not a
+request for new platform verbs — the CLI split above is the answer, and
+nothing here changes.
+
+`carlos vet` (platform-level, unchanged mechanism) gains framework-
+contributed checks, run via `rastrillo generate --check`: router/manifest
+collision, additive-migration check, mergeable-convergence, line-cap,
+i18n-catalog completeness (§10), agent tool/consent-gate consistency (§8).
+
+## 12. Preloaded CLAUDE.md and skill
+
+Per your direction: a scaffolded app should start with the family's
+conventions in reach, not waiting for the author to go find them. Both
+pieces below default on and are opt-out — nothing else in the framework
+depends on their presence (§2).
+
+**Starter `CLAUDE.md`.** `rastrillo new` writes one, split into a
+generated part and a hand-written part, mirroring the discipline the
+family already holds itself to ("generated configs are marked GENERATED
+and regenerated on a timer, never hand-edited" —
+`carlosframework/skills`, guiding principle 4):
+
+- **Generated, and re-written by every `rastrillo generate`:** a live
+ "shape" table — this app's actual declared config (store shapes in use,
+ declared locales, whether `rastrillo/crypto` is imported, the
+ manifest's serving/placement declarations) — plus a pointer into `gen/`
+ explaining what's generated vs. hand-written and the override-by-
+ existence rule (§3, §9). It stays truthful by construction the same way
+ the rest of the generated tree does: wrong the moment the manifest
+ changes and `rastrillo generate` hasn't run, never wrong after.
+- **Hand-written once, never touched again:** an empty "## The one rule"
+ heading and instructions to fill it in before writing more code — Kass's
+ literal precedent ("the first commit of Kass is literally 'Write down
+ the architecture before writing any code'" — `carlosframework/skills`)
+ — and an empty "## Settled decisions (don't relitigate casually)"
+ heading. If the app imports `rastrillo/crypto` (declares E2EE), the
+ generator pre-fills a suggested one-rule statement ("If the server is
+ compromised, the attacker gets nothing") as a starting point the author
+ confirms or overrides — never silently assumed, since Tito's own
+ deliberate server-trust exception (`carlosframework/skills`, "The one
+ rule comes first") proves this has to stay a real choice, not a default
+ nobody looked at.
+
+**Preloaded skill.** `rastrillo new` also installs
+[`carlosframework/skills`](https://github.com/carlosframework/skills)
+(`carlos:building-carlos-apps`) into the new app — as a plugin
+marketplace reference by default, falling back to a vendored copy under
+`.claude/skills/` if the author's Claude Code setup can't reach the
+marketplace — so a fresh session in the generated app has the family's
+guiding principles, the eleven-factors mapping, and the "common mistakes"
+table available immediately, without the author needing to know the
+skill exists.
+
+**No version pinning in v1.** `rastrillo new` installs whatever the
+marketplace currently serves; an app that needs a specific skill version
+pins it itself, same as any other dependency it chooses to pin. Revisit
+if drift between the framework and the skill actually causes a problem
+in practice, rather than guard against a hypothetical one now.
+
+**The skill's role narrows for a rastrillo app, and both the skill and
+this doc should say so.** A large part of that skill's own "Quick
+reference" table is exactly what this framework now enforces
+mechanically rather than asks a human to remember — the SQLite pragma
+ordering, additive-only migrations, the 300-line JS cap, integer money —
+which is the skill's own guiding principle 4 ("rules are mechanical, not
+remembered") applied to itself. For a rastrillo app, what the skill keeps
+teaching is what code genuinely can't encode: the one-rule-first
+discipline, dated-and-attributed settled decisions, the git/PR/canary
+workflow, authorship marking, and the "common mistakes" table. **This
+follow-up edit has already been made** — `carlosframework/skills` PR #2
+adds exactly this distinction, tagging what rastrillo (and separately,
+the now-live platform) automates inline, rather than let the skill and
+this framework silently say the same thing twice.
+
+**Opt-out.** `rastrillo new --no-claude-md` skips the starter file;
+`rastrillo new --no-skill` skips the plugin install. Both default on.
+Either can also just be deleted after scaffolding — nothing depends on
+them being present, the same opt-in-never-required posture the crypto
+core (§6) and event-sourcing (§5) already hold.
+
+## 13. Testing and guard rails
+
+- **Collision check fails the build** — any hand-written and generated
+ path landing on the same route or same file.
+- **Line-count cap.** Woodstar's actual convention is concrete: 300 lines
+ per JS module, 600 for a coordinator-role file (`store.js`), "caps only
+ ratchet down." That's the number this framework carries forward for any
+ generated or hand-written JS. **Go gets a cap too, though not an
+ extracted one:** no per-file Go cap was found in the surveyed repos —
+ titogo's own `shop.go` runs past 3,000 lines uncapped, closer to a
+ cautionary tale than a precedent. 400 lines per Go file, backstop only:
+ the real mechanism is directory structure — `internal/` already carries
+ the family's stated-but-under-enforced rule ("a new concern gets a new
+ small package, not a new file in the biggest one" —
+ `carlosframework/skills`), and every action already gets its own file
+ by construction (§4). The cap exists so that rule can't quietly slip
+ the way it did in titogo.
+- **sqlc/SQLite portability lint** — flags dialect-specific SQL at
+ `rastrillo generate --check` time (§5), ahead of `carlos graduate`.
+- **Generate idempotency** — re-running `rastrillo generate` with no
+ manifest change produces a byte-identical tree, checked for `gen/` and
+ for the generated half of `CLAUDE.md` (§12) alike, so neither becomes
+ diff noise on every commit.
+- **Mergeable convergence** — N writers, randomized event interleavings,
+ one required final state, reusing the exact invariant the platform spec
+ already tests at the storage layer (§9 of the platform spec), now
+ exercised at the app's `Derive` function too.
+- **Crypto golden vectors** — shared fixture, both Go and JS sides fail CI
+ if either drifts.
+- **i18n catalog completeness** — `carlos vet` fails on any key present in
+ the default locale's catalog but missing from a declared non-default
+ one (§10).
+- **Agent write-bypass check** — a build-time assertion that no
+ `Tool`-flagged write action skips the confirm-page path (§9) unless
+ explicitly declared an EXECUTOR (§8) — the mechanical form of §2's "an
+ agent is never a second consent mechanism," so the rule can't quietly
+ rot into a convention nobody checks.
+- **Zero-JS gate** — a headless-browser pass with JavaScript disabled, on
+ top of the platform's existing headless-browser console-error gate
+ (§9 of the platform spec).
+
+## 14. Non-goals (v1)
+
+- No plugin/hook/registry system, no runtime scripting (Yaegi or
+ otherwise), no reflection-driven routing — the recovered spec's
+ discipline, unchanged.
+- No general-purpose UI framework. Finite vocabulary; anything outside it
+ is a plain ejected file, never a manifest feature grown to fit one
+ screen.
+- No forced E2EE, no forced event-sourcing — both are per-`Resource`
+ choices, not framework-wide postures.
+- No new deploy CLI verbs (§11) — confirmed at spec review, not merely
+ pending.
+- No cross-engine query abstraction; SQLite-only generation, Postgres
+ handled at the platform's graduation boundary.
+- No pluralization/ICU-style grammar rules in the localization catalog
+ (§10) — flat key → string only, v1. Complex plural/gender rules are
+ left to an app's own catalog conventions until a real need surfaces one.
+- No forced skill or CLAUDE.md content (§12) — both are opt-out
+ scaffolding, not framework behavior anything else depends on.
+- No agent bypass of the confirm-page/consent mechanism (§8/§9) — v1
+ does not add a "trusted agent" write path beyond the same explicit,
+ per-action EXECUTOR declaration Eleven already uses.
+
+## 15. Open questions
+
+Six of the nine questions raised at the previous spec review are resolved
+into the design above: sqlc/SQLite portability got a lint rather than
+full dual-engine generation (§5); the CLI split (`carlos` platform,
+`rastrillo` framework) is confirmed, not just assumed (§11); the Go
+line-cap number is set — 400 lines, backstop only, directory structure
+via `internal/` doing the real work (§13); skill versioning is
+deliberately deferred — no pin in v1 (§12); the agent LLM client imports
+an SDK, a named exception to the hand-roll rule (§8); and sidecar
+tool-call budgets are app-declared, not framework-defaulted (§8). Three
+remain genuinely open:
+
+1. **TOML library.** A minimal dependency (e.g. BurntSushi/toml) needs
+ vetting against the "no needless dependency" ethos, or a small decoder
+ gets hand-rolled — deferred to implementation.
+2. **Mergeable ordering default.** Same open question the platform spec
+ already carries (§11.1 there): is a generic causal/lamport `Derive`
+ order sufficient as the framework default, or does every mergeable
+ `Resource` need to supply its own comparator? Eleven's real merge rule
+ is still unconfirmed against this shape — genuinely unresolved, not
+ merely undecided.
+3. **EXECUTOR declaration mechanism.** Eleven names specific ops
+ (`create_app`, `create_bot`, `create_view`, memory) that skip
+ confirm-gating, decided in application code. Does rastrillo need a
+ manifest-level way to mark an action agent-executable-without-
+ confirmation (`Tool{Executor: true}`?), or does that stay an
+ app-level decision entirely outside the framework's vocabulary? Also
+ genuinely unresolved.
+
+## 16. Naming
+
+The framework needed a name distinct from `carlos` (correctly reserved for
+the platform CLI). In order, and why each fell through before landing:
+
+1. **flint** — "what lights the flame." An existing, unrelated Go web
+ framework already uses this name (flintgo.netlify.app).
+2. **chassis** — the car pun matching CARLOS. `go-chassis/go-chassis` is
+ a real, established Go microservice framework — a direct collision.
+3. **shasi** — a respelling of chassis to dodge the collision. Search-clean,
+ but abandoned mid-decision when a gardening-tool angle came up instead.
+4. **dibber** — the small tool that pokes the planting hole; fit "starting
+ a new app" well. `laher/dibber` turned out to be a real (if tiny,
+ different-domain) Go project — a DB terminal-UI client — so set aside
+ rather than risk it.
+5. **trug, cloche, secateurs, trellis, trowel, loppers, cultivator,
+ twine** — a wider gardening-tool sweep. Several search-clean (trug,
+ cloche, secateurs, cultivator); several collided (trellis with a real
+ Go microservice framework, trowel with a deprecated but conceptually
+ close Sass component-generator toolkit, twine with the well-known
+ interactive-fiction tool). Not decided between before the language
+ pivoted.
+6. **rastrillo** ("rake," Spanish) — landed on after widening the sweep to
+ Spanish gardening-tool names. (Not, as an earlier draft of this section
+ claimed, a nod to `Carlos` itself — `Carlos` is Portuguese, not
+ Spanish.) Checked clean of Go-framework collisions on its own merits.
+
+## 17. Provenance
+
+Extracted from the same six systems as the platform (Eleven, Keymail,
+Woodstar, Slopbox, Kass, Tito), synthesizing two brainstorming sessions:
+this one (2026-08-01, grounded in a fresh survey of all six repos and the
+approved platform design, then stress-tested section by section against
+that same survey — §3's manifest-optionality note, §4's scope-resolution
+pattern, §6's `Sign`/`Verify` addition, and §7's new WebAuthn package all
+came out of that pass) and an earlier one recovered from a gist
+(~mid-2026), which established the codegen, override-by-existence, and
+filesystem-routing model this document builds on rather than replaces.
+
+A third pass (still 2026-08-01) promoted localization (§10) and blob
+storage (§5) from open questions to first-class design: i18n because
+default component copy needs to be translatable from day one, not bolted
+on; blobs because the platform's bucket-first architecture means object
+storage can safely be assumed present from day one, not deferred.
+
+A fourth pass added the preloaded CLAUDE.md and skill (§12), grounded in
+reading `carlosframework/skills` directly rather than assumed — its
+`building-carlos-apps` skill turns out to already state, in prose, a
+meaningful fraction of what this framework now enforces as generated
+code, which is itself evidence the extraction is pointed the right
+direction. The code will live in
+[`carlosframework/rastrillo`](https://github.com/carlosframework/rastrillo),
+created during this same pass, resolving what had been open question 5.
+That pass's follow-up — updating the skill itself to say what rastrillo
+now automates — was carried out as `carlosframework/skills` PR #2.
+
+A fifth pass added the agents system (§8), prompted by a direct question
+("should rastrillo cover embedding AI agents — Eleven's bot, or a Tito
+'describe your event in words' feature") rather than by the original
+survey. Reading Eleven's `internal/bot` and `internal/integrations`
+directly, rather than assuming their shape from the earlier summary-level
+survey, found real tool-calling infrastructure — not a simple webhook
+forward — which changed this from a from-scratch design into another
+extraction, consistent with everything else in this document.
+
+A sixth pass (still 2026-08-01) resolved six of the nine open questions
+that pass left behind, per direct answers at spec review: a portability
+lint rather than full dual-engine generation (§5); a 400-line Go cap,
+backstop only — `internal/`'s "new concern, new package" rule doing the
+real organizational work (§13); the platform/framework CLI split
+confirmed rather than assumed (§11); no skill-version pinning in v1
+(§12); the agent LLM client importing an SDK as a named exception to §2
+(§8); and sidecar tool-call budgets left app-declared (§8). Three
+questions — TOML library choice, the mergeable ordering default, and the
+EXECUTOR declaration mechanism — came back "don't know"/"maybe" and are
+carried into §15 genuinely open, not guessed at.