SapixDBSapixDB/Docs
Early Access
Phase 3 · Policy Engine

Policy Engine

The Policy Engine governs the data lifecycle of every strand. Retention policies cryptographically tombstone records when they age out — provable deletion, not silent erasure. Field-level ACL policies strip sensitive fields from API responses without touching the stored data. Both are enforced by the database layer, not application code.

Retention
  • Define max_age_secs per policy
  • Background agent tombstones expired records every 5 min
  • TOMBSTONE nucleotide = cryptographic proof of deletion
  • Most restrictive rule wins when multiple apply
Field-Level ACL
  • Define denied_fields per policy
  • Applied inline at every read response boundary
  • Stored strand data is never modified
  • All matching policies union their denied_fields
Strand data is never silently deletedA tombstone is itself a signed, hash-linked nucleotide appended to the strand. It proves deletion happened at a specific time — which is itself immutable evidence for GDPR right-to-erasure audits, HIPAA breach investigations, and SOX change trails.

Retention Policies

A retention policy defines how long records survive before the enforcement agent writes a TOMBSTONE nucleotide against them. The enforcement cycle runs every 5 minutes. Multiple retention policies are applied with the most restrictive rule winning — if two policies match and one says 30 days while the other says 90, the 30-day rule applies.

FieldTypeDescription
namestringHuman-readable label for the policy.
max_age_secsu64Age limit in seconds. 0 = no limit (keep forever).
classification_tagstring | nullOptional tag to scope the rule (matches _tag in payload). Stored but not yet enforced — see limitations.
priorityu32Higher = checked first. Tie-breaking by insertion order (oldest first).

Create a retention policy

POST /v1/policies/retention
{
  "name":               "GDPR 90-day",
  "max_age_secs":       7776000,
  "classification_tag": null,
  "priority":           10
}
// → { "id": "a1b2c3d4…", "name": "GDPR 90-day", "max_age_secs": 7776000,
//     "priority": 10, "created_at_ms": 1747003200000, ... }

List / get / delete

GET /v1/policies
{
  "retention": [
    { "id": "a1b2…", "name": "GDPR 90-day", "max_age_secs": 7776000, "priority": 10, ... }
  ],
  "acl": [...]
}
GET /v1/policies/retention/:id | DELETE /v1/policies/retention/:id
// GET  → 200 RetentionPolicy  |  404 if not found
// DELETE → 204 No Content

Field-Level ACL Policies

An ACL policy names the payload fields that must be stripped before any API response leaves the agent. The stripping happens at every read boundary — GET /v1/records/:hash, GET /v1/strand/records, POST /v1/query, and POST /v1/query/semantic. The stored strand data is never modified; only the payload_b64 in the response is re-encoded without the denied keys. The content hash in the response still reflects the original unmasked data.

Non-object payloads are returned unchangedField masking only applies to MessagePack maps (objects with named keys). Scalar payloads, arrays, and binary blobs that cannot be decoded as MessagePack are passed through without modification.
FieldTypeDescription
namestringHuman-readable label.
denied_fieldsstring[]Top-level payload field names to redact. Must be non-empty (400 otherwise).
principalstring | null"*" = all callers (default). Per-caller enforcement requires Codios integration (Phase 4.1).
priorityu32Higher = checked first. ACL policies are additive — all matching rules apply.

Create an ACL policy

POST /v1/policies/acl
{
  "name":          "Redact PII",
  "denied_fields": ["ssn", "dob", "credit_card"],
  "principal":     "*",
  "priority":      5
}
// → { "id": "f7e8d9c0…", "name": "Redact PII", "denied_fields": ["ssn","dob","credit_card"],
//     "principal": "*", "priority": 5, "created_at_ms": 1747003200000 }
// → 400 Bad Request if denied_fields is empty

List / get / delete

GET /v1/policies/acl/:id | DELETE /v1/policies/acl/:id
// GET    → 200 AclPolicy  |  404 if not found
// DELETE → 204 No Content

Conflict Resolution Hierarchy

When multiple policies apply to the same record or response, the conflict resolution rules determine the effective behavior. Call GET /v1/policies/hierarchy to see the computed effective order at any time.

1
ACL policies are additive
denied_fields from all matching ACL policies are unioned. If Policy A denies ["ssn"] and Policy B denies ["dob"], both fields are stripped from every response.
2
Retention policies are restrictive
The most limiting max_age_secs wins. If Policy A sets 30 days and Policy B sets 90 days, records are tombstoned at 30 days.
3
Higher priority = checked first
Ties are broken by insertion order — oldest policy first. Set priority explicitly to control ordering when precision matters.
GET /v1/policies/hierarchy
{
  "resolution_rules": [
    "ACL policies are additive: denied_fields from all matching rules are unioned",
    "Retention policies are restrictive: most limiting max_age_secs wins",
    "Higher priority number = checked first; ties broken by creation order (oldest first)"
  ],
  "effective_order": [
    { "kind": "retention", "id": "a1b2…", "name": "GDPR 90-day",  "priority": 10, "description": "Tombstone after 7776000s" },
    { "kind": "acl",       "id": "f7e8…", "name": "Redact PII",   "priority": 5,  "description": "Redact: ssn, dob, credit_card" }
  ]
}

Python

installation
pip install sapixdb-policy
GDPR + PII redaction + hierarchy inspection
import asyncio
from sapixdb_policy import PolicyClient

async def main():
    async with PolicyClient("http://localhost:7475") as pol:
        # Retention: tombstone records older than 90 days (GDPR right-to-erasure)
        gdpr = await pol.gdpr_retention(90, priority=10)
        print(f"Created retention policy: {gdpr.id}  ({gdpr.max_age_secs}s)")

        # Shorter window for high-sensitivity records
        ccpa = await pol.create_retention(
            "CCPA 30-day",
            max_age_secs=30 * 86_400,
            priority=20,  # checked first — stricter wins
        )

        # Field-level ACL: strip PII from every API response
        pii = await pol.redact_fields(
            ["ssn", "dob", "credit_card", "bank_account"],
            name="PII Redaction",
            priority=5,
        )
        print(f"ACL policy: {pii.id}  fields={pii.denied_fields}")

        # Inspect the effective policy order
        h = await pol.hierarchy()
        print("\nResolution rules:")
        for rule in h.resolution_rules:
            print(f"  • {rule}")
        print("\nEffective order (highest priority first):")
        for entry in h.effective_order:
            print(f"  [{entry.kind:<10}] {entry.name:<20} priority={entry.priority}")
            print(f"              {entry.description}")

        # Remove the GDPR policy
        await pol.delete_retention(gdpr.id)
        print(f"\nDeleted policy {gdpr.id}")

asyncio.run(main())

PolicyClient API

MethodDescription
list_policies()Return all retention and ACL policies.
hierarchy()Return the computed effective priority order and resolution rules.
create_retention(name, max_age_secs, *, classification_tag, priority)Create a retention policy. max_age_secs=0 means keep forever.
get_retention(policy_id)Get a retention policy by id. Raises 404 if not found.
delete_retention(policy_id)Delete a retention policy (204 No Content).
create_acl(name, denied_fields, *, principal, priority)Create a field-level ACL policy. denied_fields must be non-empty.
get_acl(policy_id)Get an ACL policy by id. Raises 404 if not found.
delete_acl(policy_id)Delete an ACL policy (204 No Content).
gdpr_retention(max_age_days, name, priority)Convenience: create a retention policy expressed in days.
redact_fields(fields, name, priority)Convenience: create a wildcard ACL policy for the given field names.
SapixClient convenience methodsIf you are already using sapixdb-agent, SapixClient exposes raw-dict wrappers — policy_list(), policy_hierarchy(), policy_create_retention(), policy_get_retention(), policy_delete_retention(), policy_create_acl(), policy_get_acl(), policy_delete_acl(). Use sapixdb-policy for typed Pydantic models and the gdpr_retention() / redact_fields() helpers.

Known Limitations

classification_tag not yet enforced
The tag field is stored and returned but not yet used for matching. Currently all retention policies apply to all records. Tag-based scoping is planned for 3.5.1.
principal defaults to wildcard
ACL principal="*" applies to all callers. Per-caller identity enforcement requires Codios integration (Phase 4.1).
Full strand scan on retention cycle
The 5-minute enforcement task scans the entire strand. For very large strands (>10M records), a time-indexed scan would be more efficient — planned after Phase 4.
Healthcare or financial workload?

The HIPAA package adds PHI field classification and access logging. The SOX package adds financial agent designation and dual-admin sign-off enforcement.