Skip to content

writes: delegated Adopt — foreign node → ReconcileOp → PR (#8) - #16

Merged
lex00 merged 1 commit into
mainfrom
feat/8-adopt
Jul 13, 2026
Merged

lex00 merged 1 commit into
mainfrom
feat/8-adopt

Conversation

@lex00

@lex00 lex00 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Closes #8.

The second write gesture — cloud→code. A foreign node (provisioned, not declared) is pulled back into typed source: Adopt triggers the project's ReconcileOp, which regenerates the source and opens a reviewable PR. behold never writes source directly — a human merges the PR.

Mechanism

  • src/adopt.ts (unit-tested):
    • isAdoptable(node) — foreign (_status === "foreign") and on a live-import lexicon.
    • extractPrUrl(line) — pull a GitHub/GitLab PR/MR URL out of an Op output line.
    • LIVE_IMPORT_LEXICONS = [aws, azure, gcp, k8s] — the substrates with a cloud→code path; the server's source of truth.
  • server: /api/ops now returns adoptLexicons; the chant run stream lifts a PR URL to a pr SSE event.
  • web: Adopt moves from the global bar to the inspect panel, rendered only on an adoptable node; a pr event links the opened PR in the now-line and pins a "PR opened →" link in the header.

Depends on chant #841 (PR INTENTIUS/chant#842)

ReconcileOp now surfaces the opened PR URL as an outcome ([outcome] PR=…), which extractPrUrl reads from the now-line. The example's ^0.18.4 caret picks up that fix once chant 0.18.5 publishes.

Verified

  • tsc --noEmit clean; 26 tests pass (+6 adopt: eligibility across managed/foreign/pending × live/non-live lexicons, and PR/MR URL extraction).

Acceptance (#8)

  • Adopt on a foreign node starts ReconcileOp and surfaces the resulting PR link
  • Never writes to source directly — a human merges the PR
  • Nodes without a live-import path show no Adopt

The cloud→code gesture. A *foreign* node (provisioned, not declared) can be
pulled back into typed source: Adopt triggers the project's ReconcileOp, which
opens a reviewable PR. behold never writes source — a human merges.

- src/adopt.ts: `isAdoptable(node)` (foreign + live-import lexicon) and
  `extractPrUrl(line)` (pull the opened PR/MR URL out of the now-line). The
  live-import lexicon set (aws/azure/gcp/k8s) is the server's source of truth.
- server: /api/ops returns `adoptLexicons`; the run stream lifts a PR URL to a
  `pr` SSE event (chant #841 surfaces it as a ReconcileOp outcome).
- web: Adopt moves from the global bar to the inspect panel, shown only on an
  adoptable node; a `pr` event links the opened PR in the now-line and pins it
  in the header.

tsc clean; 26 tests pass (+6 adopt).
@lex00
lex00 merged commit 9530ff6 into main Jul 13, 2026
1 check passed
@lex00
lex00 deleted the feat/8-adopt branch July 13, 2026 16:23
lex00 added a commit that referenced this pull request Jul 19, 2026
…e-around)

Two gaps around apply on a local emulator that can't idempotently re-apply
(github.com/lex00/floci/issues/16 — stack update re-creates fixed-name resources
→ 'already exists' → rollback):

- The apply picker was a bare name list — no way to tell which stacks are
  already applied. Annotate each option with live status (✓ deployed / ⚠ rolled
  back / ○ not deployed), from the same stack-status seam the graph paints.

- apply-all blindly re-applied everything (collides on Floci). Make it status-
  aware: no-op when all are healthy+in-sync; call out rolled-back stacks that a
  re-apply won't recover; and add a 'Reset' button on the Floci substrate pill →
  POST /api/local/reset (local-down + local-up) for a clean slate, then apply
  lands all-creates. The honest recovery, since the emulator can't re-apply.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
lex00 added a commit that referenced this pull request Jul 19, 2026
…after

local-up.sh boots the emulator AND deploys (its 'provision infra on Floci'
step), so 'reset' is a full clean reset+redeploy, not just an empty reboot.
Telling the user to apply afterward was wrong — that re-apply is exactly the
collision Floci #16 describes. Reword: reset reboots + redeploys clean, no apply
needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
lex00 added a commit that referenced this pull request Jul 19, 2026
A blind 'apply all' over a green deploy silently re-broke loom-db/frontend:
Floci re-creates their fixed-name resources on update ('... already exists') and
rolls the stack back (#16). Nothing stopped the re-apply — the header no-op only
covered the all-healthy case, and the picker path skipped it entirely.

Guard /api/apply server-side (covers every client path + stray calls): a
component with any live stack (good/warn/accent, not 'neutral') is already
deployed → 409 with a message pointing at Reset (the only clean redeploy on an
emulator that can't re-apply). ?force=1 overrides. Best-effort — a status-check
error falls through so a legitimate fresh apply isn't blocked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
lex00 added a commit that referenced this pull request Aug 7, 2026
…s, honest component status (#159)

Verified against a live kubemicrovm-ops (k3d + floci, tier prod-ha) and
fountain-ops. Five gaps, one theme: what the estate actually runs was
invisible or wrongly painted.

- Component status: an ABSENT deploy unit (`stack: {name}` — no status, no
  healthy) painted accent as if mid-deploy, outranking its own all-present
  rollup; five of kubemicrovm's seven components read "in flux" on a green
  estate. Only `healthy: false` decides now; absence falls through to the
  rollup.

- The cluster build root: the estates declare their k3d cluster as chant
  source in `cluster/`, deliberately outside sourceDir, so no view ever saw
  it — the k8s half rendered inside a synthetic `cluster <env>` box.
  `clusterRootGraphIr` reads that root the way the estates' own justfiles
  do; the merged K3d::Cluster is painted from `k3d cluster list` (running →
  good, declared-but-absent → accent) and becomes the cluster box.

- Cluster anchoring: `K3d::Cluster` joins MANAGED_CLUSTER_KINDS, and the
  bound kube context (k8s.profiles.<env>.context — the dead-code
  contextBindsCluster, finally wired, now k3d-aware) breaks the multi-
  cluster tie, so fountain-ops's two declared clusters anchor to the one
  the reads actually land on instead of declining.

- Helm releases: a deploy-step estate (helm-upgrade units, no declared
  Helm::Chart nodes) observed its releases and had nothing to hang them on
  — helm was invisible on every zoom. Observed releases matching no
  declared chart synthesize as Helm::Release nodes: component-owned ones
  (release names scraped from *.component.ts, the same convention ops.ts
  uses) good/warn by helm status, the rest warn (foreign). The logical view
  places them in their namespace boxes, creating the box under the cluster
  for an observed-but-undeclared namespace. applyHelmArtifacts + the
  synthesis + the wrong-tier note now also run on /api/refresh and the
  logical overlay branch, which silently dropped them.

- Substrates: the Floci probe accepts `chant-floci` (chant emulator up's
  container name) alongside `floci`; the Helm pill reports the PROJECT'S
  bound context instead of the ambient current-context (observed live: an
  estate bound to k3d-kubemicrovm-local whose pill named a real EKS
  cluster); the /api/apply Floci #16 pre-flight only fires when applies
  actually target Floci — on a k8s/helm estate a re-apply is the normal
  sync gesture and used to 409 with an error naming an emulator the project
  doesn't use.

pinhole ^0.2.5: reserved `_`-attrs stay off cards (no more `_status: good`
printed on every overlay card), childless containers lay out finite, and
group boxes carry the container node's status tint.


Claude-Session: https://claude.ai/code/session_01NEtbXBSwLiYFMKv2o4VMb4

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
lex00 added a commit that referenced this pull request Aug 7, 2026
… no phantom docs (#160)

Moving every write into the ⌘K palette (#73) left the header with zero action
buttons: behold looked read-only unless you happened to press ⌘K, the docs
still said "click Run floci-apply" about buttons that no longer existed, and
an estate had three differently-named routes to the same apply. Concretely:

- The PRIMARY deploy gesture is a header button again. A committed ApplyOp
  gets `▶ Deploy (<op>)` (+ `Approve <gate>` beside it when gated); a
  project with only components gets `▶ Deploy…`, which opens the dial's
  component picker. One intent, one word — the tooltip carries the
  mechanism. Everything else stays in ⌘K.

- The dial no longer vanishes wordlessly when no env is picked — it says
  what's missing and where to pick it. A static export keeps the silence
  (there is genuinely nothing to offer).

- Errors get a toast at the point of gesture, not only a line in a bottom
  log pane that is display:none until something writes to it. Successes
  toast too ("running <op> — output streams in the log below"), so a
  palette action no longer closes into dead air.

- The Floci #16 warnings (re-apply collides on the emulator) only appear
  when the estate actually targets Floci — a k3d/helm estate used to be
  warned about an emulator it doesn't use, in dialogs and in the 409 (the
  server side of this gating landed in #159).

- Docs describe the UI that exists: README, example-k8s/README, run-it.mdx,
  and acting.mdx's action table (button vs palette, per action).


Claude-Session: https://claude.ai/code/session_01NEtbXBSwLiYFMKv2o4VMb4

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

writes: delegated Adopt — ReconcileOp PR from a foreign node

1 participant