DataTalksClub / DataTalksClub/website

Define a non-activating retention-policy manifest and loader

Open
#283 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

documentation enhancement foundation integration operations P0 security testing
Dominant language
Python
Stars
0
Forks
0
PR merge metrics
No merged PRs in 30d

Description

Parent epic: #64
Authority-register prerequisite: #254
Kernel-input prerequisite: #281
Downstream kernel/runner: #255 and #257

Product outcome

Provide the missing immutable, source-only retention-policy manifest and fail-closed canonical loader that #257 can consume after its runtime prerequisites exist. The artifact translates only accepted #254 retention authority into stable technical row identities and exact negative/disabled coverage, pins the accepted #281 kernel identities without duplicating its rights/domain policy, and makes every production row non-runnable. A sealed synthetic-only helper gives later runner tests one enabled shape without creating a production activation path.

This is a prerequisite contract, not the retention runner. It introduces no Django/runtime registration, production adapter, policy decision, protected-data access, or record mutation.

Normative authority

  • _docs/PROCESS.md: role-separated issue lifecycle, source-only evidence, deterministic verification plans, and redacted artifacts.
  • _docs/specs/07-security-privacy-operations.md: the five approved default periods, unresolved educational/production-log/suppression/hold semantics, idempotent bounded retention, safe counts, and restore caveat.
  • _docs/specs/01-platform-architecture.md, _docs/architecture/app-boundaries.md, and _docs/architecture/shared-primitives.md: owning-domain services, immutable identities, bounded work, and no cross-domain mutation.
  • _docs/specs/open-decisions.md, resolved decision 12, and closed #23: only the five accepted facts; they do not approve adapters, exact field disposition, holds, activation, or the remaining HUMAN values.
  • #64: child ordering, domain-adapter separation, fail-closed drift, and HUMAN gates.
  • Accepted source-only #254: sole legal/source authority register and exact flow/gate identity.
  • Accepted #281: non-activating privacy-kernel policy/domain identity and bounded shared block/exception codes. #283 references these; it does not add rights, redefine domains, or copy their catalogs.
  • #255: later runtime privacy kernel. #257: later retention registry/runner. Neither issue prose is authority for this artifact.

Dependency gate

Depends on independently tested, PM-accepted, merged, and green-on-call source-only deliveries of #254 and #281. #254 may remain open for HUMAN/legal closure, but #283 must pin its exact accepted register_version, register_digest, inventory commit, flow IDs, retention values, and HUMAN-gate IDs. It must also pin the exact accepted #281 contract, policy, and domain-manifest identities/digests.

Engineering must not start from either frozen candidate or copy a provisional digest. Any accepted #254/#281 identity or relevant flow/policy/gate change invalidates this candidate and requires regeneration, a new manifest version, and fresh verification.

#255 does not depend on #283 merely to implement the domain-neutral request kernel: after #281 is green it may be re-groomed and delivered in parallel with #283. #257 depends on both accepted #255 runtime primitives and accepted #283 retention artifacts.

Exact owned files

This issue owns exactly six new source files:

  • _docs/legal/privacy-retention-policy.json — sole hand-edited technical retention manifest; never a second legal authority;
  • _docs/legal/privacy-retention-policy.schema.json — JSON Schema Draft 2020-12 structural contract;
  • scripts/privacy_retention_policy.py — canonicalization, immutable DTOs, and the only canonical source loader;
  • scripts/validate_privacy_retention_policy.py — read-only fail-closed semantic/cross-register validator;
  • scripts/tests/privacy_retention_policy_test_support.py — sealed in-memory synthetic enablement helper, importable only by tests;
  • scripts/tests/test_privacy_retention_policy.py — identity, drift, coverage, activation-seal, and redaction tests.

It modifies no #254 or #281 artifact. If their accepted artifacts cannot express an authoritative fact required here, the owning prerequisite is revised and reaccepted first. There is no generated Markdown, CSV/YAML copy, Django setting, environment override, database fixture, runtime registry, mutable alias, or second production manifest.

Manifest schema and immutable identity

The canonical JSON has only these required top-level sections:

  • schema_version: integer 1;
  • manifest_version and manifest_digest;
  • authority_register;
  • kernel_input_contract;
  • activation;
  • execution_envelope;
  • policy_rows;
  • flow_coverage;
  • retention_result_catalog;
  • retention_reason_catalog;
  • safe_count_catalog.

Versions are bounded immutable identifiers containing an ISO date plus positive revision. Digests are lowercase SHA-256 over UTF-8 NFC canonical JSON with sorted object keys, compact separators, preserved Unicode, integers only, and no BOM. The manifest digest excludes only manifest_digest. Every policy-row digest excludes only that row's row_digest; the manifest digest includes all row digests. Duplicate JSON keys, noncanonical file bytes, reordered identifier arrays, duplicate IDs, unknown fields, floats/NaN, mutable branch/tag names, or digest disagreement fail.

authority_register pins the accepted #254 path, schema/version/digest, inventory_as_of_commit, the closed-#23 authority ID, and every retention-affecting HUMAN-gate ID. kernel_input_contract pins the accepted #281 path plus contract, policy, and domain-manifest versions/digests and the referenced shared block/exception code IDs. Validation loads both canonical repository artifacts and rejects stale or mismatched identity before returning any definition.

Non-activation and canonical loader

activation is exact in schema version 1:

  • state: non_activating;
  • production_execution_allowed: false;
  • runtime_registration_allowed: false;
  • production_adapter_contracts_present: false;
  • synthetic_test_enablement_allowed: true;
  • successor_manifest_required_for_activation: true;
  • blocking_human_gate_ids: every applicable accepted #254 gate;
  • superseded_by: null.

Every canonical row has execution_state exactly disabled_non_activating or disabled_human_required. No canonical row may say enabled/runnable/active, carry a positive adapter/control contract version, or supply executable batch/attempt/TTL values. The validator rejects any setting, environment key, alternate path, fixture discovery, default/fallback, or status alias that could change this.

scripts/privacy_retention_policy.py exposes one production-facing loader, load_non_activating_retention_policy(), with no path, settings, environment, profile, allow_enabled, or override argument. It resolves only the canonical repository path, verifies #254/#281 identities, canonical bytes and all digests, returns frozen definitions, and rejects every enabled or unresolved-as-allowed state. It performs no Django import, network/database/provider access, discovery, caching across source changes, or mutation. A separate validator-only function may validate an in-memory document for mutation tests, but it cannot return runnable definitions.

Exact policy-row contract

Each policy_rows[] entry has exactly:

  • stable row_id, positive row_version, and lowercase row_digest;
  • one accepted #23 fact ID or applicable HUMAN-gate IDs;
  • exact #254 flow_ids, one #281 coverage/domain reference where applicable, and source_state (current, planned_absent, external_sink, or human_required);
  • policy_status (accepted_default or human_required) and the corresponding disabled execution state;
  • trigger_code, ISO-8601 duration, boundary_code, and authority_disposition only when accepted by #254; HUMAN values are null, never guessed;
  • nullable adapter_disposition_code, adapter_contract_version, hold_contract_version, exception_contract_version, batch_limit, max_attempts, and plan_ttl_seconds; all are null in this non-activating version;
  • effective_state, review_state, exact source references, blocking gate IDs, and future owning issue IDs.

The exact accepted-default row IDs are:

  1. registrations.unverified_abuse.p14d.v1;
  2. events.registration_pii.post_event_p90d.v1;
  3. email.website_delivery_metadata.p180d.v1;
  4. operations.security_audit_events.p365d.v1;
  5. operations.development_logs.p30d.v1.

They record only the exact #23 duration/direction and accepted source mapping. They remain disabled_non_activating; an accepted period never implies that a current model, adapter, hold source, field-level disposition, or runnable row exists. A superficially similar flow is not substituted: Event Q&A is not silently treated as event-registration PII, the analytics-preference cookie is not the website-delivery-metadata row, legacy Datamailer is not target Relay metadata, and planned-absent native registration/email remains absent.

The exact HUMAN row IDs are:

  1. courses.educational_records.post_active.human.v1;
  2. operations.production_logs.human.v1;
  3. email.relay_suppression.human.v1.

They use policy_status=human_required, execution_state=disabled_human_required, null trigger/duration/boundary/disposition/runtime-contract/bound values, and exact #254 gate IDs. Holds, the issued-certificate exception implementation, tombstone expiry, restored-backup disposition, and processor receipts are referenced as blocking controls, not invented as extra rows or resolved values.

execution_envelope is structural only: a later activating successor must use UTC, eligibility boundary trigger_at <= evaluated_at - duration, integer batch_limit in 1..1000, integer max_attempts in 1..100, and integer plan_ttl_seconds in 60..86400. This manifest supplies none of those executable row values. A later domain/control issue must select tighter exact values and accepted contract versions; runtime settings cannot fill them.

Complete retention-flow coverage

flow_coverage contains exactly one entry for every accepted #254 flow ID, in canonical ID order. Each entry points to exactly one policy row or uses one bounded negative classification:

  • human_policy_required;
  • planned_absent_no_runtime_source;
  • inactive_no_processor;
  • transient_not_stored;
  • aggregate_only_no_subject_records;
  • public_source_separate_workflow;
  • legacy_policy_required.

Each negative entry includes the exact #254 inventory/retention disposition and #281 flow/domain coverage reference plus a blocking gate or future issue. It cannot enter a work set. Missing, duplicate, grouped-away, newly added, or reclassified #254 flows fail. A flow cannot be promoted from absent/inactive/transient/aggregate/public/legacy status by this manifest, and runtime inference cannot add a row.

Bounded catalogs without #281 overlap

#283 references, by exact #281 identity, these shared non-dispositive block codes where applicable: policy_not_activated, manifest_mismatch, adapter_unavailable, adapter_contract_mismatch, legal_hold_active, exception_disposition_pending, domain_retry_exhausted, and domain_result_ambiguous. It does not copy or redefine their semantics.

The retention-specific result catalog is exactly:

  • definition_loaded;
  • synthetic_plan_ready;
  • synthetic_batch_succeeded;
  • synthetic_exhausted;
  • synthetic_reconciled.

The retention-specific safe reason catalog is exactly:

  • row_disabled;
  • human_required;
  • source_not_current;
  • policy_source_mismatch;
  • kernel_contract_mismatch;
  • control_unavailable;
  • input_drift;
  • zero_progress;
  • count_mismatch;
  • unsafe_output.

Only definition_loaded is possible from the canonical loader. Every synthetic_* result is marked test-only and rejected in the canonical document/loader. Reasons are bounded diagnostic categories, never a legal outcome or permission to mutate.

The safe count catalog is exactly selected, eligible, disposed, skipped_held, and skipped_changed, all nonnegative integers. It records the future invariant selected = disposed + skipped_held + skipped_changed; this source-only issue never produces domain counts. Unknown/free-form keys or strings fail.

Sealed synthetic test-only enablement seam

scripts/tests/privacy_retention_policy_test_support.py owns build_synthetic_enabled_retention_policy() and nothing outside scripts/tests/ may import it. The helper:

  • first accepts only a fully validated canonical non-activating definition;
  • creates one in-memory row with exact ID synthetic.retention.test.v1, domain/flow values under a reserved synthetic_test_only namespace, positive contract version 1, and caller-supplied bounds constrained by the execution envelope;
  • sets fixture_scope=synthetic_test_only, production_execution_allowed=false, and an unforgeable-in-production Python marker type owned by the test-support module;
  • rejects every canonical row/flow/domain ID, source reference, #254/#281 authority ID, environment/settings value, filesystem override, non-synthetic value, or attempt to serialize the result as the canonical schema;
  • performs no file write, registry discovery, Django setup, database/network/provider access, or production import.

Static import tests fail if a non-test module imports the helper or if the canonical loader imports/discovers scripts.tests. Tests prove the canonical loader rejects the helper's enabled object and every serialized enabled mutation. This seam is test evidence only; #257 must still define its own accepted runtime registry and test injection boundary after #255 exists.

Validator and security behavior

Validation is read-only and fails closed on:

  • schema/version/digest/canonical-byte/order/duplicate/unknown-field errors;
  • any #254 or #281 identity, flow, domain, retention, gate, block-code, exception-code, accepted-fact, inventory-state, or source-reference drift;
  • missing/extra/duplicate policy row or flow coverage; incorrect accepted-default period; HUMAN value filled; similar-but-wrong flow mapping; negative flow entering a row;
  • active/enabled/runnable aliases, non-null production adapter/control/bound values, missing blocker, successor/runtime override, wildcard/default/fallback, or synthetic marker/result in the canonical artifact;
  • an invented legal value, field/model/query/delete instruction, generic anonymize-and-retain behavior, hold release, certificate scope expansion, or claim of restore/processor completion;
  • names, unapproved emails, usernames, IPs, tokens, credentials, cookies, sessions, URLs with sensitive values, provider payloads, domain record IDs, production values, reversible hashes, free-form evidence, or PII in source, fixture objects, exceptions, output, logs, or artifacts.

Tests use only synthetic opaque UUIDs/digests/canaries. Validator output is deterministic and bounded to safe paths, IDs, counts, versions, and digests; rejected sensitive input is never echoed.

Scope

  • Add the exact six source-only files.
  • Bind immutable retention row/manifest identity to accepted #254 and #281 identities.
  • Cover every accepted #254 flow exactly once for retention and preserve negative/current distinctions.
  • Define exact disabled statuses, structural future bounds, bounded retention result/reason/count catalogs, canonical loader, and sealed in-memory synthetic test seam.
  • Supply the stable artifact/API contract needed to rewrite #257 after #255 exists.

Explicit non-goals

  • No modification to #254/#281 files, candidates, schemas, generated inventory, reports, HUMAN checkboxes, legal pages, or public copy.
  • No right/domain matrix, privacy-request state machine, shared block/exception semantics, legal advice/decision, notice, deadline, lawful basis, hold/release policy, or production activation.
  • No Django app/model/migration/service/registry/settings/URL/view/template/static/OpenAPI/Studio/admin/management command/job/scheduler/domain adapter/control/provider/runtime behavior.
  • No production/test database, protected data, requester, AWS/GitHub/Google/Slack/Datamailer/Relay/provider call, export, correction, retention, erasure, tombstone, restore, cache/search invalidation, or processor receipt.
  • No executable field mapping, SQL/model selector, deletion cascade, adapter version, production fixture, fake adapter discoverable outside tests, or workaround for #255/domain-adapter/control issues.
  • No commit, push, merge, deployment, or external operational change outside the normal lifecycle.

Acceptance criteria

  • The exact six files exist, with one technical manifest and no shadow legal authority, runtime registry, alternate manifest, or generated policy copy.
  • Manifest and row versions/digests are independently canonical, immutable, deterministic, and bound to exact accepted/merged #254 and #281 identities.
  • All canonical production rows are disabled; production execution/registration and adapter/control/bound fields are false/null, with no environment/settings/path/default activation route.
  • The five accepted-default and three HUMAN row IDs/states are exact, periods cannot be mapped to lookalike flows, and unresolved values remain null with exact blockers.
  • Every accepted #254 flow has exactly one retention coverage entry; negative/absent/inactive/transient/aggregate/public/legacy flows cannot enter work.
  • #281 shared block/exception codes are referenced rather than redefined; exact retention-specific result/reason/count catalogs reject unknown/free-form values and cannot decide legal disposition.
  • The canonical loader has no override argument, returns frozen non-runnable definitions only, and rejects active/synthetic/tampered/stale artifacts before returning data.
  • The sealed helper can create only the reserved in-memory synthetic enabled row within bounds; canonical/production IDs, serialization, non-test imports, discovery, and canonical-loader acceptance all fail.
  • Cross-register drift, missing/extra/reordered coverage/rows/codes, policy elevation, null-filling, lookalike mapping, bound/contract injection, digest drift, wildcard/default, hidden placeholder, and sensitive-evidence mutations each have focused rejection tests.
  • The validator passes against the accepted artifacts and produces bounded deterministic output; all tests/artifacts are synthetic and contain no protected/provider/production data.
  • Focused tests, formatting/lint/type checks, migration drift, repository quality, and graph-selected versioned verification plan pass with uv/Make.
  • The final graph proves no runtime/render path. Independent tester records Playwright/screenshots not_applicable; any runtime/render path returns the candidate to engineering as out of scope.
  • Engineer, independent tester, and PM reports record exact base/head, #254/#281 identities, manifest/row digests, changed paths, verification-plan/artifact digests, and no pending/skipped required component.

Required verification scenarios

  1. Exact accepted #254/#281 identities versus changed register/contract/policy/domain version, digest, source commit, flow/gate/retention/domain/code/fact; all drift fails before load.
  2. Manifest/row self-digest tamper, duplicate/unknown key, malformed version/digest, noncanonical bytes/order/Unicode, and stale immutable version.
  3. Each missing/duplicate/extra accepted/HUMAN row and every missing/duplicate/reclassified #254 flow coverage; wrong Q&A/analytics/Datamailer mappings fail.
  4. Attempted active/enabled/runnable state, non-null adapter/control/bounds, removed blocker, filled HUMAN value, invented trigger/disposition, wildcard/fallback, runtime setting/environment/path override, or synthetic result in canonical source.
  5. Synthetic helper called with canonical IDs, source refs, out-of-range bounds, environment-derived data, serialization attempt, non-test import/discovery, and canonical-loader handoff; every boundary fails without echo.
  6. Email/token/cookie/session/IP/provider/domain-record/reversible-hash/free-text/Unicode-control/oversize canaries at every document, loader, helper, exception, output, and log boundary.

Required verification commands

At minimum, plus the generated change-selective plan:

uv run --frozen python scripts/validate_privacy_authority_register.py
uv run --frozen python scripts/validate_privacy_kernel_input_contract.py
uv run --frozen python scripts/validate_privacy_retention_policy.py
uv run --frozen python -m unittest scripts.tests.test_privacy_authority_register scripts.tests.test_privacy_kernel_input_contract scripts.tests.test_privacy_retention_policy
make migrations-check
make verification-quality

The independent tester recomputes the plan and reruns focused validation/tests. Source-only Playwright/screenshots are not_applicable only after the tester proves zero runtime/render impact.

Delivery and downstream order

After #254 and #281 are green, #283 follows engineer → independent tester → PM acceptance → focused commit with Closes #283 → local no-ff merge/push → on-call green. In parallel after #281, PM may re-groom and deliver #255 because #283 does not alter the domain-neutral request kernel. #257 remains needs grooming and cannot start until both #255 and #283 are independently accepted, merged, and green; PM then rewrites #257 against their exact paths/APIs/versions/digests and the actual sealed runtime test seam. Production row activation and HUMAN/legal closure remain later, separately groomed domain/control work.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the accepted #254 and #281 source-only artifacts, then read the six owned files listed in the issue, especially scripts/privacy_retention_policy.py and scripts/tests/test_privacy_retention_policy.py. Verify the manifest schema, canonical identities, non-activation rules, coverage, and redaction requirements before implementation. Done means the canonical loader and validator fail closed, return only frozen non-runnable definitions, and the identity, drift, coverage, activation-seal, and redaction tests pass.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
security, tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.