v 0.2 refactor (breaking changes)#56
Closed
monkeypants wants to merge 233 commits into
Closed
Conversation
added 13 commits
December 19, 2025 15:58
Contributor
Author
|
totes squasher :( |
added 11 commits
December 21, 2025 09:05
- Create app.yaml manifests for julee documentation tools: sphinx-hcd, sphinx-c4, hcd-api, hcd-mcp, c4-api, c4-mcp - Add RST documentation for each app in docs/domain/applications/ - Enable sphinx_hcd and sphinx_c4 extensions in conf.py with configuration for julee's docs structure - Fix docutils warnings about losing classes attribute when replacing placeholder nodes in epic.py and journey.py
- Fix sphinx_c4 extension by adding setup() to main __init__.py - Create C4 model documentation: - System context: Julee + external systems (Temporal, MinIO, PostgreSQL) - Containers: Core framework, APIs, workers, Sphinx extensions, MCPs - Relationships between all components - Add C4 section to main documentation index
- Add DefinePersonaDirective to define personas with HCD metadata: name, goals, frustrations, jobs-to-be-done, and context - Add PersonaIndexDirective to render list of all personas - Update personas index to use persona-index:: directive with hidden glob toctree for discovery - Register new directives and placeholder nodes in extension setup
Proposal to extend sphinx_c4 to automatically infer C4 architectural elements from Julee's clean architecture conventions: - Software systems from solution metadata - Containers from apps/ and bounded contexts in src/ - Components from AST introspection of domain models, use cases, protocols - Relationships from import statement analysis - Integration with HCD entities (accelerators, applications, integrations) This is a discussion document, not a decision. Implementation approach and scope to be refined through review.
Key changes: - Remove external repository references - Add "Concept Unification" section proposing that HCD and C4 share the same underlying model rather than maintaining parallel concepts - Persona = C4 Person (same entity, different views) - Application = C4 Container (entry point) - Accelerator = C4 Container (bounded context) - Integration = C4 External System - Add "Idiom Refinements" section suggesting conventions that would improve inference (container types, naming conventions, etc.) - Single directive creates entity visible in both HCD and C4 views
Proposes enhancements to make clean architecture conventions more self-describing and machine-readable: 1. Architectural role decorators (@entity, @use_case, @repository_protocol) 2. Bounded context manifests (context.yaml or YAML front matter) 3. Protocol markers distinguishing RepositoryProtocol, ServiceProtocol, ExternalService 4. Relationship annotations (Reads, Writes, Uses) 5. Pipeline declarations linking workflows to use cases 6. App entry point decorators (@api_route, @cli_command) Also includes phased implementation approach (non-breaking first), validation/linting concept, and trade-off analysis.
…ve decorators Constructor signatures and type hierarchy are the source of truth. Protocol base classes declare architectural role without repetition.
…ocols - Protocols are abstract (no external system knowledge) - Implementations are concrete (know about MinIO, Anthropic, etc.) - @repository_impl/@service_impl decorators declare external dependencies - DI container wiring is the source of truth for deployments - Settings introspection discovers configured external systems
Contributor
Author
|
At this point, what I am doing in the PR is to try to get the julee framework to be self-documenting using the architecture and hcd modules. This may potentially involve some violent refactors, for that I apologise in advance. If that proves necessary then the sooner they are done the better. What I mean by self-documenting is that the framework (and solutions based on the framework) will have architecture documentation and HCD documentation that is coherent and sensible, and is mostly levered-out of the implementation. I want the docs and code to embody a doctrine that is easy for agents to follow. |
This was referenced Jan 7, 2026
2 tasks
monkeypants
pushed a commit
that referenced
this pull request
Jan 7, 2026
Cherry-picked from super-branch (PR #56). Documents the Handler pattern for decoupled use case orchestration: - Handlers have domain interfaces (accept domain objects, not requests) - Use cases hand off to handlers without knowing what happens next - Acknowledgement semantics (wilco/roger) - Fine-grained vs coarse-grained handlers Closes #62
2 tasks
2 tasks
2 tasks
monkeypants
pushed a commit
that referenced
this pull request
Jan 7, 2026
Cherry-picked from super-branch (PR #56). Documents the "docstrings ARE the documentation" principle: - Framework provides semantic scaffolding, solutions provide content - Viewpoints are projections through framework BCs - Bespoke templates per entity type - Code exists → autodoc; code doesn't exist → design doc Closes #70
2 tasks
Accelerator entity was moved from HCD to supply_chain bounded context. Update imports in admin CLI to use new location.
Contributor
Author
Recent ChangesSupply Chain Bounded Context
UNTP Projection Layer
Semantic Relations Infrastructure
Doctrine Improvements
TestsAll unit tests passing (1862 passed, 22 skipped). CEAP integration tests require MinIO infrastructure. |
Pipeline is imported under TYPE_CHECKING, so runtime type annotations using Pipeline directly cause NameError. Use string annotations instead.
monkeypants
pushed a commit
that referenced
this pull request
Jan 19, 2026
Cherry-picked from super-branch (PR #56). Documents the Handler pattern for decoupled use case orchestration: - Handlers have domain interfaces (accept domain objects, not requests) - Use cases hand off to handlers without knowing what happens next - Acknowledgement semantics (wilco/roger) - Fine-grained vs coarse-grained handlers Closes #62
monkeypants
added a commit
that referenced
this pull request
Jan 19, 2026
* Add ADR 003: Workflow Orchestration via Handler Services Cherry-picked from super-branch (PR #56). Documents the Handler pattern for decoupled use case orchestration: - Handlers have domain interfaces (accept domain objects, not requests) - Use cases hand off to handlers without knowing what happens next - Acknowledgement semantics (wilco/roger) - Fine-grained vs coarse-grained handlers Closes #62 * Clarify handler protocol placement rule for cross-BC entities Handler protocols live with the entity's BC, not the use case's BC. This keeps the dependency graph clean since the use case BC already depends on the entity BC. * Add HandlerDispatcher pattern for coarse-grained handlers Use factories instead of instances to solve circular dependencies and bootstrapping order constraints. Lazy instantiation at handle() time eliminates DI ordering complexity. --------- Co-authored-by: Chris Gough <chris.gough@gosource.com.au>
monkeypants
pushed a commit
that referenced
this pull request
Jan 19, 2026
Cherry-picked from super-branch (PR #56). Documents the "docstrings ARE the documentation" principle: - Framework provides semantic scaffolding, solutions provide content - Viewpoints are projections through framework BCs - Bespoke templates per entity type - Code exists → autodoc; code doesn't exist → design doc Closes #70
monkeypants
pushed a commit
that referenced
this pull request
Jan 19, 2026
Cherry-picked from super-branch (PR #56). Documents the "docstrings ARE the documentation" principle: - Framework provides semantic scaffolding, solutions provide content - Viewpoints are projections through framework BCs - Bespoke templates per entity type - Code exists → autodoc; code doesn't exist → design doc Closes #70
added 2 commits
February 12, 2026 18:41
model_copy(update=data) skips Pydantic model validators, which bypasses invariants like auto-setting timestamps on state transitions. Use model_validate() to reconstruct the entity so all validators fire on partial updates.
Single document covering screaming architecture, dependency rule, bounded context structure, core concepts (entities, use cases, protocols, handlers, pipelines), applications, contrib modules, doctrine/policy, documentation philosophy, and C4 mapping.
This was referenced Mar 16, 2026
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.
What started as "C4 architecture domain: sphinx extensions, API + MCP service" has grown into a rather large, breaking set of changes.
The purpose of this branch was originally to:
But things changed in interesting ways. When it was apparent that some breaking changes were necessary, I went ahead and made all the breaking changes, since this is now a hairy breaking change, we might as well get them all over and done with.
Rater than try to follow the ~230 commits and slightly absurd number of new lines of code, let me give you an overview of the changes, then propose the things to review. I'll also make a bit of a simplified retrospective narrative that hopefully serves as some kind of justification for the pain.
There are a few things going on here:
Doctrine
src/julee/core/doctrine/tests, which can be run as a test suite runner and also via thejulee-admin doctrine verifycommand. These tests scan the codebase introspectively.The tests ARE the doctrine" - docstrings state rules, test assertions enforce them.
Semantic Relations
Note test_semantic_relation.py above. You can run
julee-admin doctrine show --area semantic -vto see those tests.This is all about the behavior of the
@semantic_relationdecorator, defined insrc/julee/core/decorators.py:545-614. This is a way of asserting a semantic relationship between one domain model entity and another (from a different bounded context). For example, see how the hcd/entities/story.pyWhat this means is that:
This goes to the bigger picture of the framework. The framework has a "core" bounded context that relates to implementation of every other thing in a julee solution. The code is bound to core through doctrine. But the domain of the framework is software engineering, so we have multiple "partial ontologies" for different "aspects" of software engineering. Currently that's only HCD and Architecture, but in the future it could be extended to Security, Maintenance, Support, Packaging, Deployment, etc. Those "framework bounded contexts" all have their own semantics which projects onto core, which means they can be projected onto implementation of other julee solutions. So, for a julee solution that is not the framework itself (e.g. Evil World Domination Corporation), it might have it's own bounded contexts (Extortion, Revenge, Warfare and Politics, Very Large Kites, etc). If those are compliant with doctrine, then they can utilise the various framework-provided software engineering views over their implementation.
The semantic relations are defined by an enum:
The implementation of these semantic relation decorators are currently used for
Sphinx App
The key concept here is that rst documentation can be considered "a database that renders to documents".
So, if our rst documentation is strongly typed with lots of custom directives, then we can treat our documentation like a normalised database. This means we can read and write to it with repository methods, so it can become part of our clean architecture. The whole "rendering to html/latexpdf/etc" bag of tricks that comes with sphinx is still available, but that's a side effect in the outside world, internally they are just documented domain objects persisted to disk.
We can also use the AST to parse the code, understand inheritance, parameter types, etc - that's also "just infrastructure" that implements a repository protocol that happens to knows about the code artefacts. So we can have a directive saying "let there be an application, because I have documented it" and we can have an actual application implementation that is also documented because it exists. The sphinx app can use both repositories to build a picture of everything that is built and has been conceived/documented (but not yet built) and render them both together in the one solution documentation.
This is a work in progress, but it's not-blocking (if the docs are good enough, ship them then refine and ship again).
generic_crud usecase generators
Clean Architecture has pros and cons. The pros include clarity - single responsibility, interfaces that protect from variation, etc. The cons include verbosity - more files/classes than would otherwise be used. The worst part was all the boilerplate CRUD usecases - {GET/UPDATE/CREATE/DELETE/LIST}{the entity}. So I cheated and made a factory method for generic crud usecases (
src/julee/core/use_cases/generic_crud.py).This has a couple of tricks. First, it uses the Repository protocol specification with a template to infer the properties of the Request and Response classes. Second, the (optional) "filters" property determines what filters can be applied to the list method. It has a few other tricks (like janky pluralisation guessing code that could probably be improved) but the upshot is that we can make a {bounded-context}.usecases.crud module cheaply that gives us the CRUD methods without having to think about it, and wecan wire them up to a REST API using similar patterns, and that side of things can go very fast.
Bounded contexts and reserved words.
The idea here is that every top level directory is a bounded context of the solution, except for a handful of magic names that have other meanings, unless it is a "nested solution" (a sub solution containing bounded contexts, and reserved words). The contrib/ module is one of those nested solutions.
They are documented in
src/julee/core/doctrine_constants.py, currently asapps/ contains apps. An app has dependencies on bounded contexts, but the BC has no idea what apps depend on it.. Apps are deployable/usable things.
deploy/ depends on apps. And so on - there is potential set of more onion rings of dependencies that have the software at the core, and supporting apparatus around it (maintenance, support, governance, etc). This is what the reserved words are really about.
There is some wooly thinking about if these things are framework bounded contexts, or if they are "outer architectural layers". Basically, apps need to be well organised code that imports and uses bounded contexts, and because one app may involve multiple bounded contexts, it has to be outside them for practical reasons. Similarly, Deployment may involve multiple apps, so they have to sit outside them too. It's a pragmatic distinction rather than theoretical one.
In summary, don't try to review the changes in this PR, it's too much. Look at the PR as a new, slightly radical change. Most of the disruptive changes relate to the application of doctrine. The other changes (sphinx docs, semantic relations) are the (hopefully) useful work that I was doing as I dogfooded the doctrine. The other thing to lok at, if you have access to it (sorry it's not a public repo) is the
modernise-solution-layoutbranch of pyx-labs. This is a multi-bounded context julee solutuion that has had the doctrine applied to it too. Iterating through the doctrine changes while using that migration target was very informative. I'm sure that we will continue to refine the doctrine as we migrate other solutions, but the current doctrine version has been shaken out already.Final Note - the differences between Repositories and Services
Doctrine development forced me to clarify this.
repositories and services both furnish interfaces (protocols) that deal in domain objects and/or primitive types.
usecases furnish interfaces that are request and response objects. They consume domain language interfaces of services and repositories that are injected at construction time.
A lot of the mind-bending changes during this big hairy refactor was clarifying this - redistributing responsibility between services, usecases and repositories so that the above was always true (doctrinal compliance). These might have been the most disruptive changes, but the end result is simpler and a better basis for going forward with more services and usecases.
Merge suggestion
This is a big breaking change. I propose we create a legacy branch for 0.1.x (old master) and maintain that as long as necessary, merge this into master, and increment the Major number on the release (0.2.0). Since we are not yet in 1.x.x I think we are allowed to make non-backwards compatible changes without incrementing the architecture number.
Timing of the merge should be after the other work has landed and been deployed, then the actual merge will be a bit hairy (bringing that new work into this branch, testing and fixing as we go, then flopping this on top in a big squash).