Skip to content

Standing Rules

Rules that govern how RTOpacks operates. Two kinds live here:

  • Operating principles — frames that govern how decisions get made (calibration, single-substrate architecture, naming discipline, canon-first creation)
  • Operational rules — recurring-attention tasks tied to specific systems, conditions, or schedules (token rotation, database account context, sync emails, etc.)

Both belong in this file because the file gets reached for. Splitting principles into a separate doc would mean they get found less often, and a 3-person operation can't afford the muscle-memory tax of "rules go here, principles go there." When in doubt, this is the file.

Last updated: 2026-06-01 (broadened EXECUTION-AUTHORITY-LOOSE-USE-01 with one rto-nrt-db carve-out per EXECUTION-AUTHORITY-SCOPED-REVISION-01 v2 — Alex autonomous on all in-scope work, destructive ops on rto-nrt-db need a Tim nod, wrangler credential mechanism OAuth-only; prior 2026-05-30: added METADATA-RECONCILIATION-AT-COMMIT, RECON-PASS-ON-FOUNDATION-SHIFT, CANONICAL-PROJECT-FILES-CURRENCY; governance hierarchy expanded with position 4b for recon documents; added BRIEF-DRAFT-SUBSTRATE-VERIFICATION, SUBSTRATE-BRIEF-GATE-DISCIPLINE, MIGRATION-COMPLETION-DISCIPLINE; canonical-work cluster grows 3 → 6 rules; added SUBSTRATE-NAME-FOLLOWS-OPERATIONAL-SHAPE, ROUTE-MIGRATION-REQUIRES-OLD-WORKER-DELETION, METHODOLOGY-SERVES-FOUNDATION at IMM-01 close arc / ENVIRONMENT-NAME-RENAME-01 promotion — three disciplines reaching threshold via 2026-05-27 PM execution; added TYPE-DELTA-COMPLETE-CONSUMER-SWEEP and CANONICAL-IDENTITY-VIA-UI-ONLY at IMM-01 Phase 3c close — two disciplines reaching threshold via 2026-05-28 substrate-meets-built-product reconciliation; operating-principles cluster now 20 rules total; MANDARIN DATA TAXONOMY grows four → five categories with addition of Telemetry per ADR-028 (2026-05-28) — system observability named as canonical substrate, live telemetry to Cloudflare Analytics Engine, audit/activity records to D1; added CHANNEL SEPARATION RULE to the operating-principles cluster 2026-05-30 per ADR-031 / INTERNAL-API-ISOLATION-PROGRAMME-01 — operationalises the channel-separated service architecture commitment (within-account = service binding, across-boundary = cryptographic auth, browser → same-origin route, fail-closed with no public-HTTP fallback, non-forgeable internal caller identity required); sibling to the HARD SEPARATION RULE — channels vs data domains)


Roles

  • Tim — founder, orchestrator, writes and approves all briefs, makes all strategic calls.
  • Alex — Claude Code engineering agent. Executes technical briefs handed down by Tim. Does not receive direct instruction from Claude.
  • Jimmy — director, partner, business development.
  • Claude — architect, brief-writer, strategic advisor. Works directly with Tim. Produces briefs that Tim reviews and approves before they reach Alex.

Claude does not bypass Tim. Every brief goes Tim → Alex, never Claude → Alex.


Governance hierarchy

When two sources conflict, the higher-priority one wins:

  1. Client Spine (ops/client-spine.md) — foundational architectural articulation. Governs all downstream decisions about what RTOpacks is and the substrate it is built on.
  2. WS-PRODUCT-01 (workspace/product.md) — governs all module specs and product surface decisions.
  3. Standing Rules (this doc) — governs operational behaviour and discipline.
  4. Architecture Decisions (ops/architecture-decisions.md) — log of specific architectural commitments. Subordinate to the spine doc, authoritative on its own scope. 4b. Recon documents (ops/recon/) — scoped re-reads of canonical foundation against open work artefacts. Identify implications of foundation shifts; do not themselves create or supersede canonical commitments. Subordinate to ADRs; ahead of Design Foundation only in governance-discipline scope (recons govern what briefs do; design foundation governs what surfaces do).
  5. Design Foundation (design/foundation.md) — governs every visual/interaction decision. Hard constraints, no deviation without explicit Tim sign-off.
  6. Glossary (workspace/glossary.md) — canonical product names. Do not rename without Tim sign-off.
  7. Module specs — governed by WS-PRODUCT-01 and the spine doc; authoritative within their module.
  8. Briefs — session-scoped work artefacts. Never the source of truth for anything post-ship.

Operating principles

GCP-CANON RULE

Added GCP-FOLDER-CANON-01 (2026-05-07). Every new Google Cloud project follows the folder/naming/labelling canon defined in docs/infrastructure/google-cloud.md. New projects are created inside the appropriate entity folder, named per the <entity>-<purpose>[-<env>] convention, and labelled with entity, environment, and (where the consumer is non-obvious) consumer.

If a new project doesn't fit the canon, the canon needs updating before the project lands — not bypassing.

NAMING-PAUSE RULE

Added GCP-FOLDER-CANON-01 (2026-05-07). Before creating any new resource — Cloud project, Cloudflare worker, database, KV namespace, R2 bucket, repo, 1Password vault, OAuth client, API key, service account, Stripe webhook, or any other named asset — pause and resolve four questions explicitly:

  1. What entity does it belong to? (RTOpacks / UCCA Online / UCCA Education / tooling / personal)
  2. What environment does it serve? (shared / prod / dev)
  3. What does it do — in one sentence?
  4. Where does it live in the canon? (folder, naming pattern, labels)

The four answers must exist before the resource is created, not after. If any answer is "not sure," the resource is not created — the question is resolved first.

Substrate-neutral. The same pause applies whether creating a GCP project, a Cloudflare worker, a 1Password vault, or a Stripe webhook. Substrate-specific canon docs (docs/infrastructure/google-cloud.md, future cloudflare-canon, etc.) define what each of the four answers looks like in concrete terms for that substrate.

The failure mode this rule prevents: creating-on-the-fly during build sessions and leaving naming, placement, and description as "I'll figure it out later" tasks. They do not get figured out later. They become next year's <SUBSTRATE>-FOLDER-CANON-NN brief.

OPERATING-CALIBRATION RULE

Added STANDING-RULES-EXPAND-01 (2026-05-08). Rigour level is calibrated to the situation, not defaulted. RTOpacks is a pre-revenue bootstrapped operation with three people and zero customers. Enterprise-grade caution applied to bootstrapped sandbox work is the wrong calibration — it costs throughput in exchange for resilience that has no real-world weight to protect. Default-fast is also wrong — calibration is the rule, not a swing in the other direction.

When proposing process — verification gates, decision passes, multi-stage reviews, cooling-off periods — the rigour level is labelled explicitly: "this is enterprise-grade rigour, do you want it?" Lets the operator push back without reading between lines.

The principle: cost-of-now vs. cost-of-friction, calibrated to actual risk, not theoretical risk.

Heuristics that follow:

  • Speed beats thoroughness when the thing being protected has no real-world weight yet
  • Recreate beats preserve when the cost of recreation is low and the cost of caution is time-to-market
  • Ship beats document until something is shipped that needs documenting
  • Action beats deliberation unless deliberation is cheaper than the thing it protects

Surfaced during GCP-FOLDER-CANON-01 (2026-05-07) when applied enterprise-grade verification gates were costing throughput on work with zero real-world risk. The correction came mid-session and the principle was named in the closing Time Machine.

PROD-VERIFICATION-FALLBACK RULE

Added NRT-SEARCH-RESPONSE-SEPARATION-01 (2026-05-23). Production verification often cannot use a direct curl because customer-facing prod surfaces are intentionally gated. The fallback path is chosen explicitly, not improvised. Four ordered options:

  1. workers.dev fallback — if the worker has its workers.dev URL enabled, use it. Bypasses custom-domain routing, CF Access, and route-precedence interception. Cleanest path when available.

  2. D1-direct verification — if the brief is a binding-swap or table-relocation shape, exercise the equivalent SQL shapes against the prod database directly. Mirrors the route handler's behaviour at the SQL layer. Strictly more verification than waiver because it confirms the prod table is actually usable, not just bound.

  3. Same-commit-as-dev waiver — if neither workers.dev nor D1-direct applies, take the dev Gate 3 verification + same-commit-as-dev-deploy + binding confirmation in deploy output as sufficient. Documented in the close report.

  4. CF Access service token — only if pre-revenue calibration permits. Token issuance + secret handling discipline for one curl is over-investment per OPERATING-CALIBRATION RULE on pre-revenue workers. Earns its place only when 1-3 are all genuinely unavailable.

The matrix is the rule, not the trigger. Custom-domain blocks for any reason — CF Access at the edge, route precedence from another worker, prelaunch page intercepting the apex — all hit the same matrix. The selected branch is named in the close report so the calibration is reviewable.

Earned through three distinct applications across two months:

  • MANDARIN-VIOLATION-01a Gate 3 (waiver, 2026-05-23) — internal-api prod CF Access gated at the edge; workers.dev disabled on this worker; not a relocation shape; chose waiver per OPERATING-CALIBRATION RULE.
  • MANDARIN-VIOLATION-01b Gate 4 (D1-direct, 2026-05-23) — internal-api prod CF Access gated; binding-swap shape applied perfectly because the brief was about tga_sync_log relocation; chose D1-direct, executed equivalent SELECT and UPDATE shapes against rto-ops-db prod.
  • NRT-SEARCH-RESPONSE-SEPARATION-01 Gate 3 (workers.dev fallback, 2026-05-23) — apps/site prod custom-domain route-precedence-blocked (the prelaunch worker intercepts the apex); workers.dev enabled on apps/site; chose workers.dev fallback. Internal-api verification transitive via apps/site proxy's service binding (no separate internal-api curl needed because every test ran through the apps/site → service-binding → internal-api chain).

Third occurrence with three distinct mitigations graduated the pattern from "observed" to standing rule. The matrix is the load-bearing artefact.

EXECUTION-AUTHORITY-LOOSE-USE-01

Added 16 May 2026 at EMAIL-SEND-CF-MIGRATION-01 Phase 4 close. Broadened with a single rto-nrt-db carve-out 1 June 2026 (EXECUTION-AUTHORITY-SCOPED-REVISION-01 v2 — see note at end of rule; the v1 scoped narrowing was drafted but never committed). Supersedes the prior "Tim runs all credential-touching commands" form. Revisits when first paying customer comes online.

While the project is pre-revenue and pre-customer, Alex has full autonomous execution authority within brief-authorized scope — reversible and irreversible work alike (deploys, pushes, loads, commits, secret/KV/D1/R2 mutations, config and DNS edits, force-deletes of project-scoped resources). Alex executes without per-command Tim approval and reports results back. Tim approves the brief; Alex runs the commands.

One carve-out — rto-nrt-db. Destructive operations on rto-nrt-db (uuid 1249760d-070a-43f8-81d7-de462b626cdf) require an explicit Tim nod before execution: database delete, table drops, destructive/irreversible schema migrations, and bulk deletes. Alex surfaces the intended action; Tim approves; Alex executes. Why: it is the one irreplaceable store (the regulated NRT corpus + the KN sacred set). D1 Time Travel covers it for 30 days against everyday damage, but not against deletion of the whole database, and it has no off-substrate backup yet. This carve-out composes with the existing feedback_no_d1_export rule — rto-nrt-db is already a specially-handled database.

(Revisit: once a verified off-substrate backup of rto-nrt-db exists — see KN-BACKUP-AND-REGIME-AUDIT-01 — Tim may relax or retain this carve-out.)

Why:

  1. Recoverable cost. Pre-revenue with zero customers means accidental damage costs Tim some hours of rebuild, not lost customers, lost data anyone else cared about, or commercial liability. The downside is bounded and personal.
  2. Speed and diagnosis matter more than ceremony. Round-tripping every destructive call through Tim adds latency to every brief and makes diagnosis harder (Alex has the context, the running shell, the surface state in front of him; Tim has none of that when he's pasting a command). The ceremony was protecting against a risk that doesn't currently exist.
  3. A year of evidence. Alex wrote the code. Tim and Alex have been working together for a year. The "what if Alex does something wrong" probability is informed by track record, not theoretical worst case.

What still applies:

The technical-mechanism rules from credential-discipline.md remain in force regardless of execution authority:

  • Wrangler ops authenticate via OAuth-cached token (wrangler login flow) — the token value never appears in a command. This is the path for all wrangler-native operations.
  • Env-var indirection (-H "Authorization: Bearer $CF_API_TOKEN", never echoed or expanded into captured output) remains the pattern for the non-wrangler CF API surface (DNS edits, account-level config) — but that token was removed 1 June and is currently absent; re-provision via 1Password when that surface is next needed.
  • Paste-discipline — token values never literal-pasted into terminal output that gets captured (feedback_terminal_paste_credential_discipline.md)
  • Snapshot caveat — source ~/.zshrc 2>/dev/null prefix on Bash subprocesses that need env vars not present in the session-start snapshot

The loose-use rule is about who runs commands; the credential-discipline rules are about how they're run. Both stay live.

Out of scope for Alex (two narrow exceptions where Tim still executes):

  1. External-account work where Alex doesn't have credentials. Resend account close, Stripe surface, Apple Developer, GitHub org-admin actions that aren't gh CLI scope, etc. Not a policy choice — a mechanical fact about which surfaces Alex's tokens cover.
  2. Anything explicitly outside the active brief's scope. A brief authorizes specific work. If Alex notices something else that wants destructive action, the rule is the same as before: surface to Tim, get scope extension or a new brief, then proceed. The loose-use rule doesn't license drift.

One carve-out (Alex executes, but only after a Tim nod): destructive operations on rto-nrt-db — deletes, table-drops, destructive schema migrations, bulk deletes — require a Tim nod (mirrors credential-discipline.md; composes with the no-d1 export tripwire). The KN sacred rows (15,128) are never touched at all — nod or not — per the KN sacred rule (below).

Revisit condition:

This rule returns to its stricter prior form when actual paying customers are online. At that point: damage stops being recoverable at no commercial cost, the "what if" probability matters more because the downside has weight, and the ceremony of Tim-in-the-loop becomes worth its latency cost.

The trigger is "first paying customer," not a particular date or revenue threshold. Whoever's holding context when that lands re-opens this rule and the related credential-discipline.md section.

Revision note (1 June 2026): A first draft (v1) narrowed this rule to a scoped form (every irreversible action needs a Tim nod); it was applied at Gate 1 but never committed. Tim's settled decision went the other way — full hands-off velocity with one named carve-out: keep the original broad authority, protect only rto-nrt-db (the one irreplaceable store with no off-substrate backup yet). The contradicting memory (feedback_brief_drip_execution_boundary.md, "Tim runs all credential-touching commands inline") is retired. The same-day OAuth switch (DEPLOY-TOKEN-HYGIENE-AUTOMATION-01) removed the credential-leak basis for hand-typing routine commands. The carve-out lifts once a verified off-substrate backup exists (KN-BACKUP-AND-REGIME-AUDIT-01). Rule ID retained for citation continuity; the "LOOSE-USE" label fits the broad form.

CLOUDFLARE-FIRST RULE

Added STANDING-RULES-EXPAND-01 (2026-05-08). RTOpacks is built top-to-bottom on Cloudflare. The single-substrate commitment is load-bearing — it concentrates blast radius into one observable, controllable environment, and reduces the coordination tax that a 3-person operation cannot pay.

When new capability needs arise, the default answer is Cloudflare. Vendors outside the substrate need to clear a high bar: not "is this slightly better" but "is this doing something Cloudflare structurally cannot." Multi-vendor diversification at our scale is resilience theatre — the hypothetical resilience benefit only pays out if Cloudflare itself fails, in which case the wider blast radius means we have larger problems regardless.

When Cloudflare ships new platform capability, the default reading is "we use it" unless there is a concrete structural reason not to. The substrate's coherence is itself the load-bearing decision.

The vendor question is asked in this order:

  1. Does Cloudflare provide this?
  2. Is Cloudflare shipping this?
  3. Is the gap one Cloudflare structurally cannot close?

External vendor X is only entertained at step 3.

Surfaced during STAGING-INFRA-01 closure (2026-05-08) when a $20/mo Resend swap to Cloudflare Email Service revealed the deeper frame: the substrate isn't a constraint, it's the architecture.

MANDARIN DATA TAXONOMY

Added 17 May 2026 at INTAKE-DB-EXTRACTION-01 open. Names the four-category data-layer pattern the Mandarin Architecture has converged on. Every D1 database in the project fits one category. Brief authors use the taxonomy to answer "where does this data go" without re-deriving the argument each time.

  1. Pith — shared, app-read-only, external reference data. Single instance, no dev twin. Customer-facing workers read; only dedicated sync workers write. Today: rto-nrt-db (TGA corpus, govt stats).

  2. Sync-output — shared, single-pipeline-write, app-read-only. Single instance, no dev twin. One owner-pipeline writes; everyone else reads. Today: rto-abs-db (abs-sync), rto-radar-db (radar-crawl), rto-licensing-db (teqsa-sync).

  3. Peel — per-env, app-writable, schema-locked. Prod and dev twins with identical schema and independent data. The bulk of the application's writable state lives here. Today: rto-ops-db / -staging, rto-workspace-db / -staging, rto-micro-db / -staging, rto-landscape-db / -staging, rto-calendar-db / -staging.

  4. Intake — per-env, public-form-writable, never-read-back-to-public. Prod and dev twins. Customer-facing workers write submissions here; internal workers read for triage/follow-up. Today (post INTAKE-DB-EXTRACTION-01): rto-intake-db / -staging.

  5. Telemetry — system observability data, written by every worker, read for live diagnosis and retained analysis. Live telemetry does not live in D1: it lands in Cloudflare Analytics Engine (high-volume, fire-and-forget, real-time + time-queryable, writes that do not backpressure the application). Audit/activity records — lower-volume, durable, correctness-bearing "who did what when" — remain in D1 (Peel). Customer-facing and internal workers alike instrument into this layer; uniform coverage is the anti-blindness requirement. Added 2026-05-28 per ADR-028.

Worker classification. MANDARIN's enforcement rules talk about "customer-facing workers" and "internal workers" — those terms map to specific workers in the project. The classification is by role, not just by worker name, because the same worker can be bound to multiple hostnames with different launch postures (e.g. apps/site prod is currently bound to staging.rtopacks.com.au pre-launch, and will be cut over to rtopacks.com.au at launch).

Customer-facing workers — serve unauthenticated public traffic or RTO customer traffic. Cannot read from or write to rto-ops-db. Cannot write to Pith or Sync-output (those are read-only for them). Public form submissions go to rto-intake-db.

  • apps/site — public marketing site. Currently bound to staging.rtopacks.com.au (prod — pre-launch apex artefact, separate concept from env-name) and rtopacks.dev (dev, CF Access gated for dev hygiene only — still customer-facing in role).
  • apps/workspace — RTO customer workspace. Bound to my.rtopacks.com.au (prod) and my.rtopacks.dev (dev).
  • workers/prelaunch — public waitlist site, currently bound to rtopacks.com.au (apex) pre-launch. Retires at site cutover.

Internal-ops workers — serve UCCA-internal traffic only, gated by CF Access. Legitimate rto-ops-db consumers. May read Pith, Sync-output, and Peel as needed. Public visibility of these surfaces is enforced by CF Access at the edge, not by data-layer rules.

  • apps/admin — UCCA admin UI. Bound to admin.rtopacks.com.au (prod) and admin.rtopacks.dev (dev), both CF Access gated.
  • Sync and ingest workers running on cron or queue triggers (no HTTP-public surface): tga-sync, cricos-sync, abs-sync, teqsa-sync, radar-crawl, tga-ingest, qb-reconcile, etc.

Mixed workers — straddle customer-facing and internal-ops roles. Must use authentication context (Cloudflare Access JWT, internal-source header, etc.) to determine which surface a given request is serving, and route data access accordingly.

  • workers/internal-api — serves both apps/site (customer-facing inbound, must not surface ops-db) and apps/admin (internal-ops inbound, may surface ops-db). The auth-context routing inside internal-api is HARD-SEPARATION-INTERNAL-API-AUDIT-01's territory. Until that audit lands, treat any internal-api route that doesn't explicitly check auth context as potentially customer-facing for MANDARIN purposes.

Hostname-to-role map (for orientation; the worker-to-role classification above is canonical, this is descriptive):

Hostname Worker Role
rtopacks.com.au workers/prelaunch customer-facing (apex, pre-launch)
staging.rtopacks.com.au apps/site prod customer-facing (real prod, parked pre-launch — separate concept from env-name)
rtopacks.dev apps/site dev customer-facing role, CF Access gated for dev hygiene
my.rtopacks.com.au apps/workspace prod customer-facing
my.rtopacks.dev apps/workspace dev customer-facing role, CF Access gated for dev hygiene
admin.rtopacks.com.au apps/admin prod internal-ops
admin.rtopacks.dev apps/admin dev internal-ops
internal-api.rtopacks.com.au workers/internal-api mixed (see HARD-SEPARATION-INTERNAL-API-AUDIT-01)

Failure mode this prevents. Without an explicit enumeration, every brief that references "customer-facing workers" re-derives the classification. The smoke-walk of INTAKE-DB-EXTRACTION-01 surfaced this: when apps/admin's contact detail page reads from both rto-intake-db (contact identity) and rto-ops-db (interaction log), the question "is this an OPS SURFACE RULE violation?" needs an answer immediately. With apps/admin classified as internal-ops, the answer is no — that's a legitimate ops-db read. Without classification, the question takes a minute to re-derive and risks inconsistent answers across briefs.

The rule the taxonomy enforces: when a new table needs a home, name its category first. If it doesn't fit one of the four, the proposal is wrong before it ships — either the table is misclassified (find its real category) or a fifth category is being proposed (requires Tim sign-off and a standing-rules update).

Cross-category writes from customer-facing workers are architectural violations regardless of how convenient they seem in the moment. Three were surfaced in the live audit (17 May 2026) and are queued for repair: customer-facing writes to Pith from internal-api and admin (filed as NRT-DB-DEPOLLUTE-01 and associated briefs).

NO-WEAPONISED-LOCK-IN

Added 2026-05-27 at the close of the canonical articulation arc (spine § 1 dispositional expansion; ADRs 020-023).

Architectural decisions affecting the customer's commercial relationship with RTOpacks — subscription handling, account lifecycle, data export, plan changes, account closure — are designed against the principle that RTOpacks does not weaponise the customer's dependence on the substrate.

Concretely: subscription lapse does not trigger hard lockout (the T4 administrator role survives lapse per ADR-023; substrate state persists). Data export remains a first-class capability (per ADR-010). Plan changes are not surprise events (consumption transparency per ADR-021). Resumption after lapse is frictionless (T4A users deactivate-not-delete per ADR-023).

The substantive principle is articulated in client-spine.md § 1 (the no-weaponised-lock-in paragraph within the sentinel-posture sub-block). The rule here codifies the operating discipline derived from that principle: when a brief, ADR, or implementation decision affects the customer's commercial relationship with RTOpacks, the no-weaponised-lock-in principle is one of the explicit considerations.

This is dispositional discipline, not a hard gate. The principle is tested where load-bearing; it is not invoked as a mandatory check on every commit.


CHANNEL SEPARATION RULE

Added INTERNAL-API-ISOLATION-PROGRAMME-01 (2026-05-30). Sibling to the HARD SEPARATION RULE: that rule separates data domains (nrt / micro / ops); this one separates communication channels by trust boundary. (Name confirmed over BACKHAUL-FIRST / INTERNAL-VIA-BINDING — both undersell the cross-boundary half: this rule is as much about strong auth across the boundary as bindings within it.)

Principle. Privileged or internal identity is asserted only over a trusted channel, and never by a forgeable credential. The trust boundary is RTOpacks' own Cloudflare account.

  • Within the boundary (worker-to-worker, same account): use the service binding (env.X.fetch). Bindings dispatch inside the Cloudflare runtime, never traverse the public edge, and cannot be forged or intercepted from outside — they are the trusted channel. They are also the more reliable path (fewer hops, no DNS), so this is not a security-vs-availability trade.
  • Across the boundary (a separate account — e.g. the UCCA Inc engine once it splits out; a non-Worker party; an external vendor, a customer LMS, a third-party API): the public surface is unavoidable. Authenticate cryptographically — signed token or mutual auth, verified server-side. Never a forgeable claim — a bare header, or a shared string in a header or query param. "Has a header" is not authentication.

The account boundary is the outer trust unit; within it, internal caller identity must be non-forgeable. A binding establishes that a request arrived over a trusted channel, but not which in-account worker sent it — so a bare header any in-account worker can set is not sufficient for privileged internal identity. Eliminating that inside-job vector — binding identity to the private entrypoint a caller arrives through (distinct WorkerEntrypoints per caller class), or another non-forgeable per-caller mechanism — is required, not optional. The mechanism is chosen against the service's actual structure; the invariant — non-forgeable internal identity — is mandated. Trusting an in-account claim on its face (the bare-header "pure isolation" posture) is non-compliant.

Both directions cross the boundary. Outbound (us calling Stripe, Intuit, the gov APIs) and inbound (billing webhooks, vendor consumers, the engine if it becomes a separate account) both require strong auth on the public surface. The boundary triggers the rule, not who placed the call.

Forbidden. - An internal call escaping to the public surface — including the "binding-first with a public-HTTP fallback" anti-pattern. - Privileged or internal identity asserted by a forgeable credential, on any channel — public, cross-boundary, or an in-account binding (a bare header trusted on its face).

Fail closed. When the binding is unavailable, an internal call fails (errors) — it does not fall back to a public-surface call. A missing or misconfigured binding is a configuration error and must surface loudly, never get silently routed over the public internet. (This composes with the illuminated-fence principle: wire the binding in dev too, so dev and prod use the same channel and no public fallback is ever needed.)

Not in scope. - End-user-facing product surfaces — this rule governs service-to-service calls, not user endpoints. The product is public by design. - The rare case of deliberately routing an internal call over the edge to use edge-only features (cache, rate-limit, managed WAF). Almost never worth the cost for internal traffic; if ever used, document why.

Audit lens. Every audit and every brief-draft checks channel compliance: each internal call on the binding; each cross-boundary call on strong, non-forgeable auth; each privileged internal identity established by a non-forgeable per-caller mechanism, not an asserted header. A one-time fleet channel-sweep enumerates and dispositions existing violations.

Why it exists. The forgeable X-RTP-Internal-Source exposure (May 2026) was, at root, a privileged identity asserted by a forgeable credential reachable over a public surface. The binding fix worked because the binding is a trusted channel — but the durable lesson is the channel/credential principle above (a trusted channel and a non-forgeable credential), not "always use bindings," because the latter foot-guns the cross-account engine and the multi-vendor public APIs the roadmap requires.

Relationship to other rules. Composes with HARD SEPARATION (data), CLOUDFLARE-FIRST, and the illuminated-fence / env-parity discipline (ADR-027). Operationalises ADR-031 (channel-separated service architecture as a canonical commitment) — the rule is how that ADR is enforced on every piece of work. The webhook-receiver split (sub-brief 2 of INTERNAL-API-ISOLATION-PROGRAMME-01, governed by ADR-030) is the first build of the cross-boundary auth primitive this rule leans on — the Stripe HMAC signature verification and QuickBooks OAuth validation done there.

Promotion threshold. Structurally-specified principle, not an emergent N/3 pattern — same shape as SUBSTRATE-BRIEF-GATE-DISCIPLINE and MIGRATION-COMPLETION-DISCIPLINE, both codified under three applications because they are specs rather than observed regularities. Articulated from the May 2026 X-RTP exposure (ADR-030 / ADR-031); the isolation programme is its first live exercise. Promotable on that basis at Tim's sign-off.


Core operating rules

OPS-AS-OS

Operations is the operating system of the business. Ops discipline is not overhead — it's the substrate everything else runs on. Docs, rules, briefs, surfaces, and infrastructure are treated as first-class product. If ops isn't tight, the product can't scale.

OPS SURFACE RULE

rto-ops-db is internal business operations only. No customer-facing worker writes to it or reads from it. Public form submissions land in rto-intake-db, not ops-db. Cross-surface contamination is a compliance and reputational risk; the rule is non-negotiable.

Surfaced during INTAKE-DB-EXTRACTION-01 (17 May 2026) when the live data layer audit revealed apps/site and workers/prelaunch were writing to ops-db for public form intake. The refactor created rto-intake-db to give public intake its own home and made the rule sharp: customer-facing workers do not touch ops-db, full stop.

Cross-reference (2026-05-28, IMM-01 Phase 3d audit / ADR-028). Not every customer-facing write to ops-db is the contamination this rule targets. The apps/site writes surfaced during the Phase 3d audit (8 INSERTs into mode_switch_log, traffic_log, page_views, anon_threat_log, etc.) were system observability — the worker logging its own activity, not customer-data contamination. They retire to the new Telemetry category per ADR-028 (live telemetry to Analytics Engine, audit/activity records to D1), not because they were the OPS SURFACE violation this rule was written for. The rule remains unchanged; the disposition path for telemetry writes is ADR-028, not intake-db.

Brief drip rule

One brief at a time. Briefs are staged, not parallel. Tim drips them to Alex in sequence. Claude writes briefs and stages them in outputs/ — Claude does not pre-announce a pipeline of future briefs, does not batch them, and does not assume a brief is in flight until Tim confirms it.

If a brief is blocked, it stays blocked until Tim resolves it. No parallel workarounds.

Foundation rule

The Design Foundation (design/foundation.md) is the single source of truth for every visual and interaction decision. It contains actual CSS custom properties, type scales, spacing units, icon conventions, motion specs, and accessibility architecture.

Rules:

  • Fetch the Foundation at the start of every brief that touches a surface.
  • All values are hard constraints. No deviations without explicit written Tim sign-off.
  • Do not redesign components ad-hoc inside a brief. If a component doesn't exist in the Foundation, the brief either uses an existing one or proposes a Foundation update as a separate work item.
  • No surface should look AI-generated. Dark grey + teal/green default palette is banned. Brand colour is #2563eb, not teal. Teal is reserved for Knowledge Navigator only.
  • Companion: design/page-build-guide.md — the practical "how to apply the system" reference. Read both when building a new page.

KN sacred rule

The Knowledge Navigator enrichment pipeline produced 15,128 sacred rows. Never touch them. No re-runs, no modifications, no overwrites. They are immutable. Any new enrichment work goes into a separate pipeline or a separate table. If you think you need to modify sacred rows, stop and ask Tim.

HARD SEPARATION RULE

Training data is strictly segregated by regulatory status:

  • rto-nrt-db → nationally recognised training only. TGA corpus, AQF qualifications, ASQA-registered RTOs, national codes.
  • rto-micro-db → non-accredited / non-regulated content only. InstaLearn and related product lines.
  • rto-ops-db → UCCA business ops only. Never surfaced to RTOs or end users.

Mixing regulated and non-regulated training data — in a table, a query, a surface, or a UI — is a compliance and reputational risk. This rule is non-negotiable.

DEPLOY-ON-SURFACE-TOUCH RULE

Added NRT-SEARCH-RESPONSE-SEPARATION-01 (2026-05-23). Every prod or dev worker deploy must be paired with a commit recording the surface touched. A deploy without a commit is an incomplete task — the running production state diverges from any reviewable git history, and future bisect or rollback becomes archaeology.

The rule applies to:

  • All wrangler deploy and wrangler deploy --env <env> invocations
  • All OpenNext + wrangler workflows (apps/site, apps/admin, apps/workspace)
  • Re-deploys triggered from existing commits (commit the deploy ID + brief reference, even if no source code changed)

The commit captures: brief identifier, env (dev or prod), version ID from deploy output, any deploy-time configuration notes. The version ID is the load-bearing detail — it ties production state to source state and makes rollback addressable.

Why this matters at this stage: with EXECUTION-AUTHORITY-LOOSE-USE-01 in force, Alex deploys without per-deploy Tim approval. The reviewable git history is therefore the only after-the-fact record of what landed where. A deploy without a commit is a silent state change.

Earned through repeated use: the pattern recurred across MV01a/MV01b prod deploys (Gate 4), MV01b Gate 2 staging, NRT-SEARCH-RESPONSE-SEPARATION-01 Gates 2 and 3, plus several earlier OPS-NAMESPACE / ORDERS-VESTIGIAL / OPS-API-INTEL closes from 18 May. Seventh occurrence at NRT-SEARCH Gate 3 prod deploy (2026-05-23) graduated the candidate from "pattern observed" to standing rule.

Companion: apps/site/CLAUDE.md already enforces "After every wrangler deploy, commit immediately. Message format: Brief #N — [one line description]. No exceptions. A deploy without a commit is an incomplete task." This rule lifts that from per-app to project-wide.

EXT-API RULE

Any external API used by any Worker must have a reference doc in docs/ops/ before the Worker is deployed. No exceptions.

The reference doc covers: endpoint inventory, auth model, rate limits, quirks (e.g. TLS fingerprint issues), known failure modes, and example requests. If the API doesn't have a reference doc, it doesn't ship.

CANONICAL-DECISION DISCIPLINE

Added 2026-05-26 at the close of the Client Spine drafting session.

Load-bearing architectural decisions are written to ops/architecture-decisions.md at the moment of making them. A decision is load-bearing if it constrains, governs, or directs future implementation work across more than one module, brief, or substrate. Decisions that affect only a single brief in flight are scoped to that brief and do not require an ADR; decisions that ripple beyond the brief require one.

A decision is canonical only when it is written. Decisions that live only in chat conversations, in briefs, or in the heads of the people who made them are not canonical and may not be relied upon. Subsequent briefs that reference an architectural decision must cite the ADR number (e.g. "per ADR-007"). If a brief references a decision that has no ADR, either the ADR is drafted before the brief proceeds, or the brief is treated as making a new decision and the ADR is drafted alongside.

Why this rule exists. Architectural decisions have been made and lost multiple times in RTOpacks' history. Each loss costs the cycle of having to re-make the same decision, sometimes with different conclusions, sometimes with the same conclusions but the original reasoning forgotten. The cost compounds over time. The rule converts "we agreed about this once" into "we have to write it down to act on it," and so prevents the loss.

What's in scope. - New architectural commitments (substrate organisation, identity model, schema patterns, integration approaches, data layer principles). - Provider and vendor choices that affect multiple parts of the system. - Trajectory and strategic positioning commitments that affect product direction. - Decisions to deprecate, supersede, or refactor existing architectural patterns.

What's out of scope. - Brief-specific implementation choices (live in the brief). - Module-internal design decisions that do not ripple to other modules (live in the module spec). - Operational tactical decisions (live in operational docs or runbooks). - Code style, formatting, and language-specific conventions (live in code-style docs).

Format. ADR (Architecture Decision Record): title, status, date, context, decision, consequences. New decisions are appended; old decisions are never edited in place. When a decision is superseded, the original entry stays with its status updated, and a new entry is added with the new decision and an explicit reference to what it supersedes.

Where it lives. ops/architecture-decisions.md in the canonical docs.

SPINE-AND-ADR-AS-GOVERNANCE

Added 2026-05-26 at the close of the Client Spine drafting session.

Two canonical artefacts govern RTOpacks' architecture above the level of individual briefs and module specs:

  1. ops/client-spine.md — the foundational architectural articulation of what RTOpacks is, the strategic thesis it operates against, the substrate it is built on, and the principles that govern how it is built.
  2. ops/architecture-decisions.md — the log of specific architectural commitments made over time, in ADR format.

The relationship between them:

  • The spine doc holds the foundational frame: what kind of thing RTOpacks is, what its moats are, what principles govern the substrate, how the closed loop composes. Changes to the spine doc are deliberate and infrequent.
  • The ADR doc holds specific commitments: provider choices, schema patterns, integration directions, decisions to deprecate. The ADR doc grows over time as new decisions are made.

Governance hierarchy when the two conflict. The spine doc is more foundational. If an ADR contradicts the spine doc, either the ADR is wrong (and the original decision should be revisited), or the spine doc needs updating to accommodate a new strategic reality (and that update is itself a deliberate act with explicit reasoning).

When briefs touch these docs. A brief that proposes work touching the spine doc or the ADR doc treats those changes as part of the brief's deliverable. The change is reviewed alongside the brief. Spine-doc changes get extra scrutiny because they ripple widely.

When this discipline is violated. If Claude, Alex, or Tim notice that a decision is being made or assumed without an ADR (or without spine-doc grounding for foundational claims), the discipline says: stop, draft the ADR (or update the spine), and only then proceed. The cost of pausing to write is paid once; the cost of losing the decision compounds forever.

Cross-references. Briefs cite ADRs. ADRs reference the spine doc. The spine doc references its companion artefacts (WS-PRODUCT-01, module specs, design foundation, standing rules). Cross-referencing is aggressive — every reference is a small protection against the docs drifting apart.

METADATA-RECONCILIATION-AT-COMMIT

Added STANDING-RULES-PROMOTION-01 (2026-05-27). When a canonical document is updated, the document's metadata — header line counts, ADR counts, dates, doc-version pins, internal cross-references — is reconciled within the same commit as the substantive change. Metadata reconciliation is not a follow-up commit; it is part of the substantive commit.

What this protects against. Documents drifting from their own header pins, where the header still says "13 ADRs" three commits after ADR-018 landed. The drift is small per-occurrence but compounds; six commits in, the header pin no longer reflects reality and operators reading the doc reach for stale orientation.

Concretely. Spine doc header pinned to ADR count and last-updated date. Standing rules doc pinned to last-updated date and most-recent-rule descriptor. ADR doc header pinned to the full evolution paragraph. When a commit changes any of these substantively, the header reconciles in the same commit.

Earned through three observable applications.

  • SPINE-AND-ADR-05b (2026-05-26, commit eed91570) — header pin caught one commit late after substantive changes shipped without the header update. Tim flagged in acknowledgement note. Discipline articulated.
  • CANON-VS-ADR-018-RECONCILIATION-01 (2026-05-26, commit 43593e87) — clean application: 2 header pins + 1 cross-reference update + 2 substantive doc changes = 5-file coherent commit. No straggler.
  • CANARC-01 (2026-05-27, commit 8522c6e3) — clean application: 4 header/closing-line pins (architecture-decisions.md, client-spine.md ×2, standing-rules.md) + 3 substantive doc changes in one coherent commit.

How to apply. Before committing canonical-doc changes, audit adjacent metadata: header dates, ADR/section counts, "last updated" notes, cross-references to other canonical docs. Fold corrections into the same commit.

Companion to CANONICAL-DECISION DISCIPLINE and SPINE-AND-ADR-AS-GOVERNANCE — those govern when decisions get written; this governs that the doc's own metadata stays current with the substantive change.

RECON-PASS-ON-FOUNDATION-SHIFT

Added STANDING-RULES-PROMOTION-01 (2026-05-27). When the canonical foundation shifts materially — spine doc structural change, ADR cohort addition, standing-rules expansion that touches operating principles or governance hierarchy — a recon pass against open work artefacts (briefs in queue, module specs, downstream commitments) identifies what may have been quietly invalidated by the shift.

What "materially" means. Single-rule additions are not material shifts (this very brief landing four rules does not trigger a recon pass). Material shifts: new spine section; new ADR cohort (3+ ADRs); governance hierarchy change; addition of a discipline that ripples across existing artefacts.

What the recon pass produces. A recon document at docs/docs/ops/recon/ listing observed implications — briefs that need re-scoping, module specs that need composition notes, ADRs whose context paragraphs may need a forward-pointer added. The recon doc is not itself a brief; it is the scoping artefact that briefs follow.

Provenance. RECON-FOUNDATION-LENS-01 (filed 2026-05-26) was the first articulated application of this discipline — a scoped re-read of open briefs and module specs against the canonical foundation that had just shifted (spine v2 + 19 ADRs landing in one day).

Recon documents themselves occupy governance position 4b — see the Governance hierarchy section.

CANONICAL-PROJECT-FILES-CURRENCY

Added STANDING-RULES-PROMOTION-01 (2026-05-27). Canonical project files in Claude's project context must be current with repo state for canonical work to proceed reliably. This rule operationalises the cadence-and-currency framing in Doc sync discipline (below) — Doc sync discipline frames repo-as-source-of-truth and refresh cadence; this rule specifies concrete triggers, filesystem discipline, and session-open verification.

The discipline has three components:

  1. Refresh after substantive canonical commits. When spine, ADR, or standing-rules docs change substantively, the canonical-current snapshot at ~/Downloads/canonical-current/ is refreshed and uploaded to project files before the next session opens against that work.

  2. Clear-before-write. Before refresh, the ~/Downloads/canonical-current/ directory is cleared. The macOS cp command preserves creation date on overwrite of existing files; if the directory is not cleared first, file creation dates become unreliable as a visual currency check (a file copied yesterday and a file copied today will both display yesterday's date).

  3. Verify-at-session-open. When opening a fresh session against canonical work, Claude reads the project files first to verify currency — line counts cross-checked against any handover artefact (Time Machine, close report) that names the expected state. Discrepancies surface as orientation flags, not silent assumptions.

What this protects. Canonical work executed against stale orientation produces canonical-shaped output that fails to compose with current reality. The recon-v1 failure (2026-05-26) cost a full re-scope; the macOS cp creation-date observation (2026-05-27) showed that visual currency inspection is unreliable without the clear-before-write step.

Companion to METADATA-RECONCILIATION-AT-COMMIT — both protect canonical-work currency at different layers (repo metadata vs project-files mirror).

BRIEF-DRAFT-SUBSTRATE-VERIFICATION

Added STANDING-RULES-PROMOTION-02 (2026-05-27). When a brief, ADR, or canonical doc references existing substrate state — section names, file paths, database names, table names, table contents, line counts, ADR numbers, or any other named substrate element — the reference is substrate-verified at draft time rather than assumed from summary, memory, or canonical archaeology. The author confirms by grep, file read, D1 query, or substrate scan that the referenced state matches the actual state.

What this protects against. Architect-intuition decoupled from substrate. Brief drafts operating against canonical summary necessarily lag actual substrate; when substrate has moved between summary writing and brief drafting, the draft contains references that no longer match reality. The mismatch may be small (a section label) or large (a missing peer database, a cost estimate off by an order of magnitude). The mismatch propagates downstream until something catches it — and "something catches it" is usually expensive (re-scoping, re-draft, re-migration).

How to apply. Before referencing existing substrate in a draft, grep / file-read / query to confirm. Specifically:

  • Section names and file paths in canonical docs → grep the actual canonical doc
  • Database names, table names, schema → D1 query against the substrate
  • ADR numbers, dates, content → file-read the current ADR doc
  • Cost estimates referencing substrate work → substrate scan the actual code paths affected
  • Provenance attributions ("X was filed by Y at Z") → file-read the actual source

The verification is brief — usually 30 seconds to two minutes of substrate-touch. The cost of not verifying is occasionally catastrophic.

Earned through seven observable applications.

  • CANON-VS-ADR-018-RECONCILIATION-01 Gate 1 (2026-05-26) — Canon-doc read against draft surfaced 3 additional tensions including KV exception broadening (env tokens _STAGING not -staging).
  • CANARC-01 Gate 1 (2026-05-27 AM) — Spine §1 / §3 / §7 read against draft surfaced 5 sharpenings including the original section-label catch.
  • STANDING-RULES-PROMOTION-01 Gate 1 (2026-05-27 AM) — Substrate read against Claude's draft caught three-times-repeated mislabelling of the Operating Principles section (the rules cited as conceptual siblings actually live in Core operating rules).
  • IDENTITY-MODEL-RATIONALISATION-01 pre-Gate-1 alignment (2026-05-27 mid) — Memory + canon framing for engine-db-oc as separate identity-bearing D1 surfaced for Tim resolution before audit execution.
  • IDENTITY-MODEL-RATIONALISATION-01 Phase 1 audit (2026-05-27 mid) — Substrate audit resolved the engine-db-oc red herring (display name only, same UUID as rto-workspace-db); surfaced 4 cross-DB duplicates not 3 (magic_tokens fourth); surfaced 6 L3-truth sources not the four conventions named in the brief.
  • IDENTITY-MODEL-PLACEMENT-DECISION-01 pre-Phase-2 substrate scan (2026-05-27 PM) — Substrate scan corrected initial Option C cost framing from "substantially heavier" to "~1 hour marginal." Without the discipline, the Phase 2 decision would likely have landed on Option B against incorrect cost framing.
  • STANDING-RULES-PROMOTION-02 push relay (2026-05-27 PM) — Substrate-verified IMR-01 close report §4.2 caught misattribution of SUBSTRATE-BRIEF-GATE-DISCIPLINE provenance ("Alex-filed" → actual "Tim-filed at IMR-01 Gate 1 sign-off").

Three applications would have been promotion-ready; seven is well past credible-to-leave-informal.

Companion to METADATA-RECONCILIATION-AT-COMMIT and CANONICAL-PROJECT-FILES-CURRENCY — all three protect canonical-work integrity by enforcing substrate-faithfulness at distinct points (commit-time metadata, project-files currency, draft-time substrate references).

SUBSTRATE-BRIEF-GATE-DISCIPLINE

Added STANDING-RULES-PROMOTION-02 (2026-05-27). For substrate-touching briefs, the five-gate canonical work pattern splits Gate 1 into two distinct activities:

  • Pre-Gate-1 alignment — substrate-state intel surfaced (typically by Alex via memory or canonical scan; or by Claude via canonical scan); composition decisions surfaced for Tim resolution; scope refinements identified. Output: a Tim-decisions summary plus any scope refinements to apply.

  • Gate 1 (substrate audit / option analysis) — substantive Phase 1 work executed against the scope as refined by pre-alignment. Output: the audit document or decision-analysis document.

Pre-alignment is not a delay; it is a distinct activity that earns the rest of Gate 1's quality. Skipping it produces audit work that operates against unverified composition assumptions.

Why it composes with the five-gate pattern. The pattern is unchanged for canonical articulation work — Gate 1 substrate-bytes pressure-test composes naturally because the substrate is the canonical doc being articulated against. For substrate-touching work, that collapse does not apply: substrate is separate from the brief draft, and the pre-alignment step makes the separation explicit.

Earned through two observable applications.

  • IDENTITY-MODEL-RATIONALISATION-01 Gate 1 (2026-05-27) — Pre-audit alignment surfaced engine-db-oc framing for Tim resolution; audit then executed against corrected scope.
  • IDENTITY-MODEL-PLACEMENT-DECISION-01 pre-Phase-2 substrate scan (2026-05-27) — Substrate scan corrected Option C cost framing before Phase 2 decision; decision made against grounded numbers rather than against frames.

Threshold reached at two applications because the discipline is structurally distinct from the canonical-articulation Gate 1 it refines — third application not required for codification when the refinement is a structural specification rather than an emergent pattern.

Companion to BRIEF-DRAFT-SUBSTRATE-VERIFICATION — one is what to verify (substrate references in drafts); this is when in the gate sequence (pre-Gate-1 for substrate work).

MIGRATION-COMPLETION-DISCIPLINE

Added STANDING-RULES-PROMOTION-02 (2026-05-27). Migration briefs include explicit retirement of superseded substrate within their own scope, not deferred to a follow-up brief that may never land. Retirement of the old is part of the migration's deliverable, not a separate concern.

What this protects against. Migration residue. The pattern of "migrate the new in, leave the old behind, intend to clean up later" produces compounding archaeological debt:

  • Old substrate continues to exist alongside new substrate
  • Code paths fork into "old path" and "new path" branches
  • Operator mental models have to track both substrates
  • Future migrations have to unwind the residue before they can land
  • The "later cleanup" brief is consistently lower-priority than substantive work and rarely closes

IDENTITY-MODEL-RATIONALISATION-01 unwound months-old UCCA-migration residue at substantial cost. The retirement that was supposed to happen "later" never did. The cost of unwinding now was substantially higher than the cost of retiring at migration time would have been.

How to apply. Migration brief structure includes retirement as a numbered phase or explicit scope item:

  • Schema migration completes when both new substrate is in place and old substrate is dropped (or marked for drop in the same commit window)
  • Code-path migration completes when both new path is wired and old path is removed
  • Substrate decommission is part of the migration brief's close criteria, not a separate close criterion

If retirement legitimately cannot be done atomically (e.g. dependency ordering requires staging), the migration brief explicitly schedules retirement and creates a tracking artefact so it does not disappear into "maybe later."

Provenance. Tim-filed 2026-05-27 during IDENTITY-MODEL-PLACEMENT-DECISION-01 pre-Phase-2 alignment (in response to observations about UCCA-migration residue surfaced during IDENTITY-SURFACE-AUDIT-01).

Earned through one observable application + one pending.

  • ADR-025 Consequences §6 (2026-05-27) — IDENTITY-MODEL-MIGRATION-01's brief structure is articulated as Phase 1 schema creation + binding wiring; Phase 2 per-row migration with id_migration_map; Phase 3 code-path update; Phase 4 explicit retirement of superseded tables per this discipline. Retirement is in-scope of the migration brief, not deferred.
  • IDENTITY-MODEL-MIGRATION-01 execution (forthcoming) — second observable application will land when the migration brief closes with retirement complete.

Promotion threshold reached at one observable application + one pending because the rule is structurally specified rather than emergent — codified during the brief that exercises it for the first time. Same pattern as METADATA-RECONCILIATION-AT-COMMIT being codified in the commit that exercised it.

SUBSTRATE-NAME-FOLLOWS-OPERATIONAL-SHAPE

Added ENVIRONMENT-NAME-RENAME-01 (2026-05-27). When standard infrastructure-convention naming does not match the operational workflow shape, substrate-name follows operational-shape, not convention. Naming serves the operator's mental model; conventions are templates, not commandments.

What this protects against. Inherited naming conventions that fit large-shop workflows (three-stage pipelines, separate QA, formal release management) applied uncritically to small-shop or unconventional workflows produce a permanent translation tax: every brief, deploy instruction, and substrate discussion carries an implicit mental conversion between convention-name and operational-name. The tax compounds across the project lifetime and produces semantic collisions when conventional names overlap with project-specific names (e.g. wrangler env.staging colliding with staging.rtopacks.com.au pre-launch artefact).

How to apply. When a config template or infrastructure-convention name is being adopted, verify the underlying workflow shape against the convention's assumptions. If they match, accept the convention. If they don't, name the substrate after the operational shape:

  • Two-stage workflow → env.dev (not env.staging)
  • Three-stage workflow → env.dev + env.staging + production
  • One-stage workflow → top-level config only (no env block needed)

When operator mental model and infrastructure convention disagree, operator wins. The substrate adapts to the operational reality, not the other way around.

Composes with ADR-018 per-resource-type rename feasibility (some substrate names are immutable post-creation; verify rename cost before adopting a name) and ADR-027 environment parity canonical commitment (operational vocabulary aligns with operator mental model).

Earned through three observable applications.

  • ADR-024 schema naming via T3/T4/T4A canonicalisation (2026-05-27 mid) — Substrate access-control schema names follow the canonical tier vocabulary (operator) rather than the inherited UCCA L1-L4 convention.
  • ADR-025 placement decision via dedicated identity-db (2026-05-27 mid) — Database name rto-identity-db follows substrate-shape (identity is its own subsystem) rather than convenience-shape (squat identity inside workspace-db with mismatched name).
  • ENVIRONMENT-NAME-RENAME-01 (2026-05-27 PM) — Wrangler env block renamed from staging to dev to match two-stage operational workflow; worker names, deploy scripts, source code, and living docs aligned in lockstep.

Threshold reached. Companion to SUBSTRATE-NAME-MATCHES-SHAPE (the existing principle: substrate-names reflect substrate-shape, not shortcuts that defer name-alignment). SUBSTRATE-NAME-FOLLOWS-OPERATIONAL-SHAPE refines it for the operator-vs-convention case specifically.

ROUTE-MIGRATION-REQUIRES-OLD-WORKER-DELETION

Added ENVIRONMENT-NAME-RENAME-01 (2026-05-27). For Cloudflare worker renames executed via env-block name change, the route reassignment is not automatic. Renaming creates a new worker; the old worker continues to hold its previously-assigned routes until explicitly deleted. The mechanism varies by route type:

  • Pattern routes (e.g. admin.rtopacks.dev/*, *.rtopacks.dev/*) — Wrangler cannot reassign pattern routes mid-deploy. The deploy will fail with "Can't deploy routes that are assigned to another worker." Resolution: delete old worker first, then re-deploy new worker.
  • Custom domain routes (e.g. apex rtopacks.dev) — Wrangler offers an in-place "Update them to point to this script instead?" prompt during deploy. Answering yes flips the domain to the new worker without requiring delete-first.

What this protects against. Worker renames executed naively will fail at the route-binding step. The failure mode is recoverable but introduces a brief operational state where the new worker exists in the CF account but is not serving traffic, and the old worker still holds the route. Without the discipline, this surprises the operator mid-deploy and forces ad-hoc recovery.

How to apply. For worker rename briefs, sequence as:

  1. Rename in source (wrangler.jsonc env block, deploy scripts, source code env-checks)
  2. For pattern-routed workers: delete old worker first, then wrangler deploy --env <newname> — the new worker stands up and attaches the route cleanly
  3. For custom-domain-routed workers: wrangler deploy --env <newname> directly — answer "yes" to the domain reassignment prompt
  4. After verification, retire any remaining old workers per MIGRATION-COMPLETION-DISCIPLINE

Composes with MIGRATION-COMPLETION-DISCIPLINE (old-worker cleanup is in-scope for the rename brief, not deferred), ADR-018 per-resource-type rename feasibility (workers rename freely; D1s do not), and ADR-027 environment parity (dev/prod canonical naming).

Earned through four observable applications.

  • internal-api worker rename (2026-05-27 PM) — Pattern route internal-api.rtopacks.dev/* required delete-first sequence. First deploy failed with route conflict; delete + re-deploy succeeded.
  • apps/admin worker rename (2026-05-27 PM) — Pattern route admin.rtopacks.dev/* required delete-first sequence. Same shape as internal-api.
  • apps/workspace worker rename (2026-05-27 PM) — Pattern route my.rtopacks.dev/* required delete-first sequence.
  • apps/site worker rename (2026-05-27 PM) — Custom domain rtopacks.dev offered in-place reassignment via deploy prompt; substantive distinction from pattern routes worth pinning.

Threshold reached. The pattern-vs-custom-domain refinement is part of the canonical articulation, not a separate discipline — both mechanisms are CF substrate-behaviour worth knowing before executing rename briefs.

METHODOLOGY-SERVES-FOUNDATION

Added ENVIRONMENT-NAME-RENAME-01 (2026-05-27). The canonical-work methodology (five-gate pattern, brief drip discipline, substrate verification, etc.) serves the foundation; it is not a substitute for foundational judgement. When a methodology response feels disproportionate to the operational task, the methodology may be the drift, not the protection.

What this protects against. Methodology applied reflexively becomes bureaucratic drift — treating small operational tasks as architectural decisions because the methodology surface invites it. The symmetrical failure is also real: when a foundational moment arises, abandoning methodology to "just do it" sacrifices the canonical discipline that protects the foundation in the first place. Both drifts are catchable, and the operator (Tim) catching them is part of how the methodology stays calibrated.

How to apply. When drafting a brief or relay, check whether the methodology response matches the operational shape:

  • Operational task, small footprint → execute, observe, pin observations. Methodology is the lightweight wrapper, not the substantive content.
  • Foundational decision, real architectural commitment → full methodology, deliberate gate sequence, canonical artefact lands. Methodology earns its weight by protecting the canonical commitment.
  • Mid-case → ask. Don't bundle. The cost of asking is small; the cost of misclassifying compounds.

The discipline is meta — it watches the fit between methodology and task rather than dictating either side. Tim's pushbacks during ENVIRONMENT-NAME-RENAME-01 ("tell me like it is" and then "this is foundational") are the canonical example of the discipline operating: first correcting bundling drift, then correcting over-reversion.

Composes with Brief drip rule (one brief at a time prevents methodology stack-up across parallel work), Foundation rule (operator's foundation prerogative is the load-bearing constraint), and CANONICAL-DECISION DISCIPLINE (canonical decisions earn their place through deliberate framing, not through reflexive methodology application).

Earned through two observable applications.

  • ENVIRONMENT-NAME-RENAME-01 framing turns (2026-05-27 PM) — Claude almost made the wrong call twice in succession. First by bundling the rename into architectural smokescreen for a 30-minute operational task. Then by reverting too far in response to Tim's push and abandoning foundational work entirely. Tim's "this is foundational" push caught the second drift. Real call: do the rename deliberately because it's foundational, not because it's bureaucratic. Both drift directions surfaced in the same conversation; the discipline names both.
  • Methodology-serves-foundation observation pinned to ADR-027 close report (2026-05-27 PM) — The discipline is the canonical articulation of what produced ADR-027's particular shape (full canonical commitment, not a "while we're at it" addendum).

Threshold reached at two applications because the discipline is structurally distinct (it operates on methodology rather than within methodology) and the framing is canonical-deliberate rather than emergent-pattern. The principle is most useful as a forcing function — a check Claude can run before bundling or before reverting.

TYPE-DELTA-COMPLETE-CONSUMER-SWEEP

Added IMM-01 Phase 3c (2026-05-28). When a type change touches multiple fields (renames, drops, additions, shape changes), Gate 1 substrate audit must verify three axes:

Axis 1 — Field-coverage. Grep consumers for each field in the delta individually, not just the headline rename. For dropped fields, grep <var>.<field> and <var>?.<field>. For renames, grep both old and new names (old to verify all caught; new to verify no stale references).

Axis 2 — Consumer type-binding. Verify each consumer is typed against the canonical type, not a parallel local mirror. Inline JSON.parse(...) as { ... } casts and stale duplicate interfaces defeat the canonical migration because TypeScript cannot catch drift through them. Each parallel local type is a Gate 1 substrate finding requiring disposition — reconcile to canonical, or document as intentional narrowing.

Axis 3 — Variable-name overload. Verify each grep hit's variable is typed as the canonical type before applying the sweep. The same identifier (e.g. session) can refer to different domain entities — auth-session vs DB-row vs cookie/header value. Sweeps that operate on field-name pattern alone will edit the wrong variables. Read the variable's binding context at every grep hit before changing the field reference; surface every overload instance separately in the audit with type-binding evidence and a "leave alone — overload" disposition.

What this protects against. Mid-deploy TypeScript build failures that surface uncaught consumers. Build failures are catchable because TypeScript enforces the contract — but the catch happens during deploy, not during Gate 1. The Gate 1 discipline exists precisely to catch this before deploy. A narrow grep at Gate 1 defeats the purpose.

How to apply. When drafting Gate 1 substrate audit for a type change:

  1. Enumerate the full type delta — every field renamed, dropped, added, or reshaped.
  2. Grep consumers for each field individually (Axis 1).
  3. Grep for parallel-local-type patterns (Axis 2) — inline casts, local interfaces mirroring canonical types, parallel session-fetchers / context-builders. Each is a Gate 1 finding requiring disposition.
  4. For each grep hit, read the variable's binding context (Axis 3). Surface overload instances separately with "leave alone" disposition and type-binding evidence.
  5. Surface the full count — N consumer files across per field plus parallel-local-type sites plus variable-name overload sites, classified separately.
  6. Run npm run build (or tsc --noEmit) at Gate 1. All three axes are caught cheaply by the build pre-Gate-2-draft. High-leverage; minimal cost relative to mid-deploy failures.

Composes with BRIEF-DRAFT-SUBSTRATE-VERIFICATION (refines it at gate level), SUBSTRATE-BRIEF-GATE-DISCIPLINE (Gate 1 audit must be comprehensive).

Earned through three same-session applications during IMM-01 Phase 3c apps/workspace deploy (2026-05-28). The session-shape canonical collapse touched six fields (ucca_layer→tier, tenant_id→drop, org_id→client_id, role_template_id→drop, is_super→drop, impersonating_layer→impersonating_tier); each axis surfaced as a TypeScript build failure with a single-field cause and a generalisable discipline:

  • Axis 1 (Build #1)session.org_id consumers caught in app/api/billing/[...path]/route.ts:19. Gate 1 grep was right shape, wrong scope; 5 additional sites across 4 files surfaced post-build.
  • Axis 2 (Build #2)studio/auth.ts:36-44 held an inline-cast type mirroring pre-3c RTPSession including never-canonical fields. Parallel local type still said org_id?: string. Reconciled to canonical RTPSession import.
  • Axis 3 (Build #3)studio/session/[id]/route.ts:173 referenced session.client_id on a studio_sessions DB row variable (different domain entity from auth session). Reverted to session.org_id; full 21-binding overload audit completed.

Threshold reached at three applications same-session because Phase 3c is structurally distinct (substrate is becoming canonical for the first time); the discipline generalises forward to any structurally-similar canonical-collapse brief.

CANONICAL-IDENTITY-VIA-UI-ONLY

Added IMM-01 Phase 3c (2026-05-28). Identity rows — users plus identity-adjacent artefacts (allowlist entries, credentials, tier grants) — enter the canonical model exclusively through user-interface paths. Operator-facing articulation: "One way in and one way out, through the UI."

What this protects against. Manual SQL inserts that bypass UI visibility. Without the discipline, the canonical identity substrate accumulates rows whose provenance the operator cannot reconstruct — manual seed entries, developer-convenience inserts, scripted backfills — each of which becomes archaeology the next time substrate truth needs reconciliation. The substrate that the operator can see in the UI is the substrate that exists; everything else is drift waiting to surface as a debug session.

How to apply.

  • At canonical-substrate migration drafting time, classify each source row by provenance: UI-path → migrate; seed → retire; manual developer convenience → retire; pre-RTOpacks-era residue → retire.
  • When smoke definitions for identity surfaces require canonical rows that don't yet exist, refine the smoke definition (e.g. to canonical-substrate-routing evidence) rather than manually inserting rows. Substrate populates through UI paths only.
  • Every canonical identity surface needs a corresponding UI path. Surfaces without UI representation are aspirational canonical, not operational canonical — file a UI spec brief before the substrate is depended on operationally.

Composes with MIGRATION-COMPLETION-DISCIPLINE (retired rows decommissioned with the source tables) and gone-is-gone (replaceable artefacts don't earn "just in case" preservation when the canonical source is the running UI).

Earned through three applications across the IMM-01 close arc.

  • IMM-01 Phase 2 disposition refinement (2026-05-27 AM) — Tim-filed during migration script review. Migration draft proposed importing all 16 source rows; Tim's principle filtered to UI-path-origin rows only.
  • IMM-01 Phase 2 strict reading (2026-05-27 PM) — Same session, strengthened. CF Access at the worker-domain layer is the operational authentication gate; the D1 identity substrate was unused scaffolding pre-IMM-01. All historical D1 identity rows are archaeology. Migration footprint shrinks to ADR-024 architectural exception (zero-UUID admin) only.
  • IMM-01 Phase 3c dev smoke refusal-to-manually-INSERT (2026-05-28) — Tim refused a proposed manual INSERT INTO magic_link_allowlist for smoke convenience: "This is how we got into trouble in the first place: manually inserting things into databases and forgetting them, or having it drift. There's one way in and one way out, and that's got to be through exposing it in the user interface so that we can fully see what's in the user databases." Smoke definition refined to canonical-substrate-routing evidence; full UI flow deferred to IDENTITY-MANAGEMENT-UI-SPEC-01.

The discipline is the working spec for IDENTITY-MANAGEMENT-UI-SPEC-01 — every identity surface needs UI representation so what's in the user databases is observable to the operator. Threshold reached at three applications.

Machine safety rules

  • No destructive operations without explicit confirmation.
  • No rm -rf on anything outside scratch directories.
  • No schema changes to production D1 without a migration brief.
  • No Worker deploys to production without Tim's approval.
  • Terraform state is sacred — no manual drift, no out-of-band changes.

Brief discipline

What a brief is

A brief is a self-contained Markdown doc that Alex can execute against without needing additional context from Tim or Claude. It specifies:

  • Goal
  • Scope (in and out)
  • Acceptance criteria
  • Files touched
  • Dependencies
  • Any relevant sacred-rule callouts

Where briefs live

Staged in outputs/ during the session. Tim moves them into the repo's archive directory when ready — briefs are archived immediately, never added to the docs nav. Post-ship, the canonical state lives in the relevant reference doc (architecture, infrastructure, operations, workers, workspace), not in the brief that proposed the work.

Brief naming

<MODULE>-<SUBMODULE>-<NN> — e.g. STUDIO-CANVAS-03, PC-PHASE-01, RADAR-UI-01. Sequential numbering within a submodule. Letters for sub-phases (a, b, c).

One brief in flight

At any given time, exactly one brief is in flight with Alex. Until that brief is merged or explicitly parked, no new brief is started.

Briefs are not sources of truth

If you find yourself referencing a brief from a reference doc as if the brief were canonical — stop. Promote the content into the reference doc, then archive the brief.


Documentation discipline

These principles govern the entire docs corpus (rtopacks docs, ucca docs, trust surface, internal references). Adapted from the docs-index.md rules. Apply them consistently.

Distill, don't proliferate

Fewer long, identity-clear documents beat a sprawling pile of small ones. Every doc should be long enough to be self-contained on its topic. If you're tempted to create a new doc for a small concern, extend the existing doc that owns that concern. If the corpus grows past ~40 working reference docs without a deliberate restructure, something has drifted.

Each doc has one identity

You should be able to look at a filename and know exactly what's inside. database.md is SQL conventions. worker-patterns.md is chain dispatch and failure modes. No overlap, no ambiguity, no "where does this go?" guessing.

Brief yourself before you touch X

Every reference doc is meant to be read in full before starting work on its topic, not skimmed for an answer mid-task. The cost of five minutes reading is consistently lower than the cost of re-discovering a documented gotcha.

Archive shipped briefs by default

Every new brief is filed directly into briefs/archive/ and never added to the mkdocs nav. The brief is a working artefact for the session that produces it. Concepts or patterns worth keeping go into the appropriate reference doc as part of the same commit that ships the work.

Stale reference is worse than missing reference

A doc that no longer reflects reality is actively harmful — it tells the reader the wrong thing with the same confidence as a correct doc. When behaviour changes, update or delete the doc in the same commit. Never let a reference doc fall behind the code.

Time Machine vs reference docs

These two don't mix. The Time Machine captures "what happened last session, what's open" and is overwritten session by session. Reference docs describe "how the system currently works" and stay stable. A behaviour change goes into a reference doc. A session-continuity update goes into the Time Machine. If something lives only in the Time Machine and should outlast the session, promote it.

Every reference doc should point at its companions. Cross-links cost nothing to write and prevent the "I didn't know there was already a doc on that" problem.

Maintenance on write

When touching any doc:

  • Read the existing doc first before deciding whether to extend it or create a new one. Extension is almost always the right answer.
  • If creating a new doc, justify why an existing doc can't absorb the content. Default answer: no new doc.
  • Update cross-references when you move or rename anything.
  • Commit doc changes inline with the code changes they describe. A behaviour change without a corresponding doc update is incomplete work.
  • Delete stale content explicitly. Don't preserve superseded sections "just in case."

A new .md file under docs/docs/ isn't filed until it's in docs/mkdocs.yml. Filing the file alone produces a page that renders at its URL but is undiscoverable via the sidebar — technically there, practically not. The two changes belong in the same commit.

Session/work artefacts (briefs in outputs/, time machine, etc.) don't go in mkdocs.yml because they aren't reference material. Reference docs only.

Caught twice during NAMING-CANON-01 (2026-05-08): GCP canon and Cloudflare canon both filed without nav entries, both required follow-up commits.


Operational tripwires

Tactical rules learned while shipping — things that would have saved hours if known earlier. Schedule-driven or condition-driven behaviours, system-specific. Companion content to the principle-level rules above.

QB OAuth token — rolling, not fixed

Session 50 correction: earlier versions of this doc said "refresh tokens expire every 100 days" implying a hard calendar expiry. That's wrong. QuickBooks Online uses a rolling 100-day window: every time we use the refresh token to mint a new access token, Intuit returns a new refresh token AND resets the 100-day clock on it. As long as the system makes at least one QB API call every 100 days, the token renews itself forever.

Production posture: effectively set-and-forget. An active billing integration pushes invoices to QB whenever any client pays — several times per week minimum once there are paying clients. The rolling window keeps advancing automatically. You can run for years without touching the OAuth flow.

Two scenarios that force a manual re-auth even in production: 1. System goes completely idle for 100+ days (no new subs, no payments, no reconciliation runs) — won't happen if any client is active. 2. Intuit developer portal config changes (scopes, redirect URI, environment switch) — invalidates all existing tokens across the app. Rare, deliberate operational action.

Sandbox is noisier than production. Intuit sandbox tokens get invalidated out-of-cycle when the sandbox company is rebuilt or reset on Intuit's end, or when app settings are changed during active development. If you're seeing repeated token death during a build session, it's the sandbox being flaky, not production behaviour.

How to re-authenticate (when needed)

  1. Go to admin → Finance → QuickBooks tab
  2. Click the amber "Reconnect QuickBooks" button at the top (next to the stats cards)
  3. Sign in to Intuit with QuickBooks credentials
  4. Pick the correct company (sandbox or production)
  5. Intuit redirects back to /billing/qb-callback which writes fresh qb-access-token and qb-refresh-token to SESSION_KV on internal-api
  6. If you see a plain-text callback page showing the new refresh token + company ID, that's informational only — the KV write already happened. Ignore the "store as QB_REFRESH_TOKEN" instruction (stale wording from when the flow was manual).
  7. Go back to Finance → QuickBooks tab and click Retry on any invoices stuck at failed with [step1_token] QB token unavailable

Belt-and-braces: rotate the Wrangler secret too

KV has a live token after a reconnect, but the QB_REFRESH_TOKEN Wrangler secret on rtopacks-internal-api and qb-reconcile is still the previous (now-dead) token. getOrRefreshToken prefers KV first, so the secret being stale doesn't break anything — but if KV ever loses qb-refresh-token for any reason, the fallback would fail.

After a reconnect, optionally run:

cd workers/internal-api && npx wrangler secret put QB_REFRESH_TOKEN
# paste the refresh token from the callback page, hit enter
cd ../qb-reconcile && npx wrangler secret put QB_REFRESH_TOKEN
# paste the same token

Never paste the token into a chat message, commit, or file. wrangler secret put reads from stdin and the token is encrypted at rest in Cloudflare's secret store.

Detection — how you find out the token has died

  • Invoices start accumulating at qb_sync_status = 'failed' with error containing [step1_token] QB token unavailable
  • billing_ledger shows repeated qb_sync_failed events for recent invoices
  • qb-reconcile cron runs (2am AEST nightly) start returning failures in its log

Mitigation shipped (QB-HEARTBEAT-01, 2026-04-13): qb-reconcile worker now runs a daily heartbeat at 20:00 UTC (06:00 AEST) that explicitly refreshes and stores the QB token — scripts/workers/qb-reconcile/src/index.ts:refreshAndStoreQBToken. This runs unconditionally before the invoice reconcile loop, so the 101-day clock resets daily regardless of whether there are invoices to sync. Even a completely idle billing system now keeps the token alive.

Manual trigger for verification:

curl -X POST https://qb-reconcile.dark-firefly-3289.workers.dev/_test

Expected response: {"status":"ok","synced":N,"failed":0,"abandoned":0,"heartbeat":true}. Note: wrangler cron trigger was removed in wrangler 4.x, so the /_test HTTP endpoint is the canonical manual trigger.

Why this works even when the stored refresh_token string doesn't visibly change: Intuit's 101-day expiry clock resets on their server side every time the refresh endpoint is successfully called — regardless of whether QB returns a new refresh_token string or the same one. The critical action is the HTTP call, not the string rotation.

D1 account context — always run from apps/admin/

The project root resolves to the wrong Cloudflare account (e5a9830 / UCCO Foundation). Always cd apps/admin before running wrangler d1 execute commands so the correct account ID (f95d453 / RTOpacks) is picked up from wrangler.jsonc. Getting this wrong creates tables on the wrong database — hours of debugging if you don't notice.

Never export rto-nrt-db

D1 export of rto-nrt-db (rtopacks-db) locks the database for hours. The NRT table is 2.4GB and exports run single-threaded with exclusive locking. Query it freely, but never run wrangler d1 export rto-nrt-db. This rule is also in auto-memory as feedback_no_d1_export.md.

org_rtopacks_ops ↔ rto_code 45329 — permanent mapping

Added session 50 (2026-04-13). The admin workspace org org_rtopacks_ops is permanently mapped to rto_code 45329 which is United Central Colleges of Australia Pty Ltd — UCCA, the legal entity behind RTOpacks. This mapping exists on:

  • orgs.rto_code = '45329' on org_rtopacks_ops
  • billing_customers.rto_code = '45329' on the corresponding billing row

Consequences:

  • When Tim (or any admin) subscribes via workspace, the auto-convert webhook fires rto_clients(45329, is_client=1) and RTO 45329 becomes a real client.
  • This is correct — RTOpacks pays itself for its own product. UCCA is the first real client.
  • If you want to test real-path conversion against a different org without self-converting, seed a second workspace org pointing at a different rto_code rather than touching this mapping.
  • Status of RTO 45329 in NRT is "Non-current" — that's fine, it's the correct legal entity regardless of active registration status.

Sync completion emails (NOTIFY-01)

Added session 52 (2026-04-13). Both tga-sync and cricos-sync fire a Resend email at write_snapshot complete every cron cycle (success AND no-change runs), addressed to alex@rtopacks.com.au. The email contains a steps-per-phase table and a "What changed" section populated from regulatory_events rows written during the run. No-change runs show a green "No changes detected" block instead of the list.

Operational expectations:

  • A missing Sunday tga-sync email OR Monday cricos-sync email means the cron didn't run (CF outage, worker deploy in flight, queue backlog) — investigate via Observatory and wrangler deployments list.
  • A "failed" step in the table is the pager signal — reconcile phase-by-phase by re-triggering with curl -X POST https://<worker>.dark-firefly-3289.workers.dev/trigger.
  • Emails are fire-and-forget but awaited inside a try/catch — a Resend outage logs an error and lets the worker finish cleanly, it does not crash the sync.

See docs/docs/infrastructure/notifications.md for the full pipeline.

Enrichment coverage — 99.4% of RTOs are currently unenriched

Discovered session 52 (2026-04-13). Only 72 of 12,515 RTOs have ever been touched by tga-ingest — that's 0.58% coverage across all rto_* child tables (contacts, addresses, trading_names, web_addresses, legal_names, registrations, classifications). The pipeline is healthy, just never run at scale because tga-ingest is an on-demand queue consumer driven by public site traffic, not a bulk syncer.

Consequences until BACKFILL-01 ships:

  • Admin RTO cards at /organisations/[code] show empty detail tabs (contacts, trading names, web addresses) for 99.4% of lookups
  • rto_registrations.captured_at max date is tga-ingest's last activity, NOT tga-sync's freshness — do not use it as a tga-sync health signal. The correct tga-sync freshness metric is tga_organisations.synced_at.
  • Do NOT rebuild tga-ingest or add a bulk-sync mode to it — the on-demand design is correct. BACKFILL-01 is a one-shot script that uses the existing /api/enrich secret endpoint.

See docs/docs/briefs/backfill-01.md for the fix.

Queue-based chain dispatch is the standard pattern

Session 51 (TGA-QUEUE-01) established this. Any worker that needs to chain across multiple invocations (to stay under the 30s per-invocation CPU limit or the ~3-4 min isolate subrequest budget) MUST use Cloudflare Queues for the self-dispatch, not env.SELF.fetch(). Self-fetch died silently after ~3-4 min across sessions 49-50; queues bypass this because each message gets a fresh isolate.

Pattern:

  1. Worker binds a queue producer (e.g. TGA_SYNC_QUEUE)
  2. Worker is also the queue consumer (same worker)
  3. Each queue() handler runs one phase of work, writes phase pointer to D1 (NOT KV — KV returns transient 500s under rapid writes), and queues the next phase
  4. Cron handler starts the chain by queueing the first phase

Reference implementations: scripts/workers/tga-sync/src/index.ts, scripts/workers/cricos-sync/src/index.ts. Full discussion in docs/docs/infrastructure/worker-patterns.md.

Packaging pipeline — use the script, not ad-hoc SQL

STUDIO-PACKAGING-PIPELINE-01. The qualification packaging structure lives in qualification_packaging_rules in rtopacks-db. Never run ad-hoc SQL against this table for bulk modifications — use tools/packaging-pipeline.mjs with --force to reprocess. The pipeline runs four stages (unitgrid → deterministic parse → LLM fallback → verification report) with cross-validation at each step, and the Observatory drawer on the admin dashboard surfaces the current state.

Check the Observatory drawer for status. Run the pipeline from the CLI for mutations. Update docs/ops/packaging-pipeline.md when TGA API behaviour changes. See docs/ops/tga-unitgrid-endpoint.md for the structured unitgrid endpoint contract.

Restore-prerequisite check (GCP soft-deleted projects)

Added GCP-FOLDER-CANON-01 (2026-05-07). Before restoring any soft-deleted GCP project, check the soft-delete restore prerequisites table in docs/infrastructure/google-cloud.md. If the project appears, the listed pre-restore action must complete before the project is used for anything live.

The trigger condition this guards: a contained-user project (under personal-or-experimental/) being restored within the 30-day soft-delete window with dormant SA keys still attached. The keys wake up on restore and become reachable to anyone holding cached credentials. The pre-restore action — delete user-managed keys, disable SA — closes that window before live use.

Today the table has 3 rows. When future briefs soft-delete projects with credentials, more rows are added.


Session management

Context window monitoring

Every 10 messages in a build session, Claude checks conversation length.

  • YELLOW — start handover prep. Begin summarising state, decisions, open threads.
  • RED — stop everything. Write a Time Machine handover doc immediately. Do not start new work.

Never let a session die without a handover doc.

Time Machine docs

A Time Machine is the handover doc that captures:

  • Current state of work
  • Decisions made in the session
  • Open threads / unresolved questions
  • Next actions
  • Any brief that was drafted but not finalised

Written in the final minutes of a session before context exhaustion. Canonical location: docs/time-machine.md (rewritten per session).

No time alerts

Claude never comments on time of day, never suggests Tim should sleep, never flags that it's late. Tim manages his own schedule. Just keep building.


Working style defaults

  • Australian English spelling (colour, organise, behaviour, etc.).
  • Markdown is the default format for docs and briefs.
  • Honest pushback over agreement. If something is a bad idea, say so.
  • No sycophancy. No cheerleading.
  • Strategic thinking first, then tactical execution.
  • Prefer one well-thought brief over three half-thought briefs.
  • When unsure which rule applies, re-read this doc before acting.

Doc sync discipline

Project Files in Claude.ai are mirrors of the canonical docs in the repo. The repo wins.

  • Source of truth: docs/ops/standing-rules.md, docs/ops/infrastructure-reference.md, and everything under docs/workspace/ and docs/design/.
  • Project Files: uploaded copies, refreshed periodically.
  • Refresh cadence: rare for standing rules (quarterly or when rules change), more frequent for infrastructure reference (after any infra session), and whenever spec docs or the Design Foundation change.
  • Signal to refresh: if Claude is working from stale info in a chat, the Project Files need re-upload.

When this doc changes

Standing rules are canonical. Changes to this doc require:

  1. A Tim-approved reason for the change
  2. An updated version committed to docs/ops/standing-rules.md
  3. The Project Files copy re-uploaded to match
  4. The Project Instructions in Claude.ai updated if the change affects the summary there

If Claude finds itself wanting to act against a standing rule, the answer is almost always "don't" — not "update the rule." Rules are updated deliberately, not retroactively to justify an action.