# Accepted SaaS product-surface completeness audit

Audit date: 2026-07-10

## Verdict

The current Hub is **not a usable or production-complete SaaS product surface**
for the accepted destination. It has a worthwhile Organization-scoped backend
foundation and a well-tested single-Feishu-application Agent Run slice, but the
running product still has one process-global Feishu app/bot and one global model
provider credential. It has no platform-administrator control plane, no
Organization connection/secret plane, and no Organization-scoped Provider
Credential Mode. Its only HTML product page is a login form; successful OAuth
redirects an administrator to an unmounted `/admin/org/:orgSlug` route.

ADR-0022's required production safety surface is also almost wholly absent:
there is no durable Organization-fair admission, layered platform/Organization
limits, hard file/storage/run/process budgets, usage-alert control plane, or
audited workload brake. These are release blockers, not optional polish.

The strongest code should be retained rather than rewritten: all current
`/api/org/:orgSlug/*` routes reauthorize an active OWNER/ADMIN membership;
Folder/Project, member, Team, Team-to-Project access, session, and usage services
are Organization-scoped; and the shared project-onboarding services correctly
create Project/chat/grant relationships for the single-app case. The shortest
route is to add explicit Organization connection identity and the two private
admin control planes around those seams.

The claim-by-claim source inventory, including route, schema and test locations,
is in [accepted product-surface code inventory](product-surface-code-inventory.md).
Official Feishu identifier, OAuth, event and long-connection semantics are in
[Feishu product-surface primary sources](product-surface-feishu-primary-sources.md).

## Accepted boundary

The audit treats these as required because they are already accepted by the
repository's `CONTEXT.md`, ADR-0020, ADR-0021, ADR-0022, and Lean contract:

- a Platform Administrator is separate from Organization membership and uses a
  private `/admin/platform` area to create and operate Organizations;
- an Organization OWNER/ADMIN uses a private `/admin/org/:orgSlug` area to
  manage members, projects, Teams, connections, settings, sessions and usage;
- every Organization authenticates and exchanges bot/directory traffic through
  its own customer-owned Feishu Application;
- every Organization chooses BYOK or an Organization-distinct platform-managed
  Provider Connection, with different administrators owning the respective
  plaintext inputs;
- ordinary members can create/bind a Project from an unbound Feishu group only
  under the Organization policy, while all Project actions retain Project-level
  authorization;
- production has mandatory platform ceilings, optional lower Organization
  limits, durable fair admission, bounded work and audited emergency brakes.

The audit does not add deferred self-service signup, payment collection,
folder ACL inheritance, commercial billing, or a live editor for platform
capacity ceilings. Guided Feishu app setup and manually initiated Organization
creation by an authenticated Platform Administrator remain valid for the pilot.

## Journey result

| Accepted journey | Current product entry | Result | Owning frontier |
| --- | --- | --- | --- |
| Platform Administrator signs in | No platform login/session/guard; only a stored `PlatformRoleAssignment` type | **Missing** | tickets 42, 45 |
| Platform Administrator creates and operates Organizations | Direct Prisma calls in test fixtures only; no `/api/platform/*` or UI | **Test/database only** | ticket 45 |
| Platform Administrator configures each customer Feishu app | One env `FEISHU_APP_ID/SECRET/BOT_OPEN_ID`; no connection or secret model | **Missing and contract-divergent** | tickets 43–45 |
| Platform Administrator supplies a distinct platform-managed provider key/base URL | One process-global `ANTHROPIC_AUTH_TOKEN/BASE_URL`; resolver ignores Project scope | **Missing and contract-divergent** | tickets 43, 45 |
| Platform Administrator applies and reverses `DRAIN`/`STOP_NOW` | No persistence, guard, API or UI | **Missing** | tickets 37, 45 |
| Organization user logs in through that Organization's Feishu app | OAuth exists, but is built once from the global app and has no Organization/application issuer | **Single-app only** | tickets 43, 44 |
| Logged-in Organization OWNER/ADMIN opens the management panel | OAuth redirects to `/admin/org/:slug`; no handler/static application serves it | **Stable 404** | ticket 46 |
| Organization OWNER/ADMIN manages members, Teams, Projects/Folders and Team access | Guarded Organization JSON APIs and useful integration coverage exist | **Backend present; browser product absent** | ticket 46 |
| Organization OWNER/ADMIN manages permission/model/role/run policy | Only `membersCanCreateProjects` is exposed; other stored/runtime settings have no control plane | **Partial** | ticket 46 |
| Organization selects Provider Credential Mode and manages BYOK | No mode/connection model, API or UI | **Missing** | tickets 43, 46 |
| Organization views sessions and usage | Guarded JSON APIs exist, but have no direct product-boundary tests or UI and cannot attribute Provider Connection | **Partial** | tickets 36, 46 |
| Ordinary member creates or binds a Project in Feishu | Card/service path is present and well tested | **Works for the one-global-app case** | ticket 44 for SaaS routing |
| Authorized member runs the agent through its Organization bot | Full single-app trigger path exists; provider/app resolution is global | **Works only as a single-app deployment** | tickets 43, 44 plus lifecycle tickets |
| Accepted work queues fairly and durably across Organizations | Five-item process-memory Project queue | **Contract-divergent** | tickets 23, 32–35 |
| Organization sets lower policy limits and receives soft alerts | No policy/alert persistence or product surface | **Missing** | tickets 32, 34–36, 46 |

## Executed defect evidence

The probes used the real Fastify plugin and test PostgreSQL database through a
temporary Vitest integration file. The file was removed after capture and was
run twice to separate deterministic behavior from a transient test failure.

### 1. The successful-login destination is not served

The probe seeded an active Organization ADMIN, minted the normal signed session
cookie, and requested the exact destination selected by
`resolvePostLoginRedirect`:

```text
GET /admin/org/test-default
expected_status=200
actual_status=404
```

Both runs returned 404. The root cause is structural: `registerAdminPlugin`
mounts cookie, auth and Organization API routes only; its own comment says the
static SPA is “later”. No other repository HTML/TSX/Vue/Svelte/CSS product
application or deployment-level static handler exists. Existing OAuth tests
assert only the redirect `Location`, so they stop immediately before the broken
journey.

This is not an authorization defect in the JSON APIs. Those APIs correctly
return 401 without a session and 403 to a MEMBER. It is a reachability defect:
there is no management panel for either an authorized or unauthorized request
to reach. Ticket 46 must guard both the page shell and its data, so an anonymous
user may reach only the login entry and never management content.

### 2. Different Organizations start OAuth with the same application

The probe seeded two Organizations and requested the current OAuth entry with
two different Organization return paths:

```text
GET /auth/feishu?returnTo=/admin/org/test-default
client_id=cli_process_global

GET /auth/feishu?returnTo=/admin/org/second
client_id=cli_process_global
```

Both runs produced the same result. The root cause is that
`registerAuthRoutes` constructs one immutable OAuth configuration from plugin
startup arguments. The `returnTo` path is signed only as a destination; it does
not resolve or bind an Organization Feishu Application. `server.ts` obtains the
same app credentials from one environment tuple and also uses them for the only
WebSocket listener.

Changing only the redirect page cannot fix this. The composition root and every
identifier-bearing record/action need an explicit Organization connection
identity, backed by the encrypted resolver in ticket 43.

## Feishu identifier and transport consequence

Feishu's official identifier contract makes the current global identifier
shape unsafe for customer-owned applications:

- `open_id` identifies a user inside one application and differs across apps;
- `user_id` is stable inside one tenant but differs across tenants;
- `union_id` can link apps from the same application service provider, but the
  service cannot assume independently customer-owned apps share that provider;
- OAuth authorization identifies the app with `client_id`; the callback brings
  `code` and `state`, so state and token exchange must remain bound to the
  selected Organization/application;
- v2 HTTP event/card headers expose application and tenant identity, while an
  encrypted outer request must be routed to the correct connection before its
  verification/decryption key is known; a long connection is itself created by
  one app credential pair, so the local client/listener identity is the issuer;
- Feishu rejects cross-tenant chat/operator combinations and does not provide a
  contract that permits `chat_id` to be treated as a global SaaS key.

The current `User.feishuOpenId @unique`, session `{userId, feishuOpenId}`,
`ProjectGroupBinding.chatId`, `FeishuEventReceipt.eventId`, principal IDs,
sender caches, and batching keys omit this issuer namespace. An `open_id`
collision across customer apps could merge unrelated external identities;
different `open_id` values for one person across apps cannot be resolved as one
global identity automatically. A globally looked-up `chatId` or event ID has
the analogous ambiguity.

The implementation must therefore persist provider-local external identities
under a Feishu Application Connection, for example conceptually
`(connectionId, externalIdType, externalId)`, and carry that connection through
OAuth state/session, ingress, deduplication, chat binding, authorization,
onboarding/card actions, rate-limit keys, directory sync, outbound delivery and
audit. Whether two external identities represent the same global `User` is a
separate explicit linking decision; it must not be inferred from a provider ID
outside its documented scope.

## Root causes rather than symptoms

### 1. Tenant data was added without tenant-scoping external connections

Organization ownership is correctly present on Projects, Teams, Folders and
directory-connection metadata, but the process composition still comes from
the former single-deployment shape. The app/bot/provider credentials and the
identifiers they issue never crossed an Organization connection boundary.
`RuntimeScope.projectId` exists but is discarded by the env resolver, making
the missing seam especially explicit.

### 2. The control-plane work stopped at guarded Organization APIs

The Organization routes provide a credible backend slice, but there is no web
application, platform route family, platform guard, Organization provisioning
service, connection configuration, or browser journey test. The comment
“until admin SPA lands” and redirect-only OAuth assertion document the precise
unfinished handoff. A small catch-all page would only hide the gap; the fix must
deliver the authenticated, authorized workflows and their safe states.

### 3. Production capacity semantics exist only in the contract

ADR-0022 intentionally declares the in-memory queue and global-only settings to
be implementation divergences. There is no common admission identity that can
join Organization, Feishu connection, Project, user and request, so fair
scheduling, workload brakes, rate limits, alerts and control-plane operations
cannot currently compose. The previously created capacity tickets own this
implementation; the admin tickets must expose their safe operator/customer
surfaces rather than invent parallel controls.

## What should remain

- Keep `requireOrgRole`'s per-request session, active Organization, active
  membership and OWNER/ADMIN checks; extend the same principle to page data and
  add a separate `requirePlatformAdmin` rather than an Organization override.
- Keep the Organization-scoped explorer/member/Team/access/session/usage APIs
  as the initial backend for the org panel, while filling their missing tests,
  pagination/limits, audit and typed-error contracts through existing tickets.
- Keep shared Project creation and chat-binding services so web and Feishu
  entries do not drift into two rule sets.
- Keep the single-app Feishu handler's authorization, onboarding, run/session
  and dedup behavior, but call it from a connection-bound listener context and
  move accepted work/delivery into the durable lifecycle tickets.
- Keep missing provider cost unknown rather than fabricating zero; add Provider
  Connection attribution before presenting SaaS usage claims.

## Newly clear execution frontier

The product audit creates four implementation/decision slices after the
existing secret and browser-boundary design work:

1. [Decide the platform-administrator identity and audit boundary](../issues/42-decide-platform-admin-identity-audit.md).
2. [Implement the Organization connection and secret plane](../issues/43-implement-org-connection-secret-plane.md).
3. [Route Feishu identity and traffic by Organization connection](../issues/44-route-feishu-by-org-connection.md).
4. [Build the private platform-administrator control plane](../issues/45-build-private-platform-admin-control-plane.md).
5. [Build the private Organization-administrator control plane](../issues/46-build-private-org-admin-control-plane.md).

The Feishu routing slice precedes durable inbound/action/outbound and signed
traffic limiting so those mechanisms use the right issuer namespace from their
first durable schema. The platform panel precedes the emergency-brake product
entry so ticket 37 can reuse one authenticated/audited operator boundary.
Existing tickets 32–38 continue to own capacity enforcement, attribution,
alerts, brakes and calibration; the new UI tickets do not duplicate them.

## Required production journey evidence

The final release gate must run against at least two configured customer-owned
Feishu applications and two Organizations, using deliberately colliding fake
provider-local IDs where the test harness permits. It must prove:

1. an unauthenticated request and an authenticated wrong-role user cannot load
   either management page data or perform mutations;
2. a Platform Administrator can create an Organization, bootstrap its OWNER,
   configure/test its Feishu app and distinct platform-managed Provider
   Connection, with redacted responses and complete audit history;
3. an Organization OWNER/ADMIN logs in through that Organization's app, reaches
   the real org panel, manages the accepted settings/resources, configures BYOK
   when selected, and cannot read another Organization or platform secret;
4. each bot receives, binds, authorizes, queues and replies only inside its
   connection's Organization even when `open_id`, `chat_id`, event or callback
   values collide; SUSPENDED/ARCHIVED Organizations fail closed;
5. provider resolution for every run returns that Organization's selected
   connection and never a process-global fallback;
6. current member onboarding, Project authorization and Agent Run behavior
   remain intact after connection namespacing;
7. layered limits, fair admission, usage alerts and `DRAIN`/`STOP_NOW` are
   visible only to the corresponding authorized control plane and behave as
   proven by their dedicated fault/capacity suites;
8. the complete browser + Feishu journey survives the production-shaped deploy,
   restart, backup/restore and rollback evidence owned by ticket 08.
