@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.
- package/CONTRIBUTING.md +193 -0
- package/README.md +16 -9
- package/dist/cli/index.js +4702 -2453
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +4626 -852
- package/dist/index.js +3740 -1685
- package/dist/index.js.map +1 -1
- package/dist/templates/skills/sdd-architect.md +1 -1
- package/docs/architecture.md +1 -1
- package/docs/cli.md +174 -27
- package/docs/design/approval-baseline.md +257 -0
- package/docs/design/connecting-agent-entrypoint.md +167 -0
- package/docs/design/execution-budgets.md +199 -0
- package/docs/design/pack-scoping.md +581 -0
- package/docs/design/supervisor-doctrine.md +110 -0
- package/docs/extending-wairon.md +5 -4
- package/docs/network.md +9 -4
- package/docs/roadmap.md +2 -2
- package/package.json +9 -3
- package/schemas/design-export-2.json +15 -1
|
@@ -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`.
|
package/docs/extending-wairon.md
CHANGED
|
@@ -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/`](
|
|
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@
|
|
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`](
|
|
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`](
|
|
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.
|
|
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
|
|
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](
|
|
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.
|
|
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
|
-
"
|
|
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",
|