Repository navigation
feat(workflow)!: a rule is an effect and an exit; durations are strings - #62
Merged
Merged
Conversation
Two breaking, mechanical changes that introduce no new behaviour, so the features that need them stay additive. A rule, in every list, is now an optional condition, at most one effect (emit, workflow.call) and at most one exit (transition, result, error). `emit` no longer carries the transition inside itself, which only ever made sense in a list whose rules end the activity; `handle` becomes `workflow.call`, named for what it dispatches, and EmissionRule collapses into TransitionRule. An `emit` effect is allowed in onEmitted, which is what a filtering consumer needs. A timer's `duration` is a string in the unit shorthand (90s, 8h, 1h30m), literal or a whole-field template — `24h` was never expressible before, since proto3 JSON spells a Duration only in seconds. `duration` joins the type registry as utos:duration. Codes: UTOS-T001 reworded; C202 reworded and C203 added for a literal; E106 for what a template renders to; T004 and C504 retired. BREAKING CHANGE: TransitionRule's `action` oneof is replaced by `effect` and `exit`; EmitAction and EmissionRule are gone; CallActivityConfig .on_emitted and TimerActivityConfig.duration take new field numbers.
Tielem
added a commit
that referenced
this pull request
Sep 23, 2026
* feat(workflow): binary data — Blob and File values, WorkflowValue, BlobService (0.20.0) (#60) A Blob is now a value: response.body, transitions, sub-workflows, results and a run's input can all carry bytes. Small blobs ride inline; large ones live in object storage behind a handle, and clients upload and download directly at signed locations, multipart and resumable. - workflow/v1/value.proto: WorkflowValue/WorkflowList/WorkflowMap/Blob. Run-time values on the daemon wire move off google.protobuf.Struct, so a type is structure and never content — no key is reserved. - daemon/v1/blob.proto: BlobService, including SignUploadParts. - docs: workflow-values.md and binary-data.md (new); await, async arrows and instanceof admitted; Blob/File in expressions; response.bodyText retired (UTOS-E070); blob and file in the type registry with mediaType and maxSize; UTOS-F101–F104, V101–V102, H015, S015. - conformance: blob cases in the wire form, instanceof, schema and source cases for the published types. BREAKING CHANGE: ExecutionPayload.data, GetExecutionResponse.result and WatchOutputResponse.value/result change type; response.body is a Blob and response.bodyText is retired. * docs: end-to-end read of the spec — one owner per subject, drift corrected (#61) Each document now owns one subject and the others refer to it: template-expressions.md defines scope once (including what `output` holds per activity kind and in an onEmitted rule); the source format keeps the authoring guidance. The README maps the spec, indexes every rule-code family and lists the corpora and who runs them. Corrected, where the text had drifted from the protos or the validator: - UTOS-B006 covers every document a bundle names (branches, handle) - an onEmitted rule has four actions; EmissionRule's comment described the 0.0.13 flat dispatch - source examples wrote `result:`, which the source format rejects - the build step rewrites every document name and `self` - `$ref` chains must terminate, not "acyclic" - the content digest is provisional, not "leave it empty" - CallActivityConfig.input, a v-prefixed example version, stale README entries (registry package, "planned" implementations) History that had settled into normative text moved to Migrating sections. No wire change: buf breaking against feature/binary-data is clean. * feat(workflow)!: a rule is an effect and an exit; durations are strings (#62) Two breaking, mechanical changes that introduce no new behaviour, so the features that need them stay additive. A rule, in every list, is now an optional condition, at most one effect (emit, workflow.call) and at most one exit (transition, result, error). `emit` no longer carries the transition inside itself, which only ever made sense in a list whose rules end the activity; `handle` becomes `workflow.call`, named for what it dispatches, and EmissionRule collapses into TransitionRule. An `emit` effect is allowed in onEmitted, which is what a filtering consumer needs. A timer's `duration` is a string in the unit shorthand (90s, 8h, 1h30m), literal or a whole-field template — `24h` was never expressible before, since proto3 JSON spells a Duration only in seconds. `duration` joins the type registry as utos:duration. Codes: UTOS-T001 reworded; C202 reworded and C203 added for a literal; E106 for what a template renders to; T004 and C504 retired. BREAKING CHANGE: TransitionRule's `action` oneof is replaced by `effect` and `exit`; EmitAction and EmissionRule are gone; CallActivityConfig .on_emitted and TimerActivityConfig.duration take new field numbers. * docs: second read of the spec — one vocabulary, and an author-first order (#63) A rule has an effect and an exit since 0.20.0, but "action" survived in a dozen places, including a build step that still rewrote an onEmitted rule's `handle.workflow` — a key that no longer exists. - workflow-source-format.md reads in the order it is used: the `type` discriminator's derivation and the normative mapping move into § Building a bundle, which says it is for implementers; the worked example comes before it; § Activities keeps one author-facing table of the kinds instead of two tables with different columns. - The stream's guarantees (ordering, back-pressure, the relay, subscription lifetime) are stated only in execution-output-stream.md, which also names the handler where it defines it. The source format shows the syntax and points there. - Each long document opens with a map of its sections. - bundle.proto and workflow.proto: two more v-prefixed example versions, a description crediting the CLI with building bundles, and a note that named one of the three places a bundle names a document. * docs: finalize v0.20.0 for release (#64)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two breaking, mechanical changes from
plans/in-progress/syntax-foundations.md, riding the unreleased 0.20.0. Neither adds behaviour; both exist so that the features that need them (suspend, endpoints) can be purely additive.1. A rule is an effect and an exit
emitused to be two things at once: append a value, and go to activity X, which it carried inside itself as a requiredtransition. That reads fine inonSuccessand nowhere else — a rule that runs while an activity is live has nowhere to continue to, so every such list would need a second, transition-less spelling of the same word.Every rule, in every list, is now:
emit,workflow.call;transition,result,error.The lists differ only in whether an exit is required:
onSuccessandonFailureend the activity, so the run must go somewhere; anonEmittedrule runs once per value, and there an exit is what stops consuming.handlebecomesworkflow.call. It does what theworkflow.callactivity does, so it takes that name, and the dotted key resolves through theeffectoneof exactly astype: workflow.callresolves throughconfig. Named for what it dispatches, sinceworkspace.execis already proposed and a barecall:would stop reading.EmissionRulecollapses intoTransitionRule. One rule type, not two.emitis allowed inonEmitted, which is what a filtering consumer needs — republish only the invoices, without a document whose only job is to republish a value.UTOS-T001.2. Durations are strings
timeout: 24hwas never expressible: the source format ends by reading the document as proto3 JSON, where a duration is spelled only in seconds. The timer's own examples used30s, which is why nobody noticed.A duration is now a string in the unit shorthand that Kubernetes, Argo, Prometheus and Docker Compose use —
90s,8h,1h30m,3d— whole numbers, largest unit first, no fractions and no ISO 8601.86400sstays valid, so every timer written today keeps working.It may also be a whole-field template, so a reusable document can let its caller choose how long it waits, and a backoff can finally honour a
Retry-Afterheader. The rendered value and the instant it resolves to are recorded when the activity is entered, so a replay reads the same deadline.durationjoins the type registry asutos:duration.Codes
UTOS-T001(at most one effect, at most one exit, not neither),UTOS-C202(a literal duration is positive)UTOS-C203(a literal duration's syntax),UTOS-E106(what a duration template renders to)UTOS-T004,UTOS-C504Decisions worth a look
spawneffect yet. The plan's proto sketch lists it, but it comes fromspawn-action.md, still inpreparing/, and it would be new behaviour.DispatchEffecthascallonly and field 2 is left free, so adding it later is additive.emitandresultstaygoogle.protobuf.Struct, notWorkflowMap— the one place this departs from the plan's sketch. A bundle holds templates, never values, and no template can contain a blob. The blob case this raises (an endpoint receiving a binaryPUT) works through the template rendering to a value at run time; the two notes it does need are recorded insuspend.md.Breaking
TransitionRule'sactiononeof is replaced byeffectandexit;EmitActionandEmissionRuleare gone;CallActivityConfig.on_emittedandTimerActivityConfig.durationtake new field numbers, with the old ones reserved.buf breakingreports exactly these and nothing else.Migration tables are in
workflow-source-format.md§ Migrating.Checks
buf lintclean;buf breakingshows only the intended changes.durationform.sdk-dotnet, the daemon, the CLI andworkflows/follow; the plan tracks them.