@medicine-wheel/app 0.14.4 → 0.15.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
|
@@ -34,6 +34,7 @@ storage alongside its composable libraries.
|
|
|
34
34
|
├── @medicine-wheel/data-store-postgres ← PostgreSQL / Neon provider scaffold
|
|
35
35
|
├── @medicine-wheel/storage-provider ← Canonical persistence contract (JSONL ⇄ Neon)
|
|
36
36
|
├── @medicine-wheel/infra ← Hosts, tenants, services, port bindings as facets
|
|
37
|
+
├── @medicine-wheel/honcho ← The wheel's projection into Honcho, memory that reasons
|
|
37
38
|
├── @medicine-wheel/perception-layer ← Witness recordings as typed perceptual events
|
|
38
39
|
├── @medicine-wheel/narrative-cluster ← Events → clusters → beats → Film Edit Brief
|
|
39
40
|
├── @medicine-wheel/ceremonial-diary ← A participant's voice across the Five Phases
|
|
@@ -156,6 +157,11 @@ Typed infrastructure facets — hosts, tenants, services and port bindings — k
|
|
|
156
157
|
|
|
157
158
|
- **Dependencies:** `@medicine-wheel/ontology-core`, `zod`
|
|
158
159
|
|
|
160
|
+
### [@medicine-wheel/honcho](src/honcho)
|
|
161
|
+
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.
|
|
162
|
+
|
|
163
|
+
- **Dependencies:** `@medicine-wheel/ontology-core`
|
|
164
|
+
|
|
159
165
|
### [@medicine-wheel/perception-layer](src/perception-layer)
|
|
160
166
|
Eyes and ears of agent-supported film production — witness a belt-device recording as typed perceptual events and seed a production knowledge graph.
|
|
161
167
|
|
|
@@ -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.0",
|
|
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.0",
|
|
93
|
+
"@medicine-wheel/ceremony-protocol": "^0.15.0",
|
|
94
|
+
"@medicine-wheel/community-review": "^0.15.0",
|
|
95
|
+
"@medicine-wheel/consent-lifecycle": "^0.15.0",
|
|
96
|
+
"@medicine-wheel/creative-orientation": "^0.15.0",
|
|
97
|
+
"@medicine-wheel/data-store": "^0.15.0",
|
|
98
|
+
"@medicine-wheel/data-store-postgres": "^0.15.0",
|
|
99
|
+
"@medicine-wheel/fire-keeper": "^0.15.0",
|
|
100
|
+
"@medicine-wheel/github-ceremony": "^0.15.0",
|
|
101
|
+
"@medicine-wheel/graph-viz": "^0.15.0",
|
|
102
|
+
"@medicine-wheel/honcho": "^0.15.0",
|
|
103
|
+
"@medicine-wheel/importance-unit": "^0.15.0",
|
|
104
|
+
"@medicine-wheel/mcp": "^4.15.0",
|
|
105
|
+
"@medicine-wheel/narrative-cluster": "^0.15.0",
|
|
106
|
+
"@medicine-wheel/narrative-engine": "^0.15.0",
|
|
107
|
+
"@medicine-wheel/ontology-core": "^0.15.0",
|
|
108
|
+
"@medicine-wheel/perception-layer": "^0.15.0",
|
|
109
|
+
"@medicine-wheel/prompt-decomposition": "^0.15.0",
|
|
110
|
+
"@medicine-wheel/relational-index": "^0.15.0",
|
|
111
|
+
"@medicine-wheel/relational-query": "^0.15.0",
|
|
112
|
+
"@medicine-wheel/session-reader": "^0.15.0",
|
|
113
|
+
"@medicine-wheel/storage-provider": "^0.15.0",
|
|
114
|
+
"@medicine-wheel/transformation-tracker": "^0.15.0",
|
|
115
|
+
"@medicine-wheel/ui-components": "^0.15.0",
|
|
114
116
|
"@neondatabase/serverless": "^0.10.0",
|
|
115
117
|
"@xyflow/react": "^12.3.0",
|
|
116
118
|
"clsx": "^2.1.1",
|