Skip to content

Repository files navigation

Coral Architecture

A set of rules for organising code in a repository, written to be followed by coding agents as well as by people. The organising principle is one sentence: one trigger, one owning capability, end to end. One command or one endpoint is answered by one unit that owns the whole of answering it, and every unit of code has one of five known roles. The same shape applies to a CLI, a backend, a web app, a library, or a tool, and it composes from a single capability up to a whole system.

That core is small, and it is about ownership rather than layout. Everything most people picture alongside it — code grouped by what it does rather than by what kind of code it is, the capability's files sitting together instead of spread across handlers/, services/ and repositories/, tests beside the code they verify, no utils bucket, crosscuts injected rather than reached for, a fixed error taxonomy, transaction and retry policy, no shared database between apps — is Coral's production baseline: published as one coherent opinion, and adopted by a project explicitly rather than inherited by using Coral at all.

📖 Live docs: https://coral.appsandtools.work — the guided version, with a worked directory layout, the five kinds of code, and where the architecture does not fit.

Coral is a poor fit for dense domains where every feature reaches into one central concept — a tax engine, a scheduler, a solver. That limit is stated as a rule ([SCOPE-2]), not as a footnote.

The documents

Start with CONVENTIONS.md. It defines the eight nouns every other document uses, the rule-ID scheme, the enforcement classes, the agents-write / humans-review operating model, the Coral kernel — the rules Coral would substantially relax without the agent-author / human-architect operating model — and what applies to a project, which is how a project declares how much of Coral it has taken on. The rest refer back to it instead of restating any of it.

From there, in the order a project actually decides things:

  1. ARCHITECTURE.md — the kernel-facing app architecture: the shape of one app, and the rules whose presence or strictness Coral justifies by its operating model. This binds a Coral codebase without being adopted, and it is short.
  2. PRODUCTION.md — the production baseline for one app, and the decision whether to take it: package naming, buckets, state ownership, concurrency, idempotency, errors, config, observability, contracts, trust, testing, growth. Optional and subordinate — Coral publishes it as an opinionated production-engineering policy, and it applies only where a project's CORAL.md says production-baseline: true. Its justification is that the software needs it, not that an agent wrote it.
  3. SYSTEM.md — how separately-built apps compose over a channel. Optional as well, and two independent opt-ins: the system-scale production baseline, and the runtime-agent orchestration rules. Neither implies the other.
  4. appendix/ — one document per app profile (CLI, backend, web, library, GitHub Action), plus the runtime-agent addendum an app of any shape adds when it calls a model at runtime. Adopted by name.

And examples/ holds worked code — including a real service reviewed against the rules, which states where they would have been overkill.

To look a rule up rather than read for it, rules.md lists all of them on one page with their class, their ownership layer, their scale, and a one-line statement, grouped by document. It is generated from the documents (npm run rules:index) and the build fails if it falls behind them, because an index that can drift from what it indexes is worse than no index.

Only a small part of Coral is imposed because an agent writes the code. That subset is the kernel — ten rules, named and justified one at a time. General production-engineering policy is published separately, as the production baseline and the app profiles, and is adopted rather than inherited. The build enforces the separation structurally: an opt-in rule cannot be defined in a core document.

Rules carry stable IDs like [DUP-2], and three independent classifications: an enforcement class ([auto] / [review] / [guide]) saying how the rule is checked, an ownership layer saying who has to read it — so a CLI is not asked to reason about HTTP status codes or runtime-AI rules — and an architectural scale saying whether it governs one app or several apps composing. On the live site every citation links to its definition. The build fails if a rule has no class or no layer, if a citation has no definition, if a rule is missing from its document's Agent Execution Contract, if a contract lists an opt-in rule without saying so, if an opt-in rule is defined in a core document, if a published rule ID has disappeared or been reclassified, if the rule index is stale, if the worked CORAL.md in CONVENTIONS.md stops resolving, or if a link fragment doesn't resolve — the docs' own drift control is structural, not goodwill.

Versioning, and how a project records where it differs

Coral is versioned because it will be incomplete: rules get missed, patterns need covering, and some rules turn out to be wrong. What changed is in CHANGELOG.md, recorded per rule ID. Rule IDs are append-only — never renumbered, recycled, or removed — and rules.lock is the checked-in record the build enforces that against.

Two versions, and the difference matters now that version identity is part of applicability. The latest released version is in VERSION, and it is the only one a project can target. The working version is the rule set these documents currently describe, named by the changelog's Unreleased heading; between releases it is a successor to VERSION, so main describes rules that are not in any release yet. CONVENTIONS.md spells the split out.

A consuming project keeps a CORAL.md in its root. It is the one file that answers what rules apply here, and it carries three things:

  • the Coral version the project targets ([VER-3])
  • what it adopts ([VER-6]) — the scales it is written at, and the non-kernel scopes it takes on. The kernel applies without being declared; everything else, the production baseline included, applies because this block says so. A rule never becomes applicable just by existing in this repository, so adding a profile or a rule here changes nothing for a project until that project adopts it. A missing or invalid declaration is a configuration finding, not a licence to audit against everything.
  • two kinds of local divergence, each scoped to a path:
    • an Exception — Coral has a rule; this project knowingly breaks it for a trade-off, in one subtree
    • an Extension — Coral has no rule; this project needs one; it stays local, under its own ID prefix

Coral layers compose by union, with no precedence between them: no layer overrides another, and two Coral rules that contradict each other are a defect in Coral to be filed upstream rather than resolved locally.

A third kind isn't recorded locally at all: an Amendment is when a Coral rule is wrong or too narrow, and it goes upstream as an issue or PR on this repo. An exception that recurs across projects is the signal for one — and when the amendment lands, the local entries are deleted and the project bumps its target. The register shrinks when Coral improves.

An agent never authors an exception or an extension ([AGENT-4]); it flags the ambiguity and a human decides and records it. And it reads CORAL.md before escalating ([AGENT-5]), so a settled decision isn't re-litigated by every agent that meets it. The full convention is in CONVENTIONS.md.

From that declaration a project generates CORAL-CONTRACT.md: one Markdown file holding its complete normative Coral surface — every applicable [auto] and [review] rule, plus its own exceptions and extensions — so an agent loads that file and no Coral document.

# from a Coral checkout describing the version the project targets
npm run contract:generate -- --project /path/to/project

The command lives here, in the Coral checkout, and the consuming repository is named by --project — Coral supports projects that are not Node projects at all. The rule model always comes from the checkout that runs the command, with no flag to point it elsewhere: the documents are only half of a release and the code that reads them is the other half, so generating for another Coral version means checking out that version and running its own contract:generate. The output is generated, never edited: CORAL.md stays the only file a project writes by hand. Rules from a scale, layer or profile the project has not adopted leave no trace in it, and output is byte-identical for the same inputs. An unresolvable declaration produces an error and no contract — including no stale contract, since a failed regeneration removes the one the previous run wrote rather than leaving a file that looks current. Only files this generator wrote are ever removed or replaced; anything else at the destination is refused, not overwritten. CONVENTIONS.md documents it.

The linter

tools/coral-lint/ implements Tier 1: the [auto] rules a static check can decide. Eleven today — [BUCKET-1], [XCUT-2], [STRUCT-1], [ROOT-2], [STATE-2], [CONFIG-2], [CONC-1], [IDEM-2], [ERR-2], plus [LIB-3] and [LIB-5] for published libraries — with every other [auto] rule listed under --coverage alongside a stated reason it isn't checked yet, so nothing is silently uncovered.

It is not a blocking conformance gate today. Every rule it checks is production-baseline or app-profile, so none of them binds a project that has not adopted that layer ([VER-6]), and the tool cannot resolve a project's declaration yet. So it fails closed: by default it reports a configuration error rather than findings, and --ignore-applicability produces output that is explicitly advisory rather than a conformance verdict. Its README says what closing that gap needs.

cd tools/coral-lint
python3 -m coral_lint /path/to/repo --ignore-applicability   # advisory; exit 1 on findings
python3 -m coral_lint --coverage                             # what runs, and why the rest doesn't

No dependencies, Python 3.11+. [BUCKET-1] needs no configuration, so it is useful immediately; the rest read a coral.toml in the audited repo declaring where its slices live, because guessing is how a linter earns false positives. It is itself a Coral CLI — one slice per check — and it passes its own gates.

The audit skill

.claude/skills/coral-audit/ is the operational counterpart to the docs: point it at a repo and it produces a CORAL_AUDIT.md answering one question — is this a Coral app, and where does it diverge? Structural divergences are the findings; bugs it happens to surface are recorded as awareness notes, never the verdict. It diagnoses only — the refactor approach is decided later, by a human in a separate planning session.

Because it lives in .claude/skills/, Claude Code picks it up automatically when you work in this repo. To audit other repos — which is the point — install it at user level:

ln -s "$PWD/.claude/skills/coral-audit" ~/.claude/skills/coral-audit

A symlink rather than a copy, deliberately: two copies of the same rules drift, and the docs are the one place the rules are allowed to live.

Run the docs locally

npm install
npm run docs:dev      # live preview at http://localhost:5173
npm run docs:build    # static site → .vitepress/dist
npm run docs:preview  # serve the built site

Deployment

The site is hosted on Cloudflare Workers Static Assets at coral.appsandtools.work. It is static only: wrangler.jsonc declares no main entrypoint and no run_worker_first, so every request is served by Cloudflare's asset server rather than by a Worker invocation. That keeps the site inside the Free plan and adds no KV, R2, D1, or Durable Objects.

Deployment runs through Cloudflare Workers Builds (Cloudflare's own GitHub integration), not through GitHub Actions. Pushing to main triggers a build that runs npm run build — the full pipeline, including the version, rule, and anchor checks — and then npx wrangler deploy, which publishes .vitepress/dist. Because Cloudflare authenticates the repository through the GitHub App, no Cloudflare API token or account ID belongs in this repository.

.node-version pins the Node major used by both local and Cloudflare builds, so a change to Cloudflare's default Node version cannot silently change the build.

One-time Cloudflare setup

Do this once, in the Cloudflare dashboard, after this change is merged.

Workers Builds — Workers & Pages → coral → Settings → Build:

Setting Value
Repository throw-if-null/coral
Production branch main
Build command npm run build
Deploy command npx wrangler deploy
Non-production branch builds enabled (gives every branch and PR a preview URL)

Preview builds use Cloudflare's own preview deployment mechanism; preview_urls is set to true in wrangler.jsonc so branch builds get a versioned preview URL even though workers_dev is off. The account needs a workers.dev subdomain for those preview URLs to resolve.

Custom domain — Workers & Pages → coral → Settings → Domains & Routes:

  • add coral.appsandtools.work as a custom domain (not a route)
  • appsandtools.work must already be a zone on the same Cloudflare account
  • Cloudflare then creates and manages the DNS record and the TLS certificate

wrangler.jsonc already declares the custom domain, so npx wrangler deploy claims it on the first deploy; adding it in the dashboard is only needed if that step is skipped.

Retiring the Azure Static Web App

The Azure resources are deliberately not touched by code. Delete them by hand, and only after the Cloudflare deployment is confirmed good:

  1. deploy successfully to Cloudflare
  2. verify https://coral.appsandtools.work loads
  3. verify representative pages — CONVENTIONS, ARCHITECTURE, PRODUCTION, SYSTEM, rules, one appendix/ page, one examples/ page
  4. verify static assets and Mermaid diagrams render
  5. verify anchor links resolve, including a cross-page rule citation such as PRODUCTION#DUP-2 (linked from ARCHITECTURE, so it exercises the deep link and the page it lands on)
  6. verify a nonexistent URL returns the VitePress 404 page with a 404 status
  7. only then delete the Azure Static Web App
  8. remove the GitHub repository secret AZURE_STATIC_WEB_APPS_API_TOKEN_GRAY_HILL_09BB08B03

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages