Task: B5: Implement supported/target set model for physical space binding
Table of Contents
This page documents a task in the Resolve codegen model unification blockers story. It captures the goal, current status, acceptance, and any notes or results.
Goal
Introduce the supported/target set model for physical-space generation, following the MASD variability convention used by Dogen. Physical space is a "set of sets": technical-space → facet → archetype. Two named sets govern generation for each run:
- Supported set (S_e) — what entity
ecan generate. Derived fromores.*properties in its:PROPERTIES:drawer (specificity-resolved) intersected with the model-types filter. Entities with noores.*properties: S_e = all archetypes matching the model-type (backward compatible with current behaviour). - Target set (T) — what to generate this run. Default: T = S_e. The CLI
--addressargument sets T = the set of all archetypes matching that address (hierarchically expanded from the facet catalogue).
What actually generates for entity e = T ∩ S_e.
Warning condition: T ∩ S_e = ∅ → emit warning "<entity>: nothing to generate for address
<addr>="; continue with other entities.
Error condition: --address value is not a valid address in the facet catalogue → error.
Supported set: ores.* property-drawer syntax
The address hierarchy is ores.{technical_space}[.{facet}[.{archetype}]].enabled:
| Address | Level | Example effect on S_e |
|---|---|---|
:ores.sql.enabled: false |
technical space | removes all SQL facets from S_e |
:ores.cpp.enabled: false |
technical space | removes all C++ facets from S_e |
:ores.cpp.qt.enabled: false |
facet | removes only qt from S_e |
:ores.cpp.domain.class_header.enabled: false |
archetype | removes one archetype from S_e |
Specificity resolution: more-specific address overrides less-specific. :ores.cpp.enabled:
true + :ores.cpp.qt.enabled: false → S_e includes all C++ facets except Qt. This replicates
Dogen's :masd.cpp.enabled: / :masd.csharp.enabled: semantics exactly.
Target set: CLI –address
# Generate everything the entity supports (T = S_e) ./projects/ores.codegen/codegen.sh generate --component refdata # Generate only the sql technical space (T = all sql archetypes) ./projects/ores.codegen/codegen.sh generate --component refdata --address sql # Generate only the qt facet (T = cpp.qt archetypes) ./projects/ores.codegen/codegen.sh generate --component refdata --address cpp.qt
--profile is removed. The entity's ores.* binding IS the profile; --address is only a
filter, never an expansion. Updating all existing compass commands and skills to remove
--profile invocations is in scope.
Technical-space to facet map
For hierarchical address resolution, the TS→facet map lives in a new * Technical spaces
section of facet_catalogue.org (single authoritative source):
| Technical space | Member facets |
|---|---|
sql |
sql, non-temporal-sql |
cpp |
domain, generator, repository, service, protocol, nats-eventing, nats-handler, qt, non-temporal-domain, non-temporal-repository |
Implementation notes
- Add
* Technical spacessection tofacet_catalogue.org. - Implement in a dedicated module (
codegen/physical_space.py):compute_supported_set(properties, facet_catalogue, model_type) -> frozenset[str]— resolvesores.*properties with specificity ordering into a set of facet names for this entity.compute_target_set(address, facet_catalogue) -> frozenset[str]— expands a CLI address to the matching set of facet names; raises on unknown address.resolve_generation_set(supported, target) -> frozenset[str]— returnssupported ∩ target; warns if the intersection is empty.
- Not inline in
core.pyorgenerator.py— keep generation-set logic independently testable. - Remove
--profilefrom the CLI arg parser; add--address(optional). - Scan and update all compass commands and skills that pass
--profile.
Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Resolve codegen model unification blockers |
| Now | Nothing — shipped via PR #1361 (merged 2026-06-28). |
| Waiting on | Nothing. (B1 landed first via PR #1355, 2026-06-27.) |
| Next | Follow-ups tracked as B9/B10/B11 (rename templates, retire facet_catalogue, migrate –profile callers). |
| Last touched | 2026-06-29 |
Acceptance
Supported-set resolution
- Entity with
:ores.cpp.enabled: false→ S_e excludes all C++ facets; SQL output only. - Entity with
:ores.cpp.qt.enabled: false→ S_e includes all C++ facets except Qt. - Combined:
:ores.cpp.enabled: true+:ores.cpp.qt.enabled: false→ S_e = all C++ except Qt (more-specific address wins). - Entity with no
ores.*properties → S_e = all archetypes matching model-types filter (backward-compatible; no regression).
Target-set and intersection
- Running with no
--address→ generates all of S_e (T = S_e). - Running
--address sql→ T = all sql facets; generates T ∩ S_e. - Running
--address cppfor entity with:ores.cpp.enabled: false→ T ∩ S_e = ∅; warning emitted; run continues for other entities. - Running
--address UNKNOWN→ error (unknown address not in facet catalogue).
CLI and skills
--profileargument is removed from the codegen CLI.- All existing compass commands and skills updated to remove
--profileinvocations. --address(optional) replaces--profileas the generation scope filter.
Facet catalogue
facet_catalogue.orghas a* Technical spacessection mapping each TS to its member facets; loaded bygenerator.pyat startup.
Isolation and testing
- All supported/target set logic lives in
codegen/physical_space.py— zero logic inline incore.pyorgenerator.py. - Unit tests cover every combination: no properties (backward compat), TS-level disable,
facet-level disable, combined TS-enable + facet-disable override, target ⊆ supported
(normal filter), target ∩ supported = ∅ (warn path), target completely outside supported
(warn), unknown
--address(error path). Test file:tests/test_physical_space.py. - All existing regression tests pass.
Plan
Design decisions taken (2026-06-27):
- Doc-per-node graph — one org document per node at every level: a root
(
ores.org), one per technical space (ores.cpp.org), one per facet (ores.cpp.qt.org), one per archetype (ores.cpp.qt.controller.org). Each doc links down to its children (a table ofid:links) and up to its parent. The monolithicfacet_catalogue.org(all facets as headings in one file) is wrong and gets decomposed into these per-node docs, then retired. Likewise the duplicated literatefacet=/=facet_groupdocs collapse into this single graph. - Scaffold every node via compass — never hand-write these org files.
doc_generate.pygrows the doc types needed:technical_space(lists its facets) andarchetype(the leaf);facetalready exists (it lists its archetypes); the rootores.org. Any doc-template gap is fixed in compass, not worked around.
Codegen traverses this doc graph: root → technical space → facet →
archetype, every edge an org-roam id: link, with each node linking back up
to its parent.
Core invariant — node name = address = drawer key, normalised with the
ores root prefix everywhere (node names, --address values, and
:PROPERTIES: drawer keys are the identical string):
| Level | Node name / address / drawer key |
|---|---|
| root (general TS index) | ores |
| technical space | ores.cpp / ores.sql |
| facet | ores.cpp.qt / ores.sql.schema |
| archetype | ores.cpp.qt.controller / ores.sql.schema.create |
The ores root node IS the general technical-spaces index page; it links to
each technical space. :ores.cpp.qt.enabled: false disables a facet and
:ores.cpp.qt.controller.enabled: false disables one archetype, by pure
name match — no separate address→node lookup table. --address ores.cpp.qt
uses the same string.
Archetype naming follows the same standard convention (matching Dogen's
masd.cpp.types.class_header): one archetype per output artefact, so a
header and its implementation are two distinct archetypes, each with a clean
descriptive leaf — never the raw filename, never an _hpp=/=_cpp suffix.
E.g. cpp_domain_type_class.hpp.mustache → archetype
ores.cpp.domain.class_header; ..._entity.cpp.mustache →
ores.cpp.domain.entity_implementation. The template filename is an
attribute of the archetype node, not its identity. A standard
filename→leaf mapping is derived once in Stage 4 and applied across all
~106 archetypes.
Node set (each its own document, name = address):
- Technical spaces:
ores.cpp,ores.sql,ores.doc(ores.cmake=/ =ores.assetsfold in later). Each lists its facets.ores.docis the documentation technical space — the doc-generation system is just its slice of the same graph. Its archetypes are thedoc_*.org.mustachetemplates (doc_facet,doc_knowledge,doc_technical_space,doc_archetype, the agile/task/story scaffolds, …), grouped into doc facets. Self-hosting: the templates that scaffold technical-space / facet / archetype nodes are themselves archetypes inores.doc. The literatedoc.orgfacet +doc_group.orgare its seed.
- Facets (TS-qualified names): each lists its archetypes.
ores.sql→ores.sql.schema(schema create/drop/trigger — what the monolith mislabels** sql :facet:),ores.sql.populate,ores.sql.service,ores.sql.non-temporal.ores.cpp→ores.cpp.domain,ores.cpp.generator,ores.cpp.repository,ores.cpp.service,ores.cpp.protocol,ores.cpp.nats-eventing,ores.cpp.nats-handler,ores.cpp.qt,ores.cpp.non-temporal-domain,ores.cpp.non-temporal-repository,ores.cpp.enum,ores.cpp.field-group.
- Archetypes: one per output artefact (the existing
templates/*.mustache.orgliterate docs), retypedarchetypeand addressedores.{ts}.{facet}.{leaf}.
NB: the old B5 spec's TS→facet map (sql → {sql, non-temporal-sql}) was
imprecise — sql is the technical space, its create facet is ores.sql.schema.
Stage 3c decisions (2026-06-27):
- Scope: the ~50 entity-facet templates (15 facets). Accurate counts — domain 7, qt 12, repository 6, non-temporal-repository 6, sql.schema 7, generator 2, service 2, non-temporal-domain 3, and singletons.
- Shared archetypes:
json_io.hpp/.cppare duplicated per facet —ores.cpp.domain.json_io_*ANDores.cpp.non-temporal-domain.json_io_*, each owning its own node tangling the same source. - plantuml: its own technical space
ores.plantuml(not underores.doc);plantuml_er→ an archetype under anores.plantumlfacet. - Component-scaffolding archetypes (
cmake_*,cpp_component_*,cpp_service_app_*,sql_service_*,shell_*, the* Component archetypes) are deferred to a follow-up task — component-scope, driven by other commands, not needed for B5's per-entity--addressmodel. - Mustache filenames match the address:
ores.cpp.domain.class_header→ores_cpp_domain_class_header.mustache(dots→underscores). Renamed in Stage 5 (atomically with the:tangletargets,core.pyoutput-path map, and generator lookup) to avoid a broken intermediate.
Stages (commit each):
- ✓ Link the 106 archetype cells in
facet_catalogue.orgto their archetype docs; parser strips the link to the bare template name. (Interim — the monolith is decomposed away in later stages.) - Extend compass: add
technical_space+archetypedoc types todoc_generate.py(templates + registration +compass addhelp). - Scaffold the node docs via
compass add: rootores.org, the technical spaces (ores.cpp,ores.sql), the facets, retype the archetype docs. Wire the graph by automation, never by hand. Up-links are frontmatter parent keywords set once at scaffold time (
#+facet_group:on a facet, new#+facet:on an archetype; technical spaces are discovered by type). Down-link child tables are generated: extend the existingscripts/regenerate_facet_inventories.pyinto a graph-wide inventory generator that rewrites, from the doc frontmatter:ores.org* Technical spaces← all#+type: technical_spacedocs- each TS
* Facets←#+type: facetwith matching#+facet_group: - each facet
* Archetypes←#+type: archetypewith matching#+facet:
Keep the
--checkCI gate. Migrate facet/archetype content out of the monolith + duplicated literatefacet=/=facet_groupdocs; retire them.- Rewire codegen to traverse the doc graph (root → TS → facet → archetype)
instead of reading
facet_catalogue.org; implementcodegen/physical_space.py(compute_supported_set/compute_target_set/resolve_generation_set). (The.mustacherename to address form is deferred to task B9 — it is cosmetic, touches the legacyget_schema_template_mappingspath, and is safer as a self-contained change. The graph uses the:tangleindirection, so filenames need not change for the rewire to work.) 5f — functional retirement done: nothing readsfacet_catalogue.orgfor generation any more (load_profiles=/=resolve_profile_templateshave no callers — dead code). The physical deletion of the monolith + 18 legacy literate facet/group docs + dead code + repointing the hundreds of inboundid:links (56 to the monolith alone) is deferred to task B10 — a delicate graph-wide link migration best done on its own. - Remove
--profile; add--address— –address added, –profile kept as a deprecated shim (5e). Updating all compass commands + skills off--profileis the remaining part of stage 6. tests/test_physical_space.pycovering every supported/target combination.
Merge gate (decided 2026-06-28): do NOT merge this branch until all of
B8, B9, B10, B11 are complete and the in-flight commissions are off
--profile. Merging B5 alone would leave main in a half-migrated state
(stale facet_catalogue.org monolith + duplicated legacy docs + dead
load_profiles code + mixed --profile=/–address= callers). The whole
physical-space migration lands as one coherent change.
Mitigation for the long-held branch: rebase on origin/main periodically
(the commissions are actively changing codegen) to bound rebase drift.
Notes
Replaces the originally planned B5 (#+sql_only: true) and B6 (#+has_qt: false) tasks.
See Applied MASD and ORE Studio Variability Model for the full design.
PRs
Review
Round 1 (PR #1361, @claude, 2026-06-28):
| # | Comment summary | File | Decision | Notes |
|---|---|---|---|---|
| P1 | facets_under("ores") dead code — –address ores returned 0 on live graph | physical_space.py | Accepted | Fixed c8bf447c5: root check moved first |
| P2 | No live-graph test for facets_under("ores") | test_physical_space.py | Accepted | Fixed c8bf447c5: added regression test (16 tests) |
| m1 | Silent skip of archetypes with empty template/output or bad pattern | generate.py | Accepted | Fixed b32c04df1: log.debug on both skip paths |
| m2 | –profile deprecation silent at runtime | generate.py | Accepted | Fixed b32c04df1: runtime warning naming the –address equivalent |
| m3 | Keyword regex inconsistent with inventory script | physical_space.py | Accepted | Fixed b32c04df1: unified to \w[\w-]* |
| m4 | Facets without model_types implicitly universal | facet docs | Deferred | Empty facets own no archetypes; B8 adds model_types when populating |
Round 2 (PR #1374 — bookkeeping reconciliation, @claude, 2026-06-29):
| # | Comment summary | File | Decision | Notes |
|---|---|---|---|---|
| 1 | "Future" date 2026-06-30 in verify_codegen | task_verify_codegen.org / story.org | Declined | Accurate — that task was reconciled after midnight (journal 2026-06-30 00:00); B5/qt-upgrade fixes were 2026-06-29 |
| 2 | Story-row description still ABANDONED-language after DONE | commission_country/story.org | Accepted | Fixed 69c48269d: reworded to "goal met via unified-model migration tasks; see Result" |
| 3 | #+environment merry_newton→prime_origin not noted | b5 / qt-upgrade scaffold tasks | Acknowledged | Auto-stamped by compass task done from the running environment; expected noise |
Result
Shipped via PR #1361 (merged 2026-06-28, merge commit 65eaed5d3), with B1
(PR #1355) landing first as required. The original merge gate ("hold until
B8–B11") was lifted and B5 landed on its own; the remaining migration work is
tracked as separate backlog tasks (B9 template rename, B10 facet_catalogue
retirement, B11 –profile caller migration).
What landed on main:
codegen/physical_space.py— the isolated supported/target set engine:compute_supported_set(resolvesores.*drawer properties with specificity ordering),compute_target_set(expands a--addressto its facet set),resolve_generation_set(= supported ∩ target, warns on empty).- The per-node doc graph:
ores.orgroot → technical-space docs (ores.cpp.org,ores.sql.org) → facet docs → archetype docs (85archetype-typed + 11technical_space-typed docs), each linked via org-roamid:up/down edges; codegen traverses this graph instead of the monolith. Node name = address = drawer key,ores-prefixed throughout. - CLI:
--addressadded (optional filter, defaults to the entity's supported set);--profileretained as a deprecated runtime-warning shim (full removal deferred to B11). tests/test_physical_space.py— covers backward-compat (no properties), TS-level disable, facet-level disable, combined override, normal filter, empty-intersection warn, and unknown-address error (16 tests).
Review round 1 (6 comments) addressed in c8bf447c5 / b32c04df1; one item
(m4) deferred to B8. Acceptance criteria for supported/target resolution,
intersection, CLI, facet catalogue, and isolation/testing all met.
Bookkeeping note: this task's state was corrected to DONE on 2026-06-29 — the PR merged 2026-06-28 but the sprint health review updated only B1, leaving B5 showing STARTED. No code change; bookkeeping reconciliation only.