The org admin SPA was org-admin only: every project route used
requireOrgRole, so a plain MEMBER could not reach the projects they held
a project grant on, and an org OWNER/ADMIN could mutate any project
without holding the project's `manage` grant. That contradicts ADR-0004
(spec `Permission.lean`): org role is not a project authorization root,
and the only out-of-role override is platform-admin force-release
(`RequiresAdmin`), not org admin.
Add `requireProjectPermission` (guards.ts): resolve any org member, bind
the project to their org, then check the PermissionGrant authorizer.
`allowOrgAdminOversight=true` lets OWNER/ADMIN through for *read*
oversight only; mutations pinned to `collaborator.manage`
(grant/revoke team-access) pass `allowOrgAdminOversight=false`, so an org
admin still needs the project MANAGE grant to mutate access. The project
detail GET now also returns `actorIsOrgAdmin` and `actorCanManageProject`
so the SPA can render mutation controls only for entitled actors.
Add a member-facing project surface:
- `GET /api/org/:orgSlug/my-projects` + `listMyProjects` resolve the
actor's principals and return the projects with a READ+ grant.
- The SPA routes members (non-admin) to the projects page instead of the
admin overview, renders a member project shell on project routes, shows
a `我的项目` list for members and the full folder explorer for admins.
- The project detail page gates rename/archive/bind/sessions behind org
admin and the grant/revoke UI behind `actorCanManageProject`.
- The denied panel now points members at their authorized projects.
Update admin-members-teams integration test: seed the owner with a MANAGE
grant on the test project so the org-owner flow still passes the new
project-level gate on team-access grant/revoke.
ADR-0022 / Spec.System.Capacity pins layered capacity limits: a platform
ceiling per dimension is unbreakable, and each organization may only set a
lower `organizationLimit`. The effective limit is the minimum of the two
(`LayeredLimit.effective`); dimensions with no org override fall back to
the platform ceiling. No dimension may be unlimited (a ceiling must exist
before an org limit can be set, `LayeredLimit.Valid`).
Add the backend: the pinned 23 CapacityDimension set + labels
(src/capacity/dimensions.ts), platform ceilings sourced from existing
runtime env vars plus `HUB_CEILING_<DIMENSION>` (src/capacity/ceilings.ts),
the OrganizationCapacityPolicy prisma model + migration, the
getCapacityPolicy/setCapacityPolicy service enforcing LayeredLimit.Valid,
and org-admin GET/PUT /api/org/:orgSlug/capacity-policy routes wired into
the org route tree.
Add the admin-web surface: CapacityDimension/CapacityPolicyView api client
types, capacityPolicy/setCapacityPolicy methods, a `容量` nav entry, and a
capacity page that lists every dimension with its platform ceiling (or
`未配置` when unset), an org-limit input (disabled until a ceiling exists),
and a live effective-value column. Saving sends the partial limits map;
the service rejects values above the ceiling or for unconfigured dimensions.
Run `prettier --write .` across all source files. Changes are purely
formatting: trailing commas, line wrapping at 120 chars, import
reordering, and CSS whitespace. No logic changes. Verified with
`prettier --check .` and `svelte-check` (0 errors, 0 warnings).
ADR-0021 pins the organization<->Feishu application binding to 1:1 and
the backend already exposes
GET /api/org/:orgSlug/feishu-application-connection
PUT /api/org/:orgSlug/feishu-application-connection (rotate/create)
DELETE /api/org/:orgSlug/feishu-application-connection (disable)
backed by FeishuApplicationConnectionService with versioned envelopes
(ADR-0024). The org-admin SPA had no surface for it, so the only
connection type the spec requires was unmanageable from the admin UI.
Add a Feishu page that reads the current connection (status, redacted
app fingerprint, active version, updatedAt), rotates credentials with
appId/appSecret/botOpenId (+ optional verificationToken/encryptKey) as
the backend requires, and disables with confirmation. Add the matching
api client (feishuApplication / rotateFeishuApplication /
disableFeishuApplication), a feishu nav icon and a nav entry.
The org-admin provider page was a stale prototype wired to a removed
singular /provider-connection endpoint. It contradicted the pinned
invariants in several ways:
- It documented a process-env fallback for platform-managed credentials,
but ADR-0024 / Spec.System.Organization pins the resolver fail-closed
with no process-global key fallback.
- It exposed a BYOK<->PLATFORM_MANAGED mode toggle to org admins, but
ADR-0021 makes platform-managed connections platform-admin owned; the
org-side API (requireByokActor) rejects mutating them.
- It modeled one connection per org, while OrganizationProviderConnection
is keyed by (org, providerId) and the backend exposes a list plus a
per-providerId BYOK rotation.
- Its HTTP contract (/provider-connection, {baseUrl, hasAuthToken}) did
not match the real backend (/provider-connections + /:providerId,
{status, activeVersion, keyId}).
- It dangled a pointer to role/model pages that do not exist in the SPA;
roles/skills are managed via the CLI.
Rewrite the page to list connections, show status/version/keyId, and
rotate BYOK credentials per providerId with baseUrl + authToken (+ optional
anthropicApiKey) as the backend requires. Platform-managed rows render
read-only. Drop the fallback copy and the dangling pointer. Replace the
singular ProviderConnection API client with providerConnections /
rotateProviderConnection and remove the now-unused PROVIDER_MODES constant.