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, and this page is available as Markdown at https://docs.phax.run/releases/0-31-0.md.

phax 0.31.0

Changes since 0.30.0: v0.30.0...v0.31.0.

Highlights

  • Protected paths: files no phase of a run may change — A phase never lands a change to a file that decides whether its own work passes; what it cannot fix stays failing and reaches the operator.

Upgrading

  • Records readers: security.json is version 3. Every phase's security.json carries a required protectedPaths, between skillEditGrants and providerSkippedForSecurity: null when phax.json declares none, otherwise { globs, enforcement } with enforcement one of enforced, partial or commit-check-only. phax reads version 1 and 2 files as version 3.
  • Records readers: gate-attribution.json is stamped 0.31.0. It carries a required protectedPaths: null, or { result, changes } (a pass with no change, or a fail with at least one { path, kind, entry }). 0.26.0 documents and older still read, as null. An older @lbdremy/phax-schemas cannot read 0.31.0 attributions.

What's new

Protected paths: files no phase of a run may change

A phase never lands a change to a file that decides whether its own work passes; what it cannot fix stays failing and reaches the operator.

  • A project lists, in phax.json, the files whose change is the operator's decision, and no phase of a run may land a change to them. phax.json protectedPaths

    Now:

    {
      "version": 1,
      "gateProfiles": {
        "default": [{ "command": "steme audit" }]
      },
      "protectedPaths": ["**/steme.baseline.json"],
      "security": { "profile": "secure" }
    }
    Before
    {
      "version": 1,
      "gateProfiles": {
        "default": [{ "command": "steme audit" }]
      },
      "security": { "profile": "secure" }
    }
  • Running phax validate lists the protected paths and refuses a malformed list, one set outside phax.json, or a glob inside a submodule. phax validate

    Now:

    $ phax validate
    ✓ config is valid (project: steme-lab)
      project: /work/steme-lab/phax.json
      local:   (none)
      global:  (none)
      protected paths: **/steme.baseline.json, phax.json
    
    $ phax validate          # phax.json: "protectedPaths": ["../shared/allow.json"]
    Config validation failed: protectedPaths[0]: "../shared/allow.json" has a .. segment
      at: /work/steme-lab/phax.json
    $? = 2
    
    $ phax validate          # phax.local.json: "protectedPaths": []
    Config validation failed: "protectedPaths" is accepted only in phax.json
      at: /work/steme-lab/phax.local.json
    $? = 2
    
    $ phax validate          # phax.json: "protectedPaths": ["vendor/lint-rules/allow.json"]; .gitmodules: vendor/lint-rules
    Config validation failed: protectedPaths[0]: "vendor/lint-rules/allow.json" is inside the submodule vendor/lint-rules
      at: /work/steme-lab/phax.json
    $? = 2
    Before
    $ phax validate
    ✓ config is valid (project: steme-lab)
      project: /work/steme-lab/phax.json
      local:   (none)
      global:  (none)
  • During a run the operator sees a gate attempt fail on each protected file the phase changed, and an exhausted phase names the files left for them to decide, the agent's last fix transcript and the two ways on. phax run

    Now:

    ✗ phase-05 gate attempt 3 failed: protected paths changed
        packages/rust/steme-formats/steme.baseline.json  edited  (protectedPaths[0] **/steme.baseline.json)
    This run paused because its gate checks failed after exhausting all fix attempts.
    Protected paths this phase changed, which no phase may change:
      packages/rust/steme-formats/steme.baseline.json (edited)
    The agent's last fix attempt: ~/.phax/runs/steme-lab.audit-legs/phase-05/fix-attempt-02.jsonl
    They are yours to decide, outside this run's phases. Either put each file back to its
    content at the phase base and run `phax resume audit-legs --yes`, or make the change
    on the base branch and start a new run.
    $? = 4
    Before
    ✗ phase-05 gate attempt 3 failed: steme audit (exit 1)
    This run paused because its gate checks failed after exhausting all fix attempts.
    Fix the gate manually in the worktree, then resume — the gate is re-run first on
    resume, and if it passes the phase commits with no fresh agent invocation.
    $? = 4
  • A phase's security record shows the protected paths and how much of the list its provider refused natively, or null when the project declares none. <run>/<phase-id>/security.json

    Now:

    {
      "version": 3,
      "mode": "secure",
      "provider": "mistral-vibe",
      "skillEditGrants": [],
      "protectedPaths": {
        "globs": ["**/steme.baseline.json", "phax.json"],
        "enforcement": "enforced"
      },
      "providerSkippedForSecurity": [],
      "stop": null
    }
    
    "protectedPaths": null        (no protectedPaths in phax.json)
    "enforcement": "enforced" | "partial" | "commit-check-only"
    Before
    {
      "version": 2,
      "mode": "secure",
      "provider": "mistral-vibe",
      "skillEditGrants": [],
      "providerSkippedForSecurity": [],
      "stop": null
    }
  • A phase's gate attribution shows the protected-path check's result and each protected change, or null when the project declares none. <run>/<phase-id>/gate-attribution.json

    Now:

    {
      "$schema": "https://docs.phax.run/schemas/gate-attribution/0.31.0.json",
      "phase": "phase-05",
      "steps": [
        { "command": "steme audit", "surface": "local", "result": "pass" }
      ],
      "protectedPaths": {
        "result": "fail",
        "changes": [
          {
            "path": "packages/rust/steme-formats/steme.baseline.json",
            "kind": "edited",
            "entry": "protectedPaths[0]"
          }
        ]
      }
    }
    
    "protectedPaths": null        (no protectedPaths in phax.json)
    "result": "pass" | "fail"     ("changes" is [] on pass)
    "kind": "created" | "edited" | "deleted" | "renamed"
    Before
    {
      "$schema": "https://docs.phax.run/schemas/gate-attribution/0.26.0.json",
      "phase": "phase-05",
      "steps": [
        { "command": "steme audit", "surface": "local", "result": "pass" }
      ]
    }
  • The help of phax run and phax validate says how protected paths behave. phax.usage.kdl

    Now:

    cmd "validate" {
        help "Validate phax.json and its user overlays without any side effects; pass --plan to also validate a phax-plan.json"
        long_help "… Lists the protected paths; protectedPaths is refused in phax.local.json and ~/.phax/config.json …"
    }
    cmd "run" {
        long_help "… A gate attempt fails when the phase changed a path listed in protectedPaths …"
    }
    Before
    cmd "validate" {
        help "Validate phax.json and its user overlays without any side effects; pass --plan to also validate a phax-plan.json"
        long_help "…"
    }
  • The security guide gains a Protected paths section saying what each provider refuses natively and what the commit check catches. docs/security.md

    Now:

    ### Skill edit grants (Claude Code)
    …
    ### Protected paths
    
    | Provider       | Native rule                                   | Enforcement            |
    | -------------- | --------------------------------------------- | ---------------------- |
    | `claude-code`  | `Edit(//…)` deny, literal and `**/` paths      | enforced or partial    |
    | `mistral-vibe` | `write_file`/`edit` denylist, literal and `**/` paths | enforced or partial |
    | `codex-cli`    | none (permission profiles: later)              | commit-check-only      |
    
    ### Shell command execution
    Before
    ### Skill edit grants (Claude Code)
    …
    ### Shell command execution