> For AI agents: the complete documentation index is available at https://docs.phax.run/llms.txt, the full documentation bundle is available at https://docs.phax.run/llms-full.txt.

# phax 0.32.0 \{#phax-0320}

Changes since 0.31.0: [v0.31.0...v0.32.0](https://github.com/lbdremy/phax/compare/v0.31.0...v0.32.0).

## Highlights \{#highlights}

- **Structured output** — The provider may shape the answer; only phax's decoder accepts it.

## Upgrading \{#upgrading}

- **Records and schema readers: `hand-decision-answer` is two formats.** `spec-hand-decision-answer` and `plan-hand-decision-answer` are each the old variant with a `$schema` const. `hand-decision-answer` stays readable at 0.25.0, and phax no longer reads or asks for it.
- **Schema readers: `plan-document` no longer pairs `sourceSpec` and `completesSpec` in its schema.** phax checks the pair.
- **Schema readers: every answer's `$schema` is a const of its 0.32.0 stamp.** That covers spec and plan sidecars, the two hand-decision formats, `first-reading`, `interactive-handoff`, `code-review` and `phax-plan-extraction`. An older `@lbdremy/phax-schemas` cannot read them, and the package reads a const format only at its own stamps.
- **Schema readers: `phax-plan` is restamped 0.32.0**, its decoding unchanged. An older `@lbdremy/phax-schemas` cannot read a 0.32.0 run plan.
- **Schema readers: the `code-review` answer and file are one shape**, `$schema` in place of `version`.
- **Schema readers: new `phax-plan-extraction` format**, the answer of the plan extraction.
- **Headless authoring answers** carry `$schema` in place of `version: 1`.
- **Records and schema readers: `compliance-review.json` is stamped 0.32.0.** `$schema` is a const, and two ledgers are required: `unplannedChanges` (`phaseId`, `path`, `note`) and `unmetPromises` (`phaseId`, `promise`, `note`), each possibly empty. A 0.32.0 file without either is refused.
- **Records and schema readers: older compliance reviews read without ledgers.** A 0.17.0 or pre-schema review still reads, never with empty ledgers; `@lbdremy/phax-schemas` gives them `Unknown` ledgers.
- **Records readers: `compliance-review.md` is rendered by phax from the answer**, with fixed sections (summary, one per phase, unplanned changes, unmet promises, attention points, pointers).
- **`phax review-compliance` no longer prints `Verdict: unknown`.** A review refused twice fails with exit 1 and writes no file and no records entry; the corrective resume's transcript is `compliance-review-retry.session.jsonl`.
- **The compliance session is read-only** and writes nothing under `.phax-context/`.
- **Run `phax artifact migrate-documents` once.** It restamps every live spec and plan sidecar below its current shape, their bodies unchanged. Until then `approve`, `reopen` and `decide` refuse, naming the command.

See [Structured answers and providers](https://github.com/lbdremy/phax/blob/v0.32.0/docs/structured-answers.md) for what each provider now checks.

## What's new \{#whats-new}

### Structured output \{#structured-output}

> The provider may shape the answer; only phax's decoder accepts it.

- A compliance review whose answer is wrong gets one more chance in the same session, and when it still fails phax says why instead of showing an unknown verdict. `phax review-compliance`

  Now:

  ```text
  $ phax review-compliance audio-export
  ⚠ compliance-review answer refused — perPhase: phase "phase-04" is not a phase of this run
    resuming the session once to ask for the whole answer again
  Verdict: conformant-with-deviations
  Compliance review: ~/.phax/runs/notebook.audio-export/compliance-review.md
  Recorded: notebook.audio-export/compliance-review (3fa91c0d)

  $ phax review-compliance audio-export
  ✗ Compliance review failed: compliance-review answer rejected twice — first answer: perPhase: phase "phase-02" is missing; retry: perPhase: phase "phase-02" is missing. To retry, run: phax review-compliance audio-export
  $? = 1
  ```

  :::details Before

  ```text
  $ phax review-compliance audio-export
  Verdict: unknown
  Compliance review: ~/.phax/runs/notebook.audio-export/compliance-review.md
  ```

  :::

- The compliance review now lists the files changed beyond the plan and the promised tests that are missing, alongside its verdict. `compliance-review.json`

  Now:

  ```text
  {
    "$schema": "https://docs.phax.run/schemas/compliance-review/0.32.0.json",
    "verdict": "conformant-with-deviations",
    "summary": "All four phases delivered; phase-02 skipped one promised test.",
    "perPhase": [ … ],
    "unplannedChanges": [
      { "phaseId": "phase-02", "path": "src/export/backoff.ts", "note": "Shared helper, justified in the handoff." }
    ],
    "unmetPromises": [
      { "phaseId": "phase-02", "promise": "integration test: export retries on 503", "note": "Not found in tests/integration." }
    ],
    "attentionPoints": [ "Confirm the retry backoff with the audio team." ],
    "pointers": []
  }
  ```

  :::details Before

  ```text
  {
    "$schema": "https://docs.phax.run/schemas/compliance-review/0.17.0.json",
    "verdict": "conformant-with-deviations",
    "summary": "All four phases delivered; phase-02 skipped one promised test.",
    "perPhase": [ … ],
    "attentionPoints": [ "Confirm the retry backoff with the audio team." ],
    "pointers": []
  }
  ```

  :::

- The compliance review's Markdown is written by phax from the review itself, so it always matches the verdict and keeps the same sections from run to run. `compliance-review.md`

  Now:

  ```text
  # Compliance review — notebook.audio-export

  Verdict: conformant-with-deviations

  All four phases delivered; phase-02 skipped one promised test.

  ## phase-02 — conformant-with-deviations

  - tests · deviation — The promised integration test for export retries is missing.

  ## Unplanned changes

  - phase-02 · src/export/backoff.ts — Shared helper, justified in the handoff.

  ## Unmet promises

  - phase-02 · integration test: export retries on 503 — Not found in tests/integration.

  ## Attention points

  - Confirm the retry backoff with the audio team.

  ## Pointers

  None.
  ```

  :::details Before

  ```text
  (free prose written by the review agent, in its own layout)
  ```

  :::

- A headless code review whose answer is refused is asked once more in the same session instead of failing straight away. `phax review-code --headless`

  Now:

  ```text
  $ phax review-code greet-cli --headless
  ⚠ code-review answer refused — findings[0].lens: "security" is not a lens (expected one of surface, acceptance, boundary, core, edge, tests, architecture, docs)
    resuming the session once to ask for the whole answer again
  Code review: 2 findings (1 bug, 1 concern)
  Findings: ~/.phax/runs/notebook.greet-cli/code-review.json
  Fix them in the session: phax review-code greet-cli
  ```

  :::details Before

  ```text
  $ phax review-code greet-cli --headless
  Code review failed: answer refused: findings[0].lens: "security" is not a lens (expected one of surface, acceptance, boundary, core, edge, tests, architecture, docs). To retry, run: phax review-code greet-cli --headless
  $? = 1
  ```

  :::

- Headless authoring, decide and review sessions on Claude Code and Codex no longer stop on a wrongly shaped answer: the provider fixes the shape while it writes. `phax artifact new spec --headless (on Claude Code or Codex)`

  Now:

  ```text
  $ phax artifact new spec audio-export --headless --brief brief.md
  Wrote docs/specs/2610271600-audio-export.md
  ```

  :::details Before

  ```text
  $ phax artifact new spec audio-export --headless --brief brief.md
  ⚠ spec document refused — requirements[2].pattern: "conditional" is not one of ubiquitous, event, state, unwanted, optional
    resuming the session once to ask for the whole document again
  Wrote docs/specs/2610271600-audio-export.md
  ```

  :::

- The published plan schema now follows the strict rules Claude Code and Codex enforce: `sourceSpec` and `completesSpec` stay required and nullable, and phax checks that they go together instead of the schema. `phax artifact schema plan`

  Now:

  ```text
  $ phax artifact schema plan
  {
    "$defs": { "NonEmptyString": { "type": "string", "pattern": "^[\\s\\S]+$" } },
    "required": [ "$schema", "kind", "sourceSpec", "completesSpec", "run", "preamble", "phases" ],
    "properties": {
      "$schema": { "const": "https://docs.phax.run/schemas/plan-document/0.32.0.json" },
      "sourceSpec": { "anyOf": [ { "$ref": "#/$defs/NonEmptyString" }, { "type": "null" } ] },
      "completesSpec": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ] },
      …
    }
  }
  ```

  :::details Before

  ```text
  $ phax artifact schema plan
  {
    "$defs": { "NonEmptyString": { "type": "string", "minLength": 1 } },
    "required": [ "$schema", "kind", "sourceSpec", "completesSpec", "run", "preamble", "phases" ],
    "properties": {
      "$schema": { "type": "string", "pattern": "^https:\\/\\/docs\\.phax\\.run\\/schemas\\/plan-document\\/…\\.json$" },
      "sourceSpec": { "anyOf": [ { "$ref": "#/$defs/NonEmptyString" }, { "type": "null" } ] },
      "completesSpec": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ] },
      …
    },
    "allOf": [ { "oneOf": [
      { "properties": { "sourceSpec": { "type": "string", "minLength": 1 }, "completesSpec": { "type": "boolean" } } },
      { "properties": { "sourceSpec": { "type": "null" }, "completesSpec": { "type": "null" } } }
    ] } ]
  }
  ```

  :::
