axstack 0.10.1 → 0.11.1

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.
@@ -1,463 +1,342 @@
1
1
  # Automation sessions
2
2
 
3
- Read this when the current session is an Orca automation running a PR driver or
4
- a watchdog.
5
-
6
- ## Pair identity
7
-
8
- There are two independent automation pairs. Before acting, resolve which pair
9
- this session belongs to from its own run id and the verbatim allowlist in its
10
- own prompt; never assume.
11
-
12
- - **Pair A/B** — driver Automation A (`*/30`) and watchdog Automation B, run id
13
- `20260916-pr-automations`, allowlist `axatbhardwaj/axstack`.
14
- - **Pair C/D** — driver Automation C (hourly) and watchdog Automation D, run id
15
- `20260916-defi-automations`, carrying **two** lists rather than one:
16
- - review allowlist `defi-com/monorepo, defi-com/mobile,
17
- defi-com/azure-next-hybrid`;
18
- - repair allowlist `defi-com/monorepo, defi-com/mobile`.
19
-
20
- `defi-com/azure-next-hybrid` is therefore review-only: its own PRs are
21
- discovered and recorded but never repaired and never pushed to, and they are
22
- not review candidates either, since a PR authored by self is not a peer PR.
23
-
24
- The two pairs share no state: each has its own run id, run directory,
25
- `progress.md` and sidecars, and no sidecar is shared between them. A clause
26
- below that names a pair applies only to that pair; every other clause applies to
27
- both. Where this reference says "the driver" it means the driver of the current
28
- pair. It restates the operational contract of the approved
29
- `docs/specs/orca-automations.md`; that spec is authoritative, and nothing here
30
- widens it. Orca owns scheduling, sessions, retries, and run history. Axstack
31
- owns policy, the run record, and evidence.
32
-
33
- ## Identity and scope
34
-
35
- - **Self** is `gh api user --jq .login`, resolved at the start of every run and
36
- never hardcoded.
37
- - **Own PR:** an open PR authored by self. Authority equals the user at the
38
- keyboard: repair, test, commit, and fast-forward push to the PR branch
39
- (`git push`, no lease or force). `gh stack` sync or restack is out of scope.
40
- - **Peer PR:** an open PR where self is officially review-requested, or where a
41
- PR comment @-mentions self asking for a response. Unsolicited reviews never
42
- happen. Peer code is read-only. A mention counts as a peer request only when
43
- the driver reads the comment and it explicitly asks self to review or
44
- respond. An incidental mention is discovery data and never review authority.
45
- - **Discovery** covers every repository the account can see:
46
- `gh search prs --state open --limit 100` with `--author @me`,
47
- `--review-requested @me`, and `--mentions @me`. A result count equal to the
48
- limit is a detectable truncation and is logged as `error`. Results are
49
- deduplicated canonically by PR URL across the three searches with own-PR
50
- precedence.
51
- - **Mutation allowlist:** stated verbatim in the automation prompt, per
52
- automation. Pair A/B carries one list gating both own-PR repair and peer
53
- review. Pair C/D carries two, a review allowlist and a repair allowlist, and
54
- each action reads its own list: a repository on the review list but not the
55
- repair list is reviewed and never repaired. Where a clause says "allowlisted"
56
- without naming an action — including the precheck's "hash only allowlisted
57
- PRs" — it means the union of the applicable lists for discovery and the wake
58
- fingerprint, and the action-specific list for the action itself. Outside the
59
- allowlist the automation discovers and records only. The precheck writes the
60
- full discovery list to `pending.json`; no review, watch, gate, or other model
61
- work is launched for an external PR. External changes do not enter the wake
62
- fingerprint, so external churn never wakes the session. External PR contents
63
- never produce an escalation. This is a deliberate coverage reduction, not an
64
- implied clean review; discovery errors themselves remain automation-health
65
- findings.
66
- - **Prohibited everywhere:** force-push, rebase, merge, close. A need for any of
67
- these becomes a recorded hold, for all four automations, with no exception.
68
- - **`APPROVE` and `REQUEST_CHANGES`** are prohibited for Automations A, B and D.
69
- Automation C may submit them under "Binding review verdicts (pair C)" below
70
- and nowhere else; a need for either outside that section is a recorded hold.
71
-
72
- ## Roles per mode
73
-
74
- The automation session is the driver and the owner for every PR it handles;
75
- the driver is the automation session itself, with no `axstack-monitor` or
76
- `axstack-owner` role row materialized, and the standalone-owner branches
77
- of `axstack-watch` and `axstack-review` do not fire. `axstack-monitor` stays
78
- an optional read-only observer that never sends.
79
-
80
- - Peer PR → peer mode of [axstack-review](../../axstack-review/SKILL.md):
81
- `axstack-reviewer-primary` and `axstack-reviewer-secondary`, isolated.
82
- - Own-PR repair → authored mode: the repair author is the automation session
83
- (provider `claude`), so the one cross-family reviewer comes from actual
84
- provenance (`axstack-reviewer-primary`, Sol, in `mixed`). A repair delegated
85
- to `axstack-author` (Sol) takes `axstack-reviewer-secondary`.
86
- - "Every mode-required reviewer" means both reviewers in peer mode and the one
87
- selected reviewer in authored mode.
88
- - Gate → `axstack-auditor`.
89
- - Watchdog → `axstack-watchdog`, which never mutates GitHub and performs
90
- exactly one kind of send, a gate-authorized automation-health escalation
91
- recorded in `watchdog.json`.
92
-
93
- ## Reviewer brief and criteria
94
-
95
- Every reviewer brief ends with a required field, exactly:
3
+ Read this when the current session is the native Orca PR **driver** or its
4
+ **watchdog**. The approved contract is
5
+ `docs/specs/pr-automations.md` revision 4; this reference restates the parts an
6
+ automation session must execute and does not widen them.
7
+
8
+ Pair A/B is retired for this contract; its artefacts remain untouched.
9
+
10
+ ## Roles and authority
11
+
12
+ - **driver** — every 15 minutes, fresh Opus session in the dedicated Axstack
13
+ automations worktree. It discovers GitHub work, binds the persistent Orca
14
+ Run, creates one project-local child worktree per selected PR, dispatches the
15
+ matching Axstack agent, reconciles completions and decisions, then exits. The
16
+ driver is the automation session itself, with no `axstack-monitor` or
17
+ `axstack-owner` role row. It never performs review or repair in its own
18
+ session. Its only direct GitHub mutation is an already-approved decision:
19
+ one `gh pr review` or one fast-forward push of a preserved candidate.
20
+ - **watchdog** — hourly at a distinct minute. It is a shell precheck with no
21
+ model, never launches an agent session, never mutates GitHub, and only sends
22
+ the health notifications defined below.
23
+ - `axstack-monitor` remains an optional read-only observer that never sends.
24
+
25
+ Self is resolved on every run with `gh api user --jq .login`; never hardcode
26
+ it. The review allowlist is `defi-com/monorepo`, `defi-com/mobile`, and
27
+ `defi-com/azure-next-hybrid`. The repair allowlist is `defi-com/monorepo` and
28
+ `defi-com/mobile`. State both lists verbatim in the driver prompt. An own PR is
29
+ open, authored by self, on the repair allowlist, and not a draft. A peer PR is
30
+ open, on the review allowlist, authored by someone else, and either officially
31
+ review-requested from self or has a non-self comment that both mentions self
32
+ and asks for review or response. The driver reads and records the comment id
33
+ and its interpretation; incidental mentions are discovery only.
34
+
35
+ Outside the union of both allowlists, record only and exclude the PR from the
36
+ fingerprint. Peer code is read-only, though its worktree may install
37
+ dependencies and run repository tests. Own-PR authority permits repair, test,
38
+ commit, and fast-forward `git push` without lease or force. Force-push, rebase,
39
+ merge, close, every `gh stack` sync/restack/rebase/merge/link/submit action,
40
+ `COMMENT` reviews, and every GitHub write by the watchdog or Hermes are
41
+ prohibited.
42
+
43
+ ## Discovery and wake
44
+
45
+ The bounded driver precheck contains no model and exits 0 only for changed or
46
+ due work.
47
+
48
+ 1. If `cursor.json.tick_started_at` is newer than `tick_done_at` and younger
49
+ than 1 h, append `running` to `precheck.log` and exit 3. This overlap guard
50
+ inspects no terminal.
51
+ 2. Resolve self, then run exactly four `gh search prs --state open --limit 100
52
+ --json url,number,repository,updatedAt` searches: `--author @me`,
53
+ `--review-requested @me`, `--mentions @me`, and
54
+ `--reviewed-by @me --review changes_requested`. A result count equal to 100
55
+ is truncation: append `error`, exit 2, and do not write a fingerprint.
56
+ Deduplicate by URL with own-PR precedence and drop repositories outside the
57
+ allowlist union.
58
+ 3. For each retained PR, read `gh pr view --json headRefOid,baseRefName,isDraft,
59
+ statusCheckRollup,author,latestReviews` and resolve the base SHA with
60
+ `gh api repos/<repo>/commits/<base>`. Own PRs contribute head, base, draft,
61
+ checks reduced to `{name, conclusion|state}` pairs, and the set of
62
+ `latestReviews` `{id, state}` whose `commit` is the head; others contribute
63
+ head and base.
64
+ 4. Debounce peer and fourth-search heads: they enter the hashed subset only on
65
+ the second consecutive precheck that observes them. The first observation
66
+ goes in `pending.json.seen[]`. Own heads enter immediately. Hash this subset
67
+ as the fingerprint.
68
+
69
+ ### Due control work
70
+
71
+ Read `cursor.json` and `decisions/`. Work is due for:
72
+
73
+ - a decision in `approved` or `rejected` without `consumed_at`;
74
+ - a `spent` decision without a receipt, which needs reconciliation;
75
+ - a `deferred[]` entry whose head still matches discovery;
76
+ - an expired repair cap;
77
+ - a dispatch marker older than 3 h;
78
+ - an unsettled Orca delivery in `pending_settlement[]`.
79
+
80
+ An `open` decision is not due. Write `pending.json` with the fingerprint,
81
+ `observed_at`, `seen[]`, full discovery list, and hashed subset. Append
82
+ `<ts> changed|due|unchanged|running|error` to `precheck.log`; exit 0 for
83
+ `changed` or `due`, 1 for `unchanged`, 2 for `error`, and 3 for `running`.
84
+
85
+ ## Driver tick
86
+
87
+ The driver performs this order and exits:
88
+
89
+ 1. If `tick_started_at` is newer than `tick_done_at`, add `previous tick did
90
+ not finish` to `cursor.json.health[]`. Write `tick_started_at`. Match this
91
+ session's `ORCA_TERMINAL_HANDLE` to the driver's `terminalPtyId` from
92
+ `orca automations runs --id <driver>`, then verify the transcript model.
93
+ Non-Opus or unknown identity records a hold and `tick_outcome: held`, and
94
+ dispatches nothing.
95
+ 2. Bind the persistent Run with `orca orchestration run-use` and read the
96
+ inbox. For a `worker_done` matching a live marker, verify the review id at
97
+ the bound head, push range, or opened token. Release the worker; remove its
98
+ child worktree only after confirmed process exit and accepted settlement;
99
+ then clear the marker. Unverifiable delivery stays in
100
+ `pending_settlement[]` and blocks only that PR.
101
+ 3. Consume decisions as their sole consumer under "Decision tokens" below.
102
+ 4. Reconcile every marker older than 3 h. A live worker gets `worker-stop`; an
103
+ exited worker gets `worker-abandon`. Unknown liveness or user takeover
104
+ retains worktree and marker and blocks only that PR. After confirmed
105
+ abandon, remove the worktree, append one health line, increment the head's
106
+ `abandon_count`, and drop that head so normal selection retries once. For a
107
+ review-triggered dispatch, use the marker's `trigger` to remove exactly its
108
+ review id and digest from `processed_reviews[]`. A second abandon at that
109
+ head is a user-owned hold.
110
+ 5. Select changed PRs and matching `deferred[]` entries oldest `updatedAt`
111
+ first. Skip a current-head decision in `open` or `approved`, and skip a live
112
+ marker. Dispatch within the repair and review rules below.
113
+ 6. Promote the `pending.json` fingerprint verbatim, because it records
114
+ observation rather than completion. Write `tick_done_at` and
115
+ `tick_outcome: ok`, then exit.
116
+
117
+ ## Dispatch and repair selection
118
+
119
+ Every selected PR receives one dispatch marker with task id, dispatch id,
120
+ worktree, head, `started_at`, reservation (`verdict` or `repair`), and trigger:
121
+ `{kind: check, name, app_id}` or `{kind: review, review_id, digest}`. There is at
122
+ most one live marker per PR. The marker is the claim shared by scheduled and
123
+ attended sessions; revalidation immediately before an external call is its
124
+ second half. It makes no exactly-once claim against concurrent human GitHub
125
+ activity.
126
+
127
+ An own PR needs repair when either trigger applies:
128
+
129
+ 1. a failing check has a base check-run with the same `name` and the same
130
+ producing `app.id` observed passing through
131
+ `gh api repos/<repo>/commits/<base>/check-runs`; for a legacy commit status,
132
+ its counterpart has the same `context`. A missing, pending, or same-name
133
+ different-app base check holds repair;
134
+ 2. a `CHANGES_REQUESTED` review at the current head, by any account, has both
135
+ a review id absent from `cursor.json.processed_reviews[]` and a SHA-256 body
136
+ digest not recorded for that PR and head. Both keys are required: the same
137
+ finding under a new review id must not re-trigger repair. Record review id
138
+ and body digest when dispatching. A superseded head with a new review
139
+ triggers again subject to the 24 h cap.
140
+
141
+ Repair also requires no deploy-on-push head branch, no live repair cap, and
142
+ selection of the lowest own PR in its stack that needs repair. Create a child
143
+ worktree in that project's clone at the exact head, parented to the driver
144
+ worktree, and dispatch one `axstack-watch` agent in authored repair mode. Its
145
+ brief contains only the triggering checks or review findings. Each open
146
+ descendant records one user-owned `pending restack` hold until it stops needing
147
+ repair. The 24 h cap starts at dispatch and an abandon does not refund it.
148
+
149
+ A debounced peer PR is eligible when self has not reviewed its head. Read
150
+ `gh pr view --json reviews` before dispatch. Whenever any self review with
151
+ state `CHANGES_REQUESTED` exists on the PR, from whichever search it came,
152
+ dispatch only if the latest effective, non-dismissed self review body contains
153
+ the line prefix `<!-- axstack-automation verdict` or its id is listed in
154
+ `cursor.json.legacy_automation_reviews[]`; otherwise record and skip, so a
155
+ human-placed block is never overwritten. A dismissed block and a self-approved
156
+ PR are skipped. Dispatch one `axstack-review` agent in peer mode and link the
157
+ prior review in its brief.
158
+
159
+ Budgets are one `verdict` dispatch per tick, oldest first; at most six `repair`
160
+ markers live across all repositories; and one repair per PR per 24 h. Put every
161
+ eligible PR not dispatched because of a budget in `deferred[]` with repo, PR,
162
+ and head. Budget exhaustion records the count and is not a hold.
163
+
164
+ ## Agents and verdicts
165
+
166
+ Every agent works in its own project-local Orca child worktree at the exact
167
+ head and reports only through the Orca worker protocol.
168
+
169
+ Peer review runs the two isolated configured reviewers on the identical brief,
170
+ then the Luna gate. `APPROVE` requires complete exact-head/base reviews, gate
171
+ `proceed`, and zero validated blockers. `REQUEST_CHANGES` requires the same
172
+ completeness and `proceed`, plus at least one evidenced blocking finding.
173
+ `INCOMPLETE`, unresolved disagreement, unavailable review or gate, or unknown
174
+ GitHub state publishes nothing. Immediately before `gh pr review`, the agent
175
+ re-reads self's reviews at the head and skips with the existing id when one is
176
+ already present, then re-checks head, base, draft, authorship, and allowlist.
177
+ The verdict body ends with this exact marker line:
96
178
 
97
179
  ```text
98
- Escalate to user: yes | no — <criterion> — <reason>
180
+ <!-- axstack-automation verdict head=<sha> -->
99
181
  ```
100
182
 
101
- Criteria, exactly four: a security concern; a permanent on-chain state change;
102
- an architectural change in approach; and automation health (a
103
- model-substitution, session, precheck, discovery, or relay-delivery hold). The
104
- automation health criterion is usable only by the watchdog and the safety-hold
105
- path, never by a reviewer.
106
-
107
- ## Stack-aware repair sequencing (pair C/D)
108
-
109
- These three sections govern pair C/D only. Pair A/B's behaviour is unchanged by
110
- revision 4 except for the named cadence correction, so A does not apply stack
111
- sequencing, bot-triggered repair, the caps, or the deployment push hold.
112
-
113
- Own-PR repair covers every own PR in the allowlisted repositories; stacking
114
- changes the order of repair, never the scope. Within one stack the driver
115
- repairs only the lowest open failing PR of that stack. Every open descendant of
116
- a repaired PR records exactly one `pending restack` hold naming the repaired
117
- parent and its new head SHA, and receives no independent repair of the same
118
- finding while that hold stands. A descendant carrying a different finding from
119
- the repaired parent's is repaired on its own merits, subject to the budgets
120
- below; the hold suppresses duplicate application of the same finding, not all
121
- work on the descendant.
122
-
123
- The hold names the user as owner. It is cleared by the user's own restack, or by
124
- a later tick observing the descendant no longer failing; that is the single
125
- clearing rule. It is exempt from the watchdog's hold with no owner threshold,
126
- because its owner is the user by construction. The per-tick budget counts one
127
- unit per stack, not one per PR.
128
-
129
- The reason is duplication, not politeness: the same finding recurs across a
130
- stack, so independent per-PR repair would apply the identical fix at several
131
- levels and the user's next cascade rebase would then conflict on the duplicate.
132
- A parent fast-forward also does not change a descendant's PR diff, so a
133
- descendant's CI and reviewers never see the parent fix; the hold states that
134
- honestly instead of implying the descendant was repaired.
135
-
136
- `gh stack` sync, restack, rebase, merge, link and submit remain out of scope for
137
- every automation, and force-push and rebase stay prohibited everywhere. A
138
- descendant is held, never rewritten.
139
-
140
- ## Bot review feedback (pair C/D)
141
-
142
- A review authored by a bot account may be actionable and may trigger a repair,
143
- bounded as follows.
144
-
145
- Dedup is by processed review ID and by a digest of the review body. A repair
146
- fires only on a review whose review ID was never processed and whose body
147
- digest is not already recorded against that PR at that head SHA. Review-ID dedup alone is
148
- insufficient: a bot that re-posts the identical finding under a new review ID
149
- would otherwise re-trigger a repair on every tick.
150
-
151
- Caps: at most one repair per PR per 24 hours, counted regardless of trigger; a
152
- per-tick push budget across all allowlisted repositories combined, configured
153
- per pair and six for pair C/D; and, for pair C, a per-tick **review** budget
154
- limiting how many binding verdicts may be submitted in one tick, configured per
155
- pair and **one** for pair C. The review budget exists because C has no `COMMENT`
156
- path, so an unbudgeted first tick over a backlog would place binding verdicts on
157
- every discovered peer PR at once, unattended and on other people's work. Reviews
158
- beyond the budget are left for the next tick, oldest `updatedAt` first so the
159
- backlog drains in a stable order rather than by chance. Reaching any of the three caps records a hold naming the cap
160
- and the value reached, rather than silently dropping the work. A budget hold
161
- names the budget as its owner and is exempt from the hold with no owner
162
- threshold. When a per-PR cap has expired the precheck wakes the driver even
163
- if the forge is unchanged, so capped work is never stranded; the precheck
164
- section below carries that trigger in its due-work list.
165
-
166
- A bot review never satisfies the peer-PR trigger, which still requires self to
167
- be officially review-requested or a comment that explicitly asks self to review
168
- or respond.
169
-
170
- ## Watch window
171
-
172
- Pair A/B keeps its 24 hours per own PR from first observation, ending early on
173
- merge or close, with the deadline in `cursor.json`, the `expired` marking, and
174
- the user re-arming an expired PR in the automation session.
175
-
176
- Pair C/D uses a rolling window instead: a PR is in scope while it is open and
177
- eligible, and leaves scope on merge or close. There is no expiry and no
178
- re-arming, C/D's `cursor.json` stores no deadlines, and `expired` is not a state
179
- a C/D PR can reach.
180
-
181
- The difference is deliberate, not drift. A serves one low-volume repository
182
- where expiry is cheap, while C would otherwise start twenty or more simultaneous
183
- clocks on first observation and go dark a day later. Because that window
184
- removes expiry as a cost brake, the per-PR and per-tick budgets and the watchdog
185
- are the only brakes left on C, and D's thresholds are retuned for a fingerprint
186
- that legitimately changes on nearly every tick.
187
-
188
- ## PR eligibility for repair (pair C/D)
189
-
190
- A draft PR is discovered and recorded but never repaired; it becomes eligible
191
- when it is marked ready for review, which the ordinary event state observes as
192
- new work. A PR that already has a human reviewer requested is eligible for
193
- repair and is not excluded, deliberately: most own PRs in these repositories
194
- carry a requested human reviewer, and excluding them would empty the coverage.
195
- `defi-com/mobile` has no workflows and therefore no check signal, so a repair
196
- there is triggered only by actionable review feedback; if CI is later added the
197
- ordinary check-rollup trigger applies with no contract change.
198
-
199
- ## Deployment safety (pair C/D)
200
-
201
- A repair never pushes to a PR whose head branch is in that repository's
202
- deploy-on-push set, and an attempt to do so is a recorded hold. The rule is
203
- branch-name-agnostic: the set is enumerated per repository from that
204
- repository's workflow files, recorded, and re-verified before enabling and on
205
- any later allowlist change. It is never hardcoded to one branch name, because a
206
- repository may deploy from more than one branch and may add another at any time.
207
-
208
- ## Binding review verdicts (pair C)
209
-
210
- Automation C submits a real review verdict where pair A/B publishes a `COMMENT`.
211
- The authority is narrow and every bound below is load-bearing.
212
-
213
- Every review Automation C publishes is a binding verdict. C has no `COMMENT`
214
- path: a peer PR reached through the qualifying mention trigger gets a verdict
215
- exactly as an officially-requested one does. The only restriction is authorship
216
- — GitHub rejects a verdict from a PR's own author, so an authored-mode review of
217
- C's own repair stays internal and gates only the push. Automation D never writes
218
- to GitHub at all.
219
-
220
- `APPROVE` requires a complete mode-required review at the exact head SHA with a
221
- current base, the gate's `proceed`, and zero unresolved validated blocking
222
- findings. `REQUEST_CHANGES` requires the same completeness and gate token and at
223
- least one validated blocking finding carrying its evidence and consequence; it
224
- is the verdict that reports blockers and is not gated on their absence.
225
- `INCOMPLETE`, unresolved material disagreement, an unavailable required
226
- reviewer, or a gate `escalate` submits nothing and records a hold.
227
-
228
- ### Blocking obligations
229
-
230
- A submitted `REQUEST_CHANGES` blocks the PR and a teammate's later push does not
231
- clear it. Submitting a review also removes self from GitHub's `review-requested`
232
- results, so a PR C has just blocked can leave discovery on the next tick and C
233
- would never see it again to clear its own block.
234
-
235
- C therefore records every unresolved blocking review in `cursor.json` as an open
236
- obligation holding the PR URL, the review id, the head SHA it was bound to, and
237
- the submission timestamp, and polls those PRs directly by URL every tick
238
- regardless of discovery eligibility. An obligation is discharged only by
239
- observing the PR merged, closed, or its blocking review resolved or superseded.
240
- A failed poll retries next tick; three consecutive unpollable ticks is a health
241
- finding routed to the gate.
242
-
243
- While C holds any unresolved blocking review, every scheduled tick wakes it: an
244
- outstanding obligation is due control work in the precheck's list, alongside a
245
- watch deadline, a pending failed-relay retry, and an expired repair cap.
246
-
247
- An obligation carries a narrow authority to resolve itself: C may submit exactly one superseding verdict on that PR,
248
- including a clearing `APPROVE`, without a fresh review request. It follows the
249
- obligation chain — an obligation opened by a verdict submitted under this
250
- authority inherits it — so the authority persists on that one PR until it
251
- merges, closes, or its block is resolved, and the deadlock does not reappear one
252
- head later when a teammate pushes without fixing. It extends to nothing else: not
253
- another PR, not a discharged obligation, and never more than one superseding
254
- verdict per head SHA. A supersede whose head and base are both unchanged
255
- additionally requires a recorded finding-level reason naming what changed about
256
- the finding; without it C holds and submits nothing.
257
-
258
- ### Local review file
259
-
260
- Every review C publishes, verdict or `COMMENT`, is also written to the
261
- workspace review directory `~/defi/misc/reviews/`, carrying the same findings
262
- and evidence as the submitted review plus the reviewed SHA. The naming is the
263
- workspace convention and the prefix rules are load-bearing, because PR numbers
264
- collide across repositories: `review-PR-<num>.html` with no prefix means
265
- `defi-com/monorepo` by definition, and every other repository takes its prefix,
266
- `review-azure-next-hybrid-PR-<num>.html` and `review-mobile-PR-<num>.html`. A
267
- failure to write the file is a recorded hold; a review is never withheld or
268
- retracted because the file could not be written, so the mismatch stays visible
269
- rather than silent.
270
-
271
- ## Escalation gate
272
-
273
- After every mode-required reviewer settles, the driver spawns `axstack-auditor`
274
- with the verdicts and the candidate revision, instructing it to act as the
275
- escalation gate. It returns exactly one literal token, `escalate` or `proceed`.
276
-
277
- - The gate decides only whether the user is notified. Reviewer "yes" is input,
278
- not a veto.
279
- - `escalate` → records the hold, then one `hermes send` through
280
- [axstack-relay](../../axstack-relay/SKILL.md) naming the PR, the criterion,
281
- every reviewer's reason, where the user acts (Orca conversation, worktree, or
282
- PR), and the hold; it publishes nothing.
283
- - `proceed` → no notification; a push, a `COMMENT` publication, or an
284
- `APPROVE` then additionally requires no unresolved validated blocking finding,
285
- because `proceed` never overrides a validated blocking finding. A
286
- `REQUEST_CHANGES` is the exception and the only one: it is the publication
287
- that reports validated blocking findings, so it requires at least one and is
288
- never gated on their absence. A reviewer security
289
- "yes" that the gate does not escalate is recorded as rejected-with-evidence
290
- or returned to the author before any mutation.
291
- - Only `proceed` plus no unresolved validated blocking finding permits a push,
292
- a `COMMENT` publication or an `APPROVE`; a `REQUEST_CHANGES` needs `proceed`
293
- plus at least one such finding. A push or publication before the gate settles
294
- is forbidden either way.
295
- - Precedence: credible serious risk found by a reviewer still produces the
296
- standing internal prompt and dependent-action hold immediately, and the gate
297
- governs only external notification. That hold is the serious-risk rule of
298
- [contracts](contracts.md#serious-risk). The internal prompt lands in the run
299
- record and the automation's Orca conversation; no `hermes send` occurs
300
- without `escalate`.
301
- - The health gate takes a watchdog finding and its evidence, not reviewer
302
- verdicts or a candidate; the same two tokens apply.
303
- - An unavailable gate or required reviewer records a hold, pauses mutation for
304
- that PR, and is treated as a watchdog health finding. An unavailable gate
305
- cannot be escalated through itself: the hold stays, the gap is visible in
306
- Orca run history and the sidecar, and no substitute or unauthorized send
307
- occurs.
308
-
309
- ## Precheck (bounded shell, no model)
310
-
311
- Resolve self; run the three searches with `--json url,number,repository,updatedAt`;
312
- write the full discovery list to `pending.json`. For each allowlisted,
313
- non-expired own PR add head SHA, base SHA, and the check rollup of that head via
314
- `gh pr view --json headRefOid,baseRefOid,statusCheckRollup`; check state is
315
- data, and only authentication, command, and network errors are `error`. For pair
316
- C/D only, that call also requests `isDraft` and the precheck adds draft status
317
- to the hashed fingerprint, so a draft becoming ready wakes the driver on its
318
- own; pair A/B's queried fields and fingerprint are unchanged. Hash
319
- only allowlisted PRs. Read `cursor.json` for the last processed fingerprint and
320
- for due control work: a watch deadline at or before now (pair A/B only, since
321
- pair C/D stores no deadlines), a pending failed-relay retry, a per-PR repair cap
322
- that has expired, or an outstanding blocking-review obligation (both pair C/D
323
- only, since pair A/B has neither caps nor binding verdicts). Exit 0 when the hash differs or control work is due;
324
- otherwise exit non-zero. Exit non-zero without running when the previous driver
325
- run is still active, or on `error`. Append one line
326
- `<ts> <changed|due|unchanged|error|busy>` to `precheck.log`.
327
-
328
- Terminal hygiene runs before the searches and covers driver terminals only.
329
- Ownership is the driver automation's own recorded `terminalPtyId` from its Orca
330
- run history, never a terminal title: Orca rewrites a Claude terminal's title to
331
- the agent's current task summary, so a driver's title drifts and an unrelated
332
- session can acquire one that reads like a driver. The precheck closes the
333
- previous ticks' idle driver terminals with `--tab` — without it the pane closes
334
- but the session stays listed and is never reclaimed, which also makes an
335
- over-match destructive — and a driver terminal that is still working makes the
336
- tick `busy`. It never closes a watchdog terminal or any terminal outside this
337
- automation; an unreadable ownership source is `error`, never a silent empty
338
- sweep. No two of the four automations may share a dispatch minute: A, B, C and D each
339
- take a distinct minute, so a driver and its watchdog never collide and neither
340
- pair can disturb the other's terminal hygiene.
341
-
342
- The observed fingerprint is written to `pending.json`. After the processed
343
- tick the driver promotes exactly that value to `cursor.json`, never a
344
- recomputed one, so an event landing during a run is processed on the following
345
- tick.
183
+ Write the local review file to the workspace review directory
184
+ `~/defi/misc/reviews/` under the existing convention:
185
+ `review-PR-<num>.html` with no prefix means `defi-com/monorepo`;
186
+ `review-mobile-PR-<num>.html` and
187
+ `review-azure-next-hybrid-PR-<num>.html` name the other repositories. A write
188
+ failure is recorded but does not withhold the verdict.
346
189
 
347
- ## Driver tick
190
+ Authored repair commits a local candidate, obtains one Sol review at that
191
+ local SHA and the Luna gate, resolves every validated blocker, records test
192
+ evidence, re-reads remote head/base/draft/deploy set/allowlist, then pushes
193
+ fast-forward. The monorepo unit gate uses `~/.bun-1.2.2/bin/bun`; a suite that
194
+ cannot run locally is an explicit unverified boundary.
348
195
 
349
- Before any repair or publication the driver validates its effective session
350
- identity through Orca runtime inspection and records it; a self-written label
351
- is not evidence. Expected model: Opus. A mismatch or unknown identity holds
352
- repair and publication for that run and is a health finding.
353
-
354
- For each changed PR:
355
-
356
- - Own PR, and only when its repository is on the acting automation's repair
357
- allowlist → [axstack-watch](../../axstack-watch/SKILL.md) on the exact head
358
- SHA in a per-PR child worktree; the driver worktree never checks out a PR
359
- branch, and an own PR in a review-only repository is recorded and nothing
360
- else, with no repair, no worktree and no push. Follow its repair-publication reference: candidate committed locally,
361
- authored review at the local SHA, gate, then fast-forward push with the
362
- publication readback immediately before it.
363
- - Peer PR → two isolated `axstack-review` passes on the exact head SHA, then
364
- the gate, then one owner-synthesized review bound to the reviewed commit. For
365
- pair A/B that is a `COMMENT` under the review skill's COMMENT branch; for
366
- every pair C peer PR it is a binding verdict under "Binding review verdicts
367
- (pair C)" above, whether the PR was reached by an official review request or
368
- by the qualifying mention trigger. `INCOMPLETE` or an
369
- unavailable required reviewer records a hold and publishes nothing.
370
-
371
- Watch window, pair A/B only: 24 hours per own PR from first observation, ending
372
- early on merge or close. The deadline is stored in `cursor.json`; a due deadline
373
- wakes the driver through the precheck even when GitHub is unchanged, and the
374
- driver rechecks the deadline immediately before any publication. Expiry marks
375
- the PR `expired` in the record and sidecar, records a resumable handoff, and
376
- stops silently. The driver skips an expired PR until the user re-arms it in the
377
- automation session, and the precheck ignores it; an expired PR is never silently
378
- re-adopted. Pair C/D does not use this window at all; see "Watch window" above
379
- for its rolling replacement, and it stores no deadline and reaches no `expired`
380
- state.
381
-
382
- Per-PR event state: head SHA, base SHA, check rollup, and processed request and
383
- comment IDs. A review receipt is reused only when head, base, and scope are
384
- unchanged. A new failing check, base change, review request, or qualifying
385
- comment at an unchanged head is new work. Pair C/D additionally carries draft
386
- status in that state, and a draft status that has changed from true to false is
387
- new work for C/D, which is how a draft becoming ready for review reaches its
388
- driver.
389
-
390
- ## Watchdog tick
391
-
392
- Before its gate dispatch the watchdog validates its own effective session
393
- identity through Orca runtime inspection; unknown identity holds the dispatch
394
- and is itself recorded in `watchdog.json`.
395
-
396
- Read Orca run history for the driver, `precheck.log`, and the run record.
397
- Thresholds: three consecutive `error` lines in `precheck.log`; three
398
- consecutive failed driver runs; no successful driver run within two hours while
399
- the precheck logged `changed` or `due`; any unrequested fresh-session fallback
400
- or non-Opus effective identity recorded by the driver; any `failed` relay
401
- receipt older than one tick or any `uncertain` receipt; a hold with no owner.
402
- A quiet precheck history with no due work is healthy.
403
-
404
- The two-hour stall threshold above is pair A/B's. Pair D uses a stall window
405
- re-derived before enabling as `max(2h, 3 x the 95th-percentile observed C tick
406
- duration over at least 10 ticks)`, recorded with its sample, because C's
407
- fingerprint legitimately changes on nearly every tick and a literal two hours
408
- would fire on the first slow tick. Every other threshold is identical for both
409
- pairs.
410
-
411
- Each health finding gets an occurrence id `(type, first-observed UTC
412
- timestamp)`; it stays deduplicated while unresolved, and a later recurrence is
413
- a new occurrence. Pass a finding to the gate under the automation-health
414
- criterion. On `escalate` the watchdog itself performs that one gate-authorized
415
- `hermes send` and records the receipt in `watchdog.json`; it never mutates
416
- GitHub and never writes `progress.md` or `cursor.json`. Failed or uncertain
417
- delivery stays visibly held in `watchdog.json` and Orca run history.
418
-
419
- ## Run record and sidecar
420
-
421
- One run id per pair for the lifetime of that pair — `20260916-pr-automations`
422
- for A/B and `20260916-defi-automations` for C/D — each in the
423
- [run record](run-record.md) shape with that pair's driver as sole writer of its
424
- own `progress.md`. C/D's run directory lives under the same axstack
425
- `git-common-dir` as A/B's, in its own `axstack/runs/<run id>/` folder; no
426
- sidecar, record, or cursor is shared between the pairs. Per PR it stores processed event IDs, exact head and base SHAs, review receipts
427
- per SHA, gate decisions, `hermes send` receipts with `message_id` and state, and
428
- holds. The watch deadline and `expired` fields are pair A/B only, since pair C/D
429
- stores no deadline and cannot reach `expired`; draft status is pair C/D only,
430
- since only C/D treats a draft transition as new work. Its
431
- `Notification policy:` line reads, verbatim:
196
+ Every reviewer brief ends exactly:
432
197
 
433
198
  ```text
434
- hermes send, target telegram (home), host VPS, gate-authorized escalations only
199
+ Escalate to user: yes | no — <criterion> — <reason>
435
200
  ```
436
201
 
437
- Machine-readable sidecars in the same directory: `pending.json` (observed
438
- fingerprint plus full discovery list, written by the precheck), `cursor.json`
439
- (last processed fingerprint promoted verbatim from `pending.json`, expired PR
440
- list and per-PR watch deadlines for pair A/B only, outstanding blocking-review
441
- obligations for pair C only — each holding PR URL, review id, bound head SHA,
442
- submission timestamp and whether its one supersede is still available — pending
443
- failed-relay retries; written by the driver after each processed tick), `precheck.log` (precheck only), and
444
- `watchdog.json` (watchdog only). Orca run history remains the authoritative
445
- log; the record is derived progress, never authority.
446
-
447
- Dedup: a processed event ID is never processed twice; a review receipt is
448
- reused only for an unchanged head, base, and scope. PR notifications dedup on
449
- (PR, criterion, head SHA); health notifications dedup on the occurrence id. A
450
- `failed` relay receipt may be retried once, on the next tick, as due control
451
- work; an `uncertain` one is never auto-resent.
202
+ PR work has exactly three criteria: a security concern, a permanent on-chain
203
+ state change, or an architectural change in approach. There is no
204
+ automation-health criterion for reviewers. The Luna gate returns exactly one
205
+ token, `escalate` or `proceed`; there is no gate for health findings.
206
+ `escalate` opens a decision token, sends its message, and exits without waiting
207
+ for a reply. `proceed` does not override a validated blocker. `worker_done`
208
+ names the PR, head, action, GitHub receipt or opened token. The driver alone
209
+ writes the run record.
210
+
211
+ ## Decision tokens
212
+
213
+ Store `decisions/<token>.json`, where `<token>` contains at least 96 random
214
+ bits as lowercase hex, produced for example by `openssl rand -hex 16`.
215
+ Immutable bound fields are written once: `repo`, `pr`, `head`, `base`,
216
+ `action` (`approve-verdict`, `request-changes-verdict`, or `push`), plus exact
217
+ `body` and `commit` for verdicts or `candidate_sha`, `head_branch`, and
218
+ `expected_remote_head` for a push. Mutable fields are `state`, `created_at`,
219
+ `decided_at`, `decided_message_id`, `consumed_at`, `receipt`, `send`, and
220
+ `reason`. Never delete a token file.
221
+
222
+ Lifecycle has one named writer per transition, each by temp file + rename:
223
+
224
+ | transition | writer |
225
+ | --- | --- |
226
+ | create `open` | the agent that escalated |
227
+ | `open → approved` / `open → rejected` | the Hermes script only |
228
+ | `approved → spent` / `approved → stale` | the driver only |
229
+ | `rejected → closed` | the driver only |
230
+ | `spent` + `receipt` | the driver only |
231
+
232
+ ### Opening
233
+
234
+ Before opening a `push` token, the repair agent pins its candidate with local
235
+ ref `refs/axstack/decisions/<token>` in the project clone, so the candidate
236
+ survives worktree removal. It then sends one
237
+ `hermes send --to telegram` message naming the PR, criterion, every reviewer's
238
+ reason, and the exact replies `approve <token>` and `reject <token>`. Store the
239
+ send receipt. The driver retries a `failed` send once next tick and reconciles
240
+ an `uncertain` send against Hermes output before any retry.
241
+
242
+ The Hermes gateway's fixed `axstack-decide` script accepts only those two exact
243
+ commands. It validates the private user/channel configuration on every call,
244
+ restricts tokens to `^[0-9a-f]{24,}$`, locks and re-reads an `open` token,
245
+ writes only its decision fields by temp file and rename, and prints one line.
246
+ Hermes never runs `gh`, `git`, or `orca`.
247
+
248
+ ### Consuming
249
+
250
+ The driver is the only consumer. Immediately before acting it re-reads that
251
+ `state == approved`, then revalidates open/unmerged state, bound head/base,
252
+ absence of a self review for a verdict, or expected remote head plus reachable
253
+ candidate for a push. It writes `spent` before the GitHub call, executes
254
+ exactly the bound action with no second gate, then stores the receipt. A spent
255
+ token without a receipt is
256
+ reconciliation: match the exact review commit/body or destination
257
+ ref/candidate on GitHub; record a match, or the driver retries once under the
258
+ same approval after proving non-execution, or hold ambiguity. A revalidation
259
+ failure writes `stale`
260
+ with the reason and drops the PR's head from `cursor.json`; the driver never
261
+ mints a token. Close rejected files and skip that head. After `spent` or
262
+ `stale`, delete the candidate ref. Tokens do not expire; the watchdog reports
263
+ one open longer than 24 h.
264
+
265
+ ## Watchdog
266
+
267
+ The hourly shell precheck only reads `cursor.json`, `precheck.log`,
268
+ `decisions/`, and `orca automations runs --id <driver>`. It performs exactly
269
+ these four checks and always exits non-zero, so no model session launches:
270
+
271
+ | check | trips when |
272
+ | --- | --- |
273
+ | driver stuck | a `changed` or `due` precheck is older than 1 h with no later `tick_done_at`, including a driver that never wrote `tick_started_at` (Orca run status alone is not evidence of completion) |
274
+ | precheck failing | the last three `precheck.log` entries are `error` |
275
+ | worker stuck | a dispatch marker is older than 3 h 30 min and remains uncleared |
276
+ | decision waiting | an `open` decision is older than 24 h |
277
+
278
+ Each trip is `(check, first_observed)`. Write one JSON line per tick to
279
+ `watchdog.log` with `{ts, checks, trips, sent}` and send each occurrence once
280
+ through `hermes send --to telegram`. Persist `sent`, `failed`, or `uncertain`;
281
+ retry `failed` next tick and reconcile `uncertain` before retry. Re-send only
282
+ after the check was observed clear and later recurs. Unreadable evidence is an
283
+ `unknown` occurrence. A healthy tick writes its line and sends nothing. There
284
+ is no gate for health findings.
285
+
286
+ ## Run directory
287
+
288
+ One `.git/axstack/runs/<run id>/` directory under the Axstack git-common-dir
289
+ contains:
290
+
291
+ - `cursor.json` — driver only, with these exact keys: `fingerprint`,
292
+ `tick_started_at`, `tick_done_at`, `tick_outcome`, `prs{url: {head, base,
293
+ draft, checks, reviews, last_self_review}}`, `dispatch_markers[]` (`pr`,
294
+ `task_id`, `dispatch_id`, `worktree`, `head`, `started_at`, `reservation`,
295
+ `trigger`), `deferred[]`, `pending_settlement[]`,
296
+ `repair_caps{url: {expires_at}}`, `abandon_count{head: n}`,
297
+ `processed_reviews[]` (`review_id`, `pr`, `head`, `digest`),
298
+ `deploy_on_push{repo: [branches]}`,
299
+ `legacy_automation_reviews[]`, `health[]`;
300
+ - `pending.json`, `precheck.log` — driver precheck only;
301
+ - `decisions/<token>.json` — writers assigned by the lifecycle table;
302
+ - `watchdog.log` and `watchdog-state.json` (occurrence `first_observed` values
303
+ and send receipts) — watchdog only;
304
+ - `progress.md` — driver only, one line per tick plus holds and mention
305
+ readings, with no per-PR prose.
306
+
307
+ Timestamps are UTC `YYYY-MM-DDTHH:MM:SSZ`; an unparsable timestamp is an
308
+ `error` for the precheck and `unknown` for the watchdog, never silently
309
+ ignored.
310
+
311
+ Orca run history is the authoritative log. The launch worktree is a dedicated
312
+ Axstack worktree where nobody develops. The current `defi-automations`
313
+ worktree retires with the old pair. Keep this notification-policy edge in the
314
+ run record: `Notification policy` authorizes the token and watchdog sends;
315
+ delivery uses [axstack-relay](../../axstack-relay/SKILL.md).
452
316
 
453
317
  ## Safety holds
454
318
 
455
- - The driver records its effective identity on every tick. An unrequested
456
- fresh-session fallback, or a recorded identity different from the expected
457
- one, pauses mutation for that run, is recorded, and goes to the watchdog
458
- path. A user-run `--fresh-session` reconciles from the run record and is not
459
- a hold.
460
- - GitHub API errors leave the PR state unknown; nothing is pushed or published
461
- on unknown state.
462
- - A hold is cleared only by a later run observing the condition resolved, or by
463
- the user in the Orca conversation. Silence never clears a hold.
319
+ - Non-Opus or unknown driver identity records a hold and dispatches nothing.
320
+ - A GitHub API error makes PR state unknown; never publish or push for it.
321
+ - Enumerate deploy-on-push branches from both repair repositories before
322
+ enabling and store them in `cursor.json`; re-check on allowlist changes.
323
+ - A later tick observing resolution or an explicit user decision clears a
324
+ hold. Silence never clears one.
325
+
326
+ ## Cutover
327
+
328
+ Perform this order: the new pair exists disabled; the amended skills and
329
+ references are installed; C and D are disabled; every old driver and worker
330
+ attempt is reconciled to confirmed settlement and each unfinished candidate
331
+ is preserved; the old worktree is removed; the old run directory is made
332
+ read-only; the new pair is enabled at distinct minutes; the first real driver
333
+ tick is recorded. A failure leaves the new pair disabled, and both pairs never
334
+ run together. Historical artefacts are untouched.
335
+
336
+ ## Exclusions
337
+
338
+ No obligations table, supersede counter, or review-budget hold. No `COMMENT`
339
+ reviews. No watch deadline or `expired` state. No terminal hygiene or global
340
+ busy guard. No terminal nudge from Hermes. No Hermes access to `gh`, `git`, or
341
+ `orca`. No re-review of human-placed blocks. No per-project state. No changes
342
+ to retired artefacts. No gate for health findings.