empeira 0.1.0.pre.alpha.1

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.
Files changed (274) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +661 -0
  3. data/README.md +150 -0
  4. data/bin/empeira +18 -0
  5. data/config/defaults.yaml +229 -0
  6. data/docs/addons.md +331 -0
  7. data/docs/architecture.md +277 -0
  8. data/docs/configuration.md +1204 -0
  9. data/docs/development.md +472 -0
  10. data/docs/hiera-and-eyaml.md +361 -0
  11. data/docs/installation.md +280 -0
  12. data/docs/networking.md +362 -0
  13. data/docs/nodes.md +642 -0
  14. data/docs/proxy.md +136 -0
  15. data/docs/troubleshooting.md +361 -0
  16. data/lib/empeira/addons/environment.rb +82 -0
  17. data/lib/empeira/addons/error.rb +14 -0
  18. data/lib/empeira/addons/installation.rb +119 -0
  19. data/lib/empeira/addons/manifest.rb +112 -0
  20. data/lib/empeira/addons/service.rb +50 -0
  21. data/lib/empeira/addons/storage.rb +94 -0
  22. data/lib/empeira/agent/acquisition.rb +120 -0
  23. data/lib/empeira/agent/artifact.rb +41 -0
  24. data/lib/empeira/agent/authentication.rb +122 -0
  25. data/lib/empeira/agent/cache.rb +122 -0
  26. data/lib/empeira/agent/cache_storage.rb +82 -0
  27. data/lib/empeira/agent/download.rb +84 -0
  28. data/lib/empeira/agent/native_commands.rb +84 -0
  29. data/lib/empeira/agent/release_sources.rb +64 -0
  30. data/lib/empeira/agent/target.rb +66 -0
  31. data/lib/empeira/agent/versions.rb +29 -0
  32. data/lib/empeira/application.rb +86 -0
  33. data/lib/empeira/build.json +5 -0
  34. data/lib/empeira/build_info.rb +46 -0
  35. data/lib/empeira/cli/addons.rb +45 -0
  36. data/lib/empeira/cli/base.rb +73 -0
  37. data/lib/empeira/cli/completion.rb +42 -0
  38. data/lib/empeira/cli/config.rb +22 -0
  39. data/lib/empeira/cli/main.rb +114 -0
  40. data/lib/empeira/cli/node.rb +87 -0
  41. data/lib/empeira/cli/progress.rb +169 -0
  42. data/lib/empeira/cli/proxy.rb +36 -0
  43. data/lib/empeira/cli/update.rb +23 -0
  44. data/lib/empeira/completion/addons.rb +93 -0
  45. data/lib/empeira/completion/bash.rb +62 -0
  46. data/lib/empeira/completion/catalog.rb +170 -0
  47. data/lib/empeira/configuration/additional_services.rb +74 -0
  48. data/lib/empeira/configuration/addons.rb +85 -0
  49. data/lib/empeira/configuration/agent_schema.rb +163 -0
  50. data/lib/empeira/configuration/agent_values.rb +54 -0
  51. data/lib/empeira/configuration/bootstrap_guests.rb +42 -0
  52. data/lib/empeira/configuration/command_mocks.rb +65 -0
  53. data/lib/empeira/configuration/display.rb +20 -0
  54. data/lib/empeira/configuration/dns_rewrites.rb +58 -0
  55. data/lib/empeira/configuration/document.rb +67 -0
  56. data/lib/empeira/configuration/eyaml.rb +46 -0
  57. data/lib/empeira/configuration/hiera_modulepath.rb +40 -0
  58. data/lib/empeira/configuration/hiera_mount_schema.rb +62 -0
  59. data/lib/empeira/configuration/hiera_mounts.rb +83 -0
  60. data/lib/empeira/configuration/image_schema.rb +68 -0
  61. data/lib/empeira/configuration/loader.rb +70 -0
  62. data/lib/empeira/configuration/merge.rb +48 -0
  63. data/lib/empeira/configuration/network_redirects.rb +73 -0
  64. data/lib/empeira/configuration/node_images.rb +64 -0
  65. data/lib/empeira/configuration/schema.rb +215 -0
  66. data/lib/empeira/configuration/server_mounts.rb +106 -0
  67. data/lib/empeira/configuration/service_configuration.rb +40 -0
  68. data/lib/empeira/configuration/ssh_preferences.rb +83 -0
  69. data/lib/empeira/configuration/user.rb +46 -0
  70. data/lib/empeira/configuration/vm_interfaces.rb +147 -0
  71. data/lib/empeira/control_plane/additional_configurations.rb +106 -0
  72. data/lib/empeira/control_plane/browser.rb +73 -0
  73. data/lib/empeira/control_plane/controller.rb +763 -0
  74. data/lib/empeira/control_plane/discovery.rb +69 -0
  75. data/lib/empeira/control_plane/environment.rb +101 -0
  76. data/lib/empeira/control_plane/files.rb +263 -0
  77. data/lib/empeira/control_plane/health.rb +104 -0
  78. data/lib/empeira/control_plane/plan.rb +356 -0
  79. data/lib/empeira/control_plane/storage.rb +48 -0
  80. data/lib/empeira/errors.rb +9 -0
  81. data/lib/empeira/execution/console.rb +45 -0
  82. data/lib/empeira/execution/diagnostics.rb +210 -0
  83. data/lib/empeira/execution/launch_error.rb +14 -0
  84. data/lib/empeira/execution/result.rb +11 -0
  85. data/lib/empeira/execution/runner.rb +235 -0
  86. data/lib/empeira/execution/terminal.rb +35 -0
  87. data/lib/empeira/hostname_pattern.rb +14 -0
  88. data/lib/empeira/images/configuration.rb +44 -0
  89. data/lib/empeira/images/identity.rb +30 -0
  90. data/lib/empeira/images/recipe.rb +22 -0
  91. data/lib/empeira/images/reference.rb +43 -0
  92. data/lib/empeira/images/source.rb +19 -0
  93. data/lib/empeira/immutable.rb +19 -0
  94. data/lib/empeira/infrastructure/cleanup.rb +328 -0
  95. data/lib/empeira/infrastructure/definition.rb +59 -0
  96. data/lib/empeira/infrastructure/owned_files.rb +63 -0
  97. data/lib/empeira/infrastructure/recovery_inventory.rb +53 -0
  98. data/lib/empeira/infrastructure/service.rb +237 -0
  99. data/lib/empeira/infrastructure/status.rb +73 -0
  100. data/lib/empeira/infrastructure/store.rb +274 -0
  101. data/lib/empeira/modules/events.rb +67 -0
  102. data/lib/empeira/modules/git_safety.rb +60 -0
  103. data/lib/empeira/modules/host_git.rb +91 -0
  104. data/lib/empeira/modules/image.rb +27 -0
  105. data/lib/empeira/modules/installer.rb +67 -0
  106. data/lib/empeira/modules/request.rb +68 -0
  107. data/lib/empeira/modules/state.rb +87 -0
  108. data/lib/empeira/modules/storage.rb +35 -0
  109. data/lib/empeira/network/bootstrap_proxy.rb +157 -0
  110. data/lib/empeira/network/definition.rb +61 -0
  111. data/lib/empeira/network/direct_egress.rb +133 -0
  112. data/lib/empeira/network/egress.rb +27 -0
  113. data/lib/empeira/network/gateway.rb +79 -0
  114. data/lib/empeira/network/host_policy.rb +53 -0
  115. data/lib/empeira/network/naming.rb +28 -0
  116. data/lib/empeira/network/peer/adapter_image.rb +32 -0
  117. data/lib/empeira/network/peer/allocation.rb +45 -0
  118. data/lib/empeira/network/peer/attachment.rb +27 -0
  119. data/lib/empeira/network/peer/backend.rb +108 -0
  120. data/lib/empeira/network/peer/channel.rb +133 -0
  121. data/lib/empeira/network/peer/channel_server.rb +71 -0
  122. data/lib/empeira/network/peer/docker_adapter.rb +165 -0
  123. data/lib/empeira/network/peer/helper_security.rb +40 -0
  124. data/lib/empeira/network/peer/layout.rb +55 -0
  125. data/lib/empeira/network/peer/linux_podman.rb +114 -0
  126. data/lib/empeira/network/peer/podman_machine.rb +112 -0
  127. data/lib/empeira/network/policy.rb +39 -0
  128. data/lib/empeira/network/proxy_bindings.rb +52 -0
  129. data/lib/empeira/network/proxy_policy.rb +64 -0
  130. data/lib/empeira/network/redirects.rb +89 -0
  131. data/lib/empeira/network/resource.rb +50 -0
  132. data/lib/empeira/node/agent_installer.rb +31 -0
  133. data/lib/empeira/node/agent_package.rb +125 -0
  134. data/lib/empeira/node/agent_repository.rb +160 -0
  135. data/lib/empeira/node/apt_configuration.rb +85 -0
  136. data/lib/empeira/node/bootstrap.rb +17 -0
  137. data/lib/empeira/node/certificates.rb +98 -0
  138. data/lib/empeira/node/command_mock.rb +38 -0
  139. data/lib/empeira/node/command_mocks.rb +114 -0
  140. data/lib/empeira/node/container.rb +134 -0
  141. data/lib/empeira/node/container_agent_bootstrap.rb +43 -0
  142. data/lib/empeira/node/container_bootstrap.rb +52 -0
  143. data/lib/empeira/node/container_guest.rb +42 -0
  144. data/lib/empeira/node/container_lifecycle.rb +145 -0
  145. data/lib/empeira/node/container_preparation.rb +144 -0
  146. data/lib/empeira/node/container_state.rb +129 -0
  147. data/lib/empeira/node/definition.rb +20 -0
  148. data/lib/empeira/node/diagnostic_command.rb +19 -0
  149. data/lib/empeira/node/dnf_agent_repository.rb +202 -0
  150. data/lib/empeira/node/external_fact.rb +21 -0
  151. data/lib/empeira/node/image.rb +38 -0
  152. data/lib/empeira/node/interactive_tools.rb +33 -0
  153. data/lib/empeira/node/interface.rb +18 -0
  154. data/lib/empeira/node/inventory.rb +143 -0
  155. data/lib/empeira/node/package_bootstrap.rb +129 -0
  156. data/lib/empeira/node/package_proxy.rb +102 -0
  157. data/lib/empeira/node/package_sources.rb +19 -0
  158. data/lib/empeira/node/puppet_command.rb +12 -0
  159. data/lib/empeira/node/rpm_configuration.rb +62 -0
  160. data/lib/empeira/node/run_request.rb +52 -0
  161. data/lib/empeira/node/runtime_proxy.rb +86 -0
  162. data/lib/empeira/node/service.rb +72 -0
  163. data/lib/empeira/node/ssh_client.rb +25 -0
  164. data/lib/empeira/node/ssh_credentials.rb +78 -0
  165. data/lib/empeira/node/ssh_endpoint.rb +58 -0
  166. data/lib/empeira/node/ssh_identity.rb +37 -0
  167. data/lib/empeira/node/user_ssh.rb +60 -0
  168. data/lib/empeira/node/vm.rb +89 -0
  169. data/lib/empeira/node/vm_bootstrap.rb +53 -0
  170. data/lib/empeira/node/vm_lifecycle.rb +212 -0
  171. data/lib/empeira/node/vm_preflight.rb +37 -0
  172. data/lib/empeira/node/vm_preparation.rb +161 -0
  173. data/lib/empeira/node/vm_state.rb +161 -0
  174. data/lib/empeira/platform/facts.rb +66 -0
  175. data/lib/empeira/platform/kvm.rb +91 -0
  176. data/lib/empeira/platform/locations.rb +51 -0
  177. data/lib/empeira/platform/resolvers.rb +85 -0
  178. data/lib/empeira/platform/routes.rb +50 -0
  179. data/lib/empeira/progress.rb +70 -0
  180. data/lib/empeira/project_root.rb +47 -0
  181. data/lib/empeira/providers/lifecycle.rb +96 -0
  182. data/lib/empeira/providers/registry.rb +21 -0
  183. data/lib/empeira/providers/resource.rb +32 -0
  184. data/lib/empeira/providers.rb +55 -0
  185. data/lib/empeira/runtime/agent_helper.rb +34 -0
  186. data/lib/empeira/runtime/container.rb +184 -0
  187. data/lib/empeira/runtime/diagnostic_execution.rb +52 -0
  188. data/lib/empeira/runtime/docker.rb +87 -0
  189. data/lib/empeira/runtime/image_metadata.rb +83 -0
  190. data/lib/empeira/runtime/interface.rb +8 -0
  191. data/lib/empeira/runtime/mount_probe.rb +57 -0
  192. data/lib/empeira/runtime/podman.rb +122 -0
  193. data/lib/empeira/runtime/recipe_build.rb +103 -0
  194. data/lib/empeira/runtime/registry_diagnostics.rb +23 -0
  195. data/lib/empeira/runtime/service_arguments.rb +55 -0
  196. data/lib/empeira/runtime/service_images.rb +118 -0
  197. data/lib/empeira/runtime/service_inspection.rb +159 -0
  198. data/lib/empeira/runtime/services.rb +120 -0
  199. data/lib/empeira/runtime/update_helper.rb +97 -0
  200. data/lib/empeira/runtime/workspace_network.rb +69 -0
  201. data/lib/empeira/server/environment_configuration.rb +53 -0
  202. data/lib/empeira/server/http.rb +38 -0
  203. data/lib/empeira/server/puppetdb_runtime.rb +74 -0
  204. data/lib/empeira/server/relay_certificate.rb +76 -0
  205. data/lib/empeira/server/runtime.rb +90 -0
  206. data/lib/empeira/services/definition.rb +42 -0
  207. data/lib/empeira/updates/images.rb +104 -0
  208. data/lib/empeira/updates/modules.rb +45 -0
  209. data/lib/empeira/updates/service.rb +45 -0
  210. data/lib/empeira/version_requirement.rb +23 -0
  211. data/lib/empeira/vm/agent.rb +66 -0
  212. data/lib/empeira/vm/bootstrap_requirements.rb +69 -0
  213. data/lib/empeira/vm/certificate_runtime.rb +30 -0
  214. data/lib/empeira/vm/cloud_init.rb +171 -0
  215. data/lib/empeira/vm/disk.rb +136 -0
  216. data/lib/empeira/vm/guest.rb +123 -0
  217. data/lib/empeira/vm/guest_channel.rb +110 -0
  218. data/lib/empeira/vm/guest_network.rb +94 -0
  219. data/lib/empeira/vm/guest_output.rb +52 -0
  220. data/lib/empeira/vm/guest_shell.rb +79 -0
  221. data/lib/empeira/vm/guest_upload.rb +43 -0
  222. data/lib/empeira/vm/image_cache.rb +77 -0
  223. data/lib/empeira/vm/image_source.rb +91 -0
  224. data/lib/empeira/vm/interface.rb +8 -0
  225. data/lib/empeira/vm/interface_ownership.rb +124 -0
  226. data/lib/empeira/vm/interface_routes.rb +101 -0
  227. data/lib/empeira/vm/interfaces.rb +240 -0
  228. data/lib/empeira/vm/management.rb +73 -0
  229. data/lib/empeira/vm/prerequisites.rb +42 -0
  230. data/lib/empeira/vm/qemu.rb +83 -0
  231. data/lib/empeira/vm/qemu_runtime.rb +404 -0
  232. data/lib/empeira/vm/removal.rb +30 -0
  233. data/lib/empeira/vm/root_disk.rb +92 -0
  234. data/lib/empeira/vm/session_lock.rb +56 -0
  235. data/lib/empeira/vm/system_ssh.rb +29 -0
  236. data/lib/empeira/workspace.rb +42 -0
  237. data/lib/empeira.rb +209 -0
  238. data/resources/browser/relay.rb +22 -0
  239. data/resources/gateway/Containerfile +8 -0
  240. data/resources/gateway/bridge.rb +66 -0
  241. data/resources/gateway/configure.rb +323 -0
  242. data/resources/modules/Containerfile +15 -0
  243. data/resources/modules/Gemfile +5 -0
  244. data/resources/modules/Gemfile.lock +105 -0
  245. data/resources/modules/worker.rb +161 -0
  246. data/resources/network/adapter/Containerfile +13 -0
  247. data/resources/network/adapter/bridge_linux.go +51 -0
  248. data/resources/network/adapter/framing.go +45 -0
  249. data/resources/network/adapter/framing_test.go +53 -0
  250. data/resources/network/adapter/go.mod +3 -0
  251. data/resources/network/adapter/main_linux.go +273 -0
  252. data/resources/network/channel.rb +18 -0
  253. data/resources/network/ssh_connect.rb +32 -0
  254. data/resources/nodes/apt_sources.rb +66 -0
  255. data/resources/nodes/debian/Containerfile +10 -0
  256. data/resources/nodes/diagnostic_exec.rb +109 -0
  257. data/resources/nodes/interactive_tools.rb +139 -0
  258. data/resources/nodes/managed_file.rb +91 -0
  259. data/resources/nodes/management/guest_agent.py +415 -0
  260. data/resources/nodes/management/setup.sh +20 -0
  261. data/resources/nodes/management/shell_rc.sh +12 -0
  262. data/resources/nodes/management/system_account.sh +24 -0
  263. data/resources/nodes/management/system_home.sh +15 -0
  264. data/resources/nodes/rpm/Containerfile +10 -0
  265. data/resources/nodes/runtime_proxy.rb +244 -0
  266. data/resources/nodes/ssh/proxy.rb +16 -0
  267. data/resources/nodes/ssh/sshd_config +15 -0
  268. data/resources/nodes/ssh/start +6 -0
  269. data/resources/proxy/Containerfile +10 -0
  270. data/resources/relay/Containerfile +5 -0
  271. data/resources/relay/puppetdb.rb +81 -0
  272. data/resources/relay/server_environment.rb +10 -0
  273. data/resources/relay/server_start.sh +41 -0
  274. metadata +326 -0
data/docs/addons.md ADDED
@@ -0,0 +1,331 @@
1
+ # Standalone Git addons (API 1)
2
+
3
+ Empeira remains a local Puppet/OpenVox test environment. Addons expose independent
4
+ helper programs through its CLI, for example a converter, data synchronization
5
+ tool or Git hook installer. Core does not import their Ruby classes or know their
6
+ functionality. There is no plugin runtime, registry, package manager, dependency
7
+ installer or lifecycle hook. No production helper tools ship with Empeira.
8
+
9
+ ## Companion addons
10
+
11
+ These independently maintained addons can be installed through Empeira but are
12
+ not bundled with the core application. See each repository for installation and
13
+ usage details.
14
+
15
+ | Addon | Description |
16
+ | --- | --- |
17
+ | [Convert](https://github.com/c8m6/empeira-addon-convert) | Convert YAML, JSON, XML, Java Properties and Puppet hashes, and generate EPP templates with typed defaults. |
18
+ | [Pre-Commit](https://github.com/c8m6/empeira-addon-pre-commit) | Manage Git pre-commit hooks for control and Hiera repositories using locally built Docker or Podman images. |
19
+ | [Sync](https://github.com/c8m6/empeira-addon-sync) | Synchronize configured files and directories with host rsync, using source and destination to define direction. |
20
+
21
+ ## Configuration and commands
22
+
23
+ Declare addons in the existing effective configuration, normally the control
24
+ repository's `.empeira.yaml`:
25
+
26
+ ```yaml
27
+ addons:
28
+ converter:
29
+ git: https://github.com/example/empeira-converter.git
30
+ ref: v1.2.0
31
+ config:
32
+ output_format: yaml
33
+ paths: [data, defaults]
34
+ git-hooks:
35
+ git: git@example.org:team/empeira-git-hooks.git
36
+ ref: main
37
+ ```
38
+
39
+ Each entry requires `git` and `ref` and may optionally contain `config`, a mapping
40
+ of addon-specific settings. The addon itself interprets those settings; the
41
+ `converter` example above is illustrative, not a built-in Empeira option.
42
+ Config keys use lowercase letters, digits and underscores, starting with a letter;
43
+ values may be nested mappings, arrays or simple YAML scalars. Do not put secrets
44
+ in addon config: it is visible in project configuration and the addon's environment.
45
+ `git` and `ref` must be nonempty strings after merging. Names start with a lowercase letter and contain lowercase letters,
46
+ digits, underscores or hyphens, up to 63 characters. Built-in defaults contain
47
+ `addons: {}`. Mappings deep-merge and project overrides may replace just a ref.
48
+ Normal defaults/project/user/CLI precedence and validation remain in force.
49
+ The existing user preference file does **not** accept addon definitions. There is
50
+ no additional user configuration file and no executable or completion settings
51
+ in Empeira configuration.
52
+
53
+ ```console
54
+ empeira addons list
55
+ empeira addons info converter
56
+ empeira addons sync
57
+ empeira addons update
58
+ empeira addon converter convert input.xml output.json
59
+ ```
60
+
61
+ `addons list` inspects configured names and reports missing, installed or invalid
62
+ local installations. `addons info NAME` shows the source, configured ref, resolved
63
+ commit, absolute installation path and complete validated manifest. Neither
64
+ command downloads anything. Invalid installations are reported explicitly.
65
+
66
+ `addons sync` and `addons update` synchronize all configured sources and refs.
67
+ Both use the same explicit refresh semantics as `update modules`: branches and
68
+ moved tags follow the fetched source; a commit reference selects that commit.
69
+ They attempt every configured addon and return failure if any cannot be installed.
70
+ Already published versions survive failed updates. They need host Git and network
71
+ access to the configured sources, but no container engine, server, node, network,
72
+ proxy or infrastructure inventory.
73
+
74
+ `addon NAME ...` runs exactly one executable. Everything after NAME, including
75
+ `--help`, `--version`, `--`, empty arguments and shell metacharacters, is passed
76
+ unchanged. Core options belong **before** `addon`, for example
77
+ `empeira --container-engine docker addon example ...`. Manifest commands describe
78
+ completion; they do not restrict the executable's argument parser or validate its
79
+ arguments. Singular `addon` executes; plural `addons` manages.
80
+
81
+ Direct execution automatically installs a missing ref. Subsequent executions use
82
+ the locally installed ref without network freshness checks. `up` also installs
83
+ missing refs, after the Puppet infrastructure is ready. An addon failure makes
84
+ `up` return failure with a separate addon diagnostic; the successfully prepared
85
+ Puppet infrastructure remains usable. Invalid addon manifests do not affect Core
86
+ commands. Invalid Empeira configuration still follows normal configuration errors.
87
+ `update images`, `update modules` and the currently unavailable `update all` retain
88
+ their existing behavior; addon updates use `addons update`.
89
+
90
+ ## Git references and storage
91
+
92
+ Empeira reuses the Puppetfile host Git acquisition boundary, including native
93
+ SSH configuration, agents, HTTPS credential helpers and sanitized Git diagnostics.
94
+ Sources support HTTP(S), SSH URLs and SSH scp syntax. Do not embed HTTP credentials,
95
+ URL query strings or fragments; authentication belongs to host Git. Local fixture
96
+ servers are used in tests, without external Git services.
97
+
98
+ `ref` uses the Puppetfile/r10k native Git resolution, `ref^{commit}`: branch, tag,
99
+ full or unambiguous abbreviated commit, or `HEAD`. It is not a semantic version
100
+ constraint. Unknown refs fail. API 1 requires an explicit ref (`HEAD` is the
101
+ explicit equivalent of Puppetfile's default). It does not support Forge versions
102
+ or Puppetfile Ruby symbols such as `:control_branch`.
103
+
104
+ Data lives under `~/.empeira/addons/`, resolved through the existing platform home
105
+ abstraction on Linux, macOS and WSL2. Each source has a SHA-256 namespace containing
106
+ a bare host mirror, immutable commit directories, hashed reference pointers and a
107
+ stable lock file. Equal source strings and commits can be reused across names,
108
+ refs and workspaces. Different URLs never collide simply because their addon names
109
+ match. Different versions remain available in parallel. No cache eviction is
110
+ automatic; do not remove a cache while an addon or synchronization is using it.
111
+
112
+ Acquisition and publication use an exclusive per-source lock. New mirrors and
113
+ revisions are staged privately, checked and atomically renamed. A reference is
114
+ published only after manifest and executable validation. Interrupted staging
115
+ directories are never considered installed. The control repository is not used as
116
+ an installation destination and its tracked files are not modified.
117
+ If the configured user home or a cache symlink would put the cache inside the
118
+ control repository, installation is rejected before writing there.
119
+
120
+ Snapshots are materialized by native Git with a disposable index, without a `.git`
121
+ working directory. Local checkout ignores inherited Git configuration and disables
122
+ hooks, so filters, submodules, addon entrypoints and install scripts are not run.
123
+ Tracked files keep their Git content and executable bits. Addons must ship
124
+ everything needed for execution or document prerequisites the user installs
125
+ separately.
126
+
127
+ ## Manifest contract
128
+
129
+ Every addon repository has exactly one root-level `empeira-addon.yaml` and one
130
+ executable entrypoint:
131
+
132
+ ```text
133
+ empeira-addon-example/
134
+ ├── empeira-addon.yaml
135
+ ├── bin/
136
+ │ └── example
137
+ └── README.md
138
+ ```
139
+
140
+ ```yaml
141
+ schema: 1
142
+ description: Convert configuration files
143
+ executable: bin/example
144
+ commands:
145
+ convert:
146
+ description: Convert a file
147
+ arguments:
148
+ - name: input
149
+ completion: file
150
+ - name: output
151
+ completion: file
152
+ options:
153
+ --format:
154
+ description: Output format
155
+ completion: enum
156
+ values: [yaml, text]
157
+ --input:
158
+ completion: file
159
+ --directory:
160
+ completion: directory
161
+ --verbose:
162
+ completion: none
163
+ validate:
164
+ description: Validate input
165
+ arguments:
166
+ - name: input
167
+ completion: file
168
+ ```
169
+
170
+ Required root fields are `schema` (integer 1), `description`, `executable` and
171
+ `commands` (a mapping, possibly empty). Every command requires a description;
172
+ `arguments` (ordered array) and `options` (mapping) are optional. Arguments require
173
+ `name` and `completion`. Option names are explicit `-x` or `--long-name` spellings,
174
+ with optional descriptions. An option has one value when its completion is `file`,
175
+ `directory` or `enum`; `none` declares a flag without a value. To provide short and
176
+ long forms, declare each spelling. `enum` requires a nonempty string list `values`;
177
+ other types must not have `values`. `none` on an argument disables its completion.
178
+ Names and descriptions cannot contain control characters. Unknown fields, YAML
179
+ object tags, aliases, duplicate keys and unsupported schema versions are rejected.
180
+
181
+ There is no nested command framework, dynamic completion, option bundling or
182
+ variadic argument schema. The program may implement its own richer CLI; Empeira
183
+ offers completion only for the declared interface.
184
+
185
+ The entrypoint must be a regular executable inside the addon directory. Absolute
186
+ paths, `.`/`..` path components, and symlinks escaping that directory are rejected,
187
+ including an escaping manifest symlink. Keep the executable bit in Git. Scripts
188
+ need a shebang naming an installed interpreter; native binaries must be compatible
189
+ with the host. Empeira does not invoke a shell command string or interpolate argv.
190
+
191
+ ## Environment API
192
+
193
+ Configuration is freshly loaded and merged for every invocation. Empeira exports
194
+ its approved fields as `EMPEIRA_CONFIG_*`, without JSON export or temporary
195
+ configuration files. Keys become uppercase, with punctuation replaced by `_`.
196
+
197
+ | Value | Export |
198
+ | --- | --- |
199
+ | String | Original value, including an empty string |
200
+ | Integer/float | String representation |
201
+ | Boolean | `true` / `false` |
202
+ | Mapping | Recursively flattened keys |
203
+ | Array | `_COUNT` and zero-based indexed elements |
204
+ | Empty array | `_COUNT=0` |
205
+ | Null | No variable |
206
+
207
+ Array elements can be mappings, arrays or scalars. For example:
208
+
209
+ ```yaml
210
+ proxy:
211
+ enabled: true
212
+ global: [forge.puppet.com, github.com]
213
+ server:
214
+ mounts:
215
+ - source: /home/user/data
216
+ target: /mnt/data
217
+ readonly: true
218
+ ```
219
+
220
+ produces `EMPEIRA_CONFIG_PROXY_ENABLED=true`,
221
+ `EMPEIRA_CONFIG_PROXY_GLOBAL_COUNT=2`,
222
+ `EMPEIRA_CONFIG_PROXY_GLOBAL_0=forge.puppet.com`,
223
+ `EMPEIRA_CONFIG_PROXY_GLOBAL_1=github.com`,
224
+ `EMPEIRA_CONFIG_SERVER_MOUNTS_COUNT=1`, and the variables
225
+ `EMPEIRA_CONFIG_SERVER_MOUNTS_0_SOURCE`, `_TARGET`, `_READONLY`.
226
+
227
+ ### Per-addon configuration
228
+
229
+ Only the selected addon's `config` mapping is additionally exported as
230
+ `EMPEIRA_ADDON_CONFIG_*`. It uses the same flattening rules as the effective
231
+ configuration above. For the example, `converter` receives:
232
+
233
+ ```text
234
+ EMPEIRA_ADDON_CONFIG_OUTPUT_FORMAT=yaml
235
+ EMPEIRA_ADDON_CONFIG_PATHS_COUNT=2
236
+ EMPEIRA_ADDON_CONFIG_PATHS_0=data
237
+ EMPEIRA_ADDON_CONFIG_PATHS_1=defaults
238
+ ```
239
+
240
+ Other addons' `config` values are **not** included in the general
241
+ `EMPEIRA_CONFIG_*` export or the selected addon's export. The optional
242
+ `config` mapping deep-merges like other project mappings; arrays replace.
243
+ No new addon API version, configuration file or install hook is needed.
244
+
245
+ Runtime context is separate:
246
+
247
+ | Variable | Meaning |
248
+ | --- | --- |
249
+ | `EMPEIRA_VERSION` | Installed build version, or `development` |
250
+ | `EMPEIRA_ADDON_API_VERSION` | `1` |
251
+ | `EMPEIRA_ADDON_NAME` | Configured logical name |
252
+ | `EMPEIRA_ADDON_VERSION` | Configured Git ref |
253
+ | `EMPEIRA_ADDON_PATH` | Absolute resolved commit snapshot directory |
254
+ | `EMPEIRA_WORKSPACE` | Absolute canonical workspace project directory |
255
+ | `EMPEIRA_CONTROL_REPO` | Absolute canonical control repository root |
256
+
257
+ Workspace means the existing project identity, not a running lab. Project-bound
258
+ CLI calls require the existing Git-root `.empeira.yaml` marker, including when
259
+ invoked from a subdirectory. No workspace infrastructure is created for an addon.
260
+ Context variables are omitted if a caller has no such context.
261
+
262
+ The schema explicitly excludes `vm.console.root_password`, `eyaml.private_key`,
263
+ `browser.start_url`, `server.runtime.startup.arguments`,
264
+ `puppetdb.runtime.java_arguments`, `puppetdb.runtime.main_arguments`, and each
265
+ additional container's `environment`, `command` and `configuration.content`.
266
+ These include opaque content which may contain secrets under ordinary names; this
267
+ is not a suspicious-key-name filter. Other validated configuration is exported.
268
+ No private key files are read for export.
269
+
270
+ Normal inherited user variables remain available. All inherited `EMPEIRA_*` names
271
+ are cleared before the authoritative configuration and runtime values are set, so
272
+ stale, forged or excluded values cannot leak through that namespace. Environment
273
+ plus argv size is checked against the OS limit, with pointer/headroom allowance
274
+ and a conservative 128-KiB per-string limit. Oversized data or null bytes fail
275
+ before execution without printing values; there is no file-based fallback.
276
+
277
+ ## Bash completion
278
+
279
+ Use the existing registration:
280
+
281
+ ```bash
282
+ source <(empeira completion bash)
283
+ ```
284
+
285
+ It completes `addons list/info/sync/update`, names after `addons info` and `addon`,
286
+ then local manifest commands, options and enum values. Positional and option
287
+ `file`/`directory` arguments use native Bash filename completion, including relative
288
+ and absolute paths, subdirectories, spaces and shell punctuation. `--option=value`
289
+ is supported as well as a separate value. `--` ends option parsing.
290
+
291
+ Completion reads only configuration and installed manifests. It never fetches Git
292
+ sources or calls an addon executable. A configured missing addon name is offered,
293
+ but its commands become available only after installation. Missing or invalid
294
+ manifests produce no addon candidates and leave Core completion usable. There is
295
+ no additional completion executable, script to install or cached file list.
296
+
297
+ ## Development, trust and troubleshooting
298
+
299
+ The complete [minimal fixture](../spec/fixtures/addon/README.md) can be copied into
300
+ a separate Git repository. It prints arguments and demonstrates all completion
301
+ types. It does not implement a production converter, data sync or Git hook tool.
302
+ Keep runtime dependencies and platform instructions in the addon's README. An
303
+ addon can read the environment in any language and uses the caller's current
304
+ directory and inherited stdin/stdout/stderr. Terminal colors and interactive input
305
+ remain available. The CLI process is replaced by the executable: exit statuses and
306
+ signal termination remain native, without an intermediary process swallowing them.
307
+ Start failures distinguish 126 (not executable) and 127 (entrypoint/interpreter
308
+ not found); actual addon failure exits are returned unchanged.
309
+
310
+ Addons run as the invoking user. They are trusted local programs, **not sandboxed**.
311
+ The export filter limits what Core supplies; it cannot prevent a trusted program
312
+ from reading files or credentials already accessible to the user. Review sources
313
+ before execution and prefer immutable commits for reproducibility. Git source
314
+ authentication continues to use the user's existing Git/SSH tools; credentials
315
+ are not copied into an addon cache by Empeira. Git diagnostics use Core redaction;
316
+ addon stdout/stderr are direct program output and remain the addon's responsibility.
317
+
318
+ Supported platforms are native Linux, native macOS and Linux in WSL2. Native
319
+ Windows is unsupported. Host Git, the normal Empeira Ruby requirements and any
320
+ addon interpreter/binary prerequisites must be installed. No Docker or Podman is
321
+ needed to install, manage or execute addons.
322
+
323
+ For a missing addon, check the effective `addons` mapping and run `addons sync`.
324
+ For an unknown ref, verify it with the source owner and correct `ref`. For Git
325
+ authentication failures, use the normal host Git/SSH configuration. For a missing
326
+ or invalid manifest, fix it upstream and update to a valid revision. For 126/127,
327
+ check the executable bit, shebang, interpreter and host architecture. If a cache
328
+ is incomplete, stop users of that source and remove only its affected source
329
+ namespace before synchronizing again; never delete a live lock as recovery.
330
+ For missing command completion, inspect `addons info NAME` and source the current
331
+ Bash completion again after an Empeira upgrade.
@@ -0,0 +1,277 @@
1
+ # Architecture
2
+
3
+ Empeira has one control-plane lifecycle and interchangeable infrastructure adapters.
4
+ The CLI presents output and calls `Application`; the core loads without Thor.
5
+ `Application` composes a canonical Git project, immutable effective configuration,
6
+ workspace identity, platform locations and the central execution runner. Constructing
7
+ it does not start infrastructure. Registries expose names without constructing providers.
8
+ The loader validates personal SSH preferences separately from the effective project
9
+ configuration. `Application` passes them only to the node-service login resolver;
10
+ that resolver selects user/identity fields for interactive SSH before dispatching
11
+ to the recorded provider. Internal VM transport retains its managed credentials.
12
+ A shared hostname-pattern helper supplies matching semantics without coupling SSH
13
+ preferences to proxy destination policy. Personal login values never enter context
14
+ configuration, definitions, fingerprints or inventory.
15
+
16
+ ```mermaid
17
+ flowchart TD
18
+ CLI[CLI and completion] --> App[Application and context]
19
+ App --> Config[Configuration and version gate]
20
+ App --> State[Inventory and mutation lock]
21
+ App --> Plane[Control-plane lifecycle]
22
+ App --> Nodes[Node lifecycle]
23
+ Plane --> Runtime[Docker / Podman]
24
+ Nodes --> Container[Container provider]
25
+ Nodes --> VM[VM provider]
26
+ Container --> Runtime
27
+ VM --> QEMU[QEMU: KVM / HVF]
28
+ VM --> Images[Immutable image cache and QCOW2 overlays]
29
+ VM --> Peer[Replaceable peer-network backend]
30
+ Peer --> Runtime
31
+ Runtime --> Runner[Execution runner]
32
+ QEMU --> Runner
33
+ ```
34
+
35
+ ## Software and provider boundaries
36
+
37
+ | Boundary | Responsibility |
38
+ | --- | --- |
39
+ | Configuration | Safe YAML, deep merge, complete-path validation and permitted overrides |
40
+ | Platform | Host capabilities, architecture, home, state/cache locations and DNS discovery |
41
+ | Container runtime | Owned OCI containers, networks, volumes, images and runtime transport |
42
+ | Server runtime | Validated startup, paths and semantic environment mapping |
43
+ | Node provider | Common identity, bootstrap, enrollment and lifecycle for `container` or `vm` |
44
+ | VM engine | QEMU process, acceleration, console and overlay lifecycle |
45
+ | Image source | Upstream metadata, integrity verification and immutable VM base images |
46
+ | Execution | Argument-array processes, timeouts, streaming and diagnostic redaction |
47
+ | Addons | Declarative manifests, user Git snapshots and standalone process handoff; no infrastructure dependency |
48
+
49
+ VM interface rules resolve through Configuration using the shared hostname glob
50
+ matcher. The VM provider invokes the guest interface reconciler through its existing
51
+ VirtIO management channel after agent installation and on start/up. Device intent
52
+ and recovery definitions live in the locked node inventory;
53
+ atomically assigned locally administered MAC addresses and verified aliases bind
54
+ devices to workspace/node ownership. Guest observations establish device type,
55
+ parent, address and routes before mutation. The VM engine,
56
+ QEMU NIC topology, peer adapters and gateway policy remain independent of these
57
+ guest-only devices. See [interface lifecycle](nodes.md#additional-vm-interface-lifecycle).
58
+
59
+ OpenVox is the default software. `images.server`, `images.puppetdb` and
60
+ `images.postgres` select OCI images independently. `server.runtime` and
61
+ `puppetdb.runtime` describe compatible startup contracts without classifying image
62
+ names. Agent selection is independent of those images. Container recipes add base
63
+ tools. A shared agent acquisition component resolves native source metadata in an
64
+ owned, short-lived runtime helper, validates the package and publishes it atomically
65
+ in the user cache. Cache identity includes the native version, target and source.
66
+ Valid hits use local integrity and configured checksum checks and work offline,
67
+ including artifacts from authenticated sources. HTTP authentication and login occur
68
+ only during acquisition. Both node providers upload the same host artifact and
69
+ install it locally through native guest package managers. Bootstrap verifies cleanup
70
+ and enrolls certificates before Puppet runs. Cloud-Init is a VM delivery mechanism only.
71
+ VM disk preparation creates and resizes a private QCOW2 overlay, verifies its exact
72
+ backing image and configured virtual capacity, then publishes it atomically. The
73
+ immutable base is unchanged. First-boot readiness observes the actual mounted root
74
+ partition and filesystem after cloud-init growth, before managed package bootstrap.
75
+ Resume and workspace reconciliation preserve existing disk capacities.
76
+
77
+ Both providers reconcile normal guest proxy intent in the existing node inventory
78
+ after bootstrap cleanup. The shared reconciler uses the public control-plane
79
+ proxy environment, owned APT files and a bounded DNF block; provider transports
80
+ only execute guest operations. Managed Puppet receives explicit `env` arguments
81
+ inside the root guest command, preserving the runtime proxy policy.
82
+
83
+ Interactive tool PATH uses the same provider guest transports and node inventory.
84
+ Its startup fragments apply only to interactive Bash, with a small owned block
85
+ in the distribution's global initializer. Personal dotfiles and non-interactive
86
+ process environments stay under their existing owners.
87
+
88
+ VMs seed a Python 3 standard-library execution adapter through Cloud-Init, before
89
+ project scripts and package bootstrap. The private VirtIO-Serial channel has no
90
+ network listener or guest account authentication. A systemd service runs with
91
+ numeric UID/GID 0, independently of root shadow, PAM, NSS login policy, sudoers and
92
+ system SSH. Guest commands use argument arrays and owned process groups; stdout
93
+ and stderr stream in bounded frames without truncation. Timeouts, interrupts and
94
+ host disconnects terminate those groups. Binary uploads are SHA-256 verified,
95
+ staged privately and atomically installed in the real guest filesystem.
96
+
97
+ The QEMU monitor verifies workspace/node/instance identity before management.
98
+ A 0700 host directory and 0600 Unix socket restrict access; recorded device/inode
99
+ identity rejects replaced sockets. Sessions also verify the instance identity and
100
+ operation ID. No management NIC, host port, additional privileged host helper or
101
+ SSH credentials are needed. Existing workspace locks and VM session guards apply.
102
+ `node puppet` uses this targeted path without server readiness or full guest
103
+ reconciliation.
104
+
105
+ The user SSH adapter connects directly to the guest's peer IP through the owned
106
+ peer attachment and its isolated TCP client, independently of management. Personal
107
+ user/key/port resolution
108
+ applies only to `node ssh`; the default guest port is 22. The locked `empeira`
109
+ system account and `/var/lib/empeira` home are seeded once for regular SSH, without
110
+ sudo. Puppet may remove or change them. Empeira never unlocks root or restores
111
+ Puppet-managed authentication. Unsupported VM layouts require explicit recreation;
112
+ there is no migration, fallback transport or automatic resource deletion.
113
+
114
+ Nodes share lifecycle contracts: create rejects an existing name, stop preserves
115
+ state, start resumes it, and destroy removes owned state. Already satisfied lifecycle
116
+ operations return `changed: false`; absent start/stop and ownership conflicts fail.
117
+ Names are unique across both providers under the shared workspace transaction.
118
+ The structured Empeira classification fact contains `facts.empeira.provider` and
119
+ the fixed `facts.empeira.hierarchy: empeira` Hiera selector.
120
+
121
+ ## Persistent state and ownership
122
+
123
+ | Location | Linux / WSL2 | macOS |
124
+ | --- | --- | --- |
125
+ | Cache | `$XDG_CACHE_HOME/empeira` or `~/.cache/empeira` | `~/Library/Caches/empeira` |
126
+ | State | `$XDG_STATE_HOME/empeira` or `~/.local/state/empeira` | `~/Library/Application Support/empeira` |
127
+ | Temporary work | Ruby `Dir.tmpdir` | Ruby `Dir.tmpdir` |
128
+
129
+ Relative XDG locations are ignored. Workspace identity hashes the canonical checkout
130
+ and user-home paths; moving a checkout changes its identity. Destroy needed resources
131
+ before moving it, or preserve its original path for cleanup.
132
+
133
+ `workspaces/ID/infrastructure.json` records the owning runtime, logical resources,
134
+ observed backend IDs, service/volume/node inventories, peer allocation and definition
135
+ fingerprints. Only the current schema is accepted. Atomic mode-0600 writes use
136
+ flush/fsync and rename. Invalid or unreadable state fails closed.
137
+
138
+ Resources carry `io.empeira.managed-by`, `io.empeira.workspace` and purpose labels.
139
+ Volumes also have a random identity label. Names alone never authorize adoption or
140
+ deletion. Mutations validate recorded IDs, labels and isolation, and persist creation
141
+ intent before changing runtime resources. Uncertain outcomes are inspected before
142
+ retry. Missing retained storage or database credentials never initialize replacement
143
+ data implicitly.
144
+
145
+ The runtime is authoritative for observed existence; inventory supplies ownership
146
+ and recovery context. Runtime migration is explicit: destroy with the owning engine,
147
+ then change configuration. `down` preserves CA and database storage; `destroy`
148
+ removes them. Both preserve control code, modules and caches.
149
+
150
+ All mutations share a nonblocking filesystem `flock` and version gate. Read-only
151
+ status takes no exclusive lock. The lock inode stays stable; never delete its file
152
+ as stale-lock recovery. Unsupported state has no automatic migration.
153
+
154
+ ## Definitions and live code
155
+
156
+ Canonical SHA-256 fingerprints cover relevant managed definitions, independently
157
+ of application versions, YAML formatting and unrelated node defaults. Component
158
+ fingerprints allow selective service reconciliation while preserving owned storage.
159
+ Each `up` batches service and volume observations and reuses them only within that
160
+ locked transaction. Starts, replacements and network attachments are inspected
161
+ again; destructive operations verify fresh ownership and recorded IDs. Later CLI
162
+ invocations never inherit these observations. Local image inspection results are
163
+ shared by availability, reviewed-recipe, ID and architecture checks within one
164
+ image-preparation block. A pull or build discards the old result before the new
165
+ image is inspected. `up` performs no remote freshness check or full image-content
166
+ hash; native image storage and immutable IDs remain the source of metadata.
167
+ A recorded container is inspected directly by ID; a missing ID falls back to
168
+ name inventory to detect replacements and renamed resources. DNS discovery uses the complete service observation and
169
+ one node snapshot, preserving unchanged generated files. Proxy allowlist changes
170
+ reload Squid after readiness without replacing its container; unchanged policy
171
+ and bindings do not trigger HUP. The existing inventory records a validated
172
+ proxy ID and small-file digest only after native reload success; failed reloads
173
+ are retried even if generated files are already current. Gateway lockdown is
174
+ reused only while the same instance remains locked in that transaction. Policy apply clears that checkpoint
175
+ before execution so every uncertain outcome attempts fresh lockdown. Firewall
176
+ policy and namespace routing are still reconciled on every `up`.
177
+ Node access verifies network ownership and isolation with one bounded native network inspection; membership counts are collected separately
178
+ for lifecycle observations and deletion, never inferred from the access check.
179
+ VM acquisition and restart still read the complete immutable base and verify its
180
+ SHA-256 pin, using the existing OpenSSL implementation. Neither file timestamps
181
+ nor cached fingerprints replace these integrity checks.
182
+ Network changes and unsafe storage transitions require explicit lifecycle operations;
183
+ there are no implicit database-major migrations or automatic node replacements.
184
+
185
+ Managed servers disable environment caching through native Puppet configuration
186
+ (`environment_timeout = 0` in `[server]`), applied by the startup wrapper before
187
+ the configured image entrypoint. `up` checks the actual native setting and restarts
188
+ only a server whose setting changed; it preserves every other configuration key
189
+ and retained storage. The configured Puppet confdir must agree with Puppetserver
190
+ HOCON `master-conf-dir`. Catalog
191
+ requests reload live code without repository hashes or environment-cache checkpoints.
192
+ See [nodes](nodes.md#environment-cache-and-live-code).
193
+
194
+ Command mocks use the same node inventory, generated-file mechanism and mutation
195
+ lock. They reconcile atomically during initial node provisioning and start/up, preserving
196
+ unchanged files and removing only verified owned stubs.
197
+
198
+ ## Runtime and update planes
199
+
200
+ The control plane manages the server, database, DNS, workspace gateway and optional
201
+ proxy, browser and additional infrastructure services. Readiness probes test real
202
+ DNS, server, SQL and PuppetDB responses. Shared naming and peer backends preserve
203
+ container/VM connectivity and isolation. Network capabilities and isolation must
204
+ be verifiable; there is no host-network or software-emulation fallback.
205
+ See [networking](networking.md) for the privilege and trust boundaries.
206
+
207
+ `browser` validates existing infrastructure and reconciles only Chromium and its
208
+ fixed loopback UI relay. Additional services use ordinary ownership, DNS and
209
+ fingerprints, with a deliberately narrow configuration schema.
210
+
211
+ Exact DNS rewrites share the existing CoreDNS configuration and service-discovery
212
+ hosts file. Reloadable rewrite policy stays outside the DNS container definition
213
+ fingerprint, so `up` can reload it without invalidating container/VM resolver
214
+ bindings. Target lookups remain authoritative for `empeira.internal` and never
215
+ fall through externally. No separate DNS inventory or provider-specific state is
216
+ introduced. DNS image/definition replacement retains its existing node protection.
217
+
218
+ Transparent TCP and UDP DNS redirects reuse the same gateway policy, fingerprint
219
+ and discovery boundary. Exact IP/protocol/port tuples are translated to observed
220
+ internal services
221
+ with DNAT/SNAT; unresolved targets remain denied. Reconciliation blocks stale targets
222
+ before recreation and publishes their new addresses after service startup. Changed
223
+ NAT revokes namespace-local connection state while DROP is active. Unchanged rules
224
+ preserve connections and node identities. DNS and proxy policy are independent;
225
+ there is no additional service-IP inventory or provider-specific redirect plane.
226
+
227
+ Updates form a separate plane. `update modules` uses host Git acquisition and a
228
+ disposable r10k helper; `update images` uses native registry metadata and cached
229
+ pulls/builds. Helpers use the runtime's normal bridge, require no infrastructure
230
+ inventory or workspace services, and are removed after each invocation. Git/SSH and
231
+ registry credentials remain owned by their native tools. `up` acquires missing
232
+ images only and never synchronizes modules. See [Hiera and modules](hiera-and-eyaml.md).
233
+
234
+ Standalone [addons](addons.md) reuse `Application`'s configuration and project
235
+ context without constructing runtime services. `Modules::HostGit` acquires mirrors
236
+ with the user's native authentication; addon acquisition disables hooks and
237
+ submodules. Native Git resolves the same `ref^{commit}` expression used by r10k,
238
+ without its container helper. Native Git materializes local snapshots using a
239
+ disposable index with configured filters and hooks disabled; publication is atomic
240
+ under a per-source user-cache lock.
241
+ They do not participate in infrastructure inventory or component fingerprints.
242
+ The execution runner hands off directly to the entrypoint, and the existing
243
+ completion catalog/Bash adapter reads its manifest. `up` prepares missing addons
244
+ after infrastructure readiness and reports addon failures independently.
245
+
246
+ ## Diagnostic access and recovery
247
+
248
+ Exec and VM PTY shells extend the existing framed VirtIO guest adapter. Eight private
249
+ VirtIO-Serial ports have pinned socket identities and exclusive session slots; each
250
+ bounded worker owns its process session and PTY. Idle workers block on I/O. Commands,
251
+ uploads and interactive sessions retain one instance token and per-session identity
252
+ without a management network or host daemon. A local raw terminal forwards bytes,
253
+ resize and normal guest job control; cancellation kills the owned session groups.
254
+ Container exec uses native numeric-root execution and a guest Ruby supervisor with a
255
+ private completion receipt so native-client errors cannot impersonate guest exits.
256
+
257
+ Recovery uses the same workspace lock/inventory and existing runtime, QEMU monitor
258
+ and peer teardown adapters. It reads ownership fields without operational schema,
259
+ version, guest, base-image or current configuration gates. Every mutation still
260
+ checks native identity and safe paths. Resource records are removed only after confirmed
261
+ removal; partial failures retain evidence and continue independent cleanup. Retired
262
+ public certificate fingerprints permit later verified hostname reuse without requiring
263
+ a live CA during node removal. The mutation-lock inode remains stable after destroy.
264
+
265
+ ## Execution and secrets
266
+
267
+ All external processes use `Execution::Runner` with explicit arguments, environment,
268
+ directory and timeout. Capture and streaming share owned-process cleanup on Linux,
269
+ macOS and WSL2. Debug logs redact sensitive values; captured output is untrusted and
270
+ failure diagnostics are filtered and bounded. Interactive shell, SSH and console
271
+ operations suspend progress rendering.
272
+
273
+ Keys and generated credentials stay in private external workspace storage or owned
274
+ volumes. Agent repository credentials use only the two documented process variables;
275
+ bootstrap delivers them through temporary 0600 files and verifies their removal.
276
+ Repository/proxy credentials and private keys stay out of image layers, inventories,
277
+ fingerprints and public release assets.