@wairon/cli 5.1.1-dev.107 → 5.1.1-dev.109

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.
@@ -0,0 +1,110 @@
1
+ # Supervisor and Actor doctrine: supervision state, shared data, the lifecycle effect
2
+
3
+ ## The problem
4
+
5
+ Two rules left the process layer without an honest shape.
6
+
7
+ - Held state lives in a dedicated data component, but a Supervisor could not
8
+ depend on a Store, Registry, Repository, Index or Query at all
9
+ (`ARCHITECTURE_VIOLATION_SUPERVISOR_DEP`). A Supervisor's own bookkeeping —
10
+ restart counts, the set of live children, the brackets of the runs it starts —
11
+ had nowhere to live.
12
+ - A component depending on a live Actor had to also depend on a Supervisor that
13
+ supervises it (`ACTOR_REACHED_WITHOUT_SUPERVISOR`). Real callers do not go
14
+ through the Supervisor: they look the Actor's live handle up by id in a
15
+ registry the Supervisor keeps. The remedy pushed a ceremonial edge instead of
16
+ the real lookup hop.
17
+
18
+ ## The rules
19
+
20
+ 1. **Supervision state.** A Supervisor may `owns` a Store or Registry that is its
21
+ own: one hop, private, read and written in full. It owns nothing else, and
22
+ nobody else depends on that state.
23
+ 2. **Shared data.** A Supervisor may call a data component it does not own only
24
+ through methods whose declared effect is `read` or `lifecycle`. A write goes
25
+ through a workflow: the Supervisor depends on the Orchestrator that does it.
26
+ Judged from the Supervisor's narrative call steps against the callee's
27
+ declared effect; a callee that declares no effect is refused too.
28
+ 3. **The lifecycle effect.** A third method effect, `lifecycle`: the method
29
+ creates, destroys, or (un)registers an entity's existence or membership and
30
+ never modifies its domain fields. It is closed under composition: a lifecycle
31
+ method calls only read and lifecycle methods besides construction and local
32
+ steps.
33
+ 4. **Supervised reach.** A component depending on a live Actor reaches it through
34
+ its supervision when it also depends on a Supervisor that supervises the
35
+ Actor, or on a Registry such a Supervisor maintains — by owning it, or by
36
+ calling it with lifecycle-effect methods.
37
+
38
+ **Honest limit.** A `read` made on behalf of a request still passes. In practice
39
+ the paired write is refused, which forces the marshalling workflow out, and the
40
+ read moves with it.
41
+
42
+ ## Codes
43
+
44
+ | Code | Severity | Rule | Reported on | Meaning |
45
+ |---|---|---|---|---|
46
+ | `SUPERVISOR_CONTAINMENT` | error | pattern-containment | the Supervisor | it owns something other than a Store or Registry |
47
+ | `SUPERVISION_STATE_INTRUSION` | error | member-visibility | the intruder | "X depends on Y, which Supervisor Z owns as its supervision state" |
48
+ | `SUPERVISOR_WRITE_SHORTCUT` | error | supervisor-shared-data (new) | the Supervisor's implementation | a call to a write-effect or untagged method on data it does not own |
49
+ | `LIFECYCLE_CALLS_WRITE` | error | lifecycle-effect-closure (new) | the implementation | a lifecycle-effect method calls a write-effect method |
50
+ | `ARCHITECTURE_VIOLATION_SUPERVISOR_DEP` | error | entrypoint-dependencies | the Supervisor | kept, narrowed: a Supervisor depending on a presentation block |
51
+ | `ACTOR_REACHED_WITHOUT_SUPERVISOR` | error | entrypoint-dependencies | the caller | kept, widened: Registry reach counts; the remedy names the Registry, or says to model one |
52
+ | `BLOCK_OWNS_MEMBERS` | error | pattern-membership | the block | kept: a Supervisor is exempt |
53
+ | `SHARED_OWNED_MEMBER` | error | pattern-membership | the later claimant | kept: Supervisors claim like patterns |
54
+ | `UNUSED_LINT_ALLOW` | warning | lint-allows | the allowing spec | kept: an allow naming an error now says an error cannot be allowed |
55
+
56
+ The existing readers of `effect` read `lifecycle` as a mutation:
57
+ `PORTAL_WRITE_SHORTCUT` refuses a Portal's direct lifecycle call as it refuses a
58
+ write, and a durable Store needs a hydration read-back for its lifecycle methods
59
+ as for its writes (`MISSING_HYDRATION`). The invariant registry still asks only
60
+ write-effect methods to assert invariants, since a lifecycle method never
61
+ modifies domain fields.
62
+
63
+ Within the brief, three choices were made:
64
+
65
+ - Exclusivity is its own code rather than a reworded `VISIBILITY_VIOLATION`:
66
+ the remedy differs (there is no facade to depend on instead; the state is
67
+ either shared data or reached through the Supervisor).
68
+ - An owned Registry counts as maintained for supervised reach, as decided. A
69
+ caller depending on it is then still an intruder, so that shape reports
70
+ `SUPERVISION_STATE_INTRUSION` once and no actor finding: a lookup Registry
71
+ callers share is shared data the Supervisor keeps through lifecycle calls.
72
+ - The lint-allow audit decides "error" from what the run saw on that spec (a
73
+ finding of the code landed there as an error) or, when nothing fired there,
74
+ from the code's resolved severity (project override, profile, default). The
75
+ validator now hands the rule context each code's default severity.
76
+
77
+ ## How the four motivating cases validate
78
+
79
+ 1. **An orchestrator calls an Actor whose handle it resolves through a router
80
+ that is really a Registry of live handles, mistyped as a Store.** Typed as a
81
+ Store, the caller reaches the Actor through no supervision and
82
+ `ACTOR_REACHED_WITHOUT_SUPERVISOR` fires on the caller. Retyped as a
83
+ Registry whose register/unregister methods are `lifecycle` and whose lookup
84
+ is `read`, with the Supervisor registering each started Actor through a
85
+ lifecycle call, the caller depending on that Registry and the Actor is
86
+ supervised reach, and the Supervisor's calls pass `SUPERVISOR_WRITE_SHORTCUT`.
87
+ A standalone Registry still needs a Store to write to
88
+ (`REGISTRY_WITHOUT_STORE`, a warning) — give the handles a Store, or wrap both
89
+ in a Repository the Supervisor keeps through lifecycle calls.
90
+ 2. **A component that spawns and owns Actors is typed Orchestrator but is a
91
+ fused Supervisor and workflow.** It depends on the Actor with no Supervisor
92
+ anywhere: `ACTOR_REACHED_WITHOUT_SUPERVISOR`, whose remedy says to give the
93
+ Actor a Supervisor that maintains a handle Registry. Split into a Supervisor
94
+ (spawns, restarts, registers handles) and a workflow (looks the Actor up in
95
+ that Registry), the tree validates.
96
+ 3. **A scheduler Supervisor reads guard state and brackets run lifetimes on a
97
+ shared repository.** The edge to the repository is no longer a finding. Its
98
+ calls — read the guard, open a run, count live runs, close the run — are
99
+ `read`, `lifecycle`, `read`, `lifecycle`: quiet. Tagging open or close as
100
+ `write`, or leaving them untagged, fires `SUPERVISOR_WRITE_SHORTCUT`; a
101
+ lifecycle open that also updates the run's fields fires
102
+ `LIFECYCLE_CALLS_WRITE` on the repository's implementation.
103
+ 4. **The same Supervisor reads and writes a shared object heap on behalf of a
104
+ call.** The read passes; the write fires `SUPERVISOR_WRITE_SHORTCUT`. Moving
105
+ the work into a marshalling Orchestrator the Supervisor calls — the read goes
106
+ with it — validates.
107
+
108
+ All four, and a fire and a control for every new or changed code, are fixtures
109
+ in `tests/rules-matrix/families/boundaries-supervision.fixtures.ts`; the
110
+ lint-allow case is in `references-lint-allows.fixtures.ts`.
@@ -9,7 +9,7 @@ rules) that wairon reads and imports. A product built this way (a
9
9
  wairon installation: no fork, no build step, and wairon core never learns
10
10
  the platform exists.
11
11
 
12
- A working wrapper lives at [`examples/wrapper/`](../examples/wrapper/) and
12
+ A working wrapper lives at [`examples/wrapper/`](https://github.com/SYW-Apps/Waffle-AIron/blob/main/examples/wrapper/) and
13
13
  is guarded by `tests/examples/wrapper-example.test.ts`, so it cannot
14
14
  silently rot.
15
15
 
@@ -98,9 +98,10 @@ arguments** — each selection carries its own source, so one command restores a
98
98
  project's doctrine anywhere:
99
99
 
100
100
  ```yaml
101
- - uses: SYW-Apps/Waffle-AIron/.github/actions/setup-wairon@v5
101
+ - uses: SYW-Apps/Waffle-AIron/.github/actions/setup-wairon@v<version> # the version you lock with (see docs/cli.md)
102
102
  with:
103
103
  packs: sync # sync (default) | none (bundled repos) | explicit sources
104
+ - run: npm ci # a TS/JS project with code: its own dependencies first (see docs/cli.md)
104
105
  - run: wairon validate --ci
105
106
  ```
106
107
 
@@ -267,7 +268,7 @@ wairon pack add ./my-product-pack --global # machine-wide
267
268
 
268
269
  The installer injects once and leaves — from then on plain `wairon
269
270
  validate` enforces the doctrine, and `wairon pack remove` undoes it. See
270
- [`examples/wrapper/install.js`](../examples/wrapper/install.js) for the
271
+ [`examples/wrapper/install.js`](https://github.com/SYW-Apps/Waffle-AIron/blob/main/examples/wrapper/install.js) for the
271
272
  template. A spec-only product (implementation lives on a cloud platform)
272
273
  needs nothing more: the wrapped repository holds specs + docs, wairon holds
273
274
  the gate.
@@ -595,7 +596,7 @@ project's externals against their live producers, and the family checks),
595
596
  `globalPacksDir`, `discoverPacks`, `setProjectRoot`, the `SddRule` /
596
597
  `RuleContext` / `LoadedExtensions` / `DeclarativePack` types, `SDD_RULES`,
597
598
  `composeRuleSequence`, and the spec loaders/savers. See
598
- [`examples/wrapper/wrapper.js`](../examples/wrapper/wrapper.js).
599
+ [`examples/wrapper/wrapper.js`](https://github.com/SYW-Apps/Waffle-AIron/blob/main/examples/wrapper/wrapper.js).
599
600
 
600
601
  ## Spec-only wrappers
601
602
 
package/docs/network.md CHANGED
@@ -6,14 +6,14 @@ wairon derives a system's network from its design. The inputs are the calls you
6
6
 
7
7
  Specs never hold an address, a port, a namespace or a label. Those deployment facts come from a **bindings file** that the team keeps outside `.wai/` and passes when generating policy. The design stays logical, and the deployment stays yours.
8
8
 
9
- Every command runs at the project you are in. At a project that declares members, the commands cover its whole family; `--no-recursive` limits them to that project alone. Run them at the family root: only the family run can narrow a `network` entry to the callers it really has.
9
+ Every command runs at the project you are in. At a project that declares members, the commands cover its whole family; `--no-recursive` limits them to that project alone. Inside a member, they answer from the enclosing family — the parent's network declaration and the callers the parent models, the same answer the family root gives — and `--no-recursive` judges the member alone. Only the family run can narrow a `network` entry to the callers it really has.
10
10
 
11
11
  ## The outputs, and what each is for
12
12
 
13
13
  | Command | Output | What a team does with it |
14
14
  |---|---|---|
15
- | `wairon network flows [--format json\|csv\|markdown] [--out <file>]` | The **allowed-flows matrix**: one row per flow (from, to, transport, binding, networks crossed, gateway, evidence) | Review it in pull requests as the authoritative "who talks to whom". A design change that adds a dependency shows up as a new row before any code or firewall change exists. The CSV suits spreadsheets and audits, and the Markdown suits review comments. |
16
- | `wairon network policy --bindings <file> [--out <file>]` | **Kubernetes `NetworkPolicy`** YAML: one ingress policy per called workload | Commit it to the deployment repository and diff it in CI. A design change becomes a policy diff that the security reviewer reads in the same PR. A design that fails the gate exits 1: the refused flows are left out and named at the head of the file. |
15
+ | `wairon network flows [--format json\|csv\|markdown] [--out <file>]` | The **allowed-flows matrix**: one row per flow (from, to, transport, binding, networks crossed, gateway, evidence) | Review it in pull requests as the authoritative "who talks to whom". A design change that adds a dependency shows up as a new row before any code or firewall change exists. The CSV suits spreadsheets and audits, and the Markdown suits review comments. A design that fails the gate exits 1. |
16
+ | `wairon network policy --bindings <file> [--out <file>]` | **Kubernetes `NetworkPolicy`** YAML: one ingress policy per called workload | Commit it to the deployment repository and diff it in CI. A design change becomes a policy diff that the security reviewer reads in the same PR. A design that fails the gate exits 1: the refused flows are left out and named at the head of the file. A design name the bindings leave unbound also exits 1. |
17
17
  | `wairon network diagram [--out <file>]` | A **Mermaid** flowchart: declared networks as nested boundaries, gateways on them, an `outside` node, and flows aggregated per workload pair | Use it as the data-flow diagram with trust boundaries that threat modelling starts from: each boundary an edge crosses is a place to ask the STRIDE questions. It also works for onboarding and on architecture pages. The hosted canvas draws the same picture in its **Network** view (`/canvas/<project>/network`; the system spec's `diagram.defaultView: network` opens the canvas there). |
18
18
  | `wairon network check --observed <file> [--bindings <file>]` | **Unexpected** flows (seen, but nothing allows the pair), **disallowed** flows (a verb the destination declares, but not allowed from that source, or only by a flow the gate refuses), **unknown verbs** (an allowed pair, but a method and path no contract of the destination declares) and **unexercised** flows (allowed, never seen) | Audit a live system against its design. Exits 1 on anything unexpected, disallowed or unknown, so it can gate a pipeline. Unexercised flows are review items, such as dead dependencies or rules to tighten, and never fail the command. |
19
19
  | `wairon network why <from> <to>` | The modelled chain behind a flow: the entry or call step, each network entered, the gateway, and the verb with its binding | Answer a firewall change request or an incident question ("why does checkout talk to payments?") from the design, in one command. Exits 1 when nothing allows the flow, and names what does reach the target instead; says so when the gate refuses the flow, when a name is unknown (a typo, not a refusal), and — exiting 0 — when the callee is reached in-process, which is never a network flow. |
@@ -69,7 +69,11 @@ outside: # what "outside" means for a policy
69
69
  - `outside` lists the CIDR blocks admitted for callers from outside, for example the internet, a load balancer range or an intranet. Without it, nothing from outside is admitted.
70
70
  - A source in another namespace gets a `namespaceSelector` on `kubernetes.io/metadata.name`. A network-wide source bound without a namespace matches in any namespace (`namespaceSelector: {}`).
71
71
 
72
- **Nothing is guessed.** A design name the bindings do not map is selected by the placeholder label `wairon.dev/workload: <name>`, which matches no pod until you bind it or label one. It is listed in the document and on stderr, and at the top of the output under `# UNBOUND:`. MessageBus flows travel through the broker rather than pod to pod, so they are listed as `# NOT EXPRESSED` instead of opening ingress. Default deny is implied: each policy selects its workload and admits only what the design allows.
72
+ **Nothing is guessed.** A design name the bindings do not map is selected by the placeholder label `wairon.dev/workload: <name>`, which matches no pod until you bind it or label one. It is listed in the document and on stderr, and at the top of the output under `# UNBOUND:`, and `policy` exits 1 so a pipeline notices. MessageBus flows travel through the broker rather than pod to pod, so they are listed as `# NOT EXPRESSED` instead of opening ingress. Default deny is implied: each policy selects its workload and admits only what the design allows.
73
+
74
+ **Renamed projects.** A bindings key, or a name in an observed-flow file, that is a project's former id (its rename trace, `previousIds`) still resolves to the renamed project, and the output carries a `# NOTE` naming the rename, so you can update the key at your pace instead of finding a policy that selects nothing or a legitimate flow filed as unexpected. `wairon project rename` lists the binding and telemetry keys to rename in its plan.
75
+
76
+ **Mixed reach.** A NetworkPolicy cannot split traffic by path. When one workload serves verbs of different reach on one port — a verb entered from `outside` and a verb only the network may call — its policy has to admit the union, and `policy` says so in a `# NOTE` naming the verbs. To keep the narrower verb narrow at L4, bind the Portal on its own (a `<project>::<portal>` key with its own selector or port): it then gets a policy of its own.
73
77
 
74
78
  ## The observed-flow format
75
79
 
@@ -93,6 +97,7 @@ Or JSON with the same fields, as a list or as `{ "flows": [...] }`:
93
97
  - A row without `method` and `path` is an L4 observation: it exercises every allowed flow between the pair.
94
98
  - A row with both is checked at L7 against the verbs' bindings. `{id}` and `:id` path segments match any value, and gRPC paths (`/pkg.Service/Method`) match the `service/method` binding.
95
99
  - A malformed row (no source or destination, or a non-integer count) is refused with its row number, and nothing is half-read.
100
+ - A name that is a project's former id is mapped to the renamed project, with a `# NOTE` (see **Renamed projects** above).
96
101
 
97
102
  ## A typical loop
98
103
 
package/docs/roadmap.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  wairon is an AIDD support tool built around Spec-Driven Development. This roadmap
6
6
  reflects what is actually shipped in `src/` and what is planned. The
7
- [CHANGELOG](../CHANGELOG.md) has the per-release detail.
7
+ [CHANGELOG](https://github.com/SYW-Apps/Waffle-AIron/blob/main/CHANGELOG.md) has the per-release detail.
8
8
 
9
9
  ---
10
10
 
@@ -79,7 +79,7 @@ reflects what is actually shipped in `src/` and what is planned. The
79
79
  and SSO; a state-scoped `lock` with approval requests; hosted members as
80
80
  records of their own; git-backed projects; Notion / Miro producers; a web UI
81
81
  (canvas, specs, admin). Self-host via Docker. See the
82
- [hosted server guide](design/hosted-mcp-server.md).
82
+ [hosted server guide](https://github.com/SYW-Apps/Waffle-AIron/blob/main/docs/design/hosted-mcp-server.md).
83
83
 
84
84
  ---
85
85
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wairon/cli",
3
- "version": "5.1.1-dev.107",
3
+ "version": "5.1.1-dev.109",
4
4
  "description": "SYW Waffle AIron — CLI for managing AI coding agent topology across projects",
5
5
  "keywords": [
6
6
  "ai",
@@ -30,7 +30,13 @@
30
30
  "schemas",
31
31
  "README.md",
32
32
  "docs/*.md",
33
- "docs/standards/**"
33
+ "docs/standards/**",
34
+ "docs/design/supervisor-doctrine.md",
35
+ "docs/design/pack-scoping.md",
36
+ "docs/design/connecting-agent-entrypoint.md",
37
+ "docs/design/execution-budgets.md",
38
+ "docs/design/approval-baseline.md",
39
+ "CONTRIBUTING.md"
34
40
  ],
35
41
  "workspaces": [
36
42
  "sdk"
@@ -72,6 +78,7 @@
72
78
  "js-yaml": "^4.1.0",
73
79
  "ora": "^5.4.1",
74
80
  "swagger-ui-dist": "^5.32.9",
81
+ "typescript": "^5.5.2",
75
82
  "yaml": "^2.9.1",
76
83
  "zod": "^3.23.8"
77
84
  },
@@ -87,7 +94,6 @@
87
94
  "rimraf": "^5.0.7",
88
95
  "tsup": "^8.1.0",
89
96
  "tsx": "^4.15.7",
90
- "typescript": "^5.5.2",
91
97
  "vitest": "^4.1.9",
92
98
  "zod-to-json-schema": "^3.25.2"
93
99
  }
@@ -638,6 +638,12 @@
638
638
  },
639
639
  "description": {
640
640
  "type": "string"
641
+ },
642
+ "formerly": {
643
+ "type": "array",
644
+ "items": {
645
+ "type": "string"
646
+ }
641
647
  }
642
648
  },
643
649
  "required": [
@@ -1096,7 +1102,15 @@
1096
1102
  "properties": {
1097
1103
  "id": {
1098
1104
  "type": "string",
1099
- "pattern": "^[a-z0-9-_]+$"
1105
+ "maxLength": 64,
1106
+ "allOf": [
1107
+ {
1108
+ "pattern": "^[a-z0-9-_]+$"
1109
+ },
1110
+ {
1111
+ "pattern": "^(?!(?:con|prn|aux|nul|com[0-9]|lpt[0-9])$)"
1112
+ }
1113
+ ]
1100
1114
  },
1101
1115
  "description": {
1102
1116
  "type": "string",