atris 3.74.21 → 3.74.23

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/bin/atris.js CHANGED
@@ -669,7 +669,7 @@ function showHelpAll() {
669
669
  console.log(' land - The landing: what is actually done vs still in the air; --reap backs up + clears overdue');
670
670
  console.log(' caretaker - Classify open pull requests on origin (scan only; no fix, comment, or merge)');
671
671
  console.log(' drive - One self-driving tick: mission doctor -> auto-fix -> count disengagements');
672
- console.log(' rsi - Read the Dream-RSI attempt ledger (trees, attempts, policy, dreams)');
672
+ console.log(' rsi - Read the Dream-RSI attempt ledger; rsi loop ships notebook fixes with no mission');
673
673
  console.log(` autoland - Approve the policy once; ${require('../lib/autoland').certifiedWorkLandsPhrase(process.cwd())}, you keep irreversible calls`);
674
674
  console.log(' engine - engine registry, answer validation, dispatch flights, and live progress');
675
675
  console.log(' link - What two things could become together: one line, why, a first thing to make, who makes it');
@@ -2495,6 +2495,10 @@ if (command === 'guide') {
2495
2495
  Promise.resolve().then(() => require('../commands/balance').balanceCommand(process.argv.slice(3)))
2496
2496
  .then((code) => exitAfterStdoutDrain(typeof code === 'number' ? code : 0))
2497
2497
  .catch((err) => { console.error(String(err.message || err).replace(/\s+/g, ' ')); process.exit(1); });
2498
+ } else if (command === 'spend') {
2499
+ // Spend guard: unattended Claude runs on paid models need a yes (2026-10-28, $900 day).
2500
+ const code = require('../commands/spend').spendCommand(process.argv.slice(3));
2501
+ exitAfterStdoutDrain(typeof code === 'number' ? code : 0);
2498
2502
  } else if (command === 'usage') {
2499
2503
  Promise.resolve().then(() => require('../commands/usage').usageCommand(process.argv.slice(3)))
2500
2504
  .then((code) => exitAfterStdoutDrain(typeof code === 'number' ? code : 0))
@@ -449,7 +449,7 @@ function wishSweepSummaryLine(summary, error = null) {
449
449
  return line;
450
450
  }
451
451
 
452
- function evaluateQueue(root, { strictVerify, acceptAll }) {
452
+ function evaluateQueue(root, { strictVerify, acceptAll, includeProtected }) {
453
453
  return readProjection(root)
454
454
  .filter((task) => task && task.status === 'review')
455
455
  .filter((task) => String(task.review?.approval_status || task.metadata?.approval_status || 'pending') === 'pending')
@@ -458,7 +458,7 @@ function evaluateQueue(root, { strictVerify, acceptAll }) {
458
458
  .map((task) => {
459
459
  const evaluation = evaluateAutoAccept(
460
460
  { ...task, workspace_root: root },
461
- { strictVerify, acceptAll, executeVerify: false },
461
+ { strictVerify, acceptAll, includeProtected, executeVerify: false },
462
462
  );
463
463
  return {
464
464
  ...evaluation,
@@ -472,6 +472,7 @@ function protectedReviewWaiting(root, policy = {}, waiting = []) {
472
472
  return evaluateQueue(root, {
473
473
  strictVerify: policy.strict_verify !== false,
474
474
  acceptAll: Boolean(policy.accept_all),
475
+ includeProtected: Boolean(policy.accept_protected),
475
476
  })
476
477
  .filter((row) => /^denied_tag_/.test(String(row.reason || '')))
477
478
  .filter((row) => !alreadyWaiting.has(row.ref));
@@ -487,7 +488,8 @@ function showStatus(root, args, deps = {}) {
487
488
  const enabled = Boolean(policy && policy.enabled);
488
489
  const strictVerify = policy ? policy.strict_verify !== false : true;
489
490
  const acceptAll = Boolean(policy && policy.accept_all);
490
- const results = evaluateQueue(root, { strictVerify, acceptAll })
491
+ const includeProtected = Boolean(policy && policy.accept_protected);
492
+ const results = evaluateQueue(root, { strictVerify, acceptAll, includeProtected })
491
493
  .filter((r) => r.reason !== 'not_in_review');
492
494
  const readyForRecheck = results.filter((r) => r.action === 'would_accept');
493
495
  const wouldLand = readyForRecheck.filter((r) => r.verification_pending !== true);
@@ -525,7 +527,8 @@ function showStatus(root, args, deps = {}) {
525
527
  : `on (flipped by ${policyOwner}${policy.enabled_at ? ` on ${String(policy.enabled_at).slice(0, 10)}` : ''})`)
526
528
  : 'off - everything waits for you';
527
529
  console.log(` policy: ${policyText}`);
528
- if (enabled && acceptAll) console.log(' bar: everything lands except the protected lanes (money, deploys, security, customer, outward)');
530
+ if (enabled && includeProtected) console.log(' bar: protected work (money, deploys, security, customer, outward) also lands itself; sends and payments still need you.');
531
+ else if (enabled && acceptAll) console.log(' bar: everything lands except the protected lanes (money, deploys, security, customer, outward)');
529
532
  console.log(` heartbeat: ${autoland.heartbeatStatusText(root, policy)}`);
530
533
  if (policy && policy.imessage_to) console.log(` daily message: ${policy.imessage_to} at ${policy.digest_hour ?? autoland.DEFAULT_DIGEST_HOUR}:00`);
531
534
  const reapTrouble = autoland.readState(root).last_reap_error;
@@ -634,6 +637,7 @@ function turnOn(root, args) {
634
637
  alarm_hours: Number.isFinite(alarmHours) && alarmHours > 0 ? alarmHours : autoland.DEFAULT_ALARM_HOURS,
635
638
  strict_verify: !args.includes('--no-strict-verify'),
636
639
  accept_all: acceptAll,
640
+ accept_protected: args.includes('--include-protected'),
637
641
  daily_experiment: previous.daily_experiment !== false,
638
642
  });
639
643
  const cronOk = autoland.installCron(root);
@@ -644,9 +648,11 @@ function turnOn(root, args) {
644
648
  });
645
649
  console.log('');
646
650
  console.log('autoland is on.');
647
- if (acceptAll) console.log(` everything in review now lands itself, accepted as ${owner}; only the protected lanes wait.`);
651
+ if (acceptAll && policy.accept_protected) console.log(` everything in review now lands itself, accepted as ${owner}.`);
652
+ else if (acceptAll) console.log(` everything in review now lands itself, accepted as ${owner}; only the protected lanes wait.`);
648
653
  else console.log(` certified, verified, reversible work now lands itself, accepted as ${owner}.`);
649
- console.log(' protected lanes (money, deploys, security, customer, outward) still wait for you.');
654
+ if (policy.accept_protected) console.log(' protected work (money, deploys, security, customer, outward) also lands itself; sends and payments still need you.');
655
+ else console.log(' protected lanes (money, deploys, security, customer, outward) still wait for you.');
650
656
  console.log(` heartbeat: ${cronOk ? 'installed, runs hourly' : 'could not install cron; run atris autoland tick yourself'}`);
651
657
  if (policy.imessage_to) {
652
658
  console.log(` daily message to ${policy.imessage_to} at ${policy.digest_hour}:00; anything waiting on you past ${policy.alarm_hours}h pings you.`);
@@ -1020,6 +1026,14 @@ function digestSentToday(state, now = new Date()) {
1020
1026
  return state.last_digest_date === utcDay(now) && state.last_digest_date !== utcDay(yesterday);
1021
1027
  }
1022
1028
 
1029
+ function acceptCommandArgs(policy) {
1030
+ const args = ['task', 'auto-accept-certified', '--json', '--certify-first'];
1031
+ if (policy.accept_protected) args.push('--include-protected');
1032
+ if (policy.accept_all) args.push('--all');
1033
+ else if (policy.strict_verify === false) args.push('--no-strict-verify');
1034
+ return args;
1035
+ }
1036
+
1023
1037
  async function runTickBody(root, { json, policy, receipt, engineValidationDeps = {} }) {
1024
1038
 
1025
1039
  try {
@@ -1044,9 +1058,7 @@ async function runTickBody(root, { json, policy, receipt, engineValidationDeps =
1044
1058
  // Refresh origin once, before anything is accepted: the landing proof reads
1045
1059
  // origin's target, and a stale ref would call landed work unlanded.
1046
1060
  refreshLandingRefs(root);
1047
- const cliArgs =['task', 'auto-accept-certified', '--json', '--certify-first'];
1048
- if (policy.accept_all) cliArgs.push('--all');
1049
- else if (policy.strict_verify === false) cliArgs.push('--no-strict-verify');
1061
+ const cliArgs = acceptCommandArgs(policy);
1050
1062
  const pendingReviewRows = readProjection(root)
1051
1063
  .filter((task) => task && task.status === 'review')
1052
1064
  .filter((task) => String(task.review?.approval_status || task.metadata?.approval_status || 'pending') === 'pending')
@@ -1442,7 +1454,7 @@ function showHelp() {
1442
1454
  console.log('');
1443
1455
  console.log('finished work that passed its checks and two independent reviews lands');
1444
1456
  console.log('automatically with a receipt. money, deploys, security, customer, and');
1445
- console.log('outward-facing work always waits for you.');
1457
+ console.log('outward-facing work waits for you unless you include protected work.');
1446
1458
  console.log('');
1447
1459
  console.log('recommended agent flight checks:');
1448
1460
  console.log(' fast suite: npm run test:fast');
@@ -1456,6 +1468,7 @@ function showHelp() {
1456
1468
  console.log(' atris autoland on --all lower the bar: everything lands except');
1457
1469
  console.log(' the protected lanes; a failing recorded');
1458
1470
  console.log(' check still blocks');
1471
+ console.log(' atris autoland on --include-protected include protected work in the policy');
1459
1472
  console.log(' atris autoland off back to approving every item');
1460
1473
  console.log(' atris autoland digest [--send] the daily message, now');
1461
1474
  console.log(' atris autoland tick [--json] one heartbeat (what the hourly cron runs)');
@@ -1501,6 +1514,7 @@ module.exports = {
1501
1514
  hasAgentJargon,
1502
1515
  runEngineAnswerValidation,
1503
1516
  runTickBody,
1517
+ acceptCommandArgs,
1504
1518
  runSshAgentSweep,
1505
1519
  sshAgentSweepNote,
1506
1520
  showStatus,
@@ -453,7 +453,7 @@ function collectImproveVitals(options = {}, deps = {}) {
453
453
  return fs.existsSync(plistPath);
454
454
  })();
455
455
  const installed = launchdInstalled || legacyCronInstalled;
456
- const installNudge = installed ? null : 'the scheduled improve loop is off. turn it on: atris pulse install --model claude-sonnet-5';
456
+ const installNudge = installed ? null : 'the scheduled improve loop is off. turn it on: atris pulse install';
457
457
 
458
458
  const experimentsPath = path.join(root, '.atris', 'state', 'experiments-daily.json');
459
459
  const experiments = readJsonFile(experimentsPath, {});
@@ -520,6 +520,13 @@ function recordMissionEngineTickOutcome(engineId, result, root = process.cwd())
520
520
  // a closed credit window is credit out. Never throws into the tick.
521
521
  function missionTickRunOutcome(result, verifierResult, maxSeconds) {
522
522
  if (result && result.status === 'ran') {
523
+ const review = verifierResult?.referee;
524
+ if (verifierResult?.passed === false && verifierResult.status === 0 && !verifierResult.timed_out
525
+ && review?.mode === 'engine-unavailable' && review.reason !== 'code changed during review') {
526
+ return { outcome: 'review unavailable', detail: review.timed_out
527
+ ? 'the build check passed, but the second review timed out'
528
+ : 'the build check passed, but the second review was unavailable' };
529
+ }
523
530
  return verifierResult && verifierResult.passed === false
524
531
  ? { outcome: 'failed', detail: 'the verifier failed' }
525
532
  : { outcome: 'landed' };
@@ -9353,7 +9360,7 @@ function buildTickPrompt(mission, tickIndex, maxTicks, frozen, pings = [], cwd =
9353
9360
  `## Your task`,
9354
9361
  `Do ONE increment of work toward the stop condition. ONE. No more.`,
9355
9362
  `- ${memberIdentityLine(mission.owner)} Your member files are preloaded above; open atris/team/${mission.owner}/MEMBER.md only if that copy is marked truncated. Work in that identity, inside its scope and stop rules. Atris writes your member log from your receipt, so end the receipt with one \`learned: <one line>\` line instead of editing the log.`,
9356
- `- FIRST: inspect current mission/task state before acting. Read the relevant files, run \`atris mission status ${mission.id}\`, \`git status\`, or \`atris task list\` as needed so you know what's already done.`,
9363
+ `- Start with the job: inspect the files or state directly relevant to the objective, then do the smallest useful action. Skip workspace boot and start-of-session routines, including brain activation, general Atris or task listings, and reading MAP, persona, or skill files solely for startup. Inspect mission or task state only if the job requires it.`,
9357
9364
  `- Pick the smallest concrete action that moves the mission forward.`,
9358
9365
  `- Before acting, state your single next move in one sentence.`,
9359
9366
  `- Edit / run / research as needed for the lane.`,
@@ -10075,6 +10082,16 @@ function engineHealthRoot(cwd) {
10075
10082
  return dir && path.basename(dir) === '.git' ? path.dirname(dir) : cwd;
10076
10083
  }
10077
10084
 
10085
+ function reviewerHealthRoot(engine) {
10086
+ return path.join(require('../lib/state-home').atrisHome(), 'reviewer-health', engine);
10087
+ }
10088
+
10089
+ function reviewerCooling(engine, registry) {
10090
+ const saved = registry.readEngineRegistry(reviewerHealthRoot(engine), { persist: false })
10091
+ .engines.find((entry) => entry.id === engine);
10092
+ return saved?.health?.status === 'cooling' && !registry.engineReadyAt(saved);
10093
+ }
10094
+
10078
10095
  // Every spelling and version of one model family counts as the same model,
10079
10096
  // with or without the long-context tag. Unknown counts as same.
10080
10097
  function reviewerMayBeSameModel(a, b) {
@@ -10102,12 +10119,12 @@ function engineVerifierLineup(mission, cwd, preferredEngine = '', options = {})
10102
10119
  const workerEngine = canonicalEngineName(mission?.runner) || 'claude';
10103
10120
  const workerModel = resolveMissionTickRunnerModel(mission);
10104
10121
  const team = registry.resolveEngineForRoleRanked('validator', root, { job: 'review', quiet: true }).team || [];
10105
- const backup = team.find((engine) => engine && engine.id !== primary
10122
+ const backup = team.find((engine) => engine && engine.id !== primary && !reviewerCooling(engine.id, registry)
10106
10123
  && !(engine.id === workerEngine && reviewerMayBeSameModel(engine.roster_model, workerModel)));
10107
10124
  if (!backup) return lineup;
10108
10125
  lineup.push({ engine: backup.id, model: backup.roster_model || '' });
10109
10126
  const saved = registry.readEngineRegistry(root, { persist: false }).engines.find((engine) => engine.id === primary);
10110
- if (saved && !registry.engineReadyAt(saved)) lineup.shift();
10127
+ if ((saved && !registry.engineReadyAt(saved)) || reviewerCooling(primary, registry)) lineup.shift();
10111
10128
  } catch {}
10112
10129
  return lineup;
10113
10130
  }
@@ -10127,7 +10144,7 @@ async function runEngineVerifier(mission, options = {}) {
10127
10144
  sessionId: crypto.randomUUID(),
10128
10145
  cwd,
10129
10146
  signal: options.signal,
10130
- timeoutMs: ENGINE_VERIFY_TIMEOUT_MS,
10147
+ timeoutMs: options.timeoutMs || ENGINE_VERIFY_TIMEOUT_MS,
10131
10148
  prompt: buildEngineVerifyPrompt(mission, options.tickIndex || 1, missionDoneLooksLike(mission, cwd), {
10132
10149
  timeoutMs: ENGINE_VERIFY_TIMEOUT_MS,
10133
10150
  checkPassed: options.checkPassed || '',
@@ -10140,11 +10157,16 @@ async function runEngineVerifier(mission, options = {}) {
10140
10157
  restoreRunnerProfile();
10141
10158
  }
10142
10159
  verdict = engineVerifierResultFromRun(engineResult, engine);
10160
+ if (engineResult?.ok && !engineResult?.timedOut && !engineResult?.aborted) {
10161
+ try { setEngineHealth(engine, 'ready', healthRoot); } catch {}
10162
+ try { setEngineHealth(engine, 'ready', reviewerHealthRoot(engine)); } catch {}
10163
+ }
10143
10164
  if (verdict.mode !== 'engine-unavailable' || engineResult?.aborted) return verdict;
10144
10165
  // Member lab 10/26: codex hung on every review, and each job waited out
10145
10166
  // the full five minutes. Bench it so the next review starts on the backup.
10146
10167
  if (engineResult?.timedOut) {
10147
10168
  try { setEngineHealth(engine, 'cooling', healthRoot); } catch {}
10169
+ try { setEngineHealth(engine, 'cooling', reviewerHealthRoot(engine)); } catch {}
10148
10170
  }
10149
10171
  }
10150
10172
  return verdict;
@@ -11103,7 +11125,8 @@ async function executeMissionRunTicksPhase(context) {
11103
11125
  next_push: result.next_push || undefined,
11104
11126
  engine: result.engine_id || runtimeMission.runner || mission.runner || null,
11105
11127
  task_type: 'executor',
11106
- verified_passed: verifierResult && typeof verifierResult.passed === 'boolean' ? verifierResult.passed : undefined,
11128
+ verified_passed: missionTickRunOutcome(result, verifierResult).outcome === 'review unavailable'
11129
+ ? undefined : verifierResult && typeof verifierResult.passed === 'boolean' ? verifierResult.passed : undefined,
11107
11130
  duration_ms: Math.max(0, (Date.parse(finishedAt) || 0) - (Date.parse(tickStart) || 0)),
11108
11131
  }).mission;
11109
11132
  if (rotateSessionForContext) {
@@ -12999,6 +13022,7 @@ module.exports = {
12999
13022
  receiptLearnedLine,
13000
13023
  tickTouchedCheckDefinition,
13001
13024
  engineVerifierLineup,
13025
+ runEngineVerifier,
13002
13026
  claudeRunnerMcpArgs,
13003
13027
  crystallizeMemberSkillLesson,
13004
13028
  listSiblingWorkspaceMissionHints,
package/commands/rsi.js CHANGED
@@ -6,6 +6,8 @@
6
6
  // outcomes, the live policy id, and the last dream receipt. Read-only: it
7
7
  // never touches record.py, never writes state, and exits 0 even when the
8
8
  // workspace has no ledger yet.
9
+ //
10
+ // `atris rsi loop` ships notebook fixes with no mission queued (lib/rsi-loop.js).
9
11
 
10
12
  const fs = require('fs');
11
13
  const rsi = require('../lib/rsi-record');
@@ -114,8 +116,29 @@ Usage:
114
116
  atris rsi same as status
115
117
  atris rsi status trees + attempts by lane, last night's outcomes, policy, last dream
116
118
  atris rsi status --json machine-readable status
119
+ atris rsi loop [--minutes M] [--json]
120
+ find notebook fixes with no mission queued, verify, commit and
121
+ push them on one reusable isolated branch (default budget 5 minutes)
122
+
123
+ status is read-only. loop checks atris/ pages for line refs that point at
124
+ moved code and for wording the slop rules can repair, commits each check
125
+ that verifies, and pushes the branch. It removes the local branch and checkout
126
+ only once the remote tip matches; the remote branch is reused on later runs.
127
+ The budget is checked between steps and limits network operations.
128
+ The main checkout is never edited.
129
+
130
+ State lives in .atris/state/rsi/ (or $ATRIS_RSI_STATE); loop reports go to loops.jsonl there.`);
131
+ }
117
132
 
118
- Read-only. State lives in .atris/state/rsi/ (or $ATRIS_RSI_STATE).`);
133
+ async function runLoopCommand(args) {
134
+ const rsiLoop = require('../lib/rsi-loop');
135
+ const i = args.indexOf('--minutes');
136
+ const raw = i >= 0 ? String(args[i + 1] || '') : '';
137
+ const minutes = i < 0 ? undefined : (/^\d+(\.\d+)?$/.test(raw) ? Number(raw) : NaN);
138
+ const result = await rsiLoop.runLoop({ minutes, workspace: process.cwd() });
139
+ console.log(args.includes('--json') ? JSON.stringify(result) : rsiLoop.formatLoopReport(result));
140
+ if (result.refused) return 2;
141
+ return result.ok ? 0 : 1;
119
142
  }
120
143
 
121
144
  function run(argv = []) {
@@ -125,6 +148,7 @@ function run(argv = []) {
125
148
  return 0;
126
149
  }
127
150
  const sub = args[0] && !args[0].startsWith('-') ? args[0] : 'status';
151
+ if (sub === 'loop') return runLoopCommand(args.slice(1));
128
152
  if (sub !== 'status') {
129
153
  console.log(`unknown rsi subcommand: ${sub}`);
130
154
  showHelp();
@@ -0,0 +1,78 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const guard = require('../lib/claude-spend-guard');
5
+
6
+ function showSpendHelp() {
7
+ console.log('usage: atris spend guard [install|uninstall|status]');
8
+ console.log(' atris spend log [--limit N]');
9
+ console.log('');
10
+ console.log('The guard stops unattended Claude runs on paid models (anything but Haiku)');
11
+ console.log(`unless ${guard.APPROVAL_ENV}=<model> names the model for that run.`);
12
+ console.log('It also stops unattended runs that carry a pay-per-use Anthropic key.');
13
+ console.log('Interactive Claude sessions are never blocked.');
14
+ }
15
+
16
+ function printStatus(status) {
17
+ if (!status.readable) {
18
+ console.log(`guard: unknown, could not read ${status.settings}`);
19
+ return;
20
+ }
21
+ console.log(status.installed ? 'guard: on' : 'guard: off');
22
+ console.log(`settings: ${status.settings}`);
23
+ console.log(`hook: ${status.script}`);
24
+ }
25
+
26
+ function guardCommand(action = 'status') {
27
+ if (action === 'install') {
28
+ const status = guard.installGuard();
29
+ printStatus(status);
30
+ if (status.installed) {
31
+ console.log('Unattended Claude runs on paid models now need a yes on this computer.');
32
+ }
33
+ return status.installed ? 0 : 1;
34
+ }
35
+ if (action === 'uninstall') {
36
+ printStatus(guard.uninstallGuard());
37
+ return 0;
38
+ }
39
+ if (action === 'status') {
40
+ const status = guard.guardStatus();
41
+ printStatus(status);
42
+ return status.installed ? 0 : 1;
43
+ }
44
+ showSpendHelp();
45
+ return 1;
46
+ }
47
+
48
+ function logCommand(args) {
49
+ const i = args.indexOf('--limit');
50
+ const limit = i >= 0 ? Math.max(1, Number(args[i + 1]) || 20) : 20;
51
+ const file = guard.ledgerPath();
52
+ if (!fs.existsSync(file)) {
53
+ console.log('no guard decisions recorded on this computer.');
54
+ return 0;
55
+ }
56
+ const rows = fs.readFileSync(file, 'utf8').trim().split('\n').slice(-limit);
57
+ for (const line of rows) {
58
+ try {
59
+ const r = JSON.parse(line);
60
+ console.log(`${r.ts} ${r.decision} ${r.model} ${r.job || ''} ${r.cwd || ''}`.trimEnd());
61
+ } catch {}
62
+ }
63
+ return 0;
64
+ }
65
+
66
+ function spendCommand(args = []) {
67
+ const [sub, ...rest] = args;
68
+ if (!sub || sub === '--help' || sub === '-h' || sub === 'help') {
69
+ showSpendHelp();
70
+ return sub ? 0 : 1;
71
+ }
72
+ if (sub === 'guard') return guardCommand(rest[0] || 'status');
73
+ if (sub === 'log') return logCommand(rest);
74
+ showSpendHelp();
75
+ return 1;
76
+ }
77
+
78
+ module.exports = { spendCommand };
package/commands/task.js CHANGED
@@ -31,6 +31,7 @@ const {
31
31
  parseVerifyCommand,
32
32
  runVerifyCommand,
33
33
  DENIED_TAGS,
34
+ protectedLaneOf,
34
35
  } = require('../lib/auto-accept-certified');
35
36
  const { evaluateAcceptVerify, nonFalsifyingIssue } = require('../lib/accept-verify-gate');
36
37
  const { checkedCodeSha, runVerifyAtJobCode } = require('../lib/job-code-verify');
@@ -10247,6 +10248,7 @@ function cmdCertifyVerified(args, options = {}) {
10247
10248
  const asJson = wantsJson(args);
10248
10249
  const silent = options.silent === true;
10249
10250
  const verifyCache = options.verifyCache || null;
10251
+ const includeProtected = options.includeProtected === true;
10250
10252
  const actor = String(flag(args, '--as') || 'autoland-verifier');
10251
10253
  const limitRaw = flag(args, '--limit');
10252
10254
  const max = limitRaw && limitRaw !== true ? Math.max(1, Number(limitRaw) || 6) : 6;
@@ -10284,7 +10286,7 @@ function cmdCertifyVerified(args, options = {}) {
10284
10286
  continue;
10285
10287
  }
10286
10288
  const tag = String(task.tag || '').toLowerCase();
10287
- if (DENIED_TAGS.has(tag)) {
10289
+ if (!includeProtected && DENIED_TAGS.has(tag)) {
10288
10290
  results.push({ ref, action: 'skipped', reason: `denied_tag_${tag}` });
10289
10291
  continue;
10290
10292
  }
@@ -10302,7 +10304,7 @@ function cmdCertifyVerified(args, options = {}) {
10302
10304
  // second time per row before the real run. executeVerify: false as well,
10303
10305
  // an untrusted trust tier re-enables the strict block internally even
10304
10306
  // with strictVerify off, which is how this probe spawned a verify per row.
10305
- const evaluation = evaluateAutoAccept(task, { strictVerify: false, executeVerify: false });
10307
+ const evaluation = evaluateAutoAccept(task, { strictVerify: false, includeProtected, executeVerify: false });
10306
10308
  if (evaluation.eligible) {
10307
10309
  results.push({ ref, action: 'skipped', reason: 'already_landable' });
10308
10310
  continue;
@@ -10415,6 +10417,7 @@ function cmdLanding(args) {
10415
10417
  function cmdAutoAcceptCertified(args) {
10416
10418
  const dryRun = hasFlag(args, '--dry-run');
10417
10419
  const acceptAll = hasFlag(args, '--all');
10420
+ const includeProtected = hasFlag(args, '--include-protected');
10418
10421
  const certifyFirst = hasFlag(args, '--certify-first');
10419
10422
  const strictVerify = !hasFlag(args, '--no-strict-verify') && !acceptAll;
10420
10423
  const actorFlag = flag(args, '--as');
@@ -10479,7 +10482,7 @@ function cmdAutoAcceptCertified(args) {
10479
10482
  // never persisted: a later heartbeat must prove the checkout again.
10480
10483
  const verifyCache = new Map();
10481
10484
  const certification = certifyFirst && !dryRun
10482
- ? cmdCertifyVerified([], { verifyCache, silent: true })
10485
+ ? cmdCertifyVerified([], { verifyCache, silent: true, includeProtected })
10483
10486
  : null;
10484
10487
 
10485
10488
  const taskDb = getTaskDb();
@@ -10514,7 +10517,7 @@ function cmdAutoAcceptCertified(args) {
10514
10517
  results.push({ ...proofBoundary, action: 'skipped' });
10515
10518
  continue;
10516
10519
  }
10517
- const evaluation = evaluateAutoAccept(task, { strictVerify, acceptAll, verifyCache });
10520
+ const evaluation = evaluateAutoAccept(task, { strictVerify, acceptAll, includeProtected, verifyCache });
10518
10521
  if (!evaluation.eligible) {
10519
10522
  results.push({ ...evaluation, action: 'skipped' });
10520
10523
  continue;
@@ -10547,7 +10550,9 @@ function cmdAutoAcceptCertified(args) {
10547
10550
  }
10548
10551
  const accepted = acceptReviewTask(taskDb, db, task.id, {
10549
10552
  actor,
10550
- proof: evaluation.proof,
10553
+ proof: includeProtected && protectedLaneOf(task)
10554
+ ? `${evaluation.proof}\nprotected lane accepted by owner policy (${protectedLaneOf(task)})`
10555
+ : evaluation.proof,
10551
10556
  reward: parsedReward.value,
10552
10557
  lesson: String(task.review?.lesson || task.metadata?.latest_agent_lesson || ''),
10553
10558
  nextTask: String(task.review?.next_task || task.metadata?.latest_agent_next_task || ''),
@@ -10632,7 +10637,17 @@ function autoAcceptSweepNormalizedTokens(value) {
10632
10637
  return [normalized, ...normalized.split(/[^a-z0-9-]+/).filter(Boolean)];
10633
10638
  }
10634
10639
 
10635
- function autoAcceptSweepDeniedReason(task) {
10640
+ function autoAcceptSweepLane(task) {
10641
+ for (const value of autoAcceptSweepLabelValues(task)) {
10642
+ for (const token of autoAcceptSweepNormalizedTokens(value)) {
10643
+ const lane = [...SWEEP_AUTO_ACCEPT_PROTECTED].find((denied) => token === denied || token.replace(/s$/, '') === denied);
10644
+ if (lane) return lane;
10645
+ }
10646
+ }
10647
+ return null;
10648
+ }
10649
+
10650
+ function autoAcceptSweepDeniedReason(task, includeProtected = false) {
10636
10651
  for (const value of autoAcceptSweepLabelValues(task)) {
10637
10652
  const tokens = autoAcceptSweepNormalizedTokens(value);
10638
10653
  for (const token of tokens) {
@@ -10642,7 +10657,7 @@ function autoAcceptSweepDeniedReason(task) {
10642
10657
  const protectedLane = [...SWEEP_AUTO_ACCEPT_PROTECTED].find((denied) =>
10643
10658
  token === denied || token.replace(/s$/, '') === denied
10644
10659
  );
10645
- if (protectedLane) return 'protected_lane';
10660
+ if (protectedLane && !includeProtected) return 'protected_lane';
10646
10661
  }
10647
10662
  }
10648
10663
  return null;
@@ -10689,7 +10704,7 @@ function autoAcceptSweepHappened(task) {
10689
10704
  );
10690
10705
  }
10691
10706
 
10692
- function evaluateSweepAutoAccept(task, root) {
10707
+ function evaluateSweepAutoAccept(task, root, includeProtected = false) {
10693
10708
  const ref = taskRef(task);
10694
10709
  if (!task) return { eligible: false, ref, reason: 'task_not_found' };
10695
10710
  if (task.status !== 'review') return { eligible: false, ref, reason: 'not_in_review' };
@@ -10698,7 +10713,7 @@ function evaluateSweepAutoAccept(task, root) {
10698
10713
  const approval = String(review.approval_status || metadata.approval_status || 'pending').toLowerCase();
10699
10714
  if (approval !== 'pending') return { eligible: false, ref, reason: `approval_${approval}` };
10700
10715
  if (metadata.auto_accepted_at) return { eligible: false, ref, reason: 'already_auto_accepted' };
10701
- const denied = autoAcceptSweepDeniedReason(task);
10716
+ const denied = autoAcceptSweepDeniedReason(task, includeProtected);
10702
10717
  if (denied) return { eligible: false, ref, reason: denied };
10703
10718
  const proof = autoAcceptSweepLatestProof(task);
10704
10719
  if (!proof) return { eligible: false, ref, reason: 'no_proof' };
@@ -10819,14 +10834,17 @@ function cmdSweep(args) {
10819
10834
  for (const item of pendingReview) {
10820
10835
  const fullProjection = enrichTaskProjection(taskDb.taskProjection(db, { taskId: item.id }));
10821
10836
  const task = fullProjection.tasks[0] || null;
10822
- const evaluation = evaluateSweepAutoAccept(task, root);
10837
+ const includeProtected = hasFlag(args, '--include-protected');
10838
+ const evaluation = evaluateSweepAutoAccept(task, root, includeProtected);
10823
10839
  if (!evaluation.eligible) {
10824
10840
  results.push({ ...evaluation, action: 'skipped', task_id: task?.id || item.id || null });
10825
10841
  continue;
10826
10842
  }
10827
10843
  const accepted = acceptReviewTask(taskDb, db, task.id, {
10828
10844
  actor,
10829
- proof: evaluation.proof,
10845
+ proof: includeProtected && (protectedLaneOf(task) || autoAcceptSweepLane(task))
10846
+ ? `${evaluation.proof}\nprotected lane accepted by owner policy (${protectedLaneOf(task) || autoAcceptSweepLane(task)})`
10847
+ : evaluation.proof,
10830
10848
  reward: parsedReward.value,
10831
10849
  lesson: String(task.review?.lesson || task.metadata?.latest_agent_lesson || ''),
10832
10850
  nextTask: String(task.review?.next_task || task.metadata?.latest_agent_next_task || ''),
@@ -1177,6 +1177,7 @@ function evaluateAutoAccept(task, options = {}) {
1177
1177
  strictVerify = true,
1178
1178
  minPasses = AGENT_CERTIFICATION_REVIEW_PASSES,
1179
1179
  acceptAll = false,
1180
+ includeProtected = false,
1180
1181
  executeVerify = true,
1181
1182
  verifyCache = null,
1182
1183
  } = options;
@@ -1200,7 +1201,7 @@ function evaluateAutoAccept(task, options = {}) {
1200
1201
  // strip, so a tag variant never slips money/deploy work past the human.
1201
1202
  const tag = String(task.tag || '').trim().toLowerCase();
1202
1203
  const deniedTag = deniedTagOf(task);
1203
- if (deniedTag) return { eligible: false, ref, reason: `denied_tag_${deniedTag}` };
1204
+ if (deniedTag && !includeProtected) return { eligible: false, ref, reason: `denied_tag_${deniedTag}` };
1204
1205
 
1205
1206
  // A protected lane declared in prose is not a protection, it is a note.
1206
1207
  // WEB-410 said "PROTECTED LANE: auth/session minting ... never self-land"
@@ -1208,7 +1209,7 @@ function evaluateAutoAccept(task, options = {}) {
1208
1209
  // safety through a tag field that authors leave empty fails open, silently.
1209
1210
  // So read the declaration where authors actually write it.
1210
1211
  const declared = declaredProtectedLane(task);
1211
- if (declared) {
1212
+ if (declared && !includeProtected) {
1212
1213
  return {
1213
1214
  eligible: false,
1214
1215
  ref,
@@ -1236,7 +1237,7 @@ function evaluateAutoAccept(task, options = {}) {
1236
1237
  // same outage as failing open, just quieter. Instead: when there is no
1237
1238
  // tag to route on, read the task text for the denied lanes themselves.
1238
1239
  // WEB-406 (Stripe checkout + paywall) landed untagged this way.
1239
- if (!tag) {
1240
+ if (!tag && !includeProtected) {
1240
1241
  const sniffed = sniffedProtectedLane(task);
1241
1242
  if (sniffed) {
1242
1243
  return {