@medicine-wheel/app 0.5.6 → 0.5.8
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/app/api/plan-perspectives/route.ts +0 -11
- package/dist/cli/skills.js +267 -96
- package/package.json +24 -23
|
@@ -13,17 +13,6 @@ export async function GET(request: Request) {
|
|
|
13
13
|
const { searchParams } = new URL(request.url);
|
|
14
14
|
const filters = planPerspectiveFiltersFromSearchParams(searchParams);
|
|
15
15
|
|
|
16
|
-
if (
|
|
17
|
-
filters.episode_path === undefined &&
|
|
18
|
-
filters.session_id === undefined &&
|
|
19
|
-
filters.id === undefined
|
|
20
|
-
) {
|
|
21
|
-
return NextResponse.json(
|
|
22
|
-
{ error: "Plan Perspective queries require episode_path, session_id, or id" },
|
|
23
|
-
{ status: 400 },
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
16
|
const store = await createProvider();
|
|
28
17
|
const planPerspectives = await store.listPlanPerspectives(filters);
|
|
29
18
|
|
package/dist/cli/skills.js
CHANGED
|
@@ -203,36 +203,62 @@ mw skill run ceremony-guide "Community data review"
|
|
|
203
203
|
title: 'Infrastructure Audit',
|
|
204
204
|
description: 'Audit hosts, services, and port bindings across the Medicine Wheel estate',
|
|
205
205
|
target: 'cli',
|
|
206
|
-
complement: 'infra-
|
|
206
|
+
complement: 'infra-topology',
|
|
207
207
|
body: `# Skill: Infrastructure Audit
|
|
208
208
|
|
|
209
209
|
## Purpose
|
|
210
|
-
|
|
210
|
+
Read the registered topology — hosts, the tenants upon them, the services those
|
|
211
|
+
tenants own, the ports those services claim — and surface conflicts and métis.
|
|
211
212
|
|
|
212
|
-
##
|
|
213
|
-
- Target host (optional — audit all if omitted)
|
|
214
|
-
- Facet type filter (host / tenant / service, optional)
|
|
215
|
-
- Conflict scope (declared / observed / union)
|
|
213
|
+
## What runs today
|
|
216
214
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
215
|
+
These MCP tools are registered and executable. This is not a roadmap.
|
|
216
|
+
|
|
217
|
+
\`\`\`
|
|
218
|
+
list_infra_topology { perspective: "host" } # machines, nested
|
|
219
|
+
list_infra_topology { perspective: "port", host: "eury" }# the slot map for one machine
|
|
220
|
+
list_infra_topology { perspective: "metis" } # only what people hold, with carriers
|
|
221
|
+
detect_port_conflicts {} # declared state against itself
|
|
222
|
+
\`\`\`
|
|
223
|
+
|
|
224
|
+
Five perspectives, because one shape cannot answer every question:
|
|
225
|
+
\`host\` nests tenants and services under machines · \`tenant\` reads accounts and
|
|
226
|
+
their linger · \`service\` reads units, scopes and stop commands · \`port\` reads the
|
|
227
|
+
slot map with \`contested\` marked · \`metis\` reads only what people hold.
|
|
228
|
+
|
|
229
|
+
## What the audit reads
|
|
230
|
+
|
|
231
|
+
- **Graph** — hosts → tenants → services, walkable as \`part-of\` and \`binds-port\`
|
|
232
|
+
edges. \`node_id\` is required and must be a registered node (take one from
|
|
233
|
+
\`list_infra_topology\`); without a centre the call returns an error rather than
|
|
234
|
+
an empty web that would read as "this host has nothing on it":
|
|
235
|
+
|
|
236
|
+
\`\`\`
|
|
237
|
+
get_relational_web { node_id: "node:land:host:eury", edge_types: ["part-of","binds-port"] }
|
|
238
|
+
\`\`\`
|
|
239
|
+
- **Port conflicts** — distinct services colliding on the same host|proto|port.
|
|
240
|
+
One service seen twice is not a conflict; ports are scarce per host, not globally
|
|
241
|
+
- **Métis surface** — exceptions, invisible work, and \`heldBy\` naming a person
|
|
242
|
+
- **Reachability** — lan / tailnet / cloudflare / ngrok, per \`HostFacet\`
|
|
243
|
+
|
|
244
|
+
## What is NOT here
|
|
245
|
+
|
|
246
|
+
Reachability is recorded, not probed. Nothing in this repo pings a host. A
|
|
247
|
+
\`reachableVia\` of \`tailnet\` is a claim somebody wrote down, and an audit that
|
|
248
|
+
presented it as a live check would be the exact class of lie \`reconcile\` exists
|
|
249
|
+
to catch.
|
|
222
250
|
|
|
223
251
|
## Usage
|
|
224
252
|
|
|
225
|
-
>
|
|
226
|
-
>
|
|
253
|
+
> \`mw skill run\` has no implementation and exits 3. A skill is a document, not
|
|
254
|
+
> a program. **The capability above is real and reachable through MCP** — this
|
|
255
|
+
> file tells you which calls to make.
|
|
227
256
|
>
|
|
228
|
-
>
|
|
229
|
-
> SKILL.md into \`.mw/skills/\`. Follow the steps above yourself, or hand this
|
|
230
|
-
> file to an agent.
|
|
257
|
+
> \`mw skill install infrastructure-audit\` writes this SKILL.md into \`.mw/skills/\`.
|
|
231
258
|
|
|
232
259
|
Planned invocation (does not run yet):
|
|
233
260
|
\`\`\`
|
|
234
261
|
mw skill run infrastructure-audit --host eury
|
|
235
|
-
mw skill run infrastructure-audit --type service --conflicts
|
|
236
262
|
\`\`\`
|
|
237
263
|
`,
|
|
238
264
|
},
|
|
@@ -241,40 +267,77 @@ mw skill run infrastructure-audit --type service --conflicts
|
|
|
241
267
|
title: 'Service Provisioning',
|
|
242
268
|
description: 'Propose and gate service deployment through ceremony-aware preconditions',
|
|
243
269
|
target: 'cli',
|
|
244
|
-
complement: 'infra-
|
|
270
|
+
complement: 'infra-topology',
|
|
245
271
|
body: `# Skill: Service Provisioning
|
|
246
272
|
|
|
247
273
|
## Purpose
|
|
248
|
-
|
|
274
|
+
Register a service into the wheel and gate it on preconditions before it runs.
|
|
249
275
|
|
|
250
|
-
##
|
|
251
|
-
- Service name and unit (e.g., assembly-mux.service)
|
|
252
|
-
- Port binding claims (host, port, proto)
|
|
253
|
-
- Owner (tenant nodeId)
|
|
254
|
-
- Optional: working directory, execStop, métis exceptions
|
|
276
|
+
## What runs today
|
|
255
277
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
278
|
+
\`\`\`
|
|
279
|
+
register_host { hostname: "eury", reachable_via: ["tailnet"] }
|
|
280
|
+
register_tenant { account: "ava", on_host: "eury", linger: "enabled" }
|
|
281
|
+
register_service {
|
|
282
|
+
unit: "zulip.service", owned_by: "ava", scope: "system",
|
|
283
|
+
ports: [{ port: 3000, proto: "tcp", host: "eury" }],
|
|
284
|
+
working_directory: "/opt/zulip",
|
|
285
|
+
exec_stop: "docker compose stop"
|
|
286
|
+
}
|
|
287
|
+
\`\`\`
|
|
288
|
+
|
|
289
|
+
Registration is **idempotent by identity**, not by node id: a host is its
|
|
290
|
+
hostname, a tenant is its account on its host, a service is its unit under its
|
|
291
|
+
owner. Re-running a provisioning step updates in place and returns \`updated\`.
|
|
292
|
+
Without that, a re-run fills the wheel with duplicate services and every one of
|
|
293
|
+
them then appears to collide with itself.
|
|
294
|
+
|
|
295
|
+
\`register_service\` returns \`port_conflicts\` involving the new service. It still
|
|
296
|
+
registers on a collision — the wheel records what is true, not what is tidy — and
|
|
297
|
+
says plainly not to provision against it.
|
|
298
|
+
|
|
299
|
+
## The gate
|
|
300
|
+
|
|
301
|
+
\`\`\`
|
|
302
|
+
mw_enforce_gate {
|
|
303
|
+
service_node_id: "node:knowledge:...",
|
|
304
|
+
preconditions: [{
|
|
305
|
+
id: "pre:ava-linger",
|
|
306
|
+
gates: "node:knowledge:...",
|
|
307
|
+
fact: { kind: "linger", facetNodeId: "node:human:ava",
|
|
308
|
+
expected: "enabled", observed: "enabled", observedAt: "..." },
|
|
309
|
+
consent: { consentId: "consent:root-step:ava", state: "active", readAt: "..." }
|
|
310
|
+
}]
|
|
311
|
+
}
|
|
312
|
+
\`\`\`
|
|
313
|
+
|
|
314
|
+
Four verdicts, and the distinctions are the point:
|
|
315
|
+
|
|
316
|
+
| verdict | means |
|
|
317
|
+
|---|---|
|
|
318
|
+
| \`satisfied\` | every declared half was read and holds |
|
|
319
|
+
| \`unsatisfied\` | a machine fact was read and does not match |
|
|
320
|
+
| \`unauthorized\` | the machine is ready and a human has not said yes, or withdrew |
|
|
321
|
+
| \`unknown\` | a declared half has **not been read** — not the same as false |
|
|
322
|
+
|
|
323
|
+
**\`linger\` is not consent.** It is a checkbox with no authority and no ability to
|
|
324
|
+
withdraw. The authorization to run the root step that set it is a
|
|
325
|
+
\`ConsentRecord\` in \`consent-lifecycle\`, referenced by id. An \`unauthorized\` gate
|
|
326
|
+
cannot be cleared by restarting anything, and a report that collapsed it into
|
|
327
|
+
\`unsatisfied\` would invite exactly that.
|
|
328
|
+
|
|
329
|
+
A service with **zero** declared preconditions returns \`ready: true\` with a
|
|
330
|
+
warning saying nothing was verified. Vacuous truth, named out loud.
|
|
262
331
|
|
|
263
332
|
## Usage
|
|
264
333
|
|
|
265
|
-
>
|
|
266
|
-
> It prints this explanation and exits 3 — nothing is executed. Planned, not shipped.
|
|
334
|
+
> \`mw skill run\` has no implementation and exits 3. The calls above are real.
|
|
267
335
|
>
|
|
268
|
-
>
|
|
269
|
-
> SKILL.md into \`.mw/skills/\`. Follow the steps above yourself, or hand this
|
|
270
|
-
> file to an agent.
|
|
336
|
+
> \`mw skill install service-provisioning\` writes this SKILL.md into \`.mw/skills/\`.
|
|
271
337
|
|
|
272
338
|
Planned invocation (does not run yet):
|
|
273
339
|
\`\`\`
|
|
274
|
-
mw skill run service-provisioning
|
|
275
|
-
--unit zulip.service \
|
|
276
|
-
--port 3000:tcp@eury \
|
|
277
|
-
--owner "node:human:ava"
|
|
340
|
+
mw skill run service-provisioning --unit zulip.service --port 3000:tcp@eury
|
|
278
341
|
\`\`\`
|
|
279
342
|
`,
|
|
280
343
|
},
|
|
@@ -283,35 +346,72 @@ mw skill run service-provisioning \
|
|
|
283
346
|
title: 'Drift Reconciliation',
|
|
284
347
|
description: 'Compare declared vs observed infrastructure state and propose healing steps',
|
|
285
348
|
target: 'cli',
|
|
286
|
-
complement: 'infra-
|
|
349
|
+
complement: 'infra-topology',
|
|
287
350
|
body: `# Skill: Drift Reconciliation
|
|
288
351
|
|
|
289
352
|
## Purpose
|
|
290
|
-
|
|
353
|
+
Compare what the wheel declares against what is actually running on a host, and
|
|
354
|
+
say the distance out loud.
|
|
291
355
|
|
|
292
|
-
##
|
|
293
|
-
- Drift scope: specific host, all hosts, or facet type
|
|
294
|
-
- Healing mode: audit-only / propose-fix / execute-with-gates
|
|
356
|
+
## What runs today
|
|
295
357
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
-
|
|
300
|
-
|
|
358
|
+
\`\`\`
|
|
359
|
+
reconcile_infra_state {
|
|
360
|
+
observed_by: "gaia",
|
|
361
|
+
observed_at: "2026-08-05T12:00:00Z",
|
|
362
|
+
services: [ /* ServiceFacet shapes read live from the machine */ ]
|
|
363
|
+
}
|
|
364
|
+
\`\`\`
|
|
365
|
+
|
|
366
|
+
**Level-triggered.** It compares the current declared level against the current
|
|
367
|
+
observed level, every time, and remembers no transitions. There is no "converged
|
|
368
|
+
a minute ago" state to go stale. The outage this answers (jgwill/gaia#74) was not
|
|
369
|
+
caused by a missing fact — it was caused by a fact that HAD been true.
|
|
370
|
+
|
|
371
|
+
\`observed_by\` and \`observed_at\` are required. An observation with no reader and
|
|
372
|
+
no time is a rumour, and reconciling against a rumour is the snapshot problem
|
|
373
|
+
wearing a different hat. \`observationAgeMs\` comes back so a caller can refuse a
|
|
374
|
+
reading that is too old.
|
|
375
|
+
|
|
376
|
+
## The four drift states
|
|
377
|
+
|
|
378
|
+
| state | means |
|
|
379
|
+
|---|---|
|
|
380
|
+
| \`converged\` | both sides agree on every compared field |
|
|
381
|
+
| \`drifted\` | both exist and disagree — the differing fields are named |
|
|
382
|
+
| \`unrealized\` | declared, not observed — written down, not running |
|
|
383
|
+
| \`undeclared\` | **observed, not declared** — running and nobody wrote it down |
|
|
384
|
+
|
|
385
|
+
\`undeclared\` carries the most information. It finds the service somebody started
|
|
386
|
+
by hand at 2am — which is the service holding the port the next tenant is about
|
|
387
|
+
to be given. Read that row first.
|
|
388
|
+
|
|
389
|
+
Port conflicts over declared ∪ observed are folded into the same answer, because
|
|
390
|
+
this is the one call where both sides are in hand at once.
|
|
391
|
+
|
|
392
|
+
## Métis is never drift
|
|
393
|
+
|
|
394
|
+
Two sides holding different tacit notes is not a discrepancy — it is two people
|
|
395
|
+
knowing different things. Métis is carried through untouched and never appears in
|
|
396
|
+
\`differences\`. A reconciler that reported it would be asking an operator to
|
|
397
|
+
delete what they know to make a table go green.
|
|
398
|
+
|
|
399
|
+
## Reading the machine
|
|
400
|
+
|
|
401
|
+
Collecting the observed side is **not** in this repo. There is no systemd or
|
|
402
|
+
runit adapter here; \`reconcile_infra_state\` takes the reading as an argument. The
|
|
403
|
+
service-manager adapter is owned by the device lane (jgwill/medicine-wheel#118),
|
|
404
|
+
which is the only body in this system that can test runit.
|
|
301
405
|
|
|
302
406
|
## Usage
|
|
303
407
|
|
|
304
|
-
>
|
|
305
|
-
> It prints this explanation and exits 3 — nothing is executed. Planned, not shipped.
|
|
408
|
+
> \`mw skill run\` has no implementation and exits 3.
|
|
306
409
|
>
|
|
307
|
-
>
|
|
308
|
-
> SKILL.md into \`.mw/skills/\`. Follow the steps above yourself, or hand this
|
|
309
|
-
> file to an agent.
|
|
410
|
+
> \`mw skill install drift-reconciliation\` writes this SKILL.md into \`.mw/skills/\`.
|
|
310
411
|
|
|
311
412
|
Planned invocation (does not run yet):
|
|
312
413
|
\`\`\`
|
|
313
414
|
mw skill run drift-reconciliation --host gaia --propose
|
|
314
|
-
mw skill run drift-reconciliation --type service --audit-only
|
|
315
415
|
\`\`\`
|
|
316
416
|
`,
|
|
317
417
|
},
|
|
@@ -449,76 +549,147 @@ mwsrv skill run session-manager
|
|
|
449
549
|
`,
|
|
450
550
|
},
|
|
451
551
|
{
|
|
452
|
-
name: 'infra-
|
|
453
|
-
title: 'Infrastructure
|
|
454
|
-
description: '
|
|
552
|
+
name: 'infra-topology',
|
|
553
|
+
title: 'Infrastructure Topology',
|
|
554
|
+
description: 'The registered topology — hosts, tenants, services, ports — and its drift, via MCP',
|
|
455
555
|
target: 'srv',
|
|
456
556
|
complement: 'infrastructure-audit',
|
|
457
|
-
body: `# Skill: Infrastructure
|
|
557
|
+
body: `# Skill: Infrastructure Topology
|
|
458
558
|
|
|
459
559
|
## Purpose
|
|
460
|
-
|
|
560
|
+
Hold the shape of the estate: which machines exist, who lives on them, what runs
|
|
561
|
+
under those accounts, which slots are claimed, and how far the record has drifted
|
|
562
|
+
from what is running.
|
|
461
563
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
564
|
+
> Renamed from \`infra-monitor\`. That record described polling systemd, which
|
|
565
|
+
> nothing in this repository does — it named a capability that did not exist and
|
|
566
|
+
> apologised for it. Topology is what is actually held here.
|
|
567
|
+
|
|
568
|
+
## What runs today
|
|
569
|
+
|
|
570
|
+
\`\`\`
|
|
571
|
+
list_infra_topology { perspective: "host" | "tenant" | "service" | "port" | "metis" }
|
|
572
|
+
detect_port_conflicts { observed: [...] } # union live readings with the record
|
|
573
|
+
reconcile_infra_state { observed_by, observed_at, services }
|
|
574
|
+
hold_metis { node_id, held_by, exceptions: [...] }
|
|
575
|
+
mw_enforce_gate { service_node_id, preconditions: [...] }
|
|
576
|
+
mw_get_direction { node_id } # service → West, tenant → South
|
|
577
|
+
\`\`\`
|
|
578
|
+
|
|
579
|
+
## Backed by
|
|
580
|
+
|
|
581
|
+
- \`@medicine-wheel/infra\` — \`HostFacet\`, \`TenantFacet\`, \`ServiceFacet\`,
|
|
582
|
+
\`PortBinding\`, \`MetisHold\`, \`Precondition\`, \`ObservedState\`,
|
|
583
|
+
\`detectPortConflicts\`, \`preconditionGuard\`, \`readyService\`, \`reconcile\`
|
|
584
|
+
- \`@medicine-wheel/ontology-core\` — \`part-of\`, \`ordered-after\`, \`binds-port\` in
|
|
585
|
+
the governed \`KINSHIP_EDGE_TYPES\`; \`INFRA_ENTITY_BINDING\` for which closed
|
|
586
|
+
\`NodeType\` each kind rides. The union stays closed at six
|
|
587
|
+
- Persistence: JSONL, or the server API when \`MW_API_URL\` is set
|
|
588
|
+
|
|
589
|
+
## What this skill does NOT do
|
|
590
|
+
|
|
591
|
+
**It does not poll.** There is no daemon, no interval, no event stream in this
|
|
592
|
+
repo. Every call above is made by an agent or a person when they want the answer.
|
|
593
|
+
A skill that claimed to watch would be claiming a process nobody starts.
|
|
594
|
+
|
|
595
|
+
**It does not read systemd.** \`reconcile_infra_state\` and \`detect_port_conflicts\`
|
|
596
|
+
take the observed side as an argument. The service-manager adapter — systemd on
|
|
597
|
+
gaia, runit on the Termux device — is owned by the device lane
|
|
598
|
+
(jgwill/medicine-wheel#118), the only body here that can test runit.
|
|
599
|
+
|
|
600
|
+
That seam is deliberate. It keeps \`@medicine-wheel/infra\` free of I/O, and it
|
|
601
|
+
keeps this skill from describing a machine it cannot reach.
|
|
474
602
|
|
|
475
603
|
## Usage
|
|
476
604
|
|
|
477
|
-
>
|
|
478
|
-
>
|
|
605
|
+
> \`mwsrv skill run\` has no implementation and exits 3. A skill is a document.
|
|
606
|
+
> **The MCP calls above are registered and executable.**
|
|
479
607
|
>
|
|
480
|
-
>
|
|
481
|
-
> SKILL.md into \`.mw/skills/\`. Follow the steps above yourself, or hand this
|
|
482
|
-
> file to an agent.
|
|
608
|
+
> \`mwsrv skill install infra-topology\` writes this SKILL.md into \`.mw/skills/\`.
|
|
483
609
|
|
|
484
610
|
Planned invocation (does not run yet):
|
|
485
611
|
\`\`\`
|
|
486
|
-
mwsrv skill run infra-
|
|
487
|
-
mwsrv skill run infra-monitor --host gaia --linger-report
|
|
612
|
+
mwsrv skill run infra-topology --host gaia
|
|
488
613
|
\`\`\`
|
|
489
614
|
`,
|
|
490
615
|
},
|
|
491
616
|
{
|
|
492
617
|
name: 'precondition-guard',
|
|
493
618
|
title: 'Precondition Guard',
|
|
494
|
-
description: 'Evaluate infrastructure preconditions (linger,
|
|
619
|
+
description: 'Evaluate infrastructure preconditions (linger, port-free, unit-present, working-directory) against the consent that authorizes them',
|
|
495
620
|
target: 'srv',
|
|
496
621
|
complement: 'service-provisioning',
|
|
497
622
|
body: `# Skill: Precondition Guard
|
|
498
623
|
|
|
499
624
|
## Purpose
|
|
500
|
-
|
|
625
|
+
Hold a service at the door until what must be true is true — and keep the machine
|
|
626
|
+
half and the human half from standing in for each other.
|
|
501
627
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
- **Consent accountability** — ConsentRecord.id must be present and active
|
|
506
|
-
- **Reachability** — host must be reachable via declared transport (lan, tailnet, cloudflare, ngrok)
|
|
628
|
+
> No longer a roadmap item. \`Precondition\`, \`preconditionGuard\` and
|
|
629
|
+
> \`readyService\` ship in \`@medicine-wheel/infra\`, reachable through
|
|
630
|
+
> \`mw_enforce_gate\`.
|
|
507
631
|
|
|
508
|
-
##
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
632
|
+
## What runs today
|
|
633
|
+
|
|
634
|
+
\`\`\`
|
|
635
|
+
mw_enforce_gate {
|
|
636
|
+
service_node_id: "node:knowledge:...",
|
|
637
|
+
preconditions: [{
|
|
638
|
+
id: "pre:gmusic-linger",
|
|
639
|
+
gates: "node:knowledge:...",
|
|
640
|
+
description: "gmusic user units must survive logout",
|
|
641
|
+
fact: { kind: "linger", facetNodeId: "node:human:gmusic",
|
|
642
|
+
expected: "enabled", observed: "enabled", observedAt: "..." },
|
|
643
|
+
consent: { consentId: "consent:root-step:gmusic", state: "active", readAt: "..." },
|
|
644
|
+
metis: { exceptions: ["restart twice; first start races the mount"],
|
|
645
|
+
heldBy: "William" }
|
|
646
|
+
}]
|
|
647
|
+
}
|
|
648
|
+
\`\`\`
|
|
649
|
+
|
|
650
|
+
Preconditions gating other services are ignored, so passing a whole host's set is
|
|
651
|
+
safe.
|
|
652
|
+
|
|
653
|
+
## Precondition kinds
|
|
654
|
+
|
|
655
|
+
\`linger\` · \`port-free\` · \`unit-present\` · \`working-directory\`
|
|
656
|
+
|
|
657
|
+
Every one of these is something a host will answer without a person present.
|
|
658
|
+
**That is exactly what disqualifies them from carrying consent.**
|
|
659
|
+
|
|
660
|
+
## The four verdicts
|
|
661
|
+
|
|
662
|
+
| verdict | means |
|
|
663
|
+
|---|---|
|
|
664
|
+
| \`satisfied\` | every declared half was read and holds |
|
|
665
|
+
| \`unsatisfied\` | a machine fact was read and does not match |
|
|
666
|
+
| \`unauthorized\` | the machine half holds and consent does not authorize |
|
|
667
|
+
| \`unknown\` | a declared half has not been read |
|
|
668
|
+
|
|
669
|
+
\`unauthorized\` is kept separate from \`unsatisfied\` so a withdrawn consent can
|
|
670
|
+
never be mistaken for a technical failure an operator can clear by restarting
|
|
671
|
+
something. \`unknown\` is separate from both because a guard that reports "blocked"
|
|
672
|
+
when nobody has looked yet is a guard operators learn to ignore.
|
|
673
|
+
|
|
674
|
+
Order of decision: an unread half decides nothing · a failed machine fact does
|
|
675
|
+
not consult the human half, because there is nothing yet to authorize · a
|
|
676
|
+
non-authorizing consent wins over any machine state · a precondition declaring
|
|
677
|
+
neither half returns \`unknown\`, never \`satisfied\`. An empty gate that reports
|
|
678
|
+
success has stopped being a gate.
|
|
679
|
+
|
|
680
|
+
## Métis travels with the block
|
|
681
|
+
|
|
682
|
+
\`readyService\` carries every \`MetisHold\` from the blocking preconditions into
|
|
683
|
+
its answer. The reason a gate is stuck is exactly where "you have to restart it
|
|
684
|
+
twice" lives, and a readiness report that dropped it would have dropped the one
|
|
685
|
+
thing the operator needed.
|
|
513
686
|
|
|
514
687
|
## Usage
|
|
515
688
|
|
|
516
|
-
>
|
|
517
|
-
>
|
|
689
|
+
> \`mwsrv skill run\` has no implementation and exits 3. A skill is a document,
|
|
690
|
+
> not a program. **The \`mw_enforce_gate\` call above is registered and executable.**
|
|
518
691
|
>
|
|
519
|
-
>
|
|
520
|
-
> SKILL.md into \`.mw/skills/\`. Follow the steps above yourself, or hand this
|
|
521
|
-
> file to an agent.
|
|
692
|
+
> \`mwsrv skill install precondition-guard\` writes this SKILL.md into \`.mw/skills/\`.
|
|
522
693
|
|
|
523
694
|
Planned invocation (does not run yet):
|
|
524
695
|
\`\`\`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@medicine-wheel/app",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.8",
|
|
4
4
|
"description": "Medicine Wheel — Interactive visual layer for Indigenous relational research with Four Directions, ceremonies, and narrative arcs",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mw": "dist/cli/mw.js",
|
|
@@ -85,28 +85,29 @@
|
|
|
85
85
|
"release:major": "npm run version:major && npm run publish:all && npm run release:commit"
|
|
86
86
|
},
|
|
87
87
|
"dependencies": {
|
|
88
|
-
"@medicine-wheel/ceremonial-diary": "^0.5.
|
|
89
|
-
"@medicine-wheel/ceremony-protocol": "^0.5.
|
|
90
|
-
"@medicine-wheel/community-review": "^0.5.
|
|
91
|
-
"@medicine-wheel/consent-lifecycle": "^0.5.
|
|
92
|
-
"@medicine-wheel/
|
|
93
|
-
"@medicine-wheel/data-store
|
|
94
|
-
"@medicine-wheel/
|
|
95
|
-
"@medicine-wheel/
|
|
96
|
-
"@medicine-wheel/
|
|
97
|
-
"@medicine-wheel/
|
|
98
|
-
"@medicine-wheel/
|
|
99
|
-
"@medicine-wheel/
|
|
100
|
-
"@medicine-wheel/narrative-
|
|
101
|
-
"@medicine-wheel/
|
|
102
|
-
"@medicine-wheel/
|
|
103
|
-
"@medicine-wheel/
|
|
104
|
-
"@medicine-wheel/
|
|
105
|
-
"@medicine-wheel/relational-
|
|
106
|
-
"@medicine-wheel/
|
|
107
|
-
"@medicine-wheel/
|
|
108
|
-
"@medicine-wheel/
|
|
109
|
-
"@medicine-wheel/
|
|
88
|
+
"@medicine-wheel/ceremonial-diary": "^0.5.8",
|
|
89
|
+
"@medicine-wheel/ceremony-protocol": "^0.5.8",
|
|
90
|
+
"@medicine-wheel/community-review": "^0.5.8",
|
|
91
|
+
"@medicine-wheel/consent-lifecycle": "^0.5.8",
|
|
92
|
+
"@medicine-wheel/creative-orientation": "^0.5.8",
|
|
93
|
+
"@medicine-wheel/data-store": "^0.5.8",
|
|
94
|
+
"@medicine-wheel/data-store-postgres": "^0.5.8",
|
|
95
|
+
"@medicine-wheel/fire-keeper": "^0.5.8",
|
|
96
|
+
"@medicine-wheel/github-ceremony": "^0.5.8",
|
|
97
|
+
"@medicine-wheel/graph-viz": "^0.5.8",
|
|
98
|
+
"@medicine-wheel/importance-unit": "^0.5.8",
|
|
99
|
+
"@medicine-wheel/mcp": "^4.5.8",
|
|
100
|
+
"@medicine-wheel/narrative-cluster": "^0.5.8",
|
|
101
|
+
"@medicine-wheel/narrative-engine": "^0.5.8",
|
|
102
|
+
"@medicine-wheel/ontology-core": "^0.5.8",
|
|
103
|
+
"@medicine-wheel/perception-layer": "^0.5.8",
|
|
104
|
+
"@medicine-wheel/prompt-decomposition": "^0.5.8",
|
|
105
|
+
"@medicine-wheel/relational-index": "^0.5.8",
|
|
106
|
+
"@medicine-wheel/relational-query": "^0.5.8",
|
|
107
|
+
"@medicine-wheel/session-reader": "^0.5.8",
|
|
108
|
+
"@medicine-wheel/storage-provider": "^0.5.8",
|
|
109
|
+
"@medicine-wheel/transformation-tracker": "^0.5.8",
|
|
110
|
+
"@medicine-wheel/ui-components": "^0.5.8",
|
|
110
111
|
"@neondatabase/serverless": "^0.10.0",
|
|
111
112
|
"@xyflow/react": "^12.3.0",
|
|
112
113
|
"clsx": "^2.1.1",
|