# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Package Overview

Shared Pydantic schemas defining the Lambda event/response contracts for the offboarding automation. This is a **library, not a deployed Lambda** — there is no handler and nothing is invoked directly. The models here are imported by the CLI Lambdas (`auth0_cli`, `github_cli`, `jira_cli`, `dynamo_cli`) to type their event inputs and response outputs. (`db_cli` exists but does not currently import from this package.)

`software-catalog.yaml` at the package root registers it in the internal software catalog as `lambda-offboarding-automation-shared` (kind: `library`, owner: `devops`).

## Package layout

`shared` is a namespace package (no root `__init__.py`); all code lives under `schemas/`.

- `schemas/__init__.py` — public export surface; re-exports models from each domain module and lists them in `__all__`.
- `schemas/common.py` — cross-domain types reused by multiple schemas.
- `schemas/jira.py` — jira_cli event/response contracts.
- `schemas/auth0.py` — auth0_cli event/response contracts.
- `schemas/github.py` — github_cli event/response contracts.
- `schemas/dynamo.py` — dynamo_cli event/response contracts (Phase 1 snapshot storage).

## Modules

### `common.py`
Shared types reused across multiple Lambda schemas.

- `TerraformHit` — a Terraform file reference found during a GitHub org search (`path`, `html_url`, `repository`, `confidence: Literal['high','low'] = 'high'`). `confidence` reflects search-term reliability: `high` for precise identifiers (full email / local-part), `low` for the derived initial+lastname form.
- `Auth0ActionResult` — per-tenant outcome of a Phase 2 Auth0 write (`tenant`, `user_id`, `operation: Literal['deleted','blocked']`), where `deleted` is the offboarding flow and `blocked` is the suspension flow.

### `jira.py`
- `Ticket` — a parsed offboarding or suspension ticket (`id`, `email`, `full_name`, `last_working_day`). `last_working_day` carries the effective date across both ticket types (last day of employment for offboarding, suspension start date for suspension) so downstream consumers stay uniform.
- `CompletedTicket` — a ticket eligible for closing, carrying only `id` (deliberately lighter than `Ticket` so closing does not depend on description parsing).
- Query events/responses (each event carries `action` + `dry_run: bool = False`; each response wraps `tickets`):
  - `QueryOffboardingTicketsEvent` / `QueryOffboardingTicketsResponse` (`list[Ticket]`)
  - `QueryApprovedTicketsEvent` / `QueryApprovedTicketsResponse` (`list[Ticket]`)
  - `QuerySuspendTicketsEvent` / `QuerySuspendTicketsResponse` (`list[Ticket]`)
  - `QueryApprovedSuspendTicketsEvent` / `QueryApprovedSuspendTicketsResponse` (`list[Ticket]`)
  - `QueryCompletedTicketsEvent` / `QueryCompletedTicketsResponse` (`list[CompletedTicket]`)
- Write events/responses:
  - `ValidateTicketEvent` / `ValidateTicketResponse` (response: `valid: bool`, `warnings: list[str]`)
  - `AddCommentEvent` / `AddCommentResponse` (`commented: bool`)
  - `AddDueDateEvent` / `AddDueDateResponse` — event `due_date` is regex-validated `^\d{4}-\d{2}-\d{2}$`; response `due_date_set: bool`
  - `AddLabelEvent` / `AddLabelResponse` (`label_added: bool`)
  - `CloseTicketEvent` / `CloseTicketResponse` — event has `target_status: str = 'Closed'`; response `closed: bool`

### `auth0.py`
- `Auth0Match` — a single Auth0 user match across tenants (`tenant`, `user_id`). **Not in `__all__`** but imported directly (e.g. `dynamo.py`, consumers via `shared.schemas.auth0`).
- `SearchUserByEmailEvent` / `SearchUserByEmailResponse` — search all tenants by email; response carries `matches: list[Auth0Match]`.
- `DeleteUserByIdEvent` / `DeleteUserByIdResponse` — single-tenant hard delete; response `deleted: bool`.
- `SuspendUserByIdEvent` / `SuspendUserByIdResponse` — single-tenant block; response `suspended: bool`.

### `github.py`
- `SearchTextInOrgEvent` / `SearchTextInOrgResponse` — search a GitHub org for a user's traces; response carries `terraform_hits: list[TerraformHit]`. Event validates `email` and `full_name` as `min_length=1`, with optional `repo_filter: str | None`.
- `OffboardUserWithCopilotEvent` / `OffboardUserWithCopilotResponse` — open Copilot-driven offboarding issues; event carries `operation: Literal['offboard','suspend'] = 'offboard'`, plus `auth0_results: list[Auth0ActionResult]` and `terraform_hits: list[TerraformHit]`. Response returns `issue_urls: list[str]` and `issue_numbers: list[int]`.

### `dynamo.py`
- `Snapshot` — frozen Phase 1 discovery results for one ticket (`ticket_id`, `email`, `full_name`, `last_working_day`, `auth0_matches: list[Auth0Match]`, `terraform_hits: list[TerraformHit]`, `auth0_action: Literal['delete','suspend'] = 'delete'`, `checked_at`). `auth0_action` defaults to `delete` so pre-existing snapshots remain valid.
- `PutSnapshotEvent` / `PutSnapshotResponse` — store a snapshot (response `stored: bool`).
- `GetSnapshotEvent` / `GetSnapshotResponse` — retrieve a snapshot (response `found: bool`, `snapshot: Snapshot | None`).
- `DeleteSnapshotEvent` / `DeleteSnapshotResponse` — delete a snapshot after Phase 2 (response `deleted: bool`).

## Public export surface

`schemas/__init__.py` re-exports the public models; `__all__` is the source of truth for what `from shared.schemas import <Name>` provides. Consumers import either from the package root or from a specific module:

```python
from shared.schemas import Ticket, PutSnapshotEvent, TerraformHit, Auth0ActionResult
from shared.schemas.jira import Ticket
from shared.schemas.auth0 import Auth0Match      # not re-exported at package root
from shared.schemas.common import Auth0ActionResult, TerraformHit
from shared.schemas.dynamo import Snapshot
```

`__all__` contains: `DeleteSnapshotEvent`, `DeleteSnapshotResponse`, `GetSnapshotEvent`, `GetSnapshotResponse`, `PutSnapshotEvent`, `PutSnapshotResponse`, `Snapshot`, `AddCommentEvent`, `AddCommentResponse`, `AddDueDateEvent`, `AddDueDateResponse`, `AddLabelEvent`, `AddLabelResponse`, `Auth0ActionResult`, `CloseTicketEvent`, `CloseTicketResponse`, `CompletedTicket`, `DeleteUserByIdEvent`, `DeleteUserByIdResponse`, `OffboardUserWithCopilotEvent`, `OffboardUserWithCopilotResponse`, `QueryApprovedSuspendTicketsEvent`, `QueryApprovedSuspendTicketsResponse`, `QueryApprovedTicketsEvent`, `QueryApprovedTicketsResponse`, `QueryCompletedTicketsEvent`, `QueryCompletedTicketsResponse`, `QueryOffboardingTicketsEvent`, `QueryOffboardingTicketsResponse`, `QuerySuspendTicketsEvent`, `QuerySuspendTicketsResponse`, `SearchTextInOrgEvent`, `SearchTextInOrgResponse`, `SearchUserByEmailEvent`, `SearchUserByEmailResponse`, `SuspendUserByIdEvent`, `SuspendUserByIdResponse`, `TerraformHit`, `Ticket`, `ValidateTicketEvent`, `ValidateTicketResponse`.

## Conventions

- **Event/response pairing** — every action has a matching `<Action>Event` and `<Action>Response` model; the event's `action` field is a `Literal` matching the action name.
- **`dry_run`** — every write event carries `dry_run: bool = False`, and its response echoes `dry_run: bool`. Read-only query/search/get events also carry `dry_run` where present, but the get-snapshot and search-user-by-email events do not.
- **`ticket_id` threading** — auth0 and github events/responses carry `ticket_id` (often defaulting to `''`) to correlate work back to the originating Jira ticket.
- **`last_working_day` shared field** — the same field name is used on `Ticket`, `Snapshot`, and `OffboardUserWithCopilotEvent` to keep the effective-date semantics uniform across offboarding and suspension flows.
- **Backward-compatible defaults** — fields added after first release (`Snapshot.auth0_action`, `TerraformHit.confidence`) default to preserve validity of previously written data.

## Tests / dev commands

No `pyproject.toml`, `Makefile`, or test suite lives in this package — it is imported source only. Tests that exercise these schemas live with each consuming Lambda (e.g. `dynamo_cli/tests/unit/test_dynamo_client.py`).
