@intentius/behold 0.17.0 → 0.19.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/AGENTS.md +757 -26
- package/LICENSE +190 -0
- package/README.md +251 -187
- package/THIRD_PARTY.md +75 -0
- package/demos.json +80 -9
- package/dist/cli.js +5479 -1673
- package/example-argo-estate/package-lock.json +6 -6
- package/example-carve/app/package-lock.json +3 -3
- package/example-choudoufu-estate/.behold.json +8 -0
- package/example-choudoufu-estate/README.md +48 -0
- package/example-choudoufu-estate/monolith/behaviour.live.json +495 -0
- package/example-choudoufu-estate/monolith/carve.json +12 -0
- package/example-choudoufu-estate/monolith/main.tf +127 -0
- package/example-choudoufu-estate/scripts/choudoufu-down.sh +8 -0
- package/example-choudoufu-estate/scripts/choudoufu-up.sh +62 -0
- package/example-choudoufu-estate/team-a/behaviour.live.json +179 -0
- package/example-choudoufu-estate/team-a/main.tf +58 -0
- package/example-choudoufu-estate/team-b/behaviour.live.json +179 -0
- package/example-choudoufu-estate/team-b/main.tf +73 -0
- package/example-choudoufu-estate/team-c/behaviour.live.json +179 -0
- package/example-choudoufu-estate/team-c/main.tf +51 -0
- package/example-flux-estate/package-lock.json +6 -6
- package/example-k8s/package-lock.json +6 -6
- package/example-terraform-estate/README.md +109 -0
- package/example-terraform-estate/backends/backend.s3.tf +10 -0
- package/example-terraform-estate/envs/dev/README.md +5 -0
- package/example-terraform-estate/envs/prod/data.tf +18 -0
- package/example-terraform-estate/envs/prod/network.tf +80 -0
- package/example-terraform-estate/envs/prod/outputs.tf +13 -0
- package/example-terraform-estate/envs/prod/provider.tf +10 -0
- package/example-terraform-estate/envs/prod/services.tf +21 -0
- package/example-terraform-estate/envs/prod/storage.tf +31 -0
- package/example-terraform-estate/envs/prod/variables.tf +17 -0
- package/example-terraform-estate/envs/prod/versions.tf +10 -0
- package/example-terraform-estate/modules/service/main.tf +58 -0
- package/example-terraform-estate/modules/service/outputs.tf +7 -0
- package/example-terraform-estate/modules/service/variables.tf +16 -0
- package/example-terraform-estate/modules/service/versions.tf +10 -0
- package/example-terraform-estate/platform/iam.tf +40 -0
- package/example-terraform-estate/platform/kms.tf +16 -0
- package/example-terraform-estate/platform/outputs.tf +7 -0
- package/example-terraform-estate/platform/variables.tf +10 -0
- package/example-terraform-estate/platform/versions.tf +10 -0
- package/example-writes/package-lock.json +3 -3
- package/licenses/cncf-artwork-LICENSE.md +13 -0
- package/licenses/iTerm2-Color-Schemes-LICENSE +31 -0
- package/licenses/kubernetes-icons-LICENSE +201 -0
- package/package.json +25 -7
- package/web/app.js +1309 -41
- package/web/behaviour-scale.js +380 -0
- package/web/behaviour-scale.test.js +307 -0
- package/web/cross-env.js +58 -0
- package/web/cross-env.test.js +61 -0
- package/web/index.html +62 -2
- package/web/pending.js +67 -0
- package/web/pending.test.js +62 -0
- package/web/read-cost.js +49 -0
- package/web/read-cost.test.js +76 -0
- package/web/refresh-queue.js +26 -0
- package/web/refresh-queue.test.js +54 -0
package/README.md
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
# behold
|
|
2
2
|
|
|
3
|
-
**A live control plane on [chant](https://github.com/INTENTIUS/chant).**
|
|
4
|
-
|
|
5
|
-
drift — then act through delegated, gated Ops.
|
|
3
|
+
**A live control plane on [chant](https://github.com/INTENTIUS/chant).** Every
|
|
4
|
+
substrate in one graph, coloured by drift. Then act through delegated, gated Ops.
|
|
6
5
|
|
|
7
|
-
📖 **[Documentation](https://intentius.io/behold/)**
|
|
8
|
-
gets you to a running graph without reading anything else first.
|
|
6
|
+
📖 **[Documentation](https://intentius.io/behold/)** starts with an agent prompt
|
|
7
|
+
that gets you to a running graph without reading anything else first.
|
|
9
8
|
|
|
10
9
|
Where Argo CD shows one cluster's tree, behold shows the mixed-substrate estate:
|
|
11
|
-
cloud drift on AWS, supply-chain drift on GitHub Actions, artifact presence
|
|
12
|
-
|
|
10
|
+
cloud drift on AWS, supply-chain drift on GitHub Actions, artifact presence on
|
|
11
|
+
Helm. Each substrate's own kind of truth, in one picture.
|
|
13
12
|
|
|
14
13
|
```
|
|
15
14
|
chant source ──build/lint──▶ graph IR ──behold──▶ live graph + drift + (delegated) actions
|
|
@@ -18,27 +17,51 @@ chant source ──build/lint──▶ graph IR ──behold──▶ live graph
|
|
|
18
17
|
|
|
19
18
|
## Quick start (npm)
|
|
20
19
|
|
|
21
|
-
No chant project yet? The bundled demo is the five-minute path
|
|
22
|
-
policy
|
|
23
|
-
(needs Docker):
|
|
20
|
+
No chant project yet? The bundled demo is the five-minute path: an S3 bucket and
|
|
21
|
+
policy against a local emulator. Needs Docker, and nothing else.
|
|
24
22
|
|
|
25
23
|
```sh
|
|
26
24
|
npx @intentius/behold demo # copies the example to ./behold-demo, installs, serves
|
|
27
|
-
# → http://localhost:4600
|
|
25
|
+
# → http://localhost:4600. Blue = declared; click Deploy, watch it turn green
|
|
28
26
|
```
|
|
29
27
|
|
|
30
28
|
The copied project is yours: edit its source and watch the graph change live.
|
|
31
|
-
There's a whole catalog
|
|
29
|
+
There's a whole catalog, and `behold demo --list` names the rest (`behold demo
|
|
32
30
|
k8s` stands the same loop up on a throwaway k3d cluster: runtime Pods, field
|
|
33
|
-
ownership; `behold demo argo-estate` needs nothing at all
|
|
31
|
+
ownership; `behold demo argo-estate` needs nothing at all, being a three-project
|
|
34
32
|
Argo CD estate, declared only, so it runs where Docker doesn't; `behold demo
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
choudoufu-estate` is four tag-owned OpenTofu estates served composed, where a
|
|
34
|
+
card's colour is **ownership** (bound, unowned, pending) rather than "nothing
|
|
35
|
+
changed": a resource whose attributes drifted out of band still carries its
|
|
36
|
+
markers and is still green, so attribute drift is a separate read you ask for
|
|
37
|
+
with ⌘K → "Re-check live with plan"; `behold demo carve` is the Terraform peel
|
|
38
|
+
walkthrough, below). Two more need no Docker either: `behold demo
|
|
39
|
+
terraform-estate` draws a multi-root Terraform estate boxed by root, and shows
|
|
40
|
+
what behold refuses to infer, since one root reads a key the other declares and
|
|
41
|
+
no edge is drawn between them; `behold demo flux-estate` is a GitOps estate on
|
|
42
|
+
k3d, a Flux control plane plus two app projects composed. `behold demo fountain`
|
|
43
|
+
clones a real self-hosted app onto a throwaway cluster. Every loaded demo lands
|
|
44
|
+
in the panel's recents, so switching between them is the Scope tab.
|
|
45
|
+
|
|
46
|
+
Working in a checkout rather than an npm install? There is a second catalog,
|
|
47
|
+
`workbench.json`, which is this checkout's and is not shipped. It is
|
|
48
|
+
deliberately absent from package.json's `files`, so an npm install has
|
|
49
|
+
`demos.json` and nothing else. It holds the eleven internal estates behold is
|
|
50
|
+
developed against, named by relative path from this repo's root, so an entry
|
|
51
|
+
whose sibling checkout you don't have says so in `--list` instead of failing
|
|
52
|
+
halfway: chant's own two examples and water park's `access/` Terraform roots
|
|
53
|
+
served in place, `../fountain-ops` on its own k3d cluster, the live-mv
|
|
54
|
+
workbench's four estates, three `estate-gen` cohorts, and the generated
|
|
55
|
+
terralith at scale 1 and 4, greenfield and adopted from a stock `terraform
|
|
56
|
+
apply` by a `live-import` you run by hand. `just example terralith-4` serves
|
|
57
|
+
one (`just example name="<entry>"`); each that boots a substrate brings up its
|
|
58
|
+
own scratch emulator and leaves a `scripts/down.sh` in the target that removes
|
|
59
|
+
it. `just e2e-workbench` loads the whole catalog and asserts each graph.
|
|
37
60
|
|
|
38
61
|
The catalog is in the panel too (#268): the Scope tab's switcher lists every
|
|
39
|
-
bundled demo under your recents, one click
|
|
40
|
-
|
|
41
|
-
install
|
|
62
|
+
bundled demo under your recents, one click from any served project. Demos
|
|
63
|
+
whose prerequisites are missing stay visible and disabled, saying what to
|
|
64
|
+
install; one that would clone from the network says so on the button.
|
|
42
65
|
|
|
43
66
|
Already have a chant project?
|
|
44
67
|
|
|
@@ -51,86 +74,67 @@ npx @intentius/behold serve . --env prod --poll 30 # live drift overlay
|
|
|
51
74
|
|
|
52
75
|
`behold doctor` is the first thing to run on a project behold hasn't seen: one
|
|
53
76
|
line each for the project's kind, its own chant install and version, declared
|
|
54
|
-
lexicons,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
77
|
+
lexicons, which of them can be aimed at an emulator, the envs the picker will
|
|
78
|
+
infer, the kube context chant binds versus your ambient one, substrate
|
|
79
|
+
readiness, committed Ops, and a line per non-chant member kind it found. Each is
|
|
80
|
+
pass, warn or fail with a one-line fix. Nothing is started and nothing changes.
|
|
81
|
+
The exit code is non-zero only when something would actually stop behold serving
|
|
82
|
+
the project well, so CI can gate on it. `--json` for scripts and agents.
|
|
83
|
+
|
|
84
|
+
Driving it from an agent or script? `GET /api` is the front door. It lists the
|
|
85
|
+
graph and action routes with a one-line description each, and
|
|
86
|
+
`GET /api/doctor?reads=1` answers what the last reads cost.
|
|
61
87
|
[AGENTS.md](./AGENTS.md) (shipped in the package) is the read/act contract.
|
|
62
88
|
|
|
63
|
-
## Preview:
|
|
89
|
+
## Preview: one project's graph, nothing set up
|
|
64
90
|
|
|
65
91
|
`behold preview` is the quick way to look at a chant project's graph in a
|
|
66
|
-
browser at one port.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
```sh
|
|
70
|
-
npm install
|
|
71
|
-
npm run dev -- preview # → http://localhost:4600, cwd as the project
|
|
72
|
-
npm run dev -- preview ../my-project # → someone else's project
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
With no path, it opens the **current directory** — run it from inside your chant
|
|
76
|
-
project. Pass a path to look at another one.
|
|
77
|
-
|
|
78
|
-
behold also ships a turnkey **demo**: the whole live experience running Loom on a
|
|
79
|
-
local [Floci](https://github.com/lex00/floci) emulator, no cloud account and
|
|
80
|
-
nothing to configure. Opt in with `--emulator`:
|
|
92
|
+
browser at one port. No env, no emulator, no config: it opens the project you
|
|
93
|
+
point it at.
|
|
81
94
|
|
|
82
95
|
```sh
|
|
83
|
-
|
|
96
|
+
npx @intentius/behold preview # the current directory
|
|
97
|
+
npx @intentius/behold preview ../my-project # somewhere else
|
|
84
98
|
```
|
|
85
99
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
Docker+Floci, no arbitrary-project switching). If Floci isn't up, **Bring up** on
|
|
90
|
-
its substrate pill boots the emulator and deploys Loom. Needs Docker.
|
|
100
|
+
With no path it opens the **current directory**, so running it from inside your
|
|
101
|
+
project is the shortest thing that works. From a checkout the same commands run
|
|
102
|
+
through `npm run dev -- preview`.
|
|
91
103
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
radial layout — where _logical_ is a traditional AWS architecture diagram: nested
|
|
95
|
-
VPC/subnet ⊃ component boxes, CIDRs as labels, one headline resource per
|
|
96
|
-
composite), watch live per-component status, read the reconcile plan, inspect any
|
|
97
|
-
node, and **deploy to the emulator** — the full observe → reconcile → apply,
|
|
98
|
-
Apply all, and a one-click Reset.
|
|
104
|
+
To colour the graph by what is actually deployed, name an environment with
|
|
105
|
+
`serve . --env prod`, below.
|
|
99
106
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
with your own `--env` and creds.
|
|
107
|
+
<sub>`preview`/`export` also take an `--emulator` flag, a v0.1.0-era turnkey path
|
|
108
|
+
for one specific demo project. `behold demo` and `serve --local` supersede it for
|
|
109
|
+
every purpose; it is kept for compatibility and is not documented further.</sub>
|
|
104
110
|
|
|
105
|
-
## Export
|
|
111
|
+
## Export and host: a shareable, interactive snapshot
|
|
106
112
|
|
|
107
113
|
`behold export` freezes whatever estate you're looking at into a **self-contained
|
|
108
|
-
static folder** that any static host can serve
|
|
109
|
-
interactive
|
|
110
|
-
resources
|
|
111
|
-
|
|
114
|
+
static folder** that any static host can serve. Read-only, and fully
|
|
115
|
+
interactive. Pan/zoom, the zoom dial (components, logical, composites,
|
|
116
|
+
resources, attributes, plus the ops track and the apply order where the project
|
|
117
|
+
has them), radial layout, the inspect pane, and the env/tier pickers all work
|
|
118
|
+
client-side; there's no live observe or deploy.
|
|
112
119
|
|
|
113
120
|
```sh
|
|
114
121
|
npm run dev -- export --out ./behold-export # defaults to cwd, like preview
|
|
115
122
|
# or someone else's project, with its live overlay:
|
|
116
123
|
# npm run dev -- export <project> --env <name> --out ./behold-export
|
|
117
|
-
# or the turnkey Loom-on-Floci demo:
|
|
118
|
-
# npm run dev -- export ../loomster --emulator --out ./behold-export
|
|
119
124
|
npx serve ./behold-export # → open it, no backend running
|
|
120
125
|
```
|
|
121
126
|
|
|
122
127
|
Like `preview`, `export` defaults to the current directory and stays plain (no
|
|
123
128
|
env, no emulator) unless you ask. `--env <name>` turns on that project's live
|
|
124
|
-
overlay for the snapshot
|
|
125
|
-
as `preview --emulator`, for exporting that demo.
|
|
129
|
+
overlay for the snapshot.
|
|
126
130
|
|
|
127
131
|
It captures every read endpoint for the whole lens matrix (each env/tier × zoom ×
|
|
128
|
-
radial) in-process
|
|
129
|
-
is byte-identical to live.
|
|
130
|
-
|
|
132
|
+
radial) in-process, through the exact same handlers the live server runs, so a
|
|
133
|
+
snapshot is byte-identical to live. The live app can't run on a Worker, since it
|
|
134
|
+
needs Docker and a chant subprocess; the pre-baked export can.
|
|
131
135
|
|
|
132
|
-
The bundle is **deploy-ready for Cloudflare
|
|
133
|
-
assets-only `wrangler.jsonc` (no server code, pure static)
|
|
136
|
+
The bundle is **deploy-ready for Cloudflare**, since `behold export` writes an
|
|
137
|
+
assets-only `wrangler.jsonc` (no server code, pure static):
|
|
134
138
|
|
|
135
139
|
```sh
|
|
136
140
|
cd ./behold-export && npx wrangler deploy # → https://<name>.<account>.workers.dev
|
|
@@ -138,18 +142,17 @@ cd ./behold-export && npx wrangler deploy # → https://<name>.<account>.wor
|
|
|
138
142
|
|
|
139
143
|
Set the Worker name with `--name`, or edit `wrangler.jsonc`. Auth via
|
|
140
144
|
`wrangler login` or `CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID` (same as any
|
|
141
|
-
Workers deploy).
|
|
142
|
-
|
|
145
|
+
Workers deploy). GitHub Pages, S3, nginx and Cloudflare Pages
|
|
146
|
+
(`wrangler pages deploy .`) all work too.
|
|
143
147
|
|
|
144
|
-
## Try it
|
|
148
|
+
## Try it: your first apply, no cloud account
|
|
145
149
|
|
|
146
150
|
The bundled `example-writes` is one S3 bucket. `serve --local` boots *that
|
|
147
151
|
project's own* local emulator (Floci, via Docker, generically through
|
|
148
|
-
`chant emulator up
|
|
149
|
-
you a **▶ Deploy (floci-apply)** button
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
`behold --help` for how the two relate:
|
|
152
|
+
`chant emulator up`, chant #920), points behold's live overlay at it, and gives
|
|
153
|
+
you a **▶ Deploy (floci-apply)** button on the panel's **Deploy** tab that
|
|
154
|
+
deploys to it, with no AWS account and no creds. It works for any
|
|
155
|
+
emulator-backed lexicon rather than one demo project.
|
|
153
156
|
|
|
154
157
|
```sh
|
|
155
158
|
npm install
|
|
@@ -159,30 +162,31 @@ npm run demo # installs example-writes' deps, then serves it with --local
|
|
|
159
162
|
|
|
160
163
|
<sub>(or by hand: `npm install --prefix example-writes && npm run dev -- serve example-writes --local --env prod`)</sub>
|
|
161
164
|
|
|
162
|
-
1. The graph shows the bucket
|
|
163
|
-
2. Click **▶ Deploy (floci-apply)**
|
|
165
|
+
1. The graph shows the bucket and its TLS policy in **blue** (declared, not yet deployed).
|
|
166
|
+
2. Click **▶ Deploy (floci-apply)** on the panel's **Deploy** tab (or ⌘K → "Deploy: Sync"). The
|
|
164
167
|
now-line streams Build → Apply → Verify; the bucket is created in the emulator
|
|
165
168
|
via the CloudFormation API.
|
|
166
|
-
3. The nodes flip **green (managed)
|
|
169
|
+
3. The nodes flip **green (managed)**: behold's overlay observed the live emulator.
|
|
167
170
|
|
|
168
|
-
No Docker running? behold still serves the source graph and tells you to start
|
|
169
|
-
it
|
|
171
|
+
No Docker running? behold still serves the source graph and tells you to start
|
|
172
|
+
it, rather than dying on you.
|
|
170
173
|
|
|
171
174
|
**Real AWS.** The same project's **▶ Deploy** button starts its `ApplyOp` against a
|
|
172
175
|
real account: `npm run dev -- serve example-writes --env prod` (needs AWS
|
|
173
|
-
credentials). What the
|
|
176
|
+
credentials). What the Deploy tab offers depends on what the project committed: a
|
|
174
177
|
committed `ApplyOp` gets the **▶ Deploy (<op>)** button (plus **Approve** when
|
|
175
178
|
gated); a project with only components gets **▶ Deploy…**, which opens the dial's
|
|
176
179
|
component picker; **Adopt** appears per foreign node (`ReconcileOp`); every other
|
|
177
180
|
Op runs from ⌘K (**Run: \<name\>**). Full walkthrough:
|
|
178
181
|
**[example-writes/README.md](example-writes/README.md)**.
|
|
179
182
|
|
|
180
|
-
## The k3d demo
|
|
183
|
+
## The k3d demo: `behold demo k8s`
|
|
181
184
|
|
|
182
|
-
`npm run demo:k8s` is the k8s analogue
|
|
185
|
+
`behold demo k8s` (or `npm run demo:k8s` from a checkout) is the k8s analogue
|
|
186
|
+
of the Floci demo above: it brings up a
|
|
183
187
|
local, single-node [k3d](https://k3d.io) cluster (Docker only, no cloud
|
|
184
|
-
account), then serves the bundled `example-k8s`
|
|
185
|
-
Service
|
|
188
|
+
account), then serves the bundled `example-k8s` (an nginx Deployment and
|
|
189
|
+
Service) with `--local`. Same mechanism, same shape: declared-not-deployed
|
|
186
190
|
(blue) → click **Run** on `k3d-apply` → managed (green), server-side applied
|
|
187
191
|
with chant's own field manager. Ctrl-C tears the cluster back down.
|
|
188
192
|
|
|
@@ -203,48 +207,32 @@ including the exact commands and what each state looks like over the API:
|
|
|
203
207
|
- The vizes only read (`chant graph`, snapshots, Temporal history).
|
|
204
208
|
- Actions don't mutate directly. Sync starts your `ApplyOp`; Adopt starts your
|
|
205
209
|
`ReconcileOp` (opens a PR a human merges). behold *triggers* Ops you committed,
|
|
206
|
-
running on your executor
|
|
210
|
+
running on your executor. It holds no apply creds.
|
|
207
211
|
- Two write gestures, both human-confirmed: **Apply** (gate signal) and **Open PR**
|
|
208
212
|
(merge). Authority stays in your source and your worker, never in behold.
|
|
209
|
-
- The first product is **read-only, full stop**: the mixed graph + drift + source
|
|
210
|
-
deep-links. Writes are a later, opt-in layer.
|
|
211
213
|
|
|
212
214
|
## Why a Node service (not an edge function)
|
|
213
215
|
|
|
214
216
|
The live path (`chant graph --live --overlay`, `chant lifecycle plan`) shells
|
|
215
217
|
`kubectl`/`aws`/`az`/the Temporal client and holds cloud creds. That needs a real
|
|
216
|
-
process, so behold is a Node service you run where your creds live
|
|
217
|
-
`argocd-server`, not like a hosted SaaS. Read-only means it only needs **read**
|
|
218
|
+
process, so behold is a Node service you run where your creds live. Read-only means it only needs **read**
|
|
218
219
|
roles (describe/list), so it's least-privilege to run.
|
|
219
220
|
|
|
220
221
|
## Agent-drivable, on chant's MCP
|
|
221
222
|
|
|
222
223
|
behold is drivable by an agent, and leans on chant's MCP rather than reinventing it:
|
|
223
224
|
|
|
224
|
-
- **Reads
|
|
225
|
+
- **Reads.** `lifecycle-diff`, `lifecycle-snapshot`, plus behold's own read API
|
|
225
226
|
(the overlay graph as JSON, blast radius, frame diffs).
|
|
226
|
-
- **Delegated actions
|
|
227
|
+
- **Delegated actions.** The writes *are* chant MCP Op tools: `op-run` starts an
|
|
227
228
|
`ApplyOp`/`ReconcileOp`, `op-signal` approves a gate, `op-status`/`op-report`
|
|
228
229
|
watch it. So an agent "syncing prod" is `op-run prod-apply` then
|
|
229
|
-
`op-signal prod-apply approve-apply
|
|
230
|
+
`op-signal prod-apply approve-apply`: gated, durable, no creds in behold.
|
|
230
231
|
|
|
231
232
|
behold's value over raw MCP is the live spatial + temporal view and the coupling
|
|
232
233
|
between them; the underlying capabilities are chant's, exposed the same way to a
|
|
233
234
|
human and an agent. See [AGENTS.md](./AGENTS.md).
|
|
234
235
|
|
|
235
|
-
## Status
|
|
236
|
-
|
|
237
|
-
Bootstrap. `behold serve <project>` renders the **source** mixed-substrate graph
|
|
238
|
-
(cross-lexicon edges are real today — verified in chant core) in a browser, with
|
|
239
|
-
click-to-inspect and source deep-links.
|
|
240
|
-
|
|
241
|
-
Both read paths work: `/api/graph` (the source mixed-substrate graph) and
|
|
242
|
-
`/api/overlay` (the source-anchored **live drift** graph — declared topology kept,
|
|
243
|
-
nodes coloured managed/foreign/pending; needs chant ≥ 0.18.1 and cloud creds).
|
|
244
|
-
|
|
245
|
-
Not yet: the deployment-lanes timeline and the delegated actions. See the concept
|
|
246
|
-
notes and the issue set below.
|
|
247
|
-
|
|
248
236
|
## Usage
|
|
249
237
|
|
|
250
238
|
```sh
|
|
@@ -259,22 +247,72 @@ Then open http://localhost:4600. With `--env`, the SPA shows the live overlay;
|
|
|
259
247
|
without it, the source graph.
|
|
260
248
|
|
|
261
249
|
**Live updates.** The server watches the served project's source and pushes a
|
|
262
|
-
refresh over SSE (`/api/events`) when a `.ts` file changes
|
|
263
|
-
graph updates
|
|
250
|
+
refresh over SSE (`/api/events`) when a `.ts` file changes, so you edit your
|
|
251
|
+
infra and the graph updates with no reload. Add `--poll <secs>` (with `--env`) to also re-query live
|
|
264
252
|
drift on an interval and push updates when a node's status changes:
|
|
265
253
|
|
|
266
254
|
```sh
|
|
267
|
-
behold serve ./infra --env prod --poll 30 #
|
|
255
|
+
behold serve ./infra --env prod --poll 30 # wait 30s between completed drift sweeps
|
|
268
256
|
```
|
|
269
257
|
|
|
258
|
+
**Read budget.** HTTP observations, background polling, and frame captures share
|
|
259
|
+
one Chant subprocess budget: two reads at once by default (one on a one-CPU
|
|
260
|
+
host). `BEHOLD_ESTATE_CONCURRENCY` overrides that process-wide limit. Up to 64
|
|
261
|
+
distinct reads can wait; excess reads fail explicitly rather than growing an
|
|
262
|
+
unbounded queue. Identical in-flight reads share work only when the project,
|
|
263
|
+
resolved Chant, source stamp, argv, and effective environment match. Source
|
|
264
|
+
watcher invalidation also separates new requests from work begun before an edit.
|
|
265
|
+
|
|
266
|
+
**What is cached, and what drops it.** A member's source IR is cached under its
|
|
267
|
+
source stamp, resolved chant and graph options (`src/member-ir.ts`), because
|
|
268
|
+
~800ms of every `chant` invocation is module load that has nothing to do with the
|
|
269
|
+
member's content. A member's completed live overlay document is cached too
|
|
270
|
+
(`src/overlay-ir.ts`), so that a re-render such as collapsing a box is a
|
|
271
|
+
re-render rather than a second pass over the account. That second cache is the
|
|
272
|
+
stated exception to "a live read is never cached", and its header names
|
|
273
|
+
everything that drops an entry: `POST /api/refresh` ("Re-check live"),
|
|
274
|
+
`GET /api/overlay?plan=1` ("Re-check live with plan"), the capture that ends an
|
|
275
|
+
Op run, and the member's source moving. What it cannot notice is a change made to
|
|
276
|
+
the account by something that is not behold, which is what the re-check rows are
|
|
277
|
+
for.
|
|
278
|
+
|
|
279
|
+
A running read has a 180-second deadline, configurable with
|
|
280
|
+
`BEHOLD_READ_TIMEOUT_MS`. A disconnected GET releases its subscription; when no
|
|
281
|
+
callers remain, the read is canceled. On Unix, cancellation stops the whole
|
|
282
|
+
npm/tsx/Node process group, escalating from TERM to KILL after one second. On
|
|
283
|
+
Windows only the direct child is terminated. Delegated writes retain their
|
|
284
|
+
existing lifecycle and are never deduplicated as reads.
|
|
285
|
+
|
|
286
|
+
The browser runs one refresh at a time and collapses intervening notifications
|
|
287
|
+
into one follow-up. It no longer starts additional reads at 3/8/15-second offsets.
|
|
288
|
+
Polling uses the same estate namespace bindings as the HTTP overlay and reuses
|
|
289
|
+
the primary member's observation for lanes capture. Slow sweeps extend the poll
|
|
290
|
+
period; they do not overlap the next sweep. These bounds control duplicate work;
|
|
291
|
+
they do not eliminate Chant's underlying live discovery cost.
|
|
292
|
+
|
|
293
|
+
**What a read cost.** Every scheduled chant read is filed with its running time,
|
|
294
|
+
its outcome and which side of the source/live split it sits on.
|
|
295
|
+
`GET /api/doctor` serves the same report `behold doctor` prints with that ledger
|
|
296
|
+
on it; `GET /api/doctor?reads=1` is the cheap half: counters, the per-member
|
|
297
|
+
samples, `inFlight` (running and queued), and `unmeasured`, the count of served
|
|
298
|
+
members the ledger is blind to because they spawn their own binary rather than
|
|
299
|
+
chant. The loading scrim shows the slowest recent member, since an estate read
|
|
300
|
+
finishes when its last member does.
|
|
301
|
+
|
|
302
|
+
**The picture can arrive in pieces.** `GET /api/overlay?progressive=1` answers at
|
|
303
|
+
once with the estate composed from each member's source, every card marked
|
|
304
|
+
pending, then streams one SSE frame per member as its own live read lands.
|
|
305
|
+
Opt-in and never the default: the blocking `/api/overlay` is what agents read and
|
|
306
|
+
what `behold export` captures, and it is unchanged.
|
|
307
|
+
|
|
270
308
|
behold shells the **project's own** chant (resolved from the project's
|
|
271
|
-
`node_modules` first), so the project decides the chant version
|
|
272
|
-
`@intentius/chant ^0.18.1` or later for the live overlay
|
|
273
|
-
nothing before that fix
|
|
309
|
+
`node_modules` first), so the project decides the chant version. Pin it to
|
|
310
|
+
`@intentius/chant ^0.18.1` or later for the live overlay; `graph --live` observed
|
|
311
|
+
nothing before that fix.
|
|
274
312
|
|
|
275
313
|
## Terraform carve-out: `behold carve <report.json>`
|
|
276
314
|
|
|
277
|
-
`chant carve advise` ranks a Terraform estate by **peelability
|
|
315
|
+
`chant carve advise` ranks a Terraform estate by **peelability**: how cleanly
|
|
278
316
|
each resource could be carved into native chant source. `behold carve` draws
|
|
279
317
|
that ranking: one card per resource, three panels (carve now / boundary work /
|
|
280
318
|
leave in Terraform) on the same `attrs._status` drift palette every other view
|
|
@@ -288,9 +326,13 @@ curl localhost:4600/api/carve # the raw report, for agents
|
|
|
288
326
|
|
|
289
327
|
behold parses no HCL and needs no Terraform tooling: the report **is** the
|
|
290
328
|
contract. A file that isn't a peelability report is refused with a structured
|
|
291
|
-
`{error, code: "carve-report", remedy}
|
|
292
|
-
never a blank graph. See
|
|
293
|
-
|
|
329
|
+
`{error, code: "carve-report", remedy}`, in the terminal and from the routes,
|
|
330
|
+
never a blank graph. See [the carve lens](https://intentius.io/behold/using/carve/) and
|
|
331
|
+
[the Terraform estate page](https://intentius.io/behold/using/terraform/) for the
|
|
332
|
+
other half of that lane: Terraform is a member
|
|
333
|
+
kind now (`MEMBER_KINDS = ["chant", "choudoufu", "terraform"]`), so an estate of
|
|
334
|
+
`.tf` files draws beside a chant project, and carve state is read from chant's
|
|
335
|
+
own `<address>.carve.json` manifests.
|
|
294
336
|
|
|
295
337
|
### The walkthrough: `behold demo carve`
|
|
296
338
|
|
|
@@ -298,41 +340,44 @@ never a blank graph. See `docs/using/carve` and issue #230 for the roadmap
|
|
|
298
340
|
npx @intentius/behold demo carve # no Docker, no cloud, no terraform binary
|
|
299
341
|
```
|
|
300
342
|
|
|
301
|
-
Copies a half-migrated estate
|
|
302
|
-
both describing the same AWS account
|
|
343
|
+
Copies a half-migrated estate (a small chant project beside a Terraform one,
|
|
344
|
+
both describing the same AWS account), installs the chant it will shell, runs
|
|
303
345
|
`chant carve advise` over the copy, and opens the banded graph with a six-step
|
|
304
346
|
stepper on the panel's **Carve** tab:
|
|
305
347
|
|
|
306
|
-
1. **Advise
|
|
307
|
-
2. **Pick
|
|
348
|
+
1. **Advise.** The bands, with what each one means.
|
|
349
|
+
2. **Pick.** Click a green card. The inspect pane shows the score arithmetic;
|
|
308
350
|
the step names the boundary the cut crosses.
|
|
309
|
-
3. **Emit
|
|
351
|
+
3. **Emit.** Runs `chant carve emit --state --select <addr>` into
|
|
310
352
|
`app/carveout/` in the copy, then shows the emitted chant source and the
|
|
311
353
|
`chant lint` result.
|
|
312
|
-
4. **Bridge
|
|
354
|
+
4. **Bridge.** Runs `chant carve bridge` (never `--apply-rewrites`) and renders
|
|
313
355
|
the proposed data source, the rewired survivors and the patch.
|
|
314
|
-
5. **Handoff
|
|
356
|
+
5. **Handoff.** The runbook's commands with copy buttons, and **not** a button:
|
|
315
357
|
`terraform state rm` and `terraform apply` change who owns a live resource,
|
|
316
358
|
so they stay yours to run. The panel says so.
|
|
317
|
-
6. **Done
|
|
359
|
+
6. **Done.** The card is marked chant-owned at the observe position;
|
|
318
360
|
`terraform import` reverses all of it.
|
|
319
361
|
|
|
320
362
|
The Emit step reports `chant lint`, not `chant build`: `build` fails on the
|
|
321
|
-
emitted bucket (scored 84 by the advisor) on one rule
|
|
322
|
-
bucket policy the source Terraform never declared
|
|
323
|
-
reason.
|
|
363
|
+
emitted bucket (scored 84 by the advisor) on one rule: WAW042, a TLS-deny
|
|
364
|
+
bucket policy the source Terraform never declared. The panel links the
|
|
365
|
+
reason. [example-carve/README.md](example-carve/README.md) has the estate's full story, the band table,
|
|
324
366
|
and the offline/`--live` split.
|
|
325
367
|
|
|
326
|
-
behold writes only into the demo copy it made
|
|
327
|
-
else. Your Terraform is never edited; see AGENTS.md, "Invariant".
|
|
368
|
+
behold writes only into `app/carveout/` inside the demo copy it made, and
|
|
369
|
+
nothing else. Your Terraform is never edited; see AGENTS.md, "Invariant".
|
|
328
370
|
|
|
329
|
-
## Configuration
|
|
371
|
+
## Configuration: `.behold.json`
|
|
330
372
|
|
|
331
373
|
An optional `.behold.json` in the served project's root is **behold's own**
|
|
332
|
-
config
|
|
333
|
-
tier picker) don't leak into chant's.
|
|
374
|
+
config, kept separate from `chant.config.ts` so behold's concerns (like the
|
|
375
|
+
tier picker) don't leak into chant's. Three keys: `tiers` below, `executor`
|
|
376
|
+
(which forge deploys an environment), and `members`, spelled `[{ "dir": "x", "kind":
|
|
377
|
+
"chant" | "choudoufu" | "terraform" }]`, how an estate root names what it
|
|
378
|
+
composes, fail-closed on a kind behold has no reader for. First, the
|
|
334
379
|
project's deploy-**tier** axis, a dimension orthogonal to `environment` (chant
|
|
335
|
-
has no native tier concept
|
|
380
|
+
has no native tier concept; it is entirely a project convention, e.g. Loom's
|
|
336
381
|
components branching on an env-conditioned `namingParams.tier`):
|
|
337
382
|
|
|
338
383
|
```json
|
|
@@ -344,17 +389,17 @@ components branching on an env-conditioned `namingParams.tier`):
|
|
|
344
389
|
}
|
|
345
390
|
```
|
|
346
391
|
|
|
347
|
-
- `envVar`
|
|
392
|
+
- `envVar` is the env var name the project's source branches on; behold sets it
|
|
348
393
|
for the chant shell-out whenever a tier is picked (`?tier=` → this var, never
|
|
349
394
|
a chant CLI flag).
|
|
350
|
-
- `values`
|
|
395
|
+
- `values` is the tier picker's options.
|
|
351
396
|
|
|
352
397
|
**No `.behold.json` (or no `tiers` key) → no tier axis:** the picker doesn't
|
|
353
|
-
render and the graph loads with no tier selected
|
|
354
|
-
that doesn't opt in. There's no other tier config surface (not
|
|
398
|
+
render and the graph loads with no tier selected, which is the default for any
|
|
399
|
+
project that doesn't opt in. There's no other tier config surface (not
|
|
355
400
|
`chant.config.ts`, not an env var behold guesses the name of).
|
|
356
401
|
|
|
357
|
-
### Deploy executors
|
|
402
|
+
### Deploy executors: `executor`
|
|
358
403
|
|
|
359
404
|
The same file can designate which forge deploys an environment (#165):
|
|
360
405
|
|
|
@@ -372,7 +417,7 @@ tell them apart. For a designated environment the Deploy button dispatches that
|
|
|
372
417
|
workflow through your own `gh` login and follows the run on the dial; a local
|
|
373
418
|
apply, a committed ApplyOp for that environment, and auto-sync are all refused
|
|
374
419
|
(`409 executor-forge`), and rollback is withheld. Any approval the workflow's
|
|
375
|
-
GitHub environment requires is granted on GitHub
|
|
420
|
+
GitHub environment requires is granted on GitHub by your identity there. The
|
|
376
421
|
dial links the run's page and offers nothing else, because behold holds no
|
|
377
422
|
identity that could clear a forge gate. A designation behold cannot honour (a
|
|
378
423
|
typo'd forge, a missing workflow, one without `workflow_dispatch`) disables
|
|
@@ -381,15 +426,15 @@ it on your machine. The workflow is read from the `.github/workflows` of the
|
|
|
381
426
|
repository the project belongs to, so a project that is one directory of a
|
|
382
427
|
monorepo works as-is.
|
|
383
428
|
|
|
384
|
-
A dispatched run's id is kept under `~/.behold/ci-runs
|
|
385
|
-
state
|
|
429
|
+
A dispatched run's id is kept under `~/.behold/ci-runs/`, the operator's own
|
|
430
|
+
state outside the project, so a behold restarted mid-deploy re-adopts the run
|
|
386
431
|
and keeps following it. A follow whose stream dies is reported as `lost`, never
|
|
387
432
|
as a verdict; `just e2e-ci-github` proves the whole contract against GitHub.
|
|
388
433
|
|
|
389
|
-
### The hand-layout sidecar
|
|
434
|
+
### The hand-layout sidecar: `.behold/layout.json`
|
|
390
435
|
|
|
391
436
|
dagre places your nodes; you can move them. Drag a card, resize a containment
|
|
392
|
-
box, and the offsets are remembered per project
|
|
437
|
+
box, and the offsets are remembered per project and lens: in `localStorage`
|
|
393
438
|
first, and (when the served project is writable) in a `.behold/layout.json`
|
|
394
439
|
sidecar beside it, so a layout is shareable, reviewable in a diff, and honoured
|
|
395
440
|
by `behold export`:
|
|
@@ -399,14 +444,14 @@ by `behold export`:
|
|
|
399
444
|
```
|
|
400
445
|
|
|
401
446
|
This is the **only** file behold writes inside a served project. It stores
|
|
402
|
-
deltas
|
|
403
|
-
on top of it
|
|
447
|
+
deltas rather than absolute positions, so the graph stays chant's and your
|
|
448
|
+
layout sits on top of it. A delta whose node has left the estate is dropped.
|
|
404
449
|
`POST /api/layout` refuses politely in preview mode, during a static-export
|
|
405
450
|
capture, on a read-only directory, and above its size caps. `↺ layout` in the
|
|
406
451
|
graph clears the current lens on both tiers.
|
|
407
452
|
|
|
408
453
|
**Gitignore it.** `.behold.json` (above) is config and belongs in the repo;
|
|
409
|
-
`.behold/` is per-user state
|
|
454
|
+
`.behold/` is per-user state, one person's arrangement of the picture, so add
|
|
410
455
|
it to the served project's `.gitignore` unless you actually want to share and
|
|
411
456
|
review a layout:
|
|
412
457
|
|
|
@@ -416,30 +461,46 @@ review a layout:
|
|
|
416
461
|
|
|
417
462
|
## Layout
|
|
418
463
|
|
|
464
|
+
A shape rather than a manifest. `src/` is ~85 modules with one concern each, and
|
|
465
|
+
[AGENTS.md](./AGENTS.md) is the map that stays current.
|
|
466
|
+
|
|
419
467
|
```
|
|
420
468
|
src/
|
|
421
|
-
cli.ts
|
|
422
|
-
server.ts
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
469
|
+
cli.ts the verbs: serve, preview, demo, doctor, export, carve
|
|
470
|
+
server.ts the Hono API + static SPA, every route read and delegated
|
|
471
|
+
|
|
472
|
+
the read path
|
|
473
|
+
chant.ts shells the PROJECT's own chant; every read is scheduled
|
|
474
|
+
read-scheduler.ts one process-wide budget, in-flight sharing, cancellation
|
|
475
|
+
read-stats.ts what each read cost (/api/doctor)
|
|
476
|
+
member-ir.ts per-member source cache; overlay-ir.ts, the live one
|
|
477
|
+
estate.ts N members composed into one graph (pinhole's composeStacks)
|
|
478
|
+
member-kind.ts what a member can be: chant, choudoufu, terraform
|
|
479
|
+
|
|
480
|
+
the lenses (pure IR -> IR, no subprocess)
|
|
481
|
+
logical*.ts one topology projection per substrate
|
|
482
|
+
ops-lens.ts declared Ops as a phase track; run-playhead.ts paints a run
|
|
483
|
+
carve-lens.ts a peelability report; stack-order.ts, chant's apply order
|
|
484
|
+
terraform-lens.ts collapse-lens.ts, edgeless.ts
|
|
485
|
+
|
|
486
|
+
render.ts pinhole painter, IR -> SVG, one pack per lexicon
|
|
487
|
+
web/ the SPA, unbundled ES modules (app.js + ~20 siblings)
|
|
488
|
+
docs/ the published site (Astro/Starlight)
|
|
489
|
+
demos.json the shipped demo catalog; workbench.json, this checkout's
|
|
490
|
+
example-*/ nine bundled projects the demos and e2e serve
|
|
491
|
+
e2e/ twelve acceptance runs, one per substrate lane
|
|
431
492
|
```
|
|
432
493
|
|
|
433
494
|
## The painter
|
|
434
495
|
|
|
435
496
|
behold reuses [pinhole](https://github.com/INTENTIUS/pinhole)'s SVG painter as a
|
|
436
|
-
library
|
|
437
|
-
speaks the overlay vocabulary managed/foreign/pending
|
|
497
|
+
library: a mature renderer with themes, icons, and `_status` drift colouring
|
|
498
|
+
that already speaks the overlay vocabulary managed/foreign/pending. The server lays the IR out
|
|
438
499
|
and paints it with `layoutIr` + `renderSvg` (`src/render.ts`); the SPA inlines the
|
|
439
500
|
SVG and wires click-inspect by `data-node-id` against the IR. pinhole's layout is
|
|
440
|
-
dagre
|
|
501
|
+
dagre, which is pure JS with no native dependency.
|
|
441
502
|
|
|
442
|
-
Where an official mark exists, a node paints it instead of a generic glyph
|
|
503
|
+
Where an official mark exists, a node paints it instead of a generic glyph. A
|
|
443
504
|
Deployment gets the Kubernetes wheel-and-helm heptagon, a Kustomization the
|
|
444
505
|
Flux mark, a `Helm::Release` the Helm wheel. The corpus is vendored under
|
|
445
506
|
`web/icons/` (30 kubernetes/community SVGs, 3 cncf/artwork marks for
|
|
@@ -449,8 +510,8 @@ keyword heuristic rather than a wrong picture.
|
|
|
449
510
|
|
|
450
511
|
dagre's layout is a good first draft, not a final one: drag a card to move
|
|
451
512
|
it, grab a containment box's corner to resize it, and both survive a reload.
|
|
452
|
-
What persists is a delta
|
|
453
|
-
an absolute position
|
|
513
|
+
What persists is a delta (`{dx,dy}` for a card, `{dw,dh}` for a box) rather
|
|
514
|
+
than an absolute position, keyed by `behold.layout.<project>.<lens>`
|
|
454
515
|
(`web/layout-store.js`), so the graph stays chant's and the arrangement on
|
|
455
516
|
top of it is yours. `↺ layout` sits beside `⤢ fit` and shows up only once
|
|
456
517
|
something on the current lens is hand-placed.
|
|
@@ -465,11 +526,11 @@ the inspect pane's declared attributes, observed live state, drift pairs and
|
|
|
465
526
|
field ownership, the op log's JSON lines, and the payload behind an `/api`
|
|
466
527
|
error card.
|
|
467
528
|
|
|
468
|
-
Type splits by purpose:
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
re-derives the whole chrome
|
|
529
|
+
Type splits by purpose: a system mono stack carries node ids, ARNs and
|
|
530
|
+
statuses, and sans carries labels only. Colour comes from 552 Ghostty terminal palettes run
|
|
531
|
+
through an OKLCH-derived token pipeline (`web/theme.js`, the palettes in
|
|
532
|
+
`web/themes.js`), so a theme switch
|
|
533
|
+
re-derives the whole chrome rather than only the graph. A node whose drift
|
|
473
534
|
status just changed pulses once in the colour it became, off under
|
|
474
535
|
`prefers-reduced-motion`.
|
|
475
536
|
|
|
@@ -489,8 +550,8 @@ One server, one SPA: passing an env turns on the live overlay (`/api/overlay`),
|
|
|
489
550
|
omitting it shows the source graph (`/api/graph`). `serve` runs via `tsx` (no build
|
|
490
551
|
step); for the built binary, `just build` then `./bin/behold.js serve <project>`.
|
|
491
552
|
|
|
492
|
-
**Which chant runs.** behold does not bundle chant
|
|
493
|
-
resolved from the served project's `node_modules
|
|
553
|
+
**Which chant runs.** behold does not bundle chant. It *shells* the chant binary
|
|
554
|
+
resolved from the served project's `node_modules`, falling back to its own dep.
|
|
494
555
|
So local testing means installing chant into a project, not into behold. The bundled
|
|
495
556
|
`example/` does exactly that; point `serve` at any real chant project the same way.
|
|
496
557
|
|
|
@@ -505,9 +566,9 @@ behold, serves the example, and asserts the read-only API against a live server.
|
|
|
505
566
|
auto-detects AWS credentials:
|
|
506
567
|
|
|
507
568
|
- **no creds** → asserts `/api/graph` (the source mixed-substrate graph, offline).
|
|
508
|
-
- **AWS creds** → asserts `/api/overlay
|
|
509
|
-
CloudFormation and checks every node carries a drift status
|
|
510
|
-
nothing is deployed is a valid pass, since the point is the live path
|
|
569
|
+
- **AWS creds** → asserts `/api/overlay`, the source-anchored live overlay: it queries
|
|
570
|
+
CloudFormation and checks every node carries a drift status. All `pending` when
|
|
571
|
+
nothing is deployed is a valid pass, since the point is the live path.
|
|
511
572
|
|
|
512
573
|
It's hermetic apart from the chant install and (optionally) the cloud read; the
|
|
513
574
|
server is torn down on exit. `BEHOLD_E2E_PORT` overrides the port.
|
|
@@ -515,16 +576,19 @@ server is torn down on exit. `BEHOLD_E2E_PORT` overrides the port.
|
|
|
515
576
|
## Unit tests
|
|
516
577
|
|
|
517
578
|
```sh
|
|
518
|
-
npm test # vitest
|
|
579
|
+
npm test # vitest, 106 files, including in-process route tests against
|
|
580
|
+
# the real Hono app. No cloud, no cluster.
|
|
519
581
|
```
|
|
520
582
|
|
|
521
|
-
##
|
|
583
|
+
## Upstream
|
|
584
|
+
|
|
585
|
+
behold is a viewer over a compiler, so some of what it cannot do is not its to
|
|
586
|
+
fix. The open asks:
|
|
587
|
+
|
|
588
|
+
- chant **#822**, diff two historical snapshots, which is what a timeline of
|
|
589
|
+
more than the frames behold captures itself would need.
|
|
590
|
+
- chant **#2481**, `chant` costs ~800ms per invocation before it does any work,
|
|
591
|
+
and behold shells it once per member per read.
|
|
592
|
+
- pinhole **#79/#80/#81**, morph-over-time and first-class drift rendering.
|
|
522
593
|
|
|
523
|
-
|
|
524
|
-
status) — done, shipped chant 0.18.31; behold adopted it in M4 (see `src/overlay.ts`).
|
|
525
|
-
- chant **#822** — diff two historical snapshots (feeds the timeline).
|
|
526
|
-
- chant **#513** — compose separate stacks into one IR — done; behold's `composeEstate`
|
|
527
|
-
(`src/estate.ts`, #31) consumes pinhole's `composeStacks` built on it.
|
|
528
|
-
- pinhole **#82** — ship the painter as a library (done; behold consumes it).
|
|
529
|
-
- pinhole **#79/#80/#81** — drive `--live`/`--overlay`, first-class drift rendering, morph-over-time.
|
|
530
|
-
- Concept notes: `~/Documents/research/chant-live-control-plane.md`.
|
|
594
|
+
behold's own work is on the [issue tracker](https://github.com/INTENTIUS/behold/issues).
|