State Law Compliance Frameworks: Statutory Rules as Executable Code

Within Core Architecture & Compliance Mapping, the state-law compliance engine is the deterministic rule layer that converts legislative text into the executable controls every disclosure decision is measured against. It sits downstream of classification and upstream of records retrieval: once a request is classified it carries a statutory clock, an exemption posture, and a jurisdiction, and this engine is what computes the binding deadline, flags the applicable exemptions, and writes the audit record that an oversight body will later read. Each state’s open-records act diverges on response windows, tolling triggers, fee schedules, and exemption categories — California’s Public Records Act, the Texas Public Information Act, and New York’s Freedom of Information Law all start and stop the clock differently — so the framework must treat every rule as a versioned artifact bound to an effective date, never as a hard-coded constant. This guide builds that engine as immutable, cryptographically fingerprinted Python objects whose every evaluation is reproducible years after the fact.

Problem Framing & Statutory Requirement

Public-records statutes are deadline machinery. The federal baseline under 5 U.S.C. § 552(a)(6)(A)(i) is a 20-business-day response window, but state acts override it in both directions: the Texas Public Information Act runs on a “promptly, and no later than 10 business days” prompt-cost-estimate trigger, the California Public Records Act sets a 10-calendar-day determination window with a 14-day extension for unusual circumstances, and New York’s FOIL requires acknowledgement within 5 business days. A framework that encodes any of these as a literal timedelta in application code guarantees eventual statutory misalignment the moment a legislature amends the window — and an agency that miscomputes a single deadline has, by definition, violated the statute.

Three failure classes drive the requirement. First, clock errors: a deadline computed against the wrong convention (calendar vs. business days, wrong tolling rule, missing state holiday) produces a missed statutory deadline that the agency believed it had met. Second, disclosure errors: an exemption that is evaluated by ad-hoc human judgment rather than against a closed rule matrix lets privileged or personal material slip past redaction, or conversely withholds releasable material and invites an appeal. Third, audit errors: if the exact rule version, input, and verdict are not captured at evaluation time, the agency cannot reconstruct why a decision was made when it is challenged on administrative appeal or in litigation.

The compliance engine therefore consumes the validated record emitted by FOIA request taxonomy design, evaluates it against the statute in effect on the date of receipt, and hands its verdict to records retrieval only after writing an immutable audit entry. The requirement is a rule layer that is closed (exemptions drawn from a controlled set), versioned (every rule stamped with an effective and supersession date), and deterministic (the same request and rule snapshot always yield the same verdict).

Prerequisites & Environment Setup

The engine is pure Python with the standard library only; deadline arithmetic and hashing need no third-party runtime dependency, which keeps it safe to run inline in the request path.

  • Python 3.11+ — required for datetime.UTC, StrEnum, and the dataclass(frozen=True, slots=True) features used below.
  • Standard library: dataclasses, datetime, enum, hashlib, json, logging, uuid — no other runtime dependencies.
  • workalendar>=17.0 (optional, recommended in production) — supplies per-state business-day calendars including statutory holidays; install with pip install workalendar. The snippets below isolate the calendar behind a single function so it can be swapped without touching rule logic.
  • pytest>=8.0 (dev only) — for the regression suite in the verification section.
  • Access controls: the compliance service needs read access to the versioned rule registry and write access only to the append-only audit log; it must hold no credentials for the records store itself, since evaluation completes before retrieval. That separation is enforced through security boundary configuration at this hop.

Treat the statutory rules themselves as a configuration artifact under version control — one rule object per statute per effective period — not as constants buried in application code. Each rule change is tagged with a semantic version and an effective date so that a request received last quarter is always re-evaluated against the statute that was authoritative when it arrived.

Architecture Overview

The engine is a deterministic transform between two pipeline stages. A normalized request enters, the active rule for its jurisdiction and receipt date is selected, the deadline and exemption posture are computed, and a verdict plus an audit hash leave. Retention windows are cross-referenced against records retention scheduling so that a request for records already past lawful disposition is handled distinctly from one for live records, and the scoping rules from the security boundary constrain the evaluation set to authorized jurisdictional domains.

Pipeline position of the state-law compliance engine A left-to-right pipeline. Request ingestion feeds FOIA taxonomy normalization, which feeds the compliance engine that computes exemptions, deadlines, and fees. The engine feeds a retention scheduling cross-reference, then a security boundary and scoping filter, then records retrieval. The compliance engine also emits a verdict and audit hash downward to an append-only audit ledger. Request ingestion FOIA taxonomy normalization THIS ENGINE Compliance engine exemptions · deadlines · fees Retention cross-reference Security boundary & scoping filter Records retrieval verdict + audit hash Append-only audit ledger

This layering prevents over-disclosure, enforces least-privilege access, and guarantees that every compliance decision is traceable to a specific statutory provision and rule version. The verdict object produced here is the contract handed downstream, so its shape — rule applied, exemption flagged, computed deadline, redaction flag, audit hash — is effectively the API between compliance and retrieval.

Step-by-Step Implementation

1. Compile statutes into immutable, versioned rule objects

Model each statute as a frozen dataclass so a compiled rule cannot be mutated at runtime, and stamp it with effective and supersession dates so the registry can select the rule that was authoritative on any given receipt date. The frozen=True setting is the structural guarantee that statutory rules behave as constants once loaded.

python
from __future__ import annotations

import hashlib
import json
import logging
import uuid
from dataclasses import dataclass
from datetime import date, datetime, timedelta, UTC
from enum import StrEnum

# --- Structured JSON audit logging (NIST SP 800-53 AU-3: content of audit records) ---
class JSONLogFormatter(logging.Formatter):
    def format(self, record: logging.LogRecord) -> str:
        payload = {
            "timestamp": datetime.now(UTC).isoformat(),
            "level": record.levelname,
            "logger": record.name,
            "message": record.getMessage(),
        }
        for key in ("request_id", "rule_id", "verdict_hash", "error_details"):
            if hasattr(record, key):
                payload[key] = getattr(record, key)
        return json.dumps(payload)

logger = logging.getLogger("state_compliance_engine")
logger.setLevel(logging.INFO)
_handler = logging.StreamHandler()
_handler.setFormatter(JSONLogFormatter())
logger.addHandler(_handler)


class ExemptionCategory(StrEnum):
    PRIVACY_PII = "privacy_pii"
    LAW_ENFORCEMENT = "law_enforcement"
    TRADE_SECRETS = "trade_secrets"
    DELIBERATIVE_PROCESS = "deliberative_process"
    NONE = "none"


@dataclass(frozen=True, slots=True)
class ComplianceRule:
    """Immutable representation of a state statutory compliance rule."""
    rule_id: str
    state_code: str               # e.g. "CA", "TX", "NY"
    statute_reference: str        # e.g. "Cal. Gov. Code Sec. 7922.535"
    exemption_type: ExemptionCategory
    response_calendar_days: int
    response_business_days: int
    use_business_days: bool       # CA PRA: calendar; TX PIA / NY FOIL: business
    effective_date: date
    superseded_date: date | None = None

    def is_active_on(self, on: date) -> bool:
        """A rule applies only within its effective window."""
        if on < self.effective_date:
            return False
        return self.superseded_date is None or on < self.superseded_date

    def generate_audit_hash(self) -> str:
        """Cryptographic fingerprint binding a verdict to the exact rule version."""
        payload = f"{self.rule_id}:{self.statute_reference}:{self.effective_date.isoformat()}"
        return hashlib.sha256(payload.encode("utf-8")).hexdigest()

Expected behavior: two ComplianceRule instances built from the same statute and effective date produce identical generate_audit_hash() values, while any amendment that ships a new effective_date produces a different fingerprint — so an audit record can be matched back to exactly one rule version.

2. Compute statutory deadlines deterministically

Deadline arithmetic is where most compliance bugs live. Calendar-day statutes (California’s PRA determination window) and business-day statutes (Texas PIA, New York FOIL) must not share a code path, and business-day math must skip weekends and the jurisdiction’s statutory holidays. Isolate the calendar behind one function so the rule logic stays pure and the holiday source can be swapped.

python
def add_business_days(start: date, n: int, state_code: str) -> date:
    """Skip weekends and per-state statutory holidays.

    In production, back this with workalendar's per-state calendar so that, e.g.,
    Cesar Chavez Day (CA) or Emancipation Day (TX) toll the clock correctly.
    """
    try:
        from workalendar.registry import registry
        cal = registry.get(f"US-{state_code}")() if registry.get(f"US-{state_code}") else None
    except Exception:  # pragma: no cover - calendar lookup is environment-specific
        cal = None

    current, remaining = start, n
    while remaining > 0:
        current += timedelta(days=1)
        is_weekend = current.weekday() >= 5
        is_holiday = bool(cal) and not cal.is_working_day(current)
        if not is_weekend and not is_holiday:
            remaining -= 1
    return current


def compute_deadline(rule: ComplianceRule, received: date) -> date:
    """Deterministic statutory deadline aligned with the governing convention."""
    if rule.use_business_days:
        # TX Gov. Code Sec. 552.221 / NY Pub. Off. Law Sec. 89(3): business-day clock.
        return add_business_days(received, rule.response_business_days, rule.state_code)
    # Cal. Gov. Code Sec. 7922.535: 10 calendar days to determine, plus extensions.
    return received + timedelta(days=rule.response_calendar_days)

Expected output: a California rule (use_business_days=False, response_calendar_days=10) evaluated against a request received 2026-06-01 returns 2026-06-11; a Texas rule (use_business_days=True, response_business_days=10) against the same date skips two weekends and returns 2026-06-15.

Calendar-day versus business-day deadline conventions for one request A strip of fifteen days from Monday June 1 to Monday June 15, 2026. A request is received on June 1. The calendar-day clock used by the California Public Records Act adds ten calendar days and lands on June 11. The business-day clock used by the Texas Public Information Act and New York FOIL counts ten business days, skipping the two weekends shown as June 6 and 7 and June 13 and 14, and lands on June 15. Each working day after receipt carries its business-day ordinal one through ten. Received Mon Jun 1, 2026 — one request, two statutory clocks 1 Mo 2 Tu 1 3 We 2 4 Th 3 5 Fr 4 6 Sa 7 Su 8 Mo 5 9 Tu 6 10 We 7 11 Th 8 12 Fr 9 13 Sa 14 Su 15 Mo 10 Calendar-day clock — CA PRA: received + 10 calendar days = Jun 11 Business-day clock — TX PIA · NY FOIL: 10 business days, weekends skipped = Jun 15 Numbered cells = business-day ordinals; weekends and state holidays toll only the business-day clock

3. Evaluate the request and emit a structured verdict

The evaluation function selects the rule active on the receipt date, computes the deadline, derives the exemption posture, and returns a verdict object rather than a bare boolean — downstream redaction and fee logic need the structure. It raises rather than guessing when no rule covers the jurisdiction, because a silent default is itself a compliance failure.

python
@dataclass(frozen=True, slots=True)
class ComplianceVerdict:
    request_id: str
    rule_applied: str
    exemption_flagged: ExemptionCategory
    response_deadline: date
    requires_redaction: bool
    audit_hash: str
    trace_id: str


def select_active_rule(rules: list[ComplianceRule], state_code: str, received: date) -> ComplianceRule:
    """Pick the single rule for this jurisdiction that was authoritative at receipt."""
    candidates = [r for r in rules if r.state_code == state_code and r.is_active_on(received)]
    if not candidates:
        # Fail closed: never evaluate against an absent or wrong-era statute.
        raise ValueError(f"No active compliance rule for {state_code} on {received.isoformat()}")
    # Most recent effective_date wins if windows overlap during a transition.
    return max(candidates, key=lambda r: r.effective_date)


def evaluate_request_compliance(
    request_payload: dict,
    rules: list[ComplianceRule],
    trace_id: str,
) -> ComplianceVerdict:
    """Evaluate a normalized request against the statute in effect at receipt."""
    received = date.fromisoformat(request_payload["received_date"])
    rule = select_active_rule(rules, request_payload["state_code"], received)

    deadline = compute_deadline(rule, received)
    requires_redaction = rule.exemption_type != ExemptionCategory.NONE

    verdict = ComplianceVerdict(
        request_id=request_payload["request_id"],
        rule_applied=rule.rule_id,
        exemption_flagged=rule.exemption_type,
        response_deadline=deadline,
        requires_redaction=requires_redaction,
        audit_hash=rule.generate_audit_hash(),
        trace_id=trace_id,
    )
    logger.info(
        "Compliance evaluated",
        extra={"request_id": verdict.request_id, "rule_id": rule.rule_id},
    )
    return verdict

The exemption posture here is intentionally coarse — a single flag — because the per-code matching logic lives in the exemption mapping detailed in how to map state-specific FOIA exemptions to Python dictionaries. This engine decides which statute governs and when the clock expires; the mapping decides which clause applies to which field.

4. Write an immutable, hash-chained audit entry

Every verdict must be recorded with the exact rule version, the hash of the input, the verdict, and a timestamp, so any disclosure or denial can withstand review. Chaining each entry’s hash over the previous entry makes silent tampering detectable.

python
def log_compliance_audit(
    verdict: ComplianceVerdict,
    request_payload: dict,
    previous_hash: str,
) -> str:
    """Append an immutable audit record and return its chaining hash.

    NIST SP 800-53 AU-9 (protection of audit information) / AU-10 (non-repudiation):
    the verdict_hash binds prior_hash + input + verdict so tampering breaks the chain.
    """
    request_hash = hashlib.sha256(
        json.dumps(request_payload, sort_keys=True).encode("utf-8")
    ).hexdigest()
    verdict_hash = hashlib.sha256(
        f"{previous_hash}:{verdict.request_id}:{verdict.audit_hash}:"
        f"{verdict.response_deadline.isoformat()}".encode("utf-8")
    ).hexdigest()

    entry = {
        "timestamp": datetime.now(UTC).isoformat(),
        "trace_id": verdict.trace_id,
        "request_hash": request_hash,
        "rule_id": verdict.rule_applied,
        "exemption_applied": verdict.exemption_flagged.value,
        "deadline": verdict.response_deadline.isoformat(),
        "prior_hash": previous_hash,
        "verdict_hash": verdict_hash,
    }
    logger.info(
        "Compliance audit recorded",
        extra={"request_id": verdict.request_id, "verdict_hash": verdict_hash},
    )
    # In production, append `entry` to write-once storage or an append-only ledger.
    return verdict_hash

In production, route these entries to write-once storage or an append-only ledger and forward them to a SIEM; the returned verdict_hash becomes the previous_hash for the next entry, forming a tamper-evident chain.

Validation & Verification

A compliance engine is only trustworthy if its determinism and its fail-closed behavior are asserted on every change. The highest-value tests catch a deadline-convention drift or a rule-selection error before it ships.

python
import pytest

CA_RULE = ComplianceRule(
    rule_id="CA-PRA-2024", state_code="CA",
    statute_reference="Cal. Gov. Code Sec. 7922.535",
    exemption_type=ExemptionCategory.PRIVACY_PII,
    response_calendar_days=10, response_business_days=0,
    use_business_days=False, effective_date=date(2024, 1, 1),
)
TX_RULE = ComplianceRule(
    rule_id="TX-PIA-2024", state_code="TX",
    statute_reference="Tex. Gov. Code Sec. 552.221",
    exemption_type=ExemptionCategory.NONE,
    response_calendar_days=0, response_business_days=10,
    use_business_days=True, effective_date=date(2024, 1, 1),
)
REQ = {"request_id": "r-1", "state_code": "CA", "received_date": "2026-06-01"}


def test_evaluation_is_deterministic():
    a = evaluate_request_compliance(dict(REQ), [CA_RULE], "t-1")
    b = evaluate_request_compliance(dict(REQ), [CA_RULE], "t-2")
    # Same request + same rule snapshot -> identical deadline and audit hash.
    assert a.response_deadline == b.response_deadline
    assert a.audit_hash == b.audit_hash

def test_calendar_vs_business_day_conventions_differ():
    ca = evaluate_request_compliance(dict(REQ), [CA_RULE], "t")
    tx = evaluate_request_compliance(dict(REQ, state_code="TX"), [TX_RULE], "t")
    assert ca.response_deadline == date(2026, 6, 11)   # 10 calendar days
    assert tx.response_deadline >= date(2026, 6, 12)   # 10 business days, skips weekends

def test_missing_rule_fails_closed():
    with pytest.raises(ValueError):
        evaluate_request_compliance(dict(REQ, state_code="ZZ"), [CA_RULE], "t")

def test_audit_chain_detects_tampering():
    v = evaluate_request_compliance(dict(REQ), [CA_RULE], "t")
    h1 = log_compliance_audit(v, dict(REQ), previous_hash="genesis")
    h2 = log_compliance_audit(v, dict(REQ, received_date="2026-06-02"), previous_hash=h1)
    assert h1 != h2  # any input change yields a different chaining hash

To assert the audit trail itself, capture log records with pytest’s caplog fixture and confirm that every evaluation emits exactly one Compliance evaluated record carrying a request_id and rule_id. Because both ComplianceRule and ComplianceVerdict are frozen=True, replaying the same request during recovery yields an equal verdict and never starts a second statutory clock — the idempotency guarantee an auditor expects to see exercised.

Troubleshooting & Edge Cases

  • Requests spanning a statutory amendment. A request arrives the week a state shortens its response window. Diagnosis: select_active_rule returns the new rule for an older request because effective-date filtering is wrong. Fix: always select on the receipt date via is_active_on, keep the superseded rule in the registry, and never mutate or delete an old rule — historical requests must re-evaluate against the statute that governed them.

  • Tolling and clock-stop events ignored. A fee estimate is sent or a clarification is requested, which under the Texas PIA and many state acts pauses the clock, but the deadline keeps counting. Diagnosis: the deadline appears earlier than the agency’s records show. Fix: model tolling explicitly as freeze intervals subtracted from elapsed business days, rather than computing a single deadline at receipt and treating it as immutable.

  • Holiday calendar gaps. A business-day deadline lands on a state holiday because the calendar source lacks that jurisdiction’s observances (e.g., Cesar Chavez Day in California, Emancipation Day in Texas). Diagnosis: a deadline one day earlier than the agency calendar. Fix: back add_business_days with the per-state workalendar calendar and assert holiday coverage in tests; never hard-code only federal holidays.

  • Duplicate submissions producing two clocks. A requester emails and then re-submits via the portal, generating two request_id values and two deadlines for one logical request. Diagnosis: two audit entries with near-identical request_hash values within a short window. Fix: deduplicate upstream in the taxonomy stage before this engine assigns a clock, and reconcile against a deterministic dedup key rather than evaluating each channel independently.

  • Litigation-hold conflict with disposition. The engine flags records as releasable while they sit under an active legal hold. Diagnosis: a disclosure verdict for held material. Fix: treat hold status as a gate evaluated after this verdict but before any release or destruction action, deferring to the records retention scheduling engine, which refuses disposition while a hold flag is set.

Compliance Verification Checklist