Repository navigation
Releases: mj-deving/waybill
Releases · mj-deving/waybill
Release list
v0.3.0
Findings from the cross-vendor audit of the 0.2.0 commits, fixed the same day, plus what it took to
make the repository something a stranger can arrive at. The audit fixes are kept in their own version
rather than folded back into 0.2.0, because rewriting after the fact what a version contained is
worse than carrying a second section.
Added
--versionon the CLI, reporting whatpackage.jsonsays rather than a second copy of the
number. A test holds the manifest, the newest changelog heading and that output to one value, so a
release cannot be cut half-done.- Releases are published from a tag. Pushing
v*runs the same three gates as a push, then
publishes a release whose notes are the matching changelog section, extracted by
bin/changelog-section.tsrather than retyped. The extractor exits non-zero on a version the
changelog does not carry, which is what stops a tag from outrunning the notes. - The community files a repository is read by: a security policy stating what the capability
declaration does and does not protect, a code of conduct, issue forms that ask for the manifest and
the substrate, and a pull-request template that asks for the claim, the probe and the mutation. - A fail-closed public export boundary.
public-export.allowlistnames every file that may be published and is the whole of the boundary: an exporter copies exactly what it lists into a separate idempotent tree, rewrites the Feed's Wrangler configs toYOUR_*account placeholders so they stay deployable, and refuses to emit a tree in which a leak class is still recoverable, including from strings a file assembles at run time. The exporter and its own tests stay behind the boundary, because a scrub that ships its denylist publishes the vocabulary it exists to remove. - Waybill is the public framework name. The CLI, package,
WAYBILL_*environment contract,.waybill/runtime directory, CI command and framework documentation move together; this installation deliberately keeps itswaybillrepository, systemd unit filenames and deployed Cloudflare worker, queue and D1 names. lib/cloud-actions.ts, the list of actions the cloud registry carries. The registry in
shared/cloud-runner.tsis typed by it, so the two cannot drift: a module added without a name
fails the type check, and a name without a module fails it too.redactAlsoon the execute options. Values the runner knows are credentials although no
manifest declares them, filtered out of every exit alongside the declared secrets.unwiredon aUnitReport, and anedge:not-wired(…)annotation in thewaybill check
listing.runs:…,edgesays the substrate would permit the unit; this says whether it is actually
deployed there. Reported rather than subtracted, so a unit that must never be deployed does not
make a target check permanently red.- A flow reaches only the units its manifest declares.
waybill checkauthorizes the graph a
manifest names, so that is the graph the runtime may use. The gate sits where the entrypoints are
injected, not in each flow's own code, and every substrate wires it. lib/manifests.ts, the manifest validators, moved out of the runner. Still the only ones, so
a manifest the checker accepts is one the runner accepts.
Fixed
- A partially resolved secret was recorded in clear. A manifest declaring two secrets where one
was absent threw with nothing attached, so the failure exit had no filter to apply. Resolution
errors now carry what they resolved. - The bearer a worker authenticated with was recorded in clear when a caller copied it into the
request body. It belongs to no manifest, so the filter had never heard of it. - An action that wrote to its own input rewrote the run record. The recorded envelope and the one
handed to the action were the same object; they are separate copies now. - A transient failure on the first append lost the run's input for good. It was written by
sequence number, so a dropped seq 0 followed by a recovering sink left the chain with no input
anywhere. It is written on the first row that lands. - The schema-parity test compared DDL text with a regex that could not see
NOT NULL, defaults,
the composite primary key or either index. Both schemas are applied to a real SQLite database and
compared through its own pragmas now. - The cloud runner's unknown-action and unknown-pipeline exits returned the envelope unredacted.
They return before the execution wrapper, so they had none of its filtering, and a pipeline hands
that envelope to its own error result for the flow to record. - An envelope carrying a value no structured clone can serialise was handed to the action
directly, so one non-cloneable field gave it write access to every other one. - Two concurrent records both persisted the run's input, because the claim on it was taken after
the sink returned rather than before. - A runner server whose target is the edge executed units the cloud registry does not carry.
placementsays what the substrate permits; a caller acting on it has to readunwiredtoo, and
the server now refuses with 403. - The unknown-unit exits filtered the envelope and not the unit name, which comes back through
actionand insideerror. On the served path that name is supplied by the caller. - The runner server did not strip its own bearer from the result or the record, so the same
request leaked or did not depending on whether the standalone worker or the served runner answered
it. It is threaded through the run options now, in one place all three entrypoints use. - A slow first append that then failed lost the run's input. The claim on it was reserved before
the sink was awaited and released on failure, by which time the next record had already committed
without one. Appends are serialised instead. waybill check --target edgeexited 0 on units the cloud runner cannot reach, and its summary
line said every unit could run there. Asking for a target is asking whether a unit could be
deployed to it, so an unwired unit now fails; barewaybill checkstill reports the wiring and
passes.- A run's recorded
inputwas whichever entry happened to write it. After a dropped first
append the next step's envelope was recorded as the input the run was called with, so the chain
reconstructed to a different starting point and nothing said so. The invocation's input is held
until a row carrying it lands. - A flow's steps ran with neither the flow's run log nor its filter.
buildFlowContextinjected
the entrypoints unbound, so a flow's steps left no rows at all locally while the same steps under
the cloud processor shared the batch's run id, and a credential the flow was invoked with came
back out of a step's output. - A flow's own summary row met no filter. It is written outside the action wrapper, which is
where every other exit gets one. waybill checklisted a unit asokwhile reporting an issue against it, for any issue that
was not a placement one. A pipeline naming a deleted action was the case that showed it.- The schema-parity test could not see anything SQLite reports in no pragma — a
CHECK, a
collation, a generated column. It compares the declarations as well now. - A flow ran whatever pipeline its caller named. The consumer flow took its pipeline from its
input, so a caller cleared the placement gate on one graph and then ran another, whose actions the
cloud registry need not even carry. The check authorized one thing and the runtime did another. - Neither cloud flow worker stripped its own bearer. The standalone action worker had done it
since the option existed, so the same value leaked or did not depending on which entrypoint
answered. The cloud runner derives the filter from its environment now, in one place all of them
go through. - The runner server's unknown-unit 404 echoed the name it was sent. Every other message on that
path names a unit that resolved and is therefore public; that one is whatever the caller sent, and
a unit name is loose enough for a bearer to arrive as one. - The filter a run was authorized with was the caller's own object, so it could be narrowed
after the run had started and every later exit filtered on different terms than the record kept.
It is copied and frozen at the boundary. - The schema comparison collapsed whitespace inside quoted literals, so two defaults differing
in stored data compared equal. It normalizes only outside them. - A cloud pipeline recorded no summary row, leaving its action rows with nothing naming the
pipeline that drove them while the identical local run recorded both. - A pipeline's own recorded input met no filter on either substrate, the same gap the flow
summary row had. - A filter narrowed before the entry point's first await resumed still applied. Every entry point
opens with a manifest load and the copy was taken after that yield, so the run proceeded on terms it
was never authorized with. A snapshot taken after a yield is not a snapshot. - The cloud runner's unknown-pipeline exit kept the caller's options, so it returned the worker's
bearer in clear while its unknown-action twin stripped it. - A run recorded the envelope of whichever unit wrote first, not the one it was invoked with. For
a flow that is its first step's envelope, which the flow built rather than received, so the chain
reconstructed to a starting point the run never had. Declared before any step records now. - The schema comparison collapsed whitespace inside double-quoted, backticked and bracketed spans.
A double-quoted token SQLite cannot resolve to an identifier is a string literal, so twoCHECK
constraints accepting opposite values compared equal. - **One run's rows could be split a...