The same platform definitions — catalog-info.yaml fields, gas charge defaults, queue/cron defaults, env-var names — used to live in four independent copies that drifted apart:
~/.claude/rules/*.md — the rules Claude Code loads each session (hand-maintained).tawa-docs/conventions/*.md — the canonical docs (git, PR-reviewed).tawa_docs) + the public docs site, synced from conventions.Nothing tied them together, so a fact like "the default charge mode is service" could be correct in the code and wrong in three doc copies at once. Worse, the builder's skills registry was seeded from the hand-maintained rules, so the distribution system just re-spread the drift.
This system makes the code the source of truth, makes tawa-docs/conventions the single canonical doc, and gives a machine a way to detect when any of them disagree — so Claude Code (and humans) can trust what they read instead of churning on stale guidance.
flowchart TD
CODE[iec-builder code - the real behavior] -->|probes verify| CONTRACT[platform-contract.json - pinned facts]
CONTRACT -->|drives corrections| CONV[tawa-docs/conventions - canonical docs]
CONV -->|sync-to-koko| KOKO[Koko conventions + gates]
CONV -->|sync-to-bible| BIBLE[Bible / tawa_docs + docs site]
CONV -->|seed-skills| REG[builder skills registry]
REG -->|tawa skills sync| RULES[every dev ~/.claude/rules]
CONTRACT -->|verify-contract --publish| EP[builder /platform/contract-status]
EP --> DASH[tawa-web admin - Platform Contract]
Two rules make it work:
~/.claude/rules) is generated from tawa-docs/conventions. Editing a rule by hand is a smell — fix the convention instead.tawa-docs/contract/platform-contract.json is a machine-readable list of facts. Each fact records the agreed value and a pointer to the code symbol that defines it.
{
"id": "charge_default",
"expected": "service",
"status": "verified",
"source": { "repo": "iec-builder", "file": "src/services/dependency-resolver.ts", "symbol": "dep.charge ?? 'service'" },
"probe": { "type": "regex-string", "pattern": "charge\\s*\\?\\?\\s*['\"](\\w+)['\"]" },
"docs": ["conventions/catalog-info.md"]
}
status distinguishes two cases:
| status | meaning | a mismatch is... |
|---|---|---|
verified | the code currently matches this value | a regression — fails CI |
target | an agreed change not yet landed in code | pending — warns only (until --strict) |
So the contract doubles as a migration tracker: a target fact flips to verified the moment the code change lands.
tawa-docs/scripts/verify-contract.mjs (zero dependencies, npm run verify:contract) does three things:
iec-builder, iec-cron, iec-queue, iec-koko) and compares it to the contract. If a verified fact no longer matches, that is a regression (exit 1). This is what catches a developer changing CURRENT_CATALOG_VERSION, a queue default, the cron gas cost, etc., without updating the docs.conventions/*.md for known-stale phrasings (e.g. callerOrg (default), retryDelayMs: 5000) and reports file:line. Set RULES_DIR=~/.claude/rules to also lint the distributed rules.id and title, or sync-to-koko silently skips it (the doc never reaches the Bible or the site). Missing frontmatter fails the guard.Run modes:
npm run verify:contract # report, exit 1 on regression/error
npm run verify:contract:strict # also fail on pending / lint / frontmatter
npm run verify:contract:json # machine-readable snapshot -> contract/status.json
npm run verify:contract:publish # POST snapshot to the builder dashboard
In CI, run verify:contract in each service repo (point DEV_ROOT at the checkout). If someone changes a pinned value, the build fails until the contract — and therefore the docs — are updated in the same PR.
iec-builder/scripts/seed-skills.ts reads tawa-docs/conventions/*.md (NOT ~/.claude/rules), maps each convention to a tawa-*.md rule via a small table, and pushes it to the builder skills registry. tawa skills sync then distributes the registry to every developer's ~/.claude/rules.
Two details matter:
1.0.<n>). The builder manifest hashes name:version, so a convention edit changes the manifest hash, and tawa skills sync detects the update. (The old static 1.0.0 meant content changes were invisible to sync.)BUILDER_TOKEN) or the platform internal key (INTERNAL_SERVICE_KEY as X-Internal-Key).NOTE:
tawa skills syncis a one-way overwrite — it replaces the local file with the registry version, with no merge. Preview first withtawa skills list,tawa skills diff <name>, andtawa skills sync --dry-run.
tawa-web Console → Admin → Platform Contract (/console/admin/contract, super_admin only) shows live drift status: per-fact OK / regression / pending, the expected value vs what the code actually has, and any stale strings or frontmatter gaps.
The data comes from the builder endpoint GET /platform/contract-status (iec-builder/src/routes/platform.ts), which serves the latest snapshot published by verify:contract:publish. If the builder has not published yet, the page falls back to a bundled snapshot. The verifier needs filesystem access to the service repos, so it runs in CI (where the repos are checked out) and publishes the result; the pod only renders it.
dependenciesThe first decision landed through this system was collapsing internalDependencies / externalDependencies into a single spec.dependencies array:
janus (metered, JWT-verified). direct is the explicit opt-in for same-org UI/API splits; gateway is public/no-URL.{SERVICE}_URL injected.internalDependencies / externalDependencies deprecated — still parsed (back-compat) but emit a warning pointing to dependencies.charge default is service — the service's own org pays gas. callerOrg is the opt-in override (forward the end-user JWT as X-Forward-User so Janus bills the caller's org).These were ruled in the contract first (as target facts), then the iec-builder code was changed (src/services/catalog.ts, dependency-resolver.ts), then the facts flipped to verified, and finally the conventions + distributed rules were corrected to match.
| Thing | Location |
|---|---|
| Canonical docs | tawa-docs/conventions/*.md |
| The contract | tawa-docs/contract/platform-contract.json |
| The guard | tawa-docs/scripts/verify-contract.mjs |
| Registry seeder | iec-builder/scripts/seed-skills.ts |
| Dashboard endpoint | iec-builder/src/routes/platform.ts (/platform/contract-status) |
| Dashboard page | tawa-web/src/app/console/admin/contract/page.tsx |
| Distributed rules | every dev's ~/.claude/rules/tawa-*.md |
tawa-docs/conventions file in the same PR. If your change touches a pinned value, also update platform-contract.json (or the guard fails). CI catches you if you forget.platform-contract.json with a source pointer and a probe. Run npm run verify:contract to confirm it matches.seed-skills (populates the registry), then tawa skills sync (pulls to local rules). Preview with --dry-run first.IMPORTANT: Never hand-edit
~/.claude/rules/tawa-*.mdas the fix. That is a downstream copy. Fix the convention, re-seed, and sync — otherwise the next sync overwrites your edit.
Last updated: June 24, 2026