What we built
Footwork in place
| Artifact | Path | Purpose |
|---|---|---|
| CUAD extractor | poc/extract_cuad.py | Downloads CUAD, normalizes the first N docs, emits the shape report |
| SQLite schema | poc/schema.sql | Mirrors the Snowflake model: dims, fact, bridge, authorization, audit |
| Seeder | poc/seed.py | Loads 30 contracts + synthetic users/matters; exports the static demo data |
| Risk engine | poc/risk.py | Shared, rule-based rubric (real CUAD categories); mirrors sql/04 |
| Access layer | poc/access.py | The enforcement point — authorization check + audit logging |
| POC web server | poc/server.py | Standard-library HTTP server; real login, real denials, real audit |
| Simulated Snowflake | poc/snowflake_sim.py | Emulates role context, row access policy, masking, query history, and a risk register offline (see Snowflake options) |
| Offline SQL harness + CI | scripts/run_sql_offline.py, .github/workflows/ci.yml | Runs sql/01…05 through fakesnow (DuckDB) in CI; reports supported vs. governance statements |
| Static demo | demo/index.html | Board-clickable login/audit demo + Snowflake console + risk register, hosted on Pages |
| Shape report | poc/analysis/cuad_shape_report.md | Full statistics over the 30-document sample |
| Evidence pack | poc/analysis/evidence_pack.md | Role × matter verification matrix (expected vs observed) with reproduction steps |
python3 poc/extract_cuad.py --limit 30 # fetch + normalize the sample
python3 poc/seed.py # build SQLite DB + demo data
python3 poc/build_demo.py # regenerate the static Pages demo
python3 poc/server.py # functional POC at http://localhost:8000
python3 scripts/run_sql_offline.py # run sql/01…05 offline via fakesnow (CI)
Analysis
How the real data aligns with the plan
30
documents extracted
1,230
annotations (30×41)
41
clause categories
1–175k
chars per contract
| Original plan assumed | CUAD actually provides | Change required |
|---|---|---|
| 510 contracts, 41 annotated categories | Confirmed (aok_v1.0) | None |
A contract_type field | No such field; type is embedded in the title with EX-10.x noise | Add title normalization |
A structured parties field | No parties field; parties are the "Parties" clause annotation | Model as annotation, not a column |
| Clauses carry Yes/No answers | Extractive spans: answers[{text, answer_start}] + is_impossible | Derive presence from is_impossible |
| Clause text with start/end offsets | Only answer_start; end must be derived from the text length | Derive end; store snippet |
| Categories like Indemnification, Limitation of Liability, Confidentiality, Warranties | None of these exist; liability is Uncapped Liability / Cap On Liability | Remap the rubric |
| Multiple paragraphs per document | One paragraph (the full contract) per document | Simplifies ingestion |
The headline finding
Four clause categories named in the original brainstorm don't exist in CUAD. The risk rubric
was remapped to real categories (
Uncapped Liability, Cap On Liability,
Termination For Convenience, Anti-Assignment, Change Of Control,
Minimum Commitment, Governing Law, Non-Compete, Exclusivity).Path to Snowflake
How the POC maps to the warehouse
| POC (SQLite / Python) | Snowflake target |
|---|---|
dim_*, fact_clause, bridge_matter_contract | Same tables under CORE (sql/02_data_model.sql) |
access.py authorization check | Row access policy over matter_authorization (sql/03) |
| PII masking in the query path | Masking policies + classification tags |
audit_log table | Append-only audit table and QUERY_HISTORY / ACCESS_HISTORY |
Risk scoring in seed.py | CORE.ref_risk_rule + MARTS.mart_matter_risk (sql/04) |
| POC web server | Not needed — enforcement moves into the warehouse; any BI/UI reads governed tables |
Interactive
Try the access-control demo
Sign in as alice, bob, or carol (attorneys/paralegal) to see only your matters, request a document outside your matters to watch the denial get recorded, then sign in as admin to read the audit log of who accessed which files.
Simulated vs. functional
The hosted demo simulates the model in the browser so it runs on static Pages. The SQLite POC
enforces it for real server-side; both share the same authorization map and denial semantics.