ace-assign 0.70.0 → 0.73.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 (181) hide show
  1. checksums.yaml +4 -4
  2. data/.ace-defaults/assign/catalog/steps/auto-merge.step.yml +8 -8
  3. data/.ace-defaults/assign/catalog/steps/create-pr.step.yml +1 -1
  4. data/.ace-defaults/assign/catalog/steps/mark-pr-ready.step.yml +1 -1
  5. data/.ace-defaults/assign/config.yml +0 -13
  6. data/.ace-defaults/assign/presets/fix-bug.yml +1 -1
  7. data/.ace-defaults/assign/presets/work-on-task-ace-development.yml +1 -1
  8. data/.ace-defaults/assign/presets/work-on-task-auto-merge.yml +1 -1
  9. data/.ace-defaults/assign/presets/work-on-task.yml +1 -1
  10. data/CHANGELOG.md +3 -2099
  11. data/README.md +58 -31
  12. data/docs/exit-codes.md +36 -44
  13. data/docs/getting-started.md +9 -4
  14. data/docs/usage.md +129 -522
  15. data/handbook/skills/as-assign-drive/SKILL.md +1 -1
  16. data/handbook/workflow-instructions/assign/drive.wf.md +47 -118
  17. data/handbook/workflow-instructions/assign/prepare.wf.md +1 -1
  18. data/handbook/workflow-instructions/assign/verify-test-suite.wf.md +8 -2
  19. data/lib/ace/assign/cli/commands/assignment_target.rb +2 -15
  20. data/lib/ace/assign/cli/commands/create.rb +1 -8
  21. data/lib/ace/assign/cli/commands/fail.rb +0 -6
  22. data/lib/ace/assign/cli/commands/finish.rb +2 -23
  23. data/lib/ace/assign/cli/commands/fork_session.rb +0 -1
  24. data/lib/ace/assign/cli/commands/select.rb +0 -1
  25. data/lib/ace/assign/cli/commands/start.rb +0 -2
  26. data/lib/ace/assign/cli/commands/status.rb +1 -31
  27. data/lib/ace/assign/cli/commands/step.rb +1 -3
  28. data/lib/ace/assign/cli.rb +1 -85
  29. data/lib/ace/assign/errors.rb +8 -80
  30. data/lib/ace/assign/models/assignment.rb +5 -28
  31. data/lib/ace/assign/molecules/assignment_manager.rb +0 -26
  32. data/lib/ace/assign/molecules/fork_session_launcher.rb +4 -12
  33. data/lib/ace/assign/molecules/lifecycle_exclusion.rb +15 -112
  34. data/lib/ace/assign/molecules/runtime_control_surface_runner.rb +2 -1
  35. data/lib/ace/assign/organisms/assignment_executor.rb +0 -6
  36. data/lib/ace/assign/organisms/task_assignment_creator.rb +3 -8
  37. data/lib/ace/assign/version.rb +1 -1
  38. data/lib/ace/assign.rb +0 -6
  39. metadata +4 -161
  40. data/.ace-defaults/assign/presets/close-delivery.yml +0 -57
  41. data/.ace-defaults/assign/presets/deliver-release.yml +0 -60
  42. data/.ace-defaults/assign/presets/integrate-batch.yml +0 -72
  43. data/.ace-defaults/assign/presets/supervise-delivery.yml +0 -69
  44. data/docs/protected-authority.md +0 -282
  45. data/exe/ace-scope-ready +0 -55
  46. data/lib/ace/assign/atoms/assignment_scope.rb +0 -58
  47. data/lib/ace/assign/atoms/attempt_state_machine.rb +0 -84
  48. data/lib/ace/assign/atoms/delivery_parameters.rb +0 -19
  49. data/lib/ace/assign/atoms/evidence_digest.rb +0 -78
  50. data/lib/ace/assign/atoms/fixed_merge_schemas.rb +0 -398
  51. data/lib/ace/assign/authority/assignment_definition.rb +0 -35
  52. data/lib/ace/assign/authority/assignment_inventory.rb +0 -290
  53. data/lib/ace/assign/authority/campaign_consumer_policy.rb +0 -100
  54. data/lib/ace/assign/authority/campaign_execution.rb +0 -119
  55. data/lib/ace/assign/authority/campaign_round_transfer.rb +0 -82
  56. data/lib/ace/assign/authority/candidate_transfer.rb +0 -327
  57. data/lib/ace/assign/authority/cleanup_result_protection.rb +0 -53
  58. data/lib/ace/assign/authority/client.rb +0 -257
  59. data/lib/ace/assign/authority/deployment.rb +0 -627
  60. data/lib/ace/assign/authority/deployment_history.rb +0 -146
  61. data/lib/ace/assign/authority/endcap.rb +0 -455
  62. data/lib/ace/assign/authority/endcap_attempts.rb +0 -274
  63. data/lib/ace/assign/authority/endcap_campaign_results.rb +0 -232
  64. data/lib/ace/assign/authority/endcap_campaign_rounds.rb +0 -266
  65. data/lib/ace/assign/authority/endcap_contexts.rb +0 -23
  66. data/lib/ace/assign/authority/endcap_inboxes.rb +0 -37
  67. data/lib/ace/assign/authority/endcap_prepared_work.rb +0 -80
  68. data/lib/ace/assign/authority/endcap_publication.rb +0 -207
  69. data/lib/ace/assign/authority/endcap_results.rb +0 -398
  70. data/lib/ace/assign/authority/endcap_review_requests.rb +0 -183
  71. data/lib/ace/assign/authority/endcap_reviews.rb +0 -130
  72. data/lib/ace/assign/authority/endcap_service_settlement.rb +0 -152
  73. data/lib/ace/assign/authority/endcap_services.rb +0 -598
  74. data/lib/ace/assign/authority/evidence_transfer.rb +0 -67
  75. data/lib/ace/assign/authority/execution_scope_observation.rb +0 -903
  76. data/lib/ace/assign/authority/inbox_context_original.rb +0 -113
  77. data/lib/ace/assign/authority/launch_attempt_consumers.rb +0 -51
  78. data/lib/ace/assign/authority/launch_campaign_registration.rb +0 -214
  79. data/lib/ace/assign/authority/launch_control_channel.rb +0 -313
  80. data/lib/ace/assign/authority/launch_control_exclusions.rb +0 -127
  81. data/lib/ace/assign/authority/launch_driver.rb +0 -393
  82. data/lib/ace/assign/authority/launch_input_inhibition.rb +0 -132
  83. data/lib/ace/assign/authority/launch_lifecycle.rb +0 -997
  84. data/lib/ace/assign/authority/launch_prepared_work.rb +0 -122
  85. data/lib/ace/assign/authority/launch_review.rb +0 -91
  86. data/lib/ace/assign/authority/launch_scope_abort.rb +0 -81
  87. data/lib/ace/assign/authority/launch_scope_admission.rb +0 -215
  88. data/lib/ace/assign/authority/launch_scope_close.rb +0 -146
  89. data/lib/ace/assign/authority/launch_scope_parent.rb +0 -44
  90. data/lib/ace/assign/authority/launch_scope_release.rb +0 -859
  91. data/lib/ace/assign/authority/launch_steering.rb +0 -353
  92. data/lib/ace/assign/authority/launch_stop.rb +0 -254
  93. data/lib/ace/assign/authority/maintenance_workspace_retirement.rb +0 -229
  94. data/lib/ace/assign/authority/ordinary_attempt_start.rb +0 -137
  95. data/lib/ace/assign/authority/ordinary_client.rb +0 -70
  96. data/lib/ace/assign/authority/ordinary_completion.rb +0 -94
  97. data/lib/ace/assign/authority/ordinary_effect_settlement.rb +0 -118
  98. data/lib/ace/assign/authority/ordinary_lifecycle.rb +0 -191
  99. data/lib/ace/assign/authority/ordinary_peer.rb +0 -54
  100. data/lib/ace/assign/authority/ordinary_progress.rb +0 -162
  101. data/lib/ace/assign/authority/ordinary_queue.rb +0 -115
  102. data/lib/ace/assign/authority/ordinary_results.rb +0 -196
  103. data/lib/ace/assign/authority/ordinary_server.rb +0 -75
  104. data/lib/ace/assign/authority/original_inbox_selection.rb +0 -41
  105. data/lib/ace/assign/authority/posix_acl.rb +0 -59
  106. data/lib/ace/assign/authority/prepared_input.rb +0 -130
  107. data/lib/ace/assign/authority/prepared_queue.rb +0 -185
  108. data/lib/ace/assign/authority/prepared_task_context.rb +0 -116
  109. data/lib/ace/assign/authority/prepared_work.rb +0 -304
  110. data/lib/ace/assign/authority/prepared_worker.rb +0 -69
  111. data/lib/ace/assign/authority/private_directory.rb +0 -33
  112. data/lib/ace/assign/authority/protected_assignment_context.rb +0 -226
  113. data/lib/ace/assign/authority/readiness_hook.rb +0 -120
  114. data/lib/ace/assign/authority/receipt_transfer.rb +0 -48
  115. data/lib/ace/assign/authority/router.rb +0 -81
  116. data/lib/ace/assign/authority/server.rb +0 -131
  117. data/lib/ace/assign/authority/service_cleanup_dispatch.rb +0 -88
  118. data/lib/ace/assign/authority/service_cleanup_evidence.rb +0 -180
  119. data/lib/ace/assign/authority/service_delivery_evidence.rb +0 -101
  120. data/lib/ace/assign/authority/service_evidence.rb +0 -286
  121. data/lib/ace/assign/authority/service_publication_evidence.rb +0 -110
  122. data/lib/ace/assign/authority/socket_server.rb +0 -255
  123. data/lib/ace/assign/authority/task_context_entry.rb +0 -32
  124. data/lib/ace/assign/authority/transfer_codec.rb +0 -187
  125. data/lib/ace/assign/cli/commands/attempt/base.rb +0 -84
  126. data/lib/ace/assign/cli/commands/attempt/evidence.rb +0 -28
  127. data/lib/ace/assign/cli/commands/attempt/finish.rb +0 -51
  128. data/lib/ace/assign/cli/commands/attempt/reconcile.rb +0 -49
  129. data/lib/ace/assign/cli/commands/attempt/start.rb +0 -45
  130. data/lib/ace/assign/cli/commands/attempt/status.rb +0 -54
  131. data/lib/ace/assign/cli/commands/authority/inbox_context_principal.rb +0 -25
  132. data/lib/ace/assign/cli/commands/authority/inbox_context_selection.rb +0 -28
  133. data/lib/ace/assign/cli/commands/authority/launch.rb +0 -97
  134. data/lib/ace/assign/cli/commands/authority/serve.rb +0 -31
  135. data/lib/ace/assign/cli/commands/authority/status.rb +0 -26
  136. data/lib/ace/assign/cli/commands/authority/task_context.rb +0 -31
  137. data/lib/ace/assign/cli/commands/authority/task_context_principal.rb +0 -25
  138. data/lib/ace/assign/cli/commands/authority/task_context_selection.rb +0 -30
  139. data/lib/ace/assign/cli/commands/authority/terminate.rb +0 -29
  140. data/lib/ace/assign/cli/commands/authority/worker.rb +0 -23
  141. data/lib/ace/assign/cli/commands/campaign_export_result.rb +0 -55
  142. data/lib/ace/assign/cli/commands/campaign_record_round.rb +0 -57
  143. data/lib/ace/assign/cli/commands/delivery.rb +0 -107
  144. data/lib/ace/assign/cli/commands/inbox_bind.rb +0 -34
  145. data/lib/ace/assign/cli/commands/protected_submission.rb +0 -61
  146. data/lib/ace/assign/cli/commands/resume.rb +0 -38
  147. data/lib/ace/assign/cli/commands/submit_candidate.rb +0 -34
  148. data/lib/ace/assign/cli/commands/submit_result.rb +0 -61
  149. data/lib/ace/assign/models/attempt.rb +0 -178
  150. data/lib/ace/assign/models/attempt_binding.rb +0 -116
  151. data/lib/ace/assign/models/evidence_event.rb +0 -86
  152. data/lib/ace/assign/models/execution_receipt.rb +0 -139
  153. data/lib/ace/assign/molecules/attempt_reconciler.rb +0 -127
  154. data/lib/ace/assign/molecules/attempt_store.rb +0 -201
  155. data/lib/ace/assign/molecules/canonical_attempt_state.rb +0 -151
  156. data/lib/ace/assign/molecules/canonical_evidence.rb +0 -153
  157. data/lib/ace/assign/molecules/canonical_read_commands.rb +0 -84
  158. data/lib/ace/assign/molecules/canonical_read_snapshot.rb +0 -313
  159. data/lib/ace/assign/molecules/evidence_calculator.rb +0 -237
  160. data/lib/ace/assign/molecules/evidence_journal.rb +0 -1912
  161. data/lib/ace/assign/molecules/execution_identity_resolver.rb +0 -137
  162. data/lib/ace/assign/molecules/execution_scope_lineage.rb +0 -345
  163. data/lib/ace/assign/molecules/fixed_effect_reference.rb +0 -31
  164. data/lib/ace/assign/molecules/installed_journal_read.rb +0 -143
  165. data/lib/ace/assign/molecules/journal_input_inhibition.rb +0 -78
  166. data/lib/ace/assign/molecules/journal_mutation.rb +0 -273
  167. data/lib/ace/assign/molecules/journal_prompt_mutation.rb +0 -241
  168. data/lib/ace/assign/molecules/ordinary_merge_cli.rb +0 -68
  169. data/lib/ace/assign/molecules/ordinary_merge_contract.rb +0 -410
  170. data/lib/ace/assign/molecules/original_launch_binding.rb +0 -37
  171. data/lib/ace/assign/molecules/proposal_journal.rb +0 -179
  172. data/lib/ace/assign/molecules/protected_control_exclusion.rb +0 -303
  173. data/lib/ace/assign/molecules/protected_workspace_exclusion.rb +0 -330
  174. data/lib/ace/assign/molecules/protected_workspace_provisioner.rb +0 -164
  175. data/lib/ace/assign/molecules/receipt_verifier.rb +0 -314
  176. data/lib/ace/assign/molecules/terminal_scope_receipt.rb +0 -88
  177. data/lib/ace/assign/organisms/attempt_coordinator.rb +0 -1337
  178. data/lib/ace/assign/organisms/delivery_coordinator.rb +0 -342
  179. data/lib/ace/assign/organisms/ordinary_merge_consumer.rb +0 -365
  180. data/lib/ace/assign/organisms/prepared_work_builder.rb +0 -194
  181. data/lib/ace/assign/organisms/protected_delivery_coordinator.rb +0 -100
@@ -1,282 +0,0 @@
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/exe/ace-scope-ready DELETED
@@ -1,55 +0,0 @@
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
@@ -1,58 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ace
4
- module Assign
5
- module Atoms
6
- # Pure validation and canonicalization of attempt scopes.
7
- #
8
- # An attempt scope is the step/subtree an attempt owns, expressed as a
9
- # step number ("010", "010.01"). Scopes are canonicalized before they
10
- # key ownership locks, journal paths, or binding comparisons so that
11
- # cosmetic differences cannot create a second writer.
12
- module AssignmentScope
13
- SCOPE_PATTERN = /\A\d+(?:\.\d+)*\z/.freeze
14
-
15
- # Canonicalize and validate a step/subtree scope.
16
- #
17
- # @param step [String] Step or subtree root number
18
- # @return [String] Canonical scope
19
- # @raise [Ace::Assign::AttemptErrors::InvalidScope] if blank or malformed
20
- def self.canonicalize(step)
21
- scope = step.to_s.strip
22
- if scope.empty? || !scope.match?(SCOPE_PATTERN)
23
- raise Ace::Assign::AttemptErrors::InvalidScope,
24
- "Invalid attempt scope '#{step}': expected a step number such as 010 or 010.01"
25
- end
26
-
27
- scope
28
- end
29
-
30
- # Filesystem-safe token for an assignment/scope pair, used to key
31
- # active-attempt ownership records.
32
- #
33
- # @param assignment_id [String] Assignment ID
34
- # @param scope [String] Canonical scope
35
- # @return [String] Ownership key token
36
- def self.ownership_key(assignment_id, scope)
37
- "#{assignment_id}-#{canonicalize(scope)}.json"
38
- end
39
-
40
- # Compare two scopes for binding identity.
41
- #
42
- # @param left [String] First scope
43
- # @param right [String] Second scope
44
- # @return [Boolean] True if both canonicalize to the same scope
45
- def self.equal?(left, right)
46
- canonicalize(left) == canonicalize(right)
47
- rescue Ace::Assign::AttemptErrors::InvalidScope
48
- false
49
- end
50
-
51
- # An owner of a subtree also owns every descendant scope.
52
- def self.overlap?(left, right)
53
- equal?(left, right) || left.start_with?("#{right}.") || right.start_with?("#{left}.")
54
- end
55
- end
56
- end
57
- end
58
- end
@@ -1,84 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Ace
4
- module Assign
5
- module Atoms
6
- # Pure state machine for assignment attempt lifecycles.
7
- #
8
- # Legal transitions:
9
- # - reserved -> running
10
- # - running -> succeeded | failed | stopped | uncertain
11
- # - uncertain -> succeeded | failed (only via reconciliation)
12
- #
13
- # Terminal states are succeeded, failed, and stopped. Terminal attempts
14
- # never accept new transitions, effects, or receipts; accepted history is
15
- # immutable. A stop is never success, and an uncertain attempt must be
16
- # resolved through explicit reconciliation, never automatic replay.
17
- module AttemptStateMachine
18
- STATES = %w[reserved running succeeded failed stopped uncertain].freeze
19
- TERMINAL_STATES = %w[succeeded failed stopped].freeze
20
- RESOLVED_STATES = %w[succeeded failed].freeze
21
-
22
- TRANSITIONS = {
23
- "reserved" => %w[running].freeze,
24
- "running" => %w[succeeded failed stopped uncertain].freeze,
25
- "uncertain" => RESOLVED_STATES.freeze
26
- }.freeze
27
-
28
- # @return [Array<String>] All known attempt states
29
- def self.states
30
- STATES
31
- end
32
-
33
- # @param state [String] State to inspect
34
- # @return [Boolean] True if the state is terminal (no further transitions)
35
- def self.terminal?(state)
36
- TERMINAL_STATES.include?(state.to_s)
37
- end
38
-
39
- # @param state [String] State to inspect
40
- # @return [Boolean] True if the state awaits reconciliation
41
- def self.uncertain?(state)
42
- state.to_s == "uncertain"
43
- end
44
-
45
- # Check whether a transition is legal without raising.
46
- #
47
- # @param from [String] Current state
48
- # @param to [String] Requested next state
49
- # @return [Boolean] True if the transition is legal
50
- def self.can_transition?(from, to)
51
- TRANSITIONS.fetch(from.to_s, []).include?(to.to_s)
52
- end
53
-
54
- # Return the legal next states for a state.
55
- #
56
- # @param from [String] Current state
57
- # @return [Array<String>] Legal next states (empty for unknown/terminal)
58
- def self.next_states(from)
59
- TRANSITIONS.fetch(from.to_s, [])
60
- end
61
-
62
- # Source-owned cessation, invoked only after original no-writers and
63
- # exhaustive independent settlement proof. This does not broaden the
64
- # ordinary uncertain receipt-reconciliation transition table.
65
- def self.proof_stopped_transition!(state)
66
- return "stopped" if %w[running uncertain].include?(state.to_s)
67
- raise AttemptErrors::InvalidTransition, "Proof-linked stop requires an active bound attempt"
68
- end
69
-
70
- # Validate a transition.
71
- #
72
- # @param from [String] Current state
73
- # @param to [String] Requested next state
74
- # @raise [Ace::Assign::AttemptErrors::InvalidTransition] if illegal
75
- def self.transition!(from, to)
76
- return to.to_s if can_transition?(from, to)
77
-
78
- raise Ace::Assign::AttemptErrors::InvalidTransition,
79
- "Illegal attempt transition #{from} -> #{to} (legal from #{from}: #{next_states(from).join(', ')})"
80
- end
81
- end
82
- end
83
- end
84
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
- require "ace/git/atoms/delivery_parameters"
3
-
4
- module Ace
5
- module Assign
6
- module Atoms
7
- # Assignment configuration consumes the shared forge-neutral owner.
8
- module DeliveryParameters
9
- def self.validate(input)
10
- Ace::Git::Atoms::DeliveryParameters.validate(input)
11
- end
12
-
13
- def self.validate_url!(url)
14
- Ace::Git::Atoms::DeliveryParameters.validate_url!(url)
15
- end
16
- end
17
- end
18
- end
19
- end
@@ -1,78 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "digest"
4
- require "json"
5
-
6
- module Ace
7
- module Assign
8
- module Atoms
9
- # Pure canonical digest helpers for attempt evidence.
10
- #
11
- # Every accepted evidence record (events, receipts, artifacts) carries a
12
- # SHA-256 digest over a canonical JSON serialization (sorted keys, no
13
- # insignificant whitespace) so records remain comparable across writers
14
- # and reloads. Digests are over non-secret metadata only.
15
- module EvidenceDigest
16
- # Canonical JSON serialization of an object.
17
- #
18
- # Sorts hash keys recursively and strips insignificant whitespace so
19
- # semantically equal payloads produce byte-identical output.
20
- #
21
- # @param object [Object] JSON-serializable object
22
- # @return [String] Canonical JSON string
23
- def self.canonical_json(object)
24
- JSON.generate(sort_keys(object))
25
- end
26
-
27
- # SHA-256 hex digest over the canonical JSON of an object.
28
- #
29
- # @param object [Object] JSON-serializable object
30
- # @return [String] 64-character hex digest
31
- def self.digest(object)
32
- digest_string(canonical_json(object))
33
- end
34
-
35
- # SHA-256 hex digest over raw bytes.
36
- #
37
- # @param bytes [String] Raw byte content
38
- # @return [String] 64-character hex digest
39
- def self.digest_string(bytes)
40
- Digest::SHA256.hexdigest(bytes)
41
- end
42
-
43
- # SHA-256 hex digest of a file's contents.
44
- #
45
- # @param path [String] File path
46
- # @return [String] 64-character hex digest
47
- # @raise [Errno::ENOENT] if the file does not exist
48
- def self.digest_file(path)
49
- digest_string(File.read(path))
50
- end
51
-
52
- # Short digest prefix for file naming (collision-resistant enough for
53
- # append-only event filenames; full digests remain in the payload).
54
- #
55
- # @param object [Object] JSON-serializable object
56
- # @return [String] 12-character hex prefix
57
- def self.short_digest(object)
58
- digest(object)[0, 12]
59
- end
60
-
61
- class << self
62
- private
63
-
64
- def sort_keys(value)
65
- case value
66
- when Hash
67
- value.sort.to_h.transform_values { |v| sort_keys(v) }
68
- when Array
69
- value.map { |item| sort_keys(item) }
70
- else
71
- value
72
- end
73
- end
74
- end
75
- end
76
- end
77
- end
78
- end