Skip to content

[feat] Extend access controls and billing settings#4330

Merged
junaway merged 28 commits into
release/v0.100.0from
feat/add-access-controls-in-env-vars
May 19, 2026
Merged

[feat] Extend access controls and billing settings#4330
junaway merged 28 commits into
release/v0.100.0from
feat/add-access-controls-in-env-vars

Conversation

@junaway
Copy link
Copy Markdown
Contributor

@junaway junaway commented May 14, 2026

What this unlocks

This PR turns the previously code-locked plan/role/catalog/pricing surface into an env-driven configuration layer. Operators may set AGENTA_ACCESS_PLANS, AGENTA_ACCESS_ROLES, AGENTA_ACCESS_ROLES_OVERLAY, AGENTA_ACCESS_DEFAULT_PLAN, AGENTA_ACCESS_DEFAULT_PLAN_OVERLAY, AGENTA_BILLING_CATALOG, and AGENTA_BILLING_PRICING at deployment time, restart the API and worker processes, and the new shape is the effective shape — no code change, no rebuild. When any of the JSON env vars are absent the system falls back to code defaults, so existing deployments need no operator action.

The immediate capabilities this lights up are: define a custom plan with a custom entitlement profile (new counter limits, new gauges, new throttle buckets, new flags), tweak a single quota on an existing plan without restating the rest (AGENTA_ACCESS_DEFAULT_PLAN_OVERLAY merges field-by-field into whichever plan AGENTA_ACCESS_DEFAULT_PLAN points to), add a custom role at the project (and workspace, by symmetry) scope without restating the platform-managed minima or the default workspace roles (AGENTA_ACCESS_ROLES_OVERLAY), mark the free and trial plan slugs inline in pricing ({free: true} / {trial: N} markers on the pricing entries themselves — no cross-env-var consistency burden), and separate display catalog from purchase pricing so the pricing modal and the Stripe line-item / per-meter-price configuration evolve independently. The closed Plan enum is gone; subscriptions.plan is a plain str, and the auth-side role serialization widened to str too — env-defined custom roles flow cleanly through WorkspacePermission.role_name and InviteRequest.roles without enum-coercion failures.

A second hard cut shipped alongside: events retention split out of billing. The old POST /admin/billing/usage/flush (spans-only) is gone; spans now flush at POST /admin/spans/flush (cron crons/spans.sh at 0,30 * * * *, lock namespace spans:flush) and events flush at POST /admin/events/flush (cron crons/events.sh at 7,37 * * * *, lock namespace events:flush). They share nothing — separate DAOs, services, routers, cron files, Redis locks — so the two flushes can run concurrently and a failure in one never blocks the other. Counter.EVENTS_INGESTED was added to the entitlement system as a retention-only counter (no write-path adjusts a meter row for it, no entry in the meters_type Postgres enum, deliberately not in REPORTS); the events flush job walks the effective plan map and respects each plan's Counter.EVENTS_INGESTED.retention. Per-plan defaults align with each plan's Counter.TRACES_INGESTED retention: Quota(period=Period.MONTHLY, retention=Retention.MONTHLY) on Hobby, QUARTERLY on Pro, YEARLY on Business, and retention=None (unlimited) on Agenta and Self-hosted Enterprise.

What changed under the hood

env vars layout. api/oss/src/utils/env.py gains two sub-models on EnvironSettings: AccessControls (plans, roles, roles_overlay, default_plan, default_plan_overlay) and BillingSettings (catalog, pricing). The JSON-bearing fields are decoded into typed Python objects at startup via small _load_json_env_* helpers, so downstream modules consume already-parsed dicts / lists and never re-parse strings. AGENTA_ACCESS_DEFAULT_PLAN is the canonical default-plan name; legacy AGENTA_DEFAULT_PLAN is honored as a read-through fallback so existing deployments are not forced to rename their env var. The trial-flow config previously carried by AGENTA_BILLING_TRIAL_PLAN / AGENTA_BILLING_TRIAL_DAYS is now inline on the pricing entry as a {trial: N} marker (positive integer days) — there can be at most one trial entry across the whole pricing dict, and BillingSettings no longer carries trial_plan / trial_days fields.

controls.py — the access-controls builder. A new module api/ee/src/core/entitlements/controls.py owns plans + roles. _build_controls() runs once at import time: it parses AGENTA_ACCESS_PLANS against _PlanOverride (Pydantic, extra="forbid") or falls back to DEFAULT_ENTITLEMENTS keyed by the DefaultPlan enum, applies AGENTA_ACCESS_DEFAULT_PLAN_OVERLAY to the resolved default plan via _merge_quota / _merge_throttle, parses AGENTA_ACCESS_ROLES and the role catalog falls back to _default_roles() (organization gets minima only, workspace and project get minima + default extras like admin / developer / editor / annotator), applies AGENTA_ACCESS_ROLES_OVERLAY to both workspace and project scopes, and hashes the resulting controls into a 12-character SHA stamp. The public surface — get_plans(), get_plan(slug), get_plan_entitlements(slug), get_plan_description(slug), get_roles(scope), get_role(scope, slug), get_role_permissions(scope, slug), get_role_description(scope, slug), get_controls_hash() — returns the frozen effective state and is what every runtime caller uses. At startup the module logs [access-controls] plans=defaults|env roles=defaults|env plan_overlay=none|env→<slug> roles_overlay=none|env hash=<12char> so multi-worker deployments can grep across logs to verify every process loaded the same config.

settings.py — the billing-settings builder. api/ee/src/core/subscriptions/settings.py parses AGENTA_BILLING_CATALOG (list of _CatalogEntry Pydantic models with extra="allow" so operators can add display-only fields the frontend renders; type ∈ {"standard", "custom"} is enforced) and AGENTA_BILLING_PRICING (object keyed by plan slug — flat shape {slug: {free?, trial?, <stripe_slot>: {price, quantity?}}} that mirrors the original STRIPE_PRICING plus the two reserved markers). Reserved keys are free (bool) and trial (positive int days); every other key on a pricing entry is a Stripe-side slot name owned by the operator (the same names they used on the Stripe dashboard — typically "traces" and "users") and is not validated against an internal enum. Cross-cutting validation runs in _build_settings(): every catalog plan slug must exist in the effective plan set, every pricing slug must exist in the effective plan set, at most one entry may carry "free": true, at most one entry may carry "trial": N, the free-plan fallback must be resolvable, and AGENTA_ACCESS_DEFAULT_PLAN (when set) must be in the effective plan set. Accessors get_catalog(), get_catalog_plan(slug), get_pricing(), get_pricing_plan(slug), get_stripe_line_items(slug), get_stripe_meter_price(plan, meter), get_free_plan(), get_trial_plan(), get_trial_days(), trial_enabled() are how the billing router, the meter reporting job, the subscription service, and the signup flow read the effective settings. [billing-settings] catalog=defaults|env pricing=defaults|env free_plan=<slug>|None trial=<slug>/<n>d|disabled is logged at startup.

Stripe meter name mapping. Internal Counter / Gauge slugs (code identifiers) and Stripe-side meter event names (external configuration the operator owns on the Stripe dashboard) are independent surfaces. api/ee/src/core/entitlements/types.py carries a single source of truth at module scope: STRIPE_METER_NAMES: dict[str, str] = {Counter.TRACES_INGESTED.value: "traces", Gauge.USERS.value: "users"}. The runtime in meters/service.py uses STRIPE_METER_NAMES.get(meter.key.value) for both the stripe.billing.MeterEvent.create(event_name=...) argument and the per-meter price-id lookup; meters not in the map fall through the existing skipped-meter log path with a clear message. The pricing-config slot names in AGENTA_BILLING_PRICING agree with the Stripe-side names (operator-owned), so an internal rename of Counter.TRACES_INGESTED does not require a coordinated Stripe-dashboard change or an env-var rewrite on every deployment. Adding a new Stripe-reportable counter or gauge requires exactly two edits: add the Counter / Gauge member to REPORTS, and add the row to STRIPE_METER_NAMES.

Default-plan overlay. AGENTA_ACCESS_DEFAULT_PLAN_OVERLAY lets self-hosted operators tweak individual entitlement values on the default plan without restating the rest. The shape mirrors _PlanOverride (description, flags, counters, gauges, throttles) with one divergence: throttles is a map keyed by category slug instead of a list, so per-category patches don't require restating the whole throttle list. Merge semantics — description replaces, flags per-key replace, counters/gauges field-merge inside each quota (overlay-set fields replace, omitted fields keep the base; pass null to clear), and throttles[category] looks up the existing single-category throttle on the base plan and field-merges its bucket. Operators who need multi-category or endpoint-keyed throttle changes use AGENTA_ACCESS_PLANS instead. Resolution happens after the base plan map is built but before the public dicts are frozen, so all downstream accessors see the merged state.

Roles overlay. AGENTA_ACCESS_ROLES_OVERLAY is the symmetric overlay for the role catalog. Today only the project top-level key is accepted; the patch is applied to both the workspace and project scopes because they share the same default role set in the code defaults. If workspace or organization appears as a key, startup fails — silent ignore would mislead operators. Per role slug: if the slug exists on the scope, the patch is a per-field replace (permissions swaps the array, description swaps the string, fields not set are preserved); if the slug does not exist, the entry is appended as a new role and both permissions and description must be supplied. owner and viewer minima are reserved and cannot be patched — they are platform-managed and synthesized fresh after every override.

Catalog vs. pricing. Splitting them is the central design choice: AGENTA_BILLING_CATALOG is the user-facing pricing-modal display (title, description, type, features, optional display price and retention for marketing copy), and AGENTA_BILLING_PRICING is the Stripe wiring (line items for checkout / subscription create, per-meter price IDs for usage reporting, plus the free / trial markers). Catalog entries without a plan field are exempt from the effective-plan-set cross-check (contact-sales tiers like Enterprise have no slug). Stripe line items are optional on a per-plan basis — custom and self-hosted plans aren't directly purchasable, and paid-checkout flows fail with a clear "Plan 'X' is not available for purchase (no Stripe line items configured)" instead of silently mis-charging. Only counters in the REPORTS allowlist (today traces_ingested and users) actually get pushed to Stripe; declaring price IDs for other slot names is accepted by the parser but those meters are not reported.

Onboarding plan resolution. get_default_plan() reads env.access_controls.default_plan first (with the legacy AGENTA_DEFAULT_PLAN as fallback), and falls back to DefaultPlan.CLOUD_V0_HOBBY when Stripe is enabled and DefaultPlan.SELF_HOSTED_ENTERPRISE when Stripe is disabled. get_free_plan() returns whichever pricing entry is marked "free": true, falling back to DefaultPlan.CLOUD_V0_HOBBY when no env-driven free plan is defined (and only if that slug is in the effective plan set — operators who restrict AGENTA_ACCESS_PLANS to a slug set without cloud_v0_hobby must mark one pricing entry as "free": true or startup fails). trial_enabled() is true when some pricing entry carries a "trial": N marker (the single trial entry's slug + day count drive get_trial_plan() and get_trial_days()). Behavior: Stripe-enabled + trial-configured → reverse-trial flow on the trial plan, then auto-downgrade to free; Stripe-enabled + no trial → direct onboarding on the free plan; Stripe-disabled → direct onboarding on get_default_plan().

Runtime refactor. Direct imports of CATALOG, ENTITLEMENTS, FREE_PLAN, REVERSE_TRIAL_PLAN, REVERSE_TRIAL_DAYS are gone from runtime code. The closed Plan enum is gone; SubscriptionDTO.plan is Optional[str], response models use str, role-validation uses controls.get_role(scope, slug) instead of WorkspaceRole(value). Constants in entitlements/types.py were renamed to DEFAULT_CATALOG / DEFAULT_ENTITLEMENTS to signal their fallback role, and DefaultPlan / DefaultRole enums survive as code-default seeds used only for the fallback and for type-safe conditional checks against well-known slugs. The throttle middleware (api/ee/src/services/throttling_service.py) iterates get_plans() and falls back to the free plan's throttle bucket when an organization is on an unknown plan, so a misconfigured / orphaned subscription still gets rate-limited instead of bypassing throttling entirely. The tracing-flush job and the events-flush job both walk get_plans() and respect each plan's Counter.TRACES_INGESTED.retention / Counter.EVENTS_INGESTED.retention per the new dynamic plan map. Permission.default_permissions(role) callers were replaced with get_role_permissions(scope, role) and WorkspaceRole.get_description(role) callers with get_role_description(scope, role) so the resolution path goes through the effective catalog, not the closed enum.

Quota hardening. Quota, Probe, Bucket, and Throttle Pydantic models all declare model_config = ConfigDict(extra="forbid") so operator typos in AGENTA_ACCESS_PLANS or AGENTA_ACCESS_DEFAULT_PLAN_OVERLAY JSON (the most common one being a leftover "monthly": true from the pre-reshape config) fail startup with a clear Pydantic error pointing at the offending field instead of silently dropping. _merge_quota round-trips through model_dump() + dict update + model_validate(), which preserves enum types (Pydantic v2 coerces JSON strings/ints back to their Period / Retention / Scope enum members) and benefits from the strict config on each merge.

Meters service value-based identity. api/ee/src/core/meters/service.py was switched from name-based identity (meter.key.name in Gauge.__members__.keys()) to value-based (meter.key.value in _GAUGE_SLUGS) where the slug sets are computed once at module scope. The Meters enum (meter table column) and the Counter / Gauge enums (entitlements) share lowercase string values but are independent types — using values rather than names keeps the dispatch correct if either side is ever renamed without touching the other. The same module-level frozensets _GAUGE_SLUGS and _COUNTER_SLUGS are reused by both the gauge-update and counter-event branches of the Stripe report loop.

Catalog → Stripe price wiring. A small helper script docs/designs/dynamic-access-and-billing/migrate_stripe_pricing.py annotates an existing flat pricing dict with the free and trial markers (annotate(pricing, free_slug=..., trial=(slug, days))) so operators who already have the original STRIPE_PRICING shape can derive the new AGENTA_BILLING_PRICING form with a single pass. Slot names ("traces", "users") are operator-owned and pass through unchanged. The legacy env vars are no longer read by the runtime — only the new AGENTA_BILLING_PRICING is consulted.

Auth-context wiring. AGENTA_ACCESS_* overrides do not touch the auth path directly, but the access-control resolution functions get_role(scope, slug) and get_role_permissions(scope, slug) are what api/ee/src/utils/permissions.py consults at every permission-check call site. WorkspaceRole is preserved in the codebase as a code-default seed used only by _default_roles(); runtime resolution goes through the effective catalog. Organization-scope viewer has no permissions by design (orgs are membership markers); workspace and project viewer get the code-default read-only permission set so existing project members keep resolving to their historical permission sets. Env overrides may add roles or customize non-minima permissions but cannot remove or rebind the owner/viewer minima — they are re-synthesized after every override.

Tests

acceptance integration unit
web +0 / ~0 / -0 +0 / ~0 / -0 +0 / ~0 / -0
services +0 / ~0 / -0 +0 / ~0 / -0 +0 / ~0 / -0
api +0 / ~3 / -0 +0 / ~0 / -0 +144 / ~0 / -0
sdk +0 / ~0 / -0 +0 / ~0 / -0 +0 / ~0 / -0

Migrations

One Alembic revision on this branch: a1b2c3d4e5f7_unify_org_member_role_to_viewer.py. It unifies the organization_members.role server-side default and any existing "member" rows to "viewer", completing the org-role rename in OrganizationRole so every scope (organization / workspace / project) shares the same least-permission role slug. Downgrade restores the legacy "member" default and re-maps rows back. Chained after the meters reshape (9d3e8f0a1b2c) so the EE core Alembic tree stays single-headed after the feat/clean-up-meters merge.

Two existing migrations were edited:

The companion meters reshape (9d3e8f0a1b2c_reshape_meters_table.py) shipped on the separate feat/clean-up-meters branch and is documented under docs/designs/extend-meters/; it is merged into this branch but is not part of this PR's design scope.

Env vars

Added (all optional — code defaults apply when unset):

  • AGENTA_ACCESS_PLANS — JSON object keyed by plan slug; values are _PlanOverride-shaped (description, flags, counters, gauges, throttles).
  • AGENTA_ACCESS_ROLES — JSON object keyed by scope (organization/workspace/project); values are non-empty arrays of role entries.
  • AGENTA_ACCESS_ROLES_OVERLAY — JSON object with a single project key; values are role-slug → {permissions?, description?} patches applied to both workspace and project scopes.
  • AGENTA_ACCESS_DEFAULT_PLAN — plan slug for signup; legacy AGENTA_DEFAULT_PLAN honored as fallback.
  • AGENTA_ACCESS_DEFAULT_PLAN_OVERLAY — JSON object mirroring a plan entry's shape; field-merged into the resolved default plan at startup.
  • AGENTA_BILLING_CATALOG — JSON array of catalog entries served by /billing/plans.
  • AGENTA_BILLING_PRICING — JSON object keyed by plan slug; flat shape with operator-owned Stripe slot names (traces, users, etc.) plus reserved free: bool and trial: int markers.

Removed: legacy STRIPE_PRICING / AGENTA_PRICING are no longer read; AGENTA_BILLING_TRIAL_PLAN / AGENTA_BILLING_TRIAL_DAYS are collapsed into the per-entry {trial: N} marker on AGENTA_BILLING_PRICING. migrate_stripe_pricing.py in this design folder annotates an existing flat pricing dict with the free / trial markers.

Tests and docs

Unit tests live in api/ee/tests/pytest/unit/: test_access_controls.py (parser-level tests for plans, roles, and overlay merge — including reserved-slug rejection, duplicate-slug rejection, unknown-permission rejection, throttle-category mismatch rejection — 61 tests), test_controls_env_override.py (subprocess-driven end-to-end env override tests covering every JSON env var across the no-override / override / overlay states), test_billing_settings.py (catalog + pricing parsers under the flat shape, _resolve_trial per-entry trial detection, free-plan validation, default-plan validation), test_billing_router.py (billing handler behavior under env overrides), test_events_retention.py (events flush service: plan iteration, project pagination, per-plan failure isolation), test_admin_retention_routers.py (spans + events admin endpoints — lock namespaces, busy-lock skip, handler shape). EE unit suite green across the three primary access/billing test files: 61 + 83 = 144 tests.

docs/designs/dynamic-access-and-billing/ carries the design story: research.md and gap.md as historical baselines, proposal.md for the as-shipped state, tasks.md as the execution checklist (initial + post-initial sections covering the default-plan relocation, events counter + retention split, and roles overlay), migrate_stripe_pricing.py as the free / trial annotation helper, and this summary.md. Operator-facing docs cover access controls only: docs/docs/self-host/04-dynamic-access-controls.mdx documents every AGENTA_ACCESS_* env var with field tables, examples, and validation rules; docs/docs/self-host/02-configuration.mdx carries the configuration-table row pointing at those variables. Billing / Stripe wiring is internal-only and not documented in the operator-facing MDX. The hosting/docker-compose/ee/env.ee.dev.example and env.ee.gh.example files were extended with the new AGENTA_ACCESS_* variables; no billing / Stripe env vars appear in either file.

Copilot AI review requested due to automatic review settings May 14, 2026 15:16
@vercel
Copy link
Copy Markdown

vercel Bot commented May 14, 2026

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

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview, Comment May 19, 2026 5:12pm

Request Review

@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. Backend labels May 14, 2026
@junaway junaway changed the base branch from main to release/v0.99.8 May 14, 2026 15:16
@junaway junaway marked this pull request as draft May 14, 2026 15:17
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 14, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 35139459-be00-4865-8a52-181a0b894f88

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/add-access-controls-in-env-vars

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR generalizes plan/role definitions from closed Python enums into runtime-configurable env vars (AGENTA_ACCESS_*, AGENTA_BILLING_*), splits the previous /admin/billing/usage/flush span retention endpoint into independent /admin/spans/flush and /admin/events/flush admin routers (each with its own service, DAO, cron, and Redis lock), and updates the frontend to treat plan slugs as plain strings while keeping a DefaultPlan enum for code-side conditionals. Backend Plan/WorkspaceRole enums are largely replaced with string slugs validated against the effective access-controls catalog at startup.

Changes:

  • New AccessControls/BillingSettings env layers parsed at startup with strict cross-reference validation; legacy STRIPE_PRICING removed and migration script provided.
  • New Counter.EVENTS plus EventsService/EventsDAO/EventsRouter/SpansRouter and events.sh cron; BillingRouter no longer owns retention.
  • Frontend Plan widened to string with DefaultPlan enum retained for known constants; updates to billing UI, banners, and useEntitlements.

Reviewed changes

Copilot reviewed 67 out of 72 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
api/oss/src/utils/env.py Adds AccessControls + BillingSettings env models with JSON loaders; drops StripeConfig.pricing.
api/ee/src/core/entitlements/types.py Renames PlanDefaultPlan, adds DefaultRole/Counter.EVENTS/Period; renames CATALOG/ENTITLEMENTS→DEFAULT_*.
api/ee/src/core/subscriptions/{settings,service,types}.py New billing settings module with strict validation; service uses dynamic free/trial accessors; types use string plans.
api/ee/src/core/entitlements/{service,controls(*not in diff)}.py Entitlements service consumes get_plan_entitlements.
api/ee/src/core/{events,tracing}/service.py New EventsService.flush_events; TracingService.flush_spans iterates dynamic plans.
api/ee/src/dbs/postgres/events/{init,dao}.py New EE retention DAO independent from OSS events DAO.
api/ee/src/apis/fastapi/{spans,events}/router.py New independent admin routers replacing /admin/billing/usage/flush.
api/ee/src/apis/fastapi/billing/router.py Drops tracing_service dep + flush endpoint; uses dynamic catalog/pricing/free-plan accessors.
api/ee/src/main.py Wires new spans/events services and routers.
api/ee/src/services/{throttling_service,workspace_manager,db_manager_ee,converters,admin_manager}.py Switch to controls accessors and string slugs.
api/ee/src/utils/{permissions,entitlements}.py Resolve role permissions and entitlements via controls module.
api/ee/src/routers/workspace_router.py Validates assigned role against effective workspace catalog.
api/ee/src/models/{db_models, api/api_models, api/workspace_models}.py Org-member default role memberviewer; widens role types to str.
api/ee/src/core/workspaces/types.py WorkspacePermission.role_name: str.
api/oss/src/core/{accounts,auth}/service.py, api/oss/src/routers/workspace_router.py Remove enum coupling; validate plan/role via EE controls.
api/ee/src/dbs/postgres/subscriptions/mappings.py Drop Plan enum coercion.
api/ee/databases/postgres/migrations/.../*.py Inline literal FREE_PLAN constants; new migration to unify memberviewer.
api/ee/src/crons/{spans.sh,events.sh,events.txt}, api/ee/docker/Dockerfile.{dev,gh}, hosting/docker-compose/ee/* Cron + image wiring for new events flush job and EE env examples.
api/ee/tests/pytest/unit/test_{access_controls,billing_settings,billing_router,controls_env_override,events_retention,admin_retention_routers}.py New/updated tests for parsers, env wiring, retention services, and admin routers.
docs/docs/self-host/{02-configuration,04-dynamic-access-controls,05-dynamic-billing-settings}.mdx Operator-facing documentation for the new env surface.
docs/designs/dynamic-access-and-billing/{research,gap,proposal,tasks,findings,migrate_stripe_pricing.py}.* Design folder + legacy STRIPE_PRICING converter.
docs/designs/data-retention/*, docs/design/ee-self-hosting/research.md, docs/openapi-cleanup/endpoints.md Updated to reference the split admin endpoints.
web/oss/src/lib/Types.ts, web/oss/src/lib/helpers/useEntitlements.ts Plan enum renamed PlanDefaultPlan; runtime plan typed as string.
web/ee/src/services/billing/types.d.ts, web/ee/src/components/SidebarBanners/state/atoms.ts, web/ee/src/components/pages/settings/Billing/* Frontend updated to use DefaultPlan constants and string plan slugs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/docs/self-host/04-dynamic-access-controls.mdx Outdated
Comment thread docs/docs/self-host/04-dynamic-access-controls.mdx
Copilot AI review requested due to automatic review settings May 14, 2026 15:50
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 241 out of 256 changed files in this pull request and generated 1 comment.

Comment thread api/ee/src/services/admin_manager.py Outdated
@junaway junaway changed the title [fix] Extend access controls and billing settings to env vars (+ clean-up) [fix] Extend access controls and billing settings May 15, 2026
@junaway junaway changed the title [fix] Extend access controls and billing settings [feat] Extend access controls and billing settings May 15, 2026
@junaway junaway changed the base branch from release/v0.99.8 to feat/clean-up-meters May 18, 2026 12:35
Copilot AI review requested due to automatic review settings May 18, 2026 14:43
Copilot AI review requested due to automatic review settings May 19, 2026 12:52
@junaway junaway changed the base branch from feat/clean-up-meters to release/v0.100.0 May 19, 2026 12:52
@junaway junaway marked this pull request as ready for review May 19, 2026 12:52
@dosubot dosubot Bot added the enhancement New feature or request label May 19, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 83 out of 98 changed files in this pull request and generated no new comments.

Comment thread docs/docs/self-host/04-dynamic-access-controls.mdx Outdated
Comment thread docs/docs/self-host/04-dynamic-access-controls.mdx
Comment thread docs/docs/self-host/04-dynamic-access-controls.mdx
Copy link
Copy Markdown
Member

@mmabrouk mmabrouk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @jp-agenta

  1. The project comment from yesterday is still unadressed
  2. Codex returned a reasonably correct issue. I added it as a comment. Worth investigating

Other than that lgtm! Same comment as yesterday, having a comprehensive evaluation here is important. Especially for self-hosted ee, since upgrades there are not automatic.

@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label May 19, 2026
Copilot AI review requested due to automatic review settings May 19, 2026 14:35
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 90 out of 106 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (4)

docs/designs/dynamic-access-and-billing/summary.md:1

  • The PR description and several places in the design docs still advertise AGENTA_BILLING_TRIAL_PLAN and AGENTA_BILLING_TRIAL_DAYS as added env vars (e.g. the "Env vars" section of the PR body and research.md lines 135–137). The shipped code (api/oss/src/utils/env.py BillingSettings, summary.md, and migrate_stripe_pricing.py) collapses the trial config into a per-entry {trial: N} marker on AGENTA_BILLING_PRICING and BillingSettings no longer carries trial_plan / trial_days fields. Please update the PR description and research.md to match the shipped flat-pricing shape so operators don't try to set the removed env vars.
    api/ee/src/services/converters.py:1
  • When \"*\" is present alongside other slugs, the wildcard branch discards every non-wildcard slug and returns the full Permission enum. That's fine for the platform-synthesized owner (which is exactly [\"*\"]), but env-overridable roles via AGENTA_ACCESS_ROLES / AGENTA_ACCESS_ROLES_OVERLAY could legitimately combine \"*\" with extras, or include a custom slug not in Permission. In the latter case Pydantic will reject the whole WorkspacePermission.permissions list because the slug isn't an enum member, producing a 500 at the API boundary. Consider filtering non-Permission slugs out (with a warning) before returning, and merging the expansion of \"*\" with any other valid Permission slugs in the input.
    api/oss/src/utils/env.py:1
  • These call _load_json_env_dict(...) at class-definition time (Pydantic field defaults are evaluated once when the class body executes). That means any ValueError raised by the loader becomes an import-time failure with a stack trace originating in module import, rather than a clean Pydantic validation error at EnvironSettings() instantiation, and the resulting dict is a mutable singleton shared across any future instances. Consider using default_factory=lambda: _load_json_env_dict(\"AGENTA_ACCESS_PLANS\") (and similar for the other JSON fields) so loading happens on instantiation and each instance gets its own object.
    api/ee/src/services/throttling_service.py:1
  • Module-level mutable warned-once flag is read/written without a lock from throttling_middleware, which can run concurrently across asyncio tasks and (under multi-worker deployments) processes. The race is benign for a warning-suppression flag — worst case the warning logs a few extra times — but if this pattern is replicated, consider using functools.lru_cache-on-a-no-arg helper or a logger filter, which makes the once-only intent explicit and process-safe.

Comment thread api/ee/src/crons/events.txt
Comment thread api/ee/src/apis/fastapi/billing/router.py
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 106 out of 122 changed files in this pull request and generated no new comments.

Copy link
Copy Markdown
Contributor

@ardaerzin ardaerzin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@junaway junaway merged commit 6ec23f5 into release/v0.100.0 May 19, 2026
26 of 27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Backend ci/cd enhancement New feature or request feature lgtm This PR has been approved by a maintainer size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants