Skip to content

Commit e83d013

Browse files
Mitchell-MCclaude
andcommitted
Add pipeline reliability framework: survival guardrails, schema-drift detection, month-end ops runbooks
- Risk guardrails, schema-drift detection, structured logging, upstream registry - Gold gate build + streaming dead-letter replay - Month-end critical-path job, ops SQL scorecards, incident runbook & postmortem template - Governance/contract/AI-risk CI check scripts and reliability test suites Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent fa27377 commit e83d013

38 files changed

Lines changed: 1406 additions & 32 deletions

‎.github/workflows/ci.yml‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ jobs:
1313
runs-on: ubuntu-latest
1414
steps:
1515
- uses: actions/checkout@v4
16+
with:
17+
fetch-depth: 0
1618

1719
- uses: actions/setup-python@v5
1820
with:
@@ -22,10 +24,19 @@ jobs:
2224
run: python -m pip install -e ".[dev]"
2325

2426
- name: Lint
25-
run: ruff check src tests
27+
run: ruff check src tests scripts
2628

2729
- name: Unit tests
28-
run: pytest -v
30+
run: pytest -v --cov=src/aml_lakehouse --cov-report=term-missing --cov-fail-under=35
31+
32+
- name: AI code safety audit
33+
run: python scripts/check_ai_risk_patterns.py
34+
35+
- name: Governance policy checks
36+
run: python scripts/check_governance_policies.py
37+
38+
- name: Contract risk check
39+
run: python scripts/check_contract_risk.py
2940

3041
- name: Validate .sql files parse into well-formed statements
3142
# Lightweight pre-deploy check: every .sql file under src/ must split into at least

‎.gitignore‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ venv/
1414
.pytest_cache/
1515
.mypy_cache/
1616
.ruff_cache/
17+
.coverage
18+
htmlcov/
1719
build/
1820
dist/
1921

‎README.md‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,10 @@ for both assumptions in full.
105105
5. **Build Gold**: run `gold/build_gold.sql`
106106
6. **Streaming**: `databricks bundle run streaming_validation_run --target dev` (drips
107107
AMLSim transactions through a paced producer into the Structured Streaming consumer)
108-
7. **Dashboard**: open `bi/AML_Dashboard.pbip` in Power BI Desktop — see
108+
7. **Month-end critical path**: `databricks bundle run month_end_critical_path --target dev`
109+
(runs guarded publish flow with reconciliation gate checks)
110+
8. **Dead-letter replay (as needed)**: `databricks bundle run replay_txn_dead_letter --target dev`
111+
9. **Dashboard**: open `bi/AML_Dashboard.pbip` in Power BI Desktop — see
109112
[bi/README.md](bi/README.md)
110113

111114
## Project status
@@ -155,5 +158,13 @@ for both assumptions in full.
155158
duplicate-amplification incident: detection, root cause, fix, verification
156159
- [docs/05_architecture.md](docs/05_architecture.md) — architecture diagram and layer
157160
responsibilities
161+
- [docs/06_pipeline_survival_framework.md](docs/06_pipeline_survival_framework.md) —
162+
fail-loud coding standards, CI gatekeepers, and governance-as-code checklist
163+
- [docs/07_month_end_incident_runbook.md](docs/07_month_end_incident_runbook.md) —
164+
month-end triage, stakeholder communication cadence, and recovery checklist
165+
- [docs/08_postmortem_template.md](docs/08_postmortem_template.md) —
166+
incident postmortem template with mandatory reliability-upgrade action
158167
- [dataset_inspection_notes.md](dataset_inspection_notes.md) — real dataset schemas, row
159168
counts, and every bug found during ingestion
169+
- [resources/ops/README.md](resources/ops/README.md) — operational SQL query pack for
170+
stale-impact triage, owner handoffs, schema drift, and SLA/cost scorecards

‎docs/01_nonfunctional_requirements.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,14 @@ controls.
6363
explainable and reproducible).
6464
- Ingestion metadata (source, timestamp, schema version, row/file counts) is immutable and
6565
queryable per batch, forming the audit trail for "what data was known at decision time."
66+
67+
## Enforced controls
68+
69+
- **Fail-loud coding standard**: broad or silent exception handlers are blocked in CI via
70+
`scripts/check_ai_risk_patterns.py`.
71+
- **Data contract guardrails**: required-column/non-empty/invalid-ratio checks are enforced
72+
with `src/aml_lakehouse/common/risk_guardrails.py`.
73+
- **Structured incident telemetry**: batch and streaming jobs emit JSON logs with row counts,
74+
lag, and quality metrics.
75+
- **Governance-as-code checks**: CI runs `scripts/check_governance_policies.py` to catch
76+
secret patterns and blocked PII field names in SQL assets before merge.

‎docs/03_schema_contracts.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ text + `_error_reason`, `_ingested_at`) rather than failing the batch — see Ph
4242
| `silver.entity_alias` | one row per alias | `entity_id`, `alias_name`, `alias_normalized` | Normalized = uppercased, punctuation-stripped, whitespace-collapsed — the actual normalization function is a Phase 3 deliverable |
4343
| `silver.entity_address` | one row per address | `entity_id`, `address_line`, `city`, `country` | Parsed out of OFAC's combined `city_state_zip_etc` and OpenSanctions' free-text `addresses` |
4444
| `silver.account` | one row per account | `account_id` (= AMLSim `nodeid`) | `synthetic_holder_name`, `synthetic_country`, `synthetic_dob` (the account's own fabricated identity attributes from the synthetic identity-linkage step — **not** a FK to `silver.entity`, see below), `init_balance`, `opened_date` (synthetic, derived from the date anchor) |
45-
| `silver.transaction` | one row per transaction | `transaction_id` (surrogate) | `source_account_id`, `target_account_id`, `amount`, `currency` (assumed USD), `event_time` (derived from synthetic date anchor + AMLSim `time` step) |
45+
| `silver.transaction` | one row per transaction | `transaction_id` (surrogate) | `source_account_id`, `target_account_id`, `amount`, `currency` (assumed USD), `event_time` (derived from synthetic date anchor + AMLSim `time` step), `event_date` (partition column for pruning) |
4646
| `silver.match_candidate` | one row per candidate match | `match_id` | `account_id`, `entity_id` (the watchlist entity), `match_confidence_band` (`exact` / `strong` / `weak`), `match_score`, `matched_on` (name/alias/dob/country), `explanation` (structured — which fields matched and how) |
4747
| `silver.elliptic_txn_risk` | one row per Elliptic tx | `txId` | `class`, `time_step`, `in_degree`, `out_degree` (from `bronze.elliptic_txs_edgelist`), `illicit_neighbor_ratio` (share of 1-hop neighbors labeled illicit — the interpretable signal this dataset can actually offer, since the 165 raw features can't be feature-engineered further). Real SQL self-join/window-function work, standalone graph-topology analytics — not natively joinable to `silver.account`, see the bridge table below. |
4848
| `silver.network_risk_reference` | one row per `account_id` that received a synthetic linkage | `account_id` | `network_risk_exposure` (0-1, sampled from the `illicit_neighbor_ratio` distribution in `silver.elliptic_txn_risk`), `linkage_note` (fixed string documenting this is a simulated bridge, not a real Bitcoin-to-remittance-account linkage) — see caveat below |
@@ -80,7 +80,8 @@ linkage is disclosed.
8080
| `gold.entity_risk_profile` | one row per entity/account, per `as_of_date` | `entity_id`, `account_id`, `as_of_date` | `watchlist_match_summary` (struct), behavioral features: `velocity_1d/7d/30d`, `counterparty_diversity_30d`, `burst_flag`, `round_dollar_ratio`, `risky_counterparty_exposure`, `network_risk_exposure` (from `silver.network_risk_reference`, nullable — only populated for accounts that received the synthetic Elliptic linkage); `composite_risk_score`, `score_band`, `top_contributing_factors` (array of struct: factor name, value, weight/contribution) |
8181
| `gold.prioritized_alert_queue` | one row per active alert | `alert_id` | `entity_id`, `account_id`, `priority_rank`, `composite_risk_score`, `escalation_reason` (`sanctions_hard_override` vs. `behavioral_threshold`), `status` (`new`/`reviewed`/`suppressed`), `generated_at` |
8282
| `gold.watchlist_match_summary` | one row per watchlist entity | `watchlist_entity_id` | `matched_account_count`, `highest_confidence_band`, `jurisdictions`, `risk_type` |
83-
| `gold.ops_control` | one row per pipeline batch | `pipeline_name`, `batch_id` | `last_successful_batch_id`, `processed_row_count`, `expected_row_count`, `lag_seconds`, `freshness_status` (`fresh`/`stale`/`degraded`), `dead_letter_count`, `checkpoint_offset` — the silent-failure/ops layer from Phase 6/8 |
83+
| `gold.ops_control` | one row per pipeline batch | `pipeline_name`, `batch_id` | `business_process`, `impacted_table`, `last_successful_batch_id`, `last_good_batch_age_minutes`, `processed_row_count`, `expected_row_count`, `lag_seconds`, `freshness_status` (`fresh`/`stale`/`degraded`), `dead_letter_count`, `checkpoint_offset`, `upstream_owner`, `upstream_contact`, `runtime_seconds`, `estimated_cloud_cost_usd` — actionable business-impact alerting + ops/cost scorecard |
84+
| `gold.month_end_reconciliation_serving` | one row per entity risk profile in serving mode | `entity_id`, `account_id`, `as_of_date` | Adds serving metadata (`as_of_batch_id`, `is_stale`, `last_good_batch_age_minutes`) so finance consumers always know freshness/lineage of month-end outputs |
8485

8586
`top_contributing_factors` and `escalation_reason` exist specifically to satisfy the audit
8687
requirement in `docs/01_nonfunctional_requirements.md` — every ranked alert must show its
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Pipeline Survival Framework (2026)
2+
3+
This checklist codifies the production-safety standards used in this repository. It exists
4+
to prevent "green but wrong" pipelines: jobs that run successfully while silently damaging
5+
trust, cost, or compliance.
6+
7+
## 1) Fail Loud by Default
8+
9+
- Broad exception handlers (`except Exception`, bare `except`) are blocked in CI by
10+
`scripts/check_ai_risk_patterns.py`.
11+
- Only recoverable cases should be handled (for example, rate-limit retries); everything
12+
else must fail immediately with traceable context.
13+
14+
## 2) Enforce Data Contracts at Boundaries
15+
16+
- Critical jobs validate required input columns and non-empty source datasets.
17+
- Contract violations raise `DataContractError` from
18+
`src/aml_lakehouse/common/risk_guardrails.py`.
19+
20+
## 3) Use Circuit Breakers for Data Quality
21+
22+
- Streaming ingestion enforces a hard invalid-row ratio cap (`MAX_INVALID_RATIO`) and aborts
23+
the micro-batch when exceeded.
24+
- This prevents bad data from propagating silently downstream.
25+
26+
## 4) Require Structured Incident Logging
27+
28+
- Batch and streaming jobs emit JSON structured events (timestamp, pipeline, batch,
29+
row counts, quality/circuit-breaker details) via
30+
`src/aml_lakehouse/common/structured_logging.py`.
31+
32+
## 5) Make Idempotency Non-Negotiable
33+
34+
- Streaming writes use deterministic natural-key `MERGE` semantics (`_merge_append`) so
35+
restarts/replays do not duplicate facts.
36+
- Silver and Gold table builds are deterministic and rerunnable.
37+
38+
## 6) Enforce CI/CD Gatekeepers
39+
40+
- CI blocks merges when lint, tests, SQL statement validation, AI safety audit, governance
41+
checks, or coverage threshold fail.
42+
- Coverage floor is enforced in `.github/workflows/ci.yml`.
43+
44+
## 7) Test Failure Paths Explicitly
45+
46+
- Unit tests must include fail-on-bad-data behavior, not just happy-path outcomes.
47+
- `tests/test_risk_guardrails.py` validates contract and circuit-breaker failures.
48+
49+
## 8) Tie Engineering to Business Outcomes
50+
51+
- Every pipeline run records operational metrics to `gold.ops_control` for before/after
52+
benchmarking and trend monitoring.
53+
- Optimization and reliability changes should be justified in terms of cost, freshness,
54+
and incident reduction.
55+
56+
## 9) Audit AI-Generated Code Before Merge
57+
58+
- Automated static checks review risky code patterns commonly emitted by LLMs.
59+
- Human review still verifies architecture, scalability, and cost-aware behavior.
60+
61+
## 10) Treat Governance as Code
62+
63+
- CI policy checks detect secret leaks and blocked PII field patterns before merge.
64+
- Data classification and auditability requirements remain defined in
65+
`docs/01_nonfunctional_requirements.md`.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Month-End Incident Runbook
2+
3+
Use this when finance-facing reconciliation outputs are stale, blank, or failed during close.
4+
5+
## 0-15 minutes: Triage and communication
6+
7+
- Answer three questions first: what failed, impact, who must know now.
8+
- Send first stakeholder update immediately: "Issue acknowledged, impact under assessment, next update in 30 minutes."
9+
- Identify failed step(s), upstream dependency status, and last successful run timestamp.
10+
11+
## 15-45 minutes: Investigation
12+
13+
- Confirm whether failure mode is schema drift, late-arriving data, permissions, or bad joins.
14+
- Compare current schema to last known schema snapshot (`gold.ops_schema_drift`).
15+
- Validate business impact:
16+
- Is `gold.month_end_reconciliation_serving` stale?
17+
- Is `as_of_batch_id` behind expected close window?
18+
19+
## 45+ minutes: Recovery
20+
21+
- Apply a temporary patch with explicit audit trail.
22+
- Re-run month-end critical path job: `month_end_critical_path`.
23+
- If invalid records caused failure, quarantine and replay via `replay_txn_dead_letter`.
24+
- Validate reconciliation gate metrics and source/target tolerances before confirming recovery.
25+
26+
## Required status update template
27+
28+
- Incident: month-end reconciliation pipeline degraded/failed
29+
- Impacted table(s): <table>
30+
- Business impact: <close blocked / stale but serving>
31+
- Last good batch: <batch id>
32+
- ETA to next update: <time>
33+
- Current owner: <team/contact>
34+
35+
## Recovery completion checklist
36+
37+
- Month-end outputs are fresh or explicitly marked stale with known reason.
38+
- `gold.ops_control` has run metadata for the recovery batch.
39+
- Schema drift entry captured in `gold.ops_schema_drift`.
40+
- Postmortem opened using `docs/08_postmortem_template.md`.

‎docs/08_postmortem_template.md‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Incident Postmortem Template
2+
3+
## Summary
4+
5+
- Incident ID:
6+
- Date/Time (UTC):
7+
- Duration:
8+
- Severity:
9+
- Owner:
10+
- Impacted business process:
11+
12+
## What happened
13+
14+
- Trigger event:
15+
- Detection channel:
16+
- First customer/stakeholder impact observed:
17+
- Last known good batch:
18+
19+
## Timeline
20+
21+
- T0:
22+
- T+15:
23+
- T+30:
24+
- T+60:
25+
- Recovery complete:
26+
27+
## Root cause
28+
29+
- Primary technical cause:
30+
- Contributing causes:
31+
- Why existing controls did not prevent it:
32+
33+
## Detection and response quality
34+
35+
- Was alert actionable?
36+
- Was ownership clear?
37+
- Did communication cadence meet runbook expectations?
38+
39+
## Corrective actions
40+
41+
- Immediate remediation:
42+
- Long-term prevention:
43+
44+
## Mandatory reliability upgrade (required)
45+
46+
At least one systemic control must be added/updated from this incident:
47+
48+
- [ ] Contract validation
49+
- [ ] Schema drift guard
50+
- [ ] Reconciliation check
51+
- [ ] Alert quality/business-impact routing
52+
- [ ] Idempotent replay/recovery automation
53+
- [ ] Documentation/runbook update
54+
55+
## Verification
56+
57+
- Test evidence for fix:
58+
- Rollback plan validated:
59+
- CI checks updated:
60+
61+
## Sign-off
62+
63+
- Engineering:
64+
- Analytics/Finance stakeholder:
65+
- Compliance (if applicable):

‎resources/dashboards/README.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,15 @@ No `network_risk_exposure` / Elliptic-sourced widget yet, and the alert-queue ta
6666
yet show `top_contributing_factors` (an `array<struct>` column — same Power Query-style
6767
"needs an explode/flatten step" caveat as `WatchlistMatchSummary.jurisdictions` in the Power
6868
BI project). Straightforward to add as a further dataset/widget; not done here for time.
69+
70+
## Ops triage companion
71+
72+
For incident response and month-end readiness checks, use the operational SQL pack in
73+
`resources/ops/` (especially `month_end_status.sql`, `owner_handoff_queue.sql`, and
74+
`schema_drift_breaking_changes.sql`). This lets on-call and finance teams answer freshness,
75+
impact, and ownership questions immediately even before dashboard updates are published.
76+
77+
The Lakeview dashboard now also includes native Operational Health tables for:
78+
79+
- month-end publish/serving status (`table_month_end_status`)
80+
- ownership-based escalation queue (`table_owner_handoff_queue`)

0 commit comments

Comments
 (0)