Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
4c35dfa
fix(tf-dispatch): stage provider stacks on the terraform read path
fruwe Jun 27, 2026
7955af6
docs(v4.5): roadmap (milestone-4 validation) + coding-agent task file
fruwe Jun 27, 2026
f7c1861
feat(tui): local-folder plugin sources + edit screen (v4.5 Bug B)
fruwe Jun 27, 2026
8765e09
docs(v4.5): Bug D (QEMU lifecycle) done; record stale-SHA fix
fruwe Jun 27, 2026
4a9c256
docs(v4.5): Bugs C, E (Vultr lifecycle + TUI pass) done
fruwe Jun 27, 2026
b60ca21
docs(v4.5): Bug F (state hygiene) done; clean junk state files
fruwe Jun 27, 2026
0a18b7d
docs(v4.5): add bugs G-J (wizard OS-compat, dialog defaults, gaming-s…
fruwe Jun 27, 2026
b84c9a0
docs(v4.5): Bug K — bundles as kind:bundle plugins + dependencies; pa…
fruwe Jun 27, 2026
3b6ca72
docs(v4.5): harden Task 8 — provisioned/good-to-go acceptance gate wi…
fruwe Jun 27, 2026
a5c1574
feat(bundles): kind:bundle plugins + members field + catalog migratio…
fruwe Jun 27, 2026
b3543a6
feat(tui): wizard differentiation + dialog convention + edit label (v…
fruwe Jun 27, 2026
a63d0f7
docs(v4.5): Bugs G, H, I, J, K done
fruwe Jun 27, 2026
39e9944
docs(v4.5): Bug L - VDD Code-43 init fix, VDD-only requirement, drop …
fruwe Jun 27, 2026
6422cd2
fix(catalog_view): extract isinstance check to variable for mypy narr…
fruwe Jun 28, 2026
d8048a0
fix(providers-status): resolve config-env + secrets like dispatch (v4…
fruwe Jun 28, 2026
f8fefdf
docs(v4.5): FSM platform + settings-as-context design (plugin-declare…
fruwe Jun 28, 2026
d6a27e1
docs(v4.5): FSM engine as a separate generic repo (engine vs eve-adap…
fruwe Jun 28, 2026
ee0b69d
feat(v4.5 N/O): FSM platform (EveFsm) over harel + settings-as-context
fruwe Jul 1, 2026
484d704
feat(v4.5 M/N): real provider context resolver + engine-rename resili…
fruwe Jul 5, 2026
4801d74
feat(v4.5 N/O): core provider machine + eve-derived configured
fruwe Jul 5, 2026
5dfbf55
build(v4.5): pin determa-state 0.0.5; import determa.state; drop skip…
fruwe Jul 5, 2026
50161f7
feat(v4.5 N/O): providers-status configured via the core provider FSM
fruwe Jul 5, 2026
0cddae0
feat(v4.5 N): declarative instance + package machines
fruwe Jul 5, 2026
e52f7f3
feat(v4.5 N): align instance machine + persisted-state migration
fruwe Jul 6, 2026
317902f
chore(v4.5 N): clean-slate state; retire fsm_migrate (delete-all over…
fruwe Jul 6, 2026
cd7f816
refactor(v4.5 N): align dispatch + state vocabulary to the core machines
fruwe Jul 6, 2026
5c1f1bf
feat(v4.5 N): FSM-driven provider_state — the instance machine is the…
fruwe Jul 6, 2026
b8394d7
feat(v4.5 N): port to determa-state 0.1.0 — format-1 bundles + pure-f…
fruwe Aug 19, 2026
bdd4474
Complete v4.5 Determa 0.2 recovery and Cloud validation (#56)
fruwe Oct 3, 2026
13341e8
refactor(v4.5): remove unused alternative auth schema
fruwe Oct 4, 2026
4282c0e
feat(v4.5): add container and typed guest access contracts
fruwe Oct 5, 2026
0491910
fix(v4.5): package runtime assets and repair provider observations
fruwe Oct 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ on:
branches:
- main
pull_request:
types: [opened, ready_for_review, reopened, synchronize]
schedule:
- cron: "0 9 * * *"
workflow_dispatch:

env:
POETRY_VIRTUALENVS_IN_PROJECT: "true"

jobs:
test:
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -116,6 +119,9 @@ jobs:
python -m pip install --upgrade poetry
poetry install --no-interaction --no-root

- name: Test isolated installed wheel
run: python scripts/test-wheel

- name: Generate Terramate code
if: runner.os == 'Linux'
run: |
Expand Down
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,11 @@ The Windows SSH server default shell is **PowerShell** (not `cmd.exe`). All scri

This project provisions environments from scratch — we control the OS, installed packages, shell, and runtime versions. **Never add fallback logic** (e.g. "try PowerShell, fall back to cmd.exe" or "check both Program Files and AppData just in case"). Always use the single correct path for the environment we build. If the environment changes, change the script — don't layer on fallbacks that mask the real requirement.

## Catalog kinds: vm vs metal
## Catalog machine kinds

Machine entries declare a `kind:` field. Current supported kinds:

- `kind: container` — disposable system-container lifecycle (`incus`), with provider-owned cloud-init SSH bootstrap. This is not a VM or a guest package.
- `kind: vm` — disposable VM lifecycle (`aws`, `gcp`, `vultr`, `truenas`, `local-qemu`). `up` creates, `down` deletes.
- `kind: metal` — persistent hardware (`raspberry-pi`). `down` tears down managed workloads, **not** the machine. Don't force metal targets through VM lifecycle assumptions.

Expand Down
2 changes: 1 addition & 1 deletion config/defaults.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

display:
fps:
resolution:
resolution: 1920x1080

global:
my_ip:
Expand Down
129 changes: 129 additions & 0 deletions core/fsm/instance.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# The instance lifecycle (v4.5 N/O) — a Determa State format-1 bundle; the declarative
# replacement for state.py's PROVIDER_STATES + PROVISION_STATES. Two machines: the
# provider (terraform) lifecycle and the provisioning lifecycle, which advance
# independently (format 1 has no orthogonal regions, and eve persists + drives the two
# as separate fields anyway). `desired` is the goal (running | stopped | absent),
# supplied by eve as an external binding. Transitions are driven by eve operations
# (create/start/stop/destroy, provision) whose outcomes come back as events, and by
# observation (observe_* maps live provider status back into a state, keeping the
# machine honest about drift).
format: 1
namespace: eve.core
events:
create: { direction: input }
start: { direction: input }
stop: { direction: input }
destroy: { direction: input }
created_ok: { direction: input }
started_ok: { direction: input }
stopped_ok: { direction: input }
destroyed_ok: { direction: input }
op_fail: { direction: input }
observe_running: { direction: input }
observe_stopped: { direction: input }
observe_absent: { direction: input }
provision: { direction: input }
provision_ok: { direction: input }
provision_fail: { direction: input }
machines:

# Provider (terraform) lifecycle. Each in-flight operation has its own transient
# state so its outcome is unambiguous; op_fail from any of them lands in error.
# `unknown` is the unobserved start; observe_* reconcile from live provider status.
#
# Every concrete (non-transient) state accepts every operator command and routes
# to that command's transient state: the operator's intent is authoritative and the
# provider command's success/failure — not the machine — decides feasibility (a
# `start` that can't apply fails and lands in `error` via op_fail, it never silently
# no-ops). So a command's terminal outcome (up/start->running, stop->stopped,
# down->absent) holds from any state, exactly as before, now with honest transients.
- machine_id: instance_provider
root:
type: composite
variables:
desired: { type: string, external: true, init: "unknown" } # running | stopped | absent | unknown
initial: { transition_to: unknown }
# (format 1 forbids YAML anchors, so the shared command block is spelled out
# in each concrete state.)
states:
unknown:
on_events:
create: { transition_to: creating }
start: { transition_to: starting }
stop: { transition_to: stopping }
destroy: { transition_to: destroying }
observe_running: { transition_to: running }
observe_stopped: { transition_to: stopped }
observe_absent: { transition_to: absent }
absent:
on_events:
create: { transition_to: creating }
start: { transition_to: starting }
stop: { transition_to: stopping }
destroy: { transition_to: destroying }
observe_running: { transition_to: running }
observe_stopped: { transition_to: stopped }
observe_absent: { transition_to: absent }
running:
on_events:
create: { transition_to: creating }
start: { transition_to: starting }
stop: { transition_to: stopping }
destroy: { transition_to: destroying }
observe_running: { transition_to: running }
observe_stopped: { transition_to: stopped }
observe_absent: { transition_to: absent }
stopped:
on_events:
create: { transition_to: creating }
start: { transition_to: starting }
stop: { transition_to: stopping }
destroy: { transition_to: destroying }
observe_running: { transition_to: running }
observe_stopped: { transition_to: stopped }
observe_absent: { transition_to: absent }
error:
on_events:
create: { transition_to: creating }
start: { transition_to: starting }
stop: { transition_to: stopping }
destroy: { transition_to: destroying }
observe_running: { transition_to: running }
observe_stopped: { transition_to: stopped }
observe_absent: { transition_to: absent }
creating:
on_events:
created_ok: { transition_to: running }
op_fail: { transition_to: error }
starting:
on_events:
started_ok: { transition_to: running }
op_fail: { transition_to: error }
stopping:
on_events:
stopped_ok: { transition_to: stopped }
op_fail: { transition_to: error }
destroying:
on_events:
destroyed_ok: { transition_to: absent }
op_fail: { transition_to: error }

# Provisioning lifecycle (independent of the provider machine).
- machine_id: instance_provision
root:
type: composite
initial: { transition_to: unknown }
states:
unknown:
on_events:
provision: { transition_to: provisioning }
provisioning:
on_events:
provision_ok: { transition_to: provisioned }
provision_fail: { transition_to: error }
provisioned:
on_events:
provision: { transition_to: provisioning } # re-converge
error:
on_events:
provision: { transition_to: provisioning } # retry
49 changes: 49 additions & 0 deletions core/fsm/package.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# The package lifecycle, shared by every package (v4.5 N/O) — a Determa State
# format-1 bundle; the declarative replacement for state.py's PACKAGE_STATES.
# Transitions are driven by eve operations (install / remove) and by observation
# (found / absent maps a live probe back to a state, keeping the machine honest
# about drift).
format: 1
namespace: eve.core
events:
absent: { direction: input } # observe: not present
found: { direction: input } # observe: the package is present on the instance
install: { direction: input }
install_fail: { direction: input }
install_ok: { direction: input }
observe_failed: { direction: input }
observe_unknown: { direction: input }
remove: { direction: input }
remove_fail: { direction: input }
remove_ok: { direction: input }
machines:
- machine_id: package
root:
type: composite
initial: { transition_to: idle }
on_events:
absent: { transition_to: idle.missing }
found: { transition_to: idle.installed }
observe_failed: { transition_to: idle.failed }
observe_unknown: { transition_to: idle.unknown }
states:
idle:
type: composite
initial: { transition_to: unknown }
on_events:
install: { transition_to: installing }
remove: { transition_to: removing }
states:
failed: {}
installed: {}
missing: {}
removed: {}
unknown: {}
installing:
on_events:
install_fail: { transition_to: idle.failed }
install_ok: { transition_to: idle.installed }
removing:
on_events:
remove_fail: { transition_to: idle.failed }
remove_ok: { transition_to: idle.removed }
45 changes: 45 additions & 0 deletions core/fsm/provider.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# The provider lifecycle, shared by every provider (v4.5 N/O) — a Determa State
# format-1 bundle.
#
# `is_configured` is derived by eve — true iff every `required` config_schema
# secret/setting resolves to a non-empty value via the ONE shared path (os.environ +
# ConfigEnv + injected secrets) — and seeded as an external creation binding.
# `reachable` is a substate of `configured`, reached by the provider's `connectivity`
# probe (host-run, delivered back as probe_ok / probe_fail). One machine: per-provider
# differences live only in the config eve resolves, never in the states — so
# `configured` can never be a plugin's ambient self-report (the AWS-off-~/.aws bug is
# structurally impossible).
format: 1
namespace: eve.core
events:
resolve: { direction: input }
probe_ok: { direction: input }
probe_fail: { direction: input }
machines:
- machine_id: provider
root:
type: composite
variables:
is_configured: { type: bool, external: true }
on_events:
env:
action: [ { refresh: {} } ] # adopt a changed configured-ness
initial: { transition_to: unconfigured }
states:
unconfigured:
on_events:
resolve:
- { guard: "is_configured == true", transition_to: configured }
configured:
type: composite
initial: { transition_to: unreachable }
on_events:
resolve:
- { guard: "is_configured != true", transition_to: unconfigured }
states:
unreachable:
on_events:
probe_ok: { transition_to: configured.reachable }
reachable:
on_events:
probe_fail: { transition_to: configured.unreachable }
Loading
Loading