# Module Contracts

This document records the current implemented package boundaries for
`agent-git-service`. The multitenant runtime uses one shared business graph and
one tenant-owned metadata database plus Git root for each configured tenant.

## Core Invariant and Authority Split

The most important architectural invariant in this repo is that
`agent-git-service` is Git-backed. Repository content, history, refs, diffs,
merges, rebases, and Git transport semantics should stay grounded in real Git
behavior.

Authority is split by concern:

- `gitstore` owns Git-native repository state and operations.
- `db` owns higher-level relational metadata such as users, auth, issues, pull
  requests, reviews, labels, workflow records, and similar product state.
- `service` coordinates flows that need both Git-backed and DB-backed state.
- surface packages should respect that ownership instead of inventing parallel
  storage rules.

## High-Level Rules

- Transport packages decode requests, call `service`, and render responses.
- Business rules live in `internal/service`.
- GORM model definitions, migrations, and seed data live in `internal/db`.
- Git-native repository operations live in `internal/gitstore`.
- Git Smart HTTP protocol bridging lives in `internal/githttp`.
- `server` is the composition root; package constructors should not open global
  process resources behind the caller's back.

The public import surface is intentionally small:

- `config` exposes strict YAML-file and environment-backed startup
  configuration.
- `server` exposes the embeddable composition-root APIs (`New`, `Run`,
  `RunWithConfig`, `RunWikiReindex`, `RunWikiReindexWithConfig`, `Handler`, and
  `Shutdown`).
- Everything else in the root module remains internal-only unless documented
  otherwise.

## Package Ownership

| Package | Owns | May Depend On | Must Not Depend On |
|---|---|---|---|
| `apperrors` | sentinel application errors and helpers | stdlib | transport rendering |
| `avatarstore` | tenant-scoped normalized avatar object persistence | stdlib, `randutil` | service policy, HTTP routing, DB models |
| `connectedlogin` | configurable login URL, token exchange, userinfo claims | HTTP helpers, stdlib | service persistence |
| `config` | strict YAML-file and environment-backed configuration | YAML parser, stdlib | internal runtime packages |
| `tenant` | validated tenant identity, public policy, and request-context helpers | stdlib | request-header parsing, config loading, persistence |
| `crypto` | secret encryption primitives | NaCl dependencies, stdlib | service or DB policy |
| `db` | models, migrations, seed data | GORM, stdlib | service, router, transports |
| `embedding` | embedding client and vector search support | HTTP helpers, stdlib | transport packages |
| `githttp` | Git Smart HTTP CGI bridge | `service`, `gitstore`, HTTP stdlib | REST/GraphQL handlers |
| `gitstore` | bare repository filesystem layout and Git commands | `pagination`, go-git, `git` CLI, stdlib | service, DB, auth |
| `graphql` | GraphQL parsing, resolver dispatch, field filtering | `service`, DB model types | REST response helpers |
| `httputil` | bounded outbound HTTP helpers | stdlib | service policy |
| `logging` | structured logging helpers | stdlib/logging dependencies | DB or Git behavior |
| `mentions` | mention parsing and extraction helpers | stdlib | persistence or auth |
| `metrics` | Prometheus collectors and recorder facade | Prometheus client, stdlib | business-rule ownership |
| `middleware` | auth extraction, current-user context, request guards | `service`, `db` model types, logging/metrics helpers | transport handlers |
| `oauth` | OAuth device-flow endpoints and headless device approval API | `service`, middleware-compatible auth context | REST transform helpers |
| `oidc` | OIDC discovery, device flow, token verification | HTTP helpers, stdlib | service persistence |
| `pagination` | overflow-safe page arithmetic | stdlib | transport or business policy |
| `randutil` | shared random-hex generation | crypto/rand, stdlib | callers' persistence rules |
| `ratelimit` | GitHub-compatible rate-limit snapshots | stdlib | transport handlers |
| `rest` | REST decoding and response orchestration | `service`, `pagination`, `rest/respond`, `rest/transform`, DB model types for shapes | direct GORM queries for business behavior |
| `router` | route registration and host rewrite | REST, GraphQL, Git HTTP, OAuth, middleware | direct DB/Git operations |
| `service` | business rules and orchestration | `db`, `gitstore`, `pagination`, `sync`, embedding/OIDC helpers, metrics/logging | transport response packages |
| `sync` | Provider-neutral inventory diff, empty-target bootstrap selection, and physical batch planning | stdlib | service persistence, transport routing, provider credentials |
| `testharness` | real-router integration harness | server-adjacent packages, TiDB test schemas | production-only process setup |
| `wikicatalog` | wiki catalog indexing support | DB/Git-adjacent helpers | transport routing |
| `wikiv2` | wiki V2 storage helpers | Git/storage helpers | transport routing |

## Tenant Scope Contract

`server` is the only package that resolves request `Host` or
`X-Forwarded-Host` into a tenant. It resolves against normalized
`config.TenantConfig` before authentication and injects immutable
`tenant.Info`. Downstream packages read only `internal/tenant` context; they
must not reinterpret forwarding headers or accept a tenant identifier from
request payloads.

The process owns one shared REST, GraphQL, Git HTTP, OAuth, and
`service.Service` graph. A `TenantResourcesProvider` maps validated `TenantID`
to tenant-owned DB, Git/wiki storage, public policy, crypto, login providers,
embedding provider, caches, locks, and worker state. Transport code must not
retain a per-tenant service pointer or select resources itself.

Every tenant-owned business operation calls `tenant.Require(ctx)` directly or
through a service scope helper. Missing, unknown, disabled, or mismatched
tenant state fails closed. There is no default-tenant fallback, including in a
single-tenant deployment.

The configured `TenantID` is stable and low-cardinality, so it may be used in
logs, Prometheus labels, audit envelopes, and jobs. Raw rejected hosts must
never become metric labels. Detached work copies the validated tenant onto the
server lifecycle context before starting.

Each tenant database and Git root is exclusively claimed by a persistent
tenant ownership marker. Repository authorization remains owned by `service`
inside the selected tenant.

## Auth Contract

`internal/middleware/auth.go` is the only package that extracts bearer, token,
and Basic-auth credentials from HTTP requests.

Authenticated routes use `TokenAuth`; discovery or public-capable routes use
`OptionalTokenAuth`. Both paths validate tokens through `service` and inject the
current `db.User` into request context for downstream code.

Embedded hosts register authenticators per `TenantID`. A process-global
authenticator is valid only when normalization produced exactly one tenant.
When an authenticator returns a trusted identity, middleware maps that identity
into a user in the selected tenant database and uses it as the current user for
REST, GraphQL, Git HTTP, OAuth approval, and optional-auth discovery paths.

When the token table is empty, any non-empty token is accepted as a
local-development convenience. Production deployments should seed explicit
tokens.

Tokens, OAuth device codes, authorization codes, sessions, keys, and embedded
identities are created and resolved only through the selected tenant resources.
A credential lookup never scans another tenant database. Existing
single-tenant credentials remain in the database explicitly adopted for that
tenant; there is no empty or process-global credential scope.

## Database Contract

The production `service.Service` graph does not own a default tenant DB.
Production service code resolves the database through its tenant scope, and
`DBForCtx` returns a tenant-required error state rather than silently selecting
a default DB. Direct dependency fields and no-tenant branches that remain for
legacy package-test fixtures are not valid server wiring and must be removed as
the test suite is migrated to explicit tenant contexts.

Transaction context helpers may replace the active GORM session only after
verifying that it belongs to the same `TenantID`. They are not a tenant routing
boundary and transport middleware must not use them to switch tenants.

Every tenant database contains an `ags_tenant_metadata` ownership record.
Startup claims or verifies that record before seeding or serving requests.
Equivalent DSNs or aliases therefore cannot assign one database to two
tenants. Configuration is authoritative when an existing database has no
ownership record; startup claims it for the tenant selecting that database.
Tenant DSNs may reuse the same authenticated database principal, but must
select separate databases; once written, the ownership record remains the
authoritative database-to-tenant binding.

Transport handlers should not query GORM directly for business decisions. If a
handler needs data, add or reuse a service method.

## Git Store Contract

`internal/gitstore` owns the physical bare-repository layout:

```text
TENANT_GIT_REPO_DIR/{owner}/{repo}.git
```

Repository full names are logical GitHub-compatible identities. Git store code
must validate full names before mapping them to filesystem paths, and
write-sensitive operations must take the per-repository mutex.

Multi-file plumbing writes create blob/tree/commit objects separately from ref
publication. Callers that coordinate Git with another system of record persist
the prepared SHA first and then publish it with a parent CAS. Any process-local
tree cache must be bounded, transfer mutable ownership to only one preparer,
and validate the cached commit against the current branch parent.

`service` resolves the tenant store before deciding whether a caller may access
a repository. `gitstore` should not know about auth or DB permissions. Each
root has a persistent tenant ownership marker, and store-level mutex keys are
tenant-scoped.

## Git HTTP Contract

`internal/githttp` bridges HTTP requests to `git-http-backend`.

- Auth middleware populates the current user before protected Git operations.
- `githttp` asks `service.HasRepoAccess(...)` for read/write authorization.
- `git-http-backend` remains responsible for protocol-level pack and ref
  negotiation.
- `git-receive-pack` holds the same concrete-store repository mutex used by
  service writes while it snapshots and updates refs.
- Post-push side effects, such as workflow sync and webhook dispatch, are
  coordinated through service-level helpers.

## REST Contract

REST handlers should:

1. parse route params, query params, and JSON request bodies
2. call `service`
3. convert models through `rest/transform`
4. write responses through `rest/respond`

Handlers may reference DB model types for request/response shape compatibility,
but business queries and mutations belong in `service`.

## GraphQL Contract

GraphQL resolvers use service methods directly and then prune response fields
according to the requested selection set. GraphQL should not call REST
transform helpers.

Stack and Merge Queue follow the same split: REST renders Stack/async/webhook
JSON, GraphQL renders Stack/queue nodes and mutations, and service alone owns
membership, queue state, ruleset evaluation, speculative refs, and atomic Git
transitions. Merge-group ref publication and GitHub-compatible
`checks_requested` Actions dispatch are coordinated through a durable
service-owned outbox. Internal `destroyed` rows coordinate terminal recovery and
speculative-ref cleanup only; they must never become webhook or Actions
delivery. Neither surface may introduce a non-GitHub Stack extension API.

## Background Work Contract

Background goroutines started from service methods copy the validated tenant
and principal onto the server lifecycle context. They must not copy a
request-scoped transaction, because it may commit before the goroutine runs.
The detached operation resolves a fresh database session from the same tenant
registry.

In-memory queue envelopes and persisted job, audit, and webhook-delivery
records carry immutable `TenantID`. Persisted envelopes live in the database
claimed by that same ID. Startup backfills legacy empty provenance and rejects
mismatches; workers compare the envelope to their validated context before
processing.

Long-running or asynchronous jobs log tenant plus relevant
repository/user/workflow IDs so failures can be diagnosed without relying on
transport-level request state.

## Testing Contract

- Package and service tests should cover business rules first.
- Router and integration tests should use `internal/testharness` when they need
  real middleware and route wiring.
- Git behavior tests should exercise real bare repositories where practical.
- E2E scripts should remain shell-based, executable, and focused on externally
  observable behavior.

The regression gate manifest is `scripts/regression_gate.list`; keep it aligned
with this document when packages move or disappear.
