Self-service Databricks Well-Architected Framework assessment with deterministic SQL over
system.*and per-pillar scoring.
The WAF Assessment Engine evaluates a Databricks workspace against the
Databricks Well-Architected Framework
(7 pillars). It runs deterministic SQL queries over system.* (lineage, audit,
billing, compute, information_schema) on behalf of the logged-in user (OBO),
joins the rows to a curated controls catalog, and produces:
- Per-pillar scores -- one score per pillar (
controls met / controls in pillar * 100) - Overall score -- average of available pillar scores (skips pillars with no controls or query failures)
- Per-control drill-down -- principle, best practice, threshold, score, recommendation, and a "Fix with Forge" deep-link
- History + drift comparison -- every run persisted in Lakebase,
from vs. todiff highlighting regressions and improvements
The engine is always enabled -- the page lives at /assessment and the
catalog is seeded on first boot from a bundled CSV.
The catalog covers all 7 Databricks WAF pillars. Each pillar has a
deterministic SQL query that scores a curated subset of its controls; the
remaining controls in each pillar are either catalog-only (no automatic
signal yet) or qualitative (scored from forge_waf_qualitative_responses).
| Pillar key | Display name | Evaluation today |
|---|---|---|
governance |
Data and AI Governance | Automatic (SQL) |
interoperability_usability |
Interoperability and Usability | Automatic (SQL, subset) |
operational_excellence |
Operational Excellence | Automatic (SQL, subset) + qualitative |
security_compliance_privacy |
Security, Compliance and Privacy | Automatic (SQL, subset) + qualitative |
reliability |
Reliability | Automatic (SQL) + qualitative |
performance_efficiency |
Performance Efficiency | Automatic (SQL) |
cost_optimisation |
Cost Optimisation | Automatic (SQL) |
The pillars with SQL queries are exported as WAF_PILLARS_WITH_QUERIES in
lib/engines/waf-assessment/types.ts and drive the engine loop. The IU / OE /
SCP queries cover a curated set of high-signal controls (4-5 each); other
controls in those pillars stay catalog-only.
runAssessment()
│
│ 1. ensureCatalogSeeded() -- upsert bundled CSV into forge_waf_controls
│
│ 2. Insert ForgeWafAssessment row (status=running)
│
▼
runAllPillars() -- Promise.allSettled over WAF_PILLARS_WITH_QUERIES
│
│ ┌──────────────┬─────────┬─────────┬─────────┬───────┬────────┬────────────┐
│ │ governance │ IU │ OE │ SCP │ rel. │ cost │ perf │
│ └──────────────┴─────────┴─────────┴─────────┴───────┴────────┴────────────┘
│ ▼ executeSQL() against the user's SQL Warehouse (OBO)
│
│ WafControlResult[] (waf_id, pillar, score, threshold, threshold_met)
│
▼
Score aggregation
│ pillarScore = round((met / total) * 100, 1)
│ overallScore = avg of available pillar scores
│
▼
Persist
│ ForgeWafControlResult.createMany() -- per-control rows
│ ForgeWafAssessment.update() -- status=completed + scores + counts
│
▼
WafAssessmentSummary returned to the API
A failure in any single pillar query is captured as { pillar, message } in
the errors[] array and surfaced in errorMessage; the other pillars still
produce results and the assessment is marked completed with whatever pillars
returned. Only when all pillars fail is the assessment marked failed.
ensureCatalogSeeded() in lib/engines/waf-assessment/catalog.ts:
- Reads
lib/engines/waf-assessment/data/waf-controls-catalog.csv - Maps each row to a
WafControl(pillar key derived frompillar_name) - Attaches the
FIX_ACTIONSmap for the "Fix with Forge" UI shortcut - Upserts into
ForgeWafControl(idempotent — runs every assessment)
runPillar(pillar) in lib/engines/waf-assessment/engine.ts loads the
corresponding SQL file from lib/engines/waf-assessment/queries/, executes it
via executeSQL(), and maps each row to a WafControlResult. Each query must
return columns:
waf_id, score_percentage, threshold_percentage, threshold_met
threshold_met is a string — met becomes true, anything else becomes
false.
runAllPillars() runs all 7 SQL pillars in parallel via
Promise.allSettled, so one failing query does not block the others.
runAssessment() in lib/engines/waf-assessment/service.ts:
- Filters results to
wafIds present in the catalog (avoids FK violations if a query returns an unknown id) createMany()per-control results inForgeWafControlResult- Computes per-pillar scores via
pillarScore(totals, pillar)--Math.round((met / total) * 1000) / 10 overallScoreis the rounded average of available pillar scores- Updates the assessment header with status + scores + counts + timestamps
The bundled catalog (waf-controls-catalog.csv, 166 rows) is the source of
truth for control metadata. It currently includes:
- 151 BPs from the official Databricks WAF spreadsheet (CombinedList) —
Databricks-style codes (e.g.
DG-01-03,R-02-04,CO-01-09) that map cleanly to the AWS / Azure WAF references. - 15 EX extensions (
<PILLAR>-EX<NN>-<NN>format, e.g.SCP-EX01-01) covering modern controls not yet in the official spreadsheet: Automatic Identity Management, encryption-in-transit, data classification, idle decommissioning, criticality tiering, SDLC, FinOps, ops standardization, FMA, and DR drills.
| Column | Notes |
|---|---|
waf_id |
Stable identifier (DG-01-01, SCP-EX01-01, ...) — primary key |
pillar_name |
Verbatim from the catalog (e.g. 01 - Data And AI Governance) — mapped to a pillar key by pillarKeyFromName() |
principle |
Higher-level grouping inside the pillar |
best_practice |
Short title shown in the UI |
capabilities |
Free-text capabilities note |
details |
Long description shown in the drill-down |
query_table_name |
Hint for which system.* table the SQL touches |
threshold_percentage |
Pass threshold — null for qualitative controls |
metric_definition |
Free-text formula / metric definition |
recommendation_if_not_met |
Recommendation surfaced when the control fails |
evaluation_type |
automatic or qualitative (160 + 6 today) |
automatic— score comes from a SQL query; the result is compared againstthreshold_percentageto derivethreshold_met.qualitative— score comes from ayes(100) /partial(50) /no(0) /not_applicable(excluded) questionnaire response stored inforge_waf_qualitative_responses. The threshold is hardcoded at 100 (must answer "yes" to count as met). Saved responses are workspace-level and reused across every assessment run — no need to re-answer between runs.
FIX_ACTIONS in catalog.ts attaches a fixActionEngine + params per
waf_id. Two flavours:
- Internal engines (
comment-engine,estate-scan,ask-forge) deep-link to a Forge surface that addresses the gap directly. Rendered with a "Fix with Forge" button (Wrench icon). docsis a fallback that links to the canonical Databricks doc when remediation is admin- or workload-level (Photon, autoscaling, runtimes). Rendered with an "Open docs" button (Book icon, new tab).
| Route | Method | Purpose |
|---|---|---|
/api/assessment |
GET | Latest assessment (with control results) + history list + qualitative controls + saved responses |
/api/assessment/run |
POST | Trigger a full assessment run, persist results, return the summary |
/api/assessment/[assessmentId] |
GET | Single assessment with per-control results |
/api/assessment/controls |
GET | Catalog of WAF controls (no run required) |
/api/assessment/qualitative |
GET / POST / DELETE | List, upsert, or remove a workspace qualitative response |
/api/assessment/ignored |
GET / POST / DELETE | List, add, or remove a workspace control exclusion |
POST /api/assessment/dashboard builds or updates the WAF dashboard at the
server-controlled /Shared/Forge Dashboards/ destination. This prebuilt
artifact uses native Lakeview parameter-filter bindings, so it is validated
with the narrow imported-lakeview contract mode. That compatibility mode is
WAF-only: Forge-generated pipeline and ad-hoc dashboards use the stricter
default forge-assembled contract.
The WAF dashboard is Forge-owned. Create/update, draft read-back, optional publish, and publication read-back all use the app service principal's M2M identity; the route never accepts a client-supplied destination and does not fall back to a user's OBO token. Assessment SQL itself continues to run as the logged-in user through OBO.
template.lvdash.json was exported from a live AI/BI workspace, so it captured
two editor-internal artifacts that are not deployable Lakeview content. Both are
corrected in the template rather than tolerated by relaxing the validator, and
this is an intentional, documented behavior change from the raw export:
-
Associative filter field removed. The
utilization_statusmulti-select filter query carried a second field,utilization_status_associativity, whose expression wasCOUNT_IF(`associative_filter_predicate_group`).associative_filter_predicate_groupis an AI/BI editor-internal pseudo column, not a column of thecluster_efficiencydataset, so a dashboard deployed with it through the Lakeview API fails at query execution withUNRESOLVED_COLUMN. The field was not encoded by the widget either, so it also violated the filter field-to-encoding contract. It is removed, and the contract validator continues to reject the shape in bothforge-assembledandimported-lakeviewmodes. Theimported-lakeviewmode was deliberately not widened to accept unencoded extra filter fields, because doing so would have blanket-weakened every imported hybrid filter rather than admitting one proven-valid shape.Behavior change: the deployed WAF dashboard no longer dims filter options that match no rows under the other active filters (AI/BI "associative" filter behavior). Filtering, selection, and every widget bound to
cluster_efficiencyare unaffected. If Databricks later documents a supported serialized form for associativity, restore it by adding the field to both the query and the widget encoding so the existing contract is satisfied without a validator exemption. -
Invisible separator widget corrected. A text widget on the
cluster_efficiencypage serialized as"lines": [""], which renders nothing and fails the contract rule that a text widget must contain visible content. It now serializes as"lines": ["---"], preserving the intended horizontal separator. The visible/non-empty text-spec rule itself is unchanged.
layoutVersion: "GRID_V1" was also added to the page that omitted it, matching
every other page in the template and the mandatory GRID_V1 page contract.
| Component | Purpose |
|---|---|
app/assessment/page.tsx |
Main /assessment page: pillar score cards, failing-controls tab, per-pillar drill-down, qualitative tab, ignored tab, AWS/Azure WAF cross-reference badges, history table, CSV export |
app/assessment/compare/page.tsx |
/assessment/compare?from=X&to=Y drift view — Regressed / Improved / Moved / Added / Removed / Unchanged |
Each control row (both automatic and qualitative) renders two clickable badges
("AWS WAF · …" / "Azure WAF · …") sourced from
lib/engines/waf-assessment/cross-references.ts. The module returns a
pillar-level fallback for every control plus per-control overrides for the
controls where the official Databricks WAF spreadsheet documents a specific
AWS / Azure reference (currently DG-02-02, CO-01-09, PE-02-04,
R-01-03, SCP-01-13).
The toolbar exports a client-side CSV (waf-assessment-YYYY-MM-DD.csv) with
columns waf_id, pillar, principle, best_practice, score, threshold, met, recommendation.
| Table | Purpose |
|---|---|
ForgeWafControl |
Curated catalog of WAF controls (one row per waf_id) — seeded from the bundled CSV. Includes evaluation_type and nullable threshold_percentage for qualitative items |
ForgeWafAssessment |
One row per assessment run: status, per-pillar scores (governance_score, iu_score, oe_score, scp_score, reliability_score, cost_score, performance_score), overall_score, met/total counts, error message, timestamps |
ForgeWafControlResult |
Per-control result rows joined to a parent assessment via FK (assessment_id × waf_id) — score, threshold, met flag, evaluated_at |
ForgeWafQualitativeResponse |
Workspace-level answers for the 6 qualitative controls — one row per waf_id, reused across assessment runs (configuration, not run-scoped) |
ForgeWafIgnoredResource |
Workspace exclusions: opt out of a control (or in a future iteration, a specific resource) with a reason. Excluded controls are skipped when computing pillar/overall scores |
Schema is auto-migrated via prisma db push --accept-data-loss on app boot
(scripts/start.sh, Step E).
The engine takes no runtime configuration today. The bundled CSV is the
source of truth for pillar coverage, thresholds, and evaluation type. To add
or modify a control, edit the CSV and re-deploy — ensureCatalogSeeded()
will upsert the change on the next assessment.
| File | Purpose |
|---|---|
lib/engines/waf-assessment/types.ts |
All TypeScript types: WafPillar, WafControl, WafControlResult, WafAssessmentSummary/Detail, WAF_PILLARS, WAF_PILLARS_WITH_QUERIES, PILLAR_LABEL |
lib/engines/waf-assessment/catalog.ts |
CSV loader, pillar-key mapping, FIX_ACTIONS map, ensureCatalogSeeded() |
lib/engines/waf-assessment/csv.ts |
Minimal RFC 4180 CSV parser (handles multiline quoted fields) |
lib/engines/waf-assessment/engine.ts |
runPillar() + runAllPillars() — load SQL, execute, map rows |
lib/engines/waf-assessment/service.ts |
runAssessment(), listAssessments(), getLatestAssessment(), getAssessmentDetail(), listControls() |
lib/engines/waf-assessment/queries/governance.sql |
Governance pillar SQL |
lib/engines/waf-assessment/queries/interoperability-usability.sql |
Interoperability & Usability pillar SQL |
lib/engines/waf-assessment/queries/operational-excellence.sql |
Operational Excellence pillar SQL |
lib/engines/waf-assessment/queries/security-compliance-privacy.sql |
Security, Compliance and Privacy pillar SQL |
lib/engines/waf-assessment/queries/reliability.sql |
Reliability pillar SQL |
lib/engines/waf-assessment/queries/cost-optimisation.sql |
Cost Optimisation pillar SQL |
lib/engines/waf-assessment/queries/performance-efficiency.sql |
Performance Efficiency pillar SQL |
lib/engines/waf-assessment/data/waf-controls-catalog.csv |
Bundled catalog (166 rows, 11 columns) |
lib/engines/waf-assessment/cross-references.ts |
Static AWS WAF / Azure WAF cross-reference resolver (pillar fallback + per-control overrides) used by the UI badges |
app/api/assessment/route.ts |
GET latest + history |
app/api/assessment/run/route.ts |
POST trigger run |
app/api/assessment/[assessmentId]/route.ts |
GET single assessment |
app/api/assessment/controls/route.ts |
GET catalog |
app/assessment/page.tsx |
Main UI |
app/assessment/compare/page.tsx |
Drift comparison UI |
prisma/schema.prisma |
ForgeWafControl, ForgeWafAssessment, ForgeWafControlResult models |
- Broaden IU / OE / SCP coverage — the current SQL queries cover a curated subset of each pillar (4-5 controls). Extend with admin-API checks for SCIM/AIM, network/IdP federation, customer-managed keys, and additional workflow/cluster policy signals.
- Resource-level exclusions — extend
ForgeWafIgnoredResource(which already accepts an optionalresourceType/resourceIdpair) so that SQL queries honor exclusions at the resource grain (e.g. exclude one warehouse fromSCP-02-01instead of the whole control). - Enrich AWS / Azure cross-references —
cross-references.tsships with pillar-level fallback for every control plus per-control overrides for a handful of controls. Sweep the official Databricks WAF spreadsheet's "AWS reference" / "Azure reference" columns and add aCONTROL_REFSentry for every documented mapping so the badges always deep-link to the most specific AWS / Azure WAF guidance.