axstack 0.34.1 → 0.34.3

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/workflows.md CHANGED
@@ -134,6 +134,7 @@ Antigravity and Grok routes.
134
134
  The mixed `axstack-auditor` uses Haiku 5.5 xhigh, pinned to `claude-haiku-5-5`;
135
135
  missing that ID or xhigh effort holds without substitution. The mixed
136
136
  `axstack-explainer-review` uses Sol medium; `axstack-explainer` stays Sonnet high.
137
+ In `claude-only`, `axstack-auditor` uses the Claude Sonnet class at high effort.
137
138
  The new `-sol` auditor, research-code, and explore-execution seats use Sol high
138
139
  in `mixed` and `codex-only`; `claude-only` records each as intentionally absent.
139
140
  Dispatch the base and Sol seats independently on the same brief, then reconcile
@@ -259,7 +260,12 @@ for explicit ownership transfer.
259
260
  archive the exact eligible thread, then remove its exact worktree without
260
261
  force and delete only eligible local branches. T3 metadata actions do not
261
262
  remove worktrees. Accepted writer or delegated completion settles thread
262
- metadata only. Author threads and worktrees remain retained and unarchived
263
+ metadata only after receipt acceptance, which is distinct from T3 sidebar
264
+ settlement. Every writer follow-up, including rebase, base update, conflict,
265
+ body and receipt changes, follows same-turn settlement and readback under
266
+ [Settlement](../skills/axstack/references/workspace-hygiene.md#settlement).
267
+ Fresh native run inventory gates zero-unsettled and cadence claims; incomplete
268
+ inventory is UNKNOWN. Author threads and worktrees remain retained and unarchived
263
269
  until their PR merges or closes; the current pass, unsettled descendants,
264
270
  and user-taken-over threads remain protected.
265
271
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "axstack",
3
- "version": "0.34.1",
3
+ "version": "0.34.3",
4
4
  "description": "Axstack installer and setup CLI: installs owned chat skills and role data, configures supported harness settings, and checks T3 Code capabilities.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -34,8 +34,8 @@
34
34
  "scripts": {
35
35
  "lint": "biome lint . --error-on-warnings",
36
36
  "typecheck": "bun node_modules/typescript/bin/tsc --noEmit --project jsconfig.json",
37
- "test": "bun test",
38
- "test:all": "bun test"
37
+ "test": "bun skills/axstack/scripts/test-repository.js",
38
+ "test:all": "bun skills/axstack/scripts/test-repository.js"
39
39
  },
40
40
  "license": "MIT",
41
41
  "devDependencies": {
@@ -71,7 +71,7 @@ Missing ordering or ownership evidence holds admission and never guesses a winne
71
71
 
72
72
  Once identified as a duplicate, the new pass does no PR work, makes no further
73
73
  shared-record write and touches no PR-job thread or descendant owned by the
74
- live manager. A duplicate's only mutation is pass-start settlement of eligible
74
+ live manager. Besides its private pass note and authorized notification, a duplicate's only shared mutation is pass-start settlement of eligible
75
75
  terminal lane pass threads under [Finite-session teardown](#finite-session-teardown).
76
76
  A duplicate pass admits nothing and runs read-only discovery
77
77
  into its own pass note in its private evidence folder, recording new eligible
@@ -330,13 +330,16 @@ decision interpreter; the driver maps card replies in its prose checks.
330
330
 
331
331
  ## Finite-session teardown
332
332
 
333
+ Decisions outliving a finite manager pass retain their receipt and hold in durable continuity, with a GitHub user action route or explicit ownership transfer.
334
+ Use the T3 action route for such decisions; a reply to a closed pass never clears a hold, and a successor reissues a card only after reconciling the retained receipt and current authority.
335
+
333
336
  After lane reconciliation at pass start, every pass, including a duplicate,
334
337
  uses `t3_thread_organize` to settle finished predecessor and duplicate lane pass
335
338
  threads as metadata only, before discovery or admission.
336
339
  Settle another lane pass thread only with terminal run evidence, no unsettled
337
- descendants, no recorded open hold naming it, no user takeover, and exclusion of
340
+ descendants, zero open holds naming it, recorded or directly observed, no user takeover, and exclusion of
338
341
  the live owner and current pass.
339
- Leave held or stuck pass threads unsettled.
342
+ Keep held passes, wedged passes and stalled owners unsettled and visible.
340
343
  Settlement is separate from owner-only worktree removal, branch deletion and
341
344
  continuity writes.
342
345
 
@@ -352,7 +355,7 @@ Then run the driver-start orphan sweep under Workspace hygiene; the sweep is
352
355
  silent when nothing was removed. Record sweep results and holds in continuity's
353
356
  Open holds table.
354
357
  The orphan sweep covers the run record's repositories plus registered repositories on this host.
355
- Retirement settles remaining duplicate threads with `t3_thread_organize`
358
+ Retirement settles remaining predecessor, duplicate and failed-successor pass threads with `t3_thread_organize`
356
359
  (settle/archive changes metadata only) before separate guarded Git worktree removal.
357
360
  Unknown, active or user-taken-over threads,
358
361
  ambiguous publication and failed salvage stay preserved.
@@ -242,6 +242,11 @@ With an unsettled launched thread, the driver turn must end only while a bound
242
242
  its ID recorded. Each wake must reconcile all unsettled runs, including a
243
243
  writer that died without sending; failed runs hold incomplete work. The watch
244
244
  inherits the driver model/workspace and adds no runtime of Axstack's own.
245
+ T3 queues a scheduled wake even while the driver is busy, so copies pile up.
246
+ At the start of each run-watch wake, the driver must cancel every other queued
247
+ copy of its own run-watch prompt with `t3_queue_list` and `t3_queue_cancel`,
248
+ keeping every other queued message, including user and worker messages.
249
+ One wake reconciles all those copies.
245
250
 
246
251
  Once nothing remains unsettled the driver must `delete_scheduled_task` for the
247
252
  run watch and use `list_scheduled_tasks` to read back its absence; an uncertain
@@ -44,6 +44,15 @@ On forge-confirmed merge or closure, follow [Native PR links and watches](t3-run
44
44
  for same-turn author settlement and its required readback.
45
45
  A repair turn automatically un-settles the thread, making it visible while working.
46
46
  The driver settles the thread again after the next accepted repair completion.
47
+ Every writer follow-up, including rebase, base update, conflict, body and receipt changes, counts as repair for same-turn settlement and readback.
48
+ Immediately before settlement require native state proving the accepted run is current and the latest overall run.
49
+ Before settlement verify zero active, pending or queued work and zero unsettled descendants.
50
+ After settlement verify exact thread/run identity, current activity and protection state; a mismatch or missing readback holds only that dispatch's next advancement.
51
+ These checks are best effort, not atomic; never issue a compensating unsettle.
52
+ Successful receipt acceptance is distinct from T3 sidebar settlement metadata.
53
+ Cancellation disposition is separate from successful acceptance and requires native terminal cancellation, exact attempt identity, a driver reason and preservation pointers.
54
+ Cancellation disposition requires zero live or queued work, zero unsettled descendants, zero user takeover, zero unanswered questions, zero QUESTION or FAILED markers and zero serious-risk holds.
55
+ Never cancel solely to make a thread eligible for settlement.
47
56
  Threads with unaccepted completion, FAILED or QUESTION markers, held, failed or interrupted runs, or anything needing the user stay unsettled and visible.
48
57
  Retain a user-taken-over T3 thread and never send cleanup commands to it, including `t3_thread_organize` settle or archive.
49
58
  Never remove the current driver, current pass, an active or unknown thread, an unsettled descendant, or a resource with ambiguous ownership.
@@ -52,6 +61,8 @@ At final settlement include run-owned worktrees in other repositories of the
52
61
  same project and report each remaining resource as a hold with its reason. Recorded
53
62
  projectId, threadId/runId, delegated taskId/childThreadId/childRunId, attempt key,
54
63
  checkout path and revision decide ownership. Idle alone never proves exit.
64
+ Before any zero-unsettled claim, cadence relaxation, handback or Close-out, read a fresh fully paginated native inventory of run-owned writers, delegated children and known orphans, including already-settled threads with unconsumed results.
65
+ Incomplete inventory or unknown ownership records UNKNOWN and holds only the affected claim or transition.
55
66
 
56
67
  Read back the durable receipt and evidence manifest before removing source copies or the worktree; missing or differing readback holds removal.
57
68
  Evidence already outside the checkout needs no copy; read back its receipt and
@@ -12,14 +12,16 @@ Example: bun dispatch-plan.js --role axstack-author --roles roles.json --capabil
12
12
  --capabilities Saved orchestrator_capabilities JSON; reuse the run's saved snapshot
13
13
  --picker Saved picker JSON; uses chosenInstanceId
14
14
  --kind task emits target; launch emits modelSelection
15
- --verify Compare t3_thread_configuration JSON; print MATCH or each mismatch
16
- --out Write full JSON here and print input/path/omitted-count summary
15
+ --verify Compare t3_thread_configuration JSON; print evidence then MATCH or each mismatch
16
+ --out Save the payload as full JSON here; stdout is an input/path/omitted-count summary
17
17
  --help Show all flags and exit codes
18
18
 
19
19
  Read-only inputs: reads supplied JSON, spawns adjacent resolve-models.js, writes stdout/stderr.
20
20
  Writes only the caller's --out path when supplied; no retained state.
21
21
  Results name input paths. Plan output over 4096 bytes requires --out.
22
22
  Large verification retains its exit code, prints mismatches and advises --out on stderr.
23
+ An unwritable --out still prints verification mismatches and retains exit 2.
24
+ An omitted provider label is unknown; exact instance/model/options/mode checks still apply.
23
25
  Never dispatches or changes snapshots. Reuse recorded exact model pins as given.
24
26
  Exit codes: 0 ok, 1 error, 2 hold (including incomplete read-back).
25
27
  `;
@@ -76,7 +78,10 @@ function verify(configuration, instanceId, provider, model, options) {
76
78
  compare('options', options, selection?.options,
77
79
  normalizedOptions(options) === normalizedOptions(selection?.options));
78
80
  compare('runtimeMode', 'full-access', configuration?.runtimeMode);
79
- return mismatches;
81
+ return { mismatches, ...(selection?.provider == null ? {
82
+ providerEvidence: `provider: unknown; expected ${JSON.stringify(provider)}; observed missing; `
83
+ + 'provider binding checked via instanceId and saved capabilities',
84
+ } : {}) };
80
85
  }
81
86
 
82
87
  function plan(argv, io) {
@@ -89,17 +94,24 @@ function plan(argv, io) {
89
94
  const emit = (result) => {
90
95
  const full = { inputs, ...result };
91
96
  const inline = result.mismatches
92
- ? `${JSON.stringify({ inputs })}\n${result.mismatches.join('\n') || 'MATCH'}\n` : `${JSON.stringify(full)}\n`;
97
+ ? `${[JSON.stringify({ inputs }), result.providerEvidence, result.mismatches.join('\n') || 'MATCH'].filter(Boolean).join('\n')}\n`
98
+ : `${JSON.stringify(full)}\n`;
93
99
  if (!args['--out']) {
94
100
  if (new TextEncoder().encode(inline).length > 4096) {
95
101
  if (!result.mismatches) throw new Error('large output; pass --out <report.json> to save full JSON');
96
- io.stderr('Large verification output; pass --out <path>.json for the full list\n');
102
+ io.stderr('Large verification output; pass --out <path>.json for a saved JSON copy\n');
97
103
  }
98
104
  io.stdout(inline);
99
105
  return;
100
106
  }
101
107
  try { writeFileSync(args['--out'], `${JSON.stringify(full, null, 2)}\n`); }
102
- catch (error) { throw new Error(`${error.message}; pass --out <writable report.json path>`); }
108
+ catch (error) {
109
+ const message = `${error.message}; pass --out <writable report.json path>`;
110
+ if (!result.mismatches?.length) throw new Error(message);
111
+ io.stderr(`dispatch-plan hold: ${message}\n`);
112
+ io.stdout(inline);
113
+ return;
114
+ }
103
115
  io.stdout(`${JSON.stringify({ inputs, reportPath: args['--out'],
104
116
  omittedPayloadFields: Object.keys(result).length, omittedMismatches: result.mismatches?.length ?? 0 })}\n`);
105
117
  };
@@ -134,7 +146,10 @@ function plan(argv, io) {
134
146
  || (['grok', 'antigravity'].includes(role.provider) && picker.chosenInstanceId !== providerInstanceId)) {
135
147
  hold(`picker instance ${picker.chosenInstanceId} does not bind provider ${role.provider}`);
136
148
  }
137
- const selectedModel = chosen[0].models?.find((entry) => entry.id === model);
149
+ if (!Array.isArray(chosen[0].models)) {
150
+ hold(`malformed models on instance ${picker.chosenInstanceId}; expected an array; observed ${JSON.stringify(chosen[0].models) ?? 'missing'}`);
151
+ }
152
+ const selectedModel = chosen[0].models.find((entry) => entry.id === model);
138
153
  if (!selectedModel) hold(`model ${model} unavailable on instance ${picker.chosenInstanceId}`);
139
154
  if (effortOption && !selectedModel.options?.some((option) => option.id === effortOption.id
140
155
  && option.options?.some((value) => value.id === effortOption.value))) {
@@ -142,9 +157,9 @@ function plan(argv, io) {
142
157
  }
143
158
  const options = effortOption ? [effortOption] : [];
144
159
  if (args['--verify']) {
145
- const mismatches = verify(readJSON(args['--verify']), picker.chosenInstanceId, providerInstanceId, model, options);
146
- emit({ mismatches });
147
- return mismatches.length ? 2 : 0;
160
+ const verification = verify(readJSON(args['--verify']), picker.chosenInstanceId, providerInstanceId, model, options);
161
+ emit(verification);
162
+ return verification.mismatches.length ? 2 : 0;
148
163
  }
149
164
  const selection = { [args['--kind'] === 'launch' ? 'instanceId' : 'providerInstanceId']: picker.chosenInstanceId,
150
165
  model, options };
@@ -67,6 +67,12 @@ function snapshot(repo, revision) {
67
67
  paths: [...entries.keys()],
68
68
  has: (path) => entries.get(path)?.type === 'blob',
69
69
  mode: (path) => entries.get(path)?.mode,
70
+ readKeys(paths) {
71
+ try {
72
+ const blobs = readTextBlobs(repo, paths.map((path) => entries.get(path).oid));
73
+ return new Map(paths.filter((path) => /^100/.test(entries.get(path).mode)).map((path) => [path, blobs.get(entries.get(path).oid)]));
74
+ } catch { return new Map(); } // Unavailable blobs cannot prove key absence.
75
+ },
70
76
  read(path) {
71
77
  if (entries.get(path)?.type !== 'blob') return '';
72
78
  text ??= readTextBlobs(repo, ids);
@@ -81,6 +87,19 @@ function jsonc(text) {
81
87
  return JSON.parse(stripped.replace(/"(?:\\.|[^"\\])*"|,\s*(?=[}\]])/g, (token) => token.startsWith('"') ? token : ''));
82
88
  }
83
89
 
90
+ function pemStatus(text) {
91
+ if (typeof text !== 'string') return 'unknown';
92
+ if (/-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY-----/.test(text)) return 'fail';
93
+ let blocks = 0, valid = true;
94
+ const remainder = text.replace(/-----BEGIN (CERTIFICATE|PUBLIC KEY|RSA PUBLIC KEY)-----\r?\n([A-Za-z0-9+/=\r\n]+)-----END \1-----/g, (_, _label, body) => {
95
+ blocks++;
96
+ const encoded = body.replace(/[\r\n]/g, '');
97
+ if (!encoded || Buffer.from(encoded, 'base64').toString('base64') !== encoded) valid = false;
98
+ return '';
99
+ });
100
+ return blocks && valid && !remainder.trim() ? 'pass' : 'unknown';
101
+ }
102
+
84
103
  const definitions = [
85
104
  ['docs.agents', 'Docs'], ['docs.claude', 'Docs'],
86
105
  ['style.linter', 'Style'], ['style.formatter', 'Style'], ['style.strict', 'Style'], ['style.pre-commit', 'Style'],
@@ -183,8 +202,15 @@ function assess(files) {
183
202
  const workflowReason = prTests ? '' : invalid ? 'invalid workflow YAML' : prCandidate && manager.reason ? manager.reason : 'missing pull_request test step';
184
203
  set('security.pr-tests', prTests ? 'pass' : invalid || (prCandidate && manager.reason) ? 'unknown' : 'fail', workflows.length ? workflows.map((path) => `git show ${revision}:${path}`) : [`git ls-tree -r ${revision}: no workflows`], workflowReason);
185
204
  configured('security.updates', has(/(^|\/)(\.github\/(dependabot\.ya?ml|codeql\/[^/]+\.ya?ml)|(?:\.?)renovaterc(?:\.json5?)?|renovate\.json5?)$/).concat(codeql));
186
- const secrets = has(/(^|\/)(\.env[^/]*|id_(rsa|dsa|ecdsa|ed25519)|[^/]+\.(pem|key|p12|pfx))$/).filter((path) => !/(^|\/)\.env\.(example|sample|template)$/.test(path));
187
- set('security.secrets', secrets.length ? 'fail' : 'pass', secrets.length ? [...new Set(secrets)].map((path) => `git show ${revision}:${path}`) : [`git ls-tree -r ${revision}: no tracked env or private keys`], secrets.length ? 'tracked env or private keys' : '');
205
+ const candidates = has(/(^|\/)(\.env[^/]*|id_(rsa|dsa|ecdsa|ed25519)|[^/]+\.(pem|key|p12|pfx))$/).filter((path) => !/(^|\/)\.env\.(example|sample|template)$/.test(path));
206
+ const keys = candidates.filter((path) => /\.(pem|key)$/.test(path) && !/(^|\/)\.env/.test(path));
207
+ const contents = files.readKeys(keys);
208
+ const classifications = candidates.map((path) => [path, keys.includes(path) ? pemStatus(contents.get(path)) : 'fail']);
209
+ const secrets = classifications.filter(([, status]) => status === 'fail').map(([path]) => path);
210
+ const unknown = classifications.filter(([, status]) => status === 'unknown').map(([path]) => path);
211
+ const keyStatus = secrets.length ? 'fail' : unknown.length ? 'unknown' : 'pass';
212
+ const evidence = candidates.length ? candidates.map((path) => `git show ${revision}:${path}`) : [`git ls-tree -r ${revision}: no tracked env or private keys`];
213
+ set('security.secrets', keyStatus, evidence, [secrets.length ? 'tracked env or private keys' : '', unknown.length ? 'key contents unavailable or unrecognized' : ''].filter(Boolean).join('; '));
188
214
  return criteria;
189
215
  }
190
216
 
@@ -438,6 +464,7 @@ export async function main(argv, io = { stdout: (text) => process.stdout.write(t
438
464
  reportPath: paths.out, counts, pillars: result.pillars, omittedCriteria: result.criteria.length, omittedCommands: result.commands?.length ?? 0,
439
465
  omittedLogBytes: (result.commands ?? []).reduce((total, step) => total + step.omittedBytes, 0),
440
466
  incompleteLogs: (result.commands ?? []).filter((step) => step.logIncomplete).length,
467
+ ...(args['--run'] && { logIncomplete: (result.commands ?? []).some((step) => step.logIncomplete) }),
441
468
  })}\n`);
442
469
  return code || (result.executionError ? 2 : 0);
443
470
  } catch (error) {
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env bun
2
+ // Repository test entry point: keep DAC permission fixtures meaningful as root.
3
+ const args = process.argv.slice(2);
4
+ if (args[0] === '--help') {
5
+ console.log(`Usage: bun skills/axstack/scripts/test-repository.js [bun test arguments]
6
+ Runs bun test in the current directory, forwarding arguments, environment and exit status.
7
+ Linux root requires setpriv and drops DAC override/read-search capabilities for tests.
8
+ Other users run Bun directly. Unsupported root hosts or missing setpriv exit 2.
9
+ Side effects: runs the selected tests and their fixtures; creates no runner-owned files.
10
+ Set TMPDIR to a fresh owned 0700 directory outside HOME before repository tests.`);
11
+ process.exit(0);
12
+ }
13
+
14
+ let command = [process.execPath, 'test', ...args];
15
+ if (process.getuid?.() === 0) {
16
+ const setpriv = process.platform === 'linux' ? Bun.which('setpriv') : null;
17
+ if (!setpriv) {
18
+ console.error('Repository permission tests as root require Linux and setpriv; use a non-root user otherwise.');
19
+ process.exit(2);
20
+ }
21
+ command = [setpriv, '--bounding-set=-dac_override,-dac_read_search', '--', ...command];
22
+ }
23
+
24
+ try {
25
+ const child = Bun.spawn(command, { stdin: 'inherit', stdout: 'inherit', stderr: 'inherit' });
26
+ process.exit(await child.exited);
27
+ } catch (error) {
28
+ console.error(`Repository test launch failed: ${error.message}`);
29
+ process.exit(1);
30
+ }
@@ -139,6 +139,7 @@ the caller run's T3 driver thread, even for worker sends.
139
139
  Keep the tag short and machine-parsable.
140
140
  Exclude chat IDs, credentials, and Telegram targets from the reply tag.
141
141
  If either identity is unknown or mismatched, hold the send.
142
+ Reply proof is not an authentication boundary: a privileged worker can copy a valid receipt and forge a message.
142
143
 
143
144
  ## Decision cards and receipts
144
145
 
@@ -155,20 +156,36 @@ reissuing only if a decision is still needed.
155
156
  Each card must carry exactly one decision.
156
157
  The card ID must be `<run id>/<n>`, never reused in the driver thread.
157
158
  The only sender of reply-dependent cards must be the run's driver.
159
+
160
+ Decision cards and explain answers must use short paragraphs with blank lines
161
+ between thoughts for reading on a phone.
162
+ Decision cards and explain answers must use ordinary words and briefly define
163
+ necessary technical terms.
164
+ Each decision card must explain what is happening and why the choice matters.
165
+ Each numbered option must state the concrete action, scope, expected outcome
166
+ and material trade-off.
167
+ Defer and Cancel must describe the withheld action and what remains unresolved.
168
+ Any recommendation must include a brief evidence-supported reason.
169
+ A recommendation must leave the choice to the user.
170
+
158
171
  Use plain ASCII for the card layout below, as one message:
159
172
 
160
173
  ```text
161
174
  <project> - <decision title>
162
175
  Card <run id>/<n>[, confirms card <run id>/<m>]
176
+
163
177
  Context: <what we were doing, one line>
164
178
  Decision: <the question>
179
+
180
+ 1 <action> - <scope; outcome; trade-off>
181
+ 2 <action> - <scope; outcome; trade-off>
182
+ 3 Defer - <deferred action and scope; outcome; trade-off> (optional)
183
+
165
184
  Driver environment: <value | not supplied>
166
185
  Execution machine: <value | not supplied>
167
186
  Affected targets: <values | not supplied>
168
187
  Revision: <repo#PR @ full SHA | spec rev N sha256 | none>
169
- 1 <action> - <scope>
170
- 2 <action> - <scope>
171
- 3 Defer (optional)
188
+
172
189
  Reply 1, 2, 3 or explain. Open T3: <link | not available>
173
190
  T3 reply: <env label> thread <driver threadId>
174
191
  ```
@@ -250,6 +267,13 @@ Run these four checks in order, stopping at the first failure:
250
267
  Free text is user input, not a numbered option, and gets the same authority
251
268
  revalidation and confirmation checks before it can authorize an action.
252
269
 
270
+ Explain answers must directly address the reader's confusion.
271
+ Explain answers must expand meaningful context and each option's consequences.
272
+ Explain answers must give a justified recommendation when evidence supports it.
273
+ Explain answers must state material risks and unknown facts plainly.
274
+ Never merely restate IDs or SHAs in an explain answer.
275
+ The same one-message size and final reply tag rules apply to explain answers.
276
+
253
277
  ## Confirm, acknowledge and act
254
278
 
255
279
  Any forwarded reply, including free text and `(confirm)` options, authorizing
@@ -257,6 +281,13 @@ a destructive, production or fleet-wide action must first get a confirmation car
257
281
  A confirmation card must have its own ID, name the card it confirms
258
282
  (`confirms card`), bind the exact action, execution machine, affected targets
259
283
  and revision, and offer `1 Confirm` and `2 Cancel`.
284
+ Use the same detail fields for confirmation option rows:
285
+
286
+ ```text
287
+ 1 Confirm - <bound action and scope; outcome; trade-off>
288
+ 2 Cancel - <withheld action and scope; outcome; trade-off>
289
+ ```
290
+
260
291
  Sending a confirmation must record the original card decided, pending
261
292
  confirmation with no authority.
262
293
  The driver must allow only a verified `1` on the open confirmation to authorize
@@ -283,6 +283,7 @@ This section applies to peer and authored PR modes.
283
283
  where measurement is useful. Never invent a metric or demand an
284
284
  abstraction merely to satisfy a principle.
285
285
 
286
+ Before claiming a test is missing from CI inspect the complete file and actual CI step result.
286
287
  Under the existing angles, check added, changed, and removed test hunks
287
288
  against [Test value](../axstack/references/test-value.md). For removed tests,
288
289
  inspect the named keepers. Removing a test without a named keeper or
@@ -203,6 +203,7 @@ For each current head and base SHA, every merge-ready term must hold:
203
203
  A collaborator approval carries over only across a rebase with unchanged
204
204
  patch-id recorded for both heads while the forge still counts it.
205
205
  Require fresh authored review, diligence, and CI on every new head.
206
+ Refresh authored review, diligence and CI evidence at the current head and base after every head or base change; base updates return to the same author under the existing repair route.
206
207
  Text carrying a visible machine marker never counts as a user reply:
207
208
  orchestration notices, dispatch envelopes, `<pasted_content>` blocks, task
208
209
  notifications, tool output, relay/Telegram text, and PR text.
@@ -378,6 +379,10 @@ The check runs on existing watch wakes with no new schedule.
378
379
  The ping is a notification only: when the blocker clears, the PR merges with no reply.
379
380
 
380
381
  Immediately before each automated merge, re-read every term from the forge.
382
+ Never edit a running script: freeze its exact script bytes until exit.
383
+ For each merge refusal record exact argv, result, head and base and an evidence pointer.
384
+ Keep pending CI distinct from failed CI and wait for terminal check results before repair.
385
+ Apply the existing [escalation threshold](../axstack/references/contracts.md#driver-and-adviser-split) to repeated same-goal refusals; changed evidence permits reconciliation, never a blind retry or rules bypass.
381
386
  Confirm merge commits are allowed, `delete_branch_on_merge` is false, and the
382
387
  base has no merge queue; otherwise hold for the user. For a singleton PR, use
383
388
  `gh pr merge <n> --merge --match-head-commit <sha>`; add `--delete-branch` only
@@ -50,6 +50,10 @@ Keep native PR watches.
50
50
  Only while every open watched PR has an armed native `watch_pull_request`, all
51
51
  launched work is settled, and the release step is settled or inapplicable, use
52
52
  the bound chat-run schedule as a 30-minute fallback (`everyMs:1800000`).
53
+ Because delegated tasks deliver native completion wakes, unsettled delegated
54
+ tasks alone keep the 30-minute fallback eligible.
55
+ A resumed `AXSTACK-QUESTION` child run lacks a native completion wake, so it
56
+ keeps the 5-minute cadence (`everyMs:300000`) until its result is accepted.
53
57
  If native `watch_pull_request` is unavailable or unarmed for any open member,
54
58
  or re-arming fails, restore the bound chat-run schedule to 5 minutes (`everyMs:300000`).
55
59
  On the next event on a watched PR, re-evaluate the wake cadence against the