Validator usage
fpd-validate — the standalone package validator, its finding kinds, and exit codes. From @faceless-photolib/package-format.
@faceless-photolib/package-format ships a standalone validator — a library
function and a CLI wrapping it — that checks a package without the editor
or a renderer: schema validity, digest integrity, reachability closure, the
mandatory preview, and the reader-must-ignore/reader-must-reject field
classes.
CLI
fpd-validate <package-directory | package.fpd>fpd-validate is the package's declared bin (installing
@faceless-photolib/package-format puts it on your PATH). Inside this repo,
run it against the built package from the repo root:
pnpm --filter @faceless-photolib/package-format build
node packages/package-format/bin/fpd-validate.mjs <target>Exit codes are the standard three-way CLI contract:
| Code | Meaning |
|---|---|
0 | Valid. Any warnings are printed first, then a valid: N reachable ref(s) checked, M warning(s) summary. |
1 | Invalid. Every error and warning is printed, then an invalid: N error(s), M warning(s) summary. |
2 | Usage or I/O error (wrong argument count, target doesn't exist, unreadable directory/zip). |
The CLI accepts exactly one argument (a directory or a .fpd file); anything
else is a usage error, never a best-effort guess at what you meant.
Library
import { validatePackage } from "@faceless-photolib/package-format";
const report = await validatePackage(files); // PackageFiles — see Package anatomy
// { kind: "valid", warnings, checkedRefs } | { kind: "invalid", errors, warnings }validatePackage runs against the logical file set (PackageFiles, a
ReadonlyMap<string, Uint8Array>) — directory and .fpd forms validate
identically because they're loaded into the same shape first.
What gets checked, and every finding kind
The validator collects every finding in one pass rather than stopping at the first problem, so a hand-built package (see Package anatomy) gets one complete report to fix from instead of a fix-one-rerun loop.
| Finding | Severity | Meaning |
|---|---|---|
missing-file | error | A required layout path (manifest, index, a referenced document/history/blob) is absent. |
malformed-json | error | A file that should be JSON doesn't parse. |
manifest-invalid / index-invalid | error | manifest.json/index.json fails its Zod schema; issues names the offending field(s). |
unsupported-container-version | error | manifest.containerVersion isn't one this reader understands. |
unsupported-required-capability | error | A capabilities.required flag this reader doesn't implement. |
critical-field | error | An unknown field whose key begins with ! — reader-must-reject (see Package anatomy). |
role-mismatch | error | A ref's declared role disagrees with where it's used. |
integrity-error | error | A blob's actual bytes don't hash to the digest its path/ref claims — content was modified without updating its address. |
preview-stale | error | previews/flat.png's bytes don't match manifest.preview's digest. |
preview-missing | error | The mandatory flat preview is absent. |
missing-content | error | A reachable ref has no payload file in the package. |
index-incomplete | error | A ref reachable from the manifest roots is absent from index.json — the index must be the full closure. |
invalid-document / invalid-history-node | error | A documents/<hash>.json or history/<hash>.json file fails its schema. |
closure-error | error | The reachability walk itself failed (e.g. a malformed embedded ref). |
index-unreachable-entry | warning | An index.json entry the manifest roots can no longer reach — GC garbage, not corruption. |
index-entry-mismatch | warning | An index entry's role/mediaType disagrees with what the closure walk found for that digest. |
foreign-file | warning | A file outside the layout grammar entirely — readers ignore it, but the validator flags it so you notice stray files. |
Severity is derived from the finding's kind, never stored as a separate
field a producer could get out of sync with the kind — index-unreachable-entry,
index-entry-mismatch, and foreign-file are the only three warning kinds;
every other finding is an error.
Directory ↔ .fpd equivalence
Because the validator operates on the logical PackageFiles shape, zipping a
valid directory package into .fpd (entries STORED, per Package
anatomy) and reopening it validates identically — every ref
resolves to the same bytes and the manifest hash is unchanged. There is no
"validate the directory form more strictly than the zip form" split.
Corpus samples
The .fpd fixtures below — the minimal valid package and the
forward-compatible unknown-fields case — are served through the same
/api/samples/<id> proxy every other demo on this site uses; no direct
storage URL ever appears in this page.
Preparing sample list…
Worker API reference
The format-worker-runtime message protocol — request/response/progress shapes, transferables, and the four async UI states.
Migrating: OpenRaster (.ora)
OpenRaster import/export scope, the best-effort/source-preservation policy, the generated blend-mode fidelity table, and a live import demo.