@medicine-wheel/app 0.14.4 → 0.15.1
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 +94 -14
- package/app/api/ceremonies/route.ts +5 -0
- package/app/api/diary/route.ts +5 -0
- package/app/api/health/route.ts +4 -0
- package/app/api/narrative/beats/route.ts +5 -0
- package/docker-entrypoint.sh +9 -0
- package/lib/honcho-projection.ts +72 -0
- package/package.json +26 -24
package/README.md
CHANGED
|
@@ -10,8 +10,25 @@
|
|
|
10
10
|
|
|
11
11
|
## News
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
31 published packages: the app, 29 libraries and the MCP server (85 tools over
|
|
14
|
+
stdio and StreamableHTTP).
|
|
15
|
+
|
|
16
|
+
Recently landed:
|
|
17
|
+
|
|
18
|
+
- **`@medicine-wheel/client`** — one typed HTTP door to a running wheel, with
|
|
19
|
+
the wheel's paging honesty carried through to the caller.
|
|
20
|
+
- **`@medicine-wheel/community-identity`** — who is in the circle: people,
|
|
21
|
+
roles, circles, memberships, invitations and self-issued credentials.
|
|
22
|
+
- **`@medicine-wheel/honcho`** — the wheel's projection into
|
|
23
|
+
[Honcho](https://honcho.dev). Set `HONCHO_URL` and every stored beat,
|
|
24
|
+
ceremony and diary entry projects on write.
|
|
25
|
+
- **A ceremony knows its episode, its circle and what it closes**, and a beat
|
|
26
|
+
knows who spoke and who witnessed.
|
|
27
|
+
- **Read routes page honestly** — `count`, `total`, `matched`, `truncated`
|
|
28
|
+
instead of a silently cut list — and `GET /api/nodes/{id}/web` serves one
|
|
29
|
+
node's neighbourhood.
|
|
30
|
+
- **An episodes surface and a lineage layout** in the app: the chronicle as a
|
|
31
|
+
spine you read left to right.
|
|
15
32
|
|
|
16
33
|
|
|
17
34
|
## Architecture
|
|
@@ -21,6 +38,7 @@ storage alongside its composable libraries.
|
|
|
21
38
|
├── @medicine-wheel/ceremony-protocol ← Ceremony state & governance
|
|
22
39
|
├── @medicine-wheel/fire-keeper ← Ceremony coordination agent
|
|
23
40
|
├── @medicine-wheel/community-review ← Elder review & consensus
|
|
41
|
+
├── @medicine-wheel/community-identity ← People, roles, circles, memberships
|
|
24
42
|
├── @medicine-wheel/consent-lifecycle ← Relational consent lifecycle
|
|
25
43
|
├── @medicine-wheel/narrative-engine ← Beat sequencing & arc validation
|
|
26
44
|
├── @medicine-wheel/importance-unit ← Relational unit of knowledge
|
|
@@ -30,10 +48,12 @@ storage alongside its composable libraries.
|
|
|
30
48
|
├── @medicine-wheel/relational-query ← Query, traversal & audit
|
|
31
49
|
├── @medicine-wheel/prompt-decomposition ← Intent extraction & PDE
|
|
32
50
|
├── @medicine-wheel/ui-components ← React components
|
|
51
|
+
├── @medicine-wheel/client ← Typed HTTP door to a running wheel
|
|
33
52
|
├── @medicine-wheel/data-store ← Shared data access (Redis)
|
|
34
53
|
├── @medicine-wheel/data-store-postgres ← PostgreSQL / Neon provider scaffold
|
|
35
54
|
├── @medicine-wheel/storage-provider ← Canonical persistence contract (JSONL ⇄ Neon)
|
|
36
55
|
├── @medicine-wheel/infra ← Hosts, tenants, services, port bindings as facets
|
|
56
|
+
├── @medicine-wheel/honcho ← The wheel's projection into Honcho, memory that reasons
|
|
37
57
|
├── @medicine-wheel/perception-layer ← Witness recordings as typed perceptual events
|
|
38
58
|
├── @medicine-wheel/narrative-cluster ← Events → clusters → beats → Film Edit Brief
|
|
39
59
|
├── @medicine-wheel/ceremonial-diary ← A participant's voice across the Five Phases
|
|
@@ -84,6 +104,11 @@ Community-based ceremonial review protocol — implements Wilson's validation th
|
|
|
84
104
|
|
|
85
105
|
- **Dependencies:** `@medicine-wheel/ontology-core`, `@medicine-wheel/ceremony-protocol`, `zod`
|
|
86
106
|
|
|
107
|
+
### [@medicine-wheel/community-identity](src/community-identity)
|
|
108
|
+
Who is in the circle, so a wheel can tell two people apart. A person is a `human` node with `metadata.kind: "person"` and a role, a circle is a `circle` node, membership is a `member_of` edge. Roles run `participant → emerging_guide → ceremony_facilitator → firekeeper → admin`, with `story_keeper` parallel to firekeeper and `companion_ai` / `integration_ai` beside them, each carrying a permission map. Invitations, credentials and the audit trail stay with the consumer — hashed, never on the wheel.
|
|
109
|
+
|
|
110
|
+
- **Dependencies:** `@medicine-wheel/ontology-core`, `zod`
|
|
111
|
+
|
|
87
112
|
### [@medicine-wheel/consent-lifecycle](src/consent-lifecycle)
|
|
88
113
|
Ongoing relational consent lifecycle — consent as a living relational obligation with lifecycle tracking, renewal, renegotiation, withdrawal cascades, and community-level consent protocols.
|
|
89
114
|
|
|
@@ -131,6 +156,11 @@ React UI component library — `DirectionCard`, `BeatTimeline`, `NodeInspector`,
|
|
|
131
156
|
- **Dependencies:** `@medicine-wheel/ontology-core`
|
|
132
157
|
- **Peer:** `react`
|
|
133
158
|
|
|
159
|
+
### [@medicine-wheel/client](src/client)
|
|
160
|
+
The typed HTTP door to a running wheel — nodes, edges, ceremonies, beats and the ceremonial diary. The wheel's paging honesty (`count`, `total`, `matched`, `truncated`) is carried through to the caller, `limit: 'all'` asks for the whole store, and errors fail fast: an unreachable wheel throws with status 502, a refusal carries the wheel's status and body. No retry.
|
|
161
|
+
|
|
162
|
+
- **Dependencies:** `@medicine-wheel/ontology-core`, `@medicine-wheel/storage-provider`
|
|
163
|
+
|
|
134
164
|
### [@medicine-wheel/data-store](src/data-store)
|
|
135
165
|
Shared Redis data-access layer — connection management (Upstash, Vercel KV, local), Node/Edge/Ceremony/Accountability CRUD, session-ceremony linking, and generic Redis helpers.
|
|
136
166
|
|
|
@@ -156,6 +186,11 @@ Typed infrastructure facets — hosts, tenants, services and port bindings — k
|
|
|
156
186
|
|
|
157
187
|
- **Dependencies:** `@medicine-wheel/ontology-core`, `zod`
|
|
158
188
|
|
|
189
|
+
### [@medicine-wheel/honcho](src/honcho)
|
|
190
|
+
The wheel's projection into [Honcho](https://honcho.dev), memory that reasons. The wheel stays canonical; Honcho holds what the history has come to mean about each peer. A beat becomes a message from its speaker in the session of its ceremony, a speaker's evolving representation is recalled before they speak again, and a derived conclusion returns as a `knowledge` node carrying `metadata.kind: "memory_projection"` with its status and provenance. Zero dependencies, `/v3` only, configured by `HONCHO_URL`. Nothing is filtered here.
|
|
191
|
+
|
|
192
|
+
- **Dependencies:** `@medicine-wheel/ontology-core`
|
|
193
|
+
|
|
159
194
|
### [@medicine-wheel/perception-layer](src/perception-layer)
|
|
160
195
|
Eyes and ears of agent-supported film production — witness a belt-device recording as typed perceptual events and seed a production knowledge graph.
|
|
161
196
|
|
|
@@ -200,36 +235,48 @@ Idea into committed design through approval gates a human holds — `explore →
|
|
|
200
235
|
|
|
201
236
|
RISE framework specifications are in [`rispecs/`](rispecs/). Start with [`medicine-wheel.spec.md`](rispecs/medicine-wheel.spec.md) for the system overview.
|
|
202
237
|
|
|
203
|
-
| Package | Spec |
|
|
238
|
+
| Package / topic | Spec |
|
|
204
239
|
|---------|------|
|
|
205
240
|
| System Overview | [medicine-wheel.spec.md](rispecs/medicine-wheel.spec.md) |
|
|
206
241
|
| ontology-core | [ontology-core.spec.md](rispecs/ontology-core.spec.md) |
|
|
207
242
|
| ceremony-protocol | [ceremony-protocol.spec.md](rispecs/ceremony-protocol.spec.md) |
|
|
208
243
|
| fire-keeper | [fire-keeper.spec.md](rispecs/fire-keeper.spec.md) |
|
|
209
244
|
| community-review | [community-review.spec.md](rispecs/community-review.spec.md) |
|
|
245
|
+
| community-identity | [community-identity.spec.md](rispecs/community-identity.spec.md) |
|
|
210
246
|
| consent-lifecycle | [consent-lifecycle.spec.md](rispecs/consent-lifecycle.spec.md) |
|
|
211
|
-
| narrative-engine | [narrative-engine.spec.md](rispecs/narrative-engine.spec.md) |
|
|
247
|
+
| narrative-engine | [narrative-engine.spec.md](rispecs/narrative-engine.spec.md) · [narrative-beats-lifecycle.spec.md](rispecs/narrative-beats-lifecycle.spec.md) |
|
|
212
248
|
| importance-unit | [importance-unit.spec.md](rispecs/importance-unit.spec.md) |
|
|
213
249
|
| relational-index | [relational-index.spec.md](rispecs/relational-index.spec.md) |
|
|
214
250
|
| transformation-tracker | [transformation-tracker.spec.md](rispecs/transformation-tracker.spec.md) |
|
|
215
251
|
| graph-viz | [graph-viz.spec.md](rispecs/graph-viz.spec.md) |
|
|
216
|
-
| relational-query | [relational-query.spec.md](rispecs/relational-query.spec.md) |
|
|
217
|
-
| prompt-decomposition | [prompt-decomposition.spec.md](rispecs/prompt-decomposition.spec.md) |
|
|
218
|
-
|
|
|
252
|
+
| relational-query | [relational-query.spec.md](rispecs/relational-query.spec.md) · [relational-web-package.spec.md](rispecs/relational-web-package.spec.md) |
|
|
253
|
+
| prompt-decomposition | [prompt-decomposition.spec.md](rispecs/prompt-decomposition.spec.md) · [decomposition-strategies.spec.md](rispecs/decomposition-strategies.spec.md) |
|
|
254
|
+
| ui-components | [ui-components.spec.md](rispecs/ui-components.spec.md) |
|
|
255
|
+
| client | [client.spec.md](rispecs/client.spec.md) |
|
|
256
|
+
| storage-provider | [storage-provider.spec.md](rispecs/storage-provider.spec.md) · [storage-provider-abstraction.spec.md](rispecs/storage-provider-abstraction.spec.md) |
|
|
219
257
|
| data-store | [data-store.spec.md](rispecs/data-store.spec.md) |
|
|
220
258
|
| data-store-postgres | [data-store-postgres.spec.md](rispecs/data-store-postgres.spec.md) |
|
|
221
|
-
| perception-layer | [perception-layer.spec.md](rispecs/perception-layer.spec.md) |
|
|
222
|
-
| narrative-cluster | [narrative-cluster.spec.md](rispecs/narrative-cluster.spec.md) |
|
|
223
259
|
| session-reader | [session-reader.spec.md](rispecs/session-reader.spec.md) |
|
|
224
260
|
| infra | [infrastructure-topology-ui.spec.md](rispecs/infrastructure-topology-ui.spec.md) |
|
|
225
|
-
|
|
|
226
|
-
|
|
|
227
|
-
|
|
|
261
|
+
| perception-layer | [perception-layer.spec.md](rispecs/perception-layer.spec.md) |
|
|
262
|
+
| narrative-cluster | [narrative-cluster.spec.md](rispecs/narrative-cluster.spec.md) |
|
|
263
|
+
| captures & recordings | [capture-registry.spec.md](rispecs/capture-registry.spec.md) |
|
|
264
|
+
| inquiry weaves | [inquiry-weave-registration.spec.md](rispecs/inquiry-weave-registration.spec.md) |
|
|
265
|
+
| plans & insights | [plan-insight-perspective-registration.spec.md](rispecs/plan-insight-perspective-registration.spec.md) |
|
|
266
|
+
| council & community | [council-record.spec.md](rispecs/council-record.spec.md) · [community-choice.spec.md](rispecs/community-choice.spec.md) |
|
|
267
|
+
| reading & scope | [reading-layer.spec.md](rispecs/reading-layer.spec.md) · [workspace-scope-and-access.spec.md](rispecs/workspace-scope-and-access.spec.md) |
|
|
268
|
+
| film production | [relational-production-protocol.spec.md](rispecs/relational-production-protocol.spec.md) · [film-production-upgrades.spec.md](rispecs/film-production-upgrades.spec.md) |
|
|
269
|
+
| kinship & bridges | [kinship-graph.spec.md](rispecs/kinship-graph.spec.md) · [narrative-medicine-wheel-bridge.spec.md](rispecs/narrative-medicine-wheel-bridge.spec.md) · [article-publishing-pipeline.spec.md](rispecs/article-publishing-pipeline.spec.md) |
|
|
228
270
|
|
|
229
271
|
## LLM Integration
|
|
230
272
|
|
|
231
273
|
- [`llms.txt`](llms.txt) — Quick navigation for LLMs
|
|
232
274
|
- [`llms-full.txt`](llms-full.txt) — Exhaustive reference with code samples
|
|
275
|
+
- [`CLAUDE.md`](CLAUDE.md) — Repo laws for agents: versioning, the topological
|
|
276
|
+
workspace order, what `mw skill run` does not do, and how a running service is
|
|
277
|
+
registered on the wheel
|
|
278
|
+
- [`RELEASING.md`](RELEASING.md) — Publishing is not deploying. Publish, install
|
|
279
|
+
globally, run the installed binary, then bump
|
|
233
280
|
|
|
234
281
|
## Getting Started
|
|
235
282
|
|
|
@@ -266,10 +313,43 @@ mwsrv --docker -D /src/myapp
|
|
|
266
313
|
mw status
|
|
267
314
|
mw directions
|
|
268
315
|
mw node list
|
|
316
|
+
mw ceremony list
|
|
317
|
+
mw beat list
|
|
318
|
+
mw web <node-id> [depth] # one node's neighbourhood
|
|
319
|
+
mw chart list # structural tension charts
|
|
320
|
+
mw validate wilson "<description>" # validators (wilson, ocap, accountability, bridge)
|
|
321
|
+
mw orient "<outcome>" # the question asked before the work
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
The `mw` CLI uses HTTP against the running server by default (`MW_API_URL`,
|
|
325
|
+
default `http://localhost:8040`); MCP fallback is available via a local
|
|
326
|
+
`MW_MCP_PATH`. `mw skill view` and `mw skill install` work with the shipped
|
|
327
|
+
skill definitions — `mw skill run` exits 3 by design, because the skills are
|
|
328
|
+
documents and there is no runtime that executes them.
|
|
329
|
+
|
|
330
|
+
### MCP server
|
|
331
|
+
```bash
|
|
332
|
+
# stdio — local JSONL store under .mw/store/
|
|
333
|
+
npx @medicine-wheel/mcp
|
|
334
|
+
|
|
335
|
+
# server-mediated store — the same relational state the app holds
|
|
336
|
+
MW_API_URL=http://localhost:8040 npx @medicine-wheel/mcp
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
85 tools over stdio and StreamableHTTP (`POST /api/mcp` on the running app).
|
|
340
|
+
|
|
341
|
+
### Honcho projection (optional)
|
|
342
|
+
```bash
|
|
343
|
+
HONCHO_URL=http://localhost:8133 # switches the river on
|
|
344
|
+
HONCHO_WORKSPACE_ID=medicine-wheel # default
|
|
345
|
+
# HONCHO_API_KEY when Honcho asks for auth
|
|
269
346
|
```
|
|
270
347
|
|
|
271
|
-
|
|
272
|
-
|
|
348
|
+
With `HONCHO_URL` set, every stored beat, ceremony and diary entry projects into
|
|
349
|
+
Honcho on write, in the background. `GET /api/health` reports `honcho.enabled`.
|
|
350
|
+
A projection never delays the wheel's answer, and a Honcho that is down is a
|
|
351
|
+
line on stderr, not an error to the writer. A running server holds its old
|
|
352
|
+
build and its old environment — restart it for either to take effect.
|
|
273
353
|
|
|
274
354
|
## License
|
|
275
355
|
|
|
@@ -2,6 +2,8 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import { parseLimit } from "@/lib/api-paging";
|
|
3
3
|
import { createProvider, detectProvider } from "@medicine-wheel/storage-provider";
|
|
4
4
|
import { ceremonyBelongsToEpisode, ceremonyEpisodePath } from "@/lib/ceremony-response";
|
|
5
|
+
import { projectCeremony } from "@medicine-wheel/honcho";
|
|
6
|
+
import { projectAfterWrite } from "@/lib/honcho-projection";
|
|
5
7
|
|
|
6
8
|
/** Episode directory names are `YYYY-MM-DD-episode-NNN-slug`; nothing else may bind. */
|
|
7
9
|
const EPISODE_PATH = /^\d{4}-\d{2}-\d{2}-episode-\d{3,}-[a-z0-9-]+$/;
|
|
@@ -139,6 +141,9 @@ export async function POST(request: Request) {
|
|
|
139
141
|
};
|
|
140
142
|
|
|
141
143
|
await store.logCeremony(ceremony);
|
|
144
|
+
// The river: the stored ceremony leaves for Honcho in the background when
|
|
145
|
+
// HONCHO_URL is set. Never awaited.
|
|
146
|
+
projectAfterWrite(() => projectCeremony(ceremony), `ceremony ${ceremony.id}`);
|
|
142
147
|
return NextResponse.json({ success: true, ceremony, provider: detectProvider() }, { status: 201 });
|
|
143
148
|
} catch (error: unknown) {
|
|
144
149
|
const message = error instanceof Error ? error.message : String(error);
|
package/app/api/diary/route.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { parseLimit } from "@/lib/api-paging";
|
|
|
3
3
|
import { createProvider, detectProvider } from "@medicine-wheel/storage-provider";
|
|
4
4
|
import type { DiaryEntryFilters, DiaryEntryRecord } from "@medicine-wheel/storage-provider";
|
|
5
5
|
import { createDiaryEntry } from "@medicine-wheel/ceremonial-diary";
|
|
6
|
+
import { projectDiaryEntry } from "@medicine-wheel/honcho";
|
|
7
|
+
import { projectAfterWrite } from "@/lib/honcho-projection";
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* The ceremonial diary door (0.14.0).
|
|
@@ -122,6 +124,9 @@ export async function POST(request: Request) {
|
|
|
122
124
|
...(typeof body.agent === "string" && body.agent ? { agent: body.agent } : {}),
|
|
123
125
|
...(typeof body.chronicle === "string" && body.chronicle ? { chronicle: body.chronicle } : {}),
|
|
124
126
|
});
|
|
127
|
+
// The river: the participant's voice leaves for Honcho in the background
|
|
128
|
+
// when HONCHO_URL is set. Never awaited.
|
|
129
|
+
projectAfterWrite(() => projectDiaryEntry(entry), `diary entry ${entry.id}`);
|
|
125
130
|
return NextResponse.json({ success: true, entry, provider: detectProvider() }, { status: 201 });
|
|
126
131
|
} catch (error: unknown) {
|
|
127
132
|
const message = error instanceof Error ? error.message : String(error);
|
package/app/api/health/route.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server';
|
|
2
2
|
import { createProvider, detectProvider } from '@medicine-wheel/storage-provider';
|
|
3
|
+
import { honchoProjectionStatus } from '@/lib/honcho-projection';
|
|
3
4
|
|
|
4
5
|
export async function GET() {
|
|
5
6
|
const providerType = detectProvider();
|
|
@@ -26,6 +27,9 @@ export async function GET() {
|
|
|
26
27
|
nodes,
|
|
27
28
|
ceremonies,
|
|
28
29
|
},
|
|
30
|
+
// The river to Honcho: enabled when HONCHO_URL is set. A wheel that
|
|
31
|
+
// reports enabled projects every stored beat, ceremony and diary entry.
|
|
32
|
+
honcho: honchoProjectionStatus(),
|
|
29
33
|
env: {
|
|
30
34
|
MW_STORAGE_PROVIDER: process.env.MW_STORAGE_PROVIDER || 'not set',
|
|
31
35
|
DATABASE_URL: process.env.DATABASE_URL ? 'configured' : 'not configured',
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import { getAllBeats, createBeat } from "@/lib/store";
|
|
3
|
+
import { projectBeat } from "@medicine-wheel/honcho";
|
|
4
|
+
import { projectAfterWrite } from "@/lib/honcho-projection";
|
|
3
5
|
|
|
4
6
|
export async function GET() {
|
|
5
7
|
try {
|
|
@@ -33,6 +35,9 @@ export async function POST(request: Request) {
|
|
|
33
35
|
speaker: body.speaker,
|
|
34
36
|
witnesses: Array.isArray(body.witnesses) ? body.witnesses : undefined,
|
|
35
37
|
});
|
|
38
|
+
// The river: a stored beat leaves for Honcho in the background when
|
|
39
|
+
// HONCHO_URL is set. Never awaited — the wheel answers on its own clock.
|
|
40
|
+
projectAfterWrite(() => projectBeat(beat), `beat ${beat.id}`);
|
|
36
41
|
// Warnings ride on the created beat rather than replacing it, so clients
|
|
37
42
|
// that read the beat back by id keep working while advisory findings stop
|
|
38
43
|
// being computed-and-discarded.
|
package/docker-entrypoint.sh
CHANGED
|
@@ -8,6 +8,10 @@ set -e
|
|
|
8
8
|
# MW_DATA_DIR — path to JSONL store directory (default: /data/store)
|
|
9
9
|
# MW_STORAGE_PROVIDER — storage backend (default: jsonl)
|
|
10
10
|
# PORT — HTTP port (default: 8040)
|
|
11
|
+
# HONCHO_URL — when set, every stored beat, ceremony and diary entry
|
|
12
|
+
# is projected into Honcho (memory that reasons);
|
|
13
|
+
# HONCHO_WORKSPACE_ID (default medicine-wheel),
|
|
14
|
+
# HONCHO_API_KEY (unset while Honcho's auth is off)
|
|
11
15
|
|
|
12
16
|
STORE_DIR="${MW_DATA_DIR:-/data/store}"
|
|
13
17
|
PORT="${PORT:-8040}"
|
|
@@ -17,6 +21,11 @@ mkdir -p "$STORE_DIR"
|
|
|
17
21
|
echo "🌿 Medicine Wheel"
|
|
18
22
|
echo "📁 Store: $STORE_DIR"
|
|
19
23
|
echo "🌐 http://localhost:$PORT"
|
|
24
|
+
if [ -n "${HONCHO_URL:-}" ]; then
|
|
25
|
+
echo "🧠 Honcho: $HONCHO_URL (workspace ${HONCHO_WORKSPACE_ID:-medicine-wheel})"
|
|
26
|
+
else
|
|
27
|
+
echo "🧠 Honcho: off (set HONCHO_URL to project the wheel into memory)"
|
|
28
|
+
fi
|
|
20
29
|
echo ""
|
|
21
30
|
|
|
22
31
|
export MW_DATA_DIR="$STORE_DIR"
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Automatic projection — the wheel pushes to Honcho on write.
|
|
3
|
+
*
|
|
4
|
+
* `@medicine-wheel/honcho` is the door; this is the river. Every write route
|
|
5
|
+
* that lands a beat, a ceremony or a diary entry calls {@link projectAfterWrite}
|
|
6
|
+
* once the record is stored. With `HONCHO_URL` set the record leaves for Honcho
|
|
7
|
+
* in the background; unset, nothing happens and nothing is logged. Either way
|
|
8
|
+
* the wheel's own answer to its caller is unchanged: a projection never delays
|
|
9
|
+
* a response and never turns a stored record into an error.
|
|
10
|
+
*
|
|
11
|
+
* A failure is reported once on stderr with the record's id, because a silent
|
|
12
|
+
* memory is worse than a noisy one. `awaitProjections()` exists for tests and
|
|
13
|
+
* for anything that must know the river has run dry before it exits.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
createHonchoClient,
|
|
18
|
+
honchoFromEnv,
|
|
19
|
+
project,
|
|
20
|
+
type HonchoClient,
|
|
21
|
+
type HonchoConfig,
|
|
22
|
+
type Projection,
|
|
23
|
+
} from '@medicine-wheel/honcho';
|
|
24
|
+
|
|
25
|
+
const inFlight = new Set<Promise<unknown>>();
|
|
26
|
+
|
|
27
|
+
let cached: { key: string; client: HonchoClient } | null = null;
|
|
28
|
+
|
|
29
|
+
function clientFor(cfg: HonchoConfig): HonchoClient {
|
|
30
|
+
const key = `${cfg.baseUrl}|${cfg.workspace}|${cfg.apiKey ?? ''}`;
|
|
31
|
+
if (cached?.key !== key) cached = { key, client: createHonchoClient(cfg) };
|
|
32
|
+
return cached.client;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** What `/api/health` reports: whether the river runs, and where to. */
|
|
36
|
+
export function honchoProjectionStatus(): { enabled: boolean; url?: string; workspace?: string } {
|
|
37
|
+
const cfg = honchoFromEnv();
|
|
38
|
+
return cfg ? { enabled: true, url: cfg.baseUrl, workspace: cfg.workspace } : { enabled: false };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Fire-and-forget. Returns immediately; the projection runs in the background.
|
|
43
|
+
* `label` names the record in the one line written when Honcho refuses or is
|
|
44
|
+
* unreachable.
|
|
45
|
+
*
|
|
46
|
+
* The projection is built by `shape`, called *inside* the error boundary. It
|
|
47
|
+
* used to be built by the caller, in the route's own expression — so a record
|
|
48
|
+
* the projection could not shape (a `learnings` that arrived as a string, say)
|
|
49
|
+
* threw synchronously and turned an already-stored write into a 500 for the
|
|
50
|
+
* writer. The wheel's answer to its caller must not depend on the river, and
|
|
51
|
+
* that includes the part that reads the record.
|
|
52
|
+
*/
|
|
53
|
+
export function projectAfterWrite(shape: () => Projection, label: string): void {
|
|
54
|
+
const cfg = honchoFromEnv();
|
|
55
|
+
if (!cfg) return;
|
|
56
|
+
const run = Promise.resolve()
|
|
57
|
+
.then(() => project(clientFor(cfg), shape()))
|
|
58
|
+
.catch((error: unknown) => {
|
|
59
|
+
const err = error as { message?: string; status?: number; body?: string };
|
|
60
|
+
console.error(
|
|
61
|
+
`[honcho] projection of ${label} into ${cfg.workspace}@${cfg.baseUrl} failed: ${err?.message ?? String(error)}` +
|
|
62
|
+
(err?.body ? ` — ${err.body}` : ''),
|
|
63
|
+
);
|
|
64
|
+
})
|
|
65
|
+
.finally(() => { inFlight.delete(run); });
|
|
66
|
+
inFlight.add(run);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Resolves once every projection started so far has settled. */
|
|
70
|
+
export async function awaitProjections(): Promise<void> {
|
|
71
|
+
while (inFlight.size) await Promise.allSettled([...inFlight]);
|
|
72
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@medicine-wheel/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.1",
|
|
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",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"src/ceremony-protocol",
|
|
33
33
|
"src/storage-provider",
|
|
34
34
|
"src/client",
|
|
35
|
+
"src/honcho",
|
|
35
36
|
"src/data-store",
|
|
36
37
|
"src/data-store-postgres",
|
|
37
38
|
"src/graph-viz",
|
|
@@ -88,29 +89,30 @@
|
|
|
88
89
|
"release:major": "npm run version:major && npm run publish:all && npm run release:commit"
|
|
89
90
|
},
|
|
90
91
|
"dependencies": {
|
|
91
|
-
"@medicine-wheel/ceremonial-diary": "^0.
|
|
92
|
-
"@medicine-wheel/ceremony-protocol": "^0.
|
|
93
|
-
"@medicine-wheel/community-review": "^0.
|
|
94
|
-
"@medicine-wheel/consent-lifecycle": "^0.
|
|
95
|
-
"@medicine-wheel/creative-orientation": "^0.
|
|
96
|
-
"@medicine-wheel/data-store": "^0.
|
|
97
|
-
"@medicine-wheel/data-store-postgres": "^0.
|
|
98
|
-
"@medicine-wheel/fire-keeper": "^0.
|
|
99
|
-
"@medicine-wheel/github-ceremony": "^0.
|
|
100
|
-
"@medicine-wheel/graph-viz": "^0.
|
|
101
|
-
"@medicine-wheel/
|
|
102
|
-
"@medicine-wheel/
|
|
103
|
-
"@medicine-wheel/
|
|
104
|
-
"@medicine-wheel/narrative-
|
|
105
|
-
"@medicine-wheel/
|
|
106
|
-
"@medicine-wheel/
|
|
107
|
-
"@medicine-wheel/
|
|
108
|
-
"@medicine-wheel/
|
|
109
|
-
"@medicine-wheel/relational-
|
|
110
|
-
"@medicine-wheel/
|
|
111
|
-
"@medicine-wheel/
|
|
112
|
-
"@medicine-wheel/
|
|
113
|
-
"@medicine-wheel/
|
|
92
|
+
"@medicine-wheel/ceremonial-diary": "^0.15.1",
|
|
93
|
+
"@medicine-wheel/ceremony-protocol": "^0.15.1",
|
|
94
|
+
"@medicine-wheel/community-review": "^0.15.1",
|
|
95
|
+
"@medicine-wheel/consent-lifecycle": "^0.15.1",
|
|
96
|
+
"@medicine-wheel/creative-orientation": "^0.15.1",
|
|
97
|
+
"@medicine-wheel/data-store": "^0.15.1",
|
|
98
|
+
"@medicine-wheel/data-store-postgres": "^0.15.1",
|
|
99
|
+
"@medicine-wheel/fire-keeper": "^0.15.1",
|
|
100
|
+
"@medicine-wheel/github-ceremony": "^0.15.1",
|
|
101
|
+
"@medicine-wheel/graph-viz": "^0.15.1",
|
|
102
|
+
"@medicine-wheel/honcho": "^0.15.1",
|
|
103
|
+
"@medicine-wheel/importance-unit": "^0.15.1",
|
|
104
|
+
"@medicine-wheel/mcp": "^4.15.1",
|
|
105
|
+
"@medicine-wheel/narrative-cluster": "^0.15.1",
|
|
106
|
+
"@medicine-wheel/narrative-engine": "^0.15.1",
|
|
107
|
+
"@medicine-wheel/ontology-core": "^0.15.1",
|
|
108
|
+
"@medicine-wheel/perception-layer": "^0.15.1",
|
|
109
|
+
"@medicine-wheel/prompt-decomposition": "^0.15.1",
|
|
110
|
+
"@medicine-wheel/relational-index": "^0.15.1",
|
|
111
|
+
"@medicine-wheel/relational-query": "^0.15.1",
|
|
112
|
+
"@medicine-wheel/session-reader": "^0.15.1",
|
|
113
|
+
"@medicine-wheel/storage-provider": "^0.15.1",
|
|
114
|
+
"@medicine-wheel/transformation-tracker": "^0.15.1",
|
|
115
|
+
"@medicine-wheel/ui-components": "^0.15.1",
|
|
114
116
|
"@neondatabase/serverless": "^0.10.0",
|
|
115
117
|
"@xyflow/react": "^12.3.0",
|
|
116
118
|
"clsx": "^2.1.1",
|