@aria_asi/cli 0.2.29 → 0.2.31

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 (98) 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 +88 -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 +526 -2
  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 +111 -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 +2 -0
  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/aria-connector/src/self-update.d.ts +2 -1
  25. package/dist/aria-connector/src/self-update.d.ts.map +1 -1
  26. package/dist/aria-connector/src/self-update.js +84 -8
  27. package/dist/aria-connector/src/self-update.js.map +1 -1
  28. package/dist/assets/hooks/aria-cognition-substrate-binding.mjs +53 -34
  29. package/dist/assets/hooks/aria-harness-via-sdk.mjs +126 -12
  30. package/dist/assets/hooks/aria-pre-tool-gate.mjs +185 -76
  31. package/dist/assets/hooks/aria-preturn-memory-gate.mjs +63 -14
  32. package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +2 -0
  33. package/dist/assets/hooks/aria-stop-gate.mjs +225 -52
  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 +24 -2
  39. package/dist/assets/opencode-plugins/harness-stop/index.js +94 -5
  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 +12 -0
  43. package/dist/runtime/discipline/doctrine_trigger_map.json +479 -0
  44. package/dist/runtime/doctrine_trigger_map.json +479 -0
  45. package/dist/runtime/fleet-engine.mjs +231 -0
  46. package/dist/runtime/harness-daemon.mjs +433 -0
  47. package/dist/runtime/local-phase.mjs +18 -0
  48. package/dist/runtime/manifest.json +1 -1
  49. package/dist/runtime/metering.mjs +100 -0
  50. package/dist/runtime/onboarding-engine.mjs +89 -0
  51. package/dist/runtime/plugin-engine.mjs +196 -0
  52. package/dist/runtime/sdk/BUNDLED.json +1 -1
  53. package/dist/runtime/sdk/index.d.ts +7 -0
  54. package/dist/runtime/sdk/index.js +120 -14
  55. package/dist/runtime/sdk/index.js.map +1 -1
  56. package/dist/runtime/service.mjs +1464 -67
  57. package/dist/runtime/vendor/aria-gate-runtime/index.d.ts +1 -1
  58. package/dist/runtime/vendor/aria-gate-runtime/index.d.ts.map +1 -1
  59. package/dist/runtime/vendor/aria-gate-runtime/index.js +16 -1
  60. package/dist/runtime/vendor/aria-gate-runtime/index.js.map +1 -1
  61. package/dist/runtime/workflow-engine.mjs +322 -0
  62. package/dist/sdk/BUNDLED.json +1 -1
  63. package/dist/sdk/index.d.ts +7 -0
  64. package/dist/sdk/index.js +120 -14
  65. package/dist/sdk/index.js.map +1 -1
  66. package/hooks/aria-cognition-substrate-binding.mjs +53 -34
  67. package/hooks/aria-harness-via-sdk.mjs +126 -12
  68. package/hooks/aria-pre-tool-gate.mjs +185 -76
  69. package/hooks/aria-preturn-memory-gate.mjs +63 -14
  70. package/hooks/aria-repo-doctrine-gate.mjs +2 -0
  71. package/hooks/aria-stop-gate.mjs +225 -52
  72. package/hooks/lib/canonical-lenses.mjs +6 -5
  73. package/hooks/lib/gate-loop-state.mjs +50 -0
  74. package/hooks/lib/hook-message-window.mjs +121 -0
  75. package/hooks/test-tier-lens-labeling.mjs +26 -58
  76. package/opencode-plugins/harness-gate/index.js +24 -2
  77. package/opencode-plugins/harness-stop/index.js +94 -5
  78. package/package.json +2 -2
  79. package/runtime-src/auth-middleware.mjs +251 -0
  80. package/runtime-src/codex-bridge.mjs +644 -0
  81. package/runtime-src/fleet-engine.mjs +231 -0
  82. package/runtime-src/harness-daemon.mjs +433 -0
  83. package/runtime-src/local-phase.mjs +18 -0
  84. package/runtime-src/metering.mjs +100 -0
  85. package/runtime-src/onboarding-engine.mjs +89 -0
  86. package/runtime-src/plugin-engine.mjs +196 -0
  87. package/runtime-src/service.mjs +1464 -67
  88. package/runtime-src/workflow-engine.mjs +322 -0
  89. package/scripts/bundle-sdk.mjs +5 -0
  90. package/src/connectors/claude-code.ts +98 -20
  91. package/src/connectors/codex.ts +534 -1
  92. package/src/connectors/doctrine-trigger-map.ts +112 -0
  93. package/src/connectors/must-read.ts +113 -0
  94. package/src/connectors/opencode.ts +3 -0
  95. package/src/connectors/runtime.ts +241 -21
  96. package/src/connectors/shell.ts +78 -3
  97. package/src/self-update.ts +89 -8
  98. package/dist/cli-0.2.0.tgz +0 -0
@@ -42,10 +42,21 @@ import { dirname } from 'node:path';
42
42
  import { homedir } from 'node:os';
43
43
  import { spawnSync } from 'node:child_process';
44
44
  import { createHmac, randomBytes as cryptoRandomBytes } from 'node:crypto';
45
+ import { lensNamesForTier } from './lib/canonical-lenses.mjs';
46
+ import { registerGateBlock } from './lib/gate-loop-state.mjs';
47
+ import {
48
+ collectRecentAssistantTextsFromMessages,
49
+ extractTextFromContent,
50
+ isMostlySystemReminder,
51
+ isToolResultOnlyContent,
52
+ normalizeContent,
53
+ normalizeRole,
54
+ } from './lib/hook-message-window.mjs';
45
55
 
46
56
  const HOME = process.env.HOME || '/tmp';
47
57
  const LOG = `${HOME}/.claude/aria-pre-tool-gate.log`;
48
58
  const HEARTBEAT = `${HOME}/.claude/aria-pre-tool-gate-heartbeat.jsonl`;
59
+ const GATE_LOOP_STATE_PATH = `${HOME}/.claude/.aria-gate-loop-state.json`;
49
60
 
50
61
  // ── Heartbeat OUTSIDE the crash boundary (doctrine #123) ───────────────
51
62
  // Per feedback_ledger_writes_outside_crash_boundary.md + doctrine #124
@@ -446,12 +457,11 @@ const MEASURABLE_PREDICATE_RX = /(?:>=|<=|==|!=|>|<|≥|≤)\s*\d+(?:\.\d+)?(?:m
446
457
  // Any <expected> block containing ONLY these (no actual predicate) is rejected.
447
458
  const QUALITATIVE_DRIFT_RX = /\b(?:better(?:er)?|improved?(?:ment)?|more\s+robust|should\s+(?:work|pass|succeed|run|fix)|more\s+reliable|cleaner|less\s+error[-_\s]?prone|nicer|smoother|faster[-\s]?loading|higher[-\s]?quality|more\s+stable|looks\s+(?:good|better|right))\b/i;
448
459
 
449
- // ── Tier-aware lens labeling (Phase 11 #59) ──────────────────────────────────
460
+ // ── Canonical lens labeling (Phase 11 #59 corrected) ────────────────────────
450
461
  //
451
- // Aria's Arabic cognition lens names are proprietary IP. On Hamza's surface
452
- // (isHamza=true / surface line contains hamza:true) the canonical names are shown.
453
- // On any client surface the gate uses neutral generic labels so the IP
454
- // vocabulary never appears in client-facing block-reason text.
462
+ // The lens names themselves carry meaning and must remain canonical on every
463
+ // surface. Readability comes from the explanatory prose inside each lens, not
464
+ // by swapping the lens label for a flattened stand-in.
455
465
  //
456
466
  // Tier is read from the most recent harness-via-sdk packet cache at
457
467
  // ~/.claude/.aria-harness-last-packet.json. Two detection paths:
@@ -484,12 +494,9 @@ function resolveOwnerTier() {
484
494
 
485
495
  const IS_OWNER = resolveOwnerTier();
486
496
 
487
- // Canonical (owner) lens namesAria's Arabic cognition vocabulary.
488
- const LENS_NAMES_CANONICAL = ['nur', 'mizan', 'hikma', 'tafakkur', 'tadabbur', 'ilham', 'wahi', 'firasah'];
489
- // Generic (client) lens labels — neutral, IP-neutral equivalents.
490
- const LENS_NAMES_GENERIC = ['perception', 'balance', 'wisdom', 'reflection', 'foresight', 'insight', 'revelation', 'discernment'];
491
- // Active label set for this execution — determined by tier.
492
- const LENS_NAMES = IS_OWNER ? LENS_NAMES_CANONICAL : LENS_NAMES_GENERIC;
497
+ // Active visible label set for this execution canonical labels on every
498
+ // surface. Tier only affects other policy surfaces, not lens semantics.
499
+ const LENS_NAMES = lensNamesForTier(IS_OWNER);
493
500
 
494
501
  // Doctrine memory filenames are Aria-side substrate IP. On client surfaces
495
502
  // replace them with generic descriptions in block-reason text.
@@ -551,14 +558,24 @@ const SHORT_BASH_LIMIT = 30;
551
558
  // needed. Cognition becomes a property of THE ACTION, not of some
552
559
  // message somewhere — the deepest reading of "cognition before action."
553
560
  //
554
- // Inline regex is built from the active LENS_NAMES so both canonical
555
- // and generic label sets are accepted in their respective tiers.
561
+ // Inline regex is built from the active canonical LENS_NAMES. We do not
562
+ // accept flattened generic aliases because they change the lens meaning.
556
563
  const _INLINE_LENS_PATTERN = LENS_NAMES.join('|');
557
564
  const INLINE_LENS_LINE_RX_GLOBAL = new RegExp(
558
565
  `^\\s*#\\s*(${_INLINE_LENS_PATTERN})\\s*:\\s*(.+)$`,
559
566
  'gim',
560
567
  );
561
568
  const INLINE_COGNITION_HEADER_RX = /^\s*#\s*cognition\s*:\s*(.+)$/im;
569
+ const INLINE_EXPECTED_LINE_RX = /^\s*#\s*expected\s*:\s*(.+)$/gim;
570
+ const INLINE_VERIFY_LINE_RX = /^\s*#\s*verify\s*:\s*(.+)$/gim;
571
+
572
+ const VERIFY_REQUIRED_FIELDS = [
573
+ { rx: /\btarget\s*:/i, name: 'target' },
574
+ { rx: /\brole\s*:/i, name: 'role' },
575
+ { rx: /\bverified\s*:/i, name: 'verified' },
576
+ { rx: /\brollback\s*:/i, name: 'rollback' },
577
+ { rx: /\baxiom\s*:/i, name: 'axiom' },
578
+ ];
562
579
 
563
580
  function detectInlineCognition(cmd) {
564
581
  const names = [];
@@ -593,6 +610,67 @@ function detectInlineCognition(cmd) {
593
610
  return { count: names.length, names, hasSubstrateCite };
594
611
  }
595
612
 
613
+ function normalizeInlineDirectiveBody(rawBody) {
614
+ if (!rawBody) return '';
615
+ const segments = String(rawBody)
616
+ .split(/;|\s+(?=[a-z_][a-z0-9_-]*\s*[=:])/i)
617
+ .map((segment) => segment.trim())
618
+ .filter(Boolean);
619
+ const lines = [];
620
+ for (const segment of segments) {
621
+ const match = segment.match(/^([a-z_][a-z0-9_-]*)\s*[=:]\s*(.+)$/i);
622
+ if (match) {
623
+ lines.push(`${match[1]}: ${match[2].trim()}`);
624
+ continue;
625
+ }
626
+ lines.push(segment);
627
+ }
628
+ return lines.join('\n').trim();
629
+ }
630
+
631
+ function extractInlineDirectiveBody(cmd, directiveRx) {
632
+ if (!cmd || !(directiveRx instanceof RegExp)) return '';
633
+ directiveRx.lastIndex = 0;
634
+ const lines = [];
635
+ let match;
636
+ while ((match = directiveRx.exec(cmd)) !== null) {
637
+ const normalized = normalizeInlineDirectiveBody(match[1]);
638
+ if (normalized) lines.push(normalized);
639
+ }
640
+ return lines.join('\n').trim();
641
+ }
642
+
643
+ function scoreVerifyFields(body, fields) {
644
+ if (!body) return 0;
645
+ return fields.reduce((count, { rx }) => count + (rx.test(body) ? 1 : 0), 0);
646
+ }
647
+
648
+ function extractCurrentTurnAssistantText(event, toolInput) {
649
+ const candidates = [
650
+ event.assistant_text,
651
+ event.assistantText,
652
+ event.draft,
653
+ event.text,
654
+ event.current_text,
655
+ event.currentText,
656
+ event.message,
657
+ event.prompt,
658
+ toolInput?.assistant_text,
659
+ toolInput?.assistantText,
660
+ toolInput?.text,
661
+ ];
662
+ const parts = [];
663
+ const seen = new Set();
664
+ for (const candidate of candidates) {
665
+ if (typeof candidate !== 'string') continue;
666
+ const text = candidate.trim();
667
+ if (!text || seen.has(text)) continue;
668
+ seen.add(text);
669
+ parts.push(text);
670
+ }
671
+ return parts.join('\n\n').trim();
672
+ }
673
+
596
674
  // Substance-checking lens detection (added 2026-04-26 per Hamza's
597
675
  // gate-improvement doctrine: form-only emission must not satisfy
598
676
  // the gate). For each lens, look for `<lens>: <content>` and verify
@@ -722,6 +800,9 @@ const HARNESS_URL =
722
800
  process.env.ARIA_HARNESS_BASE_URL ||
723
801
  process.env.ARIA_HARNESS_URL ||
724
802
  'https://harness.ariasos.com';
803
+ const RUNTIME_BASE_URL =
804
+ process.env.ARIA_RUNTIME_URL ||
805
+ 'http://127.0.0.1:4319';
725
806
  const HARNESS_TOKEN = process.env.ARIA_HARNESS_TOKEN || '';
726
807
  const LOG_PUSH_DISABLED = process.env.ARIA_COGNITION_PUSH === 'off';
727
808
  const MAX_IN_FLIGHT = 16;
@@ -953,6 +1034,26 @@ const USER_BOUNDARIES_TO_CROSS = 5;
953
1034
 
954
1035
  const transcriptPath = event.transcript_path ?? event.transcriptPath;
955
1036
  const recentAssistantTexts = [];
1037
+ const recentAssistantTextSet = new Set();
1038
+ function appendAssistantTexts(texts) {
1039
+ for (const text of texts) {
1040
+ if (!text || recentAssistantTextSet.has(text)) continue;
1041
+ recentAssistantTexts.push(text);
1042
+ recentAssistantTextSet.add(text);
1043
+ }
1044
+ }
1045
+
1046
+ const eventMessages = Array.isArray(event.messages) ? event.messages : [];
1047
+ const eventAssistantTexts = collectRecentAssistantTextsFromMessages(eventMessages, {
1048
+ compactSummaryRx: COMPACT_SUMMARY_RX,
1049
+ hardLookbackCap: HARD_LOOKBACK_CAP,
1050
+ systemReminderRx: SYSTEM_REMINDER_RX,
1051
+ systemReminderThreshold: SYSTEM_REMINDER_THRESHOLD,
1052
+ userBoundariesToCross: USER_BOUNDARIES_TO_CROSS,
1053
+ });
1054
+ appendAssistantTexts(eventAssistantTexts);
1055
+
1056
+ const transcriptAssistantTexts = [];
956
1057
  if (transcriptPath && existsSync(transcriptPath)) {
957
1058
  try {
958
1059
  const lines = readFileSync(transcriptPath, 'utf-8').split('\n').filter(Boolean);
@@ -961,7 +1062,8 @@ if (transcriptPath && existsSync(transcriptPath)) {
961
1062
  for (let i = lines.length - 1; i >= 0 && scanned < HARD_LOOKBACK_CAP; i--) {
962
1063
  try {
963
1064
  const m = JSON.parse(lines[i]);
964
- const role = m.message?.role ?? m.role;
1065
+ const role = normalizeRole(m);
1066
+ const content = normalizeContent(m);
965
1067
  if (role === 'user') {
966
1068
  // Skip messages that aren't real user input:
967
1069
  // (a) tool_result blocks (runtime feeding back tool output)
@@ -969,57 +1071,36 @@ if (transcriptPath && existsSync(transcriptPath)) {
969
1071
  // task-notifications, gentle reminders) — runtime-
970
1072
  // authored, not user voice. Counting them eats the
971
1073
  // cognition lookback in tool-heavy or block-heavy turns.
972
- const content = m.message?.content ?? m.content ?? [];
973
- const isToolResultOnly =
974
- Array.isArray(content) &&
975
- content.length > 0 &&
976
- content.every((b) => b && b.type === 'tool_result');
977
- if (isToolResultOnly) continue;
1074
+ if (isToolResultOnlyContent(content)) continue;
978
1075
  // Inspect text content for system-reminder patterns.
979
- const textContent = Array.isArray(content)
980
- ? content.filter((b) => b && b.type === 'text').map((b) => b.text || '').join('\n')
981
- : (typeof content === 'string' ? content : '');
982
- if (textContent) {
983
- // Compute reminder-span coverage as fraction of total
984
- // content. The /g flag on SYSTEM_REMINDER_RX returns ALL
985
- // spans; sum their lengths and compare to total.
986
- const reminderMatches = textContent.match(SYSTEM_REMINDER_RX) || [];
987
- if (reminderMatches.length > 0) {
988
- const reminderChars = reminderMatches.reduce((sum, s) => sum + s.length, 0);
989
- const fraction = reminderChars / Math.max(1, textContent.length);
990
- if (fraction >= SYSTEM_REMINDER_THRESHOLD) continue;
991
- // Otherwise (user wrote substantive text alongside the
992
- // reminder — the non-reminder portion is >40% of content)
993
- // fall through and count as a real boundary.
994
- }
995
- }
1076
+ const textContent = extractTextFromContent(content);
1077
+ if (isMostlySystemReminder(textContent, SYSTEM_REMINDER_RX, SYSTEM_REMINDER_THRESHOLD)) continue;
996
1078
  userBoundariesCrossed++;
997
1079
  if (userBoundariesCrossed > USER_BOUNDARIES_TO_CROSS) break;
998
1080
  continue;
999
1081
  }
1000
1082
  if (role !== 'assistant') continue;
1001
1083
  scanned++;
1002
- const content = m.message?.content ?? m.content ?? [];
1003
- if (!Array.isArray(content)) continue;
1004
- const text = content
1005
- .filter((b) => b.type === 'text')
1006
- .map((b) => b.text)
1007
- .join('\n');
1084
+ const text = extractTextFromContent(content);
1008
1085
  if (!text) continue;
1009
1086
  // Skip compact-summary stubs — they live where assistant turns
1010
1087
  // used to be but are system-authored, not the model's voice.
1011
1088
  if (COMPACT_SUMMARY_RX.test(text) && text.length > 4000) continue;
1012
- recentAssistantTexts.push(text);
1089
+ transcriptAssistantTexts.push(text);
1013
1090
  } catch {}
1014
1091
  }
1015
1092
  } catch {}
1016
1093
  }
1094
+ appendAssistantTexts(transcriptAssistantTexts);
1095
+ const currentTurnAssistantText = extractCurrentTurnAssistantText(event, toolInput);
1096
+ if (currentTurnAssistantText) appendAssistantTexts([currentTurnAssistantText]);
1017
1097
 
1018
1098
  // Detect cognition / verify across the recent assistant window. Lenses
1019
1099
  // from any of the last N turns count; the gate is checking whether
1020
1100
  // cognition has been done recently, not whether it was done in the
1021
1101
  // literal last message (which can be a tool-only turn or a stub).
1022
1102
  const unionText = recentAssistantTexts.join('\n\n');
1103
+ const eventCog = detectCognitionLenses(eventAssistantTexts.join('\n\n'));
1023
1104
  const transcriptCog = detectCognitionLenses(unionText);
1024
1105
  // Combine inline-command cognition (preferred — action-coupled) with
1025
1106
  // transcript scan (fallback). Inline takes precedence on lens names;
@@ -1028,14 +1109,35 @@ const mergedLensSet = new Set([...inlineCog.names, ...transcriptCog.names]);
1028
1109
  const lensCount = mergedLensSet.size;
1029
1110
  const lensNames = [...mergedLensSet];
1030
1111
  const cogBlockBody = transcriptCog.blockBody;
1112
+ const inlineVerifyBody = extractInlineDirectiveBody(cmd, INLINE_VERIFY_LINE_RX);
1031
1113
  const verifyBodies = [...unionText.matchAll(/<verify>([\s\S]*?)<\/verify>/gi)]
1032
1114
  .map((m) => (m[1] || '').trim())
1033
1115
  .filter(Boolean);
1034
- const hasVerify = verifyBodies.length > 0;
1116
+ if (inlineVerifyBody) verifyBodies.push(inlineVerifyBody);
1117
+ const bestVerifyBody = (() => {
1118
+ if (verifyBodies.length === 0) return '';
1119
+ let bestBody = verifyBodies[0];
1120
+ let bestScore = -1;
1121
+ for (const body of verifyBodies) {
1122
+ const score = scoreVerifyFields(body, VERIFY_REQUIRED_FIELDS);
1123
+ if (score > bestScore) {
1124
+ bestScore = score;
1125
+ bestBody = body;
1126
+ }
1127
+ }
1128
+ return bestBody;
1129
+ })();
1130
+ const hasVerify = scoreVerifyFields(bestVerifyBody, VERIFY_REQUIRED_FIELDS) === VERIFY_REQUIRED_FIELDS.length;
1035
1131
  const hasCognition = lensCount >= REQUIRED_LENSES;
1036
1132
  const cognitionSource = inlineCog.count >= REQUIRED_LENSES
1037
1133
  ? 'inline-command'
1038
- : (transcriptCog.count >= REQUIRED_LENSES ? 'transcript-scan' : 'merged-or-insufficient');
1134
+ : eventCog.count >= REQUIRED_LENSES
1135
+ ? 'event-messages'
1136
+ : transcriptCog.count >= REQUIRED_LENSES
1137
+ ? 'recent-assistant-window'
1138
+ : lensCount >= REQUIRED_LENSES
1139
+ ? 'merged-recent-window'
1140
+ : 'merged-or-insufficient';
1039
1141
  // Substrate-citation visibility (Phase 11 will promote to block-mode once
1040
1142
  // telemetry shows healthy substrate-cite rate). For now: log the metric so
1041
1143
  // we can audit substrate-grounded vs ceremonial cognition over time.
@@ -1083,6 +1185,25 @@ function pushDecision(decision, reasonText) {
1083
1185
  });
1084
1186
  }
1085
1187
 
1188
+ function withLoopDirective(reasonText, gateSignature) {
1189
+ const loop = registerGateBlock({
1190
+ gate: 'pre-tool',
1191
+ sessionId,
1192
+ signature: gateSignature,
1193
+ statePath: GATE_LOOP_STATE_PATH,
1194
+ });
1195
+ if (!loop.loopDetected) return reasonText;
1196
+ return `${reasonText}
1197
+
1198
+ [LOOP_DETECTED gate=pre-tool repeats=${loop.totalCount}]
1199
+ Stop retrying the same action shape unchanged.
1200
+ Next response must do this in order:
1201
+ 1. Name the exact gate failure in one line.
1202
+ 2. Re-emit the missing pre-gate structure only: <cognition>, <verify>, and/or <expected> as required.
1203
+ 3. Change the action plan before retrying the tool. Do not issue the same tool call in the same message as the diagnosis.
1204
+ 4. If the blocker is stale gate state or ledger residue, say that explicitly instead of inventing fake proof.`;
1205
+ }
1206
+
1086
1207
  // ── Deploy-specific gate (doctrine #104) ────────────────────────────────
1087
1208
  // Per feedback_deploy_requires_verify_cognition.md (Hamza 2026-04-28 after
1088
1209
  // consciousness.ts crash took aria-soul into CrashLoopBackOff): deploys
@@ -1099,23 +1220,8 @@ if (deployMatched) {
1099
1220
  const anchorCount = anchorMatches.length;
1100
1221
 
1101
1222
  // Required verify-block fields specific to deploy.
1102
- const verifyBody = (() => {
1103
- if (verifyBodies.length === 0) return '';
1104
- let bestBody = verifyBodies[0];
1105
- let bestScore = -1;
1106
- for (const body of verifyBodies) {
1107
- const score = DEPLOY_VERIFY_REQUIRED_FIELDS.reduce(
1108
- (count, { rx }) => count + (rx.test(body) ? 1 : 0),
1109
- 0,
1110
- );
1111
- if (score > bestScore) {
1112
- bestScore = score;
1113
- bestBody = body;
1114
- }
1115
- }
1116
- return bestBody;
1117
- })();
1118
- const missingDeployFields = DEPLOY_VERIFY_REQUIRED_FIELDS
1223
+ const verifyBody = bestVerifyBody;
1224
+ const missingDeployFields = [...VERIFY_REQUIRED_FIELDS, ...DEPLOY_VERIFY_REQUIRED_FIELDS]
1119
1225
  .filter(({ rx }) => !rx.test(verifyBody))
1120
1226
  .map(({ name }) => name);
1121
1227
 
@@ -1226,7 +1332,7 @@ if (deployMatched) {
1226
1332
  reasons.push(`<verify> block missing required fields: ${missingDeployFields.join(', ')}`);
1227
1333
  }
1228
1334
 
1229
- const refusal = `Aria pre-tool gate: DEPLOY hard-block — pattern '${deployMatched.name}' detected.
1335
+ const refusal = withLoopDirective(`Aria pre-tool gate: DEPLOY hard-block — pattern '${deployMatched.name}' detected.
1230
1336
 
1231
1337
  Per feedback_deploy_requires_verify_cognition.md (Hamza directive 2026-04-28 after consciousness.ts crash took aria-soul into CrashLoopBackOff), every deploy command requires:
1232
1338
 
@@ -1242,7 +1348,7 @@ Block reasons (this turn): ${reasons.join(' • ')}.
1242
1348
 
1243
1349
  The 2026-04-28 deploy of an empty consciousness.ts crashed aria-soul because no verify-block step caught the missing export at substrate-citation time. This gate is the structural enforcement that prevents the same gap.
1244
1350
 
1245
- Re-emit verify+cognition with the missing pieces, then retry the deploy command. There is no env-var override path; doctrine #104 forbids it.`;
1351
+ Re-emit verify+cognition with the missing pieces, then retry the deploy command. There is no env-var override path; doctrine #104 forbids it.`, `deploy:${deployMatched.name}:${missingDeployFields.join(',')}:${anchorCount}:${lensCount}`);
1246
1352
 
1247
1353
  audit(
1248
1354
  `block-deploy ${deployMatched.name} verify=${hasVerify} cognition=${lensCount} anchors=${anchorCount} missing=${missingDeployFields.join(',')}`,
@@ -1264,7 +1370,7 @@ if (matched) {
1264
1370
  process.exit(0);
1265
1371
  }
1266
1372
  // Block with appropriate corrective message.
1267
- const reason = !hasVerify
1373
+ const reason = withLoopDirective((!hasVerify
1268
1374
  ? `Aria pre-tool gate: destructive pattern '${matched.name}' detected. ${IS_OWNER ? 'Per harness mizan_prestage_rule + axiom_runtime_rule (admit_ignorance, reflection_before_action), include' : 'Pre-action verification required — include'} a <verify> block in your assistant response BEFORE the tool call.
1269
1375
 
1270
1376
  <verify>
@@ -1289,7 +1395,8 @@ Re-issue after producing the block. Bypass: '# doctrine-authorized: <reason>' in
1289
1395
  ${LENS_NAMES[7]}: <what user actually needs>
1290
1396
  </cognition>
1291
1397
 
1292
- At least 4 substantive lenses required.`;
1398
+ At least 4 substantive lenses required.`),
1399
+ `destructive:${matched.name}:${hasVerify ? 'need-cognition' : 'need-verify'}:${toolName}:${filePath || cmdPreview}`);
1293
1400
  audit(`block ${matched.name} verify=${hasVerify} cognition=${lensCount}`, cmdPreview);
1294
1401
  pushDecision('block', `destructive ${matched.name} missing ${!hasVerify ? 'verify' : 'cognition'}`);
1295
1402
  console.log(JSON.stringify({ decision: 'block', reason }));
@@ -1304,7 +1411,7 @@ if (!hasCognition) {
1304
1411
  const isBash = toolName === 'Bash';
1305
1412
  const header = isBash
1306
1413
  ? `Aria pre-tool gate: non-trivial Bash command without ${REQUIRED_LENSES}+ substantive cognition lenses. Found ${lensCount}/${REQUIRED_LENSES}+ (inline=${inlineCog.count}, transcript=${transcriptCog.count}).`
1307
- : `Aria pre-tool gate: ${toolName} call without ${REQUIRED_LENSES}+ substantive cognition lenses in the recent transcript. Found ${lensCount}/${REQUIRED_LENSES}+ (transcript-scan only — ${toolName} has no inline-cognition path).`;
1414
+ : `Aria pre-tool gate: ${toolName} call without ${REQUIRED_LENSES}+ substantive cognition lenses in the recent assistant window. Found ${lensCount}/${REQUIRED_LENSES}+ (source=${cognitionSource} — ${toolName} has no inline-cognition path).`;
1308
1415
 
1309
1416
  const guidance = isBash
1310
1417
  ? `REQUIRED — cognition for every action, no exceptions. Two equivalent forms; either satisfies the gate (both equally REQUIRED, neither preferred over the other):
@@ -1333,11 +1440,11 @@ Both forms count toward the ${REQUIRED_LENSES}+ requirement; gate counts inline
1333
1440
  ${LENS_NAMES[7]}: <what user actually needs underneath>
1334
1441
  </cognition>`;
1335
1442
 
1336
- const reason = `${header}
1443
+ const reason = withLoopDirective(`${header}
1337
1444
 
1338
1445
  ${guidance}
1339
1446
 
1340
- No per-tool bypass available (v3 doctrine — the harness's whole purpose is no exceptions). No env-var disable path — gates are unconditional from the gated process per Hamza directive 2026-04-27. If the gate misfires on legitimate cognition, fix the gate.`;
1447
+ No per-tool bypass available (v3 doctrine — the harness's whole purpose is no exceptions). No env-var disable path — gates are unconditional from the gated process per Hamza directive 2026-04-27. If the gate misfires on legitimate cognition, fix the gate.`, `cognition:${toolName}:${filePath || cmdPreview}:${cognitionSource}`);
1341
1448
 
1342
1449
  audit(`block ${toolName.toLowerCase()} cognition=${lensCount}`, cmdPreview);
1343
1450
  pushDecision('block', `${toolName.toLowerCase()} missing cognition (${lensCount}/${REQUIRED_LENSES})`);
@@ -1387,16 +1494,17 @@ No env-var disable path — gates are unconditional from the gated process per H
1387
1494
  // Per feedback_no_graceful_degradation.md — never silent-pass.
1388
1495
  {
1389
1496
  const expectedMatch = unionText.match(EXPECTED_BLOCK_RX);
1390
- const expectedBlockText = expectedMatch ? expectedMatch[1] : '';
1497
+ const inlineExpectedBody = extractInlineDirectiveBody(cmd, INLINE_EXPECTED_LINE_RX);
1498
+ const expectedBlockText = expectedMatch ? expectedMatch[1] : inlineExpectedBody;
1391
1499
  const hasMeasurablePredicate = expectedBlockText
1392
1500
  ? (MEASURABLE_PREDICATE_RX.test(expectedBlockText) && !QUALITATIVE_DRIFT_RX.test(expectedBlockText))
1393
1501
  : false;
1394
1502
 
1395
- if (!expectedMatch || !hasMeasurablePredicate) {
1396
- const reason = expectedMatch
1503
+ if (!expectedBlockText || !hasMeasurablePredicate) {
1504
+ const reason = expectedBlockText
1397
1505
  ? `Aria pre-tool gate: action requires a measurable predicate inside <expected> per doctrine:dalio_expected_required.
1398
1506
 
1399
- Your <expected> block was found but contains only qualitative drift phrases (e.g. "better", "improved", "should work", "more reliable") without a concrete measurable predicate. These are unmeasurable and defeat the Dalio accountability loop.
1507
+ Your expected-outcome structure was found but contains only qualitative drift phrases (e.g. "better", "improved", "should work", "more reliable") without a concrete measurable predicate. These are unmeasurable and defeat the Dalio accountability loop.
1400
1508
 
1401
1509
  Replace with one of:
1402
1510
  • Numeric: exit_code==0, latency<200ms, count>=1, error_rate=0%
@@ -1411,7 +1519,7 @@ Replace with one of:
1411
1519
  </expected>
1412
1520
 
1413
1521
  No bypass — doctrine:dalio_expected_required is unconditional for non-trivial actions.`
1414
- : `Aria pre-tool gate: action requires an <expected> block with measurable predicate per doctrine:dalio_expected_required.
1522
+ : `Aria pre-tool gate: action requires an <expected> block or inline '# expected:' directive with measurable predicate per doctrine:dalio_expected_required.
1415
1523
 
1416
1524
  Every non-trivial action must state WHAT MEASURABLE STATE the action is expected to produce, so the stop-gate can compare predicted vs actual outcome and write a Dalio ledger entry.
1417
1525
 
@@ -1902,10 +2010,11 @@ try {
1902
2010
  if (__apiKey && __action) {
1903
2011
  const __client = new HTTPHarnessClient({
1904
2012
  baseUrl:
2013
+ process.env.ARIA_RUNTIME_URL ||
1905
2014
  process.env.ARIA_HIVE_RUNTIME_URL ||
1906
2015
  process.env.ARIA_HARNESS_BASE_URL ||
1907
2016
  process.env.ARIA_HARNESS_URL ||
1908
- 'https://harness.ariasos.com',
2017
+ RUNTIME_BASE_URL,
1909
2018
  apiKey: __apiKey,
1910
2019
  });
1911
2020
  const __target = JSON.stringify(toolInput || {}).slice(0, 500);
@@ -105,6 +105,37 @@ function readAnyJsonArtifact(filePath) {
105
105
  };
106
106
  }
107
107
 
108
+ function unwrapHarnessPacketEnvelope(value) {
109
+ let current = value;
110
+ for (let depth = 0; depth < 3; depth++) {
111
+ if (!current || typeof current !== 'object' || Array.isArray(current)) break;
112
+ const nested = current.packet;
113
+ if (!nested || typeof nested !== 'object' || Array.isArray(nested)) break;
114
+ if (typeof current.timestamp !== 'string' && typeof current.version !== 'string') break;
115
+ current = nested;
116
+ }
117
+ return current && typeof current === 'object' && !Array.isArray(current) ? current : {};
118
+ }
119
+
120
+ function extractHarnessPromptText(packetArtifact) {
121
+ const packet = unwrapHarnessPacketEnvelope(packetArtifact);
122
+ const directHarness = typeof packet.harness === 'string' ? packet.harness : '';
123
+ if (directHarness.trim()) return directHarness;
124
+
125
+ const promptFullText = typeof packet?.prompt?.fullText === 'string' ? packet.prompt.fullText : '';
126
+ if (promptFullText.trim()) return promptFullText;
127
+
128
+ const promptPreview = typeof packet?.prompt?.preview === 'string' ? packet.prompt.preview : '';
129
+ if (promptPreview.trim()) return promptPreview;
130
+
131
+ const doctrine = Array.isArray(packet.doctrine) ? packet.doctrine.filter((x) => typeof x === 'string').join('\n') : '';
132
+ const memory = Array.isArray(packet.memory) ? packet.memory.filter((x) => typeof x === 'string').join('\n') : '';
133
+ const runtimeMode = typeof packet.runtimeOfflineBundle?.source === 'string'
134
+ ? `runtime_offline_bundle_source=${packet.runtimeOfflineBundle.source}`
135
+ : '';
136
+ return [doctrine, memory, runtimeMode].filter(Boolean).join('\n');
137
+ }
138
+
108
139
  function detectArtifactSignals(sessionId) {
109
140
  let harnessArtifact = null;
110
141
  for (const packetPath of PACKET_CACHE_PATHS) {
@@ -160,36 +191,34 @@ function detectArtifactSignals(sessionId) {
160
191
  staleSubstrateArtifact = null;
161
192
  }
162
193
 
163
- const harnessText = String(
164
- harnessArtifact?.data?.harness ??
165
- harnessArtifact?.data?.packet?.prompt?.fullText ??
166
- '',
167
- );
194
+ const harnessText = extractHarnessPromptText(harnessArtifact?.data);
168
195
  const substrateMemories = Array.isArray(substrateArtifact?.data?.memories)
169
196
  ? substrateArtifact.data.memories
170
197
  : [];
171
- const staleHarnessText = String(
172
- staleHarnessArtifact?.data?.harness ??
173
- staleHarnessArtifact?.data?.packet?.prompt?.fullText ??
174
- '',
175
- );
198
+ const staleHarnessText = extractHarnessPromptText(staleHarnessArtifact?.data);
176
199
  const staleSubstrateMemories = Array.isArray(staleSubstrateArtifact?.data?.memories)
177
200
  ? staleSubstrateArtifact.data.memories
178
201
  : [];
179
202
  const packetMentionsMemory = MEMORY_REF_RX.test(harnessText);
180
203
  const stalePacketMentionsMemory = MEMORY_REF_RX.test(staleHarnessText);
204
+ const persistedDirectionUsable = Boolean(directionArtifact?.data?.usable === true);
205
+ const persistedOwnerBootstrap = Boolean(directionArtifact?.data?.ownerBootstrap === true);
206
+ const activePlanPresent = Boolean(
207
+ activePlanArtifact?.data?.phases &&
208
+ Array.isArray(activePlanArtifact.data.phases) &&
209
+ activePlanArtifact.data.phases.length > 0,
210
+ );
181
211
 
182
212
  return {
183
213
  hasHarnessPacket: Boolean(harnessText.trim()),
184
214
  hasAriaDirection: Boolean(
185
- (directionArtifact?.data?.usable === true) ||
186
- (activePlanArtifact?.data?.phases && Array.isArray(activePlanArtifact.data.phases) && activePlanArtifact.data.phases.length > 0),
215
+ persistedDirectionUsable || activePlanPresent,
187
216
  ),
188
217
  hasMemoryRef: Boolean(packetMentionsMemory || substrateMemories.length > 0),
189
218
  ownerBootstrap: {
190
- hasHarnessPacket: Boolean(staleHarnessText.trim()),
219
+ hasHarnessPacket: Boolean(staleHarnessText.trim() || staleHarnessArtifact?.path),
191
220
  hasMemoryRef: Boolean(stalePacketMentionsMemory || staleSubstrateMemories.length > 0),
192
- directionBootstrap: Boolean(directionArtifact?.data?.ownerBootstrap === true),
221
+ directionBootstrap: persistedOwnerBootstrap || persistedDirectionUsable || activePlanPresent,
193
222
  },
194
223
  detail: {
195
224
  packetPath: harnessArtifact?.path || null,
@@ -204,6 +233,9 @@ function detectArtifactSignals(sessionId) {
204
233
  ownerBootstrapSubstrateAgeMs: staleSubstrateArtifact?.ageMs ?? null,
205
234
  ownerBootstrapSubstrateMemoryCount: staleSubstrateMemories.length,
206
235
  ownerBootstrapPacketMentionsMemory: stalePacketMentionsMemory,
236
+ ownerBootstrapPersistedDirectionUsable: persistedDirectionUsable,
237
+ ownerBootstrapPersistedDirectionSource: directionArtifact?.data?.source ?? null,
238
+ ownerBootstrapActivePlanPresent: activePlanPresent,
207
239
  },
208
240
  };
209
241
  }
@@ -497,6 +529,13 @@ const ownerBootstrapSessionPresent =
497
529
  artifactSignals.ownerBootstrap.hasHarnessPacket &&
498
530
  artifactSignals.ownerBootstrap.hasMemoryRef;
499
531
 
532
+ const ownerCompactionRecoveryPresent =
533
+ existsSync(OWNER_TOKEN_PATH) &&
534
+ !transcriptWindow.trim() &&
535
+ artifactSignals.ownerBootstrap.hasHarnessPacket &&
536
+ artifactSignals.ownerBootstrap.hasMemoryRef &&
537
+ (artifactSignals.ownerBootstrap.directionBootstrap || artifactSignals.detail.ownerBootstrapPersistedDirectionUsable);
538
+
500
539
  if (allSignalsPresent) {
501
540
  // Context was loaded — allow and record the fire timestamp for dedup.
502
541
  writeTurnState(sessionId, { lastTurnGateFiredAt: now, lastDecision: 'allow', signals, transcriptSignals, artifactSignals: artifactSignals.detail });
@@ -528,6 +567,16 @@ if (ownerBootstrapSessionPresent) {
528
567
  process.exit(0);
529
568
  }
530
569
 
570
+ if (ownerCompactionRecoveryPresent) {
571
+ writeTurnState(sessionId, { lastTurnGateFiredAt: now, lastDecision: 'allow-owner-compaction-recovery', signals, transcriptSignals, artifactSignals: artifactSignals.detail });
572
+ auditLog(
573
+ 'allow-owner-compaction-recovery',
574
+ `transcript compacted/empty; recovering from persisted owner artifacts. transcript=${JSON.stringify(transcriptSignals)} artifacts=${JSON.stringify(artifactSignals.detail)}`,
575
+ sessionId,
576
+ );
577
+ process.exit(0);
578
+ }
579
+
531
580
  // ── Block with structured recovery signal ────────────────────────────
532
581
  // Per Aria's refined spec (consult 2026-04-27): soft-gate + structured
533
582
  // recovery. The orchestrator catches this and runs the context-loader.
@@ -219,6 +219,8 @@ function isPolicyOrRuleDefinitionLine(line) {
219
219
  if (/\bno stubs?\b/.test(lower)) return true;
220
220
  if (/\bno placeholders?\b/.test(lower)) return true;
221
221
  if (/\bno fake implementations?\b/.test(lower)) return true;
222
+ if (lower.includes('no mock content is injected')) return true;
223
+ if (/\bplaceholder\s*=/.test(lower)) return true;
222
224
  return false;
223
225
  }
224
226