devrites 7.3.4 → 7.3.5
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.
- package/CHANGELOG.md +18 -0
- package/README.md +1 -1
- package/docs/adr/0039-autocomplete-structural-admission.md +87 -0
- package/docs/adr/README.md +1 -0
- package/docs/cli.md +103 -5
- package/docs/engine/commands.md +46 -1
- package/engine/internal/lib/claims.go +65 -8
- package/engine/internal/lib/continuation.go +6 -1
- package/engine/internal/lib/dispatch.go +78 -32
- package/engine/internal/lib/frontier.go +103 -47
- package/engine/internal/lib/frontier_controller.go +652 -0
- package/engine/internal/lib/frontier_roadmap.go +168 -0
- package/engine/internal/lib/next.go +9 -0
- package/engine/internal/parallel/repository.go +10 -0
- package/pack/.claude/skills/devrites-lib/reference/parallel-workspaces.md +106 -115
- package/pack/.claude/skills/devrites-lib/reference/standards/afk-hitl.md +18 -17
- package/pack/.claude/skills/devrites-lib/reference/workspace-artifact-schema.md +41 -38
- package/pack/.claude/skills/rite-autocomplete/SKILL.md +33 -38
- package/pack/.claude/skills/rite-autocomplete/reference/loop.md +46 -52
- package/pack/.claude/skills/rite-autocomplete/reference/stop-conditions.md +28 -28
- package/pack/.claude/skills/rite-build/reference/parallel-batch.md +17 -15
- package/pack/generated/claude/skills/devrites-lib/reference/parallel-workspaces.md +106 -115
- package/pack/generated/claude/skills/devrites-lib/reference/standards/afk-hitl.md +18 -17
- package/pack/generated/claude/skills/devrites-lib/reference/workspace-artifact-schema.md +41 -38
- package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +33 -38
- package/pack/generated/claude/skills/rite-autocomplete/reference/loop.md +46 -52
- package/pack/generated/claude/skills/rite-autocomplete/reference/stop-conditions.md +28 -28
- package/pack/generated/claude/skills/rite-build/reference/parallel-batch.md +17 -15
- package/pack/generated/codex/skills/devrites-lib/reference/parallel-workspaces.md +106 -115
- package/pack/generated/codex/skills/devrites-lib/reference/standards/afk-hitl.md +18 -17
- package/pack/generated/codex/skills/devrites-lib/reference/workspace-artifact-schema.md +41 -38
- package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +33 -38
- package/pack/generated/codex/skills/rite-autocomplete/reference/loop.md +46 -52
- package/pack/generated/codex/skills/rite-autocomplete/reference/stop-conditions.md +28 -28
- package/pack/generated/codex/skills/rite-build/reference/parallel-batch.md +17 -15
- package/pack/generated/devin/skills/devrites-lib/reference/parallel-workspaces.md +106 -115
- package/pack/generated/devin/skills/devrites-lib/reference/standards/afk-hitl.md +18 -17
- package/pack/generated/devin/skills/devrites-lib/reference/workspace-artifact-schema.md +41 -38
- package/pack/generated/devin/skills/rite-autocomplete/SKILL.md +33 -38
- package/pack/generated/devin/skills/rite-autocomplete/reference/loop.md +46 -52
- package/pack/generated/devin/skills/rite-autocomplete/reference/stop-conditions.md +28 -28
- package/pack/generated/devin/skills/rite-build/reference/parallel-batch.md +17 -15
- package/pack/generated/dsh/skills/devrites-lib/reference/parallel-workspaces.md +106 -115
- package/pack/generated/dsh/skills/devrites-lib/reference/standards/afk-hitl.md +18 -17
- package/pack/generated/dsh/skills/devrites-lib/reference/workspace-artifact-schema.md +41 -38
- package/pack/generated/dsh/skills/rite-autocomplete/SKILL.md +33 -38
- package/pack/generated/dsh/skills/rite-autocomplete/reference/loop.md +46 -52
- package/pack/generated/dsh/skills/rite-autocomplete/reference/stop-conditions.md +28 -28
- package/pack/generated/dsh/skills/rite-build/reference/parallel-batch.md +17 -15
- package/pack/generated/omp/skills/devrites-lib/reference/parallel-workspaces.md +106 -115
- package/pack/generated/omp/skills/devrites-lib/reference/standards/afk-hitl.md +18 -17
- package/pack/generated/omp/skills/devrites-lib/reference/workspace-artifact-schema.md +41 -38
- package/pack/generated/omp/skills/rite-autocomplete/SKILL.md +33 -38
- package/pack/generated/omp/skills/rite-autocomplete/reference/loop.md +46 -52
- package/pack/generated/omp/skills/rite-autocomplete/reference/stop-conditions.md +28 -28
- package/pack/generated/omp/skills/rite-build/reference/parallel-batch.md +17 -15
- package/pack/generated/pi/skills/devrites-lib/reference/parallel-workspaces.md +106 -115
- package/pack/generated/pi/skills/devrites-lib/reference/standards/afk-hitl.md +18 -17
- package/pack/generated/pi/skills/devrites-lib/reference/workspace-artifact-schema.md +41 -38
- package/pack/generated/pi/skills/rite-autocomplete/SKILL.md +33 -38
- package/pack/generated/pi/skills/rite-autocomplete/reference/loop.md +46 -52
- package/pack/generated/pi/skills/rite-autocomplete/reference/stop-conditions.md +28 -28
- package/pack/generated/pi/skills/rite-build/reference/parallel-batch.md +17 -15
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to DevRites are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and DevRites adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Releases are generated automatically by [semantic-release](https://semantic-release.gitbook.io/) from Conventional Commits on `main`.
|
|
4
4
|
|
|
5
|
+
## [7.3.5](https://github.com/ViktorsBaikers/DevRites/compare/v7.3.4...v7.3.5) (2026-11-05)
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
* **devrites:** enforce global autocomplete scheduling ([#208](https://github.com/ViktorsBaikers/DevRites/issues/208)) ([2e4fdb5](https://github.com/ViktorsBaikers/DevRites/commit/2e4fdb5178f2aa507ba565a70b3c2c86810cc5f7))
|
|
10
|
+
|
|
5
11
|
## [7.3.4](https://github.com/ViktorsBaikers/DevRites/compare/v7.3.3...v7.3.4) (2026-11-03)
|
|
6
12
|
|
|
7
13
|
### Fixed
|
|
@@ -47,6 +53,18 @@ All notable changes to DevRites are documented here. The format follows [Keep a
|
|
|
47
53
|
|
|
48
54
|
### Fixed
|
|
49
55
|
|
|
56
|
+
- Give an explicitly identified Autocomplete controller structural writer-admission
|
|
57
|
+
checks for actual successor preparation and source-qualified mixed reservations.
|
|
58
|
+
Surface fresh global scheduling after returns and prioritize unresolved global
|
|
59
|
+
work over local Build cursors, while preserving narrow disclosed deferrals,
|
|
60
|
+
standalone AFK, native review/proof, isolation and Ship approval.
|
|
61
|
+
- Accept one explicit decision-bound canonical sequence roadmap without rewriting
|
|
62
|
+
historical tables; reject ambiguous owners, stale identities and unsafe paths.
|
|
63
|
+
- Keep writer admission bound to immutable pending-lane paths, current controller
|
|
64
|
+
identity, disjoint active reservations and the effective global cap. Check the
|
|
65
|
+
complete writer allowlist against live foreign claims, including symlink
|
|
66
|
+
aliases, even when optional claim flags are absent or name unrelated paths.
|
|
67
|
+
|
|
50
68
|
- Use `GH_TOKEN`, then `GITHUB_TOKEN`, then optional bounded noninteractive GitHub
|
|
51
69
|
CLI credential lookup for release API requests. Keep credentials off release
|
|
52
70
|
assets and off-origin redirects, fail explicit invalid credentials, and document
|
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ history can't lose it. After you clear the conversation, switch hosts, or hand
|
|
|
31
31
|
the feature to another engineer, the next session reads the same plan,
|
|
32
32
|
decisions, and evidence before it continues.
|
|
33
33
|
|
|
34
|
-
**Status:** [`v7.3.
|
|
34
|
+
**Status:** [`v7.3.5`](https://github.com/ViktorsBaikers/DevRites/releases/tag/v7.3.5): see [`CHANGELOG.md`](CHANGELOG.md) for release notes.
|
|
35
35
|
|
|
36
36
|
This is the latest published release; `main` may contain unreleased work.
|
|
37
37
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# ADR-0039: Structural admission for the native Autocomplete controller
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted
|
|
4
|
+
- **Date:** 2026-11-05
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
Consumer traces showed a native Autocomplete controller repeatedly completing one
|
|
9
|
+
workspace while leaving recorded continuation preparation unstarted. A global
|
|
10
|
+
frontier report alone did not prevent subsequent local writer admission. The
|
|
11
|
+
controller also recognized a historical workspace rename without resolving the
|
|
12
|
+
conflict between the old roadmap table and current sequence identities.
|
|
13
|
+
|
|
14
|
+
ADR-0022 and ADR-0024 leave semantic scheduling and model dispatch with native
|
|
15
|
+
hosts. Replacing them with an engine agent bridge, persistent queue, scheduling
|
|
16
|
+
receipts, or heuristic interpretation of reviewer prose would create a second
|
|
17
|
+
orchestration authority. Existing dispatch and lease primitives already retain
|
|
18
|
+
observable preparation accounts, source-qualified reservations and write claims.
|
|
19
|
+
|
|
20
|
+
## Decision
|
|
21
|
+
|
|
22
|
+
Narrow ADR-0022's native-scheduling boundary and ADR-0024's policy boundary only
|
|
23
|
+
for structural Autocomplete writer admission:
|
|
24
|
+
|
|
25
|
+
- Explicit native Autocomplete invocation records `mode: autocomplete` in AFK.
|
|
26
|
+
This identifies the controller; it grants no readiness, independence or Ship
|
|
27
|
+
permission. Standalone AFK without this mode retains its existing contract.
|
|
28
|
+
- Before admitting a new Build-wright wave at capacity greater than one, the
|
|
29
|
+
engine checks immediate successor preparation and the eligible mixed cohort.
|
|
30
|
+
Preparation is
|
|
31
|
+
represented by an actual pending native preparation account, current native
|
|
32
|
+
readiness, or a concrete retained hold. Creating a placeholder or printing a
|
|
33
|
+
frontier does not satisfy this boundary.
|
|
34
|
+
- A source may disclose a target-specific preparation constraint through
|
|
35
|
+
`frontier-blocked: <workspace> — <reason>` in its existing evidence ledger.
|
|
36
|
+
`sequence` names only unresolved historical sequence normalization. These rows
|
|
37
|
+
defer scheduling obligations; they never waive malformed authority, source
|
|
38
|
+
readiness, trust boundaries, writer claims, path/capacity limits or proof.
|
|
39
|
+
- An already-ready foreign slice requires an existing mixed lease reservation
|
|
40
|
+
or an exact source-qualified `serial:` rationale for ordered handoff. A local
|
|
41
|
+
serial reason or preparation deferral does not discharge foreign admission.
|
|
42
|
+
- A fresh global observation follows a committed dispatch return after mutation
|
|
43
|
+
and locks settle, even if later claim release fails; its nonzero failure and
|
|
44
|
+
claim exclusion remain. Rejected/unpersisted returns do not refresh. Global
|
|
45
|
+
obligations take continuation priority without rewriting local phase cursors
|
|
46
|
+
or recursively routing local-only work.
|
|
47
|
+
- One explicit canonical `roadmap.md`, bound to the actual sequence root and a
|
|
48
|
+
unique owner-local decision, may replace historical tables for scheduling.
|
|
49
|
+
History stays unchanged; duplicate authorities, malformed inputs, conflicting
|
|
50
|
+
current identities and invalid references fail closed. Native owners decide
|
|
51
|
+
whether the decision and scope are substantively approved.
|
|
52
|
+
- Initial admission freezes each pending writer lane's normalized allowlist in
|
|
53
|
+
existing dispatch metadata. Mutable tasks, reused batch labels or replacement
|
|
54
|
+
lease rows cannot move a retained actor's authority. Exact claim identity and
|
|
55
|
+
session remain required; claim release or expiry does not prove actor exit.
|
|
56
|
+
Returned/integrated siblings may free slots for rolling refill.
|
|
57
|
+
|
|
58
|
+
The engine does not invoke a model, create a native workspace, infer semantic
|
|
59
|
+
independence, judge a rationale's truth, bind a handle to an execution checkout,
|
|
60
|
+
or authorize publishing. Native hosts still execute preparation, dispatch exact
|
|
61
|
+
profiles, reconcile results and obey human and protected-validation gates.
|
|
62
|
+
|
|
63
|
+
## Alternatives considered
|
|
64
|
+
|
|
65
|
+
| Option | Why rejected |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| More reminders and frontier reports only | The observed controller already had that guidance and still skipped preparation. |
|
|
68
|
+
| Automatically accept the newest table or rename prose | Recency and prose do not establish canonical identity or supersession. |
|
|
69
|
+
| Rewrite archived maps to match the current run | Destroys the historical decision rather than recording current authority. |
|
|
70
|
+
| Require every future workspace READY before any writer | Serializes preparation and creates cycles when future plans depend on current repair. |
|
|
71
|
+
| Engine-owned model scheduling, receipts or persistent queue | Duplicates host authority and exceeds the deterministic local boundary. |
|
|
72
|
+
|
|
73
|
+
## Consequences
|
|
74
|
+
|
|
75
|
+
Skipping required preparation now refuses an observable writer-admission step
|
|
76
|
+
rather than remaining a narrative warning. Independent work can continue with a
|
|
77
|
+
disclosed specific constraint, without disguising deferred work as completed.
|
|
78
|
+
Canonical normalization has one machine-readable owner and does not require
|
|
79
|
+
altering old evidence or guessing workspace names.
|
|
80
|
+
|
|
81
|
+
This cannot prove that an arbitrary host obeys every instruction or that a native
|
|
82
|
+
account represents real semantic progress. Runtime claims therefore distinguish
|
|
83
|
+
structural admission, recorded native launches, and actual host behavior.
|
|
84
|
+
|
|
85
|
+
Guard tests: `engine/internal/lib/frontier_controller_test.go`,
|
|
86
|
+
`engine/internal/lib/frontier_roadmap_test.go`, and
|
|
87
|
+
`engine/tests/autocomplete_global_test.go`.
|
package/docs/adr/README.md
CHANGED
|
@@ -84,3 +84,4 @@ architecture; per-feature `decisions.md` files stay scoped to that feature.
|
|
|
84
84
|
| [0036](0036-archive-recall-is-a-read-time-query.md) | Archive recall is a read-time query | Accepted; consistent with 0014/0022 (no engine index or store) | `engine/internal/lib/recall_test.go` |
|
|
85
85
|
| [0037](0037-autocomplete-lands-local-checkpoints.md) | Autocomplete lands local checkpoints and self-resolves own scratch | Accepted; supersedes the ADR-0022 local-commit approval clause | `tests/authority-drift-test.sh` |
|
|
86
86
|
| [0038](0038-engine-flags-unattributed-root-edits.md) | The engine flags unattributed root edits | Accepted; narrows the ADR-0018 no-replacement-enforcement clause for root-edit attribution | `engine/internal/lib/treebaseline_test.go` |
|
|
87
|
+
| [0039](0039-autocomplete-structural-admission.md) | Structural admission for native Autocomplete | Accepted; narrowly amends 0022/0024 for observable writer admission, retains native dispatch and semantic authority | `engine/internal/lib/frontier_controller_test.go`, `engine/internal/lib/frontier_roadmap_test.go`, `engine/tests/autocomplete_global_test.go` |
|
package/docs/cli.md
CHANGED
|
@@ -213,7 +213,9 @@ self-contained updater.
|
|
|
213
213
|
and exits `3` naming the holder when a live claim by another session covers a
|
|
214
214
|
path; `release --session <id> --id <claim>` frees it; `list [--all]` shows
|
|
215
215
|
live (or all) claims; `check [--session <id>] <path>...` is the read-only
|
|
216
|
-
preflight.
|
|
216
|
+
preflight. Conflict checks cover logical paths and physical symlink aliases;
|
|
217
|
+
unresolved physical paths fail closed. Same-session allocation stays exempt.
|
|
218
|
+
Each claim records its host and the claiming session's long-lived
|
|
217
219
|
processes; when all of them have exited on this host, `list` shows the claim
|
|
218
220
|
`dead` and it no longer conflicts. Other hosts or an unavailable `ps` never
|
|
219
221
|
prove exit. Claims coordinate concurrent same-tree sessions; they never
|
|
@@ -285,6 +287,10 @@ The report emits:
|
|
|
285
287
|
- `frontier-prepare:` for a recorded next Spec cursor or an unambiguous next entry
|
|
286
288
|
in an exact position/workspace table, or `frontier-review-sequence:` artifact
|
|
287
289
|
paths when remaining work or conflicting sequence maps require native review.
|
|
290
|
+
- `frontier-authority: <artifact-path>,...` identifies actual files supplying a
|
|
291
|
+
valid nonempty scheduling map. For explicit canonical authority it names the
|
|
292
|
+
owning `roadmap.md` and same-owner `decisions.md`; legacy maps list their actual
|
|
293
|
+
artifacts without claiming canonical status.
|
|
288
294
|
|
|
289
295
|
Relations come from exact recorded `sequence_parent` edges, not name prefixes.
|
|
290
296
|
Conflicting identities, cycles, malformed related state, or invalid declared
|
|
@@ -295,14 +301,106 @@ Conflicting or amended sequence maps suppress missing-workspace preparation even
|
|
|
295
301
|
with a recorded Spec cursor until native authority normalization resolves them;
|
|
296
302
|
valid existing live descendants remain visible and eligible for guarded selection.
|
|
297
303
|
|
|
304
|
+
An optional `roadmap.md` becomes current scheduling authority when it contains
|
|
305
|
+
either `Sequence root:` or `Decision:`. Both headers are then required:
|
|
306
|
+
|
|
307
|
+
```markdown
|
|
308
|
+
Sequence root: actual-anchor-slug
|
|
309
|
+
Decision: DEC-017
|
|
310
|
+
|
|
311
|
+
| Position | Workspace |
|
|
312
|
+
| --- | --- |
|
|
313
|
+
| 1 | actual-anchor-slug |
|
|
314
|
+
| 2 | actual-renamed-child |
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Exactly one related live or archived workspace may own this explicit map.
|
|
318
|
+
`Decision: DEC-NNN` must identify exactly one `## DEC-NNN` heading in that
|
|
319
|
+
owner's `decisions.md`; the root must match the actual chain anchor. Exactly
|
|
320
|
+
one table must have contiguous positions from 1 and unique valid workspace
|
|
321
|
+
slugs, matching every related recorded position and exact successor. Folder
|
|
322
|
+
numbering, prose amendments, and unenrolled workspaces confer no authority.
|
|
323
|
+
A valid explicit map supersedes historical tables in `decisions.md`, `spec.md`,
|
|
324
|
+
and headerless `roadmap.md` for scheduling only; preserve those historical bytes.
|
|
325
|
+
Without either header, `roadmap.md` retains conservative legacy parsing.
|
|
326
|
+
Partial/malformed headers, multiple owners, invalid root/decision/table, or a
|
|
327
|
+
symlink/unreadable explicit map fail closed without legacy fallback. Use native
|
|
328
|
+
authority normalization to record the approved actual identities while retaining
|
|
329
|
+
history. Structural validation establishes identity, not semantic approval,
|
|
330
|
+
independence, Build readiness, or Ship permission.
|
|
331
|
+
|
|
298
332
|
Under AFK, `next`, `handoff`, and `check continuation` expose the same frontier.
|
|
299
333
|
A source-local `parallel select --from-plan` without `--workspaces` also prints
|
|
300
334
|
the report to stderr, preserving its machine-readable selection on stdout and
|
|
301
335
|
source-local picks: reporting another workspace never silently admits it.
|
|
302
|
-
Native Autocomplete must inspect
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
336
|
+
Native Autocomplete must inspect returned preparation/review actions and read
|
|
337
|
+
the exact `frontier-authority:` paths plus recorded restrictions before invoking
|
|
338
|
+
Spec/Plan/Vet or mixed selection. This read is required even when the owner is an
|
|
339
|
+
archived ancestor or another related workspace outside the local read-next bundle.
|
|
340
|
+
Path disjointness alone establishes neither semantic independence nor permission
|
|
341
|
+
to lift a planning-only restriction.
|
|
342
|
+
|
|
343
|
+
### Autocomplete controller admission
|
|
344
|
+
|
|
345
|
+
The existing `/rite-autocomplete` invocation writes or upserts `mode: autocomplete`
|
|
346
|
+
in `.devrites/AFK`; no extra flag is needed. It preserves other sentinel fields
|
|
347
|
+
unless an explicit `--parallel N` changes capacity. Mode identifies the native
|
|
348
|
+
controller, not authorization. Standalone AFK without mode retains its behavior;
|
|
349
|
+
duplicate or invalid mode requires normalization rather than silently disabling
|
|
350
|
+
the controller gate.
|
|
351
|
+
|
|
352
|
+
With controller mode and global capacity above one, a new Build wright
|
|
353
|
+
`dispatch open` checks structural global scheduling before writing claims, history,
|
|
354
|
+
or a round. It checks one immediate recorded successor per admitted source, not
|
|
355
|
+
the whole future sequence:
|
|
356
|
+
|
|
357
|
+
- Missing permitted successor: start its native Spec/Plan/Vet preparation and
|
|
358
|
+
retain actual launched planning/review handles before admitting the writer.
|
|
359
|
+
- Existing successor: require pending preparation handles, a current native
|
|
360
|
+
Build-ready binding, or its concrete human/dependency hold. A placeholder
|
|
361
|
+
state/spec or a prose claim that preparation happened is insufficient.
|
|
362
|
+
- Opted-out or exhausted future opening and source human/question holds do not
|
|
363
|
+
invent new preparation obligations. Unresolved sequence authority requires
|
|
364
|
+
native normalization.
|
|
365
|
+
|
|
366
|
+
For a concrete preparation constraint, retain
|
|
367
|
+
`frontier-blocked: <successor-slug> — <specific constraint>` in the source's
|
|
368
|
+
`evidence.md`. For unresolved historical normalization on otherwise-valid local
|
|
369
|
+
work use `frontier-blocked: sequence — <specific constraint>`. These deferrals
|
|
370
|
+
remain visible for native review; they grant no approval, readiness, or claim
|
|
371
|
+
that normalization succeeded. Neither waives malformed canonical authority,
|
|
372
|
+
invalid AFK/path/readiness/security checks, or already-ready foreign Build.
|
|
373
|
+
A generic local `serial: SLICE-002 — <reason>` does not waive cross-workspace
|
|
374
|
+
preparation. A discharged deferral does not repeatedly trigger controller work.
|
|
375
|
+
|
|
376
|
+
Ready independent foreign sources must participate in explicit repository-wide
|
|
377
|
+
mixed selection and lease capacity/path reservations. Reserve the cohort with
|
|
378
|
+
`parallel create/add`; source waves may then open separately. A concrete
|
|
379
|
+
`serial: other-workspace/SLICE-001 — <constraint>` can retain an ordered handoff
|
|
380
|
+
for that exact foreign pick; an unqualified local serial reason cannot silently
|
|
381
|
+
omit it. This does not waive successor preparation or readiness, dependencies,
|
|
382
|
+
runtime/path isolation, actor claims, or reviewer/proof windows.
|
|
383
|
+
Dispatch `controller_batch` is an opaque cohort-instance admission identity,
|
|
384
|
+
recording initial whole-wave, source-qualified live-lease admission, never proof
|
|
385
|
+
of actor checkout, cwd, or runtime profile. Immutable `admitted_paths` snapshots
|
|
386
|
+
retain each pending writer lane's normalized allowlist. Missing snapshots or
|
|
387
|
+
changed reservations require actor reconciliation; mutable tasks cannot backfill
|
|
388
|
+
authority. Returned/integrated siblings may release slots for rolling refill.
|
|
389
|
+
Exact claim identity/session remains required; release or expiry does not stop
|
|
390
|
+
an actor. Retained unbound or other-cohort writers hold new actors; later lease
|
|
391
|
+
IDs cannot retrofit them. Native exact cwd/profile checks remain required.
|
|
392
|
+
Unsupported isolation permits truthful local-owner serial work and normal
|
|
393
|
+
ordered handoff, never an early same-tree foreign writer.
|
|
394
|
+
|
|
395
|
+
Controller continuation prioritizes actionable global normalization, preparation,
|
|
396
|
+
and foreign work over a direct local Build invocation while preserving the local
|
|
397
|
+
phase cursor. Local-only ready work does not recursively invoke Autocomplete.
|
|
398
|
+
After a committed dispatch return, a fresh global scheduling result resurfaces
|
|
399
|
+
the next obligation—even if later claim release fails; its nonzero failure and
|
|
400
|
+
claim exclusion remain. Rejected or unpersisted returns do not refresh.
|
|
401
|
+
Ordinary status reads remain read-only. Protected reviewer/proof windows defer
|
|
402
|
+
unsafe landing, not independent planning. No such transition invokes Ship or
|
|
403
|
+
launches a model through the engine.
|
|
306
404
|
|
|
307
405
|
The engine does not invoke models, create workspaces, change `ACTIVE`, resolve
|
|
308
406
|
semantic authority, or grant Ship approval. These outputs guide the native host;
|
package/docs/engine/commands.md
CHANGED
|
@@ -35,7 +35,7 @@ workflow strategy.
|
|
|
35
35
|
| `orient <slug>` | Alias for `observe summary`. |
|
|
36
36
|
| `observe slice <slug> <SLICE-ID>` | Print one `SLICE-###` section of `tasks.md` so Build reads a slice, not the file. |
|
|
37
37
|
| `next [slug]` | Print the minimal remaining lifecycle path plus advisory skips; in Build also `eligible_now:`, `batch:` and the `select-command:` (`parallel select --root <project> --slug <slug> --cap N --from-plan`, N = AFK max_parallel else 10) that reproduces the batch. Prints `blocked: unattributed root edit: ...` after `open-questions:` and exits `3` while the ADR-0038 tripwire flags a product path; the rest of the output still prints. |
|
|
38
|
-
| `frontier [slug]` | Read-only report across exact recorded sequence relations, including archived intermediaries and live descendants. Emits `frontier: actionable\|waiting\|blocked\|clear` (`none` for a non-sequence source), `frontier-source:`, and `frontier-workspace:` rows with location, `inspect\|prepare\|repair\|wait\|complete` action, and reason. Emits `frontier-select-command:`, `frontier-picks:`, `frontier-selection: blocked`, `frontier-prepare:`, or `frontier-review-sequence:` where applicable. Explicit slug may be archived; absent ACTIVE recovery requires a unique archived terminal tip with a recorded native Spec edge, otherwise refuses ambiguity. Never changes ACTIVE, restores archives, creates workspaces, or launches writers. Native preparation and semantic admission remain required; see [sequence frontier](../cli.md#sequence-frontier). |
|
|
38
|
+
| `frontier [slug]` | Read-only report across exact recorded sequence relations, including archived intermediaries and live descendants. Emits `frontier: actionable\|waiting\|blocked\|clear` (`none` for a non-sequence source), `frontier-source:`, and `frontier-workspace:` rows with location, `inspect\|prepare\|repair\|wait\|complete` action, and reason. Emits `frontier-select-command:`, `frontier-picks:`, `frontier-selection: blocked`, `frontier-prepare:`, or `frontier-review-sequence:` where applicable. Explicit slug may be archived; absent ACTIVE recovery requires a unique archived terminal tip with a recorded native Spec edge, otherwise refuses ambiguity. One explicit `roadmap.md` owner anywhere in the related live/archive chain may supersede historical maps for scheduling: both `Sequence root:` and `Decision:` headers, a unique owner-local decision heading, and one contiguous position/workspace table must match recorded identities and successors. Either header claims this format; invalid authority fails closed without legacy fallback. Headerless maps retain legacy parsing. Native normalization preserves historical bytes; structural identity is not semantic approval. Never changes ACTIVE, restores archives, creates workspaces, or launches writers. Native preparation and semantic admission remain required; see [sequence frontier](../cli.md#sequence-frontier). |
|
|
39
39
|
| `handoff [slug] [--resume]` | Emit the deterministic resume record: cursor fields, `awaiting_human`, open `questions.md` gate kinds, the `gates.md` reduction with unmet/stale/abandoned ids, `decisions.md` dead ends, in-flight dispatch waves (`inflight_wave:` lines: phase, state, pending roles, slices), missing required files, the canonical read-next order, and the same `continuation:` lines as `check continuation`. Read-only unless `--resume`, which appends one `resume` event (role root) to `metrics.jsonl`; `metrics summary` then reports minutes from each resume to the next dispatch. The `/rite-handoff` spine and the compaction-fallback entry point. |
|
|
40
40
|
| `recall --paths <p1,p2,...> [--limit n]` | Read-time query over `.devrites/archive/`: list archived features whose `touched-files.md` candidate manifest covers a queried project-relative path (exact or directory prefix, case-folded), ranked by overlap count, then newest archive-directory mtime, then slug (default 10 features). Each feature prints up to 8 `archive/<slug>/<file>:<line>` pointers, then `+N more`: `decisions.md` `DEC-###` entries and `## Carry-forward constraints` bullets whose `Files:` overlap (or a constraint naming no files), plus `review.md`/`seal.md`/`drift.md` lines that mention a queried path; fenced examples are ignored. Never writes, caches, or indexes (no metric either); symlinked archive entries are refused. An unreadable archive file prints `unreadable:` and the scan continues. Exit `0` (including `recall: no archive`), `1` invalid archive directory, `2` usage. |
|
|
41
41
|
| `context [slug] (--phase <p>\|--skill <name>) [--role <r>] [--slice <SLICE-ID>] [--recheck <inventory>] [--trigger a,b] [--inline-contract] [--whole]` | Emit one deduplicated read-set bundle per phase/role/skill from each skill's `loads:` manifest; `--skill` works without a workspace when `--out` is given. A root `--phase build\|autocomplete` bundle (no `--role`, no `--slice`) defaults to `--slice <active_slice>` while the state.md cursor phase is `build` and active_slice names a slice present in tasks.md (prints `scope: slice SLICE-### (auto from state.md) ...`), unless `.devrites/AFK` `max_parallel` is 2 or more or a parallel lease is `running` (the bundle stays whole); `--whole` opts out. Every bundle opens, after its header comments, with a table of contents: one `L<start>-<end> <section label>` line per `===== label =====` section, giving its exact line range in the bundle file. A bundle without `--role` (the root's own read) gives over-budget artifacts as by-reference indexes; a `--role` bundle leads with the role's agent contract and names the manifest's `rootOnly` paths instead of inlining them. When the contract resolves under `.claude/agents`, `.omp/agents`, or `.codex/agents` (the host loads it as the agent's system prompt), the bundle writes one header line naming its path, size, and SHA-256 instead of the body; other layouts (pi, devin, unknown) and `--inline-contract` (only with `--role`) inline the body, and a missing contract still exits `3`. Inside inlined files, a `--role` bundle replaces each `<!-- root-only -->`…`<!-- /root-only -->` block with a one-line stub naming the omitted heading, and does the same for each `<!-- model-surface -->` block unless the role is `security-auditor`, the feature has an `ai-spec.md`, or `spec.md`/`plan.md`/`tasks.md`/`design-brief.md` are absent or (outside the spec template's `AI-SPEC annex`) name a model, agent, RAG, or tool surface; an unbalanced or headingless marker exits `2`. `--slice` inlines that slice's `tasks.md` block and turns each over-budget artifact, and each artifact over 16 KiB except `spec.md`, into a by-reference index (path, SHA-256, headings, line hits for every ID the block cites); repeat `--slice` for several slices; an unknown slice exits `3`. In phase `build`, a `--role` bundle other than `slice-wright` without `--slice` exits `3` (`unscoped build reviewer bundle`). `--recheck <inventory>` (phase `build`, with `--role` and `--slice`; path relative to the workspace) writes `<phase>-<role>.<slice>.recheck.bundle.md`: the inventory file and `core.md` inline whole, every other non-`rootOnly` pack file becomes a by-reference index (path, SHA-256, headings with line numbers) followed by each section the inventory cites as `<file>.md § <heading>`; workspace handling matches `--slice`. A missing or empty inventory exits `2`. First reviews, full-inventory rechecks, Vet, `/rite-review`, and Seal do not use it. |
|
|
@@ -62,6 +62,51 @@ workflow strategy.
|
|
|
62
62
|
Under AFK, `next`, `handoff`, and `check continuation` include the sequence
|
|
63
63
|
frontier without replacing the source-local cursor. Reporting actionable work
|
|
64
64
|
does not authorize Build or Ship, and cannot guarantee native-host compliance.
|
|
65
|
+
For a valid nonempty map, `frontier-authority: <artifact-path>,...` exposes actual
|
|
66
|
+
authority files. Canonical maps name owner `roadmap.md` and owner-local
|
|
67
|
+
`decisions.md`; valid legacy maps name their artifacts without a canonical claim.
|
|
68
|
+
Native preparation must read those exact paths and restrictions, including
|
|
69
|
+
archived/related owners absent from the local context read-next bundle.
|
|
70
|
+
|
|
71
|
+
Native `/rite-autocomplete` automatically upserts AFK `mode: autocomplete`
|
|
72
|
+
(no new flag), preserving other fields except an explicit `--parallel N` capacity
|
|
73
|
+
update. Missing mode retains standalone AFK behavior; invalid/duplicate mode
|
|
74
|
+
fails closed. With mode and global cap >1, Build wright `dispatch open` checks
|
|
75
|
+
global scheduling before claims/history/round writes: prepare one immediate
|
|
76
|
+
recorded successor per source, with actual pending planning/review handles,
|
|
77
|
+
current native readiness, or a concrete human/dependency hold; placeholders do
|
|
78
|
+
not count. Opt-out, exhausted opening budgets, and source human/question holds
|
|
79
|
+
remain binding. Unresolved authority requires native normalization.
|
|
80
|
+
|
|
81
|
+
Source `evidence.md` may retain `frontier-blocked: <successor-slug> — <specific constraint>`
|
|
82
|
+
or `frontier-blocked: sequence — <specific constraint>` as a visible scheduling
|
|
83
|
+
deferral, never approval or completed normalization. Generic local `serial:`
|
|
84
|
+
does not waive preparation. Ready foreign picks require mixed selection and
|
|
85
|
+
global lease capacity/path reservations, or an exact qualified
|
|
86
|
+
`serial: other-workspace/SLICE-001 — <constraint>` ordered handoff. Cohort waves
|
|
87
|
+
need not open atomically. Opaque `controller_batch` identifies admission to an
|
|
88
|
+
existing lease cohort instance; reusing a batch label creates no actor entitlement.
|
|
89
|
+
It proves no actor checkout/cwd/runtime isolation and cannot retrofit a retained
|
|
90
|
+
writer. Immutable per-slice `admitted_paths` retain only each pending actor lane;
|
|
91
|
+
missing snapshots or changed reservations require actor reconciliation, not
|
|
92
|
+
backfill from mutable tasks. Returned/integrated siblings may release slots for
|
|
93
|
+
refill. Exact claim identity/session remains required even after release/expiry.
|
|
94
|
+
Readiness, dependencies, actor claims and proof windows still apply.
|
|
95
|
+
Target-specific blocking defers preparation, not ready foreign Build. Sequence
|
|
96
|
+
blocking covers historical normalization only for otherwise-valid local work;
|
|
97
|
+
malformed canonical authority and invalid AFK/path/readiness/security still fail.
|
|
98
|
+
Discharged deferrals do not repeatedly trigger controller work. Native reviews
|
|
99
|
+
constraint substance and verifies exact actor cwd/profile. Retained unbound or
|
|
100
|
+
other-cohort writers hold new actors; unsupported isolation requires local-owner
|
|
101
|
+
serial work and ordered handoff, never early same-tree foreign writing.
|
|
102
|
+
|
|
103
|
+
Controller continuation prioritizes actionable global work over direct local
|
|
104
|
+
Build without replacing the local cursor or looping on local-only ready work.
|
|
105
|
+
Committed dispatch returns emit fresh scheduling after locks release even if
|
|
106
|
+
later claim release fails; preserve that nonzero failure and claim exclusion.
|
|
107
|
+
Rejected/unpersisted returns do not refresh; status reads stay read-only.
|
|
108
|
+
Protected reviewer/proof windows constrain landing, not independent planning.
|
|
109
|
+
See [controller admission](../cli.md#autocomplete-controller-admission).
|
|
65
110
|
|
|
66
111
|
For `context --role`, optional `devrites-` prefixes and `.lane` suffixes resolve
|
|
67
112
|
the base specialist's contract, workspace read-set, and role-specific rules.
|
|
@@ -239,21 +239,39 @@ func validSession(s string) bool {
|
|
|
239
239
|
return true
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
-
// claimConflicts
|
|
243
|
-
// claimed directory covers every path beneath it, and vice versa.
|
|
244
|
-
func claimConflicts(live []*claimRecord, session string, paths []string) []*claimRecord {
|
|
242
|
+
// claimConflicts checks logical and physical overlap for other live sessions:
|
|
243
|
+
// a claimed directory covers every path beneath it, and vice versa.
|
|
244
|
+
func claimConflicts(root string, live []*claimRecord, session string, paths []string) ([]*claimRecord, error) {
|
|
245
245
|
var out []*claimRecord
|
|
246
|
+
var physical []string
|
|
246
247
|
for _, rec := range live {
|
|
247
248
|
if rec.Session == session {
|
|
248
249
|
continue
|
|
249
250
|
}
|
|
250
|
-
|
|
251
|
+
overlaps := slices.ContainsFunc(rec.Paths, func(held string) bool {
|
|
251
252
|
return slices.ContainsFunc(paths, func(p string) bool { return claimPathsOverlap(held, p) })
|
|
252
|
-
})
|
|
253
|
+
})
|
|
254
|
+
if !overlaps {
|
|
255
|
+
if physical == nil {
|
|
256
|
+
var err error
|
|
257
|
+
physical, err = physicalClaimPaths(root, paths)
|
|
258
|
+
if err != nil {
|
|
259
|
+
return nil, err
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
held, err := physicalClaimPaths(root, rec.Paths)
|
|
263
|
+
if err != nil {
|
|
264
|
+
return nil, fmt.Errorf("claim %s: %w", rec.ID, err)
|
|
265
|
+
}
|
|
266
|
+
overlaps = slices.ContainsFunc(held, func(h string) bool {
|
|
267
|
+
return slices.ContainsFunc(physical, func(p string) bool { return claimPathsOverlap(h, p) })
|
|
268
|
+
})
|
|
269
|
+
}
|
|
270
|
+
if overlaps {
|
|
253
271
|
out = append(out, rec)
|
|
254
272
|
}
|
|
255
273
|
}
|
|
256
|
-
return out
|
|
274
|
+
return out, nil
|
|
257
275
|
}
|
|
258
276
|
|
|
259
277
|
// claimPathsOverlap folds case always: on a case-sensitive filesystem a false
|
|
@@ -482,6 +500,37 @@ func addClaimTargets(root, session, reason string, ttl int, raw, workspaces []st
|
|
|
482
500
|
return id, code
|
|
483
501
|
}
|
|
484
502
|
|
|
503
|
+
// Caller holds the repository claims lock through new-wave publication.
|
|
504
|
+
func checkWriterClaimsLocked(root, session string, paths []string) error {
|
|
505
|
+
if len(paths) == 0 {
|
|
506
|
+
return nil
|
|
507
|
+
}
|
|
508
|
+
latest, order, bad, err := readClaims(claimsPath(root))
|
|
509
|
+
if err != nil {
|
|
510
|
+
return err
|
|
511
|
+
}
|
|
512
|
+
if bad > 0 {
|
|
513
|
+
return fmt.Errorf("%d unparsed ledger line(s); cannot rule out a writer claim", bad)
|
|
514
|
+
}
|
|
515
|
+
probe := newHolderProbe()
|
|
516
|
+
retained, err := retainedWriterConflicts(root, latest, paths, probe)
|
|
517
|
+
if err != nil {
|
|
518
|
+
return err
|
|
519
|
+
}
|
|
520
|
+
if len(retained) > 0 {
|
|
521
|
+
return fmt.Errorf("abandoned writer still holds claim %s on %s; confirm host termination and use dispatch abandon --release before retrying", retained[0].ID, strings.Join(retained[0].Paths, ","))
|
|
522
|
+
}
|
|
523
|
+
conflicts, err := claimConflicts(root, probe.withoutGone(liveClaims(latest, order, time.Now().UTC())), session, paths)
|
|
524
|
+
if err != nil {
|
|
525
|
+
return err
|
|
526
|
+
}
|
|
527
|
+
if len(conflicts) > 0 {
|
|
528
|
+
rec := conflicts[0]
|
|
529
|
+
return fmt.Errorf("live writer claim %s on %s held by session %q until %s", rec.ID, strings.Join(rec.Paths, ","), rec.Session, rec.ExpiresAt)
|
|
530
|
+
}
|
|
531
|
+
return nil
|
|
532
|
+
}
|
|
533
|
+
|
|
485
534
|
func claimAddLocked(path, session, reason string, ttl int, paths, admissionPaths []string, stdout, stderr io.Writer) (string, int) {
|
|
486
535
|
latest, order, bad, err := readClaims(path)
|
|
487
536
|
if err != nil {
|
|
@@ -503,7 +552,11 @@ func claimAddLocked(path, session, reason string, ttl int, paths, admissionPaths
|
|
|
503
552
|
printConflicts(stderr, retained)
|
|
504
553
|
return "", 3
|
|
505
554
|
}
|
|
506
|
-
conflicts := claimConflicts(probe.withoutGone(liveClaims(latest, order, now)), session,
|
|
555
|
+
conflicts, err := claimConflicts(filepath.Dir(path), probe.withoutGone(liveClaims(latest, order, now)), session, admissionPaths)
|
|
556
|
+
if err != nil {
|
|
557
|
+
fmt.Fprintf(stderr, "claim: %v\n", err)
|
|
558
|
+
return "", 3
|
|
559
|
+
}
|
|
507
560
|
if len(conflicts) > 0 {
|
|
508
561
|
printConflicts(stderr, conflicts)
|
|
509
562
|
return "", 3
|
|
@@ -698,7 +751,11 @@ func claimCheckLocked(root, session string, paths []string, stdout, stderr io.Wr
|
|
|
698
751
|
printConflicts(stdout, retained)
|
|
699
752
|
return 3
|
|
700
753
|
}
|
|
701
|
-
conflicts := claimConflicts(probe.withoutGone(liveClaims(latest, order, time.Now().UTC())), session, paths)
|
|
754
|
+
conflicts, err := claimConflicts(root, probe.withoutGone(liveClaims(latest, order, time.Now().UTC())), session, paths)
|
|
755
|
+
if err != nil {
|
|
756
|
+
fmt.Fprintf(stderr, "claim: %v\n", err)
|
|
757
|
+
return 2
|
|
758
|
+
}
|
|
702
759
|
if len(conflicts) > 0 {
|
|
703
760
|
printConflicts(stdout, conflicts)
|
|
704
761
|
return 3
|
|
@@ -40,6 +40,11 @@ func RunCheckContinuation(root string, args []string, stdout, stderr io.Writer)
|
|
|
40
40
|
}
|
|
41
41
|
return code
|
|
42
42
|
}
|
|
43
|
+
mode, err := autocompleteMode(root)
|
|
44
|
+
if err != nil {
|
|
45
|
+
fmt.Fprintf(stderr, "check continuation: %v\n", err)
|
|
46
|
+
return 2
|
|
47
|
+
}
|
|
43
48
|
verdict, err := classifyContinuation(root, slug)
|
|
44
49
|
if err != nil {
|
|
45
50
|
fmt.Fprintf(stderr, "check continuation: %v\n", err)
|
|
@@ -52,7 +57,7 @@ func RunCheckContinuation(root string, args []string, stdout, stderr io.Writer)
|
|
|
52
57
|
fmt.Fprintf(stderr, "check continuation: frontier: %v\n", err)
|
|
53
58
|
return 2
|
|
54
59
|
}
|
|
55
|
-
if !verdict.Required && len(frontier) > 0 && frontier[0] == "frontier: actionable" {
|
|
60
|
+
if mode && frontierGlobalPriority(slug, frontier) || !mode && !verdict.Required && len(frontier) > 0 && frontier[0] == "frontier: actionable" {
|
|
56
61
|
verdict = continuationVerdict{Required: true, Invoke: "/rite-autocomplete", Source: slug, Reason: "global frontier requires native preparation, authority review, or selection; no Ship approval"}
|
|
57
62
|
}
|
|
58
63
|
}
|
|
@@ -116,7 +116,9 @@ type dispatchWave struct {
|
|
|
116
116
|
ResliceReviewed []string `json:"reslice_reviewed,omitempty"`
|
|
117
117
|
Claim string `json:"claim,omitempty"`
|
|
118
118
|
ClaimSession string `json:"claim_session,omitempty"`
|
|
119
|
-
Tree string `json:"tree,omitempty"`
|
|
119
|
+
Tree string `json:"tree,omitempty"` // work tree at a build wright open, for check windows --since-dispatch
|
|
120
|
+
ControllerBatch string `json:"controller_batch,omitempty"` // cohort admission identity, not evidence of actor checkout
|
|
121
|
+
AdmittedPaths map[string][]string `json:"admitted_paths,omitempty"` // immutable normalized write sets, keyed by source-local slice
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
// dispatchClaim carries the wave-claim flags; ttl 0 means unset.
|
|
@@ -341,20 +343,61 @@ func RunDispatch(root string, args []string, stdout, stderr io.Writer) int {
|
|
|
341
343
|
// Serialize load-modify-save so concurrent start/return calls never lose
|
|
342
344
|
// an update and strand the wave short of complete.
|
|
343
345
|
code := 0
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
return state.
|
|
347
|
-
|
|
348
|
-
return
|
|
346
|
+
recordedReturn := false
|
|
347
|
+
mutate := func() error {
|
|
348
|
+
return state.WithFeatureLock(root, slug, func() error {
|
|
349
|
+
// Controller → feature → claims; never acquire another feature.
|
|
350
|
+
return state.WithLock(claimsPath(root)+".lock", func() error {
|
|
351
|
+
code = dispatchLocked(root, slug, sub, phase, wave, role, handle, reason, roles, sliceIDs, review, contract, packet, outcome, result, all, claim, stdout, stderr, &recordedReturn)
|
|
352
|
+
return nil
|
|
353
|
+
})
|
|
349
354
|
})
|
|
350
|
-
}
|
|
355
|
+
}
|
|
356
|
+
controllerOpen := false
|
|
357
|
+
if sub == "open" && strings.EqualFold(phase, "build") {
|
|
358
|
+
for _, candidate := range roles {
|
|
359
|
+
controllerOpen = controllerOpen || strings.Contains(candidate, "wright")
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if controllerOpen {
|
|
363
|
+
mode, err := autocompleteMode(root)
|
|
364
|
+
if err != nil {
|
|
365
|
+
fmt.Fprintf(stderr, "dispatch: BLOCKED: %v\n", err)
|
|
366
|
+
return 3
|
|
367
|
+
}
|
|
368
|
+
controllerOpen = mode
|
|
369
|
+
}
|
|
370
|
+
var lockErr error
|
|
371
|
+
if controllerOpen {
|
|
372
|
+
lockErr = parallel.WithControlLock(root, mutate)
|
|
373
|
+
} else {
|
|
374
|
+
lockErr = mutate()
|
|
375
|
+
}
|
|
376
|
+
if err := lockErr; err != nil {
|
|
351
377
|
fmt.Fprintf(stderr, "dispatch: %v\n", err)
|
|
352
378
|
return 3
|
|
353
379
|
}
|
|
380
|
+
if recordedReturn {
|
|
381
|
+
mode, err := autocompleteMode(root)
|
|
382
|
+
if err != nil {
|
|
383
|
+
fmt.Fprintf(stderr, "dispatch: return recorded; global scheduling: %v\n", err)
|
|
384
|
+
return 3
|
|
385
|
+
}
|
|
386
|
+
if mode {
|
|
387
|
+
lines, err := FrontierLines(root, slug)
|
|
388
|
+
if err != nil {
|
|
389
|
+
fmt.Fprintf(stderr, "dispatch: return recorded; global scheduling: %v\n", err)
|
|
390
|
+
return 3
|
|
391
|
+
}
|
|
392
|
+
for _, line := range lines {
|
|
393
|
+
fmt.Fprintln(stdout, line)
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
354
397
|
return code
|
|
355
398
|
}
|
|
356
399
|
|
|
357
|
-
func dispatchLocked(root, slug, sub, phase, wave, role, handle, reason string, roles, sliceIDs []string, review, contract, packet, outcome, result string, all bool, claim dispatchClaim, stdout, stderr io.Writer) int {
|
|
400
|
+
func dispatchLocked(root, slug, sub, phase, wave, role, handle, reason string, roles, sliceIDs []string, review, contract, packet, outcome, result string, all bool, claim dispatchClaim, stdout, stderr io.Writer, recordedReturn *bool) int {
|
|
358
401
|
f, path, err := loadDispatch(root, slug)
|
|
359
402
|
if err != nil {
|
|
360
403
|
fmt.Fprintf(stderr, "dispatch: %v\n", err)
|
|
@@ -392,6 +435,16 @@ func dispatchLocked(root, slug, sub, phase, wave, role, handle, reason string, r
|
|
|
392
435
|
if err := lintKnownRoles(root, phase, roles); err != nil {
|
|
393
436
|
return fail("BLOCKED: %v", err)
|
|
394
437
|
}
|
|
438
|
+
if isBuildWrightWave(w) {
|
|
439
|
+
binding, notices, err := autocompleteWriterAdmission(root, slug, w, f)
|
|
440
|
+
if err != nil {
|
|
441
|
+
return fail("BLOCKED: %v", err)
|
|
442
|
+
}
|
|
443
|
+
w.ControllerBatch = binding
|
|
444
|
+
for _, notice := range notices {
|
|
445
|
+
fmt.Fprintln(stdout, notice)
|
|
446
|
+
}
|
|
447
|
+
}
|
|
395
448
|
rounds, code := admitWrightRounds(root, slug, f, w, review, stderr)
|
|
396
449
|
if code != 0 {
|
|
397
450
|
return code
|
|
@@ -428,10 +481,16 @@ func dispatchLocked(root, slug, sub, phase, wave, role, handle, reason string, r
|
|
|
428
481
|
}
|
|
429
482
|
admissionPaths := slices.Clone(claimPaths)
|
|
430
483
|
if isWrightWave(w) {
|
|
431
|
-
|
|
484
|
+
if len(w.AdmittedPaths) > 0 {
|
|
485
|
+
for _, id := range w.Slices {
|
|
486
|
+
admissionPaths = append(admissionPaths, w.AdmittedPaths[id]...)
|
|
487
|
+
}
|
|
488
|
+
} else {
|
|
489
|
+
admissionPaths = append(admissionPaths, sliceWriteSet(root, slug, w.Slices)...)
|
|
490
|
+
}
|
|
432
491
|
}
|
|
433
492
|
if len(claimPaths) == 0 {
|
|
434
|
-
if err :=
|
|
493
|
+
if err := checkWriterClaimsLocked(root, claim.session, admissionPaths); err != nil {
|
|
435
494
|
return fail("BLOCKED: %v", err)
|
|
436
495
|
}
|
|
437
496
|
}
|
|
@@ -566,6 +625,7 @@ func dispatchLocked(root, slug, sub, phase, wave, role, handle, reason string, r
|
|
|
566
625
|
if !releaseWaveClaim(root, wave, w, stdout, stderr) {
|
|
567
626
|
return 3
|
|
568
627
|
}
|
|
628
|
+
*recordedReturn = true
|
|
569
629
|
return 0
|
|
570
630
|
}
|
|
571
631
|
if r.Returned {
|
|
@@ -604,6 +664,7 @@ func dispatchLocked(root, slug, sub, phase, wave, role, handle, reason string, r
|
|
|
604
664
|
}
|
|
605
665
|
return fail("%v", err)
|
|
606
666
|
}
|
|
667
|
+
*recordedReturn = true
|
|
607
668
|
recordMetricNote(root, slug, w.Phase, "return", role, note)
|
|
608
669
|
fmt.Fprintf(stdout, "dispatch: %s returned (wave %s %s)\n", role, wave, w.State)
|
|
609
670
|
if all {
|
|
@@ -714,28 +775,6 @@ func dispatchLocked(root, slug, sub, phase, wave, role, handle, reason string, r
|
|
|
714
775
|
return 2
|
|
715
776
|
}
|
|
716
777
|
|
|
717
|
-
// Caller holds the repository claims lock through new-wave publication.
|
|
718
|
-
func checkAbandonedWriterClaims(root string, paths []string) error {
|
|
719
|
-
if len(paths) == 0 {
|
|
720
|
-
return nil
|
|
721
|
-
}
|
|
722
|
-
latest, _, bad, err := readClaims(claimsPath(root))
|
|
723
|
-
if err != nil {
|
|
724
|
-
return err
|
|
725
|
-
}
|
|
726
|
-
if bad > 0 {
|
|
727
|
-
return fmt.Errorf("%d unparsed ledger line(s); cannot rule out an abandoned writer claim", bad)
|
|
728
|
-
}
|
|
729
|
-
conflicts, err := retainedWriterConflicts(root, latest, paths, newHolderProbe())
|
|
730
|
-
if err != nil {
|
|
731
|
-
return err
|
|
732
|
-
}
|
|
733
|
-
if len(conflicts) > 0 {
|
|
734
|
-
return fmt.Errorf("abandoned writer still holds claim %s on %s; confirm host termination and use dispatch abandon --release before retrying", conflicts[0].ID, strings.Join(conflicts[0].Paths, ","))
|
|
735
|
-
}
|
|
736
|
-
return nil
|
|
737
|
-
}
|
|
738
|
-
|
|
739
778
|
// releaseWaveClaim releases w's claim or reconciles a verified prior release.
|
|
740
779
|
// Callers surface failure without undoing the recorded return or abandonment.
|
|
741
780
|
func releaseWaveClaim(root, wave string, w *dispatchWave, stdout, stderr io.Writer) bool {
|
|
@@ -1039,6 +1078,13 @@ func wrightLane(role string) (string, bool) {
|
|
|
1039
1078
|
// errors are never refused: the check widens the batch, it never stops a
|
|
1040
1079
|
// wave that already starts every remaining pick.
|
|
1041
1080
|
func widerBatchRefusal(root, slug string, wave []string) string {
|
|
1081
|
+
mode, modeErr := autocompleteMode(root)
|
|
1082
|
+
if modeErr != nil {
|
|
1083
|
+
return fmt.Sprintf("dispatch: BLOCKED: %v", modeErr)
|
|
1084
|
+
}
|
|
1085
|
+
if mode {
|
|
1086
|
+
return ""
|
|
1087
|
+
}
|
|
1042
1088
|
capN := min(afkMaxParallel(root), parallel.MaxParallelSlices)
|
|
1043
1089
|
if capN < 2 {
|
|
1044
1090
|
return ""
|