Skip to content

v0.17.2

Choose a tag to compare

@github-actions github-actions released this 28 Oct 14:20
· 34 commits to main since this release

Changed

  • A gate with no store fails its resume instead of running its step again. A gate without one suspended and then, on the resume, ran inner again and passed the new result through, so a gate around a model call bought a second answer that the approver never saw. The resume fails now with gate_result_error, whose reason is no_store. Give the gate a store, or the run a checkpoint_store, or use a plain suspend when the step is cheap and deterministic. A gate handed its datum before it ever suspended, through resume_data on a first run, is resuming too, so with nothing stored it fails with missing, or with no_store when it has no store, since an approval given before anything was shown covers nothing.

Fixed

  • A resumed gate serves the result it showed, or fails the run. A gate's resume read its result back from the store, and a read that threw, or a store that no longer held the result, counted as a miss, so the gate ran inner again, paid for it again, wrote the new result over the old one, and passed it through an approval that covered the old one. A resume never runs inner now. It serves the stored result, or it fails with gate_result_error, which names the gate and carries a reason: read_error, with the store's error as its cause, or missing. The trajectory records the lookup's checkpoint event as before. A durable run that a failed read stopped resumes from the next event with nothing new, since the approval stays in its resume data. A first run's failed read is still a miss, since nobody has approved anything yet.
  • A flow that uses one suspend id in two places is refused before it runs. Resume data is keyed by suspend id, so a second suspend or gate with the same id found the first one's datum and passed without pausing, on an approval that was given for something else. run, run.stream, and run.until_suspended, and so durable, now walk the flow before any step runs and throw duplicate_suspend_id_error with reason: 'declared_twice', whose paths hold each place's step ids from the root and whose message names each place. A gate counts through its suspend, one suspend placed twice counts twice, and two arms of a branch count too, since a resume replays the branch's condition. The run ends failed. A flow that ran before and is refused now needs an id of its own for each suspend and gate, and a durable run that the refusal failed has to be deleted and started again on the fixed flow, since renaming an id changes the flow's fingerprint. A run that reaches one suspend again fails there too, with reason: 'reached_twice', when the input differs from the one its datum was taken for: a later round of a loop, the next item of a map, or a call through ctx.call that the tree doesn't show. The input is the same when its JSON, with the keys sorted, reads the same as it did when the datum was taken, so an object changed in place since counts as another input, and one that JSON can't hold has to be the same value. A retry that runs the step again on the same input still passes. A map with settle: true settles the second item as the error, and a flow that wants a decision per round or per item approves a map's batch at a gate before the map, or a loop's result at a gate after it. A gate checks its own input the same way, on a first run as well as a resume, before it reads or writes its store, since its key is fixed and its suspend always sees a view of the one stored result. A map whose items each reach one gate used to show the approver one item's result and hand every item another's, and now its second item fails with reached_twice before it runs inner, so the store keeps the first item's result. Core exports the comparison as same_input_as.
  • A provider failure that retry_on leaves out throws a typed error. retry_with_policy threw such a failure again as it caught it, and for a 429, a 5xx, or a network failure that was the plain record that the classifier built, so it reached the caller as something like { kind: 'provider_5xx', status: 503 }, and the run's run_end read [object Object]. It throws what exhaustion would have thrown now: rate_limit_error for a rate limit, with attempts counting every failure so far, and provider_error for the rest, with cause_kind set to provider_5xx or network, and the status and body of a 5xx. The message is the provider's own, or else says that retry_on leaves the kind out. A failure that was already an Error, such as turn_timeout_error or a native adapter's network failure, comes back as itself.
  • checkpoint can fail the run on a read that failed, with on_read_error: 'fail'. A store.get that threw counted as a miss, so checkpoint ran inner again and wrote the new result over the old one, and a hand-built checkpoint before a suspend that showed its result passed an approval through to something the approver never saw. on_read_error: 'fail' records the same read_error event and then throws the store's error, without running inner or writing anything, the read-side twin of on_write_error: 'fail'. A checkpoint whose result a suspend shows should pass 'fail' for both. 'miss' is the default and keeps the old behavior, and the option stays out of the step's config, so describe and a durable run's fingerprint don't change.

Internal

  • The publish workflow keeps its OIDC identity away from the job that runs dependencies. publish.yaml held id-token: write in the same job that installed, ran the gate, and built, so a compromised dev dependency could have minted the workflow's identity and published under it. It runs three jobs now: unpublished looks the version up on npm, build installs, runs the gate, builds, and packs the tarball with only contents: read, and publish holds id-token: write and the npm-publish gate, runs nothing from the repo, and publishes the packed tarball with --ignore-scripts. The gate runs before the approval, so the reviewer approves a green build, and SECURITY.md describes the split.