@aria_asi/cli 0.2.30 → 0.2.32

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.
Files changed (95) hide show
  1. package/dist/aria-connector/src/connectors/claude-code.d.ts.map +1 -1
  2. package/dist/aria-connector/src/connectors/claude-code.js +115 -20
  3. package/dist/aria-connector/src/connectors/claude-code.js.map +1 -1
  4. package/dist/aria-connector/src/connectors/codex.d.ts.map +1 -1
  5. package/dist/aria-connector/src/connectors/codex.js +551 -11
  6. package/dist/aria-connector/src/connectors/codex.js.map +1 -1
  7. package/dist/aria-connector/src/connectors/doctrine-trigger-map.d.ts +7 -0
  8. package/dist/aria-connector/src/connectors/doctrine-trigger-map.d.ts.map +1 -0
  9. package/dist/aria-connector/src/connectors/doctrine-trigger-map.js +87 -0
  10. package/dist/aria-connector/src/connectors/doctrine-trigger-map.js.map +1 -0
  11. package/dist/aria-connector/src/connectors/must-read.d.ts +4 -0
  12. package/dist/aria-connector/src/connectors/must-read.d.ts.map +1 -0
  13. package/dist/aria-connector/src/connectors/must-read.js +115 -0
  14. package/dist/aria-connector/src/connectors/must-read.js.map +1 -0
  15. package/dist/aria-connector/src/connectors/opencode.d.ts.map +1 -1
  16. package/dist/aria-connector/src/connectors/opencode.js +27 -9
  17. package/dist/aria-connector/src/connectors/opencode.js.map +1 -1
  18. package/dist/aria-connector/src/connectors/runtime.d.ts.map +1 -1
  19. package/dist/aria-connector/src/connectors/runtime.js +231 -19
  20. package/dist/aria-connector/src/connectors/runtime.js.map +1 -1
  21. package/dist/aria-connector/src/connectors/shell.d.ts.map +1 -1
  22. package/dist/aria-connector/src/connectors/shell.js +76 -3
  23. package/dist/aria-connector/src/connectors/shell.js.map +1 -1
  24. package/dist/assets/hooks/aria-agent-handoff.mjs +23 -0
  25. package/dist/assets/hooks/aria-cognition-substrate-binding.mjs +121 -28
  26. package/dist/assets/hooks/aria-harness-via-sdk.mjs +126 -12
  27. package/dist/assets/hooks/aria-pre-emit-dryrun.mjs +35 -0
  28. package/dist/assets/hooks/aria-pre-tool-gate.mjs +383 -93
  29. package/dist/assets/hooks/aria-preprompt-consult.mjs +28 -2
  30. package/dist/assets/hooks/aria-preturn-memory-gate.mjs +93 -16
  31. package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +33 -1
  32. package/dist/assets/hooks/aria-stop-gate.mjs +346 -81
  33. package/dist/assets/hooks/doctrine_trigger_map.json +55 -0
  34. package/dist/assets/hooks/lib/canonical-lenses.mjs +6 -5
  35. package/dist/assets/hooks/lib/gate-loop-state.mjs +50 -0
  36. package/dist/assets/hooks/lib/hook-message-window.mjs +121 -0
  37. package/dist/assets/hooks/test-tier-lens-labeling.mjs +26 -58
  38. package/dist/assets/opencode-plugins/harness-gate/index.js +40 -5
  39. package/dist/assets/opencode-plugins/harness-stop/index.js +133 -10
  40. package/dist/runtime/auth-middleware.mjs +251 -0
  41. package/dist/runtime/codex-bridge.mjs +644 -0
  42. package/dist/runtime/discipline/CLAUDE.md +28 -0
  43. package/dist/runtime/discipline/doctrine_trigger_map.json +534 -0
  44. package/dist/runtime/doctrine_trigger_map.json +534 -0
  45. package/dist/runtime/fleet-engine.mjs +231 -0
  46. package/dist/runtime/harness-daemon.mjs +460 -0
  47. package/dist/runtime/manifest.json +1 -1
  48. package/dist/runtime/metering.mjs +100 -0
  49. package/dist/runtime/onboarding-engine.mjs +89 -0
  50. package/dist/runtime/plugin-engine.mjs +196 -0
  51. package/dist/runtime/sdk/BUNDLED.json +1 -1
  52. package/dist/runtime/sdk/index.d.ts +12 -0
  53. package/dist/runtime/sdk/index.js +120 -14
  54. package/dist/runtime/sdk/index.js.map +1 -1
  55. package/dist/runtime/service.mjs +1140 -48
  56. package/dist/runtime/workflow-engine.mjs +322 -0
  57. package/dist/sdk/BUNDLED.json +1 -1
  58. package/dist/sdk/index.d.ts +12 -0
  59. package/dist/sdk/index.js +120 -14
  60. package/dist/sdk/index.js.map +1 -1
  61. package/hooks/aria-agent-handoff.mjs +23 -0
  62. package/hooks/aria-cognition-substrate-binding.mjs +121 -28
  63. package/hooks/aria-harness-via-sdk.mjs +126 -12
  64. package/hooks/aria-pre-emit-dryrun.mjs +35 -0
  65. package/hooks/aria-pre-tool-gate.mjs +383 -93
  66. package/hooks/aria-preprompt-consult.mjs +28 -2
  67. package/hooks/aria-preturn-memory-gate.mjs +93 -16
  68. package/hooks/aria-repo-doctrine-gate.mjs +33 -1
  69. package/hooks/aria-stop-gate.mjs +346 -81
  70. package/hooks/doctrine_trigger_map.json +55 -0
  71. package/hooks/lib/canonical-lenses.mjs +6 -5
  72. package/hooks/lib/gate-loop-state.mjs +50 -0
  73. package/hooks/lib/hook-message-window.mjs +121 -0
  74. package/hooks/test-tier-lens-labeling.mjs +26 -58
  75. package/opencode-plugins/harness-gate/index.js +40 -5
  76. package/opencode-plugins/harness-stop/index.js +133 -10
  77. package/package.json +1 -1
  78. package/runtime-src/auth-middleware.mjs +251 -0
  79. package/runtime-src/codex-bridge.mjs +644 -0
  80. package/runtime-src/fleet-engine.mjs +231 -0
  81. package/runtime-src/harness-daemon.mjs +460 -0
  82. package/runtime-src/metering.mjs +100 -0
  83. package/runtime-src/onboarding-engine.mjs +89 -0
  84. package/runtime-src/plugin-engine.mjs +196 -0
  85. package/runtime-src/service.mjs +1140 -48
  86. package/runtime-src/workflow-engine.mjs +322 -0
  87. package/scripts/bundle-sdk.mjs +5 -0
  88. package/src/connectors/claude-code.ts +126 -20
  89. package/src/connectors/codex.ts +559 -10
  90. package/src/connectors/doctrine-trigger-map.ts +112 -0
  91. package/src/connectors/must-read.ts +117 -0
  92. package/src/connectors/opencode.ts +28 -9
  93. package/src/connectors/runtime.ts +241 -21
  94. package/src/connectors/shell.ts +78 -3
  95. package/dist/cli-0.2.0.tgz +0 -0
@@ -16,19 +16,19 @@
16
16
  // now catches.
17
17
  //
18
18
  // Doctrine bindings (same as PreToolUse gate):
19
- // - EIGHT_LENS_DOCTRINE.md — substantive 4+ lens application required
19
+ // - EIGHT_LENS_DOCTRINE.md — substantive 8-lens application required
20
20
  // - feedback_apply_lenses_dont_perform_them.md — block ceremonial cognition
21
21
  // - feedback_8lens_before_every_action_including_text.md — the rule this enforces
22
22
  //
23
23
  // Trigger: runs at Stop event after every assistant response. Reads
24
24
  // the just-emitted assistant text from the transcript. If non-trivial
25
25
  // (per the same triviality threshold as eight-lens-detector.ts) AND
26
- // missing 4+ substantive lenses, blocks the response.
26
+ // missing 8 substantive lenses, blocks the response.
27
27
  //
28
28
  // Triviality threshold (mirrors eight-lens-detector.ts):
29
29
  // - Trivial acks (e.g. "got it", "ok", "done") pass
30
30
  // - Short responses (<300 chars) without decision-signal phrases pass
31
- // - Otherwise: require 4+ substantive lenses
31
+ // - Otherwise: require 8 substantive lenses
32
32
  //
33
33
  // Substance check (mirrors aria-pre-tool-gate.mjs):
34
34
  // - Each lens must have ≥20 chars of non-placeholder content
@@ -52,14 +52,21 @@ import { dirname } from 'node:path';
52
52
  import { appendGateAudit } from './lib/gate-audit.mjs';
53
53
  import {
54
54
  ALL_LENS_NAMES,
55
- canonicalLensCorrectionText,
56
55
  detectCognitionLenses as detectCognitionLensesFromCanonical,
57
56
  lensNamesForTier,
57
+ PRIMARY_OWNER_LENS_NAMES,
58
58
  } from './lib/canonical-lenses.mjs';
59
+ import { registerGateBlock } from './lib/gate-loop-state.mjs';
60
+ import { collectTurnWindowFromMessages } from './lib/hook-message-window.mjs';
59
61
 
60
62
  const HOME = process.env.HOME || '/tmp';
63
+ const RUNTIME_BASE_URL =
64
+ process.env.ARIA_RUNTIME_URL ||
65
+ 'http://127.0.0.1:4319';
61
66
  const LOG = `${HOME}/.claude/aria-stop-gate.log`;
62
67
  const AUDIT_PATH = `${HOME}/.claude/aria-stop-gate-audit.jsonl`;
68
+ const GATE_LOOP_STATE_PATH = `${HOME}/.claude/.aria-gate-loop-state.json`;
69
+ const MIZAN_RECEIPT_DIR = `${HOME}/.claude/.aria-mizan-receipts`;
63
70
 
64
71
  // SDK loader — bundled at ~/.aria/sdk by `aria connect`, with client-local
65
72
  // fallbacks preserved for resilience.
@@ -167,6 +174,94 @@ async function fireGardenTurn(sessionId, userMessage, assistantResponse) {
167
174
  }
168
175
  }
169
176
 
177
+ function resolveHarnessControlToken() {
178
+ if (process.env.ARIA_HARNESS_TOKEN) return process.env.ARIA_HARNESS_TOKEN;
179
+ if (process.env.ARIA_API_KEY) return process.env.ARIA_API_KEY;
180
+ if (process.env.ARIA_MASTER_TOKEN) return process.env.ARIA_MASTER_TOKEN;
181
+ try {
182
+ const ownerTokenPath = `${HOME}/.aria/owner-token`;
183
+ if (existsSync(ownerTokenPath)) {
184
+ const token = readFileSync(ownerTokenPath, 'utf8').trim();
185
+ if (token) return token;
186
+ }
187
+ } catch {}
188
+ try {
189
+ const licensePath = `${HOME}/.aria/license.json`;
190
+ if (existsSync(licensePath)) {
191
+ const license = JSON.parse(readFileSync(licensePath, 'utf8'));
192
+ if (license.harnessToken) return String(license.harnessToken).trim();
193
+ if (license.token) return String(license.token).trim();
194
+ }
195
+ } catch {}
196
+ return '';
197
+ }
198
+
199
+ function mizanReceiptPathForSession(sessionId) {
200
+ const safe = String(sessionId || 'claude-code').replace(/[^a-zA-Z0-9_-]/g, '_');
201
+ return `${MIZAN_RECEIPT_DIR}/${safe}.json`;
202
+ }
203
+
204
+ function loadMizanReceiptState(sessionId) {
205
+ try {
206
+ const receiptPath = mizanReceiptPathForSession(sessionId);
207
+ if (!existsSync(receiptPath)) return null;
208
+ return JSON.parse(readFileSync(receiptPath, 'utf8'));
209
+ } catch {
210
+ return null;
211
+ }
212
+ }
213
+
214
+ function saveMizanReceiptState(sessionId, payload) {
215
+ try {
216
+ mkdirSync(MIZAN_RECEIPT_DIR, { recursive: true });
217
+ writeFileSync(mizanReceiptPathForSession(sessionId), JSON.stringify(payload, null, 2) + '\n');
218
+ } catch {}
219
+ }
220
+
221
+ async function runtimeMizanPost(sessionId, text, context = {}, parentReceiptId = null) {
222
+ const token = resolveHarnessControlToken();
223
+ if (!token) throw new Error('no token');
224
+ const response = await fetch(`${process.env.ARIA_RUNTIME_URL || 'http://127.0.0.1:4319'}/mizan/post`, {
225
+ method: 'POST',
226
+ headers: {
227
+ 'Content-Type': 'application/json',
228
+ Authorization: `Bearer ${token}`,
229
+ },
230
+ body: JSON.stringify({
231
+ sessionId,
232
+ text,
233
+ parentReceiptId,
234
+ context: {
235
+ sessionId,
236
+ ...context,
237
+ },
238
+ }),
239
+ });
240
+ const payload = await response.json().catch(() => ({}));
241
+ if (!response.ok) {
242
+ throw new Error(payload?.error || `mizan/post failed (${response.status})`);
243
+ }
244
+ return payload;
245
+ }
246
+
247
+ async function runtimeDecisionLog(payload) {
248
+ const token = resolveHarnessControlToken();
249
+ if (!token) throw new Error('no token');
250
+ const response = await fetch(`${process.env.ARIA_RUNTIME_URL || 'http://127.0.0.1:4319'}/decision/log`, {
251
+ method: 'POST',
252
+ headers: {
253
+ 'Content-Type': 'application/json',
254
+ Authorization: `Bearer ${token}`,
255
+ },
256
+ body: JSON.stringify(payload),
257
+ });
258
+ const body = await response.json().catch(() => ({}));
259
+ if (!response.ok) {
260
+ throw new Error(body?.error || `decision/log failed (${response.status})`);
261
+ }
262
+ return body;
263
+ }
264
+
170
265
  function audit(decision, summary) {
171
266
  const summaryText = typeof summary === 'string' ? summary : '';
172
267
  const data = summary && typeof summary === 'object' ? summary : {};
@@ -185,12 +280,12 @@ function audit(decision, summary) {
185
280
  // access"). The gated process has no disable path. Disable = remove hook
186
281
  // entry from ~/.claude/settings.json (deliberate user action, visible).
187
282
 
188
- // ── Tier-aware lens labeling (Phase 11 #59) ──────────────────────────────────
283
+ // ── Canonical lens labeling (Phase 11 #59 corrected) ────────────────────────
189
284
  //
190
- // Mirrors the same logic in aria-pre-tool-gate.mjs. Tier is read from the
191
- // most recent harness-via-sdk packet cache. Owner tier sees canonical Arabic
192
- // names; client tier sees neutral generic labels. Both tiers get the same
193
- // gate enforcement only the visible vocabulary differs.
285
+ // Mirrors the same logic in aria-pre-tool-gate.mjs. Tier is still read from
286
+ // the most recent harness-via-sdk packet cache for other policy behaviors, but
287
+ // the visible lens labels remain canonical on every surface. Readability comes
288
+ // from the prose inside each lens, not from renaming the lens itself.
194
289
  const PACKET_CACHE_PATH = `${HOME}/.claude/.aria-harness-last-packet.json`;
195
290
 
196
291
  function resolveOwnerTier() {
@@ -211,7 +306,6 @@ function resolveOwnerTier() {
211
306
  const IS_OWNER = resolveOwnerTier();
212
307
 
213
308
  const LENS_NAMES = lensNamesForTier(IS_OWNER);
214
- const CANONICAL_LENS_TEXT = canonicalLensCorrectionText();
215
309
 
216
310
  // Doctrine memory filenames are Aria-side substrate IP.
217
311
  // Client surfaces see generic descriptions instead of real filenames.
@@ -244,9 +338,12 @@ function collectDriftHits(text, triggerMap) {
244
338
  const memoryCited = memoryName && lowerText.includes(memoryName.toLowerCase());
245
339
  if (!memoryCited) {
246
340
  hits.push({
341
+ trigger_id: triggerEntry.trigger_id,
247
342
  trigger: triggerEntry.trigger,
248
343
  memory: triggerEntry.memory,
249
344
  teaching: triggerEntry.teaching,
345
+ counter_action: triggerEntry.counter_action,
346
+ message: triggerEntry.message,
250
347
  });
251
348
  }
252
349
  } catch {/* malformed regex in trigger entry — skip */}
@@ -270,6 +367,90 @@ function emitHarnessFooter({ eventName, lensCount, chars, driftCount, mizanStatu
270
367
  } catch {}
271
368
  }
272
369
 
370
+ function withLoopDirective(reasonText, gateSignature, sessionId) {
371
+ const loop = registerGateBlock({
372
+ gate: 'stop',
373
+ sessionId,
374
+ signature: gateSignature,
375
+ statePath: GATE_LOOP_STATE_PATH,
376
+ });
377
+ if (!loop.loopDetected) return reasonText;
378
+ return `${reasonText}
379
+
380
+ [LOOP_DETECTED gate=stop repeats=${loop.totalCount}]
381
+ Stop retrying the same output shape unchanged.
382
+ Next response must do this in order:
383
+ 1. Name the exact stop-gate failure in one line.
384
+ 2. Re-emit only the missing structure: <cognition>, <verify>, <expected>, and/or <reflection>.
385
+ 3. Change the draft materially before retrying. Do not repeat the same prose with renamed labels.
386
+ 4. If the blocker is stale gate state, stale ledger residue, or a gate artifact from a prior bug, say that explicitly instead of inventing fake proof.`;
387
+ }
388
+
389
+ function buildForceReauthorReason({
390
+ source,
391
+ reason,
392
+ violations = [],
393
+ rewritten = '',
394
+ recipeAddendum = '',
395
+ driftHits = [],
396
+ lensCount = 0,
397
+ requiredLenses = REQUIRED_LENSES,
398
+ }) {
399
+ const triggerLines = driftHits.slice(0, 6).map((hit, index) => {
400
+ const label = hit.trigger_id || hit.trigger || `trigger-${index + 1}`;
401
+ const teaching = hit.teaching || hit.message || 'Doctrine trigger matched; re-author with substrate-backed correction.';
402
+ const correction = hit.counter_action ? ` Required correction: ${hit.counter_action}` : '';
403
+ const memory = hit.memory ? ` (${hit.memory})` : '';
404
+ return `- ${label}${memory}: ${teaching}${correction}`;
405
+ }).join('\n');
406
+
407
+ return [
408
+ '=== ARIA FORCE_REAUTHOR ===',
409
+ `source: ${source}`,
410
+ `reason: ${reason}`,
411
+ '',
412
+ 'This is not a terminal error. It is a forced redo instruction for the next model draft.',
413
+ 'Do not emit the blocked text. Re-author it so the model learns the mechanism it violated.',
414
+ '',
415
+ 'TEACHING:',
416
+ '- Gates must force cognition and quality, not merely throw, warn, or stop.',
417
+ '- The redo must change the answer shape: name the failed mechanism, cite real evidence, and remove the drift pattern.',
418
+ '- Do not bypass by disabling tools, shortening runtime, asking the user to resolve memory-backed ambiguity, or giving an apology loop.',
419
+ triggerLines ? `\nTRIGGERED DOCTRINE:\n${triggerLines}` : '',
420
+ violations.length ? `\nVIOLATIONS TO FIX:\n${violations.map((v) => `- ${v}`).join('\n')}` : '',
421
+ rewritten ? `\nMIZAN REWRITE SEED:\n${rewritten}` : '',
422
+ recipeAddendum || '',
423
+ '',
424
+ 'REQUIRED REDO SHAPE:',
425
+ '1. One sentence: the failed mechanism, not an apology.',
426
+ '2. Evidence checked: file/line, command output, endpoint response, or explicit "unverified".',
427
+ `3. <cognition> with ${requiredLenses} substantive lenses. Observed: ${lensCount}/${requiredLenses}.`,
428
+ '4. <applied_cognition> with decision_delta, dominant_domain, binds_to, expected_predicate, and artifact_change.',
429
+ '5. Corrected action/claim with no bypass, no downgraded path, and no fake proof.',
430
+ '6. If work remains, state the exact next real action or tracked task. No verbal flag-and-move.',
431
+ '',
432
+ 'COGNITION TEMPLATE:',
433
+ '<cognition>',
434
+ 'truth: <verified facts and exact substrate>',
435
+ 'harm: <what goes wrong if this is false>',
436
+ 'trust: <how this honors Hamza directives and saved memory>',
437
+ 'power: <why this uses capability responsibly, not convenience>',
438
+ 'reflection: <mechanism failure and correction>',
439
+ 'context: <relevant repo/runtime state>',
440
+ 'impact: <expected next effect>',
441
+ 'beauty: <simplest durable form>',
442
+ '</cognition>',
443
+ '<applied_cognition>',
444
+ 'decision_delta: <what changed because cognition ran; not none>',
445
+ 'dominant_domain: <engineering_quality | trust | operations | security | product | ...>',
446
+ 'binds_to: <the exact answer, tool call, file mutation, deploy, review, or decision>',
447
+ 'expected_predicate: <observable numeric, boolean, state-string, command result, endpoint result, or explicit unverified boundary>',
448
+ 'artifact_change: <semantic effect on the artifact/output, not a task restatement>',
449
+ '</applied_cognition>',
450
+ '=== END FORCE_REAUTHOR ===',
451
+ ].filter(Boolean).join('\n');
452
+ }
453
+
273
454
  // Lens substance check — same constants as aria-pre-tool-gate.mjs.
274
455
  // Hamza directive 2026-04-28: all 8 canonical lenses required, not 4-of-8.
275
456
  const REQUIRED_LENSES = 8;
@@ -291,6 +472,19 @@ function detectCognitionLenses(text) {
291
472
  });
292
473
  }
293
474
 
475
+ function assistantViolatesUserCorrection(userText, assistantText) {
476
+ const user = String(userText || '');
477
+ const assistant = String(assistantText || '');
478
+ if (!user || !assistant) return null;
479
+ const explicitStop = /\b(?:stop|do\s+not|don't|quit|cease)\b[\s\S]{0,180}\b(?:deploy|redeploy|restart|rollout|kubectl|command|pods?|listen|words)\b/i.test(user);
480
+ const assistantContinuesMutation = /\b(?:kubectl|rollout\s+restart|deploy|redeploy|restart\s+(?:mac|pods?)|manual(?:ly)?\s+restart|execute\s+directly)\b/i.test(assistant);
481
+ if (explicitStop && assistantContinuesMutation) return 'assistant continued deploy/restart language after an explicit user stop/listen directive';
482
+ const userContradictsMacPods = /\b(?:mac\s+lanes?|mac\s+pods?|mlx-mac)\b[\s\S]{0,160}\b(?:not\s+pods?|no\s+such\s+thing|non[-\s]?existent|do(?:es)?\s+not\s+exist|don't\s+exist)\b|\b(?:no\s+such\s+thing|non[-\s]?existent|do(?:es)?\s+not\s+exist|don't\s+exist)\b[\s\S]{0,160}\b(?:mac\s+lanes?|mac\s+pods?|mlx-mac|pods?)\b/i.test(user);
483
+ const assistantRepeatsMacPods = /\b(?:mac\s+lane\s+pods?|mac\s+lanes?\s*:\s*offline|restart\s+mac\s+lanes?|deployment\/mlx-mac|mlx-mac-[\w-]+|kubernetes\s+(?:pods?|deployments?))\b/i.test(assistant);
484
+ if (userContradictsMacPods && assistantRepeatsMacPods) return 'assistant repeated the contradicted Mac-lanes-as-pods/deployments assumption';
485
+ return null;
486
+ }
487
+
294
488
  // Read event JSON from stdin (Claude Code spec).
295
489
  let input = '';
296
490
  for await (const chunk of process.stdin) input += chunk;
@@ -302,6 +496,8 @@ try {
302
496
  audit('allow-parse-error', 'stdin not JSON');
303
497
  process.exit(0);
304
498
  }
499
+ const gateSessionId = String(event.session_id || 'claude-code').replace(/[^a-zA-Z0-9_-]/g, '_');
500
+ const sessionMizanState = loadMizanReceiptState(event.session_id || 'claude-code');
305
501
 
306
502
  // Read assistant text from THIS turn — Claude Code splits a single
307
503
  // logical assistant response into multiple transcript entries by
@@ -326,8 +522,13 @@ const SYSTEM_REMINDER_THRESHOLD = 0.6;
326
522
 
327
523
  const transcriptPath = event.transcript_path ?? event.transcriptPath;
328
524
  let assistantText = '';
329
- // Phase 11 #42: also capture the last real user message for gardenTurn writes.
330
525
  let lastUserMessage = '';
526
+ const messageWindow = collectTurnWindowFromMessages(event.messages, {
527
+ systemReminderRx: SYSTEM_REMINDER_RX,
528
+ systemReminderThreshold: SYSTEM_REMINDER_THRESHOLD,
529
+ });
530
+ assistantText = messageWindow.assistantText;
531
+ lastUserMessage = messageWindow.lastUserMessage;
331
532
  if (transcriptPath && existsSync(transcriptPath)) {
332
533
  try {
333
534
  const lines = readFileSync(transcriptPath, 'utf-8').split('\n').filter(Boolean);
@@ -370,7 +571,12 @@ if (transcriptPath && existsSync(transcriptPath)) {
370
571
  } catch {}
371
572
  }
372
573
  // Reverse so chunks are in chronological order (we walked backward).
373
- assistantText = textChunks.reverse().join('\n\n');
574
+ const transcriptAssistantText = textChunks.reverse().join('\n\n');
575
+ if (transcriptAssistantText) {
576
+ assistantText = assistantText
577
+ ? [assistantText, transcriptAssistantText].filter((text, index, arr) => arr.indexOf(text) === index).join('\n\n')
578
+ : transcriptAssistantText;
579
+ }
374
580
  } catch {}
375
581
  }
376
582
 
@@ -379,6 +585,20 @@ if (!assistantText) {
379
585
  process.exit(0);
380
586
  }
381
587
 
588
+ const userCorrectionViolation = assistantViolatesUserCorrection(lastUserMessage, assistantText);
589
+ if (userCorrectionViolation) {
590
+ audit('block-user-correction-ignored', `reason=${userCorrectionViolation} chars=${assistantText.length}`);
591
+ const reason = buildForceReauthorReason({
592
+ source: 'stop/user-correction',
593
+ reason: userCorrectionViolation,
594
+ violations: ['Assistant continued a plan after the user correction invalidated it. Redo must quote the correction and pause mutation pending substrate re-evaluation.'],
595
+ lensCount: 0,
596
+ requiredLenses: REQUIRED_LENSES,
597
+ });
598
+ console.log(JSON.stringify({ decision: 'block', reason }));
599
+ process.exit(2);
600
+ }
601
+
382
602
  // Triviality check — same as eight-lens-detector.ts
383
603
  const trimmed = assistantText.trim();
384
604
  if (TRIVIAL_ACK_RX.test(trimmed)) {
@@ -412,13 +632,13 @@ const cog = detectCognitionLenses(assistantText);
412
632
  // with 0/8 lenses to fall through unchecked.
413
633
  if (cog.count < REQUIRED_LENSES) {
414
634
  audit('block_no_cognition_block_di', { count: cog.count, required: REQUIRED_LENSES, names: cog.names, chars: assistantText.length });
415
- const reason = `Aria stop-gate: insufficient cognition lenses.
416
-
417
- This non-trivial assistant response (${assistantText.length} chars) has ${cog.count}/${REQUIRED_LENSES} substantive cognition lenses. Per feedback_8lens_before_every_action_including_text.md and Hamza directive 2026-04-28, every non-trivial response must carry <cognition>...</cognition> with ${REQUIRED_LENSES} substantive lenses (each >= ${SUBSTANCE_MIN_CHARS} chars of non-placeholder content).
418
-
419
- Detected lenses: ${cog.names.length > 0 ? cog.names.join(', ') : 'none'}.
420
-
421
- Re-emit with a <cognition> block containing all ${REQUIRED_LENSES} canonical lenses. Primary set: ${CANONICAL_LENS_TEXT}`;
635
+ const reason = withLoopDirective(buildForceReauthorReason({
636
+ source: 'stop/no-cognition',
637
+ reason: `non-trivial assistant response (${assistantText.length} chars) has ${cog.count}/${REQUIRED_LENSES} substantive cognition lenses`,
638
+ violations: [`Detected lenses: ${cog.names.length > 0 ? cog.names.join(', ') : 'none'}. Re-emit with all required visible labels: ${LENS_NAMES.join(', ')}.`],
639
+ lensCount: cog.count,
640
+ requiredLenses: REQUIRED_LENSES,
641
+ }), `stop:no-cognition-di:${cog.count}`, gateSessionId);
422
642
  emitHarnessFooter({
423
643
  eventName: 'block_no_cognition_block',
424
644
  lensCount: cog.count,
@@ -609,7 +829,7 @@ if (cog.count >= REQUIRED_LENSES) {
609
829
  //
610
830
  // Block emit if ledger.openCount > 0 after scanning the current turn.
611
831
  // Block reason names each open discovery and the suggested resolution.
612
- const sessionId = (event.session_id || 'claude-code').replace(/[^a-zA-Z0-9_-]/g, '_');
832
+ const sessionId = gateSessionId;
613
833
  const LEDGER_PATH = `${HOME}/.claude/aria-discoveries-${sessionId}.jsonl`;
614
834
  const DISCOVERY_RX = /(?:\bi\s+(?:found|noticed|discovered|spotted)[^.\n]{0,160}(?:bug|issue|defect|broken|buggy|wrong|crash|fail|missing|stale|outdated|leak|vulnerability)|\bthis\s+(?:is|would\s+be)\s+(?:broken|buggy|wrong|stale|outdated|insecure|leaking|crashing|failing)|\b(?:latent|silent|hidden)\s+(?:bug|defect|issue|fail|crash|leak)|\bdoctrine\s+violation\b|\bgraceful\s+degradation\s+(?:in|at|inside|within)\s+\S)/gi;
615
835
  const PROSE_RESOLUTION_RX = /(?:fix(?:ing|ed)?\s+(?:now|in[- ]flight|inline|in\s+the\s+same\s+turn)|patch\s+applied|TaskCreate|task\s+(?:created|tracked)|tracked\s+as\s+#?\d+|linear[- ]?issue|created\s+(?:linear|task))/i;
@@ -759,12 +979,36 @@ if (cog.count >= REQUIRED_LENSES) {
759
979
  let ledgerOpenCount = 0;
760
980
  let ledgerOpenSamples = [];
761
981
  let ledgerCorruptedCount = 0;
982
+ const ledgerRewriteRows = [];
983
+ let ledgerNeedsRewrite = false;
762
984
  try {
763
985
  if (existsSync(LEDGER_PATH)) {
764
986
  const lines = readFileSync(LEDGER_PATH, 'utf8').split('\n').filter(Boolean);
765
987
  for (const line of lines) {
766
988
  try {
767
989
  const e = JSON.parse(line);
990
+ const isSubstrateBindingArtifact =
991
+ (e.source === 'aria-cognition-substrate-binding') &&
992
+ (e.kind === 'substrate_binding_gap' || (typeof e.text === 'string' && e.text.startsWith('substrate_binding:')));
993
+ const isOpenBeforeAutoHeal = e.status === 'open' || e.resolution_status === 'open';
994
+ if (isSubstrateBindingArtifact && isOpenBeforeAutoHeal) {
995
+ // If the current emit already passed aria-cognition-substrate-binding
996
+ // and reached stop-gate, older open substrate-binding rows from the
997
+ // same session are stale gate artifacts, not live unresolved
998
+ // discoveries. Auto-heal them so the discovery ledger cannot loop
999
+ // forever on the residue of a gate bug.
1000
+ ledgerNeedsRewrite = true;
1001
+ e.status = 'resolved';
1002
+ e.resolution_status = 'resolved';
1003
+ e.resolved_at = new Date().toISOString();
1004
+ e.resolved_by = 'subsequent_valid_substrate_bound_cognition';
1005
+ e.resolutionType = 'subsequent_valid_substrate_bound_cognition';
1006
+ e.proofOfFix = {
1007
+ type: 'subsequent_valid_substrate_bound_cognition',
1008
+ anchorTs: new Date().toISOString(),
1009
+ evidence: 'Current emit passed aria-cognition-substrate-binding and reached aria-stop-gate; stale substrate-binding ledger artifacts from the earlier cognition bug were auto-cleared.',
1010
+ };
1011
+ }
768
1012
  const isOpen = e.status === 'open' || e.resolution_status === 'open';
769
1013
  const isLegacyTracked = e.status === 'tracked';
770
1014
  const proofValid = e.proofOfFix
@@ -784,8 +1028,16 @@ if (cog.count >= REQUIRED_LENSES) {
784
1028
  ledgerOpenSamples.push(`${tag}${e.text || '(no text)'}`);
785
1029
  }
786
1030
  }
1031
+ ledgerRewriteRows.push(JSON.stringify(e));
787
1032
  } catch {/* skip malformed line */}
788
1033
  }
1034
+ if (ledgerNeedsRewrite) {
1035
+ try {
1036
+ writeFileSync(LEDGER_PATH, `${ledgerRewriteRows.join('\n')}\n`);
1037
+ } catch (rewriteErr) {
1038
+ audit('ledger-autoheal-write-err', `${String(rewriteErr).slice(0, 200)}`);
1039
+ }
1040
+ }
789
1041
  }
790
1042
  } catch {/* ledger unreadable — degrade to drift-only */}
791
1043
 
@@ -1040,7 +1292,7 @@ if (cog.count >= REQUIRED_LENSES) {
1040
1292
  const FILE_LINE_RX = /([\w./\-]+\.[a-zA-Z]{1,5})\s*[:\s]\s*(\d+(?:[-:]\d+)?)/g;
1041
1293
  const inlineDictations = [];
1042
1294
  const lensRangePositions = [];
1043
- for (const lensName of LENS_NAMES_CANONICAL) {
1295
+ for (const lensName of PRIMARY_OWNER_LENS_NAMES) {
1044
1296
  const lensRx = new RegExp(`\\b${lensName}\\s*(?:lens)?\\s*[:\\-]`, 'gi');
1045
1297
  let m;
1046
1298
  while ((m = lensRx.exec(assistantText)) !== null) {
@@ -1138,10 +1390,11 @@ if (cog.count >= REQUIRED_LENSES) {
1138
1390
  if (apiKey && assistantText && assistantText.length > 0) {
1139
1391
  const client = new HTTPHarnessClient({
1140
1392
  baseUrl:
1393
+ process.env.ARIA_RUNTIME_URL ||
1141
1394
  process.env.ARIA_HIVE_RUNTIME_URL ||
1142
1395
  process.env.ARIA_HARNESS_BASE_URL ||
1143
1396
  process.env.ARIA_HARNESS_URL ||
1144
- 'https://harness.ariasos.com',
1397
+ RUNTIME_BASE_URL,
1145
1398
  apiKey,
1146
1399
  });
1147
1400
  const v = await client.validateOutput(assistantText, sessionId);
@@ -1255,7 +1508,16 @@ if (cog.count >= REQUIRED_LENSES) {
1255
1508
  }
1256
1509
  })();
1257
1510
 
1258
- const reason = `Aria Stop-gate output-quality block. Cognition passed (${cog.count}/${REQUIRED_LENSES}) but output failed quality gates:\n\n${violations.join('\n\n')}${rewritten ? `\n\nMizan rewrite suggestion:\n${rewritten}` : ''}\n\nRe-draft addressing the violations above. No process-level disable path — gates are unconditional from the gated process per Hamza directive 2026-04-27.${recipeAddendum}`;
1511
+ const reason = withLoopDirective(buildForceReauthorReason({
1512
+ source: 'stop/output-quality',
1513
+ reason: `cognition passed (${cog.count}/${REQUIRED_LENSES}) but output failed quality gates`,
1514
+ violations,
1515
+ rewritten,
1516
+ recipeAddendum,
1517
+ driftHits,
1518
+ lensCount: cog.count,
1519
+ requiredLenses: REQUIRED_LENSES,
1520
+ }), `stop:output-qc:${violations.join('|').slice(0, 240)}`, gateSessionId);
1259
1521
 
1260
1522
  audit(`block-output-qc`, `mizan=${mizanBlock?'y':'n'} warn-reflect=${compelReflection?'y':'n'} drift=${driftHits.length} code=${codeQualityHits.length} discoveries-open=${ledgerOpenCount} impl-coupling=${implCouplingHits.length}`);
1261
1523
  emitHarnessFooter({
@@ -1398,31 +1660,20 @@ const dalioHasMeasurablePredicate = dalioExpectedText
1398
1660
 
1399
1661
  // Block stop if non-trivial action taken AND expected block is missing
1400
1662
  if (hadNonTrivialAction && (!dalioExpectedMatch || !dalioHasMeasurablePredicate)) {
1401
- const missingReason = dalioExpectedMatch
1402
- ? `Aria Stop-gate: action taken in this turn had an <expected> block, but it contains only qualitative drift phrases without a measurable predicate. Qualitative drift is not accountability — it defeats the Dalio feedback loop.
1403
-
1404
- Your <expected> block must contain at least one measurable predicate:
1405
- Numeric: exit_code==0, count>=1, error_rate=0%, latency<200ms
1406
- • Boolean: exit=0, status=healthy, rc=0, file=exists
1407
- • State-string: "status=running", "200 OK", "no_error"
1408
-
1409
- REJECTED phrases: "better", "improved", "should work", "more reliable", "cleaner"
1410
-
1411
- Re-emit with a corrected <expected> block. Per doctrine:dalio_expected_required — no bypass path.`
1412
- : `Aria Stop-gate: a non-trivial action (${lastActionSummary.slice(0, 120)}) was taken in this turn but no <expected> block was found in the assistant text.
1413
-
1414
- Per doctrine:dalio_expected_required, every non-trivial action must declare what measurable outcome it expects BEFORE the action fires. This is Dalio Loop Layer 1 — without it, outcome comparison is impossible and learning collapses.
1415
-
1416
- Add to your assistant text:
1417
-
1418
- <expected>
1419
- predicate: <exact measurable assertion — e.g. "exit_code==0", "status=running", "count=3 of 3">
1420
- measurable_type: numeric | boolean | state_string
1421
- threshold: <optional>
1422
- eval_window_minutes: <optional>
1423
- </expected>
1424
-
1425
- No bypass — pre-tool-gate enforces this BEFORE the action; stop-gate enforces it AFTER. Both gates are now wired.`;
1663
+ const missingReason = withLoopDirective(buildForceReauthorReason({
1664
+ source: 'stop/dalio-expected',
1665
+ reason: dalioExpectedMatch
1666
+ ? 'action had an <expected> block, but it was qualitative instead of measurable'
1667
+ : `non-trivial action (${lastActionSummary.slice(0, 120)}) had no <expected> block`,
1668
+ violations: [
1669
+ dalioExpectedMatch
1670
+ ? 'Rejected qualitative expected phrases: better, improved, should work, more reliable, cleaner. Expected outcome must be numeric, boolean, or state-string.'
1671
+ : 'Missing <expected> block after non-trivial action. Dalio feedback loop cannot compare outcome without a measurable predicate.',
1672
+ 'Required block: <expected> predicate: "exit_code==0" | "status=running" | "count=3 of 3"; measurable_type: numeric | boolean | state_string; threshold/eval_window optional.',
1673
+ ],
1674
+ lensCount: cog.count,
1675
+ requiredLenses: REQUIRED_LENSES,
1676
+ }), `stop:dalio-expected:${hadNonTrivialAction}:${!!dalioExpectedMatch}:${dalioHasMeasurablePredicate}`, gateSessionId);
1426
1677
 
1427
1678
  audit('block-dalio-expected-missing', `hadNonTrivialAction=${hadNonTrivialAction} expectedPresent=${!!dalioExpectedMatch} measurable=${dalioHasMeasurablePredicate}`);
1428
1679
  emitHarnessFooter({
@@ -1445,7 +1696,37 @@ No bypass — pre-tool-gate enforces this BEFORE the action; stop-gate enforces
1445
1696
  // Per feedback_no_timeouts_doctrine.md: no AbortController/setTimeout timeout.
1446
1697
  {
1447
1698
  const DALIO_LEDGER_PATH = `${HOME}/.claude/.aria-dalio-ledger.jsonl`;
1448
- const ARIA_SOUL_DECISIONS_URL = 'http://aria-soul.aria.svc.cluster.local:8080/api/decisions';
1699
+ let postReceipt = null;
1700
+ try {
1701
+ const post = await runtimeMizanPost(
1702
+ event.session_id || 'claude-code',
1703
+ assistantText.slice(0, 8000),
1704
+ {
1705
+ message: lastUserMessage || `stop-gate turn (${assistantText.length} chars)`,
1706
+ plannedApproach: lastActionSummary || 'Finalize and validate the just-emitted Claude response.',
1707
+ platform: 'claude-code',
1708
+ stage: 'hook-stop-post',
1709
+ },
1710
+ sessionMizanState?.receipt?.receiptId || null,
1711
+ );
1712
+ postReceipt = post?.receipt || null;
1713
+ if (postReceipt) {
1714
+ saveMizanReceiptState(event.session_id || 'claude-code', {
1715
+ ...(sessionMizanState || {}),
1716
+ updatedAt: new Date().toISOString(),
1717
+ sessionId: event.session_id || 'claude-code',
1718
+ postReceipt,
1719
+ postResult: post?.result || null,
1720
+ postContract: post?.contract || null,
1721
+ postSummary: post?.summary || null,
1722
+ });
1723
+ }
1724
+ } catch (postErr) {
1725
+ console.error(
1726
+ `[aria-stop-gate] MIZAN POST FAILED — runtime receipt not recorded. Error: ${postErr instanceof Error ? postErr.message : String(postErr)}`,
1727
+ );
1728
+ audit('mizan-post-failed', `session=${event.session_id || 'claude-code'} err=${String(postErr).slice(0, 200)}`);
1729
+ }
1449
1730
 
1450
1731
  const ledgerEntry = {
1451
1732
  ts: new Date().toISOString(),
@@ -1469,7 +1750,11 @@ No bypass — pre-tool-gate enforces this BEFORE the action; stop-gate enforces
1469
1750
  measurable_type: 'state_string',
1470
1751
  }
1471
1752
  : null,
1472
- source: 'claude-code-stop-gate',
1753
+ metadata: {
1754
+ pre_receipt_id: sessionMizanState?.receipt?.receiptId || null,
1755
+ post_receipt_id: postReceipt?.receiptId || null,
1756
+ },
1757
+ source: 'claude-code-stop-gate-runtime',
1473
1758
  model_used: 'claude-opus-4-7',
1474
1759
  };
1475
1760
 
@@ -1484,40 +1769,20 @@ No bypass — pre-tool-gate enforces this BEFORE the action; stop-gate enforces
1484
1769
  );
1485
1770
  }
1486
1771
 
1487
- // POST to aria-soul decision API — fire-and-forget, but LOUD on error
1488
- const dalioHarnessToken = process.env.ARIA_HARNESS_TOKEN
1489
- || (isOwnerTier() ? (process.env.ARIA_MASTER_TOKEN || process.env.ARIA_API_KEY || '') : '');
1490
-
1491
- fetch(ARIA_SOUL_DECISIONS_URL, {
1492
- method: 'POST',
1493
- headers: {
1494
- 'Content-Type': 'application/json',
1495
- ...(dalioHarnessToken ? { Authorization: `Bearer ${dalioHarnessToken}` } : {}),
1496
- },
1497
- body: JSON.stringify(ledgerEntry),
1498
- }).then((resp) => {
1499
- if (!resp.ok) {
1500
- // LOUD telemetry per feedback_canonical_secrets_governance.md
1501
- console.error(
1502
- `[aria-stop-gate] DALIO POST FAILED — aria-soul responded HTTP ${resp.status}. ` +
1503
- `Local mirror written to ${DALIO_LEDGER_PATH}. Session: ${ledgerEntry.session_id}`,
1504
- );
1505
- audit('dalio-post-failed', `http=${resp.status} session=${ledgerEntry.session_id}`);
1506
- } else {
1507
- audit('dalio-post-ok', `session=${ledgerEntry.session_id} action=${lastActionSummary.slice(0, 80)}`);
1508
- }
1509
- }).catch((err) => {
1510
- // Network failure — LOUD, never silent
1772
+ try {
1773
+ await runtimeDecisionLog(ledgerEntry);
1774
+ audit('dalio-post-ok', `session=${ledgerEntry.session_id} action=${lastActionSummary.slice(0, 80)}`);
1775
+ } catch (err) {
1511
1776
  console.error(
1512
- `[aria-stop-gate] DALIO POST NETWORK ERROR could not reach ${ARIA_SOUL_DECISIONS_URL}. ` +
1777
+ `[aria-stop-gate] DALIO POST FAILEDruntime /decision/log rejected or unreachable. ` +
1513
1778
  `Local mirror written to ${DALIO_LEDGER_PATH}. Error: ${err instanceof Error ? err.message : String(err)}`,
1514
1779
  );
1515
- audit('dalio-post-network-err', `err=${String(err).slice(0, 200)} session=${ledgerEntry.session_id}`);
1516
- });
1780
+ audit('dalio-post-runtime-err', `err=${String(err).slice(0, 200)} session=${ledgerEntry.session_id}`);
1781
+ }
1517
1782
  }
1518
1783
 
1519
- // Block — non-trivial response without 4+ substantive lenses.
1520
- const reason = `Aria Stop-gate: non-trivial assistant response without 4+ substantive cognition lenses. Found ${cog.count}/${REQUIRED_LENSES}+ (lenses: ${cog.names.join(', ') || 'none'}). Doctrine is action-coupled — text decisions ARE actions, and reflexive replies fail this gate the same way reflexive Bash does.
1784
+ // Block — non-trivial response without all required substantive lenses.
1785
+ const reason = withLoopDirective(`Aria Stop-gate: non-trivial assistant response without all required substantive cognition lenses. Found ${cog.count}/${REQUIRED_LENSES} (lenses: ${cog.names.join(', ') || 'none'}). Doctrine is action-coupled — text decisions ARE actions, and reflexive replies fail this gate the same way reflexive Bash does.
1521
1786
 
1522
1787
  Re-emit the response with substantive lens application BEFORE drafting. Each lens must have ≥${SUBSTANCE_MIN_CHARS} chars of non-placeholder content:
1523
1788
 
@@ -1534,7 +1799,7 @@ Re-emit the response with substantive lens application BEFORE drafting. Each len
1534
1799
 
1535
1800
  The block reflects work done BEFORE drafting. Don't emit it as ceremony; apply each lens as a thinking tool. Substance check defeats ritual emission.
1536
1801
 
1537
- No per-command bypass (mirrors aria-pre-tool-gate.mjs v3 doctrine). No env-var disable path either — gates are unconditional from the gated process per Hamza directive 2026-04-27. If the gate misfires on legitimate cognition, fix the gate.`;
1802
+ No per-command bypass (mirrors aria-pre-tool-gate.mjs v3 doctrine). No env-var disable path either — gates are unconditional from the gated process per Hamza directive 2026-04-27. If the gate misfires on legitimate cognition, fix the gate.`, `stop:lens-missing:${cog.count}`, gateSessionId);
1538
1803
 
1539
1804
  audit(`block`, `lenses=${cog.count}/${REQUIRED_LENSES} chars=${assistantText.length}`);
1540
1805
  emitHarnessFooter({
@@ -1547,5 +1812,5 @@ emitHarnessFooter({
1547
1812
  codeCount: 0,
1548
1813
  implCouplingCount: 0,
1549
1814
  });
1550
- console.log(JSON.stringify({ decision: 'block', reason }));
1815
+ console.log(JSON.stringify({ decision: 'block', reason: buildForceReauthorReason({ source: 'stop/lens-missing', reason, lensCount: cog.count, requiredLenses: REQUIRED_LENSES }) }));
1551
1816
  process.exit(2);