Skip to content

Releases: mj-deving/waybill

Release list

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 29 Jul 11:14

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

  • --version on the CLI, reporting what package.json says 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.ts rather 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.allowlist names 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 to YOUR_* 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 its waybill repository, 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.ts is 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.
  • redactAlso on the execute options. Values the runner knows are credentials although no
    manifest declares them, filtered out of every exit alongside the declared secrets.
  • unwired on a UnitReport, and an edge:not-wired(…) annotation in the waybill check
    listing. runs:…,edge says 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 check authorizes 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.
    placement says what the substrate permits; a caller acting on it has to read unwired too, and
    the server now refuses with 403.
  • The unknown-unit exits filtered the envelope and not the unit name, which comes back through
    action and inside error. 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 edge exited 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; bare waybill check still reports the wiring and
    passes.
  • A run's recorded input was 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. buildFlowContext injected
    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 check listed a unit as ok while 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 two CHECK
    constraints accepting opposite values compared equal.
  • **One run's rows could be split a...
Read more