{
  "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": "4f4b6b1e-4e59-4a19-8701-b8f2ce3f673c",
      "key": "ACCESS",
      "title": "Tighten a partner portal's access boundaries",
      "field": "Security",
      "summary": "Make partner access explicit across list APIs, batch commands, invitations, cached sessions, and exports.",
      "context": "A fictional manufacturing company shares purchase-order documents with partner organizations. Buyers, partner administrators, and read-only agents use the same API. Recent support reports suggest list filters and background downloads disagree about who may see an order.",
      "stack": [
        "TypeScript",
        "NestJS",
        "PostgreSQL",
        "Redis"
      ],
      "prerequisites": [
        "HTTP APIs",
        "Session authentication",
        "Tenant-scoped data access"
      ],
      "developerValue": "Practice authorization as a service-level invariant, including revocation timing, concurrent membership changes, and asynchronous data delivery.",
      "companyValue": "Inspect a concrete record of cross-organization denial, least-privilege decisions, and access-recovery behavior using fictional partner records.",
      "delivery": "Ten defensive engineering issues over mapping, enforcement, and assurance phases; use synthetic accounts and documents in an owned test environment.",
      "phases": [
        {
          "id": "map",
          "title": "Map permissions",
          "goal": "Identify resources and define authorized actions."
        },
        {
          "id": "enforce",
          "title": "Enforce at every boundary",
          "goal": "Apply tenant and permission rules across synchronous and asynchronous access."
        },
        {
          "id": "assure",
          "title": "Prove revocation and explain denials",
          "goal": "Handle changes in authority and provide useful audit records."
        }
      ],
      "tickets": [
        {
          "id": "ff8477d9-e707-4c86-8738-9dfb12ebf572",
          "key": "ACCESS-101",
          "title": "Write the partner permission matrix from existing routes",
          "type": "TASK",
          "priority": "HIGH",
          "difficulty": "FOUNDATIONAL",
          "estimateMinutes": 120,
          "phaseId": "map",
          "dependsOn": [],
          "scenario": "The portal has three roles, but engineers infer permissions from page names. A read-only agent can discover an Edit route that was added for partner administrators.",
          "acceptanceCriteria": [
            "Inventory order, document, invitation, and export actions at route and service boundaries.",
            "Define allowed actions for buyer, partner administrator, and read-only agent.",
            "Mark unspecified combinations as denied and identify ownership or tenant conditions."
          ],
          "implementationNotes": [
            "A hidden button is not an authorization rule; include non-UI callers."
          ],
          "verification": [
            "Map each fixture route to exactly one documented action.",
            "Show explicit denials for agent edits and cross-partner document reads."
          ],
          "deliverables": [
            "Permission matrix and route-to-action inventory"
          ],
          "rollout": "Review the matrix before enforcing new checks; record intentional compatibility changes for test clients.",
          "skills": [
            "Threat modeling",
            "Authorization design",
            "API inventory"
          ],
          "fieldMix": [
            {
              "field": "Security",
              "percentage": 80
            },
            {
              "field": "System design",
              "percentage": 20
            }
          ],
          "patterns": []
        },
        {
          "id": "f5e4be88-ea30-4a39-91a3-23070a721d59",
          "key": "ACCESS-102",
          "title": "Remove partner scope from client-controlled list filters",
          "type": "BUG",
          "priority": "HIGH",
          "difficulty": "INTERMEDIATE",
          "estimateMinutes": 150,
          "phaseId": "enforce",
          "dependsOn": [
            "ACCESS-101"
          ],
          "scenario": "Changing partnerId in the purchase-order query reveals another partner's orders. The controller authenticates the session but trusts the query filter to choose the tenant.",
          "acceptanceCriteria": [
            "Derive authorized partner scope from the server-side actor context.",
            "Apply that scope within the order repository query.",
            "Return scoped counts and pagination cursors that cannot widen access."
          ],
          "implementationNotes": [
            "Test the repository directly as well as the route; controller-only filtering is insufficient."
          ],
          "verification": [
            "List two pages of the authorized partner's orders with correct counts.",
            "Replace partnerId and replay a foreign cursor; assert no foreign records or totals."
          ],
          "deliverables": [
            "Tenant-scoped list boundary and denial regressions"
          ],
          "rollout": "Switch the synthetic partner list first; disable the route if scoped count comparisons fail.",
          "skills": [
            "Tenant isolation",
            "Repository boundaries",
            "Pagination security"
          ],
          "fieldMix": [
            {
              "field": "Security",
              "percentage": 60
            },
            {
              "field": "Backend",
              "percentage": 40
            }
          ],
          "patterns": []
        },
        {
          "id": "a2e46e44-4b53-4b0b-88c8-1de3b29465e7",
          "key": "ACCESS-103",
          "title": "Make bulk order updates all-or-nothing across authorization checks",
          "type": "BUG",
          "priority": "HIGH",
          "difficulty": "ADVANCED",
          "estimateMinutes": 180,
          "phaseId": "enforce",
          "dependsOn": [
            "ACCESS-101",
            "ACCESS-102"
          ],
          "scenario": "A batch update contains nine permitted order IDs and one foreign ID. The service updates the first nine before discovering the forbidden order and returning 403.",
          "acceptanceCriteria": [
            "Authorize the complete bounded target set before committing changes.",
            "A missing, duplicate, or foreign order prevents the entire batch update.",
            "Record no success audit when the transaction is rejected."
          ],
          "implementationNotes": [
            "Do not reveal which foreign order exists through different error shapes."
          ],
          "verification": [
            "Update a fully authorized synthetic batch atomically.",
            "Mix permitted and forbidden IDs and inject a final-write failure; verify unchanged records."
          ],
          "deliverables": [
            "Atomic bulk command and mixed-scope reproduction"
          ],
          "rollout": "Enable a bounded batch size on the synthetic client; revert batch routing without relaxing individual authorization.",
          "skills": [
            "Batch authorization",
            "Transactions",
            "Information disclosure"
          ],
          "fieldMix": [
            {
              "field": "Security",
              "percentage": 50
            },
            {
              "field": "Database engineering",
              "percentage": 30
            },
            {
              "field": "Backend",
              "percentage": 20
            }
          ],
          "patterns": []
        },
        {
          "id": "5d6b5439-cd65-427c-acb3-343f39b60d6a",
          "key": "ACCESS-104",
          "title": "Stop a document lookup from revealing foreign filenames",
          "type": "BUG",
          "priority": "HIGH",
          "difficulty": "FOUNDATIONAL",
          "estimateMinutes": 90,
          "phaseId": "enforce",
          "dependsOn": [
            "ACCESS-102"
          ],
          "scenario": "A foreign document request returns Forbidden: invoice-acme-september.pdf. Download is blocked, but the error itself leaks the other partner's filename.",
          "acceptanceCriteria": [
            "Use the same public not-found response for missing and out-of-scope documents.",
            "Do not include filename, owner, storage key, or existence hints in denied responses.",
            "Keep an internal denial reason behind authorized audit access."
          ],
          "implementationNotes": [
            "Apply the projection to metadata and download-link routes, not just the file response."
          ],
          "verification": [
            "Retrieve an authorized filename normally.",
            "Compare missing and foreign-document response bodies and inspect sanitized logs."
          ],
          "deliverables": [
            "Document denial projection and metadata-leak regression"
          ],
          "rollout": "Replace external error text immediately in the fixture routes; keep internal reason codes for diagnosis.",
          "skills": [
            "Error hygiene",
            "Information disclosure",
            "Read authorization"
          ],
          "fieldMix": [
            {
              "field": "Security",
              "percentage": 70
            },
            {
              "field": "API design",
              "percentage": 30
            }
          ],
          "patterns": []
        },
        {
          "id": "658fe6b7-55ac-4d8b-a2e9-0cbaf7707c1b",
          "key": "ACCESS-105",
          "title": "Consume partner invitations once under concurrent acceptance",
          "type": "TASK",
          "priority": "HIGH",
          "difficulty": "ADVANCED",
          "estimateMinutes": 210,
          "phaseId": "enforce",
          "dependsOn": [
            "ACCESS-101"
          ],
          "scenario": "Two acceptance requests use the same invitation token at nearly the same time. The portal creates duplicate memberships and occasionally accepts a token after its role was revoked.",
          "acceptanceCriteria": [
            "Bind an invitation to partner, intended recipient, role, expiry, and current invitation state.",
            "Consume it and create membership in one transaction.",
            "Concurrent acceptance yields one membership with a documented replay or conflict response."
          ],
          "implementationNotes": [
            "Persist token hashes only; synthetic invitation secrets must not enter logs."
          ],
          "verification": [
            "Accept a valid invitation once and inspect its membership.",
            "Race acceptance and try expired, revoked, and wrong-recipient tokens; assert denial or one winner."
          ],
          "deliverables": [
            "Invitation consumption command and concurrency cases"
          ],
          "rollout": "Issue the new token format for future synthetic invitations; retain a documented expiry window for legacy links.",
          "skills": [
            "Single-use tokens",
            "Atomic authorization",
            "Identity binding"
          ],
          "fieldMix": [
            {
              "field": "Security",
              "percentage": 50
            },
            {
              "field": "Database engineering",
              "percentage": 30
            },
            {
              "field": "Backend",
              "percentage": 20
            }
          ],
          "patterns": []
        },
        {
          "id": "9c66c4a3-e81d-491c-8da7-bad5c9858ee2",
          "key": "ACCESS-106",
          "title": "Narrow integration keys to the permissions they were issued",
          "type": "STORY",
          "priority": "HIGH",
          "difficulty": "INTERMEDIATE",
          "estimateMinutes": 150,
          "phaseId": "enforce",
          "dependsOn": [
            "ACCESS-101",
            "ACCESS-102"
          ],
          "scenario": "A partner creates a read-only reporting key while an administrator is logged in. Requests using that key inherit the administrator's full session permissions.",
          "acceptanceCriteria": [
            "Evaluate key scopes separately from browser-session roles.",
            "Restrict effective permissions to both key scope and current partner authority.",
            "Support key revocation without disabling unrelated partner sessions."
          ],
          "implementationNotes": [
            "A key cannot grant a permission its issuer was not authorized to delegate."
          ],
          "verification": [
            "Read permitted reports through a scoped synthetic key.",
            "Attempt order edits, cross-partner reads, and use after revocation; assert denial."
          ],
          "deliverables": [
            "Integration-key authorization policy and scope tests"
          ],
          "rollout": "Create scoped test keys alongside existing clients; remove broad key support after explicit client migration.",
          "skills": [
            "API key scopes",
            "Least privilege",
            "Credential revocation"
          ],
          "fieldMix": [
            {
              "field": "Security",
              "percentage": 80
            },
            {
              "field": "Backend",
              "percentage": 20
            }
          ],
          "patterns": []
        },
        {
          "id": "2cd47283-b943-4cb6-9d2a-d041a0f6856b",
          "key": "ACCESS-107",
          "title": "Recheck export authority when a background job completes",
          "type": "BUG",
          "priority": "HIGH",
          "difficulty": "EXPERT",
          "estimateMinutes": 300,
          "phaseId": "assure",
          "dependsOn": [
            "ACCESS-103",
            "ACCESS-104"
          ],
          "scenario": "An administrator starts a large partner export and loses membership while it runs. The worker later emails a long-lived storage URL using authorization captured only at request time.",
          "acceptanceCriteria": [
            "Record immutable request scope without treating it as permanent authorization.",
            "Reauthorize delivery and each download against current membership and export scope.",
            "Revoked users cannot receive or use new access grants; completed artifacts remain tenant-restricted."
          ],
          "implementationNotes": [
            "Use an authenticated download boundary or similarly revocable design; do not send real email in this exercise."
          ],
          "verification": [
            "Complete and download a permitted synthetic export.",
            "Revoke access between request, completion, and download; verify denial at each relevant boundary."
          ],
          "deliverables": [
            "Export authorization lifecycle and revocation timing tests"
          ],
          "rollout": "Route synthetic exports through the new download boundary; expire legacy links before removing their compatibility path.",
          "skills": [
            "Asynchronous authorization",
            "Revocation",
            "Artifact access",
            "Time-of-check races"
          ],
          "fieldMix": [
            {
              "field": "Security",
              "percentage": 60
            },
            {
              "field": "Distributed systems",
              "percentage": 20
            },
            {
              "field": "Storage systems",
              "percentage": 20
            }
          ],
          "patterns": []
        },
        {
          "id": "055312f5-1ff9-417c-83a3-c680b320ec4f",
          "key": "ACCESS-108",
          "title": "Invalidate cached permissions after a role downgrade",
          "type": "BUG",
          "priority": "HIGH",
          "difficulty": "ADVANCED",
          "estimateMinutes": 210,
          "phaseId": "assure",
          "dependsOn": [
            "ACCESS-102",
            "ACCESS-106"
          ],
          "scenario": "A partner administrator is downgraded to read-only but retains edit access on one API instance for an hour because permission caches are local and keyed only by user ID.",
          "acceptanceCriteria": [
            "Scope cached decisions by actor, partner, and authority revision.",
            "Enforce a documented maximum revocation delay with an authoritative fallback.",
            "Fail closed for privileged writes when current authority cannot be established."
          ],
          "implementationNotes": [
            "User ID alone is insufficient when the same user belongs to multiple partners."
          ],
          "verification": [
            "Downgrade a role across two synthetic API instances and measure the revocation delay.",
            "Simulate cache invalidation loss and authority-store failure; assert privileged writes cannot persist indefinitely."
          ],
          "deliverables": [
            "Permission cache policy and revocation-latency reproduction"
          ],
          "rollout": "Shorten cache lifetime before enabling revisioned entries; bypass the cache if revision propagation fails.",
          "skills": [
            "Authorization caching",
            "Consistency",
            "Fail-closed behavior"
          ],
          "fieldMix": [
            {
              "field": "Security",
              "percentage": 60
            },
            {
              "field": "Distributed systems",
              "percentage": 40
            }
          ],
          "patterns": []
        },
        {
          "id": "35edffbc-ea3b-4211-a788-538dc64e8a91",
          "key": "ACCESS-109",
          "title": "Record useful access-denial audits without copying documents",
          "type": "CHORE",
          "priority": "MEDIUM",
          "difficulty": "FOUNDATIONAL",
          "estimateMinutes": 120,
          "phaseId": "assure",
          "dependsOn": [
            "ACCESS-104",
            "ACCESS-106"
          ],
          "scenario": "Security receives Denied entries with no action name, while application debug logs include complete document metadata. Neither source is suitable for reviewing a partner complaint.",
          "acceptanceCriteria": [
            "Record actor reference, scoped action, tenant reference, reason code, and correlation ID.",
            "Exclude document text, credential values, request bodies, and raw download URLs.",
            "Protect audit queries with a dedicated read permission and bounded date range."
          ],
          "implementationNotes": [
            "Security audit records are append-only; generic analytics must not receive sensitive audit detail."
          ],
          "verification": [
            "Follow an authorized denial investigation through its correlation ID.",
            "Attempt audit access as a partner agent and scan secret-like fixture values for absence."
          ],
          "deliverables": [
            "Minimized audit schema and investigation example"
          ],
          "rollout": "Mirror fixture denials into the new sink and inspect redaction before enabling operator queries.",
          "skills": [
            "Security logging",
            "Data minimization",
            "Audit access"
          ],
          "fieldMix": [
            {
              "field": "Security",
              "percentage": 60
            },
            {
              "field": "Privacy engineering",
              "percentage": 40
            }
          ],
          "patterns": []
        },
        {
          "id": "7b78b18d-f3a7-4f74-a630-2749ab82b6a2",
          "key": "ACCESS-110",
          "title": "Serialize membership removal with privileged order approval",
          "type": "TASK",
          "priority": "HIGH",
          "difficulty": "EXPERT",
          "estimateMinutes": 300,
          "phaseId": "assure",
          "dependsOn": [
            "ACCESS-103",
            "ACCESS-108",
            "ACCESS-109"
          ],
          "scenario": "A buyer is removed from a partner while their order-approval request is between authorization and commit. The current implementation commits the approval after removal without a defined policy.",
          "acceptanceCriteria": [
            "Declare the transactional ordering rule for removal and approval.",
            "Reconcile authority revision within the privileged write transaction so one ordering is enforced.",
            "Keep successful approvals and rejected stale authority attempts separately auditable."
          ],
          "implementationNotes": [
            "A second controller check does not close the race; prove the service/database boundary controls it."
          ],
          "verification": [
            "Commit approval before removal under the declared ordering and inspect attribution.",
            "Commit removal first while approval is paused and assert no unauthorized order mutation."
          ],
          "deliverables": [
            "Authority/write serialization design and deterministic interleaving tests"
          ],
          "rollout": "Apply the rule to order approvals before broader privileged writes; retain audit records through application rollback.",
          "skills": [
            "Transactional authorization",
            "Concurrency",
            "Revocation semantics",
            "Audit integrity"
          ],
          "fieldMix": [
            {
              "field": "Security",
              "percentage": 50
            },
            {
              "field": "Database engineering",
              "percentage": 50
            }
          ],
          "patterns": []
        }
      ]
    }
  ]
}
