Skip to content

M4 Workstream B + M2 wsid registry: quota, OAuth, billing, admin, egress CLI, workspaces - #9

Merged
MikeBengtson merged 10 commits into
mainfrom
feat/m4-workstream-b
May 15, 2026
Merged

MikeBengtson merged 10 commits into
mainfrom
feat/m4-workstream-b

Conversation

@MikeBengtson

@MikeBengtson MikeBengtson commented May 14, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Six stories landed under M4 productization (`gm-o9t8.4.`) and M2 federation (`gm-o9t8.2.`). All beads created, claimed, and closed.

gm-o9t8.4.2.1 — Tier-aware quota + rate-limit middleware

  • `internal/quota/` — `Tier`, `Limits`, `LimitsForTier` (free/solo/teams/enterprise defaults), token bucket with refill, `Counters` interface + memory impl.
  • `internal/server/middleware/quota.go` — `WithQuota`: extracts tenant, looks up tier, hits bucket, gates concurrent-VM/workspace limits on a route allowlist, returns 429 with `Retry-After`. Bypasses on missing tenant.
  • `internal/tenant/` — `Tier` field on Tenant + idempotent SQL migration.
  • `--quota-enforce` flag (default true).

gm-o9t8.4.4.1 — GitHub OAuth org/team gating

  • Existing `OrgGate` wired into login handler. Non-members get 403 `not_in_allowed_orgs`.
  • `--oauth-github-allowed-orgs` (csv) + `--oauth-github-allowed-team` (repeatable).

gm-o9t8.4.1.1 — Billing usage counters + `/api/v1/tenants/:tid/usage`

  • Default `billing.Aggregator` mounted at boot. Three meters fed by existing audit→aggregator bus.
  • `--billing-meters-enable` flag.

gm-o9t8.4.3.1 — Operator admin endpoints

  • Coverage of pre-existing `/api/v1/admin/{tenants,sessions,audit/verify}`.

gm-o9t8.4.3.2 — Egress policy templates (CLI)

  • `gemba egress template list|apply --workspace --name <github-only|pypi+npm|wide-open>`. Idempotent.

gm-o9t8.2.4 — wsid registry for multi-tenant resolution

  • `internal/workspaces/` — Registry interface, SQLStore (self-migrating workspaces table, unique (tenant_id, slug)), MemStore, conformance suite.
  • Cross-tenant collision case covered.
  • Status endpoint prefers registry; falls back to legacy basename match.
  • `--multi-tenant` boots SQL-backed registry; `--workspaces-bootstrap` migrates existing dirs as `t-default:`.
  • `gemba workspace list|create|delete` CLI.

Test plan

  • `go build ./...` clean
  • `go test ./...` — 3842+ pass
  • `golangci-lint run --timeout=2m` — 0 issues

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

MikeBengtson and others added 5 commits May 13, 2026 22:01
OAuth org-gating (gm-o9t8.4.4.1):
- AttachOrgGates setter on Router
- oauthLogin handler calls OrgGate.Allow after fetchGitHubUser;
  non-members get 403 not_in_allowed_orgs before tenant provisioning
- serve flags --oauth-github-allowed-orgs (csv) and
  --oauth-github-allowed-team (repeatable org:team); installs global
  gate keyed on empty tenant id
- ServeConfig.OAuthGitHubAllowedOrgs/Teams
- Test: TestOAuthLogin_OrgGateRefusesNonMember (httptest stub for
  /user, /user/orgs, /user/teams)

Billing usage (gm-o9t8.4.1.1):
- serve wires default billing.Aggregator via AttachUsageAggregator
  when --billing-meters-enable (default true)
- ServeConfig.BillingMetersEnable
Wires a thin CLI surface over the existing egress.BuiltinTemplates
bundle + the `/api/v1/workspaces/{wsid}/egress-rules` HTTP surface.

- `gemba egress template list` (--json) reads BuiltinTemplates and
  prints the available bundles + their network-default stance + hosts.
- `gemba egress template apply --workspace <wsid> --name <tpl>` fetches
  the workspace's current rules, materializes the template's allowlist
  into per-host rules with stable IDs (tpl-<name>-<host>), and POSTs
  only the rules not already present. Re-applying is a no-op.
- `wide-open` emits a slog.Warn on apply documenting the operator's
  acknowledgement that the workspace bypasses host filtering.

Tests use a stubbed egressAPIClient (no httptest needed). Story
admin endpoints (gm-o9t8.4.3.1) were already implemented in
internal/server/admin_console.go and tenants_admin.go on the Router;
this commit ships the egress story (gm-o9t8.4.3.2) and references the
already-landed admin surface in the closure note.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a per-tenant quota envelope keyed on subscription tier (free / solo /
teams / enterprise) and the HTTP middleware that enforces it after
WithTenant. Internals:

- internal/quota: Tier + Limits model with LimitsForTier defaults,
  standalone goroutine-safe Bucket + BucketStore, and a read-only
  Counters projection (with a MemCounters test impl).
- internal/server/middleware/quota.go: WithQuota composes after
  WithTenant. 429 + Retry-After on bucket exhaustion (emits
  quota.rate.limit audit event when an Auditor is wired), and 429 /
  402 on the route-allowlisted concurrent-VM / monthly-minute /
  workspace ceilings. No-tenant requests bypass so upstream auth
  owns the 401.
- internal/tenant: Tenant.Tier column with idempotent ALTER migration
  (information_schema probe + ADD COLUMN); MemStore + SQLStore both
  honor the new field.
- internal/cli/serve.go: wires the tier middleware on /api after
  WithTenant when --quota-enforce=true (default). In-memory
  BucketStore today; Redis-backed store is the production follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MikeBengtson and others added 2 commits May 14, 2026 08:45
…pnpm artifacts

- internal/quota/quota_test.go: split `b.Allow() || b.Allow()` into two
  assignments so the second call actually executes (the || would have
  short-circuited on the first true return).
- .gitignore: ignore /.pnpm-store/ (local pnpm cache) and
  testing/e2e/package-lock.json (this repo uses pnpm-lock.yaml).
- Remove the two files that slipped in via an earlier `git add -A`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MikeBengtson and others added 2 commits May 14, 2026 09:04
Add internal/workspaces package providing a Registry contract with two
implementations: SQLStore (Dolt-backed, self-migrating) and MemStore
(in-memory). Both share a conformance suite that pins
Resolve/List/Create/Delete semantics, the (tenant_id, slug) uniqueness
invariant, and cross-tenant slug collisions (t-A:foo vs t-B:foo
resolve independently).

Router gains AttachWorkspaceRegistry; the workspaceStatus handler
prefers registry resolution and falls back to the legacy
config.ListAllProjects basename match when the registry is unset or
ErrNotFound, preserving M1 single-user behaviour.

cmd/gemba serve wires the registry: SQLStore when --multi-tenant is on
and a Dolt *sql.DB is available, otherwise MemStore. On first boot
(controlled by --workspaces-bootstrap, default true) the server scans
--workspaces-root and inserts a default-tenant row for every existing
project dir.

New `gemba workspace list|create|delete` subcommand operates against
the same registry either via a direct Dolt SQL URL or an in-memory
fallback for dry runs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@MikeBengtson MikeBengtson changed the title M4 Workstream B: quota middleware, OAuth org-gating, billing, admin endpoints, egress templates CLI M4 Workstream B + M2 wsid registry: quota, OAuth, billing, admin, egress CLI, workspaces May 14, 2026
@MikeBengtson
MikeBengtson merged commit 8afcea4 into main May 15, 2026
8 of 10 checks passed
@MikeBengtson
MikeBengtson deleted the feat/m4-workstream-b branch May 15, 2026 02:15
@MikeBengtson
MikeBengtson restored the feat/m4-workstream-b branch June 10, 2026 02:02
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