ace-lab 0.9.0 → 0.13.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +399 -102
  3. data/README.md +66 -100
  4. data/docs/usage.md +563 -592
  5. data/lib/ace/lab/atoms/install_schema.rb +444 -0
  6. data/lib/ace/lab/cli/commands/install.rb +161 -0
  7. data/lib/ace/lab/cli/commands/prepare.rb +120 -0
  8. data/lib/ace/lab/cli/commands/project_add.rb +101 -0
  9. data/lib/ace/lab/cli/commands/status.rb +109 -0
  10. data/lib/ace/lab/cli/commands/verify.rb +115 -0
  11. data/lib/ace/lab/cli.rb +21 -23
  12. data/lib/ace/lab/install_manifest_path.rb +23 -0
  13. data/lib/ace/lab/models/status_report.rb +38 -0
  14. data/lib/ace/lab/molecules/account_provisioner.rb +196 -0
  15. data/lib/ace/lab/molecules/config_state_classifier.rb +145 -0
  16. data/lib/ace/lab/molecules/directory_provisioner.rb +208 -0
  17. data/lib/ace/lab/molecules/gemset_provisioner.rb +222 -0
  18. data/lib/ace/lab/molecules/grant_provisioner.rb +250 -0
  19. data/lib/ace/lab/molecules/grant_resolver.rb +34 -23
  20. data/lib/ace/lab/molecules/hitl_routes.rb +263 -0
  21. data/lib/ace/lab/molecules/install_manifest_loader.rb +64 -0
  22. data/lib/ace/lab/molecules/key_provisioner.rb +736 -0
  23. data/lib/ace/lab/molecules/lab_state_scanner.rb +55 -0
  24. data/lib/ace/lab/molecules/overseer_provisioner.rb +188 -0
  25. data/lib/ace/lab/molecules/project_registry.rb +251 -0
  26. data/lib/ace/lab/molecules/repository_provisioner.rb +141 -0
  27. data/lib/ace/lab/molecules/role_verifier.rb +223 -0
  28. data/lib/ace/lab/organisms/install.rb +218 -0
  29. data/lib/ace/lab/organisms/prepare.rb +170 -0
  30. data/lib/ace/lab/organisms/project_add.rb +170 -0
  31. data/lib/ace/lab/organisms/status_service.rb +70 -0
  32. data/lib/ace/lab/organisms/verify.rb +836 -0
  33. data/lib/ace/lab/version.rb +1 -1
  34. data/lib/ace/lab.rb +34 -6
  35. metadata +35 -108
  36. data/lib/ace/lab/atoms/protected_publication_input.rb +0 -50
  37. data/lib/ace/lab/atoms/protected_workspace_prune_input.rb +0 -114
  38. data/lib/ace/lab/atoms/protected_workspace_prune_preview.rb +0 -86
  39. data/lib/ace/lab/atoms/service_input.rb +0 -92
  40. data/lib/ace/lab/cli/commands/assignment.rb +0 -92
  41. data/lib/ace/lab/cli/commands/command_execute.rb +0 -28
  42. data/lib/ace/lab/cli/commands/run.rb +0 -65
  43. data/lib/ace/lab/cli/commands/service.rb +0 -240
  44. data/lib/ace/lab/models/service_receipt.rb +0 -18
  45. data/lib/ace/lab/molecules/command_owner_admission.rb +0 -183
  46. data/lib/ace/lab/molecules/command_policy.rb +0 -158
  47. data/lib/ace/lab/molecules/hitl_authorizer.rb +0 -63
  48. data/lib/ace/lab/molecules/ordinary_admission_resolver.rb +0 -131
  49. data/lib/ace/lab/molecules/ordinary_listener.rb +0 -262
  50. data/lib/ace/lab/molecules/ordinary_merge_read.rb +0 -45
  51. data/lib/ace/lab/molecules/ordinary_native_read.rb +0 -106
  52. data/lib/ace/lab/molecules/ordinary_owner_admission.rb +0 -152
  53. data/lib/ace/lab/molecules/ordinary_owner_config.rb +0 -232
  54. data/lib/ace/lab/molecules/ordinary_receiver_config.rb +0 -94
  55. data/lib/ace/lab/molecules/ordinary_socket.rb +0 -126
  56. data/lib/ace/lab/molecules/ordinary_worktree_read.rb +0 -216
  57. data/lib/ace/lab/molecules/protected_cleanup_owner_admission.rb +0 -163
  58. data/lib/ace/lab/molecules/protected_cleanup_owner_client.rb +0 -165
  59. data/lib/ace/lab/molecules/protected_cleanup_owner_identity.rb +0 -239
  60. data/lib/ace/lab/molecules/protected_publication_publisher.rb +0 -110
  61. data/lib/ace/lab/molecules/protected_service_handler.rb +0 -43
  62. data/lib/ace/lab/molecules/protected_service_policy.rb +0 -156
  63. data/lib/ace/lab/molecules/service_executor.rb +0 -99
  64. data/lib/ace/lab/molecules/service_policy.rb +0 -289
  65. data/lib/ace/lab/organisms/authority_composition.rb +0 -87
  66. data/lib/ace/lab/organisms/command_executor.rb +0 -220
  67. data/lib/ace/lab/organisms/command_run_client.rb +0 -131
  68. data/lib/ace/lab/organisms/ordinary_assignment_client.rb +0 -120
  69. data/lib/ace/lab/organisms/ordinary_authority_composition.rb +0 -153
  70. data/lib/ace/lab/organisms/ordinary_merge_receiver.rb +0 -679
  71. data/lib/ace/lab/organisms/ordinary_merge_request.rb +0 -456
  72. data/lib/ace/lab/organisms/protected_cleanup_owner.rb +0 -361
  73. data/lib/ace/lab/organisms/protected_service_client.rb +0 -129
  74. data/lib/ace/lab/organisms/protected_service_listener.rb +0 -181
  75. data/lib/ace/lab/organisms/protected_service_publication.rb +0 -268
  76. data/lib/ace/lab/organisms/protected_service_receiver.rb +0 -267
  77. data/lib/ace/lab/organisms/protected_service_recovery.rb +0 -185
  78. data/lib/ace/lab/organisms/protected_service_request.rb +0 -128
  79. data/lib/ace/lab/organisms/protected_service_worker.rb +0 -89
  80. data/lib/ace/lab/organisms/service_request_service.rb +0 -249
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a7d3d64373a6eaadb24dde7d501cd5c4b8e2ebfb370e054334297fc0796a8c9c
4
- data.tar.gz: 98961e2da9d0b7383cb48a3ae3a801b7018a21c8822fa95fde779c94f2ddfb87
3
+ metadata.gz: 347fa4748ebbcf945253d67f0e9f2f7de3cfd88a11abf134cf2aaf40128c9054
4
+ data.tar.gz: 934f7b130e93180ab259a0ec4efa2598980af7ce3935628bf9e295e8b4689451
5
5
  SHA512:
6
- metadata.gz: fae21d841c34b8ff24586a806778377589a476ea4e824e9716c6ff55700d207fc9a501656fe2d1712c5c8358a29e22a31a2c8fb2cde8713d464e700f6fdea795
7
- data.tar.gz: feacdf38956ff9a1c3afe9296e4cc762deba5029ece4dbc6e9d6bd3a1d6e28e825d746e29cd0f52268c39ba5fe4bd5d97a48e93d7a8f901da38ad9225db41ffc
6
+ metadata.gz: 1def278779e6c0593732ebb7872e0ee05aeaed5bacff5c6d623d13a15a1edea755cfe7bb6506edd11d2b22f8c71d8d64c3870bf9b35207395622f2f9c8ccd781
7
+ data.tar.gz: 0b459c559a716459349d6b7b96ff3dfb521413c526593549e06a18d48a1b682d0d1e006e783ffa80eea9409dabf639341e8556dcf17dcd40fc2baec3c7da9259
data/CHANGELOG.md CHANGED
@@ -7,118 +7,415 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
- ## [0.9.0] - 2026-10-16
10
+ ## [0.13.1] - 2026-10-26
11
11
 
12
12
  ### Added
13
13
 
14
- - Wire ordinary artifact retention to fresh installed project visibility and the existing exact service or canonical proposal decision; retention does not select an executor or consume publication authorization.
15
- - Expose ordinary candidate upload, assigned review export/acceptance and canonical merge evidence through retained assignment requests. Compose their proof owner with the existing merge receiver and command executor journal.
16
- - Expose ordinary driver status, bootstrap, progress, result and exact recovery commands using the selected Assign owner; authenticate coordinator replacement with bounded process-generation observation.
17
-
18
- ### Changed
19
-
20
- - Require an explicit `purpose: command` or `purpose: test` on every installed command-policy entry. Only test commands with exact candidate head/tree and canonical successful execution can authorize merge evidence; previous entries without purpose are refused.
21
-
22
- ## [0.8.0] - 2026-10-16
23
-
24
- ### Added
25
-
26
- - Compose ordinary Assign, HITL and Forgejo merge under one installed owner and journal, with readiness barriers and fresh native/project admission.
27
- - Optionally serve existing mTLS HITL beside the ordinary Unix listener under the same canonical owner, journal, proposal policy and one-use OTP vault. Validate the configured project and service identity before either listener starts.
28
- - Add retained assignment prepare/request clients and bounded canonical worktree observations. Separate stable configuration identity from changing decisions, preserve no-effect settlement after slow provider calls, and clean delivery handler process groups.
29
- - This source cut enables initial installation; it does not claim autonomous release or installed two-project acceptance.
30
-
31
- ## [0.7.0] - 2026-10-16
32
-
33
- ### Added
34
-
35
- - Run the ordinary merge listener and receiver through `service listen`, with authenticated admission, canonical Assign acknowledgement/status/receipts, restart reconciliation and bounded fixed execution.
36
- - Invoke and inspect real Forgejo merges under the installed scoped service identity; revalidate authorization per request and bind the provider mutation to the approved candidate head.
37
-
38
- ### Changed
39
-
40
- - Require ace-assign 0.69 and ace-git-forgejo 0.7 for the receiver record-listing and scoped provider APIs.
41
-
42
- - Move the Forgejo merge provider ownership out of ace-lab (xz9.5): `ForgejoMergeAdapter` is now a thin delegation to the `ace-git-forgejo` provider (`Ace::Git::Forgejo::PullRequestApi` over its `HttpClient`, under the host-installed scoped service identity) — HTTP transport, authentication, payload shape, and failure classification live in the provider package, while the adapter keeps the receiver's closed outcome translation (`:invoked`/`:refused`/`:ambiguous`/`Unavailable`), the exact-head binding guard, and the normalized readback/nil-absence contract unchanged.
43
-
44
- ## [0.6.0] - 2026-10-14
14
+ - Gem-set installation into the target lab runtime (gad.2 acceptance
15
+ gap #1, owner spec 8xg.t.sbz.7, L8 install group): the trusted
16
+ install manifest (still schema v1) gains an optional `gemset` section
17
+ — a sibling of `tools` — declaring the target runtime (`gem_home`,
18
+ an absolute path converged as GEM_HOME and GEM_PATH) and the required
19
+ gem set (`name` + exact `version`; unique names, versions validated
20
+ as real Gem versions, so ranges and "latest" are rejected at load).
21
+ A manifest without the section behaves exactly as before.
22
+ `ace-lab prepare` (and therefore `ace-lab install`) converges the gem
23
+ set into the declared runtime idempotently through a new
24
+ `GemsetProvisioner` molecule: exact version present = no-op
25
+ (`exists`), missing = installed, different version present = updated
26
+ (the declared version installed; RubyGems keeps versions side by
27
+ side and verify judges the declared set). The gem subprocesses run
28
+ with `GEM_HOME`/`GEM_PATH` pinned to the runtime through the same
29
+ argv runner seam as every other prepare step — with the invoking
30
+ process's bundler context stripped (`RUBYOPT`, `RUBYLIB`,
31
+ `BUNDLE_*`/`BUNDLER_*`), so `bundle exec ace-lab prepare` converges
32
+ the LAB runtime instead of its own bundle. The runtime directory is
33
+ created only with administrator privileges (the AccountProvisioner
34
+ seam); a symlink or non-directory runtime is refused (the
35
+ DirectoryProvisioner takeover posture); runtime failures fail every
36
+ gem item as a dependency and gate the install's projects and grants
37
+ like any other machine-level failure. `--dry-run` predicts
38
+ `would_install`/`would_update`/`exists` read-only and predicts the
39
+ wet privilege failure. The install report echoes the declared gemset
40
+ under `gemset`. `ace-lab verify` gained a matching read-only `gems`
41
+ check — every declared gem must be installed at its exact version in
42
+ the declared runtime, judged through the SAME probe the provisioner
43
+ converges from (a missing `gem` executable reports `unknown`, the
44
+ offline posture). `project add` stays project-scoped: gem
45
+ installation is machine-level provisioning and is deliberately not
46
+ added there. (PR #77)
47
+
48
+
49
+ ## [0.13.0] - 2026-10-20
45
50
 
46
51
  ### Added
47
52
 
48
- - Add the ordinary fixed merge producer mode (`ace-lab service request/status --mode ordinary-merge`) per xz9.4: durable-once admission that issues the opaque reference only through the committed journal record, canonical SHA-256 request digests over the full eight-field binding, identical-replay acknowledgment, authorization/binding conflict refusals, read-only status, and terminal receipts bound to the admitted record and its execution identity.
49
-
50
- ## [0.5.2] - 2026-10-11
53
+ - `ace-lab verify` (L8.5, task 8xg.t.sbz.7.4): read-only end-of-install
54
+ readiness verification UNDER THE TARGET ACCOUNTS. For every account,
55
+ directory, key pair, role, project and grant declared in the trusted
56
+ install manifest it recomputes the facts from the same artifacts
57
+ install/resume derive truth from — no journal, no second state
58
+ owner, nothing written. Checks: account existence/login class
59
+ (non-login shells block readiness), declared directory mode/owner,
60
+ key pair usability (0600/0644/`.ssh` 0700 modes, account ownership,
61
+ and the public fingerprint matching the key derived from the private
62
+ half — the exact probe `prepare` applies, now exposed as a public
63
+ read-only `KeyProvisioner#pair_fingerprint`), role instruction
64
+ readability by the role's account (the `prepare` role verifier),
65
+ repository structure with origin matching the manifest remote
66
+ (local origins probed with `git ls-remote`; network origins reported
67
+ `unknown`, not dialed by the read-only verifier), principal grants
68
+ resolved through the real read-side mechanics (`CallerAuthorizer`)
69
+ against the trusted document, per-project registry visibility, and
70
+ the private `.ace-tasks/_overseer/` folder being present and
71
+ Git-ignored (`git check-ignore`). Every check reports pass/fail/
72
+ unknown with a concrete reason; a failure never hides healthy data
73
+ (the full report renders first) and blocks the `ready` verdict; the
74
+ command exits non-zero naming every failed check. Output: table or
75
+ stable JSON (`ready`/`checks`/`counts`) for the overseer or the next
76
+ tool. A fresh install and a resumed one verify identically.
77
+
78
+ - Resuming the installer's own interrupted installation (L8.4, task
79
+ 8xg.t.sbz.7.3): re-running `ace-lab install` after a mid-run crash
80
+ recognizes the partial state — derived from the real artifacts the
81
+ steps read and write, with no journal or marker file — and finishes
82
+ it. Steps that already completed report `exists`/`verified` without
83
+ destructive rework (converged directories and key pairs keep their
84
+ inode; a converged trusted document stays byte-identical), missing
85
+ steps complete (a half-written key pair regenerates only the public
86
+ half; partially granted own principals converge while foreign
87
+ sections of the document are kept), and grants are still configured
88
+ only over a converged install. Foreign content is refused at every
89
+ step: a project directory with foreign content, a repository with a
90
+ foreign origin, a foreign declared directory (e.g. a symlink) and a
91
+ trusted document granting undeclared projects each fail their step
92
+ with a classified, concrete message before any takeover or cleanup,
93
+ wet and in `--dry-run` alike (prediction parity on partial states).
94
+
95
+ - `ace-lab install` (L8.3, task 8xg.t.sbz.7.2): the full new-lab
96
+ installation from the trusted install manifest, composing the
97
+ existing molecules in a fixed order — `Prepare` (L8.1: accounts,
98
+ directories, keys, roles), `ProjectAdd` (L8.2) per manifest-declared
99
+ project, and the new `GrantProvisioner` that writes the trusted-tier
100
+ principal grants into `/etc/lab/ace-lab/authorization.yml` so the
101
+ installed projects are visible to `ace-lab projects` and
102
+ `ace-overseer projects` with no read-side change. The manifest schema
103
+ (still v1) gains optional `projects` (id/path/remote/label; unique
104
+ ids and paths) and `grants` (principal → declared project ids)
105
+ sections. A foreign lab is refused before any mutation: an existing
106
+ trusted document granting projects the manifest does not declare
107
+ aborts with a classified error and zero writes, and a manifest that
108
+ would remove an existing grant is refused (grants only grow);
109
+ undeclared principals and every other document section are preserved.
110
+ A converged wet re-run writes nothing at all — the trusted document
111
+ (admin-authored comments included) stays byte-identical; a manifest
112
+ with no `grants` over an absent document is a no-op (`skipped`,
113
+ nothing created). The existing document's principals are validated
114
+ exactly like the read side before any write (shared validation with
115
+ `GrantResolver`; malformed policies are refused with a classified
116
+ error instead of silently rewritten, whitespace-padded principal
117
+ names are normalized, and duplicate-after-strip principals are
118
+ rejected by the manifest schema).
119
+ Idempotent re-runs converge (`exists` statuses); `--dry-run`
120
+ faithfully predicts every phase (`would_*`) with exit 0, wet failures
121
+ render the report then exit non-zero naming each failed step.
122
+
123
+ - `ace-lab project add` (L8.2, task 8xg.t.sbz.7.1): registers a project
124
+ and makes it ready for team work. A `ProjectAdd` organism runs three
125
+ idempotent steps in a fixed order — `RepositoryProvisioner` (clone the
126
+ declared origin into the target path, or verify/converge an existing
127
+ repository's origin; refuse a different origin or a foreign
128
+ non-repository location), `OverseerProvisioner` (prepare the private
129
+ gitignored `.ace-tasks/_overseer/` folder plus its `.gitignore` entry,
130
+ never touching existing content), and `ProjectRegistry` (register the
131
+ project's id/label/path/remote in the project-tier
132
+ `.ace/lab/config.yml` cascade document, refusing contradictory
133
+ registrations, path conflicts, unparseable or foreign documents before
134
+ any write). Consumers need no change: `ace-lab status` classifies the
135
+ project as configured through the ordinary cascade and
136
+ `ace-lab projects` / `ace-overseer projects` list it through the
137
+ existing topology surface. `--dry-run` predicts with zero mutations;
138
+ wet failures print the report and exit non-zero naming each step.
139
+
140
+ - Adapter and tool declarations in the install manifest (tasks
141
+ 8xg.t.sbz.7.2/.7.4, LAB-3): the schema (v1) now accepts an
142
+ `adapters` list (`id`, `kind` `communication`/`hitl`, optional local
143
+ `command`, optional `config` mapping) and a `tools` list of required
144
+ executable names. `ace-lab install` validates and normalizes the
145
+ declarations (the trusted manifest stays their single state owner —
146
+ no second configuration file), echoes them in its report under
147
+ `adapters`/`tools`, and finishes with a NEW final `verify` stage over
148
+ the converged lab whose readiness gates the install's `ok` (dry runs
149
+ skip the stage — nothing has converged to verify). `ace-lab verify`
150
+ gained matching checks: every required tool must resolve to an
151
+ executable on PATH (presence only — no execution, no network), and
152
+ every declared adapter must have a valid configuration with its
153
+ command, when declared, resolvable; a missing tool or broken adapter
154
+ is a classified check failure that blocks readiness, never a crash.
155
+ Network reachability of communication adapters stays out of scope
156
+ (L9). The install CLI renders the verify verdict and names every
157
+ failed check in its non-zero-exit summary.
158
+
159
+ - Target-executor ownership propagation for installed projects (task
160
+ 8xg.t.sbz.7.4, LAB-2): `ProjectAdd` accepts an `owner` account and,
161
+ when the installer runs as root, chowns a converged project tree to
162
+ the declared executor (derived by install from the owning declared
163
+ directory above the project) so root installs yield
164
+ executor-writable repositories; non-root installers report the step
165
+ skipped and `verify` judges the executor's actual access on the real
166
+ permission bits. The report carries the new `owner` step.
51
167
 
52
168
  ### Changed
53
169
 
54
- - Follow the `ace-hitl` 0.14 lifecycle boundary line with dedicated per-authority socket-group configuration.
55
-
56
- ## [0.5.1] - 2026-10-10
170
+ - `ace-lab status` refined to the reviewed `l7-statuses` implementation
171
+ (task 8xg.t.sbz.6.0; extracted from the superseded PR #382): tri-state
172
+ classification moved into a `ConfigStateClassifier` molecule with the
173
+ corrected config cascade precedence and missing-state classification,
174
+ ADR-034 state-owner scanning isolated into a `LabStateScanner`
175
+ (per-location error isolation), and a `StatusReport` model behind a
176
+ `StatusService` organism — table/json/`-q` output preserved.
57
177
 
58
178
  ### Fixed
59
179
 
60
- - Require the published producer and consumer patch versions containing the protected context and managed-attempt APIs; exclude incompatible prior dependency graphs.
61
-
62
- ## [0.5.0] - 2026-10-08
63
-
64
- ### Added
65
-
66
- - Deliver scoped RubyGems publication through the fixed receiver and canonical HITL authorization, preserving original request, OTP challenge and publication-result evidence.
67
-
68
- ### Changed
69
-
70
- - Compose actual service/result owners without a read-proof completion handler; retain authenticated service settlement and cleanup boundaries.
71
-
72
- ### Fixed
73
-
74
- - Load protected policy/listener dependencies directly and keep cleanup inspection tied to the original receiver and immutable result evidence.
75
-
76
- ## [0.4.0] - 2026-10-05
77
-
78
- ### Added
79
-
80
- - Protected service policy, receiver and authority composition; incomplete full-service startup remains refused.
81
- - Resolve immutable second-commander proposals through confirmed-delivery sixteen-hour policy and canonical Assign authorization.
82
-
83
- ### Changed
84
-
85
- - Declare the required direct dependencies and minimum producer versions for this coordinated release: `ace-assign ~> 0.64`, `ace-runtime ~> 0.2`.
86
-
87
- ## [0.3.1] - 2026-10-04
88
-
89
- ### Changed
90
- - Track the runtime-neutral ace-assign 0.62 line (the published 0.3.0 still allows `~> 0.61`, which resolves 0.62 — this release ships the tightened constraint).
91
-
92
- ## [0.3.0] - 2026-10-04
93
-
94
- ### Added
95
- - **HITL delivery authorization policy (spec 8wq.t.34i)**: `Molecules::HitlAuthorizer` derives the lab-side transport facts (trusted transport uids, trusted HITL service identity, per-project Captain visibility) from the SAME trusted deployment document the grant resolver reads — uid-exact, project-scoped, never from claimed names or the cascade.
96
-
97
- ## [0.2.0] - 2026-10-02
98
-
99
- ### Added
100
-
101
- - `service request` and `service status` for exact authorized operations,
102
- idempotent assignment-journal claims, peer-verified execution receipts,
103
- dry-run previews, and conservative uncertain-state recovery.
104
-
105
- ### Fixed
106
-
107
- - Recheck the configured operation, authorization, and lease at dispatch;
108
- bind terminal receipts to the assignment request before acceptance.
109
-
110
- ## [0.1.0] - 2026-09-28
111
-
112
- ### Added
180
+ - Review 8xirj8: a failed payload write inside `HitlRoutes`' exclusive
181
+ temp-file helper no longer leaks the temp file (close + delete +
182
+ re-raise); the shallow per-key merge semantics are documented.
183
+
184
+ - Review 8xir47 (findings 1, 3, 4): a decidable HITL route failure is
185
+ no longer masked as UNKNOWN when PATH is unset (route failures take
186
+ precedence over the PATH-unset probe message and always FAIL
187
+ readiness); the ownership-skip message names the actually failing
188
+ step instead of always blaming the repository step; the
189
+ prepare-dependency project report carries the `hitl` key like every
190
+ other ProjectAdd report variant.
191
+
192
+ - Review 8xir47 (findings 5-6) in `HitlRoutes`: the runtime route
193
+ configuration (`.ace/hitl/config.yml`) is now written ATOMICALLY
194
+ (exclusive temp file + rename, the ProjectRegistry discipline) — a
195
+ crash mid-write can no longer truncate the operator-facing config.
196
+ Convergence is now by route NAME everywhere: on re-install the
197
+ manifest-declared keys are merged into the existing same-name route
198
+ (manifest keys win, operator extra keys such as `pane: p5` survive
199
+ instead of being silently replaced), operator-added routes are kept,
200
+ and `verify` accepts a same-name route carrying every
201
+ manifest-declared key/value even when the operator extended it with
202
+ extra keys — an extended route no longer fails verification until
203
+ re-install. An unchanged route list is never rewritten (comments
204
+ survive idempotent no-op installs); a converging write is a YAML
205
+ round-trip and drops comments, as documented in the class docstring.
206
+
207
+ - PR #393 final audit (FINAL-LAB1): `ProjectAdd` now resolves declared
208
+ project owners through the REAL system passwd lookup by default (the
209
+ same `Etc.getpwnam` seam `RoleVerifier`/`Verify` use). The previous
210
+ `account_lookup: nil` default made every declared owner
211
+ "nonexistent" in production installs — only tests injected a lookup,
212
+ masking the bug. A root installer also no longer skips ownership
213
+ propagation when the project root already carries the target uid: a
214
+ root uid match proves nothing about generated children (`.ace/lab/
215
+ config.yml`, `.ace/hitl/config.yml` may still be root-owned), so the
216
+ whole tree is always converged. A declared directory matching a
217
+ project path exactly (not only as a prefix) now selects that
218
+ project's owner too.
219
+
220
+ - PR #393 final audit (FINAL-LAB2): `kind: hitl` adapter declarations
221
+ are now projected into the runtime HITL configuration contract —
222
+ `hitl.adapters.routes` in every declared project's
223
+ `.ace/hitl/config.yml`, the exact cascade location the ace-hitl
224
+ route loader reads (an empty adapter configuration used to yield
225
+ `ok:true`/`ready:true` while the runtime failed with NoRoutesError).
226
+ The new `HitlRoutes` molecule installs the routes idempotently
227
+ (operator-owned routes and settings are preserved; unparseable
228
+ documents are refused, not overwritten), and `ace-lab verify`
229
+ re-derives the expected route per adapter and validates the
230
+ resulting configuration against the runtime loader's rules
231
+ (non-empty mapping list, required `adapter`, single-segment route
232
+ names, no duplicates) in every declared project — a missing or
233
+ invalid route configuration blocks readiness. The committed suite
234
+ cross-checks the written document against the real
235
+ `Ace::Hitl::Molecules::HitlRouteLoader` so the mirrored validation
236
+ cannot drift.
237
+
238
+ - PR #393 final audit (FINAL-LAB3): required tools and adapter
239
+ commands are now probed FOR THE TARGET EXECUTORS, not the installing
240
+ account — an installer-owned mode-0700 executable fails readiness
241
+ when the declared executor (different uid) cannot run it. The new
242
+ `RoleVerifier#executable_access_reason` applies the same
243
+ governing-class permission logic (owner/group/other short-circuit,
244
+ supplementary groups, ancestor search) as the repository access
245
+ checks, and the project-directory access mask now requires
246
+ read+write+search (a directory without x on itself is unusable to
247
+ the executor however rw it is). Tool/adapter availability that is
248
+ undecidable (unset PATH) still reports `unknown`; as with every
249
+ other unknown it does not block readiness by itself and never counts
250
+ as a verified pass.
251
+
252
+ - Review fix: `check_adapters` status precedence — a malformed adapter
253
+ configuration (not a mapping) stays a decidable FAIL even when PATH
254
+ is unset (only the command probe may degrade to unknown); the dead
255
+ `tool_reason` helper introduced with the tri-state probe is removed;
256
+ and two review-round acceptance scenarios (gitignore-symlink
257
+ read-through refusal, verify-stage crash classification) were
258
+ accidentally defined outside their minitest classes and never
259
+ executed — they now run (and pass) in the package suite.
260
+
261
+ - Critical review fix: `ProjectAdd`'s privileged ownership propagation
262
+ called `FileUtils.chown_R` with the wrong argument order (path
263
+ passed as the user, the gid Integer as the file list), so every
264
+ root install with a declared owner failed the `owner` step with a
265
+ misleading error. The chown is now invoked as
266
+ `chown_R(uid, gid, path)`, rescued for TypeError as well, and is an
267
+ injectable seam so the root branch is testable without uid 0.
268
+ - Review fix: an unset or empty `PATH` no longer produces false FAILs
269
+ for the tools/adapters checks — the probe is tri-state and yields
270
+ `unknown` ("PATH is unset; cannot probe …") instead of blocking
271
+ readiness in minimal-PATH environments.
272
+ - Review fix (trust posture): a privileged (root) installer never
273
+ executes the repository-controlled `bin/ace-nav` binstub during
274
+ final verification — under root only the PATH-installed resolver is
275
+ used, consistent with the LAB-1 symlink refusals.
276
+ - Review fix: an unexpected exception from the final verify stage is
277
+ a classified `verify: failed` install stage, never a raw crash.
278
+ - Review fix: a symlinked `.gitignore` is refused up front even when
279
+ the ignore rule would already match — no privileged read-through
280
+ (`git check-ignore` follows the link) either.
281
+
282
+ - Privileged provisioning writes no longer follow repository-controlled
283
+ symlinks (LAB-1, audit PR #393): `OverseerProvisioner` refuses a
284
+ symlinked `.gitignore` (checked via lstat before any read/append) or
285
+ `.ace-tasks` component before creating anything, and
286
+ `ProjectRegistry` refuses a symlinked `.ace`/`.ace/lab` ancestor and
287
+ creates its atomic-write temp file EXCLUSIVELY (`O_CREAT|O_EXCL`)
288
+ under an unguessable random name instead of the predictable
289
+ `.config.yml.tmp.<PID>` that a pre-planted symlink could capture
290
+ (F3) — a committed symlink can no longer redirect installer writes
291
+ outside the project.
292
+
293
+ - `ace-lab verify` now judges project readiness for the declared
294
+ TARGET EXECUTOR, not the invoking installer (LAB-2): every account a
295
+ role binds must be able to reach the project through its ancestors
296
+ (search permission) and read/write the project directory — a
297
+ repository behind an installer-owned 0700 ancestor with a different
298
+ target UID is NOT ready even though the installer's git access
299
+ succeeded. The check shares the role verifier's governing-class
300
+ permission logic so the two can never drift apart.
301
+
302
+ - Final verification resolves protocol role instructions
303
+ (`wfi://`, `prompt://`, `tmpl://`) through the actual resolver for
304
+ the execution context (LAB-4): prepare's syntax-only protocol check
305
+ is no longer reused as proof of readiness, so a syntactically valid
306
+ but nonexistent role (`wfi://does-not-exist/not-a-role`) fails the
307
+ check and blocks readiness. Resolution runs `bin/ace-nav resolve`
308
+ (project binstub, else PATH) from every declared project root; an
309
+ environment without the resolver reports `unknown` and does not
310
+ block, mirroring the offline posture for network remotes.
311
+
312
+ - `_overseer` ignore provisioning decides through REAL Git evaluation
313
+ (LAB-5): `OverseerProvisioner` uses `git check-ignore` (the same
314
+ probe verify applies) instead of matching `.gitignore` lines, so a
315
+ later negation (`!.ace-tasks/_overseer/`) no longer masquerades as
316
+ an effective ignore rule — the effective rule is appended and the
317
+ decision re-evaluated to confirm.
318
+
319
+ - A malformed per-project registry (e.g. `topology: invalid`) is now a
320
+ classified per-project check failure instead of a `TypeError` from
321
+ `dig` that killed the entire readiness report (LAB-6): the document
322
+ is validated through `TopologySchema.normalize!`, and healthy checks
323
+ beside the failure keep running and stay visible.
324
+
325
+ - `RepositoryProvisioner` compares project identity against the RAW
326
+ stored remote URL (`git config --get remote.origin.url`) instead of
327
+ the `insteadOf`-expanded `git remote get-url` value (LAB-7):
328
+ idempotent re-add/resume under a common SSH rewrite configuration
329
+ (`url.<base>.insteadOf`) no longer false-fails an identical project —
330
+ connection rewriting is Git transport policy, not a changed identity.
331
+
332
+ - `KeyProvisioner`'s internal pair-usability probe could return
333
+ `false` instead of `nil` for an unusable pair (the new public
334
+ `pair_fingerprint` probe normalizes to `nil`); existing internal
335
+ callers treated both as falsy and were unaffected.
336
+
337
+ - `GrantProvisioner` failure reports now carry `kept: []` like every
338
+ other grants status, so machine consumers of the install report see
339
+ one stable schema for the grants section regardless of outcome.
340
+
341
+ - `project add` review fixes (round 1, task 8xg.t.sbz.7.1):
342
+ `RepositoryProvisioner#repository?` now requires the target path to
343
+ be the Git repository toplevel (`rev-parse --show-toplevel` compared
344
+ against the expanded path), so a non-empty directory nested inside a
345
+ parent repository's work tree is refused as a foreign location
346
+ instead of converging and mutating the parent's origin (8xikzhvh);
347
+ `ProjectRegistry` pre-validates the loaded document with
348
+ `TopologySchema.normalize!` (a malformed `topology.projects` entry is
349
+ a classified "not this lab's registry" failure, not a `TypeError`
350
+ crash — 8xikzhvi), validates the updated document before the dry-run
351
+ branch so `--dry-run` predicts invalid id/label failures exactly like
352
+ the wet run (8xikzhvj), writes the registry atomically via a temp
353
+ file plus `File.rename` (8xikzhvk), updates the label on re-add with
354
+ a changed `--label` and reports the new `updated`/`would_update`
355
+ statuses (8xikzhvl), refuses an empty-but-existing registry file with
356
+ a clear message (8xikzhvq), and `OverseerProvisioner` no longer
357
+ counts a `!` negation line as an existing ignore entry (8xikzhvp).
358
+ `docs/usage.md` documents the trusted-tier grant and project-root
359
+ prerequisites for listing the new project (8xikzhvm) and that
360
+ `path`/`remote` registry fields are installer-owned data outside the
361
+ public projection (8xikzhvs).
362
+ - Security (8xi1p4z5): when an ancestor of the key path denies search
363
+ permission (`EACCES` on realpath, e.g. an account locking out the
364
+ unprivileged installer), `KeyProvisioner`'s git-worktree guard now
365
+ fails closed — the path is judged as inside a worktree and refused —
366
+ instead of falling back to the lexical spelling, which a symlink
367
+ behind the denied ancestor could fool (the pre-F2 behavior). The
368
+ `:repair_public` classification branch is documented as a deliberate
369
+ TOCTOU safety net (8xi1p4z7), and the dry-run contract comment now
370
+ states that prediction uses read-only filesystem probes plus
371
+ read-only `ssh-keygen` invocations (8xi1p4z4).
372
+ - `RoleVerifier` (8xi16mz1): the reachability walk now resolves the
373
+ instruction file's OWN symlink too (`File.realpath(path)`), not just
374
+ the ancestors of the link's path. Previously a symlinked instruction
375
+ pointing at a file behind an unsearchable directory (e.g. 0700
376
+ other-owned) verified as reachable because only the symlink's own
377
+ ancestors were checked; the target's directory chain is what open(2)
378
+ traverses, so it is what the walk judges now.
379
+ - `KeyProvisioner` (8xi16mz2/8xi16mz5/8xi16mz6): the dry-run
380
+ prediction and the wet existing path now share one
381
+ `classify_existing_pair` step (read-only `pair_fingerprint`,
382
+ including a 0600 tmpdir copy when the private key's mode has drifted
383
+ and ssh-keygen would refuse to load it), run BEFORE the mode-drift
384
+ branch. A garbage pair with mode drift now predicts `failed` (not
385
+ usable) instead of `would_fix_mode` — the wet run converges the
386
+ modes and then fails verification, and the prediction says exactly
387
+ that; `would_fix_mode` is only ever predicted for a verified pair.
388
+ The duplicated not-usable message became the shared
389
+ `NOT_USABLE_TEMPLATE` constant.
390
+ - Security (PR #392 F2): `KeyProvisioner`'s git-worktree guard now
391
+ resolves the PHYSICAL ancestors of the key path (realpath of the
392
+ deepest existing ancestor plus the lexical tail) before any
393
+ generate/mutation step. Previously the guard walked lexical
394
+ directories only, so `/outside/alias/.ssh/id_ed25519` with `alias`
395
+ a symlink into a repository passed and the real key landed inside
396
+ the worktree — including when `.ssh` did not exist yet behind the
397
+ alias. Symlinked ancestors resolving outside any worktree remain
398
+ legitimate.
399
+ - `KeyProvisioner` (PR #392 F4): a pair that cannot be verified as
400
+ usable is now a `failed` item, never `exists`/`created` with
401
+ `fingerprint: nil`. Verification requires the `.pub` fingerprint to
402
+ match the public key derived from the private key via
403
+ `ssh-keygen -y` (comparing `-lf` on the private file is not sound —
404
+ ssh-keygen resolves the adjacent `.pub`), so garbage files and a
405
+ `.pub` belonging to a different private key both fail the prepare
406
+ aggregate (`ok: false`, non-zero exit). An unknown pair is still
407
+ never overwritten (takeover-refusal policy).
408
+ - `RoleVerifier` (PR #392 F5): instruction files are verified for
409
+ reachability, not just read bits — every ancestor directory of the
410
+ resolved path (realpath follows ancestor symlinks) must grant the
411
+ account search permission through its governing POSIX class (owner,
412
+ primary/supplementary group via the `group_source` seam, or other;
413
+ shared with the file-read check so the two cannot drift). A 0644
414
+ file behind a 0700 directory owned by another account is now
415
+ `failed`, not `verified`. ACLs remain out of scope.
416
+ - `AccountProvisioner` (PR #392, macOS UID allocation):
417
+ `next_darwin_uid` parses only the trailing `UniqueID` column of
418
+ each `dscl -list` line instead of scanning the whole output, so
419
+ digits inside account names such as `deploy-2026` can no longer be
420
+ mistaken for UIDs.
113
421
 
114
- - Topology/routing CLI (`ace-lab`) addressing projects, agents, and services by
115
- stable IDs: `projects`, `agents`, `services`, `resolve`, `route` commands with
116
- deterministic JSON output (`--format json`).
117
- - ADR-022 configuration contract for Lab topology (`schema_version: 1`) with
118
- validated stable IDs, project references, capability normalization, binding
119
- attestation fields, and verified-local-principal authorization mapping.
120
- - Classified query outcomes: `missing`, `ambiguous`, `stale`, `unauthorized`,
121
- `invalid_configuration`.
122
- - Allowlist-based public projection; tokens, auth-file paths, endpoint
123
- credentials, URL userinfo, query parameters, fragments, and raw pane/session
124
- identifiers never appear in public output.