axstack 0.55.0 → 0.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/concepts.md +3 -1
- package/docs/installation.md +5 -2
- package/docs/workflows.md +3 -1
- package/package.json +1 -1
- package/skills/axstack/references/fleet-dispatch.md +166 -0
- package/skills/axstack/references/fleet-route-schema.md +11 -0
- package/skills/axstack/references/fleet-verification.md +102 -0
- package/skills/axstack/references/lifecycle.md +19 -2
- package/skills/axstack/references/role-roster.md +6 -0
- package/skills/axstack/references/routing.md +12 -0
- package/skills/axstack/references/run-record.md +83 -0
- package/skills/axstack/references/t3-runtime.md +173 -6
- package/skills/axstack/references/workspace-hygiene.md +8 -0
- package/skills/axstack/scripts/dispatch-plan.js +19 -2
- package/skills/axstack/scripts/fleet-destination.js +39 -0
- package/skills/axstack/scripts/fleet-dispatch.js +6 -0
- package/skills/axstack/scripts/fleet-facts.js +12 -1
- package/skills/axstack/scripts/fleet-inventory.js +76 -0
- package/skills/axstack/scripts/fleet-receipt.js +130 -0
- package/skills/axstack/scripts/fleet-route.js +4 -3
- package/skills/axstack-cleanup/SKILL.md +21 -0
- package/skills/axstack-implement/SKILL.md +12 -5
- package/skills/axstack-watch/SKILL.md +3 -0
- package/skills/axstack-watch/references/watch-runtime.md +18 -5
- package/skills/voa/SKILL.md +96 -0
- package/skills/voa/references/observations.md +79 -0
- package/skills/voa/references/recovery.md +94 -0
- package/skills/voa/references/verification.md +35 -0
- package/skills/voa/scripts/cleanup.js +39 -0
- package/skills/voa/scripts/evaluate.js +122 -0
- package/skills/voa/scripts/observations.js +68 -0
- package/skills/voa/scripts/records.js +48 -0
- package/skills/voa/scripts/voa.js +43 -0
- package/src/installer.js +3 -2
- package/src/instructions.js +11 -3
|
@@ -55,6 +55,8 @@ For substantive delegated or resumable work, use the shared
|
|
|
55
55
|
[run record](../axstack/references/run-record.md). Reconcile it on restart with
|
|
56
56
|
the approved scope, T3 threads/runs and forge state, exact revisions, tickets, and watches.
|
|
57
57
|
Reuse valid owners and authors; ambiguity holds a replacement writer.
|
|
58
|
+
When a logical Coordinator coordinates project drivers, follow
|
|
59
|
+
[Coordinating multiple drivers](../axstack/references/t3-runtime.md#coordinating-multiple-drivers).
|
|
58
60
|
|
|
59
61
|
At execution start, bind work to the T3 driver thread and one authoritative
|
|
60
62
|
dispatch attempt. Preserve the actual thread/task/run IDs and process completion
|
|
@@ -281,8 +283,10 @@ For each PR:
|
|
|
281
283
|
Load and follow [Round-3 step-back](references/step-back.md).
|
|
282
284
|
Load and follow [Review-round budgets](../axstack/references/run-record.md#review-round-budgets) before dispatching any repair from the step-back.
|
|
283
285
|
|
|
284
|
-
The T3 run watch reconciles
|
|
285
|
-
|
|
286
|
+
The T3 run watch reconciles unsettled launched-fallback Writer runs; native
|
|
287
|
+
completion delivers delegated receipts. Never status-check running delegate_task
|
|
288
|
+
children on scheduled wakes. The bounded forge check wait is the only other
|
|
289
|
+
implementation wait.
|
|
286
290
|
After verified readback of every own PR publication, the driver arms or joins
|
|
287
291
|
its maintain-mode chat-run watch under [Autopilot](../axstack/references/autopilot.md).
|
|
288
292
|
That watch owns the bound T3 schedule wake; its runtime sets the cadence.
|
|
@@ -290,10 +294,13 @@ A turn with unsettled launched threads must end only under the bound-watch rule
|
|
|
290
294
|
With settled threads, end a turn only when every required PR is `merge-ready` or `held`.
|
|
291
295
|
Follow [Route each wake](../axstack-watch/SKILL.md#4-route-each-wake).
|
|
292
296
|
Follow [Advised holds](../axstack/references/contracts.md#advised-holds) when raising eligible holds.
|
|
293
|
-
Raise user-decision and serious-risk holds in the T3 driver thread and
|
|
294
|
-
turn waiting on the decision
|
|
297
|
+
Raise user-decision and serious-risk holds in the owning T3 driver thread and
|
|
298
|
+
end the turn waiting on the decision when all remaining authorized work is held.
|
|
299
|
+
Continue independent authorized work under Autopilot's scoped-hold rules.
|
|
300
|
+
Serious risks are raised immediately.
|
|
295
301
|
After bounded safe recovery, raise a genuine blocked operation that still needs
|
|
296
|
-
the user in the T3 driver thread, and the driver ends its turn waiting on it
|
|
302
|
+
the user in the owning T3 driver thread, and the driver ends its turn waiting on it
|
|
303
|
+
when all remaining authorized work is held.
|
|
297
304
|
|
|
298
305
|
Merge-ready opens the merge boundary. Own PRs default to automatic merge under
|
|
299
306
|
[watch §5](../axstack-watch/SKILL.md#5-state-readiness-precisely).
|
|
@@ -130,6 +130,9 @@ wake-ups drive observation; never poll or keep a model active between events.
|
|
|
130
130
|
|
|
131
131
|
## 4. Route each wake
|
|
132
132
|
|
|
133
|
+
Follow [Bounded recovery boundary](../axstack/references/t3-runtime.md#bounded-recovery-boundary)
|
|
134
|
+
for recovery continuations and waiting-turn records.
|
|
135
|
+
|
|
133
136
|
Re-read the remote head and base, then reconcile the event against acknowledged
|
|
134
137
|
IDs and the recorded mode. A changed head, CI result, or review comment is an
|
|
135
138
|
event, not repair authority. Stale or ambiguous observations authorize nothing.
|
|
@@ -50,16 +50,26 @@ human review, with no unsettled worker and no PR needing watch events, set the r
|
|
|
50
50
|
at once.
|
|
51
51
|
When work restarts, re-evaluate cadence against the recovery conditions below.
|
|
52
52
|
Keep native PR watches for forge-resolved waits.
|
|
53
|
+
Never status-check running delegate_task children on scheduled wakes.
|
|
54
|
+
Delegate existence alone arms no schedule: native completion delivers receipts.
|
|
55
|
+
The Driver-owned 5-minute lifecycle schedule (`everyMs:300000`) covers a
|
|
56
|
+
launched-fallback Writer without native completion. Keep the Driver's 60-minute
|
|
57
|
+
own-PR/release backstop and own-PR watch-unavailable/rate-limited 5-minute
|
|
58
|
+
fallback; recovery never polls launched Writers. Only named external-dependency
|
|
59
|
+
checks move to recovery after owners retire exact old IDs and verify absence.
|
|
60
|
+
Remove active-delegate polling rather than migrating it. Preserve review
|
|
61
|
+
managers and unrelated sweeps; cadence alone establishes no compatible recovery
|
|
62
|
+
service or authority to repurpose one. No retained schedule polls delegates.
|
|
53
63
|
Delegated tasks and native PR watches provide native completion paths: they deliver their own wakes.
|
|
54
64
|
Launched writers, including their resumed marker questions, lack native completion paths.
|
|
55
|
-
|
|
65
|
+
For an own-PR/release watch, with native completion paths for all unsettled work and armed native PR watches,
|
|
56
66
|
use the 60-minute backstop (`everyMs:3600000`), including while release is pending.
|
|
57
67
|
While a writer or follow-up is unsettled without a native completion path, use
|
|
58
68
|
the 5-minute cadence (`everyMs:300000`).
|
|
59
69
|
A launched-writer `AXSTACK-QUESTION` marker resume lacks a native completion wake, so it
|
|
60
70
|
keeps the 5-minute cadence (`everyMs:300000`) while any launched writer exists.
|
|
61
|
-
Delegated-child questions retain native completion paths and
|
|
62
|
-
when other recovery conditions are clear.
|
|
71
|
+
Delegated-child questions retain native completion paths, and an existing own-PR/release
|
|
72
|
+
watch uses the 60-minute backstop when other recovery conditions are clear.
|
|
63
73
|
An open PR left unwatched by a user Stop never triggers the 5-minute recovery cadence.
|
|
64
74
|
If native `watch_pull_request` is unavailable or unarmed for any open member,
|
|
65
75
|
or re-arming fails, restore the bound chat-run schedule to 5 minutes (`everyMs:300000`).
|
|
@@ -69,7 +79,7 @@ fallback conditions in the same turn.
|
|
|
69
79
|
Apply these conditions before ending each driver turn, including after initial
|
|
70
80
|
arming or a work restart; the parking and forge-resolved wait rules above still apply.
|
|
71
81
|
Read back each schedule update and record its receipt; an uncertain update holds affected work.
|
|
72
|
-
Each wake reconciles
|
|
82
|
+
Each wake reconciles unsettled launched-fallback Writer runs before running the authorized maintenance loop.
|
|
73
83
|
A failed run holds incomplete work even when its writer sent no receipt.
|
|
74
84
|
A missing schedule capability holds activation. Delegated roles follow T3 runtime;
|
|
75
85
|
add no daemon and no polling model between wakes.
|
|
@@ -89,7 +99,10 @@ Run `scripts/pr-digest.js` only on schedule wakes, using:
|
|
|
89
99
|
Use a per-repository JSON file in the private run directory for the watermark,
|
|
90
100
|
separate from `progress.md` (for example, `pr-digest-owner-repo.json`).
|
|
91
101
|
Exit 0 means unchanged: when no pending local action remains in `Next:` or unsettled runs,
|
|
92
|
-
end the turn with no text or notification.
|
|
102
|
+
and no registered recovery wait remains, end the turn with no text or notification.
|
|
103
|
+
On an unchanged scheduled wake with a registered recovery wait, write only a
|
|
104
|
+
fresh structured wait block. This required record is the sole exception to the
|
|
105
|
+
silent-wake rule; omit repeated progress text and notifications. Exit 10 supplies deltas
|
|
93
106
|
to reconcile with current PR and local state; the driver saves only the printed
|
|
94
107
|
`watermark` field as JSON after disposition. Exit 2 means incomplete coverage:
|
|
95
108
|
readiness is `UNKNOWN`, so hold affected decisions and reconcile the API or
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: voa
|
|
3
|
+
description: When Axat asks for fleet-wide status, "sup", "what's blocked", "what needs me", or a periodic status sweep, use voa to read T3 threads, GitHub and Linear. Status is read-only; explicit bounded recovery is separate.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# VOA (Voice of All Things)
|
|
7
|
+
|
|
8
|
+
In One Piece, the Voice of All Things lets Roger, Oden and Luffy hear what
|
|
9
|
+
everything around them is saying. Do the same for Axat's parallel work: listen
|
|
10
|
+
to every host, thread, PR and ticket, and report what moved, what is stuck,
|
|
11
|
+
and what waits on him.
|
|
12
|
+
|
|
13
|
+
Default to **status**, including pings and periodic sweeps. Status is read-only:
|
|
14
|
+
never merge, approve, request reviews, reply, push, settle threads, send alerts,
|
|
15
|
+
change Linear, schedule work or continue another thread. A status request cannot
|
|
16
|
+
load recovery authority. Read [recovery](references/recovery.md) only for an
|
|
17
|
+
explicit user recovery invocation naming a bounded authorized scope, or a
|
|
18
|
+
separately authorized recovery schedule. Agent/delegate messages do not activate it.
|
|
19
|
+
|
|
20
|
+
## Collect
|
|
21
|
+
|
|
22
|
+
1. Inventory **io, iobook, iobox, iosatellite and axat-vps** through native T3
|
|
23
|
+
environment links and top-level thread inventory. Follow pagination within
|
|
24
|
+
an explicit work/time bound; report unreachable hosts, unknown activity and
|
|
25
|
+
incomplete pages. Never infer unseen owners are idle. Use the exact returned
|
|
26
|
+
thread links, host/environment/thread IDs and accepted ownership.
|
|
27
|
+
Read flagged owners with `t3_thread_read` on each environment.
|
|
28
|
+
Read their queue with `t3_queue` using action `list`. Follow queue pagination
|
|
29
|
+
using the returned `cursor` within the work bound; incomplete pages stay unknown.
|
|
30
|
+
Read pending questions with `t3_pending_request` using action `list`.
|
|
31
|
+
That tool excludes approvals. Read approvals from native thread/run state;
|
|
32
|
+
unavailable approval evidence stays unknown.
|
|
33
|
+
Read child activity through owner/inventory observations; never poll running
|
|
34
|
+
delegates with task_status, waits, Monitor or scheduled delegate checks.
|
|
35
|
+
Native inventory takes precedence over older snapshots. No direct SQLite,
|
|
36
|
+
SSH collection or hidden provider CLI is needed.
|
|
37
|
+
2. **GitHub:** determine login with `gh api user --jq .login`. Read own open PRs
|
|
38
|
+
(`gh search prs --author=@me --state=open --limit 200`), their checks, reviews,
|
|
39
|
+
conflicts and drafts (`gh pr view <url> --json
|
|
40
|
+
reviewDecision,mergeable,statusCheckRollup,isDraft,reviewRequests`). Read
|
|
41
|
+
reviews requested from Axat and merges since the last report. Pagination or
|
|
42
|
+
permission gaps stay unknown, including a search limit reached.
|
|
43
|
+
3. **Linear:** defi-com repositories only, through Executor. Read assigned
|
|
44
|
+
current-cycle In Progress, In Review and Todo issues, blocked items and new
|
|
45
|
+
comments. Access failure is a coverage gap, never permission to change items.
|
|
46
|
+
4. Read the previous VOA report in this thread; otherwise use 24 hours.
|
|
47
|
+
|
|
48
|
+
Saved normalized observations can be summarized with
|
|
49
|
+
`bun <this skill>/scripts/voa.js --mode status --input <saved.json>`.
|
|
50
|
+
Read [observations](references/observations.md) for the explicit offline input.
|
|
51
|
+
The helper reads only that file and writes JSON to stdout; it never collects,
|
|
52
|
+
dispatches, sends, schedules or chooses a workflow.
|
|
53
|
+
For source/pack verification and the unverified live boundary, see
|
|
54
|
+
[verification](references/verification.md).
|
|
55
|
+
|
|
56
|
+
## Join
|
|
57
|
+
|
|
58
|
+
- Match threads, full PR URLs, branches and DEF issue keys. Exact ownership
|
|
59
|
+
evidence determines Coordinator/Driver/Worker roles; titles, parent ID alone,
|
|
60
|
+
silence, elapsed time and free text cannot establish recovery eligibility.
|
|
61
|
+
- Read the last owner message before calling a thread blocked. Idle plus an
|
|
62
|
+
unanswered question is needs-you. Confirm delivery/closing evidence before
|
|
63
|
+
calling merged work done; suggest scoped settlement, never perform it.
|
|
64
|
+
- For status attention only: working with no progress for two hours, failed
|
|
65
|
+
runs, pending questions/approvals over 30 minutes and stopped queued messages
|
|
66
|
+
are signals to inspect. They never authorize a continuation or retry.
|
|
67
|
+
- Red CI, conflicts and requested changes need attention. Report failed/stuck
|
|
68
|
+
Workers under their exact parent; workers receive status only.
|
|
69
|
+
|
|
70
|
+
## Report
|
|
71
|
+
|
|
72
|
+
Scan-fast, one screen. Lead with time (IST), counts and coverage.
|
|
73
|
+
|
|
74
|
+
1. **Needs you:** exact action and full PR/ticket/native thread link, oldest first.
|
|
75
|
+
2. **Blocked on others:** who, what, since when and links.
|
|
76
|
+
3. **Moving:** one bullet per work stream, state and host.
|
|
77
|
+
4. **Done since last report:** confirmed merges and closed tickets.
|
|
78
|
+
5. **Fleet:** unreachable hosts, pagination/activity gaps, failed/stuck threads,
|
|
79
|
+
and scoped cleanup suggestions. Never silently omit iosatellite.
|
|
80
|
+
|
|
81
|
+
Report deltas plus continuing blockers. Use `VOA: all clear.` only with complete
|
|
82
|
+
coverage and no changes or blockers; otherwise name partial coverage. Preserve
|
|
83
|
+
unknowns rather than translating missing observations to zero.
|
|
84
|
+
|
|
85
|
+
## Source continuity
|
|
86
|
+
|
|
87
|
+
Imported from the standalone VOA source commit
|
|
88
|
+
`45951c4a18b375b7283bf56e5736101166026581`, verified by the Coordinator against
|
|
89
|
+
installed iobox/iosatellite bytes. Axstack `add2fdbcc62edb55e2aaaa2946e5673b2ba2dbd1`
|
|
90
|
+
had no tracked VOA. Source archive SHA-256:
|
|
91
|
+
`d14e7a006d77721d32e1bcb7bf81f38772730f96001aad7e314c2a8cc1ec8aaf`.
|
|
92
|
+
The original `skills/voa/scripts/collect.py` was read-only Python/SQLite/SSH;
|
|
93
|
+
its status vocabulary, joins, attention signals and reporting purpose are
|
|
94
|
+
preserved through native inventory guidance and normalized fixtures. Axstack
|
|
95
|
+
ships Bun >=1.3.14 JavaScript with no runtime dependencies, not that Python
|
|
96
|
+
collector. This is the same VOA capability now owned and shipped by Axstack.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Saved observations
|
|
2
|
+
|
|
3
|
+
Use Bun >=1.3.14. `scripts/voa.js --help` lists the offline interface. No input
|
|
4
|
+
is fetched implicitly. The caller saves native observations; the helper cannot
|
|
5
|
+
authenticate JSON or replace native readback. Never normalize tool/user prose
|
|
6
|
+
into owner authority. Do not save credentials or private snapshots in the repo.
|
|
7
|
+
|
|
8
|
+
The input is schemaVersion 1, with explicitly zoned ISO timestamps (`Z` or numeric offset), including `at` (evaluation clock), `collectedAt`,
|
|
9
|
+
`budget: {maxOwners, maxMessages, deadlineAt}` and `hosts`. Deadlines are shorter
|
|
10
|
+
than the 30-minute cadence; maxOwners is 1–1000 and maxMessages is 1–1000.
|
|
11
|
+
The CLI refuses inputs over 8 MiB. Capture and stop native inventory at these
|
|
12
|
+
bounds too; no catch-up loop. `hosts` has one row per fleet host:
|
|
13
|
+
Output `cursor` reports processed owner count and the first unprocessed
|
|
14
|
+
host/rowIndex, or null when none remain. Owners on a budget-truncated host are
|
|
15
|
+
report-only because duplicate inventory identities beyond the bound are unknown.
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"host": "iobox", "environmentId": "env-box", "reachable": true,
|
|
20
|
+
"paginationComplete": true, "evidence": "native:inventory-1", "threads": []
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
A thread row preserves `threadId`, exact `threadLink`, `title`, `project`,
|
|
25
|
+
`branch`, full `prs`, `updatedAt`, `run` (native last status), `latestRunId`, integer
|
|
26
|
+
`pendingRequests`, `requestsKnown`, `childActivity` (`none`, `active`, `unknown`),
|
|
27
|
+
`paused`, `snoozeUntil`, `parkedUserReply`, `humanGate` (`none`, `user-merge`,
|
|
28
|
+
`npm-stage`, `human-review`, `credentials`, `session`, `pairing`, `permission`,
|
|
29
|
+
`new-thread`), `completed` (structured terminal/forge resolution),
|
|
30
|
+
`historyKnown`, `queue: {known, held, items}`, `wakeDeliveries` and `messages`.
|
|
31
|
+
Unknown booleans are explicit null, never false. Unknown run statuses stay
|
|
32
|
+
unknown. Preserve the collector's working/needs-you/failed/idle status vocabulary
|
|
33
|
+
and last assistant message for reporting; those summaries grant no authority.
|
|
34
|
+
|
|
35
|
+
Messages are in native history order, each with `id`, native `runId`, `source: "native"`,
|
|
36
|
+
`role`, exact `author: {host, environmentId, threadId, threadLink}` and `text`.
|
|
37
|
+
Keep raw roles: tool output and user pastes are never assistant messages.
|
|
38
|
+
An owner's current record is the last JSON fenced waiting/closing block in its
|
|
39
|
+
latest own assistant message, matching the R7c schema in
|
|
40
|
+
`axstack/references/run-record.md` (candidate `c784fe3d`). A later owner message
|
|
41
|
+
without a fresh block invalidates the old block. Require the message `runId`
|
|
42
|
+
to match the native `latestRunId`; a later silent run also invalidates it.
|
|
43
|
+
Missing run IDs are report-only; never infer them from message text. Unknown schemas and incomplete
|
|
44
|
+
records are report-only. The helper does not write wait records.
|
|
45
|
+
|
|
46
|
+
For recovery only, include `ownership` rows: exact `owner`, `role` (Coordinator,
|
|
47
|
+
Driver or Worker), `topLevel`, accepted `scope`, `authorityEvidence`, and
|
|
48
|
+
`nextActionEvidence` (array of already accepted action pointers). Never infer
|
|
49
|
+
these from a title or parent ID. A thread's `blocker` is a separately inspected
|
|
50
|
+
structured fact with `kind` (`check`, `review`, `upstream-pr`, `dependency`),
|
|
51
|
+
exact `id`, `state` (`unexplained`, `open`, `cleared`), `nonHuman: true`,
|
|
52
|
+
`inspectedAt` and `evidence` pointer. It must match the owner's dependency.
|
|
53
|
+
|
|
54
|
+
`deliveryHistory: {known, entries}` combines native owner history and recovery's
|
|
55
|
+
own assistant-authored delivery receipts. Entries contain the `key` (three-item
|
|
56
|
+
array: host, threadId, conditionId), `outcome` (accepted, queued, running,
|
|
57
|
+
ambiguous), exact `id` and evidence pointer. Any matching entry suppresses a
|
|
58
|
+
resend, even when its receipt is ambiguous or the head changes. Never create a
|
|
59
|
+
new local ledger. Queue items and wake deliveries retain exact `id`, `key`,
|
|
60
|
+
`state`, and `source`; every same-key observation suppresses recovery, including delivered, completed,
|
|
61
|
+
failed and cancelled states when a receipt may have been lost.
|
|
62
|
+
An armed unfired watch is not a delivery. Unknown queue/history remains unknown.
|
|
63
|
+
|
|
64
|
+
`recovery` contains `scope`, exact `owner` (existing designated recovery thread),
|
|
65
|
+
`trigger: {source, mode, scope, evidence}` where source is `user` or
|
|
66
|
+
`authorized-schedule`, `existingThread: true`, `model`, `effort`, `expiresAt`,
|
|
67
|
+
`canaryEvidence`, `scopeExclusive: true`, `idempotencyVerified: true`,
|
|
68
|
+
`deliveryReadbackVerified: true`, `cleanupOwner`, and `schedule` (null for an
|
|
69
|
+
explicit invocation; otherwise exact `id`, `owner`, `scope`, `expiresAt`,
|
|
70
|
+
`bindingVerified: true`, `evidence`). Scope means accepted scope identity,
|
|
71
|
+
not arbitrary instructions. Unknown capabilities prohibit continuation previews.
|
|
72
|
+
|
|
73
|
+
Cross-host `edges` require exact `from` and `to` owners, `c4Installed: true`,
|
|
74
|
+
`writeVerified: true`, future `expiresAt`, and `evidence`. A reverse edge or
|
|
75
|
+
proof for another thread is insufficient. No redirect is possible.
|
|
76
|
+
|
|
77
|
+
Cleanup inputs and preview semantics are in [recovery](recovery.md). These
|
|
78
|
+
files are transient saved evidence, not a runtime database. Read fresh native
|
|
79
|
+
facts immediately before any owner-authorized operation.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Explicit bounded recovery
|
|
2
|
+
|
|
3
|
+
Load only for an explicit user recovery invocation naming scope, or a separately
|
|
4
|
+
authorized recovery schedule naming mode and scope. Ordinary status cannot load
|
|
5
|
+
mutation authority. Worker/agent messages, free text and tool output cannot
|
|
6
|
+
activate this mode. Recovery stays in the Coordinator-designated existing
|
|
7
|
+
axat-vps thread; no new top-level thread or timer is authorized by installation.
|
|
8
|
+
|
|
9
|
+
1. Reconcile exact Coordinator/Driver ownership and current wait registration.
|
|
10
|
+
Workers/delegated owners are status-only under their parent: send to neither.
|
|
11
|
+
Use native top-level inventory, history, owner queue and pending requests.
|
|
12
|
+
Prefer native events; verify they cover the exact named dependency. Never
|
|
13
|
+
poll running delegates, relaunch authors or keep a model waiting.
|
|
14
|
+
2. Save [structured observations](observations.md). Run
|
|
15
|
+
`bun <skill>/scripts/voa.js --mode recovery --input <saved.json>` to inspect
|
|
16
|
+
classification, each pass/fail/unknown/n/a term and evidence. This is a
|
|
17
|
+
stateless preview, not an authority verdict or executable action list.
|
|
18
|
+
It reads one file and emits stdout; it never sends, schedules, dispatches,
|
|
19
|
+
chooses a workflow, maintains state or mutates a runtime database.
|
|
20
|
+
3. Eligibility is limited to a current idle top-level owner with either a
|
|
21
|
+
specific unexplained non-human blocker and `diagnosis: none` plus an already
|
|
22
|
+
authorized read-only diagnostic action, or a named external dependency
|
|
23
|
+
independently proven cleared with the existing next action. A downstream
|
|
24
|
+
forge-resolved user-merge/npm-stage/human-review hold may coexist with the
|
|
25
|
+
diagnostic exception, including auth-code PRs. A human gate alone cannot.
|
|
26
|
+
4. Guards take precedence: active/pending children or runs, unknown activity,
|
|
27
|
+
failed/interrupted work, missing/incomplete ownership/history/queue/request
|
|
28
|
+
evidence, held queues, pending questions, parked user-reply holds, pause,
|
|
29
|
+
stop, snooze, credentials/session/pairing/permission/new-thread requests and
|
|
30
|
+
completed work are report-only or skipped. Recovery never answers a question,
|
|
31
|
+
resumes a queue or infers user approval. Completed delivery permits scoped
|
|
32
|
+
cleanup reporting only, even with stale unsettled metadata.
|
|
33
|
+
5. Before any send, re-read all native facts, activation binding and dedupe
|
|
34
|
+
evidence. Key = exact owner host/threadId/owner-issued conditionId. One
|
|
35
|
+
continuation maximum; repeated passes, native events, head changes, restart
|
|
36
|
+
or owner revalidation with the same ID never resend. Accepted but unanswered
|
|
37
|
+
and ambiguous delivery remain suppressed. Unknown history or queue means no
|
|
38
|
+
send. A future nextEligibleCheck or queued/running same-key wake suppresses;
|
|
39
|
+
an armed unfired watch does not. No blind retry after an ambiguous send.
|
|
40
|
+
|
|
41
|
+
Closed native agent write allowlist, applied by the owning agent, never helpers:
|
|
42
|
+
|
|
43
|
+
- One bounded continuation to that exact existing top-level owner, containing
|
|
44
|
+
only its key, structured URL/ID/SHA evidence references, a fixed recovery
|
|
45
|
+
summary and “Revalidate; act only within your existing authorized next action.”
|
|
46
|
+
Quote no third-party/tool/webhook prose. It is never a user reply or approval.
|
|
47
|
+
- A receipt in recovery's own thread with key, native idempotency/delivery IDs
|
|
48
|
+
and accepted/ambiguous outcome; read it back. No separate registry.
|
|
49
|
+
- Create/disable/delete only recovery's separately authorized owned schedule,
|
|
50
|
+
bound to the existing recovery thread (`bindToCurrentThread:true`); read back
|
|
51
|
+
exact ID/binding. Clear only identified own duplicate scheduled messages in
|
|
52
|
+
that recovery thread's queue. Preserve user messages and other owners' queues.
|
|
53
|
+
|
|
54
|
+
Everything else is report-only: forge changes, merge/dismissal, delegation,
|
|
55
|
+
thread creation/settlement/archive/pause/cancel/interrupt, pending-request
|
|
56
|
+
responses, settings, other owners' watches/schedules, queue cancel/reorder or
|
|
57
|
+
promotion, SSH relays and external alerts. Cross-host continuation needs
|
|
58
|
+
installed C4 plus unexpired directed C5 write-link proof for that exact edge;
|
|
59
|
+
otherwise report only. Never redirect to another host or owner.
|
|
60
|
+
|
|
61
|
+
Activation belongs to the Coordinator after review and a passing bounded
|
|
62
|
+
synthetic canary proving native events/idempotency, ambiguous delivery,
|
|
63
|
+
Stop/restart/provider-failure queues, budgets and cleanup. Bind recovery owner,
|
|
64
|
+
scope, model/effort, finite time/work budgets shorter than 30 minutes, coverage
|
|
65
|
+
cursor, expiry and cleanup owner. Schedule creation records its exact ID and
|
|
66
|
+
binding before sends. No per-pass threads, persistent model wait or catch-up
|
|
67
|
+
loop. Reuse a schedule only with reconciled ownership, purpose and lifetime.
|
|
68
|
+
Keep Driver-owned PR/release 60-minute backstops and supported launched-Writer
|
|
69
|
+
lifecycle/watch fallbacks; recovery never inherits those resources. Move only
|
|
70
|
+
overlapping external-dependency checks after owners retire exact old IDs with
|
|
71
|
+
absence proof. Persistent shared service needs separate user authority.
|
|
72
|
+
|
|
73
|
+
## Cleanup preview
|
|
74
|
+
|
|
75
|
+
Use `--mode cleanup` with the same saved input and `cleanup` containing exact
|
|
76
|
+
`owner`, `scope`, `reason` (completed/cancelled/expired/empty/handoff),
|
|
77
|
+
`authorized: true`, `authorityEvidence`, `inventoryKnown: true`, `resources`,
|
|
78
|
+
`messages` and optional `readback: {known, scheduleIds, messageIds, evidence}`.
|
|
79
|
+
For reason `empty`, require `openRegistrationsKnown: true` and
|
|
80
|
+
`openRegistrations: []` from complete native registration inventory; no eligible
|
|
81
|
+
continuation this pass is insufficient.
|
|
82
|
+
Resources have `id`, exact `owner`, `scope`, `kind: "schedule"`, `lifetime:
|
|
83
|
+
"run"` and evidence. Shared services have `lifetime: "shared"` and registration
|
|
84
|
+
IDs; completing one registration preserves the service and all other runs.
|
|
85
|
+
Messages need exact `id`, owner, scope, `source: "schedule"`, matching
|
|
86
|
+
`scheduleId`, `duplicate: true` and evidence. Titles never identify ownership.
|
|
87
|
+
|
|
88
|
+
The preview identifies exact owned IDs, preserving same-title/different-ID
|
|
89
|
+
resources, user messages, shared services and other owners. It never deletes.
|
|
90
|
+
The owner validates before deleting, performs deletion separately, then reads
|
|
91
|
+
back absence by exact ID. Unknown deletion/readback remains a cleanup finding,
|
|
92
|
+
never a reason to redo product work. After expiry every recovery pass sends
|
|
93
|
+
nothing, even if deletion failed. Empty scope means no open registrations,
|
|
94
|
+
not merely none eligible. Finishing a registration never deletes a shared service.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Local verification and live boundary
|
|
2
|
+
|
|
3
|
+
In an Axstack source checkout, use the repository `verify-axstack` Launch and
|
|
4
|
+
Doctor with an owned 0700 TMPDIR outside HOME, an isolated fixture HOME and
|
|
5
|
+
offline network namespace. Run these actual interfaces:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun run test tests/workflows/voa-install.test.js \
|
|
9
|
+
tests/workflows/voa-recovery.test.js tests/workflows/voa-cleanup.test.js \
|
|
10
|
+
tests/workflows/voa-boundaries.test.js tests/workflows/voa-coverage.test.js \
|
|
11
|
+
tests/workflows/voa-cli.test.js
|
|
12
|
+
bun run test tests/installer/package.test.js tests/installer/regressions.test.js \
|
|
13
|
+
tests/installer/preset-bundle-validation.test.js tests/workflows/prose-size.test.js
|
|
14
|
+
bun pm pack --dry-run
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The fixed cases exercise pre/post snooze, specific missing diagnosis, open and
|
|
18
|
+
cleared dependencies, head/restart/native-event dedupe, completion, human/auth
|
|
19
|
+
and question holds, active/unknown Workers, untrusted/incomplete records,
|
|
20
|
+
partial five-host inventory and budgets, exact owned cleanup and shared
|
|
21
|
+
services, status isolation, wake precedence and directed C4/C5 proof.
|
|
22
|
+
CLI checks use saved synthetic JSON, no provider CLI on PATH, and an empty
|
|
23
|
+
fixture HOME with Bun's transpiler cache disabled. They compare input bytes
|
|
24
|
+
and output determinism; no execution adapter exists. Package tests extract
|
|
25
|
+
the actual artifact and byte-check all installed assets through uninstall.
|
|
26
|
+
Skill Creator's `quick_validate.py skills/voa` validates authoring metadata;
|
|
27
|
+
Python is a development validator, not a shipped VOA runtime.
|
|
28
|
+
|
|
29
|
+
Fixture success cannot prove native authentication, inventory/API pagination,
|
|
30
|
+
cross-host write links, events/idempotency, Stop/restart/provider-failure queues,
|
|
31
|
+
schedule binding, actual deletion or live delivery. The Coordinator owns the
|
|
32
|
+
separately authorized bounded R7d canary after source review and R7c integration.
|
|
33
|
+
Record exact versions, host/thread, scope, budgets, expiry, delivery IDs and
|
|
34
|
+
cleanup readbacks. Preserve unknowns and perform no live canary from author
|
|
35
|
+
fixtures. Installation alone neither activates recovery nor changes schedules.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { identity, pointer, sameOwner, term } from './observations.js';
|
|
2
|
+
|
|
3
|
+
export function cleanupPlan(input) {
|
|
4
|
+
const c = input.cleanup;
|
|
5
|
+
const schedule = input.recovery?.schedule;
|
|
6
|
+
const bound = c?.authorized === true && pointer(c.authorityEvidence) && identity(c.owner)
|
|
7
|
+
&& sameOwner(c.owner, input.recovery?.owner) && pointer(c.scope) && c.scope === input.recovery?.scope
|
|
8
|
+
&& ['completed', 'cancelled', 'expired', 'empty', 'handoff'].includes(c.reason)
|
|
9
|
+
&& (c.reason !== 'empty' || c.openRegistrationsKnown === true
|
|
10
|
+
&& Array.isArray(c.openRegistrations) && c.openRegistrations.length === 0);
|
|
11
|
+
const known = bound && c.inventoryKnown === true && Array.isArray(c.resources) && Array.isArray(c.messages);
|
|
12
|
+
const result = { scheduleIds: [], messageIds: [], preserveIds: [], terms: [
|
|
13
|
+
term('cleanup-binding', bound ? 'pass' : 'unknown', c?.authorityEvidence ?? 'unknown owned cleanup authority'),
|
|
14
|
+
term('cleanup-inventory', known ? 'pass' : 'unknown', 'native exact-ID resource and queue inventory'),
|
|
15
|
+
] };
|
|
16
|
+
for (const resource of (Array.isArray(c?.resources) ? c.resources : [])) {
|
|
17
|
+
const owned = known && resource.kind === 'schedule' && resource.lifetime === 'run'
|
|
18
|
+
&& pointer(resource.id) && resource.id === schedule?.id && schedule.bindingVerified === true
|
|
19
|
+
&& sameOwner(schedule.owner, c.owner) && schedule.scope === c.scope && pointer(schedule.evidence)
|
|
20
|
+
&& sameOwner(resource.owner, c.owner) && resource.scope === c.scope && pointer(resource.evidence);
|
|
21
|
+
(owned ? result.scheduleIds : result.preserveIds).push(resource.id);
|
|
22
|
+
}
|
|
23
|
+
for (const message of (Array.isArray(c?.messages) ? c.messages : [])) {
|
|
24
|
+
const owned = known && pointer(message.id) && sameOwner(message.owner, c.owner) && message.scope === c.scope
|
|
25
|
+
&& message.source === 'schedule' && message.duplicate === true && pointer(message.evidence)
|
|
26
|
+
&& result.scheduleIds.includes(message.scheduleId);
|
|
27
|
+
(owned ? result.messageIds : result.preserveIds).push(message.id);
|
|
28
|
+
}
|
|
29
|
+
// Readback is evidence only. The helper performs no deletion or queue edit.
|
|
30
|
+
for (const [ids, field, label] of [[result.scheduleIds, 'scheduleIds', 'schedule'], [result.messageIds, 'messageIds', 'message']]) {
|
|
31
|
+
for (const id of ids) {
|
|
32
|
+
const readback = c?.readback;
|
|
33
|
+
const knownReadback = readback?.known === true && Array.isArray(readback[field]) && pointer(readback.evidence);
|
|
34
|
+
result.terms.push(term(`${label}-absent:${id}`, !knownReadback ? 'unknown'
|
|
35
|
+
: readback[field].includes(id) ? 'fail' : 'pass', readback?.evidence ?? 'absence not read back'));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { identity, instant, normalize, pointer, sameKey, sameOwner, term, validKey } from './observations.js';
|
|
2
|
+
import { condition, currentRecord } from './records.js';
|
|
3
|
+
import { cleanupPlan } from './cleanup.js';
|
|
4
|
+
|
|
5
|
+
function activation(input, at) {
|
|
6
|
+
const r = input.recovery;
|
|
7
|
+
const trigger = r?.trigger;
|
|
8
|
+
const schedule = r?.schedule;
|
|
9
|
+
const validSchedule = schedule === null || (pointer(schedule?.id) && sameOwner(schedule.owner, r?.owner)
|
|
10
|
+
&& schedule.scope === r?.scope && schedule.bindingVerified === true && pointer(schedule.evidence)
|
|
11
|
+
&& instant(schedule.expiresAt) && Date.parse(schedule.expiresAt) > at
|
|
12
|
+
&& Date.parse(schedule.expiresAt) <= Date.parse(r?.expiresAt));
|
|
13
|
+
return [
|
|
14
|
+
term('explicit-mode', ['user', 'authorized-schedule'].includes(trigger?.source)
|
|
15
|
+
&& trigger.mode === 'recovery' && trigger.scope === r?.scope && pointer(trigger.evidence) ? 'pass' : 'unknown',
|
|
16
|
+
trigger?.evidence ?? 'no explicit recovery invocation'),
|
|
17
|
+
term('recovery-binding', identity(r?.owner) && r.owner.host === 'axat-vps' && r.existingThread === true
|
|
18
|
+
&& identity(r.cleanupOwner) && pointer(r.scope) && validSchedule
|
|
19
|
+
&& (trigger?.source !== 'authorized-schedule' || schedule !== null) ? 'pass' : 'unknown',
|
|
20
|
+
schedule?.evidence ?? 'existing recovery-thread binding'),
|
|
21
|
+
term('activation-proof', pointer(r?.canaryEvidence) && pointer(r?.model) && pointer(r?.effort)
|
|
22
|
+
&& r.scopeExclusive === true && r.idempotencyVerified === true && r.deliveryReadbackVerified === true ? 'pass' : 'unknown',
|
|
23
|
+
r?.canaryEvidence ?? 'missing canary/model/effort/exclusivity/delivery proof'),
|
|
24
|
+
term('recovery-lifetime', instant(r?.expiresAt) && Date.parse(r.expiresAt) > at ? 'pass' : 'fail',
|
|
25
|
+
r?.expiresAt ?? 'unknown expiry'),
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function ownerTerms(input, row, record, at, base) {
|
|
30
|
+
const raw = row.raw;
|
|
31
|
+
const matches = (Array.isArray(input.ownership) ? input.ownership : []).filter((entry) => sameOwner(entry.owner, row.owner));
|
|
32
|
+
const accepted = matches.length === 1 ? matches[0] : null;
|
|
33
|
+
const key = record ? [row.owner.host, row.owner.threadId, record.conditionId] : null;
|
|
34
|
+
const reason = condition(record, raw.blocker, at);
|
|
35
|
+
const history = input.deliveryHistory;
|
|
36
|
+
const prior = Array.isArray(history?.entries) ? history.entries.filter((entry) => sameKey(entry.key, key)) : [];
|
|
37
|
+
const deliveryKnown = (entry) => pointer(entry?.id) && validKey(entry.key)
|
|
38
|
+
&& ['queued', 'running', 'delivered', 'completed', 'cancelled', 'failed'].includes(entry.state);
|
|
39
|
+
const queueKnown = raw.queue?.known === true && raw.queue.held === false && Array.isArray(raw.queue.items)
|
|
40
|
+
&& raw.queue.items.every(deliveryKnown);
|
|
41
|
+
const wakesKnown = Array.isArray(raw.wakeDeliveries) && raw.wakeDeliveries.every(deliveryKnown);
|
|
42
|
+
const historyKnown = history?.known === true && Array.isArray(history.entries)
|
|
43
|
+
&& history.entries.every((entry) => pointer(entry?.id) && validKey(entry.key) && pointer(entry.evidence)
|
|
44
|
+
&& ['accepted', 'queued', 'running', 'ambiguous'].includes(entry.outcome));
|
|
45
|
+
const queued = queueKnown && raw.queue.items.some((entry) => sameKey(entry.key, key));
|
|
46
|
+
const waking = wakesKnown && raw.wakeDeliveries.some((entry) => sameKey(entry.key, key));
|
|
47
|
+
const sameHost = input.recovery?.owner?.host === row.owner.host;
|
|
48
|
+
const edge = (Array.isArray(input.edges) ? input.edges : []).find((entry) => sameOwner(entry.from, input.recovery?.owner)
|
|
49
|
+
&& sameOwner(entry.to, row.owner) && entry.c4Installed === true && entry.writeVerified === true
|
|
50
|
+
&& instant(entry.expiresAt) && Date.parse(entry.expiresAt) > at && pointer(entry.evidence));
|
|
51
|
+
const temporal = record?.recordType === 'waiting-turn' && record.pause === false
|
|
52
|
+
&& (record.snoozeUntil === null || Date.parse(record.snoozeUntil) <= at)
|
|
53
|
+
&& raw.paused === false && (raw.snoozeUntil === null || (instant(raw.snoozeUntil) && Date.parse(raw.snoozeUntil) <= at))
|
|
54
|
+
&& Date.parse(record.lifetime.expiresAt) > at && Date.parse(record.lastMeaningfulProgress.at) <= at;
|
|
55
|
+
const gates = ['none', 'user-merge', 'npm-stage', 'human-review'];
|
|
56
|
+
return { key, reason, terms: [
|
|
57
|
+
...base,
|
|
58
|
+
term('host-inventory', row.inventoryKnown ? 'pass' : 'unknown', row.owner.host),
|
|
59
|
+
term('current-record', record ? 'pass' : 'unknown', record?.conditionId ?? 'missing/stale/untrusted/incomplete record'),
|
|
60
|
+
term('accepted-owner', accepted?.topLevel === true && ['Coordinator', 'Driver'].includes(accepted.role)
|
|
61
|
+
&& accepted.scope === record?.authority.scope && accepted.scope === input.recovery?.scope
|
|
62
|
+
&& accepted.authorityEvidence === record?.authority.evidence && pointer(accepted.authorityEvidence)
|
|
63
|
+
&& Array.isArray(accepted.nextActionEvidence) && accepted.nextActionEvidence.includes(record?.nextAction.evidence) ? 'pass' : 'unknown',
|
|
64
|
+
accepted?.authorityEvidence ?? 'unknown or delegated owner'),
|
|
65
|
+
term('idle-owner', row.state === 'idle' && raw.childActivity === 'none' && raw.completed === false ? 'pass' : 'unknown',
|
|
66
|
+
`run=${raw.run}; children=${raw.childActivity}; completed=${raw.completed}`),
|
|
67
|
+
term('human-guards', raw.requestsKnown === true && raw.pendingRequests === 0 && raw.parkedUserReply === false
|
|
68
|
+
&& record?.wake.mechanism !== 'user-reply'
|
|
69
|
+
&& gates.includes(raw.humanGate) ? 'pass' : 'unknown', `pending=${raw.pendingRequests}; gate=${raw.humanGate}`),
|
|
70
|
+
term('pause-lifetime', temporal ? 'pass' : 'fail', record?.lifetime.expiresAt ?? 'unknown pause/expiry'),
|
|
71
|
+
term('queue-known', queueKnown && wakesKnown ? 'pass' : 'unknown', 'native queue/held state and wake deliveries'),
|
|
72
|
+
term('history-known', raw.historyKnown === true && historyKnown ? 'pass' : 'unknown',
|
|
73
|
+
'owner and recovery receipt history'),
|
|
74
|
+
term('condition-dedupe', prior.length === 0 ? 'pass' : 'fail', prior[0]?.evidence ?? 'no prior same-key delivery in saved history'),
|
|
75
|
+
term('wake-precedence', record && (record.nextEligibleCheck === null || Date.parse(record.nextEligibleCheck) <= at)
|
|
76
|
+
&& !queued && !waking ? 'pass' : 'fail', 'next eligible check and same-key observed delivery'),
|
|
77
|
+
term('directed-edge', sameHost ? 'n/a' : edge ? 'pass' : 'unknown', sameHost ? 'same-host' : edge?.evidence ?? 'missing installed C4/directed C5 proof'),
|
|
78
|
+
term('named-condition', reason && (raw.humanGate === 'none' || reason === 'missing-diagnosis') ? 'pass' : 'fail',
|
|
79
|
+
raw.blocker?.evidence ?? record?.dependency?.id ?? 'no inspected specific nonhuman condition'),
|
|
80
|
+
] };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function classify(row, record, at) {
|
|
84
|
+
if (row.raw.completed === true || record?.recordType === 'closing') return 'completed-cleanup';
|
|
85
|
+
if (row.state === 'working' || row.raw.childActivity === 'active') return 'active-worker';
|
|
86
|
+
if (row.state === 'failed') return 'failed-interrupted';
|
|
87
|
+
if (row.raw.paused === true || record?.pause === true
|
|
88
|
+
|| (instant(row.raw.snoozeUntil) && Date.parse(row.raw.snoozeUntil) > at)
|
|
89
|
+
|| (instant(record?.snoozeUntil) && Date.parse(record.snoozeUntil) > at)) return 'explicit-pause';
|
|
90
|
+
if (row.state === 'needs-you' || row.raw.parkedUserReply === true) return 'human-approval';
|
|
91
|
+
if (row.state === 'unknown' || row.raw.childActivity !== 'none') return 'unknown';
|
|
92
|
+
return record?.waitKind ?? 'unknown';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function evaluate(input, mode = 'status') {
|
|
96
|
+
if (!['status', 'recovery', 'cleanup'].includes(mode)) throw Error('unknown mode');
|
|
97
|
+
const saved = normalize(input);
|
|
98
|
+
const result = { schemaVersion: 1, mode, coverage: saved.coverage, cursor: saved.cursor,
|
|
99
|
+
owners: [], continuations: [], cleanup: null };
|
|
100
|
+
if (mode === 'cleanup') result.cleanup = cleanupPlan(input);
|
|
101
|
+
const base = mode === 'recovery' ? activation(input, saved.at) : [];
|
|
102
|
+
for (const row of saved.threads) {
|
|
103
|
+
const record = currentRecord(row.raw, row.owner, input.budget.maxMessages);
|
|
104
|
+
const classification = classify(row, record, saved.at);
|
|
105
|
+
const { raw, inventoryKnown, ...status } = row;
|
|
106
|
+
const facts = { ...status, humanGate: raw.humanGate ?? 'unknown', classification,
|
|
107
|
+
terms: [term('activity-known', row.state !== 'unknown' && ['none', 'active'].includes(raw.childActivity)
|
|
108
|
+
&& raw.requestsKnown === true ? 'pass' : 'unknown', `native run=${raw.run}; children=${raw.childActivity}`)] };
|
|
109
|
+
if (mode === 'recovery') {
|
|
110
|
+
const checked = ownerTerms(input, row, record, saved.at, base);
|
|
111
|
+
facts.terms = checked.terms;
|
|
112
|
+
if (classification !== 'completed-cleanup' && checked.reason
|
|
113
|
+
&& checked.terms.every((entry) => ['pass', 'n/a'].includes(entry.state))) {
|
|
114
|
+
result.continuations.push({ key: checked.key, target: row.owner, reason: checked.reason,
|
|
115
|
+
evidence: [record.authority.evidence, record.nextAction.evidence, raw.blocker.evidence],
|
|
116
|
+
message: 'Revalidate; act only within your existing authorized next action.' });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
result.owners.push(facts);
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
}
|