Ctrl K
ComunidadGuías…

docs/ROADMAP.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Roadmap

What exists today, and what is planned. No dates — this is a statement of intent, not a
commitment. Items move only when they are actually done.

## Shipped

- **Feature flags per project and environment** — create, toggle, delete, with a per-project
  default state for new flags.
- **Context-aware targeting** — ordered strategies with AND-ed constraints over typed context
  fields (string, enum, boolean, number, version, date).
- **Admin UI** — React application covering projects, flags, environments, context fields,
  targeting, API keys and users.
- **Role-based access control** — owner, admin, editor and viewer roles, with protected
  environments and a two-admin approval flow for production toggles.
- **Three-tier API keys** — root admin, project admin and client keys, with optional TTL on
  admin keys.
- **TypeScript SDK** — [`@flagraft/sdk`](../packages/sdk-js), with TTL caching, a cache size
  cap, stale-on-error fallback, request timeouts, negative caching for unknown flags,
  rate-limit backoff and per-call default values.
- **Python SDK** — [`flagraft`](../packages/sdk-python), a sync, thread-safe, zero-dependency
  port of the TypeScript client with identical failure behaviour.
- **Conditional requests** — `ETag` / `If-None-Match` on the bulk evaluation endpoint, so an
  unchanged flag list costs a 304 with no body. Handled automatically by the SDK.
- **Import and export** — a versioned `flagraft.export` document that round-trips losslessly,
  plus a one-way importer for [Unleash](https://www.getunleash.io) exports and an Unleash-shaped
  export. Every import is previewable with `dryRun`, runs in one transaction, and reports anything
  it could not represent rather than approximating it — a strategy that cannot be carried over
  exactly is dropped, never widened. See [API.md](API.md#import-and-export).
- **OpenAPI / Swagger UI** at `/docs`.

## Planned

### Evaluation

- **Percentage rollouts** — deterministic bucketing on a context field so a flag can be
  enabled for a stable subset of users. The Unleash importer skips `flexibleRollout` below
  100% today and will map it once this exists, so this is worth more than it looks: it is
  the single most common reason an Unleash migration comes out incomplete.
- **Flag variants** — beyond boolean, returning a string or JSON value per matched strategy.
  The Unleash importer drops variants today and will map them once this exists.
- **Constraint-level evaluation detail** — report _which_ constraint failed, so "why is this
  off for this user" is answerable without re-deriving the strategy by hand.

### Delivery and freshness

- **SSE push invalidation** — let a flag change reach running SDKs immediately instead of on
  the next poll. The server already clears its own cache on every write, so the delay today is
  the SDK's `ttl` alone: up to 30 seconds by default. That is fine for a gradual rollout and
  poor for a kill switch, which is the case this is for.

  Design settled, not yet built:
  - `GET /api/v1/client/stream`, authenticated with the client key. That key is already scoped
    to one project and environment, so the key is the subscription — no subscribe protocol.
  - The event carries **invalidation, not data** (`{ environmentId, version }`). The server
    cannot push evaluated results because evaluation depends on each caller's context. The SDK
    drops the affected cache entries and refetches, and that refetch is a conditional request,
    so ETag support is what keeps it cheap.
  - The seven existing mutation sites that call `cache.delete`/`deleteByPrefix` collapse into
    one `flagsChanged(projectId, environmentId?)` decorator that clears and publishes together.
  - Opt-in in the SDK (`stream: true`), with reconnect backoff and a fall back to TTL polling
    whenever the stream is down. It must be an optimisation over today's behaviour, never a
    replacement for it.
  - Needs `LISTEN/NOTIFY` (below) to work on more than one server instance.
  - Not usable on Lambda, and awkward on Cloudflare Workers, since it needs a long-lived
    connection. The SDK compatibility table will need a note.

- **Cross-instance cache invalidation via Postgres `LISTEN/NOTIFY`** — see Known limitations.
  Required before SSE can work behind a load balancer, and worth doing on its own merits.

- **Configurable shared cache layer** — the flag-state cache is in-process only. BentoCache,
  already the cache in use, is a two-tier design: L1 is local memory, L2 is a shared store, and
  a bus keeps every instance's L1 in step. Only L1 is wired up today.

  What the operator would choose:
  - **Nothing** — today's behaviour, and the right answer for a single instance. Must stay the
    default: requiring Redis to run a flag server would be a poor trade.
  - **Redis as L2**, with the Redis bus for invalidation. The usual choice once more than one
    instance is running.
  - **A database table as L2**, via BentoCache's `database` driver, for deployments that will
    not add Redis. Slower than Redis and it needs a bus of its own, which is where
    `LISTEN/NOTIFY` fits — the two items are the same piece of work approached from either end.

  There is no third tier to configure: BentoCache stops at L1 and L2, so "Redis and the
  database" is a choice between them for the L2 slot, not a stack of both. Postgres is already
  a hard dependency and holds the truth anyway, so a miss at L2 falls through to a query, which
  is the layer a notional L3 would have been.

  The point of L2 is not speed — an in-memory hit is far quicker than a Redis round trip. It is
  that a cold or restarted instance warms from a shared store instead of hammering Postgres,
  and that invalidation reaches every instance at once.

### Operations

- **Audit log** — who changed which flag, when, and in which environment. A stored record with
  an actor and a before/after, queryable from the admin UI and filterable by project,
  environment and actor. Not application logs: those answer "what happened to this request",
  and the question here is "who turned this on last Thursday". Writes only — flag and strategy
  changes, key creation and revocation, role and membership changes, approval decisions. Read
  traffic is deliberately excluded; the evaluation endpoint is polled on a timer and logging it
  would bury everything worth reading.

- **SSO via OIDC** — sign in through an existing identity provider (Keycloak, Okta, Entra,
  Google Workspace) instead of managing a second set of passwords. Sketch:
  - Authorization code flow with PKCE, landing on the same session cookie the local login
    already issues, so nothing downstream of authentication changes.
  - IdP groups map to the existing owner / admin / editor / viewer roles, configured per
    deployment. Roles stay Flagraft's, not the IdP's.
  - Local accounts keep working alongside it. An SSO outage must never lock an operator out of
    their own kill switches.
  - SAML and SCIM provisioning are a separate, larger job and not part of this.
  - Not a paid tier. Charging for the login that makes a self-hosted tool usable in a company
    is the pattern this project exists to avoid.

- **SAML and SCIM provisioning** — a separate, larger job than the OIDC flow above: SAML for
  identity providers that don't speak OIDC, SCIM for automated user provisioning and
  deprovisioning from the IdP instead of manual invites. Typically a hard requirement for
  larger enterprise buyers regardless of which SSO protocol they use.
- **Custom RBAC roles** — today's four roles (owner/admin/editor/viewer) are fixed. Let a
  deployment define its own permission sets per project or environment for teams whose
  approval structure doesn't map onto four tiers.
- **Webhooks on flag and audit events** — push flag changes and audit entries to an external
  endpoint (Slack, SIEM, internal notification service) instead of requiring a poll.
- **Scheduled flag changes** — enable or disable a flag at a future time, for planned launches
  and time-boxed rollouts.
- **Evaluation metrics** — optional reporting of which flags were evaluated, so stale flags
  can be found and removed.

### Security and compliance

Flagraft is self-hosted, so SOC2/HIPAA-style certification is mostly the deploying
organization's responsibility, not the project's. What the project can do is close the
technical-control gaps a security review actually checks for, and document how the rest maps
onto a deployer's own audit.

- **MFA/2FA on login** — currently password (argon2) plus a session cookie only, no second
  factor. Expected by SOC2 CC6.1 and HIPAA's access-control safeguard for privileged accounts.
- **Security response headers** — `@fastify/cors` is registered; `@fastify/helmet` is not
  (no CSP, HSTS, X-Frame-Options).
- **Dependency and vulnerability scanning in CI** — `.github/workflows/` has no Dependabot
  config, CodeQL, or equivalent. A common ask in vendor security questionnaires.
- **Independent security audit / pen test** — `SECURITY.md` already states plainly that
  Flagraft is pre-1.0 and has not had one. Usually the first thing a security review asks for.
- **Encryption-at-rest documentation** — data lives in the customer's own Postgres, which is
  their responsibility, but there is no doc walking a HIPAA-covered deployer through
  configuring it (e.g. Postgres TDE / disk encryption) to meet the safeguard.
- **Compliance control mapping doc** — a short published mapping from Flagraft's actual
  controls (RBAC, two-admin approval, scoped API keys, session cookie flags, and the audit log
  above once shipped) to SOC2 CC6 / HIPAA technical safeguards, so an enterprise security team
  can self-serve their questionnaire instead of asking for a certificate that does not apply to
  self-hosted OSS.

Account lockout is a related, already-tracked gap — see
[Known limitations](#known-limitations).

### Ecosystem

- **React SDK** — `@flagraft/react`, a thin wrapper over the existing TypeScript client rather
  than a second implementation. A provider that hydrates the whole flag list in one call, a
  `useFlag(key, default)` hook that re-renders on change, and SSR support so the server-rendered
  markup and the first client render agree instead of flickering. The browser constraints drive
  the design: a client key shipped to a browser is public, so it must stay a client key scoped
  to one environment, and the evaluation context is whatever the page already knows about the
  user.
- **Go SDK.** Any language can already call the HTTP API directly; this would add the same
  caching and fail-safe behaviour as the TypeScript and Python clients. Idiomatic where it
  matters — a `context.Context` on every call — but the same behaviour under failure, since
  an SDK that fails differently per language is worse than no SDK.
- **Async Python client**, if asked for. The sync client works from asyncio through
  `asyncio.to_thread` today.
- **PyPI release pipeline** for `flagraft`.
- **npm release pipeline** for `@flagraft/sdk`.

## Known limitations

- **Cache invalidation does not cross server instances.** The flag-state cache is in-process
  (`bentostore().useL1Layer(memoryDriver())`), with no shared layer and no bus. Every write
  clears the cache on the instance that handled it, so a single-server deployment is always
  consistent. Run two or more behind a load balancer and the other instances keep serving the
  previous state until their own entries expire — up to `CACHE_TTL_SECONDS`, 30 by default.

  Workaround today: run one instance, or lower `CACHE_TTL_SECONDS`. The fix is Postgres
  `LISTEN/NOTIFY`, which needs no new infrastructure since Postgres is already required, or a
  BentoCache bus over Redis for deployments that already run one — see Configurable shared
  cache layer.

- **Regex constraints are screened, not sandboxed.** A `regex` pattern is rejected at write
  time when it has nested quantifiers, which covers the patterns that cause exponential
  backtracking by accident. The check is a star-height heuristic, so a deliberately
  ambiguous pattern such as `(a|a)*` still gets through, and patterns that are merely
  polynomial are allowed -- those stay cheap because the context values they run against
  arrive in a query string, which the HTTP layer already bounds. Closing the gap properly
  needs a matcher that can be given a timeout, which Node's `RegExp` cannot.

- **Login throttling is per IP, with no account lockout.** An attacker spread across many
  IPs can still work through one account's password space slowly, and the limit counts
  successful logins alongside failed ones because `@fastify/rate-limit` cannot tell them
  apart. Lockout was left out on purpose: it needs failed-attempt state on the user row, an
  unlock path for the locked-out admin, and it hands anyone a way to lock a colleague out by
  guessing at their email. Worth doing, but it is a feature rather than a patch.

## Not planned

- **Hosted SaaS.** Flagraft is self-hosted by design.
- **Experimentation and analytics.** Flag evaluation is the scope; measuring outcomes belongs
  in your analytics stack.
- **Automatic bulk fetching inside `isEnabled`.** Considered and rejected. Having a single flag
  check pull the whole catalogue helps an app with few distinct contexts and hurts one that
  passes per-user context, where every user would download every flag to answer one question.
  Calling `getFeatures(context)` once already gives the same benefit — later `isEnabled` calls
  with that context are answered from the cached bulk response with no request.