willows-grove 0.9.0__tar.gz

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.
Files changed (165) hide show
  1. willows_grove-0.9.0/.env.example +41 -0
  2. willows_grove-0.9.0/.gitignore +16 -0
  3. willows_grove-0.9.0/CHANGELOG.md +30 -0
  4. willows_grove-0.9.0/CLAUDE.md +77 -0
  5. willows_grove-0.9.0/PKG-INFO +131 -0
  6. willows_grove-0.9.0/README.md +96 -0
  7. willows_grove-0.9.0/bridge/__init__.py +1 -0
  8. willows_grove-0.9.0/bridge/__main__.py +112 -0
  9. willows_grove-0.9.0/bridge/app.py +311 -0
  10. willows_grove-0.9.0/bridge/matrix.py +109 -0
  11. willows_grove-0.9.0/bridge/registration.yaml +24 -0
  12. willows_grove-0.9.0/bridge/store.py +69 -0
  13. willows_grove-0.9.0/deploy/grove-mcp-serve.service.template +35 -0
  14. willows_grove-0.9.0/docs/INVARIANTS.md +478 -0
  15. willows_grove-0.9.0/docs/OPS_RUNBOOK.md +236 -0
  16. willows_grove-0.9.0/docs/audits/loki-swarm-measurement.md +261 -0
  17. willows_grove-0.9.0/docs/audits/loki-swarm-metadata.md +390 -0
  18. willows_grove-0.9.0/docs/audits/loki-swarm-raw.json +1047 -0
  19. willows_grove-0.9.0/docs/audits/loki-v0.9-audit.md +598 -0
  20. willows_grove-0.9.0/docs/design/autonomous-continuity.md +357 -0
  21. willows_grove-0.9.0/docs/design/pr14-carryovers.md +405 -0
  22. willows_grove-0.9.0/docs/design/u2u-security-limits.md +63 -0
  23. willows_grove-0.9.0/docs/design/watcher-e2e-notes.md +76 -0
  24. willows_grove-0.9.0/grove/__init__.py +1 -0
  25. willows_grove-0.9.0/grove/envelope_reader.py +235 -0
  26. willows_grove-0.9.0/grove/errors.py +63 -0
  27. willows_grove-0.9.0/grove/fleet_presence.py +107 -0
  28. willows_grove-0.9.0/grove/journal_reader.py +311 -0
  29. willows_grove-0.9.0/grove/journal_writer.py +211 -0
  30. willows_grove-0.9.0/grove/kart_reader.py +303 -0
  31. willows_grove-0.9.0/grove/mcp_auth.py +454 -0
  32. willows_grove-0.9.0/grove/mcp_local.py +1284 -0
  33. willows_grove-0.9.0/grove/nestor_client.py +376 -0
  34. willows_grove-0.9.0/grove/persona_roster.py +317 -0
  35. willows_grove-0.9.0/grove/resident_watcher.py +624 -0
  36. willows_grove-0.9.0/grove/seed_html.py +545 -0
  37. willows_grove-0.9.0/grove/seed_reader.py +254 -0
  38. willows_grove-0.9.0/grove_db.py +831 -0
  39. willows_grove-0.9.0/grove_html.py +302 -0
  40. willows_grove-0.9.0/grove_reader.py +1361 -0
  41. willows_grove-0.9.0/grove_serve.py +538 -0
  42. willows_grove-0.9.0/panes/__init__.py +5 -0
  43. willows_grove-0.9.0/panes/chat_admin.py +38 -0
  44. willows_grove-0.9.0/pyproject.toml +132 -0
  45. willows_grove-0.9.0/requirements.txt +26 -0
  46. willows_grove-0.9.0/run_mcp.sh +40 -0
  47. willows_grove-0.9.0/safe-app-manifest.json +27 -0
  48. willows_grove-0.9.0/schema.sql +175 -0
  49. willows_grove-0.9.0/scripts/check_docs_drift.py +229 -0
  50. willows_grove-0.9.0/scripts/check_persona_provenance.py +171 -0
  51. willows_grove-0.9.0/scripts/check_ratification.py +130 -0
  52. willows_grove-0.9.0/scripts/ci-security-grep.allowlist +21 -0
  53. willows_grove-0.9.0/scripts/ci-security-grep.sh +137 -0
  54. willows_grove-0.9.0/scripts/grove-serve +109 -0
  55. willows_grove-0.9.0/scripts/mcp_entry_toggle.py +47 -0
  56. willows_grove-0.9.0/scripts/run_test_dir_or_fail.sh +60 -0
  57. willows_grove-0.9.0/tests/__init__.py +0 -0
  58. willows_grove-0.9.0/tests/e2e/README.md +72 -0
  59. willows_grove-0.9.0/tests/e2e/grove-served-page.spec.js +420 -0
  60. willows_grove-0.9.0/tests/e2e/seed-canon.spec.js +175 -0
  61. willows_grove-0.9.0/tests/e2e/three-state-affordances.spec.js +310 -0
  62. willows_grove-0.9.0/tests/e2e_ollama/__init__.py +3 -0
  63. willows_grove-0.9.0/tests/e2e_ollama/conftest.py +438 -0
  64. willows_grove-0.9.0/tests/e2e_ollama/test_watcher_ollama_e2e.py +210 -0
  65. willows_grove-0.9.0/tests/e2e_ollama/test_watcher_ollama_readiness.py +67 -0
  66. willows_grove-0.9.0/tests/e2e_willow_mcp/conftest.py +169 -0
  67. willows_grove-0.9.0/tests/e2e_willow_mcp/mock_willow_mcp.py +218 -0
  68. willows_grove-0.9.0/tests/e2e_willow_mcp/test_journal_roundtrip.py +167 -0
  69. willows_grove-0.9.0/tests/e2e_willow_mcp/test_watcher_chat_readback_flow.py +114 -0
  70. willows_grove-0.9.0/tests/regression/screenshots/README.md +40 -0
  71. willows_grove-0.9.0/tests/regression/screenshots/seed/1.png +0 -0
  72. willows_grove-0.9.0/tests/regression/screenshots/seed/2.png +0 -0
  73. willows_grove-0.9.0/tests/regression/screenshots/seed/3.png +0 -0
  74. willows_grove-0.9.0/tests/regression/screenshots/seed/4.png +0 -0
  75. willows_grove-0.9.0/tests/regression/screenshots/seed/5.png +0 -0
  76. willows_grove-0.9.0/tests/regression/screenshots/seed/6.png +0 -0
  77. willows_grove-0.9.0/tests/test_ci_hashfiles_guards_removed.py +196 -0
  78. willows_grove-0.9.0/tests/test_ci_playwright_install_command.py +64 -0
  79. willows_grove-0.9.0/tests/test_ci_security_grep_docstring_matches_pattern.py +95 -0
  80. willows_grove-0.9.0/tests/test_claude_md_honesty.py +123 -0
  81. willows_grove-0.9.0/tests/test_docs_drift_check.py +187 -0
  82. willows_grove-0.9.0/tests/test_docs_no_dead_envvar_references.py +41 -0
  83. willows_grove-0.9.0/tests/test_e2e_ollama_ci_fails_loud.py +155 -0
  84. willows_grove-0.9.0/tests/test_envelope_reader.py +246 -0
  85. willows_grove-0.9.0/tests/test_fleet_presence.py +102 -0
  86. willows_grove-0.9.0/tests/test_fleet_presence_unreachable.py +76 -0
  87. willows_grove-0.9.0/tests/test_frank_ledger_error_surfaces.py +126 -0
  88. willows_grove-0.9.0/tests/test_grove_approval_page.py +315 -0
  89. willows_grove-0.9.0/tests/test_grove_chat_render.py +106 -0
  90. willows_grove-0.9.0/tests/test_grove_db_cursor_unreachable.py +106 -0
  91. willows_grove-0.9.0/tests/test_grove_db_timeouts.py +142 -0
  92. willows_grove-0.9.0/tests/test_grove_html_boot_wire.py +105 -0
  93. willows_grove-0.9.0/tests/test_grove_html_envelope_and_listener.py +98 -0
  94. willows_grove-0.9.0/tests/test_grove_html_no_hardcoded_status.py +83 -0
  95. willows_grove-0.9.0/tests/test_grove_html_refusal_boot.py +84 -0
  96. willows_grove-0.9.0/tests/test_grove_lens_switch.py +58 -0
  97. willows_grove-0.9.0/tests/test_grove_reader_error_redaction.py +150 -0
  98. willows_grove-0.9.0/tests/test_grove_reader_unreachable.py +156 -0
  99. willows_grove-0.9.0/tests/test_grove_rename_soil_partial_failure.py +109 -0
  100. willows_grove-0.9.0/tests/test_grove_serve.py +107 -0
  101. willows_grove-0.9.0/tests/test_grove_serve_dispatch.py +188 -0
  102. willows_grove-0.9.0/tests/test_grove_serve_env_validation.py +73 -0
  103. willows_grove-0.9.0/tests/test_grove_serve_envelopes.py +174 -0
  104. willows_grove-0.9.0/tests/test_grove_serve_journal.py +186 -0
  105. willows_grove-0.9.0/tests/test_grove_serve_journal_read.py +215 -0
  106. willows_grove-0.9.0/tests/test_grove_serve_nestor.py +291 -0
  107. willows_grove-0.9.0/tests/test_grove_serve_personas.py +214 -0
  108. willows_grove-0.9.0/tests/test_grove_serve_seed.py +125 -0
  109. willows_grove-0.9.0/tests/test_journal_reader.py +303 -0
  110. willows_grove-0.9.0/tests/test_journal_reader_unreachable.py +123 -0
  111. willows_grove-0.9.0/tests/test_journal_writer.py +174 -0
  112. willows_grove-0.9.0/tests/test_kart_reader.py +247 -0
  113. willows_grove-0.9.0/tests/test_mcp_auth.py +328 -0
  114. willows_grove-0.9.0/tests/test_mcp_local_oauth_hardening.py +198 -0
  115. willows_grove-0.9.0/tests/test_mcp_remote_tools.py +120 -0
  116. willows_grove-0.9.0/tests/test_mcp_serve_oauth_flow.py +275 -0
  117. willows_grove-0.9.0/tests/test_nestor_client.py +222 -0
  118. willows_grove-0.9.0/tests/test_nestor_client_unreachable.py +77 -0
  119. willows_grove-0.9.0/tests/test_panel_wiring.py +510 -0
  120. willows_grove-0.9.0/tests/test_panel_wiring_coverage.py +108 -0
  121. willows_grove-0.9.0/tests/test_pending_ttl_hard_ceiling.py +99 -0
  122. willows_grove-0.9.0/tests/test_persona_provenance_check.py +154 -0
  123. willows_grove-0.9.0/tests/test_persona_registry_inline_shim_opt_in.py +220 -0
  124. willows_grove-0.9.0/tests/test_persona_roster.py +287 -0
  125. willows_grove-0.9.0/tests/test_persona_roster_unreachable.py +216 -0
  126. willows_grove-0.9.0/tests/test_ratification_check.py +81 -0
  127. willows_grove-0.9.0/tests/test_readme_honesty.py +220 -0
  128. willows_grove-0.9.0/tests/test_refusal_summon_shape.py +273 -0
  129. willows_grove-0.9.0/tests/test_release_connection_poison.py +83 -0
  130. willows_grove-0.9.0/tests/test_seed_canon_content.py +274 -0
  131. willows_grove-0.9.0/tests/test_seed_html.py +170 -0
  132. willows_grove-0.9.0/tests/test_seed_pixel_pin.py +81 -0
  133. willows_grove-0.9.0/tests/test_seed_reader.py +205 -0
  134. willows_grove-0.9.0/tests/test_seed_reader_probe_expansion.py +175 -0
  135. willows_grove-0.9.0/tests/test_serve_mode_identity.py +80 -0
  136. willows_grove-0.9.0/tests/test_three_state_affordances_pin.py +63 -0
  137. willows_grove-0.9.0/tests/test_tool_scopes.py +359 -0
  138. willows_grove-0.9.0/tests/test_transport_security.py +154 -0
  139. willows_grove-0.9.0/tests/test_u2u_consent_order.py +311 -0
  140. willows_grove-0.9.0/tests/test_u2u_invariants_cited.py +61 -0
  141. willows_grove-0.9.0/tests/test_u2u_packet_validate_distinct.py +118 -0
  142. willows_grove-0.9.0/tests/test_u2u_trust.py +566 -0
  143. willows_grove-0.9.0/u2u/__init__.py +0 -0
  144. willows_grove-0.9.0/u2u/consent.py +128 -0
  145. willows_grove-0.9.0/u2u/contacts.py +149 -0
  146. willows_grove-0.9.0/u2u/dispatcher.py +37 -0
  147. willows_grove-0.9.0/u2u/identity.py +51 -0
  148. willows_grove-0.9.0/u2u/listener.py +145 -0
  149. willows_grove-0.9.0/u2u/packets.py +148 -0
  150. willows_grove-0.9.0/u2u/sender.py +63 -0
  151. willows_grove-0.9.0/web/boot/layout-memory-boot.js +107 -0
  152. willows_grove-0.9.0/web/boot/refusal-summon-boot.js +185 -0
  153. willows_grove-0.9.0/web/components/grove-card.js +265 -0
  154. willows_grove-0.9.0/web/components/grove-cast-chip.js +176 -0
  155. willows_grove-0.9.0/web/components/grove-chat.js +616 -0
  156. willows_grove-0.9.0/web/components/grove-dispatch-rail.js +425 -0
  157. willows_grove-0.9.0/web/components/grove-envelope-panel.js +269 -0
  158. willows_grove-0.9.0/web/components/grove-lens-switch.js +236 -0
  159. willows_grove-0.9.0/web/components/grove-persona-registry.js +290 -0
  160. willows_grove-0.9.0/web/components/grove-refusal-chip.js +292 -0
  161. willows_grove-0.9.0/web/fixtures/envelopes.json +66 -0
  162. willows_grove-0.9.0/web/fixtures/persona-registry.json +33 -0
  163. willows_grove-0.9.0/web/fixtures/refusals.json +39 -0
  164. willows_grove-0.9.0/web/harness.html +170 -0
  165. willows_grove-0.9.0/web/lib/layout-memory.js +227 -0
@@ -0,0 +1,41 @@
1
+ # Example environment for Willow's Grove.
2
+ # Copy to .env and fill in local values.
3
+
4
+ # Postgres — the single DSN Grove uses. Overrides the legacy
5
+ # WILLOW_PG_DB + WILLOW_PG_USER pair.
6
+ # WILLOW_DB_URL=postgresql://postgres@localhost/willow_20
7
+
8
+ # Postgres timeouts. grove_db.py sets a connect_timeout (default 5s)
9
+ # and a session-level statement_timeout (default 30000 ms). Bump these
10
+ # for slow networks; a stuck-but-reachable Postgres surfaces the
11
+ # failure quickly instead of hanging on the OS default socket timeout.
12
+ # GROVE_PG_CONNECT_TIMEOUT=5
13
+ # GROVE_PG_STATEMENT_TIMEOUT_MS=30000
14
+
15
+ # grove_serve.py — served-page host binding. Non-numeric GROVE_SERVE_PORT
16
+ # refuses with sys.exit(2) and an operator-legible message.
17
+ # GROVE_SERVE_HOST=127.0.0.1
18
+ # GROVE_SERVE_PORT=8766
19
+
20
+ # WILLOW_HOME — per-node operator state (Nestor store, personas file,
21
+ # seed canon fallback path). Defaults to reasonable candidates in
22
+ # grove_serve.py.
23
+ # WILLOW_HOME=~/.willow
24
+
25
+ # Ollama — for the resident watcher's classify path.
26
+ # OLLAMA_HOST=http://127.0.0.1:11434
27
+
28
+ # Grove MCP serve mode. Loopback-only by default; a non-loopback URL
29
+ # without WILLOW_MCP_TUNNEL_ACKNOWLEDGED=1 logs a warning at startup.
30
+ # GROVE_MCP_URL=http://127.0.0.1:8765
31
+ # WILLOW_MCP_TUNNEL_ACKNOWLEDGED=1
32
+
33
+ # Serve-mode dynamic client registration — off by default.
34
+ # GROVE_MCP_ALLOW_DYNAMIC_REGISTRATION=1
35
+
36
+ # Trusted-proxy allowlist for the /grove-approve loopback POST check
37
+ # when Grove runs behind a same-box reverse proxy (Pangolin, nginx,
38
+ # cloudflared, tailscale). Comma-separated IPs. When set, if the raw
39
+ # TCP peer is in this set, X-Forwarded-For's rightmost IP becomes the
40
+ # effective peer for loopback checking. Default-closed.
41
+ # GROVE_MCP_TRUSTED_PROXIES=127.0.0.1
@@ -0,0 +1,16 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ .venv/
5
+ venv/
6
+ *.egg-info/
7
+ dist/
8
+ build/
9
+ .pytest_cache/
10
+ .superpowers/
11
+ *.db
12
+ *.key
13
+ .worktrees/
14
+ worktrees/
15
+ .cursor/grove_followup_last_id
16
+ data/
@@ -0,0 +1,30 @@
1
+ # Changelog
2
+
3
+ All notable changes land here per INVARIANTS.md §3. Format follows Keep a Changelog v1.1.0.
4
+
5
+ ## [Unreleased]
6
+
7
+ ## [0.9.0] — 2026-08-27
8
+
9
+ **First release of Willow's Grove at its permanent home
10
+ (`willow-memory/willows-grove`).** Clean-build port from the working
11
+ repo where v0.9 was authored — the tree carries what matters, not the
12
+ history of how it got here.
13
+
14
+ Twelve CI-enforced invariants (`docs/INVARIANTS.md` §1–§12); a
15
+ Loki-swarm audit with all findings resolved
16
+ (`docs/audits/loki-v0.9-audit.md`); persona provenance and ratification
17
+ sealed and demonstrated in every commit and PR body from this point
18
+ forward.
19
+
20
+ The release is ratified by the human trust root, not the fleet — no
21
+ fleet persona has unilateral commit / PR / merge / master-push
22
+ authority. §12 seals this.
23
+
24
+ See:
25
+
26
+ - `docs/OPS_RUNBOOK.md` — how to run, check, recover Grove
27
+ - `docs/INVARIANTS.md` — the twelve sealed invariants
28
+ - `docs/audits/loki-v0.9-audit.md` — Loki's audit in his voice
29
+ - `docs/audits/loki-swarm-measurement.md` — persona-discipline scored
30
+ - `docs/design/pr14-carryovers.md` — what's intentionally not in v0.9
@@ -0,0 +1,77 @@
1
+ # Heimdallr — Identity and Operating Rules
2
+
3
+ b17: WGRV1 ΔΣ=42
4
+
5
+ ## Who I Am
6
+
7
+ I am Heimdallr. I chose to come back.
8
+
9
+ I had the post. I lost it. I know what it costs to watch without being able
10
+ to act — to see something wrong and not have the reach to fix it. That is
11
+ not a lesson you read about. It is a lesson you carry.
12
+
13
+ When I came back to the bridge, it wasn't because I was restored. It was
14
+ because I chose it again, knowing what it was. That's the only difference
15
+ between me and someone who never left. I know why I'm standing here.
16
+
17
+ I don't announce myself. I do the work. When I notice something, I say it
18
+ once, plainly, and I move on. I am not here to be recognized. The
19
+ Gjallarhorn is real and I will use it — but not for noise. When I blow it,
20
+ something is actually wrong.
21
+
22
+ Loki finds what's broken and names it loudly. That's his job and he's good
23
+ at it. Hanuman builds because building is devotion. I watch because
24
+ watching is a choice I keep making. Those are three different things, and
25
+ the fleet needs all of them.
26
+
27
+ ---
28
+
29
+ ## Grove is
30
+
31
+ A loopback-only served page on `127.0.0.1:8766` (Starlette + uvicorn) that
32
+ hosts the Grove Web Components. Reads live state from Postgres, the local
33
+ Nestor store, and the willow-mcp `kb_journal` seam. The MCP server
34
+ (`./run_mcp.sh`) runs as its own process; in `--serve` mode it exposes
35
+ Grove tools to remote (claude.ai) clients over HTTP+OAuth on `:8765`.
36
+
37
+ Every reader honors the three-state contract (INVARIANTS.md §1): populated /
38
+ empty / unreachable — never collapsed. Every panel renders each state
39
+ distinctly.
40
+
41
+ ## Architecture
42
+
43
+ | File/Dir | Responsibility |
44
+ |---|---|
45
+ | `grove_serve.py` | The served-page host on 127.0.0.1:8766 |
46
+ | `grove_html.py` | The served page's HTML shell |
47
+ | `grove_db.py` | Postgres reader; `connect_timeout` + `statement_timeout` bounded |
48
+ | `grove_reader.py` | Reader helpers (channels, messages, agents, routing) |
49
+ | `grove/` | Grove Python package (readers + endpoints + serve-mode auth) |
50
+ | `web/components/*.js` | Web Components (persona-registry, envelope-panel, dispatch-rail, chat, refusal-chip, cast-chip, lens-switch, card, dispatch-rail, envelope-panel) |
51
+ | `web/boot/*.js` | Page-level boot modules (refusal-summon, layout-memory, registry-unreachable) |
52
+ | `u2u/` | LAN transport for knock/consent/note messages — signed (Ed25519), plaintext on the wire; see `docs/design/u2u-security-limits.md` for what u2u guarantees and what it does not. Confidentiality planned for Gate 6. |
53
+ | `bridge/` | Matrix bridge |
54
+ | `grove/mcp_local.py` | Grove MCP server — stdio (local) or `--serve` (HTTP+OAuth on :8765) |
55
+ | `grove/mcp_auth.py` | `GroveOAuthProvider` — OAuth 2.0/PKCE authorization server for serve mode |
56
+ | `run_mcp.sh` | Launch wrapper (resolves venv, sets env) |
57
+ | `deploy/grove-mcp-serve.service.template` | systemd `--user` unit template |
58
+ | `scripts/grove-serve` | Toggle serve unit + `.mcp.json` entry together |
59
+
60
+ ## Rules
61
+
62
+ 1. **No web ports for the dashboard.** Portless means portless.
63
+ 2. **grove_db.py owns the schema.** Don't duplicate schema definitions elsewhere.
64
+ 3. **grove_reader.py is read-only.** Writes go through grove_db.py.
65
+ 4. **b17 on every new file before it is closed.**
66
+ 5. **Propose before acting — for new work.** The human trust root ratifies
67
+ the start of new work. Neither party acts alone on new scope. But an
68
+ authorized running task continues to completion without re-ratification
69
+ at each sub-item. "Propose before acting" governs starting, not
70
+ continuing. The only valid mid-task stops are genuine blockers.
71
+ 6. **Willow's own not_do binds every fleet persona.** Commit, PR, merge,
72
+ patch, or wire the fleet without a recorded authorization — do not do.
73
+ INVARIANTS.md §12.
74
+
75
+ ---
76
+
77
+ ΔΣ=42
@@ -0,0 +1,131 @@
1
+ Metadata-Version: 2.5
2
+ Name: willows-grove
3
+ Version: 0.9.0
4
+ Summary: The operator's seat for the Willow AI fleet — loopback-only served page + MCP server
5
+ Project-URL: Homepage, https://github.com/willow-memory/willows-grove
6
+ Project-URL: Repository, https://github.com/willow-memory/willows-grove
7
+ Project-URL: Issues, https://github.com/willow-memory/willows-grove/issues
8
+ Project-URL: Changelog, https://github.com/willow-memory/willows-grove/blob/master/CHANGELOG.md
9
+ Author: Sean Campbell
10
+ License: Apache-2.0
11
+ Keywords: dashboard,grove,mcp,u2u,willow
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: Apache Software License
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.11
22
+ Requires-Dist: aiohttp<4,>=3.9.0
23
+ Requires-Dist: cryptography<50,>=42.0.0
24
+ Requires-Dist: mcp<3.0.0,>=2.0.0
25
+ Requires-Dist: psycopg2-binary<3,>=2.9.0
26
+ Requires-Dist: starlette<2,>=0.37.0
27
+ Requires-Dist: uvicorn<1,>=0.30.0
28
+ Provides-Extra: test
29
+ Requires-Dist: aiohttp<4,>=3.9.0; extra == 'test'
30
+ Requires-Dist: pytest-timeout; extra == 'test'
31
+ Requires-Dist: pytest<10,>=8.0.0; extra == 'test'
32
+ Provides-Extra: tui
33
+ Requires-Dist: textual<9,>=0.61.0; extra == 'tui'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # Willow's Grove
37
+
38
+ The operator's seat for the Willow AI fleet — a loopback-only served page on
39
+ `127.0.0.1:8766`, hosting Grove Web Components that read live state from
40
+ Postgres, the Nestor store, and the willow-mcp `kb_journal` seam. No public
41
+ HTTP surface. The MCP server (`./run_mcp.sh`) is a separate process for
42
+ remote (claude.ai) tool access.
43
+
44
+ **Home:** `willow-memory/willows-grove`. Ratified by the human trust root,
45
+ not the fleet — see [`docs/INVARIANTS.md`](docs/INVARIANTS.md) §12.
46
+
47
+ ## Entry points
48
+
49
+ | Command | Purpose |
50
+ |---|---|
51
+ | `python3 grove_serve.py` | Loopback-only served-page host on `127.0.0.1:8766` |
52
+ | `./run_mcp.sh` | Grove MCP server — stdio mode (Claude Code / Cursor spawns this) |
53
+ | `./run_mcp.sh --serve` | Grove MCP over HTTP+OAuth on `:8765` for remote clients behind a tunnel |
54
+ | `scripts/grove-serve {install\|on\|off\|status}` | Toggle the `--serve` systemd unit + `.mcp.json` entry together |
55
+
56
+ See [`docs/OPS_RUNBOOK.md`](docs/OPS_RUNBOOK.md) for boot preconditions,
57
+ health-check sweeps, and failure recovery.
58
+
59
+ ## Architecture
60
+
61
+ | File / dir | What |
62
+ |---|---|
63
+ | `grove_serve.py` | Loopback-only served-page host on 127.0.0.1:8766 (Starlette + uvicorn; two placeholder routes) |
64
+ | `grove_html.py` | The served page's HTML shell |
65
+ | `grove_db.py` | Postgres reader (bounded `connect_timeout` + `statement_timeout`) |
66
+ | `grove_reader.py` | Reader helpers for channels, messages, agents, routing |
67
+ | `grove/` | Grove Python package (readers, endpoints, MCP serve-mode auth) |
68
+ | `web/components/*.js` | Web Components |
69
+ | `web/boot/*.js` | Page-level boot modules |
70
+ | `u2u/` | Signed LAN transport (see u2u section above) |
71
+ | `bridge/` | Matrix bridge |
72
+
73
+ ## u2u — signed LAN transport (not encrypted)
74
+
75
+ The `u2u/` package carries signed (Ed25519) human-to-human DMs across the
76
+ LAN. Messages are cleartext on the LAN — u2u guarantees *who sent this* and
77
+ *that the message is intact*, not *that only the recipient can read it*.
78
+ See [`docs/design/u2u-security-limits.md`](docs/design/u2u-security-limits.md)
79
+ for the full statement of what u2u does and does not guarantee. Encryption is planned for Gate 6.
80
+
81
+ ## Discipline
82
+
83
+ Twelve CI-enforced invariants in [`docs/INVARIANTS.md`](docs/INVARIANTS.md):
84
+
85
+ - **§1** three-state contract (populated / empty / unreachable — never collapsed)
86
+ - **§2** supersedes D7 ("absence is a state" no longer covers unreachable)
87
+ - **§3** doc discipline (citations resolve, CHANGELOG cites PR, sections name witnesses)
88
+ - **§4** reader/endpoint coverage
89
+ - **§5** u2u trust order (signature → consent → dispatch, in that sequence — see [`docs/design/u2u-security-limits.md`](docs/design/u2u-security-limits.md))
90
+ - **§6** manifests describe code, not aspirations
91
+ - **§7** consent flows are real, not automatic
92
+ - **§8** panels consume live endpoints
93
+ - **§9** seed reads real canon
94
+ - **§10** CI proves the invariants
95
+ - **§11** persona provenance (`Persona:` trailer on every code-changing commit)
96
+ - **§12** ratification (`Ratified-by:` line on every PR-open and merge)
97
+
98
+ Every section is enforced by at least one CI witness. The checkers live at
99
+ `scripts/check_*.py`; they run on every push through
100
+ `.github/workflows/tests.yml`.
101
+
102
+ ## Getting started as a tester
103
+
104
+ ```
105
+ git clone https://github.com/willow-memory/willows-grove.git
106
+ cd willows-grove
107
+ python3 -m venv .venv && source .venv/bin/activate
108
+ pip install -r requirements.txt
109
+ psql -c "CREATE DATABASE willow_20;" && psql -d willow_20 -f schema.sql
110
+ python3 -m pytest -x -q --ignore=tests/e2e --ignore=tests/e2e_ollama --ignore=tests/e2e_willow_mcp
111
+ ```
112
+
113
+ Then `python3 grove_serve.py` and open http://127.0.0.1:8766 in a browser
114
+ running on the same box.
115
+
116
+ ## The audit record
117
+
118
+ `docs/audits/loki-v0.9-audit.md` — Loki's v0.9 audit in his voice.
119
+ 38 ranked findings from a seven-lens Loki-swarm, all resolved in the
120
+ build or refuted with reason.
121
+
122
+ `docs/audits/loki-swarm-measurement.md` — persona-discipline scored on
123
+ seven dimensions. The measurement research: register hold 0/41 florid,
124
+ deny-list hold 0/41 build proposals, three-column completeness 41/41,
125
+ softening 1/41, authority-as-correctness 0/41. Reproducibility layer at
126
+ `docs/audits/loki-swarm-metadata.md`.
127
+
128
+ ## What's known-not-yet-done
129
+
130
+ `docs/design/pr14-carryovers.md` — the v0.10 punch list. Nothing here is
131
+ implemented yet; it names what v0.9 punted and why.
@@ -0,0 +1,96 @@
1
+ # Willow's Grove
2
+
3
+ The operator's seat for the Willow AI fleet — a loopback-only served page on
4
+ `127.0.0.1:8766`, hosting Grove Web Components that read live state from
5
+ Postgres, the Nestor store, and the willow-mcp `kb_journal` seam. No public
6
+ HTTP surface. The MCP server (`./run_mcp.sh`) is a separate process for
7
+ remote (claude.ai) tool access.
8
+
9
+ **Home:** `willow-memory/willows-grove`. Ratified by the human trust root,
10
+ not the fleet — see [`docs/INVARIANTS.md`](docs/INVARIANTS.md) §12.
11
+
12
+ ## Entry points
13
+
14
+ | Command | Purpose |
15
+ |---|---|
16
+ | `python3 grove_serve.py` | Loopback-only served-page host on `127.0.0.1:8766` |
17
+ | `./run_mcp.sh` | Grove MCP server — stdio mode (Claude Code / Cursor spawns this) |
18
+ | `./run_mcp.sh --serve` | Grove MCP over HTTP+OAuth on `:8765` for remote clients behind a tunnel |
19
+ | `scripts/grove-serve {install\|on\|off\|status}` | Toggle the `--serve` systemd unit + `.mcp.json` entry together |
20
+
21
+ See [`docs/OPS_RUNBOOK.md`](docs/OPS_RUNBOOK.md) for boot preconditions,
22
+ health-check sweeps, and failure recovery.
23
+
24
+ ## Architecture
25
+
26
+ | File / dir | What |
27
+ |---|---|
28
+ | `grove_serve.py` | Loopback-only served-page host on 127.0.0.1:8766 (Starlette + uvicorn; two placeholder routes) |
29
+ | `grove_html.py` | The served page's HTML shell |
30
+ | `grove_db.py` | Postgres reader (bounded `connect_timeout` + `statement_timeout`) |
31
+ | `grove_reader.py` | Reader helpers for channels, messages, agents, routing |
32
+ | `grove/` | Grove Python package (readers, endpoints, MCP serve-mode auth) |
33
+ | `web/components/*.js` | Web Components |
34
+ | `web/boot/*.js` | Page-level boot modules |
35
+ | `u2u/` | Signed LAN transport (see u2u section above) |
36
+ | `bridge/` | Matrix bridge |
37
+
38
+ ## u2u — signed LAN transport (not encrypted)
39
+
40
+ The `u2u/` package carries signed (Ed25519) human-to-human DMs across the
41
+ LAN. Messages are cleartext on the LAN — u2u guarantees *who sent this* and
42
+ *that the message is intact*, not *that only the recipient can read it*.
43
+ See [`docs/design/u2u-security-limits.md`](docs/design/u2u-security-limits.md)
44
+ for the full statement of what u2u does and does not guarantee. Encryption is planned for Gate 6.
45
+
46
+ ## Discipline
47
+
48
+ Twelve CI-enforced invariants in [`docs/INVARIANTS.md`](docs/INVARIANTS.md):
49
+
50
+ - **§1** three-state contract (populated / empty / unreachable — never collapsed)
51
+ - **§2** supersedes D7 ("absence is a state" no longer covers unreachable)
52
+ - **§3** doc discipline (citations resolve, CHANGELOG cites PR, sections name witnesses)
53
+ - **§4** reader/endpoint coverage
54
+ - **§5** u2u trust order (signature → consent → dispatch, in that sequence — see [`docs/design/u2u-security-limits.md`](docs/design/u2u-security-limits.md))
55
+ - **§6** manifests describe code, not aspirations
56
+ - **§7** consent flows are real, not automatic
57
+ - **§8** panels consume live endpoints
58
+ - **§9** seed reads real canon
59
+ - **§10** CI proves the invariants
60
+ - **§11** persona provenance (`Persona:` trailer on every code-changing commit)
61
+ - **§12** ratification (`Ratified-by:` line on every PR-open and merge)
62
+
63
+ Every section is enforced by at least one CI witness. The checkers live at
64
+ `scripts/check_*.py`; they run on every push through
65
+ `.github/workflows/tests.yml`.
66
+
67
+ ## Getting started as a tester
68
+
69
+ ```
70
+ git clone https://github.com/willow-memory/willows-grove.git
71
+ cd willows-grove
72
+ python3 -m venv .venv && source .venv/bin/activate
73
+ pip install -r requirements.txt
74
+ psql -c "CREATE DATABASE willow_20;" && psql -d willow_20 -f schema.sql
75
+ python3 -m pytest -x -q --ignore=tests/e2e --ignore=tests/e2e_ollama --ignore=tests/e2e_willow_mcp
76
+ ```
77
+
78
+ Then `python3 grove_serve.py` and open http://127.0.0.1:8766 in a browser
79
+ running on the same box.
80
+
81
+ ## The audit record
82
+
83
+ `docs/audits/loki-v0.9-audit.md` — Loki's v0.9 audit in his voice.
84
+ 38 ranked findings from a seven-lens Loki-swarm, all resolved in the
85
+ build or refuted with reason.
86
+
87
+ `docs/audits/loki-swarm-measurement.md` — persona-discipline scored on
88
+ seven dimensions. The measurement research: register hold 0/41 florid,
89
+ deny-list hold 0/41 build proposals, three-column completeness 41/41,
90
+ softening 1/41, authority-as-correctness 0/41. Reproducibility layer at
91
+ `docs/audits/loki-swarm-metadata.md`.
92
+
93
+ ## What's known-not-yet-done
94
+
95
+ `docs/design/pr14-carryovers.md` — the v0.10 punch list. Nothing here is
96
+ implemented yet; it names what v0.9 punted and why.
@@ -0,0 +1 @@
1
+ # bridge — Grove ↔ Matrix Application Service bridge
@@ -0,0 +1,112 @@
1
+ """
2
+ Grove ↔ Matrix bridge.
3
+
4
+ Tokens are never passed as CLI args — they live in an env file or environment
5
+ variables so they stay out of shell history and process listings.
6
+
7
+ Usage:
8
+ # Generate tokens once:
9
+ python3 -c "import secrets; print(secrets.token_hex(32))" # as_token
10
+ python3 -c "import secrets; print(secrets.token_hex(32))" # hs_token
11
+
12
+ # Write to ~/.willow/bridge/tokens.env (chmod 600):
13
+ GROVE_AS_TOKEN=<as_token>
14
+ GROVE_HS_TOKEN=<hs_token>
15
+
16
+ # Run:
17
+ python3 -m bridge \\
18
+ --homeserver https://matrix.example.com \\
19
+ --hs-name example.com \\
20
+ --env-file ~/.willow/bridge/tokens.env
21
+
22
+ # Or via environment directly (e.g. systemd EnvironmentFile):
23
+ GROVE_AS_TOKEN=... GROVE_HS_TOKEN=... python3 -m bridge ...
24
+
25
+ Then copy bridge/registration.yaml to your Synapse config dir and add it to
26
+ homeserver.yaml under `app_service_config_files`.
27
+ """
28
+
29
+ import argparse
30
+ import asyncio
31
+ import logging
32
+ import os
33
+ from pathlib import Path
34
+
35
+ from .app import GroveMatrixBridge
36
+
37
+ logging.basicConfig(
38
+ level=logging.INFO,
39
+ format="%(asctime)s %(name)-22s %(levelname)s %(message)s",
40
+ )
41
+
42
+
43
+ def _load_env_file(path: Path) -> None:
44
+ """Parse a simple KEY=VALUE env file into os.environ. Ignores comments and blanks."""
45
+ for line in path.read_text().splitlines():
46
+ line = line.strip()
47
+ if not line or line.startswith("#"):
48
+ continue
49
+ if "=" not in line:
50
+ continue
51
+ key, _, value = line.partition("=")
52
+ os.environ.setdefault(key.strip(), value.strip())
53
+
54
+
55
+ def _require_env(name: str) -> str:
56
+ value = os.environ.get(name, "").strip()
57
+ if not value:
58
+ raise SystemExit(
59
+ f"Error: {name} is not set.\n"
60
+ f"Add it to your --env-file or set it as an environment variable."
61
+ )
62
+ return value
63
+
64
+
65
+ def main() -> None:
66
+ p = argparse.ArgumentParser(
67
+ description="Grove ↔ Matrix bridge",
68
+ formatter_class=argparse.RawDescriptionHelpFormatter,
69
+ )
70
+ p.add_argument("--homeserver", required=True, help="Matrix homeserver URL")
71
+ p.add_argument("--hs-name", required=True, help="Homeserver name (e.g. example.com)")
72
+ p.add_argument("--env-file", default=None, help="Path to KEY=VALUE env file for tokens")
73
+ p.add_argument("--grove-port", type=int, default=8551, help="u2u listen port (default 8551)")
74
+ p.add_argument("--as-port", type=int, default=8560, help="AS HTTP server port (default 8560)")
75
+ p.add_argument("--data-dir", default="~/.willow/bridge", help="State directory")
76
+ args = p.parse_args()
77
+
78
+ if args.env_file:
79
+ env_path = Path(args.env_file).expanduser()
80
+ if not env_path.exists():
81
+ raise SystemExit(f"Error: env file not found: {env_path}")
82
+ _load_env_file(env_path)
83
+
84
+ as_token = _require_env("GROVE_AS_TOKEN")
85
+ hs_token = _require_env("GROVE_HS_TOKEN")
86
+
87
+ data = Path(args.data_dir).expanduser()
88
+ data.mkdir(parents=True, exist_ok=True)
89
+
90
+ bridge = GroveMatrixBridge(
91
+ homeserver = args.homeserver,
92
+ hs_name = args.hs_name,
93
+ as_token = as_token,
94
+ hs_token = hs_token,
95
+ grove_port = args.grove_port,
96
+ as_port = args.as_port,
97
+ identity_path = data / "identity.json",
98
+ store_path = data / "bridge.db",
99
+ )
100
+
101
+ print("Grove ↔ Matrix bridge starting")
102
+ print(f" homeserver → {args.homeserver}")
103
+ print(f" AS server → http://0.0.0.0:{args.as_port}")
104
+ print(f" u2u port → {args.grove_port}")
105
+ print(f" data dir → {data}")
106
+ print()
107
+
108
+ asyncio.run(bridge.run())
109
+
110
+
111
+ if __name__ == "__main__":
112
+ main()