# Claude guidelines for this repo

## Spike doc style

These docs are read by Permissions Platform engineers, external engineers that are unfamiliar with the system, and
VP-level stakeholders skimming for the key decision. Write for all audiences.

### Prose

- Keep paragraphs to 3–4 sentences maximum.
- After every paragraph of explanation, add a visual break: a diagram placeholder, a code block, or a bullet list.
- Prefer bullet lists over dense prose for enumerating properties, steps, or trade-offs.
- Use bold sparingly — only for the most important terms introduced in a section.
- Limit Pros and Cons the 3 most important pros and cons. Ask if you are not sure.
- Do not write a recommendation section. That's for the team to discuss.

### Diagram placeholders

When prose would benefit from a diagram, insert a placeholder comment:

```markdown
<!-- DIAGRAM: <diagram-filename> — one-line description of what it shows -->
```

Then create the diagram file in the ticket's `diagrams/` directory.

### Diagrams (Mermaid / Whimsical)

- Use **kebab-case** filenames (e.g. `option1-request.mmd`).
- **6 participants maximum** per sequence diagram. Split into multiple diagrams rather than cramming more in.
- Do **not** put line breaks (`<br/>`) inside edge labels — edit those manually in Whimsical if needed.
- Do **not** write `participant Foo as Foo` — omit the `as` alias when it would be identical to the identifier.
- Prefer `flowchart TD` for init/startup flows (CI → cache → service), and `sequenceDiagram` for request-time interactions.
- Name diagrams to reflect their scope, e.g. `option1-init.mmd`, `option1-request.mmd`, `option2-write-path.mmd`, `option2-request.mmd`.

### Code blocks

Use short, representative snippets — not full implementations. Prefer:

- JSON blocks for data shapes (DynamoDB rows, API responses)
- Python pseudocode for logic flows
- Cypher blocks for Neo4j queries
- Shell/HTTP for endpoint signatures

### Comparison tables

Always include a comparison table when presenting multiple options. Keep columns to the most decision-relevant dimensions (latency, complexity, consistency, known risks).

### Open questions

List open questions at the bottom, numbered. Strike through resolved ones with the resolution inline:

```markdown
1. ~~**Question**~~ — **Resolved**: answer here.
```

---

## Ticket doc style

Ticket docs break a spike option into individually shippable units of work. Each ticket targets one service and one layer.

### Ticket page structure

A ticket page contains, in order:

1. A **Ticket Summary table** — one row per ticket with columns: Ticket, Title, Service, Blocked by.
2. A **Dependency Graph** — ASCII art showing which tickets block which.
3. **Individual ticket sections** — one `###` heading per ticket, in dependency order.

### Jira Ticket Number Placeholder

At first, use placeholders for the jira ticket number (`PP-XXXX`). `PP` is the Jira project name
used by our team `Permissions Platform`.

### Creating Jira tickets from a ticket doc

Use the `/create-jira-tickets` slash command to create Jira tasks from a finished `tickets.md`:

```
/create-jira-tickets [path/to/tickets.md] --epic <PP-NNNN>
```

- Create the Jira epic manually first, then pass its key via `--epic`.
- The command creates one `Task` per ticket section, nested under the epic, in dependency order.
- Only **Summary + Acceptance Criteria** go into Jira. Implementation Details stay in the markdown doc.
- After creation, the command offers to replace `PP-XXXX` placeholders in `tickets.md` with real keys and wire up "is blocked by" links between tasks.
- `acli` must be authenticated: `acli jira auth login --site "https://theorchard.atlassian.net/" --email <you> --token <token>`

### Ticket section structure

Each ticket section must have these parts, in this order:

```markdown
### Ticket PP-XXXX (NN) — <Service>: <Short Title>

**Service**: <service-name> | **Blocked by**: <ticket ref or —> | **Blocks**: <ticket ref or —>

#### Summary
(2–3 sentences max. What is added, which file, and why.)

#### Acceptance Criteria
- Bulleted list of verifiable outcomes.
- Include unit test cases inline here.

#### Implementation Details
(File path(s) as bold headers, followed by short representative code snippets.)

#### Notes  ← optional
- Edge cases, deferred scope, or cross-ticket dependencies worth calling out.
```

### Scoping rules

- One ticket = one service + one layer (model, logic, handler, etc.). Do not mix layers.
- The Handler tickets should include an integration test in the acceptance criteria.
- Keep each ticket independently reviewable. A reviewer should be able to understand it without reading siblings.
- Mark deferred scope explicitly: *"X is intentionally excluded and deferred to PP-NNNN."*

### Code snippets in tickets

- Show the function/class signature and a representative body — not a full working implementation.
- Use `# comment` lines to summarize logic that doesn't need to be spelled out.
- For new files, include the full import block so the implementer doesn't have to guess.
- For modified files, show only the added/changed lines with enough context to locate them.

### Acceptance criteria style

- Write each criterion as a single falsifiable statement (starts with a noun or verb, not "should").
- Unit test cases belong in acceptance criteria, not in implementation details.
- Integration test cases with QA pre-requisites belong in their own `#### Integration Tests` sub-section under Implementation Details.

### Dependency graph

Use indented ASCII art with `└──` / `├──` connectors. Show independent roots at the top. Call out the final integration step and any independently-shippable side branches explicitly below the graph.

---

## Python microservices conventions

### Layering

Handlers must be thin — one call to the logic layer, return the result. No business logic in handlers.

```python
@app.route('/lookup/profiles/identity/uuids/', methods=['POST'])
@validate_request_data(SomeSchema())
def my_handler(deserialize_schema):
    """NOTE: No access rule checks — PIP endpoint for Permissions Platform."""
    return flaskify(logic_module.my_function(deserialize_schema['uuids']))
```

### Request validation

Use marshmallow schemas in `permissions/validation/schemas/lookup.py`:

```python
from marshmallow import Schema, fields

class LookupSomethingByUuids(Schema):
    uuids = fields.List(fields.UUID(required=True), required=True)
```

### Batch / dataloader pattern

Batch lookup endpoints return results as an **ordered list matching the input UUID order**. The logic layer is responsible for ordering; the model layer returns raw results.

```python
def lookup_by_uuids(uuids: list[str]) -> dict:
    rows = models.thing.get_by_uuids(uuids, session)
    by_uuid = {r["uuid"]: r for r in rows}
    return {"items": [by_uuid[u] for u in uuids if u in by_uuid]}
```

Unmatched UUIDs are omitted (callers treat missing entries as a no-op).
