ace-assign 0.63.1 → 0.66.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 (147) hide show
  1. checksums.yaml +4 -4
  2. data/.ace-defaults/assign/catalog/steps/auto-merge.step.yml +11 -0
  3. data/.ace-defaults/assign/catalog/steps/create-pr.step.yml +2 -2
  4. data/.ace-defaults/assign/catalog/steps/mark-pr-ready.step.yml +1 -1
  5. data/.ace-defaults/assign/catalog/steps/release-minor.step.yml +4 -2
  6. data/.ace-defaults/assign/catalog/steps/release.step.yml +4 -2
  7. data/.ace-defaults/assign/catalog/steps/update-pr-desc.step.yml +1 -1
  8. data/.ace-defaults/assign/presets/fix-bug.yml +15 -3
  9. data/.ace-defaults/assign/presets/work-on-task-ace-development.yml +16 -4
  10. data/.ace-defaults/assign/presets/work-on-task-auto-merge.yml +15 -3
  11. data/.ace-defaults/assign/presets/work-on-task.yml +16 -4
  12. data/CHANGELOG.md +62 -0
  13. data/docs/demo/canonical-skill-source.tape.yml +4 -4
  14. data/docs/protected-authority.md +282 -0
  15. data/docs/usage.md +205 -3
  16. data/exe/ace-scope-ready +55 -0
  17. data/handbook/workflow-instructions/assign/drive.wf.md +28 -3
  18. data/handbook/workflow-instructions/assign/prepare.wf.md +2 -2
  19. data/lib/ace/assign/atoms/attempt_state_machine.rb +8 -0
  20. data/lib/ace/assign/atoms/delivery_parameters.rb +19 -0
  21. data/lib/ace/assign/authority/assignment_inventory.rb +290 -0
  22. data/lib/ace/assign/authority/campaign_consumer_policy.rb +100 -0
  23. data/lib/ace/assign/authority/campaign_execution.rb +119 -0
  24. data/lib/ace/assign/authority/campaign_round_transfer.rb +82 -0
  25. data/lib/ace/assign/authority/candidate_transfer.rb +327 -0
  26. data/lib/ace/assign/authority/cleanup_result_protection.rb +53 -0
  27. data/lib/ace/assign/authority/client.rb +257 -0
  28. data/lib/ace/assign/authority/deployment.rb +627 -0
  29. data/lib/ace/assign/authority/deployment_history.rb +146 -0
  30. data/lib/ace/assign/authority/endcap.rb +455 -0
  31. data/lib/ace/assign/authority/endcap_attempts.rb +274 -0
  32. data/lib/ace/assign/authority/endcap_campaign_results.rb +232 -0
  33. data/lib/ace/assign/authority/endcap_campaign_rounds.rb +266 -0
  34. data/lib/ace/assign/authority/endcap_contexts.rb +23 -0
  35. data/lib/ace/assign/authority/endcap_inboxes.rb +37 -0
  36. data/lib/ace/assign/authority/endcap_prepared_work.rb +80 -0
  37. data/lib/ace/assign/authority/endcap_publication.rb +207 -0
  38. data/lib/ace/assign/authority/endcap_results.rb +398 -0
  39. data/lib/ace/assign/authority/endcap_review_requests.rb +183 -0
  40. data/lib/ace/assign/authority/endcap_reviews.rb +130 -0
  41. data/lib/ace/assign/authority/endcap_service_settlement.rb +152 -0
  42. data/lib/ace/assign/authority/endcap_services.rb +598 -0
  43. data/lib/ace/assign/authority/evidence_transfer.rb +67 -0
  44. data/lib/ace/assign/authority/execution_scope_observation.rb +903 -0
  45. data/lib/ace/assign/authority/inbox_context_original.rb +113 -0
  46. data/lib/ace/assign/authority/launch_attempt_consumers.rb +51 -0
  47. data/lib/ace/assign/authority/launch_campaign_registration.rb +214 -0
  48. data/lib/ace/assign/authority/launch_control_channel.rb +313 -0
  49. data/lib/ace/assign/authority/launch_control_exclusions.rb +141 -0
  50. data/lib/ace/assign/authority/launch_driver.rb +393 -0
  51. data/lib/ace/assign/authority/launch_input_inhibition.rb +132 -0
  52. data/lib/ace/assign/authority/launch_lifecycle.rb +997 -0
  53. data/lib/ace/assign/authority/launch_prepared_work.rb +122 -0
  54. data/lib/ace/assign/authority/launch_review.rb +91 -0
  55. data/lib/ace/assign/authority/launch_scope_abort.rb +81 -0
  56. data/lib/ace/assign/authority/launch_scope_admission.rb +215 -0
  57. data/lib/ace/assign/authority/launch_scope_close.rb +146 -0
  58. data/lib/ace/assign/authority/launch_scope_parent.rb +44 -0
  59. data/lib/ace/assign/authority/launch_scope_release.rb +859 -0
  60. data/lib/ace/assign/authority/launch_steering.rb +353 -0
  61. data/lib/ace/assign/authority/launch_stop.rb +254 -0
  62. data/lib/ace/assign/authority/maintenance_workspace_retirement.rb +229 -0
  63. data/lib/ace/assign/authority/original_inbox_selection.rb +41 -0
  64. data/lib/ace/assign/authority/posix_acl.rb +59 -0
  65. data/lib/ace/assign/authority/prepared_input.rb +130 -0
  66. data/lib/ace/assign/authority/prepared_queue.rb +185 -0
  67. data/lib/ace/assign/authority/prepared_task_context.rb +116 -0
  68. data/lib/ace/assign/authority/prepared_work.rb +304 -0
  69. data/lib/ace/assign/authority/prepared_worker.rb +69 -0
  70. data/lib/ace/assign/authority/private_directory.rb +33 -0
  71. data/lib/ace/assign/authority/protected_assignment_context.rb +226 -0
  72. data/lib/ace/assign/authority/readiness_hook.rb +120 -0
  73. data/lib/ace/assign/authority/receipt_transfer.rb +48 -0
  74. data/lib/ace/assign/authority/router.rb +76 -0
  75. data/lib/ace/assign/authority/server.rb +300 -0
  76. data/lib/ace/assign/authority/service_cleanup_dispatch.rb +88 -0
  77. data/lib/ace/assign/authority/service_cleanup_evidence.rb +180 -0
  78. data/lib/ace/assign/authority/service_delivery_evidence.rb +101 -0
  79. data/lib/ace/assign/authority/service_evidence.rb +286 -0
  80. data/lib/ace/assign/authority/service_publication_evidence.rb +110 -0
  81. data/lib/ace/assign/authority/task_context_entry.rb +32 -0
  82. data/lib/ace/assign/authority/transfer_codec.rb +187 -0
  83. data/lib/ace/assign/cli/commands/assignment_target.rb +15 -2
  84. data/lib/ace/assign/cli/commands/attempt/base.rb +49 -0
  85. data/lib/ace/assign/cli/commands/attempt/finish.rb +20 -4
  86. data/lib/ace/assign/cli/commands/attempt/reconcile.rb +11 -0
  87. data/lib/ace/assign/cli/commands/authority/inbox_context_principal.rb +25 -0
  88. data/lib/ace/assign/cli/commands/authority/inbox_context_selection.rb +28 -0
  89. data/lib/ace/assign/cli/commands/authority/launch.rb +97 -0
  90. data/lib/ace/assign/cli/commands/authority/serve.rb +31 -0
  91. data/lib/ace/assign/cli/commands/authority/status.rb +26 -0
  92. data/lib/ace/assign/cli/commands/authority/task_context.rb +31 -0
  93. data/lib/ace/assign/cli/commands/authority/task_context_principal.rb +25 -0
  94. data/lib/ace/assign/cli/commands/authority/task_context_selection.rb +30 -0
  95. data/lib/ace/assign/cli/commands/authority/terminate.rb +29 -0
  96. data/lib/ace/assign/cli/commands/authority/worker.rb +23 -0
  97. data/lib/ace/assign/cli/commands/campaign_export_result.rb +55 -0
  98. data/lib/ace/assign/cli/commands/campaign_record_round.rb +57 -0
  99. data/lib/ace/assign/cli/commands/create.rb +11 -1
  100. data/lib/ace/assign/cli/commands/delivery.rb +107 -0
  101. data/lib/ace/assign/cli/commands/fail.rb +6 -0
  102. data/lib/ace/assign/cli/commands/finish.rb +23 -2
  103. data/lib/ace/assign/cli/commands/fork_session.rb +6 -2
  104. data/lib/ace/assign/cli/commands/inbox_bind.rb +34 -0
  105. data/lib/ace/assign/cli/commands/protected_submission.rb +61 -0
  106. data/lib/ace/assign/cli/commands/resume.rb +38 -0
  107. data/lib/ace/assign/cli/commands/select.rb +2 -0
  108. data/lib/ace/assign/cli/commands/start.rb +2 -0
  109. data/lib/ace/assign/cli/commands/status.rb +4 -2
  110. data/lib/ace/assign/cli/commands/step.rb +3 -1
  111. data/lib/ace/assign/cli/commands/submit_candidate.rb +34 -0
  112. data/lib/ace/assign/cli/commands/submit_result.rb +61 -0
  113. data/lib/ace/assign/cli.rb +56 -4
  114. data/lib/ace/assign/errors.rb +139 -0
  115. data/lib/ace/assign/models/assignment.rb +26 -4
  116. data/lib/ace/assign/models/evidence_event.rb +4 -2
  117. data/lib/ace/assign/molecules/assignment_manager.rb +12 -2
  118. data/lib/ace/assign/molecules/attempt_reconciler.rb +51 -12
  119. data/lib/ace/assign/molecules/canonical_attempt_state.rb +151 -0
  120. data/lib/ace/assign/molecules/canonical_evidence.rb +153 -0
  121. data/lib/ace/assign/molecules/canonical_read_snapshot.rb +374 -0
  122. data/lib/ace/assign/molecules/evidence_journal.rb +922 -91
  123. data/lib/ace/assign/molecules/execution_identity_resolver.rb +49 -9
  124. data/lib/ace/assign/molecules/execution_scope_lineage.rb +345 -0
  125. data/lib/ace/assign/molecules/fork_session_launcher.rb +16 -6
  126. data/lib/ace/assign/molecules/journal_input_inhibition.rb +78 -0
  127. data/lib/ace/assign/molecules/journal_mutation.rb +271 -0
  128. data/lib/ace/assign/molecules/journal_prompt_mutation.rb +241 -0
  129. data/lib/ace/assign/molecules/lifecycle_exclusion.rb +182 -26
  130. data/lib/ace/assign/molecules/original_launch_binding.rb +37 -0
  131. data/lib/ace/assign/molecules/proposal_journal.rb +151 -0
  132. data/lib/ace/assign/molecules/protected_control_exclusion.rb +303 -0
  133. data/lib/ace/assign/molecules/protected_workspace_exclusion.rb +330 -0
  134. data/lib/ace/assign/molecules/protected_workspace_provisioner.rb +164 -0
  135. data/lib/ace/assign/molecules/queue_scanner.rb +26 -11
  136. data/lib/ace/assign/molecules/receipt_verifier.rb +68 -32
  137. data/lib/ace/assign/molecules/step_writer.rb +27 -5
  138. data/lib/ace/assign/molecules/terminal_scope_receipt.rb +88 -0
  139. data/lib/ace/assign/organisms/assignment_executor.rb +10 -4
  140. data/lib/ace/assign/organisms/attempt_coordinator.rb +302 -13
  141. data/lib/ace/assign/organisms/delivery_coordinator.rb +342 -0
  142. data/lib/ace/assign/organisms/prepared_work_builder.rb +194 -0
  143. data/lib/ace/assign/organisms/protected_delivery_coordinator.rb +100 -0
  144. data/lib/ace/assign/organisms/task_assignment_creator.rb +11 -4
  145. data/lib/ace/assign/version.rb +1 -1
  146. data/lib/ace/assign.rb +4 -102
  147. metadata +186 -8
@@ -0,0 +1,282 @@
1
+ ---
2
+ doc-type: user
3
+ purpose: Fixed protected authority deployment and canonical launch lifecycle
4
+ ace-docs:
5
+ last-updated: 2026-10-05
6
+ last-checked: 2026-10-05
7
+ subject:
8
+ - code:
9
+ diff:
10
+ paths:
11
+ - ace-assign/lib/ace/assign/authority/**/*.rb
12
+ - ace-assign/lib/ace/assign/molecules/execution_scope_lineage.rb
13
+ - ace-runtime/lib/ace/runtime/molecules/systemd_scope_manager.rb
14
+ ---
15
+
16
+ # Protected native launch
17
+
18
+ `ace-assign authority serve --authority AUTHORITY` runs the standalone launch
19
+ composition. Lab owns the services composition using the same Assign server,
20
+ router, lifecycle, journal and endpoint. Its full-service startup guard remains
21
+ closed while required finish, recovery, inbox and no-effect handlers are incomplete.
22
+ The installed `composition` must match the entrypoint. A project has exactly one
23
+ configured authority owner; duplicate project owners or endpoints fail before
24
+ listener creation.
25
+
26
+ An administrator provisions `/etc/ace/assignment-authorities.json`. There is no
27
+ caller-selected configuration path or local fallback. The outer object contains
28
+ `schema: "ace.assign.authorities/v2"`, `authorities`, `projects`, and
29
+ `launch_mappings`; unknown fields are refused. The fixed schema is implemented
30
+ by `Ace::Assign::Authority::Deployment`:
31
+
32
+ | Installed object | Required fields |
33
+ | --- | --- |
34
+ | `authorities.ID` | `uid`, `gid`, sorted unique `groups`, `socket_path`, `state_root`, `composition` (`launch` or `services`) |
35
+ | `projects.ID` | `journal_repository`, `evidence_git_ref` (`refs/ace/execution`), `evidence_checkout_root`, `assignment_root`, `candidate_root`, `campaign_repository`, `campaign_store_root`, `launcher_uids`, `reviewer_uids`, `worker_uids`, `service_executor_uids`, `supervisor_uids`, `peer_credentials`, optional `service_receivers` |
36
+ | `projects.ID.peer_credentials.UID` | `gid`, sorted unique `groups`, `scratch_root`; every configured role UID has one fixed entry |
37
+ | `launch_mappings.ID` | `project_id`, `authority_id`, `launcher_uid`, `launcher_gid`, `launcher_groups`, `worker_uid`, `worker_gid`, `worker_groups`, `worker_actor`, `worker_cwd`, `worker_entry`, `worker_env`, `bootstrap`, `bootstrap_sha256`, `native`, `execution_scope`, `task_context_entry` |
38
+ | `launch_mappings.ID.native` | `socket_path`, `executable`, `executable_sha256`, `version` (`0.9.3`), `protocol` (`22`), canonical `workspace_id` (`wN`) |
39
+ | `launch_mappings.ID.execution_scope` | `backend` (`linux_systemd_cgroup_v2`), `slot_id`, `slice_unit`, `service_unit`, `unit_manifest_sha256`, `boundary_manifest_sha256`, `root_directory`, `runtime_directory`, `network_namespace_path` |
40
+
41
+ A services composition requires a nonempty installed `service_receivers` map; launch composition may omit it. Each fixed service ID maps to exactly `{executor_uid, socket_path, staging_root}`. The positive nonroot executor UID belongs to the project's executor allowlist and cannot also be any configured authority, launcher, reviewer, worker or supervisor. This keeps kernel role selection unambiguous and private authority journals inaccessible to executors.
42
+
43
+ Receiver socket and staging paths are canonical absolute paths with root-or-executor owned, nonwritable ancestry. Staging is executor-owned mode0700 and disjoint from other receiver staging and authority state roots. The endpoint resides outside private staging, where the actual authority can traverse its ancestry and inspect it. A pre-existing endpoint must be an executor-owned socket with its installed GID and no permissions for others. Duplicate receiver/authority endpoints, substituted paths and unreadable placement refuse before the authority opens its listener or creates journal state. These mappings select identity and placement; operation argv and grants remain the existing Lab ServicePolicy.
44
+
45
+ All paths are canonical and absolute. The native socket identity is the observed
46
+ `[device, inode, worker_uid]`. The server identity is the exact observed object
47
+ `{pid, uid, gid, groups, parent_pid, started_at, host}`, where `started_at` is
48
+ `linux:BOOT_ID:START_TICKS`. These observations must be made after the root-owned
49
+ native service starts; they cannot be invented, copied from a previous boot, or
50
+ supplied by a worker. The immutable per-attempt native stage owns these facts;
51
+ the deployment record contains no persisted live server/socket tuple. A replacement
52
+ cannot be repinned into the original generation.
53
+ The root-installed native service, its executable, environment and configuration
54
+ are trusted. Merely running arbitrary worker-supplied server code under the worker
55
+ account does not meet this deployment contract.
56
+
57
+ The authority, launcher and worker are distinct non-root accounts. Authority
58
+ journal/cache/import/candidate/assignment/state roots are authority-owned 0700
59
+ with protected ancestry. Peer scratch roots are private to their mapped account.
60
+ The authority socket's directory prevents worker/launcher replacement; its group
61
+ and narrow installer ACL must permit the configured peers to connect. Native
62
+ socket ACL permits the trusted launcher to use the pinned Herdr API. The
63
+ bootstrap, payload executable, native executable, mapping and their ancestors are
64
+ root-owned and non-writable by the worker. Payload argv/cwd/environment are fixed
65
+ installed data; loader and interpreter injection environment fields are refused.
66
+
67
+ Linux Yama `ptrace_scope=2`, empty Inh/Prm/Eff/Bnd/Amb capabilities,
68
+ `NoNewPrivs=1`, fixed real/effective/saved/filesystem UID/GID and supplementary
69
+ groups are mandatory for every participant, including the pinned native server.
70
+ The shipped [native build/install instructions](../../ace-runtime/native/README.md)
71
+ describe architecture, distro dependencies, immutable artifact digest and service
72
+ unit requirements. ACE verifies and refuses missing policy; it does not modify
73
+ sysctls, create accounts, change another user's Herdr, or install service policy.
74
+
75
+ After installation, the trusted launcher preflights without any mutation or
76
+ native creation:
77
+
78
+ ```sh
79
+ ace-assign authority launch --mapping MAPPING --dry-run
80
+ ```
81
+
82
+ Launch one managed definition using a stable invocation ID:
83
+
84
+ ```sh
85
+ ace-assign authority launch --mapping MAPPING --assignment ASSIGNMENT \
86
+ --definition /absolute/prepared-definition.json \
87
+ --prepared-bundle /absolute/prepared-input.bundle --step 010 \
88
+ --base-head EXACT_40_CHARACTER_COMMIT --mutation INVOCATION_ID
89
+ ```
90
+
91
+ The final definition (maximum 32 KiB) includes the exact `prepared_work` Git
92
+ head/tree, manifest and selected subtree reference, with matching `session_id`,
93
+ `task_id` and installed `project_id`. `--prepared-bundle` is mandatory: it supplies
94
+ the original complete prepared-input Git bundle, bounded to 64 MiB. Both inputs
95
+ are read from held regular files with bounded reads and no final symlink following
96
+ before launch effects. The authority derives the final definition from the verified
97
+ prepared tree and commits it with the exact received bundle in one journal mutation.
98
+ Reusing the same definition with different bundle bytes refuses. The owner-private
99
+ definition cache remains separate from the worker queue. An active attempt blocks
100
+ changed definitions. A fresh reservation creates canonical intent/provisioning,
101
+ then the owner activates and binds its independent parent under the slot exclusion.
102
+ It pins existing resource objects, the authority mount namespace and the installed
103
+ nsfs network namespace. Parent binding selects protected network evidence references
104
+ without asserting policy readiness or requiring an available report.
105
+
106
+ Native service admission is a separate private canonical CAS after all required
107
+ readiness evidence succeeds. Only its fresh winner may start the fixed service;
108
+ replay and lost replies never permit another start. Native and original-child
109
+ bindings remain separate immutable stages before payload release.
110
+
111
+ The original authenticated native layout response fixes workspace/tab/pane and
112
+ exact child lineage. Record captures that original child; bind creates the running
113
+ attempt without payload permission. The immutable native bootstrap connects once,
114
+ sets non-dumpable before admission, and waits for the separate release. Release
115
+ commits issuance before one framed permission. Replay returns canonical issuance
116
+ without sending another frame. Reply loss after issuance is potentially executed.
117
+ A worker-controlled native input/close path can deny or interrupt launch, but cannot
118
+ replace the authenticated origin or forge authority release.
119
+
120
+ Inspect or request exact native termination through the same installed mapping.
121
+ Once the original launcher process has exited, run these commands as a mapped
122
+ recovery supervisor with the installed native close ACL; a new process under the
123
+ launcher UID does not inherit the original launcher incarnation:
124
+
125
+ ```sh
126
+ ace-assign authority status --mapping MAPPING --assignment ASSIGNMENT --attempt ATTEMPT
127
+ ace-assign authority terminate --mapping MAPPING --assignment ASSIGNMENT --attempt ATTEMPT
128
+ ```
129
+
130
+ A child pidfd proving exit cannot release the whole parent reservation. The
131
+ connected never-native-admitted path seals the original parent before fixed service
132
+ stop, records fresh exact recursive-empty/no-pending/resource closure, selects that
133
+ canonical proof in the guarded pre-release abort, then privately commits reservation
134
+ release after service/inbox settlement at the same immutable journal commit. A lost
135
+ reply keeps the original accepted response. Restart can resume a missing private
136
+ release without issuing native or payload permission. A new reservation first retires
137
+ its exactly released predecessor through the same owner and slot exclusion.
138
+
139
+ `observe_execution_scope` is read-only. `close_execution_scope` is an ordinary
140
+ canonical mutation: the first fresh call seals, a later fresh call records closure
141
+ when verifiable. A fresh sealed close can resume a lost fixed service stop; replay
142
+ performs no manager I/O. The private `scope_service_admission` and
143
+ `scope_reservation_release` operations are absent from the public allowlist.
144
+ Reserve/close RPCs have a fixed 90-second ceiling for bounded manager waits;
145
+ other control calls retain their existing ceiling.
146
+
147
+ This source checkpoint proves the connected never-native-admitted failure/recovery
148
+ path with controlled native-owner observations and real canonical journals. Full
149
+ native hook/readiness and post-native writer observation, admitted-but-unbound
150
+ cleanup, non-abort terminal release, and complete original/candidate installation
151
+ maintenance remain separate required implementation work. The current maintenance
152
+ helpers do not yet implement the accepted candidate-deployment union contract.
153
+ No source fixture establishes installed namespace, network policy or containment
154
+ acceptance; every installed SC remains open.
155
+
156
+ Control replies are at most 16 KiB. Source-selected service transfer operations use
157
+ a 16 KiB authenticated header followed by bounded raw bytes, a shared 30-second
158
+ deadline, exact hashes, upload write-EOF and at most two active transfers. Exact
159
+ origin/role admission happens before private spool allocation and is revalidated
160
+ before canonical mutation. The standalone launch composition exposes no transfer
161
+ operations.
162
+
163
+ Source tests and Linux primitive fixtures are separate from installed deployment
164
+ acceptance. A Docker kernel with Yama=0 is expected to refuse the product gate;
165
+ it does not establish the required positive protected launch proof.
166
+
167
+ Protected launch uses only `layout.apply` in the installed workspace. `workspace.get` checks that exact workspace ID before creation; its reply must echo the ID even when another workspace is active. Missing or replaced containers refuse without fallback. Container contents supply no worker evidence. The original fresh layout reply remains the source of the new tab/pane identity.
168
+
169
+ Receiver admission reads Linux `system.posix_acl_access` on every directory from
170
+ the endpoint parent and private staging root to `/`, and evaluates search access
171
+ for the installed executor UID, primary GID and supplemental groups. Named user
172
+ entries override group/other entries; the ACL mask constrains named user and
173
+ group entries. Absence of an access ACL uses ordinary mode permissions;
174
+ unsupported, unreadable or malformed ACL data refuses startup. The authority
175
+ must also actually traverse the endpoint and staging parents. This inspection
176
+ does not claim an executor readiness handshake or bypass additional LSM policy;
177
+ the installed receiver still performs its own bind and private staging checks.
178
+
179
+ ## Canonical result Client API
180
+
181
+ The source-owned services handlers accept a live mapped worker's bounded result
182
+ without terminalizing the attempt or releasing scope. The public seam is
183
+ `Ace::Assign::Authority::Client`; no result CLI is introduced. Launch-only
184
+ `attempt_status` retains its existing schema without a result selector. These
185
+ source handlers and their deterministic tests do not establish installed
186
+ full-service deployment acceptance.
187
+
188
+ From an admitted services attempt, discover its current candidate and authority
189
+ generation, then upload receipt JSON bytes followed by its declared artifact
190
+ bytes in order:
191
+
192
+ ```ruby
193
+ require "ace/assign"
194
+ require "json"
195
+ require "digest"
196
+
197
+ client = Ace::Assign::Authority::Client.new(mapping_id: mapping_id)
198
+ binding = {"assignment_id" => assignment_id, "attempt_id" => attempt_id}
199
+ status = client.call("attempt_status", binding.merge(
200
+ "result_candidate_generation" => nil
201
+ )).data
202
+ receipt_bytes = File.binread(local_receipt_path)
203
+ receipt = JSON.parse(receipt_bytes)
204
+ artifacts = receipt.fetch("artifacts").map { |ref| File.binread(ref.fetch("path")) }
205
+ params = binding.merge(
206
+ "expected_generation" => status.fetch("authority_generation"),
207
+ "candidate_generation" => status.fetch("result_candidate_generation"),
208
+ "head" => receipt.fetch("head"),
209
+ "receipt_sha256" => Digest::SHA256.hexdigest(receipt_bytes)
210
+ )
211
+ reply = client.call("submit_result", params, mutation_id: persisted_mutation_id,
212
+ upload_parts: [receipt_bytes, *artifacts], purpose: :receipt_artifacts, timeout: 30)
213
+ result = reply.data
214
+ ```
215
+
216
+ Only local byte strings cross the boundary; receipt paths are declarations, not
217
+ authority filesystem access. The authority checks exact canonical worker
218
+ attribution, original receipt digest and SHA256, candidate, native lineage and
219
+ current visibility. It normalizes artifact references and writes private
220
+ `result_submitted` metadata, imported blobs and a sanitized mutation reply in one
221
+ Git CAS. Failed results may have `artifacts: []`; no synthetic artifact is added.
222
+ Campaign receipts refuse until the trusted campaign owner is integrated.
223
+
224
+ Persist the exact params, mutation ID and ordered byte strings before submission.
225
+ An exact retry while the worker remains live and the attempt active returns the
226
+ same result without another import. A changed input or fresh mutation ID for the
227
+ same candidate conflicts. Correcting a result requires a new candidate generation
228
+ even at unchanged HEAD; both records remain immutable. Submission creates no
229
+ `receipt_accepted` event or terminal transition.
230
+
231
+ The reply contains result ID, head, candidate generation, verdict, ordered
232
+ canonical `{path, sha256}` references, generation and journal commit. Its
233
+ `uploaded_receipt_sha256`, `original_receipt_digest` and normalized
234
+ `receipt_digest` are distinct values. Private receipt, producer and process
235
+ binding are excluded from fresh and replay result replies.
236
+
237
+ After reply loss or worker exit, the exact owning launcher or mapped supervisor
238
+ can discover the retained result and fetch one referenced artifact:
239
+
240
+ ```ruby
241
+ status = client.call("attempt_status", binding.merge(
242
+ "result_candidate_generation" => retained_generation # nil selects latest
243
+ )).data
244
+ result = status.fetch("submitted_result")
245
+ if result && (reference = result.fetch("artifacts").first)
246
+ fetched = client.call("evidence_fetch", binding.merge(
247
+ "kind" => "result", "purpose_id" => result.fetch("result_id"),
248
+ "artifact_id" => reference.fetch("path").delete_prefix("evidence/imports/")
249
+ ), download: true, purpose: :artifacts, timeout: 30)
250
+ artifact_bytes = fetched.parts.fetch(0)
251
+ end
252
+ ```
253
+
254
+ Client closes its request write side for services status and evidence fetch;
255
+ Server refuses extra body bytes before exposing either read projection. Status
256
+ reads launch and result projections from one immutable canonical commit.
257
+ `authority_generation` is the current CAS generation; a known candidate without
258
+ a result has `submitted_result: null`. Unknown selected generation is `missing`;
259
+ corrupt or missing referenced provenance is `evidence_unavailable`. Disposable
260
+ caches and requester files never supply evidence.
261
+
262
+ Fetch returns exactly `descriptor`, `generation`, `journal_commit` and the
263
+ Server-generated `transfer` descriptor, with one binary part. Client validates
264
+ the closed canonical descriptor, exact selected purpose, one-part framing,
265
+ aggregate length and SHA256 before exposing bytes. Owning workers read only
266
+ their own results; assigned reviewers read their exact retained review and
267
+ same-candidate result under their live process binding; recorded executors read
268
+ only their own service evidence. Exact owning launchers and mapped supervisors
269
+ read permitted attempt evidence, including retained terminal history. Current
270
+ visibility and peer authorization apply to every read. Reassignment revokes the
271
+ old reviewer purpose. Inbox and observation evidence refuse until their existing
272
+ signer and observer owners provide complete canonical provenance.
273
+
274
+ The fixed worker entry is the closed `{interpreter: REF, wrapper: REF}` pair, with
275
+ exact path/bytes/SHA256 references (interpreter<=32MiB, wrapper<=1MiB). The
276
+ existing installed unit manifest associates the wrapper with `worker_executable`
277
+ and the interpreter uniquely with `runtime_dependency`. The wrapper is source,
278
+ so it need not carry executable permission; the selected interpreter must. The
279
+ original retained mapping pair is part of the authenticated compact release.
280
+ The native gate hashes held files, seals a readonly code snapshot, and uses the
281
+ fixed isolated Python FD4/FD5 command while preserving operational stdio. No
282
+ worker argv/shebang/PATH fallback remains. Native installed acceptance is separate.
data/docs/usage.md CHANGED
@@ -89,7 +89,9 @@ ace-assign add --yaml .ace-local/assign/jobs/add-task.yml --after 010 --child
89
89
 
90
90
  Execution evidence lives in assignment attempts, not in reports. An attempt binds an immutable ID to an assignment, step/subtree scope, project, boundary-derived actor/role/runtime, source task, and `base_head`; the deliverable `candidate_head` is pinned only when accepted evidence is submitted, and the evidence journal commit is tracked separately from both.
91
91
 
92
- ### Attempt lifecycle
92
+ ### Ordinary local attempt lifecycle
93
+
94
+ Lifecycle exclusion uses the selected repository's actual Git common directory, shared by linked worktrees. `CACHE_BASE` changes assignment cache storage but cannot split creator and prune locks into a separate namespace. A missing Git repository or malformed retained removal fence refuses admission; it does not create a fallback lock root.
93
95
 
94
96
  ```bash
95
97
  ace-assign attempt start --assignment ASSIGNMENT --step STEP --project PROJECT
@@ -101,7 +103,21 @@ ace-assign attempt reconcile --attempt ATTEMPT --receipt receipt.json
101
103
  - `attempt start` returns a JSON projection of the attempt (ID, state, binding facts, evidence references, digests). A repeated identical start returns the same attempt; a conflicting start exits 5 without launching a second writer.
102
104
  - `attempt status` exposes only state, binding, references, and digests -- never credentials or receipt bytes.
103
105
  - `attempt finish` accepts a structured receipt: operation, attributed producer, exact tested/reviewed head, verdict, artifact paths with SHA-256 digests, executed checks, and -- for review operations -- an executed independent reviewer verdict for that head.
104
- - `attempt reconcile` classifies interrupted attempts (`stopped` before process start, `uncertain` when an effect cannot be proven either way, still `running` only for verifiably live processes) and resolves uncertainty only against a verified receipt attributed to the recorded execution boundary. Merge, publish, and deploy effects are never replayed automatically.
106
+ - `attempt reconcile` classifies interrupted attempts (`uncertain` when launch or effect completion cannot be proven either way, still `running` only for verifiably live processes) and resolves uncertainty only against a verified receipt attributed to the recorded execution boundary. Merge, publish, and deploy effects are never replayed automatically.
107
+
108
+ ### Protected completion, recovery and Inbox registration
109
+
110
+ ```bash
111
+ ace-assign attempt finish --mapping MAP --assignment ASSIGNMENT --attempt ATTEMPT --result RESULT --head SHA --candidate-generation N --mutation ID --expected-generation N
112
+ ace-assign attempt reconcile --mapping MAP --assignment ASSIGNMENT --attempt ATTEMPT --mutation ID --expected-generation N
113
+ ace-assign inbox-bind --mapping MAP --assignment ASSIGNMENT --attempt ATTEMPT --event EVENT --inbox-context CONTEXT --mutation ID --expected-generation N
114
+ ```
115
+
116
+ The installed authority authenticates the current caller. Finish and recovery require the original launcher or an authorized mapped supervisor; Inbox binding requires the original worker or its authenticated descendant and an open execution scope. Installed current or retained authority, service and Inbox accounts also stay on the protected path: unsupported roles refuse rather than opening local authority.
117
+
118
+ Protected finish selects an already submitted canonical result. A succeeded result requires accepted independent review for that exact candidate, prior no-writers proof and complete service settlement. It does not close the scope or accept `--receipt`. Protected recovery observes one original attempt; unresolved service effects remain unresolved. Terminal/released recovery returns authenticated retained selectors without creating another mutation or restarting execution. Inbox binding retains the original event/attempt attribution; fresh binding after scope seal refuses.
119
+
120
+ Keep the original `--mutation` and `--expected-generation` for exact retries. Authority generation is distinct from `--candidate-generation`; rereading a newer generation changes the request. Assignment, attempt and mapping hints must agree with explicit selection. Commands print canonical JSON metadata and never resend effects during recovery.
105
121
 
106
122
  ### State machine
107
123
 
@@ -138,7 +154,14 @@ External service operations (driven through `ace-lab service request`) claim the
138
154
 
139
155
  - Task-attached (managed) attempts journal accepted evidence to the configured evidence Git ref (default `refs/ace/execution`) through an isolated, disposable audit checkout, outside the deliverable candidate branch. Journal commits never advance or exempt the reviewed candidate.
140
156
  - Taskless assignments persist attempts under `.ace-local/assign` with `recovery_mode: local_only`. This path never claims Git-backed recovery and cannot record external effects (`merge`, `publish`, `deploy`, `release`); attach the assignment to a source task before managed delivery.
141
- - Actor identity always comes from the execution boundary (OS login for local use, or the trusted service executor's identity). Unknown identity fails closed; flags never grant identity or authority.
157
+ - Actor identity always comes from the execution boundary (the kernel process account for local use, or the trusted service executor's identity). Local execution requires equal real and effective UIDs and resolves the effective UID's OS account; terminal login, `USER`, and `LOGNAME` do not select the actor. Unknown accounts, changing credentials, and unsupported privilege transitions fail closed; flags never grant identity or authority. Historical attempt ownership is never rewritten.
158
+
159
+ For developer callers, `Ace::Assign::Molecules::ExecutionIdentityResolver.new(adapter: "local").resolve.actor`
160
+ returns `Etc.getpwuid(Process.euid).name` for an ordinary process, including one without a terminal login.
161
+ If the OS account cannot be established or real/effective UIDs differ, it raises
162
+ `Ace::Assign::AttemptErrors::UnauthorizedIdentity` (CLI exit code 5). Correct the process launch/account;
163
+ setting `USER` or `LOGNAME` cannot authorize it. A previously recorded foreign owner still requires the
164
+ existing recovery process; this change does not transfer ownership.
142
165
 
143
166
  ### status integration
144
167
 
@@ -409,3 +432,182 @@ ace-assign select --clear
409
432
  | 130 | Interrupted (SIGINT) |
410
433
 
411
434
  See [exit-codes.md](exit-codes.md) for complete descriptions.
435
+
436
+ ## Resume accepted assignment state
437
+
438
+ ```bash
439
+ ace-assign resume --assignment ID --dry-run
440
+ ace-assign resume --assignment ID
441
+ ```
442
+
443
+ Resume returns JSON with task goal, checkpoint, attempts, pending HITL references and unresolved effect/inbox references. Each attempt reports `adopt`, `restart-required` or `reconcile-required`, liveness, recovery reason and its last verified observation. Dry-run writes nothing. Actual resume journals a bounded observation; unproven running attempts become uncertain and keep their writer slot. Intent-only reservations remain `reserved` with `reconcile-required`: absent process-start history never proves that a child did not launch. `attempt start` refuses to reuse an unbound reservation, and `attempt reconcile` reports that protected launch reconciliation requires positive no-execution/no-surviving-writer proof before abort. The protected launch authority owns that proof and abort path; these source primitives do not implement it. Finish also rejects an unbound reservation before accepting any receipt, even if a stale local cache says running. Imported canonical evidence stores the exact submitted bytes without Git clean filters or line-ending conversions; immutable reuse compares the accepted Git blob. It never launches a new process or resends a payload. Restart goes through a new `attempt start` only after the previous attempt ends with accepted evidence.
444
+
445
+ Managed agents invoking attempt start inside tmux/Herdr obtain a runtime-owned process binding. Recovery compares PID, UID, OS start time, host, exact pane/native session and Herdr terminal/native thread. A surviving shell is not the agent. Missing, unreadable or reused identity remains unknown. A plain local CLI establishes its actor through its effective UID's OS account, requiring equal real/effective UIDs, but supplies no durable agent identity; trusted service executors may supply `process_pid` in their configured identity JSON. Process observation never grants service privilege or effect authority.
446
+
447
+ Pending HITL references retain their exact scope/attempt and are not rebound by
448
+ resume. Uncertain external effects require their actual owner/result evidence;
449
+ message submission cannot establish business success. `AttemptCoordinator#bind_inbox`
450
+ retains original event attribution only. There is no agent-read reconciliation
451
+ or signing-key requirement for ending or recovering an attempt. Inspect current
452
+ agent progress at its original Herdr/tmux panel; uncertain sends are not repeated
453
+ automatically.
454
+
455
+ Installed Herdr/Pi compaction and writing-child stop/close acceptance must be recorded separately from deterministic fixture coverage before Lab cutover.
456
+
457
+ ### Attempt-bound forge delivery
458
+
459
+ `ace-assign create --task TASK --preset work-on-task --delivery-parameters delivery.json`
460
+ accepts an explicit delivery mapping. A local-only preset rejects that mapping.
461
+ The same mapping is retained as `delivery` in the assignment job:
462
+
463
+ ```json
464
+ {"forge_server":"lab-forge","pr_provenance":{"mode":"fork","head_repository_url":"https://forge.example/worker/project","head_ref":"task-branch","base_repository_url":"https://forge.example/team/project","base_ref":"main"}}
465
+ ```
466
+
467
+ Use either `forge_server` or boolean `forge_default: true`. With neither,
468
+ the first remote delivery resolves the repository remote and journals the selected
469
+ forge. Subsequent mutations retain that identity. Canonical mode requires equal
470
+ head and base repository URLs; fork mode names both repositories explicitly.
471
+ The source ref must already contain the exact local candidate commit.
472
+
473
+ `ace-assign delivery --assignment ID --attempt ATTEMPT --operation create --title TITLE`
474
+ creates a draft. Operations `update`, `review` and `status` reuse that attempt;
475
+ status and review do not append evidence. `ready --tests TEST-REF.json --review
476
+ REVIEW-REF.json` requires references containing `attempt_id` and `receipt_digest`
477
+ to coordinator-accepted, executed tests and independent approval at the current
478
+ candidate. Remote CI remains visible and advisory. An unresolved mutation is
479
+ reconciled by exact provider identity; absence or conflicting matches remain
480
+ unresolved and never trigger an automatic second write. Retrying readiness uses
481
+ the original intent's evidence references, revalidated against the current head.
482
+
483
+ Merge runs in the authorized qjx executor, which calls the neutral provider merge
484
+ primitive under its existing scoped policy. The worker only consumes completion:
485
+ `delivery --operation merge --service-request REQUEST --tests TEST-REF.json
486
+ --review REVIEW-REF.json`. A completed, verified receipt must bind this assignment,
487
+ attempt, candidate and exact PR resource; credentials do not grant authority.
488
+ Version and changelog preparation do not authorize publication.
489
+
490
+ The neutral `git/pr/create` and `git/pr/update` workflow and skill assets require
491
+ qkb.1 companion adoption across installed consumers. Their source tests do not
492
+ claim fresh installed workflow acceptance or authorized live merge acceptance.
493
+
494
+ Delivery context belongs to the assignment's existing evidence journal, while each
495
+ operation belongs to its own scoped attempt. Closing create step `030` and starting
496
+ update `147` or readiness `148` retains the exact PR and forge. An unresolved intent
497
+ from any earlier scoped attempt prevents a second mutation: reconciliation records
498
+ a result under the current attempt and links the original intent digest and attempt.
499
+ Status history includes each event's owning `attempt_id` and `event_digest`. No
500
+ single delivery attempt needs to remain active throughout the recipe.
501
+ If reconciliation recovers a different operation than the requested step (for
502
+ example, create while invoking readiness), the command reports that the requested
503
+ step still needs execution. It does not return success for an unperformed step.
504
+ Retry that requested operation with its current evidence after recovery settles.
505
+
506
+ Recovery compares the exact requested effect, not only its operation name. An
507
+ uncertain update of one title/body cannot complete a later update with different
508
+ content. Creation also checks the requested title/body against the observed draft,
509
+ including adoption of an already known draft. A mismatched request receives
510
+ `AttemptErrors::CurrentEffectRequired` after the old intent is settled; the new
511
+ effect remains unperformed until explicitly retried. Explicit readiness references
512
+ must match the recovered intent; omitting them during retry retains and revalidates
513
+ its original references. Merge always consumes the exact named qjx service request,
514
+ its candidate/target binding and verified receipt, never an older PR result.
515
+
516
+
517
+ ## Protected cross-user native launch
518
+
519
+ The fixed installed authority exposes `ace-assign authority serve`, `launch`,
520
+ `status` and `terminate`. See [protected authority](protected-authority.md) for
521
+ OS policy, native artifact installation, canonical launch stages and safe replay.
522
+
523
+ ## Source-owned installation maintenance inventory
524
+
525
+ The trusted installation owner loads exact protected content references for the
526
+ original and staged candidate descriptor using `Authority::Deployment.load_artifact`.
527
+ This Ruby-only source interface validates immutable authenticated descriptor bytes;
528
+ it does not establish installed network or native readiness and is not exposed
529
+ through worker transport or a CLI selector.
530
+
531
+ `LaunchLifecycle#with_execution_slots(mapping_ids:, candidate_deployment:)` holds
532
+ the same slot exclusions as normal admission while yielding frozen fixed
533
+ mapping/journal/commit contexts. Changed, added and removed mappings cannot be
534
+ omitted. Removed mappings retain their original journal selection; new mappings
535
+ use the staged selection. Existing mappings and physical slots cannot be recycled
536
+ to hide history. Ref changes, unreadable roots and conflicting aliases refuse.
537
+
538
+ Full `slot_reusable!` maintenance eligibility and parent retirement remain
539
+ unavailable until complete historical authentication and actual current inventory
540
+ verification are connected. The inventory block grants neither policy publication
541
+ nor positive maintenance readiness. Finish/stop wiring and installed acceptance
542
+ remain separate unfinished 9c2 requirements. Existing normal admission and guarded
543
+ abort/release behavior retain their current checks.
544
+
545
+ ## Authenticated readiness and terminal scope closure
546
+
547
+ The source-owned fixed ExecStartPost readiness hook uploads one bounded report
548
+ through the authenticated authority connection, closes its write side and waits
549
+ for acknowledgement. The authority joins the exact manager-selected hook/server
550
+ actors, immutable runtime/configuration dependencies, original boot/network
551
+ proofs and complete live mount/IPC/resource observations before binding native
552
+ admission. The hook’s observed server network namespace must match both the original
553
+ parent selection and the canonical network installation admission. A missing,
554
+ malformed or different identity refuses native binding before child release.
555
+ Unknown writable projections, writable API descendants, changed
556
+ socket endpoints and incomplete evidence refuse. A read-only socket mount does
557
+ not authorize messages.
558
+
559
+ Original service issuance remains tracked while the manager command runs outside
560
+ the callback's exclusions. Sealing cannot publish an empty-scope proof while an
561
+ original issuer can still start the service. Admitted-but-unbound starts use the
562
+ same positive cleanup and worker baseline as bound starts; an inactive snapshot
563
+ does not assert that execution never happened.
564
+
565
+ After the genuine terminal owner accepts authenticated result evidence, the
566
+ existing release producer joins that receipt to the exact sealed empty scope.
567
+ Current activation, jobs, recursive population, original resource identities,
568
+ native directory absence and outside worker UID writers are checked again before
569
+ publishing or consuming the proof. Empty scope alone supplies no terminal result.
570
+
571
+ The required per-boot host proof is selected before parent activation and retained
572
+ in canonical `scope_bound.boot_baseline_selection`; later reads authenticate that
573
+ original immutable artifact rather than a current pointer. Domain gad.8/gad.b
574
+ still must deliver trusted cold-start/boot refresh and activation inhibition.
575
+ These source interfaces and controlled tests do not establish installed native,
576
+ network or boot acceptance, and task 9c2 remains in progress through integration
577
+ and independent review.
578
+
579
+
580
+ ### Protected sealed-service no-effect settlement
581
+
582
+ For the fixed original executor, `service_status` includes a closed immutable `settlement_context` containing the authenticated original request, execution binding and accepted current challenge (or null). Full original mapping/project/receiver credentials are checked before this private context is exposed. Ordinary status roles retain their existing projection. Terminal status still authenticates the imported original receipt through the canonical reader.
583
+
584
+ The fixed original executor uses `claim_service_settlement` against the sealed original request. The authority records an acyclic challenge selecting the latest canonical uncertain/failed outcome. `complete_no_effect` uploads the exact original receipt and bounded inspection artifact through the existing private transfer. It authenticates the accepted challenge, original dispatch binding and imported artifact before atomically reaching `failed-settled`. Exact retries retain their first reply and do not invoke a new effect. A later genuine failure needs a fresh challenge; missing/orphan/stale challenges and changed input refuse.
585
+
586
+ This generic authority path attributes the closed inspection report to the recorded executor. Actual operation-specific target absence, handler termination and surviving-writer inspection are an open gad.b producer obligation; a closed scope or timeout does not prove no effect. The current protected ingress atomically accepts a request and executor claim, so an absent canonical request cannot be converted into a requested-but-unclaimed settlement record.
587
+
588
+
589
+ ### Protected original worker submission
590
+
591
+ Run under the actual original prepared worker while its lineage is live. Read
592
+ `ace-assign authority status --mapping M --assignment A --attempt T` and save
593
+ `authority_generation` for the next new mutation. Candidate submission uses
594
+ current `result_candidate_generation` (zero if null), then returns the next
595
+ positive candidate generation. Review/result use that returned generation.
596
+
597
+ ```sh
598
+ ace-assign submit-candidate --mapping M --assignment A --attempt T --head H \
599
+ --candidate-generation CURRENT --expected-generation G --mutation CANDIDATE --bundle candidate.bundle
600
+ ace-assign submit-result --mapping M --assignment A --attempt T --head H \
601
+ --candidate-generation ACCEPTED --expected-generation G --mutation RESULT --receipt result.json \
602
+ --artifact first-evidence --artifact second-evidence
603
+ ```
604
+
605
+ The actual worker builds `Ace::Assign::Models::ExecutionReceipt` from its original
606
+ assignment/project/scope, installed worker actor, tested head and actual executed
607
+ checks/artifact digests. Failed receipts may have zero artifacts; succeeded
608
+ receipts require evidence. Captured queue completion does not create a receipt or
609
+ canonical acceptance. Independent installed reviewers use the existing
610
+ `ace-overseer review` consumer for that exact candidate. Result submission obtains
611
+ a new generation after review; exact retries retain the original generation,
612
+ mutation and identical ordered bytes. Supervisor finish still revalidates result,
613
+ review, writer exclusion and settlement. A lost reply never causes automatic resend.
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/ruby
2
+ # frozen_string_literal: true
3
+ # Private fixed interpreter action; the unit supplies --disable=gems,rubyopt.
4
+ abort "fixed readiness requires one slot" unless ARGV.length == 1 && ARGV.first.match?(/\A[A-Za-z0-9][A-Za-z0-9._-]{0,127}\z/)
5
+ abort "readiness startup features differ" if defined?(Gem) || ENV.keys.any? { |key| key.match?(/\A(?:RUBY|GEM|BUNDLE)/) }
6
+ protected_path = lambda do |path, directory|
7
+ abort "readiness path substituted" unless path.start_with?("/") && File.expand_path(path) == path && File.realpath(path) == path
8
+ current = path
9
+ loop do
10
+ stat = File.lstat(current)
11
+ abort "readiness path is writable" unless stat.uid.zero? && !stat.symlink? && (stat.mode & 0o022).zero?
12
+ abort "readiness directory differs" if current == path && directory && !stat.directory?
13
+ break if current == "/"
14
+ current = File.dirname(current)
15
+ end
16
+ end
17
+ $LOAD_PATH.each { |path| protected_path.call(path, true) }
18
+ require "json"
19
+ require "digest"
20
+ path = "/etc/ace/execution-slots/#{ARGV.first}/readiness.json"
21
+ protected_path.call(path, false)
22
+ bytes = File.open(path, File::RDONLY | File::NOFOLLOW) { |file| file.read(65_537) }
23
+ abort "readiness configuration bound differs" unless bytes.bytesize.between?(1, 65_536)
24
+ config = JSON.parse(bytes.force_encoding(Encoding::UTF_8), create_additions: false, max_nesting: 8,
25
+ allow_duplicate_key: false, allow_comments: false)
26
+ runtime = config.fetch("runtime")
27
+ references = runtime.fetch("dependencies")
28
+ abort "readiness runtime closure oversized" unless references.is_a?(Array) && references.size.between?(1, 256)
29
+ total = 0
30
+ references.each do |ref|
31
+ abort "readiness dependency reference differs" unless ref.is_a?(Hash) && ref.keys.sort == %w[bytes path sha256] &&
32
+ ref["path"].is_a?(String) && ref["path"].bytesize.between?(1, 4096) && !ref["path"].include?("\0") &&
33
+ ref["bytes"].is_a?(Integer) && ref["bytes"].between?(1, 1_048_576) &&
34
+ ref["sha256"].is_a?(String) && ref["sha256"].match?(/\A[0-9a-f]{64}\z/)
35
+ total += ref.fetch("bytes")
36
+ abort "readiness runtime closure byte bound differs" if total > 16_777_216
37
+ protected_path.call(ref.fetch("path"), false)
38
+ content = File.open(ref.fetch("path"), File::RDONLY | File::NOFOLLOW) { |file| file.read(ref.fetch("bytes") + 1) }
39
+ abort "readiness dependency changed" unless content.bytesize == ref.fetch("bytes") && Digest::SHA256.hexdigest(content) == ref.fetch("sha256")
40
+ end
41
+ builtins = %w[enumerator.so thread.rb fiber.so rational.so complex.so ruby2_keywords.rb]
42
+ $LOADED_FEATURES.each do |feature|
43
+ abort "readiness startup loaded undeclared code" unless builtins.include?(feature) || references.any? { |ref| ref.fetch("path") == feature }
44
+ end
45
+ paths = runtime.fetch("load_paths")
46
+ abort "readiness load paths oversized" unless paths.is_a?(Array) && paths.size.between?(1, 16)
47
+ paths.each { |selected| protected_path.call(selected, true) }
48
+ $LOAD_PATH.replace(paths)
49
+ require "ace/runtime/molecules/readiness_configuration"
50
+ require "ace/runtime/molecules/readiness_runtime"
51
+ configuration = Ace::Runtime::Molecules::ReadinessConfiguration.decode(bytes, slot: ARGV.first)
52
+ Ace::Runtime::Molecules::ReadinessRuntime.new(configuration: configuration.data).activate!
53
+ require "ace/assign/errors"
54
+ require "ace/assign/authority/readiness_hook"
55
+ Ace::Assign::Authority::ReadinessHook.new(slot: ARGV.first, configuration: configuration).run
@@ -40,7 +40,8 @@ If this workflow is invoked with an argument (for example `/as-assign-drive abc1
40
40
 
41
41
  ```bash
42
42
  # Set once from workflow argument or internal scoped default (empty when neither is provided)
43
- ASSIGNMENT_TARGET="${1:-${ACE_ASSIGN_DEFAULT_TARGET:-}}"
43
+ DEFAULT_ASSIGNMENT_TARGET="${ACE_ASSIGN_DEFAULT_TARGET:-}"
44
+ ASSIGNMENT_TARGET="${1:-$DEFAULT_ASSIGNMENT_TARGET}"
44
45
 
45
46
  # Resolve and pin assignment identity for the full drive loop
46
47
  if [ -n "$ASSIGNMENT_TARGET" ]; then
@@ -659,6 +660,30 @@ Follow the instructions directly, performing the described work:
659
660
 
660
661
  Every external-facing step (implement, verify, review, merge, publish, deploy, release) runs inside a scoped attempt. The attempt -- not the report and not exit status -- is the delivery record.
661
662
 
663
+ For an original protected PreparedWorker, use its already admitted assignment,
664
+ attempt and selected scope. Do not run the local `attempt start`/receipt-file
665
+ sequence below, acquire another attempt, or execute an outer preset step. The
666
+ delivery step must already belong to the immutable captured subtree. Submit the
667
+ actual candidate and obtain the installed independent reviewer acceptance through
668
+ the protected commands supplied by the original drive prompt.
669
+
670
+ For an authorized protected merge, retain the exact approved head/candidate
671
+ generation, structured input file, installed receiver, authorization and stable
672
+ request ID. Read current authority generation for the first new request. Invoke
673
+ `ace-lab service request` with those original mapping/assignment/attempt/scope
674
+ selectors. Inspect `ace-lab service status` with the same request, input digest and
675
+ target, then consume succeeded canonical evidence with `ace-assign delivery
676
+ --operation merge --service-request REQUEST` and the same selectors. Include
677
+ `--artifact-digest` on status and consumption when the original target has one.
678
+ Exact retries retain the original generation, mutation/request ID and input bytes;
679
+ uncertainty never permits refreshing or resending an effect. The protected delivery
680
+ consumer verifies the imported receipt and canonical delivery event without another
681
+ provider mutation. Only that succeeded result permits finishing the active queue
682
+ step. Refusal, unavailable evidence or uncertainty leaves it unfinished and reports
683
+ the exact blocker. Credentials and queue completion do not replace authorization
684
+ or canonical evidence. The following local attempt sequence applies only to
685
+ ordinary standalone assignments.
686
+
662
687
  **Step 1: Start the attempt before executing the step.** Actor identity is derived from the execution boundary; never pass actor flags.
663
688
 
664
689
  ```bash
@@ -924,10 +949,10 @@ When executing a step with a `skill:` field:
924
949
  |-------|-----------|---------|
925
950
  | `onboard` | `/as-onboard` | Load project context |
926
951
  | `ace:task-work` | `/as-task-work <taskref>` | Implement task changes |
927
- | `ace:github-pr-create` | `/as-github-pr-create` | Create pull request |
952
+ | `ace:git-pr-create` | `/as-git-pr-create` | Create pull request |
928
953
  | `ace:review-pr` | `/as-review-pr [pr#]` | Review code changes |
929
954
  | `ace:git-commit` | `/as-git-commit` | Generate commit message |
930
- | `ace:github-pr-update` | `/as-github-pr-update` | Update PR description |
955
+ | `ace:git-pr-update` | `/as-git-pr-update` | Update PR description |
931
956
 
932
957
  ## Error Handling
933
958