Skip to content

Close three EU AI Act regulatory gaps (art. 5 carve-outs, art. 27 FRIA, art. 25 flip)#3

Merged
abk1969 merged 14 commits into
mainfrom
feat/classify-regulatory-gaps
May 16, 2026
Merged

Close three EU AI Act regulatory gaps (art. 5 carve-outs, art. 27 FRIA, art. 25 flip)#3
abk1969 merged 14 commits into
mainfrom
feat/classify-regulatory-gaps

Conversation

@abk1969
Copy link
Copy Markdown
Owner

@abk1969 abk1969 commented May 15, 2026

Closes three regulatory gaps in computeCategory surfaced by the 2026-05-15 audit against Reg. (EU) 2024/1689.

Feature A — art. 5(2)–(5) carve-outs (prohibitions h/f/g)

  • Export ART5_CARVEOUTS metadata (3 entries: art. 5(2)-(3) law-enforcement RBI carve-out; art. 5(1)(f) medical/safety; art. 5(1)(g) law-enforcement / legally-acquired-dataset).
  • computeCategory now partitions selected prohibitions into carved-out vs un-carved-out; INTERDIT short-circuit fires only if at least one prohibition has no valid carve-out claim.
  • Carve-out justification refs flow through to clipboard/PDF report via the existing justifications-rendering path.
  • UI: optional carve-out sub-question appears inline on the prohibitions step.
  • Stale-state cleanup: deselecting a prohibition strips its carve-out claim.

Feature C — art. 27(1) FRIA applicability gating

  • New pure function computeRoleNotes(answers, role, lang) → { friaRequired, friaReason } implementing art. 27(1)(a) (public-body / private-public-service deployer + Annex III ≠ §2) and art. 27(1)(b) (Annex III §5 pathway).
  • FRIA quickwin in QUICKWINS.HAUT_RISQUE_ANNEXE_III is now hidden (screen, clipboard, PDF) unless actually required.
  • isFriaItem helper deduplicates the filter predicate across screen/clipboard/print paths.
  • UI: deployer-kind sub-question (public_body / private_public_service / private_other) added to the role step; deployerKind cleared on role switch.

Feature D — art. 25 substantial-modification provider flip

  • computeCategory detects systeme_sur_gpai + substantialModification === 'oui' and reclassifies the integrator as a GPAI (or GPAI_RS) provider.
  • isGPAI_RS definition broadened to cover both native GPAI providers and flipped integrators.
  • art. 25 flip justification emitted in flip path; art. 53–55 obligations called out.
  • French label uses fournisseur (official AI Act terminology), not "provider".
  • UI: yes/no substantial-modification sub-question appears inline on the nature step with recital 84/109 guidance; substantialModification cleared on nature switch.

Tests

  • 68 tests pass (61 in classify.test.js, 7 in i18n.test.js).
  • +25 new tests across 4 new describe blocks.
  • 100% statement/line/function coverage on classify.js; 96.3% branch.
  • Coverage on all new logic includes a negative-flip invariant test (native GPAI provider does NOT emit art. 25 flip justification).

Regulatory citations verified

  • art. 5(1)(f)/(g)/(h), art. 5(2)-(3) (carve-outs for h)
  • art. 25, art. 25 + art. 53 (integrator note vs flip note)
  • art. 27(1)(a), art. 27(1)(b) (FRIA paths)
  • art. 53–55 (GPAI obligations referenced in flip label)

Deferred follow-up

Reg. 2024/1689 art. 27(1)(b) specifically names Annex III §5(b) credit-scoring and §5(c) life/health insurance. The current UI exposes Annex III §5 as a single bucket without sub-items (a)/(b)/(c)/(d), so computeRoleNotes triggers FRIA on any §5 selection — conservative (false-positive) but not strictly precise. Splitting §5 into sub-items requires UI + data changes worth a separate PR.

Test plan

  • Selecting prohibition (h) without a carve-out claim → INTERDIT
  • Selecting prohibition (h) + claiming the art. 5(2)-(3) carve-out → not INTERDIT; carve-out ref present in justifications
  • Provider + Annex III §3 (education) → FRIA quickwin NOT shown
  • Public-body deployer + Annex III §3 → FRIA quickwin shown with art. 27(1)(a) reason
  • Private-other deployer + Annex III §5 → FRIA quickwin shown with art. 27(1)(b) reason
  • Integrator + substantial modification = oui + systemic risk = oui → primary = GPAI_RS, art. 25 flip note in justifications
  • Integrator + substantial modification = non → primary = RISQUE_MINIMAL, art. 25 + art. 53 integrator note present (no flip)
  • Deselecting a prohibition after claiming its carve-out → re-selecting does not pre-fill the carve-out
  • PDF export includes carve-out / FRIA / art. 25 flip refs in the justification section

🤖 Generated with Claude Code

abk1969 and others added 14 commits May 15, 2026 09:49
…rt. 27 FRIA, art. 25 flip)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Rename test description to match what it asserts
- Document id vs appliesTo distinction in ART5_CARVEOUTS
- Tighten fall-through test with explicit length assertion
- Treat unmapped carve-out claims as no-claim (still INTERDIT) + cover with test

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Replace permissive /FRIA|évaluation d'impact/i with /FRIA requise/
- Merge duplicate ./classify.js import

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tch codebase

Plan and C1 implementation hardcoded English 'deployer' / 'provider', but the
actual values in answers.role are French ('deployeur', 'fournisseur', ...) per
ROLES in ai-act-compass.jsx. computeRoleNotes would have silently returned
friaRequired=false for every real deployer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…II area

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…switch

- DRY the art. 27 filter predicate across screen/print/clipboard paths
- Reset deployerKind to null when role changes (prevent stale state linger)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…grators

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… fournisseur + tighter tests

- Hoist flipsViaArt25 and broaden isGPAI_RS to cover the flip path (replaces inline disjunction)
- Use 'fournisseur' instead of English 'provider' in the French flip label (matches official AI Act terminology + existing ROLES data)
- Tighten 'keeps integrator note' tests with explicit RISQUE_MINIMAL assertion
- Add negative-flip test asserting native GPAI providers do NOT get the art. 25 justification

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…egrators

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously, toggling a prohibition off left its carve-out key intact in
prohibitionCarveOuts, so re-selecting the same prohibition would re-show
the carve-out as pre-checked. The classifier ignored orphaned keys
(carveOuts[id] only consulted for IDs still in answers.prohibitions),
but the UX was surprising.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 15, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ai_act_compass Ready Ready Preview, Comment May 15, 2026 9:08am

@abk1969 abk1969 merged commit 701bddf into main May 16, 2026
3 checks passed
@abk1969 abk1969 deleted the feat/classify-regulatory-gaps branch May 16, 2026 12:41
abk1969 added a commit that referenced this pull request May 16, 2026
Bug #1 (critical) — Step 7 systemic-risk question was hidden for art. 25 flip
  Integrators flipping to provider via substantialModification='oui' could
  never reach GPAI_RS classification because Step 7's gpaiQuestionApplicable
  check only matched nature==='gpai'. Now also matches the flip path:
  nature==='systeme_sur_gpai' && substantialModification==='oui'. canNext
  updated symmetrically. The classifier already supported GPAI_RS for the
  flip path (PR #3 D1) — this UI fix makes that path reachable.

Bug #2 (important) — Prohibition short-circuit ignored carve-out claims
  goToResultIfProhibited jumped to verdict on ANY prohibition selection,
  bypassing Annex I/III/50/GPAI steps. A user with prohibition (h) +
  art. 5(2)-(3) carve-out claim would land on RISQUE_MINIMAL without
  ever being asked about Annex III §1 biometrics. Now short-circuits only
  if at least one selected prohibition has no claimed-and-matched carve-out.

Bug #3 (copy) — q7Sub / q7NotApplicable mentioned only GPAI providers
  Updated EN+FR to acknowledge the art. 25 flip path so the "not applicable"
  message no longer contradicts a flipped integrator's status.

UX note — added a cumulativity rappel to q5Sub
  Reminds the user that art. 5 prohibitions (step 3), Annex III §1 (step 5),
  and art. 50 transparency (step 6) are cumulative — same underlying tech
  may need ticking in multiple steps because each tests a distinct legal
  regime.

Tests + build green at 113/113.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
abk1969 added a commit that referenced this pull request May 16, 2026
The previous demo ended on a plain INTERDIT verdict after ticking subliminal
techniques. The post-audit narrative is stronger: pick the most-prohibited
practice (real-time remote biometric ID by law enforcement, art. 5(1)(h)),
then claim the law-enforcement carve-out (art. 5(2)-(3)) — modeled by
PR #3 Item A. The frame holds on both cards selected so the viewer reads
"even the most-prohibited AI tech has a regulated path".

Flow change:
- Step 3 pick: Subliminal (art. 5(1)(a)) → Real-time RBI (art. 5(1)(h))
- After-pick: View verdict click → click the art. 5(2)-(3) carve-out card
- Hold: 900 ms after verdict → 1100 ms after carve-out reveal

Selectors anchor on the unique `sub` text (art. 5(1)(h), art. 5(2)-(3))
since the OptionCard's aria-label composes title + sub + desc — same
strategy as e2e/parcours.spec.js.

Output unchanged in shape: 1920×1080 H.264 30 fps. New duration ≈11.7 s
(was 9.9 s); the extra hold on the carve-out reveal is worth it.

To regenerate the MP4 release asset:
  node make-demo.cjs

(linkedin-demo.mp4 stays gitignored — distributed via GitHub releases.)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant