@intentius/behold 0.15.0 → 0.17.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 CHANGED
@@ -171,6 +171,49 @@ and nothing else about the run is behold's to decide: a stream that dies leaves
171
171
  the playhead at the last settled step and says so, and a gate paints as pending
172
172
  only when chant's `gateState` query named it.
173
173
 
174
+ ### The executor contract (#165, #61)
175
+
176
+ `.behold.json` may designate which forge deploys an environment:
177
+
178
+ ```json
179
+ { "executor": { "prod": { "forge": "github", "workflow": "deploy-prod.yml" } } }
180
+ ```
181
+
182
+ It designates the committed WORKFLOW, not just the forge: two environments'
183
+ generated pipelines carry identical job ids, so a picker cannot tell them
184
+ apart, and a contract that names prod must not sit on a guess. For a
185
+ designated env, `POST /api/apply` and a committed ApplyOp's `/api/ops/:name/run`
186
+ answer `409 {code: "executor-forge"}`, auto-sync declines out loud, and the
187
+ only deploy is `POST /api/ci/dispatch?env=<env>`, which runs the designated
188
+ workflow through the operator's own `gh` and follows it on the dial. Any
189
+ approval the workflow's environment requires is granted on the forge by a
190
+ GitHub identity behold does not have; the dial links the run's page and offers
191
+ nothing else. Fail-closed: a designation behold cannot honour (a typo'd forge,
192
+ a missing or non-dispatchable workflow, a forge with no trigger here) disables
193
+ Deploy for that env with the reason on `/api/project`'s `executor` block, and
194
+ never falls back to running it here. `/api/project` reports `{forge, workflow,
195
+ ok, reason?}` per designated env.
196
+
197
+ Without a designation, dispatch picks the committed workflow named for the env
198
+ (`chant-components-<env>`, chant ≥ 0.54.0); on the older job-overlap match it
199
+ refuses a tie rather than letting directory order choose, and a workflow named
200
+ for another env never stands in. `just e2e-ci-github` proves the contract
201
+ against the real forge (example-ci + `.github/workflows/behold-e2e-dispatch.yml`),
202
+ including the `lost` verdict, which `BEHOLD_CI_FOLLOW_TIMEOUT_MS` and
203
+ `BEHOLD_CI_POLL_FAIL_BUDGET` let a run force on purpose; a lost run is the one
204
+ `POST /api/ci/readopt` re-follows.
205
+
206
+ A dispatched run is followed honestly (#165 §6, PR #350): only GitHub's own
207
+ `completed` + conclusion paints `ok`/`failed`; a poll-failure budget or the
208
+ follow deadline promotes the run to `lost` (chips frozen at last-observed,
209
+ the run possibly still live at its own page), never to a verdict. The adopted
210
+ run id is persisted as one JSON per project under `~/.behold/ci-runs/` — the
211
+ operator's machine-state, outside the project tree, so the Invariant below
212
+ still names the whole in-project write surface. `GET /api/ci/run` reads the
213
+ record; `POST /api/ci/readopt` (also attempted once at boot) re-follows an
214
+ unconcluded run by its saved id, so a restart mid-deploy leaves the run a
215
+ reader instead of nothing.
216
+
174
217
  ## The operating loop (a strip, a timeline, and a gate that is not the run gate)
175
218
 
176
219
  A project that declares a `ConvergeOp` gets an operator strip on the ops lens:
@@ -277,11 +320,13 @@ live here, with the enforceable ones enforced.
277
320
  it merges. A check that has merely settled is not a pass — read the lines.
278
321
  No `gh pr merge --auto`; merge one PR at a time, in order, and rebase the
279
322
  next onto what landed.
280
- - **The local gate** is `just check` (tsc, tests, build). `just test` keeps
281
- the reporter's full output in `vitest.log`, because #334's one-shot
282
- first-run failures kept losing the failing file's name — if the suite fails
283
- once and passes on rerun, the log from the first run is the evidence; attach
284
- it to #334.
323
+ - **The local gate** is `just check` (tsc, tests, build). Every vitest run
324
+ also writes `.vitest-last.json` (the json reporter, PR #351), and `just
325
+ test` keeps the reporter's full output in `vitest.log` — both gitignored —
326
+ because #334's one-shot first-run failures kept losing the failing file's
327
+ name. If the suite fails once and passes on rerun, those two files from the
328
+ first run are the evidence; attach them to #334 (`jq '.testResults[] |
329
+ select(.status != "passed") | .name' .vitest-last.json` names the file).
285
330
  - **Releasing** is `just release`, after the version-bump PR merged: it tags
286
331
  `behold-v<version>`, pushes the tag (which is what runs `release.yml`), and
287
332
  waits for npm to show the version. It refuses in every state where a tag
package/README.md CHANGED
@@ -8,8 +8,8 @@ drift — then act through delegated, gated Ops.
8
8
  gets you to a running graph without reading anything else first.
9
9
 
10
10
  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 on Helm —
12
- each substrate's own kind of truth, in one picture.
11
+ cloud drift on AWS, supply-chain drift on GitHub Actions, artifact presence and
12
+ render provenance on Helm — each substrate's own kind of truth, in one picture.
13
13
 
14
14
  ```
15
15
  chant source ──build/lint──▶ graph IR ──behold──▶ live graph + drift + (delegated) actions
@@ -354,6 +354,38 @@ render and the graph loads with no tier selected — the default for any project
354
354
  that doesn't opt in. There's no other tier config surface (not
355
355
  `chant.config.ts`, not an env var behold guesses the name of).
356
356
 
357
+ ### Deploy executors — `executor`
358
+
359
+ The same file can designate which forge deploys an environment (#165):
360
+
361
+ ```json
362
+ {
363
+ "executor": {
364
+ "prod": { "forge": "github", "workflow": "deploy-prod.yml" }
365
+ }
366
+ }
367
+ ```
368
+
369
+ It names the committed **workflow**, not just the forge, because two
370
+ environments' generated pipelines carry identical job ids and a picker cannot
371
+ tell them apart. For a designated environment the Deploy button dispatches that
372
+ workflow through your own `gh` login and follows the run on the dial; a local
373
+ apply, a committed ApplyOp for that environment, and auto-sync are all refused
374
+ (`409 executor-forge`), and rollback is withheld. Any approval the workflow's
375
+ GitHub environment requires is granted on GitHub, by your identity there — the
376
+ dial links the run's page and offers nothing else, because behold holds no
377
+ identity that could clear a forge gate. A designation behold cannot honour (a
378
+ typo'd forge, a missing workflow, one without `workflow_dispatch`) disables
379
+ Deploy for that environment with the reason, and never falls back to running
380
+ it on your machine. The workflow is read from the `.github/workflows` of the
381
+ repository the project belongs to, so a project that is one directory of a
382
+ monorepo works as-is.
383
+
384
+ A dispatched run's id is kept under `~/.behold/ci-runs/` — the operator's own
385
+ state, outside the project — so a behold restarted mid-deploy re-adopts the run
386
+ and keeps following it. A follow whose stream dies is reported as `lost`, never
387
+ as a verdict; `just e2e-ci-github` proves the whole contract against GitHub.
388
+
357
389
  ### The hand-layout sidecar — `.behold/layout.json`
358
390
 
359
391
  dagre places your nodes; you can move them. Drag a card, resize a containment