DataTalksClub / DataTalksClub/website
Define a non-activating retention-policy manifest and loader
Nobody has claimed this yet.
- 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: integer1;manifest_versionandmanifest_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, positiverow_version, and lowercaserow_digest; - one accepted #23 fact ID or applicable HUMAN-gate IDs;
- exact #254
flow_ids, one #281 coverage/domain reference where applicable, andsource_state(current,planned_absent,external_sink, orhuman_required); policy_status(accepted_defaultorhuman_required) and the corresponding disabled execution state;trigger_code, ISO-8601duration,boundary_code, andauthority_dispositiononly when accepted by #254; HUMAN values arenull, never guessed;- nullable
adapter_disposition_code,adapter_contract_version,hold_contract_version,exception_contract_version,batch_limit,max_attempts, andplan_ttl_seconds; all arenullin 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:
registrations.unverified_abuse.p14d.v1;events.registration_pii.post_event_p90d.v1;email.website_delivery_metadata.p180d.v1;operations.security_audit_events.p365d.v1;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:
courses.educational_records.post_active.human.v1;operations.production_logs.human.v1;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 reservedsynthetic_test_onlynamespace, positive contract version1, 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
- 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.
- Manifest/row self-digest tamper, duplicate/unknown key, malformed version/digest, noncanonical bytes/order/Unicode, and stale immutable version.
- Each missing/duplicate/extra accepted/HUMAN row and every missing/duplicate/reclassified #254 flow coverage; wrong Q&A/analytics/Datamailer mappings fail.
- 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.
- 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.
- 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
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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