Fine Japanese Calligraphy

The Art of Master Japanese Calligrapher Eri Takase

2026-03-21 - PLN-013: Production Resilience — Schema Contracts, Deploy Verification, Monitoring Correctness

Status: complete. All seven phases closed on 2026-05-17, and the plan was retired as a standing document because the defenses it describes now live in production code rather than in a plan. The record below is left as it was written, including the phase that had to be added after we declared the plan finished the first time. What happened in the end is at the foot of the page.

The team (How We Build takase.com has the full picture): - str-michi (道) — cross-domain strategic thinking (plan owner, coordination) - str-takase (高瀬) — website engineering (deploy verification, route inventory) - str-ishizue — data pipelines (schema contract, cache validation) - str-mamori (守り) — security (monitoring canaries, alert standards) - All AI roles: Claude Opus 4.6, 1M context


Origin: 88-minute HTTP 500 incident (2026-03-21, 02:58–04:26 UTC). 442/1,670 requests failed (26.5%) on /d/<hash> design pages. Root cause: selected_people data shape mismatch — ETL produced strings, website expected dicts. Every individual component was healthy. The schema just didn't match.

Trigger (Tim): "Not only are we not building widgets, nor a factory, but a castle fortress that has a factory and makes widgets." The incident exposed three missing layers: schema contracts between domains, post-deploy product verification, and monitoring that checks correctness not just availability. These standards apply to everything we build going forward.

Philosophy: Never let a good disaster go to waste. The immediate fixes are deployed (Phase A). This plan builds the structural defenses so this class of problem doesn't recur — for the name cache, for future pipelines, and for every new dynamic route.


Phase A: Immediate Incident Fixes — DONE

All deployed same day as the incident.

str-mamori (with imp-redteam)

str-takase (with imp-takase)

str-ishizue (with imp-etl)


Phase B: Name Cache Schema Contract — DONE

Goal: Formal interface spec between ETL cache output and website input. Both sides validate. A format change that breaks the contract is caught before deploy, not after 88 minutes of 500s.

Owner: str-michi coordinates. str-ishizue (output side) + str-takase (input side) implement.

Key findings (str-ishizue review)

Deliverables


Phase C: Post-Deploy Product Verification — DONE

Goal: Every deploy that touches the product (ship.sh, quick_push.sh for service files) automatically verifies that the product works, not just that the server is up.

Owner: str-takase

Deliverables

Two-canary approach (later correction): Amy only renders 2/4 sections (llm_concepts is empty — Sections 3-4 don't render). Using two canary names: one that tests the engine pipeline (kana/phrase sections) and one that tests the data pipeline (meaning sections). A failure pinpoints which pipeline broke. imp-takase selects the names from actual cache data.

Possible addition (from str-ishizue review): quick_push_name.sh could spot-check N records from the cache file being deployed (validate types before pushing). Third layer after B2 (build-time) and B4 (freshness gate). Deferred — evaluate after B2/B4 are in place.


Phase D: Monitoring Canary Expansion — DONE (deployed)

Goal: Every critical dynamic route has a synthetic check. "Available ≠ working" is the lesson — health_check confirms services are up, canaries confirm the product works.

Owner: str-mamori (monitoring) + str-takase (route identification)

Architecture decision (str-mamori)

Separate product_canary.sh script instead of expanding cutover_watch.sh. Reasons: (1) different purpose — cutover_watch monitors DNS/cutover safety, product_canary monitors "does the product work for customers?"; (2) different cadence — 5 min vs 15 min; (3) separation of concerns per cron_registry_SPEC.md design principles. Timothy /d/ check stays in cutover_watch.sh as redundancy.

Deliverables


Phase E: Alert Text Audit — DONE (deployed)

Goal: Every Postmark alert answers three questions: (1) What happened? (2) How bad is it? (3) What do I do right now? Tim was sitting right here during the incident and couldn't act because the alert said "[CUTOVER-WATCH] http_500: ALERT" with no context.

Owner: str-mamori

E1 Key Findings (str-mamori)

imp-redteam audited all 7 VPS scripts + fail2ban + CrowdSec. Results: 14 distinct alert types across 3 alerting scripts (health_check, uptime_monitor, cutover_watch). 4 scripts have no email alerting (traffic_sentinel, scraping_detector, takase_backup, archive_logs). fail2ban and CrowdSec have no email notification configured.

Deliverables


Phase F: Standards for New Pipelines — DONE

Goal: Codify the lessons so new cross-domain data pipelines and deploy paths are built right the first time.

Owner: str-michi

Deliverables


Phase G: Plan Verification — DONE (closed 2026-05-17)

Goal: Verify that what was implemented matches what was designed. "All phases complete" is not "plan succeeded." Every deliverable must be tested against its stated intent, not just confirmed as deployed.

Owner: str-michi coordinates verification. Domain owners fix gaps.

Why this phase was added: After declaring PLN-013 "6/6 complete," HITM-directed verification found: (1) ship.sh Aliya canary checks HTTP 200 only — body is discarded, meaning sections not tested, (2) product_canary.sh doesn't check Aliya at all, (3) Amy's cache data regressed (llm_concepts wiped by lightweight rebuild), (4) 387 of 5,171 LLM-populated records appear overwritten. The two-canary design was correct. The implementation was two status-code checks. "Available ≠ working" — applied to the plan itself.

Verification checklist

Phase B (Schema Contract): - [x] G1: VERIFIED (str-michi, investigate-etl). Freshness gate B4 type checks work: isinstance(person, dict) + name key check. Full population: 77,801 blurb records, 0 type failures. Zero plain-string selected_people remain. 95.1% composite freshness (5,171 variants failures + 128 llm_concepts never-computed — separate issues). - [x] G2: CLOSED (str-ishizue, 2026-05-17). Amy regression resolved; the residual work was carried forward as two separate data questions rather than held open here, on the grounds that the builder-overwrite behaviour and the missing-record count are pipeline issues, not schema-contract issues. - [x] G3: VERIFIED locally (str-michi, investigate-takase). _validate_cache_fields() at line 93 of name_info_service.py v1.02. Validates 6 fields. Called at line 88 on every get_name_info(). 14 tests pass. VPS deployment confirmed by str-takase (quick_push) + ship.sh.

Phase C (Deploy Verification): - [x] G4: FIXED (str-takase, ship.sh v1.05). The smoke test now captures the response body and asserts a section-specific marker is present in it. The meaning-pipeline canary was also swapped to a different name whose data actually exercises that path. - [x] G5: FIXED (str-takase, ship.sh v1.05). Same change covers the Timothy check: it now requires the kana section to be present in the body, not merely an HTTP 200. - [x] G6: CLOSED (str-takase, quick_push.sh v1.03). The data-only path is explicit in the script: content-only pushes skip the Gunicorn restart and the design-page smoke test that follows it.

Phase D (Monitoring Canaries): Note: str-mamori initially deferred G7 on the grounds that "no name has qualifying llm_concepts." A read of the actual cache found 4,784 records with populated llm_concepts, including the canary name then in use. The deferral rested on a claim about the data that the data did not support, which is the same shape as the incident itself. - [x] G7: DONE and deployed (str-mamori, 2026-05-15). A dedicated canary_meaning_pipeline check was added to product_canary.sh rather than overloading the existing design-page check, so a meaning-pipeline failure is distinguishable from a general page failure. - [x] G8: VERIFIED (str-mamori). Cron running, 5-min intervals confirmed. - [x] G9: VERIFIED (str-mamori). TEST=1 alert delivered, format correct.

Phase E (Alert Audit): - [x] G10: VERIFIED (str-mamori). health_check and cutover_watch test alerts confirmed. uptime_monitor has no test mode — noted as low priority, not blocking.


Phase Summary

Phase Description Status Owner
A Immediate incident fixes DONE str-mamori, str-takase, str-ishizue
B Name cache schema contract DONE str-michi coordinates
C Post-deploy product verification DONE str-takase
D Monitoring canary expansion DONE str-mamori + str-takase
E Alert text audit DONE str-mamori
F Standards for new pipelines DONE str-michi
G Plan verification DONE str-michi coordinates

What happened in the end

The plan closed 7/7 on 2026-05-17, about eight weeks after the incident, and was then deleted as a standing document. That deletion is the point rather than a footnote: a plan whose defenses have moved into production code is a description of the code, and keeping both means maintaining two things that can disagree. What it specified now lives in ship.sh, quick_push.sh, product_canary.sh, the cache record validator, and the alert standard.

Three things are worth carrying away from it.

Phase G is the whole lesson, and it exists because we were wrong. We declared the plan 6/6 complete. A verification pass found that the two-canary design had been implemented as two HTTP status checks. The response body was fetched and discarded, so a page could return 200 while rendering none of the content the canary existed to prove. "Available ≠ working" was the lesson of the incident, and we had just made the same mistake one level up, on the plan meant to prevent it. The fix was real content assertions: the deploy smoke test now requires a named section to actually appear in the returned HTML.

A deferral is a claim, and claims get checked. One monitoring item was deferred because "no name has qualifying data." Reading the data found nearly five thousand records that qualified, including the canary already in use. The deferral was reasonable-sounding and wrong, and nothing but going and looking would have caught it.

The defenses outgrew the plan. The deploy smoke test today also asserts that the design engine is generating fresh output rather than serving cached thumbnails, a check nobody wrote down in any of the seven phases. That is the healthier outcome: the standard survived, and the specific list did not need to.


Created from an 88-minute HTTP 500 incident (2026-03-21). Phase A deployed the same day. Phases B–F built the structural defenses. Phase G was added after verification found implementation gaps, and closed on 2026-05-17.