Migration 0034: two tables. repo_connections carries the workspace's
per-provider config (owner, base_url, label, last_synced_at,
last_sync_error) and points at an app_connections row for the PAT.
repos is the per-connection cache with (connection_id, external_id)
unique so upsert is idempotent across re-syncs. Cascading deletes clean
up cleanly on connection removal.
cm-secrets grows a FetchAuthorized op — GET with the stored PAT injected
as bearer, returns status + JSON body without ever exposing the
credential to cm-api. This is the least-privilege door for read-only
provider APIs (list repos), distinct from the InvokeHttp path that still
requires a single-use approval grant for outbound writes.
cm-api::routes::repos wires:
- POST /api/repos/connections (broker store_secret + insert both rows +
initial sync + mark_synced)
- GET /api/repos/connections
- DELETE /api/repos/connections/:id
- POST /api/repos/connections/:id/sync
- GET /api/repos (500 cap, newest provider_updated first)
- GET /api/repos/:id (full detail incl. clone_url + html_url)
GitHub provider inline for v1 — paginated pull of /orgs/:owner/repos
(when owner set) or /user/repos (when absent), 100/page, capped at 20
pages (~2k repos) to keep first-sync latency bounded. Non-2xx surface
back to the caller as sync_error; parse failures are best-effort per
repo (skipped, logged, don't abort the batch).
Gitea + GitLab providers land in a follow-up — mostly URL swap +
response-shape adapter.
- GET /api/team/orgchart — each member grouped with the claws they manage
(agents.managed_by); GET /api/team/leaderboard — every claw ranked by
its real usage_events rollup (credits/tokens/runs, zeros included).
Both tested against real Postgres.
- Stripe Buy-credits (the Slack/Clerk integration pattern): [billing]
config (stripe keys + price + webhook secret + credits_per_pack);
POST /api/credits/checkout opens a real Checkout Session; POST
/api/billing/stripe verifies Stripe's t=,v1= HMAC (constant-time) and
grants one credit lot, idempotent on the session id; GET
/api/billing/config gates the button (honest degradation when unset).
Offline tests: signed grant + replay no-double-grant + forged-sig 400 +
config flag. Live checkout creation deferred to a CM_LIVE_STRIPE test.
- /apps global page support: clawId now optional on connect + directory;
absent => workspace-wide connection (app_connections.agent_id NULL) via
new connections::list_for_workspace.
- ApiError gains a From<sqlx::Error> so inline queries use ? cleanly.
cm-api 10 test files incl. team_tabs (2) + stripe_billing (3); clippy clean.
Co-Authored-By: Claude Fable 5 <[email protected]>
Full-depth rename per the approved plan; the 'claw' product vocabulary
(claws, /claws routes, clawId, Claw Chat) stays — it is now the brand.
- Display brand: Clawmates (manifest, titles, hero, login/rail logo
'clawmates'); default host app.clawmates.work; registry
ghcr.io/clawmates
- Crates tc-* -> cm-* (16 crates + all imports); binaries
clawmates-server/broker/bundler; images clawmates/*; env prefix
CLAWMATES_* (+ CM_TEST_DATABASE_URL / CM_LIVE_LLM); config
clawmates.toml; helm chart deploy/helm/clawmates with clawmates-*
resources; db names clawmates*; sockets /run/clawmates; cookie
cm_session; kind cluster clawmates-test; seccomp node profile
clawmates-agent-profile.json
- All 9 Playwright brand assertions updated in lockstep; historical
spec document left untouched as the only remaining 'TeamClaw'
- Local env migrated: dev pg clawmates-dev-pg/clawmates_dev, shared
test server clawmates-test-pg, kind cluster recreated with image +
profile, compose images rebuilt under clawmates/*
Verified end to end: 161 Rust + 68 frontend tests, 29 Playwright
journeys, 4 live kind tests, helm/install/LOC/placeholder gates, and
the clean-room install rehearsal serving the clawmates login page from
a signed bundle of the rebuilt images.
Co-Authored-By: Claude Fable 5 <[email protected]>