# Store Package Boundaries

`internal/store` owns TiDB-backed persistence implementations for AgentTeam core data. It
is not an application-service layer and should not become a root facade for all
business behavior.

## Ground Rules

1. `internal/store/graph.go` is a composition container only.
   - It wires concrete stores for process startup and integration tests.
   - It should not grow business workflow methods.
   - API and service-orchestration packages should receive narrow ports instead of the whole
     graph or broad concrete stores.
2. Store packages own persistence plus store-local domain rules.
   - Keep SQL and table ownership close to the package that owns the invariant.
   - Move cross-domain orchestration to `internal/service/<domain>` or composition
     wiring instead of adding callbacks to oversized stores by default.
3. New dependencies point inward/downward only.
   - `internal/store` must not import `internal/api`, `internal/router`,
     `internal/server`, `internal/service`, `cmd`, `web`, or `daemon`.
   - Store subpackages must not import the root `internal/store` package.
4. Prefer consumer-side ports.
   - Define small interfaces in `internal/api/*` or `internal/service/<domain>` where
     they are consumed.
   - Let concrete store packages implement those ports without exporting broad
     provider-side interfaces.
5. Do not split by CRUD table or generic layers.
   - Avoid packages named only `repository`, `service`, `model`, or `helpers`.
   - Split by bounded capability and invariant instead: discussion, transport,
     collaboration, search, read state, Feishu provider integration, etc.

## Transaction API

Migrated store domains expose a non-transactional `Store` API and a same-named
transaction-bound `TxStore` API returned by `Store.InTx(tx)`. Keep transaction
lifecycle in `Store` wrappers, coordinators, or services; keep SQL and domain
behavior in `TxStore` methods.

The canonical rules are in
[`specs/storage/store-domain-transaction-api.md`](../../specs/storage/store-domain-transaction-api.md).

## Oversized Domain Split Ratchet

The current oversized package baseline, including the combined PR01–PR04
taskboard package-internal boundary split, is embedded in
`guardrails/store/check-store-domain-split-guardrails.py` and checked by:

```bash
guardrails/store/check-store-domain-split-guardrails.py
```

Temporary planning notes for the split sequence should stay outside the repo,
for example under `/tmp/agentteam-store-domain-split-pr01/`, until they become a
long-lived spec or guardrail.

Allowed child package map for the current split sequence:

| Parent package | Planned child packages | Purpose |
|---|---|---|
| `internal/store/app` | `registry`, `installation`, `runtime`, `webhooks` | Isolate app definitions/manifests, installation/grant persistence helpers, runtime/auth/connection persistence helpers, and webhook/provider-watch helpers. |
| `internal/store/taskboard` | `discussion`, `collaboration`, `context` | Keep taskboard store focused on persistence plus transport-neutral contracts. Runtime and HTTP handlers use the in-process store port directly; no remote task-service HTTP transport is planned in this package map. |
| `internal/store/organization` | `feishu` | Isolate provider/client/import/sync details from organization core. |
| `internal/store/message` | `diagnostics`, `search`, `readstate` | Split strong read/diagnostic boundaries without touching core write/access primitives first. |
| `internal/store/privateagent` | none in the first PR sequence | Private-agent projection substrate; defer further splits until owner-private projection ownership is explicit. |

## Domain Ownership Matrix

This matrix captures the high-risk store-domain boundaries that should remain
stable while the oversized packages are reduced. It is intentionally lightweight:
packages may share tables for read models, but each package should keep writes
close to the invariant it owns and expose only the narrow callback surface needed
by another domain.

| Domain | Write invariants owned here | Primary table ownership | Shared read models | Allowed cross-domain callbacks |
|---|---|---|---|---|
| `internal/store/app` | App definitions, installations, credentials, webhooks, runtime auth, subscriptions, app connections, provider secrets, and app-owned delivery preferences. App runtime persistence is canonical-only; the package retains the stable product facade. | Service Actor, Service Event, provider-config, and app-owned notification tables. | May read workspace/member/channel/message/agent state only to authorize or present app-owned workflows. | Existing callbacks into access/message/agent/private-agent projection/attachment/reaction/taskboard are narrow product seams. New seams should be consumer-side ports or service wiring; do not add provider-side `internal/store/app/ports`. |
| `internal/store/privateagent` | Owner-private agent projection, participant lifecycle, channel/thread notification wake state, and active-task target state while the remaining substrate is being collapsed behind parent targets. | Private-agent projection tables, `scope_memberships`, scope notification wake state, and task-target rows where this package owns the transition. | May read access/member/message/attachment/agent data to build projections and authorize participants. | Existing `Options` callbacks may bridge message attachment/reaction/access behavior. Do not split this package until ownership of projection, participants, notification wake state, and task-target state is explicit. |
| `internal/store/message` | Scope message append/read-state/search/diagnostic invariants, reply-thread metadata, message-level fanout jobs, and message-owned routing primitives. | `scope_messages`, `scope_message_sources`, `scope_message_search_*`, `scope_memberships` cursor state, `scope_activity_events` for non-message activity, and `scope_fanout_outbox` jobs where message owns the mutation semantics. | May read access, identity, attachment, and member projections to render message views. | Child packages must own a real capability (`search`, `readstate`, diagnostics) and tests. Avoid utility-only packages; promote helpers only when they carry an invariant or fold them back into the parent. |
| `internal/store/organization` | Organization account/team/unit membership, invitations, workspace grants from organization context, and provider directory sync state. | Organization account/team/unit/invite/grant tables plus Feishu credential/import/sync state. | May read model/storeerr/schema and provider-specific Feishu records for sync presentation. | Provider transport/client details belong in `organization/feishu`; future directory/sync splits should own a state machine, not generic ports. |

Boundary notes:

- `members`, `scope_messages`, and `scope_memberships` are intentionally shared read-model tables. Do not force a one-package-per-table split when ownership is by invariant.
- `access` is the shared authorization/read-model kernel; keep policy checks close to it instead of copying authz SQL into every consumer.
- `testkit` may depend broadly on store packages, but production packages should not use it.
- New child packages under `internal/store` must be capability/invariant named. `internal/store/app/{registry,installation,runtime,webhooks}` are app capability child packages: registry owns app definitions/manifest behavior, installation owns installable-app reads/installation row locks/grant persistence helpers, runtime owns runtime auth/connection/machine capability persistence helpers, and webhooks owns webhook/provider-watch routing/persistence helpers. Generic child package names such as `helpers`, `ports`, or `repository` are blocked by guardrails unless a future planning PR explicitly allowlists one.

## Refactor Ratchet

During the split sequence, the existing oversized parent packages should not grow
in file count, LOC, exported surface, or sibling-store imports unless a PR
explicitly updates the baseline with a planner-approved reason. PR #476
intentionally ratchets the external importer baseline for taskboard transport
extraction and message child-package adapters while keeping parent
LOC/file/exported-surface limits non-growing. New child packages under the
tracked parents must either be in the planned map above or be added through a
dedicated planning/guardrail PR.

Each implementation PR should be an independent closed agentteam:

1. sync from latest `origin/main`;
2. make one structural change type only (port introduction, file relocation,
   facade delegation, or pure helper extraction);
3. keep runtime/API/wire/status/schema behavior unchanged;
4. run the package/domain tests plus guardrails;
5. request one batch review when the PR is ready.
