File formats

Persisted formats

Every document phax writes starts with $schema, naming its format and the phax release that wrote it; every shape phax has written since that first release stays readable, and an older document without $schema is read by its format's pre-schema shape or reported unsupported.

FormatFormat idWhere it livesRead it withJSON Schema
Run registryregistry~/.phax/registry.jsonparseRegistryjson/registry.schema.json
Run statusrun-status<run-dir>/run-status.jsonparseRunStatusjson/run-status.schema.json
Phase statusphase-status<run-dir>/<phase-id>/status.jsonparsePhaseStatusjson/phase-status.schema.json
phax-planphax-plan<run-dir>/phax-plan.jsonparsePhaxPlanjson/phax-plan.schema.json
Compliance reviewcompliance-review<run-dir>/compliance-review.jsonparseComplianceReviewjson/compliance-review.schema.json
Plan approvalsplan-approvalsdocs/plans/approvals.jsonparsePlanApprovalsjson/plan-approvals.schema.json
Spec approvalsspec-approvalsdocs/specs/approvals.jsonparseSpecApprovalsjson/spec-approvals.schema.json
Phase record manifestphase-record-manifest<runId>/<phaseId>/record.json on phax/records/v1parsePhaseRecordManifestjson/phase-record-manifest.schema.json
Authoring record manifestauthoring-record-manifestauthoring/<authoringId>/record.json on phax/records/v1parseAuthoringRecordManifestjson/authoring-record-manifest.schema.json
Gate attributiongate-attribution<record>/gate-attribution.jsonparseGateAttributionjson/gate-attribution.schema.json
File reconciliationphase-file-reconciliation<record>/file-reconciliation.jsonparsePhaseFileReconciliationjson/phase-file-reconciliation.schema.json
Gate diagnosticsgate-diagnostics<record>/checks-attempt-NN.diagnostics.jsonparseGateDiagnosticsjson/gate-diagnostics.schema.json
Gate pendinggate-pending<record>/checks-attempt-NN.pending.jsonparseGatePendingjson/gate-pending.schema.json
Spec documentspec-document.json sidecar beside a headless-authored specparseSpecDocumentjson/spec-document.schema.json
Plan documentplan-document.json sidecar beside a headless-authored planparsePlanDocumentjson/plan-document.schema.json
Record manifest (union)record-manifestany record.json on phax/records/v1parseRecordManifestjson/record-manifest.schema.json

Everything in this table comes from @lbdremy/phax-schemas, and parseDocument reads any document carrying $schema, whatever its format.

Read phax files from code

Reading phax's persisted files from another tool — a dashboard, a cockpit, a docs pipeline — needs only the schemas package, not phax itself:

npm install @lbdremy/phax-schemas
// read-record.mjs: Node 20+, phax not installed, only @lbdremy/phax-schemas
import { execFileSync } from "node:child_process";
import { parsePhaseRecordManifest } from "@lbdremy/phax-schemas";

const [runId, phaseId] = process.argv[2].split("/"); // "<runId>/phase-01"
const git = (...args) => execFileSync("git", args, { encoding: "utf8" }).trim();

// Each commit on the records branch holds only its own record: find the
// record's commit by its trailers, then read the file at that commit.
const sha = git(
  "log",
  "phax/records/v1",
  "--format=%H",
  "--fixed-strings",
  "--all-match",
  `--grep=Run-Id: ${runId}`,
  `--grep=Phase-Id: ${phaseId}`,
  "-1",
);
if (!sha) {
  console.error(`no record for ${runId}/${phaseId} on phax/records/v1`);
  process.exit(1);
}
const raw = git("show", `${sha}:${runId}/${phaseId}/record.json`);

const parsed = parsePhaseRecordManifest(JSON.parse(raw));
if (!parsed.ok) {
  console.error(`record.json: ${parsed.error.path}: ${parsed.error.message}`);
  process.exit(1);
}
const { outcome, usage } = parsed.value; // typed PhaseRecord
console.log(runId, phaseId, outcome, usage.available ? usage.usage.provider : "no usage");
node read-record.mjs <runId>/phase-01

Each commit on phax/records/v1 holds only its own record, so git show phax/records/v1:<path> sees only the newest one; find a record by its commit trailers (Run-Id and Phase-Id for a phase, Authoring-Id for an authoring session) as above. To walk the whole branch, list its commits (git log phax/records/v1 --format=%H), read the record.json each one holds (git ls-tree -r --name-only <sha>, then git show <sha>:<path>) and give it to parseRecordManifest, which accepts either manifest shape.

For a docs pipeline that renders a format's JSON Schema, read it straight from the installed package: node_modules/@lbdremy/phax-schemas/json/<format>.schema.json.

A parse failure is a value, never an exception — parsed.ok is false, with a path and a message. Every document stays readable: an older shape still parses, upgraded in memory to the latest shape by its toLatest* function (a fact phax did not yet track becomes { kind: "unknown" }), while a document written by a phax release newer than the installed package fails, asking you to upgrade @lbdremy/phax-schemas.

Served JSON Schemas

Every $schema URL a released phax writes is served here, byte for byte:

  • https://docs.phax.run/schemas/authoring-record-manifest/0.17.0.json
  • https://docs.phax.run/schemas/compliance-review/0.17.0.json
  • https://docs.phax.run/schemas/gate-attribution/0.17.0.json
  • https://docs.phax.run/schemas/gate-diagnostics/0.17.0.json
  • https://docs.phax.run/schemas/gate-pending/0.17.0.json
  • https://docs.phax.run/schemas/phase-file-reconciliation/0.17.0.json
  • https://docs.phax.run/schemas/phase-record-manifest/0.17.0.json
  • https://docs.phax.run/schemas/phase-status/0.17.0.json
  • https://docs.phax.run/schemas/phax-plan/0.17.0.json
  • https://docs.phax.run/schemas/plan-approvals/0.17.0.json
  • https://docs.phax.run/schemas/plan-document/0.17.0.json
  • https://docs.phax.run/schemas/registry/0.17.0.json
  • https://docs.phax.run/schemas/run-status/0.17.0.json
  • https://docs.phax.run/schemas/spec-approvals/0.17.0.json
  • https://docs.phax.run/schemas/spec-document/0.17.0.json