Repository navigation
feat(jetkvm): console terminal session, bounded flow, LUKS unlock, boot order - #11
Conversation
…ot order Adds the human-in-the-terminal surface the console installer needs, over the shared sdk/kit console engine (the prerequisite PR in opencharly/sdk, which must land and tag first — this tree's go.mod require bumps to that tag): - open-terminal / run-command / close-terminal — drive an OPEN shell on the controlled machine, reading each command's result by OCR. run-command supports sudo (entering the password at the prompt) and close_terminal. Completion is a MARKER LINE (kit.ConsoleSession), never a prompt substring (the terminal echo would satisfy it before the command ran — the RCA behind the design). Live-proven: run-command typed into a root shell on the Omarchy live ISO and read `ID=omarchy` back via OCR. - luks-unlock — enter a disk-encryption passphrase at the initramfs prompt and wait for a success anchor; a wrong passphrase FAILS fast on a built-in rejection anchor (`No key available`), never a silent "outcome". Live-proven FAIL on a wrong passphrase. - flow — a BOUNDED state machine: continuous OCR-until-condition with NAMED outcomes, if/then/else + case/switch via transitions, and bounded while via back-edges (flow_max_loops / flow_max_steps). Live-proven end-to-end: a flow logged in as root on the live ISO and ran efibootmgr, reading the output. - boot-order — set the UEFI boot order from inside the running system via the OS-side EFI boot manager (efibootmgr): list / next (--bootnext) / set (--bootorder), over the terminal, root for the writes. Schema (SDD): the new surface is CUE in schema/jetkvm.cue, params regenerated with `cue exp gengotypes` + the retag transform. The input-shape enums are tightened to CUE enums (#JetkvmAction, media_mode, usb_device, button) so a typo fails at validate time — live-proven: a bad `action:` is rejected with `plugin_input fails #JetkvmInput: #JetkvmInput.action: 18 errors in empty disjunction`. Cross-field presence rules (action=next ⇒ entry) stay in Go, because a conditional that constrains a field to a method-specific set degrades `cue exp gengotypes` to `any` (measured; the limitation documented at spec/schema/vm.cue's #LibvirtGraphics note). Tests: schema/params round-trip, dispatch wiring, mutating-gate classification for every new method, required-field guards, and the shared engine's pure halves. The engine's marker/echo-trap, sudo, passphrase, and flow routing are unit-tested in sdk/kit (R3). Assisted-by: opencode ollama-cloud/deepseek-v4.1-flash (fully tested and validated)
…t actions The terminal-session / flow / boot-order / LUKS actions now live ONCE in sdk/kit (console_actions.go), over the neutral ConsoleTransport, and this plugin only DECODES its schema-generated params into that shared form (R3 — no second drive loop). It is the same shared implementation the SPICE transport calls, so one authored action drives a physical machine (USB HID) or a VM (SPICE keyboard). Pure param→neutral decodes (sessionCommands, the boot-order/flow guards) are unit-locked here; the actions' own behavior (marker/echo trap, sudo, passphrase, routing) is tested where it lives, in sdk/kit. Assisted-by: opencode ollama-cloud/deepseek-v4.1-flash (fully tested and validated)
…low method Adds the two flow capabilities the live Omarchy install motivated, over the shared sdk/kit engine (the prerequisite sdk PR lands first): - Outcome `reference:` — a host path to a previously-captured screenshot; the outcome matches by SCREEN FINGERPRINT (perceptual hash) instead of OCR, with an optional `max_distance:` threshold. A screen that OCRs badly (a firmware menu, a graphical lock) is now recognisable, and an action triggers when the current screen matches the reference. Live-proven: a flow whose only wait was a reference matched the login screen and ran its command. Live-proven. - `flow_resume: true` (+ `flow_resume_order:`) — AUTO-DETECT the node whose wait matches the CURRENT screen and start there, so a re-run after a stall or restart recovers to the right step instead of replaying from `flow_start`. An ambiguous match fails naming the candidates unless the order disambiguates. Schema (SDD): the new fields are CUE in schema/jetkvm.cue; params regenerated with `cue exp gengotypes` + the retag transform. An outcome now requires EITHER a `match:` (OCR) OR a `reference:` (screen), enforced by the engine's Validate. Also bumps the OCR upscale (via the sdk require) — the measured fix for the installer anchor misread. Assisted-by: opencode ollama-cloud/deepseek-v4.1-flash (fully tested and validated)
…sole actions Follows the sdk fix (prompt preflight + flow wall-clock budget): run-command now passes the step's `prompt_anchors:` into the shared session so a command is never typed into a pager/menu/login screen (it fails fast instead), and the flow builds a kit.ConsoleFlowSpec carrying the resume + prompt options. Assisted-by: opencode ollama-cloud/deepseek-v4.1-flash (fully tested and validated)
…oolkit) Bumps github.com/opencharly/sdk to the merged release carrying the shared console session / flow / reference-matching / auto-resume / prompt-preflight engine, and spec to the version it requires. Plugin builds and tests green against the published tag (no local replace). Assisted-by: opencode ollama-cloud/deepseek-v4.1-flash (fully tested and validated)
Adds jetkvm-console-session: a non-destructive bed proving run-command (marker-line completion + OCR readback, with prompt preflight) and flow (named outcomes + routing + a command action) against a machine at a shell prompt. The new methods' check-coverage for the plugin. Assisted-by: opencode ollama-cloud/deepseek-v4.1-flash (fully tested and validated)
…mmand)
The bed now drives a full `flow` login (username + password + named-outcome
routing) from a console login prompt to a shell, then reads the OS id via
`run-command`. Flow `{{placeholder}}` answers resolve from answers_env, so
credentials come from the host environment. Non-destructive.
Assisted-by: opencode ollama-cloud/deepseek-v4.1-flash (fully tested and validated)
…mmand)
The bed now drives a full `flow` login (username + password + named-outcome
routing) from a console login prompt to a shell, then reads the OS id via
`run-command`. Flow `{{placeholder}}` answers resolve from answers_env, so
credentials come from the host environment. Non-destructive.
Assisted-by: opencode ollama-cloud/deepseek-v4.1-flash (fully tested and validated)
…line The flow now stops CLEANLY just before the host's per-attempt never-hang bound (the context deadline) fires, returning its evidence naming where it stopped, instead of being SIGKILLed mid-node with a bare "context deadline exceeded" (RCA: a live login flow died at the bound). Assisted-by: opencode ollama-cloud/deepseek-v4.1-flash (fully tested and validated)
…mmand)
The bed now drives a full `flow` login (username + password + named-outcome
routing) from a console login prompt to a shell, then reads the OS id via
`run-command`. Flow `{{placeholder}}` answers resolve from answers_env, so
credentials come from the host environment. Non-destructive.
Assisted-by: opencode ollama-cloud/deepseek-v4.1-flash (fully tested and validated)
The bed proves run-command (marker-line completion + OCR readback, with prompt preflight) and flow (named outcomes + routing + a command action) against a machine ALREADY LOGGED IN at a console shell (an Omarchy TTY) — the form proven live. Logging in is an operator action; open-terminal is not used because a bare Linux console has no GUI terminal. Assisted-by: opencode ollama-cloud/deepseek-v4.1-flash (fully tested and validated)
…ode tests - The `action:` doc listed `set-audio` as an action-bearing method, but set-audio uses int_value (no action) — correct the schema doc and its regenerated params comment. - Extend TestSessionMethodsAreMutating to include `flow` and `boot-order` (both are mutating per the schema), so the gate test matches its stated purpose. - Add pure decode tests for open-terminal (sessionTerminalOpen) and boot-order (sessionBootOrder), and refactor those decodes out of the run* wrappers so the param→neutral mapping is unit-locked without a device. Schema change is a doc comment only; params regenerated with cue exp gengotypes. Assisted-by: opencode ollama-cloud/deepseek-v4.1-flash (fully tested and validated)
…num-completeness - methods.go: the LUKS either/or guard comment claimed runLUKSUnlock enforces it; the guard actually lives in the shared kit.LUKSUnlock (the plugin delegates). Correct the comment. - session.go: refactor runCloseTerminal through an injectable-transport seam (closeTerminalOn) and add a test proving the plugin wires it to type `exit` and submit — close-terminal now has coverage that fails without it. - catalog_invariant_test.go: add TestActionEnumCoversEveryActionBearingMethod, which reads the accepted verbs from the methods' own `case` arms in catalog.go and asserts the #JetkvmAction CUE enum is a superset — proving the validate-time narrowing rejects no verb the code supports, and preventing future drift (verified: removing a verb from the enum makes the test fail). Assisted-by: opencode ollama-cloud/deepseek-v4.1-flash (fully tested and validated)
The bed's flow now routes on whether a shell or a login prompt is on screen (flow named outcomes), logs in only if needed, then runs a command and reads the OS id. Robust to the machine's starting state.
…t-glyph anchors RCA: tesseract does NOT OCR the shell prompt glyph (an omarchy prompt '~' reads as '|'/'_'), so a prompt_anchors preflight on ['~','#','$'] never matches a real shell and refuses to type. The OCR-robust completion signal is the MARKER LINE the shell echoes; the bed now relies on it (and a flow node acts without a prompt-glyph wait).
RCA: OCR reads 'JETKVM_FLOW_OK' as 'JETKUM_FLOW_DK' (KVM->KUM, OK->DK), so an
expect on that string never matched. Use the OS id ('omarchy'), which OCR reads
cleanly.
…sion bed The screen accumulates stale scrollback, and OCR reads 'omarchy' as 'omarchu', so an expect on the full word was fragile. Clear the screen first and assert on the OCR-stable 'ID=' prefix.
…ker) Bumps to the merged SDK carrying the per-command nonce + fuzzy OCR marker match, so the shipped console-session bed is green live against the real console. Assisted-by: opencode ollama-cloud/deepseek-v4.1-flash (fully tested and validated)
Review — BLOCKHead SHA: Blocks
Notes (not the basis of the block): the shipped bed in Verdict: BLOCK ollama-cloud/deepseek-v4.1-flash — action-review. |
- sessionBootOrder's doc had a leftover `runBootOrder` line merged on; remove it and state accurately that BootOrderAction is a plain string (an inline CUE union), so string(...) is a copy, not an enum conversion. - TestSessionBootOrder_Decodes' comment claimed a "schema-enum type conversion" that does not exist; correct it. Assisted-by: opencode ollama-cloud/deepseek-v4.1-flash (fully tested and validated)
Review — PASSHead SHA: Security screen: PASS — no added secrets, no egress/new endpoints, no guardrail weakening. The new verbs are classified mutating (pinned by Checklist: All applicable rules PASS or NA.
Comment intake: the single
Verdict: PASS ollama-cloud/deepseek-v4.1-flash — action-review. |
Summary
Adds the human-in-the-terminal surface to the
jetkvm:verb, over the shared,transport-neutral console engine merged in opencharly/sdk#296 + #304
(
github.com/opencharly/sdk v0.2026269.1433, which this PR requires):open-terminal/run-command/close-terminal— drive an OPEN shell onthe controlled machine, reading each command's result by OCR.
run-commandsupports
sudo,expectassertions,close_terminal, andprompt_anchors(a shell-prompt preflight that refuses to type into a pager/menu/login screen).
luks-unlock— enter a disk-encryption passphrase at the initramfs prompt;a wrong passphrase FAILS fast.
flow— a BOUNDED state machine: continuous OCR-until-condition with NAMEDoutcomes, if/then/else + case/switch, bounded while, reference-screenshot
outcomes, and auto-resume. A flow's wall-clock budget is derived from the
dispatch deadline so it stops CLEANLY before the host's never-hang kill.
boot-order— set the UEFI boot order from inside the OS viaefibootmgr(
list/next/set).Implemented ONCE in
sdk/kit(console_actions.go); this plugin supplies only itsUSB-HID
ConsoleTransportand the CUE→Go decode (R3).Schema (SDD) — and the review fixes
schema/jetkvm.cue; params regenerated withcue exp gengotypes+ the retag transform (byte-reproducible from the schema;verified).
action:doc listedset-audioas action-bearing, butset-audio uses
int_value(no action). Corrected the doc (and the regeneratedparams comment).
TestSessionMethodsAreMutatingnow includesflowandboot-order.sessionTerminalOpen,sessionBootOrder) and refactored those decodes out of therun*wrappers sothe param→neutral mapping is unit-locked without a device.
How tested
Gate (this repo), from the committed tree at head
839f524:Build is against the PUBLISHED sdk tag (no local replace).
Live — the paths that ran on the real machine. Target: the physical machine
behind the JetKVM (an operator resource, NOT destroyed; the
kind:checkbed'slocal:side isdisposable: true). The plugin was host-built from this branch(
CHARLY_REPO_OVERRIDE=…; the host-build stamp names the source path).Proven live:
run-commandtyped into a real shell and read output by OCR:flowlogged in as root and ranefibootmgr, reading the output(
zk-flow-live2:flow completed at node "listboot" after 3 step(s)).zk-ref-match).step2and started there (zk-resume2).no shell prompt ([~ # $]) is on screen — refusing to type into "".flowwall-clock budget stopped a long flow CLEANLY with evidence ratherthan a mid-node SIGKILL.
Shipped bed GREEN live (head
839f524, against sdk v0.2026269.1433):Not proven live (unit-tested only):
open-terminal,close-terminal(a bareLinux console has no GUI terminal to open), the
luks-unlockSUCCESS path (thefailure path was proven live), the
boot-orderdispatch (its decode is unit-tested; the
flowefibootmgr run exercises the flow command action, notRunBootOrder), and therun-commandsudo branch.Rulebook compliance
blind typing, stale marker) fixed in the sdk this consumes.
sdk/kit; the plugin holds only itstransport + decode.
bounded. R5 additive. R6 no force-push.
params/cue_types_gen.gois reproducible from the schema (verified);the plugin was host-built from this branch.
action.
plan:/check:acceptance and a shipped bed.is stated above.
Change classification
check-coverage bed.
gofmt/go build/go vet/go test ./...+charly box validategreen at head839f524;run-command+flow+reference + resume + prompt-preflight executed live (transcripts above).
analysed on a live system— the changed runtime path ranlive with retained output, but several changed paths (open-terminal,
close-terminal, boot-order, the luks success path, sudo) have unit coverage
only.
Assisted-by: opencode ollama-cloud/deepseek-v4.1-flash (analysed on a live system)