asylumwarden 0.0.0 → 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/README.md CHANGED
@@ -1,14 +1,16 @@
1
1
  # AsylumWarden
2
2
 
3
- **Status: architecture phase. No runtime code. The npm package `asylumwarden@0.0.0`
4
- is a name reservation; the first functional release will be `0.1.0`.**
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`.**
5
6
 
6
7
  AsylumWarden is a local, file-first control plane for multi-seat AI work. It
7
8
  keeps the plan, leases, receipts, and ledger on disk. It does not run a daemon,
8
9
  require a database, or replace the host's sandbox.
9
10
 
10
11
  - GitHub: <https://github.com/TachikomaGundam/AsylumWarden>
11
- - npm: `asylumwarden` (0.0.0 name reservation; no runtime code yet)
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))
12
14
  - License: MIT
13
15
 
14
16
  ## What it is
@@ -30,8 +32,11 @@ require a database, or replace the host's sandbox.
30
32
  ## Design
31
33
 
32
34
  The approved architecture is in
33
- [`docs/architecture-handoff.md`](docs/architecture-handoff.md). The PCB pilot
34
- boundary is in [`docs/PILOT-PCB.md`](docs/PILOT-PCB.md). Release rules are 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
35
40
  [`docs/RELEASING.md`](docs/RELEASING.md).
36
41
 
37
42
  ## Boundary with Abathur
@@ -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,6 @@
1
1
  {
2
2
  "name": "asylumwarden",
3
- "version": "0.0.0",
3
+ "version": "0.1.0",
4
4
  "private": false,
5
5
  "description": "Local, file-first control plane for multi-seat AI work: registry, DAG, leases, receipts, ledger.",
6
6
  "type": "module",
@@ -8,10 +8,23 @@
8
8
  "engines": {
9
9
  "node": ">=22.22.1"
10
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
+ },
11
23
  "files": [
12
24
  "src",
13
25
  "schemas",
14
26
  "bin",
27
+ "adapters",
15
28
  "README.md",
16
29
  "LICENSE"
17
30
  ],
@@ -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
+ }
@@ -0,0 +1,102 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "asylumwarden/receipt.schema.json",
4
+ "title": "AsylumWarden receipt",
5
+ "type": "object",
6
+ "allOf": [
7
+ {
8
+ "anyOf": [
9
+ {
10
+ "description": "normal receipt: shadow keys forbidden",
11
+ "properties": {
12
+ "version": { "const": 1 },
13
+ "cell": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$" },
14
+ "actor": { "type": "string", "minLength": 1, "maxLength": 256 },
15
+ "epoch": { "type": "integer", "minimum": 1 },
16
+ "status": { "enum": ["done", "failed"] },
17
+ "summary": { "type": "string", "minLength": 1, "maxLength": 4096 },
18
+ "commands": {
19
+ "type": "array",
20
+ "maxItems": 256,
21
+ "items": {
22
+ "type": "object",
23
+ "properties": {
24
+ "cmd": { "type": "string", "minLength": 1, "maxLength": 4096 },
25
+ "exit": { "type": "integer", "minimum": 0 }
26
+ },
27
+ "required": ["cmd", "exit"],
28
+ "additionalProperties": false
29
+ }
30
+ },
31
+ "artifacts": {
32
+ "type": "array",
33
+ "maxItems": 1024,
34
+ "items": {
35
+ "type": "object",
36
+ "properties": {
37
+ "path": { "type": "string", "pattern": "^/" },
38
+ "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }
39
+ },
40
+ "required": ["path", "sha256"],
41
+ "additionalProperties": false
42
+ }
43
+ },
44
+ "reads": {
45
+ "type": "array",
46
+ "maxItems": 4096,
47
+ "items": { "type": "string", "pattern": "^/" }
48
+ }
49
+ },
50
+ "required": ["version", "cell", "actor", "epoch", "status", "summary"],
51
+ "additionalProperties": false
52
+ },
53
+ {
54
+ "description": "shadow receipt: mode and shadow_of required; wave id carries the shadow- prefix",
55
+ "properties": {
56
+ "version": { "const": 1 },
57
+ "cell": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$" },
58
+ "actor": { "type": "string", "minLength": 1, "maxLength": 256 },
59
+ "epoch": { "type": "integer", "minimum": 1 },
60
+ "status": { "enum": ["done", "failed"] },
61
+ "summary": { "type": "string", "minLength": 1, "maxLength": 4096 },
62
+ "mode": { "const": "shadow" },
63
+ "shadow_of": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
64
+ "commands": {
65
+ "type": "array",
66
+ "maxItems": 256,
67
+ "items": {
68
+ "type": "object",
69
+ "properties": {
70
+ "cmd": { "type": "string", "minLength": 1, "maxLength": 4096 },
71
+ "exit": { "type": "integer", "minimum": 0 }
72
+ },
73
+ "required": ["cmd", "exit"],
74
+ "additionalProperties": false
75
+ }
76
+ },
77
+ "artifacts": {
78
+ "type": "array",
79
+ "maxItems": 1024,
80
+ "items": {
81
+ "type": "object",
82
+ "properties": {
83
+ "path": { "type": "string", "pattern": "^/" },
84
+ "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }
85
+ },
86
+ "required": ["path", "sha256"],
87
+ "additionalProperties": false
88
+ }
89
+ },
90
+ "reads": {
91
+ "type": "array",
92
+ "maxItems": 4096,
93
+ "items": { "type": "string", "pattern": "^/" }
94
+ }
95
+ },
96
+ "required": ["version", "cell", "actor", "epoch", "status", "summary", "mode", "shadow_of"],
97
+ "additionalProperties": false
98
+ }
99
+ ]
100
+ }
101
+ ]
102
+ }
@@ -0,0 +1,43 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "asylumwarden/registry.schema.json",
4
+ "title": "AsylumWarden machine registry (registry.json / shadow-registry.json)",
5
+ "type": "object",
6
+ "properties": {
7
+ "version": { "const": 1 },
8
+ "lines": {
9
+ "type": "object",
10
+ "minProperties": 1,
11
+ "additionalProperties": {
12
+ "type": "object",
13
+ "properties": {
14
+ "roots": {
15
+ "type": "array",
16
+ "minItems": 1,
17
+ "maxItems": 64,
18
+ "items": { "type": "string", "pattern": "^/" }
19
+ },
20
+ "forbidden_surfaces": {
21
+ "type": "array",
22
+ "maxItems": 256,
23
+ "items": { "type": "string", "pattern": "^[^/]" }
24
+ },
25
+ "forbidden_absolute": {
26
+ "type": "array",
27
+ "maxItems": 256,
28
+ "items": { "type": "string", "pattern": "^/" }
29
+ },
30
+ "read_roots": {
31
+ "type": "array",
32
+ "maxItems": 64,
33
+ "items": { "type": "string", "pattern": "^/" }
34
+ }
35
+ },
36
+ "required": ["roots", "forbidden_surfaces"],
37
+ "additionalProperties": false
38
+ }
39
+ }
40
+ },
41
+ "required": ["version", "lines"],
42
+ "additionalProperties": false
43
+ }