Skip to content

48 — Configuration across the hierarchy — define-at vs apply-at, cascade & override (envisioning)

Consolidation / envisioning doc. Governed by 00-ROADMAP.mddefer to it on conflict. Created 2026-06-29 (owner-raised, after the doc-46 workflow editor landed): "still unsure if the workspace can be applied to different dimensions of the hierarchy (Roster / Artist / Project / Track / Variant)… and the LEGO software — how would it apply in Portals, Browse, and Manage." This doc recognises both uncertainties as one theme and gives it a single model.

2 decisions locked with the owner (2026-06-29):

  1. Envision first (this doc), then build — matching how doc-47 was done.
  2. Priority = the studio-wide workflow cascade — a process defined once above the project that projects inherit by default. So §5 is concrete/buildable; attributes (LEGO) and per-variant are framed here but defer their depth to doc-43 / doc-46.

▶ PRIORITY BACKEND BUILT 2026-06-29 — the studio-wide workflow cascade ships (§5): new workspace module (Workspace doc keyed by owner — the doc-44 container seed — with defaultWorkflowTemplateId; GET /workspace, PATCH /workspace/default-workflow), and ProjectService.resolveWorkflowStructure walks project.workflow ▸ owner studio default ▸ system so an un-instantiated project inherits the studio process with zero setup. release-readiness uses it. nest build + jest 75/75 + workflow e2e 5/5 (incl. a cascade test). The cascade also drives mgmt.ctrl Board/Matrix/Overview readiness. §8 Q1+Q4 resolved by owner: workspace/owner default (interim-store on the per-owner Workspace doc, forward-compatible with doc-44). iOS surface shipped 2026-06-29 (xcodebuild green): Configure ▸ Workflow "Set as studio default" + STUDIO DEFAULT badge; Project Portal shows inherited-vs-custom source + Reset to studio default (DELETE /project/:id/workflow; release-readiness now returns workflowSource). The studio-default loop is end-to-end. Remaining: Artist override (rung 2) · attributes on the same rails (doc-43). See CHANGELOG.md.

Needs confirmation (remaining §8 open questions: cascade depth · override granularity).


1. The realisation: two questions, one shape

The owner asked two things that look separate but are the same problem:

  • "Can a workflow apply at different levels — Roster / Artist / Project / Track / Variant?" (doc-46)
  • "How does the LEGO (configurable attributes) apply across Portals / Browse / Manage?" (doc-43)

Both are: "a configurable thing, defined at one level of the hierarchy, applied (and overridable) at others, and surfaced consistently everywhere." Workflows and attributes are two instances of the same define-at / apply-at / cascade abstraction. Solve it once; both inherit it.

2. The hierarchy (what the dimensions actually are)

Workspace (owner / tenant — doc-44; no entity yet, owner is the de-facto root)
  └─ ArtistSpace            ← carries an optional `roster` { name, color } (a TAG, doc-26)
       └─ Project           ← the release container; holds the workflow INSTANCE today
            └─ Track         ← a recording; carries readiness map + `variantType` (doc-28)
                 └─ Variant  ← NOT a child entity — it's `Track.variantType` + relations (doc-28)

Two nuances that shape everything below:

  • Roster is not a container, it's a tag + lens (ArtistSpace.roster, doc-26). "Apply at the roster level" = "apply to every ArtistSpace whose roster = X" — a query/scope, not a parent pointer.
  • Variant is not a node, it's an attribute of a Track (variantType, doc-28). "Per-variant" = a predicate on the track, not a separate place in the tree.

So the real container ladder is Workspace → ArtistSpace → Project → Track; Roster and Variant are scopes/predicates layered on top.

3. Current state (verified against the schema, 2026-06-29)

DimensionWorkflow todayAttributes/LEGO today
Workspace/ownerworkflow templates are owner-scoped (WorkflowTemplate.owner) — authored here, but not auto-appliednone — forbidNonWhitelisted rejects unknown fields
ArtistSpacenonenone (only roster tag + lifecycle statusCode)
Projectthe only anchor — embedded Project.workflow {stages}, instantiated via POST /project/:id/workflow/instantiate; rollup in release-readinessnone
Tracksteps can be level: 'track' inside the project workflow (roll up across tracks) — but no track-OWNED workflowTrack.readiness map (per-step status) + variantType
Variantnone (appliesToVariants not built)variantType exists (doc-28)

Takeaway: authoring is workspace-scoped (templates), but application is Project-only. There is no cascade — every project must be instantiated individually. That's exactly the gap the owner felt.

4. The model: define-at · apply-at · resolve (cascade + override)

Three orthogonal ideas — keep them separate:

  1. Define-atwhere a config is authored/stored. Workflow templates are defined at the Workspace (owner). Attribute definitions (doc-43) would be defined at the Workspace too (per-workspace catalog).

  2. Apply-atwhere a config is attached as the default. A default workflow could be attached at Workspace, ArtistSpace, or Project. Today only Project.

  3. Resolvewhat actually governs a given node, by walking most-specific-wins up the container ladder, then falling back:

    resolveWorkflow(project) =
         project.workflow                        // explicit instance (most specific)
      ?? project.artistSpace.defaultWorkflow      // studio/label default for this artist
      ?? workspace(owner).defaultWorkflow         // the studio-wide default  ← the priority
      ?? SYSTEM_DEFAULT                            // shipped "Studio Release"

    This is the same cascade-with-override rule already used for permissions (doc-44: most-specific scope wins → deny>allow) and lifecycle status (doc-41). One mental model across the platform. Default = inherit; override is explicit and local.

Per-variant (doc-46 appliesToVariants) is an apply-at predicate, not another ladder rung: a step (or a whole sub-process) is tagged with the variant types it applies to; resolution filters steps by the track's variantType. Layered on top of the ladder above.

5. Priority build — the studio-wide workflow cascade

The owner's #1: "my studio works this way" — author the process once, every project inherits it.

What's needed (small, leverages everything built):

  • A default-workflow pointer at the studio level. Two honest options for where (§8 Q1):
    • (A) Workspace/owner default — the cleanest match (templates are already owner-scoped). Needs a home for "owner's default workflow templateId" (a Workspace doc — doc-44 wants one — or interim on the user /settings). One default per studio.
    • (B) ArtistSpace defaultArtistSpace.defaultWorkflowTemplateId. Lets a multi-label operator run different processes per artist/label. More flexible, more to manage.
    • Recommendation: ship (A) workspace default first (covers "my studio works this way"), add (B) artist override as the next rung — the resolver (§4) already accommodates both.
  • resolveWorkflow(project) server-side (the cascade above) — and release-readiness uses it when a project has no explicit project.workflow, instead of falling straight to the system default. So a brand-new project already reflects the studio default with zero per-project setup.
  • Instantiate semantics unchanged: applying a template on a project still writes project.workflow (an explicit local override). New verb: "set as studio default" (and later "set as this artist's default") in Configure ▸ Workflow.

iOS surfacing (after backend):

  • Configure ▸ Workflow — mark a template "Studio default" (a badge + a "Make default" action).
  • Project Portal — the pipeline shows "Inherited from studio default" vs "Custom for this project", with "Customise" (instantiate → local override) and "Reset to studio default" (clear project.workflow).

This is backend-first, then a thin iOS surface — and it reuses the doc-46 pipeline + the Configure editor already shipped.

6. Attributes (LEGO) ride the same rails — defer depth to doc-43

The configurable-attribute engine (doc-43) is the same define-at/apply-at/resolve pattern, with values instead of process:

  • Define-at — a per-Workspace AttributeDefinition catalog (doc-43): name, type, and which levels it attaches to (Project-only? Track-only? any?).
  • Apply-at — a typed attributes bag on the entity (Project/Track/…); some attributes may inherit down (e.g. a Project's "Genre" defaulting onto its Tracks) using the §4 resolver.
  • Resolve — same most-specific-wins walk.

doc-48 does not design the attribute engine (that's doc-43) — it just asserts attributes must use this shared cascade so workflow and LEGO behave identically. The Configure "Attributes" / "Workspace" Soon bubbles are where define-at lives; §7 is where apply-at shows up.

7. Surfacing — how config + attributes appear in the three zones (doc-47)

The owner asked specifically how this shows in Portals / Browse / Manage. One rule per zone:

ZoneHow configuration/attributes surface
Browse — Portals (per node)each Portal renders the node's resolved config: workflow pipeline (doc-46, shipped) + an Attributes section (doc-43 values), each marked inherited vs overridden here. The Portal is where you override locally.
Browse — grid/lensesgroup-by / filter on attributes + workflow state (doc-26 lenses + doc-40) — "show projects in Mastering stage", "group by Genre".
Manage — x-ray (doc-42)attributes become matrix columns + filters; workflow stage becomes a cross-catalog status axis. The eagle-view reads the resolved value at every node. This is where the "Soon" bubbles ultimately pay off.

Define-at lives in Configure (the catalog/editors); apply-at + view live in Browse/Manage. Clean split, consistent with doc-47.

Updated by doc-54 (2026-07-04): Configure onboards progressively — config surfaces appear as they become relevant (doc-47 §7 principle 7); workspaces and portals start at the preset minimum ("starting portals with almost nothing") and grow user-friendly. The cascade above is why zero-setup already works: the preset minimum is always in force; Configure is where you change it, never a prerequisite. Architecturally present from the beginning, shipped when ready.

8. Open questions (confirm before building §5)

  1. Where does the studio default live? Workspace/owner doc (cleanest; needs the doc-44 Workspace container — or an interim store) vs ArtistSpace field (more flexible). Lean: Workspace default first, Artist override next.
  2. How deep does cascade go? Workspace → Artist → Project is clearly useful. Do we also want a Roster-scoped default (apply to all artists with roster = X — a query-based rule, heavier)? Lean: no for v1 — roster stays a lens, not a config anchor.
  3. Override granularity — is "explicit project.workflow overrides inherited" enough, or do we need partial overrides (inherit the studio process but tweak two steps on this project)? Lean: full override first (simplest); partial later.
  4. Does the Workspace container get built now (doc-44) or do we interim-store the studio default on the owner/user until then? (Decides §5 effort.)
  5. Per-variant — confirm it's an apply-at predicate (appliesToVariants on steps), filtered at resolve — not a separate workflow per variant. (doc-46.)

9. Sequencing (after confirmation)

  1. Confirm §8 (esp. Q1 + Q4 — where the default lives).
  2. Backend: studio-default pointer + resolveWorkflow(project) cascade + release-readiness uses it for un-instantiated projects. (+ "set as studio default" verb.)
  3. iOS: "Studio default" badge/action in Configure ▸ Workflow; "Inherited vs Custom / Reset" on the Project Portal.
  4. Then: Artist-level override (rung B) · per-variant predicate (doc-46) · the LEGO attribute engine on these same rails (doc-43) · Manage columns/filters from attributes (doc-42).

Nothing here changes the MVP loop (doc-19); it makes the configuration the owner already authored actually propagate, and gives attributes a home to land on.

10. Cross-references

Ctrl-Audio Platform Documentation