On this page
MSO Cloud is built by coding agents working under a rulebook that is enforced by machines, not by review etiquette: rules live in path-routed packs, a bug becomes a permanent guard, every change passes the same gates, and work is split across models with a reviewer in charge.
The premise is simple. An agent forgets, and a human reviewer gets tired. So every lesson the project has paid for is written down once, in one place, and wired into a command that fails.
1. The rulebook is a router, not a manual#
The root CLAUDE.md is a router. It holds the agent execution contract, the CI definition, and the Definition of Done, plus a table that maps every numbered rule to the file that owns it. Domain rules do not live there.
- Path-routed rule packs. Six packs under
.claude/rules/carry the numbered catalog:ecom-data.md(money and time invariants, SQL and date mechanics, ingest),ecom-recon.md(creator-content merge and the reconciliation contract),ui-charts-copy.md(charts, formatters, dates, copy, i18n),ui-shell-design.md(shell, design tokens, plugin gates, performance budgets),security-tenancy.md(RLS, auth, rate limits, data-plane isolation),mso-decision.md(decision engine and AI resolution). Each pack declares apaths:list in its frontmatter and loads only when the session touches a matching file. An agent editing a chart never pays context for the tenancy pack. - Area maps. Nested
CLAUDE.mdfiles (packages/schema,packages/connectors,packages/metrics,apps/web,apps/worker, and deeper maps under the content-intelligence subtree) hold recipes for their subtree and load on demand. - Stable numbers. Every rule carries a stable section number and lives in exactly one pack. Numbers are never reused or renumbered because they are cited throughout the codebase, from source and tests to audit docstrings and SQL comments; a citation has to keep resolving years later.
- Incident-rule format. Every entry is written as
Rule. Why (one line). Enforcement: layer/path (exception token if any).The incident story lives in the docstring of the test or audit that enforces it, and in git history. It is never retold in the rule. - Size budgets. An audit caps the router, the agent guide, and each nested area map at hard character budgets, measured in characters because that is what the agent harness measures. The audit runs in the standard gate and has no suppression token: when a new rule lands, detail moves out to the owning docstring or doc, or the rule does not land. The router had once bloated to several times its budget and degraded every session before the restructure.
AGENTS.md is the tool-agnostic entry point for non-Claude agents. It defines the startup protocol and defers to the router on conflict.
2. The class-of-bug contract#
The rule in the root file: a recurring bug never gets fixed only where it was found. It gets a stable rule plus the strongest proportional guard at the layer that owns the invariant, wired into the one canonical gate.
The guard ladder, strongest first:
- Type, schema, or DB constraint. A unique index or a discriminated union that makes the bug unrepresentable.
- Behavioral test. A unit test at the layer that owns the math.
- Static audit. An AST or pattern audit over the repository when the bug is a shape, not a value.
- Grep ratchet. A pinned per-file baseline that may shrink and never grow, for classes too large to fix at once.
A numeric bug carries an extra requirement. It also gets a reconciliation assertion in scripts/audit/data-reconciliation.ts, and the expected value has to trace to external ground truth: an operator rule, a client's own export, or a frozen oracle script derived from one. Never a second derivation in code, because a second derivation reproduces the same misunderstanding and agrees with itself. One episode "fixed" a correct number downward for exactly that reason, which is why the rule now states that a discrepancy is not presumed to be a bug until the semantics are confirmed against the external source.
The reconciliation harness exists because a series of data-correctness bugs shipped to production while typecheck, lint, unit tests, and the production build were all green. Cross-surface arithmetic invariants (headline equals the sum of the rendered rows, a rolling metric equals the period metric over the same window, a two-feed merge does not double count) only hold against real data in a real database.
3. The gate pipeline#
Done means all three stages are green. The stages are deliberately unequal: the local hooks are fast and honest about what they skip, and CI owns everything that needs a database or a browser.
flowchart TD E["Edit"] --> PC["pre-commit<br/>format:check · security:scan"] PC --> PP["pre-push<br/>typecheck · lint · unit tests · audit:all · dependency audit"] PP --> CI["GitHub Actions ci.yml"] CI --> J1["check<br/>build + budgets<br/>+ topology"] CI --> J2["production<br/>images"] CI --> J3["data reconciliation<br/>fresh Postgres"] CI --> J5["e2e smoke"] J3 --> J4["verify:rls<br/>runtime role"] J1 --> D["Done"] J2 --> D J4 --> D J5 --> D
Properties that matter more than the list itself:
- One enumeration.
audit:allinpackage.jsonis the only place the static audits are listed. The pre-push hook and the CI job both call that script, so local green and CI green cannot drift. Adding an audit inline in a hook or a workflow is prohibited. - The hook is not CI-equivalent, and says so. The pre-push comment states plainly that it omits the DB-backed gates. The canonical local gate is
pnpm run verify, which adds build, performance budgets, production Compose verification,verify:data, andverify:rls. - Verify at the root.
pnpm testandpnpm run verifyrun at the workspace root. Single-package--filterruns are banned as a verification claim: a filtered pass hides schema, connector, and metrics failures that the root gate catches. - The data job runs the real pipeline. CI migrates a fresh Postgres, seeds adversarial fixtures that exhibit the edge cases (including a deliberately wrong row), then runs the actual promoters and rollups before reconciling. It reconciles the production code path, not a fixture-shaped twin.
- The RLS job changes identity. It reconnects as the non-superuser application role with
RLS_REQUIRE_APP_RUNTIME=1, because a superuser connection string silently bypasses every policy while the SQL still looks correct. - No bypass.
--no-verifyis not an option in this project. A high-severity dependency advisory blocks the push and prose acceptance does not unblock it.
4. The static audit fleet#
scripts/audit/ holds the audit fleet, written in Python and TypeScript. Nearly all of it is wired into audit:all (several audits run a self-test first, so the audit itself is tested before it judges the repository); a further set runs under verify:rls, and the reconciliation harness runs under verify:data. The fleet only grows: every recurring incident adds a script, and none is retired while its bug class exists.
Each audit encodes one shipped incident, with the story in its docstring. A sample of the classes they hold:
- Timezone day edges. SQL
::dateprojections must be bucketed at Asia/Ho_Chi_Minh and cast to text, because a UTC day edge drags an adjacent day into the range and a driver-returned Date re-buckets downstream. This class shipped more than ten times before the audit existed. - SQL comment interpolation. In a Drizzle tagged template every
${expr}becomes a bound parameter even inside a--comment, which corrupts the parameter sequence and fails at runtime only. Typecheck, lint, and build all pass. - Nullable upsert targets. Postgres treats NULLs as distinct in a unique constraint, so an
ON CONFLICTwhose target includes a nullable column never fires for NULL-keyed rows and every re-sync inserts duplicates. This inflated a spend denominator in production. - Runtime ACL inventory. Every Drizzle table must be registered in the runtime grant inventory. The production migration fails closed on drift, so an unregistered table passes every local gate and breaks only at deploy; the audit moves that failure to pre-push.
- RLS brand pinning. A ratchet on the literal NULL-brand pin idiom, which silently disables the brand leg of a policy while the SQL still reads as brand-safe.
- Copy quality. One audit blocks marketing filler and self-describing chart subtitles in user-facing strings; another enforces a single quote glyph across the repository after a generated deck came back wearing a quote character the product never prints.
- i18n hardcoding. A ratchet with a pinned per-file baseline: counts may shrink, never grow.
- Operator standing rules. Table pagination defaults, semantic CSS tokens, design-token drift, and similar rules that started as a repeated operator correction and ended as a script.
Audits fail closed. The only escape is an explicit audit-ok-* token at the call site, and only where the owning rule lists that token. Ratchet audits carry a checked-in baseline file that may only decrease.
5. Multi-model orchestration#
Work is split across models rather than run as one long single-agent session.
- An orchestrating model owns the decision and the review. It reads the request, sets scope and verification, and integrates the result. It does not do the bulk implementation.
- Worker models run bounded briefs in parallel. The agent contract states the terms: a sub-agent gets concrete scope, the expected artifact, read and write ownership, and a verification requirement, and the primary agent reviews the evidence and the integrated result before it counts. Delegation without a verification requirement is not delegation.
- Adversarial review before ship. Read-only audits are commissioned as their own workstream with an explicit scope, and land as reviewed documents — commissioned and curated separately from the model that wrote the code, with findings batched into waves and applied through the normal change process.
- Expert-panel debate for design decisions. Contested architecture is put through a multi-role review before it becomes an ADR. ADR 0019 carries its five-role council review in the record.
- Cross-vendor portability. Repository skills live in
skills/and the Codex-native copies in.codex/skills/, so the same workflow runs from a different vendor's CLI. The rulebook is split the same way:CLAUDE.mdfor the Claude harness,AGENTS.mdfor everything else, one of them authoritative. - Workflows are skills, not prose. Ten repository skills currently encode multi-step procedures (connector backfills, data maintenance, corpus onboarding, market-data pulls, PDF export, diagram authoring, brand identity, dev-only org import). Only the name and description load at startup; the body loads when the skill is invoked. The router carries a routing table from request shape to skill.
6. Memory and decisions#
- ADRs for architecture.
docs/adr/holds the numbered decision records, each as Markdown with a generated HTML twin, covering the plugin architecture, runtime RLS and the transactional outbox, the demo posture, brand-scoped ingest, the universal report surface, proof-carrying numbers, tenant domains, and the rest. An ADR states the decision and its consequences. - A persistent operator memory. Standing operator rules and incident lessons persist across sessions outside the repository and are the raw material for repository rules. The path is one-directional: a correction repeated twice becomes a memory entry, a memory entry that touches the codebase becomes a numbered rule, and a numbered rule that can be checked mechanically becomes an audit in
audit:all. The site-wide table pagination default followed exactly that path. - Documents record final decisions only. No narrative of how the decision changed, no appended revision history inside the document, no "first we chose X". A superseded decision is edited in place or replaced by a new ADR. Reference documentation stays in Markdown when it is grepped; everything read front to back is published as HTML through the docs pipeline.
7. Honesty discipline#
The anti-fabrication rules the product enforces on its own numbers apply to the engineering record too.
- Graded, dated, and not rounded up. Platform capability is published on explicit ladders with a dated status per tier: an evidence ladder (L0 to L5) for how trustworthy a number is, and an independent autonomy ladder (A0 to A4) for how much the platform is permitted to act. Tiers are marked as passed, shipped, prototype, or foundation. A high evidence grade does not grant a high autonomy grade.
- Abstain over invent. Metric paths return null,
na, orinsufficientrather than a plausible value; missing data is stored as missing, never as zero. - The same rule for agents. A gate that was skipped, failed, or ran against a stale cache is never reported as green, and a report states what was verified, what was skipped, and what risk remains. An audit is evidence; invoking a skill named "review" is not.