# Output contract

The manifest schema and the JSON output are a versioned, stable contract for your tooling.

The run-manifest schema and the `--output json` shapes are a stable, versioned public
contract. Build tooling on them. Changes get a new schema version, existing versions keep
verifying.

## Object layout (per run)

```
{host}/{org}/{repo}/{YYYY-MM-DD}/{repo}.bundle      git data, git bundle --all HEAD
{host}/{org}/{repo}/{YYYY-MM-DD}/{repo}.meta.json   metadata dump (gitdr.meta/v1)
{host}/{org}/{repo}/{YYYY-MM-DD}/{repo}.sha256      sha256sum line for the bundle
{host}/{org}/{repo}/{YYYY-MM-DD}/{repo}.lfs.tar     LFS objects, when present
{host}/{org}/manifests/{YYYYMMDDThhmmssZ}.manifest.json       signed run-manifest
{host}/{org}/manifests/{YYYYMMDDThhmmssZ}.manifest.json.sig   detached signature
```

Every object is written create-only, under object-lock retention when the bucket has it.

## Run-manifest (`gitdr.manifest/v2`)

```json
{
  "schema": "gitdr.manifest/v2",
  "runId": "20260613T120000Z-a1b2c3d4e5f6",
  "tool": { "name": "gitdr", "version": "v0.1.0 (abc123def456)" },
  "source": { "type": "github", "host": "github.com" },
  "destination": {
    "type": "s3", "bucket": "my-worm-bucket",
    "wormMode": "COMPLIANCE", "wormImmutable": true,
    "wormDetails": "Object Lock enabled; default retention COMPLIANCE"
  },
  "startedAt": "2026-06-13T12:00:00Z",
  "finishedAt": "2026-06-13T12:03:00Z",
  "status": "success",
  "repos": [
    {
      "slug": "octo/hello",
      "status": "success",
      "artifacts": [
        { "kind": "bundle", "key": "github.com/octo/hello/2026-06-13/hello.bundle",
          "size": 12345, "sha256": "…", "retainUntil": "2026-07-13T12:00:00Z" }
      ]
    }
  ]
}
```

The parts that matter:

- `destination.wormImmutable` records the immutability **observed at write time**. The
  manifest is signed, so this is a tamper-evident answer to "was this backup on WORM
  storage".
- `status` (run and per-repo): `success`, `failed`, or `skipped` (resume found the repo
  already backed up for the date). `repos[].error` appears only on `failed`.
- `artifacts[].kind`: `bundle`, `meta`, `sha256`, or `lfs`.
- Timestamps are RFC 3339 UTC. The signature is ed25519 over the exact stored bytes,
  base64 in the `.sig` sidecar.

## `--output json` per command

| Command | stdout shape |
|---|---|
| `backup` | the run-manifest above |
| `restore` | `{ "bundleKey", "sha256", "outDir", "verified" }` |
| `verify` | `{ "manifestKey", "signatureValid", "artifactsChecked", "artifactsOk", "failures": [...] }` |
| `doctor` | `{ "ok", "checks": [ { "name", "ok", "detail" } ] }` |

Logs stay on stderr, so `gitdr backup --output json | jq .status` just works.

## Metadata dump (`gitdr.meta/v1`)

`{repo}.meta.json` holds raw upstream objects per section. GitHub: `repo`, `labels`,
`milestones`, `issues`, `comments`, `pullRequests`, `reviewComments`, `releases`. GitLab:
`project`, `labels`, `milestones`, `issues`, `mergeRequests`, `releases`, `notes`.

It's an audit record, not a restorable snapshot. No tool can replay issues or PRs into a
host with the original numbers, authors, and timestamps.