# MeshX Resource Repository Lifecycle

> Canonical path: `specs/ags/resource-repository-lifecycle.md`
> Status: living architecture spec
> Scope: common repository lifecycle, access distribution, and Desktop repository primitives for MeshX resources backed by AGS repositories, especially Memory, Skill, and KB source repositories.

## 1. Purpose

MeshX has several product resources that are backed by Agent Git Service (AGS)
repositories. Memory, Skill, and KB source repositories share enough plumbing
that the repository lifecycle and access distribution rules should be explicit,
but their product semantics are different enough that they must not collapse
into a single generic resource service.

This spec defines the stable boundaries for shared code and the points where
Memory, Skill, and KB must fork into domain-specific behavior.

## 2. Goals

- Keep repository-backed MeshX resources easy to reason about across server,
  Desktop, and web surfaces.
- Reuse boring infrastructure for repository creation, canonical repository
  mapping, short-lived sync leases, and access-change notifications.
- Preserve Memory, Skill, and KB product invariants without hiding them behind a
  broad abstraction.
- Make partial failures, stale access-change events, branch policy, and
  Desktop cache semantics explicit.
- Define characterization coverage that protects existing behavior whenever
  shared helpers are introduced or reused.

## 3. Non-goals

- Do not introduce a large `ResourceService` that owns Memory, Skill, and KB
  business semantics.
- Do not move Memory watcher, push, rebind, force-push, or local import logic
  into a generic repository-cache package.
- Do not move Skill `SKILL.md` scanning, import safety, nested-root handling,
  duplicate-name detection, symlink checks, scan limits, or runtime
  materialization into a generic repository package.
- Do not merge short-lived sync leases with long-lived collaborator grant and
  reconciliation ledgers.
- Do not add KB-like AGS repository access profiles to Memory or Skill human
  permissions. Their product roles are `owner` / `manage` / `view` / `none`;
  any human AGS direct access must come from explicit durable access intent, not
  from workspace defaults or implicit creator collaborator grants.

## 4. Current invariants

| Resource | Product authority | Repository authority | Agent binding shape | Desktop behavior |
| --- | --- | --- | --- | --- |
| Memory | `Memory`, `MemoryGrant`, `MemoryAgentBinding` | one canonical `.mem` row in `resource_repositories` | one agent has at most one Memory; one Memory is bound to at most one agent | read/write vault clone, watcher, commit, push, stop-session flush, rebind handling |
| Skill | `Skill`, `SkillAgentBinding`, `SkillRepositoryBranch` | one canonical `.sk` row per Skill resource; one repo may contain several detected skill roots | many Skills per agent; repo branch is pinned per workspace/repo | read-only managed clone/fetch, cached paths, runtime skill materialization on launch/execution boundaries |
| KB source | `knowledge_base_sources`, KB grants, repository access grant ledger | canonical/mirror/insights rows in `resource_repositories` depending source mode | KB agent access is grant-driven, not Memory/Skill binding-driven | repository access is reconciled into AGS collaborators; retrieval/runtime reads through KB product authority |

AGS repository access is necessary for repository operations, but it is not the
product source of truth. Product authorization remains in the owning MeshX
resource tables and grant/binding ledgers. Memory and Skill repositories are not
workspace-directory resources by default; AGS access is an execution detail for
MeshX and short-lived agent sync leases unless a durable human access intent is
explicitly reconciled.

## 5. Three planes

### 5.1 Lifecycle plane

The lifecycle plane owns repository creation, initialization, canonical mapping,
and compensation ledgers. It answers: "Which AGS repository exists for this
business resource, and how do we recover if cross-system creation partially
failed?"

Shared lifecycle code may own:

- repository name stem and reserved suffix validation;
- storage owner resolution for personal/workspace storage;
- AGS `EnsureRepository` request construction from an explicit create spec;
- `EnsuredRepository` output after AGS creation succeeds;
- `resource_repositories` mapping construction;
- lifecycle-event recording for create-bind and delete-cleanup failures.

`internal/store/resource/repos` owns the persistence models and bounded
queries for repository mappings, access-grant materialization state, and
lifecycle events. It is a root store domain and must not depend on MeshX
packages. Reusable lifecycle orchestration remains in
`internal/service/resource/repos`; resource services compose that
orchestration with the persistence store instead of defining parallel mapping,
grant, or lifecycle-event stores.

Domain code must still own:

- Memory template initialization, Memory labels, product owner grants, and any
  durable creator access intent;
- Skill template initialization, skill count, repository scan metadata, product
  owner grants, optional workspace visibility grants, non-catalog visibility
  defaults, and import behavior;
- KB source mode, mirror/insights repositories, KB grants, and repository
  collaborator reconciliation.

### 5.2 Access plane

The access plane owns binding/grant lookup, short-lived sync lease construction,
and access-change notifications. It answers: "Which agents may access which
repository-backed asset right now, with what repository capabilities?"

Shared access code may own:

- agent-id deduplication and owned-agent validation helpers;
- canonical repository lookup by resource type/id/role;
- sync lease repository scope construction;
- asset access-change outbox enqueueing;
- generation/version vocabulary for stale event guards;
- typed adapter helpers for Memory and Skill access-batch responses.

Domain code must still own:

- Memory one-to-one binding rules and explicit write branch selection;
- Skill many-to-many binding rules and repository-branch pin uniqueness;
- KB product grants and long-lived repository collaborator grant reconciliation;
- response DTO shape differences between Memory single-record access and Skill
  list access.

### 5.3 Content plane

The content plane owns product-specific repository contents and runtime use. It
answers: "What does this repository mean to the product, and how does the local
runtime consume or write it?"

This plane remains domain-specific:

- Memory: vault clone, local recovery files, watcher, commit, push, flush,
  conflict/rebind strategy, and existing-local-content import.
- Skill: `SKILL.md` discovery, import from archive/URL/repo, nested detected
  roots, duplicate skill names, lower-case `skill.md` normalization, managed
  skill cache, and runtime materialization.
- KB: source parsing, indexing, retrieval, source/insights repository
  access-required states, and collaborator reconciliation status.

## 6. Core abstractions

### 6.1 EnsuredRepository

`EnsuredRepository` describes an AGS repository that has been created or
confirmed to exist. It is not yet a canonical MeshX mapping.

```go
type EnsuredRepository struct {
    AGSRepoID     string
    Owner         string
    Name          string
    FullName      string
    DefaultBranch string
    Visibility    string
    UserVisible   bool
}
```

Use it only between `EnsureRepository` and the domain transaction that persists
the MeshX resource plus `resource_repositories` mapping.

### 6.2 ResourceRepositoryRef

`ResourceRepositoryRef` mirrors `resource_repositories`. It describes the
canonical MeshX binding from a business resource to an AGS repository.

```go
type ResourceRepositoryRef struct {
    WorkspaceID  string
    ResourceType string
    ResourceID   string
    RepoRole     string
    OwnerKind    string
    RepoOwner    string
    RepoName     string
    RepoFullName string
    AGSRepoID    string
    DefaultRef   string
    Visibility   string
    UserVisible  bool
}
```

Do not put creation-only inputs such as requested storage form fields or domain
metadata into this ref. Those belong to create specs or domain resources. The
canonical ref answers only: "Which AGS repository is mapped to this MeshX
resource role?"

### 6.3 RepositoryLifecycleSpec

A lifecycle spec is the shared create pipeline plus domain callbacks.

```go
type RepositoryLifecycleSpec struct {
    ResourceType string
    RepoRole     string
    NameSuffix   string
    AGSKind      string
    Visibility   string
    UserVisible  bool
    Features     AGSRepositoryFeatures

    ResolveCreateInput func() (ResolvedCreateInput, error)
    Initialize         func(context.Context, EnsuredRepository) error
    DomainTx           func(context.Context, EnsuredRepository) (ResourceRepositoryRef, error)
}
```

The common implementation must keep the create operation two-staged:

```text
ResolveCreateInput
  -> EnsureRepository => EnsuredRepository
  -> DomainInitializer(EnsuredRepository)
  -> DomainTx:
       persist product resource
       create resource_repositories mapping
       run domain after-persist work such as grants or scan metadata
  -> lifecycle event succeeded/failed
```

The distinction between `EnsuredRepository` and `ResourceRepositoryRef` is
mandatory. It is what makes the "AGS repo exists but MeshX DB write failed"
case repairable.

### 6.4 BranchPolicy

Branch policy must be explicit and domain-owned.

```go
type BranchPolicy interface {
    ResolveBranch(context.Context, BranchResolveInput) (string, error)
    ValidateBeforeBind(context.Context, BranchBindInput) error
    ReleaseAfterUnbind(context.Context, BranchReleaseInput) error
}
```

Memory and Skill policies differ:

- Memory branch is binding and write-back configuration. It must not be silently
  defaulted by a generic helper when the API requires explicit input.
- Skill branch is a distribution version pin. It can default from request,
  repository default ref, or `main`, but the active branch for a workspace/repo
  must remain unique and conflicts must be visible.

### 6.5 AccessEnvelope and typed DTOs

A server-internal access envelope can carry common facts:

```go
type AccessEnvelope struct {
    WorkspaceID string
    AgentID     string
    AssetType   string
    Repo        ResourceRepositoryRef
    Branch      string
    Capabilities []string
    Version     int64
}
```

Public and Desktop-facing DTOs must remain typed at first:

- Memory returns a single `AgentAccessibleMemory` or `null` per agent, plus
  Memory-specific flags.
- Skill returns a list of `AgentAccessibleSkill` records per agent, plus repo id
  and branch metadata.

Do not make `json.RawMessage` the main public contract. If an internal envelope
is useful, use typed adapters at the Memory and Skill boundaries.

## 7. Flow contracts

### 7.1 Create flow

```text
MemoryService.CreateMemory / SkillService.CreateSkill / KB source create
  -> lifecycle common:
       validate name and storage input
       resolve repository owner/name/default ref
       ensure AGS repository
       produce EnsuredRepository
  -> domain initializer:
       Memory template + labels
       Skill template
       KB source/mirror/insights initialization
  -> domain DB transaction:
       persist product resource
       upsert resource_repositories row
       persist grants, skill metadata, or KB access intents
  -> lifecycle event:
       record failure when AGS changed but MeshX did not finish
```

Fork points:

| Hook | Memory | Skill | KB source |
| --- | --- | --- | --- |
| repository suffix | `.mem` | `.sk` | source-mode dependent, usually `.kb` for managed repos |
| initializer | Memory template and labels | Skill template | canonical/mirror/insights source init |
| after-persist | owner MemoryGrant plus optional creator access intent | owner Skill grant / optional workspace view grant / skill count / scan metadata; no workspace catalog default | KB grants and repository access intents |
| partial failure | create-bind lifecycle event | create-bind lifecycle event | create-bind lifecycle events per source repo role |

### 7.2 Bind and unbind flow

```text
Bind resource to agent
  -> validate acting subject
  -> validate owned agent
  -> validate domain permission
  -> load canonical ResourceRepositoryRef
  -> resolve and validate BranchPolicy
  -> persist domain binding/grant mutation
  -> enqueue asset access-change with Version
```

Fork points:

| Decision | Memory | Skill | KB source |
| --- | --- | --- | --- |
| cardinality | one agent <-> one Memory | agent <-> many Skills | KB grants and retrieval scope |
| capability | read, clone, write | read, clone | usually read through KB product, collaborator grant can be read/write for humans |
| branch | explicit write branch | repo-level distribution pin | source default ref / repository role specific |
| unbind cleanup | stop live write access, preserve offline clone | delete `SkillRepositoryBranch` when the last relevant binding is gone | revoke or reconcile KB-owned collaborator grants only |

Skill branch pin release must be defined by the Skill domain: when the last
binding that uses a repository pin is removed or archived, the
`SkillRepositoryBranch` row should be deleted if no remaining Skill binding for
that workspace/repo depends on it. This keeps later branch changes visible and
avoids a stale pin blocking future assignment.

### 7.3 Access-batch flow

```text
Desktop provider asks /api/v1/<asset>/agents/access:batch
  -> server deduplicates requested agent ids
  -> server verifies ownership of every requested agent
  -> server loads domain bindings and canonical repository refs
  -> server mints sync lease scoped to returned repositories
  -> server returns typed DTOs
```

Server authority is binary for each requested agent:

- bound: the response contains a complete typed access record;
- unbound: Memory returns `null` or Skill returns an empty list.

`Unknown` is a Desktop provider observation state, not a server authority state.
Desktop reports `Unknown` when the resolver fails, the endpoint is unreachable,
the response omits the requested agent, a required repository ref is incomplete,
or a sync lease cannot be parsed. `Unknown` preserves local cache/clone state
only within the same agent connection scope. When an agent's owning channel,
server URL, or workspace changes, Desktop must first withdraw the prior scope's
runtime-visible managed assets; a first-query `Unknown` in the new scope cannot
authorize the prior scope's worktrees or generated index entries.

### 7.4 Desktop refresh flow

```text
asset-access outbox row
  -> worker delivers AssetsAccessChangedEvent
  -> Desktop routes by asset type
  -> provider performs access-batch lookup
  -> generic state handling:
       Bound: apply repository/lease info
       Unbound: apply authoritative empty semantics
       Unknown: preserve existing local state within the same connection scope
  -> domain runtime:
       Memory vault/watcher/push handling
       Skill managed clone/materialize handling
```

Desktop also reconciles Memory, Skill, and KB access at startup, after the
initial retry delay, and periodically. Each asset domain owns an independent
bounded refresh budget so one unavailable provider cannot starve the others.
Realtime access-change events remain the fast path; periodic reconcile is the
event-loss recovery path.

Memory and Skill share credential and clone/fetch primitives, but diverge after
state resolution. KB active worktrees and generated `kb/index.md` entries are
bound to the current agent connection scope; inactive-scope files must not stay
under the runtime-visible agent worktree.

### 7.5 Memory push flow

Memory push is domain-owned and must not be generalized.

```text
local Memory filesystem change
  -> memory watcher debounce
  -> commit local change
  -> push with Memory sync lease
  -> stop-session flush performs best-effort final push
  -> rebind handles replace-local-content or repoint/force-push policy
```

Skill and KB source Desktop flows do not use this push path.

### 7.6 Archive and delete flow

Repository-backed resources need an explicit delete/cleanup flow:

```text
Archive/Delete resource
  -> domain precheck and authorization
  -> stop or mark bindings/grants as revoked as domain requires
  -> enqueue access-change for affected agents
  -> delete managed AGS repositories through AGS API
  -> update product resource deleted/archive state
  -> remove resource_repositories and MeshX-owned access mappings
  -> record delete_cleanup lifecycle failure when AGS or DB cleanup partially fails
  -> Desktop observes authoritative unbound and applies domain cache policy
```

Rules:

- Missing AGS repositories are treated as already deleted when the domain is
  executing a delete cleanup.
- If AGS deletion fails, keep product metadata and canonical mappings so the
  operation can be retried.
- If AGS deletion succeeds but MeshX DB cleanup fails, record a lifecycle event
  for compensation and operator repair.
- Desktop `Unknown` during delete cleanup is not a revoke. Only authoritative
  unbound responses may clear authoritative cache state.

## 8. Idempotency and concurrency guardrails

- Repository lifecycle must be retryable. If AGS repository creation succeeded
  but MeshX resource or mapping persistence failed, retry or reconciliation must
  be able to converge from the `EnsuredRepository` identity and lifecycle event.
- `resource_repositories` canonical mapping creation must be idempotent for the
  same `(workspace_id, resource_type, resource_id, repo_role)` tuple and must
  surface conflicts for different resources using the same repository full name.
- Asset access-change outbox rows are notifications, not the truth source.
  Desktop must re-query access-batch and use the server response as authority.
- Outbox `Version` is the binding/access generation. It is monotonic per
  `(workspace_id, agent_id, asset_type)` and guards against old events causing a
  stale refresh to overwrite newer cache state.
- Sync lease refresh must fail closed. A refresh failure yields Desktop
  `Unknown` and preserves cache; it must not be interpreted as unbound.
- Permission downgrade must converge by shortening future sync leases and
  revoking long-lived grants where applicable. Old short-lived token plaintext
  must not be renewed or widened. Human self-demotion across KB / Memory / Skill
  requires explicit confirmation before the product accepts the narrowing change.
- Branch policy must be passed explicitly at bind time. Common helpers must not
  silently default a branch in a way that changes Memory write-back semantics or
  hides Skill branch-pin conflicts.

## 9. Negative abstraction boundaries

The following rules belong only to their domain packages:

| Domain | Do not move into generic modules |
| --- | --- |
| Memory | watcher, flush/push, rebind, replace-local-content, repoint/force-push, existing local `MEMORY.md` import, offline clone retention |
| Skill | nested `SKILL.md` roots, duplicate names, symlink safety, scan limits, lower-case `skill.md` normalization, archive/URL import safety, runtime materialization precedence |
| KB | KB grant roles, source retrieval scope, collaborator grant reconciliation, source access-required UI states, insights repository exposure policy |

Shared modules may accept callbacks or typed policy objects for these behaviors,
but they must not interpret or reimplement them.

## 10. Implementation ownership

Package ownership:

| Package | Ownership |
| --- | --- |
| `internal/service/resource/repos` | lifecycle-plane repository identity, actor, ensure/grant/revoke/delete and sync-token contracts, mapping construction, name resolution, lifecycle failure, and cleanup helpers |
| `internal/agsbridge/wiki` | AGS wiki write, prune, and reconcile publication port expressed with `internal/service/resource/repos` lifecycle values |
| `internal/service/resource/access` | access-plane helpers: agent id dedupe, outbox enqueue, short-lived sync lease request construction, and repository clone-scope vocabulary |
| `internal/service/memory` | Memory product semantics and domain callbacks |
| `internal/service/skills` | Skill product semantics and domain callbacks |
| KB service packages | KB grants, source lifecycle, collaborator reconciliation |
| `desktop/internal/assetprovider` | Desktop access-batch primitives, server URL/token validation, batch request transport, clone URL composition, Desktop observation states |
| `desktop/internal/skillprovider`, `desktop/internal/kbprovider`, and Memory provider/vault packages | domain application of access states, scope-bound local cache/worktree/watcher/materialization behavior |

## 11. Characterization coverage before extraction

Before moving behavior behind shared helpers, focused tests should pin these
contracts:

- Memory one-to-one binding: one agent cannot bind multiple Memories and one
  Memory cannot be bound to multiple agents.
- Skill many-to-many binding and repository-branch uniqueness: several Skills
  can bind to one agent, but a workspace/repo active branch conflict is visible.
- Access-batch ownership: every requested agent must be owned by the acting
  subject; batch reads fail closed when one requested agent is not owned.
- Desktop access states: service responses distinguish bound and unbound, while
  resolver/HTTP/decode/lease failures produce Desktop `Unknown` and preserve
  cache or clone state.
- Stale outbox events: an old access-change notification cannot roll Desktop
  cache back after a newer binding has been applied because refresh uses
  authoritative access-batch and monotonic version semantics.
- Sync lease behavior: Memory lease scopes include read/clone/write; Skill lease
  scopes include read/clone only; refresh failure does not clear local state.
- Repository lifecycle failures: create-bind and delete-cleanup partial failures
  record enough repository identity for retry or operator repair.
- Archive/delete behavior: resource bindings/grants, canonical mappings,
  lifecycle events, and Desktop cache policy converge without treating unknown
  observation states as authoritative revokes.

## 12. Shared helper criteria

Shared helpers are allowed only when the owning plane is explicit and the helper
keeps product semantics at the domain boundary:

- Lifecycle-plane value objects and mapping helpers belong in
  `internal/service/resource/repos`; domain initialization, grants, and
  product-specific metadata remain in the Memory, Skill, and KB services.
- Access-plane helpers may deduplicate agent ids, build sync lease requests, and
  enqueue access-change outbox rows; public API DTOs remain typed at the domain
  boundary.
- Desktop access-batch, clone/fetch, and lease primitives may be shared only when
  they do not own Memory watcher behavior, Memory push/rebind behavior, Skill
  materialization, or KB retrieval semantics.
- Web/API shell helpers may be shared only for repeated transport or layout
  plumbing. Memory and Skill domain state machines stay in their owning
  components when the repeated surface is layout-deep.

Code that cannot name the lifecycle, access, content, or shell plane that owns a
behavior must keep that behavior in the domain package until ownership is clear.

## 13. Web/API shell boundary

Web/API shell code may share transport and layout helpers, but it must not own
Memory, Skill, or KB domain state machines.

Stable shared seams:

- API agent-asset binding responses share the `ownedAgents`, `bindings`, and
  resource-list envelope. A typed normalizer helper may normalize that envelope
  and default missing branch names to `main`, but domain modules must still pass
  typed binding and asset normalizers. Memory keeps its strict invalid-payload
  rejection for missing arrays; Skill keeps its existing empty-array fallback for
  omitted lists.
- Create/fork dialogs share repository-backed draft fields: resource name,
  owner/scope preview, description, template locale, and optional
  repository-name override. A shared field shell may own that preview and input
  plumbing while callers supply domain labels, repository suffixes, and
  create-only vs copy/fork input shaping.

Generic settings and bind/unbind orchestration are domain-owned, not shared shell
behavior:

- Memory settings include permissions, pinning, delete-vs-archive, inline
  Memory unbind, and Desktop access-change notifications.
- Skill settings include detected `SKILL.md` content, import/rescan actions,
  inline many-to-many revoke, and archive-only semantics.
- Memory assignment is one-to-one, has select/create/fork modes, and exposes
  write-back flags such as existing-local-content import and replace-on-switch.
- Skill assignment is many-to-many, edits a checked list of Skill libraries, and
  preserves per-skill repository branch pins.

UI sharing requires behavior-facing coverage for the repeated transport or
layout glue and must not hide domain differences. Generic UI code must not own
Memory/Skill copy, branch policy, permission copy, or runtime side effects.
