Slice isolation (TWA0009)
Slices are independently removable vertical units. Folders organize humans; the
namespace under SliceRoot is the law. Analyzer TWA0009 is the reactive safety net;
this skill is for correct first placement so greenfield work does not couple product
areas by accident.
Enforcement ships in TimeWarp.Architecture.Analyzers (and monorepo ProjectReference). Do not
re-interpret rules here — mirror the analyzer.
Detection — when to invoke
| Signal |
How to find it |
Product area under features/ |
SPA: web-spa/features/…; server product code: web/features/<slice>/ (axis-1 cohesive tree; layer projects glob *-{layer}.cs) |
| Namespace under SliceRoot |
…Features.<Id> (or nested …Features.Admin.Roles) |
TWA0009 / CrossSliceReference |
diagnostic text or attribute on a type |
| “Where does this page/state live?” |
any new product capability before scaffolding |
Terms
| Term |
Meaning |
| Slice |
Removable vertical capability; the isolation unit |
| Feature |
Informal product language only — not the analyzer’s unit name |
| Module |
IModule / host DI composition — not a product slice |
Prefer saying slice in diagnostics, opt-outs, and agent prose. Namespace path may still use
…Features.* (familiar VSA / contracts layout).
SliceRoot
| Setting |
Value |
| Default |
{RootNamespace}.Features (from MSBuild RootNamespace) |
| Override |
MSBuild property TimeWarpSliceRoot (must be CompilerVisibleProperty) |
Slice id = path under SliceRoot after stripping structural suffixes Pages, Components,
Application. Nested ids are the full path:
| Namespace |
Slice id / tier |
…Features |
Substrate (bare root) |
…Features.Applications |
Platform id Applications |
…Features.Counters |
Product id Counters |
…Features.Counters.Pages |
Product id Counters (suffix stripped) |
…Features.Admin.Roles |
Product id Admin.Roles |
…Components, layouts, app root types |
Outside SliceRoot |
Folders usually mirror slices (features/counter/ ↔ …Features.Counters) but moving a file
does not change legal dependencies — only the namespace does.
Tiers (dependency rules)
| Tier |
Example |
May reference |
| Outside |
…Components, shell, layouts |
Anything (composition free) |
| Substrate |
bare …Features (BaseComponent, shared base types) |
Outside, substrate, platform — not product without opt-out |
| Platform |
…Features.Applications |
Outside, substrate, platform — not product without opt-out |
| Product |
…Features.Counters, …Features.Admin.Roles |
Outside, substrate, platform, own product id, contracts (other assembly). Not another product id without opt-out |
Forbidden without opt-out (TWA0009):
- product A → product B (A ≠ B)
- platform → product
- substrate → product
Free by design:
- product → platform (
Applications) — e.g. CounterPage → ApplicationState
- any code → other assemblies (contracts are sharing-by-design)
- Outside → product (nav shell, shared components)
Placement matrix
| Artifact |
Where |
| Page, state, actions, slice-local UI |
Inside the product slice namespace |
App chrome / shell / empty MainLayout |
Outside SliceRoot (e.g. …Components) — see tw-blazor-layout |
| Shared UI used by multiple slices |
components/ → namespace outside SliceRoot |
| Shared API shapes |
Contracts project (other assembly; free under TWA0009) |
| Host-wide modal / menu / branding state |
Platform Applications |
Living anchors (timewarp-architecture template):
| Role |
Path |
Namespace |
| Product |
web-spa/features/counter/ |
…Features.Counters |
| Nested product |
web-spa/features/admin/roles/ |
…Features.Admin.Roles |
| Platform |
web-spa/features/application/ |
…Features.Applications |
| Substrate |
web-spa/features/base/base-component.cs |
…Features |
| Outside shell |
web-spa/components/TimeWarpPage.razor |
…Components |
Greenfield scaffold workflow
- Name the slice — plural, domain-oriented (
Clients, Admin.Roles).
- Folder — kebab path under the project’s
features/ tree (match sibling casing).
- SPA UI stays under
web-spa/features/<slice>/ (conventional; not rehomed by axis-1).
- Contracts / application / server product files live in the cohesive tree
web/features/<slice>/ with filename grammar <name>[-<function>]-<layer>.cs
(see tw-feature-placement for the full grammar and registry).
- Namespace —
{RootNamespace}.Features.{SliceId} (plural segments; nested with .).
Folders rehome freely; namespaces are not renamed with folder moves.
- Colocate page, state, actions, and slice-private components in that namespace (pages are
not shared infrastructure — no grab-bag
…Pages for product UI).
- Contracts — same plural slice path under
web/features/<slice>/ with -contracts.cs
suffix (tw-web-api-contracts skill). Other assembly → free across SPA slices.
- Wire nav / DI from Outside or platform — not from another product slice.
- Prefer share (Components / contracts) over
[CrossSliceReference].
- Build — fix TWA0009 by relocating code first; mute only for deliberate demo/integration edges.
Share vs opt-out
Prefer share
- Promote shared controls to
…Components.
- Share API shapes via contracts assembly.
- Depend product → platform without attributes when the dependency is real host state.
Opt-out — deliberate edges only
using TimeWarp.Foundation.Features;
[CrossSliceReference(typeof(CounterState), "Living style guide exercises the counter throw-exception pipeline.")]
partial class StyleGuidePage;
Rules:
- Attribute on the referencing type (
AllowMultiple = true for multiple foreign slices).
typeof(T) maps to the target product slice; only edges into that slice are suppressed.
- Reason must be non-empty (human paperwork).
- Partial-safe (semantic attributes).
- Type:
TimeWarp.Foundation.Features.CrossSliceReferenceAttribute (package
TimeWarp.Foundation.Contracts / monorepo foundation-contracts).
Living examples:
- Style guide → Counters:
web-spa/features/style-guide/pages/StyleGuidePage.razor.cs
- Auth pipeline deliberate edges: authentication code-behinds with multiple
[CrossSliceReference(...)] when needed
Limits agents must know
| Limit |
Implication |
| Same assembly only |
Contracts / NuGet metadata types never trip TWA0009 |
| Hand-written C# |
GeneratedCodeAnalysisFlags.None — pure .razor without .razor.cs is not scanned; put deliberate edges on code-behind |
| Folders can lie |
Namespace still owns the type |
| Structural suffixes |
…Counters.Pages is still product id Counters |
Good / bad examples
| Bad |
Why |
Fix |
…Features.Counters type uses WeatherForecastsState |
product → product |
Share via Components/contracts, or reasoned [CrossSliceReference] |
Product page in grab-bag …Pages outside any slice |
not removable with the capability |
Namespace under …Features.<Id> |
Platform type reaches into Counters |
platform ↛ product |
Invert dependency (product → platform) or rare opt-out on platform type |
| Mute TWA0009 globally / without reason |
hides real coupling |
Relocate or scoped opt-out |
| Good |
Why |
CounterPage uses ApplicationState |
product → platform free |
TimeWarpPage in …Components |
Outside composition free |
Admin.Roles only uses own types + contracts + platform |
nested product isolation |
Agent checklist
Removing a demo slice
Counter and EventStream ship in every generated app. They are teaching material, not
template flags — remove them by deleting code.
A slice is an independently removable vertical unit. Identity is the namespace under
{RootNamespace}.Features (e.g. …Features.Counters), not the folder path. The compiler
is the checklist: delete the slice folder, then fix every compile error until dev build
is 0/0.
Deleting a SPA demo touches, at minimum:
- The slice folder (
web-spa/features/counter/, web-spa/features/event-stream/).
components/NavMenu.razor — its nav link.
global-usings.cs / _Imports.razor — its namespace usings.
- Tests under
tests/container-apps/web/web-spa-integration-tests/ plus shared pipeline
tests that exercise its state (e.g. CloneStateBehavior uses CounterState).
- Cross-slice opt-outs that targeted it — e.g. Style Guide's
[CrossSliceReference(typeof(CounterState), …)].
SPA-only demos have no EF mapping. If you remove a mapped entity (one that participates
in PostgresDbContext / IEntityTypeConfiguration), delete the domain type, configuration,
and DbSet, then add an EF migration that drops the unused tables. Do not hand-edit only
the live database — AppHost AddEFMigrations applies committed migrations. See
tw-aggregate-pattern (schema evolution).
Related skills and pointers
tw-feature-placement — filename grammar and layer membership once you know which slice a
file belongs to (<name>[-<function>]-<layer>.cs, TWA0015/TWA0016, membership guard)
tw-web-api-contracts — contract placement; contracts assemblies are free under TWA0009; still use plural …Features.* aligned with SPA product slices
tw-blazor-layout — empty layout + shell; chrome outside SliceRoot
tw-blazor-css-strategy — shell/component styling only
tw-agent-context-regions — Purpose/Design on new files (TWA0004)
- AGENTS.md — TWA diagnostic table (row TWA0009)
- Analyzer (source of truth):
source/analyzers/timewarp-architecture-convention-analyzers/slice-isolation-analyzer.cs
- Opt-out attribute:
source/foundation/foundation-contracts/base/cross-slice-reference-attribute.cs
1---2name: tw-slice-isolation3description: **TIMEWARP SKILL** — product slice placement and TWA0009 isolation (SliceRoot, namespaces, platform Applications, Components/contracts sharing, CrossSliceReference opt-out). Invoke before scaffolding a new feature/slice/page or when fixing TWA0009 / cross-slice references. WHEN: Add a new clients feature page, Where does this state live?, TWA0009 slice references another product slice, CrossSliceReference, new product area under features/, greenfield slice scaffolding.4---56# Slice isolation (TWA0009)78Slices are **independently removable vertical units**. Folders organize humans; the9**namespace under SliceRoot is the law**. Analyzer **TWA0009** is the reactive safety net;10this skill is for **correct first placement** so greenfield work does not couple product11areas by accident.1213Enforcement ships in `TimeWarp.Architecture.Analyzers` (and monorepo ProjectReference). Do not14re-interpret rules here — mirror the analyzer.1516## Detection — when to invoke1718| Signal | How to find it |19|--------|----------------|20| Product area under `features/` | SPA: `web-spa/features/…`; server product code: `web/features/<slice>/` (axis-1 cohesive tree; layer projects glob `*-{layer}.cs`) |21| Namespace under SliceRoot | `…Features.<Id>` (or nested `…Features.Admin.Roles`) |22| TWA0009 / `CrossSliceReference` | diagnostic text or attribute on a type |23| “Where does this page/state live?” | any new product capability before scaffolding |2425## Terms2627| Term | Meaning |28|------|---------|29| **Slice** | Removable vertical capability; the isolation unit |30| **Feature** | Informal product language only — not the analyzer’s unit name |31| **Module** | `IModule` / host DI composition — **not** a product slice |3233Prefer saying **slice** in diagnostics, opt-outs, and agent prose. Namespace path may still use34`…Features.*` (familiar VSA / contracts layout).3536## SliceRoot3738| Setting | Value |39|---------|--------|40| Default | `{RootNamespace}.Features` (from MSBuild `RootNamespace`) |41| Override | MSBuild property `TimeWarpSliceRoot` (must be `CompilerVisibleProperty`) |4243**Slice id** = path under SliceRoot after stripping structural suffixes `Pages`, `Components`,44`Application`. Nested ids are the **full path**:4546| Namespace | Slice id / tier |47|-----------|-----------------|48| `…Features` | **Substrate** (bare root) |49| `…Features.Applications` | **Platform** id `Applications` |50| `…Features.Counters` | **Product** id `Counters` |51| `…Features.Counters.Pages` | **Product** id `Counters` (suffix stripped) |52| `…Features.Admin.Roles` | **Product** id `Admin.Roles` |53| `…Components`, layouts, app root types | **Outside** SliceRoot |5455Folders usually mirror slices (`features/counter/` ↔ `…Features.Counters`) but **moving a file56does not change legal dependencies** — only the namespace does.5758## Tiers (dependency rules)5960| Tier | Example | May reference |61|------|---------|---------------|62| **Outside** | `…Components`, shell, layouts | Anything (composition free) |63| **Substrate** | bare `…Features` (`BaseComponent`, shared base types) | Outside, substrate, platform — **not** product without opt-out |64| **Platform** | `…Features.Applications` | Outside, substrate, platform — **not** product without opt-out |65| **Product** | `…Features.Counters`, `…Features.Admin.Roles` | Outside, substrate, platform, **own** product id, contracts (other assembly). **Not** another product id without opt-out |6667**Forbidden without opt-out (TWA0009):**6869- product A → product B (A ≠ B)70- platform → product71- substrate → product7273**Free by design:**7475- product → platform (`Applications`) — e.g. `CounterPage` → `ApplicationState`76- any code → **other assemblies** (contracts are sharing-by-design)77- Outside → product (nav shell, shared components)7879## Placement matrix8081| Artifact | Where |82|----------|--------|83| Page, state, actions, slice-local UI | **Inside** the product slice namespace |84| App chrome / shell / empty `MainLayout` | **Outside** SliceRoot (e.g. `…Components`) — see `tw-blazor-layout` |85| Shared UI used by multiple slices | `components/` → namespace outside SliceRoot |86| Shared API shapes | **Contracts** project (other assembly; free under TWA0009) |87| Host-wide modal / menu / branding state | Platform `Applications` |8889Living anchors (timewarp-architecture template):9091| Role | Path | Namespace |92|------|------|-----------|93| Product | `web-spa/features/counter/` | `…Features.Counters` |94| Nested product | `web-spa/features/admin/roles/` | `…Features.Admin.Roles` |95| Platform | `web-spa/features/application/` | `…Features.Applications` |96| Substrate | `web-spa/features/base/base-component.cs` | `…Features` |97| Outside shell | `web-spa/components/TimeWarpPage.razor` | `…Components` |9899## Greenfield scaffold workflow1001011. **Name the slice** — plural, domain-oriented (`Clients`, `Admin.Roles`).1022. **Folder** — kebab path under the project’s `features/` tree (match sibling casing).103 - **SPA** UI stays under `web-spa/features/<slice>/` (conventional; not rehomed by axis-1).104 - **Contracts / application / server** product files live in the cohesive tree105 `web/features/<slice>/` with filename grammar `<name>[-<function>]-<layer>.cs`106 (see `tw-feature-placement` for the full grammar and registry).1073. **Namespace** — `{RootNamespace}.Features.{SliceId}` (plural segments; nested with `.`).108 Folders rehome freely; **namespaces are not renamed with folder moves**.1094. **Colocate** page, state, actions, and slice-private components in that namespace (pages are110 **not** shared infrastructure — no grab-bag `…Pages` for product UI).1115. **Contracts** — same plural slice path under `web/features/<slice>/` with `-contracts.cs`112 suffix (`tw-web-api-contracts` skill). Other assembly → free across SPA slices.1136. **Wire nav / DI** from Outside or platform — not from another product slice.1147. **Prefer share** (Components / contracts) over `[CrossSliceReference]`.1158. **Build** — fix TWA0009 by relocating code first; mute only for deliberate demo/integration edges.116117## Share vs opt-out118119### Prefer share120121- Promote shared controls to `…Components`.122- Share API shapes via contracts assembly.123- Depend product → platform without attributes when the dependency is real host state.124125### Opt-out — deliberate edges only126127```csharp128using TimeWarp.Foundation.Features;129130[CrossSliceReference(typeof(CounterState), "Living style guide exercises the counter throw-exception pipeline.")]131partial class StyleGuidePage;132```133134Rules:135136- Attribute on the **referencing** type (`AllowMultiple = true` for multiple foreign slices).137- `typeof(T)` maps to the **target product slice**; only edges into that slice are suppressed.138- Reason must be non-empty (human paperwork).139- Partial-safe (semantic attributes).140- Type: `TimeWarp.Foundation.Features.CrossSliceReferenceAttribute` (package141 `TimeWarp.Foundation.Contracts` / monorepo foundation-contracts).142143Living examples:144145- Style guide → Counters: `web-spa/features/style-guide/pages/StyleGuidePage.razor.cs`146- Auth pipeline deliberate edges: authentication code-behinds with multiple147 `[CrossSliceReference(...)]` when needed148149## Limits agents must know150151| Limit | Implication |152|-------|-------------|153| **Same assembly only** | Contracts / NuGet metadata types never trip TWA0009 |154| **Hand-written C#** | `GeneratedCodeAnalysisFlags.None` — pure `.razor` without `.razor.cs` is not scanned; put deliberate edges on code-behind |155| **Folders can lie** | Namespace still owns the type |156| **Structural suffixes** | `…Counters.Pages` is still product id `Counters` |157158## Good / bad examples159160| Bad | Why | Fix |161|-----|-----|-----|162| `…Features.Counters` type uses `WeatherForecastsState` | product → product | Share via Components/contracts, or reasoned `[CrossSliceReference]` |163| Product page in grab-bag `…Pages` outside any slice | not removable with the capability | Namespace under `…Features.<Id>` |164| Platform type reaches into `Counters` | platform ↛ product | Invert dependency (product → platform) or rare opt-out on platform type |165| Mute TWA0009 globally / without reason | hides real coupling | Relocate or scoped opt-out |166167| Good | Why |168|------|-----|169| `CounterPage` uses `ApplicationState` | product → platform free |170| `TimeWarpPage` in `…Components` | Outside composition free |171| `Admin.Roles` only uses own types + contracts + platform | nested product isolation |172173## Agent checklist174175- [ ] New capability has a slice id and matching `{Root}.Features.{Id}` namespace176- [ ] Page, state, and actions live in that namespace177- [ ] Shell/layout stay outside SliceRoot (or platform only when truly host state)178- [ ] Cross-slice data via Components or contracts first179- [ ] Any remaining edge has `[CrossSliceReference(typeof(T), "reason")]`180- [ ] No silent coupling or unexplained suppressions181182## Removing a demo slice183184Counter and EventStream ship in every generated app. They are teaching material, **not**185template flags — remove them by deleting code.186187A slice is an independently removable vertical unit. Identity is the namespace under188`{RootNamespace}.Features` (e.g. `…Features.Counters`), not the folder path. The compiler189is the checklist: delete the slice folder, then fix every compile error until `dev build`190is 0/0.191192Deleting a SPA demo touches, at minimum:1931941. The slice folder (`web-spa/features/counter/`, `web-spa/features/event-stream/`).1952. `components/NavMenu.razor` — its nav link.1963. `global-usings.cs` / `_Imports.razor` — its namespace usings.1974. Tests under `tests/container-apps/web/web-spa-integration-tests/` plus shared pipeline198 tests that exercise its state (e.g. `CloneStateBehavior` uses `CounterState`).1995. Cross-slice opt-outs that targeted it — e.g. Style Guide's200 `[CrossSliceReference(typeof(CounterState), …)]`.201202SPA-only demos have no EF mapping. If you remove a **mapped** entity (one that participates203in `PostgresDbContext` / `IEntityTypeConfiguration`), delete the domain type, configuration,204and `DbSet`, then add an EF migration that drops the unused tables. Do not hand-edit only205the live database — AppHost `AddEFMigrations` applies committed migrations. See206`tw-aggregate-pattern` (schema evolution).207208## Related skills and pointers209210- `tw-feature-placement` — filename grammar and layer membership once you know which slice a211 file belongs to (`<name>[-<function>]-<layer>.cs`, TWA0015/TWA0016, membership guard)212- `tw-web-api-contracts` — contract placement; **contracts assemblies are free** under TWA0009; still use plural `…Features.*` aligned with SPA product slices213- `tw-blazor-layout` — empty layout + shell; chrome **outside** SliceRoot214- `tw-blazor-css-strategy` — shell/component styling only215- `tw-agent-context-regions` — Purpose/Design on new files (TWA0004)216- **AGENTS.md** — TWA diagnostic table (row TWA0009)217- **Analyzer (source of truth):** `source/analyzers/timewarp-architecture-convention-analyzers/slice-isolation-analyzer.cs`218- **Opt-out attribute:** `source/foundation/foundation-contracts/base/cross-slice-reference-attribute.cs`