All authoring work on a Quoin site goes through a tool surface that enforces the constitution mechanically. This spec is the contract for that surface. The reference implementation is the standalone quoin-cli package (D-005): one global CLI and one stdio MCP server, site-agnostic, resolving the site from the working directory. Site repos contain no tool code; they expose the site adapter (D-005).

1. The enforced loop

Every change follows one loop, and every arrow refuses when its invariant fails:

begin_change → put_page | put_menu | put_tokens | put_theme_file
            → validate → open_pr → CI build → merge
  • begin_change creates a change/<slug> branch. It refuses on main and on a dirty worktree.
  • Every write tool refuses on main — Constitution §4 at the tool layer.
  • validate runs the full schema, menu, ref, and theme-conformance gate without a full build. open_pr refuses while validation fails.
  • The CI build is the final gate (Constitution §2); merge is the only path to production (Constitution §4).

A tool MUST report refusals as structured, one-line reasons naming the invariant. A refused operation MUST NOT partially apply.

2. Tool catalogue

Tool Purpose Enforcement highlights
quoin_status Site state: identity, branch, collections and counts, migration stubs, placeholder flags, tokens, active theme and capabilities read-only
quoin_list_pages / quoin_get_page Enumerate and read pages read-only
quoin_begin_change Start a change branch refuses on main, dirty tree, duplicate branch, bad slug
quoin_put_page Create or update a content page strict schema (§3); ref integrity; blob justification; update-intent rule (§4)
quoin_put_menu Write a named menu menu schema (exactly-one-of rule, parent exemption D-003); ref integrity
quoin_put_tokens Write design-token values validates merged set against the active theme manifest’s token list; unknown names refused listing the valid names; hex format
quoin_put_theme_file Write any file inside any theme directory (themes/**) the jail (§5); post-write validation gate
quoin_validate Full gate without a build schemas + menus + refs + theme conformance; --all-themes checks content against every installed theme
quoin_open_pr Commit, push, open the PR refuses while validation fails; the only sanctioned shipping path for agent work
quoin_preview Build, serve, open the rendered page in a browser the visual review surface; --theme <name|all> renders any installed theme via a QUOIN_THEME build override without touching site.yaml
quoin_blocks List block types available to the active theme manifest-driven
quoin_themes / quoin_theme_set / quoin_theme_chrome Multi-theme support (D-007) themes read-only; theme_set edits ONLY the theme: field (one-line diff; refuses themes that cannot render current content); theme_chrome edits ONLY the chrome menu wiring
quoin round (CLI, D-010) The round mechanism (spec/03 §7): fold all open change/* PRs, gate, build, stamp expiry ribbons, upload the browsable preview (+ optional review dir) to stamped append-only slugs refuses on dirty tree, fold conflicts, or any gate failure; reloads the site adapter from the fold; append-only slugs — never overwrites what a requestor saw; --dry-run

3. Creation and update are distinct intents

put_page on a page id that already exists MUST be refused unless the caller passes an explicit update flag (update: true). The result reports whether the page was created or updated. Rationale: page ids are stable URLs; a silent overwrite can mask a typo’d id landing on the wrong page, and the editorial workflow treats “new page” and “edit page” as different intents with different risk. The PR diff remains the authoritative record either way.

4. The theme jail

put_theme_file is path-jailed to themes/** (D-007):

  • Any theme directory — active or not, existing or new — kebab-case dir names enforced. Building a theme alongside the live one requires no switch.
  • Refuses absolute paths, any .. traversal, trailing-slash (directory) targets, and anything resolving outside themes/.
  • Refuses src/styles/tokens.yaml with a redirect to quoin_put_tokens (token values are schema-validated data).
  • Unreachable by any tool, always: site.yaml except the theme knobs (quoin_theme_set edits only the theme: field; quoin_theme_chrome edits only the chrome menu wiring — both PR-gated), trust.yaml, src/lib/**, src/content/** (use put_page/put_menu), tools/**, src/content.config.ts.
  • Every write is followed by the full validation gate; the write is kept on failure (the agent iterates on the branch) but open_pr refuses until validation passes.

Token semantics (companion fix, D-007): tokens.yaml is the site’s knob set; a theme manifest declares which knobs it consumes. Validation requires every consumed knob to exist (missing → build failure) and every site token to be hex; site tokens a theme does not consume are allowed — other themes may declare them.

5. Site adapter and discovery

Normative from D-005: the tool resolves the site by walking up from the working directory to the first directory containing site.yaml (env override QUOIN_SITE), then loads <site>/src/lib/schemas.ts — the site adapter — and verifies the contract symbols on every load. A missing symbol is a hard error naming the contract. Site repos contain no tool code.

6. Region annotation and inspect mode

Every page rendered by a theme MUST annotate its regions so a human can give precise, nameable feedback:

  • Chrome landmarks carry data-quoin-region with the reserved names site-header, banner-nav, page-main, site-footer.
  • Each rendered block is wrapped with data-quoin-region="block: <type>" (e.g. block: feature). Wrappers MUST be layout-neutral.
  • When a page is viewed with ?inspect in the query string, the theme draws labelled outlines over every annotated region (dashed border + name badge). Without the parameter the annotation MUST be fully inert — no styles, no layout effect, script no-op.
  • Region names are a reserved vocabulary: themes MUST NOT repurpose them, and new region kinds (e.g. named columns) MUST follow the same data-quoin-region mechanism so tooling can enumerate them.

7. What no tool may reach

Whatever the enforcement boundary, some files change only through human PRs, with no tool path at all: page-type schemas and the content config (the structural vocabulary), site.yaml identity/contact/registration/footer fields, trust.yaml (the dial), and the tool surface itself. This is deliberate: the rules that define the enforcement boundary must sit outside it.