devrites 7.3.4 → 7.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +1 -1
  3. package/docs/adr/0039-autocomplete-structural-admission.md +87 -0
  4. package/docs/adr/0040-early-preparation-admission.md +76 -0
  5. package/docs/adr/README.md +2 -0
  6. package/docs/cli.md +103 -5
  7. package/docs/engine/commands.md +88 -1
  8. package/engine/internal/lib/claims.go +65 -8
  9. package/engine/internal/lib/context.go +50 -2
  10. package/engine/internal/lib/continuation.go +8 -1
  11. package/engine/internal/lib/dispatch.go +147 -46
  12. package/engine/internal/lib/frontier.go +139 -59
  13. package/engine/internal/lib/frontier_controller.go +667 -0
  14. package/engine/internal/lib/frontier_preparation.go +210 -0
  15. package/engine/internal/lib/frontier_roadmap.go +168 -0
  16. package/engine/internal/lib/next.go +14 -2
  17. package/engine/internal/lib/triggers.go +9 -0
  18. package/engine/internal/parallel/repository.go +10 -0
  19. package/pack/.claude/skills/devrites-lib/SKILL.md +2 -0
  20. package/pack/.claude/skills/devrites-lib/reference/parallel-workspaces.md +108 -115
  21. package/pack/.claude/skills/devrites-lib/reference/standards/afk-hitl.md +18 -17
  22. package/pack/.claude/skills/devrites-lib/reference/workspace-artifact-schema.md +41 -38
  23. package/pack/.claude/skills/rite-autocomplete/SKILL.md +35 -38
  24. package/pack/.claude/skills/rite-autocomplete/reference/loop.md +66 -59
  25. package/pack/.claude/skills/rite-autocomplete/reference/stop-conditions.md +28 -28
  26. package/pack/.claude/skills/rite-build/reference/parallel-batch.md +17 -15
  27. package/pack/generated/claude/skills/devrites-lib/SKILL.md +2 -0
  28. package/pack/generated/claude/skills/devrites-lib/reference/parallel-workspaces.md +108 -115
  29. package/pack/generated/claude/skills/devrites-lib/reference/standards/afk-hitl.md +18 -17
  30. package/pack/generated/claude/skills/devrites-lib/reference/workspace-artifact-schema.md +41 -38
  31. package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +35 -38
  32. package/pack/generated/claude/skills/rite-autocomplete/reference/loop.md +66 -59
  33. package/pack/generated/claude/skills/rite-autocomplete/reference/stop-conditions.md +28 -28
  34. package/pack/generated/claude/skills/rite-build/reference/parallel-batch.md +17 -15
  35. package/pack/generated/codex/skills/devrites-lib/SKILL.md +2 -0
  36. package/pack/generated/codex/skills/devrites-lib/reference/parallel-workspaces.md +108 -115
  37. package/pack/generated/codex/skills/devrites-lib/reference/standards/afk-hitl.md +18 -17
  38. package/pack/generated/codex/skills/devrites-lib/reference/workspace-artifact-schema.md +41 -38
  39. package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +35 -38
  40. package/pack/generated/codex/skills/rite-autocomplete/reference/loop.md +66 -59
  41. package/pack/generated/codex/skills/rite-autocomplete/reference/stop-conditions.md +28 -28
  42. package/pack/generated/codex/skills/rite-build/reference/parallel-batch.md +17 -15
  43. package/pack/generated/devin/skills/devrites-lib/SKILL.md +2 -0
  44. package/pack/generated/devin/skills/devrites-lib/reference/parallel-workspaces.md +108 -115
  45. package/pack/generated/devin/skills/devrites-lib/reference/standards/afk-hitl.md +18 -17
  46. package/pack/generated/devin/skills/devrites-lib/reference/workspace-artifact-schema.md +41 -38
  47. package/pack/generated/devin/skills/rite-autocomplete/SKILL.md +35 -38
  48. package/pack/generated/devin/skills/rite-autocomplete/reference/loop.md +66 -59
  49. package/pack/generated/devin/skills/rite-autocomplete/reference/stop-conditions.md +28 -28
  50. package/pack/generated/devin/skills/rite-build/reference/parallel-batch.md +17 -15
  51. package/pack/generated/dsh/skills/devrites-lib/SKILL.md +2 -0
  52. package/pack/generated/dsh/skills/devrites-lib/reference/parallel-workspaces.md +108 -115
  53. package/pack/generated/dsh/skills/devrites-lib/reference/standards/afk-hitl.md +18 -17
  54. package/pack/generated/dsh/skills/devrites-lib/reference/workspace-artifact-schema.md +41 -38
  55. package/pack/generated/dsh/skills/rite-autocomplete/SKILL.md +35 -38
  56. package/pack/generated/dsh/skills/rite-autocomplete/reference/loop.md +66 -59
  57. package/pack/generated/dsh/skills/rite-autocomplete/reference/stop-conditions.md +28 -28
  58. package/pack/generated/dsh/skills/rite-build/reference/parallel-batch.md +17 -15
  59. package/pack/generated/omp/skills/devrites-lib/SKILL.md +2 -0
  60. package/pack/generated/omp/skills/devrites-lib/reference/parallel-workspaces.md +108 -115
  61. package/pack/generated/omp/skills/devrites-lib/reference/standards/afk-hitl.md +18 -17
  62. package/pack/generated/omp/skills/devrites-lib/reference/workspace-artifact-schema.md +41 -38
  63. package/pack/generated/omp/skills/rite-autocomplete/SKILL.md +35 -38
  64. package/pack/generated/omp/skills/rite-autocomplete/reference/loop.md +66 -59
  65. package/pack/generated/omp/skills/rite-autocomplete/reference/stop-conditions.md +28 -28
  66. package/pack/generated/omp/skills/rite-build/reference/parallel-batch.md +17 -15
  67. package/pack/generated/pi/skills/devrites-lib/SKILL.md +2 -0
  68. package/pack/generated/pi/skills/devrites-lib/reference/parallel-workspaces.md +108 -115
  69. package/pack/generated/pi/skills/devrites-lib/reference/standards/afk-hitl.md +18 -17
  70. package/pack/generated/pi/skills/devrites-lib/reference/workspace-artifact-schema.md +41 -38
  71. package/pack/generated/pi/skills/rite-autocomplete/SKILL.md +35 -38
  72. package/pack/generated/pi/skills/rite-autocomplete/reference/loop.md +66 -59
  73. package/pack/generated/pi/skills/rite-autocomplete/reference/stop-conditions.md +28 -28
  74. package/pack/generated/pi/skills/rite-build/reference/parallel-batch.md +17 -15
  75. package/package.json +1 -1
@@ -0,0 +1,667 @@
1
+ package lib
2
+
3
+ import (
4
+ "fmt"
5
+ "os"
6
+ "path/filepath"
7
+ "regexp"
8
+ "sort"
9
+ "strings"
10
+
11
+ "github.com/devrites/devrites/internal/devritespaths"
12
+ "github.com/devrites/devrites/internal/markdowntext"
13
+ "github.com/devrites/devrites/internal/parallel"
14
+ "github.com/devrites/devrites/internal/state"
15
+ )
16
+
17
+ func autocompleteMode(root string) (bool, error) {
18
+ raw, err := readBoundedRegularFile(filepath.Join(root, "AFK"), workspaceArtifactLimit)
19
+ if os.IsNotExist(err) {
20
+ return false, nil
21
+ }
22
+ if err != nil {
23
+ return false, err
24
+ }
25
+ seen := false
26
+ for _, line := range strings.Split(string(raw), "\n") {
27
+ key, value, ok := strings.Cut(strings.TrimSpace(line), ":")
28
+ if !ok || strings.TrimSpace(key) != "mode" {
29
+ continue
30
+ }
31
+ if seen {
32
+ return false, fmt.Errorf("duplicate AFK mode; normalize controller identity")
33
+ }
34
+ seen = true
35
+ value = strings.TrimSpace(value)
36
+ if i := strings.Index(value, " #"); i >= 0 {
37
+ value = strings.TrimSpace(value[:i])
38
+ }
39
+ if value != "autocomplete" {
40
+ return false, fmt.Errorf("invalid AFK mode; expected autocomplete")
41
+ }
42
+ }
43
+ return seen, nil
44
+ }
45
+
46
+ func frontierRelated(nodes map[string]*frontierWorkspace, chain []string) ([]string, map[string]bool) {
47
+ related := map[string]bool{chain[len(chain)-1]: true}
48
+ for range maxSequenceChain {
49
+ changed := false
50
+ for name, n := range nodes {
51
+ if related[n.parent] && !related[name] {
52
+ related[name], changed = true, true
53
+ }
54
+ }
55
+ if !changed {
56
+ break
57
+ }
58
+ }
59
+ ordered := make([]string, 0, len(related))
60
+ depth := make(map[string]int, len(related))
61
+ for name := range related {
62
+ ordered = append(ordered, name)
63
+ ancestors, _ := frontierChain(nodes, name)
64
+ depth[name] = len(ancestors)
65
+ }
66
+ sort.Slice(ordered, func(i, j int) bool {
67
+ a, b := nodes[ordered[i]], nodes[ordered[j]]
68
+ if depth[a.slug] != depth[b.slug] {
69
+ return depth[a.slug] < depth[b.slug]
70
+ }
71
+ if a.position != b.position {
72
+ return a.position < b.position
73
+ }
74
+ return a.slug < b.slug
75
+ })
76
+ return ordered, related
77
+ }
78
+
79
+ type frontierRecords struct {
80
+ blocked map[string]string
81
+ serial map[string]string
82
+ }
83
+
84
+ var frontierBlockedRe = regexp.MustCompile("(?m)^[ \\t]*(?:[-*][ \\t]+)?frontier-blocked:[ \\t]+([a-zA-Z0-9][a-zA-Z0-9._-]*)[ \\t]+—[ \\t]+([^\\s`][^\\n]*|`[^\\s`][^\\n]*)")
85
+
86
+ func frontierEvidence(root, slug string) (frontierRecords, error) {
87
+ records := frontierRecords{}
88
+ raw, err := readSequenceArtifact(root, slug, "evidence.md")
89
+ if os.IsNotExist(err) {
90
+ return records, nil
91
+ }
92
+ if err != nil {
93
+ return records, err
94
+ }
95
+ raw, err = markdowntext.Structural(raw)
96
+ if err != nil {
97
+ return records, err
98
+ }
99
+ text := string(raw)
100
+ for _, match := range frontierBlockedRe.FindAllStringSubmatch(text, -1) {
101
+ if devritespaths.ValidSlug(match[1]) {
102
+ if records.blocked == nil {
103
+ records.blocked = map[string]string{}
104
+ }
105
+ records.blocked[match[1]] = strings.TrimSpace(match[2])
106
+ }
107
+ }
108
+ for _, match := range serialRecordRe.FindAllStringSubmatch(text, -1) {
109
+ if !serialReasonWaives(match[2]) {
110
+ continue
111
+ }
112
+ for _, token := range strings.FieldsFunc(match[1], func(r rune) bool { return r == ',' || r == ' ' || r == '\t' || r == '`' }) {
113
+ workspace, id, qualified := strings.Cut(token, "/")
114
+ if !qualified {
115
+ id, workspace = workspace, ""
116
+ }
117
+ id = strings.ToUpper(id)
118
+ if !state.ValidSliceID(id) || qualified && !devritespaths.ValidSlug(workspace) {
119
+ continue
120
+ }
121
+ if records.serial == nil {
122
+ records.serial = map[string]string{}
123
+ }
124
+ records.serial[parallel.SliceKey(workspace, "SLICE-"+sliceNumber(id))] = strings.TrimSpace(match[2])
125
+ }
126
+ }
127
+ return records, nil
128
+ }
129
+
130
+ func frontierSerialReason(records frontierRecords, source, workspace, id string) string {
131
+ if source == workspace {
132
+ workspace = ""
133
+ }
134
+ return records.serial[parallel.SliceKey(workspace, "SLICE-"+sliceNumber(id))]
135
+ }
136
+
137
+ func frontierPendingPreparation(dispatch *dispatchFile) bool {
138
+ if dispatch == nil {
139
+ return false
140
+ }
141
+ for _, wave := range dispatch.Waves {
142
+ if wave.SequenceResearch {
143
+ continue
144
+ }
145
+ if wave.State != "open" && wave.State != "sealed" {
146
+ continue
147
+ }
148
+ for role, actor := range wave.Roles {
149
+ if actor.Handle == "" || actor.StartedAt == "" || actor.Returned {
150
+ continue
151
+ }
152
+ base := roleBase(role)
153
+ if preparationRole(strings.ToLower(wave.Phase), base) {
154
+ return true
155
+ }
156
+ }
157
+ }
158
+ return false
159
+ }
160
+
161
+ func frontierSuccessor(n *frontierWorkspace, nodes map[string]*frontierWorkspace, mapping map[int]string) (string, error) {
162
+ target := frontierSpecTarget(n)
163
+ if mapped := mapping[n.position+1]; n.hasPosition && mapped != "" {
164
+ if target != "" && mapped != target {
165
+ return "", fmt.Errorf("next Spec conflicts with recorded successor; native authority-normalization required")
166
+ }
167
+ target = mapped
168
+ }
169
+ for _, child := range nodes {
170
+ if child.parent != n.slug || !n.hasPosition || !child.hasPosition || child.position != n.position+1 {
171
+ continue
172
+ }
173
+ if target != "" && target != child.slug {
174
+ return "", fmt.Errorf("conflicting successor identities; native authority-normalization required")
175
+ }
176
+ target = child.slug
177
+ }
178
+ if child := nodes[target]; child != nil && child.parent != n.slug {
179
+ return "", fmt.Errorf("successor %s is not bound to source %s; native authority-normalization required", target, n.slug)
180
+ }
181
+ return target, nil
182
+ }
183
+
184
+ func frontierPreparationAdmission(root, source string, nodes map[string]*frontierWorkspace, mapping map[int]string, problem string, allow bool, records frontierRecords) ([]string, error) {
185
+ n := nodes[source]
186
+ if n == nil {
187
+ return nil, fmt.Errorf("source %s is not an existing sequence workspace", source)
188
+ }
189
+ if hold, err := frontierQuestionHold(n); err != nil || hold != "" {
190
+ return nil, err
191
+ }
192
+ target, err := frontierSuccessor(n, nodes, mapping)
193
+ for _, identity := range [...]string{frontierSpecTarget(n), mapping[n.position+1], target} {
194
+ if child := nodes[identity]; child != nil && child.problem != nil {
195
+ return nil, child.problem
196
+ }
197
+ }
198
+ if problem != "" || err != nil || target == "" && n.hasRemaining && n.remaining > 0 {
199
+ if reason := records.blocked["sequence"]; reason != "" {
200
+ return []string{"frontier-blocked: sequence — " + reason + "; scheduling deferred, not normalized"}, nil
201
+ }
202
+ reason := problem
203
+ if err != nil {
204
+ reason = err.Error()
205
+ }
206
+ if reason == "" {
207
+ reason = "positive remaining budget has no exact recorded successor"
208
+ }
209
+ return nil, fmt.Errorf("source %s native authority-normalization required before forward-phase admission: %s; read sequence artifacts, record current roadmap through its owning decision, or retain frontier-blocked: sequence — <specific constraint>", source, reason)
210
+ }
211
+ if target == "" || nodes[target] == nil && (!allow || n.hasRemaining && n.remaining == 0) {
212
+ return nil, nil
213
+ }
214
+ var successorDispatch *dispatchFile
215
+ if child := nodes[target]; child != nil {
216
+ if _, err := frontierChain(nodes, target); err != nil {
217
+ return nil, err
218
+ }
219
+ successorDispatch, err = frontierPreparationBindings(child, nil)
220
+ if err != nil {
221
+ return nil, err
222
+ }
223
+ }
224
+ if reason := records.blocked[target]; reason != "" {
225
+ return []string{"frontier-blocked: " + target + " — " + reason + "; preparation deferred, not approved"}, nil
226
+ }
227
+ if child := nodes[target]; child != nil {
228
+ if hold, err := frontierQuestionHold(child); err != nil || hold != "" {
229
+ return nil, err
230
+ }
231
+ if frontierTerminal(child) {
232
+ return nil, nil
233
+ }
234
+ if frontierPendingPreparation(successorDispatch) {
235
+ return nil, nil
236
+ }
237
+ ready, notices, err := WorkspaceReady(filepath.Dir(root), source, target)
238
+ if err != nil {
239
+ return nil, err
240
+ }
241
+ if len(ready) > 0 || len(notices) == 0 {
242
+ var diagnostics []string
243
+ for _, notice := range notices {
244
+ diagnostics = append(diagnostics, "frontier-readiness: "+notice)
245
+ }
246
+ return diagnostics, nil
247
+ }
248
+ dependenciesOnly := true
249
+ for _, notice := range notices {
250
+ dependenciesOnly = dependenciesOnly && strings.Contains(notice, ": waiting for durable BUILT/integrated producer ")
251
+ }
252
+ if dependenciesOnly {
253
+ return nil, nil
254
+ }
255
+ }
256
+ return nil, fmt.Errorf("successor %s preparation has not actually started; invoke /rite-spec %s or its native Plan/Vet cursor and retain launched planning/review handles before continuing forward phases of %s; alternatively retain frontier-blocked: %s — <specific constraint> in evidence.md", target, frontierQuote(target), source, target)
257
+ }
258
+
259
+ func frontierNormalizePaths(paths []string) error {
260
+ for i, path := range paths {
261
+ normalized, err := parallel.NormalizePath(path)
262
+ if err != nil {
263
+ return err
264
+ }
265
+ paths[i] = normalized
266
+ }
267
+ return nil
268
+ }
269
+
270
+ func frontierSamePaths(admitted, reserved []string) bool {
271
+ if len(admitted) == 0 || len(admitted) != len(reserved) {
272
+ return false
273
+ }
274
+ for _, reservedPath := range reserved {
275
+ found := false
276
+ for _, path := range admitted {
277
+ found = found || strings.EqualFold(path, reservedPath)
278
+ }
279
+ if !found {
280
+ return false
281
+ }
282
+ }
283
+ return true
284
+ }
285
+
286
+ func frontierLeaseReserved(lease *parallel.Lease, controller, source string, ids []string, ready []parallel.SlicePaths) bool {
287
+ if lease == nil || lease.Status != parallel.StatusRunning && lease.Status != parallel.StatusIntegrateFailed || lease.PendingLanding != nil {
288
+ return false
289
+ }
290
+ for _, id := range ids {
291
+ found := false
292
+ for _, row := range lease.Slices {
293
+ workspace := row.Workspace
294
+ if workspace == "" {
295
+ workspace = controller
296
+ }
297
+ if workspace != source || sliceNumber(row.ID) != sliceNumber(id) || row.WrightStatus == parallel.WrightIntegrated {
298
+ continue
299
+ }
300
+ for _, candidate := range ready {
301
+ if sliceNumber(candidate.ID) == sliceNumber(id) && frontierSamePaths(candidate.Paths, row.Paths) {
302
+ found = true
303
+ }
304
+ }
305
+ }
306
+ if !found {
307
+ return false
308
+ }
309
+ }
310
+ return len(ids) > 0
311
+ }
312
+
313
+ func frontierWriterRetention(root, source, controller, binding string, lease *parallel.Lease, nodes map[string]*frontierWorkspace, ordered, ids []string, current *dispatchFile, checkPreparation bool) error {
314
+ var claims map[string]*claimRecord
315
+ for _, slug := range ordered {
316
+ if _, err := frontierChain(nodes, slug); err != nil {
317
+ return err
318
+ }
319
+ dispatch := current
320
+ if slug != source {
321
+ raw, err := readBoundedRegularFile(filepath.Join(nodes[slug].dir, "dispatch.json"), workspaceArtifactLimit)
322
+ if os.IsNotExist(err) {
323
+ continue
324
+ }
325
+ if err != nil {
326
+ return err
327
+ }
328
+ dispatch, err = decodeDispatch(raw)
329
+ if err != nil {
330
+ return err
331
+ }
332
+ if checkPreparation {
333
+ if _, err := frontierPreparationBindings(nodes[slug], dispatch); err != nil {
334
+ return err
335
+ }
336
+ }
337
+ }
338
+ for name, wave := range dispatch.Waves {
339
+ if wave.State == "complete" || wave.TerminalAt != "" {
340
+ continue
341
+ }
342
+ writerChecked := false
343
+ for role, actor := range wave.Roles {
344
+ wright := strings.Contains(role, "wright")
345
+ if actor.Returned || actor.Handle == "" && (!wright || wave.State == "abandoned") {
346
+ continue
347
+ }
348
+ if !wright {
349
+ if slug == source && (isReviewerRole(role) || roleBase(role) == "proof-runner") {
350
+ return fmt.Errorf("source %s retains a protected reviewer/proof account; reconcile it before writer admission", source)
351
+ }
352
+ continue
353
+ }
354
+ if !writerChecked {
355
+ if binding == "" || wave.ControllerBatch != binding {
356
+ return fmt.Errorf("retained writer %s/%s has unknown/same-tree isolation; reconcile its native handle before another writer; later lease IDs cannot retrofit actor checkout", slug, name)
357
+ }
358
+ if wave.Claim != "" {
359
+ if claims == nil {
360
+ latest, _, bad, err := readClaims(claimsPath(root))
361
+ if err != nil {
362
+ return err
363
+ }
364
+ if bad > 0 {
365
+ return fmt.Errorf("%d unparsed ledger line(s); retained writer claim identity cannot be validated", bad)
366
+ }
367
+ claims = latest
368
+ }
369
+ claim := claims[wave.Claim]
370
+ if claim == nil || claim.Session != wave.ClaimSession {
371
+ return fmt.Errorf("retained writer %s/%s claim %s has missing or mismatched ledger session", slug, name, wave.Claim)
372
+ }
373
+ // Release/expiry changes allocation, never the pending actor's frozen authority.
374
+ } else if wave.ClaimSession != "" {
375
+ return fmt.Errorf("retained writer %s/%s has a claim session without its exact claim identity", slug, name)
376
+ }
377
+ writerChecked = true
378
+ }
379
+ retainedID := ""
380
+ if lane, ok := wrightLane(role); ok {
381
+ for _, id := range wave.Slices {
382
+ if sliceNumber(id) == sliceNumber(lane) {
383
+ retainedID = id
384
+ break
385
+ }
386
+ }
387
+ } else if len(wave.Slices) == 1 {
388
+ retainedID = wave.Slices[0]
389
+ }
390
+ admitted := wave.AdmittedPaths[retainedID]
391
+ if retainedID == "" || len(admitted) == 0 {
392
+ return fmt.Errorf("retained writer %s/%s role %s lacks immutable admitted paths; reconcile the native actor, not mutable tasks or lease IDs", slug, name, role)
393
+ }
394
+ for _, path := range admitted {
395
+ normalized, err := parallel.NormalizePath(path)
396
+ if err != nil || normalized != path {
397
+ return fmt.Errorf("retained writer %s/%s has invalid admitted paths for %s", slug, name, retainedID)
398
+ }
399
+ }
400
+ counted, matchingPaths := false, false
401
+ for _, row := range lease.Slices {
402
+ workspace := row.Workspace
403
+ if workspace == "" {
404
+ workspace = controller
405
+ }
406
+ if workspace == slug && sliceNumber(row.ID) == sliceNumber(retainedID) && row.WrightStatus != parallel.WrightIntegrated {
407
+ counted = true
408
+ matchingPaths = frontierSamePaths(admitted, row.Paths)
409
+ break
410
+ }
411
+ }
412
+ if !counted {
413
+ return fmt.Errorf("retained writer %s/%s lost its active lease reservation for %s; reconcile the native actor before admitting more work", slug, name, retainedID)
414
+ }
415
+ if !matchingPaths {
416
+ return fmt.Errorf("retained writer %s/%s admitted paths for %s no longer match its active lease reservation; mutable tasks cannot move actor authority", slug, name, retainedID)
417
+ }
418
+ if slug == source && containsSlice(ids, retainedID) {
419
+ return fmt.Errorf("retained writer %s/%s still owns %s", slug, name, retainedID)
420
+ }
421
+ }
422
+ }
423
+ }
424
+ return nil
425
+ }
426
+
427
+ // The binding records admission into an existing cohort, never an actor's cwd/profile.
428
+ func autocompleteWriterAdmission(root, source string, wave *dispatchWave, current *dispatchFile) (string, []string, error) {
429
+ mode, err := autocompleteMode(root)
430
+ if err != nil || !mode {
431
+ return "", nil, err
432
+ }
433
+ allow, capN, err := frontierPolicy(root)
434
+ if err != nil {
435
+ return "", nil, err
436
+ }
437
+ controller, lease, err := parallel.ControllerLease(filepath.Dir(root))
438
+ if err != nil {
439
+ return "", nil, err
440
+ }
441
+ if lease != nil {
442
+ effectiveCap, occupied := capN, 0
443
+ if lease.Capacity > 0 && lease.Capacity < effectiveCap {
444
+ effectiveCap = lease.Capacity
445
+ }
446
+ if lease.Status == parallel.StatusRunning || lease.Status == parallel.StatusIntegrateFailed {
447
+ for _, row := range lease.Slices {
448
+ if row.WrightStatus != parallel.WrightIntegrated {
449
+ occupied++
450
+ }
451
+ }
452
+ }
453
+ if occupied > effectiveCap {
454
+ return "", nil, fmt.Errorf("retained cohort has %d occupied reservations above effective cap %d; reconcile capacity and drain existing work before any new writer launch; do not cancel workers", occupied, effectiveCap)
455
+ }
456
+ for i := range lease.Slices {
457
+ if lease.Slices[i].WrightStatus != parallel.WrightIntegrated {
458
+ if err := frontierNormalizePaths(lease.Slices[i].Paths); err != nil {
459
+ return "", nil, err
460
+ }
461
+ }
462
+ }
463
+ }
464
+ if capN <= 1 && lease == nil {
465
+ return "", nil, nil
466
+ }
467
+ ids := wave.Slices
468
+ nodes, err := frontierScan(root)
469
+ if err != nil {
470
+ return "", nil, err
471
+ }
472
+ checkPreparation := capN > 1 && (lease == nil || lease.Capacity != 1)
473
+ if checkPreparation {
474
+ if _, err := frontierPreparationBindings(nodes[source], current); err != nil {
475
+ return "", nil, err
476
+ }
477
+ }
478
+ chain, err := frontierChain(nodes, source)
479
+ if err != nil {
480
+ return "", nil, err
481
+ }
482
+ ordered, related := frontierRelated(nodes, chain)
483
+ mapping, _, problem, invalidCanonical := frontierRoadmap(nodes, ordered)
484
+ if invalidCanonical {
485
+ return "", nil, fmt.Errorf("invalid canonical roadmap: %s", problem)
486
+ }
487
+ if controller == "" {
488
+ for _, slug := range ordered {
489
+ if nodes[slug].location == "live" {
490
+ controller = slug
491
+ break
492
+ }
493
+ }
494
+ } else if !related[controller] {
495
+ return "", nil, fmt.Errorf("retained repository controller %s is outside source sequence", controller)
496
+ }
497
+ records, err := frontierEvidence(root, source)
498
+ if err != nil {
499
+ return "", nil, err
500
+ }
501
+ var notices []string
502
+ if checkPreparation {
503
+ notices, err = frontierPreparationAdmission(root, source, nodes, mapping, problem, allow, records)
504
+ if err != nil {
505
+ return "", nil, err
506
+ }
507
+ }
508
+ local, withheld, err := WorkspaceReady(filepath.Dir(root), controller, source)
509
+ if err != nil {
510
+ return "", nil, err
511
+ }
512
+ if len(local) == 0 && len(withheld) > 0 {
513
+ return "", nil, fmt.Errorf("source readiness: %s", strings.Join(withheld, "; "))
514
+ }
515
+ for _, notice := range withheld {
516
+ notices = append(notices, "frontier-readiness: "+notice)
517
+ }
518
+ for _, id := range ids {
519
+ eligible := false
520
+ for _, candidate := range local {
521
+ eligible = eligible || sliceNumber(candidate.ID) == sliceNumber(id)
522
+ }
523
+ if !eligible {
524
+ return "", nil, fmt.Errorf("source readiness: %s/%s is not eligible; %s", source, id, strings.Join(withheld, "; "))
525
+ }
526
+ }
527
+ for i := range local {
528
+ if err := frontierNormalizePaths(local[i].Paths); err != nil {
529
+ return "", nil, err
530
+ }
531
+ }
532
+ binding := ""
533
+ reserved := frontierLeaseReserved(lease, controller, source, ids, local)
534
+ if reserved {
535
+ binding = controller + "/" + lease.BatchID + "@" + lease.CreatedAt
536
+ }
537
+ if err := frontierWriterRetention(root, source, controller, binding, lease, nodes, ordered, ids, current, checkPreparation); err != nil {
538
+ return "", nil, err
539
+ }
540
+ var candidates []parallel.SlicePaths
541
+ for _, slug := range ordered {
542
+ if slug == source {
543
+ candidates = append(candidates, local...)
544
+ continue
545
+ }
546
+ if nodes[slug].location != "live" {
547
+ continue
548
+ }
549
+ n := nodes[slug]
550
+ if hold, err := frontierQuestionHold(n); err != nil {
551
+ return "", nil, err
552
+ } else if hold != "" {
553
+ continue
554
+ }
555
+ if _, _, err := frontierLocal(root, controller, n, &candidates, nil); err != nil {
556
+ return "", nil, err
557
+ }
558
+ }
559
+ filtered := candidates[:0]
560
+ for _, candidate := range candidates {
561
+ workspace := candidate.Workspace
562
+ if workspace == "" {
563
+ workspace = controller
564
+ }
565
+ if workspace != source {
566
+ if reason := frontierSerialReason(records, source, workspace, candidate.ID); reason != "" {
567
+ notices = append(notices, "frontier-serial: "+workspace+"/"+candidate.ID+" — "+reason+"; ordered handoff retained")
568
+ continue
569
+ }
570
+ }
571
+ filtered = append(filtered, candidate)
572
+ }
573
+ _, picks, _, err := parallel.SelectForSlug(filepath.Dir(root), controller, capN, filtered)
574
+ if err != nil {
575
+ return "", nil, err
576
+ }
577
+ for _, id := range ids {
578
+ selected := reserved
579
+ for _, pick := range picks {
580
+ workspace := pick.Workspace
581
+ if workspace == "" {
582
+ workspace = controller
583
+ }
584
+ selected = selected || workspace == source && sliceNumber(pick.ID) == sliceNumber(id)
585
+ }
586
+ if !selected || source != controller && !reserved {
587
+ return "", nil, fmt.Errorf("%s/%s is not admitted by global lease capacity/path/readiness selection; run frontier and reserve the mixed cohort with parallel create/add before foreign dispatch", source, id)
588
+ }
589
+ }
590
+ var missing []string
591
+ for _, pick := range picks {
592
+ workspace := pick.Workspace
593
+ if workspace == "" {
594
+ workspace = controller
595
+ }
596
+ if workspace == source && containsSlice(ids, pick.ID) {
597
+ continue
598
+ }
599
+ if frontierSerialReason(records, source, workspace, pick.ID) != "" {
600
+ continue
601
+ }
602
+ missing = append(missing, parallel.SliceKey(pick.Workspace, pick.ID))
603
+ }
604
+ if len(missing) != 0 {
605
+ return "", nil, fmt.Errorf("global picks %s omitted; run devrites-engine parallel select --root %s --slug %s --from-plan --workspaces %s --cap %d and reserve picks with parallel create/add, or record exact serial: <source/SLICE-ID> — <constraint>", strings.Join(missing, ","), frontierQuote(filepath.Dir(root)), frontierQuote(controller), frontierQuote(strings.Join(ordered, ",")), capN)
606
+ }
607
+ wave.AdmittedPaths = make(map[string][]string, len(ids))
608
+ for _, id := range ids {
609
+ for _, candidate := range local {
610
+ if sliceNumber(candidate.ID) == sliceNumber(id) {
611
+ wave.AdmittedPaths[id] = candidate.Paths
612
+ break
613
+ }
614
+ }
615
+ }
616
+ return binding, notices, nil
617
+ }
618
+
619
+ const frontierInheritedPreparationWait = "preparatory descendant; continue immediate-parent preparation before this native cursor"
620
+
621
+ func frontierSourcePreparationWait(source string, lines []string) bool {
622
+ wait := "frontier-workspace: " + source + " location=live action=wait reason=" + frontierInheritedPreparationWait
623
+ for _, line := range lines {
624
+ if line == wait {
625
+ return true
626
+ }
627
+ }
628
+ return false
629
+ }
630
+
631
+ func frontierGlobalPriority(source string, lines []string) bool {
632
+ controller := source
633
+ for _, line := range lines {
634
+ if strings.HasPrefix(line, "frontier-select-command:") {
635
+ fields := strings.Fields(line)
636
+ for i, field := range fields {
637
+ if field == "--slug" && i+1 < len(fields) {
638
+ controller = strings.Trim(fields[i+1], "'")
639
+ }
640
+ }
641
+ }
642
+ if strings.HasPrefix(line, "frontier-prepare:") || strings.HasPrefix(line, "frontier-review-sequence:") {
643
+ return true
644
+ }
645
+ if strings.HasPrefix(line, "frontier-workspace:") {
646
+ fields := strings.Fields(line)
647
+ if len(fields) > 3 && fields[1] != source && (fields[3] == "action=repair" || fields[3] == "action=inspect" || fields[3] == "action=prepare") {
648
+ return true
649
+ }
650
+ }
651
+ }
652
+ for _, line := range lines {
653
+ if !strings.HasPrefix(line, "frontier-picks: ") {
654
+ continue
655
+ }
656
+ for _, key := range strings.Split(strings.TrimPrefix(line, "frontier-picks: "), ",") {
657
+ workspace, _, qualified := strings.Cut(key, "/")
658
+ if !qualified {
659
+ workspace = controller
660
+ }
661
+ if workspace != source {
662
+ return true
663
+ }
664
+ }
665
+ }
666
+ return false
667
+ }