ace-assign 0.57.1 → 0.60.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/config.yml +13 -0
- data/CHANGELOG.md +46 -12
- data/docs/exit-codes.md +10 -0
- data/docs/usage.md +49 -0
- data/handbook/workflow-instructions/assign/drive.wf.md +64 -8
- data/lib/ace/assign/atoms/assignment_scope.rb +53 -0
- data/lib/ace/assign/atoms/attempt_state_machine.rb +76 -0
- data/lib/ace/assign/atoms/evidence_digest.rb +78 -0
- data/lib/ace/assign/cli/commands/attempt/base.rb +35 -0
- data/lib/ace/assign/cli/commands/attempt/evidence.rb +28 -0
- data/lib/ace/assign/cli/commands/attempt/finish.rb +35 -0
- data/lib/ace/assign/cli/commands/attempt/reconcile.rb +38 -0
- data/lib/ace/assign/cli/commands/attempt/start.rb +45 -0
- data/lib/ace/assign/cli/commands/attempt/status.rb +54 -0
- data/lib/ace/assign/cli/commands/status.rb +24 -1
- data/lib/ace/assign/cli.rb +34 -2
- data/lib/ace/assign/models/assignment.rb +21 -4
- data/lib/ace/assign/models/attempt.rb +178 -0
- data/lib/ace/assign/models/attempt_binding.rb +116 -0
- data/lib/ace/assign/models/evidence_event.rb +82 -0
- data/lib/ace/assign/models/execution_receipt.rb +139 -0
- data/lib/ace/assign/molecules/assignment_manager.rb +35 -3
- data/lib/ace/assign/molecules/attempt_reconciler.rb +88 -0
- data/lib/ace/assign/molecules/attempt_store.rb +201 -0
- data/lib/ace/assign/molecules/evidence_calculator.rb +144 -91
- data/lib/ace/assign/molecules/evidence_journal.rb +423 -0
- data/lib/ace/assign/molecules/execution_identity_resolver.rb +97 -0
- data/lib/ace/assign/molecules/fork_session_launcher.rb +38 -19
- data/lib/ace/assign/molecules/lifecycle_exclusion.rb +209 -0
- data/lib/ace/assign/molecules/receipt_verifier.rb +272 -0
- data/lib/ace/assign/organisms/assignment_executor.rb +12 -4
- data/lib/ace/assign/organisms/attempt_coordinator.rb +618 -0
- data/lib/ace/assign/organisms/task_assignment_creator.rb +1 -1
- data/lib/ace/assign/version.rb +1 -1
- data/lib/ace/assign.rb +56 -0
- metadata +36 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3a8f387dd91c6da87ca5eb3712cb5fb71051a0326dfa13cf0d9d91e054c1cc00
|
|
4
|
+
data.tar.gz: 4698ad04a950a1401caec3387ef085e8c66512a07e9dd5af168446d80e7d89eb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 89c8df3b2e0823c195dfc3d3b59fbcc58e79c97b3978e0db2e4395c83e86307e8d450a637e633b9aa947efeb79ff94ba78307bc24de577effb8cb55b53e9b8f5
|
|
7
|
+
data.tar.gz: dd2e0a73591ea2e646c9c8164cb56269ff6353860005acdfb57bc268ca8e2d5c89b77b3a19af8a1b895dcfa8716199ae0272a1705387dc4c5f5bda424c062668
|
|
@@ -28,6 +28,19 @@ execution:
|
|
|
28
28
|
timeout: 1800
|
|
29
29
|
launch_mode: auto # auto | headless | tmux
|
|
30
30
|
|
|
31
|
+
# Attempt evidence persistence (managed attempts)
|
|
32
|
+
attempt:
|
|
33
|
+
# Dedicated Git ref holding accepted execution evidence, outside the
|
|
34
|
+
# deliverable candidate branch; journal paths are execution/<assignment-id>/
|
|
35
|
+
evidence_git_ref: "refs/ace/execution"
|
|
36
|
+
# Disposable isolated audit checkout root (relative to project root)
|
|
37
|
+
evidence_checkout_root: ".ace-local/assign/evidence-checkout"
|
|
38
|
+
# Trusted execution-boundary identity adapter: local (interactive
|
|
39
|
+
# coordinator) or service (identity supplied by a trusted service executor
|
|
40
|
+
# via service_identity_env). Unknown identity fails closed.
|
|
41
|
+
identity_adapter: "local"
|
|
42
|
+
service_identity_env: "ACE_ASSIGN_SERVICE_IDENTITY"
|
|
43
|
+
|
|
31
44
|
# Subtree execution behavior
|
|
32
45
|
subtree:
|
|
33
46
|
pre_commit_review: true
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.60.0] - 2026-10-01
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Preserve task/project attachment when the public assignment creation path archives its source configuration, so managed attempts retain immutable journal authority.
|
|
14
|
+
- Cache-free evidence lookup handles a seed-only journal ref without creating a checkout or treating an absent attempt as a corrupt journal.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- `attempt evidence --kind review-approval` validates accepted independent approval separately from collection; retained collection/approval receipts support explicit historical-head validation without granting current check authority.
|
|
18
|
+
- Bind campaign check names to accepted executed operations and review collection to accepted `review-collect` artifacts, through `attempt evidence` purpose checks.
|
|
19
|
+
|
|
20
|
+
- `ace-assign attempt evidence` exposes read-only coordinator-accepted current-head check proof. Journal reads use immutable Git objects without writing cache/audit checkout state.
|
|
21
|
+
- Review receipts may consume a durable ace-review campaign result. The existing coordinator still validates authority, live head, independent reviewer, executed checks and artifact digests; stale or conflicting campaign snapshots are rejected.
|
|
22
|
+
|
|
23
|
+
## [0.59.0] - 2026-09-29
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- Durable lifecycle exclusion (`Ace::Assign::Molecules::LifecycleExclusion`): prune holds the exclusive side from final evidence reads through removal while every supported start path -- attempt registration, fork/driver session launch, and worktree provisioning -- holds the shared side. Locks live outside every deletion target (sandbox-aware cache base or the shared Git common dir) so they survive removal of the target, and a `removed` marker makes post-prune starts fail closed instead of recreating a deleted identity.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
- `AttemptCoordinator#start` and `ForkSessionLauncher#launch` now participate in the prune exclusion: a concurrent prune blocks new writers for the identity, and a start targeting a pruned assignment raises a conflict instead of writing into a recreated path.
|
|
30
|
+
|
|
31
|
+
## [0.58.0] - 2026-09-28
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
- Durable assignment attempts: `ace-assign attempt start|status|finish|reconcile` bind an immutable attempt ID to an assignment step/subtree, project, boundary-derived actor identity, and `base_head`, with a `reserved -> running -> succeeded/failed/stopped/uncertain` lifecycle and conservative reconciliation of interruptions (no automatic replay of merge/publish/deploy).
|
|
35
|
+
- Managed attempts journal accepted execution evidence to the configured evidence Git ref (default `refs/ace/execution`) through an isolated audit checkout with flock serialization and expected-old-value `git update-ref` compare-and-swap; `refs/ace/execution` never advances the deliverable candidate branch.
|
|
36
|
+
- Structured execution receipts: verifiable artifact digests, executed checks, executed independent reviewer verdicts for the exact current head, and rejection of credentials/terminal output in accepted evidence.
|
|
37
|
+
- `ace-assign status` (JSON) now includes the active attempt, `base_head`, `candidate_head`, `evidence_git_ref`, `journal_commit`, and unresolved effects.
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
- BREAKING: delivery evidence is derived exclusively from accepted, current-head journal receipts. The previous interpretation that a current `.ace-local/review`/`.ace-local/release` report plus hardcoded `terminal` feedback authorizes merge is removed; `merge_decision` is now `authorized` or `approval-required` (never `report-only`). Existing `EvidenceCalculator` consumers must read evidence from attempts.
|
|
41
|
+
- BREAKING: taskless assignments persist attempts locally (`recovery_mode: local_only`), never claim Git-backed recovery, and cannot record external effects until attached to a source task.
|
|
42
|
+
- New `attempt` error family exits with code 5 (conflict, rejected receipt, unauthorized identity, unavailable evidence, illegal attempt state).
|
|
43
|
+
|
|
10
44
|
## [0.57.1] - 2026-09-27
|
|
11
45
|
|
|
12
46
|
### Technical
|
|
@@ -490,7 +524,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
490
524
|
- Unified dynamic step default instructions under a single `AssignmentExecutor::DEFAULT_DYNAMIC_STEP_INSTRUCTIONS` source.
|
|
491
525
|
- Limited canonical batch expansion to explicit `workflow`, `skill`, or declared `sub_steps` inputs, preserving prior numbering behavior for plain flat batch inserts.
|
|
492
526
|
- Updated usage, getting-started, and handbook docs to cover the new `add --from` workflow.
|
|
493
|
-
- Updated `record-demo` step instructions to require diagnosis before skipping on failure
|
|
527
|
+
- Updated `record-demo` step instructions to require diagnosis before skipping on failure -- agents must check config, available fonts, and spike findings before reporting a non-blocking skip.
|
|
494
528
|
- Added `decision_notes.non_blocking_policy` to `record-demo` step catalog entry clarifying that "non-blocking" means "diagnose and retry before skipping," not "skip on first failure."
|
|
495
529
|
|
|
496
530
|
### Technical
|
|
@@ -946,7 +980,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
946
980
|
## [0.20.0] - 2026-03-05
|
|
947
981
|
|
|
948
982
|
### Added
|
|
949
|
-
- Provider-specific session detection fallback in `ForkSessionLauncher`
|
|
983
|
+
- Provider-specific session detection fallback in `ForkSessionLauncher` -- when a provider doesn't return a native `session_id`, scans local session storage via `SessionFinder` to detect the forked session by prompt matching.
|
|
950
984
|
- Session metadata file (`<root>-session.yml`) written for every fork run, capturing `session_id`, `provider`, `model`, and `completed_at` for traceability.
|
|
951
985
|
- Stall error messages now include `Session: <id>` when session metadata is available, enabling direct trace to agent session.
|
|
952
986
|
|
|
@@ -1031,14 +1065,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
1031
1065
|
## [0.16.1] - 2026-03-04
|
|
1032
1066
|
|
|
1033
1067
|
### Fixed
|
|
1034
|
-
- Correct `.agents/skills` typo to `.agent/skills` in default config and `SkillAssignSourceResolver`
|
|
1068
|
+
- Correct `.agents/skills` typo to `.agent/skills` in default config and `SkillAssignSourceResolver` -- skill discovery now uses the canonical provider-neutral path
|
|
1035
1069
|
|
|
1036
1070
|
## [0.16.0] - 2026-03-04
|
|
1037
1071
|
|
|
1038
1072
|
### Added
|
|
1039
|
-
- New `onboard-base` catalog step
|
|
1040
|
-
- New `task-load` catalog step
|
|
1041
|
-
- Taskref placeholder substitution in catalog step descriptions
|
|
1073
|
+
- New `onboard-base` catalog step -- loads base project context via `ace-bundle project-base`
|
|
1074
|
+
- New `task-load` catalog step -- loads task behavioral spec via `ace-bundle task://<taskref>`
|
|
1075
|
+
- Taskref placeholder substitution in catalog step descriptions -- `<taskref>` in step descriptions is replaced with actual task reference during child instruction building
|
|
1042
1076
|
|
|
1043
1077
|
### Changed
|
|
1044
1078
|
- Default assignment presets updated to use ace-task
|
|
@@ -1052,7 +1086,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
1052
1086
|
## [0.15.1] - 2026-03-01
|
|
1053
1087
|
|
|
1054
1088
|
### Added
|
|
1055
|
-
- Fork-run crash recovery protocol in drive workflow
|
|
1089
|
+
- Fork-run crash recovery protocol in drive workflow -- detection, commit partial work, progress report, inject recovery steps, re-fork pattern for partial completion scenarios
|
|
1056
1090
|
|
|
1057
1091
|
## [0.15.0] - 2026-02-28
|
|
1058
1092
|
|
|
@@ -1209,7 +1243,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
1209
1243
|
## [0.12.14] - 2026-02-22
|
|
1210
1244
|
|
|
1211
1245
|
### Added
|
|
1212
|
-
- Subtree guard step in drive workflow
|
|
1246
|
+
- Subtree guard step in drive workflow -- driver reviews all fork report files before continuing to next step
|
|
1213
1247
|
- Report review instruction in split-subtree-root step template for fork context
|
|
1214
1248
|
|
|
1215
1249
|
## [0.12.13] - 2026-02-22
|
|
@@ -1282,13 +1316,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
1282
1316
|
## [0.12.1] - 2026-02-19
|
|
1283
1317
|
|
|
1284
1318
|
### Fixed
|
|
1285
|
-
- `reflect-verify-cycle` pair changed from `sequential` to `conditional`
|
|
1319
|
+
- `reflect-verify-cycle` pair changed from `sequential` to `conditional` -- prevents overriding optional strength of reflect-and-refactor
|
|
1286
1320
|
- Renamed `max_recursion` to `max_reruns` in replan config for clarity
|
|
1287
1321
|
|
|
1288
1322
|
## [0.12.0] - 2026-02-19
|
|
1289
1323
|
|
|
1290
1324
|
### Added
|
|
1291
|
-
- New `reflect-and-refactor` step in catalog
|
|
1325
|
+
- New `reflect-and-refactor` step in catalog -- analyzes implementation against ATOM principles and executes targeted refactoring before PR creation
|
|
1292
1326
|
- Composition rules for reflect-and-refactor: ordering (after verify, before mark-done/release/retro), pairs (verify cycle, fix cycle, replan cycle), and conditional suggestion
|
|
1293
1327
|
- `create-retro` step now consumes `findings-report` from reflect-and-refactor as recommended prerequisite
|
|
1294
1328
|
- `implement-with-pr` recipe updated to include optional reflect-and-refactor step after work-on-task
|
|
@@ -1302,7 +1336,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
1302
1336
|
|
|
1303
1337
|
### Fixed
|
|
1304
1338
|
|
|
1305
|
-
- `assignment_state` now checks `completed` before `failed`
|
|
1339
|
+
- `assignment_state` now checks `completed` before `failed` -- assignments where all steps are done/failed correctly report `:completed` instead of `:failed`
|
|
1306
1340
|
|
|
1307
1341
|
### Added
|
|
1308
1342
|
|
|
@@ -1893,7 +1927,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
1893
1927
|
|
|
1894
1928
|
### Fixed
|
|
1895
1929
|
|
|
1896
|
-
- CLI `start` command crashes with positional argument (`ace-assign start job.yaml`) because `option :config` requires `--config` flag
|
|
1930
|
+
- CLI `start` command crashes with positional argument (`ace-assign start job.yaml`) because `option :config` requires `--config` flag -- renamed command to `create` with `argument :config`
|
|
1897
1931
|
- `ace-bundle wfi://prepare-assignment` fails due to missing project-level wfi:// protocol registration
|
|
1898
1932
|
|
|
1899
1933
|
### Added
|
data/docs/exit-codes.md
CHANGED
|
@@ -20,6 +20,7 @@ This document describes the exit codes returned by the `ace-assign` CLI.
|
|
|
20
20
|
| 2 | Assignment error | No active assignment or assignment not found |
|
|
21
21
|
| 3 | Configuration not found | Config file does not exist |
|
|
22
22
|
| 4 | Step not found | Referencing a step that does not exist |
|
|
23
|
+
| 5 | Attempt error | Attempt conflict, rejected receipt, unauthorized identity, unavailable evidence, or illegal attempt state |
|
|
23
24
|
|
|
24
25
|
## Exit Code Details
|
|
25
26
|
|
|
@@ -55,6 +56,15 @@ The requested step reference does not exist in the target assignment:
|
|
|
55
56
|
- `retry` with unknown step number
|
|
56
57
|
- Commands that target a specific missing step
|
|
57
58
|
|
|
59
|
+
### Exit Code 5: Attempt Error
|
|
60
|
+
An attempt, receipt, identity, or evidence problem rejected the operation:
|
|
61
|
+
- `attempt start` with a conflicting binding for an owned subtree
|
|
62
|
+
- `attempt finish`/`attempt reconcile` with a receipt that fails verification (wrong binding, stale head, digest mismatch, self-approval, forbidden fields)
|
|
63
|
+
- Worker-identity attempts to accept a succeeded verdict
|
|
64
|
+
- Unknown or missing execution-boundary identity
|
|
65
|
+
- Reconciling a terminal attempt, or resolving uncertainty without a receipt
|
|
66
|
+
- Unavailable or unwritable evidence storage blocking a managed effect
|
|
67
|
+
|
|
58
68
|
## See Also
|
|
59
69
|
|
|
60
70
|
- [Usage Guide](usage.md) for full command reference
|
data/docs/usage.md
CHANGED
|
@@ -85,6 +85,55 @@ ace-assign add --step review-pr --after 100 --child
|
|
|
85
85
|
ace-assign add --yaml .ace-local/assign/jobs/add-task.yml --after 010 --child
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
+
## Attempt Evidence
|
|
89
|
+
|
|
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
|
+
|
|
92
|
+
### Attempt lifecycle
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
ace-assign attempt start --assignment ASSIGNMENT --step STEP --project PROJECT
|
|
96
|
+
ace-assign attempt status --assignment ASSIGNMENT --format json
|
|
97
|
+
ace-assign attempt finish --attempt ATTEMPT --receipt receipt.json
|
|
98
|
+
ace-assign attempt reconcile --attempt ATTEMPT --receipt receipt.json
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
- `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
|
+
- `attempt status` exposes only state, binding, references, and digests -- never credentials or receipt bytes.
|
|
103
|
+
- `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.
|
|
105
|
+
|
|
106
|
+
### State machine
|
|
107
|
+
|
|
108
|
+
`reserved -> running -> succeeded | failed | stopped | uncertain`; reconciliation may resolve `uncertain -> succeeded | failed`. Terminal attempts accept no new effects, and accepted history is append-only.
|
|
109
|
+
|
|
110
|
+
### Read accepted execution authority
|
|
111
|
+
|
|
112
|
+
`attempt evidence` reads a managed coordinator-accepted receipt from immutable journal history without transitions, cache writes, or audit checkout creation. Supply the accepted attempt ID and exact canonical receipt digest. The trusted local operator or OS-enforced service verifies the actual executed outcome before accepting its receipt; worker-authored success claims cannot accept themselves. This reader consumes that attestation and does not execute a test or model.
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
ace-assign attempt evidence --attempt ATTEMPT --receipt-digest SHA256 --format json \
|
|
116
|
+
--kind check --check-name tests
|
|
117
|
+
ace-assign attempt evidence --attempt ATTEMPT --receipt-digest SHA256 --format json \
|
|
118
|
+
--kind review-collection
|
|
119
|
+
ace-assign attempt evidence --attempt ATTEMPT --receipt-digest SHA256 --format json \
|
|
120
|
+
--kind review-collection --historical-head EXACT_RECORDED_HEAD
|
|
121
|
+
ace-assign attempt evidence --attempt ATTEMPT --receipt-digest SHA256 --format json \
|
|
122
|
+
--kind review-approval
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
A check proof requires the live candidate head, intact accepted artifacts, and the matching executed operation: `tests` uses `test`, other explicit check names use that operation name. Review collection requires an accepted `review-collect` operation with a passed `review-execution` outcome and the retained metadata/report/prompt artifacts. Review approval requires an ordinary accepted `review` operation with the approved independent reviewer verdict, exact head and retained report artifacts; collection proof cannot substitute for approval. Both collection and approval support an exact `--historical-head`. Historical review validation exposes `historical: true` and the recorded head; it preserves accepted source authority across head drift and cannot certify live checks. Unknown receipts, changed artifacts, unaccepted/unmanaged history, wrong purposes and stale current queries fail closed. Retain the configured evidence ref alongside durable review campaigns.
|
|
126
|
+
|
|
127
|
+
### Evidence storage and recovery modes
|
|
128
|
+
|
|
129
|
+
- 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.
|
|
130
|
+
- 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.
|
|
131
|
+
- 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.
|
|
132
|
+
|
|
133
|
+
### status integration
|
|
134
|
+
|
|
135
|
+
`ace-assign status --format json` includes the active attempt, `base_head`, `candidate_head`, `evidence_git_ref`, `journal_commit`, and unresolved effects. Feedback state is derived from accepted current-head evidence: `terminal` (accepted evidence at the current head), `open` (attempts without a terminal outcome), `uncertain` (unresolved interruptions), or `unknown` (no attempts). Merge authorization requires an executed independent review verdict for the current head plus an independent release receipt when relevant; report files, exit codes, and prose never authorize a merge.
|
|
136
|
+
|
|
88
137
|
## Commands
|
|
89
138
|
|
|
90
139
|
### `ace-assign create`
|
|
@@ -657,21 +657,71 @@ Follow the instructions directly, performing the described work:
|
|
|
657
657
|
|
|
658
658
|
### 4. External Action Rule (Attempt-First)
|
|
659
659
|
|
|
660
|
-
|
|
660
|
+
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
661
|
|
|
662
|
-
|
|
663
|
-
- If blocked, capture concrete evidence:
|
|
662
|
+
**Step 1: Start the attempt before executing the step.** Actor identity is derived from the execution boundary; never pass actor flags.
|
|
664
663
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
664
|
+
```bash
|
|
665
|
+
ATTEMPT_JSON=$(ace-assign attempt start --assignment "$ASSIGNMENT_ID" --step "$STEP_NUMBER" --project "$PROJECT_ID")
|
|
666
|
+
echo "$ATTEMPT_JSON"
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
- A repeated identical start returns the same attempt; a conflicting start fails (exit 5). Never launch a second writer for an owned subtree.
|
|
670
|
+
- `base_head` is captured exactly once at start. Record the `attempt_id`.
|
|
671
|
+
|
|
672
|
+
**Step 2: Execute the step command(s).** If blocked, capture concrete evidence:
|
|
673
|
+
|
|
674
|
+
- command attempted
|
|
675
|
+
- exact error output
|
|
676
|
+
- why the step cannot proceed
|
|
668
677
|
|
|
669
|
-
|
|
678
|
+
Mark the step failed with evidence (do not report synthetic completion):
|
|
670
679
|
|
|
671
680
|
```bash
|
|
672
681
|
ace-assign fail --message "Command failed: <cmd>. Error: <exact stderr>" --assignment "$ASSIGNMENT_TARGET"
|
|
673
682
|
```
|
|
674
683
|
|
|
684
|
+
**Step 3: Submit a structured receipt -- never prose -- to close the attempt.**
|
|
685
|
+
|
|
686
|
+
```bash
|
|
687
|
+
cat > /tmp/attempt-receipt.json << 'EOF'
|
|
688
|
+
{
|
|
689
|
+
"attempt_id": "<ATTEMPT_ID>",
|
|
690
|
+
"assignment_id": "<ASSIGNMENT_ID>",
|
|
691
|
+
"project_id": "<PROJECT_ID>",
|
|
692
|
+
"scope": "<STEP_NUMBER>",
|
|
693
|
+
"operation": "implement",
|
|
694
|
+
"producer": {"actor": "<boundary-actor>", "role": "worker", "runtime": "<boundary-runtime>"},
|
|
695
|
+
"head": "<exact tested/reviewed HEAD sha>",
|
|
696
|
+
"verdict": "succeeded",
|
|
697
|
+
"artifacts": [{"path": "report.md", "sha256": "<sha256>"}],
|
|
698
|
+
"checks": [{"name": "ace-test", "verdict": "passed"}],
|
|
699
|
+
"review": {"reviewer": {"actor": "<independent-reviewer>", "runtime": "<reviewer-runtime>"}, "verdict": "approved", "head": "<same HEAD sha>"}
|
|
700
|
+
}
|
|
701
|
+
EOF
|
|
702
|
+
|
|
703
|
+
ace-assign attempt finish --attempt "$ATTEMPT_ID" --receipt /tmp/attempt-receipt.json
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
Receipt rules enforced by the coordinator:
|
|
707
|
+
|
|
708
|
+
- `succeeded` verdicts require verifiable artifact digests, passing checks, and trusted coordinator/service authority; worker runtimes may submit attributable results but cannot self-approve.
|
|
709
|
+
- Review approval requires an executed independent reviewer verdict for the exact current head; same author/reviewer and report-only existence are rejected.
|
|
710
|
+
- Receipts carrying credentials, environment dumps, or terminal output are rejected outright.
|
|
711
|
+
- External effects (`merge`, `publish`, `deploy`, `release`) additionally require an accepted independent review receipt for the current head. Taskless assignments cannot record external effects.
|
|
712
|
+
|
|
713
|
+
**Step 4: Handle interruptions by classifying and reconciling -- never by assuming.**
|
|
714
|
+
|
|
715
|
+
```bash
|
|
716
|
+
ace-assign attempt status --assignment "$ASSIGNMENT_ID" --format json
|
|
717
|
+
ace-assign attempt reconcile --attempt "$ATTEMPT_ID"
|
|
718
|
+
ace-assign attempt reconcile --attempt "$ATTEMPT_ID" --receipt /tmp/attempt-receipt.json
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
- A lost receipt after a possible effect is `uncertain`; it resolves only against a verified receipt attributed to the recorded execution boundary.
|
|
722
|
+
- Running attempts stay running only while their recorded process is verifiably live; anything else becomes `stopped` or `uncertain`.
|
|
723
|
+
- Never replay `merge`, `publish`, or `deploy` automatically. Reconciliation records evidence; it never re-executes effects.
|
|
724
|
+
|
|
675
725
|
### Human-in-the-Loop (HITL) Stall Protocol
|
|
676
726
|
|
|
677
727
|
Canonical source of truth: `wfi://hitl`.
|
|
@@ -717,7 +767,7 @@ For a blocked step:
|
|
|
717
767
|
|
|
718
768
|
### 5. Write Report (Only After Real Execution)
|
|
719
769
|
|
|
720
|
-
After
|
|
770
|
+
After the attempt accepted a receipt (or the step was failed with evidence), write a brief report summarizing what was accomplished. The report advances the queue; the accepted attempt receipt carries the delivery evidence:
|
|
721
771
|
|
|
722
772
|
```bash
|
|
723
773
|
# Write report content to a temp file
|
|
@@ -737,6 +787,12 @@ EOF
|
|
|
737
787
|
ace-assign finish --message /tmp/step-report.md --assignment "$ASSIGNMENT_TARGET"
|
|
738
788
|
```
|
|
739
789
|
|
|
790
|
+
Prohibited:
|
|
791
|
+
|
|
792
|
+
- Advancing a step when its attempt has no accepted receipt (or recorded failure evidence).
|
|
793
|
+
- Treating a written report, exit 0, or prose as proof of completion. Check `ace-assign attempt status --assignment "$ASSIGNMENT_ID" --format json` for accepted evidence and unresolved effects.
|
|
794
|
+
- Reporting `uncertain` attempts as complete; reconcile them first.
|
|
795
|
+
|
|
740
796
|
### 6. Verify State Transition (Required)
|
|
741
797
|
|
|
742
798
|
After each `ace-assign finish --message` or `ace-assign fail`, verify queue state:
|
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
# Validate a transition.
|
|
63
|
+
#
|
|
64
|
+
# @param from [String] Current state
|
|
65
|
+
# @param to [String] Requested next state
|
|
66
|
+
# @raise [Ace::Assign::AttemptErrors::InvalidTransition] if illegal
|
|
67
|
+
def self.transition!(from, to)
|
|
68
|
+
return to.to_s if can_transition?(from, to)
|
|
69
|
+
|
|
70
|
+
raise Ace::Assign::AttemptErrors::InvalidTransition,
|
|
71
|
+
"Illegal attempt transition #{from} -> #{to} (legal from #{from}: #{next_states(from).join(', ')})"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
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
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Ace
|
|
6
|
+
module Assign
|
|
7
|
+
module CLI
|
|
8
|
+
module Commands
|
|
9
|
+
# Public `ace-assign attempt` command namespace (interface contract:
|
|
10
|
+
# start, status, finish, reconcile).
|
|
11
|
+
module Attempt
|
|
12
|
+
# Shared coordinator construction, option validation, and JSON output.
|
|
13
|
+
module Base
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def build_coordinator
|
|
17
|
+
Organisms::AttemptCoordinator.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def require_option(options, key, usage)
|
|
21
|
+
value = options[key].to_s.strip
|
|
22
|
+
raise Ace::Support::Cli::Error, "Missing --#{key}: usage: ace-assign attempt #{usage}" if value.empty?
|
|
23
|
+
|
|
24
|
+
value
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def emit_json(payload)
|
|
28
|
+
puts JSON.generate(payload)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative "base"
|
|
3
|
+
module Ace
|
|
4
|
+
module Assign
|
|
5
|
+
module CLI
|
|
6
|
+
module Commands
|
|
7
|
+
module Attempt
|
|
8
|
+
class Evidence < Ace::Support::Cli::Command
|
|
9
|
+
include Attempt::Base
|
|
10
|
+
desc "Read coordinator-accepted live execution or historical review authority"
|
|
11
|
+
option :attempt, required: true, desc: "Accepted execution attempt ID"
|
|
12
|
+
option :receipt_digest, required: true, desc: "Accepted receipt digest"
|
|
13
|
+
option :kind, default: "check", desc: "Evidence purpose: check, review-collection or review-approval"
|
|
14
|
+
option :check_name, default: "tests", desc: "Required check name (tests binds operation test)"
|
|
15
|
+
option :historical_head, desc: "Recorded collection/approval head for historical authority only"
|
|
16
|
+
option :format, default: "json", desc: "JSON evidence projection"
|
|
17
|
+
|
|
18
|
+
def call(**options)
|
|
19
|
+
raise Ace::Support::Cli::Error, "evidence supports --format json" unless options[:format] == "json"
|
|
20
|
+
emit_json(build_coordinator.evidence(attempt_id: options[:attempt], receipt_digest: options[:receipt_digest],
|
|
21
|
+
kind: options[:kind], check_name: options[:check_name], historical_head: options[:historical_head]))
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "base"
|
|
5
|
+
|
|
6
|
+
module Ace
|
|
7
|
+
module Assign
|
|
8
|
+
module CLI
|
|
9
|
+
module Commands
|
|
10
|
+
module Attempt
|
|
11
|
+
# Accept a structured execution receipt and record the outcome.
|
|
12
|
+
# Terminal and uncertain attempts refuse new receipts.
|
|
13
|
+
class Finish < Ace::Support::Cli::Command
|
|
14
|
+
include Ace::Support::Cli::Base
|
|
15
|
+
include Attempt::Base
|
|
16
|
+
|
|
17
|
+
desc "Finish an attempt with a structured execution receipt"
|
|
18
|
+
|
|
19
|
+
option :attempt, desc: "Attempt ID"
|
|
20
|
+
option :receipt, desc: "Path to the receipt JSON file"
|
|
21
|
+
|
|
22
|
+
def call(**options)
|
|
23
|
+
attempt_id = require_option(options, :attempt, "finish --attempt ID --receipt FILE")
|
|
24
|
+
receipt = require_option(options, :receipt, "finish --attempt ID --receipt FILE")
|
|
25
|
+
|
|
26
|
+
result = build_coordinator.finish(attempt_id: attempt_id, receipt_path: receipt)
|
|
27
|
+
|
|
28
|
+
emit_json(result.projection)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|