HR-ATS-Portal/docs/architecture/00-scope-classification.md

585 lines
86 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 00 — Scope Classification
## Status / Scope of this document
This document is the scope baseline for the Utopia Brands internal HR Recruitment & ATS
platform ("TalentFlow"). It does four things and nothing else:
1. Assigns a stable `REQ-` identifier to every **confirmed** requirement, so every later
document in this package — data model, API design, module design, phasing, traceability
matrix — can cite a requirement instead of restating it.
2. Separates confirmed requirements from **proposed**, **deferred** and **rejected** scope,
so nobody has to guess which of the three a given feature is.
3. Gives a verdict on **every** unconfirmed idea raised in the assignment, with the specific
condition that would change that verdict.
4. Records the **open business decisions** with a recommended assumption for each, so that
design and implementation are never blocked waiting on a business answer.
**There is no meeting transcript in this repository.** A `find` across the repository and
`~/Documents` to depth 2 returned no transcript and no recruitment source document — only
the BRD `.docx` this project itself produced (findings §B). Nothing in this package was
derived from a recorded conversation, and no document in this package should imply
otherwise. **The assignment prompt is therefore the authoritative requirements source.**
`docs/TalentFlow-ATS-Business-Requirements-v1.0.docx` (findings §A) is a **corroborating
internal artefact, not an independent source** — it was written by this project, so where it
and the assignment prompt differ, the assignment prompt wins. It is cited here as `BRD §…`
because it carries the only stable `FR-`, `AI-`, `NFR-`, `BO-` and `OQ-` numbering that
exists, and downstream documents need those handles.
This document is consistent with `_decisions.md`. Five genuine inconsistencies **inside**
`_decisions.md` (Part 1 versus Part 2) are recorded as risks in §10 rather than silently
resolved here.
---
## 1. Requirements source and authority
| Source | Status | How it is used here |
|---|---|---|
| Assignment prompt §3 (confirmed requirements) | **Authoritative** | Becomes the `REQ-` set in §2 |
| Assignment prompt §4 (unconfirmed ideas) | **Authoritative list of open ideas** | Every item gets a verdict in §6 |
| Assignment prompt §5.1§5.6 (data-model principles) | **Authoritative, non-negotiable** | Folded into the `REQ-` set as constraints |
| Assignment prompt §29 (open questions) | **Authoritative** | Becomes the `OBD-` table in §8 |
| `_repo-findings.md` | **Verified evidence** (inspection, 2026-07-29) | Cited for every claim about what the repository does or does not contain |
| `_decisions.md` | **Binding** architecture and database decisions | Every verdict and phase in this document is consistent with it |
| `TalentFlow-ATS-Business-Requirements-v1.0.docx` | **Corroborating, self-produced** | Provides `FR-`/`AI-`/`NFR-`/`OQ-` numbering only |
| Meeting transcript | **Does not exist** (findings §B) | Not used, not implied, not referenced |
| Existing repository code | **Prototype only** — no backend, DB, auth, tests, build, Docker, env files (findings §B) | Source of evidence about gaps, and of the retained assets (REQ-NFR-09/10/11) |
### How to classify a new item
```mermaid
flowchart TD
A["New scope item"] --> B{"Stated in assignment §3<br/>or §5.1-§5.6?"}
B -- yes --> C["CONFIRMED - assign REQ- id"]
B -- no --> D{"Required to satisfy<br/>a confirmed REQ?"}
D -- yes --> E["PROPOSED - assign PROP- id,<br/>needs owner sign-off, not re-debate"]
D -- no --> F{"Wanted, but not needed<br/>for the phase in hand?"}
F -- yes --> G["DEFERRED - assign DEF- id<br/>with phase and entry condition"]
F -- no --> H{"Would it break a constraint<br/>or add unearned cost?"}
H -- yes --> I["REJECTED - record reason<br/>and the condition that reopens it"]
H -- no --> J["OPEN BUSINESS DECISION -<br/>assign OBD- id and proceed<br/>on the recommended assumption"]
```
---
## 2. Confirmed requirements
Rules for this table. One line per requirement. `REQ-` ids are **stable and never reused**
if a requirement is dropped, its id is retired, not recycled. `Source` cites the assignment
constraint or the BRD handle. `Module` is the owning module from `_decisions.md`. `Phase`
follows the `_decisions.md` phasing (0 = hardening/foundations, 1 = vertical slice, 24 as
listed there). A requirement being confirmed says nothing about *when* it lands — see the
Phase column, and §5 for what that means in practice.
### 2.1 Raw intake and inbound channels
| REQ | Requirement | Source | Module | Phase |
|---|---|---|---|---|
| REQ-INT-01 | Every inbound submission is recorded as an immutable raw-intake row before any candidate or application can exist. | Constraint §5.1 | `intake` | 1 |
| REQ-INT-02 | All eleven inbound channels normalise into one candidate record structure. | BO-4, BRD §8.1 | `integrations_inbound` | 1 / 3 / 4 |
| REQ-INT-03 | Parsing is attempted for every inbound document regardless of channel. | BRD §6.3 | `document_parsing` | 1 |
| REQ-INT-04 | Parse failures are surfaced to the recruiter with the document retained, never silently dropped. | BRD §6.3, FR-7 | `intake` | 1 |
| REQ-INT-05 | Each document reports a per-document state of Parsed, Pending, Parsing or Failed. | FR-7, BRD §9.2 | `intake` | 1 |
| REQ-INT-06 | The inbox is a single triage queue with read/unread state and per-source attribution. | FR-2 | `intake` | 1 |
| REQ-INT-07 | Where the parser cannot determine a field with confidence, the field is left empty rather than guessed. | BRD §6.3 | `document_parsing` | 1 |
| REQ-INT-08 | Redelivery of the same channel message is idempotent and never produces a second intake row. | BRD §11 (cross-channel dedupe) | `intake` | 1 |
| REQ-INT-09 | A submission that can never become a candidate has a terminal representable state carrying no candidate. | Constraint §5.1; findings §F | `intake` | 1 |
| REQ-INT-10 | Manual recruiter CV upload is a channel through the same intake path, not an exception route. | FR-7 | `intake` | 1 |
| REQ-INT-11 | Attachments are held in object storage with checksum and virus-scan state, addressed from the intake row. | BRD §7.4 | `files` | 1 |
| REQ-INT-12 | A parse may be retried, and a parser-version change replayed, without mutating the arrival record. | Constraint §5.1 | `intake` | 1 |
### 2.2 Candidate identity
| REQ | Requirement | Source | Module | Phase |
|---|---|---|---|---|
| REQ-CAN-01 | Candidate identity is a separate entity from job applications; one candidate may hold many applications over time. | Constraint §5.2; findings §F (`js/data.js:117-127`) | `candidate` | 1 |
| REQ-CAN-02 | The candidate master record supports filter, sort, rank, bulk actions and a full profile view. | FR-4 | `candidate` | 1 |
| REQ-CAN-03 | A candidate cannot be created without a prior raw-intake row, including for manual entry. | Constraint §5.1 | `candidate` | 1 |
| REQ-CAN-04 | A candidate must carry at least one usable contact channel; a malformed email cannot produce a candidate. | Constraint §5.1 | `candidate` | 1 |
| REQ-CAN-05 | One normalised email address resolves to at most one live candidate identity. | Constraint §5.2 | `candidate` | 1 |
| REQ-CAN-06 | The candidate record carries the BRD §9 field groups: identity, experience, education, skills and ownership. | BRD §9 | `candidate` | 1 |
| REQ-CAN-07 | CV revisions are retained as candidate documents, each traceable to the intake it arrived on. | BRD §6.3, §9 | `candidate` | 1 |
| REQ-CAN-08 | Skills resolve against a controlled vocabulary with aliases; unmapped parser labels remain storable and reviewable. | BRD §9.2 | `candidate`, `config` | 1 |
| REQ-CAN-09 | Candidate-facing identifiers are non-enumerable. | Constraint (data protection) | `candidate` | 1 |
| REQ-CAN-10 | Previously sourced candidates not hired for their original role can be retained and re-surfaced. | FR-5 | `talent_pool` | 3 |
| REQ-CAN-11 | Experience is stored at a precision that preserves ordering, not rounded to whole years. | findings §F (`js/data.js:121`) | `candidate` | 1 |
### 2.3 Duplicate detection and merge
| REQ | Requirement | Source | Module | Phase |
|---|---|---|---|---|
| REQ-DUP-01 | Duplicate detection runs against the existing candidate base for every new candidate. | BRD §6.3 | `duplicate_review` | 1 |
| REQ-DUP-02 | Suspected duplicates are **marked**, never deleted. | BRD §6.3 | `duplicate_review` | 1 |
| REQ-DUP-03 | Duplicates arriving through different channels are detected and marked for recruiter review. | BRD §11 | `duplicate_review` | 1 |
| REQ-DUP-04 | Merge requires manual human review; nothing merges automatically, ever. | Constraint | `duplicate_review` | 2 |
| REQ-DUP-05 | Merge is reversible, with enough recorded detail that reversal is a mechanical replay. | Constraint | `duplicate_review` | 2 |
| REQ-DUP-06 | A "not a duplicate" decision persists and suppresses re-flagging of that pair. | Constraint (reviewability) | `duplicate_review` | 1 |
| REQ-DUP-07 | The reviewer sees per-signal evidence, not only a composite similarity score. | BRD §7.3 (explainability) | `duplicate_review` | 2 |
| REQ-DUP-08 | Merge never deletes rows and never double-counts history in reports. | Constraint §5.5 | `duplicate_review` | 2 |
### 2.4 Requisitions, requirements and versioning
| REQ | Requirement | Source | Module | Phase |
|---|---|---|---|---|
| REQ-JOB-01 | Requisitions are managed across department, business unit, location, employment type, grade and status. | FR-3, BRD §9.1 | `requisition` | 1 |
| REQ-JOB-02 | Jobs are versioned; a published version is immutable. | Constraint §5.3; findings §F (`js/data.js:85-108`) | `requisition` | 1 |
| REQ-JOB-03 | Requirements are version-scoped and weighted; they can never be edited in place on the job. | Constraint §5.3 | `requisition` | 1 |
| REQ-JOB-04 | Scoring configuration is versioned, and its binding to a job is itself historical. | Constraint §5.3 | `scoring` | 1 |
| REQ-JOB-05 | Job statuses are Open, On Hold, Closed, Draft. | FR-3, BRD §9.2 | `requisition` | 1 |
| REQ-JOB-06 | A posting records the exact requisition version text an applicant read. | Constraint §5.3 (defensibility) | `requisition` | 1 |
| REQ-JOB-07 | Publishing a requisition version requires approval by a Hiring Manager or Department Head. | BRD §4 (Approve level); chain scope is OBD-12 | `requisition` | 1 |
| REQ-JOB-08 | Pipeline stages, statuses and vocabularies are editable reference data, not code constants. | BRD §9.2; findings §F | `config`, `pipeline` | 1 |
### 2.5 Applications and pipeline
| REQ | Requirement | Source | Module | Phase |
|---|---|---|---|---|
| REQ-APP-01 | An application is the join between a candidate and a requisition version, and is the unit ATS scores attach to. | Constraint §5.2, §5.6 | `application` | 1 |
| REQ-APP-02 | The pipeline is a kanban board across the seven stages with drag-and-drop progression. | FR-6, BRD §9.2 | `pipeline` | 2 |
| REQ-APP-03 | Every stage and status change is recorded with actor, actor kind, reason and timestamp. | Constraint §5.5 | `application` | 1 |
| REQ-APP-04 | Each application retains source attribution back to the intake it originated from. | BO-4, FR-2 | `application` | 1 |
| REQ-APP-05 | At most one live application per candidate per job; reapplication is a distinct, ordered attempt. | Constraint §5.2 | `application` | 1 |
| REQ-APP-06 | A transition to a terminal-negative state requires a human actor. | BRD §7.1 | `application` | 1 |
| REQ-APP-07 | Time-in-stage is directly queryable for funnel and SLA reporting. | FR-9, FR-17 | `application`, `analytics` | 1 |
### 2.6 Recruiter assignment
| REQ | Requirement | Source | Module | Phase |
|---|---|---|---|---|
| REQ-ASG-01 | Assignment is flexible: several people may be attached to one requisition or application in distinct roles. | Constraint §5.4; findings §F (`js/data.js:96,123`) | `assignment` | 1 |
| REQ-ASG-02 | Assignment is historical — "who owned this in March" is answerable from stored data. | Constraint §5.4 | `assignment` | 1 |
| REQ-ASG-03 | Exactly one current primary recruiter exists per requisition at any instant. | Constraint §5.4 | `assignment` | 1 |
| REQ-ASG-04 | Per-recruiter workload, efficiency, SLA state and hiring trend are reportable. | FR-9 | `assignment`, `analytics` | 2 |
### 2.7 ATS scoring and explainability
| REQ | Requirement | Source | Module | Phase |
|---|---|---|---|---|
| REQ-SCR-01 | The ATS score is per application, never a property of the candidate. | Constraint §5.6; findings §F (`js/data.js:123`) | `scoring` | 1 |
| REQ-SCR-02 | The score is expressed on a 0100 scale. | BRD §6.2 | `scoring` | 1 |
| REQ-SCR-03 | Matched skills and missing skills are exposed alongside the score. | BRD §6.2, §11 | `scoring` | 1 |
| REQ-SCR-04 | A recommendation band is derivable from the score and exposed with it. | BRD §6.2 | `scoring` | 1 |
| REQ-SCR-05 | The score is reproducible: the same candidate and role yield the same score absent a model or data change. | BRD §6.2 | `scoring` | 1 |
| REQ-SCR-06 | A score change caused by a model version change is traceable to that version. | BRD §6.2 | `scoring` | 1 |
| REQ-SCR-07 | Every score carries the factors that produced it, at a level a recruiter can restate to a hiring manager. | BRD §7.3 | `scoring` | 1 |
| REQ-SCR-08 | Historical scores are never mutated; a rescore appends a new result and supersedes the old one. | Constraint §5.6 | `scoring` | 1 |
| REQ-SCR-09 | Sorting the candidate list by AI Relevance returns a stable, reproducible order. | BRD §11 | `scoring` | 1 |
| REQ-SCR-10 | Protected characteristics — name, age, gender, nationality, photograph — are not ranking features. | BRD §7.2 | `scoring` | 1 |
| REQ-SCR-11 | Ranking across the full candidate base does not degrade the interface. | NFR-9 | `scoring` | 1 |
| REQ-SCR-12 | A score pins every input that could change it: requisition version, scoring config version, scorer code version, document and parse. | Constraint §5.3, §5.6; BRD §6.2 | `scoring` | 1 |
### 2.8 AI capabilities
All fifteen are confirmed requirements. Priority is the BRD's; Phase is `_decisions.md`.
Today all fifteen are interface preview only, with "Model endpoint · Not connected"
(BRD §6) — nothing in the repository performs inference (findings §B).
| REQ | Capability | Priority | Module | Phase |
|---|---|---|---|---|
| REQ-AIC-01 | Resume Ranking — ordered shortlist per job by fit (AI-1). | P0 | `scoring` | 1 |
| REQ-AIC-02 | Candidate Matching — best-fit open roles across all Utopia brands (AI-2). | P0 | `scoring`, `talent_pool` | 1 |
| REQ-AIC-03 | Resume Summary — structured one-click summary for reviewer hand-off (AI-3). | P0 | `ai_orchestration` | 1 |
| REQ-AIC-04 | Skill Gap Analysis — skills present and missing, per candidate and per pipeline (AI-4). | P1 | `scoring` | 2 |
| REQ-AIC-05 | Natural Language Search over the candidate base (AI-5). | P1 | `assistant`, `candidate` | 2 |
| REQ-AIC-06 | JD Generator from a short requisition brief (AI-6). | P1 | `ai_orchestration`, `requisition` | 2 |
| REQ-AIC-07 | Email Generator — candidate correspondence in Utopia brand voice (AI-7). | P1 | `ai_orchestration`, `notifications` | 3 |
| REQ-AIC-08 | Interview Question Generator — role-specific question banks (AI-8). | P1 | `ai_orchestration`, `interview` | 3 |
| REQ-AIC-09 | Recruitment Analytics — natural-language questions across the funnel (AI-9). | P1 | `assistant`, `analytics` | 3 |
| REQ-AIC-10 | Recruiter Copilot — assistant in every workflow with current-screen context (AI-10). | P1 | `assistant` | 2 (read) / 4 (full) |
| REQ-AIC-11 | Candidate Comparison — side-by-side against role criteria (AI-11). | P2 | `scoring` | 3 |
| REQ-AIC-12 | Candidate Recommendation — proactive suggestions of who to contact (AI-12). | P2 | `talent_pool`, `worklist` | 4 |
| REQ-AIC-13 | Offer Letter Generator from offer parameters (AI-13). | P2 | `ai_orchestration`, `offer` | 3 |
| REQ-AIC-14 | Hiring Forecast — time-to-hire prediction and pipeline health flags (AI-14). | P2 | `analytics` | 4 |
| REQ-AIC-15 | Hiring Insights — weekly generated digest of notable movements (AI-15). | P2 | `analytics`, `notifications` | 4 |
### 2.9 AI governance, fairness and audit
| REQ | Requirement | Source | Module | Phase |
|---|---|---|---|---|
| REQ-GOV-01 | No candidate may be rejected solely by an automated decision. | BRD §7.1; Constraint | `application` | 1 |
| REQ-GOV-02 | No offer is issued without explicit human confirmation. | BRD §7.1 | `offer` | 3 |
| REQ-GOV-03 | The interface identifies machine-generated content before a recruiter acts on it. | BRD §7.1 | frontend, `ai_orchestration` | 1 |
| REQ-GOV-04 | Disparate-impact evaluation is completed before production release and repeated on every model version change. | BRD §7.2 | `fairness_evaluation` | 3 |
| REQ-GOV-05 | Evaluation results are recorded and readable by the business, not held only in engineering. | BRD §7.2, §11 | `fairness_evaluation` | 3 |
| REQ-GOV-06 | Every AI-influenced decision is logged with model version, input reference, output and timestamp. | BRD §7.3 | `audit`, `ai_orchestration` | 1 |
| REQ-GOV-07 | Audit records are retained for the period required by the jurisdictions in which the requisition was posted. | BRD §7.3; jurisdictions per OBD-04 | `audit` | 1 |
| REQ-GOV-08 | Every AI invocation is individually addressable, versioned and reviewable after the fact. | Constraint | `ai_orchestration` | 1 |
| REQ-GOV-09 | The chatbot never bypasses access controls; every AI data access carries the asking human's identity. | Constraint | `assistant`, `identity` | 2 |
| REQ-GOV-10 | AI Studio reflects true per-capability availability; the "not connected" state clears only when a capability is live. | FR-19, BRD §11 | `ai_orchestration` | 1 |
| REQ-GOV-11 | TalentFlow remains fully operable with the AI service unavailable. | NFR-7, BRD §8.3, §11 | all | 1 |
| REQ-GOV-12 | AI output is a suggestion record that a human accepts; it is never a direct domain write. | Constraint | `ai_orchestration` | 1 |
### 2.10 Interviews
| REQ | Requirement | Source | Module | Phase |
|---|---|---|---|---|
| REQ-ITV-01 | Seven interview types across three meeting modes, with four lifecycle states. | FR-12, BRD §9.2 | `interview` | 2 |
| REQ-ITV-02 | Structured scorecards; an interviewer sees and scores only their own interviews. | FR-12, BRD §4 | `interview` | 2 |
| REQ-ITV-03 | Interview instants are stored in UTC alongside the organiser's wall-clock intent and IANA zone. | Constraint (findings §F: no tz discipline, `js/data.js:237`) | `interview` | 2 |
| REQ-ITV-04 | A participant cannot be double-booked across overlapping scheduled interviews. | Constraint (correctness) | `interview` | 2 |
| REQ-ITV-05 | A month view of scheduled interviews and hiring events exists. | FR-16 | `interview` (view) | 2 |
| REQ-ITV-06 | A submitted scorecard locks. | FR-12, BRD §7.3 | `interview` | 2 |
### 2.11 Assessments and offers
| REQ | Requirement | Source | Module | Phase |
|---|---|---|---|---|
| REQ-ASM-01 | Six assessment types can be assigned and scored. | FR-13, BRD §9.2 | `assessment` | 3 |
| REQ-ASM-02 | Assessments report Code Quality, Problem Solving and Time Management. | FR-13 | `assessment` | 3 |
| REQ-OFR-01 | Offers move through Draft, Sent, Negotiating, Accepted, Declined, Expired. | FR-14, BRD §9.2 | `offer` | 3 |
| REQ-OFR-02 | An offer revision is a new immutable offer version with its own approval, not a field edit. | Constraint §5.3, §5.5 | `offer` | 3 |
| REQ-OFR-03 | Issuing an offer requires an explicit human confirmation step that cannot be automated. | BRD §7.1 | `offer` | 3 |
### 2.12 Search
| REQ | Requirement | Source | Module | Phase |
|---|---|---|---|---|
| REQ-SRC-01 | The candidate base is filterable, sortable and rankable on structured attributes. | FR-4 | `candidate` | 1 |
| REQ-SRC-02 | The candidate base is searchable in plain English. | AI-5 | `assistant`, `candidate` | 2 |
| REQ-SRC-03 | Name and employer search is fuzzy and typo-tolerant. | BRD §6.3 (dedupe signals reuse) | `candidate` | 1 |
| REQ-SRC-04 | Relevance blending is attributable to a versioned configuration, not hardcoded. | Constraint §5.3; findings §F (`js/candidates.js:18`) | `candidate` | 1 |
### 2.13 Analytics, reporting and dashboard
| REQ | Requirement | Source | Module | Phase |
|---|---|---|---|---|
| REQ-ANL-01 | Eight headline KPIs: Open Jobs, Total Candidates, Interviews Today, Offers Accepted, Time to Hire, Time to Fill, Cost per Hire, Closed Jobs. | FR-1 | `analytics` | 2 |
| REQ-ANL-02 | Hiring-trend and pipeline visualisations on the dashboard. | FR-1 | `analytics` | 2 |
| REQ-ANL-03 | Hiring funnel, time-to-hire versus time-to-fill, department performance, and a saved report library. | FR-17 | `analytics` | 3 |
| REQ-ANL-04 | Analytics set: hiring trend, applications received, source breakdown, offer acceptance, pipeline distribution, applications by department, recruiter performance. | FR-18 | `analytics` | 2 |
| REQ-ANL-05 | Natural-language report and analytics generation. | AI-9, FR-17, FR-18 | `assistant`, `analytics` | 3 |
| REQ-ANL-06 | Leadership has a single view across all Utopia brands and departments. | BO-6 | `analytics` | 2 |
| REQ-ANL-07 | Analytics results are scoped to the viewer's role — a recruiter sees own, leadership sees aggregate. | BRD §4, FR-21 | `analytics`, `identity` | 2 |
| REQ-ANL-08 | Time to hire is measurable against the current 27-day baseline. | BO-2 | `analytics` | 2 |
| REQ-ANL-09 | Source and channel performance is comparable across the eleven inbound channels. | FR-8, FR-18, BO-4 | `analytics` | 3 |
### 2.14 Tasks, worklist and notifications
| REQ | Requirement | Source | Module | Phase |
|---|---|---|---|---|
| REQ-WRK-01 | Recruiter actions are assignable and trackable, with open-item counts surfaced in navigation. | FR-10 | `worklist` | 2 |
| REQ-WRK-02 | AI next-best-action suggestions appear as tasks, visibly marked as AI-originated. | FR-10, AI-12, BRD §7.1 | `worklist` | 2 |
| REQ-WRK-03 | System and hiring-event notifications exist with unread counts. | FR-20 | `notifications` | 2 |
### 2.15 Identity, access control and application security
| REQ | Requirement | Source | Module | Phase |
|---|---|---|---|---|
| REQ-SEC-01 | Every request is attributable to an authenticated user; there is no anonymous internal access. | findings §D (no auth of any kind) | `identity` | 0 |
| REQ-SEC-02 | RBAC is enforced server-side over a seeded catalogue of roles, permission-controlled modules and permission verbs. The catalogue is **configuration, not structure** — Phase 1 seeds 7 roles × 25 modules × 10 verbs (`05` §2.1/§2.2/§2.9); adding a role or module is an INSERT plus grants. The prototype's 8 roles / 13 modules / 8 permission types (`js/data.js:425-446`) are demo data derived from a `level` cutoff index (findings §D) and are **not** the requirement; BRD §4 is a 66-seat allocation, not a permission catalogue. | FR-21, BRD §3, §4 | `identity` | 1 |
| REQ-SEC-03 | There is exactly one authorization decision point, used by the UI, the API and the assistant alike. | Constraint (chatbot access control) | `identity` | 1 |
| REQ-SEC-04 | Interviewers can see only the candidates attached to their assigned interviews. | BRD §4 | `identity`, `interview` | 2 |
| REQ-SEC-05 | Match-score visibility per role is a configuration setting, not a hardcoded rule. | OQ-5 → OBD-05 | `identity`, `scoring` | 1 |
| REQ-SEC-06 | No data-derived value is rendered without output escaping, and a CSP without `unsafe-inline` for scripts is in force. | findings §E (34 unescaped `innerHTML` sites, `js/candidates.js:68,121`) | frontend | 0 |
| REQ-SEC-07 | Candidate-facing links are scoped, expiring, revocable tokens — never a bare entity identifier. | Constraint (data protection) | `identity` | 3 |
| REQ-SEC-08 | Security settings — SSO, 2FA, session timeout, password policy — are enforced, not display chrome. | findings §D (`js/settings.js:148-154`), FR-22 | `identity` | 1 |
| REQ-SEC-09 | The permission matrix governs behaviour; it is not a display widget. | findings §D (`js/rbac.js:78,111-112` — no `can()` exists) | `identity` | 1 |
### 2.16 Data protection and retention
| REQ | Requirement | Source | Module | Phase |
|---|---|---|---|---|
| REQ-DAT-01 | Candidate data is processed only within Utopia-controlled infrastructure or by a processor under a data-processing agreement. | BRD §7.4, §3.3 | platform | 1 |
| REQ-DAT-02 | Candidate data must not be used to train third-party foundation models. | BRD §7.4 | `ai_orchestration` | 1 |
| REQ-DAT-03 | Retention and deletion honour candidate rights requests, including within derived embeddings and indexes. | BRD §7.4 | `files`, `candidate` | 2 |
| REQ-DAT-04 | Every column on a candidate-touching table carries a machine-readable PII classification. | BRD §7.4 (enforceability) | platform | 1 |
| REQ-DAT-05 | No special-category data (diversity, health, accommodation) is stored in Phase 1. | BRD §7.2 scope decision | all | 1 |
| REQ-DAT-06 | Erasure and permanent history coexist: identifying data is removable without tearing holes in history or reporting. | Constraint §5.5 + BRD §7.4 | platform | 2 |
### 2.17 Current state, history and audit
| REQ | Requirement | Source | Module | Phase |
|---|---|---|---|---|
| REQ-HIS-01 | Current state and full history both exist, as separate stored things. | Constraint §5.5; findings §F (no history tables) | all domain | 1 |
| REQ-HIS-02 | Every history row carries actor, actor kind and reason. | Constraint §5.5 | all domain | 1 |
| REQ-HIS-03 | A single append-only audit log spans all modules and is queryable by actor and by candidate. | BRD §7.3, §11 | `audit` | 1 |
| REQ-HIS-04 | The application has no update or delete path into the audit log. | BRD §7.3 | `audit` | 1 |
| REQ-HIS-05 | Access events — profile viewed, export run, chatbot answer returned — are audited, not only data changes. | BRD §7.3, §7.4 | `audit` | 1 |
| REQ-HIS-06 | Requisition, requirement and scoring-config versions are immutable once published. | Constraint §5.3 | `requisition`, `scoring` | 1 |
### 2.18 Outbound publishing
| REQ | Requirement | Source | Module | Phase |
|---|---|---|---|---|
| REQ-PUB-01 | Requisition versions publish to eight external platforms with per-platform connection state and cost banding. | FR-8, BRD §8.2 | `integrations_outbound` | 4 |
| REQ-PUB-02 | Publishing is an authorised action with a cost implication, restricted above recruiter level. | BRD §8.2 (cost bands), §4 | `integrations_outbound` | 4 |
### 2.19 API contract
| REQ | Requirement | Source | Module | Phase |
|---|---|---|---|---|
| REQ-API-01 | The service is exposed over a documented, versioned HTTP JSON API. | BRD §8.3 | API layer | 1 |
| REQ-API-02 | Adopting a new model version requires no interface change. | BRD §8.3 | API layer | 1 |
| REQ-API-03 | The API degrades gracefully — AI absent rather than request failed. | BRD §8.3, NFR-7 | API layer | 1 |
| REQ-API-04 | Long-running operations (bulk ranking, batch parsing) are asynchronous with retrievable job status. | BRD §8.3 | API layer | 1 |
| REQ-API-05 | Rate limits and expected latency are published so the interface can set user expectations. | BRD §8.3 | API layer | 1 |
### 2.20 Non-functional and experience requirements (must not regress)
These are already met by the prototype and verified (findings §G). They are confirmed
requirements precisely because the migration must not lose them.
| REQ | Requirement | Source | Phase |
|---|---|---|---|
| REQ-NFR-01 | WCAG 2.1 AA contrast across all views in both themes. | NFR-1; verified 23 routes × 2 themes (findings §G) | 0 onward |
| REQ-NFR-02 | Usable from 320px to ultrawide without horizontal overflow. | NFR-2 | 0 onward |
| REQ-NFR-03 | Minimum 44×44px touch targets. | NFR-3 | 0 onward |
| REQ-NFR-04 | Light and dark themes following OS preference until the user chooses. | NFR-4 (`js/app.js:64,193,198`) | 0 onward |
| REQ-NFR-05 | Utopia Brands palette and typeface hierarchy per the brand guideline. | NFR-5 | 0 onward |
| REQ-NFR-06 | Works on iOS, Android, macOS and Windows browsers, with safe-area handling. | NFR-6 | 0 onward |
| REQ-NFR-07 | Interactive AI responses land within a latency threshold that does not interrupt recruiter flow. | NFR-8; ceiling is OBD-03 | 1 |
| REQ-NFR-08 | Recruiters act on AI output from the screens where they already work. | BRD §2.3 | 1 |
| REQ-NFR-09 | The existing 23-route information architecture is preserved as the screen backlog. | findings §G (`js/app.js:7-16`) | 0 onward |
| REQ-NFR-10 | The design system in `css/styles.css` is retained as the design contract, not rebuilt. | findings §G; `_decisions.md` | 0 onward |
| REQ-NFR-11 | The dependency-free canvas chart engine `js/charts.js` is retained rather than replaced by a charting dependency. | findings §G (`js/charts.js:339`) | 1 onward |
### 2.21 Money and time
| REQ | Requirement | Source | Phase |
|---|---|---|---|
| REQ-MON-01 | Every monetary value carries an explicit ISO-4217 currency; an amount without a currency is not storable. | findings §F (`js/data.js:126`, `js/offers.js:129` — bare integers, no currency) | 1 |
| REQ-MON-02 | The original amount and currency are immutable; any conversion is stored alongside the rate it used. | Constraint §5.3 (pinning) | 3 |
| REQ-MON-03 | All instants are stored in UTC; calendar-only values are stored as dates. | findings §F (hardcoded "today", `js/data.js:237`) | 1 |
---
## 3. Proposed requirements
Not stated in assignment §3, but **required** to satisfy something that is. Each needs an
owner's acknowledgement, not a re-debate — if a `PROP-` item is rejected, the confirmed
requirement it supports becomes unsatisfiable and that must be stated explicitly.
| PROP | Proposed requirement | Supports | Why it is proposed rather than confirmed | Owner | Phase |
|---|---|---|---|---|---|
| PROP-01 | Phase 0 escaping + CSP patch of the existing prototype, with a CI gate against new unescaped interpolation. | REQ-SEC-06 | §3 does not mention the prototype's XSS exposure; findings §E makes it P0 the moment real CV or mail data lands. 23 developer-days. | Talha Ahmed | 0 |
| PROP-02 | A recruiter-facing intake triage queue with explicit `needs_review`, `rejected_unusable` and `quarantined` outcomes. | REQ-INT-04, REQ-INT-09 | §3 requires failures be visible; it does not specify the surface. This is where OBD-06 lands operationally. | Talent Ops | 1 |
| PROP-03 | Candidate-facing access tokens (hashed, scoped, expiring, revocable) for status pages, upload links and interview confirmations. | REQ-SEC-07, REQ-CAN-09 | Emailed links leak by forwarding and archives; an unguessable id can never expire or be revoked. | Talha Ahmed | 3 |
| PROP-04 | A fairness-evaluation **gate**: no scoring configuration version becomes active without a passing evaluation reference. | REQ-GOV-04, REQ-GOV-05 | §3 requires evaluation before release; a gate is what makes that enforceable rather than procedural. | Legal + Talent Lead | 3 |
| PROP-05 | A machine-readable PII classification registry with a CI completeness check. | REQ-DAT-03, REQ-DAT-04 | Three jobs must read the classification (purge, subject-access export, non-production anonymisation); prose cannot be read by a job. | Ahmed Mujtaba | 1 |
| PROP-06 | Controlled vocabularies and templates administered through a back-office admin rather than a bespoke Settings UI in Phase 1. | REQ-JOB-08 | Removes FR-22 from the Phase 1 critical path without blocking anyone. | Talent Ops | 1 |
| PROP-07 | A reapplication cooling-off period with an audited override. | REQ-APP-05 | §3 requires reapplication support but sets no interval. A hard block invites recruiters to create duplicate candidates to evade it. | Talent Lead (OBD-08) | 1 |
| PROP-08 | Audit hash chaining plus a daily export of closed audit partitions to write-once storage. | REQ-HIS-03, REQ-HIS-04 | Grants and triggers prevent application tampering; only an off-box immutable copy provides an independent check. Stated as tamper *evidence*, not prevention. | Talha Ahmed | 2 |
| PROP-09 | An FX rate table plus a per-row pinned reporting currency for cross-jurisdiction compensation reporting. | REQ-MON-02, REQ-ANL-06 | Six posting jurisdictions and a single leadership view require conversion; converting at read time makes reports drift. | Finance + Talent Lead | 3 |
| PROP-10 | A non-identifying address list (agency mailboxes, `info@`, shared family addresses) excluded from the one-email-one-identity rule. | REQ-CAN-05 | Real agency and referral submissions will otherwise fail resolution and silently back up the review queue. | Talent Ops | 1 |
| PROP-11 | An unmerge confirmation screen that lists exactly which rows will remain with the survivor before the recruiter confirms. | REQ-DUP-05 | Rows created after a merge have no defensible pre-merge owner; showing the list converts an invisible surprise into an informed decision. | Ahmed Mujtaba | 2 |
| PROP-12 | Database-level prevention of interviewer double-booking. | REQ-ITV-04 | Application-level checks race under concurrent scheduling. | Talha Ahmed | 2 |
| PROP-13 | A score-explanation panel plus AI-provenance badges on every AI-derived value in the UI. | REQ-SCR-07, REQ-GOV-03 | §3 requires explainability and content labelling; neither exists as a UI surface today. | Ahmed Mujtaba | 1 |
| PROP-14 | A dedicated recruiting mailbox plus an Entra ID app registration with admin-consented mail scopes. | REQ-INT-02 | Inbound channel #1 is Outlook (BRD §8.1) and this dependency sits with corporate IT, outside the team's control. Start in Phase 0. | Corporate IT | 0 |
| PROP-15 | Retention purge implemented as pseudonymisation with skeleton-row retention, not row deletion. | REQ-DAT-06 | The only mechanism that lets permanent history and erasure coexist. | Legal | 2 |
| PROP-16 | Five end-to-end smoke journeys in CI plus module-facade test coverage. | REQ-GOV-11, all | There are no tests, no test runner and no CI anywhere today (findings §B). | Ahmed Mujtaba | 0 |
| PROP-17 | An intake-to-candidate resolution record with its own actor, timestamp, reason and evidence. | REQ-INT-01, REQ-INT-09 | §3 requires raw intake before candidate creation; the *decision* to promote is the reviewable artefact and needs to be a stored thing. | Talha Ahmed | 1 |
| PROP-18 | A stated per-capability availability model behind AI Studio, driven by real capability status. | REQ-GOV-10 | All fifteen capabilities are already visible in the UI, which creates the expectation that they are nearly done. | Talha Ahmed | 1 |
| PROP-19 | An `actor_unknown` data-quality signal wherever a history row was written without an attributable actor. | REQ-HIS-02 | Background jobs, imports and ad-hoc SQL fixes will bypass actor propagation; a visible gap is far better than a wrong attribution. | Talha Ahmed | 1 |
| PROP-20 | A parse-confidence threshold below which a field is left empty and flagged for review, per field. | REQ-INT-07 | BRD §11 asks for population "without recruiter re-keying"; real mixed-quality PDFs will not reach that unqualified standard, so the review step must be designed in rather than discovered. | Talent Ops | 1 |
---
## 4. Deferred requirements
Confirmed or accepted in principle, deliberately **not** in the phase in hand. Each carries
the phase it is expected in and the condition that must hold before it starts.
| DEF | Deferred item | Target phase | Entry condition | Consequence of deferring |
|---|---|---|---|---|
| DEF-01 | Per-requisition custom pipeline configuration (beyond the seven default stages). | 3 | A second job family demonstrably needs different stages. | Phase 12 uses one default pipeline; FR-6 still satisfied. |
| DEF-02 | Assessments module (FR-13, REQ-ASM-01/02). | 3 | Interviews and scorecards are live. | Assessment data stays outside the platform until Phase 3. |
| DEF-03 | Offers with approval chain (FR-14, REQ-OFR-*). | 3 | Requisition approval chain proven in Phase 1. | Offers continue outside the platform; a real gap for Talent Ops, stated. |
| DEF-04 | Outbound publishing to eight platforms (FR-8, REQ-PUB-*). | 4 | Job-board credentials and cost approval exist (OBD-13). | Requisitions are posted manually until then. |
| DEF-05 | Full tool-using assistant (AI-10, REQ-AIC-10 full). | 4 | RBAC enforced and audited; PROP-03 and REQ-GOV-09 verified. | Phase 2 assistant is read-only — see §6 row 15. |
| DEF-06 | Talent pool re-surfacing and rematch (FR-5, REQ-CAN-10). | 3 | Candidate/application split live and scoring stable. | Rejected candidates are not systematically re-surfaced until Phase 3. |
| DEF-07 | Outbound email delivery **pipeline** — templates UI, retry with backoff, bounce and complaint handling, digests, per-user preferences (FR-20 email, AI-7). | **2 (full). A minimal send slice is Phase 1, not deferred** — see the note below this table. | Notification templates approved; sender domain configured. | Phase 1 sends only the transactional replies the intake failure paths need; internal notifications are in-app rows until Phase 2. |
| DEF-08 | Saved report library (FR-17). | 3 | Dashboard KPIs stable and read models materialised. | Phase 2 ships fixed KPIs, not ad-hoc reports. |
| DEF-09 | Bespoke Settings UI (FR-22). | 4 | Admin back-office proves insufficient. | Covered in Phase 1 by PROP-06 and `identity`. |
| DEF-10 | Help / knowledge base (FR-23). | 4 | — | Lowest value; static docs suffice. |
| DEF-11 | Referral, agency, campus and walk-in intake forms (4 of 11 channels). | 4 | Form ownership and field sets agreed (OBD-11). | Those channels arrive via Outlook or manual upload in the meantime. |
| DEF-12 | Job-board inbound ingestion — LinkedIn, Indeed, Rozee, Mustakbil (4 of 11 channels). | 3 | Per-board API entitlement or a stable email format (OBD-13, and §6 row 2). | Board applications arrive through the Outlook channel. |
| DEF-13 | AI capabilities AI-4 through AI-15 (REQ-AIC-04..15). | 24 by BRD priority | AI-1/2/3 live and evaluated. | AI Studio must show honest availability (PROP-18) or the team is judged against the mockup. |
| DEF-14 | pgvector semantic retrieval and hybrid search. | 2 | Phase 1 FTS + trigram search measured and found insufficient for a named query class. | Phase 1 search is lexical and fuzzy, not semantic. |
| DEF-15 | Row-level security for the AI query path. | 2 | The application authorization layer is live and tested. | Phase 1 candidate PII protection rests entirely on the new application layer — a stated risk. |
| DEF-16 | A separately isolated untrusted-parsing worker queue. | 2 | — | Phase 1 mitigations (timeouts, memory caps, restricted OS user, no outbound network) are weaker than a sandbox. Stated risk. |
| DEF-17 | Candidate-facing self-service portal. | Out of scope this phase | Product decision (OQ-7 → OBD-07). | The RBAC role exists with zero provisioned seats (BRD §4). |
| DEF-18 | Special-category / diversity data capture. | Not planned | Separate lawful basis, aggregate-only reads and distinct access control agreed with Legal. | Fairness evaluation must run on proxies or externally supplied cohorts (OBD-02). |
| DEF-19 | PWA installability and offline shell. | 4 | A data-protection position on caching candidate PII on personal devices. | Responsive web already meets NFR-2/3/6. |
| DEF-20 | Read replica dedicated to search. | On trigger | p95 search latency exceeds 500 ms after index and query tuning. | Not expected at the assumed volume (ASM-03). |
| DEF-21 | Hiring-manager and calendar modules as standalone stores. | Never as modules | — | Managers are users with roles and assignments; calendar is a view over interviews. Deliberate consolidation, not an omission. |
**DEF-07 — the outbound-mail split, ruled once.** Four documents previously phased outbound
email four different ways (this table at 23, OBD-21 at Phase 3, `03` §5 / §33 migration 018 and
`07` §5.1 at Phase 2, `04` §9.1 row 5 arguing for Phase 1). The binding split, recorded here and
mirrored in `03` §22 / §33, `04` §9.1 row 5, `07` §4.1 / §4.2 / §5.1 and `08` §2.14 / §7 finding 10:
| Phase | What lands | Why it cannot sit on the other side of the line |
|---|---|---|
| **1 — minimal slice** (~46 dev-days, `07` T-16b) | One `app.outbound_message` row per send; `Mail.Send` through the **same** `MailProvider` port the inbound Graph adapter already uses; the send idempotency guard; NDR/bounce **classification** on the inbound side; one seeded transactional template ("send an unprotected copy of your CV"). Plus the in-app `app.notification` row `07` §4.2 already promised. | Phase 1 owns inbound email and CV parsing. Without a send path, **every parse failure is a dead end a recruiter resolves in Outlook by hand, outside the audit trail** — and an NDR arriving with no `outbound_message` row to attach to cannot be classified at all. This is a correctness hole in Phase 1, not a missing Phase 2 feature. |
| **2 — full pipeline** (`07` T-29, A-31, A-36) | Template authoring UI and versioning workflow, retry with backoff, complaint handling, digest sends, per-user `notification_preference`, the in-app notification centre. | None of it is needed to make a Phase 1 failure path actionable, and all of it needs sender-domain configuration and template approval, which are other people's work items. |
The distinction that makes this coherent: Phase 1 sends **transactional candidate-facing replies
the intake pipeline itself generates**. Phase 1 does **not** email internal users — that is
OBD-21's subject and it stays in-app until Phase 2.
---
## 5. One-month expectation — stated plainly
One month with two developers delivers **Phase 0 plus the first end-to-end thread of
Phase 1**: one channel ingesting real mail, a CV parsed into a candidate, one application
created against a versioned requisition, one score with visible components, on one hardened
screen. It does not deliver a platform, and any plan that says otherwise is wrong.
| Phase | Scope | Range | Confidence |
|---|---|---|---|
| 0 | Prototype XSS/CSP hardening; repo, CI, container, Postgres, migration skeleton; `identity` + `audit` + `config`; ADRs; frontend shell scaffold | 23 weeks | High |
| 1 | Vertical slice: intake → parsing → candidate → versioned requisition → application → assignment → scoring v1 with explanations; three inbound channels; enforced RBAC; five screens | 1014 weeks | Medium |
| 2 | Pipeline board; interviews + scorecards; duplicate review and reversible merge UI; dashboard KPIs; notifications; worklist; read-only assistant | 710 weeks | Medium-low |
| 3 | Assessments; offers with approvals; report library; fairness-evaluation gate; talent pool; job-board inbound | 710 weeks | Low |
| 4 | Outbound publishing; full tool-using assistant; remaining P2 AI capabilities; Settings and Help | 58 weeks | Low |
Confidence degrades after Phase 2 because Phase 3 depends on unresolved business decisions —
OBD-01 (model hosting), OBD-02 (historic outcome data) and OBD-04 (jurisdictions).
---
## 6. Verdict on every unconfirmed idea (assignment §4)
Verdicts use exactly one of: **Recommended now** / **Recommended later** / **Not
recommended** / **Dependent on existing stack** / **Dependent on business confirmation**.
| # | Idea | Verdict | Reason | Condition that changes the verdict |
|---|---|---|---|---|
| 1 | Mandatory public screening form before CV submission | **Dependent on business confirmation** | Changes the intake mix and the candidate experience, and knock-out questions are a regulated screening decision in several of the six posting jurisdictions. The career portal already exists as channel #2 (BRD §8.1); making a structured form *mandatory* is a policy choice, not an engineering one. Recommended shape if approved: optional per requisition, structured answers stored on the application, never used as an automatic reject. | Talent Lead confirms it may be mandatory per requisition **and** Legal approves the question set as non-discriminatory. Then: Phase 2, small. |
| 2 | Direct LinkedIn API integration | **Dependent on business confirmation** | There is no general-purpose LinkedIn ATS ingestion API outside its Talent Solutions partner programme; access requires a commercial entitlement Utopia may or may not hold, and scraping is prohibited by LinkedIn's terms. This is a contract question before it is a build question. Interim path costs nothing: LinkedIn application notifications arrive as email and are ingested through the Outlook channel (REQ-INT-02). | Utopia produces a Recruiter System Connect / Talent Hub entitlement and API credentials. Then: DEF-12, Phase 3. |
| 3 | Automatic HRMS sync | **Dependent on existing stack** | HRIS is explicitly out of scope this phase (BRD §3.2) and no HRMS is named anywhere in the assignment or the repository. A bidirectional sync also creates a second source of truth for a person, which is the failure mode already rejected for the `managers` entity. | An HRMS is named, has a documented API, and Talent Ops confirms direction. Recommended shape: **one-way export of hired candidates only**, Phase 4. Bidirectional sync stays not recommended. |
| 4 | Native mobile app (iOS/Android) | **Not recommended** | 66 named seats, of which 24 are interviewers who consume only assigned-interview data (BRD §4). The responsive web app already meets 320px, 44px touch targets and safe-area handling, verified (NFR-2/3/6, findings §G). Two app stores, two release trains and a third codebase for two developers is indefensible. | Interviewer scorecard completion measurably fails on mobile web, **or** push notification delivery becomes a hard requirement that web push cannot meet. Even then, evaluate DEF-19 first. |
| 5 | Progressive Web App (installability, offline shell) | **Recommended later** | Cheap once the React shell exists — a manifest, an app-shell service worker and an install prompt. Not now for one substantive reason: caching candidate PII on personal devices needs a data-protection position under BRD §7.4, and there is no Phase 1 offline data story worth having. | Nothing blocks it technically. Phase 4 (DEF-19), after Legal accepts a cache policy that excludes candidate PII. |
| 6 | Vue / React / another frontend framework | **Recommended now** — React 18 + Vite + TypeScript, progressive migration | Decided in `_decisions.md`: the CSS is the valuable verified asset (1269 lines, 93 design tokens across 159 custom-property declarations and 470 `var(--…)` references (measured, `01` §12), dual themes, WCAG AA across 23 routes × 2 themes) and is preserved verbatim; the *rendering layer* is the liability — 34 unescaped `innerHTML` sites (findings §E), ~115 form-control sites across 15 files, 22 ordered `<script>` tags with everything on `window` (findings §C, `index.html:264-285`). JSX escaping makes the XSS class structurally impossible rather than a per-line discipline. React over Vue/Svelte for team-shape reasons: the junior's task stream needs ubiquity and help availability, not minimal code. Cost stated: a build step the repo deliberately lacks, ~2030 developer-days across Phases 14, and two frontends coexisting for 612 months. | None — this is decided. The migration order, not the framework, is the remaining variable. |
| 7 | S3 versus MinIO for CV blob storage | **Dependent on existing stack** | The right answer is managed object storage in the **same cloud as the identity provider and the mail source**, which collapses three integration problems into one tenant. Under the Azure/M365 assumption (ASM-05) that is Azure Blob Storage. MinIO is a self-operated service — backup, patching, capacity and durability become the senior developer's second job, which is only worth paying for if blobs are legally required to stay on Utopia-operated hardware. | Legal requires candidate documents on Utopia-operated infrastructure (BRD §7.4 read strictly) → MinIO, with the operational burden explicitly accepted. Utopia is an AWS shop → S3, and revisit the Azure assumption for identity and mail too. |
| 8 | AWS Glue (or any managed ETL service) | **Not recommended** | There is no ETL problem. One relational database is a hard constraint, there is no warehouse and no second source of record, and analytics is served by read-only SQL views over the same database. Glue would introduce a second cloud dependency, a second scheduling system and a data-freshness question, to move data from Postgres to Postgres. | A genuine cross-system warehouse appears — ATS **plus** a named HRMS **plus** finance — with more than one source of record and a reporting need that cannot be served from one database. Not before Phase 4, and even then a scheduled SQL job is the first thing to try. |
| 9 | Elasticsearch / OpenSearch | **Recommended later** | Phase 1 search is Postgres FTS with weighted `tsvector` plus trigram fuzzy matching, which also supplies the duplicate-detection signals — one mechanism tuned and understood instead of two. A search cluster brings a permanent dual-write and reindex-drift cost that two developers feel every week. The escalation ladder to exhaust first, in order: index and query tuning → a read replica for search (DEF-20) → a materialised search table → a Postgres BM25 extension. | Any of: candidate rows above ~2,000,000 or indexed text above ~50 GB; p95 search latency above 500 ms **after** tuning and a read replica; sustained throughput above ~50 queries/second degrading write latency; or a requirement Postgres genuinely cannot serve (learning-to-rank, live BM25 experimentation, sub-second facets across >10 dimensions). At the assumed 10⁴10⁵ candidates (ASM-03) this is three to four orders of magnitude away and will very likely never fire. |
| 10 | Dedicated vector database (Pinecone, Weaviate, Qdrant, …) | **Not recommended** | `pgvector` in the *same* Postgres instance, Phase 2, with HNSW indexing and hybrid retrieval (lexical candidate generation, vector rerank). This is precisely what makes "no separate AI service in Phase 1" achievable rather than aspirational, and it keeps embeddings inside the retention and erasure boundary that BRD §7.4 requires — a separate vector store means candidate erasure has to reach into a second system. Hybrid retrieval is also cheaper and more explainable than pure vector search for recruiter queries, which are part keyword and part concept. | Embedding corpus outgrows one Postgres instance, or ANN recall and latency requirements exceed what HNSW in Postgres delivers after tuning. Not foreseeable at the assumed volume. |
| 11 | Microservices | **Not recommended** | Two developers, 66 named seats, ~2025 peak concurrency (ASM-04), one master data model and **one** relational database. Services sharing one database is the anti-pattern; splitting the model violates an explicit constraint. The Phase 1 critical path (intake → parse → candidate → application → score) spans four modules and needs transactional integrity — in-process that is one transaction, across services it is a saga with compensations that two developers will get wrong. Boundaries are enforced instead by package isolation, one service facade per module, and `import-linter` contracts in CI, so a violation fails the build. | Not team growth alone. Only a genuine hard split trigger on a specific module (see row 13), and even then the answer is one additional deployable, not a fleet. |
| 12 | Kubernetes | **Not recommended** | Explicitly excluded, and unjustifiable: the entire Phase 14 topology is two revisions from one image plus a managed Postgres. A managed container platform gives independent scaling, rolling deploys and log aggregation with no cluster to operate. Kubernetes at two developers means one of them becomes a part-time platform engineer. | Never, at this shape. If the deployment surface ever exceeds ~5 independent services with real autoscaling needs, revisit — that is a different product. |
| 13 | Separate AI service (own deployable) | **Recommended later** | Not in Phase 1 — but note what *is* delivered now: a separate **worker process** from the same image, so CPU-bound parsing and multi-second model calls never share a request thread with a sub-second recruiter request. That is the real property difference. A third deployable would duplicate the data model, the auth layer and the deploy surface, and fracture the audit trail, since AI run rows must join to applications in the same database. BRD §8.3's actual demands — documented versioned API, graceful degradation, async jobs with retrievable status — are all met by an internal module boundary plus a circuit breaker and a job-status endpoint; §8.3 is a **contract** requirement, not a deployment one. | Any single hard trigger: an ML/OCR dependency cannot coexist in the image or pushes it past ~2 GB; inference needs a GPU or >4 vCPU / >8 GB steady state; untrusted-file handling needs a sandbox the worker cannot provide; or a required model runtime is not Python. Soft triggers (queue starvation, release-cadence conflict, blast radius) need two sustained for 2+ weeks. |
| 14 | Full-platform delivery in one month | **Not recommended** | 25 logical modules, 15 AI capabilities, 11 inbound and 8 outbound integrations, a greenfield backend with no database, no auth, no tests and no CI (findings §B), and two developers of whom one is junior with a single reviewer. One month yields Phase 0 plus one vertical thread (§5). Committing to more does not compress the work; it moves the failure from a schedule conversation to a quality one, and the things that get cut under that pressure are authorization, history and versioning — the three most expensive to retrofit. | Nothing available to this team. A larger team does not fix it either inside one month: the Phase 1 critical path is sequential (intake before candidate before application before score), so it does not parallelise cleanly. |
| 15 | Action-taking chatbot (creates, edits, transitions) | **Recommended later** | Phase 2 ships a **read-only** assistant, and even that carries the asking user's identity into the same authorization check the REST API uses — no service account, no post-hoc filtering, no text-to-SQL. Action-taking is Phase 4 because it multiplies the blast radius of both an authorization bug and a model error, in a system where the authorization layer will itself be new (findings §D: nothing gates anything today). | All of: RBAC enforced and audited in production; every tool call routed through a module service facade with the human actor propagated; each action individually confirmable and reversible; and access events audited with the delegating user recorded. Then Phase 4. |
| 16 | Automatic rejection of candidates | **Not recommended — permanently** | Prohibited by BRD §7.1 and by the assignment constraint, and it is the single requirement most likely to create legal exposure across US, UK, EU, Canada, Singapore and Pakistan. AI ranks and recommends; a person decides. This is enforced structurally, not by policy: AI output is a suggestion record, the intelligence tier physically cannot call the application transition service, and a terminal-negative transition requires a human actor. | None. Not a phasing decision and not a configuration flag. A business request to enable it is a request to remove a governance guarantee and must be escalated, not implemented. |
| 17 | Separate regional databases | **Not recommended** | Forbidden by an explicit constraint, and wrong on the merits at this size: one master data model across all brands is the point of the platform, six jurisdictions of postings do not imply six datastores, and per-region databases would make the leadership single view (BO-6) a federated-query problem. Residency obligations are met per **record** — pseudonymising purge, per-record retention schedules, and erasure that reaches derived embeddings and indexes — not per region. | A legal requirement for in-jurisdiction storage of candidate data. That conflicts **directly** with the one-database constraint and requires an explicit written exception from the business (OBD-04); it is not something to work around architecturally. |
---
## 7. Rejected / not-recommended ideas beyond assignment §4
Recorded so they are not re-litigated. Each is rejected with a reason a reviewer can check.
| Idea | Verdict | Reason |
|---|---|---|
| Retain the prototype rendering layer as the end state | Not recommended | Escaping fixes the security hole (PROP-01) but not the absence of modules, types, tests or component reuse — and the forms still to be built (versioned requisitions with weighted requirements, scorecards, offer approvals) are the most complex in the product. |
| Rebuild the CSS / design system | Not recommended | Discards the single most valuable verified asset (findings §G) and risks regressing WCAG AA across 23 routes. It ports unchanged because it uses semantic class names, not utility classes. |
| Add DOMPurify to the prototype | Not recommended | These are text fields, not rich HTML — output escaping is the correct layer — and it adds a dependency to a codebase with no package manager (findings §B). |
| Django templates + HTMX instead of an SPA | Not recommended (genuine contender) | Cheaper for CRUD and autoescaped by default, but the product's centre of gravity is interactive: drag-and-drop kanban across 7 stages, a streaming assistant dock on every screen, canvas charts, live score updates. |
| GraphQL | Not recommended | BRD §8.3 asks for a documented versioned JSON HTTP API; DRF plus a generated typed client is less to maintain for two developers. |
| A BFF service in front of the monolith | Not recommended | An unnecessary network hop for one internal SPA consumer. |
| Kafka or any log-based broker | Not recommended | Forbidden, and absurd at hundreds of jobs per day. Transactional enqueue in the same database is both simpler and safer for the one flow that must never lose data. |
| Automatic merge of duplicate candidates above a similarity threshold | Not recommended | The failure mode is silent and cross-contaminates two people's application and compensation history — a data-protection incident, not a bug. Reversal machinery exists to recover from human error, not to make automation safe. |
| Text-to-SQL for the assistant over an application database role | Not recommended | An unbounded read capability that no prompt-level guard reliably constrains. |
| A service account for the chatbot with post-hoc result filtering | Not recommended | The standard mistake; it fails the first time the filter has a bug, and it directly violates the chatbot access-control constraint. |
| A separate `search` module, `embeddings` service, `workflow_engine`, `reporting_warehouse`, or `tenant`/`region` module | Not recommended | None owns state or an invariant. Each would pay a fixed per-module cost (migrations, facade, permissions, tests) and return nothing. |
| A standalone `Manager` entity parallel to users | Not recommended | The prototype keeps manager name, title and department separately from identity — two sources of truth for a person is how permission checks drift onto the wrong record. |
| Hard delete on retention expiry | Not recommended | Breaks foreign keys, tears holes in funnel metrics, and makes merge reversal unreplayable. Pseudonymisation (PROP-15) satisfies erasure and keeps the statistical shape. |
| One generic history / event table for everything | Not recommended | No foreign keys, no typed columns, worst-selectivity largest table in the database, and the most common query (stage funnel) becomes a filtered scan of every change in the system. |
| Event sourcing | Not recommended | Correct in theory, far too much machinery for two developers, and it makes ordinary list queries hard. |
| Native Postgres enum types for stages and statuses | Not recommended | Cannot carry ordering or display metadata, and reordering or removing a value requires a type rewrite for what should be a row insert. |
| Money as float, as the Postgres `money` type, or as integer minor units | Not recommended | Float rounding in compensation is indefensible; `money` is locale-dependent with an implicit single-currency assumption; integer minor units are correct but force a mental division into every ad-hoc query, and this team will write many. |
| `timestamp without time zone` plus a "everything is UTC" convention | Not recommended | One forgotten cast and the data is wrong with no way to detect it. |
| Storing an interview as UTC only, or as local time plus a numeric offset | Not recommended | UTC-only loses the organiser's intent, which reschedule and recurrence need; offsets do not survive DST or zone-rule changes. |
| SQLite in CI for speed | Not recommended | The moment tests run on SQLite the team starts avoiding the Postgres features this design depends on, and the suite stops telling the truth. |
| Coverage percentage as the quality gate | Not recommended | Rewards testing getters. Gates are: boundary contracts, a migration check, real-Postgres tests, and five smoke journeys. |
| Self-managed Postgres on a VM | Not recommended | Cheaper on paper; backup, patching and failover become the senior developer's unpaid second job. |
| A separate audit database, SIEM stream, or blockchain notarisation | Not recommended | New infrastructure for no Phase 1 requirement. Off-box immutable partition export (PROP-08) gives the independent check at a fraction of the cost. |
| One module per inbound channel (11 modules) | Not recommended | Fake decomposition — the channels differ only in transport and normalise to one shape. |
---
## 8. Open business decisions
**Design is not blocked by any of these.** Each row carries a recommended assumption that
the design proceeds on, and states what breaks if the real answer differs. Rows OBD-01 to
OBD-07 correspond to BRD OQ-1 to OQ-7 and inherit their owners; OBD-08 onward were surfaced
by the architecture and database decisions and have no BRD counterpart.
| OBD | Decision | Why it matters | Options | RECOMMENDED ASSUMPTION | Owner | What breaks if the answer differs |
|---|---|---|---|---|---|---|
| OBD-01 | Model hosting approach (BRD OQ-1) | Determines the entire BRD §7.4 data-protection position and the Phase 1 infrastructure bill | Self-hosted on own GPUs / private cloud endpoint / contracted API provider under a DPA | **Contracted API provider under a data-processing agreement, invoked only from the worker plus one streaming endpoint, with no training on Utopia data** | AI Technology (Talha) | Self-hosting adds GPU infrastructure, model serving and an MLOps burden two developers cannot absorb; every phase range in §5 breaks. This is the single decision most likely to invalidate the design. |
| OBD-02 | Availability of historic hiring outcome data (BRD OQ-2) | The fairness evaluation and any score validation beyond face plausibility depend on real outcomes | Available with consent basis / available anonymised / not available | **Not available for Phase 1; scoring ships as rule-and-requirement-weighted with pinned versions, and the Phase 3 fairness gate runs on prospective data collected from go-live** | Talent Ops + Legal | If it *is* available, scoring can be calibrated far earlier and Phase 3 shortens. If it is genuinely never available, the Phase 3 activation gate may block the ranking release with no engineering fix — that is a business risk, not a bug. |
| OBD-03 | Latency ceiling for interactive AI (BRD OQ-3, NFR-8) | Sets whether streaming is sufficient, whether results are precomputed, and what the UI promises | Sub-2s / sub-5s / async-with-notification acceptable | **Interactive responses stream first token under 2s and complete under 8s; anything longer is an async job with retrievable status (REQ-API-04)** | AI Technology + Talent Lead | A hard sub-2s *complete* ceiling forces precomputation of summaries and ranks on ingest, which changes the worker sizing and the cost model. |
| OBD-04 | Jurisdictions the fairness evaluation and retention must cover (BRD OQ-4) | Drives audit retention periods, the evaluation cohort design, and the residency question | All six posting jurisdictions / a named subset / a single primary jurisdiction | **All six (US, UK, EU, Canada, Singapore, Pakistan), with the strictest applicable retention and erasure standard applied uniformly, one database region, and residency handled per record** | Legal | A requirement for in-jurisdiction storage conflicts directly with the one-database constraint and needs a written exception (§6 row 17). Differing retention periods per jurisdiction mean per-record retention policies rather than one global schedule — supported by design, but more configuration. |
| OBD-05 | Who may see the match score (BRD OQ-5) | Determines whether the score is a recruiter tool or a shared decision artefact, and how hiring managers are trained | Recruiters only / recruiters + hiring managers / all internal roles including interviewers | **Recruiters and hiring managers see the score with its explanation; interviewers do not (to avoid anchoring); implemented as a role-configurable setting (REQ-SEC-05), not a hardcoded rule** | Talent Lead | If interviewers must see it, the interview module gains a score surface and the anchoring risk must be addressed in interviewer guidance. If recruiters only, hiring-manager shortlist review loses its rationale and BRD §7.3 ("restate to a hiring manager") becomes verbal only. |
| OBD-06 | Fallback when parsing fails (BRD OQ-6) | Determines whether the intake queue is a work queue or a bin, and sets recruiter workload expectations | Queue for manual entry / reject the document / auto-retry then queue | **Retain the document, auto-retry with backoff, then queue for manual entry with the raw document viewable inline; never reject automatically** | Talent Ops | Rejecting documents automatically violates BRD §6.3 ("never silently dropped") and loses real applicants. Manual entry has a labour cost that Talent Ops must staff — quantify it against the assumed parse success rate (ASM-08). |
| OBD-07 | Candidate-facing portal in a later phase (BRD OQ-7) | Determines whether candidate-facing access tokens, status pages and self-service uploads are built at all | Yes, later phase / no / limited status-check page only | **A limited, token-gated application-status page only (PROP-03), Phase 3; no full self-service portal** | Product | A full portal is a separate product surface with its own auth, threat model and support load — it would need its own phase, not an increment. The RBAC role already exists with zero seats, so nothing breaks by deferring. |
| OBD-08 | Reapplication cooling-off period | Whether the same person can reapply to the same requisition immediately, and whether recruiters will evade the rule | No restriction / 90 days / per-rejection-reason | **90 days by default; 0 for candidate withdrawal; shorter where the rejection reason was "role filled"; always overridable by a named user with an audited reason** | Talent Lead | A hard block with no override drives recruiters to create duplicate candidate records to evade it, which is strictly worse than an audited override. No restriction inflates every funnel metric. |
| OBD-09 | Whether shared and agency email addresses may identify a candidate | The one-email-one-identity rule (REQ-CAN-05) is a hard database rule; real agency submissions will violate it | Enforce strictly / maintain a non-identifying address list / drop the rule | **Maintain a non-identifying address list (agency mailboxes, `info@`, shared family addresses) excluded from the uniqueness rule (PROP-10) — decided before go-live, not after the queue backs up** | Talent Ops | Enforcing strictly means agency- and referral-sourced intakes pile up unresolvable in review. Dropping the rule loses the safety net that forces missed matches into duplicate review. |
| OBD-10 | Who may merge and unmerge candidate identities | Merge re-points history across two identities; a false merge is a data-protection incident | Any recruiter / HR Administrator and above / System Administrator only | **HR Administrator and above may merge and unmerge; recruiters may only flag suspected duplicates** | HR Administrator + Talent Lead | Widening it to all 15 recruiters raises false-merge frequency, and every false merge consumes an unmerge with stack-discipline constraints. Narrowing to sysadmin only makes the duplicate queue a bottleneck of two people. |
| OBD-11 | Ownership and field sets for the referral, agency, campus and walk-in intake forms | Four of eleven channels cannot be built without agreed fields and a named owner per form | Talent Ops owns all four / per-channel owners / channels stay email-only | **Talent Ops owns all four form definitions; until they are agreed, those channels arrive through Outlook or manual upload (DEF-11)** | Talent Ops | Without agreed field sets, four channels stay on the email path indefinitely and per-source attribution (REQ-INT-06) stays coarse for them. |
| OBD-12 | Requisition approval chain — who approves, and in what order | Determines whether publishing is one approval or a sequence, and whether it varies by grade or budget | Hiring Manager only / Hiring Manager then Department Head / value-threshold-driven | **Hiring Manager approves; Department Head approval additionally required for grades L6L7 or where the salary range exceeds a Finance-set threshold** | Department Head + Finance | A longer chain adds states and notification paths to Phase 1's critical path. A shorter chain (recruiter self-publish) removes the cost control that the job-board cost bands imply. |
| OBD-13 | Job-board account ownership, credentials and publishing budget | Four inbound and eight outbound integrations depend on accounts and per-post spend authority | Central Talent Ops accounts / per-brand accounts / per-recruiter accounts | **Central Talent Ops accounts held in the platform secret store; publishing restricted to HR Administrator because it has direct cost implications (REQ-PUB-02)** | Talent Ops + Finance | Per-brand or per-recruiter accounts multiply credential management and make channel-performance reporting (REQ-ANL-09) incomparable across brands. No budget authority means outbound publishing (DEF-04) cannot be tested against real platforms. |
| OBD-14 | Cloud provider and region for the single database | Determines the identity provider, the mail integration path, object storage choice, and the residency answer | Azure / AWS / GCP / on-premise | **Azure, single region, on the assumption that Utopia runs Microsoft 365 (Outlook is inbound channel #1) so that SSO and mail app registration land in the same tenant** (ASM-05) | Corporate IT | If Utopia is an AWS shop, revisit identity (Entra vs an alternative IdP), mail access, and object storage (§6 row 7) together — the value of the Azure choice is entirely that it collapses those three into one tenant. If on-premise is required, hosting, backup and PITR become team-owned work that is not in any phase estimate. |
| OBD-15 | Single sign-on provider and whether local password login exists at all | Determines the Phase 01 identity build and whether password policy code is needed | SSO only / SSO plus local fallback / local only | **SSO only for internal users, with no local password store; break-glass access is a named administrator account managed by Corporate IT** | Corporate IT | A local password requirement adds password policy, reset flows, lockout and breach-response handling to Phase 1 — real work that is currently inert UI (findings §D, `js/settings.js:148-154`). |
| OBD-16 | Retention period for candidate records by outcome | Drives the purge schedule, the pseudonymisation design, and storage cost | Uniform (e.g. 24 months) / by outcome (hired / rejected / withdrawn) / by jurisdiction | **By outcome, with the strictest applicable jurisdiction standard: hired candidates retained per employment-record obligations; non-hired pseudonymised 24 months after last meaningful activity; explicit holds override the purge** | Legal + Talent Ops | Shorter periods erode the historic data that OBD-02 might otherwise supply and permanently block merge reversal for purged candidates. Longer periods increase data-protection exposure and storage cost. |
| OBD-17 | Audit log retention, independent of candidate retention | BRD §7.3 ties audit retention to posting jurisdictions, which differ from candidate retention | Match candidate retention / longer, fixed / per jurisdiction | **13 months hot in the primary database, then detached and archived to immutable storage for 7 years** | Legal | A longer hot window changes database sizing. A shorter archive period may fail a jurisdiction's evidential requirement — and audit gaps cannot be reconstructed after the fact. |
| OBD-18 | Whether compensation data is visible to hiring managers and interviewers | Salary is classified sensitive personal data and appears on requisitions, applications and offers | Recruiters and HR only / plus hiring managers / all approvers | **Recruiters, HR Administrators and Department Heads see amounts; Hiring Managers see the requisition band only; interviewers see nothing** | HR Administrator + Legal | Wider visibility expands the sensitive-data surface and the audit-access volume. Narrower visibility means hiring managers cannot evaluate offer approvals they are asked to approve (OBD-12 interacts). |
| OBD-19 | Whether score bands are the BRD's example labels or Utopia's own | Band labels appear in the UI, in reports, and in conversations with hiring managers | Use BRD examples (Strong Hire / Hire / Lean Hire / No Hire) / define Utopia labels / numeric only | **Adopt the four BRD example bands as versioned reference data with configurable thresholds, so renaming or re-cutting them is a data change, not a deployment** | Talent Lead | Changing labels after go-live rewrites the meaning of historical reports unless band assignment is pinned per score row — which the design does, so this is recoverable rather than fatal. |
| OBD-20 | Acceptable disparate-impact thresholds and the cohorts to evaluate against | The fairness gate cannot pass or fail without a stated threshold, and Phase 3 depends on it | Four-fifths rule / stricter internal standard / qualitative review | **Four-fifths (80%) adverse-impact ratio as the initial documented gate, evaluated on cohorts supplied by Talent Ops rather than inferred from candidate data (no special-category data is stored — REQ-DAT-05)** | Legal + Talent Lead | Without a stated threshold the Phase 3 gate is unenforceable and REQ-GOV-04 becomes procedural. If cohort data cannot be supplied, the evaluation can only use proxies, which Legal must explicitly accept or reject. |
| OBD-21 | Whether recruiter and hiring-manager notifications go to email, in-app, or both | Determines when the internal-notification delivery pipeline and its preference model are built | In-app only / email only / both with per-user preference | **In-app only in Phase 1; email delivery to internal users in Phase 2 with a per-user preference (DEF-07 full slice).** Revised from "Phase 3" — Phase 1 already builds a *minimal* candidate-facing send path (`outbound_message`, `Mail.Send` through the `MailProvider` port, idempotency guard, NDR classification — DEF-07 note, `07` T-16b) because the intake failure paths cannot be resolved in-product without it, so by Phase 2 the provider, the port and the sent-record table already exist and only the pipeline around them is new work | Talent Lead | The Phase-3 answer was set on the assumption that *any* email meant standing up a delivery pipeline from nothing. It does not: the pipeline's expensive parts (sender domain, template approval, bounce policy, digests) are separable from a single `Mail.Send` call, and Phase 1 needs the latter regardless of how this question is answered. What remains genuinely at stake is only **internal** notification email — pulling that into Phase 1 would add preference modelling and digest logic to the Phase 1 critical path for no Phase 1 outcome, since all 66 seats are in-product daily. |
| OBD-22 | Who owns career-portal content, branding and the application form fields | The career portal is inbound channel #2 and outbound platform #1, and it is Utopia-owned | Talent Ops / Marketing / shared with Talent Ops owning fields | **Talent Ops owns the application form fields and screening questions; Marketing owns branding and copy; the platform renders both from configuration** | Talent Lead + Marketing | Split ownership without a stated boundary stalls the career-portal channel, which is one of only three Phase 1 channels. Interacts with OBD-25 if screening becomes mandatory. |
| OBD-23 | Whether data is segregated or merely attributed by brand | "Consolidates hiring across every Utopia brand" (BRD §1) versus what recruiters and leadership may see across brands | Full cross-brand visibility / attributed but role-scoped / segregated per brand | **One master data model with brand as an attribute; visibility scoped by role assignment, so leadership sees across brands (BO-6) and recruiters see their assigned scope** | Talent Lead | A requirement for genuine per-brand segregation would push toward the multi-tenant shape the constraints explicitly exclude, and would break the single leadership view. |
| OBD-24 | Whether the prototype may ever be pointed at real candidate data | The prototype has 34 unescaped rendering sites; real CV or mailbox content in it is a stored-XSS execution in a recruiter session (findings §E) | Never / only after the Phase 0 patch / freely for demos | **Never. Real data is wired only to the migrated frontend; the prototype is frozen after the Phase 0 patch and used for demonstration with synthetic data only** | Talha Ahmed + Talent Lead | If stakeholders demo with real data before the migrated screens exist, a single missed interpolation executes attacker-controlled script with full recruiter privileges. This is the cheapest decision here and the most expensive to get wrong. |
| OBD-25 | Whether public screening questions may be mandatory, and who approves the question set | Directly gates §6 row 1, and knock-out questions are a regulated screening decision | Optional always / mandatory per requisition / mandatory platform-wide | **Optional per requisition in Phase 2; answers stored on the application and never used as an automatic reject; any mandatory question set requires Legal sign-off** | Talent Lead + Legal | Mandatory screening changes the intake funnel shape and the applicant experience; using answers as knock-outs would create an automated rejection path, which REQ-GOV-01 forbids outright. |
---
## 9. Assumptions
Every item below is **an assumption**, not a finding. Each states what it affects and how it
would be falsified.
| ASM | Assumption | Affects | How it would be falsified |
|---|---|---|---|
| ASM-01 | Application volume is 20,00060,000 per year. | Worker sizing, storage projection, partition cadence | Actual intake counts after one quarter of live Outlook ingestion. |
| ASM-02 | Peak document throughput is 200600 documents per day. | Parse worker concurrency, queue depth alerting | Observed daily intake volume at peak season. |
| ASM-03 | The candidate base accumulates to the order of 10⁴10⁵ rows over several years. | Search strategy, the decision not to add a search cluster, index sizing | Candidate row count trend after two quarters; a bulk job-board feed would invalidate it by one to two orders of magnitude. |
| ASM-04 | Peak concurrency is ~2025 users, from 66 named seats of which 24 are read-mostly interviewers. | Web process sizing, connection pool, the no-HA decision | Session concurrency metrics after go-live. |
| ASM-05 | Utopia Brands runs Microsoft 365, so Entra ID SSO and the Graph mail app registration land in the same tenant. | Cloud choice (OBD-14), identity (OBD-15), object storage (§6 row 7), inbound channel #1 | Corporate IT confirming the actual tenant and mail platform. This is the highest-leverage assumption in the document. |
| ASM-06 | A contracted API model provider under a DPA is acceptable to Legal. | AI orchestration design, Phase 1 infrastructure, every phase range | Legal's answer to OBD-01. |
| ASM-07 | Candidate data may reside in a single cloud region provided retention and erasure are per record. | The one-database constraint, residency answer | Legal requiring in-jurisdiction storage (OBD-04). |
| ASM-08 | CV parsing on mixed-quality PDFs and scanned documents will not populate every field reliably, so a recruiter review step is required. | REQ-INT-07, PROP-20, OBD-06, the BRD §11 acceptance wording | Measured per-field parse accuracy on a real sample of Utopia's inbound CVs. |
| ASM-09 | Both developers are available approximately full time for the duration, with Talha Ahmed as the only reviewer. | Every range in §5, the bus-factor risk | Leave, reassignment, or a second senior joining. |
| ASM-10 | The Talent Lead is the accepting stakeholder for each phase demonstration. | Phase boundaries being drawn at demonstrable increments | A different acceptance authority with different criteria. |
| ASM-11 | No special-category data (diversity, health, accommodation) needs to be stored in Phase 1. | REQ-DAT-05, the fairness cohort design (OBD-20) | A fairness requirement that can only be met with self-reported protected characteristics. |
| ASM-12 | Compensation is classified sensitive personal data rather than ordinary personal data. | Audit payload rules, OBD-18, masking strategy | Legal classifying it differently. |
| ASM-13 | Audit volume reaches tens of millions of rows over a few years, given data-change plus access auditing. | Partitioning cadence, archive design, OBD-17 | Observed audit growth over the first two quarters. |
| ASM-14 | Job-board applications currently arrive in a parseable email format via the Outlook channel. | DEF-12, §6 row 2 — the interim path for four channels | Inspecting real LinkedIn/Indeed/Rozee/Mustakbil notification emails from the recruiting mailbox. |
| ASM-15 | The 27-day time-to-hire baseline in the BRD is a real measured figure and not a demo value. | BO-2 measurement, REQ-ANL-08 | Talent Ops confirming the source of the figure. Note the prototype's data layer is entirely synthetic (findings §C, `js/data.js:8-10`), so any figure sourced from it is not evidence. |
| ASM-16 | 25 logical modules and 23 existing routes represent the full surface; no unstated module is required. | Scope completeness, phasing | A stakeholder naming a workflow with no home in the module list. |
---
## 10. Inconsistencies noted in `_decisions.md`
Recorded here as risks per that file's own instruction, not silently resolved.
> **The binding resolution for all five is in `_open-items.md`**, the package's single arbitration
> register. This table is the evidence and the reasoning; the ruling is there. Rows 1 → RULING-02
> (migration tooling), 2 → RULING-04 (two concrete assignment tables), 3 → RULING-08 (no reversal
> window), 4 → RULING-07 (`pgvector` installed Phase 1, used Phase 2; PG major pinned at 16),
> 5 → RULING-03 (Part 2's table names are the database vocabulary; the mapping is published as
> `_glossary.md`). The "Recommended resolution" column below is superseded wherever it differs.
| # | Inconsistency | Where | Impact | Recommended resolution |
|---|---|---|---|---|
| 1 | **Migration tooling contradiction.** Part 1 selects Django 5 partly *because* migrations are built in and there is no migration tooling to inherit. Part 2 mandates ordered plain-SQL migration files applied by a thin runner, with the ORM mapping to the schema and never generating it. | `_decisions.md` §"Backend language and framework" vs §"Schema tooling and migration strategy" (and its own risk note) | Real and blocking at the first migration. Django's `makemigrations --check` CI gate named in the testing decision presumes ORM-generated migrations. | **Settled — this row no longer carries a recommendation.** The binding ruling is **ADR 0017** (`adr/0017-plain-sql-migrations-as-schema-authority.md`), whose canonical text is quoted verbatim in `02-system-architecture.md` §12.4 and reproduced in `04` §9.1 and `05` §9.1 I-1. In summary: `db/migrations/NNN_*.sql` is the schema authority; Django supplies ordering and the applied-state ledger via `SeparateDatabaseAndState` + `RunSQL` and authors no DDL; every model stays `managed = True`; `makemigrations --check` is retained as a **model-vs-state** gate, with objects Django cannot model listed in a reviewed `db/schema-ignore.toml` and covered by a second **SQL-level** `pg_dump`-plus-catalogue-diff gate. Six documents previously carried six separately-worded recommendations; ADR 0017 supersedes all of them. Signed off in **T-04, Phase 0 week 1**, and a merge blocker on migration `001`. |
| 2 | **Polymorphic versus concrete assignment tables.** Part 1's module table defines `Assignment(subject_type ∈ requisition/application, subject_id, …)`. Part 2 explicitly rejects a polymorphic assignment table and mandates two concrete tables (`job_assignment`, `job_application_assignment`) because a polymorphic FK cannot be enforced by the database. | `_decisions.md` module 12 vs §"Which entities get current-plus-history…" | Moderate. Two documents would describe different tables for the same requirement (REQ-ASG-01/02/03). | Part 2 wins — it is the database authority and its reason (unenforceable FK) is correct. The module facade can still present one `assignment` service API over two tables. |
| 3 | **Merge reversibility window.** Part 1's `MergeOperation` carries a `reversible_until` field. Part 2 states there is no time limit on reversal and explicitly rejects a fixed reversal window as arbitrary, blocking only on stack discipline or a retention purge. | `_decisions.md` module 8 vs §"Unmerge / reversal semantics" | Moderate — affects REQ-DUP-05 and the unmerge UI (PROP-11). | Part 2 wins. Drop `reversible_until`; the blocking conditions are the later-merge stack check and `reversal_blocked_reason`. |
| 4 | **`pgvector` timing and Postgres major version.** Part 1's deployment decision provisions `pgvector` at Phase 1 and names PostgreSQL 16; Part 2 lists `pgvector` as Phase 2 only and targets "16+ (target 17)" with the major deliberately unpinned, noting UUIDv7 generation differs by version. | `_decisions.md` §"Deployment topology" vs §"Database engine" and its final risk | Low, but it will produce different environments if unresolved at provisioning. | Enable the extension at provisioning (free) while keeping *use* of it in Phase 2; pin one major version and one UUIDv7 source in a single provisioning decision. |
| 5 | **Entity naming.** Part 1 names `Requisition`/`RequisitionVersion`/`Application`; Part 2 names `job`/`job_version`/`job_application`. | Throughout both parts | Low but pervasive — it will make the traceability matrix ambiguous. | Adopt Part 2's table names as the database vocabulary and Part 1's module names as the code vocabulary, and state the mapping once in the data-model document. |