File size: 3,241 Bytes
6952bcb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
-- 007_knowledge (sqlite) — Phase 6 first-customer UX (docs/HARDENING.md):
-- company-knowledge ingestion (vault docs) + generated L5 exam drafts.
--
-- Data class T2 "crown jewels" (docs/TENANCY.md): uploaded company documents
-- and the exam items derived from them. Protection = T1 (org-scoped queries
-- via atp/tenant_db.py) PLUS Fernet encryption at rest via
-- atp/crypto.py encrypt_for_org (per-org HKDF subkeys):
--   * vault_docs.content_enc / summary_enc  — 'enc1:…' ciphertext, never
--     plaintext (leak surface #7: T2 columns are ciphertext in dumps by
--     construction).
--   * l5_drafts.items_json_enc              — exam items derived from the
--     docs, encrypted for the same reason; the blueprint (section names +
--     weights only) stays plaintext for review listings.
-- Tenant files NEVER land under /videos or /media (leak surface #4) — there
-- is no file path at all: content lives only in these encrypted columns.
--
-- Conventions (002/004/005/006):
--   * ts columns are ISO-8601 UTC strings ("%Y-%m-%dT%H:%M:%SZ").
--   * org_id defaults to 'org-demo', the bootstrap org (005 bridge), so dev
--     mode (BU_AUTH_DISABLED=1) keeps working unchanged.
--   * SQLite has NO row-level security: dev/demo isolation relies on layers
--     1-2 of docs/TENANCY.md (route deps + org-scoped SQL). The Postgres
--     twin (007_knowledge.pg.sql) adds RLS.
--
-- Mutability model: NOT append-only, by design. Drafts are working documents
-- (draft -> approved | rejected state transitions; docs move uploaded ->
-- drafted -> signed). The tamper-evident record of who signed what lives in
-- the atp_evidence hash chain instead: approve/reject writes a signed
-- 'sme_signoff' evidence row via atp/signing.py append_chain.
--
-- Statement separator convention: a line containing only `--;;` splits this
-- file into statements (see atp/db.py).

CREATE TABLE IF NOT EXISTS vault_docs (
  id          TEXT PRIMARY KEY,
  org_id      TEXT NOT NULL DEFAULT 'org-demo',
  title       TEXT NOT NULL,
  filename    TEXT,
  mime        TEXT,
  content_enc TEXT NOT NULL,   -- Fernet 'enc1:…' via crypto.encrypt_for_org
  summary_enc TEXT,            -- heuristic summary, encrypted too (T2)
  uploaded_by TEXT,
  created_at  TEXT NOT NULL,
  status      TEXT NOT NULL DEFAULT 'uploaded'
              CHECK (status IN ('uploaded', 'drafted', 'signed'))
);
--;;
-- (org_id, <pk>) per docs/TENANCY.md invariants.
CREATE INDEX IF NOT EXISTS idx_vault_docs_org ON vault_docs(org_id, id);
--;;
CREATE TABLE IF NOT EXISTS l5_drafts (
  id             TEXT PRIMARY KEY,
  org_id         TEXT NOT NULL DEFAULT 'org-demo',
  cert_label     TEXT NOT NULL,
  blueprint_json TEXT NOT NULL,  -- sections/weights only — reviewable, plaintext
  items_json_enc TEXT NOT NULL,  -- T2: items derived from docs are encrypted
  doc_ids_json   TEXT NOT NULL,  -- JSON array of vault_docs.id this draft covers
  status         TEXT NOT NULL DEFAULT 'draft'
                 CHECK (status IN ('draft', 'approved', 'rejected')),
  created_by     TEXT,
  created_at     TEXT NOT NULL,
  reviewed_by    TEXT,
  reviewed_at    TEXT,
  review_note    TEXT
);
--;;
CREATE INDEX IF NOT EXISTS idx_l5_drafts_org ON l5_drafts(org_id, id);