# PRD-SOQ-TMS-1.1 · Formal Requirement Oracle

| Field | Normative value |
|---|---|
| Doc-ID | `PRD-SOQ-TMS-1.1` |
| Baseline | `ATT-A-HK-2026-08` |
| Conformance | RFC 2119 |
| Identifier namespaces | `FR-{domain}-{nn}` · `NFR-{nn}` · `DQ-{nn}` |
| Status | `DRAFT_ARCH_ALIGNED` |

> **Reader profile:** Staff engineers, QA automation, integration owners. Non-engineering interpretation discouraged — refer §23–§25 for commercial semantics.

---

## 0 · Identifier lattice

| Prefix | Semantics | Test binding |
|---|---|---|
| `FR-*` | Functional SHALL/MUST | `tests/acceptance/fr/{id}.test.ts` |
| `NFR-*` | Quality attribute | k6 · axe · chaos · DR drill |
| `DQ-*` | Discovery delta (unresolved) | Block release if `M` priority |
| `SYS-*` | Attachment-A invariant | Trace matrix §00 |

MoSCoW: **M** = release blocker · **S** = contract milestone · **C** = backlog · **W** = won't v1

---

## 1 · Problem invariant (compressed)

Legacy SOQ stack = `{legacy_tms, privyr, xero}` without unified `student_id` keyspace. Symptom classes:

| Symptom-ID | Root cause | Target subsystem |
|---|---|---|
| `S-RECON-01` | Dual SoR enrolment vs CRM | `Enrolment` + `Crm` handoff event |
| `S-FUND-02` | Retroactive SWDA evidence assembly | `SwdaHub` outbox from `AttendanceFact` |
| `S-FIN-03` | Manual Xero re-key | `FinanceOrders` → `XeroAdapter` mirror |
| `S-EXM-04` | Hard-coded exemption window | `policy-engine` DSL |

---

## 2 · Product boundary (normative)

\`\`\`
APP = { public-web, admin-workspace, student-portal, trainer-portal }
SVC = { swda-hub, xero-adapter, notification-dispatcher, policy-engine }
|APP_user_facing| = 3 + 1 public
\`\`\`

Course-type taxonomy: `DIPLOMA | SHORT | WSQ` — enum frozen in `catalogue.course_funding_type`.

SWDA nomenclature: canonical `SWDA`; adapter maintains `legacy_alias ∈ {SSG, WSG}` field map until vendor EOL.

---

## 3 · Success metrics (instrumentation IDs)

| Metric-ID | Definition | Collection | MVP target |
|---|---|---|---|
| `M-CONV-01` | `enrolled / enquiries` | `crm.funnel` | +10% (S) |
| `M-ADM-02` | `median(handle_time) diploma_app` | OTel span | −60% (M) |
| `M-SWDA-03` | `reject_rate(submissions)` | `integration_txn_log` | <2% (M) |
| `M-FIN-04` | `manual_journal_count` | Xero diff job | −50% (S) |

---

## 4 · Scope tensor

**In-scope v1:** FR domains §7–§15 · SwdaHub P2 · Xero mirror · RBAC §15 · Outbox async

**Out-of-scope v1:** GATCOMS agency portal · ai.HumanCapital · D-GENIO proctor parity · native mobile · multi-region funding

---

## 5 · Persona → RBAC role map

| Persona | Role slug | Data scope |
|---|---|---|
| SysAdmin | `platform.admin` | `*` |
| Admissions | `admissions.officer` | `programme_id ∈ assigned` |
| Finance | `finance.officer` | `order.*` read/write |
| Sales | `sales.agent` | `lead.*` pre-enrolment |
| Trainer | `trainer` | `session_id ∈ roster` |
| Student | `student` | `self` |
| Auditor | `auditor.ro` | read-only append-log |

---

## 6 · Container overview

See CRD §17. Event bus: Redis Streams or Horizon queue — **domain events MUST NOT invoke HTTP synchronously to SWDA from request thread**.

---

## 7 · FR — Student Portal (public layer)

| ID | Priority | Requirement |
|---|---|---|
| FR-STU-001 | M | `GET /v1/catalogue` paginated; filter `funding_type, category, run_date` |
| FR-STU-010 | M | Applicant auth: `email_otp \| singpass_oidc \| overseas_credential` |
| FR-STU-020 | M | WSQ reg wizard: sequential gate `identity → eligibility → fee_estimate → payment` |
| FR-STU-030 | S | Course compare max 3 SKUs client-side |
| FR-STU-040 | C | Pigeonhole embed post-MVP |

Illegal state transition → HTTP `409` + `error_code=FSM_VIOLATION`.

---

## 8 · FR — Admissions / registration / funding

| ID | Priority | Requirement |
|---|---|---|
| FR-ADM-001 | M | Diploma FSM: `DRAFT→SUBMITTED→SCREENING→OFFERED→ACCEPTED→ENROLLED` |
| FR-ADM-010 | M | Seat lock: `SELECT FOR UPDATE` on `course_run.seats_available` |
| FR-ADM-020 | M | WSQ: `subsidy_estimate` computed pre-payment via `policy-engine` |
| FR-ADM-030 | M | SFC hold/release integrated with payment intent |
| FR-ADM-040 | S | Waitlist priority queue FIFO with `promoted_at` |

---

## 9 · FR — Admin Workspace

| ID | Priority | Requirement |
|---|---|---|
| FR-ADM-100 | M | Programme version immutability post first enrolment on version |
| FR-ADM-110 | M | Scheduler conflict detection O(n²) slots acceptable MVP; optimize P2 |
| FR-ADM-120 | M | Exemption: `POL-EXM-*` output binds `order_line adjustment` |
| FR-ADM-130 | M | Student360 materialized view refresh lag ≤ 60s |
| FR-ADM-140 | M | Attendance QR nonce TTL 120s single-use |

---

## 10 · FR — CRM / commission

| ID | Priority | Requirement |
|---|---|---|
| FR-CRM-001 | M | Lead dedupe key `hash(normalize(email), normalize(mobile))` |
| FR-CRM-010 | S | Privyr import idempotent on `external_id` |
| FR-CRM-020 | M | `ApplicationCreated` event freezes CRM opportunity writes |
| FR-CRM-030 | M | Commission accrual on `PaymentCleared`; clawback on `RefundPosted` |

---

## 11 · FR — Learning portal (enrolled)

| ID | Priority | Requirement |
|---|---|---|
| FR-LRN-001 | M | Timetable ICS export optional |
| FR-LRN-010 | M | Check-in `POST /v1/sessions/{id}/checkin {nonce}` |
| FR-LRN-020 | S | SCORM/LTI adapter interface stub only MVP |

---

## 12 · FR — Trainer portal

| ID | Priority | Requirement |
|---|---|---|
| FR-TRN-001 | M | Roster accept/decline within configurable TTL |
| FR-TRN-010 | M | Attendance batch submit triggers `AttendanceFactRecorded` × N |
| FR-TRN-020 | S | Payable export `POST /v1/trainer/payables/export-xero` |

---

## 13 · FR — SwdaHub

| ID | Priority | Requirement |
|---|---|---|
| FR-SWDA-001 | M | All gov IO via hub; credential vault |
| FR-SWDA-010 | M | Outbox + idempotency keys |
| FR-SWDA-020 | M | Ops dashboard: pending/rejected/DLQ |
| FR-SWDA-030 | S | Sandbox/prod env switch RBAC-gated |

API inventory: `DQ-SWDA-001` — confirm against SOQ subscription manifest.

---

## 14 · FR — Finance / Xero

| ID | Priority | Requirement |
|---|---|---|
| FR-FIN-001 | M | Order aggregate root owns pricing snapshot |
| FR-FIN-010 | M | InvoiceMirror 1:1 order |
| FR-FIN-020 | M | Cert issuance blocked if `financial_hold.active` |
| FR-FIN-030 | S | Reconciliation diff report cron |

---

## 15 · FR — Platform (RBAC, audit, notify, docs)

| ID | Priority | Requirement |
|---|---|---|
| FR-PLT-001 | M | RBAC middleware on all `/v1/*` mutators |
| FR-PLT-010 | M | `audit_log` append-only; no UPDATE/DELETE |
| FR-PLT-020 | M | PDPA consent record on applicant PII create |
| FR-PLT-030 | S | Notification template versioning |

---

## 16 · NFR catalogue

| ID | Requirement |
|---|---|
| NFR-01 | 99.9% availability monthly |
| NFR-02 | API p95 < 300ms reads at 500 RPS burst registration |
| NFR-03 | SWDA submission exactly-once effect |
| NFR-04 | WCAG 2.2 AA public/student |
| NFR-05 | PDPA access logging |
| NFR-06 | RPO 24h / RTO 4h MVP |
| NFR-07 | PCI SAQ-A (gateway redirect) |

---

## 17 · Journey sequences (reference)

WSQ happy path:

\`\`\`
catalogue.select → singpass.auth → eligibility.check → order.create → payment.capture → enrolment.commit → swda.register (async)
\`\`\`

Sequence diagrams: external `docs/sequences/` (not in this oracle).

---

## 18 · Conceptual ERD (abbreviated)

\`\`\`
organisation ||--o{ programme : owns
programme ||--o{ course_run : schedules
course_run ||--o{ session : contains
student ||--o{ enrolment : has
enrolment ||--o{ order : generates
order ||--o{ order_line : contains
session ||--o{ attendance_fact : records
\`\`\`

Full DDL: `services/api/database/migrations/` (implementation phase).

---

## 19 · Dependencies / constraints

| Dep-ID | System | Blocking |
|---|---|---|
| DEP-01 | SWDA sandbox credentials | P2 start |
| DEP-02 | Singpass OIDC client | WSQ public reg |
| DEP-03 | Xero OAuth tenant | P3 |
| DEP-04 | Payment gateway MID | Paid reg |

---

## 20 · Risk register (technical)

| R-ID | Risk | Mitigation |
|---|---|---|
| R-01 | SWDA API drift | Adapter version pins + contract tests |
| R-02 | Rate limit 429 storm | Token bucket worker pool |
| R-03 | Xero sync drift | Reconciliation cron + alert |

---

## 21 · Release milestones

| Phase | Gate | FR subset |
|---|---|---|
| P1 | Non-SWDA E2E | FR-STU, FR-ADM-100–140 (subset) |
| P2 | SWDA parallel-run | FR-SWDA-* |
| P3 | Finance reconcile | FR-FIN-* |
| P4 | CRM conversion | FR-CRM-* |

---

## 22 · Open discovery (`DQ-*`)

| ID | Question | Owner |
|---|---|---|
| DQ-SWDA-001 | Complete API manifest | HK |
| DQ-XERO-001 | COA mapping | Finance |
| DQ-PRIV-001 | Privyr import mode | CPO |

---

## 23 · Glossary (symbol table)

| Term | Definition |
|---|---|
| **SWDA** | **Skills and Workforce Development Agency** — Singapore agency (from Jul 2026 merger of SSG + WSG) overseeing WSQ, subsidies, SkillsFuture Credit, and ATP compliance via systems such as TPGateway |
| **WSQ** | Workforce Skills Qualifications — national skills certification framework |
| **ATP** | Approved Training Provider |
| **TPGateway** | Training Partners Gateway — SWDA platform for ATP course/trainee/claim submissions |
| **SFC** | SkillsFuture Credit — individual learner funding wallet |
| **SoR** | System of Record |
| **NYC** | Not Yet Competent |
| **Outbox** | Transactional event relay pattern |

---

*Author: Eddy (CPO) · AIEE.DEV · Cross-ref: CRD §02 · Implementation Doc v1*
