# Workspace Asset Quota

> Canonical path: `specs/saas/workspace-asset-quota.md`
> Status: living-spec
> Scope: SaaS billing-enabled deployments treat MeshX-managed Memory and Skill
> repository counts as unlimited for every workspace member.

Related canonical specs:

- [Organization Seat Purchases](../billing/organization-seat-purchases.md)
- [Managed Memory](../memory/memory.md)
- [Managed Skill](../skill/skill.md)
- [Timed Seat Grants](./timed-seat-grants.md)
- [Specs And Guardrails Layout](../repository/specs-and-guardrails-layout.md)

## 1. Purpose

This spec defines the quota contract for MeshX-managed Memory repositories and
Skill repositories when AgentTeam runs with billing enabled.

AgentTeam owns the commercial entitlement model and the decision that these resource
counts are not seat-plan limited. MeshX owns resource metadata, AGS-backed
repositories, permission facts, count APIs, and realization-impact APIs. MeshX
must not read AgentTeam billing, plan, or grant tables.

The durable rule is:

- Free organizations have unlimited Memory and Skill repository capacity.
- Organizations with an active paid seat subscription have unlimited Memory and
  Skill repository capacity.
- Resource grant rows for Memory and Skill repository metrics do not affect
  runtime capacity.

## 2. Ownership Boundaries

AgentTeam-owned:

- plan defaults and grant ledgers in AgentTeam storage;
- effective limit calculation for a workspace, owner, metric, and scope;
- browser-originated MeshX create/fork limit decisions in the AgentTeam API
  forwarder;
- backend orchestration limit decisions before Role realization creates MeshX
  assets;
- quota-exceeded API envelopes and browser-facing error formatting.

MeshX-owned:

- Memory and Skill metadata;
- AGS repository lifecycle for those resources;
- Memory and Skill permission grants and bindings;
- bounded usage count APIs for the current resource owner/workspace;
- Role realization impact calculation before AgentTeam applies a role.

MeshX is not a billing policy engine. It exposes resource facts to AgentTeam and
executes resource mutations after AgentTeam has authorized the request.

## 3. Limit Model

Quota metrics:

- `managed_memories`
- `skill_repositories`

Both metrics use `quota_scope='workspace_member'`. The scope means the current
human owner in the current workspace, not a whole-workspace cap and not an
organization-wide pool.

Plan default rows live in `organization_resource_plan_limits`:

- Free and paid plan rows use `limit_value=-1`; `-1` means unlimited.
- Active `organization_resource_quota_grants` rows for these metrics are
  ignored by runtime limit decisions.

Runtime limit calculation uses `plan.Store.LoadWorkspaceResourceLimit`:

1. Resolve the workspace organization.
2. Load the effective seat entitlement.
3. Return `Limit=-1` for the requested Memory or Skill repository metric.

Resource grants are additive positive deltas. They do not create paid
subscriptions, do not appear as seat order history, and do not prove purchase.
For Memory and Skill repository metrics they are retained as ledger facts only;
they are not read by runtime resource-quota decisions.
Historical `organization_quota_overrides` and seat entitlement grant rows are
migrated into the canonical grant ledger. Fresh databases omit those legacy
tables; upgraded databases may retain them for compatibility. Runtime
resource-quota checks do not read them.

Existing resources are not deleted, archived, hidden, or disabled by quota
calculation. New Memory and Skill repositories are not rejected for count-based
quota.

## 4. Forwarder Enforcement

The AgentTeam API forwarder resolves the resource limit before proxying matching
browser-originated MeshX mutations when billing is enabled.

Canonical public request paths subject to this check are:

- `POST /api/v1/workspaces/:workspaceId/memory`
- `POST /api/v1/workspaces/:workspaceId/memory/.../fork`
- `POST /api/v1/workspaces/:workspaceId/skills`

After AgentTeam validates the canonical workspace route,
`forwardCanonicalWorkspaceMeshX` strips the workspace prefix. The quota
classifier `meshXResourceQuotaTargetForRequest` therefore matches these private
in-process targets, not public URLs:

- private in-process target `POST /api/v1/memory`
- private in-process target `POST /api/v1/memory/.../fork`
- private in-process target `POST /api/v1/skills`

The forwarder flow is:

1. Issue the trusted MeshX JWT for the authenticated AgentTeam user.
2. Resolve the `workspace_id` from the AgentTeam workspace route.
3. Load the workspace resource limit for the authenticated user and metric.
4. Skip the count because `Limit < 0`.
5. Proxy the original request to MeshX.

If a future product contract reintroduces finite limits, the forwarder must call
MeshX internal count APIs with the trusted JWT and workspace header before
proxying:

- `GET /api/v1/internal/memory/count`
- `GET /api/v1/internal/skills/count`

Store or entitlement errors are returned through the normal AgentTeam API store-error
path. Count failures are relevant only for finite limits; finite-limit checks
must fail closed instead of proxying blindly.

Adding a new browser-reachable MeshX route that creates or forks a managed
Memory or Skill resource must extend the forwarder quota classifier and its
tests in the same change.

## 5. Backend Orchestration Enforcement

AgentTeam backend orchestration must load the same resource limits before calling
MeshX operations that can create Memory or Skill resources.

Role realization follows this pattern:

1. Skip quota checks when billing is disabled, the request is a dry run, or the
   request is not applying changes.
2. Ask MeshX for the realization impact.
3. For each positive Memory or Skill delta, load the workspace resource limit.
4. Skip owner/workspace count checks because `Limit < 0`.
5. Call MeshX realization.

Backend orchestration must not maintain an independent AgentTeam usage counter for
MeshX assets. MeshX remains the source of resource usage facts; AgentTeam remains
the source of limit facts.

## 6. Consistency And Failure Semantics

Memory and Skill repository count checks are disabled by the unlimited runtime
limit. If finite limits are introduced later, quota checks must remain
request-time checks against MeshX usage facts. The count and the
create/fork/realization mutation would be separate operations, so concurrent
requests could temporarily exceed the configured limit. This is acceptable for
resource quotas because keeping billing policy out of MeshX is the stronger
ownership boundary.

AgentTeam must not introduce a denormalized usage table for these MeshX assets
unless a later spec also defines reconciliation, drift handling, and ownership
for that table.

Finite-limit enforcement must fail closed when AgentTeam cannot verify usage against
MeshX. This keeps the quota boundary meaningful under partial dependency
failure. Unlimited limits skip the MeshX count call.

## 7. Error Contract

Memory and Skill repository creation should not produce count-based quota
rejections while their runtime limit is unlimited. If a finite limit is
introduced later, quota rejection uses `plan.QuotaExceededError` with:

- `code='quota_exceeded'`
- `metric='managed_memories'` or `metric='skill_repositories'`
- `quotaScope='workspace_member'`
- `limit`
- `current`
- `requestedDelta`
- `plan`
- `planName`
- `organizationId`

The browser API status for this domain error is `403`. Browser clients that
surface this error must recognize the `quota_exceeded` envelope and show
localized copy using `metric`, `limit`, `planName`, and `quotaScope` instead of
hard-coding server English. The shared plan-quota formatter is the preferred
owner for this formatting when the UI surface can use it. User-facing copy
changes must include complete i18n coverage.

## 8. Verification Requirements

Changes to this quota surface should update or run the smallest relevant checks:

- `internal/store/plan/seat_billing_quota_integration_test.go` for limit and
  grant calculation;
- `internal/forwarder/routes_test.go` for browser-originated MeshX create/fork
  checks and count failure behavior;
- `internal/service/role/agents/*_test.go` for Role realization quota impact and
  rejection paths;
- `internal/tidb/migrate_test.go` when plan-limit or grant schema/default rows
  change;
- `guardrails/repository/specs-and-guardrails-layout.sh` when spec indexing or
  canonical spec paths change.
