ace-lab 0.9.0 → 0.13.0

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +363 -104
  3. data/README.md +66 -100
  4. data/docs/usage.md +529 -592
  5. data/lib/ace/lab/atoms/install_schema.rb +373 -0
  6. data/lib/ace/lab/cli/commands/install.rb +161 -0
  7. data/lib/ace/lab/cli/commands/prepare.rb +118 -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/grant_provisioner.rb +250 -0
  18. data/lib/ace/lab/molecules/grant_resolver.rb +34 -23
  19. data/lib/ace/lab/molecules/hitl_routes.rb +263 -0
  20. data/lib/ace/lab/molecules/install_manifest_loader.rb +64 -0
  21. data/lib/ace/lab/molecules/key_provisioner.rb +736 -0
  22. data/lib/ace/lab/molecules/lab_state_scanner.rb +55 -0
  23. data/lib/ace/lab/molecules/overseer_provisioner.rb +188 -0
  24. data/lib/ace/lab/molecules/project_registry.rb +251 -0
  25. data/lib/ace/lab/molecules/repository_provisioner.rb +141 -0
  26. data/lib/ace/lab/molecules/role_verifier.rb +223 -0
  27. data/lib/ace/lab/organisms/install.rb +213 -0
  28. data/lib/ace/lab/organisms/prepare.rb +156 -0
  29. data/lib/ace/lab/organisms/project_add.rb +170 -0
  30. data/lib/ace/lab/organisms/status_service.rb +70 -0
  31. data/lib/ace/lab/organisms/verify.rb +768 -0
  32. data/lib/ace/lab/version.rb +1 -1
  33. data/lib/ace/lab.rb +34 -6
  34. metadata +34 -108
  35. data/lib/ace/lab/atoms/protected_publication_input.rb +0 -50
  36. data/lib/ace/lab/atoms/protected_workspace_prune_input.rb +0 -114
  37. data/lib/ace/lab/atoms/protected_workspace_prune_preview.rb +0 -86
  38. data/lib/ace/lab/atoms/service_input.rb +0 -92
  39. data/lib/ace/lab/cli/commands/assignment.rb +0 -92
  40. data/lib/ace/lab/cli/commands/command_execute.rb +0 -28
  41. data/lib/ace/lab/cli/commands/run.rb +0 -65
  42. data/lib/ace/lab/cli/commands/service.rb +0 -240
  43. data/lib/ace/lab/models/service_receipt.rb +0 -18
  44. data/lib/ace/lab/molecules/command_owner_admission.rb +0 -183
  45. data/lib/ace/lab/molecules/command_policy.rb +0 -158
  46. data/lib/ace/lab/molecules/hitl_authorizer.rb +0 -63
  47. data/lib/ace/lab/molecules/ordinary_admission_resolver.rb +0 -131
  48. data/lib/ace/lab/molecules/ordinary_listener.rb +0 -262
  49. data/lib/ace/lab/molecules/ordinary_merge_read.rb +0 -45
  50. data/lib/ace/lab/molecules/ordinary_native_read.rb +0 -106
  51. data/lib/ace/lab/molecules/ordinary_owner_admission.rb +0 -152
  52. data/lib/ace/lab/molecules/ordinary_owner_config.rb +0 -232
  53. data/lib/ace/lab/molecules/ordinary_receiver_config.rb +0 -94
  54. data/lib/ace/lab/molecules/ordinary_socket.rb +0 -126
  55. data/lib/ace/lab/molecules/ordinary_worktree_read.rb +0 -216
  56. data/lib/ace/lab/molecules/protected_cleanup_owner_admission.rb +0 -163
  57. data/lib/ace/lab/molecules/protected_cleanup_owner_client.rb +0 -165
  58. data/lib/ace/lab/molecules/protected_cleanup_owner_identity.rb +0 -239
  59. data/lib/ace/lab/molecules/protected_publication_publisher.rb +0 -110
  60. data/lib/ace/lab/molecules/protected_service_handler.rb +0 -43
  61. data/lib/ace/lab/molecules/protected_service_policy.rb +0 -156
  62. data/lib/ace/lab/molecules/service_executor.rb +0 -99
  63. data/lib/ace/lab/molecules/service_policy.rb +0 -289
  64. data/lib/ace/lab/organisms/authority_composition.rb +0 -87
  65. data/lib/ace/lab/organisms/command_executor.rb +0 -220
  66. data/lib/ace/lab/organisms/command_run_client.rb +0 -131
  67. data/lib/ace/lab/organisms/ordinary_assignment_client.rb +0 -120
  68. data/lib/ace/lab/organisms/ordinary_authority_composition.rb +0 -153
  69. data/lib/ace/lab/organisms/ordinary_merge_receiver.rb +0 -679
  70. data/lib/ace/lab/organisms/ordinary_merge_request.rb +0 -456
  71. data/lib/ace/lab/organisms/protected_cleanup_owner.rb +0 -361
  72. data/lib/ace/lab/organisms/protected_service_client.rb +0 -129
  73. data/lib/ace/lab/organisms/protected_service_listener.rb +0 -181
  74. data/lib/ace/lab/organisms/protected_service_publication.rb +0 -268
  75. data/lib/ace/lab/organisms/protected_service_receiver.rb +0 -267
  76. data/lib/ace/lab/organisms/protected_service_recovery.rb +0 -185
  77. data/lib/ace/lab/organisms/protected_service_request.rb +0 -128
  78. data/lib/ace/lab/organisms/protected_service_worker.rb +0 -89
  79. 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: ff20c0d594202a649d5597257411b611d6faf6b66128fd8ff4ad0702989b6e16
4
+ data.tar.gz: 1d6575916e526eec678ee39c9be5025f53653fcdfac27c8075692c936d3351bd
5
5
  SHA512:
6
- metadata.gz: fae21d841c34b8ff24586a806778377589a476ea4e824e9716c6ff55700d207fc9a501656fe2d1712c5c8358a29e22a31a2c8fb2cde8713d464e700f6fdea795
7
- data.tar.gz: feacdf38956ff9a1c3afe9296e4cc762deba5029ece4dbc6e9d6bd3a1d6e28e825d746e29cd0f52268c39ba5fe4bd5d97a48e93d7a8f901da38ad9225db41ffc
6
+ metadata.gz: 32b6ee73258e1534a9ff91f2bc83d30ff4265b124aefcee056652dd0544ef770aba4036e055e282fc35ea1c2a993cfefa67f46480e82ecf4f294fbda5561deeb
7
+ data.tar.gz: 786a3ca1af5fad657f722d3ea2c0ba525988c5461ed18a94023650e17f5b7870c15a754d71560ee442e78bd6527a42823d25dc297911666156bf57287072e9c7
data/CHANGELOG.md CHANGED
@@ -7,118 +7,377 @@ 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
11
10
 
12
- ### Added
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
11
+ ## [0.13.0] - 2026-10-20
32
12
 
33
13
  ### Added
34
14
 
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.
15
+ - `ace-lab verify` (L8.5, task 8xg.t.sbz.7.4): read-only end-of-install
16
+ readiness verification UNDER THE TARGET ACCOUNTS. For every account,
17
+ directory, key pair, role, project and grant declared in the trusted
18
+ install manifest it recomputes the facts from the same artifacts
19
+ install/resume derive truth from — no journal, no second state
20
+ owner, nothing written. Checks: account existence/login class
21
+ (non-login shells block readiness), declared directory mode/owner,
22
+ key pair usability (0600/0644/`.ssh` 0700 modes, account ownership,
23
+ and the public fingerprint matching the key derived from the private
24
+ half — the exact probe `prepare` applies, now exposed as a public
25
+ read-only `KeyProvisioner#pair_fingerprint`), role instruction
26
+ readability by the role's account (the `prepare` role verifier),
27
+ repository structure with origin matching the manifest remote
28
+ (local origins probed with `git ls-remote`; network origins reported
29
+ `unknown`, not dialed by the read-only verifier), principal grants
30
+ resolved through the real read-side mechanics (`CallerAuthorizer`)
31
+ against the trusted document, per-project registry visibility, and
32
+ the private `.ace-tasks/_overseer/` folder being present and
33
+ Git-ignored (`git check-ignore`). Every check reports pass/fail/
34
+ unknown with a concrete reason; a failure never hides healthy data
35
+ (the full report renders first) and blocks the `ready` verdict; the
36
+ command exits non-zero naming every failed check. Output: table or
37
+ stable JSON (`ready`/`checks`/`counts`) for the overseer or the next
38
+ tool. A fresh install and a resumed one verify identically.
39
+
40
+ - Resuming the installer's own interrupted installation (L8.4, task
41
+ 8xg.t.sbz.7.3): re-running `ace-lab install` after a mid-run crash
42
+ recognizes the partial state — derived from the real artifacts the
43
+ steps read and write, with no journal or marker file — and finishes
44
+ it. Steps that already completed report `exists`/`verified` without
45
+ destructive rework (converged directories and key pairs keep their
46
+ inode; a converged trusted document stays byte-identical), missing
47
+ steps complete (a half-written key pair regenerates only the public
48
+ half; partially granted own principals converge while foreign
49
+ sections of the document are kept), and grants are still configured
50
+ only over a converged install. Foreign content is refused at every
51
+ step: a project directory with foreign content, a repository with a
52
+ foreign origin, a foreign declared directory (e.g. a symlink) and a
53
+ trusted document granting undeclared projects each fail their step
54
+ with a classified, concrete message before any takeover or cleanup,
55
+ wet and in `--dry-run` alike (prediction parity on partial states).
56
+
57
+ - `ace-lab install` (L8.3, task 8xg.t.sbz.7.2): the full new-lab
58
+ installation from the trusted install manifest, composing the
59
+ existing molecules in a fixed order — `Prepare` (L8.1: accounts,
60
+ directories, keys, roles), `ProjectAdd` (L8.2) per manifest-declared
61
+ project, and the new `GrantProvisioner` that writes the trusted-tier
62
+ principal grants into `/etc/lab/ace-lab/authorization.yml` so the
63
+ installed projects are visible to `ace-lab projects` and
64
+ `ace-overseer projects` with no read-side change. The manifest schema
65
+ (still v1) gains optional `projects` (id/path/remote/label; unique
66
+ ids and paths) and `grants` (principal → declared project ids)
67
+ sections. A foreign lab is refused before any mutation: an existing
68
+ trusted document granting projects the manifest does not declare
69
+ aborts with a classified error and zero writes, and a manifest that
70
+ would remove an existing grant is refused (grants only grow);
71
+ undeclared principals and every other document section are preserved.
72
+ A converged wet re-run writes nothing at all — the trusted document
73
+ (admin-authored comments included) stays byte-identical; a manifest
74
+ with no `grants` over an absent document is a no-op (`skipped`,
75
+ nothing created). The existing document's principals are validated
76
+ exactly like the read side before any write (shared validation with
77
+ `GrantResolver`; malformed policies are refused with a classified
78
+ error instead of silently rewritten, whitespace-padded principal
79
+ names are normalized, and duplicate-after-strip principals are
80
+ rejected by the manifest schema).
81
+ Idempotent re-runs converge (`exists` statuses); `--dry-run`
82
+ faithfully predicts every phase (`would_*`) with exit 0, wet failures
83
+ render the report then exit non-zero naming each failed step.
84
+
85
+ - `ace-lab project add` (L8.2, task 8xg.t.sbz.7.1): registers a project
86
+ and makes it ready for team work. A `ProjectAdd` organism runs three
87
+ idempotent steps in a fixed order — `RepositoryProvisioner` (clone the
88
+ declared origin into the target path, or verify/converge an existing
89
+ repository's origin; refuse a different origin or a foreign
90
+ non-repository location), `OverseerProvisioner` (prepare the private
91
+ gitignored `.ace-tasks/_overseer/` folder plus its `.gitignore` entry,
92
+ never touching existing content), and `ProjectRegistry` (register the
93
+ project's id/label/path/remote in the project-tier
94
+ `.ace/lab/config.yml` cascade document, refusing contradictory
95
+ registrations, path conflicts, unparseable or foreign documents before
96
+ any write). Consumers need no change: `ace-lab status` classifies the
97
+ project as configured through the ordinary cascade and
98
+ `ace-lab projects` / `ace-overseer projects` list it through the
99
+ existing topology surface. `--dry-run` predicts with zero mutations;
100
+ wet failures print the report and exit non-zero naming each step.
101
+
102
+ - Adapter and tool declarations in the install manifest (tasks
103
+ 8xg.t.sbz.7.2/.7.4, LAB-3): the schema (v1) now accepts an
104
+ `adapters` list (`id`, `kind` `communication`/`hitl`, optional local
105
+ `command`, optional `config` mapping) and a `tools` list of required
106
+ executable names. `ace-lab install` validates and normalizes the
107
+ declarations (the trusted manifest stays their single state owner —
108
+ no second configuration file), echoes them in its report under
109
+ `adapters`/`tools`, and finishes with a NEW final `verify` stage over
110
+ the converged lab whose readiness gates the install's `ok` (dry runs
111
+ skip the stage — nothing has converged to verify). `ace-lab verify`
112
+ gained matching checks: every required tool must resolve to an
113
+ executable on PATH (presence only — no execution, no network), and
114
+ every declared adapter must have a valid configuration with its
115
+ command, when declared, resolvable; a missing tool or broken adapter
116
+ is a classified check failure that blocks readiness, never a crash.
117
+ Network reachability of communication adapters stays out of scope
118
+ (L9). The install CLI renders the verify verdict and names every
119
+ failed check in its non-zero-exit summary.
120
+
121
+ - Target-executor ownership propagation for installed projects (task
122
+ 8xg.t.sbz.7.4, LAB-2): `ProjectAdd` accepts an `owner` account and,
123
+ when the installer runs as root, chowns a converged project tree to
124
+ the declared executor (derived by install from the owning declared
125
+ directory above the project) so root installs yield
126
+ executor-writable repositories; non-root installers report the step
127
+ skipped and `verify` judges the executor's actual access on the real
128
+ permission bits. The report carries the new `owner` step.
37
129
 
38
130
  ### Changed
39
131
 
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
45
-
46
- ### Added
47
-
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
51
-
52
- ### Changed
53
-
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
132
+ - `ace-lab status` refined to the reviewed `l7-statuses` implementation
133
+ (task 8xg.t.sbz.6.0; extracted from the superseded PR #382): tri-state
134
+ classification moved into a `ConfigStateClassifier` molecule with the
135
+ corrected config cascade precedence and missing-state classification,
136
+ ADR-034 state-owner scanning isolated into a `LabStateScanner`
137
+ (per-location error isolation), and a `StatusReport` model behind a
138
+ `StatusService` organism — table/json/`-q` output preserved.
57
139
 
58
140
  ### Fixed
59
141
 
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
142
+ - Review 8xirj8: a failed payload write inside `HitlRoutes`' exclusive
143
+ temp-file helper no longer leaks the temp file (close + delete +
144
+ re-raise); the shallow per-key merge semantics are documented.
145
+
146
+ - Review 8xir47 (findings 1, 3, 4): a decidable HITL route failure is
147
+ no longer masked as UNKNOWN when PATH is unset (route failures take
148
+ precedence over the PATH-unset probe message and always FAIL
149
+ readiness); the ownership-skip message names the actually failing
150
+ step instead of always blaming the repository step; the
151
+ prepare-dependency project report carries the `hitl` key like every
152
+ other ProjectAdd report variant.
153
+
154
+ - Review 8xir47 (findings 5-6) in `HitlRoutes`: the runtime route
155
+ configuration (`.ace/hitl/config.yml`) is now written ATOMICALLY
156
+ (exclusive temp file + rename, the ProjectRegistry discipline) — a
157
+ crash mid-write can no longer truncate the operator-facing config.
158
+ Convergence is now by route NAME everywhere: on re-install the
159
+ manifest-declared keys are merged into the existing same-name route
160
+ (manifest keys win, operator extra keys such as `pane: p5` survive
161
+ instead of being silently replaced), operator-added routes are kept,
162
+ and `verify` accepts a same-name route carrying every
163
+ manifest-declared key/value even when the operator extended it with
164
+ extra keys — an extended route no longer fails verification until
165
+ re-install. An unchanged route list is never rewritten (comments
166
+ survive idempotent no-op installs); a converging write is a YAML
167
+ round-trip and drops comments, as documented in the class docstring.
168
+
169
+ - PR #393 final audit (FINAL-LAB1): `ProjectAdd` now resolves declared
170
+ project owners through the REAL system passwd lookup by default (the
171
+ same `Etc.getpwnam` seam `RoleVerifier`/`Verify` use). The previous
172
+ `account_lookup: nil` default made every declared owner
173
+ "nonexistent" in production installs — only tests injected a lookup,
174
+ masking the bug. A root installer also no longer skips ownership
175
+ propagation when the project root already carries the target uid: a
176
+ root uid match proves nothing about generated children (`.ace/lab/
177
+ config.yml`, `.ace/hitl/config.yml` may still be root-owned), so the
178
+ whole tree is always converged. A declared directory matching a
179
+ project path exactly (not only as a prefix) now selects that
180
+ project's owner too.
181
+
182
+ - PR #393 final audit (FINAL-LAB2): `kind: hitl` adapter declarations
183
+ are now projected into the runtime HITL configuration contract —
184
+ `hitl.adapters.routes` in every declared project's
185
+ `.ace/hitl/config.yml`, the exact cascade location the ace-hitl
186
+ route loader reads (an empty adapter configuration used to yield
187
+ `ok:true`/`ready:true` while the runtime failed with NoRoutesError).
188
+ The new `HitlRoutes` molecule installs the routes idempotently
189
+ (operator-owned routes and settings are preserved; unparseable
190
+ documents are refused, not overwritten), and `ace-lab verify`
191
+ re-derives the expected route per adapter and validates the
192
+ resulting configuration against the runtime loader's rules
193
+ (non-empty mapping list, required `adapter`, single-segment route
194
+ names, no duplicates) in every declared project — a missing or
195
+ invalid route configuration blocks readiness. The committed suite
196
+ cross-checks the written document against the real
197
+ `Ace::Hitl::Molecules::HitlRouteLoader` so the mirrored validation
198
+ cannot drift.
199
+
200
+ - PR #393 final audit (FINAL-LAB3): required tools and adapter
201
+ commands are now probed FOR THE TARGET EXECUTORS, not the installing
202
+ account — an installer-owned mode-0700 executable fails readiness
203
+ when the declared executor (different uid) cannot run it. The new
204
+ `RoleVerifier#executable_access_reason` applies the same
205
+ governing-class permission logic (owner/group/other short-circuit,
206
+ supplementary groups, ancestor search) as the repository access
207
+ checks, and the project-directory access mask now requires
208
+ read+write+search (a directory without x on itself is unusable to
209
+ the executor however rw it is). Tool/adapter availability that is
210
+ undecidable (unset PATH) still reports `unknown`; as with every
211
+ other unknown it does not block readiness by itself and never counts
212
+ as a verified pass.
213
+
214
+ - Review fix: `check_adapters` status precedence — a malformed adapter
215
+ configuration (not a mapping) stays a decidable FAIL even when PATH
216
+ is unset (only the command probe may degrade to unknown); the dead
217
+ `tool_reason` helper introduced with the tri-state probe is removed;
218
+ and two review-round acceptance scenarios (gitignore-symlink
219
+ read-through refusal, verify-stage crash classification) were
220
+ accidentally defined outside their minitest classes and never
221
+ executed — they now run (and pass) in the package suite.
222
+
223
+ - Critical review fix: `ProjectAdd`'s privileged ownership propagation
224
+ called `FileUtils.chown_R` with the wrong argument order (path
225
+ passed as the user, the gid Integer as the file list), so every
226
+ root install with a declared owner failed the `owner` step with a
227
+ misleading error. The chown is now invoked as
228
+ `chown_R(uid, gid, path)`, rescued for TypeError as well, and is an
229
+ injectable seam so the root branch is testable without uid 0.
230
+ - Review fix: an unset or empty `PATH` no longer produces false FAILs
231
+ for the tools/adapters checks — the probe is tri-state and yields
232
+ `unknown` ("PATH is unset; cannot probe …") instead of blocking
233
+ readiness in minimal-PATH environments.
234
+ - Review fix (trust posture): a privileged (root) installer never
235
+ executes the repository-controlled `bin/ace-nav` binstub during
236
+ final verification — under root only the PATH-installed resolver is
237
+ used, consistent with the LAB-1 symlink refusals.
238
+ - Review fix: an unexpected exception from the final verify stage is
239
+ a classified `verify: failed` install stage, never a raw crash.
240
+ - Review fix: a symlinked `.gitignore` is refused up front even when
241
+ the ignore rule would already match — no privileged read-through
242
+ (`git check-ignore` follows the link) either.
243
+
244
+ - Privileged provisioning writes no longer follow repository-controlled
245
+ symlinks (LAB-1, audit PR #393): `OverseerProvisioner` refuses a
246
+ symlinked `.gitignore` (checked via lstat before any read/append) or
247
+ `.ace-tasks` component before creating anything, and
248
+ `ProjectRegistry` refuses a symlinked `.ace`/`.ace/lab` ancestor and
249
+ creates its atomic-write temp file EXCLUSIVELY (`O_CREAT|O_EXCL`)
250
+ under an unguessable random name instead of the predictable
251
+ `.config.yml.tmp.<PID>` that a pre-planted symlink could capture
252
+ (F3) — a committed symlink can no longer redirect installer writes
253
+ outside the project.
254
+
255
+ - `ace-lab verify` now judges project readiness for the declared
256
+ TARGET EXECUTOR, not the invoking installer (LAB-2): every account a
257
+ role binds must be able to reach the project through its ancestors
258
+ (search permission) and read/write the project directory — a
259
+ repository behind an installer-owned 0700 ancestor with a different
260
+ target UID is NOT ready even though the installer's git access
261
+ succeeded. The check shares the role verifier's governing-class
262
+ permission logic so the two can never drift apart.
263
+
264
+ - Final verification resolves protocol role instructions
265
+ (`wfi://`, `prompt://`, `tmpl://`) through the actual resolver for
266
+ the execution context (LAB-4): prepare's syntax-only protocol check
267
+ is no longer reused as proof of readiness, so a syntactically valid
268
+ but nonexistent role (`wfi://does-not-exist/not-a-role`) fails the
269
+ check and blocks readiness. Resolution runs `bin/ace-nav resolve`
270
+ (project binstub, else PATH) from every declared project root; an
271
+ environment without the resolver reports `unknown` and does not
272
+ block, mirroring the offline posture for network remotes.
273
+
274
+ - `_overseer` ignore provisioning decides through REAL Git evaluation
275
+ (LAB-5): `OverseerProvisioner` uses `git check-ignore` (the same
276
+ probe verify applies) instead of matching `.gitignore` lines, so a
277
+ later negation (`!.ace-tasks/_overseer/`) no longer masquerades as
278
+ an effective ignore rule — the effective rule is appended and the
279
+ decision re-evaluated to confirm.
280
+
281
+ - A malformed per-project registry (e.g. `topology: invalid`) is now a
282
+ classified per-project check failure instead of a `TypeError` from
283
+ `dig` that killed the entire readiness report (LAB-6): the document
284
+ is validated through `TopologySchema.normalize!`, and healthy checks
285
+ beside the failure keep running and stay visible.
286
+
287
+ - `RepositoryProvisioner` compares project identity against the RAW
288
+ stored remote URL (`git config --get remote.origin.url`) instead of
289
+ the `insteadOf`-expanded `git remote get-url` value (LAB-7):
290
+ idempotent re-add/resume under a common SSH rewrite configuration
291
+ (`url.<base>.insteadOf`) no longer false-fails an identical project —
292
+ connection rewriting is Git transport policy, not a changed identity.
293
+
294
+ - `KeyProvisioner`'s internal pair-usability probe could return
295
+ `false` instead of `nil` for an unusable pair (the new public
296
+ `pair_fingerprint` probe normalizes to `nil`); existing internal
297
+ callers treated both as falsy and were unaffected.
298
+
299
+ - `GrantProvisioner` failure reports now carry `kept: []` like every
300
+ other grants status, so machine consumers of the install report see
301
+ one stable schema for the grants section regardless of outcome.
302
+
303
+ - `project add` review fixes (round 1, task 8xg.t.sbz.7.1):
304
+ `RepositoryProvisioner#repository?` now requires the target path to
305
+ be the Git repository toplevel (`rev-parse --show-toplevel` compared
306
+ against the expanded path), so a non-empty directory nested inside a
307
+ parent repository's work tree is refused as a foreign location
308
+ instead of converging and mutating the parent's origin (8xikzhvh);
309
+ `ProjectRegistry` pre-validates the loaded document with
310
+ `TopologySchema.normalize!` (a malformed `topology.projects` entry is
311
+ a classified "not this lab's registry" failure, not a `TypeError`
312
+ crash — 8xikzhvi), validates the updated document before the dry-run
313
+ branch so `--dry-run` predicts invalid id/label failures exactly like
314
+ the wet run (8xikzhvj), writes the registry atomically via a temp
315
+ file plus `File.rename` (8xikzhvk), updates the label on re-add with
316
+ a changed `--label` and reports the new `updated`/`would_update`
317
+ statuses (8xikzhvl), refuses an empty-but-existing registry file with
318
+ a clear message (8xikzhvq), and `OverseerProvisioner` no longer
319
+ counts a `!` negation line as an existing ignore entry (8xikzhvp).
320
+ `docs/usage.md` documents the trusted-tier grant and project-root
321
+ prerequisites for listing the new project (8xikzhvm) and that
322
+ `path`/`remote` registry fields are installer-owned data outside the
323
+ public projection (8xikzhvs).
324
+ - Security (8xi1p4z5): when an ancestor of the key path denies search
325
+ permission (`EACCES` on realpath, e.g. an account locking out the
326
+ unprivileged installer), `KeyProvisioner`'s git-worktree guard now
327
+ fails closed — the path is judged as inside a worktree and refused —
328
+ instead of falling back to the lexical spelling, which a symlink
329
+ behind the denied ancestor could fool (the pre-F2 behavior). The
330
+ `:repair_public` classification branch is documented as a deliberate
331
+ TOCTOU safety net (8xi1p4z7), and the dry-run contract comment now
332
+ states that prediction uses read-only filesystem probes plus
333
+ read-only `ssh-keygen` invocations (8xi1p4z4).
334
+ - `RoleVerifier` (8xi16mz1): the reachability walk now resolves the
335
+ instruction file's OWN symlink too (`File.realpath(path)`), not just
336
+ the ancestors of the link's path. Previously a symlinked instruction
337
+ pointing at a file behind an unsearchable directory (e.g. 0700
338
+ other-owned) verified as reachable because only the symlink's own
339
+ ancestors were checked; the target's directory chain is what open(2)
340
+ traverses, so it is what the walk judges now.
341
+ - `KeyProvisioner` (8xi16mz2/8xi16mz5/8xi16mz6): the dry-run
342
+ prediction and the wet existing path now share one
343
+ `classify_existing_pair` step (read-only `pair_fingerprint`,
344
+ including a 0600 tmpdir copy when the private key's mode has drifted
345
+ and ssh-keygen would refuse to load it), run BEFORE the mode-drift
346
+ branch. A garbage pair with mode drift now predicts `failed` (not
347
+ usable) instead of `would_fix_mode` — the wet run converges the
348
+ modes and then fails verification, and the prediction says exactly
349
+ that; `would_fix_mode` is only ever predicted for a verified pair.
350
+ The duplicated not-usable message became the shared
351
+ `NOT_USABLE_TEMPLATE` constant.
352
+ - Security (PR #392 F2): `KeyProvisioner`'s git-worktree guard now
353
+ resolves the PHYSICAL ancestors of the key path (realpath of the
354
+ deepest existing ancestor plus the lexical tail) before any
355
+ generate/mutation step. Previously the guard walked lexical
356
+ directories only, so `/outside/alias/.ssh/id_ed25519` with `alias`
357
+ a symlink into a repository passed and the real key landed inside
358
+ the worktree — including when `.ssh` did not exist yet behind the
359
+ alias. Symlinked ancestors resolving outside any worktree remain
360
+ legitimate.
361
+ - `KeyProvisioner` (PR #392 F4): a pair that cannot be verified as
362
+ usable is now a `failed` item, never `exists`/`created` with
363
+ `fingerprint: nil`. Verification requires the `.pub` fingerprint to
364
+ match the public key derived from the private key via
365
+ `ssh-keygen -y` (comparing `-lf` on the private file is not sound —
366
+ ssh-keygen resolves the adjacent `.pub`), so garbage files and a
367
+ `.pub` belonging to a different private key both fail the prepare
368
+ aggregate (`ok: false`, non-zero exit). An unknown pair is still
369
+ never overwritten (takeover-refusal policy).
370
+ - `RoleVerifier` (PR #392 F5): instruction files are verified for
371
+ reachability, not just read bits — every ancestor directory of the
372
+ resolved path (realpath follows ancestor symlinks) must grant the
373
+ account search permission through its governing POSIX class (owner,
374
+ primary/supplementary group via the `group_source` seam, or other;
375
+ shared with the file-read check so the two cannot drift). A 0644
376
+ file behind a 0700 directory owned by another account is now
377
+ `failed`, not `verified`. ACLs remain out of scope.
378
+ - `AccountProvisioner` (PR #392, macOS UID allocation):
379
+ `next_darwin_uid` parses only the trailing `UniqueID` column of
380
+ each `dscl -list` line instead of scanning the whole output, so
381
+ digits inside account names such as `deploy-2026` can no longer be
382
+ mistaken for UIDs.
113
383
 
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.