Skip to content

docs(planning): add FF-EPIC-17 selection lists epic (FFRNT-186) - #528

Open
izzywdev wants to merge 5 commits into
masterfrom
claude/selectionlist-microservice-uj9nol
Open

docs(planning): add FF-EPIC-17 selection lists epic (FFRNT-186)#528
izzywdev wants to merge 5 commits into
masterfrom
claude/selectionlist-microservice-uj9nol

Conversation

@izzywdev

@izzywdev izzywdev commented Aug 3, 2026

Copy link
Copy Markdown
Owner

📋 Description

Adds docs/planning/epics/EPIC-17-selection-lists.md — the version-controlled source of truth for the SelectionList microservice, mirroring the FFRNT-186 epic in FuzeFront SCRUM (19 stories, 61 sub-tasks, 342 h).

Docs only — one file. No service, contract, or UI code in this PR.

The problem it records. FuzeFront has no way for an organization to define its own reference data. Every dropdown in every consuming product is hard-coded or invented ad hoc, so two apps in the same org disagree about what "Region" means, and renaming a label needs a code change and a deploy. There is also no runtime i18n path for user-authored content — packages/i18n and packages/i18n-translate translate static UI strings at build time into git, which is structurally the wrong mechanism for content an org authors at runtime.

🔄 Type of Change

  • 📝 Documentation update

🧪 Testing

  • Manual — Jira linkage verified by JQL: parent = FFRNT-186 returns exactly 19 stories; labels = selection-lists AND issuetype = Sub-task returns exactly 61. Story points equal the sum of their sub-tasks; every sub-task is {2, 4, 8}.
  • CI — 54 check runs on the head commit, all success or skipped.

Unit / integration / E2E are not applicable — this PR adds a markdown document.

🔧 Implementation Details

Changes Made

  • Documentation: one new file, docs/planning/epics/EPIC-17-selection-lists.md, following the frontmatter and section structure of EPIC-09EPIC-16.

No frontend, backend, or SDK changes.

The decision worth reviewing

Per-list ReBAC with action granularity, and explicitly not per-item ACLs. Two reasons, recorded so they survive into implementation:

  1. Items are the wrong entity to attach authorization to. They are the high-cardinality one — hundreds of values × 11 locales per list. Per-item Permit resource instances would be tens of thousands per org, and every list render becomes a per-item filter instead of one check.
  2. A partially-visible selection list is a broken selection list. The list is the unit of meaning: a closed vocabulary. If a viewer cannot see value X their picker silently omits it, and the UUIDs another user stored become un-interpretable to them. Two people filling the same form get different option sets — a data-consistency bug, not access control.

What the governance question actually needs is action granularity: read / add_value / update_value / remove_value / translate / update / delete / manage_access, held per-user per-list. translate is deliberately separate — a translator renders an existing vocabulary and must not change what it contains. Items keep created_by from day one, so "you may only edit values you created" later becomes an ABAC condition on an existing column rather than a new authorization subsystem.

This follows Organization.roles['org-admin'] in backend/src/permit/schema.ts, which already derives via granted_to.users_with_role[].linked_by_relation.

Other decisions recorded

  • Archive by default, purge explicit and audit-logged. Consuming apps persist the item UUID, and the platform cannot know which external systems hold one. Archived values still resolve so historical records keep rendering.
  • selection_list_access is a read-model mirror, never an authorization source. It exists only so the list index paginates in SQL instead of making N PDP calls. Every mutation still calls the PDP, fail-closed. A test asserts a stale mirror row cannot authorize — a mirror that quietly becomes the authority is a classic security regression.
  • Quota resolution behind one QuotaResolver interface (per-org DB override → Permit plan-tier attribute → platform config ceiling), so the planned move to the key-value configuration-storage microservice is a new implementation plus a wiring change. Enforcement is advisory-locked because count-then-insert races.
  • Packaging: @fuzeone/* on GitHub Packages (scope must equal the owning org), Python client to PyPI, service stays in this monorepo.

Two publishing defects this epic had to work around

Found while planning, both now tracked on FFRNT-266 — worth a reviewer's attention because they affect the whole family, not just this epic:

  • packages-publish.yml has never published anything. It is guarded if: github.repository_owner == 'fuzefront' while this repo is owned by izzywdev, so the job has been a no-op for its entire life — its own header comment states the constraint. Four other publishers (auth-ui, chat, design-system, security) guard on izzywdev and do run. Publishing is split across two inconsistent guards, and nothing in CI distinguishes a dormant job from a passing one.
  • There is no Python publish workflow anywhere, yet packages/identity-py/README.md already instructs consumers to pip install fuzefront-identity. That command resolves against nothing.

Hence FFRNT-266 requires a dormant publish job to fail loudly, and FFRNT-276 verifies each artifact by installing it from a clean checkout against the real registry — a green publish job is not evidence that anything was published.

Code Quality

  • Code follows the project's coding standards
  • Self-review completed
  • No console.log or debugging statements left in code

Documentation

  • Documentation has been updated — this PR is the documentation

🚨 Breaking Changes

None. Documentation only.

📋 Checklist

Pre-submission

  • Self-review performed
  • Corresponding documentation changes made
  • No new warnings or errors

Code Quality

  • Conventional commit format

Security Checklist

  • No sensitive data exposed — no credentials, tokens, or internal hostnames in the document

🔗 Related Issues and PRs

  • Jira epic: FFRNT-186
  • Related: FF-EPIC-06 (feature-flags platform); FFRNT-129 (records the same unpublished-OpenAPI-specs gap)

📝 Additional Notes

Deployment Notes

None — no code ships in this PR, and the diff touches nothing under services/** or frontend/**, so release.yml's path filters will not fire an image build. The eventual service is gated by fuzefront.selection-lists.service, default OFF, with its Helm block defaulting to enabled: false.

Deploy window: the epic is labelled deploy-window. master is deploy-on-push with required_signatures.

Future Work

The 19 stories under FFRNT-186. S1 (frozen contract) and S2 (approved design frames) are the two sequential gates; everything else fans out behind them. gate-frames-first blocks any feature-UI PR without approved frames.

Questions for Reviewers

  • The per-list vs per-item granularity call is the one worth disagreeing with if you're going to disagree with anything.
  • Sizing follows the FuzePlan SIZING.md convention where 1 point = 1 hour and sub-tasks are strictly {2, 4, 8} — this differs from the story-point scale used on the EPIC-09…16 stories.
  • @fuzeone only publishes if the repo actually moves to that org. GitHub Packages matches scope to owner, so under izzywdev today @fuzeone/* fails exactly the way @fuzefront/* does. FFRNT-266 names this as its central assumption.

📊 Performance Impact

  • No significant impact

🔄 Backwards Compatibility

  • Fully backwards compatible

claude and others added 2 commits August 3, 2026 13:29
Version-controlled source of truth for the SelectionList microservice,
mirroring the FFRNT-186 epic created in FuzeFront SCRUM.

Records the four owner-confirmed decisions and, most importantly, why
authorization is per-list rather than per-item: items are the
high-cardinality entity (hundreds of values x 11 locales), and a
partially-visible selection list is a broken one — the list is the unit
of meaning, a closed vocabulary, so a viewer-dependent option set makes
another user's stored UUIDs un-interpretable. Action granularity
(read / add_value / update_value / remove_value / translate / update /
delete / manage_access) answers the governance question without per-item
ACLs, and items keep created_by so per-item ownership stays a cheap ABAC
addition later.

Also captures the schema (archive-not-delete, immutable code, side
translation tables), the read-model mirror that must never become an
authorization source, atomic advisory-locked quota enforcement, and the
16-story / 51-sub-task breakdown authored against the FuzePlan
ticket-creator skills and validated against the ticket-enforcer rubric.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018PKRvNfpskTKPUfDc8X1G1
@github-actions
github-actions Bot enabled auto-merge (squash) August 3, 2026 13:30
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Automated code review (gate-code-review)

Credit balance is too low

Report-only — this check never blocks merge.

claude added 2 commits August 3, 2026 17:03
The Governance Sync bot pushed b53bd83 ("reconcile managed files to
FuzeSDLC v1") on top of this branch with [skip ci], so no checks ran
against the current head SHA — every green result belongs to a175723,
the commit below it. This empty commit gives CI a head SHA it will
actually evaluate, so required checks can report and the PR can leave
mergeable_state=blocked.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018PKRvNfpskTKPUfDc8X1G1
Resolves mergeable_state=behind (master gained #524 and #517) and gives
CI a head SHA with real file changes — the path-filtered workflows never
ran against the empty commit cafae89.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018PKRvNfpskTKPUfDc8X1G1
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Automated code review (gate-code-review)

Credit balance is too low

Report-only — this check never blocks merge.

 scope

Extends the epic to four shipped artifacts: the deployed service, two npm
packages, and a Python client. Owner decisions recorded — @FuzeOne scope on
GitHub Packages, Python client to PyPI, service stays in the monorepo.

Records two publishing defects found while planning, both of which this
epic now has to work around rather than inherit:

- packages-publish.yml has never published anything. It is guarded on
  repository_owner == 'fuzefront' while the repo is owned by izzywdev, so
  the job has been a no-op for its entire life. Four other publishers are
  guarded on izzywdev and do run, so publishing is split across two
  inconsistent guards — and nothing in CI distinguishes a dormant job from
  a passing one.
- No Python publish workflow exists, yet packages/identity-py already
  instructs consumers to pip install fuzefront-identity. That command
  resolves against nothing.

Hence FFRNT-266 requires a dormant publish job to fail loudly, and
FFRNT-276 verifies each artifact by installing it from a clean checkout
against the real registry — a green publish job is not evidence that
anything was published.

Adds S17 (FFRNT-264, Python client), S18 (FFRNT-265, Swagger UI + consumer
guide) and S19 (FFRNT-266, publishing) with 10 sub-tasks; 19 stories /
61 sub-tasks / 342 h. The critical path is unchanged — the new stories run
parallel to it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018PKRvNfpskTKPUfDc8X1G1
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Automated code review (gate-code-review)

Credit balance is too low

Report-only — this check never blocks merge.

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.

2 participants