{
  "policy": {
    "version": 5,
    "patterns": {
      "version": 1,
      "method": "CURATED_PRACTICE_TOPIC",
      "notice": "Pattern topics identify design choices to practice. Read the ticket's acceptance criteria and justify the simplest suitable approach. Tags are not capability or ownership evidence; an untagged ticket has no curated pattern topic assigned."
    },
    "fieldMix": {
      "version": 1,
      "method": "CURATED_ESTIMATE",
      "notice": "Field percentages are editorial estimates of the ticket's engineering focus. They total 100%; they are not measured time, proficiency scores, or ownership evidence."
    },
    "contentStatus": "PRACTICE_BRIEF",
    "assessmentStatus": "NOT_QUALIFIED",
    "evidenceUse": "NONE",
    "aiPolicy": "AI tools are welcome during implementation. Record assumptions, review the result, and verify its behavior.",
    "notice": "Fictional engineering practice briefs. Starter repositories, fixtures, automated grading, and verified ownership are not included.",
    "outcomeEvidence": "Tests, patches, and runbooks are requested deliverables. They become Outcome Evidence only through a qualified Mission and immutable Evidence IDs.",
    "ownershipEvidence": "Independent adaptation must be observed under a declared verification policy and cite immutable Evidence IDs. Completing a planning ticket establishes no Ownership Evidence."
  },
  "patternTopics": [
    {
      "id": "factory-method",
      "label": "Factory Method",
      "group": "Creational"
    },
    {
      "id": "abstract-factory",
      "label": "Abstract Factory",
      "group": "Creational"
    },
    {
      "id": "builder",
      "label": "Builder",
      "group": "Creational"
    },
    {
      "id": "prototype",
      "label": "Prototype",
      "group": "Creational"
    },
    {
      "id": "singleton",
      "label": "Singleton",
      "group": "Creational"
    },
    {
      "id": "adapter",
      "label": "Adapter",
      "group": "Structural"
    },
    {
      "id": "bridge",
      "label": "Bridge",
      "group": "Structural"
    },
    {
      "id": "composite",
      "label": "Composite",
      "group": "Structural"
    },
    {
      "id": "decorator",
      "label": "Decorator",
      "group": "Structural"
    },
    {
      "id": "facade",
      "label": "Facade",
      "group": "Structural"
    },
    {
      "id": "flyweight",
      "label": "Flyweight",
      "group": "Structural"
    },
    {
      "id": "proxy",
      "label": "Proxy",
      "group": "Structural"
    },
    {
      "id": "chain-of-responsibility",
      "label": "Chain of Responsibility",
      "group": "Behavioral"
    },
    {
      "id": "command",
      "label": "Command",
      "group": "Behavioral"
    },
    {
      "id": "interpreter",
      "label": "Interpreter",
      "group": "Behavioral"
    },
    {
      "id": "iterator",
      "label": "Iterator",
      "group": "Behavioral"
    },
    {
      "id": "mediator",
      "label": "Mediator",
      "group": "Behavioral"
    },
    {
      "id": "memento",
      "label": "Memento",
      "group": "Behavioral"
    },
    {
      "id": "observer",
      "label": "Observer",
      "group": "Behavioral"
    },
    {
      "id": "state",
      "label": "State",
      "group": "Behavioral"
    },
    {
      "id": "strategy",
      "label": "Strategy",
      "group": "Behavioral"
    },
    {
      "id": "template-method",
      "label": "Template Method",
      "group": "Behavioral"
    },
    {
      "id": "visitor",
      "label": "Visitor",
      "group": "Behavioral"
    },
    {
      "id": "ports-and-adapters",
      "label": "Ports and Adapters",
      "group": "Architectural"
    },
    {
      "id": "cqrs",
      "label": "CQRS",
      "group": "Architectural"
    },
    {
      "id": "strangler-fig",
      "label": "Strangler Fig",
      "group": "Architectural"
    },
    {
      "id": "saga",
      "label": "Saga",
      "group": "Distributed and reliability"
    },
    {
      "id": "transactional-outbox",
      "label": "Transactional Outbox",
      "group": "Distributed and reliability"
    },
    {
      "id": "circuit-breaker",
      "label": "Circuit Breaker",
      "group": "Distributed and reliability"
    },
    {
      "id": "bulkhead",
      "label": "Bulkhead",
      "group": "Distributed and reliability"
    }
  ],
  "projects": [
    {
      "id": "db38a42a-5068-4d2f-8c64-3e0473bb51c1",
      "key": "RRETENTION",
      "title": "Expire support attachments without losing control of exceptions",
      "summary": "Implement explicit retention clocks, protected exceptions and bounded purge recovery.",
      "context": "A fictional support service keeps uploaded diagnostic files indefinitely. The exercise policy expires attachments 30 days after case closure, while a separately authorized investigation hold pauses removal. These are invented product rules, not legal advice or compliance certification.",
      "stack": [
        "TypeScript",
        "PostgreSQL",
        "Object storage adapter"
      ],
      "prerequisites": [
        "Create synthetic cases, attachment metadata and a fake object store with controllable failures.",
        "Use an injected UTC clock and an authorized operator fixture; no real support uploads are needed."
      ],
      "developerValue": "Practice temporal policy, deletion races, exception authority and truthful recovery reporting.",
      "companyValue": "Develop inspectable retention behavior that a company can review against its own approved data policy.",
      "delivery": "Ten scoped tickets using local synthetic records. Policy approval, real account access and production deletion are outside the exercise.",
      "phases": [
        {
          "id": "policy",
          "title": "Define expiry and exceptions",
          "goal": "Make retention decisions explainable and correctly scoped."
        },
        {
          "id": "purge",
          "title": "Apply removal safely",
          "goal": "Recheck authority and recover partial failures."
        },
        {
          "id": "operate",
          "title": "Verify ongoing retention",
          "goal": "Measure backlog and rehearse policy changes without obscuring retained data."
        }
      ],
      "field": "Privacy engineering",
      "tickets": [
        {
          "id": "f6e832ae-82db-42c6-88a6-3a6cd9062497",
          "key": "RRETENTION-101",
          "title": "Calculate attachment expiry from the case closure instant",
          "type": "TASK",
          "priority": "MEDIUM",
          "difficulty": "FOUNDATIONAL",
          "estimateMinutes": 60,
          "phaseId": "policy",
          "dependsOn": [],
          "scenario": "Files uploaded long before a case closes are being removed too early because the prototype starts the retention clock at upload.",
          "acceptanceCriteria": [
            "Use case closure plus 30 elapsed days as the exercise expiry instant.",
            "Keep open cases ineligible and represent missing closure data explicitly.",
            "Return the policy version and reason with each eligibility decision."
          ],
          "implementationNotes": [
            "Use UTC instants and an injected clock; do not substitute local calendar dates."
          ],
          "verification": [
            "Evaluate immediately before and at expiry.",
            "Evaluate open and missing-closure fixtures without scheduling deletion."
          ],
          "deliverables": [
            "Retention decision function and boundary fixtures"
          ],
          "rollout": "Run decisions in preview mode before enabling any removal path.",
          "skills": [
            "Temporal modeling",
            "Data lifecycle"
          ],
          "fieldMix": [
            {
              "field": "Privacy engineering",
              "percentage": 70
            },
            {
              "field": "Backend",
              "percentage": 30
            }
          ],
          "patterns": []
        },
        {
          "id": "2d085eba-9d03-4742-a6dc-a51ed12f605e",
          "key": "RRETENTION-102",
          "title": "Inventory every storage location used by one support attachment",
          "type": "TASK",
          "priority": "MEDIUM",
          "difficulty": "FOUNDATIONAL",
          "estimateMinutes": 75,
          "phaseId": "policy",
          "dependsOn": [
            "RRETENTION-101"
          ],
          "scenario": "Removing the primary upload leaves its thumbnail and a cached diagnostic preview behind.",
          "acceptanceCriteria": [
            "List primary object, derived previews, metadata and any backup copy in a bounded data-flow inventory.",
            "Assign an owner and retention behavior to each location.",
            "Mark unknown or external copies explicitly instead of declaring removal complete."
          ],
          "implementationNotes": [
            "Use the fictional architecture and local adapters; do not discover or copy real customer data."
          ],
          "verification": [
            "Trace one synthetic upload through each declared location.",
            "Add an unknown derived location and verify the inventory marks coverage incomplete."
          ],
          "deliverables": [
            "Attachment lifecycle map and location registry"
          ],
          "rollout": "Review the registry before wiring purge adapters; new derived stores require an inventory update.",
          "skills": [
            "Data mapping",
            "Storage lifecycle"
          ],
          "fieldMix": [
            {
              "field": "Privacy engineering",
              "percentage": 60
            },
            {
              "field": "Storage systems",
              "percentage": 40
            }
          ],
          "patterns": []
        },
        {
          "id": "fb83a7c5-0dac-4b0d-a2b0-8d48246d6dd0",
          "key": "RRETENTION-103",
          "title": "Require scoped authority to place an attachment on investigation hold",
          "type": "STORY",
          "priority": "MEDIUM",
          "difficulty": "INTERMEDIATE",
          "estimateMinutes": 150,
          "phaseId": "policy",
          "dependsOn": [
            "RRETENTION-101"
          ],
          "scenario": "Any support member can currently set a permanent hold with no reason, and the flag has no owner for later review.",
          "acceptanceCriteria": [
            "Require tenant-scoped hold authority, a bounded reason category and a review date.",
            "Append hold creation and release records with actor and policy version.",
            "Deny a foreign-tenant or ordinary-member hold command before mutation."
          ],
          "implementationNotes": [
            "Keep free-text case content out of generic audit logs; an audit records the privileged action and safe identifiers."
          ],
          "verification": [
            "Create and release a hold as the authorized synthetic operator.",
            "Attempt both commands from another tenant and an unprivileged member; verify no change."
          ],
          "deliverables": [
            "Hold commands and authorization regressions"
          ],
          "rollout": "Introduce hold management before purge activation so active exceptions can be represented.",
          "skills": [
            "Authorization",
            "Auditability"
          ],
          "fieldMix": [
            {
              "field": "Security",
              "percentage": 50
            },
            {
              "field": "Privacy engineering",
              "percentage": 50
            }
          ],
          "patterns": []
        },
        {
          "id": "ff8e19b3-a4a0-4d27-bffb-7b1406f8f038",
          "key": "RRETENTION-104",
          "title": "Preview the attachment purge set with stable decision reasons",
          "type": "STORY",
          "priority": "MEDIUM",
          "difficulty": "INTERMEDIATE",
          "estimateMinutes": 150,
          "phaseId": "purge",
          "dependsOn": [
            "RRETENTION-101",
            "RRETENTION-102",
            "RRETENTION-103"
          ],
          "scenario": "Operators cannot tell why two similarly aged attachments are treated differently by the retention job.",
          "acceptanceCriteria": [
            "Return bounded pages of eligible, held and ineligible records with policy and decision timestamp.",
            "Keep preview tenant-scoped and omit file contents and unrestricted object URLs.",
            "State that preview is advisory and execution rechecks current eligibility."
          ],
          "implementationNotes": [
            "Use stable cursor ordering; a preview must never claim to lock the future purge set."
          ],
          "verification": [
            "Compare expiry and hold fixtures with the decision function.",
            "Place a hold after preview and verify the preview itself causes no deletion."
          ],
          "deliverables": [
            "Purge-preview endpoint and scoped pagination tests"
          ],
          "rollout": "Expose preview to authorized local operators first; disable the view independently of lifecycle records.",
          "skills": [
            "Operational tooling",
            "Data minimization"
          ],
          "fieldMix": [
            {
              "field": "Privacy engineering",
              "percentage": 40
            },
            {
              "field": "Developer tooling",
              "percentage": 30
            },
            {
              "field": "Security",
              "percentage": 30
            }
          ],
          "patterns": []
        },
        {
          "id": "53096897-e90d-4401-ac1c-347e4eb16136",
          "key": "RRETENTION-105",
          "title": "Recheck holds when a queued attachment purge begins",
          "type": "BUG",
          "priority": "HIGH",
          "difficulty": "ADVANCED",
          "estimateMinutes": 210,
          "phaseId": "purge",
          "dependsOn": [
            "RRETENTION-103",
            "RRETENTION-104"
          ],
          "scenario": "An attachment becomes held after the purge queue is populated, but the worker trusts the old eligibility flag and removes it anyway.",
          "acceptanceCriteria": [
            "Re-evaluate current policy and hold state before issuing removal.",
            "Define coordination between hold creation and a purge already crossing its irreversible boundary.",
            "Return an explicit conflict or too-late state without claiming a newly created hold restored deleted bytes."
          ],
          "implementationNotes": [
            "Model the race using a controlled storage adapter; document the exact serialization boundary."
          ],
          "verification": [
            "Pause before removal, place a hold and verify the object remains.",
            "Race hold creation with confirmed removal and verify the declared conflict outcome and truthful audit."
          ],
          "deliverables": [
            "Purge/hold transition protocol and race tests"
          ],
          "rollout": "Keep deletion disabled until both race orderings pass; preserve metadata for any ambiguous external operation.",
          "skills": [
            "Concurrency",
            "State transitions"
          ],
          "fieldMix": [
            {
              "field": "Privacy engineering",
              "percentage": 50
            },
            {
              "field": "Database engineering",
              "percentage": 30
            },
            {
              "field": "Backend",
              "percentage": 20
            }
          ],
          "patterns": []
        },
        {
          "id": "e2dbb92c-0e45-4b9c-8b14-9d2b636fc0c6",
          "key": "RRETENTION-106",
          "title": "Retry partial attachment removal without forgetting derived copies",
          "type": "BUG",
          "priority": "HIGH",
          "difficulty": "ADVANCED",
          "estimateMinutes": 240,
          "phaseId": "purge",
          "dependsOn": [
            "RRETENTION-102",
            "RRETENTION-105"
          ],
          "scenario": "The primary object is gone but thumbnail removal timed out. A retry treats the missing primary as success for the entire attachment.",
          "acceptanceCriteria": [
            "Track per-location progress and idempotent removal outcomes.",
            "Distinguish not-found from authorization and transport failure.",
            "Mark the purge complete only when every required location is confirmed or an explicit unresolved exception remains."
          ],
          "implementationNotes": [
            "Keep evidence of unresolved copies in restricted operational records; do not expose storage credentials in failure text."
          ],
          "verification": [
            "Fail thumbnail removal after primary success, retry and verify both locations are reconciled.",
            "Inject a forbidden response and confirm it remains unresolved rather than being treated as already absent."
          ],
          "deliverables": [
            "Per-location purge state and partial-failure regressions"
          ],
          "rollout": "Retry only incomplete locations; stop promotion when a provider cannot give a trustworthy deletion outcome.",
          "skills": [
            "Idempotency",
            "Partial failure"
          ],
          "fieldMix": [
            {
              "field": "Privacy engineering",
              "percentage": 40
            },
            {
              "field": "Distributed systems",
              "percentage": 30
            },
            {
              "field": "Storage systems",
              "percentage": 30
            }
          ],
          "patterns": []
        },
        {
          "id": "0fb4fb2c-c3fa-4c63-bb7b-4eae198a7a7b",
          "key": "RRETENTION-107",
          "title": "Record a reopened case without silently resetting attachment history",
          "type": "BUG",
          "priority": "HIGH",
          "difficulty": "ADVANCED",
          "estimateMinutes": 180,
          "phaseId": "purge",
          "dependsOn": [
            "RRETENTION-101",
            "RRETENTION-105"
          ],
          "scenario": "Reopening a case overwrites its closure timestamp. Support can no longer explain whether an attachment was eligible when a purge started.",
          "acceptanceCriteria": [
            "Append case lifecycle events and derive the current retention clock under an explicit reopening rule.",
            "Preserve previous decisions and completed removal history.",
            "Prevent reopening from implying deleted attachments can be recovered."
          ],
          "implementationNotes": [
            "Define the exercise rule before coding: reopening pauses eligibility; a later closure starts a new 30-day period for remaining attachments."
          ],
          "verification": [
            "Reopen before expiry and verify ineligibility, then close again and verify the new boundary.",
            "Reopen after confirmed purge and show the attachment remains removed with its original decision history."
          ],
          "deliverables": [
            "Reopening policy and historical decision tests"
          ],
          "rollout": "Version the policy and preview changed eligibility before activating the new clock behavior.",
          "skills": [
            "Lifecycle history",
            "Policy versioning"
          ],
          "fieldMix": [
            {
              "field": "Privacy engineering",
              "percentage": 60
            },
            {
              "field": "Data engineering",
              "percentage": 40
            }
          ],
          "patterns": []
        },
        {
          "id": "730afd07-8bf9-446a-8a56-b58fa10f7b49",
          "key": "RRETENTION-108",
          "title": "Report retention backlog without listing customer file names",
          "type": "CHORE",
          "priority": "MEDIUM",
          "difficulty": "INTERMEDIATE",
          "estimateMinutes": 120,
          "phaseId": "operate",
          "dependsOn": [
            "RRETENTION-104",
            "RRETENTION-106"
          ],
          "scenario": "The only retention report exports every filename to a general analytics dashboard.",
          "acceptanceCriteria": [
            "Expose aggregate eligible, held, failed and completed counts plus oldest eligible age.",
            "Keep filenames, object keys and case content out of generic metrics.",
            "Provide authorized drilldown through the scoped operational view instead of metric labels."
          ],
          "implementationNotes": [
            "Use bounded outcome categories and distinguish zero eligible records from unavailable measurements."
          ],
          "verification": [
            "Reconcile aggregate counts with a synthetic fixture containing each outcome.",
            "Insert sensitive-looking filenames and verify no value reaches the metric payload."
          ],
          "deliverables": [
            "Retention metrics and sanitized payload tests"
          ],
          "rollout": "Run aggregate reporting beside the restricted preview; remove the old filename-based dashboard after validation.",
          "skills": [
            "Observability",
            "Data minimization"
          ],
          "fieldMix": [
            {
              "field": "Privacy engineering",
              "percentage": 60
            },
            {
              "field": "Site reliability",
              "percentage": 40
            }
          ],
          "patterns": []
        },
        {
          "id": "7ce555bb-3f68-4e7f-97e0-e6a3a0650a84",
          "key": "RRETENTION-109",
          "title": "Rehearse a shorter retention policy without deleting on preview",
          "type": "TASK",
          "priority": "MEDIUM",
          "difficulty": "EXPERT",
          "estimateMinutes": 300,
          "phaseId": "operate",
          "dependsOn": [
            "RRETENTION-104",
            "RRETENTION-105",
            "RRETENTION-107"
          ],
          "scenario": "Product proposes reducing the exercise retention period from 30 to 14 days. Applying the constant immediately would make a large backlog eligible at once.",
          "acceptanceCriteria": [
            "Create a new policy version and produce a tenant-scoped impact preview.",
            "Define activation time, bounded purge batches and preserved hold semantics.",
            "Document that rollback can stop future deletion but cannot restore confirmed removed objects."
          ],
          "implementationNotes": [
            "Treat 14 days as a proposed fictional rule requiring review in the exercise workflow, not a real-world policy recommendation."
          ],
          "verification": [
            "Compare old and new decisions at activation boundaries with active holds.",
            "Cancel activation and verify no objects were removed by the preview; rehearse stopping after one controlled purge batch."
          ],
          "deliverables": [
            "Policy-change plan, impact report and stop procedure"
          ],
          "rollout": "Require the explicit local activation command after review; retain previous policy and append the activation record.",
          "skills": [
            "Change management",
            "Retention policy"
          ],
          "fieldMix": [
            {
              "field": "Privacy engineering",
              "percentage": 60
            },
            {
              "field": "Site reliability",
              "percentage": 40
            }
          ],
          "patterns": []
        },
        {
          "id": "75fcf31a-1b97-42b2-b150-3449b2dbcc36",
          "key": "RRETENTION-110",
          "title": "Verify retention recovery after an interrupted purge run",
          "type": "TASK",
          "priority": "MEDIUM",
          "difficulty": "EXPERT",
          "estimateMinutes": 330,
          "phaseId": "operate",
          "dependsOn": [
            "RRETENTION-106",
            "RRETENTION-108",
            "RRETENTION-109"
          ],
          "scenario": "The purge process crashes between provider acknowledgement and state persistence. The dashboard cannot tell which objects remain.",
          "acceptanceCriteria": [
            "Reconcile uncertain per-location states through the declared provider contract.",
            "Preserve held and not-yet-eligible objects while converging repeated retries.",
            "Produce a report separating confirmed removal, retained exceptions and unresolved provider outcomes."
          ],
          "implementationNotes": [
            "Use synthetic objects and deterministic crash points; a completed job record alone does not prove every copy is gone."
          ],
          "verification": [
            "Interrupt before and after each storage acknowledgement and compare actual fixture objects with recorded states.",
            "Repeat reconciliation and verify no duplicate privileged transition and no newly removed held object."
          ],
          "deliverables": [
            "Crash matrix, reconciliation procedure and truthful retention report"
          ],
          "rollout": "Enable scheduled local purge only after recovery cases pass; unresolved states remain visible for authorized review.",
          "skills": [
            "Recovery engineering",
            "Data lifecycle verification"
          ],
          "fieldMix": [
            {
              "field": "Privacy engineering",
              "percentage": 40
            },
            {
              "field": "Site reliability",
              "percentage": 30
            },
            {
              "field": "Distributed systems",
              "percentage": 30
            }
          ],
          "patterns": []
        }
      ]
    }
  ]
}
