Skip to main content
Petanque Life
← Back to all features
05

Officials & Judiciary

108 features · 9 subsystems

Management of umpires (all grades L1-L5), competition officials, disciplinary processes, and the judicial framework of petanque. Covers training, certification, assignment, and the entire disciplinary pipeline.

Umpire Registry

F05.01
Planned

### Rebuild status — D-OFF m-1 (Go-backend, `60df81b6.m-1`)

How it works
  • F05.01.01 Shipped

    Configurable umpire grade system per tenant — each federation defines its own grade names, count, and progression path. Examples: Sweden (3: distrikts→nationell→internationell), Germany (4: Landesverband→DPV→CEP→FIPJP), France (5: club→départemental→régional→national→international). Grade list is tenant configuration, not hardcoded.

    ✅ PL-F0501a
  • F05.01.02 Shipped

    Umpire profile (grade, languages, specializations, photo, exam history)

    ✅ PL-F0501a
  • F05.01.03 Shipped

    Grade progression tracking (configurable path per tenant)

    ✅ PL-F0501a
  • F05.01.04 Shipped

    Certification exam management (schedule, results, pass/fail). Supports FIPJP exam rules: max 2 candidates per nation per year, age limit 60, exam held at VM.

    ✅ PL-F0501a
  • F05.01.05 Shipped

    Annual license renewal for umpires

    ✅ PL-F0501a
  • F05.01.06 Shipped

    Umpire availability calendar

    ✅ PL-F0501b
  • F05.01.07 Shipped

    Languages spoken (for international assignments)

    ✅ PL-F0501b
  • F05.01.08 Shipped

    Umpire statistics (matches officiated, competitions, incidents)

    ✅ PL-F0501b
  • F05.01.09 Shipped

    Umpire directory (searchable by grade, district/region, language, availability)

    ✅ PL-F0501b
  • F05.01.10 Shipped

    Umpire ID card (digital)

    ✅ PL-F0501b
  • F05.01.11 Shipped

    CEP/FIPJP certification tracking — umpire can hold both national and international certifications. CEP certification as stepping stone to FIPJP. Candidacy period (Germany: 1 year).

    ✅ PL-F0501c
  • F05.01.12 Shipped

    Playing umpire flag — configurable per tenant/competition level: some nations allow umpires to also play at local events (Sweden: local only), others never.

    ✅ PL-F0501c
  • F05.01.13 Shipped

    Required umpire grade per competition level — configurable per tenant (e.g., Sweden: distriktsdomare for local/regional, nationell for national, internationell for international)

    ✅ PL-F0501c

Umpire Assignment

F05.02
Planned

> **Rebuild (D-OFF m-5 · `60df81b6.m-5` t-1):** F05.02.09–F05.02.10 byggda som rena `/v1`-kontrakt i

How it works
  • F05.02.01 Shipped

    Competition umpire requirements definition

    ✅ PL-F0502a
  • F05.02.02 Shipped

    Umpire invitation and acceptance workflow

    ✅ PL-F0502a
  • F05.02.03 Shipped

    Automatic assignment suggestions (based on grade, location, availability)

    ✅ PL-F0502a
  • F05.02.04 Shipped

    Conflict of interest detection (umpire vs. participating club/player)

    ✅ PL-F0502a
  • F05.02.05 Shipped

    Umpire expense reimbursement tracking — flat arvode (m-4) + full domarutlägg/resa-ledger med F1-6-attest + PAY-utbetalning (m-7). referee_expense_claim: rader (logi/mat/traktamente/resa/övrigt) + kvittoreferenser (→ D-MED), attestkedja MANAGER_REVIEW → FINANCE_APPROVED (separation of duties), PAY-2 resolve + F1-11 payments, PAY OnSuccess → paid. Speglat coach/mentor.

    ✅ PL-F0507a (m-7 t-1)
  • F05.02.06 Shipped

    Post-competition umpire evaluation

    ✅ PL-F0502b
  • F05.02.07 Shipped

    Assignment history per umpire

    ✅ PL-F0502b
  • F05.02.08 Shipped

    Referee team composition — for major events: designate head referee (arbitre principal) + supporting referees. Configurable team structure per competition level.

    ✅ PL-F0502b
  • F05.02.09 Shipped

    Matchday umpire rota (rondschema) — bind confirmed umpires/measurers to courts per round against D-COMP's draw/schedule (read-only, soft court_ref), round-robin court balancing, vilopass, needs_rebind on schedule drift, publish. D-OFF owns the binding; D-COMP owns draw/schedule/match.

    ✅ PL-F0505a (m-5 t-1)
  • F05.02.10 Shipped

    In-day frånvaro/ersättare/substitution — reserve/standby role; assignments/{id}/substitute flips ordinary→substituted, activates a backup and transfers the rota slots atomically, preserving the F1-6 request (no new request, no FSM).

    ✅ PL-F0505a (m-5 t-1)

Umpire Training

F05.03
Planned

### Rebuild status — D-OFF m-3 t-1 (Go-backend, `60df81b6.m-3`)

How it works
  • F05.03.01 Shipped

    Training course catalog (per grade level)

    ✅ PL-F0503a
  • F05.03.02 Shipped

    Online training modules (rules, scenarios, video)

    ✅ PL-F0503a
  • F05.03.03 Shipped

    Practical exam scheduling

    ✅ PL-F0503a
  • F05.03.04 Shipped

    Mentorship program (experienced umpire + trainee)

    ✅ PL-F0503a
  • F05.03.05 Shipped

    Continuing education requirements

    ✅ PL-F0503a
  • F05.03.06 Shipped

    Training material management (per language)

    ✅ PL-F0503b
  • F05.03.07 Shipped

    Candidacy/apprentice status — configurable per tenant: formal candidacy period between grades where candidate is observed before exam (Germany: Anwärterjahr, 1 year). Time-based progression gate.

    ✅ PL-F0503b
  • F05.03.08 Shipped

    Re-certification after license lapse — configurable per tenant: referees who stop for a period must complete refresher course before regaining grade (Spain: reciclaje).

    ✅ PL-F0503b
  • F05.03.09 Shipped

    Exam question bank management — configurable per tenant: question banks with point thresholds, multi-part exams (written + practical). Germany: 204 questions, 368 points. FIPJP: max 2 candidates/nation/year, age limit 60.

    ✅ PL-F0503b
  • F05.03.10 Shipped

    Training-module content-CRUD — UmpireTrainingModule med versionerade kapitel, multi-lang body_md, embedded MediaAsset, prereq-DAG och publish-validering (final_quiz_required, cyclic_prerequisite).

    ✅ PL-T224
  • F05.03.11 Shipped

    Practice-exam engine — timed, randomized, auto-scored attempts (single/multi/ordered/scenario), cooldown + max-attempts, manual-review-routing för scenario_judgement utan deterministisk korrekthet.

    ✅ PL-T224
  • F05.03.12 Shipped

    Certification progress vy — denormaliserad CertificationProgress med next_required_action-derivation, CE-credit-rolling-12m, mentor- och commission-visibility.

    ✅ PL-T224
  • F05.03.13 Shipped

    Official evaluation från template — EvaluationTemplate med diplomatic-phrase-library + OfficialEvaluation med visibility-nivåer, validering av incomplete_responses, ack-flöde mot pathway-step.

    ✅ PL-T224

Competition Jury

F05.04
Planned

**Realtids-domarkallelse (`referee_call`, D-OFF m-6):** `POST /v1/referee-calls` uplink-ingest (D-SCORE/D-COMP, `scoring:write`, idempotent) → state-machine `open → acknowledged → arrived`/`cancelled`, deterministic domar-lookup (head_umpire+confirmed → any confirmed → otilldelad), `response_time_seconds`/`arrival_time_seconds` SLA + `…/referee-calls/stats`, admin-bord SSE. D-OFF owns the kallelse-state-machine only; push is D-PUSH's. (See `specs/api/endpoints/referee-calls.md`.)

How it works
  • F05.04.01 Shipped

    Jury composition per competition (president, members)

    ✅ PL-F0504
  • F05.04.02 Shipped

    Jury authority and scope definition

    ✅ PL-F0504
  • F05.04.03 Shipped

    On-field decision recording

    ✅ PL-F0504
  • F05.04.04 Shipped

    Appeal handling (team appeal to jury)

    ✅ PL-F0504
  • F05.04.05 Shipped

    Jury decision publication

    ✅ PL-F0504
  • F05.04.06 Shipped

    Jury report generation post-competition

    ✅ PL-F0504
  • F05.04.07 Shipped

    Rebuild /v1 Go contract — competition_jury (migration 00057), CRUD + append-only decisions + report publish; score_protest routed to the real jury (official:jury_decide granted at jury-constitution; approve triggers D-COMP's unchanged OnApprove hook — no match mutation in D-OFF).

    ✅ PL-F0506m6 (m-6 t-1)

Disciplinary System

F05.05
Planned

### Initiativ-avslut D-DISCIPLINE (`7635e69d.m-6`) — legacy-scenariernas bokföring

How it works
  • F05.05.01 Shipped

    Incident reporting (by umpire, official, player, or observer)

    ✅ PL-F0505a
  • F05.05.02 Shipped

    Incident classification (misconduct, cheating, doping, violence, abuse)

    ✅ PL-F0505a
  • F05.05.03 Shipped

    Disciplinary case management (open > investigate > hearing > decision)

    ✅ PL-F0505a
  • F05.05.04 Shipped

    Hearing scheduling and management

    ✅ PL-F0505a
  • F05.05.05 Shipped

    Evidence attachment (photos, videos, witness statements) — rebuilt on the Go contract: POST /v1/discipline/cases/{id}/evidence (multipart file + optional label), per-tenant MIME allow-list, magic-signature sniff, EXIF strip before hashing, 50 MiB cap, and the additive evidence timeline on the case detail.

    ✅ PL-F0505a · ✅ 7635e69d.m-2 (t-1)
  • F05.05.06 Shipped

    Sanction types (warning, fine, suspension, ban, point deduction) — rebuilt as the per-tenant catalogue GET

    PUT /v1/discipline/sanction-catalog (whole-layer PUT under If-Match, hidden entries stay valid on old decisions, money as INTEGER minor units + currency). Point deduction is not a category of its own: it belongs to the competition's own result model, not to the sanction ledger. | ✅ PL-F0505b · ✅ 7635e69d.m-3 (t-1)
  • F05.05.07 Shipped

    Sanction enforcement (automatic competition ban during suspension) — rebuilt as the active_sanction lookup (initiative §5d.3, NORMATIVE): an active suspension/ban in ANY scope — including a purely intra-tenant club/district sanction that never propagates — writes the lookup row D-COMP's entry gate (BR04.09) reads via GET /v1/internal/active-sanctions (M2M, service-scope, scope-rank coverage). warning/fine never write one. The licence bar goes through D-MEMBER's OWN seam (BR01.14).

    ✅ PL-F0505b · ✅ 7635e69d.m-3 (t-1)
  • F05.05.08 Shipped

    Appeal process (to higher federation level) — rebuilt: the next instance is resolved from the tenant's OWN org-node tree (the strictly higher node, m-1's depth rule), the window is m-3's EXISTING AppealWindowDays (one truth about the same deadline, never two), and the instance rule applies: in tier ≥ 2 the decision maker's mandate must reach escalation_target_org_node_id or higher — the instance that ruled first may not review its own decision. A federation without a higher node has an EXHAUSTED chain: no window opens, both fields are null and the answer says so in plain language.

    ✅ PL-F0505b · ✅ 7635e69d.m-5 (t-1)
  • F05.05.09 Shipped

    Sanction notification to all relevant parties

    ✅ PL-F0505b
  • F05.05.10 Shipped

    Sanction register (searchable by federation level) — rebuilt as GET /v1/discipline/sanctions ({data,page}, filters status/category/scope/case_id/subject, newest first). Deliberately a READ surface: an application is mutated only through propose/confirm/lift, the seams that also write the audit trail.

    ✅ PL-F0505b · ✅ 7635e69d.m-3 (t-1)
  • F05.05.11 Shipped

    Sanction expiry and rehabilitation tracking — rebuilt as the F1-7 job discipline_sanction_expiry_tick (*/15 * * * *): an active sanction whose effective_until has passed becomes expired, its lookup row is removed and the licence bar lifted through the SAME clearing seam the amnesty uses. A ban imposed *tills vidare* (null duration) is never touched — the clock cannot end what no one time-limited.

    ✅ PL-F0505c · ✅ 7635e69d.m-3 (t-1)
  • F05.05.12 Shipped

    Cross-federation sanction sharing (suspended player cannot play elsewhere) — rebuilt end to end (BR07.04): a national/international suspension/ban from a propagating catalogue entry is published to the FIPJP registry and mirrored into every subscribing federation's local_suspension_cache, so the subscriber's own GET /v1/internal/active-sanctions answers active=true with source.kind=cross_tenant for a player it never sanctioned itself. Subscription is an OPT-OUT (tenant_configs.cross_tenant_suspension_subscribed, default true).

    ✅ PL-F0505c · ✅ 7635e69d.m-4 (t-1)
  • F05.05.13 Shipped

    Full audit trail on all disciplinary actions

    ✅ PL-F0505c
  • F05.05.14 Shipped

    Alternative dispute resolution — configurable per tenant: formal arbitration/mediation as alternative to disciplinary proceedings (Spain: arbitraje de equidad with 3 arbiters). Configurable escalation path. ⊘ Byggs INTE i D-DISCIPLINE (7635e69d.m-5 non-goal 3): ADR står inte i den auktoritativa visionens §Vad som ingår och ägs av D-DISPUTE (docs/migration/initiatives/D-DISPUTE.md §Scope 3/5). Mockupens ADR-kort renderas därför inte i adminytans överklagandevy — en knapp utan en domän bakom sig är en attrapp.

    ⊘ ägs av D-DISPUTE ✅ PL-F0505c
  • F05.05.15 Shipped

    Discipline case-spine — full state-machine: draft → submitted → triage → evidence_collection → hearing_scheduled → hearing_in_progress → deliberation → decision_issued → appeal_window_open → appeal_window_closed/under_appeal → appeal_decision_issued → archived (+ withdrawn/dismissed/escalated side-paths).

    ✅ PL-T213
  • F05.05.16 Shipped

    Role-template-based intake — five role-driven intake forms (club_referee, court_marshal, event_marshal, jury_president, public) with required-field validation per role.

    ✅ PL-T213
  • F05.05.17 Shipped

    Severity auto-classification — derives severity_hint from offence-code → severity rank table at submit time; manual override requires rationale.

    ✅ PL-T213
  • F05.05.18 Shipped

    Tenant offence vocabulary — per-tenant offence taxonomy (codes, names, severity defaults) layered on canonical FIPJP offences; configurable per federation.

    ✅ PL-T213
  • F05.05.19 Shipped

    Tamper-evident evidence locker — SHA-256 hash chain per case (chain_hash = sha256(prev \

    | content_hash || iso_ms(submitted_at) || submitted_by)); periodic verifier flags integrity_alert. Rebuilt: the rule is ONE exported pure function frozen by a golden test, iso_ms pinned to 2026-06-21T14:32:05.123Z (UTC, exactly three decimals, Z), GET …/evidence/verify is a pure read returning {ok, items_checked, first_breach_index, breach_evidence_id}. | ✅ PL-T213 · ✅ 7635e69d.m-2 (t-1)
  • F05.05.20 Shipped

    WORM evidence blob storage — locked retention (default 10 years), MIME-type allow-list, EXIF stripping for photos. Rebuilt: the EvidenceBlobStore port has NO delete and NO overwrite path (second PutLocked ⇒ error); backend chosen by PL_EVIDENCE_BLOB_BACKEND (unknown ⇒ boot error, surface not mounted); image/heic dropped from the allow-list (no CGO-free strip — an unstripped photo blob would be a defect). Azure Blob is no longer assumed.

    ✅ PL-T213 · ✅ 7635e69d.m-2 (t-1)
  • F05.05.21 Shipped

    Evidence supersede / withdraw / redact — versioned evidence with chain-preserving supersede, withdrawal marker, and GDPR-redaction overlay (original blob untouched). Rebuilt: If-Match on the CASE ETag + Idempotency-Key, mandatory rationale on withdraw/redact, supersede refused on top of a broken chain (409 evidence_chain_breach), redact allowed in EVERY state including archived.

    ✅ PL-T213 · ✅ 7635e69d.m-2 (t-1)
  • F05.05.22 Shipped

    Sanction catalog with recommended-range engine — propose-sanction validates against precedent-derived [min,max] window; deviation requires rationale. Rebuilt: the percentile method is pinned NEAREST-RANK (1-based d[⌈0,25n⌉]/d[⌈0,5n⌉]/d[⌈0,75n⌉], golden [2,4,6,10,20] ⇒ 4/6/10) as ONE pure function in api/internal/discipline; cohort minimum 5 with widening to the same severity, and below that sample_size: 0 with the deviation gate SUPPRESSED — a range computed on three cases is a guess with decimals.

    ✅ PL-T213 · ✅ 7635e69d.m-3 (t-1)
  • F05.05.23 Shipped

    Panel decision with optimistic locking — confirm_panel_decision requires expected_deliberation_version; majority-vote enforced; auto-advances case to appeal_window_open. Rebuilt as POST /v1/discipline/cases/{id}/sanctions/{aid}/confirm: stale panel version ⇒ 409 sanction_conflict with ZERO writes (distinct from the case ETag's 412 stale_version), majority is approve > ⌊n/2⌋ with abstentions in the denominator (⇒ 422 majority_not_reached), and the two spine edges run through m-1's own transition seam in ONE transaction.

    ✅ PL-T213 · ✅ 7635e69d.m-3 (t-1)
  • F05.05.24 Shipped

    Sanction lifecycle ticks — rebuilt as discipline_appeal_window_close_tick (*/5 * * * *) and discipline_sanction_expiry_tick (*/15 * * * *), runnable as api run-job <name> and by the worker. Both are SINGLE-FIRE (source status + clock are WHERE conditions in the write itself ⇒ a dense re-run changes zero rows and writes zero audit), take the clock as an argument, are batch-capped and handle errors per row. The close tick also drives the case's own appeal_window_open → appeal_window_closed edge — with an anonymous system actor, because no human closed that window.

    ✅ PL-T213 · ✅ 7635e69d.m-3 (t-1)
  • F05.05.25 Shipped

    Formal appeals workflow — file → admissibility → hearing → decided (overturned/modified/upheld/dismissed); cascades sanction status; opens next-tier escalation window if upheld/modified. Rebuilt on the Go contract: TWO filing routes but ONE seam (POST /v1/me/discipline/sanctions/{aid}/appeal self-guarded per BR24.09, POST /v1/discipline/cases/{id}/appeals under the case ETag), the appeal FSM in api/internal/discipline (fail-closed; an illegal edge ⇒ 409 appeal_lifecycle_error, distinct from the case's case_lifecycle_error and the sanction's sanction_conflict), and the outcome cascade INSIDE m-3's two lifecycle seams — clearSanctionEffects for overturned/modified, activateSanction for upheld/dismissed/inadmissible with the start RE-ANCHORED to the decision moment (the time under review was not a served suspension). Appealing is suspensive only BEFORE enforcement (an already-active sanction keeps its lookup, licence bar and ACTIVE registry row), the clock decides the window (two additive spine edges so a tick can never beat a human by milliseconds), and modified gives birth to a NEW sanction row with its own registry entry.

    ✅ PL-T213 · ✅ 7635e69d.m-5 (t-1)
  • F05.05.26 Shipped

    Cross-tenant suspension registry — published to FIPJP tenant, signed payload (HMAC-SHA512 stub → ES512 in prod), replicated to subscriber LocalSuspensionCache for O(1) license checks. Rebuilt: there is NO publish endpoint — the entry and its fan-out are written INSIDE m-3's activateSanction (a DB write + a pure signature; no network in the transaction) and the activation is NEVER failed by the registry, only booked (cross_tenant_seam: published / skipped_not_eligible / skipped_unidentifiable_subject / skipped_no_registry_tenant / skipped_no_signing_key / deferred_error). Eligibility is ONE pure predicate over five conditions, the canonical payload's twelve fields are frozen by a unit test (privacy as a schema property: no column exists for case number, offence, severity, grounds, evidence or parties), and source_sanction_application_id is UNIQUE — one activation, one entry, for ever.

    ✅ PL-T213 · ✅ 7635e69d.m-4 (t-1)
  • F05.05.27 Shipped

    Cross-tenant fan-out reconciler — every 4 h: retries pending fan-outs (max 8 attempts), expires entries past effective_until, ack tracking per tenant. Rebuilt as the F1-7 job discipline_cross_tenant_reconcile (0 */4 * * *, api run-job …) with four duties in order — publish sweep (the GUARANTEE: a missed publication can never become a permanent gap), retry of UNCONFIRMED deliveries, new subscribers, expiry (effective_until = null is never touched). The retry discriminator is CONFIRMED DELIVERY, not the status word, so a dense re-run changes zero rows; the cap of 8 sets failed once with an operator alarm. Injected clock, batch caps, per-row error handling.

    ✅ PL-T213 · ✅ 7635e69d.m-4 (t-1)
  • F05.05.28 Shipped

    Precedent materializer — every 1 h: snapshots terminal cases into SanctionPrecedent registry (anonymised case-id hash) so search & recommended-range stay warm. Rebuilt as the F1-7 job discipline_precedent_materialize: the A-7 filter lives in the QUERY (terminal AND a panel-confirmed sanction — a dismissed, sanctionless case has no days to recommend from, and counting it as "0 days" would drag every recommended range down), the key is the domain's OWN AnonymizedCaseKey (never a hex literal), appeal_outcome is read from the case's DECIDED appeal_record (m-5's written hand-over) else not_appealed, a case without a decision node lands on org level national (the cohort m-3's fixtures already warmed, never a "null level" nobody reads), and precedent_materialized_at is null sits in the UPDATE's own WHERE ⇒ a dense re-run writes zero rows and zero audit. The sweep IS the guarantee: a transition hook could be forgotten in one of the paths to terminal and would never catch up with history.

    ✅ PL-T213 · ✅ 7635e69d.m-6 (t-1)
  • F05.05.29 Shipped

    Public protocol publication — anonymised, locked Markdown ProtocolDocument; refuses safeguarding cases and minor subjects; corrections appended as immutable diffs (original markdown preserved). Rebuilt on the Go contract: publishable states are EXACTLY {appeal_window_closed, appeal_decision_issued, archived} (fail-closed on unknown), the protection gates are structurally un-overridable (no redaction_overrides in the body, no override/force/bypass parameter in ProtocolProtectedParty's signature — mechanically guarded both statically and behaviourally) and carry a THIRD reason beyond legacy: unverifiable_age (a person subject without subject_birth_year cannot be proven an adult ⇒ protected, A-2). Refusal is 403 protected_party with the reason CLASS in detail, never the subject's data; the gate ORDER (locale-422 → spine-409 → protection-403 → duplicate-409) is normative because the wrong order leaks a case's content through WHICH refusal answers. Parties are aliased stably and irreversibly, evidence is a COUNT, markdown is written once (no code path UPDATEs the column), corrections are append-only diffs, and unpublishing is TERMINAL: all three public URLs answer 410 with the one-liner *"Dokumentet har dragits tillbaka."*

    ✅ PL-T213 · ✅ 7635e69d.m-6 (t-1)
  • F05.05.30 Shipped

    GDPR legal-hold + retention sweep — cases default legal_hold=True; case_retention_sweep (24 h) flags expired non-held archived cases for purge. Rebuilt with the HALF the legacy surface never had: POST /v1/discipline/cases/{id}/legal-hold {held, rationale} (A-5 — without a release action no case could ever become un-held and the whole retention model was decoration), the F1-7 job discipline_case_retention_sweep with all four conditions in the UPDATE's WHERE (dense re-run ⇒ zero rows), and the actual erasure in D-GDPR's pipe: D-DISCIPLINE registers its erasure module (Guard + OnErasure) and answers RetentionGuard per row — keep(legal_hold) / keep(retention_until) / anonymise — with every keep reported in deletion_proof. Anonymisation replaces the party JSONB with ALIAS content, nulls user_ref (⇒ the generated columns go null and /me stops matching) and scrubs intake_partial_payload + appeal_record.grounds; the case row, case_number, statuses, sanctions and audit REMAIN de-identified (A-13), and evidence_item is never touched — chain_hash binds submitted_by/submitted_at/content_hash, so discipline_evidence_chain_verify is green before AND after (mechanically measured).

    ✅ PL-T213 · ✅ 7635e69d.m-6 (t-1)
  • F05.05.31 Shipped

    Discipline audit dual-write — every action writes both DisciplinaryAuditEntry (domain-specific) and platform AuditLog.

    ✅ PL-T213
  • F05.05.32 Shipped

    Self-service player view — /me/discipline/cases lists own cases & sanctions; /me/discipline/sanctions/{id}/appeal files appeal within window.

    ✅ PL-T213
  • F05.05.33 Shipped

    Rebuild /v1 Go contract — case-spine core (migration 00181, package api/internal/discipline): the 16-state machine driven by VERBS (POST /v1/discipline/cases/{id}/transition; illegal jump ⇒ 409 case_lifecycle_error), role-template intake (soft at create, hard at submit), severity derivation + override-with-rationale, race-safe immutable DISC-<YYYY>-<NNNN> case numbers, tenant offence vocabulary over the canonical FIPJP base, the scope-filtered commission queue with SLA/overdue, /me (404-not-403) and audit dual-write in ONE transaction. New capabilities: discipline:create, discipline:manage, discipline:decide, discipline:configure, self.discipline.access. The OrgNode ladder (club ≤ MODERATE, district ≤ SEVERE, national/cep/fipjp ≤ CRITICAL, plus org_node_autonomy.discipline) gates issue-decision ⇒ 403 decision_rights_exceeded. Local fixture seed-discipline; contract specs/api/endpoints/discipline-cases.md. Evidence locker, sanctions/panel, cross-tenant registry, appeals and protocols follow in later milestones.

    ✅ 7635e69d.m-1 (t-1)
  • F05.05.34 Shipped

    Rebuild /v1 Go contract — tamper-evident evidence locker (migration 00182, api/internal/discipline/evidence.go): the append-only chain (seq allocated under the case row lock, unique (case_id, seq), ms-normalised submitted_at that survives the DB round-trip), the WORM port without a delete path, the per-tenant MIME layer over code constants + EXIF strip before hashing, supersede/withdraw/redact, chain verification, HMAC-signed short-lived downloads (503 rather than an unsigned link), the additive evidence timeline + integrity_alert on the case detail, dual-written audit (discipline.evidence.*, discipline.case.integrity_alert) and the F1-7 job discipline_evidence_chain_verify (24 h, single-fire, never auto-clears the flag). Local fixture: the seed-discipline evidence delta (a REAL five-item chain built through the API's own seams — no hand-written hashes). Contract specs/api/endpoints/discipline-evidence.md. Admin/app surfaces follow in 7635e69d.m-2 (t-2); sanctions/panel, cross-tenant registry, appeals and protocols in later milestones.

    ✅ 7635e69d.m-2 (t-1)
  • F05.05.35 Shipped

    Rebuild /v1 Go contract — sanction core (migration 00183, api/internal/discipline/sanction.go, api/internal/store/discipline_sanction_lifecycle.go): the per-tenant sanction catalogue (whole-layer PUT under If-Match, hidden entries, money as integer minor units), the NEAREST-RANK recommended-range engine over anonymised precedent, propose (state gate, category rules with field pointers, BR24.05's deviation gate, snapshot of the range, bumping BOTH the case version and deliberation_version), the panel confirm (409 sanction_conflict on a stale panel version, 422 majority_not_reached, window anchored at confirmed_at + 14 d, two spine edges through m-1's seam), the manual lift, the active_sanction lookup + GET /v1/internal/active-sanctions (M2M) that makes an INTRA-tenant suspension visible to D-COMP's entry gate, the licence seam to D-MEMBER (BR01.14 — discipline never writes a licence table itself), the sanction register and the additive detail//me projections (the party never sees voting record, range snapshot or deviation rationale), audit dual-write (discipline.sanction.*) and the two F1-7 jobs discipline_appeal_window_close_tick (5 min) and discipline_sanction_expiry_tick (15 min). Local fixture: the seed-discipline sanction delta (catalogue, armed precedent cohort, an OPEN panel case and an ACTIVE one driven through the close tick's OWN job seam — no hand-written statuses). Contract specs/api/endpoints/discipline-sanctions.md. Admin/app surfaces follow in 7635e69d.m-3 (t-2); cross-tenant registry, appeals and protocols in later milestones.

    ✅ 7635e69d.m-3 (t-1)
  • F05.05.36 Shipped

    Rebuild /v1 Go contract — cross-tenant suspension registry (migration 00186, api/internal/discipline/cross_tenant.go, api/internal/store/discipline_cross_tenant.go): the registry on the FIPJP tenant (resolved on code='FIPJP' + tenant_type='fipjp' — D-OFF's exact resolution), the eligibility predicate, the FROZEN twelve-field canonical payload in the {kid, alg, payload, signature} envelope (HS512 hex-HMAC-SHA512 over sorted-key, whitespace-free JSON with ms-precision UTC; key from PL_DISCIPLINE_CROSS_TENANT_SIGNING_SECRET, documented dev fixture only in local/test, and NEVER an unsigned row), publication + fan-out INSIDE m-3's activation seam and the status cascade inside clearSanctionEffects, the normalised propagation table projected to the vision's propagation_status_per_tenant, the subscriber mirror (local_suspension_cache, idempotent, soft source_entry_id), the F1-7 job discipline_cross_tenant_reconcile (0 */4 * * *, four duties, retry on CONFIRMED DELIVERY, cap 8, single-fire), the registry routes (list/detail/ack/lift; a non-FIPJP tenant ⇒ 403 registry_tenant_required, M2M read+ack but never lift), the PUBLIC privacy-safe check GET /v1/public/discipline/suspensions/verify (five fields, {found:false} instead of an existence oracle, per-IP cap) and the read-only operator health GET /v1/sys/discipline/suspension-health, plus the ADDITIVE source.kind ∈ {local, cross_tenant} on GET /v1/internal/active-sanctions. OverturnCrossTenantSuspension is built as the RECEIVING edge only — the appeal sender is m-5. Local fixture: the seed-discipline registry delta (FIPJP tenant ensured through the house's own tenant seam, ops identity disciplin-fipjp-seed@demo.petanque.life, the published row produced by the REAL chain, FRA acknowledged through the real ack seam, NOR left pending, no seeded failed row). Contract specs/api/endpoints/discipline-fipjp-suspensions.md. FIPJP/sys surfaces follow in 7635e69d.m-4 (t-2); appeals and protocols in later milestones.

    ✅ 7635e69d.m-4 (t-1)
  • F05.05.37 Shipped

    Rebuild /v1 Go contract — public protocol, GDPR retention & measurement (migration 00192, api/internal/discipline/protocol.go, api/internal/store/discipline_protocols.go / …_retention.go / …_precedent_materialize.go / …_dashboard.go / gdpr_erasure_discipline.go): the nine routes (publish / corrections / unpublish, the legal-hold release, the commission dashboard, the operator overview and the three PUBLIC read surfaces …/protocols/{year}[/{alias}[/print]]?tenant=<KOD>), the two NEW problem codes protected_party (403) and protocol_conflict (409), the two F1-7 jobs discipline_precedent_materialize (hourly) and discipline_case_retention_sweep (24 h), and the erasure module registered in the house's registry (RetentionGuard per row + OnErasure + the THIRD retentionSweepSources row {discipline, discipline_case, discipline_records}, row-anchored so a federation without a configured TTL is still swept — otherwise the marking would be inert exactly there). Privacy is a SCHEMA property, not a filter: the rendering input struct carries only {party type, index}, the public row's key set is FROZEN, corrections carry the role word "kommissionen" publicly, and avg_resolution_days is null without evidence — never 0 as a lie. Local fixture: the seed-discipline protocol delta (a published, locked demo protocol WITH a correction driven through the real seams — the audit rows are the proof — the two protection-gate negatives left UNPUBLISHED, a retention-ripe case released through the hold ROUTE, and materializer substrate). Contracts specs/api/endpoints/discipline-protocols.md + specs/api/endpoints/discipline-precedents.md. Admin/web/sys surfaces follow in 7635e69d.m-6 (t-2).

    ✅ 7635e69d.m-6 (t-1)

Referee Delegate

F05.06
Planned
How it works
  • F05.06.01 Shipped

    Delegate appointment per competition

    ✅ PL-F0506
  • F05.06.02 Shipped

    Delegate report template and submission

    ✅ PL-F0506
  • F05.06.03 Shipped

    Competition compliance checklist

    ✅ PL-F0506
  • F05.06.04 Shipped

    Delegate observation notes (umpire performance)

    ✅ PL-F0506
  • F05.06.05 Shipped

    Rebuild /v1 Go contract — referee_delegate (migration 00057); appoint + report (compliance_checklist + observations_md, If-Match optimistic lock), audit double-write.

    ✅ PL-F0506m6 (m-6 t-1)

Disciplinary Cases per Nation

F05.07
Planned

Per-nation disciplinary commission structures, investigation process configuration, appeal escalation chains, sanction catalogs, CAS appeals, anonymized public protocols, and dashboard.

How it works
  • F05.07.01 Shipped

    France — Commission de discipline (3 levels: département → régionale → nationale)

    ✅ PL-F0507a
  • F05.07.02 Shipped

    Sweden — Disciplinnämnd och Bestraffningsdomstol (RF)

    ✅ PL-F0507a
  • F05.07.03 Shipped

    Germany — Sportgericht på Landes- und Bundesebene

    ✅ PL-F0507a
  • F05.07.04 Shipped

    Standard investigation process — configurable steps, deadlines, responsible roles

    ✅ PL-F0507a
  • F05.07.05 Shipped

    Per-nation sanktionskatalog — each federation defines predefined sanctions (codes, types, durations, fines, bans) in TenantConfig. Catalog entries filterable by category, sanction_type, min_level.

    ✅ PL-F0507b
  • F05.07.06 Shipped

    CAS-överklagan (Court of Arbitration for Sport) — CAS appeal for international disciplinary cases. Requires exhausted domestic appeal. Rebuilt as a REGISTRATION, not a state machine (7635e69d.m-5 A-7): POST /v1/discipline/appeals/{id}/escalate-cas stamps the arbitration court's OWN case reference + cas_filed_at and writes an audit row — and changes NOTHING in the sanction, the case or the cross-tenant registry. The gate is mechanical: the instance must be decided AND the domestic chain exhausted (no higher instance left, no open instance), else 409 cas_chain_not_exhausted. Avvikelse mot statusflödet ovan: the five-step FSM (registered → under_review → …) has no home in the authoritative vision and is not built — the house cannot observe what an external court does, and a fabricated process would be worse than none.

    ✅ PL-F0507b · ✅ 7635e69d.m-5 (t-1)
  • F05.07.07 Shipped

    Anonymiserat publikt protokoll — public-facing endpoint with PII stripped (no player/club IDs, reporter, investigator, attendees, evidence URLs). Only decided/closed cases. Evidence count instead of URLs. Rebuilt as the three unauthenticated routes GET /v1/public/discipline/protocols/{year}[/{alias}[/print]]?tenant=<KOD>: the federation is chosen by its CODE (mandatory — an omitted or unknown code is 404, never an empty list), the year-list row's key set is FROZEN in the acceptance suite (the freeze IS the leak guard for a rule that is negative), /print answers an IDENTICAL payload (A-9 — the print variant is the web surface's styling concern), and an unpublished document answers 410 with a one-line note on all three URLs. Legacy's free-text search (?q=, ?offence= as substring match against the markdown) is deliberately struck: a searchable protocol body is an oracle one can ask about names.

    ✅ PL-F0507b · ✅ 7635e69d.m-6 (t-1)
  • F05.07.08 Shipped

    Discipline case management dashboard — aggregated statistics: cases by status/category, sanction distribution, open case aging buckets, appeal stats, average resolution days. Rebuilt as GET /v1/discipline/dashboard (discipline:manage in ANY scope, counted with aggregate queries and filtered EXACTLY like the case queue, so an org-scoped case worker sees her own subtree's numbers) plus the operator's cross-tenant GET /v1/sys/discipline/overview behind GuardSysConsole — read-only, aggregate-only, with a FROZEN row projection (A-12: no case data, no parties, no free text, and no way into a case from there; sys has no decision rights in a tenant's disciplinary cases). Empty ground truth yields honest zeros and null, never a fabricated statistic.

    ✅ PL-F0507b · ✅ 7635e69d.m-6 (t-1)

Umpire Training & Certification per Nation

F05.08
Planned

Nation-specific training pathways, multi-part exams, prerequisite grade years, refresher/recyclage courses, exam time limits, online exams, recertification cycles, registry expiration, and assignment history.

How it works
  • F05.08.01 Shipped

    France — Arbitre Régional / National / International — multi-part exams (written + practical) with per-part scoring and pass/fail, written-before-practical ordering, training pathways (course → exam → practical) per grade, recyclage (refresher) courses for renewal, training provider levels (comité/ligue/fédération)

    ✅ PL-F0508a
  • F05.08.02 Shipped

    Sverige — SDF-baserad domarutbildning — 3-grads system (L1/L2/L3), SDF som utbildningsanordnare, spelande domare tillåten på lokalnivå, erfarenhetskrav per grad

    ✅ PL-F0508a
  • F05.08.03 Shipped

    Deutschland — Schiedsrichter A/B/C — 4-grads system (LV/DPV/CEP/FIPJP), Anwärterjahr (1 år kandidatperiod), 204-frågeexamen med poängtröskel, CEP→FIPJP kräver 5 års innehavstid

    ✅ PL-F0508a
  • F05.08.04 Shipped

    Training pathway engine — configurable training steps per grade, multi-part exam status tracking, prerequisite grade years check, exam time limit with automatic expiry, pathway progress endpoint

    ✅ PL-F0508a
  • F05.08.05 Shipped

    Domarexamen online med tidsbegränsning — online exam serving (questions without answers), draft answer saving, automatic expiry on time limit, remaining-time check, expired session listing

    ✅ PL-F0508b
  • F05.08.06 Shipped

    Recertifiering var 4:e år — recertification eligibility check (CE credits, assignments, cert status), recertification schedule (certs approaching expiry within configurable window)

    ✅ PL-F0508b
  • F05.08.07 Shipped

    Domarregister med expirering — batch process-expirations (active→expired), expiry report aggregated by grade/month, grace period listing for recently expired certs

    ✅ PL-F0508b
  • F05.08.08 Shipped

    Domarens uppdragshistorik — career summary (total assignments, head umpire count, distinct levels, career span), season breakdown (per-year role/status/level counts)

    ✅ PL-F0508b

Global Referee Registry & Cross-Tenant Lending

F05.09
Planned

FIPJP-centralt domarregister med cross-tenant domarutlåning. Aggregerar nationella certifieringar, stödjer formell utlåning mellan federationer vid internationella mästerskap.

How it works
  • F05.09.01 Shipped

    GlobalReferee-modell (FIPJP-tenant) — global_referee_id (FIPJP-REF-YYYY-NNNN), passport_id FK, certifications list, languages, status. Unique index on passport_id.

    ✅ PL-T015
  • F05.09.02 Shipped

    RefereeLoanArrangement-modell — cross-tenant loan workflow (REQUESTED→ACCEPTED→APPROVED→ACTIVE→COMPLETED), conflict detection, compensation, approval history.

    ✅ PL-T015
  • F05.09.03 Shipped

    FIPJP referee admin endpoints — CRUD + sync-certifications + promote + status change + sync-from-nation (M2M).

    ✅ PL-T015
  • F05.09.04 Shipped

    Public referee search — by-passport lookup + multi-filter search (discipline, level, language, availability). Privacy-safe: no contact info.

    ✅ PL-T015
  • F05.09.05 Shipped

    Loan workflow endpoints — create, accept-referee, approve-source, reject-source, cancel, complete. Double-booking detection (409).

    ✅ PL-T015
  • F05.09.06 Shipped

    Auto-certification-sync — TenantConfig.fipjp_referee_publish_enabled, sync service pulls from national UmpireCertification.

    ✅ PL-T015
  • F05.09.07 Shipped

    Admin UI — FIPJP referee dashboard (list, filter, sync, promote, suspend) + loan arrangement dashboard (status tracking, approve/reject/cancel/complete).

    ✅ PL-T015