/**
* Site-level declarative contract — sibling to the manifest grammar,
* not part of it.
*
* The 4-atom manifest grammar (Schema/View/Procedure/Trigger) describes
* content types. `SiteConfig` describes deployment metadata: locales the
* site publishes in, brand label, the operator-visible title and
* description, the canonical absolute origin used for canonical URLs.
*
* Lives in `mantle-spec` (not `-runtime`) because the spec validator
* checks declared `siteDefaults.locales` are canonical BCP 47 at boot
* (see `domain/service/SiteDefaultsValidator`). The runtime operations
* that read/write `site_config` rows from the DB live in
* `mantle-runtime` — they go through the `DatabaseDriver`.
*
* Future v0.1.x: optional standalone `site.yaml` parsed via a
* `kind: SiteConfig` envelope sibling to the 4-atom manifest grammar.
* Today the consumer declares `siteDefaults` in `src/mantle/config.ts` as a
* TS object; either path conforms to this type.
*/
export interface SiteConfig {
/** Site title — `
` suffix and og:site_name. */
readonly title: string;
/** Default `` when an entry has none. */
readonly description: string;
/** Canonical absolute origin (no trailing slash), e.g. `https://my-blog.com`. */
readonly origin: string;
/** All locales the site publishes in. Empty = locale subsystem off
* site-wide (ADR-0010). Drives ``
* emission only when non-empty. */
readonly locales: readonly string[];
/** Canonical locale (`locales[0]`) or `null` when locales is empty.
* Templates emit `` only when this is non-null —
* silent omission is correct for zero-locale sites, not a fake
* default. */
readonly canonicalLocale: string | null;
/** Operator-facing brand label — admin chrome (sidebar header,
* sign-in card). Distinct from `title` so single-tenant operators
* can ship one and agencies can override the other. */
readonly brand: string;
/** One site identity shared by browser favicons, Admin chrome, and
* MCP `serverInfo.icons`. Always contains at least the SDK default. */
readonly icons: readonly SiteIcon[];
/** Starter-declared media taxonomy. Empty array = no first-party
* media uploads permitted; the runtime disables the
* `create_media_upload` / `commit_media_upload` MCP tools and the
* admin upload lifecycle on deployments that don't declare any
* purpose, symmetric with "no `MediaStorage` configured". When
* non-empty, `create_media_upload` rejects any `purpose` not in
* this set with `MEDIA_PURPOSE_REJECTED`. */
readonly media: SiteMediaConfig;
}
/** Runtime read shape for the `media` section of `SiteConfig`. Always
* present after seed; `purposes` may be empty when the consumer
* didn't declare any. Each purpose carries the required mime set the
* Worker enforces and the per-mime byte cap the uploader is held to. */
export interface SiteMediaConfig {
readonly purposes: readonly MediaPurposePolicy[];
}
/**
* Per-purpose policy. `name` is the slug consumers reference from
* `create_media_upload`; `required` is the closed set of mime types
* the variants manifest MUST cover; `maxBytes` caps each variant's
* byteSize keyed by its declared mime.
*
* Optimization runs agent-side in the MCP client's runtime; the
* Worker only verifies the uploaded bytes match the policy. The
* runtime also enforces a "suspicious shape" heuristic so an uploader
* that ships a 200KB jpeg + a 400KB "avif" (i.e. forgot to optimize)
* fails closed rather than producing a `` that's slower than
* a plain ``.
*/
export interface MediaPurposePolicy {
/** Lowercase-slug identifier; matches `MEDIA_PURPOSE_SLUG_PATTERN`. */
readonly name: string;
/**
* Ordered list of acceptable mime slots. Each entry uses the HTML
* `` grammar:
*
* - full mime: `"image/jpeg"`, `"image/avif"`
* - comma-list of mimes: `"image/jpg,image/png"` (either is OK)
* - shorthand subtype: `"webp"` → `"image/webp"`,
* `"jpg"` → `"image/jpeg"`
*
* Slot position does NOT determine variant role. Per-asset, the
* agent picks ONE mime per slot from that slot's acceptable set
* and independently declares exactly one supplied variant as
* `role: "primary"` (the format `` falls back to); the rest
* are `role: "alternate"` (preferred via ``).
* This decoupling preserves back-compat with alpha.14 fixtures
* such as `["image/avif", "image/webp", "image/jpeg"]` that ship
* jpeg as primary despite avif occupying slot 0. The multi-mime
* slot form (e.g. `"image/jpg,image/png"`) lets a single purpose
* accept either jpeg-primary (photos) or png-primary (logos /
* transparent assets) without splitting into two purposes.
*
* Parse + expand via `parseMimeAccept` / `expandPolicyRequired`.
* Mime sets across slots MUST NOT overlap; `maxBytes` keys MUST
* cover every mime that appears in any expanded slot.
*
* See aotter/mantle#282 for the editor-uploads-transparent-PNG
* motivating case.
*/
readonly required: readonly string[];
/** Per-mime byte cap, keyed by FULLY-EXPANDED mime type (e.g.
* `"image/jpeg"`, not the shorthand `"jpg"`). MUST cover every
* mime that appears in any slot of `required` after parsing.
* Mimes outside the expanded set are ignored. The use case
* rejects with `MEDIA_VARIANT_SIZE_EXCEEDED` when a variant's
* declared `byteSize` exceeds its cap. */
readonly maxBytes: Readonly>;
}
/**
* Defaults declared by the consumer in `src/mantle/config.ts`. Operator-owned
* fields seed once, while deployment-owned fields (`origin`, `icons`, `locales`,
* and media purposes) sync on every boot. Empty / blank fields skip, preventing a
* partial declaration from clobbering stored values.
*
* Validated during storage preparation by `assertSiteDefaultsCanonical`; a
* non-canonical locale tag rejects boot rather than corrupting the seed.
*
* `SiteDefaults` is the **author-time** declaration (what the consumer
* writes); `SiteConfig` (above) is the **runtime read shape**
* (what the dispatcher and templates see after the seed has been
* applied and the operator has had a chance to edit). The runtime's
* `DatabaseSiteConfigRepository` owns that seed/load conversion; spec defines
* both types and the validator.
*/
export interface SiteDefaults {
readonly locales?: ReadonlyArray;
readonly brand?: string;
readonly title?: string;
/** Default `` and og:description for entries
* that don't carry their own. */
readonly description?: string;
/** Canonical absolute origin (no trailing slash), e.g.
* `https://my-blog.com`. The render pipeline uses this to build
* absolute URLs in `/llms.txt` and the `.md` mirrors; an empty
* origin produces relative URLs that are useless to off-site agent
* consumers. The runtime syncs it from `src/mantle/config.ts` on every boot
* so a later custom-domain change becomes canonical without a D1 edit. */
readonly origin?: string;
/** Browser / Admin / MCP icon renditions. Root-relative or absolute HTTPS. */
readonly icons?: ReadonlyArray;
/** Starter-declared media taxonomy. See `SiteConfig.media` for
* runtime semantics. Omit the whole `media` key (or declare it
* with an empty `purposes` array) on archetypes that don't
* exercise first-party media uploads — the runtime will keep the
* upload tools disabled, symmetric with "no `MediaStorage`
* configured". Each purpose's `name` is slug-shaped
* (`^[a-z0-9]+(-[a-z0-9]+)*$`); validated synchronously at boot. */
readonly media?: SiteMediaDefaults;
}
export interface SiteMediaDefaults {
readonly purposes?: ReadonlyArray;
}
/** MCP-compatible icon metadata, reused directly by every site surface. */
export interface SiteIcon {
readonly src: string;
readonly mimeType?: "image/png" | "image/jpeg" | "image/svg+xml" | "image/webp";
readonly sizes?: ReadonlyArray;
readonly theme?: "light" | "dark";
}
export declare const DEFAULT_SITE_ICONS: readonly SiteIcon[];
/** Slug regex for `media.purposes[].name`. Matches a lowercase
* alphanumeric word, optionally followed by dash-separated
* alphanumeric segments — no leading/trailing dashes, no double
* dashes. The R2 adapter already uses a looser variant of this for
* storage-key prefixes (`/^[a-z0-9-]+$/`); spec-level validation is
* stricter so admin / docs / object-store dashboards see clean
* slugs. */
export declare const MEDIA_PURPOSE_SLUG_PATTERN: RegExp;
//# sourceMappingURL=SiteConfig.d.ts.map