← Files NaClARCHIVED FILE

skills/nacl-publish/resources/workflows/nacl-tl-release/SKILL.md

11.9 KB · Oct 2, 2026 · 00:29 UTC

↓ Download file

---
name: nacl-tl-release
description: |
  Coordinate NaCl release readiness, verification evidence, production deploy
  checks, changelog, and release reporting. Use when preparing or executing a
  release, promoting staging to production, or when the user says
  `/nacl-tl-release`.
---

# NaCl TL Release For Codex

Read `../nacl-tl-core/SKILL.md` and `../nacl-tl-core/references/tl-codex-contract.md` before executing this workflow.

Release is a gated workflow. It should aggregate evidence before any production
state changes.

## Workflow

1. Resolve release scope, target environment, branch, version, and upstream task
   evidence.
2. Check verification, QA, sync, deploy, and regression evidence.
3. Present the release plan, risks, and production-impacting commands.
4. Stop for confirmation before tagging, pushing, deploying, or updating
   external trackers.
5. Execute approved release actions through available tools.
6. Run post-release health checks.
7. Update changelog and release report when file editing is available and
   confirmed.

## Source-Parity Requirements

- Release readiness requires verified task evidence, clean or understood git
  state, passing required local checks, CI evidence, deploy evidence, health
  evidence, docs/changelog evidence, and stub gate evidence.
- Do not promote `BLOCKED`, `FAILED`, or `UNVERIFIED` work. The post-W4
  override paths are (a) a signed exception under
  `.tl/exceptions/<exception_id>.yaml` enumerating specific
  `affected_gates`, OR (b) emergency mode (three env vars on the
  invoking shell — see "Release Blocking Gates (Strict-Only)"
  below). There are no inline operator-prompt overrides at
  Strict-Only gates.
- Tag creation, pushing, release publication, deployment, tracker moves, and
  graph updates require confirmation and read-back.
- Tie every release tag or deployed commit back to verified task and delivery
  evidence.
- Missing CI, deploy, health, docs, changelog, or release artifact evidence
  downgrades the release report.

## Release Blocking Gates (Strict-Only)

**Introduced in:** W4-blocking-release.

The release skill refuses VERIFIED → release-tag / promote when ANY
of the seven conditions below holds. These gates are **strict-only**
— there is no fallback branch, no flag-driven bypass, and no inline
operator-prompt override. The Project-Alpha stale-graph episode and the
project-beta health-only episode are the canonical episodes these
gates exist to prevent.

### The Seven Block Conditions

| # | Condition | Closed `Status:` | Workflow detail |
|---|---|---|---|
| 1 | Upstream `nacl-tl-sync` verdict is `UNVERIFIED` (per W2) — wire-evidence missing for any UC with `actor != SYSTEM` | `BLOCKED` | `upstream-sync-unverified` |
| 2 | `nacl-tl-qa` aggregate is `UNVERIFIED` (per W3) — a mandatory stage (typically `LIVE_PROVIDER_SMOKE` or `PROD_GOLDEN_PATH`) is `NOT_RUN`, OR aggregate weakest-stage rule yielded `UNVERIFIED` | `BLOCKED` | `upstream-qa-unverified` |
| 3 | **Graph staleness detected** — snapshot vs live mismatch on the project's Neo4j instance. **Baseline MUST come from a LIVE capture; NEVER from a stale `.cypher` export.** A pre-release live capture (node count, label histogram, rel-type histogram) is compared to the current live state via direct Cypher query. Any delta = STALE. | `BLOCKED` | `graph-stale` |
| 4 | `/nacl-sa-validate full` reports `Status: FAILED` with at least one finding at `severity: CRITICAL` | `BLOCKED` | `sa-validate-critical` |
| 5 | **Missing PROD_GOLDEN_PATH evidence.** A bare HTTP 200 from `/health` is `HEALTH_ONLY` evidence and is **never product-readiness evidence**. The release requires a `PROD_GOLDEN_PATH` evidence string in the QA aggregate (per W3 six-stage decomposition) for every UC where the matrix marks `PROD_GOLDEN_PATH` mandatory. | `BLOCKED` | `missing-prod-golden-path` |
| 6 | **PR / CI skipped without `project_kind: prototype` AND a signed exception.** Direct-strategy releases (no PR, no CI) are permitted only when `config.yaml` declares `project_kind: prototype` AND `.tl/exceptions/` contains a valid exception with `affected_gates` including the literal `skipped-pr` and / or `skipped-ci`. | `BLOCKED` | `skipped-pr-without-prototype-exception` or `skipped-ci-without-prototype-exception` |
| 7 | **Stale downstream of an unreviewed change.** `/nacl-sa-validate full` reports an `L8` finding — ≥1 node carries `review_status='stale'` (a UC/entity/endpoint changed upstream and its dependents, typically Tasks, were never re-synced). Distinct from #4 (any CRITICAL) and #3 (snapshot vs live count): #7 names "a recorded change with un-propagated dependents". Clear by running `/nacl-tl-plan` or re-reviewing the flagged nodes. | `BLOCKED` | `stale-downstream` |

### HEALTH_ONLY vs PROD_GOLDEN_PATH

`HEALTH_ONLY` evidence (a 200 OK from `{production_url}{health_endpoint}`)
confirms only that the deploy reached a running process and the
process can serve one HTTP request. It does NOT confirm that any
product flow executed end-to-end against production. **HEALTH_ONLY
is NEVER product-readiness evidence on its own.** The project-beta
episode (health green; upload golden path 404 on first real call)
is the canonical proof.

`PROD_GOLDEN_PATH` evidence (per W3 six-stage decomposition) is a
recorded end-to-end run of the UC's primary happy path against
production: real auth, real database write, real provider call,
real artifact returned. It lives in the QA aggregate as the
`qa-stage:prod-golden-path:VERIFIED` evidence string. The release
gate (condition #5) fires when this evidence is missing or
`NOT_RUN` on a UC where the W3 matrix marks `PROD_GOLDEN_PATH`
mandatory, EVEN IF the `/health` probe returned 200.

### `project_kind: prototype` + Signed Exception (PR/CI carve-out)

**The carve-out is conjunctive.** Direct-strategy release (no PR,
no CI) is permitted only when **both**:

1. `config.yaml` declares `project_kind: prototype`, AND
2. A signed exception exists with `affected_gates` enumerating
   exactly the gate names being skipped (`skipped-pr`,
   `skipped-ci`, or both).

Neither alone is sufficient. `project_kind: prototype` does NOT
carve out: graph-staleness, `/nacl-sa-validate` CRITICAL,
PROD_GOLDEN_PATH, upstream `tl-sync` / `tl-qa` UNVERIFIED. Each
of those requires its own signed exception with its own
`affected_gates` entry.

### Signed Exception Schema (Binding)

`.tl/exceptions/<exception_id>.yaml` is the only override mechanism
for the seven block conditions (other than emergency mode). The
schema is defined in `.tl/exceptions/_template.yaml`. The eight
required fields are:

| Field | Type | Notes |
|---|---|---|
| `exception_id` | string, format `EXC-YYYY-MM-DD-<slug>` | enforced via regex `^EXC-\d{4}-\d{2}-\d{2}-[a-z0-9][a-z0-9-]*$` |
| `owner` | string | GitHub handle or team name |
| `reason` | string | concrete justification; single-word values like `"urgent"`, `"blocked"`, `"needed for demo"` are rejected |
| `created_at` | ISO-8601 timestamp (UTC) | wall-clock at file creation |
| `expiry` | ISO-8601 timestamp (UTC) | wall-clock at which the exception STOPS overriding |
| `affected_gates` | list of strings | MUST enumerate specific gate names; `["*"]`, `["all"]`, or any catch-all token is rejected |
| `affected_projects` | list of strings | project ids the exception applies to |
| `followup_task` | string | task id or in-repo path of the follow-up that closes the underlying issue |

#### The Four Binding Rules

1. **Expired = blocker.** When `expiry` is in the past, the
   exception is treated as ABSENT. No grace period.
2. **No silent extension.** Editing the `expiry` of an existing
   exception is detected as schema tampering (content-hash
   recorded at first read).
3. **Renewal requires a new `exception_id`.** The id format
   embeds the creation date; a renewal is a new file with a new
   id, and the prior id appears in the renewal's `reason`.
4. **No blanket overrides.** `affected_gates` MUST list specific
   gate names; catch-all tokens are rejected.

#### Surfacing

Active signed exceptions consumed by a release run are surfaced in
**three places**: the GitHub release notes (Step 8) under
`## Active exceptions`; `.tl/release-status.json` under an
`"exceptions"` array; `.tl/conductor-state.json` under the
conductor-maintained `exceptions[]` array (W5 owns this).

#### Removed-Flag Rule

The five W4-owned removed flags — SKIP-MERGE, SKIP-VERIFY, SKIP-
DEPLOY, NO-TEST, FORCE — and the cross-wave removed flags (bulk-
QA-skip flag owned by W3; SKIP-DELIVER flag owned by W5; SKIP-PLAN
flag owned by W9) are **NOT re-enabled by signed exceptions**.
The flag surface is gone. Bulk-bypass routes through emergency
mode only. (Literal flag tokens are scrubbed from this skill's
prose to satisfy the W4 grep acceptance check.)

### Emergency Mode (the bulk-bypass path)

When a release must advance past one or more Strict-Only gates in
a situation that signed exceptions cannot anticipate (production
outage, security rollback), the operator invokes **emergency
mode** — NOT a flag, but a triple of environment variables:

```bash
NACL_EMERGENCY=1 \
NACL_EMERGENCY_REASON="<concrete text>" \
NACL_EMERGENCY_OWNER="<github_handle_or_team>" \
  <invocation>
```

All three REQUIRED. Behavior:

- Every Strict-Only gate still evaluates.
- Every gate that would have refused VERIFIED prints a bypass
  banner (one per gate, on stderr).
- The skill advances past refusal and writes a structured event
  to `.tl/emergencies/<UTC-timestamp>-<slug>.yaml`.
- `release-status.json` gets an `"emergency"` key.
- `.tl/changelog.md` gets a blockquote line under the in-flight
  version heading.
- The terminal closed `Status:` is at best `PARTIALLY_VERIFIED`
  with a `(emergency-bypass)` suffix on the headline — NEVER
  `VERIFIED`.

Full schema and rules: `nacl-tl-core/references/emergency-mode.md`.
Event-file template: `.tl/emergencies/_template.yaml`.

Emergency mode does NOT re-enable any removed flag, does NOT
silence the gates, and does NOT extend over multiple invocations.

## Capabilities

### May Do

- Aggregate release readiness evidence.
- Run approved build, test, regression, CI, and deploy checks.
- Create release notes or changelog entries.
- Tag or publish releases when tools and confirmation are available.
- Update graph or task tracker release metadata when confirmed.

### Must Not Do

- Promote code with missing or failing required evidence without explicit user
  direction.
- Mutate production, git tags, graph, or trackers without confirmation.
- Treat staging verification as production verification without a production
  health check.
- Modify root-level source skill folders.
- Select or constrain the runtime model.

### Conditional Tools And Actions

- Git, CI, deploy, and release tooling require availability and confirmation.
- Graph and tracker updates require available tooling.
- Changelog and report writes require writable workspace access.
- Production checks require reachable configured targets.

### Blocked Or Unverified Reporting

- Use `BLOCKED` when release scope, tools, target config, evidence, or
  confirmation are missing.
- Use `FAILED` when build, test, deploy, health, or release actions fail.
- Use `PARTIALLY_VERIFIED` when some release gates pass but others cannot run.
- Use `NOT_RUN` for intentionally skipped gates.
- Use `UNVERIFIED` when release state or production health cannot be confirmed.

## Source Comparison

- Source Claude skill path: `../../nacl-tl-release/SKILL.md`

### Preserved Methodology

- Release readiness aggregation.
- Production-impacting confirmation gates.
- Changelog and release reporting.
- Post-release health checks.

### Removed Claude Mechanics

- Source headline vocabulary outside the closed status set.
- Guaranteed CI, deploy, and tracker tooling.
- Runtime-specific generated metadata assumptions.
- Model routing fields.

### Codex Replacement Behavior

- Treat every production-impacting action as confirmed and conditional.
- Aggregate evidence before release mutation.
- Report partial or unknown release confidence explicitly.
- Use the closed verification vocabulary.

SHA-256: 4de77f54a3b288576c75940d534469d0185e6645252a4b380ee7e9287906fca6