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.
- package/CHANGELOG.md +34 -0
- package/README.md +1 -1
- package/docs/adr/0039-autocomplete-structural-admission.md +87 -0
- package/docs/adr/0040-early-preparation-admission.md +76 -0
- package/docs/adr/README.md +2 -0
- package/docs/cli.md +103 -5
- package/docs/engine/commands.md +88 -1
- package/engine/internal/lib/claims.go +65 -8
- package/engine/internal/lib/context.go +50 -2
- package/engine/internal/lib/continuation.go +8 -1
- package/engine/internal/lib/dispatch.go +147 -46
- package/engine/internal/lib/frontier.go +139 -59
- package/engine/internal/lib/frontier_controller.go +667 -0
- package/engine/internal/lib/frontier_preparation.go +210 -0
- package/engine/internal/lib/frontier_roadmap.go +168 -0
- package/engine/internal/lib/next.go +14 -2
- package/engine/internal/lib/triggers.go +9 -0
- package/engine/internal/parallel/repository.go +10 -0
- package/pack/.claude/skills/devrites-lib/SKILL.md +2 -0
- package/pack/.claude/skills/devrites-lib/reference/parallel-workspaces.md +108 -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 +35 -38
- package/pack/.claude/skills/rite-autocomplete/reference/loop.md +66 -59
- 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/SKILL.md +2 -0
- package/pack/generated/claude/skills/devrites-lib/reference/parallel-workspaces.md +108 -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 +35 -38
- package/pack/generated/claude/skills/rite-autocomplete/reference/loop.md +66 -59
- 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/SKILL.md +2 -0
- package/pack/generated/codex/skills/devrites-lib/reference/parallel-workspaces.md +108 -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 +35 -38
- package/pack/generated/codex/skills/rite-autocomplete/reference/loop.md +66 -59
- 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/SKILL.md +2 -0
- package/pack/generated/devin/skills/devrites-lib/reference/parallel-workspaces.md +108 -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 +35 -38
- package/pack/generated/devin/skills/rite-autocomplete/reference/loop.md +66 -59
- 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/SKILL.md +2 -0
- package/pack/generated/dsh/skills/devrites-lib/reference/parallel-workspaces.md +108 -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 +35 -38
- package/pack/generated/dsh/skills/rite-autocomplete/reference/loop.md +66 -59
- 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/SKILL.md +2 -0
- package/pack/generated/omp/skills/devrites-lib/reference/parallel-workspaces.md +108 -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 +35 -38
- package/pack/generated/omp/skills/rite-autocomplete/reference/loop.md +66 -59
- 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/SKILL.md +2 -0
- package/pack/generated/pi/skills/devrites-lib/reference/parallel-workspaces.md +108 -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 +35 -38
- package/pack/generated/pi/skills/rite-autocomplete/reference/loop.md +66 -59
- 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
|
@@ -5,7 +5,6 @@ import (
|
|
|
5
5
|
"io"
|
|
6
6
|
"os"
|
|
7
7
|
"path/filepath"
|
|
8
|
-
"sort"
|
|
9
8
|
"strconv"
|
|
10
9
|
"strings"
|
|
11
10
|
|
|
@@ -241,6 +240,10 @@ func frontierQuote(s string) string { return "'" + strings.ReplaceAll(s, "'", "'
|
|
|
241
240
|
|
|
242
241
|
// FrontierLines reports structural obligations, never semantic authorization or Ship approval.
|
|
243
242
|
func FrontierLines(root, slug string) ([]string, error) {
|
|
243
|
+
mode, err := autocompleteMode(root)
|
|
244
|
+
if err != nil {
|
|
245
|
+
return nil, err
|
|
246
|
+
}
|
|
244
247
|
if slug == "" {
|
|
245
248
|
var err error
|
|
246
249
|
slug, err = devritespaths.ActiveSlug(root)
|
|
@@ -248,11 +251,13 @@ func FrontierLines(root, slug string) ([]string, error) {
|
|
|
248
251
|
return nil, err
|
|
249
252
|
}
|
|
250
253
|
}
|
|
254
|
+
workspaceOverride := ""
|
|
251
255
|
if slug != "" {
|
|
252
256
|
if !devritespaths.ValidSlug(slug) {
|
|
253
257
|
return nil, fmt.Errorf("invalid source slug")
|
|
254
258
|
}
|
|
255
|
-
|
|
259
|
+
workspaceOverride, err = devritespaths.WorkspaceOverrideChecked(root, slug)
|
|
260
|
+
if err != nil {
|
|
256
261
|
return nil, err
|
|
257
262
|
}
|
|
258
263
|
}
|
|
@@ -288,44 +293,21 @@ func FrontierLines(root, slug string) ([]string, error) {
|
|
|
288
293
|
}
|
|
289
294
|
chain, chainErr := frontierChain(nodes, slug)
|
|
290
295
|
if chainErr != nil {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
if !changed {
|
|
304
|
-
break
|
|
305
|
-
}
|
|
306
|
-
}
|
|
296
|
+
location := "work"
|
|
297
|
+
if source.location == "archive" {
|
|
298
|
+
location = "archive"
|
|
299
|
+
}
|
|
300
|
+
artifact := filepath.Join(root, location, slug, "state.md")
|
|
301
|
+
return []string{
|
|
302
|
+
"frontier: blocked", "frontier-source: " + slug,
|
|
303
|
+
fmt.Sprintf("frontier-workspace: %s location=%s action=repair reason=%s", slug, source.location, chainErr),
|
|
304
|
+
"frontier-review-sequence: reason=native authority-normalization required: " + chainErr.Error() + " artifacts=" + artifact,
|
|
305
|
+
}, nil
|
|
306
|
+
}
|
|
307
|
+
ordered, related := frontierRelated(nodes, chain)
|
|
307
308
|
if len(related) == 1 && source.parent == "" && !source.hasPosition && !source.hasRemaining && frontierSpecTarget(source) == "" {
|
|
308
309
|
return []string{"frontier: none"}, nil
|
|
309
310
|
}
|
|
310
|
-
ordered := make([]string, 0, len(related))
|
|
311
|
-
for name := range related {
|
|
312
|
-
ordered = append(ordered, name)
|
|
313
|
-
}
|
|
314
|
-
depth := make(map[string]int, len(ordered))
|
|
315
|
-
for _, name := range ordered {
|
|
316
|
-
chain, _ := frontierChain(nodes, name)
|
|
317
|
-
depth[name] = len(chain)
|
|
318
|
-
}
|
|
319
|
-
sort.Slice(ordered, func(i, j int) bool {
|
|
320
|
-
a, b := nodes[ordered[i]], nodes[ordered[j]]
|
|
321
|
-
if depth[a.slug] != depth[b.slug] {
|
|
322
|
-
return depth[a.slug] < depth[b.slug]
|
|
323
|
-
}
|
|
324
|
-
if a.position != b.position {
|
|
325
|
-
return a.position < b.position
|
|
326
|
-
}
|
|
327
|
-
return a.slug < b.slug
|
|
328
|
-
})
|
|
329
311
|
controller := ""
|
|
330
312
|
for _, name := range ordered {
|
|
331
313
|
if nodes[name].location == "live" {
|
|
@@ -350,6 +332,9 @@ func FrontierLines(root, slug string) ([]string, error) {
|
|
|
350
332
|
if err != nil {
|
|
351
333
|
return nil, err
|
|
352
334
|
}
|
|
335
|
+
if workspaceOverride != "" {
|
|
336
|
+
allowFuture = false
|
|
337
|
+
}
|
|
353
338
|
if lease != nil && lease.Capacity > 0 && capN > lease.Capacity {
|
|
354
339
|
capN = lease.Capacity
|
|
355
340
|
}
|
|
@@ -364,12 +349,43 @@ func FrontierLines(root, slug string) ([]string, error) {
|
|
|
364
349
|
if source.hasRemaining && source.remaining == 0 {
|
|
365
350
|
allowFuture = false
|
|
366
351
|
}
|
|
367
|
-
maps, artifacts, mapProblem := frontierRoadmap(nodes, ordered)
|
|
352
|
+
maps, artifacts, mapProblem, invalidCanonical := frontierRoadmap(nodes, ordered)
|
|
353
|
+
if invalidCanonical {
|
|
354
|
+
return []string{"frontier: blocked", "frontier-source: " + slug, "frontier-review-sequence: reason=native authority-normalization required: invalid canonical authority: " + mapProblem + " artifacts=" + strings.Join(artifacts, ",")}, nil
|
|
355
|
+
}
|
|
356
|
+
if len(maps) > 0 && mapProblem == "" {
|
|
357
|
+
lines = append(lines, "frontier-authority: "+strings.Join(artifacts, ","))
|
|
358
|
+
}
|
|
359
|
+
records := frontierRecords{}
|
|
360
|
+
if mode {
|
|
361
|
+
records, err = frontierEvidence(root, slug)
|
|
362
|
+
if err != nil {
|
|
363
|
+
return nil, err
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
sequenceDeferred := mode && records.blocked["sequence"] != ""
|
|
368
367
|
if mapProblem != "" {
|
|
369
|
-
|
|
370
|
-
|
|
368
|
+
if sequenceDeferred {
|
|
369
|
+
lines = append(lines, "frontier-blocked: sequence — "+records.blocked["sequence"]+"; scheduling deferred, not normalized")
|
|
370
|
+
} else {
|
|
371
|
+
lines = append(lines, "frontier-review-sequence: reason=native authority-normalization required: "+mapProblem+" artifacts="+strings.Join(artifacts, ","))
|
|
372
|
+
blocked = true
|
|
373
|
+
}
|
|
371
374
|
}
|
|
375
|
+
deferred := map[string]string{}
|
|
372
376
|
prepared := map[string]bool{}
|
|
377
|
+
var preparatory map[string]bool
|
|
378
|
+
var preparationAccounts map[string]*dispatchFile
|
|
379
|
+
if mode && capN > 1 {
|
|
380
|
+
preparatory = make(map[string]bool, len(ordered))
|
|
381
|
+
preparationAccounts = make(map[string]*dispatchFile, len(ordered))
|
|
382
|
+
for _, name := range ordered {
|
|
383
|
+
preparatory[name], preparationAccounts[name], err = frontierPreparatoryOnly(root, nodes[name], nodes, maps, mapProblem)
|
|
384
|
+
if err != nil {
|
|
385
|
+
return nil, err
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
373
389
|
for _, name := range ordered {
|
|
374
390
|
n := nodes[name]
|
|
375
391
|
action, reason := "complete", "archived predecessor; no writer admission"
|
|
@@ -382,25 +398,38 @@ func FrontierLines(root, slug string) ([]string, error) {
|
|
|
382
398
|
} else if gateReason != "" {
|
|
383
399
|
action, reason, held, blocked = "wait", gateReason, true, true
|
|
384
400
|
} else if n.location == "live" {
|
|
385
|
-
action, reason, err = frontierLocal(root, controller, n, &candidates)
|
|
401
|
+
action, reason, err = frontierLocal(root, controller, n, &candidates, preparationAccounts[name])
|
|
386
402
|
if err != nil {
|
|
387
403
|
action, reason, n.selectionHeld = "repair", err.Error(), true
|
|
388
404
|
}
|
|
405
|
+
if mode && action == "prepare" && deferred[name] != "" {
|
|
406
|
+
action, reason, n.selectionHeld = "wait", "frontier-blocked: "+name+" — "+deferred[name], true
|
|
407
|
+
}
|
|
408
|
+
inheritedPreparation := preparatory[name] && preparatory[n.parent] && (n.phase == "frame" || preparationPhase(n.phase)) && action == "prepare"
|
|
409
|
+
if inheritedPreparation {
|
|
410
|
+
action, reason, n.selectionHeld = "wait", frontierInheritedPreparationWait, true
|
|
411
|
+
}
|
|
389
412
|
if !n.selectionHeld {
|
|
390
413
|
live = append(live, name)
|
|
391
414
|
}
|
|
392
|
-
held = action == "blocked" || err != nil
|
|
415
|
+
held = inheritedPreparation || action == "blocked" || err != nil || mode && action == "wait" && deferred[name] != ""
|
|
393
416
|
switch action {
|
|
394
417
|
case "inspect", "prepare", "repair":
|
|
395
418
|
actionable = true
|
|
396
419
|
case "wait":
|
|
397
420
|
waiting = true
|
|
421
|
+
case "no-cursor":
|
|
422
|
+
blocked = true
|
|
423
|
+
action = "wait"
|
|
398
424
|
case "blocked":
|
|
399
425
|
blocked = true
|
|
400
426
|
action = "wait"
|
|
401
427
|
}
|
|
402
428
|
}
|
|
403
429
|
lines = append(lines, fmt.Sprintf("frontier-workspace: %s location=%s action=%s reason=%s", name, n.location, action, reason))
|
|
430
|
+
if preparatory[name] {
|
|
431
|
+
continue
|
|
432
|
+
}
|
|
404
433
|
if invalid != nil || held || !allowFuture || n.hasRemaining && n.remaining == 0 || n.status == "awaiting_human" || n.status == "blocked" || n.question != "" {
|
|
405
434
|
continue
|
|
406
435
|
}
|
|
@@ -409,18 +438,18 @@ func FrontierLines(root, slug string) ([]string, error) {
|
|
|
409
438
|
target = maps[n.position+1]
|
|
410
439
|
}
|
|
411
440
|
if mapProblem != "" {
|
|
412
|
-
if target != "" || n.hasRemaining && n.remaining > 0 {
|
|
441
|
+
if !sequenceDeferred && (target != "" || n.hasRemaining && n.remaining > 0) {
|
|
413
442
|
actionable = true
|
|
414
443
|
}
|
|
415
444
|
continue
|
|
416
445
|
}
|
|
417
446
|
if target != "" && frontierConflictingSuccessor(nodes, n, target) {
|
|
418
|
-
lines = append(lines, "frontier-review-sequence:
|
|
447
|
+
lines = append(lines, "frontier-review-sequence: reason=conflicting successor identities; native authority-normalization required source="+name+" artifacts="+strings.Join(artifacts, ","))
|
|
419
448
|
actionable, blocked = true, true
|
|
420
449
|
continue
|
|
421
450
|
}
|
|
422
451
|
if target != "" && n.hasPosition && maps[n.position+1] != "" && maps[n.position+1] != target {
|
|
423
|
-
lines = append(lines, "frontier-review-sequence:
|
|
452
|
+
lines = append(lines, "frontier-review-sequence: reason=next Spec conflicts with recorded successor; native authority-normalization required source="+name+" artifacts="+strings.Join(artifacts, ","))
|
|
424
453
|
actionable, blocked = true, true
|
|
425
454
|
continue
|
|
426
455
|
}
|
|
@@ -429,16 +458,47 @@ func FrontierLines(root, slug string) ([]string, error) {
|
|
|
429
458
|
actionable, blocked = true, true
|
|
430
459
|
continue
|
|
431
460
|
}
|
|
461
|
+
if mode && target != "" {
|
|
462
|
+
sourceRecords := records
|
|
463
|
+
if name != slug {
|
|
464
|
+
sourceRecords, err = frontierEvidence(root, name)
|
|
465
|
+
if err != nil {
|
|
466
|
+
return nil, err
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if reason := sourceRecords.blocked[target]; reason != "" {
|
|
470
|
+
deferred[target] = reason
|
|
471
|
+
lines = append(lines, "frontier-blocked: "+target+" source="+name+" — "+reason+"; preparation deferred, not approved")
|
|
472
|
+
continue
|
|
473
|
+
}
|
|
474
|
+
}
|
|
432
475
|
if target != "" && nodes[target] == nil && !prepared[target] {
|
|
433
476
|
prepared[target] = true
|
|
434
477
|
// A table identifies a preparation obligation, not approval to implement it.
|
|
435
478
|
lines = append(lines, fmt.Sprintf("frontier-workspace: %s location=missing action=prepare reason=recorded next workspace; native scope and independence review required", target), fmt.Sprintf("frontier-prepare: /rite-spec %s source=%s reason=read recorded sequence authority before preparation; no workspace created", frontierQuote(target), name))
|
|
436
479
|
actionable = true
|
|
437
480
|
} else if target == "" && n.hasRemaining && n.remaining > 0 {
|
|
438
|
-
lines = append(lines, "frontier-review-sequence:
|
|
481
|
+
lines = append(lines, "frontier-review-sequence: reason=remaining work unresolved; native authority-normalization required source="+name+" artifacts="+strings.Join(artifacts, ","))
|
|
439
482
|
actionable, blocked = true, true
|
|
440
483
|
}
|
|
441
484
|
}
|
|
485
|
+
if mode {
|
|
486
|
+
filtered := candidates[:0]
|
|
487
|
+
for _, candidate := range candidates {
|
|
488
|
+
workspace := candidate.Workspace
|
|
489
|
+
if workspace == "" {
|
|
490
|
+
workspace = controller
|
|
491
|
+
}
|
|
492
|
+
if workspace != slug {
|
|
493
|
+
if reason := frontierSerialReason(records, slug, workspace, candidate.ID); reason != "" {
|
|
494
|
+
lines = append(lines, "frontier-serial: "+workspace+"/"+candidate.ID+" — "+reason+"; ordered handoff retained")
|
|
495
|
+
continue
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
filtered = append(filtered, candidate)
|
|
499
|
+
}
|
|
500
|
+
candidates = filtered
|
|
501
|
+
}
|
|
442
502
|
if controller != "" && controllerProblem == "" && len(live) > 0 {
|
|
443
503
|
lines = append(lines, fmt.Sprintf("frontier-select-command: devrites-engine parallel select --root %s --slug %s --from-plan --workspaces %s --cap %d", frontierQuote(filepath.Dir(root)), frontierQuote(controller), frontierQuote(strings.Join(live, ",")), capN))
|
|
444
504
|
if len(candidates) > 0 {
|
|
@@ -533,7 +593,7 @@ func frontierQuestionHold(n *frontierWorkspace) (string, error) {
|
|
|
533
593
|
return "", nil
|
|
534
594
|
}
|
|
535
595
|
|
|
536
|
-
func frontierLocal(root, controller string, n *frontierWorkspace, candidates *[]parallel.SlicePaths) (string, string, error) {
|
|
596
|
+
func frontierLocal(root, controller string, n *frontierWorkspace, candidates *[]parallel.SlicePaths, dispatch *dispatchFile) (string, string, error) {
|
|
537
597
|
n.selectionHeld = true
|
|
538
598
|
if frontierTerminal(n) {
|
|
539
599
|
return "complete", "terminal workspace", nil
|
|
@@ -544,15 +604,20 @@ func frontierLocal(root, controller string, n *frontierWorkspace, candidates *[]
|
|
|
544
604
|
if n.status != "running" && n.status != "ready" {
|
|
545
605
|
return "blocked", "status lacks an actionable native cursor; normalize state without inventing authority", nil
|
|
546
606
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
}
|
|
551
|
-
if err == nil {
|
|
552
|
-
dispatch, err := decodeDispatch(raw)
|
|
553
|
-
if err != nil {
|
|
607
|
+
if dispatch == nil {
|
|
608
|
+
raw, err := readBoundedRegularFile(filepath.Join(n.dir, "dispatch.json"), workspaceArtifactLimit)
|
|
609
|
+
if err != nil && !os.IsNotExist(err) {
|
|
554
610
|
return "", "", err
|
|
555
611
|
}
|
|
612
|
+
if err == nil {
|
|
613
|
+
dispatch, err = decodeDispatch(raw)
|
|
614
|
+
if err != nil {
|
|
615
|
+
return "", "", err
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
if dispatch != nil {
|
|
620
|
+
var err error
|
|
556
621
|
retained, pending := false, false
|
|
557
622
|
var built map[string]bool
|
|
558
623
|
for _, wave := range dispatch.Waves {
|
|
@@ -638,12 +703,22 @@ func frontierLocal(root, controller string, n *frontierWorkspace, candidates *[]
|
|
|
638
703
|
n.selectionHeld = true
|
|
639
704
|
return "wait", "Ship requires existing native authority; controller invocation is not approval", nil
|
|
640
705
|
}
|
|
706
|
+
if n.next == "" {
|
|
707
|
+
// Missing local work is not a hold on independently recorded preparation.
|
|
708
|
+
n.selectionHeld = true
|
|
709
|
+
return "no-cursor", "no actionable native internal cursor; review state without inventing authority", nil
|
|
710
|
+
}
|
|
641
711
|
return "blocked", "no actionable native internal cursor; review state without inventing authority", nil
|
|
642
712
|
}
|
|
643
713
|
|
|
644
|
-
//
|
|
645
|
-
//
|
|
646
|
-
func frontierRoadmap(nodes map[string]*frontierWorkspace, ordered []string) (map[int]string, []string, string) {
|
|
714
|
+
// Current canonical authority supersedes historical tables without rewriting them.
|
|
715
|
+
// Without it, differing exact legacy maps require native normalization.
|
|
716
|
+
func frontierRoadmap(nodes map[string]*frontierWorkspace, ordered []string) (map[int]string, []string, string, bool) {
|
|
717
|
+
if mapping, artifacts, err := frontierCanonicalRoadmap(nodes, ordered); err != nil {
|
|
718
|
+
return nil, artifacts, err.Error(), true
|
|
719
|
+
} else if mapping != nil {
|
|
720
|
+
return mapping, artifacts, "", false
|
|
721
|
+
}
|
|
647
722
|
result := map[int]string{}
|
|
648
723
|
var artifacts []string
|
|
649
724
|
problem := ""
|
|
@@ -743,7 +818,7 @@ func frontierRoadmap(nodes map[string]*frontierWorkspace, ordered []string) (map
|
|
|
743
818
|
artifacts = append(artifacts, filepath.Join(nodes[slug].dir, "state.md"))
|
|
744
819
|
}
|
|
745
820
|
}
|
|
746
|
-
return result, artifacts, problem
|
|
821
|
+
return result, artifacts, problem, false
|
|
747
822
|
}
|
|
748
823
|
|
|
749
824
|
func frontierCells(line string) []string {
|
|
@@ -782,6 +857,12 @@ func frontierBoundSpec(root, slug, action string) bool {
|
|
|
782
857
|
if err != nil {
|
|
783
858
|
return false
|
|
784
859
|
}
|
|
860
|
+
ordered, _ := frontierRelated(nodes, chain)
|
|
861
|
+
mapping, _, problem, invalidCanonical := frontierRoadmap(nodes, ordered)
|
|
862
|
+
if invalidCanonical {
|
|
863
|
+
return false
|
|
864
|
+
}
|
|
865
|
+
// Existing checked children remain resumable under ambiguous historical maps.
|
|
785
866
|
if child := nodes[target]; child != nil {
|
|
786
867
|
if child.parent != slug {
|
|
787
868
|
return false
|
|
@@ -791,7 +872,6 @@ func frontierBoundSpec(root, slug, action string) bool {
|
|
|
791
872
|
}
|
|
792
873
|
return true
|
|
793
874
|
}
|
|
794
|
-
mapping, _, problem := frontierRoadmap(nodes, chain)
|
|
795
875
|
if problem != "" || n.hasPosition && mapping[n.position+1] != "" && mapping[n.position+1] != target {
|
|
796
876
|
return false
|
|
797
877
|
}
|