Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 101 additions & 21 deletions .codex/adapters/README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,115 @@
# Deployment Adapters

Stage 8 deployment is pluggable. The selected adapter tells the Platform role
how to build, deploy, smoke-test, and write `pipeline/deploy-log.md` plus
`pipeline/gates/stage-08.json`.

Select an adapter in `.codex/config.yml`:
Stage 8 deployment is pluggable. Each adapter provides a concrete procedure for
how the Platform role builds, deploys, and smoke-tests a release. Projects pick
one adapter in `.codex/config.yml`:

```yaml
deploy:
adapter: docker-compose
adapter: docker-compose # or: kubernetes, terraform, custom
```

Built-in adapters:
## Why Adapters

Prior to adapters, deployment was hardcoded to `docker compose`. That is fine
for local demos and toy projects but useless for any real environment (K8s,
serverless, cloud IaC, enterprise CI/CD). Adapters let a project declare its
actual deployment story without rewriting the platform role prompt.

## Built-in Adapters

| Adapter | File | Use |
| Adapter | File | Suits |
|---|---|---|
| `docker-compose` | `docker-compose.md` | Local demos and single-host services |
| `kubernetes` | `kubernetes.md` | Clusters via `kubectl` or Helm |
| `terraform` | `terraform.md` | IaC-managed infrastructure deploys |
| `custom` | `custom.md` | Project-owned deploy scripts |
| `docker-compose` (default) | `docker-compose.md` | Local dev, demos, single-host deploys |
| `kubernetes` | `kubernetes.md` | K8s clusters via `kubectl` / Helm |
| `terraform` | `terraform.md` | IaC-managed infra on any cloud |
| `custom` | `custom.md` | Project-specific script (escape hatch) |

## Contract

Every adapter must:
Every adapter MUST satisfy the following contract. Adapters that omit any of
these items are incomplete — do not use them for a Stage 8 run.

### Required Config Keys

The adapter document must list a `Config (.codex/config.yml)` section naming
each key it reads. At minimum:
- `deploy.adapter` — the adapter name
- Any adapter-specific sub-key block (e.g. `deploy.docker_compose`)
- `TODO(project)` markers for any key the project must supply

### Required Procedure Steps

The adapter procedure must include these steps in order:

1. **Preconditions** — verify Stage 7 `pm_signoff: true`, verify
`pipeline/runbook.md` exists with `## Rollback` section, verify adapter
prerequisites (CLI tools, config files, credentials).
2. **Execute** — the adapter-specific build and deploy commands, each with
a defined failure mode that writes `status: FAIL` or `status: ESCALATE`.
3. **Verify** — smoke tests confirming the deploy is live and healthy.
4. **Log** — write `pipeline/deploy-log.md` with commands run, results,
smoke tests, and a recovery pointer.

### Required Gate Body Shape

`pipeline/gates/stage-08.json` must include, in addition to the baseline
gate fields (`stage`, `status`, `agent`, `track`, `timestamp`, `blockers`,
`warnings`):

```json
{
"adapter": "<adapter-name>",
"environment": "<target environment or namespace>",
"smoke_test_passed": true | false,
"runbook_referenced": true,
"adapter_result": {
// adapter-specific fields — documented per adapter
}
}
```

### Required Runbook Hooks

Every adapter must document a `Runbook Hooks` section naming which sections of
`pipeline/runbook.md` the adapter depends on. At minimum:
- `## Rollback` — adapter-specific rollback commands
- `## Health signals` — what confirms the deploy is healthy post-recovery

### Required Failure-Mode Notes

Every adapter must document how to handle failure: what leaves a partial state
that must not be retried, what is safe to retry, and where not to auto-rollback.

## Writing a New Adapter

To add a new adapter (e.g. `nomad`, `ecs`, `cloudfoundry`):

1. Create `.codex/adapters/<name>.md` following the structure of the built-in
adapters. Include all sections required by this contract.
2. Document your adapter's name and suitability in the table above.
3. Test by running a pipeline end-to-end against a representative target
environment.

Adapters are Markdown instructions, not code. The Platform role reads the
selected adapter's Markdown and follows it. This keeps the surface easy to
extend and review — adding an adapter is writing down what you would do by
hand, not shipping a new module.

## Selecting an Adapter

The default is `docker-compose`. To change:

1. Read `pipeline/gates/stage-07.json` and confirm deployment is allowed before touching infrastructure.
2. Require `pipeline/runbook.md` before a passing Stage 8 gate.
3. Halt on non-zero deploy or smoke-test commands and write blockers to `pipeline/gates/stage-08.json`.
4. Write `pipeline/deploy-log.md` with commands run, results, smoke tests, and recovery pointers.
5. Write `pipeline/gates/stage-08.json` with `adapter`, `environment`, `smoke_test_passed`, `runbook_referenced`, and `adapter_result`.
6. Avoid automatic rollback. The runbook is the source of truth for recovery.
1. Edit `.codex/config.yml`:
```yaml
deploy:
adapter: kubernetes
```
2. Adjust adapter-specific config in the same file — each adapter's
documentation lists what it reads, including `TODO(project)` markers.
3. Run `npm run pipeline -- "<feature>"` or `npm run hotfix -- "<bug>"` as
normal; the Platform role picks up the new adapter automatically.

Adapters are Markdown instructions, not hidden executors. This keeps deployment
behavior reviewable and project-specific while preserving deterministic gates.
Note: `.codex/config.yml` may carry environment-specific credentials or
paths. Review the adapter's doc for exactly which fields are safe to commit
versus which should live in a gitignored local override file.
144 changes: 128 additions & 16 deletions .codex/adapters/custom.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,109 @@
# Adapter: custom

Runs a project-provided deployment script. Use this when built-in adapters do
not fit and the project already has a reliable deploy entrypoint.
Escape hatch. Runs a project-provided deploy script. Use when the built-in
adapters don't fit and writing a new adapter file isn't worth the investment yet.

The custom adapter is minimalist by design — the project owns the substance;
this file just frames the inputs, outputs, and gate contract.

## Assumptions

- The script path is relative to the project root.
- The script is executable and returns non-zero on failure.
- The script is safe enough to rerun or its limitations are documented.
- `pipeline/runbook.md` explains rollback and script behavior.
- The project has a script that, when given a plan file and the working
directory, deploys the current build. The script is idempotent enough
that re-running is not catastrophic.
- The script either succeeds (exit 0 and the deploy is live) or fails
(non-zero exit with stderr that a human can read).

## Config
## Config (`.codex/config.yml`)

```yaml
deploy:
adapter: custom
custom:
# TODO(project): path relative to the project root. Must be executable.
script: scripts/deploy.sh
# Optional args passed to the script
args:
- --environment
- prod
# How long to wait for the script before declaring hung
timeout_s: 1200
# Optional smoke-test commands the adapter runs AFTER the script
# completes. Each entry is a shell command; a zero exit = pass.
smoke_commands:
# TODO(project): add smoke-test commands
- curl -sf https://api.example.com/health
- ./scripts/check_queue_depth.sh
```

## Prebuild

The custom adapter does not define a prebuild step — the deploy script owns
its own build/push story. Document any prebuild steps in `pipeline/runbook.md`
under `## Prebuild` so that a fresh environment can replicate the run.

If the script builds images or assets, record the artifact identifiers (image
tags, artifact IDs) in `pipeline/deploy-log.md` before overwriting them so a
rollback can reference the prior version.

## Procedure

1. Read `pipeline/gates/stage-07.json`; if deploy approval is missing, write an `ESCALATE` Stage 8 gate and stop.
2. Confirm `pipeline/runbook.md` exists.
3. Confirm the configured script exists and is executable.
4. Run `timeout <timeout_s> <script> <args...>`.
5. Run each smoke command after the script succeeds.
6. Capture script output, smoke results, and recovery pointers in `pipeline/deploy-log.md`.
### 1. Preconditions

- Stage 7 gate check: confirm `pm_signoff: true`. If absent: write
`status: ESCALATE` with reason "PM sign-off missing — cannot deploy" and halt.
- `pipeline/runbook.md` must exist with `## Rollback` and `## Script contract`
sections. If missing: write `status: ESCALATE`.
- `script` path must exist and be executable. If not: `status: FAIL`
with the path as blocker.

### 2. Run the script

```bash
timeout <timeout_s> <script> <args...>
```

Capture stdout and stderr to `pipeline/deploy-log.md`.

Non-zero exit: `status: FAIL`, stderr as blocker, halt.
Timeout: `status: FAIL`, reason "deploy script exceeded <timeout_s>s", halt.

### 3. Smoke tests

For each `smoke_commands` entry:

```bash
<command>
```

Zero exit = pass. Any non-zero: capture the command, exit code, and stderr
as a blocker. `status: FAIL`, halt.

### 4. Write outputs

#### `pipeline/deploy-log.md`

```markdown
# Deploy Log

**Date**: <ISO>
**Method**: custom — <script>
**Runbook**: pipeline/runbook.md §<section>

## Script invocation
<script> <args>

## Script output
<captured stdout/stderr>

## Smoke tests
<per-command pass/fail with exit code>

## Recovery procedure
See runbook §Rollback.
```

See `## Gate Body` below for the gate shape.

## Gate Body

Expand All @@ -41,7 +112,7 @@ deploy:
"stage": "stage-08",
"status": "PASS",
"agent": "platform",
"track": "full",
"track": "<track>",
"timestamp": "<ISO>",
"adapter": "custom",
"environment": "<from config or script output>",
Expand All @@ -57,7 +128,48 @@ deploy:
}
```

## Rollback

Do not auto-rollback. The deploy script's behavior on rollback is
project-specific and not assumed safe to run again automatically.

The `pipeline/runbook.md §Rollback` section must describe:
- The rollback command or script path
- Whether it is safe to run multiple times
- Any preconditions (e.g. the deploy must have completed before rollback is valid)

## Smoke Test Failure Notes

- A failed smoke command does not automatically mean the deploy is broken.
Some post-deploy checks are advisory. If a check is advisory, mark it as
such in `pipeline/runbook.md §Script contract` and convert it to a warning
rather than a blocker.
- For long-running deploys that take time to become healthy, add a retry loop
in the smoke command itself or increase `timeout_s`.

## Failure-Mode Notes

- If the script is not idempotent: do NOT re-run it as a recovery path.
The runbook `§Script contract` must declare whether the script is safe to
re-run and under what conditions.
- If the script times out: check for hung processes via `ps aux`. The timed-out
script may still be running in the background — address that before retrying.
- If the script exits non-zero but the deploy succeeded: this is a script bug.
Fix the script, update the runbook, and promote to a named adapter.

## When to Switch to a Named Adapter

If the custom script grows past ~100 lines or gets re-used across projects,
promote it to a proper adapter file under `.codex/adapters/` following
`README.md §Writing a new adapter`.

## Runbook Hooks

The runbook must document rollback and the script contract. If this script
becomes shared across projects, promote it to a named adapter.
`pipeline/runbook.md` must include:

- **§Rollback** — the project's rollback procedure. The custom adapter does not
attempt a rollback; the runbook is the authoritative source.
- **§Script contract** — what the deploy script does and doesn't do, so a
future on-call engineer can trust its idempotency claims.
- **§Prebuild** — any image build, asset compilation, or push steps that must
happen before the script is invoked.
Loading
Loading