A workflow declares what its runs are about - #134
Merged
Conversation
czpython
force-pushed
the
eng-777-subject-is-a-class
branch
3 times, most recently
from
July 28, 2026 05:48
52ec75f to
60e8fc6
Compare
A subject was spelled three ways depending on where the author stood: a class on a subscriber, a string on the extension, and nothing at all on the workflow that produces the run. The read side had to search for it, and the search only ever found subjects that happened to have a table. A workflow declares `subject = SomeSubjectClass`. That declaration is the lookup, so the mapper walk is gone; `Subject` is subclassable and derives its `subject_type` from the class name, the way `StoredSubject` already did. `start()`, `Workflow.cancel()` and `Gate.answer()` hold a caller to it. `Extension.subject_type` is deleted — an extension's subjects follow from its workflows, and each gets its board and page. Summary and listing move onto the subject class, where the data is: ship's `WorkItem`, review's new `PullRequest`, and `ProjectRepo`, which gets a board it never had. `subject=` on `@subscribe` is derived from `workflow=` and no longer written beside it.
czpython
force-pushed
the
eng-777-subject-is-a-class
branch
from
July 28, 2026 05:52
60e8fc6 to
764508d
Compare
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.
A subject was spelled three ways, and which one an author wrote depended on where they were standing:
The one place that produces a run said nothing about what the run is about, so the read side searched for it — and the search only ever found subjects that happened to have a table.
A workflow declares its subject
A run is one episode in the life of something that outlives it. Druks can only show that life, or offer what may be done to it, before any run about it exists, if the workflow says what kind of thing it is about — and the workflow is the only place that noun is known at import time.
The declaration is the lookup, so the mapper walk in
Workflow.subjectis gone rather than replaced.start(),Workflow.cancel()andGate.answer()hold a caller to it: a wrong subject, a missing one, and an unexpected one all fail before anything is enqueued. Subscribers derive their subject class fromworkflow=, so the declaration has to be true.What moved
workflow=subject=WorkItemworkflow=subject=WorkItemsubject_type = "work_item"subject = WorkItemSubjectis now subclassable and derives itssubject_typefrom the class name, the wayStoredSubjectalready did; it stays a separate frozen dataclass, with no base shared with the ORM. Both answersubject_type,id,identityandlabel, and both now answerget_for_subject_id(),get_summary()andlist_summaries()— the summary and listingExtensionused to carry, moved to the class that owns the data.An extension's mounted subject types follow from its workflows, so
Shipmounts two:ProjectRepogets the board and page it never had, and its profiling runs stop being stamped, deduped and queryable with nowhere to show it. Review'sPullRequestbecomes a real class that owns its own id parsing, which leaves itsschemas.pyas response fields only. The route-collision guard now runs once per mounted subject rather than once per extension.Known cost
subject = WorkItemis a plain class attribute and buys zero type-checking —self.subjectis stillAny, andBuild.start(subject=a_repo)type-checks clean. Runtime enforcement is the guarantee.Class names become durable: renaming a subject class changes its
subject_type, and with it route prefixes, dedup keys, event identities and replay lookup. There is no alias and no override.Acting on another extension's subject is now an import edge between pluggable apps. The alternative was two extensions agreeing on a magic string and composing two different ids for one thing.