# S03 Research: Git Commit SHA in Pipeline Version Metadata

## Summary

Straightforward slice. The version metadata infrastructure already exists — `TechniquePageVersion.pipeline_metadata` is a JSONB column, `_capture_pipeline_metadata()` in `stages.py` builds the dict, and the frontend already renders a metadata panel for historical versions. The work is: (1) capture the git commit SHA at Docker build time, (2) inject it into `_capture_pipeline_metadata()`, and (3) add a commit hash display row in the frontend metadata panel.

The only non-trivial aspect: no `.git` directory exists inside the Docker container, so `git rev-parse HEAD` can't run at runtime inside the worker. The SHA must be baked in at build time.

## Recommendation

Use a Docker build arg (`GIT_COMMIT_SHA`) written to a file (`/app/.git-commit`) during image build. The `_capture_pipeline_metadata()` function reads this file. The `docker-compose.yml` passes the arg using shell command substitution. Fallback to `"unknown"` when the file is missing (local dev without Docker).

Three tasks:
1. **Dockerfile + compose** — add build arg, write SHA to file
2. **Backend** — read SHA file, add to `_capture_pipeline_metadata()` return dict
3. **Frontend** — add commit hash display in the version metadata panel

## Implementation Landscape

### Backend — `_capture_pipeline_metadata()` (pipeline/stages.py, lines 557–601)

Currently returns:
```python
{
    "models": { "stage2": ..., "stage3": ..., "stage4": ..., "stage5": ..., "embedding": ... },
    "modalities": { "stage2": ..., "stage3": ..., "stage4": ..., "stage5": ... },
    "prompt_hashes": { "stage2_segmentation.txt": ..., ... },
}
```

Add `"git_commit_sha": "<sha>"` to this dict. Read from `/app/.git-commit` (Docker) or attempt `git rev-parse --short HEAD` as fallback (local dev). Use a helper function with error suppression — if neither works, return `"unknown"`.

**No DB migration needed.** `pipeline_metadata` is JSONB — adding a new key is purely a code change.

### Docker — `docker/Dockerfile.api`

Add:
```dockerfile
ARG GIT_COMMIT_SHA=unknown
RUN echo "${GIT_COMMIT_SHA}" > /app/.git-commit
```

Place after the `COPY backend/ /app/` line so it doesn't bust the cache for code changes.

### Docker Compose — `docker-compose.yml`

Add build arg to both `chrysopedia-api` and `chrysopedia-worker` services (they share the same Dockerfile):
```yaml
build:
  context: .
  dockerfile: docker/Dockerfile.api
  args:
    GIT_COMMIT_SHA: ${GIT_COMMIT_SHA:-unknown}
```

At build time, invoke with:
```bash
GIT_COMMIT_SHA=$(git rev-parse --short HEAD) docker compose build
```

Or export `GIT_COMMIT_SHA` before building. The `:-unknown` default means builds without the env var still succeed.

### Config — `backend/config.py`

Add `git_commit_sha: str = "unknown"` to Settings. This allows overriding via env var as an alternative to the file-based approach (useful for local dev). The `_capture_pipeline_metadata()` helper should check the file first, then fall back to `settings.git_commit_sha`, then `"unknown"`.

### Frontend — `frontend/src/pages/TechniquePage.tsx` (lines 316–360)

The version metadata panel currently checks for `"model"`, `"captured_at"`, and `"prompt_hashes"` keys. Add a check for `"git_commit_sha"`:

```tsx
{"git_commit_sha" in versionDetail.pipeline_metadata && (
  <div className="version-metadata__item">
    <span className="version-metadata__key">Commit</span>
    <code className="version-metadata__value" style={{ fontFamily: '"SF Mono", "Fira Code", monospace' }}>
      {String(versionDetail.pipeline_metadata.git_commit_sha).slice(0, 7)}
    </code>
  </div>
)}
```

Uses the existing `version-metadata__item` CSS classes — no new styles needed. The monospace font from `version-metadata__hash-value` class can be reused or applied inline.

### Frontend note — existing metadata key mismatch

The backend sends `"models"` (plural, a dict of per-stage models) but the frontend checks for `"model"` (singular). This means the "Model" row in the metadata panel never actually renders with current data. This is a pre-existing bug — not in scope for this slice, but the planner should be aware.

### Verification approach

1. **Unit**: Run `_capture_pipeline_metadata()` locally → confirm `git_commit_sha` key present
2. **Docker build**: `docker compose build` with `GIT_COMMIT_SHA` arg → `docker exec chrysopedia-api cat /app/.git-commit` shows the SHA
3. **Frontend**: Navigate to a technique page with versions → switch to a historical version → confirm commit hash appears in metadata panel
4. **TypeScript**: `npm run build` in frontend → zero errors

### File inventory

| File | Change |
|------|--------|
| `docker/Dockerfile.api` | Add `ARG GIT_COMMIT_SHA` + `RUN echo` |
| `docker-compose.yml` | Add `args:` block to api and worker build sections |
| `backend/config.py` | Add `git_commit_sha` setting |
| `backend/pipeline/stages.py` | Add SHA reading helper, extend `_capture_pipeline_metadata()` |
| `frontend/src/pages/TechniquePage.tsx` | Add commit hash display in metadata panel |

### Risk

Low. No schema changes. No new dependencies. The JSONB column accepts any dict shape. The frontend change is additive (conditional render of a new key). Docker build arg has a safe default.
