asylumwarden 0.0.0-stage → 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TachikomaGundam
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,54 @@
1
- # Temporary Holding Version
1
+ # AsylumWarden
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Status: core + CLI implemented (milestones M1–M2), adapter layer in
4
+ progress. The npm package `asylumwarden@0.0.0` is a name reservation; the
5
+ first functional release will be `0.1.0`.**
6
+
7
+ AsylumWarden is a local, file-first control plane for multi-seat AI work. It
8
+ keeps the plan, leases, receipts, and ledger on disk. It does not run a daemon,
9
+ require a database, or replace the host's sandbox.
10
+
11
+ - GitHub: <https://github.com/TachikomaGundam/AsylumWarden>
12
+ - npm: `asylumwarden` (0.0.0 name reservation; functional release `0.1.0`
13
+ follows the release gate in [`docs/RELEASING.md`](docs/RELEASING.md))
14
+ - License: MIT
15
+
16
+ ## What it is
17
+
18
+ - A registry of work lines and their writable / read-only / forbidden surfaces.
19
+ - A DAG planner with cycle and dependency checks.
20
+ - Atomic claims with owner, epoch, TTL, and heartbeat.
21
+ - Receipt validation: commands, artifacts, read mentions, and path containment.
22
+ - An append-only, hash-chained ledger that is tamper-evident, not tamper-proof.
23
+ - A thin CLI and optional opencode adapter.
24
+
25
+ ## What it is not
26
+
27
+ - Not a sandbox. Same-uid processes can still bypass declarations.
28
+ - Not a replacement for PCB's control plane or Abathur's gates.
29
+ - Not a required dependency of Abathur. It is an optional companion.
30
+ - Not a daemon, scheduler service, or task database.
31
+
32
+ ## Design
33
+
34
+ The approved architecture is in
35
+ [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md). Wire formats
36
+ and error codes have a single source: [`docs/CONTRACT.md`](docs/CONTRACT.md)
37
+ with machine-readable schemas in `schemas/`. Security limits are stated
38
+ plainly in [`docs/SECURITY.md`](docs/SECURITY.md). The PCB pilot boundary is
39
+ in [`docs/PILOT-PCB.md`](docs/PILOT-PCB.md). Release rules are in
40
+ [`docs/RELEASING.md`](docs/RELEASING.md).
41
+
42
+ ## Boundary with Abathur
43
+
44
+ Abathur does not import AsylumWarden and does not install it. AsylumWarden may
45
+ read Abathur data, but the dependency is one-way:
46
+
47
+ ```text
48
+ AsylumWarden ---reads/obeys---> versioned contract
49
+ Abathur ---does not depend on---> AsylumWarden
50
+ ```
51
+
52
+ ## License
53
+
54
+ MIT. See [`LICENSE`](LICENSE).
@@ -0,0 +1,282 @@
1
+ // THIN ADAPTER (M3). No business rules here; all validation is core's (fail-closed).
2
+ // Every warden_* handler does exactly one thing: map tool args -> CLI argv, run the
3
+ // core in-process via runCli(), return { output, exit }. No retries, no state
4
+ // access, no defaults, no interpretation of the envelope.
5
+ //
6
+ // PCB pilot boundary (docs/PILOT-PCB.md): this adapter must NEVER be installed into
7
+ // PCB sessions. The PCB pilot is CLI-only and shadow-only; the plugin path is out
8
+ // of bounds for that line.
9
+ //
10
+ // Plugin shape: verified against a real installed plugin on this machine,
11
+ // opencode-wiki-historian@0.6.3 (dist/index.js + dist/tools.js), 2026-11-05:
12
+ //
13
+ // // dist/index.js (historian):
14
+ // async function server(input, options) { ... return { tool: buildTools(opts) }; }
15
+ // const plugin = { id: 'opencode-historian', server };
16
+ // export default plugin;
17
+ // // dist/tools/read.js (historian):
18
+ // import { tool } from '@opencode-ai/plugin';
19
+ // const s = tool.schema; // zod
20
+ // tool({ description, args: { key: s.string() }, execute: async (raw) => string })
21
+ //
22
+ // The real plugin builds each tool with the SDK's `tool()` helper and zod schemas.
23
+ // This adapter must not import any host SDK (AGENTS.md: adapters never create a
24
+ // path back into core; the zero-dep promise; task constraint), so tool definitions
25
+ // are the same plain objects the helper returns — { description, args, execute } —
26
+ // with `args` declared as JSON Schema instead of zod. If the host-side loader ever
27
+ // needs zod args, that wrapping belongs at install time, not in core and not here.
28
+ // Everything host-facing is duck-typed: no @opencode-ai/* import anywhere, static
29
+ // or dynamic. If the host shape is missing/unexpected we register nothing and log
30
+ // once — never throw at load.
31
+ //
32
+ // Core is imported through the public library entry only (src/index.mjs exports
33
+ // runCli); zero subprocess. Import direction: adapters -> core, never the reverse
34
+ // (enforced by tools/import-gate.mjs).
35
+
36
+ import { runCli } from "../../src/index.mjs";
37
+
38
+ // CLI surface per docs/CONTRACT.md §8. `command` = fixed leading argv tokens;
39
+ // positional/value/bool entries map 1:1 onto CLI flags (camelCase tool param ->
40
+ // kebab-case flag, e.g. stateHome -> --state-home). Required-ness is NOT checked
41
+ // here — core fails closed (USAGE, exit 1) on a missing --actor/--epoch/etc.
42
+ const TOOL_SPECS = [
43
+ {
44
+ tool: "warden_init",
45
+ command: ["init"],
46
+ description: "Initialize the state home (genesis ledger entry + anchor push). " +
47
+ "With bootstrapRegistry=true, create an absent registry file instead (needs registry, self, root).",
48
+ positional: [],
49
+ valueFlags: [
50
+ { key: "config", flag: "config", desc: "Path to asylumwarden.json" },
51
+ { key: "stateHome", flag: "state-home", desc: "State home override (test/shadow)" },
52
+ { key: "registry", flag: "registry", desc: "Registry path (with bootstrapRegistry)" },
53
+ { key: "self", flag: "self", desc: "Own line name (with bootstrapRegistry)" },
54
+ { key: "root", flag: "root", desc: "Own writable root (with bootstrapRegistry)" },
55
+ ],
56
+ boolFlags: [
57
+ { key: "force", flag: "force", desc: "Overwrite an existing registry file" },
58
+ { key: "bootstrapRegistry", flag: "bootstrap-registry", desc: "Bootstrap the registry file" },
59
+ ],
60
+ },
61
+ {
62
+ tool: "warden_plan",
63
+ command: ["plan"],
64
+ description: "Validate a wave manifest and materialize the wave.",
65
+ positional: [{ key: "manifest", desc: "Path to wave.json" }],
66
+ valueFlags: [
67
+ { key: "config", flag: "config", desc: "Path to asylumwarden.json" },
68
+ { key: "stateHome", flag: "state-home", desc: "State home override" },
69
+ ],
70
+ boolFlags: [{ key: "force", flag: "force", desc: "Force re-plan" }],
71
+ },
72
+ {
73
+ tool: "warden_ready",
74
+ command: ["ready"],
75
+ description: "List claimable cells of a wave (pending, dependencies done).",
76
+ positional: [{ key: "wave", desc: "Wave id" }],
77
+ valueFlags: [
78
+ { key: "config", flag: "config", desc: "Path to asylumwarden.json" },
79
+ { key: "stateHome", flag: "state-home", desc: "State home override" },
80
+ ],
81
+ boolFlags: [],
82
+ },
83
+ {
84
+ tool: "warden_claim",
85
+ command: ["claim"],
86
+ description: "Acquire the lease on a cell (owner, epoch, TTL).",
87
+ positional: [
88
+ { key: "wave", desc: "Wave id" },
89
+ { key: "cell", desc: "Cell id" },
90
+ ],
91
+ valueFlags: [
92
+ { key: "actor", flag: "actor", desc: "Actor name (required by core)" },
93
+ { key: "ttl", flag: "ttl", desc: "Lease TTL in seconds", type: "number" },
94
+ { key: "config", flag: "config", desc: "Path to asylumwarden.json" },
95
+ { key: "stateHome", flag: "state-home", desc: "State home override" },
96
+ ],
97
+ boolFlags: [{ key: "steal", flag: "steal", desc: "Steal a live lease (audited)" }],
98
+ },
99
+ {
100
+ tool: "warden_renew",
101
+ command: ["renew"],
102
+ description: "Heartbeat: extend the lease of the current epoch.",
103
+ positional: [
104
+ { key: "wave", desc: "Wave id" },
105
+ { key: "cell", desc: "Cell id" },
106
+ ],
107
+ valueFlags: [
108
+ { key: "actor", flag: "actor", desc: "Actor name (required by core)" },
109
+ { key: "epoch", flag: "epoch", desc: "Lease epoch (required by core)", type: "number" },
110
+ { key: "ttl", flag: "ttl", desc: "New TTL in seconds", type: "number" },
111
+ { key: "config", flag: "config", desc: "Path to asylumwarden.json" },
112
+ { key: "stateHome", flag: "state-home", desc: "State home override" },
113
+ ],
114
+ boolFlags: [],
115
+ },
116
+ {
117
+ tool: "warden_release",
118
+ command: ["release"],
119
+ description: "Voluntarily release a held lease.",
120
+ positional: [
121
+ { key: "wave", desc: "Wave id" },
122
+ { key: "cell", desc: "Cell id" },
123
+ ],
124
+ valueFlags: [
125
+ { key: "actor", flag: "actor", desc: "Actor name (required by core)" },
126
+ { key: "epoch", flag: "epoch", desc: "Lease epoch (required by core)", type: "number" },
127
+ { key: "config", flag: "config", desc: "Path to asylumwarden.json" },
128
+ { key: "stateHome", flag: "state-home", desc: "State home override" },
129
+ ],
130
+ boolFlags: [],
131
+ },
132
+ {
133
+ tool: "warden_requeue",
134
+ command: ["requeue"],
135
+ description: "Move a failed cell back to pending.",
136
+ positional: [
137
+ { key: "wave", desc: "Wave id" },
138
+ { key: "cell", desc: "Cell id" },
139
+ ],
140
+ valueFlags: [
141
+ { key: "config", flag: "config", desc: "Path to asylumwarden.json" },
142
+ { key: "stateHome", flag: "state-home", desc: "State home override" },
143
+ ],
144
+ boolFlags: [],
145
+ },
146
+ {
147
+ tool: "warden_done",
148
+ command: ["done"],
149
+ description: "Finish a cell as done with a receipt (core validates the receipt).",
150
+ positional: [
151
+ { key: "wave", desc: "Wave id" },
152
+ { key: "cell", desc: "Cell id" },
153
+ ],
154
+ valueFlags: [
155
+ { key: "actor", flag: "actor", desc: "Actor name (required by core)" },
156
+ { key: "epoch", flag: "epoch", desc: "Lease epoch (required by core)", type: "number" },
157
+ { key: "receipt", flag: "receipt", desc: "Receipt file path (required by core)" },
158
+ { key: "config", flag: "config", desc: "Path to asylumwarden.json" },
159
+ { key: "stateHome", flag: "state-home", desc: "State home override" },
160
+ ],
161
+ boolFlags: [],
162
+ },
163
+ {
164
+ tool: "warden_fail",
165
+ command: ["fail"],
166
+ description: "Finish a cell as failed with a reason.",
167
+ positional: [
168
+ { key: "wave", desc: "Wave id" },
169
+ { key: "cell", desc: "Cell id" },
170
+ ],
171
+ valueFlags: [
172
+ { key: "actor", flag: "actor", desc: "Actor name (required by core)" },
173
+ { key: "epoch", flag: "epoch", desc: "Lease epoch (required by core)", type: "number" },
174
+ { key: "reason", flag: "reason", desc: "Failure reason (required by core)" },
175
+ { key: "config", flag: "config", desc: "Path to asylumwarden.json" },
176
+ { key: "stateHome", flag: "state-home", desc: "State home override" },
177
+ ],
178
+ boolFlags: [],
179
+ },
180
+ {
181
+ tool: "warden_status",
182
+ command: ["status"],
183
+ description: "Status report for one wave or all waves.",
184
+ positional: [{ key: "wave", desc: "Wave id (omit for all waves)" }],
185
+ valueFlags: [
186
+ { key: "config", flag: "config", desc: "Path to asylumwarden.json" },
187
+ { key: "stateHome", flag: "state-home", desc: "State home override" },
188
+ ],
189
+ boolFlags: [],
190
+ },
191
+ {
192
+ tool: "warden_verify",
193
+ command: ["verify"],
194
+ description: "Verify the ledger hash chain + anchor + receipt spot-check.",
195
+ positional: [],
196
+ valueFlags: [
197
+ { key: "wave", flag: "wave", desc: "Restrict verification to one wave" },
198
+ { key: "config", flag: "config", desc: "Path to asylumwarden.json" },
199
+ { key: "stateHome", flag: "state-home", desc: "State home override" },
200
+ ],
201
+ boolFlags: [],
202
+ },
203
+ {
204
+ tool: "warden_registry_check",
205
+ command: ["registry", "check"],
206
+ description: "Validate the registry, print its digest and the line table.",
207
+ positional: [],
208
+ valueFlags: [
209
+ { key: "config", flag: "config", desc: "Path to asylumwarden.json" },
210
+ { key: "stateHome", flag: "state-home", desc: "State home override" },
211
+ ],
212
+ boolFlags: [],
213
+ },
214
+ ];
215
+
216
+ // Pure arg -> argv mapping. Undefined/null params are omitted so core sees
217
+ // exactly what the caller passed (core decides required-ness, fail-closed).
218
+ export function buildArgv(spec, args) {
219
+ const argv = [...spec.command];
220
+ for (const p of spec.positional) {
221
+ if (args[p.key] !== undefined && args[p.key] !== null) argv.push(String(args[p.key]));
222
+ }
223
+ for (const f of spec.valueFlags) {
224
+ if (args[f.key] !== undefined && args[f.key] !== null) argv.push(`--${f.flag}`, String(args[f.key]));
225
+ }
226
+ for (const b of spec.boolFlags) {
227
+ if (args[b.key] === true) argv.push(`--${b.flag}`);
228
+ }
229
+ return argv;
230
+ }
231
+
232
+ function jsonSchemaArgs(spec) {
233
+ const args = {};
234
+ for (const p of spec.positional) args[p.key] = { type: "string", description: p.desc };
235
+ for (const f of spec.valueFlags) args[f.key] = { type: f.type ?? "string", description: f.desc };
236
+ for (const b of spec.boolFlags) args[b.key] = { type: "boolean", description: b.desc };
237
+ return args;
238
+ }
239
+
240
+ function buildTools() {
241
+ const tools = {};
242
+ for (const spec of TOOL_SPECS) {
243
+ tools[spec.tool] = {
244
+ description: spec.description,
245
+ args: jsonSchemaArgs(spec),
246
+ execute: async (rawArgs) => {
247
+ const argv = buildArgv(spec, rawArgs ?? {});
248
+ let output = "";
249
+ const exit = await runCli(argv, { write: (chunk) => { output += chunk; } });
250
+ return { output, exit };
251
+ },
252
+ };
253
+ }
254
+ return tools;
255
+ }
256
+
257
+ let logged = false;
258
+ function logOnce(reason) {
259
+ if (logged) return;
260
+ logged = true;
261
+ console.error(`[asylumwarden-adapter] ${reason} — no warden_* tools registered (the CLI still works).`);
262
+ }
263
+
264
+ // Same hooks contract the historian plugin ships: server(input, options) resolves
265
+ // hooks whose `tool` field maps tool name -> definition. Degrades to a no-op
266
+ // (empty hooks, one console.error) when the host passes a missing/unexpected
267
+ // shape — registration must never crash host startup.
268
+ async function server(input, _options) {
269
+ try {
270
+ if (input === null || typeof input !== "object") {
271
+ logOnce(`unexpected host API shape: server(input) received ${input === null ? "null" : typeof input}`);
272
+ return {};
273
+ }
274
+ return { tool: buildTools() };
275
+ } catch (err) {
276
+ logOnce(`tool registration failed: ${err instanceof Error ? err.message : String(err)}`);
277
+ return {};
278
+ }
279
+ }
280
+
281
+ const plugin = { id: "asylumwarden", server };
282
+ export default plugin;
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import process from "node:process";
3
+ import { runCli } from "../src/cli/main.mjs";
4
+
5
+ process.exitCode = await runCli(process.argv.slice(2));
package/package.json CHANGED
@@ -1,6 +1,39 @@
1
1
  {
2
2
  "name": "asylumwarden",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "Local, file-first control plane for multi-seat AI work: registry, DAG, leases, receipts, ledger.",
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "engines": {
9
+ "node": ">=22.22.1"
10
+ },
11
+ "bin": {
12
+ "asylumwarden": "bin/asylumwarden.mjs"
13
+ },
14
+ "scripts": {
15
+ "test": "node tools/workfile-gate.mjs && node --test 'test/**/*.test.mjs'",
16
+ "gate:imports": "node tools/import-gate.mjs",
17
+ "gate:workfiles": "node tools/workfile-gate.mjs"
18
+ },
19
+ "exports": {
20
+ ".": "./src/index.mjs",
21
+ "./server": "./adapters/opencode/index.js"
22
+ },
23
+ "files": [
24
+ "src",
25
+ "schemas",
26
+ "bin",
27
+ "adapters",
28
+ "README.md",
29
+ "LICENSE"
30
+ ],
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/TachikomaGundam/AsylumWarden.git"
34
+ },
35
+ "homepage": "https://github.com/TachikomaGundam/AsylumWarden",
36
+ "bugs": {
37
+ "url": "https://github.com/TachikomaGundam/AsylumWarden/issues"
38
+ }
39
+ }
@@ -0,0 +1,93 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "asylumwarden/config.schema.json",
4
+ "title": "AsylumWarden project config (asylumwarden.json)",
5
+ "type": "object",
6
+ "allOf": [
7
+ {
8
+ "anyOf": [
9
+ {
10
+ "description": "normal mode: shadow keys forbidden",
11
+ "properties": {
12
+ "version": { "const": 1 },
13
+ "registry": { "type": "string", "pattern": "^~?/" },
14
+ "registry_sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
15
+ "self": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$" },
16
+ "state_home": { "type": "string", "pattern": "^~?/" },
17
+ "mode": { "const": "normal" },
18
+ "kinds": {
19
+ "type": "object",
20
+ "additionalProperties": {
21
+ "type": "object",
22
+ "properties": { "description": { "type": "string", "minLength": 1, "maxLength": 4096 } },
23
+ "required": ["description"],
24
+ "additionalProperties": false
25
+ }
26
+ },
27
+ "anchor": {
28
+ "anyOf": [
29
+ { "type": "null" },
30
+ {
31
+ "type": "object",
32
+ "properties": {
33
+ "type": { "enum": ["command", "remote", "human-checkpoint", "file"] },
34
+ "target": { "type": "string", "minLength": 1, "maxLength": 4096 },
35
+ "signer": { "type": "string", "minLength": 1, "maxLength": 256 }
36
+ },
37
+ "required": ["type", "target"],
38
+ "additionalProperties": false
39
+ }
40
+ ]
41
+ }
42
+ },
43
+ "required": ["version", "registry", "registry_sha256", "self", "state_home", "mode"],
44
+ "additionalProperties": false
45
+ },
46
+ {
47
+ "description": "shadow mode: shadow_of and shadow_home required",
48
+ "properties": {
49
+ "version": { "const": 1 },
50
+ "registry": { "type": "string", "pattern": "^~?/" },
51
+ "registry_sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
52
+ "self": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$" },
53
+ "state_home": { "type": "string", "pattern": "^~?/" },
54
+ "mode": { "const": "shadow" },
55
+ "shadow_of": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
56
+ "shadow_home": { "type": "string", "pattern": "^/" },
57
+ "protect": {
58
+ "type": "array",
59
+ "maxItems": 64,
60
+ "items": { "type": "string", "pattern": "^~?/" }
61
+ },
62
+ "kinds": {
63
+ "type": "object",
64
+ "additionalProperties": {
65
+ "type": "object",
66
+ "properties": { "description": { "type": "string", "minLength": 1, "maxLength": 4096 } },
67
+ "required": ["description"],
68
+ "additionalProperties": false
69
+ }
70
+ },
71
+ "anchor": {
72
+ "anyOf": [
73
+ { "type": "null" },
74
+ {
75
+ "type": "object",
76
+ "properties": {
77
+ "type": { "enum": ["command", "remote", "human-checkpoint", "file"] },
78
+ "target": { "type": "string", "minLength": 1, "maxLength": 4096 },
79
+ "signer": { "type": "string", "minLength": 1, "maxLength": 256 }
80
+ },
81
+ "required": ["type", "target"],
82
+ "additionalProperties": false
83
+ }
84
+ ]
85
+ }
86
+ },
87
+ "required": ["version", "registry", "registry_sha256", "self", "state_home", "mode", "shadow_of", "shadow_home", "protect"],
88
+ "additionalProperties": false
89
+ }
90
+ ]
91
+ }
92
+ ]
93
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "asylumwarden/ledger.schema.json",
4
+ "title": "AsylumWarden ledger entry (one per JSONL line)",
5
+ "type": "object",
6
+ "properties": {
7
+ "seq": { "type": "integer", "minimum": 1 },
8
+ "ts": { "type": "string", "format": "date-time" },
9
+ "type": {
10
+ "enum": ["genesis", "plan", "claim", "renew", "release", "requeue", "steal", "done", "fail", "anchor"]
11
+ },
12
+ "wave": { "type": ["string", "null"], "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$" },
13
+ "cell": { "type": ["string", "null"], "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$" },
14
+ "actor": { "type": ["string", "null"], "minLength": 1, "maxLength": 256 },
15
+ "payload": { "type": "object" },
16
+ "prev": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
17
+ "hash": { "type": "string", "pattern": "^[0-9a-f]{64}$" }
18
+ },
19
+ "required": ["seq", "ts", "type", "wave", "cell", "actor", "payload", "prev", "hash"],
20
+ "additionalProperties": false
21
+ }
@@ -0,0 +1,49 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "asylumwarden/manifest.schema.json",
4
+ "title": "AsylumWarden wave manifest (wave.json)",
5
+ "type": "object",
6
+ "properties": {
7
+ "version": { "const": 1 },
8
+ "wave": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$" },
9
+ "cells": {
10
+ "type": "array",
11
+ "minItems": 1,
12
+ "maxItems": 1024,
13
+ "items": {
14
+ "type": "object",
15
+ "properties": {
16
+ "id": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$" },
17
+ "kind": { "type": "string", "pattern": "^[a-z][a-z0-9_-]{0,31}$" },
18
+ "line": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$" },
19
+ "cwd": { "type": "string", "pattern": "^/" },
20
+ "allowed_roots": {
21
+ "type": "array",
22
+ "minItems": 1,
23
+ "maxItems": 64,
24
+ "items": { "type": "string", "pattern": "^/" }
25
+ },
26
+ "read_roots": {
27
+ "type": "array",
28
+ "maxItems": 64,
29
+ "items": { "type": "string", "pattern": "^/" }
30
+ },
31
+ "deps": {
32
+ "type": "array",
33
+ "maxItems": 1024,
34
+ "items": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$" }
35
+ },
36
+ "role": { "type": "string", "minLength": 1, "maxLength": 256 },
37
+ "role_file": { "type": "string", "pattern": "^/" },
38
+ "session_started_at": { "type": "string", "format": "date-time" },
39
+ "brief": { "type": "string", "maxLength": 4096 },
40
+ "acceptance": { "type": "string", "maxLength": 4096 }
41
+ },
42
+ "required": ["id", "kind", "line", "cwd", "allowed_roots", "deps"],
43
+ "additionalProperties": false
44
+ }
45
+ }
46
+ },
47
+ "required": ["version", "wave", "cells"],
48
+ "additionalProperties": false
49
+ }