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.
- checksums.yaml +4 -4
- data/.ace-defaults/assign/catalog/steps/auto-merge.step.yml +8 -8
- data/.ace-defaults/assign/catalog/steps/create-pr.step.yml +1 -1
- data/.ace-defaults/assign/catalog/steps/mark-pr-ready.step.yml +1 -1
- data/.ace-defaults/assign/config.yml +0 -13
- data/.ace-defaults/assign/presets/fix-bug.yml +1 -1
- data/.ace-defaults/assign/presets/work-on-task-ace-development.yml +1 -1
- data/.ace-defaults/assign/presets/work-on-task-auto-merge.yml +1 -1
- data/.ace-defaults/assign/presets/work-on-task.yml +1 -1
- data/CHANGELOG.md +3 -2099
- data/README.md +58 -31
- data/docs/exit-codes.md +36 -44
- data/docs/getting-started.md +9 -4
- data/docs/usage.md +129 -522
- data/handbook/skills/as-assign-drive/SKILL.md +1 -1
- data/handbook/workflow-instructions/assign/drive.wf.md +47 -118
- data/handbook/workflow-instructions/assign/prepare.wf.md +1 -1
- data/handbook/workflow-instructions/assign/verify-test-suite.wf.md +8 -2
- data/lib/ace/assign/cli/commands/assignment_target.rb +2 -15
- data/lib/ace/assign/cli/commands/create.rb +1 -8
- data/lib/ace/assign/cli/commands/fail.rb +0 -6
- data/lib/ace/assign/cli/commands/finish.rb +2 -23
- data/lib/ace/assign/cli/commands/fork_session.rb +0 -1
- data/lib/ace/assign/cli/commands/select.rb +0 -1
- data/lib/ace/assign/cli/commands/start.rb +0 -2
- data/lib/ace/assign/cli/commands/status.rb +1 -31
- data/lib/ace/assign/cli/commands/step.rb +1 -3
- data/lib/ace/assign/cli.rb +1 -85
- data/lib/ace/assign/errors.rb +8 -80
- data/lib/ace/assign/models/assignment.rb +5 -28
- data/lib/ace/assign/molecules/assignment_manager.rb +0 -26
- data/lib/ace/assign/molecules/fork_session_launcher.rb +4 -12
- data/lib/ace/assign/molecules/lifecycle_exclusion.rb +15 -112
- data/lib/ace/assign/molecules/runtime_control_surface_runner.rb +2 -1
- data/lib/ace/assign/organisms/assignment_executor.rb +0 -6
- data/lib/ace/assign/organisms/task_assignment_creator.rb +3 -8
- data/lib/ace/assign/version.rb +1 -1
- data/lib/ace/assign.rb +0 -6
- metadata +4 -161
- data/.ace-defaults/assign/presets/close-delivery.yml +0 -57
- data/.ace-defaults/assign/presets/deliver-release.yml +0 -60
- data/.ace-defaults/assign/presets/integrate-batch.yml +0 -72
- data/.ace-defaults/assign/presets/supervise-delivery.yml +0 -69
- data/docs/protected-authority.md +0 -282
- data/exe/ace-scope-ready +0 -55
- data/lib/ace/assign/atoms/assignment_scope.rb +0 -58
- data/lib/ace/assign/atoms/attempt_state_machine.rb +0 -84
- data/lib/ace/assign/atoms/delivery_parameters.rb +0 -19
- data/lib/ace/assign/atoms/evidence_digest.rb +0 -78
- data/lib/ace/assign/atoms/fixed_merge_schemas.rb +0 -398
- data/lib/ace/assign/authority/assignment_definition.rb +0 -35
- data/lib/ace/assign/authority/assignment_inventory.rb +0 -290
- data/lib/ace/assign/authority/campaign_consumer_policy.rb +0 -100
- data/lib/ace/assign/authority/campaign_execution.rb +0 -119
- data/lib/ace/assign/authority/campaign_round_transfer.rb +0 -82
- data/lib/ace/assign/authority/candidate_transfer.rb +0 -327
- data/lib/ace/assign/authority/cleanup_result_protection.rb +0 -53
- data/lib/ace/assign/authority/client.rb +0 -257
- data/lib/ace/assign/authority/deployment.rb +0 -627
- data/lib/ace/assign/authority/deployment_history.rb +0 -146
- data/lib/ace/assign/authority/endcap.rb +0 -455
- data/lib/ace/assign/authority/endcap_attempts.rb +0 -274
- data/lib/ace/assign/authority/endcap_campaign_results.rb +0 -232
- data/lib/ace/assign/authority/endcap_campaign_rounds.rb +0 -266
- data/lib/ace/assign/authority/endcap_contexts.rb +0 -23
- data/lib/ace/assign/authority/endcap_inboxes.rb +0 -37
- data/lib/ace/assign/authority/endcap_prepared_work.rb +0 -80
- data/lib/ace/assign/authority/endcap_publication.rb +0 -207
- data/lib/ace/assign/authority/endcap_results.rb +0 -398
- data/lib/ace/assign/authority/endcap_review_requests.rb +0 -183
- data/lib/ace/assign/authority/endcap_reviews.rb +0 -130
- data/lib/ace/assign/authority/endcap_service_settlement.rb +0 -152
- data/lib/ace/assign/authority/endcap_services.rb +0 -598
- data/lib/ace/assign/authority/evidence_transfer.rb +0 -67
- data/lib/ace/assign/authority/execution_scope_observation.rb +0 -903
- data/lib/ace/assign/authority/inbox_context_original.rb +0 -113
- data/lib/ace/assign/authority/launch_attempt_consumers.rb +0 -51
- data/lib/ace/assign/authority/launch_campaign_registration.rb +0 -214
- data/lib/ace/assign/authority/launch_control_channel.rb +0 -313
- data/lib/ace/assign/authority/launch_control_exclusions.rb +0 -127
- data/lib/ace/assign/authority/launch_driver.rb +0 -393
- data/lib/ace/assign/authority/launch_input_inhibition.rb +0 -132
- data/lib/ace/assign/authority/launch_lifecycle.rb +0 -997
- data/lib/ace/assign/authority/launch_prepared_work.rb +0 -122
- data/lib/ace/assign/authority/launch_review.rb +0 -91
- data/lib/ace/assign/authority/launch_scope_abort.rb +0 -81
- data/lib/ace/assign/authority/launch_scope_admission.rb +0 -215
- data/lib/ace/assign/authority/launch_scope_close.rb +0 -146
- data/lib/ace/assign/authority/launch_scope_parent.rb +0 -44
- data/lib/ace/assign/authority/launch_scope_release.rb +0 -859
- data/lib/ace/assign/authority/launch_steering.rb +0 -353
- data/lib/ace/assign/authority/launch_stop.rb +0 -254
- data/lib/ace/assign/authority/maintenance_workspace_retirement.rb +0 -229
- data/lib/ace/assign/authority/ordinary_attempt_start.rb +0 -137
- data/lib/ace/assign/authority/ordinary_client.rb +0 -70
- data/lib/ace/assign/authority/ordinary_completion.rb +0 -94
- data/lib/ace/assign/authority/ordinary_effect_settlement.rb +0 -118
- data/lib/ace/assign/authority/ordinary_lifecycle.rb +0 -191
- data/lib/ace/assign/authority/ordinary_peer.rb +0 -54
- data/lib/ace/assign/authority/ordinary_progress.rb +0 -162
- data/lib/ace/assign/authority/ordinary_queue.rb +0 -115
- data/lib/ace/assign/authority/ordinary_results.rb +0 -196
- data/lib/ace/assign/authority/ordinary_server.rb +0 -75
- data/lib/ace/assign/authority/original_inbox_selection.rb +0 -41
- data/lib/ace/assign/authority/posix_acl.rb +0 -59
- data/lib/ace/assign/authority/prepared_input.rb +0 -130
- data/lib/ace/assign/authority/prepared_queue.rb +0 -185
- data/lib/ace/assign/authority/prepared_task_context.rb +0 -116
- data/lib/ace/assign/authority/prepared_work.rb +0 -304
- data/lib/ace/assign/authority/prepared_worker.rb +0 -69
- data/lib/ace/assign/authority/private_directory.rb +0 -33
- data/lib/ace/assign/authority/protected_assignment_context.rb +0 -226
- data/lib/ace/assign/authority/readiness_hook.rb +0 -120
- data/lib/ace/assign/authority/receipt_transfer.rb +0 -48
- data/lib/ace/assign/authority/router.rb +0 -81
- data/lib/ace/assign/authority/server.rb +0 -131
- data/lib/ace/assign/authority/service_cleanup_dispatch.rb +0 -88
- data/lib/ace/assign/authority/service_cleanup_evidence.rb +0 -180
- data/lib/ace/assign/authority/service_delivery_evidence.rb +0 -101
- data/lib/ace/assign/authority/service_evidence.rb +0 -286
- data/lib/ace/assign/authority/service_publication_evidence.rb +0 -110
- data/lib/ace/assign/authority/socket_server.rb +0 -255
- data/lib/ace/assign/authority/task_context_entry.rb +0 -32
- data/lib/ace/assign/authority/transfer_codec.rb +0 -187
- data/lib/ace/assign/cli/commands/attempt/base.rb +0 -84
- data/lib/ace/assign/cli/commands/attempt/evidence.rb +0 -28
- data/lib/ace/assign/cli/commands/attempt/finish.rb +0 -51
- data/lib/ace/assign/cli/commands/attempt/reconcile.rb +0 -49
- data/lib/ace/assign/cli/commands/attempt/start.rb +0 -45
- data/lib/ace/assign/cli/commands/attempt/status.rb +0 -54
- data/lib/ace/assign/cli/commands/authority/inbox_context_principal.rb +0 -25
- data/lib/ace/assign/cli/commands/authority/inbox_context_selection.rb +0 -28
- data/lib/ace/assign/cli/commands/authority/launch.rb +0 -97
- data/lib/ace/assign/cli/commands/authority/serve.rb +0 -31
- data/lib/ace/assign/cli/commands/authority/status.rb +0 -26
- data/lib/ace/assign/cli/commands/authority/task_context.rb +0 -31
- data/lib/ace/assign/cli/commands/authority/task_context_principal.rb +0 -25
- data/lib/ace/assign/cli/commands/authority/task_context_selection.rb +0 -30
- data/lib/ace/assign/cli/commands/authority/terminate.rb +0 -29
- data/lib/ace/assign/cli/commands/authority/worker.rb +0 -23
- data/lib/ace/assign/cli/commands/campaign_export_result.rb +0 -55
- data/lib/ace/assign/cli/commands/campaign_record_round.rb +0 -57
- data/lib/ace/assign/cli/commands/delivery.rb +0 -107
- data/lib/ace/assign/cli/commands/inbox_bind.rb +0 -34
- data/lib/ace/assign/cli/commands/protected_submission.rb +0 -61
- data/lib/ace/assign/cli/commands/resume.rb +0 -38
- data/lib/ace/assign/cli/commands/submit_candidate.rb +0 -34
- data/lib/ace/assign/cli/commands/submit_result.rb +0 -61
- data/lib/ace/assign/models/attempt.rb +0 -178
- data/lib/ace/assign/models/attempt_binding.rb +0 -116
- data/lib/ace/assign/models/evidence_event.rb +0 -86
- data/lib/ace/assign/models/execution_receipt.rb +0 -139
- data/lib/ace/assign/molecules/attempt_reconciler.rb +0 -127
- data/lib/ace/assign/molecules/attempt_store.rb +0 -201
- data/lib/ace/assign/molecules/canonical_attempt_state.rb +0 -151
- data/lib/ace/assign/molecules/canonical_evidence.rb +0 -153
- data/lib/ace/assign/molecules/canonical_read_commands.rb +0 -84
- data/lib/ace/assign/molecules/canonical_read_snapshot.rb +0 -313
- data/lib/ace/assign/molecules/evidence_calculator.rb +0 -237
- data/lib/ace/assign/molecules/evidence_journal.rb +0 -1912
- data/lib/ace/assign/molecules/execution_identity_resolver.rb +0 -137
- data/lib/ace/assign/molecules/execution_scope_lineage.rb +0 -345
- data/lib/ace/assign/molecules/fixed_effect_reference.rb +0 -31
- data/lib/ace/assign/molecules/installed_journal_read.rb +0 -143
- data/lib/ace/assign/molecules/journal_input_inhibition.rb +0 -78
- data/lib/ace/assign/molecules/journal_mutation.rb +0 -273
- data/lib/ace/assign/molecules/journal_prompt_mutation.rb +0 -241
- data/lib/ace/assign/molecules/ordinary_merge_cli.rb +0 -68
- data/lib/ace/assign/molecules/ordinary_merge_contract.rb +0 -410
- data/lib/ace/assign/molecules/original_launch_binding.rb +0 -37
- data/lib/ace/assign/molecules/proposal_journal.rb +0 -179
- data/lib/ace/assign/molecules/protected_control_exclusion.rb +0 -303
- data/lib/ace/assign/molecules/protected_workspace_exclusion.rb +0 -330
- data/lib/ace/assign/molecules/protected_workspace_provisioner.rb +0 -164
- data/lib/ace/assign/molecules/receipt_verifier.rb +0 -314
- data/lib/ace/assign/molecules/terminal_scope_receipt.rb +0 -88
- data/lib/ace/assign/organisms/attempt_coordinator.rb +0 -1337
- data/lib/ace/assign/organisms/delivery_coordinator.rb +0 -342
- data/lib/ace/assign/organisms/ordinary_merge_consumer.rb +0 -365
- data/lib/ace/assign/organisms/prepared_work_builder.rb +0 -194
- data/lib/ace/assign/organisms/protected_delivery_coordinator.rb +0 -100
data/docs/protected-authority.md
DELETED
|
@@ -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
|