# P8-identity-contracts — findings

| | |
| --- | --- |
| Reviewer | Raheem takes the decisions; Zayed records them and locks |
| Reviewed | 23 سبتمبر 2026 — Zayed, on his own authority after the grilling of that day (Raheem's review may still overrule; an overrule is absorbed as a story and, where it changes a screen, a new P<n>) |
| Status | locked |
| Supersedes | the **registration and claim** half of `P2-onboarding-profile`, and the **roster and contract** half of `P4-club-admin`, both locked **21 سبتمبر 2026** |
| Superseded by | — |

## What this supersedes, and why

Two locked prototypes are superseded **in part**, and it matters which part.

**`P2-onboarding-profile`, locked 21 سبتمبر 2026** — its registration and claim
screens are superseded. **Its form shape (D1), its control-per-data-type answer
(D2), its agent area (D4), its mobile navigation (D5), its session expiry (D6),
its account shape (D7) and its notifications inbox (D8) are untouched and stand.**

**`P4-club-admin`, locked 21 سبتمبر 2026** — its roster and contract screens are
superseded. **Its season control (D1), its structure drill-down (D2), its squad
refusal (D3) and its attach-or-create answer (D4) are untouched and stand.**

**What made a successor necessary** is T6, #14, #20, T5, T7 and T1, all of them
in Raheem's **22 September 2026** response. T6 gives identity two key sets and
had no screen. #14 turns corporate registration into a single submission and
closes a question the 21 September lock record carried as open. T5 introduces a
contract with no end date. T7 brings the signed document back into the platform
as the source of every date. T1 turns the roster's window from an eligibility
gate into an initiation flip.

**Why these two halves are one prototype and not two.** His own build order has
a single first phase — accounts, registration and claim, the profile registry,
and the contract entity with `contract_type` and a nullable `end_date` — and the
reason they are one phase is visible on one screen here: **مهند العسيري is an
identity fact and a contract fact at the same time.** He has no contract end
date, which is a contract question, and he is registered to a club, which is an
identity one. T5 cannot be judged unless both are in front of the reviewer at
once. The precedent for folding is P2 itself, which took in three screens from
build tickets that had no prototyping ticket rather than let them be built
undrawn.

**A reader who lands on either locked prototype from an old link finds this one
through the registry**: both entries in `prototypes.json` and in the root
`index.html` read `superseded-in-part` and name `P8-identity-contracts`.

## Outcome per decision

**Filled on 23 سبتمبر 2026 by Zayed; every outcome is a decision of the record on `sports-deal-web` #39.**

| Id | Decision | Options built | Switch value | Outcome | Evidence | Writes back to |
| --- | --- | --- | --- | --- | --- | --- |
| **D1** | Does an amateur's club registration constrain his movement? | unrestricted · still bound | `amateur=free` · `amateur=registered` | **unrestricted: he initiates and moves on any day; his registration is a record of where he plays; registration windows still govern registering the resulting deal** (`amateur=free`) | `screenshots/roster--default--ar--free.png`, `screenshots/roster--default--ar--registered.png`, `screenshots/contract--partial--ar--free.png` | (a) |
| **D2** | The referee, and types that are not transacted | a flag on the reference data · drop the type | `transactable=flag` · `transactable=drop` | **the flag; a non-transactable profile is a claimable identity with no market presence and no offers** (`transactable=flag`) | `screenshots/registry--default--ar--flag.png`, `screenshots/registry--default--ar--drop.png` | (a) |
| **D3** | One search box, two key sets — *posed here, not by him* | detect the script · choose nationality first | `search=detect` · `search=scoped` | **neither as posed: one box, always searched against both name columns and both keys; a name it cannot place returns no match** (—) | `screenshots/index--default--ar--detect.png`, `screenshots/index--default--ar--scoped.png` | (a) |
| **D4** | What an open, unclaimed account holds — *posed here, not by him* | nothing · a provisional profile | `unclaimed=none` · `unclaimed=provisional` | **nothing until self-onboarding or a claim; a self-onboarded profile is market-visible at the lowest verification tier** (`unclaimed=none`) | `screenshots/index--partial--ar--none.png`, `screenshots/index--partial--ar--provisional.png` | (a) |
| **D5** | The CR lookup hits a club that already has an owner | a challenge · a join request | `crhit=challenge` · `crhit=join` | **a challenge, the record type P2 D3 locked, worked by the platform** (`crhit=challenge`) | `screenshots/company--partial--ar--challenge.png`, `screenshots/company--partial--ar--join.png` | (a) |
| **D6** | The signed contract upload | a gate on closing · a follow-up | `upload=gate` · `upload=followup` | **a gate on registered, not on agreed: the deal is agreed without the document and registered only with the signed contract's dates** (`upload=gate`) | `screenshots/contract--default--ar--gate.png`, `screenshots/contract--default--ar--followup.png` | (a) |
| **D7** | The free window on the roster | a state on the row · a queue of its own | `window=row` · `window=queue` | **both: a state on the row and a fourth queue over the same rows, shown regardless of the club's intentions** (`window=row` and `window=queue`) | `screenshots/roster--default--ar--row.png`, `screenshots/roster--default--ar--queue.png` | (a) |

### Two decisions are this prototype's, not his

**D3 and D4 are questions this prototype is posing.** They are marked as such
here, in `screens.md` and on the screens themselves, because a decision
attributed to the wrong person is worse than an open question.

- **D3** — T6 says a Saudi is searched **in Arabic** and a non-Saudi's name is
  stored **in English**. It never says how one box serves both, and
  Mateo Ferreira has no Arabic form of record at all.
- **D4** — T6 says registration is **open** and all verification sits at the
  **claim**. It never says what an account holds in the gap between the two.

### The second half of each decision

| Id | The second half |
| --- | --- |
| D1 | If still bound — bound by **what**: a notice period the club sets, or the federation's registration window? |
| D2 | If the flag — **what is a non-transactable profile for?** Board item #20 has carried this open since 21 سبتمبر 2026. |
| D3 | What does the box do with a name it cannot place? Neither option describes it. |
| D4 | Is a provisional profile searchable by other people? If it is, it is a duplicate; if it is not, it is nearly the first answer. |
| D5 | What happens when the sitting owner never answers a join request? |
| D6 | Under the follow-up answer, what are a deal's dates before the upload arrives? T1 reads them and they do not exist yet. |
| D7 | Is the window shown on a person whose club has no intention of losing him? Today that is all of them. |

---

# Conflicts with locked decisions

## C1 — The availability vocabulary cannot say what an amateur is

`P1-market-journey` locked the five availability statuses and they live in
`shared/tokens.css`, which the Next.js app copies verbatim: **لاعب حر، منفتح على
العروض، متاح للإعارة، متعاقد، غير معروف.**

**مهند العسيري is none of them.** He is registered to نادي الصحراء, so he is not
لاعب حر. He holds no contract with an end date, so he is not متعاقد. And غير
معروف is a gap in the platform's knowledge, not a fact about him — the platform
knows exactly what he is.

**Resolved by addition, on Zayed's instruction of 22 سبتمبر 2026.** A sixth
status, **«هاوٍ مسجَّل» / "Registered amateur"**, with its own token pair and its
own `.badge-amateur`. The change is **purely additive**: two custom properties
were added to each of `:root` and `.dark`, **no existing value was changed**, and
no locked prototype names the class or the variable, so none of them can see it.
`shared/tokens.css` is the source of truth for the web app and the new pair must
be copied across with the rest.

Evidence: `screenshots/roster--default--ar--free.png`,
`screenshots/contract--partial--ar--free.png`.

## C2 — Two locked screens render a non-Saudi's name against T6

T6 keys a non-Saudi on a passport and stores the name **in English**. The cast
holds three non-Saudis. **Joao Ribeiro — جواو ريبيرو** is written Latin-first
with the Arabic beside it, which is what T6 implies. **إيفان ماركوفيتش** and
**مامادو ديوب** are written in Arabic alone, in prototypes that are locked and
cannot be repaired.

**Not repaired.** Under T6 their record is the Latin form and the Arabic is a
rendering. The two locked screens stand as the dated record; the rule is stated
in `FIXTURES.md` and the mismatch goes to the re-plan.

## A distinction the locked roster has no way to draw

Not a conflict — a gap, and the clearest single finding in this prototype.

`P4-club-admin`'s roster has a `partial` state of **exactly three people with
«لا عقد مسجّل»**. مهند العسيري is **not a fourth**: he has a contract, and it has
no end date. The "ends" column is blank for all four and the reason is different
in one of them. **"No contract recorded" is a gap in the data to be filled;
"a contract with no end date" is a complete fact about a person.** The locked
roster cannot say the second, and the sixth availability status exists because
of it.

Evidence: `screenshots/roster--partial--ar--free.png`.

---

# Write-back

**Nothing below is written back until a reviewer has filled the outcome table.**

**No requirement id from any parked requirement document appears anywhere below.**

## (a) New stories — appended

> **Track A is held with Zayed.** The two identity keys, `contract_type` with a
> nullable `end_date`, `is_transactable` and the shadow club record are all model
> changes and every one of them is on the hand-off list. **Nothing in
> `safqa-sport` and nothing in the Track A half of `gh-project/manifest.json`
> was edited.**

Named by what they build, because these epics do not yet exist under an id in
`gh-project/manifest.json` — the same convention P2's and P5's `findings.md`
used. Append-only.

### Track D — identity, registration and claim

```
- **As a Saudi registering**, my name is stored in Arabic and in English and my record is keyed on my national ID, so that two profiles can never share one identity.
  - Accept: the national ID is the uniqueness key; both name forms are stored; search is performed in Arabic.
- **As a non-Saudi registering**, my name is stored in English and my record is keyed on my passport number, so that an identity with no Arabic form is still unique.
  - Accept: the passport number is the uniqueness key; no Arabic name is asked for or required.
- **As anyone at all**, I may create an account with no restriction, so that registration does not gate on a document I do not have yet.
  - Accept: registration asks for no proof of identity; the sign-up error discloses nothing about who is already registered.
- **As someone claiming a profile**, I attach a passport or a national ID, so that a known player's profile cannot be seized by somebody impersonating him.
  - Accept: a claim without a document is not submitted; the document is checked against the profile before the claim is granted.
- **As the platform**, the uniqueness constraint catches a collision even when the user walked past the search, so that skipping a step cannot manufacture a duplicate.
  - Accept: the guard runs at creation on the national ID, the passport number and the commercial registration; a match returns the existing record and creates nothing.
- **As someone registering a club**, my credentials, the club's identity and my evidence of claim are one submission, so that a corporate applicant is not asked to make an account and then start again.
  - Accept: the commercial registration is looked up before any record is created; an existing club profile is attached, never duplicated; the applicant is designated the owner on approval.
### Track D — contracts, the amateur and the window

- **As a club**, a person on my roster may hold an amateur contract with no end date, so that the platform covers amateurs and not only professionals.
  - Accept: `contract_type` is professional or amateur; `end_date` is nullable and valid only for an amateur; amateur status is never inferred from age; age category stays an independent axis.
- **As the platform**, every date-derived rule states that it does not apply to an amateur rather than returning nothing, so that a rule that cannot apply is not read as missing data.
  - Accept: the free window, expiry alerts and pre-contract eligibility are each explicitly conditional on a professional contract and say so on screen.
- **As a club**, I see at a glance which of my people hold a contract and which hold no contract record at all, so that a gap in the data is not confused with a fact about a person.
  - Accept: "no contract recorded" and "a contract with no end date" are distinct states with distinct availability statuses.
- **As a buying club**, I upload the signed contract with its start and end dates, so that the dates every rule reads come from the document the parties actually signed.
  - Accept: drafting and signing happen outside the platform; the uploaded dates drive the free window and the expiry alerts; automated generation and clause execution are out of scope.
- **As a club**, a person I already hold is renewable on any day of the contract, so that the market's windows govern only contracting with somebody another club holds.
  - Accept: renewal is available throughout the contract; market windows are not consulted for it.
### Track D — the registry and club records

- **As the platform**, a profile type records whether it is transacted at all, so that a valid profile with no contracts module is representable.
  - Accept: interpreters, therapists and media staff are transacted; a type that is not gets a stated refusal rather than an empty select.
- **As anyone reading a club record**, a club on the platform and a club that is not are told apart, so that the platform holds a contact address only where it needs one.
  - Accept: a non-member is a shadow record with a name and a contact address and nothing else; it is not counted among the approved clubs.
```

## (b) Notes on existing stories

| Epic | Which story | Note |
| --- | --- | --- |
| the registry-driven self-onboarding epic | sign-up | registration is **open**: no document is asked for, and every verification moved to the claim |
| the registry / reference-data epic | the profile types | each type now records whether it is transacted at all; interpreters, therapists and media staff are, a referee is not |
| the claim-a-pre-created-profile epic | the claim | a claim now **requires a document**, specifically to stop a known player's profile being seized |
| the claims epic | the uniqueness rule | unchanged, and now keyed two ways: national ID for a Saudi, passport number for a non-Saudi |
| the club registration and approval epic | the club application | **the open question is closed**: it is one submission carrying credentials, entity identity and claim evidence, with the owner designated on approval |
| the roster epic | the contract record | `contract_type` and a **nullable** `end_date`; "no contract recorded" and "a contract with no end date" are different states |
| the roster epic | the expiry horizons | one and two years stand, and a third horizon joins them: the contract's end minus six months, where initiation flips |
| the club-directory epic | the club record | a club that is not on the platform is a shadow record with a contact address, and is not counted among the approved clubs |

## (c) Rules for the code repo — `.ai/rules/`

| Glob | Title | Note |
| --- | --- | --- |
| `src/**/contract*` | `end_date` is nullable from the first migration | Retrofitting a nullable end date onto a table every date rule already reads is materially harder than carrying it from the start. |
| `src/**/contract*` | Date rules are conditional on `professional` | A rule that cannot apply to an amateur must not apply at all. Returning null makes a complete fact read as missing data. |
| `src/**/profile*` | Never infer amateur status from age | An amateur may be well over 23. Age category and professional status are independent axes. |
| `src/**/identity*` | Two uniqueness keys, not one | National ID for a Saudi, passport number for a non-Saudi. The guard runs at creation regardless of the path taken to get there. |
| `src/styles/tokens.css` | The sixth availability status | Copied from `sports-deal-prototypes/shared/tokens.css`. Additive: no existing value changed. |

---

## Rejected options

| Option | Why it was rejected | Evidence |
| --- | --- | --- |
| Two prototypes — identity, and contracts | Merged: seven decisions and eight screens, smaller than P2 was at eight and twelve, and P2 was one review slot. Splitting buys two shorter sittings at the cost of a whole scheduling slot, and the amateur is only legible with both halves on one screen. | `screenshots/roster--partial--ar--free.png` |
| Superseding P2 and P4 whole | Twelve of their sixteen decisions are untouched by the 22 September response. Superseding them whole would re-ask twelve settled questions. | `../P2-onboarding-profile/findings.md`, `../P4-club-admin/findings.md` |
| Rendering the amateur as a free agent and not touching `tokens.css` | Considered and put to Zayed on 22 سبتمبر 2026, who chose the sixth value. The badge would have contradicted his club registration on the same row. | `screenshots/roster--default--ar--free.png` |
| Drawing the member/non-member **decision** here | The indicator's purpose is to drive which action is offered, and both actions exist only on the negotiation screens. The record is drawn here; the fork is settled in `P7-negotiation-market`. | `screenshots/clubs--partial--ar.png` |
| A notification event catalogue screen | An enumeration with no surface. P2 D8 already locked the inbox shape and that a notification is never an action. | — |

## Not settled

- **What is a non-transactable profile for?** Open since 21 سبتمبر 2026. `is_transactable` names the state without answering it.
- **What does the search box do with a name it cannot place?** The cast holds no example, deliberately.
- **Who owns an amateur's notice period** under D1's second answer — the club, or the federation?
- **What are a deal's dates before the contract is uploaded**, under D6's follow-up answer?
- **Does a provisional profile appear in another person's claim search?**
- **What happens when a sitting club owner never answers a join request?**
- **The cross-club amateur case** — his registration moving between two clubs in one division, which is what the transcript describes — has no screen, and one club's roster cannot draw it.

## Lock record — 23 سبتمبر 2026

Locked by Zayed on his own authority, every decision answered in the grilling of 23 September 2026 and recorded on `sports-deal-web` #39. The second halves: an amateur is bound by nothing but the registration of the resulting deal; a non-transactable profile is a claimable identity with credentials and no market presence; a name the box cannot place returns no match and the create path runs the key guard; no provisional profile exists, so nothing of it is searchable; the sitting owner is not waited on, the platform decides the challenge; before the upload a deal has no contract dates because it is not registered; the window is shown on every person inside it. C1's sixth status stands, additive. C2 stands as a dated record. The Track A write-back is the re-plan of the same day: [A1.1], [A1.3], [A2.1], [A2.6], [A2.8], [A0.13], [A3.5] in `gh-project/manifest.json`.
