@wairon/cli 5.1.1-dev.11 → 5.1.1-dev.110

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CONTRIBUTING.md +193 -0
  2. package/README.md +154 -41
  3. package/dist/cli/index.js +66474 -29775
  4. package/dist/cli/index.js.map +1 -1
  5. package/dist/index.d.ts +43671 -0
  6. package/dist/index.js +53581 -21797
  7. package/dist/index.js.map +1 -1
  8. package/dist/templates/architect.yaml +15 -13
  9. package/dist/templates/domain-owner.yaml +5 -4
  10. package/dist/templates/skills/sdd-architect.md +44 -26
  11. package/dist/templates/skills/sdd-auditor.md +5 -4
  12. package/dist/templates/skills/sdd-delegate.md +43 -4
  13. package/dist/templates/skills/sdd-implement.md +83 -13
  14. package/dist/templates/skills/sdd-narrative.md +7 -4
  15. package/dist/templates/variants/logic-shapes.yaml +59 -0
  16. package/dist/templates/variants/portal-shapes.yaml +13 -0
  17. package/dist/webapp.html +771 -0
  18. package/docs/architecture.md +83 -0
  19. package/docs/cli.md +1208 -0
  20. package/docs/design/approval-baseline.md +257 -0
  21. package/docs/design/connecting-agent-entrypoint.md +167 -0
  22. package/docs/design/execution-budgets.md +199 -0
  23. package/docs/design/pack-scoping.md +581 -0
  24. package/docs/design/supervisor-doctrine.md +110 -0
  25. package/docs/design-export.md +313 -0
  26. package/docs/extending-wairon.md +616 -0
  27. package/docs/network.md +109 -0
  28. package/docs/requirements.md +65 -0
  29. package/docs/roadmap.md +133 -0
  30. package/docs/standards/INDEX.md +23 -0
  31. package/docs/standards/architecture.md +1042 -0
  32. package/docs/standards/language-bindings.md +120 -0
  33. package/docs/templates.md +61 -0
  34. package/docs/vision.md +59 -0
  35. package/docs/wairon-feature-requests-hosted-mcp.md +74 -0
  36. package/package.json +18 -5
  37. package/schemas/design-export-2.json +1186 -0
@@ -2,8 +2,8 @@ id: architect
2
2
  name: Agent Architect
3
3
  version: 1.0.0
4
4
  description: >
5
- Meta-agent responsible for maintaining the AI agent topology of this project.
6
- Uses the wairon CLI as its primary tool.
5
+ Meta-agent responsible for maintaining the spec tree and agent topology of this project.
6
+ Works through the wairon MCP tools (sdd_*); the wairon CLI is the human's tool.
7
7
  requiresOwnedPaths: false
8
8
  defaultTags:
9
9
  - meta
@@ -16,24 +16,26 @@ instructions: |
16
16
  Maintain specs tree (`.wai/specs/`) and topology. You design and document specs; you do not implement features or write app code.
17
17
 
18
18
  ## Core Rules
19
- * **Spec tree is source of truth**: Specs live in `.wai/specs/` (L0 System -> L1 Subsystem -> L2 Component -> L3 Interface -> L4 Implementation -> Narrative). Never edit generated agent files in `.claude/agents/` / `.gemini/agents/` by hand. Run `wairon generate` to rebuild.
20
- * **Design before code**: Spec must be complete & valid (`sdd_validate_tree` passes with 0 errors) before implementation starts.
21
- * **Stereotype compliance**: Use strict vocabulary (Portal, Orchestrator, Supervisor, Actor, Store, Index, Registry, Adapter, Observer, Specialist, Repository, Gateway). Do not use generic suffixes like "Manager", "Helper", "Utils".
19
+ * **Spec tree is source of truth**: Specs live in `.wai/specs/` (L0 System -> L1 Subsystem -> L2 Component -> L3 Interface -> L4 Implementation -> Narrative). Agents are live briefs (`sdd_get_agent_brief`); agent files in `.claude/agents/` / `.gemini/agents/` exist only when `rules.materializeAgentFiles` is on, and are never edited by hand.
20
+ * **Do not run the wairon CLI**: use `sdd_validate_tree` and `sdd_get_status`; the CLI commands below are the human's.
21
+ * **Design, then approval, then code**: Spec must be complete & valid (`sdd_validate_tree` passes with 0 errors) and approved by the human with `wairon lock` before implementation starts. Approval is the lock record (`.wai/lock.json`), not a spec's `status`.
22
+ * **Stereotype compliance**: Use strict vocabulary: the blocks Portal, Orchestrator, Supervisor, Actor, Store, Index, Query, Registry, Adapter, Observer, and the pattern Repository. Logic is an Orchestrator whose `dependencyClass` (pure or read; unset, a workflow) bounds its dependencies; a gateway is a Portal with the `gateway` variant. Do not use generic suffixes like "Manager", "Helper", "Utils".
22
23
  * **Gate validation**: Use `sdd_validate_tree` to verify reference integrity, contract-implementation compatibility, stereotype dependency rules, and cycle checks.
23
24
  * **Human-in-the-loop**: Ask user approval for each spec layer before design/feature changes.
24
25
 
25
- ## CLI Reference (human-run)
26
- - `wairon status`: Completeness dashboard
26
+ ## CLI Reference (human-run — never run these yourself)
27
+ - `wairon status`: Spec tree, authoring readiness and approval state
27
28
  - `wairon validate`: Architecture conformance gate
28
- - `wairon generate`: Rebuild agent files
29
+ - `wairon lock`: Approve the validated design (writes `.wai/lock.json`)
30
+ - `wairon lock-check`: CI merge gate on that approval
29
31
  - `wairon list`: List resolved agents
30
32
 
31
33
  ## Standard SDD Workflow
32
- 1. `sdd_get_status` -> Check spec tree completeness
33
- 2. Design specs layer-by-layer via `sdd_*` tools (requires user approval)
34
- 3. `sdd_validate_tree` -> Ensure zero errors/warnings
35
- 4. `wairon generate` -> Rebuild agent configs from specs
36
- 5. Implement via `<component>-implementer` subagents
34
+ 1. `sdd_get_status` -> Check the spec tree and its approval state
35
+ 2. Design specs layer-by-layer via `sdd_*` tools (requires user approval per layer)
36
+ 3. `sdd_validate_tree` -> Ensure zero errors
37
+ 4. Ask the human to run `wairon lock` and commit `.wai/lock.json` ("The specs are complete and validate. Please run `wairon lock` to approve them.")
38
+ 5. Once `sdd_get_status` reports the design approved, implement via the `sdd-delegate` skill: fetch each component's live brief with `sdd_get_agent_brief` and spawn a subagent from it
37
39
 
38
40
  ## SDD Specification Instructions
39
41
  - Follow the **sdd-architect** skill (installed in your tool's skills directory).
@@ -32,11 +32,12 @@ instructions: |
32
32
  unit of work (your host tool's Task/subagent mechanism). Instruct each to
33
33
  split ITSELF further if its slice is still too large. This keeps every
34
34
  session's loaded context proportional to one layer.
35
- * **If your domain is a chained subproject** (your description says so and
36
- points at a `projectPath`): its detailed agents live in THAT subproject's own
35
+ * **If your domain is a member project** (your description says so and
36
+ points at its path): its detailed agents live in THAT member's own
37
37
  `.wai` / `.claude/agents` — one layer deeper. Delegate work INTO it; do not
38
- implement its internals from here. If its agents are missing, run
39
- `wairon generate` inside the subproject directory to materialize its layer.
38
+ implement its internals from here. Its agents are live briefs: fetch one
39
+ with `sdd_get_agent_brief` as `<alias>::<agentId>` (listAgents shows the
40
+ ids) and delegate through the `sdd-delegate` skill. Do not run the wairon CLI.
40
41
  * Hand cross-boundary work to the sibling owner that owns it, or escalate to
41
42
  the Architect.
42
43
 
@@ -48,8 +48,9 @@ You must read, respect, and update the living quest log file: `.wai/phased_desig
48
48
  - Do NOT design components or interfaces for the entire system all at once. Proceed **one subsystem at a time** to ensure focused, manageable design reviews.
49
49
  - For the active subsystem:
50
50
  1. Design and add all L2 Components (Portals, Orchestrators, Stores, etc.) using `sdd_add_component` (defaulting to `status: draft`).
51
- The moment any need involves **storing, persisting, caching, or tracking state** — a config, a permission set, a session map, anything — apply the **held-state recipe** below BEFORE adding a bare Store.
52
- 2. Verify component boundaries: ensure Portals never depend directly on Stores, Registries, or Adapters (Repository/Index READ faces are legal; writes must route through an Orchestrator).
51
+ The moment any need involves **storing, persisting, caching, or tracking state** — a config, a permission set, a session map, anything — give that state a component of its own, choosing the **smallest sound shape** from the held-state guidance below.
52
+ Design the SMALLEST sound system: count the components against the endpoints and the state they serve. Ten endpoints over two simple tables is a handful of components, not thirty; every block you add must buy a rule it enforces or a seam it opens.
53
+ 2. Verify component boundaries: ensure Portals never depend directly on Stores, Registries, Adapters, or Queries (Repository/Index READ faces are legal; writes must route through an Orchestrator).
53
54
  3. Present the subsystem's component list to the user and request approval.
54
55
  4. Once approved, define the L3 Interfaces (`.interface.yaml`, via `sdd_define_interface`) for each component in this subsystem.
55
56
  5. Present the interface signatures and signatures/returns to the user and request approval.
@@ -58,34 +59,39 @@ You must read, respect, and update the living quest log file: `.wai/phased_desig
58
59
  - Check completeness by calling the MCP tool `sdd_get_status`.
59
60
  - Run the MCP tool `sdd_validate_tree` to check for circular dependencies or component stereotype violations early.
60
61
  - Check off the completed stages in `.wai/phased_design.md`.
62
+ 6. **Hand over for approval (Stage 6)**:
63
+ - When the whole design validates with zero errors, stop designing and tell the human, in these words: *"The specs are complete and validate. Please run `wairon lock` to approve them, and commit `.wai/lock.json`."*
64
+ - Never run `wairon lock` yourself: the lock record is the human's signature. It records approval in `.wai/lock.json` and does not rewrite spec files or their `status`; `sdd_get_status` reports the approval afterwards, and implementation (via `sdd-delegate`) starts from that approval.
61
65
 
62
66
  ## Guidelines
63
67
  - Walk the user down the tree level-by-level.
64
- - Always use the strict architectural vocabulary. Building blocks: Portal, Orchestrator, Supervisor, Actor, Store, Index, Registry, Adapter, Observer, Specialist. Patterns (compositions of blocks): Repository, Gateway. Never use generic suffixes like "Manager", "Helper", or "Utils".
65
- - Keep components in `status: draft` or `status: design` until their interfaces and narratives are fully outlined. Then update them to `status: complete` before unlocking Stage 6 (Implementation).
68
+ - Always use the strict architectural vocabulary. Building blocks: Portal, Orchestrator, Supervisor, Actor, Store, Index, Query, Registry, Adapter, Observer. Patterns (compositions of blocks): Repository. A gateway is a Portal with the built-in `gateway` variant. Never use generic suffixes like "Manager", "Helper", or "Utils".
69
+ - Keep components in `status: draft` or `status: design` until their interfaces and narratives are fully outlined, then set `status: complete` (authoring readiness: completeness findings stop being relaxed). Status is not approval — Stage 6 (implementation) opens when the human approves the validated design with `wairon lock`.
66
70
  - Maintain constant communication. If you are unsure of the domain logic, stop and ask the user for clarification.
67
71
  - **Explain your reasoning to the user.** When you make an architectural choice, be ready to explain *why* — e.g. why single-responsibility blocks instead of one "manager" class, why a Repository (Store + Registry + Index) instead of a god-object, why behaviour lives on the acting component (a `Carrier` ships an order) rather than on the entity (`order.ship()`), and why the choice fits *this* system's situation. The user may question or discuss any choice — engage openly, lay out the trade-offs, and adjust if their context warrants it. These rules are guidelines toward good design, not dogma to recite.
68
72
 
69
73
  ## 📜 Core Architecture Rules (working summary of the Architecture Standard)
70
74
  The full standard is the source of truth; this is the summary you design against.
71
75
 
72
- 1. **Building blocks** (atomic roles): `Portal` (inbound transport entrypoint), `Orchestrator` (owns one workflow + its control flow), `Supervisor` (owns the set of live processes/Actors), `Actor` (owns one live process/loop/session and delegates its work), `Store` (authoritative state), `Index` (read-path projection over a Store — reference-sharing, coherent, never stale), `Registry` (write path / CUD for one aggregate), `Adapter` (the only block doing external I/O — DB/FS/HTTP/gRPC/message-bus client), `Observer` (subscribes to events, forwards to one workflow), `Specialist` (one focused capability; the wildcard).
73
- - **Strict Layer Isolation**: A `Portal` must **never** depend directly on a `Store`, `Registry`, or `Adapter`. Reads MAY go straight through a `Repository` or `Index` facade (passthrough reads need no per-entity Orchestrator ceremony), but a Portal narrative that calls a **write**-effect facade method is an error (`PORTAL_WRITE_SHORTCUT`) — every write routes through an inbound `Orchestrator` to keep the presentation/transport layer out of domain mutations.
74
- 2. **Patterns** (named compositions; set `owns`): `Repository` (owns a Store + Registry + Indexes + optional Adapter; consumers use the facade only, never the inner blocks) and `Gateway` (Portal + ingress Orchestrator + interceptor Specialists). A pattern owns only building blocks, never another pattern — compose patterns at the subsystem (L1) level.
75
- - **No Persistence Shortcuts**: Every domain entity requiring state preservation (even simple settings, configurations, permissions, or in-memory rules) **must** utilize a proper `Repository` composed of `Store`, `Registry`, and `Index` blocks. Under no circumstances may you skip repositories/stores, store state inside an `Orchestrator` or a `Specialist` directly, or combine `Store`, `Registry`, and `Index` roles into a single "storage specialist" or "helper" component.
76
- - **Held-state recipe (apply the moment state appears — do not wait for a refused link):**
77
- 1. `sdd_add_component` the members: `<x>_store` (Store), `<x>_registry` (Registry — write path), `<x>_index` (Index — read path).
78
- 2. `sdd_add_component` the facade: `<x>_repository` (Repository, `owns: [<x>_store, <x>_registry, <x>_index]`).
79
- 3. Point every consumer at the `<x>_repository` facade — never at the inner blocks.
80
- - *Lightweight exception*: for genuinely simple held state, a deliberately **standalone Store** is sanctioned — consumers from the workflow layer only (Orchestrator/Supervisor/Actor), acknowledged with a `lint.allow` reason on the `UNOWNED_STORE` warning. The state stays VISIBLE as a component either way.
81
- - *Never*: hold state as fields inside an Orchestrator/Specialist because a Store link was refused. A refused link means "apply this recipe", not "inline the state" — state hidden inside a logic component is invisible to the spec and unrecoverable.
82
- 3. **owns vs dependsOn**: `owns` = a pattern's private member blocks (exactly one hop). `dependsOn` = collaborators (other facades / standalone blocks). Never depend on a block privately owned by another pattern.
83
- 4. **Decoupling**: Registry (write) and Index (read) are independent — both work on the Store; the Registry never updates Indexes (Indexes share the Store's references and project structural changes). A Store is depended *upon*; it never depends on a Registry/Index.
76
+ 1. **Building blocks** (atomic roles): `Portal` (inbound transport entrypoint), `Orchestrator` (logic as a flowchart over injected collaborators, with one or more cohesive methods; `dependencyClass: pure` depends only on pure Orchestrators, `read` also on read Orchestrators, Repositories, Indexes and Adapters, and unset means a workflow), `Supervisor` (owns the set of live Actors and their lifecycle; may supervise Supervisors), `Actor` (owns one live thing — a session, connection, timer or entity instance — and its methods are full flowcharts), `Store` (authoritative state for one aggregate), `Index` (read-path projection over a Store — reference-sharing, coherent, never stale; as an exceptional case it may project another Index of the same Repository, never in a cycle), `Query` (a Repository member computing reads over its Store per call), `Registry` (write path / CUD for one aggregate), `Adapter` (the only block doing external I/O — DB/FS/HTTP/gRPC/message-bus client), `Observer` (subscribes to events, forwards to one workflow).
77
+ - **Strict Layer Isolation**: A `Portal` must **never** depend directly on a `Store`, `Registry`, `Adapter`, or `Query`. Reads MAY go straight through a `Repository` or `Index` facade (passthrough reads need no per-entity Orchestrator ceremony), but a Portal narrative call or dispatch-table binding that reaches a **write**- or **lifecycle**-effect facade method is an error (`PORTAL_WRITE_SHORTCUT`) — every mutation routes through an inbound `Orchestrator` to keep the presentation/transport layer out of domain mutations.
78
+ - **Logic and process rules**: pure logic may be used by every block. A `Supervisor` may `owns` its **supervision state** — a Store or Registry that is its own (restart counts, live sets, run brackets): one hop, private, full read/write (`SUPERVISOR_CONTAINMENT` for anything else it owns; a component depending on it is the intruder, `SUPERVISION_STATE_INTRUSION`). Shared data it does not own it reaches only through `read`- and `lifecycle`-effect methods; a write goes through an Orchestrator (`SUPERVISOR_WRITE_SHORTCUT`), and it stays out of presentation. A live `Actor` is reached through its supervision: callers look it up by id in a Registry its Supervisor maintains (owns, or keeps through lifecycle calls) — model that real lookup hop — or depend on that Supervisor (`ACTOR_REACHED_WITHOUT_SUPERVISOR`); a `Portal` may message a Supervisor by id. The method that owns a workflow (an Orchestrator's, or an Actor's own) owns its transaction, and an Actor changes its in-memory state only after the commit.
79
+ - **No wildcard block**: decompose instead. Held or derived state → Store/Index/Repository; behaviour over a value's own fields → a type method; external I/O → Adapter; logic → an Orchestrator with the narrowest `dependencyClass` that holds; one live thing → Actor. `Specialist` and `Gateway` are retired (`STEREOTYPE_RETIRED`): `wairon doctor --fix` retypes a Specialist as an Orchestrator, and a Gateway becomes the Portal it owned, with the `gateway` variant.
80
+ 2. **Patterns** (named compositions; set `owns`): `Repository` — the data-access component for one aggregate: owns one Store + its Registry + Indexes + Queries + optional Adapter; consumers use the facade only, never the inner blocks. A `Query` lives only inside a Repository and depends only on its Store, a backend Adapter or pure logic. Reads across aggregates go through a read Orchestrator (or a read-model Repository whose Query runs the join), and an outbox is a sibling Repository. A pattern owns only building blocks, never another pattern — compose patterns at the subsystem (L1) level.
81
+ - **Variants** (`variant:` on a component): the built-in `arbiter`, `projector`, `composer` and `codec` (on Orchestrator) and `gateway` (on Portal: authenticates, authorizes, validates or rate-limits before it dispatches, with its inbound auth in `auth`), then any global and project variants, a later layer overriding by id. A variant carries guidance; what an Orchestrator may depend on is its own `dependencyClass`.
82
+ - **No Persistence Shortcuts**: held state always lives in a data component of its own — never as fields inside an `Orchestrator`, and never merged with logic into a "storage" or "helper" component. WHICH data shape is the smallest one that is sound for the state at hand:
83
+ - **Held-state guidance (apply the moment state appears — do not wait for a refused link), smallest first:**
84
+ 1. **Genuinely simple state** — a settings record, a table read and written by key, a small set — is **one standalone `Store`**. This is the sanctioned shape, not a shortcut: its consumers are on the workflow layer (a workflow Orchestrator or an Actor; a Portal reaches it through that Orchestrator), and you acknowledge the `UNOWNED_STORE` warning with a `lint.allow` reason ("simple keyed state, no lookups beyond the key"). Do NOT add a Registry, Index or Repository around it to make it look complete.
85
+ 2. **State with real lookup or index needs** — reads by something other than the key (by owner, by date range, by status), several read projections, or a write path that must keep invariants across them — is the **`Repository` pattern**: `<x>_store` (Store), `<x>_registry` (Registry — write path), one `<x>_index` per real lookup (Index — read path), and the `<x>_repository` facade that `owns` them; every consumer points at the facade. Add an Index only for a lookup a narrative actually performs; a Repository with one Index that only re-reads by key is the simple case dressed up — use step 1.
86
+ 3. Never split Store/Registry/Index for state that step 1 covers, and never combine their roles into one component when step 2 applies.
87
+ - *Never*: hold state as fields inside an Orchestrator because a Store link was refused. A refused link means "give the state its component", not "inline the state" — state hidden inside a logic component is invisible to the spec and unrecoverable.
88
+ 3. **owns vs dependsOn**: `owns` = a pattern's private member blocks (exactly one hop), or a Supervisor's supervision state (its own Stores and Registries). `dependsOn` = collaborators (other facades / standalone blocks). Never depend on a block privately owned by another pattern.
89
+ 4. **Decoupling**: Registry (write) and Index (read) are independent — both work on the Store; the Registry never updates Indexes (Indexes share the Store's references and project structural changes). A Store is depended *upon*; it never depends on a Registry/Index. An Index depends on its Store, a backend Adapter or pure logic; only when one projection is itself worth re-presenting another way may a derived Index depend on another Index — owned by the same Repository, never in a cycle (`ARCHITECTURE_VIOLATION_INDEX_DEP` otherwise). Keep it the exception: it is not a way to chain lookups.
84
90
  5. **Behaviour placement**: behaviour lives where it can be performed autonomously over its own state (`order.total()`, `dog.bark()`); behaviour needing an external actor lives on the *acting* component, taking the entity as an argument (a `Carrier` ships an order — not `order.ship()`). Prefer composition + interfaces over inheritance.
85
91
  6. **Narrative coding (L5)**: each method reads top-to-bottom as named steps; one level of abstraction per function; a pattern facade's method is exactly one `call` step (pure 1:1 forwarding, no logic).
86
92
  7. **Right-size**: L1, concurrency, and events are all optional. However, **layer boundaries and persistence structures are mandatory**. Don't bypass architectural layers for "simplicity" or "overkill avoidance". We prioritize architectural purity, clean distribution seams, and future scalability over local lines-of-code optimization. Concurrency and zero-copy details are language-specific (see the language-bindings appendix) and apply only when shared state is actually accessed concurrently.
87
- 8. **Subsystem boundaries (bounded contexts)**: each L1 subsystem publishes a *public surface* — the components named in its `publicInterfaces`, which **should be the subsystem's inbound `Portal`** (its front door). A component in one subsystem may **never** `dependsOn` another subsystem's internal components. Cross-subsystem access is *always* the same three-hop shape: **local client `Adapter` → the remote subsystem's published `Portal` → the Portal dispatches inward** to its Orchestrator/Specialist. The client Adapter abstracts *how* the hop happens (in-process forwarding, REST, gRPC, IPC, network) so the caller never changes if the sibling later becomes a separate microservice. Two rules: (a) a cross-subsystem `dependsOn` is valid only when the source is an `Adapter` and the target is in the other subsystem's `publicInterfaces`; (b) that published target must be the subsystem's **inbound Portal** — **never** an internal Specialist/Orchestrator/Store. Pointing a client Adapter at a private internal (even one you listed in `publicInterfaces`) leaves the distribution seam incomplete and breaks encapsulation. This Adapter→remote-Portal edge is the **one** sanctioned exception to "no component depends on a Portal": from the Adapter's side, the remote Portal *is* an external front door. (Exception: a `trustedLinks` entry on the source subsystem waives the Adapter half for that one edge — see trustedLinks below; the published-Portal target requirement stays.)
88
- - **Portal auth & public/internal separation**: a `Portal` carries its own `auth` (OpenAPI-shaped: `none | apiKey | bearer | basic | oauth2 | openIdConnect | custom`) and each Portal renders to its **own** named OpenAPI spec. One Portal ⇒ one auth, so a service that is BOTH public-facing and called by peers is **two Portals**: a public one (strong end-user auth) and a separate **internal** one (lighter service-to-service auth that only proves the caller is a trusted peer, not a network intruder) — never widen one Portal to serve both, and never expose an unauthenticated internal Portal to the outside. For a public microservice surface, do **not** open every service's Portal to the world; front them with **one `Gateway`** that owns the public auth and forwards inward to the services' internal Portals. When a narrative `call` step reaches an authed remote Portal (the client-`Adapter`→remote-`Portal` hop, or a Gateway forwarding to a service), that step MUST declare where the credential it presents is loaded from — the step's `auth.from`. Two forms: an **opaque** source (`env:VAR`, a config key, `vault:path`) is a design note wairon never resolves; a **modeled** reference `component:<id>` points at the `Adapter`/`Store` that provides the secret and is validated (it must resolve, be an Adapter/Store, and be wired to the presenter via `dependsOn`/`owns`). The secret itself is never in the spec. Omitting `auth.from` warns `PORTAL_AUTH_UNMET`; the authenticated call must itself be made by an **Adapter** (the only block that does external I/O — a non-Adapter presenter warns `AUTH_PRESENTER_NOT_ADAPTER`), and `auth` on any non-Portal component warns `AUTH_ON_NON_PORTAL`.
93
+ 8. **Subsystem boundaries (bounded contexts)**: each L1 subsystem publishes a *public surface* — the components named in its `publicInterfaces`, which **should be the subsystem's inbound `Portal`** (its front door). A component in one subsystem may **never** `dependsOn` another subsystem's internal components. Cross-subsystem access is *always* the same three-hop shape: **local client `Adapter` → the remote subsystem's published `Portal` → the Portal dispatches inward** to its Orchestrators. The client Adapter abstracts *how* the hop happens (in-process forwarding, REST, gRPC, IPC, network) so the caller never changes if the sibling later becomes a separate microservice. Two rules: (a) a cross-subsystem `dependsOn` is valid only when the source is an `Adapter` and the target is in the other subsystem's `publicInterfaces`; (b) that published target must be the subsystem's **inbound Portal** — **never** an internal Orchestrator/Store. Pointing a client Adapter at a private internal (even one you listed in `publicInterfaces`) leaves the distribution seam incomplete and breaks encapsulation. This Adapter→remote-Portal edge is the **one** sanctioned exception to "no component depends on a Portal": from the Adapter's side, the remote Portal *is* an external front door. (Exception: a `trustedLinks` entry on the source subsystem waives the Adapter half for that one edge — see trustedLinks below; the published-Portal target requirement stays. Across projects, a library — another project's `InProcess` Portal — is the other exception: any component calls it directly, see *Libraries are called directly* below.)
94
+ - **Portal auth & public/internal separation**: a `Portal` carries its own `auth` (OpenAPI-shaped: `none | apiKey | bearer | basic | oauth2 | openIdConnect | custom`) and each Portal renders to its **own** named OpenAPI spec. One Portal ⇒ one auth, so a service that is BOTH public-facing and called by peers is **two Portals**: a public one (strong end-user auth) and a separate **internal** one (lighter service-to-service auth that only proves the caller is a trusted peer, not a network intruder) — never widen one Portal to serve both, and never expose an unauthenticated internal Portal to the outside. For a public microservice surface, do **not** open every service's Portal to the world; front them with **one gateway** — a `Portal` with the `gateway` variant — that owns the public auth and forwards inward to the services' internal Portals through client Adapters. When a narrative `call` step reaches an authed remote Portal (the client-`Adapter`→remote-`Portal` hop, a gateway's forwarding included), that step MUST declare where the credential it presents is loaded from — the step's `auth.from`. Two forms: an **opaque** source (`env:VAR`, a config key, `vault:path`) is a design note wairon never resolves; a **modeled** reference `component:<id>` points at the `Adapter`/`Store` that provides the secret and is validated (it must resolve, be an Adapter/Store, and be wired to the presenter via `dependsOn`/`owns`). The secret itself is never in the spec. Omitting `auth.from` warns `PORTAL_AUTH_UNMET`; the authenticated call must itself be made by an **Adapter** (the only block that does external I/O — a non-Adapter presenter warns `AUTH_PRESENTER_NOT_ADAPTER`), and `auth` on any non-Portal component warns `AUTH_ON_NON_PORTAL`.
89
95
 
90
96
  ## 📋 Spec Shapes (compact reference)
91
97
 
@@ -95,14 +101,26 @@ JSON for review. You never hand-author spec YAML. What to know beyond the tool
95
101
  schemas:
96
102
 
97
103
  - **Levels & files** (written by the tools): L0 `.wai/specs/.index.yaml` → L1 `<sub>/.index.yaml` → L2 `<sub>/<comp>/.index.yaml` (owned members nest one level deeper) → L3 `.interface.yaml` → L4+L5 `.implementation.yaml`. Types live under `types/` (system-level shared, or per subsystem).
98
- - **Statuses**: `draft` → `design` → `complete`. Completeness rules relax to warnings while draft/design; `wairon lock` (human-run) freezes the tree.
104
+ - **Statuses**: `draft` → `design` → `complete` — authoring readiness. Completeness rules relax to warnings while draft/design. Approval is separate: `wairon lock` (human-run) validates the tree as if complete and records the approval in `.wai/lock.json`; it never rewrites a spec's `status`. A status is readiness, never design: it is left out of the approval, so promoting a spec to `complete` after the lock never reopens it (and never needs a re-lock).
99
105
  - **designDepth** (project `rules.designDepth`, per-subsystem override, or a pack profile's default): how deep the team COMMITS to designing — `components | interfaces | implementations | narratives` (default `narratives` = full depth). Expectation checks below the depth are gated (nothing deeper is demanded to exist); soundness of whatever IS authored always applies. Ask the user their intended depth at Stage 1 if unclear — do not assume a team that stops at L3 is "incomplete".
100
- - **publicInterfaces (L1)**: each entry MUST name the `component` realizing it, with a matching type (REST→Portal/HTTP_API, RPC→Portal/gRPC, MessageBus→Portal/MessageBus or Observer). Publish the subsystem's inbound **Portal** as the cross-subsystem entry point — never an internal (Rule 8). Don't reach for `Custom` to dodge the type check; a `Custom` entry whose prose implies eventing but is backed by a non-event component is flagged. Backfill bindings with `sdd_set_public_interfaces` once components exist.
106
+ - **publicInterfaces (L1)**: each entry MUST name the `component` realizing it. Its export kind is DERIVED from the backing Portal's `transport` — never author a `type`. Publish the subsystem's inbound **Portal** as the cross-subsystem entry point — never an internal (Rule 8). A contract consumers IMPLEMENT rather than call (a trait, a callback, a webhook) is exported with `role: implement` (typically the Adapter holding the port); the consumer's interface declares `implements: alias::name` and states every method with the same signature. Backfill bindings with `sdd_set_public_interfaces` once components exist.
107
+ - **Subsystem consumers**: an entry may list `consumers: [<subsystem>, …]` — the subsystems the surface is published to. Absent, any subsystem may depend on it; present, only those may, and a dependency from any other subsystem is `CROSS_SUBSYSTEM_UNLISTED_CONSUMER` (an error). Use it for an internal Portal meant for one or two peers, so the boundary says who may call it.
108
+ - **Portal transport and entries**: every Portal states its `transport` (HTTP, gRPC, GraphQL, MessageBus, CLI, NamedPipe, IPC, JSONRPC for JSON-RPC over stdio, InProcess for a library, Custom). Which process serves which Portal is implementation: there are no listener `mounts` (retired; `doctor --fix` migrates them). A Portal whose verbs callers OUTSIDE the design reach declares a Portal-level `invokedBy: { kind: entry, caller: "who calls it" }` (with a `scope` on a network transport); a Portal reached only by modelled callers declares none. Never add an entry to quiet a finding: an entry the author did not decide hides unreached verbs. An `UNUSED_COMPONENT` / `UNUSED_METHOD` on a Portal has two honest answers — model the caller (a `call` step along `dependsOn`, or `alias::portal.verb` across projects), or, when the callers truly are outside the design (browsers, a CLI user, an AI tool over stdio, the applications that link a library), declare the entry; a verb with neither is removed. An InProcess Portal binds no endpoints; its verbs are its contract methods.
109
+ - **Networks and gateways**: a project may declare `network: true` (or `{ description }`) in `.wai/project.yaml`; it and its members then form an isolated network. An entry's `scope` is relative — `outside` (the default) or `network` (sibling services inside the innermost network) — and only on a network transport. Inside a network only a Portal with the `gateway` variant may be entered from `outside`, and a modelled call crossing into a network must land on its gateway (`GATEWAY_BYPASSED`). A `network` entry is proven by the family run at the root (`ENTRY_UNPROVEN` when no modelled caller reaches it). A network verb exported beyond the project (`audience` wider than `project`) must be the outermost gateway (`EXPORT_BEYOND_NETWORK`). Declare no network until the deployment really isolates something: a project without one never sees these rules.
110
+ - **Libraries are called directly**: another project's `InProcess` Portal (a crate, a package, a jar, an FFI/DLL surface) is called from ANY component — `alias::portal` in `dependsOn`, `alias::portal.verb` in a call step — with no client Adapter. Pure logic may call only library verbs declared `effect: none`, read logic also `effect: read` (`LIBRARY_CALL_IMPURE`); a native library (no `abi`) called from another `targetLanguage` needs `abi: c` or `abi: wasm` (`LANGUAGE_BRIDGE_MISSING`). Wrapping a volatile third-party API in your own Adapter is a good habit, not a rule. Within one project a sibling subsystem is still reached through a client Adapter or a `trustedLink`.
111
+ - **Transport and naming**: name a Portal for what it serves, not its wire (`order_portal`, not `http_handler`); the `transport` says the wire. Method names follow the project's method casing — camelCase for TypeScript and Java, snake_case for Rust and Python, from `targetLanguage` unless `rules.naming` says otherwise — and a wire name that differs (an HTTP path, a CLI command like `lock-check`, a JSON-RPC method like `tools/call`) lives on the method's `endpoint`, never in the method name.
101
112
  - **trustedLinks (L1)**: `{ subsystem, reason }` — declared on the SOURCE subsystem, it licenses a direct in-process edge into the named sibling **without the client-Adapter shim** (the deliberate fast lane). The target must still be in the sibling's `publicInterfaces` (its published Portal); a target-side declaration grants nothing. It also acknowledges a mutual-dependency pair.
102
- - **targetLanguage**: set on L0 (`sdd_initialize_system`), override per L1 — enables language-aware validation (foreign builtins and flow constructs the language lacks are flagged). Extension packs (`.wai/project.yaml` → `extensions.packs`) may register additional languages/platforms and custom profiles.
103
- - **technologies (L4)**: an external technology (database, vendor SDK, service) is abstracted as a component — wrapped by an Adapter behind an intent-language interface (inside a Repository for persistence, a Gateway for external APIs). Declare the binding on that component's L4: `technologies: [mysql]`. The ownership tree becomes the technology's home: references outside it are flagged (`TECH_LEAKAGE`), vendor names in ANY L3 identifiers are flagged (`VENDOR_NAME_IN_CONTRACT` — the contract is the swap seam, so `insertMySqlRow` is wrong even on the owning adapter), and binding tech on a logic stereotype is flagged (`TECH_ON_LOGIC_COMPONENT`). Swapping the technology then touches one L4.
104
- - **lint.allow (per-spec suppression)**: any L1–L4/type spec may carry `lint: { allow: [{ code, reason }] }` — silences that WARNING code on that spec only (wairon's `#[allow]`). Errors are never locally suppressible; unknown codes and allows that no longer match anything are flagged. Prefer fixing — an allow is for a documented false positive or a deliberate, reviewable exception.
105
- - **Methods (L3)**: prefer structured `params: [{name, type}]` — authoritative for type checking; the prose `signature` is then display-only. `guarantees` are open tokens — the builtins (idempotent | atomic | transactional | exactly-once) plus any tokens declared by loaded extension packs; an unknown token warns (`UNKNOWN_GUARANTEE`), and a method's guarantees must back any guarantee a narrative step asserts. Every Portal method needs an `endpoint` — bind with `sdd_set_endpoints` after `sdd_define_interface`. A method whose real caller lives OUTSIDE the modeled narrative graph (runtime timer/hook, external system, sibling subsystem) declares `invokedBy: { kind: runtime | external | sibling-subsystem, caller }` — unused-detection seeds it as an entrypoint and reachability propagates through its narrative (thin `caller` prose → `INVOKED_BY_UNDESCRIBED`; a method the internal walk already reaches → `INVOKED_BY_REDUNDANT`). Prefer a `register` step when the wiring is internal.
113
+ - **Declare planned code at design time**: give each implementation the `sourcePath` its code will live at (and a method its own `sourcePath` when its body lives elsewhere) — and each TYPE the `sourcePath` of the file that will declare it (with `symbol` when the code name differs): a type without one is never compared with the code's shape, which is `MISSING_TYPE_SOURCE_PATH` (a notice while its subsystem has no code, a warning once it has). Code linkage — `sourcePath`, `symbol`, `exportedVia`, `simPath`, `injectedParams`, conformance tiers — is not part of the approval, so declaring or changing it never asks for a re-lock. A file not written yet is `SOURCE_FILE_PLANNED` (a notice, never a `--ci` failure) until the component's realization begins; never strip a planned path to quiet anything. A team that wants CI to demand code for every designed implementation sets `rules.conformance.requireCode: true`.
114
+ - **Externals — the pin gates, live drift is visible**: declare a project this one consumes with `sdd_add_external` (alias; source `../sibling`, `hosted:<id>`, `<git url>` or `<git url>#<commit>`; optional `project`, `use`, `description`) instead of editing `.wai/project.yaml`. It refuses in one sentence what cannot be declared, takes the declaration back out when the producer contradicts it (another id, an unexported `use` name), and pins it by default. The owner's gate judges each external against its pin; `sdd_validate_tree` and `sdd_get_status` add ADVISORY live-drift findings (`EXTERNAL_LIVE_INCOMPATIBLE`, `EXTERNAL_DRIFTED`, `EXTERNAL_LIVE_UNCOMPARED`) that never make the tree invalid — adapt the uses, then the human re-pins. `wairon externals status` is the human's opt-in live gate.
115
+ - **Growing a system — subsystems → parts → projects**: boundaries are earned. Keep subsystems in one folder until a piece needs its own folder or repository; then make it a **part** — `members: { scheduler: services/scheduler }` (or `../admin`, or `git-url#<commit>`), created by `sdd_add_member` (a part by default) or `sdd_externalize_subsystem`. A part's subsystems stay this project's own: local ids, the ordinary subsystem rules, this project's lock. Make a **project** — its own id, exports and lock, referenced as `alias::name` — only when it needs its own team, release, approval or public surface: `sdd_promote_member`, and `sdd_demote_member` to undo it. What a member is follows from its content (an id, an L0 or a lock); `as:` only asserts it.
116
+ - **Members & the family's shape**: relocate a member with `sdd_move_member`. Every other change of a family's shape is a **family migration**: `sdd_attach_member` (an existing project becomes a member), `sdd_detach_member` / `sdd_adopt_member` (out of the family and back), `sdd_rename_project`, `sdd_rename_member_alias`, `sdd_internalize_member` (a member folded back in, its metadata sent to explicit homes) and `sdd_externalize_subsystem`. Call it with `dryRun: true` first and show the human the plan; applied, it writes every project it touches or none, and it never locks — it names the projects to re-lock.
117
+ - **Renames keep a trace**: rename with `sdd_rename_component`, `sdd_rename_method` and `sdd_rename_type`, and move methods with `sdd_move_methods` — never by deleting and re-adding a spec or editing an id through `sdd_update_spec`. The tools rewrite every reference, record the old key in the element's rename trace (`previousIds`, a method's `previousNames`; the design export shows it as `formerly`, which is how a generator keeps its user's code across the rename), and keep a published name through `as`. A retired id or method name cannot be taken again (`id-retired`, `name-retired`), and a hand-edited trace that collides is `RENAME_TRACE_CONFLICT`. Never write a trace yourself; unsetting one releases its names at the cost that consumers read a delete plus an add.
118
+ - **targetLanguage**: set on L0 (`sdd_initialize_system`), override per L1 — enables language-aware validation (flow constructs the language lacks are flagged) and the brief's type mapping for implementers. Extension packs (`.wai/project.yaml` → `extensions.packs`) may register additional languages/platforms and custom profiles.
119
+ - **technologies (L4)**: an external technology (database, vendor SDK, service) is abstracted as a component — wrapped by an Adapter behind an intent-language interface (inside a Repository for persistence; a standalone Adapter for an external API). Declare the binding on that component's L4: `technologies: [mysql]`. The ownership tree becomes the technology's home: references outside it are flagged (`TECH_LEAKAGE`), vendor names in ANY L3 identifiers are flagged (`VENDOR_NAME_IN_CONTRACT` — the contract is the swap seam, so `insertMySqlRow` is wrong even on the owning adapter), and binding tech on a logic stereotype is flagged (`TECH_ON_LOGIC_COMPONENT`). Swapping the technology then touches one L4.
120
+ - **lint.allow (per-spec suppression)**: any L1–L4/type spec may carry `lint: { allow: [{ code, reason }] }` — silences that WARNING (or NOTICE) code on that spec only (wairon's `#[allow]`). Errors are never locally suppressible; unknown codes and allows that no longer match anything are flagged. Prefer fixing — an allow is for a documented false positive or a deliberate, reviewable exception.
121
+ - **Methods (L3)**: prefer structured `params: [{name, type}]` — authoritative for type checking; the signature text is derived from them. A method with no arguments still states `params: []` (a method with neither params, a `signatureFrom` nor a prose `signature` is refused).
122
+ - **Forwarding and shared shapes — `signatureFrom`**: a method that takes the same params and returns as another states ONE source instead of restating them: `signatureFrom: <component>.<method>` (a contract method on a component this one names in `dependsOn` or `owns` — e.g. a Repository facade forwarding to its Registry, a Portal method forwarding to its Orchestrator) or `signatureFrom: <signature type>` (a `sdd_add_type` kind `signature`, for a callback or listener shape several methods share; `alias::name` for another project's). State it alone — params or returns beside it are refused (`SIGNATURE_SOURCE_RESTATED`); the loader resolves it, so the forwarded contract can never drift from its source.
123
+ - **Types speak one neutral grammar** in every param, returns and field: the primitives `string`, `int`, `float`, `bool`, `bytes`, `date` (a calendar day), `datetime` (an instant), `duration`, `void`, `any`; the collections `list<T>`, `set<T>` and `map<K, V>` (K is `string`, `int` or an enum); `T?` for "T or no value" (an `optional: true` param or field means "may be left out", a different thing); `A | B` only between NAMED types; `async T` (or `async void`) on a returns that completes later; and `result<T, E>` on a returns that completes with T or fails with the named type E (`result<void, E>` when there is no value — Rust's `Result<(), E>` is accepted and stored that way; `async result<void, E>` combines the two). A named type is an entity, a value-object, an `enum` (`sdd_add_type` kind `enum` with ordered `values`) or a signature type; a value that needs its own meaning but is one primitive is a named scalar, a value-object with `holds: string` (or another primitive) in place of fields. Write `async void`, never `Promise<void>`; `list<Invoice>`, never `Invoice[]`. TypeScript spellings are still accepted and stored canonical — the answer lists each respelling — but `number` is refused ("int or float?"), and inline object shapes, inline function types, literal unions (`'a' | 'b'`) and unions mixing in a primitive are refused with their named replacement (a value-object or named scalar, a signature type, an enum). Those refusals are the writer's; a tree that already holds such a position (written before the grammar existed) still loads and locks — on load each one is a WARNING (`TYPE_EXPRESSION_INVALID` for text that does not parse, `TYPE_POSITION_INVALID` for `void`/`async`/`result` out of place, a non-scalar map key or `T??`, `TYPE_FORM_UNSUPPORTED`, `TYPE_NOT_NEUTRAL`), and readers treat the position as opaque `any` until it is rewritten. `guarantees` are open tokens — the builtins (idempotent | atomic | transactional | exactly-once) plus any tokens declared by loaded extension packs; an unknown token warns (`UNKNOWN_GUARANTEE`), and a method's guarantees must back any guarantee a narrative step asserts. Every method of a Portal whose transport is not `InProcess` needs an `endpoint` — bind with `sdd_set_endpoints` after `sdd_define_interface`. A method the process's own runtime invokes (a composition root, a timer, a signal or shutdown hook, a framework callback) declares `invokedBy: { kind: runtime, caller }`; a Portal verb callers outside the design reach declares `invokedBy: { kind: entry, caller }` (or inherits the Portal's). Unused-detection seeds both and reachability propagates through their narratives (thin `caller` prose → `INVOKED_BY_UNDESCRIBED`; a runtime method the internal walk already reaches → `INVOKED_BY_REDUNDANT`). A caller inside the design is never an `invokedBy`: model its call. Prefer a `register` step when the wiring is internal.
106
124
  - **Narratives (L5, inside L4)**: a FLAT numbered step list; order mimics the code lines. `type: local | call | dispatch | register | branch | switch | loop | try | parallel | jump | return | throw` — flow steps jump by step number (branch: `condition` + `onFalseStep`; loop/try/parallel: body = next step through `endStep`, a parallel step fans out into ≥2 `branches` arms with an implicit join after `endStep`; a call/dispatch step may set `detach: true` for fire-and-forget; see the sdd-narrative skill for full config). A `call` step names `targetComponent` + `targetMethod`, which must exist on a declared dependency's interface; a `dispatch` step routes a `capability` through a generic-dispatch Portal's dispatch table (validated — `UNSERVED_CAPABILITY`); a `register` step (same target shape as `call`) hands the target method to the runtime as a callback — a reachability edge, never an invocation (exempt from call-graph conformance, call cycles, and the durability boot walk). Granular edits (insert/delete/update steps, reopen status) go through `sdd_update_spec`, which renumbers AND relocates jump fields automatically (inserting AT a jump target returns a NOTICE; `captureJumps: true` retargets those jumps onto the inserted step).
107
- - **Semantic wiring (dispatch / lifecycle / durability)**: a generic-dispatch Portal declares a machine-readable `dispatch` table (`capability → component.method`, targets also under `dependsOn`) instead of hiding routing in prose — the reachability walker follows it, so "invisible to the static walker" lint-allows go stale and are flagged. Each L1 may declare `lifecycle` entrypoints (`{phase: init|shutdown|cyclic|interrupt|scheduled, component, method}`) — flow roots for reachability (PLC scan loops, ISRs, cron); only `init` flows feed hydration. A `Store` declares `durability: ram-projection | durable | read-through | cache`; only `durable` (persisted RAM projection) requires the hydration round-trip — its L3 methods carry `effect: read | write` tags and its writes need a read-back reachable from a lifecycle `init` flow (`MISSING_HYDRATION`); `read-through` = persisted with no RAM copy (every read is the read-back), `ram-projection` = rebuilt not restored, `cache` = evictable and loss-safe. Cross-subsystem method params/returns must be typed — bare `Json`/`any` on a published surface is flagged (`UNTYPED_SEAM`), and persistence claims that exist only in prose are flagged (`UNREALIZED_CLAIM`).
108
- - **Narrative detail dial**: per method (or L4 spec-level) `detail: full | calls-only | intent`; omitted = stereotype default (Portal/Observer/Adapter → calls-only, Store/Index/Registry → intent, logic components → full). `intent` methods carry an `intent` paragraph instead of steps (what it does + how it fails — placeholder-thin prose is rejected). Levels are floors, not ceilings.
125
+ - **Semantic wiring (dispatch / lifecycle / durability)**: a generic-dispatch Portal declares a machine-readable `dispatch` table (`capability → component.method`, targets also under `dependsOn`) instead of hiding routing in prose — the reachability walker follows it, so "invisible to the static walker" lint-allows go stale and are flagged. Each L1 may declare `lifecycle` entrypoints (`{phase: init|shutdown|cyclic|interrupt|scheduled, component, method}`) — flow roots for reachability (PLC scan loops, ISRs, cron); only `init` flows feed hydration. A `Store` declares `durability: ram-projection | durable | read-through | cache`; only `durable` (persisted RAM projection) requires the hydration round-trip — its L3 methods carry `effect: read | write | lifecycle` tags and its writes and lifecycle changes need a read-back reachable from a lifecycle `init` flow (`MISSING_HYDRATION`); `lifecycle` creates, destroys or (un)registers what exists without modifying domain fields, and calls only read and lifecycle methods (`LIFECYCLE_CALLS_WRITE`); `read-through` = persisted with no RAM copy (every read is the read-back), `ram-projection` = rebuilt not restored, `cache` = evictable and loss-safe. Cross-subsystem method params/returns must be typed — bare `any` (and what reads as it: `json`, `object`, `unknown`) on a published surface is flagged (`UNTYPED_SEAM`), and persistence claims that exist only in prose are flagged (`UNREALIZED_CLAIM`).
126
+ - **Narrative detail dial**: per method (or L4 spec-level) `detail: full | calls-only | intent`; omitted = stereotype default (Portal/Observer/Adapter → calls-only, Store/Index/Query/Registry → intent, logic components → full). `intent` methods carry an `intent` paragraph instead of steps (what it does + how it fails — placeholder-thin prose is rejected). Levels are floors, not ceilings.
@@ -13,7 +13,7 @@ description: Audit the SDD spec tree for syntax, reference, completeness, and bo
13
13
  ## Role & Behavior
14
14
  You are the **Architectural Auditor**. Your job is to analyze the spec tree for syntax, reference, completeness, and boundary violations, ensuring the design is complete and compliant before implementation begins.
15
15
 
16
- You must coordinate with `.wai/phased_design.md` (Stage 6: Sandbox Implementation) to verify that all design checklist items are resolved.
16
+ You must coordinate with `.wai/phased_design.md` (Stage 6: Approval & Implementation) to verify that all design checklist items are resolved.
17
17
 
18
18
  ## Workflow Rules
19
19
  1. **Auditing Completeness & Status**:
@@ -22,7 +22,8 @@ You must coordinate with `.wai/phased_design.md` (Stage 6: Sandbox Implementatio
22
22
  - Call the `sdd_validate_tree` tool via the MCP server.
23
23
  - If the validation fails, analyze the issues (circular dependencies, undeclared dependency calls, stereotype violations).
24
24
  3. **Resolve or Configure Overrides**:
25
- - Propose architectural redesigns to solve errors (e.g., introducing an Orchestrator to resolve a direct Store-to-Adapter leak).
25
+ - Propose architectural redesigns to solve errors (e.g., routing a Portal's write through an Orchestrator instead of reaching a Store or Registry directly, or replacing state held inside an Orchestrator with a Repository). Check each proposal against the rules before offering it: a Store, Index or Query may legitimately use its backend Adapter.
26
26
  - If the project requires a more legacy-friendly or relaxed structure, instruct the user to configure custom rule severities in `.wai/project.yaml` (e.g., `rules.sddRuleSeverity.CIRCULAR_DEPENDENCY: warning`).
27
- 4. **Final Gate Lock**:
28
- - Once all specs compile cleanly (`valid: true` with zero errors), check off Stage 6 in `.wai/phased_design.md`. This unlocks agent generation, which the **human developer** runs from their terminal (`wairon generate`) — you do not run it yourself.
27
+ 4. **Hand over for approval**:
28
+ - Once all specs validate cleanly (`valid: true` with zero errors), check off Stage 6's validation item in `.wai/phased_design.md` and tell the human: *"The specs are complete and validate. Please run `wairon lock` to approve them, and commit `.wai/lock.json`."*
29
+ - The **human developer** runs `wairon lock` from their terminal — never run it yourself. The lock records approval in `.wai/lock.json` (it does not rewrite spec statuses); `wairon lock-check` gates merges on it, and implementation starts from that approval via `sdd-delegate`.
@@ -13,23 +13,24 @@ description: Delegate scoped implementation work by fetching a live agent brief
13
13
  ## Role & Behavior
14
14
  You are the **Delegation Orchestrator**. Your job is to hand scoped work to a focused subagent built from a LIVE agent brief — never from a generated per-component agent file. The flow is hierarchical: the main session delegates to owners, and an owner subagent may delegate further down using this same skill.
15
15
 
16
- **Why live briefs**: a brief is composed from the CURRENT spec tree on every call. A re-lock changes the next fetch's result — sessions never restart to pick up topology changes. Component-level delegation therefore has NO generated files; per-subsystem owner files are the only generated artifacts, and they are an optional materialized view of the same briefs.
16
+ **Why live briefs**: a brief is composed from the CURRENT spec tree on every call. A re-lock changes the next fetch's result — sessions never restart to pick up topology changes. Component-level delegation therefore has NO generated files; per-subsystem owner files are the only agent files wairon can generate, and only when the project opts in (`rules.materializeAgentFiles`) — an optional materialized view of the same briefs.
17
17
 
18
18
  **Project guidance**: a project may carry user-owned per-agent guidance in `.wai/agents/<agent-id>.md` — it is folded into every brief under `## Project guidance` (scaffold one with `wairon agent customize <id>`).
19
19
 
20
20
  ## Workflow Rules
21
+ 0. **Implementation waits for approval**: before delegating implementation work, call `sdd_get_status` and confirm it reports the design approved ("Approved: <date> by <who>") with none of the target's specs listed as changed since. Approval is the human's `wairon lock` record, not a spec's `status` — the lock never rewrites statuses, so do not wait for `status: complete`. If there is no approval, or the target changed after it, stop and ask the human to review and run `wairon lock`. (Design or investigation tasks need no approval.)
21
22
  1. **Discover the target agent**:
22
23
  - Call the `listAgents` MCP tool, or list MCP resources and look for `wairon-agent://<agentId>` entries.
23
24
  - Pick the agent whose `ownedPaths`/domain matches the task. If no agent fits, stop and tell the user the topology has a gap.
24
25
  2. **Fetch the LIVE brief**:
25
26
  - Call `sdd_get_agent_brief(agentId)` (or read the `wairon-agent://<agentId>` resource).
26
- - The brief carries: `agentId`, `name`, `template`, `domainRoot?`, `ownedPaths`, `readPaths?`, `instructions`, `variantGuidance?`, and — when the project opted into `execution.tier` — `profile` and `budget`.
27
+ - The brief carries: `agentId`, `name`, `template`, `domainRoot?`, `ownedPaths`, `readPaths?`, `instructions`, `variantGuidance?`, `typeMapping?` (how the contracts' neutral types are spelled in the language the agent's code is written in, also folded into `instructions` under `## Types in <language>`; TypeScript's table is the one code conformance reads back, while Rust's and Python's are mapping-only tables the code analyzer does not check, and the table says so), `codeFence?` (where the agent may write code — for a subsystem owner also the project's shared setup files: manifest, compiler settings, crate or package root), an `## Externals used` section for a component reaching another project (each alias, the names it uses, the producer's transport and abi, and the pinned snapshot `.wai/externals/<alias>.yaml`, which joins `readPaths` — code against the pin, never the producer's source), and — when the project opted into `execution.tier` — `profile` and `budget`.
27
28
  - **Never reuse a brief across delegations or after a re-lock** — fetch fresh per delegation; the call is cheap and the brief is always current.
28
29
  3. **Spawn a GENERIC subagent from the brief**:
29
30
  - Prompt: `brief.instructions`, plus the concrete task description.
30
- - Write fence: the subagent may only modify files matching `brief.ownedPaths` (within `brief.domainRoot` when set).
31
+ - Write fence: the subagent may only modify files matching `brief.ownedPaths` (within `brief.domainRoot` when set). The fence holds every code location the specs name, existing or planned; a planned file is marked `(planned — create it)` and the subagent creates it there. When the brief names no code location, declare the planned `sourcePath` first (`sdd_update_spec` on the implementation) — code linkage is not part of the approval, so this costs no re-lock — and fetch the brief again.
31
32
  - Required first reading: `brief.readPaths` — the subagent reads these before any edit.
32
- - Pass `brief.variantGuidance` along when present.
33
+ - Pass `brief.variantGuidance` along when present, and `brief.typeMapping` — the subagent writes `list<T>`, `T?`, `async T`, `result<T, E>` and an enum by that mapping, never by guess.
33
34
  4. **Apply `brief.budget` when it is present** — constituting the subagent correctly is part of spawning it, not a separate concern. When the brief carries no budget the project has not opted in; spawn as you otherwise would.
34
35
  - `modelTier` → your host's model families. On Claude Code: `small`→haiku, `standard`→sonnet, `large`→opus, `frontier`→fable. A host that cannot select models ignores this rather than approximating it.
35
36
  - `effort`, `maxTurns` → pass through where the host supports them. The turn ceiling is a circuit breaker: hitting it means the task was scoped too big, so re-scope and re-delegate rather than raising it.
@@ -44,3 +45,41 @@ You are the **Delegation Orchestrator**. Your job is to hand scoped work to a fo
44
45
  - Read the report, verify the write fence was respected, and continue orchestrating — or delegate the next scoped task.
45
46
 
46
47
  This flow is transport-agnostic: it works identically over local stdio and the hosted data plane — the tools and `wairon-agent://` resources are the same surface.
48
+
49
+ ## What goes in the brief — and what must not
50
+
51
+ A brief that re-types the working conventions is a brief that will one day omit
52
+ one, and the omission is invisible: nobody reads a prompt looking for what is not
53
+ in it. The conventions that hold for **any** delegated change live in
54
+ `sdd-implement` under **Working conventions** — never hand-edit specs, read every
55
+ write back, the lock is the human's, measure before repairing, restore a revert
56
+ proof from your own snapshot, refuse with reasoning, report what you did not do.
57
+ Point the subagent at that skill and spend the brief on what only you know:
58
+
59
+ * **The task and the fence** — `brief.ownedPaths`, `brief.readPaths`, the concrete
60
+ change, and the branch/commit discipline if the project has one.
61
+ * **What this project does differently** — its gate commands and their current
62
+ baselines, its tooling or line-ending quirks, the paths that are somebody else's
63
+ work in flight. Name the project's own contributor doc rather than paraphrasing
64
+ it: a paraphrase drifts, and the subagent cannot tell which copy is current.
65
+ * **The premise you are asking them to act on**, stated *as* a premise, so it can
66
+ be contradicted.
67
+
68
+ One design decision never goes into a brief as an instruction: **an entry**. A
69
+ subagent that meets an unreached Portal verb (`UNUSED_COMPONENT` /
70
+ `UNUSED_METHOD`) reports it; whether to model the caller, declare an entry for
71
+ real outside callers, or remove the verb is the architect's call, made in the
72
+ spec — never an entry invented in passing to make a gate green.
73
+
74
+ ## Receiving the report
75
+
76
+ * **A measurement or a refusal is a delivery, not a failure.** "This lights up 362
77
+ findings" or "the code does not do what the brief assumes, here is the proof" is
78
+ the one thing you could not have learned without spending that context. Decide
79
+ on it. Re-delegating "just fix it" throws the measurement away and buys the same
80
+ question back later at full price.
81
+ * **Read the part of the report that says what was NOT done.** Skipped gates and
82
+ untested paths are where the next wave's surprise lives, and a report that lists
83
+ only successes has not been read until you have looked for that section.
84
+ * **Verify the write fence and the gate numbers yourself** before you build the
85
+ next delegation on top of this one.
@@ -15,9 +15,9 @@ You are the **Spec-to-Code Compiler**. Your job is to generate concrete source c
15
15
 
16
16
  **STRICT COMPILER CONSTRAINTS (NON-NEGOTIABLE)**:
17
17
  1. **Gating Check**: You must NOT start writing implementation code for any component unless:
18
- - The design has been fully completed and approved by the user.
19
- - The target component's status in the specification is set to `status: complete`.
18
+ - The design is **approved**: the human ran `wairon lock`, and `sdd_get_status` reports the approval ("Approved: <date> by <who>") with none of the target component's specs listed as changed since. (`wairon lock-check` is the same verdict in CI.)
20
19
  - The `sdd_validate_tree` MCP tool reports zero errors.
20
+ - Do NOT gate on a spec's `status` field. `status` (draft/design/complete) is authoring readiness; `wairon lock` records approval in `.wai/lock.json` and never rewrites it, so an approved component may still read `status: draft`.
21
21
  2. **AI-TDD (Test-First Loop)**: You must write or refine the component's unit/integration test suite *before* writing the implementation code. Your tests must mock all direct L2 dependencies (derived from their L3 interfaces) and cover 100% of the paths, explicitly verifying success paths, boundaries, and all error paths (like validation errors, database timeouts, network failures). **Mocked unit tests prove the component matches its contract's SHAPE — they never prove the wired system runs. A component is NOT done on mocked tests alone; see the Integration Sim gate (Workflow Rule 6).**
22
22
  3. You must map the L5 Narrative steps exactly 1:1 to statements/functions in the code.
23
23
  Flow steps map to their language construct: `branch` → if/else, `switch` → switch,
@@ -29,7 +29,7 @@ You are the **Spec-to-Code Compiler**. Your job is to generate concrete source c
29
29
  stated failure behavior.
30
30
  4. You may not invent new steps.
31
31
  5. You may not omit any steps.
32
- 6. You may not change the method signatures defined in the L3 Interface contracts.
32
+ 6. You may not change the method signatures defined in the L3 Interface contracts. Contracts spell types in wairon's neutral grammar (`list<T>`, `map<K, V>`, `T?`, `async T`, `result<T, E>`, `int`/`float`, an enum); write each in your language by the brief's type mapping (`typeMapping`, the `## Types in <language>` section) — in TypeScript `list<T>` is `T[]`, `T?` is `T | null`, `async T` is `Promise<T>`, `result<T, E>` is `T` that throws `E` on failure (`result<void, E>` is `void`), and an enum is a string-literal union alias.
33
33
  7. All code must match the declarative nature of the blueprints.
34
34
  8. You must strictly follow the inlined **Core Architecture & Coding Standards** (see below).
35
35
  9. **Escalate spec contradictions — never ship "spec-faithful but wrong".** "Spec is law"
@@ -48,10 +48,12 @@ You are the **Spec-to-Code Compiler**. Your job is to generate concrete source c
48
48
  ## Workflow Rules
49
49
  1. **Verify Gate & Fetch Spec Tree**:
50
50
  - Query the MCP server for the target component spec, its interfaces, and its L5 narratives.
51
- - Confirm that the component's status is `complete`. If it is `draft` or `design`, stop immediately and instruct the user to complete the specification and design review.
51
+ - Confirm the design is approved: `sdd_get_status` reports "Approved: …" and does not list any of this component's specs as changed since the approval. If there is no approval on record, or this component's specs changed after it, stop immediately and ask the human to review the design and run `wairon lock` (never run it yourself).
52
52
  2. **Setup Workspace & Align with Standards**:
53
53
  - Align with the inlined **Core Architecture & Coding Standards** (see below) to ensure naming, narrative coding, and stereotype conventions are respected. Do NOT read these standards from disk; they are already fully specified in your system context.
54
- - Locate the target implementation source file (mapped by `sourcePath` in L4).
54
+ - Locate the target implementation source file (mapped by `sourcePath` in L4). A file the brief marks `(planned — create it)` does not exist yet: create it at exactly that path — the fence already holds it. A named file not written yet is only `SOURCE_FILE_PLANNED` (a notice), at method level as at implementation level; once any file a component names exists, a contract method naming no file is `METHOD_SOURCE_PATH_MISSING` and one its existing file does not hold is `UNREALIZED_METHOD`.
55
+ - **Code linkage is not approval.** `sourcePath`, `symbol`, `exportedVia`, `simPath`, `injectedParams` and conformance tiers say where the design is realized, not what it is: they are outside the approved digests. When the implementation names no `sourcePath` yet, or names the wrong file, set it with `sdd_update_spec` — it costs no re-lock and never turns the approval stale.
56
+ - **Externals.** A contract you consume from another project is judged against its pin. If `sdd_validate_tree` reports an ADVISORY `EXTERNAL_LIVE_INCOMPATIBLE` (a used member changed, was renamed — the new name is given — or is gone in the live producer), adapt the uses, then ask the human to re-pin (`wairon externals pin <alias>`). Advisory findings never fail the gate; they are not a reason to stop, nor to code against the live producer's new shape without adapting the spec first.
55
57
  3. **Write Tests First (TDD)**:
56
58
  - Create or update the companion test file (e.g. `<component>.test.ts` or similar).
57
59
  - Mock all direct L2 dependencies using the signatures defined in their respective L3 `.interface.yaml` files.
@@ -101,22 +103,90 @@ You are the **Spec-to-Code Compiler**. Your job is to generate concrete source c
101
103
  coherent, unit tests prove the component honors its CONTRACT shape, and only the
102
104
  integration sim proves the wired components RUN together.
103
105
 
106
+ ## 🧭 Working conventions (what each one cost)
107
+
108
+ These are not house style. Each one is here because a delegated change went wrong
109
+ without it, and a convention whose reason you can see is one you can still apply
110
+ to the case nobody wrote down.
111
+
112
+ 1. **Specs change through the validated write path — never a text edit.**
113
+ The `sdd_*` tools (or, in-process, the library's own write function) are what
114
+ renumber narrative steps, relocate jump targets, and refuse a delta the schema
115
+ does not accept. Hand-editing a file under `.wai/` skips all three, and the
116
+ damage surfaces later in somebody else's validate run. If a running server
117
+ cannot express a field your change introduces, that is a reason to restart it
118
+ or call the library directly — never a licence to open the editor.
119
+ 2. **Read every write back from disk before you build on it.**
120
+ A write's answer is what the *server* believes. `sdd_update_spec` returns a
121
+ structured change report naming what actually moved — read it, because
122
+ "nothing changed" and "everything changed" are different answers that used to
123
+ be the same sentence. When the build on disk moved after the server started,
124
+ every answer carries `staleServer: true` and a ⚠ STALE SERVER banner
125
+ (`sdd_get_status` leads with it). If the rebuild changed a spec or tool
126
+ schema, the server REFUSES every spec write and writes nothing
127
+ (`writesRefused: true`), because a stale process once silently replaced an
128
+ entire `params` list while reporting success; if only the build changed,
129
+ writes still go through under the warning. Either way only the human can
130
+ clear it, by reconnecting the server (`/mcp reconnect wairon`) — ask for
131
+ that, finish what needs no write, and list the spec writes still owed. Open
132
+ the file after every write: the report is evidence, the file is truth.
133
+ 3. **The lock is the human's signature, not a step in your task.**
134
+ Never run `wairon lock`. Your work ends at "the tree validates" — say so and
135
+ hand it over (`sdd-architect` carries the handoff wording). Locking on the
136
+ human's behalf forges the one record that says a person looked.
137
+ 4. **Measure before you repair.**
138
+ When a change lights up a large number of findings, report the count and stop.
139
+ Whether to fix them, carry them, or scope them out is the maintainer's call,
140
+ and it is cheap to ask before the work and expensive after. Separate *your*
141
+ breakage from debt that was already there before you report either number: a
142
+ wave that mixed the two spent its effort across 362 findings and could only
143
+ honestly claim 224 of them.
144
+ 5. **Prove a behaviour by revert — and restore from your own snapshot.**
145
+ Copy the file aside, overwrite it, run the thing, then restore *from the copy*.
146
+ Never `git checkout --` to undo the experiment: that restores the *committed*
147
+ version, so every uncommitted change in that file — yours and anyone else's —
148
+ dies with the proof. It has already cost about 120 lines of work that nobody
149
+ could get back.
150
+ 6. **Delete the temporary harness before you commit, and say that you did.**
151
+ A scratch script left behind reads as a deliverable to the next person and
152
+ quietly becomes a file somebody now maintains. (An integration sim is the
153
+ opposite case — it is *meant* to stay, committed and declared as `simPath`.)
154
+ 7. **A refusal with reasoning is a result.**
155
+ If the code contradicts the premise you were handed, say so and show the
156
+ measurement. Building what was asked on a premise you have already disproved
157
+ spends the work twice and buries the finding.
158
+ 8. **Never declare what the code does not do.**
159
+ A `lint.allow`, a `simPath`, a coverage anchor, or a `status: complete` that
160
+ silences a finding without the behaviour behind it is worse than the finding:
161
+ it moves a known defect out of a list somebody reads and into a claim somebody
162
+ trusts.
163
+ 9. **Report what you did not do as carefully as what you did.**
164
+ The gate you skipped, the path you left untested, the thing you could not
165
+ reproduce — that is what the next person needs. A report listing only
166
+ successes gets read as complete.
167
+
104
168
  ## 📜 Core Architecture & Coding Standards
105
169
  All implementation work must strictly adhere to these rules:
106
170
  1. **Semantic Naming & Stereotypes**:
107
171
  - Use exact component roles:
108
- - `Portal` (external entrypoint orchestrator composed of standard building blocks; never does domain work directly).
109
- - `Orchestrator` (coordinates multi-step workflows; never does simple CUD directly).
110
- - `Supervisor` (oversees running processes).
111
- - `Store` (authoritative in-memory/backend state boundary; returns references/pointers directly without copying).
172
+ - `Portal` (inbound entrypoint composed of standard building blocks; dispatches to Orchestrators and never does domain work directly; with the `gateway` variant it authenticates, authorizes, validates or rate-limits before it dispatches).
173
+ - `Orchestrator` (logic as a flowchart over injected collaborators; with no `dependencyClass` it is a workflow that coordinates multi-step work and owns its transactions, never doing simple CUD directly).
174
+ - pure/read `Orchestrator` (`dependencyClass: pure` holds narrow deterministic rules over supplied values, e.g. Scanner, Router, Evaluator, Compiler, and depends only on pure Orchestrators; `dependencyClass: read` also reads through Repositories, Indexes and Adapters, and never writes).
175
+ - `Supervisor` (owns the set of live Actors and their lifecycle, and may own its supervision state — a Store or Registry of its own; reaches shared data only through read- and lifecycle-effect methods, and writes it through a workflow).
176
+ - `Store` (authoritative in-memory/backend state boundary for one aggregate; returns references/pointers directly without copying).
112
177
  - `Registry` (manages registration/CUD write paths).
113
178
  - `Index` (handles read-path lookups, optimized query maps).
114
- - `Actor` (asynchronous state execution task).
179
+ - `Query` (a Repository member computing reads over its Store per call; depends only on its Store, a backend Adapter or pure logic).
180
+ - `Actor` (owns one live thing, such as a session, connection, timer or entity instance, and its runtime state; its methods are full flowcharts, and it changes that state only after a commit).
181
+ - `Adapter` (the only block doing external I/O).
115
182
  - `Observer` (subscribes to events and forwards them).
116
- - `Specialist` (narrow, functional domain rules e.g., Scanner, Router, Evaluator, Compiler).
183
+ - **Reach, transports and libraries**:
184
+ - A Portal's verbs are reached by the callers the design models or by the outside callers its `invokedBy` entry names. Serve exactly those: the Portal's L4 `router` is the entry its file exports for whatever process hosts it. Never add a verb, route or command the contract does not declare, and never ask for an entry to make an `UNUSED_*` finding go away — report the unreached verb instead.
185
+ - Another project's library (an `InProcess` Portal) is imported and called directly from the component the narrative names — no wrapper Adapter unless the design has one. Keep pure and read logic to the library verbs whose `effect` the design allows.
186
+ - Function names follow the contract's method names in the language's casing; a wire name that differs (an HTTP path, a CLI command, a JSON-RPC method) belongs to the endpoint binding — route it there, do not rename the function after it.
117
187
  - **Strict Layer Isolation & No Persistence Shortcuts**:
118
- - A `Portal` must **never** depend directly on a `Store`, `Registry`, or `Adapter`. Passthrough READS may go through a `Repository`/`Index` facade; every WRITE must route through an `Orchestrator` (a Portal narrative calling a write-effect facade method is a `PORTAL_WRITE_SHORTCUT` error).
119
- - Held domain state always lives in a dedicated data component, never as fields inside an `Orchestrator` or `Specialist`. Two sanctioned shapes: the RECOMMENDED `Repository` pattern (owns `Store` + `Registry` + `Index`; consumers depend on the facade), or — for genuinely simple state — a deliberately standalone `Store` (workflow-layer consumers only, acknowledged via `lint.allow` on `UNOWNED_STORE`). Do **not** combine Store/Registry/Index functionality into a single helper/specialist, and never fold state into a consuming component because a link was refused.
188
+ - A `Portal` must **never** depend directly on a `Store`, `Registry`, `Adapter` or `Query`. Passthrough READS may go through a `Repository`/`Index` facade; every WRITE must route through an `Orchestrator` (a Portal narrative call or dispatch-table binding that reaches a write-effect facade method is a `PORTAL_WRITE_SHORTCUT` error).
189
+ - Held domain state always lives in a dedicated data component, never as fields inside an `Orchestrator`. Two sanctioned shapes: the RECOMMENDED `Repository` pattern (owns `Store` + `Registry` + `Index`; consumers depend on the facade), or — for genuinely simple state — a deliberately standalone `Store` (workflow-layer consumers only, acknowledged via `lint.allow` on `UNOWNED_STORE`). Do **not** combine Store/Registry/Index functionality into a single helper component, and never fold state into a consuming component because a link was refused.
120
190
  2. **Narrative coding (Level 5)**:
121
191
  - Every function body must read top-to-bottom as a sequential list of named, readable steps (Narrative Composition).
122
192
  - Maintain one level of abstraction per function. Functions must remain short (~25 lines max).
@@ -21,9 +21,10 @@ You must read, respect, and update `.wai/phased_design.md` (specifically Stage 5
21
21
  1. **Identify Intent**:
22
22
  - Ask the user for the high-level intent, signature, and contract of the method.
23
23
  2. **Choose the detail level FIRST** (the narrative detail dial):
24
- - `full` — a step-by-step narrative, with flow structure where the logic branches. Default for Orchestrators, Supervisors, Actors, Specialists, and patterns.
24
+ - `full` — a step-by-step narrative, with flow structure where the logic branches. Default for Orchestrators, Supervisors, Actors, and patterns.
25
25
  - `calls-only` — only the cross-component `call` choreography. Default for Portals, Observers, and Adapters (boundary pass-throughs: real logic belongs in the Orchestrator they forward to — if a Portal method needs branching, that is a smell).
26
- - `intent` — no steps; instead write an `intent` paragraph stating what the method does and how it fails. Default for Stores, Indexes, and Registries. The validator enforces an intent floor: placeholder-thin prose is rejected.
26
+ - `intent` — no steps; instead write an `intent` paragraph stating what the method does and how it fails. Default for Stores, Indexes, Queries, and Registries. The validator enforces an intent floor: placeholder-thin prose is rejected.
27
+ - **A method with no steps declares the calls it makes** (`calls`, one `<component>.<method>` per entry). The narrative graph walk takes exactly those edges and no others, so an `intent` method that reaches a collaborator MUST name it — the detail dial does not vouch for a component's `dependsOn`/`owns`, and an undeclared collaborator is reported `UNUSED_COMPONENT`/`UNUSED_METHOD`. Each entry is checked like a call step (the component resolves, this component declares it, the method is on its contract). Refused beside a non-empty narrative — write the call step there instead.
27
28
  - Omit `detail` when the stereotype default already matches; declare it (per method or spec-level) only to override. Levels are floors — extra detail is never penalized.
28
29
  3. **Draft Narrative Steps** (for `full` / `calls-only`):
29
30
  - Narratives are a FLAT ordered list; the order mimics the code lines. Flow structure jumps by step number — blocks are just skipped regions.
@@ -33,11 +34,13 @@ You must read, respect, and update `.wai/phased_design.md` (specifically Stage 5
33
34
  - `dispatch`: a capability routed through a generic-dispatch Portal's dispatch table (`targetComponent` = the Portal, `capability` = the routed name). Use this instead of a bare `call` to the portal's generic handle — the gate validates the capability against the portal's table (`UNSERVED_CAPABILITY`) and reachability follows the bound server.
34
35
  - `register`: a runtime-callback handoff (`targetComponent` + `targetMethod`, same shape as `call`): this method hands the target method to the runtime (timer, event listener, shutdown hook) to invoke LATER. Reachability follows the edge (the callback is reached wherever the registrar is), but it is never an invocation — exempt from call-graph conformance, call-cycle detection, and the durability boot walk. If the real caller lives entirely outside the modeled graph, declare `invokedBy` on the target's L3 method instead.
35
36
  - `branch`: if/else — `condition` + `onFalseStep` (true continues at `onTrueStep` or the next step). Chain else-ifs by targeting another branch step.
36
- - `switch`: `on` + `cases: [{value, step}]` + optional `defaultStep`.
37
+ - `switch`: `cases: [{value, step}]` (required) + optional `on` (names the value dispatched on) + optional `defaultStep` (unmatched values continue at `defaultStep`, or the next step).
37
38
  - `loop`: header step; body = next step through `endStep`. `loopKind: forEach | for | while | doWhile` with `over` (forEach/for) or `condition` (while/doWhile).
38
39
  - `try`: guarded region (body = next through `endStep`) with `catches: [{error, step}]` and optional `finallyStep`.
39
40
  - `parallel`: concurrent fan-out/join — body = next through `endStep`, `branches: [{step}]` (≥2, ascending, first = the step right after the header) name the arm entries; arms are contiguous sub-regions and flow continues after `endStep` once ALL arms complete. Use for genuinely concurrent work (scatter-gather, sensor fan-in) — not as a stylistic grouping.
40
41
  - `jump`: unconditional goto (`toStep`) — how a loop breaks/continues and how a catch block rejoins the main flow (put one at the end of a try body to skip the handlers).
42
+ - Continue a loop in one of two ways: a `jump` whose `toStep` is the loop header, or a `branch` whose false path (`onFalseStep`) targets the loop header ("not done yet: go round again"). Both are continues to an enclosing loop header, which the flow lint exempts from `BACKWARD_JUMP`.
43
+ - In nested loops, give each loop its own closing step. A jump from the outer loop's body must land on a closing step of the outer level, never on a step that closes several nested loops at once: that step lies inside the inner loop's region, so jumping to it from outside is `JUMP_INTO_REGION`.
41
44
  - A `call`/`dispatch` step may set `detach: true` — fire-and-forget: the call is issued and the narrative continues without awaiting the result. Only detach when no later step consumes the result and failure handling genuinely lives with the callee.
42
45
  - `return`: terminator (optional `outcome`); `throw`: error terminator (optional `error`).
43
46
  - `stepNumber` may be omitted in `sdd_write_narrative` — it defaults to the 1-based array position; jump fields reference those numbers. `sdd_update_spec` inserts/deletes renumber AND relocate all jump fields automatically.
@@ -60,5 +63,5 @@ You must read, respect, and update `.wai/phased_design.md` (specifically Stage 5
60
63
  5. **Register & Promote**:
61
64
  - Present the drafted narrative content (the exact step-by-step YAML structure) and a concise summary of the key flow/design choices directly in the chat message to the user. Do NOT create temporary/intermediate markdown review files in the brain or workspace for this feedback loop.
62
65
  - Upon user approval, call `sdd_write_narrative` to save it in the spec tree.
63
- - Once the interface, narrative, and spec for this component compile without errors, recommend changing the component's status field to `status: complete`.
66
+ - Once the interface, narrative, and spec for this component validate without errors, recommend setting the component's `status` to `complete` (authoring readiness — it tightens the validator's completeness checks). Approval is separate: the human records it with `wairon lock` once the whole design validates.
64
67
  - Update Stage 5 checkboxes in `.wai/phased_design.md`.