Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@ 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
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
8 changes: 4 additions & 4 deletions core/fsm/instance.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -112,18 +112,18 @@ machines:
- machine_id: instance_provision
root:
type: composite
initial: { transition_to: unprovisioned }
initial: { transition_to: unknown }
states:
unprovisioned:
unknown:
on_events:
provision: { transition_to: provisioning }
provisioning:
on_events:
provision_ok: { transition_to: provisioned }
provision_fail: { transition_to: provision_error }
provision_fail: { transition_to: error }
provisioned:
on_events:
provision: { transition_to: provisioning } # re-converge
provision_error:
error:
on_events:
provision: { transition_to: provisioning } # retry
48 changes: 27 additions & 21 deletions core/fsm/package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,44 @@
format: 1
namespace: eve.core
events:
found: { direction: input } # observe: the package is present on the instance
absent: { direction: input } # observe: not present
found: { direction: input } # observe: the package is present on the instance
install: { direction: input }
install_ok: { 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: unknown }
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:
unknown:
idle:
type: composite
initial: { transition_to: unknown }
on_events:
found: { transition_to: installed }
absent: { transition_to: missing }
install: { transition_to: installing }
missing:
on_events:
install: { transition_to: installing }
found: { transition_to: installed } # drift: appeared out of band
remove: { transition_to: removing }
states:
failed: {}
installed: {}
missing: {}
removed: {}
unknown: {}
installing:
on_events:
install_ok: { transition_to: installed }
install_fail: { transition_to: failed }
installed:
on_events:
remove: { transition_to: removed }
absent: { transition_to: missing } # drift: disappeared
failed:
install_fail: { transition_to: idle.failed }
install_ok: { transition_to: idle.installed }
removing:
on_events:
install: { transition_to: installing } # retry
removed:
on_events:
install: { transition_to: installing }
remove_fail: { transition_to: idle.failed }
remove_ok: { transition_to: idle.removed }
6 changes: 5 additions & 1 deletion core/schema/observed-state.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@
"properties": {
"status": {
"type": "string",
"enum": ["unknown", "installing", "installed", "missing", "failed", "removed"]
"enum": ["failed", "installed", "installing", "missing", "removed", "removing", "unknown"]
},
"updated_at": {
"type": "string",
Expand All @@ -154,6 +154,10 @@
"type": "string",
"description": "Dotted operation name (e.g. provider.up, package.install)."
},
"package": {
"type": "string",
"description": "Package id for a package operation."
},
"type": {
"type": "string",
"description": "Operation category (provider, provision, package).",
Expand Down
14 changes: 14 additions & 0 deletions core/schema/plugin-manifest.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,20 @@
"$ref": "#/$defs/field_spec"
}
},
"required_any": {
"description": "Provider-only alternative complete credential/configuration groups. At least one group must have every referenced field set. References use config.<field> or secrets.<field>.",
"type": "array",
"minItems": 1,
"items": {
"type": "array",
"minItems": 1,
"items": {
"type": "string",
"pattern": "^(config|secrets)\\.[a-z][a-z0-9_]*$"
},
"uniqueItems": true
}
},
"secrets": {
"type": "object",
"additionalProperties": {
Expand Down
131 changes: 131 additions & 0 deletions docs/recovery-cloud-validation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# v4.5 recovery validation in Cloud

Validated on 2026-10-03 UTC from fresh GitHub recovery checkouts. No backup files or
ignored personal configuration were used. All five initial heads matched the
recovery request:

| Repository | Recovery PR | Initial head |
| --- | --- | --- |
| eve | #56 | c2c0f5a |
| eve-providers | #11 | 16ef4ef |
| eve-packages-linux | #7 | 5f9bb7c |
| eve-packages-windows | #5 | 5b4e261 |
| eve-plugins-ai | #3 | 1557a55 |

## Determa API and version

The [Python upstream release](https://github.com/fruwehq/determa-state-python/releases/tag/v0.2.0),
[specification release](https://github.com/fruwehq/determa-state-spec/releases/tag/v0.2.0),
and PyPI release metadata all identify 0.2.0 as the latest release. No newer
compatible or breaking release was found. Retain `>=0.2.0,<0.3.0` and the existing
0.2.0 lockfile entry.

Eve uses the documented public `load_bundle`, `create`, and `dispatch` functions,
format-1 machine definitions, root-targeted event envelopes, external bindings,
and reserved `env` refresh events. Aggregate `runtimes`, active paths, and root
scopes are documented public state data. Side effects remain host-owned.
`ExecutionHost` is optional; its checkpoint stores are not required for pure
foreground transitions. Eve's small seed-by-replay adapter is restricted to its
context-free finite lifecycle graphs. A future machine with context or history
must persist and restore the complete aggregate, as the adapter documents.
No Determa design defect requiring an upstream issue was reproduced.

## Fresh validation

The Cloud environment used Python 3.12.14, dependencies installed from
`poetry.lock`, Terraform 1.14.9, Terramate 0.17.0, ShellCheck 0.11.0, and portable
PowerShell 7.5.4. PowerShell's writable XDG directories were isolated for testing.

- The full `scripts/test` passed. Final validation of all Python tests passed
528 tests with one intentional skip (an empty-registry instance comparison).
- The standalone FSM tests now run with the default Python suite, rather than
being omitted by its old `tests/python` selection.
- Linux launcher tests: 23 passed with `python -m pytest -q tests`.
- Providers: all seven provider conformance checks, host-resolver checks, five
AWS connectivity tests, five status-error tests, and environment contracts passed.
- Every tracked manifest across all four companion repositories passed
conformance, including nested bundle and OS manifests.
- The combined catalog loaded 47 plugins: seven providers, 25 packages, eleven
bundles, and four OS plugins. Shared Linux/Windows package configuration
schemas match; standalone bundle membership resolves.
- Actual OS runners passed failure, success, resume, manifest, and structured
status checks. PowerShell additionally passed explicit exit, thrown-error,
and informational native-tool exit cases.
- ShellCheck and Ruff passed for provider and Linux scripts. All 22 tracked
provider/Windows PowerShell files parsed successfully.
- External provider stack staging, Terramate generation, Terraform formatting,
Terramate formatting, and the core boundary check with real plugins passed.
- Linux and Windows integration plans, including all applicable packages, were
generated from synthetic instances. Fake-provider lifecycle tests exercised
create/status/IP/stop/start/provision/destroy without live infrastructure.

Reproduce cross-repository checks from the core checkout:

```sh
scripts/test-cross-repo \
--plugin-root ../eve-providers \
--plugin-root ../eve-packages-linux \
--plugin-root ../eve-packages-windows \
--plugin-root ../eve-plugins-ai
scripts/test-provision-runner --plugin-roots ../eve-providers
```

Run with the core project environment on `PATH`. Add `pwsh` to `PATH` for Windows
runner checks. The ordinary core suite stays hermetic; cross-repository runners
accept explicit roots and never need live providers.

## Audit findings and fixes

Determa remains the transition authority for provider, provisioning, package,
and interrupted-operation recovery. Operation IDs are allocated under the state
lock and remain monotonic when history is trimmed. Authentication evaluates all
universal requirements plus at least one complete alternative group
(`required_any` is OR-of-AND). Provider and package command environments are
operation-local and inject only declared target credentials.

Fresh checks identified and fixed these additional problems:

- Nested package observations could supply a root status through line scanning.
JSON extraction now skips nested objects and reads the last matching root.
- Secret payload files are created with mode 0600 before bytes are written,
including Windows JSON. Remote state directories are restricted before upload.
Partial secret uploads trigger cleanup. Failed Windows deletions and native
ACL errors propagate instead of silently succeeding.
- Literal shell quoting had prevented staged installer paths from expanding
`$HOME`. Only that known prefix expands; filenames and secrets stay quoted.
- The Windows runner treated an explicit `exit 42` as success. Each step now
executes in the current PowerShell runtime's separate `-File` process.
- Terraform status helpers suppressed backend failures. Missing explicit local
state remains absence; generation, backend, malformed JSON, and AWS API
failures now propagate. No alternate backend is queried for missing explicit
instance state.
- Linux contained tracked bytecode and lint findings. Bytecode was removed,
generated Python artifacts ignored, and lint findings fixed.
- Core contained a concrete provider identifier in an FSM comment that failed
the real-plugin core boundary check. The example is now generic.

## GitHub and remaining rollout dependency

Core recovery CI skipped because its PR was a draft; the cross-repository job is
scheduled/manual only. Provider and Linux/Windows recovery CI was absent because
those workflows filtered PR bases to `main`. Companion workflows now include
`v4.5`, select the corresponding core ref, and validate nested manifests. Core
and companion workflows explicitly handle `ready_for_review`. Provider CI now
runs status-error and OS-runner tests, and Linux CI runs its launcher suite.

Enabling core CI exposed a pre-existing environment mismatch: Poetry created a
cached environment while the test runners require `.venv/bin/python`. The
workflow now explicitly creates its environment inside the project, as used in
fresh Cloud validation, and the pipeline contract check enforces this setting.

No open issues or unresolved review threads were found on the affected PRs.
The only additional open PR found was Eve #46, a TUI portrait-blink change
outside this migration. Its `tui/app.py` overlap should be reviewed separately;
its code was not incorporated into recovery.

Offline readiness does not establish live Windows GPU usability. The existing
v4.5 roadmap still requires a Vultr `vultr-vcg-a40-2c` Windows gaming instance
with the VDD as the sole active display and working Sunshine streaming. Cloud
currently has no provider secret or outbound identity bindings. Main-facing PRs
must state this remaining live dependency accurately; do not claim that demo or
GPU validation has occurred. No billable resources were created.
29 changes: 16 additions & 13 deletions docs/state.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# v3 Instance State
# v4.5 Instance State

Concrete instance state lives in `.eve/state/instances/<instance>.json` and
is updated through `scripts/instance-state`. Core dispatchers should not write
Expand All @@ -7,22 +7,25 @@ these JSON files directly.
## Top-Level States

- `desired_state`: `unknown`, `running`, `stopped`, `absent`
- `provider_state`: `unknown`, `initializing`, `initialized`, `planned`,
`changing`, `running`, `stopped`, `absent`, `error`
- `provider_state`: `unknown`, `creating`, `starting`, `stopping`, `destroying`,
`running`, `stopped`, `absent`, `error`
- `provision_state`: `unknown`, `provisioning`, `provisioned`, `error`

## Package States

Package entries under `package_state` use:

- `unknown`
- `failed`
- `installed`
- `installing`
- `missing`
- `failed`
- `removed`
- `reinstalled`
- `removing`
- `unknown`

Package state updates must pass both `--package` and `--package-state`.
Dispatchers derive package state by firing operation and observation events into
`core/fsm/package.yaml`. The `--package-state` pair remains available only for explicit
compatibility writes.

## Observed State Cache

Expand Down Expand Up @@ -52,13 +55,14 @@ commands, so they do not append lifecycle operations to `operation_history`.
Every write records `last_operation` and appends to `operation_history`.
Operation entries include:

- `id`: monotonic per retained history window
- `id`: monotonic even after the retained history window is trimmed
- `name`: full operation name, such as `provider.up`, `package.status`, or
`provision`
- `type`: operation prefix, such as `provider`, `package`, or `provision`
- `status`: `running`, `succeeded`, `failed`, or `skipped`
- `at`: UTC timestamp
- `error`: present only for failed or error-bearing entries
- `package`: package id, present on package operations

The history keeps the latest 50 entries by default.

Expand All @@ -79,8 +83,7 @@ the local state before retrying:
eve instance recover --instance <name>
```

This marks the last running operation as `failed`, records a recovery error, and
sets `provider_state` or `provision_state` to `error` when the interrupted
operation belongs to that surface. It does not destroy or change remote
resources. After recovery, run `eve instance status --instance <name>` and retry
the relevant lifecycle/provision/package command.
This marks the last running operation as `failed`, records a recovery error, and fires
the matching failure event into the provider, provision, or package machine. It does not
destroy or change remote resources. After recovery, run
`eve instance status --instance <name>` and retry the relevant command.
Loading
Loading