/** * K8s-style manifest envelope, scoped to the cms group of the mantle universe. * * apiVersion: cms.mantle.aotter.net/v1 * kind: * metadata: { name } * spec: { ... kind-specific ... } * * Sibling group `analytics.mantle.aotter.net/v1` lives in the parallel mantle (OLAP) * project and is parsed there — the two systems share lineage and mantle.ai * domain but not parsers. * * The parser rejects keys and enum values outside this shipped grammar. * Future syntax is added only with its implementation. */ export declare const API_VERSION: "cms.mantle.aotter.net/v1"; export type ApiVersion = typeof API_VERSION; /** Media-shaped `x-mcp-hint` values — the subset marking a field as * holding a media asset URL. */ export type MediaMcpHint = "media" | "media-image" | "media-video" | "media-file"; export declare function isMediaMcpHint(value: unknown): value is MediaMcpHint; /** Loose JSON Schema shape — we don't constrain it at the type level. * Manifest authoring stays JSON Schema; runtime validators translate * to zod (Workers-CSP-safe). Cross-collection refs use the custom * keyword `x-mantle-ref: ` on string-typed fields holding * foreign-key IDs; `x-mcp-hint` is a widget-intent hint. The grammar * accepts strings; the v0.1 conventional values agents and admin * widgets should understand are `markdown`, `richtext`, `code`, * `media`, `media-image`, `media-video`, `media-file`, `money-minor`, * `timestamp-ms`, and `idempotency-key`. */ export type JsonSchema = { readonly $defs?: Readonly>; readonly $ref?: string; readonly oneOf?: readonly JsonSchema[]; readonly const?: unknown; readonly type?: string | readonly string[]; readonly properties?: Readonly>; readonly required?: readonly string[]; readonly items?: JsonSchema; readonly enum?: readonly unknown[]; readonly format?: string; readonly pattern?: string; readonly minLength?: number; readonly maxLength?: number; readonly minimum?: number; readonly maximum?: number; readonly minItems?: number; readonly maxItems?: number; readonly nullable?: boolean; /** Standard JSON Schema annotation. At the root of a Schema manifest, * Mantle keeps generic Admin and Staff MCP authoring read-only while * allowing trusted Procedure handlers to maintain the projection. */ readonly readOnly?: boolean; readonly default?: unknown; readonly additionalProperties?: boolean | JsonSchema; /** Standard JSON Schema keyword: help text for this property, shown * under the admin-UI form field (`entry-edit-view.tsx`) and the * row-operation dialog's bound-field label fallback * (`row-operations.tsx`). Accepts a plain string OR the same * `LocalizedText` locale-map shape used by `Schema.spec.description` * (#453, mirroring the property `title` keyword's #443 shape) — * admin-ui resolves it client-side with `resolveLocalizedText`. `en` * can stay the dev/OpenAPI-doc string while `zh-TW` etc. carry * operator-readable copy. Optional; absent renders no help text * (unchanged v0.1 behavior). */ readonly description?: LocalizedText; /** Standard JSON Schema keyword (#443): a human-facing label for this * property, for admin-UI form labels / list column headers / * operation form labels. Accepts a plain string OR the same * `LocalizedText` locale-map shape used by `Schema.spec.title` — * admin-ui resolves it client-side with `resolveLocalizedText`, same * as any other `LocalizedText` field. Optional; absent means the * consumer humanizes the property name instead (unchanged v0.1 * behavior). */ readonly title?: LocalizedText; /** Custom: cross-collection reference target. */ readonly "x-mantle-ref"?: string; /** Custom: hint for MCP tool / agent prompt context. */ readonly "x-mcp-hint"?: string; readonly [key: string]: unknown; }; export declare const MANTLE_REF_KEYWORD: "x-mantle-ref"; export declare const MCP_HINT_KEYWORD: "x-mcp-hint"; export declare const MANTLE_BIND_KEYWORD: "x-mantle-bind"; /** * A human-facing label/blurb that is either a plain string (single * language, the v0.1 shape) or a map of locale code → string (e.g. * `{ en: "Products", "zh-TW": "商品" }`) so one manifest can serve a * multi-language admin UI. `Schema.spec.title`/`.description`, * `Procedure.spec.title`/`.description`, and `View.spec.title` (#443) * use this shape; consumers resolve it to a single displayable string * with `resolveLocalizedText`. */ export type LocalizedText = string | Readonly>; /** * Resolve a `LocalizedText` value to a single displayable string for * `preferred` (typically the viewer's chosen admin language), falling * back to `canonical` (the site's canonical locale) and finally to the * record's first own-enumerable entry (insertion order). A plain * string is returned as-is — even an empty string, since only * `null`/`undefined` map to `null` here; shape validation (rejecting * empty strings) is the parser's job, not this resolver's. `null` / * `undefined` input (the field was never set) resolves to `null`, and * an empty record (structurally invalid, but resolved defensively) * also resolves to `null` since there is nothing to fall back to. */ export declare function resolveLocalizedText(value: LocalizedText | null | undefined, preferred: string, canonical?: string | null): string | null; /** * Four declarative atoms. Each maps 1-to-1 to a Postgres primitive — see * ADR-0001 / `docs/handbook/concepts/four-atoms.md` for the mapping. `Procedure` * is the only kind with a code seam (handler ref to consumer's TS file). */ export type ManifestKind = "Schema" | "View" | "Procedure" | "Trigger"; export interface ManifestMetadata { /** Resource identifier, e.g. `posts`. Required. Globally unique within * `(kind, deployment)`. */ readonly name: string; } interface ManifestEnvelope { readonly apiVersion: ApiVersion; readonly kind: K; readonly metadata: ManifestMetadata; readonly spec: S; } export type SchemaManifest = ManifestEnvelope<"Schema", SchemaManifestSpec>; export interface SchemaManifestSpec { /** Human-readable label for the admin UI. Required at v0.1.x — * the SPA renders this in the sidebar and elsewhere instead of * the bare `metadata.name`. Either a plain string, or a * `LocalizedText` map of locale → string (e.g. * `{ en: "Products", "zh-TW": "商品" }`) so one manifest can serve a * multi-language admin UI — the SPA resolves it client-side via * `resolveLocalizedText`. AI authors MUST populate it in the user's * primary language (the install-time chosen locale) at minimum, * since end-admin users may not even read English. See ADR-0010 and * the authoring contract § Schema authoring. */ readonly title: LocalizedText; /** Same string-or-locale-map shape as `title`. Optional. */ readonly description?: LocalizedText; /** JSON Schema Draft 2020-12 describing per-entry data. May carry the * v0.1 property extensions: `x-mantle-bind`, `x-mantle-ref`, `x-mcp-hint`. */ readonly schema: JsonSchema; /** Admin-only presentation. Closed Schema roots: `fields`, `list`, * `nav`. Optional. */ readonly uiSchema?: Record; /** Composite unique-index declarations, e.g. `[[slug, locale]]`. */ readonly uniqueIndexes?: ReadonlyArray>; /** Ordered composite non-unique indexes over top-level scalar fields. */ readonly indexes?: ReadonlyArray>; /** Top-level string fields included in Admin/MCP free-text search. * Entry id is always searched; absent or empty means id-only. This is * intentionally independent from `indexes`: substring LIKE queries do * not benefit from ordinary B-tree indexes. */ readonly searchableFields?: readonly string[]; /** Whether entries in this collection carry a per-row locale. Default * `false`. When `true`, `data.locale` MUST be present and ∈ site * `locales`; when `false`, `data.locale` MUST be absent. See * ADR-0010. Mutually constrained with `translates`. */ readonly localized?: boolean; /** Parent/child translation pattern: this Schema is the translatable * companion to a non-localized parent Schema, joined by a shared * field. Requires explicit `localized: true` and at least one * locale-specific field besides `locale` and the join field. See ADR-0010. */ readonly translates?: TranslatesBinding; /** Content-workflow mode. Default `'publishing'` (draft → published → * archived, no approval queue). `'operational'` marks record * Schemas (orders, inventory snapshots, audit rows) that are written * by Procedures rather than authored: entries are live on creation, * editable in place, and have no publish/unpublish transitions — the * admin hides the content-lifecycle chrome for them. */ readonly lifecycle?: LifecycleMode; } export interface TranslatesBinding { /** Name of the parent Schema this translation table joins to. Must * resolve to a declared Schema; the parent itself MUST NOT be * `localized: true` (it carries the non-translatable facts). */ readonly parent: string; /** Field present in both parent's and this Schema's * `spec.schema.properties` and used as the join key. Conventionally * `slug` for content, but any stable identifier works. */ readonly on: string; } export type LifecycleMode = "publishing" | "operational"; export type ViewManifest = ManifestEnvelope<"View", ViewManifestSpec>; export interface ViewCachePolicy { /** Maximum shared-cache staleness for anonymous REST reads, in seconds. */ readonly sharedMaxAge: number; } export declare const VIEW_SURFACES: readonly ["public", "staff", "internal"]; export type ViewSurface = (typeof VIEW_SURFACES)[number]; export interface ViewManifestSpec { /** Human-readable label for the admin UI's report sidebar / report * page (#443). Same string-or-locale-map `LocalizedText` shape as * `Schema.spec.title`. Optional — Views didn't carry a title before * v0.1.x; when absent the admin UI falls back to a Title-Cased * rendering of `metadata.name`, exactly as before this field * existed. */ readonly title?: LocalizedText; /** Admin-only presentation/query affordances for `surface: staff` * Views. v0.1 supports `uiSchema.list.columns`, `searchFields`, and * `filterFields`; public REST and MCP semantics stay unchanged. */ readonly uiSchema?: Record; /** Legacy declarative source Schema name (bare; no namespace). * Exactly one of `from` or `sql` is required. */ readonly from?: string; /** A single read-only SQLite SELECT over Schema logical tables. * Named `:params` are declared by `params` and bound by the runtime. */ readonly sql?: string; /** Adapter exposure policy. * `"public"` auto-mounts at the public * `GET /api/views/` (v0.1 default; `requires` may still gate * the call). When `"staff"` the View is * NOT mounted on the public path — it mounts at * `GET /admin/api/views/` behind the staff gate and becomes * the report-sidebar source. Guards data behind a staff session; use * it for any View over sensitive rows. `"internal"` mounts on no * adapter and is callable only through `MantleRuntime.executeView`. */ readonly surface: ViewSurface; /** Optional anonymous REST response-cache policy. Validation limits this * to caller-independent public declarative Views over publishing Schemas. */ readonly cache?: ViewCachePolicy; /** Auth gate. Identical shape to `ProcedureManifestSpec.requires.auth`. * When absent the View is public — `ExecuteViewUseCase` skips the * predicate check. When present, ALL predicates must hold; the * runtime enforces with `evaluateAuthAll`. Closed predicate * vocabulary: `ctx.user`, `ctx.staff`, `ctx.auth`, and * `ctx.auth.scope`; an optional guard names one consumer Procedure. */ readonly requires?: AuthorizationRequirements; /** Filter AST. v0.1 grammar: comparison ops plus and/or. Comparison * values may be literals, `{ $param: }`, or the identity-bound * `{ "$ctx.user": "id" }` sentinel. */ readonly filter?: FilterAst; /** Projection. */ readonly fields?: readonly string[]; /** Order. */ readonly orderBy?: ReadonlyArray<{ readonly field: string; readonly direction?: "asc" | "desc"; }>; /** Server-enforced cap on rows returned per call. The public REST * surface accepts `?show=`; the runtime trims to `min(show, limit)`. * Defaults to a runtime-internal value (50) when absent. */ readonly limit?: number; /** Caller-supplied parameter shape for the public REST surface * (`GET /api/views/`). MUST be `type: "object"` with declared * `properties`. Reserved names (`page`, `show`, `cursor`) are rejected * by the parser since the runtime owns those for pagination. */ readonly params?: JsonSchema; } /** v0.1 View filter comparison operators. */ export declare const FILTER_COMPARISON_OPS: readonly ["eq", "gt", "gte", "lt", "lte"]; export type FilterComparisonOp = (typeof FILTER_COMPARISON_OPS)[number]; /** v0.1 filter AST: comparisons plus `and` / `or`. */ export type FilterAst = FilterComparison | FilterAnd | FilterOr; export type FilterComparison = FilterEq | FilterGt | FilterGte | FilterLt | FilterLte; interface FilterComparisonNode { readonly field: string; /** Comparison value. Either a literal, a request `ParamRef`, or the * site-local identity `CtxUserRef`. */ readonly value: unknown; } export interface FilterEq { readonly eq: FilterComparisonNode; } export interface FilterGt { readonly gt: FilterComparisonNode; } export interface FilterGte { readonly gte: FilterComparisonNode; } export interface FilterLt { readonly lt: FilterComparisonNode; } export interface FilterLte { readonly lte: FilterComparisonNode; } /** Sentinel object form for filter values that pull from caller-supplied * params at request time. */ export interface ParamRef { readonly $param: string; } export declare function isParamRef(v: unknown): v is ParamRef; /** Site-local Better Auth user id. Deliberately closed to `id`; provider * claims and Platform identities do not belong in View filters. */ export interface CtxUserRef { readonly "$ctx.user": "id"; } export declare function hasCtxUserRefKey(v: unknown): v is Record<"$ctx.user", unknown>; export declare function isCtxUserRef(v: unknown): v is CtxUserRef; /** Reserved query-string names on the public View REST surface * (`/api/views/?...`). The runtime owns these for pagination; a * View manifest that declares `params.` for any of them is * rejected at parse time (`VIEW_PARAMS_RESERVED_NAME`). */ export declare const VIEW_PARAMS_RESERVED: readonly ["page", "show", "cursor"]; export type ViewParamReserved = (typeof VIEW_PARAMS_RESERVED)[number]; export interface FilterAnd { readonly and: readonly FilterAst[]; } export interface FilterOr { readonly or: readonly FilterAst[]; } export type ProcedureManifest = ManifestEnvelope<"Procedure", ProcedureManifestSpec>; export interface ProcedureManifestSpec { /** Human-readable label for the admin UI's staff-operations surface * (#430). Same string-or-locale-map `LocalizedText` shape as * `Schema.spec.title`. Optional — Procedures didn't carry a title * before v0.1.x; when absent the admin UI falls back to a * Title-Cased rendering of `metadata.name`. */ readonly title?: LocalizedText; /** Same string-or-locale-map shape as `title`. Optional. Surfaced by * `GET /admin/api/operations` as the operation's `description` * field (replaces the pre-#430 hack of reading * `spec.input.description`). */ readonly description?: LocalizedText; /** Authorization via `requires.auth.all` plus one optional * `requires.guard.procedure`; static predicates are closed to * `ctx.user`, `ctx.staff`, `ctx.auth`, and `ctx.auth.scope`. */ readonly requires?: AuthorizationRequirements; /** JSON Schema for the request body. */ readonly input: JsonSchema; /** Admin-only field widget choices. Does not affect input validation * or the MCP tool schema. */ readonly uiSchema?: Record; /** JSON Schema for the response body. */ readonly output: JsonSchema; /** Handler binding. v0.1.0 ships `kind: "ref"` (consumer supplies * a handler map) and `kind: "builtin"` (5-op CRUD shortcut over * the entry-writer chokepoint). */ readonly handler: HandlerBinding; /** MCP tool annotations the author declares because Core cannot infer * them for a `ref` handler (#972). Emitted verbatim on the tool; * `idempotentHint` is inferred from an `x-mcp-hint: idempotency-key` * input and is not declarable. A `readOnlyHint: true` on a writing * builtin handler is rejected at validation. */ readonly mcp?: ProcedureMcpAnnotations; } export interface ProcedureMcpAnnotations { readonly readOnlyHint?: boolean; readonly destructiveHint?: boolean; readonly openWorldHint?: boolean; } export declare const PROCEDURE_MCP_ANNOTATION_KEYS: readonly ["readOnlyHint", "destructiveHint", "openWorldHint"]; export type HandlerBinding = HandlerRefBinding | HandlerBuiltinBinding; export interface HandlerRefBinding { readonly kind: "ref"; /** Opaque registration key (NOT a path). The consumer passes a matching * key in the runtime/Worker `handlers` map. */ readonly ref: string; } /** Shipped v0.1 builtin op vocabulary. `archive` is runtime-wired but * meaningful only for a lifecycle that can transition to archived. * New entries require an explicit grammar-revise round. */ export declare const BUILTIN_OPS: readonly ["create", "update", "upsert", "delete", "archive"]; export type BuiltinOp = (typeof BUILTIN_OPS)[number]; export interface HandlerBuiltinBinding { readonly kind: "builtin"; /** Storage op. */ readonly op: BuiltinOp; /** Target Schema name (`Schema.metadata.name`). The op writes to * this collection. */ readonly schema: string; /** Ordered composite unique-index field names for matched upsert (valid only when `op: upsert`). */ readonly match?: readonly string[]; } /** Closed predicate vocabulary. `ctx.user` and `ctx.auth` are bare * strings; `ctx.staff` and `ctx.auth.scope` carry scalar data under * literal object keys. */ export interface AuthorizationRequirements { readonly auth?: { readonly all: readonly AuthPredicate[]; }; /** Dynamic, consumer-owned authorization check. The named Procedure * receives the target's validated input/params and the same * HandlerContext before the target executes. */ readonly guard?: { readonly procedure: string; }; } export type AuthPredicate = CtxUserPredicate | CtxStaffPredicate | CtxAuthPredicate | CtxAuthScopePredicate; export type CtxUserPredicate = "ctx.user"; /** Requires any verified credential normalized into HandlerContext.auth. */ export type CtxAuthPredicate = "ctx.auth"; export interface CtxStaffPredicate { readonly "ctx.staff": readonly StaffRole[]; } /** Requires one opaque, consumer-owned scope. Repeat under `all` for * multiple required scopes. */ export interface CtxAuthScopePredicate { readonly "ctx.auth.scope": string; } /** * Staff role hierarchy. `users` is the base identity layer (runtime); * `staff` is the privilege overlay (one row per privileged user). A * user without a staff row is a regular site member with no admin * access. * * owner — full control, manages staff, manages settings * editor — publish, approve/reject, manage all entries * contributor — create and edit drafts * * `StaffRole` lives in spec because the manifest grammar references * it directly: `requires.auth.all: [{ "ctx.staff": [, ...] }]` * — the parser checks each role string is in `STAFF_ROLES` at boot. * The `Staff` runtime row shape (with grantedBy / grantedAt) lives in * `mantle-runtime`; only the closed enum is grammar. */ export declare const STAFF_ROLES: readonly ["owner", "editor", "contributor"]; export type StaffRole = (typeof STAFF_ROLES)[number]; export declare function isStaffRole(s: string): s is StaffRole; export type TriggerManifest = ManifestEnvelope<"Trigger", TriggerManifestSpec>; export interface TriggerManifestSpec { readonly source: TriggerSource; /** The Procedure invoked when this Trigger fires. */ readonly target: { readonly procedure: string; }; } /** Supported Trigger sources. */ export type TriggerSource = HttpTriggerSource | LifecycleTriggerSource | McpTriggerSource; export interface HttpTriggerSource { readonly kind: "http"; readonly method: HttpMethod; /** OpenAPI `{param}` syntax. Path params auto-bind to identically-named * fields on the target Procedure's `input`. No optional segments. */ readonly path: string; } /** Shipped v0.1 lifecycle hook vocabulary: before/after × * create/update/delete plus the publish boundary. New entries require an * explicit grammar-revise round. */ export declare const LIFECYCLE_HOOKS: readonly ["before_create", "after_create", "before_update", "after_update", "before_delete", "after_delete", "before_publish", "after_publish"]; export type LifecycleHook = (typeof LIFECYCLE_HOOKS)[number]; export type HookErrorPolicy = "abort" | "continue"; export interface LifecycleTriggerSource { readonly kind: "lifecycle"; /** Schema this Trigger watches (`Schema.metadata.name`). */ readonly schema: string; /** Hooks bound by this Trigger. Non-empty. */ readonly on: readonly LifecycleHook[]; /** Override the per-phase default. before_* defaults to "abort"; * after_* defaults to "continue". */ readonly errorPolicy?: HookErrorPolicy; } /** Surfaces an MCP-source Trigger can be bound to. `staff` ⇒ * `/mcp/staff` (bearer + role gate); `public` ⇒ `/mcp` (bearer only). * The procedure's own `requires.auth` still gates the call — * surface determines visibility in `tools/list`. */ export declare const MCP_TRIGGER_SURFACES: readonly ["staff", "public"]; export type McpTriggerSurface = (typeof MCP_TRIGGER_SURFACES)[number]; export interface McpTriggerSource { readonly kind: "mcp"; /** Which MCP surface this Procedure is callable from. The * Procedure's `requires.auth` continues to evaluate against the * authenticated caller — surface only controls discovery. */ readonly surface: McpTriggerSurface; } /** v0.1 HTTP methods that may carry a body. GET is intentionally absent * — read endpoints are Views, not Procedures. */ export type HttpMethod = "POST" | "PUT" | "PATCH" | "DELETE"; export type Manifest = SchemaManifest | ViewManifest | ProcedureManifest | TriggerManifest; /** v0.1 closed enum for `x-mantle-bind` Schema-property values. New entries * require an explicit grammar-revise round (see ADR-0002). */ export declare const MANTLE_BIND_VALUES: readonly ["ctx.user", "ctx.staff", "now"]; export type MantleBindValue = (typeof MANTLE_BIND_VALUES)[number]; /** Storage-row metadata columns reserved across every Schema. Used by * the View SQL compiler (to project them as native columns rather * than `json_extract`) and by the type emitter (to surface them on * every Entry interface). Adding a new reserved column is a grammar * revise — touch this constant + every consumer. */ export declare const RESERVED_ENTRY_COLUMNS: readonly ["id", "status", "version", "createdAt", "updatedAt", "authorId"]; export type ReservedEntryColumn = (typeof RESERVED_ENTRY_COLUMNS)[number]; /** * Reserved Procedure input wire names. `expectedVersion` is the observed * native `entry.version` at read time (not version+1). First-party Admin/SDK * bind and hide it; other callers supply it. Schema `spec.schema.properties` * must not collide — validate is fail-closed (`INVALID_MANIFEST_ENVELOPE`). * New reserved names need an ADR (ADR-0022). */ export declare const EXPECTED_VERSION_PROPERTY: "expectedVersion"; export declare const RESERVED_PROCEDURE_INPUT_NAMES: readonly ["expectedVersion"]; export type ReservedProcedureInputName = (typeof RESERVED_PROCEDURE_INPUT_NAMES)[number]; export {}; //# sourceMappingURL=ManifestGrammar.d.ts.map