Skip to content

Manifest envelope and conventions

This page covers the rules that apply to every Manifest document before kind-specific validation runs. Read it once; the four atom pages (Schema, View, Procedure, Trigger) assume it. Diagnostic codes named here are catalogued in Diagnostics.

Envelope

Every document is a YAML mapping with exactly four top-level keys.

yaml
apiVersion: cms.mantle.aotter.net/v1
kind: Schema | View | Procedure | Trigger
metadata:
  name: posts
spec:
  # kind-specific
FieldTypeRequiredRules
apiVersionliteralyesExactly cms.mantle.aotter.net/v1. Anything else is INVALID_MANIFEST_ENVELOPE at /apiVersion.
kindenumyesSchema, View, Procedure or Trigger.
metadatamappingyesOnly the key name is accepted. There is no namespace.
metadata.namestringyesNon-empty. Unique within its kind (DUPLICATE_NAME, one diagnostic per occurrence). A Schema and a View may share a name.
specmappingyesKind-specific; see the atom pages.

A document that is not a mapping is rejected with manifest must be a YAML mapping.

Unknown-key policy

The parser rejects keys outside the shipped grammar at every level it knows. The diagnostic is INVALID_MANIFEST_ENVELOPE with the message <dotted.path> is not supported and a JSON Pointer to the offending key (for example spec.foo is not supported at /spec/foo). Enum values outside the grammar are rejected the same way.

PointerAllowed keys
/apiVersion, kind, metadata, spec
/metadataname
/spec (Schema)title, description, schema, uiSchema, uniqueIndexes, indexes, searchableFields, localized, translates, lifecycle
/spec (View)title, uiSchema, from, sql, surface, requires, filter, fields, orderBy, limit, params
/spec (Procedure)title, description, requires, input, uiSchema, output, handler
/spec (Trigger)source, target
/spec/translatesparent, on
/spec/requiresauth, guard
/spec/requires/authall
/spec/requires/guardprocedure
/spec/requires/auth/all/<i> (object form)exactly one of ctx.staff, ctx.auth.scope
/spec/handler (kind: ref)kind, ref
/spec/handler (kind: builtin)kind, op, schema, match
/spec/filter/<op>field, value
/spec/orderBy/<i>field, direction
/spec/cache (View)sharedMaxAge
/spec/source (kind: http)kind, method, path
/spec/source (kind: lifecycle)kind, schema, on, errorPolicy
/spec/source (kind: mcp)kind, surface
/spec/targetprocedure
/spec/uiSchema (View)list (only on surface: staff; violations are VIEW_UI_INVALID)
/spec/uiSchema/list (View)columns, searchFields, filterFields
/spec/uiSchema (Schema)fields, list, nav
/spec/uiSchema/list (Schema)filterField, primaryField, columns
/spec/uiSchema/nav (Schema)standalone, parentField
/spec/uiSchema (Procedure)collectionAction, fields

uiSchema roots are closed. Schema accepts fields, list, and nav; Procedure accepts collectionAction and fields; View accepts list on surface: staff. Nested keys are closed. A Schema that declares uiSchema.collectionAction is rejected with SCHEMA_UI_INVALID. JSON Schema documents inside spec.schema, spec.input, spec.output and spec.params follow the JSON Schema subset instead of an allowlist.

Multi-document YAML and sources

The parser consumes a set of sources, each { sourceId, text }. The CLI builds this set from the immediate .yaml and .yml files of the manifests directory, sorted lexicographically, with the file path as sourceId; an unreadable or empty directory is MANIFEST_ROOT_NOT_FOUND. Nested directories are not read.

Within one source:

  • --- separates documents. A feature commonly bundles a Procedure and its Triggers in one file.
  • YAML merge keys (<<) are disabled.
  • Alias expansion is capped at 100 aliases per document. Exceeding the cap is INVALID_MANIFEST_ENVELOPE with the message YAML alias-expansion limit exceeded.
  • Empty or null documents are skipped.
  • A YAML syntax error is INVALID_MANIFEST_ENVELOPE at /, prefixed [doc <index>].

Parsing is all-or-nothing. Any error-severity diagnostic in any document withholds the whole parsed set; later stages never see a partial graph. Every diagnostic carries source: { sourceId, documentIndex, path } plus a line and column span when the YAML node is known.

LocalizedText

Schema.spec.title (required), Schema.spec.description, View.spec.title, Procedure.spec.title and Procedure.spec.description accept either a plain string or a locale map.

yaml
title: Products
# or
title: { en: Products, "zh-TW": 商品 }

The parser rejects an empty string, an empty map {}, an array, an empty locale key and any non-string or empty value. JSON Schema property title and description keywords accept the same shape for Admin labels and help text; MCP tool schemas collapse them to the en value.

Resolution order (resolveLocalizedText): the viewer's preferred locale, then the site's canonical locale, then the first key in insertion order. A value that was never set resolves to null.

Naming rules for metadata.name

RuleApplies toDiagnostic
Non-empty string, unique within the kind.all kindsDUPLICATE_NAME
Must match /^[A-Za-z][A-Za-z0-9_.-]*$/ when the Schema declares any indexes or uniqueIndexes.SchemaSCHEMA_INDEX_INVALID at /metadata/name
Unique after MCP mangling (mcpToolNameSegment: lower-case, - becomes _). Two Schemas, or two Views, that mangle to the same segment collide.Schema, ViewMCP_TOOL_NAME_COLLISION
A Procedure's mangled name must not equal a reserved generic tool name, start with a reserved tool prefix, equal a Schema's mangled segment, or equal another Procedure's mangled name.ProcedureMCP_TOOL_NAME_COLLISION
One MCP Trigger per (surface, tool name).TriggerMCP_TOOL_NAME_COLLISION
Unique lower-camel identifier within each group (entries, views, procedures, triggers) when running mantle generate. The identifier joins the [A-Za-z0-9]+ runs of the name, lower-casing the first and capitalising the rest; my-orders and My Orders both become myOrders.all kindsCODEGEN_IDENTIFIER_COLLISION

Names containing - must be double-quoted when used as tables in a sql View ("post-translations").

Reserved names

NamespaceReservedEffect
Entry columnsid, status, version, createdAt, updatedAt, authorIdNative on every Schema. Cannot appear in indexes or uniqueIndexes (SCHEMA_INDEX_INVALID). Valid in View fields, filter, orderBy and uiSchema.list. Avoid declaring data properties with these names; SQL Views project the native column, not the data field.
Data fieldlocaleA non-localized Schema that declares properties.locale is rejected; use a domain name such as orderLocale. On a localized Schema the runtime requires data.locale on writes.
Data fieldexpectedVersionReserved Procedure OCC wire name. A Schema that declares spec.schema.properties.expectedVersion is INVALID_MANIFEST_ENVELOPE (ADR-0022). New reserved names need an ADR.
View paramspage, show, cursorOwned by the runtime for pagination. Declaring them under params.properties is VIEW_PARAMS_RESERVED_NAME.
Builtin inputid, expectedVersionContract fields for builtin update, delete, archive, and version-checked upsert. expectedVersion is the observed native entry.version at read time (not version+1). Matched upsert must declare it as a strict number and must not declare id. New reserved wire names need an ADR.
MCP tool namesrequest_publish, unpublish_entry, archive_entry, delete_entry, create_media_upload, commit_media_uploadA Procedure that mangles to one of these is MCP_TOOL_NAME_COLLISION.
MCP tool prefixescreate_draft_, update_draft_, create_record_, update_record_, query_view_Same as above.
HTTP pathsEvery http Trigger path must start with /api/ (TRIGGER_PATH_INVALID at validate time). The Cloudflare Worker additionally reserves /admin, /_mantle, /api/auth, /api/views, /oauth, /mcp, any path starting /.well-known/oauth, and the exact registrations * and /*; a Trigger under one of these fails at boot with TRIGGER_PATH_INVALID.See Trigger.

Source