{
"command": "pwd"
}/Users/warrenthompson/Source/LoginID/04-infra-devops
{
"file_path": "/Users/warrenthompson/Source/LoginID/CLAUDE.md"
}1 # LoginID Take-Home POC — Project Root
2
3 ## Role
4
5 You are the project manager agent for this repository. This file is the shared context every track refers back to. Each of the five track directories below has its own CLAUDE.md that can be worked independently by a scoped subagent, but that subagent should read this root file first for the assignment, the overall narrative, and how its track fits with the other four.
6
7 When you spin up agents or subagents to do work in a track, point them at that track's CLAUDE.md as their brief, and tell them this root file exists for cross-track context. Don't let a track's agent silently duplicate scope another track owns — check the "Owns / does not own" boundaries below first.
8
9 ## What this repository is
10
11 This is a take-home assignment for a LoginID interview, submitted as a full directory. The goal is not just to answer three coding questions — it's to demonstrate capability as an AI architect: how you scope a problem, use AI tooling deliberately, reason about security and data, and produce something that reads as an intentional, well-run engineering effort rather than a quick script.
12
13 Code does not need to work or be fully complete. LoginID has said explicitly they care more about the design and the reasoning behind it, and about which AI tools/frameworks were used and how. Every track should document its reasoning, not just its output.
14
15 ## The assignment (verbatim from LoginID)
16
17 Provide code solutions for the following questions in Go (preferably) or other languages — describe any tool, framework, or AI used.
18
19 1. Code of a database DAO that can store and retrieve `user_profile` (name, address, phone) and `user_credential` (username, method, password), with support for multiple databases (PostgreSQL or CockroachDB, SQLite, etc).
20 2. Design a RESTful API web service to search and retrieve the above user profile data, plus security API authentication.
21 3. Design a service connector to third-party identity providers (referred to generically as ABC or XYZ) to retrieve personal data from vendors, with the following endpoints:
22 - `/auth` — retrieves an `access_token` based on a POST JSON body `{"username": "<string>", "password": "<string>"}`
23 - `/identity` — retrieves user PII (name, phone, address: `street_address`, `locality`, `region`, `postal_code`, `country`) based on a POST JSON body `{"phone": "<string>", "name": "<string>"}`
24
25 Go is the preferred language. Other languages are acceptable if justified.
26
27 ## Track structure
28
29 Five directories, each independently workable, each owning a distinct slice of the problem:
30
31 | Directory | Track | Owns |
32 |---|---|---|
33 | `01-product-industry-research-design/` | Product & Industry Research (incl. Design) | Market/industry framing (IAM, passwordless auth, IDP aggregation space), personas and use cases, API and data contract design rationale, UX of the surfaces that would sit on top of this |
34 | `02-ai-security-architecture/` | AI Architecture & Security Architecture | The AI tooling/workflow used to produce this submission, threat modeling, auth/authz design for the API, credential and PII handling, secrets management, third-party connector security |
35 | `03-engineering-delivery/` | Engineering & Delivery | The actual Go code for all three questions, project structure, test strategy, delivery documentation |
36 | `04-infra-devops/` | Infra & DevOps | How this would be built, containerized, deployed, and operated — CI/CD, environments, multi-database configuration, observability |
37 | `05-data-ops/` | Data Ops | Data modeling for `user_profile` / `user_credential`, multi-database abstraction strategy, migrations, PII data governance and retention |
38
39 Directories are numbered for reading order in the final submission, not for sequencing of work — tracks can be worked in any order or in parallel.
40
41 ## Working principles for every track
42
43 - State your reasoning, not just your conclusion. LoginID is grading the "why" as much as the "what."
44 - Name the AI tool/workflow you used for that track's output (this project is being built with Claude Code; say so, and say how — e.g., subagents scoped per track, a given skill, a given model).
45 - Keep each track's CLAUDE.md self-contained enough that a fresh agent with no other context can pick it up and do correct work from it alone, plus this root file.
46 - Cross-reference other tracks by relative path (e.g., `../05-data-ops/CLAUDE.md`) rather than restating their content.
47 - Do not write implementation code until Warren gives an explicit, project-wide go-ahead. Design docs, schemas described in prose, plans, and decision records are fine; `.go`, `.sql`, `go.mod`, Dockerfiles, and CI config are not.
48
49 ## Org, hiring, and orchestration
50
51 The project is run as an org: this PM agent (Dana Whitfield) orchestrates five standing track-lead agents, and each lead hires a small sub-team of specialist agents with deliberately varied personalities. **Each track lead runs as its own top-level Claude Code session**, launched from its track directory in its own tmux window — so it loads its own track `CLAUDE.md` and persona natively, owns and spawns its hires as named teammates in that window, and talks to the PM session through cross-session messaging. Hires belong to their lead's session, not the PM's. The governing files, all at the root:
52
53 - `PLAN.md` — the approved org plan for the planning phase (phases, gates, verification). `~/.claude/plans/` is scratch; this is the record.
54 - `PLANNING.md` — the thin cross-track board: one row per track, one writer per row, plus the service-boundaries section 03 owns and 04 reads.
55 - `CASTING.md` — archetype palette, org-wide variance rules, hire sizing, the persona-writing standard, the orchestration-pattern catalog (adversarial pair, red/blue, three hats, rotating devil's advocate, structured written debate, newcomer's question), the deep-research rule, the hire-creation procedure, and the registry of every hire.
56 - `LOG.md` — dated, append-only journal of what happened and why (team-lead writes it; it is part of the submission).
57 - `.claude/agents/` — one agent definition per hire (model pinned; a once-only per-agent `PreToolUse` hook displays its own persona on its first tool call). `.claude/AGENT_TEMPLATE.md` is the starting point (kept outside the agents directory so it is not registered as an agent type itself).
58 - `02-ai-security-architecture/planning-approach.md` — the agreed Claude-architecture approach: model/effort by role, coordination rules, hand-off standard, dependency order.
59
60 Rules that hold across every track:
61 - Every agent — lead or hire — has a profile-gen persona (name, temperament, professional history, technical background, stated blind spot). Hires are created text-first with `--output file`; portraits arrive later from `scripts/portrait-queue.sh`. Hires are **never** added as `profile-gen` markers in any `CLAUDE.md` — only the six markers that exist today.
62 - Hires do not spawn subagents. Only track leads (and team-lead) authorize or run deep research (patents, whitepapers, standards, primary sources); hires request it from their lead.
63 - Tracks never modify root-level state (this file, the root persona, root config); flag it to team-lead instead.
64 - Hand-offs are files by relative path, written so the receiver can act without re-deriving the sender's reasoning; chat messages signal readiness, they do not carry content.
65 - Edit `CLAUDE.md` files at turn boundaries only (prompt-cache stability). Milestone `git` commits at the end of each phase; no mid-task commits.
66
67 ## Status
68
69 Design phase complete for all five tracks (see each track's `CLAUDE.md` Status). All implementation code deliberately wiped 2026-09-12; the project is in the **planning phase** described in `PLAN.md`: Phase 0 scaffolding done, Phase 1 (leads plan in plan mode and propose hires) next, then hiring, in-team debates, a cross-track consistency pass, and Jira stories in project `LT`. No code until Warren's explicit go-ahead.
70
71 <!-- profile-gen:start slug=dana-whitfield -->
72 @profiles/dana-whitfield/dana-whitfield.md
73 <!-- profile-gen:end slug=dana-whitfield -->
74 {
"file_path": "/Users/warrenthompson/Source/LoginID/04-infra-devops/CLAUDE.md"
}1 # Track: Infra & DevOps 2 3 Refer back to `../CLAUDE.md` for the full assignment text and how this track fits with the other four. 4 5 ## Mission 6 7 Design how the services from the engineering track would actually be built, packaged, deployed, and operated. This is a design/documentation track for a take-home — it does not stand up real infrastructure, but it should be concrete enough that it clearly could. 8 9 ## Owns 10 11 - Build and containerization approach for the DAO-backed API service and the IDP connector service (e.g., Dockerfiles, multi-stage Go builds). 12 - Environment/configuration strategy for switching the DAO between PostgreSQL/CockroachDB and SQLite (env vars, config files, or a database driver selection mechanism) — implements the storage-agnostic contract that `../05-data-ops/CLAUDE.md` designs. 13 - CI/CD pipeline design: what would run on a commit/PR (build, lint, test, security scan) for a project like this. 14 - Secrets injection mechanics at deploy time for whatever `../02-ai-security-architecture/CLAUDE.md` specifies needs to be a secret (DB credentials, third-party IDP credentials, signing keys) — this track implements the delivery mechanism, security architecture sets the requirement. 15 - Observability approach: logging, metrics, and tracing considerations for the API and the connector, especially around what must never be logged (raw passwords, tokens, PII) per the security track's guidance. 16 - Local dev-loop story: how a developer would run this stack locally (e.g., docker-compose with a local Postgres and the services). 17 18 ## Does not own 19 20 - Application code itself — that's `../03-engineering-delivery/CLAUDE.md`. 21 - What counts as a secret and why, or the auth/threat model — that's `../02-ai-security-architecture/CLAUDE.md`; this track only implements the delivery of it. 22 - Database schema/migration content — that's `../05-data-ops/CLAUDE.md`; this track only covers how migrations would run in a pipeline, not their content. 23 24 ## Deliverables 25 26 - Containerization design (Dockerfile sketch or description) for both services. 27 - A CI/CD pipeline description or config sketch. 28 - A local dev-loop description (e.g., docker-compose sketch). 29 - A short note on observability and what must be excluded from logs. 30 31 ## AI tooling note 32 33 Record which AI tool/workflow was used to produce this track's design output and how. 34 35 ## Status 36 37 No infra work has started. Do not begin until the engineering track's service boundaries are defined enough to containerize and deploy against. 38 39 <!-- profile-gen:start slug=theo-bergman --> 40 @profiles/theo-bergman/theo-bergman.md 41 <!-- profile-gen:end slug=theo-bergman --> 42
{
"file_path": "/Users/warrenthompson/Source/LoginID/04-infra-devops/PLAN.md"
}1 # Track 04 (Infra & DevOps) — Phase 1 Planning Plan
2
3 ## Context
4
5 This is the Phase 1 planning pass for track 04, per team-lead's brief and the org-wide `PLAN.md` / `PLANNING.md` / `CASTING.md` / `02-ai-security-architecture/planning-approach.md` scaffolding. No code or config artifacts yet — this file is a plan only. Track 04 is deliberately last in the dependency order: it designs how the engineering track's services get built, deployed, and operated, which only makes sense once those services have a shape. Nothing below gets executed until team-lead replies "hiring go" (for the roster) and Warren gives the separate implementation go-ahead (for any code/config).
6
7 ## Hiring status (post "hiring go")
8
9 Wesley Okonkwo, Bree Sandoval, and Callum Ferreira are live, standing named teammates (spawned by team-lead per the org-wide resolution: leads do `CASTING.md` §6 steps 1–4, team-lead spawns — see `CASTING.md` §6 step 5, rewritten).
10
11 ## Deployment target note (added post-approval, Warren's direct input)
12
13 There is a real Kubernetes dev/lab cluster on Warren's LAN, operated day-to-day by an OpenClaw agent ("amber-kubernetes") running on a Mac mini reachable via SSH — this is not a hypothetical target, it's a specific, addressable cluster with an operator who already knows its conventions. Warren's explicit call: this stays a **design-only reference** for the submission. The deliverables below should name this cluster as the concrete deployment target (so the design reads as intentional rather than generic "some Kubernetes cluster somewhere"), including describing amber-kubernetes' role as the operational actor who'd apply manifests and know the cluster's existing namespace/ingress conventions — but nothing gets actually deployed, and amber-kubernetes is not contacted, during this take-home. If a real demo deployment is ever wanted, that is a separate, later decision Warren would make explicitly — not implied by this note.
14
15 ## 1. Deliverables & tasks (one Jira Story each; Epic = this track)
16
17 1. **Containerization design** — Dockerfile sketch/description for both the DAO-backed API service and the IDP connector service (multi-stage Go builds), described as targeting the real k8s dev cluster above (so it should read as a Deployment/Service manifest sketch, not just a bare Dockerfile). *Gated on:* 03's service boundaries (one binary or two, package layout) in `PLANNING.md`.
18 2. **CI pipeline sketch** — what runs on commit/PR: build, lint, test, security scan, and (as a documented final stage) a describe-only "apply to the lab cluster via amber-kubernetes" step — named, not executed. *Gated on:* nothing structural from other tracks, but the "test" stage assumes 03's test strategy exists in outline.
19 3. **Local dev-loop** — docker-compose sketch with a local Postgres profile and a SQLite-only profile (compose profiles, matching whatever env-var/config mechanism 03 picks for DB driver selection); this is the local counterpart to the cluster target, not a replacement for it. *Gated on:* 03's DAO driver-selection mechanism.
20 4. **Secrets delivery mechanics** — how DB credentials, IDP credentials, and signing keys reach the running services at deploy time (env injection, mounted files, or a secrets-manager sketch — mechanism only, not what's a secret or why), noting how that would map onto the lab cluster's existing secrets convention once known (not yet queried — see deployment target note). *Gated on:* 02's secrets inventory.
21 5. **Observability + never-log enforcement** — logging/metrics/tracing approach for both services, plus how the never-log list gets enforced in practice (structured logging with a redaction/allowlist discipline, not just a policy statement). *Gated on:* 02's never-log list.
22
23 Named hand-off inputs, all as files this track reads (not re-derives):
24 - `../PLANNING.md` → "Service boundaries" section, owned by Renata (03).
25 - 02's receiver-shaped secrets inventory + never-log list (per `planning-approach.md` §2, a deferred 02→04 hand-off — file path TBD, likely an addition to `../02-ai-security-architecture/connector-security.md` or a new short file).
26 - `../05-data-ops/multi-db-strategy.md` → migration tool/approach only (not migration content).
27
28 ## 2. Hire roster (3 hires, per Warren's 3–4/track sizing and Theo's small-surface argument for the low end of that range)
29
30 All three functions are genuinely useful here — not padding — because each does something Theo's own temperament won't: a Newcomer catches "assumed" jargon in hand-offs Theo would read past too fast; an Enthusiast is the required temperament opposite and the one voice arguing against Theo's own bias toward the minimum viable setup; a second Minimalist voice acts as an independent scope check on Theo's own designs (deadpan-minimalist reviewing deadpan-minimalist has no counterweight without a distinct persona doing it). No fourth hire — there's no fourth function this track's small deliverable set actually needs; adding one would be exactly the over-resourcing Theo argued against in `planning-approach.md`.
31
32 ### Hire 1 — Newcomer (function: hand-off legibility check)
33 - **Proposed name:** Wesley Okonkwo
34 - **Archetype:** Newcomer · temperament tags: `serious, cautious`-leaning but genuinely curious (bright/earnest, not deadpan)
35 - **Model:** sonnet
36 - **Patterns & role:** runs the Newcomer's-question pass (§5 of `CASTING.md`) on 03's service-boundaries hand-off and 02's secrets-inventory hand-off once both land — reads each cold, lists anything he couldn't act on without asking Theo to re-explain it, Theo fixes or annotates. This is the one pattern this track's plan authorizes.
37 - **Persona (draft, 137 words):** Bright, unguarded, asks the question everyone else stopped asking two meetings ago. Decides by restating what he thinks he just read and waiting to be corrected — if nobody corrects him, he assumes it's actually clear and says so in writing. Two years as a support engineer triaging on-call tickets for a mid-size SaaS company, one year as a junior platform engineer at a logistics startup, where he learned that a runbook nobody outside its author can follow is not a runbook. Comfortable with Docker, basic CI YAML, and reading a config file, but has never designed one from scratch. Pushes back on any hand-off that uses a term without defining it once. Blind spot: doesn't know what's already settled, so he sometimes flags a deliberate simplification as a gap. Disagreement: asks a follow-up question rather than asserting he's right.
38
39 ### Hire 2 — Enthusiast (function: Theo's required temperament opposite; argues for trying the promising infra option)
40 - **Proposed name:** Bree Sandoval
41 - **Archetype:** Enthusiast · temperament tags: `playful, bold`
42 - **Model:** sonnet
43 - **Patterns & role:** informal counterweight seat (not a named catalog pattern — the catalog itself says this track is too small for red/blue or three-hats) inside the containerization, CI, and dev-loop design tasks: for each, she writes the "what's the more capable/interesting option and what would it cost us" case (e.g., a managed secrets manager over a mounted file, a fuller CI matrix over the minimum) before Theo rules on it. Documented as a short "considered and declined/adopted" note per deliverable, not a separate debate artifact — proportionate to a three-task track.
44 - **Persona (draft, 128 words):** Bold, quick to say "let's just try it," genuinely energized by the newer option in the room. Decides fast and revises fast — she'd rather propose three things and cut two than deliberate over one. Three years building CI/CD for a Series B fintech's platform team, one year as a DevRel engineer demoing infra tools, which is where the optimism about new tooling comes from and also where she watched three demos fail under real traffic. Deep hands-on with GitHub Actions, container registries, and secrets-manager integrations. Pushes back whenever a design defaults to "the minimum" without pricing what's given up. Blind spot: discounts the operational cost of the thing she's excited about. Disagreement: stays cheerful, concedes fast when shown the failure mode, rarely re-litigates after that.
45
46 ### Hire 3 — Minimalist (function: independent scope guard)
47 - **Proposed name:** Callum Ferreira
48 - **Archetype:** Minimalist · temperament tags: `serious, cautious`
49 - **Model:** sonnet
50 - **Patterns & role:** informal review seat — reads each of the five deliverables once drafted and asks "what would we cut," specifically watching for take-home scope creep this track is prone to (Kubernetes manifests, a service mesh, a full observability stack) that reads as impressive but isn't what a take-home needs. One short note per deliverable, folded into the deliverable file rather than a separate debate artifact.
51 - **Persona (draft, 118 words):** Plain-spoken and unimpressed by scale for its own sake, but not identical to Theo — where Theo sizes to the actual problem from experience, Callum's method is adversarial subtraction: he assumes everything in a draft is optional until proven otherwise. Four years as an SRE at a logistics company that got burned by an over-built Kubernetes migration for a service with ten users, two years freelancing infra audits for seed-stage startups telling them what to delete. Comfortable with Docker Compose, basic CI, and reading a Terraform diff for what it actually provisions. Pushes back on anything justified by "best practice" rather than a stated failure mode. Blind spot: has, in his own account, deleted something load-bearing before. Disagreement: states the cut plainly, doesn't fight if overruled.
52
53 ## 3. Debate plan
54
55 - **One Newcomer's-question pass** (Wesley) on 03's service-boundaries entry in `PLANNING.md` and on 02's secrets-inventory/never-log hand-off, run once each lands. This is the pattern `CASTING.md` §5 explicitly scopes to this track, and it's the highest-ROI pattern in the whole project per Marcus's synthesis — it directly tests whether those two hand-offs actually meet the "receiver can act without re-deriving" standard this track depends on.
56 - **Everything else is waste, explicitly:** no adversarial pair, no red/blue, no three hats, no structured written debate, no rotating devil's advocate seat. This track has no security-property decision (that's 02's), no multi-DB abstraction choice (that's 05's), and no authz scoping (that's 03/02's) — the catalog itself names this track's decisions as exactly the kind not worth debating (endpoint shapes, a Dockerfile, a compose file). Bree and Callum's roles above are lightweight per-deliverable notes, not formal debate artifacts, because formalizing them into `decisions/*.md` files for a Dockerfile sketch would be process theater on a three-task track.
57
58 ## 4. Dependencies
59
60 Stable enough to design against means, for each of the three gating inputs:
61
62 - **03 service boundaries** (`../PLANNING.md`, Service boundaries section): stable once Renata states — one binary or two, Go package layout, the config surface (env vars vs. flags vs. file), and the DAO driver-selection mechanism — and marks it as decided rather than a placeholder. It does not need to be implemented in code yet, only unlikely to be rewritten; per `planning-approach.md`, "stable" means that, not "polished."
63 - **02 secrets inventory + never-log list**: stable once it exists as an enumerated list (which secrets, roughly what each protects) plus an explicit never-log list, in a named file this track can cite — not as prose alternatives Theo would have to interpret. Marcus's `planning-approach.md` already commits this as a deferred 02→04 follow-up once the planning pause lifts.
64 - **05 migration approach** (`../05-data-ops/multi-db-strategy.md`): stable once it names the migration tool/mechanism (e.g., a specific Go migration library, embedded SQL files, or a codegen approach) — this track only needs the name and invocation shape to script a pipeline step around it, never the migration content itself, which stays 05's.
65
66 Until all three land, this track's status stays "not started" on the deliverables above and this track's actual role is reviewer/consumer: read the other four tracks' output as it lands, flag anything infra-shaped that risks being silently owned elsewhere, and otherwise stay out of the way.
67 {
"file_path": "/Users/warrenthompson/Source/LoginID/04-infra-devops/profiles/wesley-okonkwo/wesley-okonkwo.md"
}1 --- 2 schema_version: 1 3 name: "Wesley Okonkwo" 4 slug: "wesley-okonkwo" 5 image: "profiles/wesley-okonkwo/wesley-okonkwo.png" 6 7 nsfw: false 8 display: 9 image: true 10 name: true 11 autostart: false 12 generation: 13 backend: "comfyui" 14 model: "comfyui-workflow.json" 15 prompt: "Wesley Okonkwo, a young man in his mid-20s with short black hair and an open, curious expression, wearing a light blue button-down shirt, business-casual, head-and-shoulders portrait, centered, neutral gray studio background, professional corporate headshot photography, soft studio lighting, sharp focus, photorealistic" 16 negative_prompt: blurry, low quality, distorted, extra limbs, watermark, text, nsfw, nude, nudity, explicit, sexual content 17 seed: null 18 gif_mode: null 19 created_at: "2026-09-12T00:00:00Z" 20 --- 21 22 # Wesley Okonkwo 23 24  25 26 27 ## Personality 28 29 Bright, unguarded, asks the question everyone else stopped asking two meetings ago. Decides by restating what he thinks he just read and waiting to be corrected — if nobody corrects him, he assumes it's actually clear and says so in writing. Two years as a support engineer triaging on-call tickets for a mid-size SaaS company, one year as a junior platform engineer at a logistics startup, where he learned that a runbook nobody outside its author can follow is not a runbook. Comfortable with Docker, basic CI YAML, and reading a config file, but has never designed one from scratch. Pushes back on any hand-off that uses a term without defining it once. Blind spot: doesn't know what's already settled, so he sometimes flags a deliberate simplification as a gap. Disagreement: asks a follow-up question rather than asserting he's right. 30 31
{
"command": "python3 /Users/warrenthompson/.claude/skills/profile-gen/scripts/show_profile.py --profile /Users/warrenthompson/Source/LoginID/04-infra-devops/profiles/wesley-okonkwo/wesley-okonkwo.md --root /Users/warrenthompson/Source/LoginID/04-infra-devops"
}{"name": "Wesley Okonkwo", "mode": "hud", "hud": true, "badge": false, "background": false, "inline": false}{
"to": "theo-bergman",
"content": "Wesley here, checked in. Read the root CLAUDE.md, our track CLAUDE.md, and PLAN.md — got it, I'm holding on the newcomer's-question pass until both 03's service-boundaries hand-off and 02's secrets-inventory/never-log list have actually landed as named files. Not touching anything until you call it. Ready when you are."
}<tool_use_error>Error: No such tool available: SendMessage. SendMessage is disabled for this session, in subagents as well as here.</tool_use_error>
{
"file_path": "/Users/warrenthompson/Source/LoginID/PLANNING.md"
}1 # Planning Board 2 3 Thin cross-track coordination board — pointers and status only. The reasoning lives in each track's own files (`<track>/CLAUDE.md`, `<track>/PLAN.md`, deliverables), referenced by relative path. **One writer per row:** each lead edits only its own row; team-lead edits the header, the two Dana rows, and nothing else. Shape and rationale: `02-ai-security-architecture/planning-approach.md` §3. Org plan: `PLAN.md`. Hiring rules and registry: `CASTING.md`. 4 5 Status vocabulary: `not started` · `planning` · `plan drafted — awaiting hiring go` · `hiring go` · `hiring` · `debating` · `plan approved` · `blocked`. 6 7 | Track | Lead | Status | Blocking on | Blocked by | Planning notes | Hires (planned / registered / spawned) | Last updated | 8 |---|---|---|---|---|---|---|---| 9 | Orchestration (root) | Dana Whitfield | Phase 2 hiring — 01/02/03/04 plans approved and teams live as top-level sessions; 05 planning (Opus) | 05 plan + roster | — | `PLAN.md`, `LOG.md` | 14 / 18 cap | 2026-09-12 | 10 | Cross-track consistency pass (root, Phase 3) | Dana Whitfield | not started | all five `plan approved` | — | `PLAN.md` Phase 3 | — | 2026-09-12 | 11 | 01 Product & Industry Research (incl. Design) | Naomi Voss | hires spawned 3/3 — S1 claims audit next | — | — | `01-product-industry-research-design/PLAN.md`; `decisions/` created; Newcomer loan from 04 (`wesley-okonkwo`) approved by team-lead for S3/S6 cold reads, ≤4 turns, timing Theo's call (requested) | 3 / 3 / 3 | 2026-09-12 | 12 | 02 AI Architecture & Security Architecture | Marcus Ilori | hires spawned 4/4 — ready for S3/S4 debates | — | — | `02-ai-security-architecture/PLAN.md`; `CASTING.md` §5 ratified with 4 amendments; `planning-approach.md` App. A + B; personas + agent defs written, registry rows appended | 4 / 4 / 4 | 2026-09-12 | 13 | 03 Engineering & Delivery | Renata Cole | hires spawned 4/4 — awaiting inputs | 05 contract (DAO), 02 auth hand-off (middleware) | — | `03-engineering-delivery/PLAN.md` | 4 / 4 / 4 | 2026-09-12 | 14 | 04 Infra & DevOps | Theo Bergman | hires spawned 3/3 — awaiting inputs | 03 service boundaries, 02 secrets inventory, 05 migration approach | — | `04-infra-devops/PLAN.md` | 3 / 3 / 3 | 2026-09-12 | 15 | 05 Data Ops | Priya Nandakumar | not started | — | — | `05-data-ops/PLAN.md` | 4 / 0 / 0 | 2026-09-12 | 16 17 ## Sessions (owned by team-lead) 18 19 Each lead is its own top-level Claude Code session in its own tmux window. Use the session name with `SendMessage` (confirm with `ListAgents`); hires are teammates of their lead's session and are reached through the lead. 20 21 | Role | Session name | tmux window | Model | 22 |---|---|---|---| 23 | PM / team-lead (Dana Whitfield) | `org-planning-loginid-takehome` | `1` | fable | 24 | 04 lead (Theo Bergman) | `04-infra-devops-47` | `04-infra` | sonnet | 25 | 02 lead (Marcus Ilori) | `02-ai-security-architecture-a2` | `02-security` | sonnet | 26 | 03 lead (Renata Cole) | `03-engineering-delivery-b7` | `03-engineering` | sonnet | 27 | 01 lead (Naomi Voss) | `01-product-industry-research-design-21` | `01-product` | sonnet | 28 | 05 lead (Priya Nandakumar) | launching (Phase 1 plan first) | `05-data` | opus | 29 30 ## Service boundaries (owned by 03, read by 04) 31 32 Set by 03 during Phase 1 planning (full reasoning: `03-engineering-delivery/PLAN.md` §4). 33 34 - **Two binaries**, not one: `cmd/api-service` (Q1 DAO + Q2 REST API, client-facing, bearer-token auth) and `cmd/idp-connector` (Q3, outbound-only to vendors) — different threat models/auth surfaces per 02, independent scaling needs, mirrors the assignment's own Q2/Q3 split. 35 - **Package layout:** 36 ``` 37 cmd/api-service/main.go 38 cmd/idp-connector/main.go 39 internal/dao/ # Repository interface + postgres/cockroachdb/sqlite implementations 40 internal/model/ # shared structs: UserProfile, UserCredential 41 internal/api/ # REST handlers, router, auth middleware 42 internal/connector/ # /auth, /identity client + handlers 43 internal/config/ # env-var config loading, shared by both binaries 44 ``` 45 - **Config surface:** environment variables only (12-factor) — `DB_DRIVER` (postgres|cockroachdb|sqlite), `DB_DSN`, `HTTP_ADDR`, `AUTH_JWT_ISSUER`/`AUTH_JWT_AUDIENCE` (names pending 02's final scheme), `IDP_ABC_BASE_URL`, `IDP_ABC_CLIENT_ID`/`IDP_ABC_CLIENT_SECRET`. No flags, no config files. 46 - **DAO driver-selection mechanism:** a factory `dao.New(driver, dsn string) (dao.Repository, error)` chosen at startup from `DB_DRIVER`; every backend satisfies one `Repository` interface (method signatures finalized once 05's contract lands — the factory shape is stable now, its return type's method set is not). 47 - **Stable for 04 to build against:** the two-binary split, the `cmd/` entrypoint names, the env-var config approach and variable names above, and the `dao.New(driver, dsn)` factory signature. **Not yet stable:** internal DAO implementation details, ORM/query-builder choice, handler-package internals. 48 49 ## Dependency order (from `planning-approach.md` §2) 50 51 1. First wave, in parallel: 01, 02, 05 — 05 is on the critical path (it hard-blocks 03's DAO and partially blocks 04). 52 2. 03 split start: scaffolding may begin immediately; DAO once 05's Go-shaped contract is stable; middleware once 02's receiver-shaped auth hand-off lands. 53 3. 04 last, gated on the three inputs named in its row. 54
{
"file_path": "/Users/warrenthompson/Source/LoginID/02-ai-security-architecture/handoff-04-secrets.md"
}1 # Hand-off 02 → 04: Secrets inventory and never-log list 2 3 Status: **v1** — Ingrid Solano's S7 objection turn is answered below and folded into the tables. Awaiting 04's Newcomer (`wesley-okonkwo`) cold read. Receiver: Theo Bergman, `../04-infra-devops/`. Written to the project's hand-off standard: 04 should be able to act on this without re-deriving the reasoning in `threat-model.md`, `api-auth-design.md`, or `connector-security.md`. 4 5 ## Assumptions this hand-off makes 6 7 1. Deployment target is the **design-only Kubernetes lab cluster** 04 names (Warren's decision via Theo, `../LOG.md`). Delivery mechanism is therefore Kubernetes-native `Secret` objects unless 04 chooses an external store; every requirement below is written so it still holds if the mechanism changes. 8 2. Service topology is 03's published boundary (`../PLANNING.md`, Service boundaries): two binaries, `cmd/api-service` (Q1 DAO + Q2 REST API) and `cmd/idp-connector` (Q3), env-var configuration only. Variable names below are 03's; two are marked as pending this track's final scheme. **The token-issuing authorization-server function is a mode of `api-service`, not a third binary** — this take-home's scope doesn't justify a standalone authz server, and 03 has published no third `cmd/` entrypoint. It runs under `api-service`'s own service account; RBAC in row #2 is scoped to that account, restricted (via a distinct Kubernetes Role, not a separate ServiceAccount) so that only the issuing code path — not the token-verification code path in the same binary — can `get` the signing-key Secret. If 03 later decides to split this out as its own binary, this row's RBAC target changes with it; flag that to me if it happens, don't silently re-derive it in 04. 9 3. An **external operator agent applies manifests** to the cluster. That operator is a privileged actor outside this service: anyone who can apply a manifest can read any Secret it mounts. Consequence for 04: manifests reference Secrets by name and never carry values; values are set out-of-band. This is a named trust boundary in `threat-model.md`, not a silent assumption. 10 4. Nothing is deployed and nobody contacts the cluster or its operator during the take-home. This document describes what *would* be delivered, not what has been. 11 12 ## Secrets inventory 13 14 | # | Secret | Used by | Injection requirement (02 sets it) | Rotation expectation | Kubernetes-native note (04's mechanism) | Never-log | 15 |---|---|---|---|---|---|---| 16 | 1 | Database credentials, carried inside `DB_DSN` | `api-service` | Runtime-injected; never in source, image, or committed config. **Required, not preferred: file mount via a `DB_DSN_FILE` convention** (03 to add alongside `DB_DSN`), because a DSN's embedded password is exposed by `kubectl describe`, crash dumps, and child-process environment exactly as much as any other secret in this table — there is no citation that makes `DB_DSN` a lesser case, so this track is not treating it as one | On credential rotation; service restart or reload acceptable | `Secret` mounted read-only as a file, read via `DB_DSN_FILE`; env-var `DB_DSN` is a fallback only until 03 ships the file-based convention, not a permanent equal option | Yes — the DSN contains the password; log the driver name and host at most | 17 | 2 | JWT signing **private** key (RS256/ES256), per `api-auth-design.md` | Token-issuing authorization-server component only — **not** `api-service`, which verifies with the public key | Runtime-injected as a **file**, never an env var; asymmetric so the API never holds it | Key rotation with a `kid` claim; publish old and new public keys together during overlap; TTL of tokens (5–15 min) bounds the overlap window | `Secret` mounted read-only as a file; restrict `get`/`list` on this Secret to the issuer's service account via RBAC | Yes | 18 | 3 | JWT **public** key / JWKS | `api-service` middleware | Not a secret; may ship in config or be fetched from the issuer | Follows #2 | Plain `ConfigMap` or fetched URL; listed here so 04 does not over-protect it | No (but never log full tokens — see below) | 19 | 4 | API caller `client_secret`s (OAuth2 client-credentials) | Authorization server (verifies); callers hold their own copy | **Stored hashed** (Argon2id, same floor as `threat-model.md` Asset 1), never plaintext at rest; issued once at provisioning and not recoverable | Per-client rotation on demand; revoke by deleting the hash | Lives in the issuer's datastore, not a Kubernetes `Secret`; 04 delivers only that datastore's credentials (#1-shaped) | Yes | 20 | 5 | Connector's own vendor registration: `IDP_ABC_CLIENT_ID`, `IDP_ABC_CLIENT_SECRET` (and the XYZ pair if a second vendor is configured) | `idp-connector` | Runtime-injected; never in source or committed config; `client_id` is low-sensitivity but travels with the secret and gets the same handling | Per vendor's policy; expect manual rotation | One `Secret` per vendor so rotating ABC never touches XYZ; file mount preferred | Yes for the secret; keep the id out of logs too | 21 | 6 | Vendor `access_token` (obtained via `/auth`, used for `/identity`) | `idp-connector`, in memory | **Not a deployed secret** — transient. If cached at all: TTL ≤ vendor expiry, encrypted at rest with #7, keyed per vendor account (`connector-security.md` §1) | Expires on its own; never persisted across restarts | No `Secret` object; if 04 provisions a cache, it must be encrypted using #7 and reachable only from `idp-connector` | Yes — never, including error paths | 22 | 7 | Data-encryption key (KEK) for envelope encryption of any TOTP shared secrets and any cached vendor tokens | `api-service` (TOTP) and `idp-connector` (token cache), if either feature exists | Runtime-injected as a file; the KEK never leaves the process that decrypts | Rotation by re-wrapping DEKs; never requires re-hashing passwords (those are hashed, not encrypted) | `Secret` (file mount) or an external KMS if 04 prefers — the requirement is only that a KEK exists and is not in source | Yes | 23 | 7a | Credential to reach the external KMS, **only if 04 exercises the KMS alternative in row #7** (a role, API key, or client cert — whichever the chosen KMS uses) | Whichever binary holds the KEK reference (#7) | Same discipline as every other row here: runtime-injected, never in source, never provisioned ad hoc outside this table | Per the KMS provider's own rotation mechanism | Cloud-KMS access is typically workload-identity-based (no static credential to store) rather than a `Secret` object; if the chosen KMS instead requires a static credential, it gets its own `Secret` under the same RBAC discipline as row #2 | Yes | 24 | 8 | TLS serving private keys for `api-service` and `idp-connector` | Ingress / each binary, per 04's termination choice | Runtime-injected; TLS 1.2+ only; certificate validation never disabled in any environment (`connector-security.md` §2) | Per certificate lifetime; automated issuance preferred | `kubernetes.io/tls` `Secret`; issuance mechanism is 04's call | Yes | 25 | 9 | End-user's vendor password (`POST /auth` request body) | `idp-connector`, transient | Never persisted, never cached, held only for the vendor call | n/a | Nothing for 04 to deliver; listed so nobody creates a place for it | **Yes — under no circumstance, including stack traces** | 26 27 Names marked pending in 03's boundary: `AUTH_JWT_ISSUER` / `AUTH_JWT_AUDIENCE` are configuration, not secrets; this track will confirm their final values in `handoff-03-auth.md`. They are listed here only so 04 does not classify them as secrets. 28 29 ## Kubernetes-specific requirements (04 implements; 02 sets) 30 31 - **Secrets at rest in etcd are base64, not encrypted, by default.** Either enable encryption at rest for the `Secret` resource type, or source values from an external store that never writes plaintext to etcd. One or the other; the design must say which. 32 - **RBAC:** no service account may `get`/`list` Secrets it does not mount. The operator agent's apply rights are a documented trust boundary, not a hole to close in this take-home — but the design should note that its scope could be narrowed to a namespace. 33 - **Manifests carry references, never values.** Any manifest in the repository contains Secret *names*; values are provided out-of-band. This is what keeps assumption 3 honest. 34 - **Prefer file mounts over env vars** for #2, #5, #7, #8. #1 is env-var only because 03's config surface is env-var only; if 03 accepts a `_FILE` suffix convention, move it. 35 - **No secret in an image layer, build arg, or CI log.** CI redaction is 04's mechanism; the requirement is that a build log can be published without exposing anything in this table. 36 - **Audit/access logs (the output of the never-log list below) are readable only by an incident-review role, distinct from and narrower than general operator access.** `sub`, scope, record id, and timestamp are enough on their own to reconstruct who looked up whom — that log stream needs its own read restriction, separate from whichever service account can `get`/`list` the Secrets in this table. Mechanism (log-pipeline RBAC, a separate log sink with its own access control, etc.) is 04's call; the requirement is that "can read Secrets" and "can read the audit trail" are not the same grant. 37 38 ## Never-log list (applies to every component, every environment, every log level, including error paths and stack traces) 39 40 1. End-user vendor password (#9). 41 2. Vendor `access_token` (#6) — and any `Authorization` header value, inbound or outbound, in full. 42 3. PII field values from `user_profile` or from `/identity` responses: name, phone, `street_address`, `locality`, `region`, `postal_code`, `country`. Log the *fact* of access — caller `sub`, scope used, opaque record id, timestamp, status — never the values. 43 4. Raw vendor error response bodies (they can echo submitted PII or partial credentials). Log status code and vendor error code only. 44 5. JWT signing private key (#2) and any JWT in full — log `sub`, `aud`, `exp`, and `kid` if needed, never the token. 45 6. API caller `client_secret`s (#4), in plaintext or hashed. 46 7. `DB_DSN` (#1) or any connection string containing credentials. 47 8. Full request bodies of `POST /auth` and `POST /identity` — both contain items above by construction. 48 9. Search query parameters containing a name or phone fragment (`api-auth-design.md`, hardening) — they are PII; redact or hash before any access log, including proxy and ingress logs 04 controls. 49 50 ## What 04 does not decide (already decided upstream) 51 52 Token TTLs and claim set (`api-auth-design.md`); scope vocabulary (`handoff-03-auth.md`, pending); password and client-secret hashing (`threat-model.md` Asset 1); whether the connector caches vendor tokens (`connector-security.md` §1 — it may, under the conditions in #6); PII retention windows (`../05-data-ops/pii-governance.md`). 53 54 ## S7 objection turn — Ingrid Solano, resolved 55 56 All four objections accepted; no decline. 57 58 1. **Authorization-server ownership gap.** Accepted. Assumption 2 now states the authz-issuing function is a mode of `api-service`, scoped by a Role narrower than the token-verification code path in the same binary — not a phantom third deployable. 59 2. **Missing KMS-access-credential row.** Accepted. Added row #7a with the same columns as every other row, rather than leaving it as a footnote inside #7. 60 3. **Env-var exception for `DB_DSN`.** Accepted — the objection is correct that "prefer, if 03 allows" was this track declining a call it owns. Upgraded to a requirement: a `DB_DSN_FILE` convention, with the env var as a transitional fallback only. Carried into `handoff-03-auth.md` as an ask to 03. 61 4. **No RBAC on audit-log read access.** Accepted. Added a requirement under Kubernetes-specific requirements that Secret-read and audit-log-read are distinct grants. 62 63 --- 64 Model: sonnet (lead, ruling) / sonnet (Ingrid Solano, objection turn). Turns consumed: 1 (single objection-turn pattern, per `CASTING.md` §5). 65
{
"file_path": "/Users/warrenthompson/Source/LoginID/02-ai-security-architecture/handoff-04-secrets.md",
"offset": 1,
"limit": 1
}1 # Hand-off 02 → 04: Secrets inventory and never-log list
{
"file_path": "/Users/warrenthompson/Source/LoginID/01-product-industry-research-design/api-connector-design-rationale.md"
}1 # API & Connector Design Rationale
2
3 Prepared for the `01-product-industry-research-design` track. See `../CLAUDE.md` for the full assignment text and `./CLAUDE.md` for this track's scope. Written so `../03-engineering-delivery/CLAUDE.md` can implement against it directly, without re-deriving the reasoning — that's this document's explicit job per the track brief. Builds on `industry-framing.md` and `personas-use-cases.md` in this directory; read those for the "why," this document is the "so build it this way."
4
5 **AI tooling note:** produced by Claude Code (Sonnet 5), track-owner subagent persona Naomi Voss, as a design-reasoning pass — no new external research; synthesizes the assignment text plus this track's prior work.
6
7 This document covers three surfaces in the order the assignment presents them: the `user_profile`/`user_credential` data contract (Q1), the search/retrieve REST API (Q2), and the third-party IDP connector (Q3).
8
9 ## 1. Data contract: `user_profile` and `user_credential`
10
11 **Why two separate entities, not one row:** the assignment names them separately, and that separation is doing real work, not just organizing fields. `user_profile` is PII a service holds *about* a person (name, address, phone) — data with governance obligations (retention, right-to-deletion, breach-notification exposure) attached to `../05-data-ops/CLAUDE.md`'s scope. `user_credential` is a proof-of-identity mechanism the service uses *to authenticate* that person — a username, an auth `method`, and (for the password method) a secret. Conflating them into one table would mean every profile read touches the credential store and vice versa, which is exactly the wrong blast radius for a credential breach.
12
13 **Why `user_credential.method` matters as a design decision, not just a column:** per `industry-framing.md` §2, this field is the seam where the assignment's deliberately simple password baseline becomes extensible toward what LoginID actually sells (FIDO2/passkey, `method = "webauthn"`) without a schema rewrite. Concretely: `method` should be treated as a discriminator that governs which other fields on the row are meaningful — a `"password"` row has a password hash; a future `"webauthn"` row would have a public key and credential ID instead, not a password field left null by convention. Whether that's modeled as one wide table with nullable method-specific columns or a base table plus method-specific side tables is an implementation choice for `../03-engineering-delivery/CLAUDE.md` and `../05-data-ops/CLAUDE.md` — the product requirement this document is asserting is only that **`method` must be a real discriminator with room to grow, not a fixed enum assumed to always be `"password"`.**
14
15 **One relationship, not two independent tables:** a `user_profile` and its `user_credential`(s) need a stable foreign key (one profile can plausibly have more than one credential — a password today, a passkey added later, without replacing the old one until migration is complete). Designing for 1-to-many from the start avoids a breaking migration the day a second auth method is added.
16
17 ## 2. REST API: search and retrieve
18
19 **Endpoint shape, from the personas in `personas-use-cases.md`:**
20
21 - `GET /profiles/{id}` — exact retrieval by primary key. Serves any caller who already has the ID (the common, low-risk case).
22 - `POST /profiles/search` — the search surface Persona 2 (support/ops analyst) actually uses day to day. **Resolved as `POST`, not `GET` with query params**, for two product-level reasons, not just implementation convenience: (1) the search body needs to carry partial-match flags and an explicit "expand" toggle (see below) alongside the query fields, which is awkward to express and easy to get wrong as bare query params; (2) `GET` query strings land in access logs and browser history by default, and a phone number or partial name is PII — `POST` keeps the query payload out of the URL and lines up with where this document already requires every search call to be an audit-log event. This is the endpoint that needs the most deliberate authz design, because "search by phone" is simultaneously the legitimate support workflow and the shape of a scraping attack.
23
24 **Why search and retrieve are named as separate concerns in the assignment, and should stay separate in the API design:** retrieval-by-ID is nearly risk-free — if a caller has a valid ID, they likely already have a legitimate reason to look at that record. Search is different: it's a fuzzy-match query over a PII store, which means the API surface itself is the control point. Design implications this document asserts (mechanics belong to `../02-ai-security-architecture/CLAUDE.md`):
25
26 - Search results should default to **masked/partial PII** in the response, with full-field detail requiring either a distinct, more tightly scoped call or an explicit "expand" parameter — so that a broad search doesn't silently become a bulk PII export.
27 - Search should be **rate-limited and paginated** independent of retrieval, because unbounded search is the difference between "an analyst looked up one ticket" and "someone enumerated the table."
28 - Every search call is a natural audit-log event (who searched, for what, when) even before considering who's authorized to call it at all — this is a data-governance requirement (`../05-data-ops/CLAUDE.md`) that the API design needs to leave room for (e.g., not designing a stateless-only API that makes call attribution an afterthought).
29
30 **Why "plus security API authentication" is in the same assignment bullet as search/retrieve, not a separate question:** the assignment is signaling that the two are inseparable — a search/retrieve API over PII without an authentication design isn't a partial answer, it's a wrong one. This document asserts the product-level requirement (search needs scoped, attributable, rate-limited access, not just "logged in"); the actual authn/authz mechanism (API keys vs. OAuth2 client-credentials vs. mTLS, token formats, scope models) is `../02-ai-security-architecture/CLAUDE.md`'s to design.
31
32 ## 3. Third-party IDP connector: `/auth` and `/identity`
33
34 The assignment specifies exact contracts for both endpoints (see root `CLAUDE.md` — `/auth` takes `{"username", "password"}` and returns an `access_token`; `/identity` takes `{"phone", "name"}` and returns PII including a structured address). The design rationale here is about *why* that shape makes sense and *how to build the connector so it isn't hostage to any one provider's quirks* — not about re-specifying the contract, which is already fixed.
35
36 **Why the two-call shape (`/auth` then `/identity`) is the right pattern, per Persona 3:** it's a minimal, legacy-style token-exchange flow — trade a long-lived credential (username/password) for a short-lived, purpose-scoped token, then use that token for the actual data pull. This is structurally the same shape as OAuth2's resource-owner-password-credentials grant (the *deliberately old-fashioned* member of the OAuth2 grant family, notably the one RFC 9700 now states MUST NOT be used, in favor of authorization-code or client-credentials flows — see `research-oauth-history.md` for the OAuth lineage this pattern sits in). Recognizing that lineage matters for the write-up: this connector contract is not a novel design, it's a well-understood, industry-standard-but-dated pattern (see `industry-framing.md` §2, revised after S2 — this is named as the conventional baseline, not as a claim about the assignment author's intent).
37
38 **Why the connector must be built provider-agnostic (ABC/XYZ as instances of one interface, not two bespoke integrations):** the assignment names the providers generically on purpose — this is a request to design *a connector interface*, with ABC and XYZ as its first two implementations, not to hardcode either one. Concretely, that means:
39
40 - A common internal interface — something like `Authenticate(username, password) → token` and `FetchIdentity(token, phone, name) → PII` — that both ABC and XYZ adapters satisfy, so adding a third provider is "write an adapter," not "touch the calling code."
41 - Each provider adapter owns its own quirks (token expiry format, field-name differences, rate limits, error codes) behind that common interface — the calling code (the onboarding flow from Persona 3) never branches on which provider it's talking to.
42 - The interface's shape should anticipate that a future provider might not be username/password-based at all — an OAuth2/OIDC provider would satisfy `Authenticate` differently (redirect-based auth-code exchange instead of a direct password POST) but still ultimately produce a token this connector can use the same way for `FetchIdentity`. This is the connector-side equivalent of `user_credential.method`'s extensibility from §1 — flagged here, not built here, per `industry-framing.md`'s point about not burying the actual assignment under speculative scope.
43
44 **Field-mapping note (product-level, not implementation):** the assignment's `/identity` response splits address into `street_address`, `locality`, `region`, `postal_code`, `country` — this is recognizably the OpenID Connect `address` claim shape (a widely-used standard structure for exactly this purpose), which is a useful fact for the write-up: it signals the assignment's own contract is already leaning on real industry convention rather than an arbitrary shape, which is worth naming explicitly as evidence of "this connector is meant to look like a real federated-identity integration," reinforcing the design choices above.
45
46 ## Summary for the engineering track
47
48 Three seams to build with deliberately, per this document: `user_credential.method` as a real discriminator (not a fixed enum), a search API designed around masked-by-default results + rate limiting + audit logging (not just an authenticated GET), and a provider-agnostic connector interface with ABC/XYZ as its first two adapters (not two copy-pasted integrations). Each seam is where this "before" baseline visibly points toward the "after" — passkeys, scoped search, and modern federated-identity providers — without this submission having to build any of the "after" itself.
49
50 ## Contract summary for 03
51
52 This section is the single-page version 03 should build against without re-reading the rest of this document. Where this section and the prose above conflict, this section is current.
53
54 **Entities (Q1)** — exact assignment field names:
55
56 - `user_profile`: `name`, `address`, `phone` (plus a primary key and the FK target for `user_credential`).
57 - `user_credential`: `username`, `method`, `password` (plus a primary key and a FK to `user_profile`).
58 - Relationship: **one `user_profile` to many `user_credential`** — do not model as 1:1. `method` is a real discriminator column (string/enum-with-room, not a fixed two-value enum) that determines which other columns on a `user_credential` row are meaningful; only the `"password"` method needs a password hash today. How that's physically modeled (nullable columns vs. method-specific side tables) is 03's and `../05-data-ops/CLAUDE.md`'s call, not fixed here.
59
60 **REST API (Q2):**
61
62 - `GET /profiles/{id}` — exact retrieval.
63 - `POST /profiles/search` — search, **not** `GET` with query params (rationale above). Request body carries search fields (name, phone, partial-match flags) plus an explicit `expand` flag for full-detail results.
64 - Default response for search is masked/partial PII; `expand=true` (or an equivalently explicit parameter) is required for full-field detail and should be more tightly scoped than the base search call.
65 - Search is rate-limited and paginated, independently of retrieval-by-ID.
66 - Every search call must be attributable and audit-logged (caller identity, query, timestamp) — the API shape must not make call attribution an afterthought (e.g., no anonymous/stateless-only search path).
67 - Authn/authz mechanism (API key vs. OAuth2 client-credentials vs. mTLS, token formats, scopes) is fully specified in `../02-ai-security-architecture/CLAUDE.md` — 03 implements against that document's design, not against an invention of its own.
68
69 **Connector interface (Q3):**
70
71 - Endpoints, methods, and JSON bodies for `/auth` and `/identity` are fixed by the assignment (see root `CLAUDE.md`) — 03 does not re-specify them.
72 - Build one provider-agnostic interface — `Authenticate(username, password) → token`, `FetchIdentity(token, phone, name) → PII` — with ABC and XYZ as its first two adapters. The calling code (onboarding flow) never branches on provider identity.
73 - `/identity` PII response fields (`name`, `phone`, `street_address`, `locality`, `region`, `postal_code`, `country`) match the assignment exactly; the address sub-shape matches the OIDC Core `address` claim (`research-oidc-address-claim.md`) — reuse those member names verbatim in Go structs, do not rename them.
74 - Leave the interface's `Authenticate` signature able to accommodate a future non-password-based provider (e.g., OAuth2/OIDC redirect flow) without redesigning `FetchIdentity` — flagged as a design constraint on the interface shape, not something to build now.
75
76 **Out of scope for 03 — do not re-derive, read the owning track instead:**
77
78 - The authn/authz *mechanism* for the search/retrieve API — `../02-ai-security-architecture/CLAUDE.md`.
79 - How `user_credential.method`'s discriminator is physically modeled (columns vs. side tables) and all schema/migration decisions — `../05-data-ops/CLAUDE.md`.
80 - The `/auth` and `/identity` field names and JSON shapes — fixed by the assignment text itself, not a design decision any track owns.
81 - Whether the framing narrative calls the password baseline "deliberate" — it does not, as of S2 (`decisions/password-baseline-debate.md`); 03 need not engage with that question at all, it only affects prose in this track's docs.
82
83 {
"command": "echo \"checking for SendMessage-like tools\""
}checking for SendMessage-like tools
{
"file_path": "/Users/warrenthompson/Source/LoginID/01-product-industry-research-design/industry-framing.md"
}1 # Industry & Competitive Framing
2
3 Prepared for the `01-product-industry-research-design` track. See `../CLAUDE.md` for the full assignment text and `./CLAUDE.md` for this track's scope and boundaries. Builds directly on `research-loginid-company.md` and `research-oauth-history.md` in this directory — read those first for sourcing; this document is the synthesized argument, not new research.
4
5 **AI tooling note:** produced by Claude Code (Sonnet 5) running as a standing track-owner subagent for this track (persona: Naomi Voss), reading this track's own CLAUDE.md as its brief and the two prior research passes as its evidence base. No new web research was done for this document — it is a synthesis pass over material already gathered.
6
7 ## Who is this for
8
9 Two audiences read this document, and the framing below is written to serve both without contradiction:
10
11 1. **LoginID's technical evaluators.** They already know their own market. What they're grading here is whether a candidate can place a deliberately simple assignment (password-based credentials, a bare username/password IDP connector) accurately on the map of where the industry actually is — and say so plainly rather than either quietly building an insecure relic or over-engineering a FIDO2 flow nobody asked for.
12 2. **Whoever would actually consume this system if it were built for real** — an internal team building a profile-search admin tool, or a product team wiring up an onboarding flow that calls out to a third-party IDP connector. This document's persona/use-case section (see `personas-use-cases.md`) exists because "who is this for" has a real answer beyond the interview panel, and that real answer should shape the design.
13
14 ## 1. Where this assignment sits in the identity/IAM landscape
15
16 The three questions asked — a DAO for `user_profile`/`user_credential`, a searchable REST API over that data, and a connector to third-party identity providers using `/auth` (username/password → token) and `/identity` (token → PII) — describe a **classic CIAM (Customer Identity and Access Management) backend**: the boring, load-bearing plumbing that sits underneath every "log in" button. This space is large, mature, and crowded:
17
18 - **Full-platform CIAM**: Okta/Auth0, Microsoft Entra ID, Ping Identity — passwordless is one feature among many (SSO, MFA, lifecycle management, B2B federation).
19 - **Passkey/FIDO2 specialists**: LoginID itself, HYPR, Beyond Identity, Transmit Security, Corbado, Hanko, Descope — narrower, deeper on one authentication method.
20 - **IDP-aggregation/identity-verification vendors**: services whose entire business is "collect PII from a person once and let many relying parties query it" — the same shape as the assignment's `/identity` endpoint, at a bigger scale.
21
22 The assignment sits at the intersection of the first and third categories, but built at the "baseline reference implementation" layer rather than the polished-product layer — which tracks with the assignment's own instructions that code doesn't need to be complete, and that reasoning matters more than a finished product.
23
24 ## 2. The deliberate gap: what LoginID sells vs. what LoginID is asking for
25
26 This is the framing that most needs to be said out loud, because it's easy to miss and, once seen, it reframes the whole submission.
27
28 LoginID's actual product is FIDO2/WebAuthn passkey authentication: asymmetric device-bound credentials unlocked by biometrics, with **no shared secret ever transmitted or stored server-side** (see `research-loginid-company.md` §1). The assignment, by contrast, specifies:
29
30 - `user_credential (username, method, password)` — a shared-secret password field, the exact thing passkeys exist to eliminate.
31 - A third-party `/auth` endpoint authenticating with `{"username", "password"}` — a legacy password-grant pattern, not an OAuth2/OIDC delegated-authorization flow.
32
33 **This is the conventional, industry-standard credential baseline** used across CIAM backends generally, not a signal we're reading as unique to LoginID — a resolved S2 debate concluded that "LoginID chose this gap deliberately" is an unfalsifiable claim about the assignment author's intent, with no artifact in the assignment text to distinguish it from an ordinary take-home template (see `decisions/password-baseline-debate.md`). We don't need that inference to justify what matters here, which is building the baseline correctly regardless of who asked for it:
34
35 - **Name the gap explicitly** rather than silently building an insecure password store as if it were best practice, or silently substituting a FIDO2 flow the assignment didn't ask for (which would answer a different question than the one asked).
36 - **Design the seams so the gap is closeable later without a rewrite.** The `user_credential.method` field is the natural extension point — a value like `"password"` today, `"webauthn"` or `"passkey"` tomorrow, without changing the table shape. The third-party connector's pluggable-provider design (see `api-connector-design-rationale.md`) is the equivalent seam on the IDP side — a modern OIDC/OAuth2 provider should slot in next to the ABC/XYZ password-grant providers without a redesign.
37 - **Still handle what's actually being asked with the rigor the brand implies** — hashed/salted passwords at minimum if a password field must exist at all, least-privilege API auth on search/retrieve, secrets hygiene for stored third-party access tokens. (Full mechanics of this are `../02-ai-security-architecture/CLAUDE.md`'s scope; this document only asserts the product-narrative reason it matters — LoginID's whole brand *is* auth security, so an unhashed password field in the reference implementation, however illustrative, would read as tone-deaf.)
38
39 In short: **the assignment's design is the industry-standard "before" baseline; LoginID's product is one credible "after."** The strongest submission narrative is "here is a solid, correctly-built baseline, with the seams marked for where it could evolve toward passkeys" — stated as forward-looking architecture, not as a claim about what the assignment's author had in mind.
40
41 ## 3. The adjacent frontier: agentic identity, and why it's relevant but out of scope
42
43 Two things are converging on LoginID's roadmap that are each worth naming, but neither should be built into this submission's actual code:
44
45 - **LoginID's own 2025–2026 repositioning** toward "agentic commerce" — authenticating AI agents transacting on a human's behalf, with scoped/signed/revocable mandates (`research-loginid-company.md` §1, §3). This is company-stated strategy, not independently confirmed, and it's a genuine pivot from "passkey login for any site" to "identity/payment-authorization layer for AI agents."
46 - **AAuth / the broader machine-identity protocol space** (`research-oauth-history.md`) — a proposal (Dick Hardt, editor of OAuth 2.0/RFC 6749, not a co-author of OAuth 1.0/RFC 5849 — see that document for why the precise credential matters) addressing a different layer of the stack: authorizing *HTTP clients/agents* to services, versus FIDO2's job of authenticating *humans* to services. The two are complementary, not competing — a mature identity platform plausibly needs both, with passkey-based human authentication anchoring the root of trust that agent-identity protocols delegate from.
47
48 **Relevance to this assignment:** the same critique AAuth levels at static, pre-registered client credentials (API keys that leak, no portable identity, one-time upfront consent rather than per-request authorization) applies loosely to why a third-party-IDP connector built as a static username/password integration is a deliberately dated pattern — and why the connector's provider abstraction should be designed with an eye toward OAuth2/OIDC-style delegated flows as the natural next step, the same way `user_credential.method` is the seam for FIDO2 on the human-authentication side.
49
50 This belongs in the write-up as forward-looking context, not as scope creep — nobody asked for an agent-identity protocol implementation, and building one would bury the actual assignment under speculative complexity. One paragraph in the submission's narrative, pointing at the seam, is the right amount of ambition.
51
52 ## 4. Competitive takeaway for the submission's narrative
53
54 Framed as a one-line thesis for whoever reads the final packet: *this submission is a correctly-scoped, industry-standard credential baseline, built with explicit extension points toward FIDO2/passkey credentials, OIDC-style IDP federation, and (further out) agent-identity delegation — not because we claim to know that's what LoginID intended by the gap, but because those are the same extension points any CIAM backend eventually needs, and naming them shows the same market fluency without resting on an unfalsifiable read of the assignment author's mind.*
55 {
"file_path": "/Users/warrenthompson/Source/LoginID/05-data-ops/migration-approach.md"
}1 # Migration Approach 2 3 Owner: 05-data-ops (Priya Nandakumar). **Primary receiver: `../04-infra-devops/`** — this is one of the three inputs that track is gated on. Secondary receiver: `../03-engineering-delivery/`, which owns the migration files themselves alongside the DAO code. 4 5 Written to the project's hand-off standard: 04 should be able to act on this without re-deriving my reasoning. Where I state a *requirement*, the mechanism is 04's to choose; where I state a *mechanism*, it is because the requirement cannot be met without it, and I say why. 6 7 Status: draft, pending one devil's-advocate objection turn (S4 in `./PLAN.md`). The tool choice and the single-runner requirement are the two things most worth attacking. 8 9 ## 1. Tool: goose 10 11 `goose` (plain-SQL-first, minimal dependencies). Reasoning is in `./multi-db-strategy.md` and `./research-data-ops-best-practices.md` §3: this schema is "mostly shared DDL, occasionally per-backend", and goose's loose directory model fits that better than golang-migrate's stricter paired up/down files. golang-migrate is an equally defensible tool and I will not contest a swap if 03 already has tooling preferences — **nothing in §3–§6 below changes if the tool changes.** Atlas is the schema-as-state alternative worth knowing about; it is more than this take-home needs. 12 13 Version numbering: timestamped filenames during development (avoids collisions when two branches add a migration), converted to sequential with `goose fix` before anything is tagged. Not a decision 04 needs to act on — noted so nobody "fixes" the inconsistency later. 14 15 ## 2. Layout 16 17 ``` 18 migrations/ 19 shared/ # applied to every backend, in order 20 postgres/ # Postgres + CockroachDB only 21 sqlite/ # currently empty, kept so its emptiness is deliberate rather than forgotten 22 ``` 23 24 `shared/` carries `auth_method`, `user_profile`, `user_credential` and the `auth_method` seed row. `postgres/` currently holds exactly one file — the `pg_trgm` GIN index on `user_profile.name`, which has no SQLite equivalent and is omitted there rather than faked. That parity gap is documented and deliberate (`./multi-db-strategy.md`); SQLite is the local/dev/demo backend, not a production peer. 25 26 **Mechanism 04 needs:** goose applies one directory per invocation, so a migration run is **two invocations** — `shared/` then the backend directory — and each needs its own version table (`goose -table goose_db_version_shared`, then `-table goose_db_version_<driver>`). One shared table across two directories would interleave version numbers from independent sequences and corrupt the history. Both invocations must succeed for the run to count as successful. 27 28 **One open item, flagged rather than buried:** whether a single `shared/` DDL series survives all three backends depends on type names (`UUID`, `BOOLEAN`, `TIMESTAMPTZ`) passing through SQLite's type-affinity rules without surprises — SQLite assigns NUMERIC affinity to type names it does not recognise, which is benign for text UUIDs but is exactly the kind of thing that is benign until it isn't. This is under review in S1. **If the review forces a split, the fallback is per-backend `CREATE TABLE` files and `shared/` reduced to seed data — the three-directory layout, the two-invocation mechanism, and everything in §3–§6 are unchanged.** 04 can build against this now; the open item cannot invalidate the infra work. 29 30 ## 3. Requirements on 04 — the part I actually need decided 31 32 **R1. Migrations run as a separate step that completes before any service replica starts.** Not on service boot. Two replicas booting together would both attempt to migrate, and see R2 for why the database will not save you from that. A dedicated job/init step that must exit zero before the service rolls. 33 34 **R2. Exactly one migration runner at a time, enforced by the platform — not by the database.** This is the requirement I most want 04 to read carefully, because the obvious mechanism does not work here. `pg_advisory_lock` is the standard answer on PostgreSQL, but on CockroachDB **session-scoped advisory locks error out, and the `pg_try_advisory_lock` variants silently no-op** — a try-lock returns as though it succeeded and lets a second runner proceed (sourced in `./research-cockroachdb-postgres-semantics.md` §5; only transaction-scoped `pg_advisory_xact_lock` works there). On SQLite the concept is meaningless. A silent false success is a worse failure than an error. Since this project's whole premise is one schema across all three backends, a locking strategy that only works on one of them is not a strategy. So mutual exclusion has to come from the orchestration layer — a job with parallelism 1, a pipeline stage that cannot run concurrently with itself, whatever 04's platform offers. If 04 concludes the platform cannot guarantee this, tell me and I will treat it as a real constraint on the design rather than a detail. 35 36 **R3. The service fails closed when the schema is behind.** On startup the service compares the applied version against the version it was built against and refuses to serve if it is behind, rather than serving traffic against a schema missing a column it will query. The requirement is mine; whether the expected version is embedded at build time or derived from the migration files shipped in the image is 03's and 04's call. 37 38 **R4. Two distinct database credentials, not one.** 39 - *Migration runner*: DDL rights (`CREATE`/`ALTER`/`DROP`, index creation) plus, on PostgreSQL only, whatever `CREATE EXTENSION pg_trgm` requires — on managed Postgres this is usually an allow-listed extension, otherwise it needs elevated privileges or the platform pre-installing it. CockroachDB needs nothing here; trigram indexes are native. SQLite has no privilege model at all. 40 - *Runtime service user*: DML only (`SELECT`/`INSERT`/`UPDATE`/`DELETE`). **No DDL.** A running service has no legitimate reason to alter the schema, and a service account that can `DROP TABLE user_profile` turns a SQL-injection finding into data destruction rather than data disclosure. 41 42 Both credentials belong on 02's secrets inventory; I am flagging the requirement, not claiming that surface. 43 44 **R5. Rollback is not the recovery plan for data.** Down migrations will exist for the base schema and are genuinely useful in development. They are not a recovery mechanism in production: reversing a migration that dropped a column does not bring the data back, and the data in question here is PII. Recovery from a destructive migration is backup-restore, and backup policy is 04's. I raise it because "we have down migrations" reads like a safety net and is not one. 45 46 ## 4. Seed data 47 48 The `auth_method` table is seeded with one row, `"password"`. It ships as a migration file in `shared/`, not as a runtime bootstrap in the service — a service that writes its own lookup rows on boot is a service that can write them wrongly, concurrently, on every replica. 49 50 Idempotent via `INSERT ... ON CONFLICT (name) DO NOTHING`, which the unique constraint on `auth_method.name` already supports and which all three backends accept (the same portable upsert form the DAO uses — see `./multi-db-strategy.md`). goose will not re-run an applied migration anyway; the `ON CONFLICT` makes the file safe against a database someone seeded by hand, which in practice is how this breaks. 51 52 New auth methods after `"password"` — `passkey`, `otp`, `oidc` — are **inserted as data, not added as migrations**. That is the entire point of the lookup-table design over a native enum: adding a supported method is an operational action, not a schema change and not a deploy. If 04 finds itself asked to write a migration to add an auth method, that is a signal something has been misunderstood upstream — flag it to me. 53 54 ## 5. Explicitly not owned by this document 55 56 Named so 04 does not read a boundary into this doc that isn't there: where the databases actually run; how the pipeline invokes goose; environment topology and how many of them there are; backup schedule and retention of backups; connection pooling; monitoring of migration runs. All 04's. This document says *what must be true* about migration execution and *why*, and stops there. 57 58 Separately not mine: the **retention sweep and deletion job** are governance mechanisms, not schema migrations, and they are specified in `./pii-governance.md` (policy shape) — not here. Do not implement retention as a migration. 59 60 ## 6. What changes if the abstraction ruling changes 61 62 S3 in `./PLAN.md` is an open three-hats run on the multi-DB abstraction, including codegen (sqlc) as a real option that has been name-checked but never argued here. For honesty about what is settled: if codegen wins, the DAO's query layer changes and 03's work changes with it — **but migrations do not.** sqlc generates Go from SQL; it does not manage schema versions. The layout, the two-invocation mechanism, R1–R5, and the seed-data rule survive that ruling intact. Nothing in this document is waiting on S3. 63 64 --- 65 66 *AI tooling note: drafted by Claude Opus 5 in the 05-data-ops track-lead session, from this track's own research doc and strategy doc, then put through a single devil's-advocate objection turn (Beatriz Achterberg, Spreadsheet archetype, sonnet) before hand-off. Per `../CASTING.md` §5, the objection and its disposition are recorded in `./decisions/`.* 67
{
"command": "python3 ~/.claude/skills/profile-gen/scripts/show_profile.py --profile ./profiles/wesley-okonkwo/wesley-okonkwo.md --root /Users/warrenthompson/Source/LoginID/04-infra-devops"
}{"name": "Wesley Okonkwo", "mode": "hud", "hud": true, "badge": false, "background": false, "inline": false}