# Service Descriptor Schema

Reference for `flydocs/context/service.json` — the dual-purpose descriptor that
provides cross-repo context AND intra-repo orientation.

## Purpose

The service descriptor serves two roles from a single file:

- **Cross-repo export** — `apis`, `dependencies`, `purpose`, `stack` tell
  sibling repos what this service does and how it connects. Stored by relay,
  queried by workspace composite.
- **Intra-repo orientation** — `structure` section tells THIS repo's agent
  where things are: entry points, shared types, build system, package boundaries.
  Not exported cross-repo.

Generated by the user's coding agent during `flydocs init`, or by the
server-side AI scanning pipeline (v2).

## Schema

```typescript
interface ServiceDescriptor {
  version: 1 | 2;
  name: string; // Human-readable service name
  repoSlug: string; // owner/repo format (matches workspace.repoSlug)
  purpose: string; // One-sentence description of what this service does
  stack: string[]; // Key technologies: ["next", "convex", "typescript"]

  // Cross-repo export surface (what siblings see)
  apis: ApiSurface[];
  dependencies: ServiceDependency[];

  // Intra-repo orientation (what THIS repo's agent uses)
  structure: ServiceStructure;

  // v2 fields (present when version is 2, optional for backward compat)
  generatedBy?: "server" | "agent"; // Who generated this descriptor
  generatedAt?: string; // ISO 8601 timestamp of generation
  provenance?: Provenance; // FLY-1592: where an agent-generated one came from
}

interface Provenance {
  generator: "agent"; // What produced the descriptor
  branch?: string; // git rev-parse --abbrev-ref HEAD, when resolvable
  commit?: string; // git rev-parse HEAD, when resolvable
  at: string; // ISO 8601 timestamp of the push
}

interface ApiSurface {
  type: "rest" | "graphql" | "grpc" | "event" | "package";
  path: string; // Route prefix, event topic, or package path
  description: string; // What this API surface does
  methods?: string[]; // HTTP methods for REST (optional)
}

interface ServiceDependency {
  service: string; // Repo slug or service name of the dependency
  interface: string; // What interface is consumed (e.g., "REST /api/relay/*")
  description: string; // Why this dependency exists
}

interface ServiceStructure {
  entryPoints: string[]; // Where request handling or app logic starts
  sharedTypes: string[]; // Where shared type definitions live
  buildSystem: string; // "turbo", "nx", "next", "tsup", "vite", "cargo", etc.
  packages?: PackageInfo[]; // Monorepo only: name, path, purpose per package
}

interface PackageInfo {
  name: string; // Package name (e.g., "@flydocs/cli")
  path: string; // Relative path from repo root
  purpose: string; // What this package does
}
```

## Field Notes

- `version` is `1` (agent-generated, legacy) or `2` (supports server generation).
  All consumers accept both versions. The v2 fields are additive — v1 descriptors
  remain valid and fully functional.
- `repoSlug` must match the slug registered in the workspace dashboard.
- `structure` is local-only — not pushed to relay or included in workspace
  composite. One exception: `context.py push` (FLY-1592) keeps it, because the
  descriptor the server stores is what `flydocs update` writes back to
  `flydocs/context/service.json`, and stripping it there would delete this
  repo's own orientation section on the next update. A descriptor pushed that
  way therefore carries `structure` into
  `GET /api/relay/workspace/services`, where sibling repos can see it — keep
  it to paths and boundaries, not to anything you would not share.
- `apis` and `dependencies` create PROVIDES/CONSUMES edges in the graph.
- `stack` is a flat array of lowercase identifiers (framework names, languages).
- `generatedBy` distinguishes server-generated (AI scanning pipeline) from
  agent-generated (local `flydocs init`) descriptors. Absent on v1 descriptors.
- `generatedAt` tracks freshness for server-generated descriptors. ISO 8601 format.
- `provenance` is stamped by `context.py push` (`flydocs run context.push`), not
  written by hand: an agent-generated descriptor is only as trustworthy as the
  tree it was read from, so the branch and commit travel with it. `branch` and
  `commit` are omitted when the tree is not a git checkout or has no commits
  yet — a missing one is unknown provenance, never a failed push.

## Examples

### Example 1: Single-App CLI Tool (Type 1)

```json
{
  "version": 1,
  "name": "FlyDocs CLI",
  "repoSlug": "plastrlab/flydocs-core",
  "purpose": "CLI tool that installs and manages FlyDocs skill templates, hooks, and configuration in user projects",
  "stack": ["typescript", "node", "commander"],
  "apis": [
    {
      "type": "package",
      "path": "@flydocs/cli",
      "description": "npm package providing the flydocs CLI binary"
    }
  ],
  "dependencies": [
    {
      "service": "plastrlab/flydocs-app",
      "interface": "REST /api/relay/*",
      "description": "Cloud tier pushes config, descriptors, and issue operations to relay API"
    }
  ],
  "structure": {
    "entryPoints": ["src/cli.ts"],
    "sharedTypes": ["src/lib/types.ts"],
    "buildSystem": "tsup"
  }
}
```

### Example 2: Full-Stack Web App (Type 1)

```json
{
  "version": 1,
  "name": "FlyDocs App",
  "repoSlug": "plastrlab/flydocs-app",
  "purpose": "Web dashboard and relay API for FlyDocs cloud tier — workspace management, issue relay, and service descriptor storage",
  "stack": ["next", "react", "convex", "typescript", "tailwind"],
  "apis": [
    {
      "type": "rest",
      "path": "/api/relay",
      "description": "Relay API for CLI operations — config generation, issue proxy, service descriptors",
      "methods": ["GET", "POST", "PUT", "PATCH"]
    },
    {
      "type": "rest",
      "path": "/api/auth",
      "description": "Authentication endpoints for CLI and dashboard login",
      "methods": ["GET", "POST"]
    }
  ],
  "dependencies": [
    {
      "service": "linear",
      "interface": "GraphQL API",
      "description": "Issue tracker backend — all issue CRUD proxied through relay"
    },
    {
      "service": "convex",
      "interface": "Convex functions",
      "description": "Real-time database for workspaces, repos, user state"
    }
  ],
  "structure": {
    "entryPoints": ["src/app/api/", "convex/"],
    "sharedTypes": ["src/types/", "convex/schema.ts"],
    "buildSystem": "next"
  }
}
```

### Example 3: Monorepo Multi-Service (Type 3)

```json
{
  "version": 1,
  "name": "Acme Platform",
  "repoSlug": "acme/platform",
  "purpose": "Monorepo containing API server, worker service, and shared packages for the Acme SaaS platform",
  "stack": ["typescript", "express", "prisma", "redis", "turborepo"],
  "apis": [
    {
      "type": "rest",
      "path": "/api/v2",
      "description": "Public REST API for client applications",
      "methods": ["GET", "POST", "PUT", "DELETE"]
    },
    {
      "type": "event",
      "path": "jobs.*",
      "description": "Redis pub/sub events consumed by worker service"
    },
    {
      "type": "package",
      "path": "@acme/sdk",
      "description": "Published TypeScript SDK for API consumers"
    }
  ],
  "dependencies": [
    {
      "service": "stripe",
      "interface": "REST API + webhooks",
      "description": "Payment processing and subscription management"
    },
    {
      "service": "acme/marketing-site",
      "interface": "REST /api/v2/pricing",
      "description": "Marketing site fetches pricing data from API"
    }
  ],
  "structure": {
    "entryPoints": ["apps/api/src/server.ts", "apps/worker/src/index.ts"],
    "sharedTypes": ["packages/shared/src/types/"],
    "buildSystem": "turbo",
    "packages": [
      {
        "name": "@acme/api",
        "path": "apps/api",
        "purpose": "Express API server — handles all client requests"
      },
      {
        "name": "@acme/worker",
        "path": "apps/worker",
        "purpose": "Background job processor — email, billing, data sync"
      },
      {
        "name": "@acme/shared",
        "path": "packages/shared",
        "purpose": "Shared types, utilities, and validation schemas"
      },
      {
        "name": "@acme/sdk",
        "path": "packages/sdk",
        "purpose": "Published TypeScript SDK for external API consumers"
      }
    ]
  }
}
```

## Graph Integration

When `graph_build.py` processes `service.json`:

1. Creates a `repo:{repoSlug}` node with `purpose` and `stack` as properties
2. For each entry in `apis`: creates a PROVIDES edge from this repo to any
   repo that lists it in their `dependencies`
3. For each entry in `dependencies`: creates a CONSUMES edge from this repo
   to the dependency's repo node
4. Edge properties include `interface` and `description` from the source data

Cross-repo edges are only created when both repos have service descriptors in
the graph (either from local sibling reads or relay workspace composite).

## Relay Integration

- **Push:** `push_service.py` sends this repo's descriptor via
  `PUT /api/relay/workspace/service` (excludes `structure` section)
- **Pull:** `pull_services.py` fetches workspace composite via
  `GET /api/relay/workspace/services` (returns all repos with descriptors)
- **Local fallback:** Read sibling `flydocs/context/service.json` files directly

## Topology Context

The service descriptor works alongside topology detection (stored in config).
Topology tells the agent HOW repos are laid out; the descriptor tells it WHAT
each repo does and how they connect.

| Topology Type | Layout                  | Detection Signal                             |
| ------------- | ----------------------- | -------------------------------------------- |
| 1             | Single repo, single app | One `.git`, no workspace config              |
| 2             | Monorepo, single app    | One `.git`, one root app                     |
| 3             | Monorepo, multi-service | One `.git`, workspace config (pnpm/nx/turbo) |
| 4             | Sibling repos           | Parent dir has multiple `.git` children      |
