Imported from PerishLab/wharf (
AGENTS.md). Install upstream withnpx skills add PerishLab/wharf. Copyright stays with the author.
wharf
wharf is the distribution hub of perish.code. It sits above Plumb: it distributes
Plumb, so Plumb does not govern it. This file is the only unstructured document in
the repository; CLAUDE.md only points here.
Shape
| Path | Holds |
|---|---|
.github/workflows/ |
orchestration, one workflow per distribution path |
lib/ |
every piece of shared logic, one module per function |
scripts/ |
thin entry points run as python -m scripts.<name> from the repository root |
tests/ |
tests mirroring lib/ and scripts/ |
resources/ |
non-code files, read only through lib/resources.py |
.githooks/pre-commit |
the only gate |
scripts may import lib; lib never imports scripts; scripts never import each
other; lib has no import cycles. Only lib/resources.py knows where files live.
Working rules
mainis production. Merging is deploying; a bad change is reverted.- Python in
libholds atomic actions. An action owns only its own arguments, outputs and preconditions, refuses early withRefusal, and knows nothing about other actions or their order. Workflows own ordering, conditions and composition. - No comments or docstrings anywhere, including workflow YAML. Names carry meaning;
facts that need structure live in
resources/. - Keep Ectropy-level limits: at most 300 lines per file, 4 parameters per function, 4 nested blocks, 10 entries per directory and 3 levels below a top-level directory.
- Words claimed elsewhere in perish.code are listed in
resources/vocabulary.jsonand must not name anything here. - Third-party Python dependencies are allowed only when locked to exact versions and
hashes. Actions are official
actions/*only, pinned by SHA inresources/actions.json. - Enable the gate with
git config core.hooksPath .githooks. It runspython3 -B -m scripts.selfcheckand the test suite.
Workloads
A workload key is sha256 over the canonical JSON of the hash version, the basis
and the implementation. The basis is the effective content an entry resolves to,
recorded item by item, widened rather than narrowed where resolution is imprecise.
The implementation is the content of the action module, its lib import closure
and any resources it reads. Records live under workload/<hash_version>/<key>/ as
basis.json, blobs/* and finally record.json; every write is create-only, and
different bytes under an existing key refuse as non-determinism. Jobs hand work to
each other only through recorded workloads, never through run artifacts.
Every run attempt writes one trigger record at
trigger/<owner>/<repository>/<marker>/<run>-<attempt>.json; a run is complete only
when every job succeeded or was skipped by plan.
Release identity
Sources declare version 0.0.0. A distributable binary is built unbound and receives
its release identity afterwards, so one built workload serves several markers. The
identity region format is owned here: resources/identity/format.json holds its
structure and resources/identity/fixtures/ its shared evidence, which the writer
must reproduce byte for byte and every reader must test against.
- The region lives in one 4096-byte section with file content and no relocations; a signed PE input is refused.
- An unbound region has payload length 0 and nothing after the length field.
- Binding an already bound region succeeds only with an identical binding and then changes nothing.
- The binding
digestissha256over the canonical release{repository, marker, commit, tree};workloadis the key of the unbound binary. - A product with prefix
Pbuilds withP_BUILD_TARGETandP_BUILD_CHANNEL=unbound; such an executable refuses to run commands until bound.