@intentius/behold 0.11.0 → 0.13.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
@@ -53,7 +53,20 @@ preview-locked, and one load runs at a time (409 otherwise).
53
53
  `?env=`, `/api/overlay` is the live entity overlay; `/api/diff?env=` slices
54
54
  per-node observed state, drift and field ownership; `/api/reconcile?env=`
55
55
  summarizes the pending change set; `/api/substrates` reports substrate
56
- readiness; `/api/events` (SSE) pushes `changed`/`op`/`apply`/`pr`.
56
+ readiness; `/api/events` (SSE) pushes `changed`/`op`/`apply`/`run`/`pr`.
57
+ `?ops=1` is the ops lens — the project's declared Ops as a phase track, read
58
+ from each emitted `dist/ops/<name>/op.json`, with the current run painted
59
+ over it (`meta.run`, `meta.gate`); `/api/ops/<name>/status` reads that Op's
60
+ durable run status and pending gate (`chant run status --temporal`). A
61
+ `ConvergeOp` also gets one card per rule from its `convergeTick` step's
62
+ `args.rules` (`attrs._step: "rule"`), carrying `when` (chant's JSON predicate,
63
+ rendered as the condition it states), `then`, and the `why` chant requires of
64
+ every rule, verbatim. `then: run(<op>)` is an edge to that Op's first step
65
+ when the Op is in the rendered set, and `attrs.dangling` when it isn't.
66
+ `meta.operator` on that same lens names the ConvergeOps the project declares
67
+ (chant's own `searchAttributes.Converge === "true"`, read from op.json — no
68
+ subprocess), and `/api/operator/status` fills the strip in from
69
+ `chant operator status --json`.
57
70
  2. **focus** — narrow with chant graph options as query params: `?detail=0..3`,
58
71
  `?components=1`, `?logical=1`, `?lens=blast:<id>&down=1`, `?lens=lexicon:aws`,
59
72
  `?env=`, `?stack=`, `?tier=`, `?target=`.
@@ -74,6 +87,32 @@ To move a resource: confirm its band on `/api/carve`, then run chant's own
74
87
  applying the generated survivor rewrites stay a human gate — behold has no
75
88
  endpoint that writes Terraform, and adding one would break the invariant below.
76
89
 
90
+ ### Carve state, from chant's manifests (#230 M3)
91
+
92
+ chant ≥ 0.52.2 writes `<address>.carve.json` into `carve emit --output`; `carve
93
+ bridge` and `carve apply` add their own records to the same file. behold reads
94
+ those and never writes one, so the progression is on disk rather than in a
95
+ session.
96
+
97
+ `GET /api/project`'s `carve.state` publishes it: `{manifests, progress:
98
+ {applied, bridged, emitted, inFlight, total, label, detail}, states[], apply:
99
+ {human, note}}`. Each entry carries `target`, `stage`
100
+ (`emitted`/`bridged`/`applied`), `graduated`, `note` and a retypeable
101
+ `applyCommand`. The same field appears on an ordinary `behold serve` whose
102
+ project directory carries a carveout — no report and no demo needed — and is
103
+ absent entirely when nothing has been carved.
104
+
105
+ In the graph, an `applied` address draws inside the chant member box (keeping
106
+ its Terraform address as its node id) instead of in a band; `emitted` and
107
+ `bridged` stay banded with `attrs._status: "accent"` and the stage in
108
+ `attrs.carve`. Only `applied` means ownership moved.
109
+
110
+ **There is no `/api/carve/apply`.** `chant carve apply` graduates ownership;
111
+ behold renders what the manifest records and echoes the command. Do not add the
112
+ endpoint — src/carve-manifest.ts `APPLY_IS_HUMAN` is the statement of it, and
113
+ src/carve-actions.ts and src/server.ts both carry the refusal where the route
114
+ would go.
115
+
77
116
  ### The walkthrough (`behold demo carve`, #254)
78
117
 
79
118
  `behold demo carve` copies a bundled half-migrated estate (a chant project
@@ -117,6 +156,42 @@ behold does not apply. To change the estate:
117
156
  Every mutation is a gated, durable Temporal workflow with a human-confirmable gate
118
157
  and saga rollback. There is no behold endpoint that mutates the cloud.
119
158
 
159
+ A run behold triggered is asked for structured per-step records (chant#1676:
160
+ `--progress-json` on the durable path, `--json` on the local one), so the ops
161
+ lens paints the run over the declared track and a pending gate renders as a card
162
+ with an Approve button. That button is `op-signal` — the same delegated write —
163
+ and nothing else about the run is behold's to decide: a stream that dies leaves
164
+ the playhead at the last settled step and says so, and a gate paints as pending
165
+ only when chant's `gateState` query named it.
166
+
167
+ ## The operating loop (a strip, and a gate that is not the run gate)
168
+
169
+ A project that declares a `ConvergeOp` gets an operator strip on the ops lens:
170
+ per loop, the last tick's own log line (verbatim, with its instant), the lease,
171
+ and how many gates are pending. **One** tick — `chant operator status` keeps only
172
+ `records.at(-1)` and no CLI exposes the history (chant#2029), so this is a strip
173
+ and never a timeline; the statusbar says so rather than leaving you to assume.
174
+
175
+ Two gate cards exist and they are **different acts**:
176
+
177
+ - The **run gate** — `POST /api/ops/:name/signal/:gate` → `chant run signal`.
178
+ Releases the waiting Temporal workflow.
179
+ - The **converge gate** — `POST /api/operator/approve/:op/:gate` →
180
+ `chant approve <op> <gate>`. **Records a fact and unblocks nothing.** chant's
181
+ gate ledger says it outright: the local executor still refuses a gated op,
182
+ resolution or not. The next tick reads the fact. The card says this in its
183
+ body, and the toast after the click says "recorded; the next tick acts on it".
184
+
185
+ A pending gate fact carries no URL (chant#2028), so the card renders no link and
186
+ no placeholder for one. A gate someone already resolved simply leaves
187
+ `pendingGates` — the status read carries no `resolvedBy`/`when` — so behold shows
188
+ no resolved-by line rather than attributing an approval to nobody.
189
+
190
+ An `OperatorStack` (chant#1940) declares the loop as a k8s estate, so it already
191
+ appears in the entity graph. behold names it there — the Namespace as the loop's
192
+ home, each CronJob as a converge tick — off chant's own
193
+ `app.kubernetes.io/component: converge-tick` labels, never a naming convention.
194
+
120
195
  ## Invariant
121
196
 
122
197
  If a request would have behold write to a cloud or to source directly, it's wrong.