@aria_asi/cli 0.2.36 → 0.2.38

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 (198) hide show
  1. package/CLIENT-ONBOARDING.md +4 -2
  2. package/bin/aria.js +11 -7
  3. package/dist/aria-connector/src/auth.d.ts +14 -0
  4. package/dist/aria-connector/src/auth.d.ts.map +1 -1
  5. package/dist/aria-connector/src/auth.js +103 -1
  6. package/dist/aria-connector/src/auth.js.map +1 -1
  7. package/dist/aria-connector/src/chat.d.ts.map +1 -1
  8. package/dist/aria-connector/src/chat.js +13 -8
  9. package/dist/aria-connector/src/chat.js.map +1 -1
  10. package/dist/aria-connector/src/config.d.ts +6 -1
  11. package/dist/aria-connector/src/config.d.ts.map +1 -1
  12. package/dist/aria-connector/src/config.js.map +1 -1
  13. package/dist/aria-connector/src/connectors/claude-code.d.ts.map +1 -1
  14. package/dist/aria-connector/src/connectors/claude-code.js +50 -6
  15. package/dist/aria-connector/src/connectors/claude-code.js.map +1 -1
  16. package/dist/aria-connector/src/connectors/codex.d.ts.map +1 -1
  17. package/dist/aria-connector/src/connectors/codex.js +290 -32
  18. package/dist/aria-connector/src/connectors/codex.js.map +1 -1
  19. package/dist/aria-connector/src/connectors/opencode.d.ts.map +1 -1
  20. package/dist/aria-connector/src/connectors/opencode.js +35 -11
  21. package/dist/aria-connector/src/connectors/opencode.js.map +1 -1
  22. package/dist/aria-connector/src/connectors/repo-guard.d.ts +10 -0
  23. package/dist/aria-connector/src/connectors/repo-guard.d.ts.map +1 -1
  24. package/dist/aria-connector/src/connectors/repo-guard.js +110 -164
  25. package/dist/aria-connector/src/connectors/repo-guard.js.map +1 -1
  26. package/dist/aria-connector/src/connectors/runtime.d.ts.map +1 -1
  27. package/dist/aria-connector/src/connectors/runtime.js +17 -7
  28. package/dist/aria-connector/src/connectors/runtime.js.map +1 -1
  29. package/dist/aria-connector/src/connectors/shell.d.ts.map +1 -1
  30. package/dist/aria-connector/src/connectors/shell.js +12 -8
  31. package/dist/aria-connector/src/connectors/shell.js.map +1 -1
  32. package/dist/aria-connector/src/harness-client.d.ts +3 -1
  33. package/dist/aria-connector/src/harness-client.d.ts.map +1 -1
  34. package/dist/aria-connector/src/harness-client.js +7 -20
  35. package/dist/aria-connector/src/harness-client.js.map +1 -1
  36. package/dist/aria-connector/src/model-context.d.ts.map +1 -1
  37. package/dist/aria-connector/src/model-context.js +5 -0
  38. package/dist/aria-connector/src/model-context.js.map +1 -1
  39. package/dist/aria-connector/src/providers/types.d.ts +1 -1
  40. package/dist/aria-connector/src/providers/types.d.ts.map +1 -1
  41. package/dist/aria-connector/src/providers/xai.d.ts +3 -0
  42. package/dist/aria-connector/src/providers/xai.d.ts.map +1 -0
  43. package/dist/aria-connector/src/providers/xai.js +40 -0
  44. package/dist/aria-connector/src/providers/xai.js.map +1 -0
  45. package/dist/aria-connector/src/setup-wizard.js +1 -0
  46. package/dist/aria-connector/src/setup-wizard.js.map +1 -1
  47. package/dist/aria-connector/src/types.d.ts +2 -0
  48. package/dist/aria-connector/src/types.d.ts.map +1 -1
  49. package/dist/assets/hooks/aria-cognition-substrate-binding.mjs +51 -9
  50. package/dist/assets/hooks/aria-first-class-coach.mjs +129 -0
  51. package/dist/assets/hooks/aria-harness-via-sdk.mjs +33 -6
  52. package/dist/assets/hooks/aria-pre-tool-gate.mjs +86 -8
  53. package/dist/assets/hooks/aria-pre-tool-use.mjs +75 -0
  54. package/dist/assets/hooks/aria-preprompt-consult.mjs +5 -6
  55. package/dist/assets/hooks/aria-preturn-memory-gate.mjs +5 -0
  56. package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +15 -0
  57. package/dist/assets/hooks/aria-stop-gate.mjs +125 -17
  58. package/dist/assets/hooks/doctrine_trigger_map.json +11 -0
  59. package/dist/assets/hooks/lib/emergency-gateoff-impl.mjs +39 -0
  60. package/dist/assets/hooks/lib/emergency-gateoff.mjs +6 -0
  61. package/dist/assets/hooks/lib/first-class-coach.mjs +755 -0
  62. package/dist/assets/hooks/lib/skill-autoload-gate-impl.mjs +103 -0
  63. package/dist/assets/hooks/lib/skill-autoload-gate.mjs +1 -14
  64. package/dist/assets/opencode-plugins/harness-context/auth-token.mjs +126 -0
  65. package/dist/assets/opencode-plugins/harness-context/inject-context.mjs +62 -22
  66. package/dist/assets/opencode-plugins/harness-context/task-project-ledger.mjs +290 -0
  67. package/dist/assets/opencode-plugins/harness-gate/index.js +87 -27
  68. package/dist/assets/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +1 -14
  69. package/dist/assets/opencode-plugins/harness-outcome/index.js +29 -24
  70. package/dist/assets/opencode-plugins/harness-stop/index.js +229 -68
  71. package/dist/assets/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +1 -14
  72. package/dist/runtime/auth-token.mjs +121 -0
  73. package/dist/runtime/coach-kernel.mjs +377 -0
  74. package/dist/runtime/codex-bridge.mjs +440 -69
  75. package/dist/runtime/discipline/doctrine_trigger_map.json +11 -0
  76. package/dist/runtime/discipline/skills/aria-cognition/aria-essence/SKILL.md +18 -0
  77. package/dist/runtime/discipline/skills/aria-cognition/aria-forge-guardrails/SKILL.md +18 -0
  78. package/dist/runtime/discipline/skills/aria-cognition/aria-repo-doctrine/SKILL.md +18 -0
  79. package/dist/runtime/discipline/skills/aria-cognition/forge-quality-rules/SKILL.md +18 -0
  80. package/dist/runtime/discipline/skills/aria-cognition/ghazali-8lens/SKILL.md +18 -0
  81. package/dist/runtime/discipline/skills/aria-cognition/istiqra-induction/SKILL.md +18 -0
  82. package/dist/runtime/discipline/skills/aria-cognition/ladunni-22/SKILL.md +18 -0
  83. package/dist/runtime/discipline/skills/aria-cognition/mizan/SKILL.md +18 -0
  84. package/dist/runtime/discipline/skills/aria-cognition/nadia/SKILL.md +18 -0
  85. package/dist/runtime/discipline/skills/aria-cognition/nadia-psi/SKILL.md +18 -0
  86. package/dist/runtime/discipline/skills/aria-cognition/predictor/SKILL.md +18 -0
  87. package/dist/runtime/discipline/skills/aria-cognition/qiyas-analogy/SKILL.md +18 -0
  88. package/dist/runtime/discipline/skills/aria-cognition/soul-domains/SKILL.md +18 -0
  89. package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-intra-phase/SKILL.md +18 -0
  90. package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-post-phase/SKILL.md +18 -0
  91. package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-pre-phase/SKILL.md +18 -0
  92. package/dist/runtime/discipline/skills/aria-harness/aria-harness-deploy/SKILL.md +18 -0
  93. package/dist/runtime/discipline/skills/aria-harness/aria-harness-no-stripping/SKILL.md +18 -0
  94. package/dist/runtime/discipline/skills/aria-harness/aria-harness-onboarding/SKILL.md +18 -0
  95. package/dist/runtime/discipline/skills/aria-harness/aria-harness-output-discipline/SKILL.md +18 -0
  96. package/dist/runtime/discipline/skills/aria-harness/aria-harness-substrate-binding/SKILL.md +18 -0
  97. package/dist/runtime/doctrine_trigger_map.json +11 -0
  98. package/dist/runtime/hooks/aria-cognition-substrate-binding.mjs +51 -9
  99. package/dist/runtime/hooks/aria-first-class-coach.mjs +129 -0
  100. package/dist/runtime/hooks/aria-harness-via-sdk.mjs +33 -6
  101. package/dist/runtime/hooks/aria-pre-tool-gate.mjs +86 -8
  102. package/dist/runtime/hooks/aria-pre-tool-use.mjs +75 -0
  103. package/dist/runtime/hooks/aria-preprompt-consult.mjs +5 -6
  104. package/dist/runtime/hooks/aria-preturn-memory-gate.mjs +5 -0
  105. package/dist/runtime/hooks/aria-repo-doctrine-gate.mjs +15 -0
  106. package/dist/runtime/hooks/aria-stop-gate.mjs +125 -17
  107. package/dist/runtime/hooks/doctrine_trigger_map.json +11 -0
  108. package/dist/runtime/hooks/lib/emergency-gateoff-impl.mjs +39 -0
  109. package/dist/runtime/hooks/lib/emergency-gateoff.mjs +6 -0
  110. package/dist/runtime/hooks/lib/first-class-coach.mjs +755 -0
  111. package/dist/runtime/hooks/lib/skill-autoload-gate-impl.mjs +103 -0
  112. package/dist/runtime/hooks/lib/skill-autoload-gate.mjs +1 -14
  113. package/dist/runtime/local-phase.mjs +8 -0
  114. package/dist/runtime/manifest.json +2 -2
  115. package/dist/runtime/provider-proxy.mjs +136 -33
  116. package/dist/runtime/sdk/BUNDLED.json +2 -2
  117. package/dist/runtime/sdk/auth.d.ts +17 -0
  118. package/dist/runtime/sdk/auth.js +158 -0
  119. package/dist/runtime/sdk/auth.js.map +1 -0
  120. package/dist/runtime/sdk/index.d.ts +8 -1
  121. package/dist/runtime/sdk/index.js +15 -1
  122. package/dist/runtime/sdk/index.js.map +1 -1
  123. package/dist/runtime/service.mjs +1711 -74
  124. package/dist/runtime/task-project-ledger.mjs +290 -0
  125. package/dist/sdk/BUNDLED.json +2 -2
  126. package/dist/sdk/auth.d.ts +17 -0
  127. package/dist/sdk/auth.js +158 -0
  128. package/dist/sdk/auth.js.map +1 -0
  129. package/dist/sdk/index.d.ts +8 -1
  130. package/dist/sdk/index.js +15 -1
  131. package/dist/sdk/index.js.map +1 -1
  132. package/hooks/aria-cognition-substrate-binding.mjs +51 -9
  133. package/hooks/aria-first-class-coach.mjs +129 -0
  134. package/hooks/aria-harness-via-sdk.mjs +33 -6
  135. package/hooks/aria-pre-tool-gate.mjs +86 -8
  136. package/hooks/aria-pre-tool-use.mjs +75 -0
  137. package/hooks/aria-preprompt-consult.mjs +5 -6
  138. package/hooks/aria-preturn-memory-gate.mjs +5 -0
  139. package/hooks/aria-repo-doctrine-gate.mjs +15 -0
  140. package/hooks/aria-stop-gate.mjs +125 -17
  141. package/hooks/doctrine_trigger_map.json +11 -0
  142. package/hooks/lib/emergency-gateoff-impl.mjs +39 -0
  143. package/hooks/lib/emergency-gateoff.mjs +6 -0
  144. package/hooks/lib/first-class-coach.mjs +755 -0
  145. package/hooks/lib/skill-autoload-gate-impl.mjs +103 -0
  146. package/hooks/lib/skill-autoload-gate.mjs +1 -14
  147. package/opencode-plugins/harness-context/auth-token.mjs +126 -0
  148. package/opencode-plugins/harness-context/inject-context.mjs +62 -22
  149. package/opencode-plugins/harness-context/task-project-ledger.mjs +290 -0
  150. package/opencode-plugins/harness-gate/index.js +87 -27
  151. package/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +1 -14
  152. package/opencode-plugins/harness-outcome/index.js +29 -24
  153. package/opencode-plugins/harness-stop/index.js +229 -68
  154. package/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +1 -14
  155. package/package.json +8 -2
  156. package/runtime-src/auth-token.mjs +121 -0
  157. package/runtime-src/coach-kernel.mjs +377 -0
  158. package/runtime-src/codex-bridge.mjs +440 -69
  159. package/runtime-src/local-phase.mjs +8 -0
  160. package/runtime-src/provider-proxy.mjs +136 -33
  161. package/runtime-src/service.mjs +1711 -74
  162. package/scripts/bundle-sdk.mjs +8 -0
  163. package/scripts/check-client-compatibility.mjs +422 -0
  164. package/scripts/check-coach-kernel.mjs +204 -0
  165. package/scripts/check-managed-runtime-ledger.mjs +107 -0
  166. package/scripts/check-opencode-config-contract.mjs +78 -0
  167. package/scripts/check-quality-ledger.mjs +121 -0
  168. package/scripts/self-test-harness-gates.mjs +179 -11
  169. package/scripts/self-test-repo-guard.mjs +38 -0
  170. package/scripts/validate-skill-prompts.mjs +14 -1
  171. package/skills/aria-cognition/aria-essence/SKILL.md +18 -0
  172. package/skills/aria-cognition/aria-forge-guardrails/SKILL.md +18 -0
  173. package/skills/aria-cognition/aria-repo-doctrine/SKILL.md +18 -0
  174. package/skills/aria-cognition/forge-quality-rules/SKILL.md +18 -0
  175. package/skills/aria-cognition/ghazali-8lens/SKILL.md +18 -0
  176. package/skills/aria-cognition/istiqra-induction/SKILL.md +18 -0
  177. package/skills/aria-cognition/ladunni-22/SKILL.md +18 -0
  178. package/skills/aria-cognition/mizan/SKILL.md +18 -0
  179. package/skills/aria-cognition/nadia/SKILL.md +18 -0
  180. package/skills/aria-cognition/nadia-psi/SKILL.md +18 -0
  181. package/skills/aria-cognition/predictor/SKILL.md +18 -0
  182. package/skills/aria-cognition/qiyas-analogy/SKILL.md +18 -0
  183. package/skills/aria-cognition/soul-domains/SKILL.md +18 -0
  184. package/src/auth.ts +136 -1
  185. package/src/chat.ts +13 -8
  186. package/src/config.ts +6 -1
  187. package/src/connectors/claude-code.ts +62 -18
  188. package/src/connectors/codex.ts +288 -32
  189. package/src/connectors/opencode.ts +35 -12
  190. package/src/connectors/repo-guard.ts +117 -172
  191. package/src/connectors/runtime.ts +19 -7
  192. package/src/connectors/shell.ts +12 -8
  193. package/src/harness-client.ts +8 -22
  194. package/src/model-context.ts +6 -0
  195. package/src/providers/types.ts +1 -1
  196. package/src/providers/xai.ts +55 -0
  197. package/src/setup-wizard.ts +1 -0
  198. package/src/types.ts +2 -0
@@ -2,9 +2,10 @@
2
2
 
3
3
  import { createServer } from 'node:http';
4
4
  import { createRequire } from 'node:module';
5
+ import { spawnSync } from 'node:child_process';
5
6
  import { createHash, createCipheriv, createDecipheriv, randomBytes, randomUUID, scryptSync } from 'node:crypto';
6
- import { existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
7
- import { fileURLToPath } from 'node:url';
7
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
8
+ import { fileURLToPath, pathToFileURL } from 'node:url';
8
9
  import { dirname, join } from 'node:path';
9
10
 
10
11
  import { HTTPHarnessClient } from './sdk/index.js';
@@ -13,6 +14,7 @@ import {
13
14
  callProviderForOpenAI,
14
15
  extractAnthropicUserMessage,
15
16
  extractOpenAIUserMessage,
17
+ resolveProviderConfig,
16
18
  } from './provider-proxy.mjs';
17
19
  import { recordTokenUsage, brandModel, getUsageSummary, getBillingSummary, getSubscriptionTier } from './metering.mjs';
18
20
  import { deployFleet, loadFleet, saveFleet, getFleetStatus, buildAgentSystemPrompt } from './fleet-engine.mjs';
@@ -35,6 +37,13 @@ import {
35
37
  evaluateMizanPre,
36
38
  summarizeMizanBundle,
37
39
  } from './mizan-scheduler.mjs';
40
+ import {
41
+ COACH_PHASES,
42
+ formatCoachClientBlock,
43
+ readCoachState,
44
+ recordCoachPhase,
45
+ } from './coach-kernel.mjs';
46
+ import { resolveAriaAuthToken } from './auth-token.mjs';
38
47
 
39
48
  const require = createRequire(import.meta.url);
40
49
  const { runFullChain } = require('./vendor/aria-gate-runtime/index.js');
@@ -66,12 +75,24 @@ const STATE_DIR = join(__dirname, 'state');
66
75
  const LEASE_PATH = join(STATE_DIR, 'lease.enc');
67
76
  const OFFLINE_BUNDLE_PATH = join(STATE_DIR, 'offline-policy-bundle.enc');
68
77
  const RUNTIME_META_PATH = join(STATE_DIR, 'runtime-meta.json');
78
+ const MANAGED_RUNTIME_LEDGER_PATH = join(STATE_DIR, 'managed-runtime-ledger.jsonl');
69
79
  const AUTONOMY_STATE_PATH = join(STATE_DIR, 'autonomy.json');
70
80
  const COGNITION_STATE_PATH = join(STATE_DIR, 'cognition-state.enc');
71
81
  const HIVE_FILE_LEASES_PATH = join(STATE_DIR, 'hive-file-leases.json');
72
82
  const REVOCATION_LOCK_PATH = join(STATE_DIR, 'revoked.json');
73
83
  const CONFIG_PATH = join(process.env.HOME || '', '.aria', 'config.json');
74
84
  const CODEBASE_AWARENESS_STATE_PATH = join(process.env.HOME || '', '.aria', 'codebase-awareness-state.json');
85
+ const CURRENT_RECOVERY_PATH = join(process.env.HOME || '', '.aria', 'governance-recovery-current.json');
86
+ const OPENCLAW_BIN_CANDIDATES = [
87
+ process.env.OPENCLAW_BIN,
88
+ join(process.env.HOME || '', '.npm-global', 'bin', 'openclaw'),
89
+ 'openclaw',
90
+ ].filter(Boolean);
91
+ const OPENCLAW_RUNTIME_MODULE_CANDIDATES = [
92
+ process.env.OPENCLAW_RUNTIME_MODULE,
93
+ join(process.env.HOME || '', '.npm-global', 'lib', 'node_modules', 'openclaw', 'dist', 'plugins', 'runtime', 'index.js'),
94
+ join(process.cwd(), 'node_modules', 'openclaw', 'dist', 'plugins', 'runtime', 'index.js'),
95
+ ].filter(Boolean);
75
96
  const LEGACY_PACKET_CACHE_CANDIDATES = [
76
97
  join(process.env.HOME || '', '.aria', '.aria-harness-last-packet.json'),
77
98
  join(process.env.HOME || '', '.claude', '.aria-harness-last-packet.json'),
@@ -83,6 +104,7 @@ const PRINCIPLE_LIMIT = 400;
83
104
  const PATTERN_LIMIT = 400;
84
105
  const RECEIPT_LIMIT = 500;
85
106
  const OWNER_TOKEN_PATH = join(process.env.HOME || '', '.aria', 'owner-token');
107
+ const LICENSE_PATH = join(process.env.HOME || '', '.aria', 'license.json');
86
108
  const COGNITION_BLOCK_RX = /<cognition>([\s\S]*?)<\/cognition>/i;
87
109
  const VERIFY_BLOCK_RX =
88
110
  /<verify>[\s\S]*?target\s*:[\s\S]*?role\s*:[\s\S]*?verified\s*:[\s\S]*?rollback\s*:[\s\S]*?axiom\s*:[\s\S]*?<\/verify>/i;
@@ -93,6 +115,14 @@ const QUALITATIVE_DRIFT_RX = /\b(?:better|improved|should work|more reliable|cle
93
115
  const DECISION_SIGNAL_RX = /(?:should|recommend|propose|suggest|let'?s|go with|i'd|i would|here'?s the plan|i'll|next step|action item|ship it|yes do|let me)/i;
94
116
  const TRIVIAL_ACK_RX = /^(?:got it|on it|ok|sure|yes|no|done|ack)\b/i;
95
117
  const NON_TRIVIAL_MIN_CHARS = 300;
118
+ const PROVIDER_RECOVERY_ATTEMPTS = Math.min(
119
+ 2,
120
+ Math.max(0, Number(process.env.ARIA_RUNTIME_PROVIDER_RECOVERY_ATTEMPTS || 2)),
121
+ );
122
+ const PROVIDER_RECOVERY_MIN_TOKENS = Math.max(
123
+ 1024,
124
+ Number(process.env.ARIA_RUNTIME_PROVIDER_RECOVERY_MIN_TOKENS || 2048),
125
+ );
96
126
  const READABLE_LENS_SLOTS = [
97
127
  { key: 'truth', aliases: ['truth', 'nur', 'zahir'] },
98
128
  { key: 'harm', aliases: ['harm', 'mizan', 'batin'] },
@@ -114,6 +144,14 @@ const DOCTRINE_TRIGGER_MAP_CANDIDATES = [
114
144
  join(process.env.HOME || '', '.opencode', 'doctrine_trigger_map.json'),
115
145
  ];
116
146
  const DOCTRINE_TRIGGER_SYNC_INTERVAL_MS = Number(process.env.ARIA_DOCTRINE_TRIGGER_SYNC_INTERVAL_MS || 5000);
147
+ const SKILL_BODY_MAX_CHARS = Number(process.env.ARIA_RUNTIME_SKILL_BODY_MAX_CHARS || 20000);
148
+ const SKILL_SEARCH_ROOTS = [
149
+ join(__dirname, 'discipline', 'skills', 'aria-harness'),
150
+ join(__dirname, 'discipline', 'skills', 'aria-cognition'),
151
+ join(__dirname, '..', 'skills', 'aria-cognition'),
152
+ join(process.env.HOME || '', '.aria', 'runtime', 'discipline', 'skills', 'aria-harness'),
153
+ join(process.env.HOME || '', '.aria', 'runtime', 'discipline', 'skills', 'aria-cognition'),
154
+ ];
117
155
  const TOOL_DEPLOY_PATTERNS = [
118
156
  /\b(?:\.\/)?scripts\/deploy-/i,
119
157
  /\bkubectl\s+apply\b/i,
@@ -385,7 +423,16 @@ function loadDoctrineTriggerMap() {
385
423
  return latest.map;
386
424
  }
387
425
 
426
+ function isAriaControlOutput(text) {
427
+ return /^(?:ARIA CODEX RECOVERY CONTRACT|Aria runtime blocked final output for this Codex turn\.|Aria runtime blocked this output after running the recovery contract\.|Aria runtime post-phase blocked emission:|Aria stop gate blocked output:|Aria task\/project ledger blocked output claim\.|Aria .*Recovery contract:)/i.test(String(text || '').trim());
428
+ }
429
+
430
+ function doctrineTriggerLabel(entry = {}) {
431
+ return entry.trigger_id || entry.id || entry.memory || entry.doctrine || 'doctrine_trigger';
432
+ }
433
+
388
434
  function collectDoctrineTriggerHits(text) {
435
+ if (isAriaControlOutput(text)) return [];
389
436
  const triggerMap = loadDoctrineTriggerMap();
390
437
  const source = String(text || '');
391
438
  const lowerText = source.toLowerCase();
@@ -399,7 +446,8 @@ function collectDoctrineTriggerHits(text) {
399
446
  const memoryCited = memoryName && lowerText.includes(memoryName.toLowerCase());
400
447
  if (memoryCited) continue;
401
448
  hits.push({
402
- trigger: entry.trigger,
449
+ trigger: doctrineTriggerLabel(entry),
450
+ triggerId: entry.trigger_id || entry.id || null,
403
451
  memory: entry.memory || null,
404
452
  teaching: entry.teaching || null,
405
453
  message: entry.message || null,
@@ -422,6 +470,142 @@ function buildToolGateBlockMessage(blockers) {
422
470
  ].join('\n');
423
471
  }
424
472
 
473
+ function uniqueStrings(values) {
474
+ return Array.from(new Set(values.map((value) => String(value || '').trim()).filter(Boolean)));
475
+ }
476
+
477
+ function failureSummaryFromEvaluation(evaluation) {
478
+ if (!evaluation || typeof evaluation !== 'object') return ['runtime evaluation failed before producing a verdict'];
479
+ const validationIssues = Array.isArray(evaluation.validation?.violations)
480
+ ? evaluation.validation.violations.map((violation) => `validation: ${violation}`)
481
+ : [];
482
+ const layer3Issues = Array.isArray(evaluation.layer3?.failures)
483
+ ? evaluation.layer3.failures
484
+ .filter((failure) => !failure?.severity || String(failure.severity).toLowerCase() === 'block')
485
+ .map((failure) => `layer3: ${failure.detail || failure.kind || 'runtime gate failed'}`)
486
+ : [];
487
+ const postIssues = Array.isArray(evaluation.postResult?.notes)
488
+ ? evaluation.postResult.notes.map((note) => `post: ${note}`)
489
+ : [];
490
+ const doctrineIssues = Array.isArray(evaluation.doctrineBlockers)
491
+ ? evaluation.doctrineBlockers.map((blocker) => `doctrine: ${blocker}`)
492
+ : [];
493
+ const toolIssues = Array.isArray(evaluation.toolGateBlockers)
494
+ ? evaluation.toolGateBlockers.map((blocker) => `tool_gate: ${blocker}`)
495
+ : [];
496
+ return uniqueStrings([
497
+ ...validationIssues,
498
+ ...layer3Issues,
499
+ ...postIssues,
500
+ ...doctrineIssues,
501
+ ...toolIssues,
502
+ ]).slice(0, 10);
503
+ }
504
+
505
+ function buildProviderRecoveryBlockMessage(evaluation, recoveryAttempts = []) {
506
+ const remaining = failureSummaryFromEvaluation(evaluation);
507
+ const attempts = recoveryAttempts.length
508
+ ? recoveryAttempts.map((attempt) => {
509
+ const status = attempt.success ? 'passed' : attempt.error ? 'errored' : 'blocked';
510
+ const reason = attempt.error || (attempt.reasons || []).slice(0, 2).join('; ') || 'no detailed reason';
511
+ return `- attempt ${attempt.attempt} (${attempt.mode}): ${status}${reason ? ` — ${reason}` : ''}`;
512
+ })
513
+ : ['- none: recovery was disabled for this request'];
514
+ return [
515
+ 'Aria runtime blocked this output after running the recovery contract.',
516
+ '',
517
+ 'Recovery attempts:',
518
+ ...attempts,
519
+ '',
520
+ 'Remaining blockers:',
521
+ ...(remaining.length ? remaining.map((reason) => `- ${reason}`) : ['- runtime did not return a specific blocker']),
522
+ ].join('\n');
523
+ }
524
+
525
+ function recoveryAttemptCount(body = {}) {
526
+ if (body.allowAriaRecovery === false || body.disableAriaRecovery === true) return 0;
527
+ if (Number.isFinite(Number(body.ariaRecoveryAttempts))) {
528
+ return Math.min(2, Math.max(0, Number(body.ariaRecoveryAttempts)));
529
+ }
530
+ return PROVIDER_RECOVERY_ATTEMPTS;
531
+ }
532
+
533
+ function buildRecoveryPrompt(turn, candidateText, evaluation, attempt, mode) {
534
+ const failures = failureSummaryFromEvaluation(evaluation);
535
+ const firstPrinciple = turn.preResult?.firstPrincipleText || 'The loaded harness packet first principle governs this response.';
536
+ return [
537
+ 'ARIA RUNTIME RECOVERY CONTRACT',
538
+ `mode: ${mode}`,
539
+ `attempt: ${attempt}`,
540
+ '',
541
+ 'The previous assistant draft was blocked by runtime gates. Re-author the answer from scratch so it can pass without weakening any gate.',
542
+ '',
543
+ 'Original user request:',
544
+ turn.userMessage || '(no user message was extracted)',
545
+ '',
546
+ 'Blocked draft:',
547
+ String(candidateText || '').slice(0, 4000) || '(empty draft)',
548
+ '',
549
+ 'Observed blockers:',
550
+ ...(failures.length ? failures.map((failure) => `- ${failure}`) : ['- runtime returned a block without detailed failures']),
551
+ '',
552
+ 'Mandatory recovery behavior:',
553
+ '- Preserve the user intent; do not answer with a generic gate explanation unless the work truly cannot be performed.',
554
+ '- Include a readable <cognition> block with truth, harm, trust, power, reflection, context, impact, beauty, and first_principle labels.',
555
+ '- Include <applied_cognition> with decision_delta, dominant_domain, binds_to, expected_predicate, and artifact_change.',
556
+ '- If you claim something is done, fixed, ready, verified, or passing, include explicit evidence in the prose; otherwise say it is not verified yet.',
557
+ '- If a tool action is required, include the required cognition and verification framing before requesting it.',
558
+ '- Do not mention this recovery prompt, internal retries, or hidden runtime mechanics in the user-facing answer unless the answer is still blocked.',
559
+ '',
560
+ 'Use this first principle inside the cognition block:',
561
+ firstPrinciple,
562
+ '',
563
+ mode === 'architect_recovery'
564
+ ? 'Architect mode: diagnose the failure class, choose the smallest safe corrected response, and make the corrected answer directly usable.'
565
+ : 'Self-recovery mode: repair the draft once using the observed blockers and return only the corrected assistant response.',
566
+ ].join('\n');
567
+ }
568
+
569
+ function buildRecoveryBody(body, turn, candidateText, evaluation, attempt, mode, providerStyle) {
570
+ const prompt = buildRecoveryPrompt(turn, candidateText, evaluation, attempt, mode);
571
+ const minTokens = PROVIDER_RECOVERY_MIN_TOKENS;
572
+ const maxTokens = Math.max(Number(body.max_tokens || body.max_completion_tokens || 0), minTokens);
573
+ const metadata = {
574
+ ...(body.metadata && typeof body.metadata === 'object' ? body.metadata : {}),
575
+ aria_recovery_attempt: attempt,
576
+ aria_recovery_mode: mode,
577
+ aria_recovery_for_session: turn.sessionId,
578
+ };
579
+ if (providerStyle === 'anthropic') {
580
+ return {
581
+ ...body,
582
+ max_tokens: maxTokens,
583
+ metadata,
584
+ messages: [{ role: 'user', content: [{ type: 'text', text: prompt }] }],
585
+ };
586
+ }
587
+ return {
588
+ ...body,
589
+ max_tokens: maxTokens,
590
+ max_completion_tokens: maxTokens,
591
+ metadata,
592
+ messages: [{ role: 'user', content: prompt }],
593
+ };
594
+ }
595
+
596
+ function recoveryAttemptRecord(attempt, mode, evaluation, error = null) {
597
+ return {
598
+ attempt,
599
+ mode,
600
+ success: error ? false : !evaluation?.blocked,
601
+ error: error ? (error instanceof Error ? error.message : String(error)) : null,
602
+ validationSeverity: evaluation?.validation?.severity || null,
603
+ layer3Pass: evaluation?.layer3?.pass ?? null,
604
+ postQuality: evaluation?.postResult?.qualityScore ?? null,
605
+ reasons: error ? [] : failureSummaryFromEvaluation(evaluation),
606
+ };
607
+ }
608
+
425
609
  async function readJson(req) {
426
610
  const chunks = [];
427
611
  for await (const chunk of req) chunks.push(chunk);
@@ -431,13 +615,32 @@ async function readJson(req) {
431
615
  return JSON.parse(raw);
432
616
  }
433
617
 
618
+ function readHeader(req, name) {
619
+ const value = req.headers[name];
620
+ if (Array.isArray(value)) return value[0] || '';
621
+ return typeof value === 'string' ? value : '';
622
+ }
623
+
434
624
  function readBearer(req) {
435
- const auth = req.headers.authorization;
436
- if (typeof auth !== 'string') return null;
625
+ const auth = readHeader(req, 'authorization');
437
626
  const match = auth.match(/^Bearer\s+(.+)$/i);
438
627
  return match ? match[1] : null;
439
628
  }
440
629
 
630
+ function isProviderCompatibilityPath(pathname) {
631
+ return pathname === '/v1/chat/completions' ||
632
+ pathname === '/v1/messages' ||
633
+ pathname === '/v1/responses' ||
634
+ pathname === '/responses';
635
+ }
636
+
637
+ function normalizeProviderCompatibilityPath(pathname) {
638
+ if (pathname === '/v1/v1/chat/completions') return '/v1/chat/completions';
639
+ if (pathname === '/v1/v1/messages') return '/v1/messages';
640
+ if (pathname === '/v1/v1/responses') return '/v1/responses';
641
+ return pathname;
642
+ }
643
+
441
644
  function hashKey(secret) {
442
645
  return createHash('sha256').update(secret).digest('hex');
443
646
  }
@@ -451,9 +654,22 @@ function readOwnerToken() {
451
654
  }
452
655
  }
453
656
 
657
+ function readLocalHarnessToken() {
658
+ const ownerToken = readOwnerToken();
659
+ if (ownerToken) return ownerToken;
660
+ try {
661
+ if (!existsSync(LICENSE_PATH)) return '';
662
+ const license = JSON.parse(readFileSync(LICENSE_PATH, 'utf8'));
663
+ return coerceNonEmptyString(license.harnessToken) || coerceNonEmptyString(license.token) || '';
664
+ } catch {
665
+ return '';
666
+ }
667
+ }
668
+
454
669
  function synthesizeOwnerLease(apiKey, reason) {
455
670
  const now = Date.now();
456
- const recommended = 300;
671
+ const hours = 24;
672
+ const recommended = hours * 3600; // 24-hour owner heartbeat cadence
457
673
  return {
458
674
  keyHash: hashKey(apiKey),
459
675
  validatedAt: new Date(now).toISOString(),
@@ -738,6 +954,35 @@ function readCodebaseAwarenessState() {
738
954
  });
739
955
  }
740
956
 
957
+ function readCurrentGovernanceRecovery() {
958
+ try {
959
+ if (!existsSync(CURRENT_RECOVERY_PATH)) return null;
960
+ const recovery = JSON.parse(readFileSync(CURRENT_RECOVERY_PATH, 'utf8'));
961
+ const mode = String(recovery?.governanceMode || '').trim();
962
+ return mode && mode !== 'allow' ? recovery : null;
963
+ } catch {
964
+ return null;
965
+ }
966
+ }
967
+
968
+ function formatGovernanceRecoveryForPrompt(recovery) {
969
+ if (!recovery) return '';
970
+ const loop = recovery.recoveryLoop && typeof recovery.recoveryLoop === 'object' ? recovery.recoveryLoop : {};
971
+ const contract = recovery.recoveryContract && typeof recovery.recoveryContract === 'object' ? recovery.recoveryContract : {};
972
+ return [
973
+ '--- CURRENT GOVERNANCE RECOVERY CONTEXT ---',
974
+ `Mode: ${recovery.governanceMode}`,
975
+ `Decision: ${recovery.decision || 'warn'}`,
976
+ `Fingerprint: ${loop.fingerprint || 'unknown'}`,
977
+ `Next step: ${loop.nextStep || 'execute recovery contract before claiming completion'}`,
978
+ `Architect fallback: ${loop.architectFallback || contract.fallbackWhenAriaUnavailable || 'if Aria consult is unavailable, use the strongest available client LLM under the architect harness'}`,
979
+ `Load skills first: ${(contract.loadSkillsFirst || []).join(', ') || '(none)'}`,
980
+ `Repair recovery cycle: ${(contract.repairRecoveryCycle || []).join(', ') || '(none)'}`,
981
+ `Retest: ${contract.retest || 'run the concrete verification probe'}`,
982
+ 'Mandatory behavior: execute this recovery state before final output across new/resumed/pre-compact/post-compact sessions.',
983
+ ].join('\n');
984
+ }
985
+
741
986
  function hashProjectionEmbedding(text, dimension = 256) {
742
987
  const vector = Array.from({ length: dimension }, () => 0);
743
988
  const tokens = String(text || '')
@@ -867,6 +1112,7 @@ function summarizeCognitionState(state) {
867
1112
  hive: Array.isArray(state.hive) ? state.hive.length : 0,
868
1113
  cognitiveStrings: Array.isArray(state.cognitiveStrings) ? state.cognitiveStrings.length : 0,
869
1114
  receipts: Array.isArray(state.receipts) ? state.receipts.length : 0,
1115
+ governanceRecovery: state.governanceRecovery ? state.governanceRecovery.governanceMode || 'present' : null,
870
1116
  lastUpdatedAt: state.lastUpdatedAt || null,
871
1117
  };
872
1118
  }
@@ -907,6 +1153,222 @@ function writeJsonFile(filePath, payload, mode = 0o600) {
907
1153
  writeFileSync(filePath, JSON.stringify(payload, null, 2) + '\n', { mode });
908
1154
  }
909
1155
 
1156
+ function hashLedgerValue(value) {
1157
+ return createHash('sha256')
1158
+ .update(typeof value === 'string' ? value : JSON.stringify(value ?? null))
1159
+ .digest('hex');
1160
+ }
1161
+
1162
+ function firstLedgerString(...values) {
1163
+ for (const value of values) {
1164
+ if (typeof value === 'string' && value.trim()) return value.trim();
1165
+ }
1166
+ return '';
1167
+ }
1168
+
1169
+ function explicitProviderApiKeyPresent(body = {}) {
1170
+ const llm = body.llm && typeof body.llm === 'object' ? body.llm : null;
1171
+ const providerConfig = body.providerConfig && typeof body.providerConfig === 'object' ? body.providerConfig : null;
1172
+ return Boolean(
1173
+ firstLedgerString(body.providerApiKey) ||
1174
+ firstLedgerString(llm?.apiKey) ||
1175
+ firstLedgerString(providerConfig?.apiKey)
1176
+ );
1177
+ }
1178
+
1179
+ function credentialPlaneForProvider(body = {}, providerPlan = {}) {
1180
+ const explicit = firstLedgerString(body?.metadata?.credentialPlane, body?.credentialPlane);
1181
+ if (explicit) return explicit;
1182
+ if (explicitProviderApiKeyPresent(body)) return 'customer_byo_request_key';
1183
+ if (providerPlan.provider === 'ollama') return 'local_runtime_no_provider_key';
1184
+ if (/local|virtual|mac|lane/i.test(String(providerPlan.provider || ''))) return 'local_runtime_lane_key_optional';
1185
+ if (providerPlan.apiKey) return 'owner_or_runtime_managed_secret';
1186
+ return 'missing_or_unresolved_provider_key';
1187
+ }
1188
+
1189
+ function loadedSkillIdsForTurn(body = {}, turn = {}) {
1190
+ const metadataSkills = Array.isArray(body?.metadata?.loadedSkills) ? body.metadata.loadedSkills : [];
1191
+ const bodySkills = Array.isArray(body.loadedSkills) ? body.loadedSkills : [];
1192
+ const turnSkills = Array.isArray(turn.loadedSkillIds) ? turn.loadedSkillIds : [];
1193
+ return [...new Set([...metadataSkills, ...bodySkills, ...turnSkills])]
1194
+ .filter((skill) => typeof skill === 'string' && skill.trim())
1195
+ .map((skill) => skill.trim())
1196
+ .sort();
1197
+ }
1198
+
1199
+ function loadedSkillHashesForTurn(body = {}, turn = {}) {
1200
+ const metadataHashes = Array.isArray(body?.metadata?.loadedSkillHashes) ? body.metadata.loadedSkillHashes : [];
1201
+ const bodyHashes = Array.isArray(body.loadedSkillHashes) ? body.loadedSkillHashes : [];
1202
+ const turnHashes = Array.isArray(turn.loadedSkillHashes) ? turn.loadedSkillHashes : [];
1203
+ return [...new Set([...metadataHashes, ...bodyHashes, ...turnHashes])]
1204
+ .filter((hash) => typeof hash === 'string' && hash.trim())
1205
+ .map((hash) => hash.trim())
1206
+ .sort();
1207
+ }
1208
+
1209
+ function roleProfileForTurn(body = {}, turn = {}) {
1210
+ return firstLedgerString(
1211
+ body?.metadata?.roleProfile,
1212
+ body?.metadata?.role_profile,
1213
+ body.roleProfile,
1214
+ body.role_profile,
1215
+ process.env.OPENCODE_ARIA_ROLE_PROFILE,
1216
+ process.env.ARIA_ROLE_PROFILE,
1217
+ turn.turnClass?.kind,
1218
+ 'interactive_chat'
1219
+ );
1220
+ }
1221
+
1222
+ function resolveProviderPlanForLedger(body = {}) {
1223
+ try {
1224
+ const config = resolveProviderConfig(body);
1225
+ return { ok: true, config, error: null };
1226
+ } catch (error) {
1227
+ return {
1228
+ ok: false,
1229
+ config: {
1230
+ provider: firstLedgerString(body.provider, body?.llm?.provider, body?.providerConfig?.provider) || 'unresolved',
1231
+ model: firstLedgerString(body.model, body?.llm?.model, body?.providerConfig?.model) || 'unresolved',
1232
+ apiKey: '',
1233
+ baseUrl: '',
1234
+ },
1235
+ error: error instanceof Error ? error.message : String(error),
1236
+ };
1237
+ }
1238
+ }
1239
+
1240
+ function appendManagedRuntimeLedger(record = {}) {
1241
+ mkdirSync(STATE_DIR, { recursive: true, mode: 0o700 });
1242
+ const line = {
1243
+ schema: 'aria.managed_runtime_provider_ledger.v1',
1244
+ at: new Date().toISOString(),
1245
+ ...record,
1246
+ };
1247
+ appendFileSync(MANAGED_RUNTIME_LEDGER_PATH, `${JSON.stringify(line)}\n`, { mode: 0o600 });
1248
+ return {
1249
+ ledgerPath: MANAGED_RUNTIME_LEDGER_PATH,
1250
+ ledgerRecordId: line.ledger_record_id,
1251
+ };
1252
+ }
1253
+
1254
+ function buildManagedRuntimeLedgerRecord({ phase, body = {}, turn = {}, providerStyle = 'openai', providerPlan = {}, evaluation = null, repairAttempts = [], releaseDecision = 'pending', blockers = [], evidenceRefs = [], extra = {} } = {}) {
1255
+ const requestId = firstLedgerString(turn.requestId, body.requestId, body.request_id, body?.metadata?.requestId, body?.metadata?.request_id) || `req_${randomUUID()}`;
1256
+ const providerConfig = providerPlan.config || providerPlan || {};
1257
+ const selectedAlias = firstLedgerString(body.model, body?.llm?.model, body?.providerConfig?.model, providerConfig.model) || 'unresolved';
1258
+ const skillIds = loadedSkillIdsForTurn(body, turn);
1259
+ const skillHashes = loadedSkillHashesForTurn(body, turn);
1260
+ const providerPlanError = providerPlan.error ? [providerPlan.error] : [];
1261
+ return {
1262
+ ledger_record_id: `mrt_${randomUUID().replace(/-/g, '')}`,
1263
+ request_id: requestId,
1264
+ session_id: turn.sessionId || 'unknown',
1265
+ surface: body.surface || body.platform || body.client || body?.metadata?.surface || providerStyle,
1266
+ lane: 'managed_aria_provider',
1267
+ phase,
1268
+ harness_packet_hash: hashLedgerValue(turn.packet || {}),
1269
+ role_profile: roleProfileForTurn(body, turn),
1270
+ loaded_skill_ids: skillIds,
1271
+ loaded_skill_hashes: skillHashes,
1272
+ selected_alias: selectedAlias,
1273
+ upstream_provider: providerConfig.provider || 'unresolved',
1274
+ upstream_model: providerConfig.model || selectedAlias,
1275
+ credential_plane: credentialPlaneForProvider(body, providerConfig),
1276
+ quality_gate_status: evaluation?.blocked ? 'blocked' : evaluation ? 'passed' : 'pending',
1277
+ compliance_gate_status: providerPlan.ok === false ? 'blocked_provider_config' : 'pre_generation_bound',
1278
+ repair_attempts: repairAttempts,
1279
+ release_decision: releaseDecision,
1280
+ evidence_refs: evidenceRefs,
1281
+ blockers: [...providerPlanError, ...blockers].filter(Boolean),
1282
+ evolution_learning: [],
1283
+ debug: {
1284
+ provider_style: providerStyle,
1285
+ packet_bypassed: turn.packetBypassed === true,
1286
+ turn_class: turn.turnClass || null,
1287
+ operator_plan: turn.operatorPlan || null,
1288
+ api_key_present: Boolean(providerConfig.apiKey),
1289
+ base_url_present: Boolean(providerConfig.baseUrl),
1290
+ required_skill_reasons: turn.requiredSkillPlan?.reasons || [],
1291
+ missing_skill_ids: turn.missingSkillIds || [],
1292
+ ...extra,
1293
+ },
1294
+ };
1295
+ }
1296
+
1297
+ function surfaceForRuntimeCoach(body = {}, providerStyle = 'openai') {
1298
+ return firstLedgerString(
1299
+ body.surface,
1300
+ body.platform,
1301
+ body.client,
1302
+ body?.metadata?.surface,
1303
+ body?.metadata?.client,
1304
+ providerStyle,
1305
+ 'aria-mounted-runtime'
1306
+ );
1307
+ }
1308
+
1309
+ function recordRuntimeCoachPhase({ phase, body = {}, turn = {}, providerStyle = 'openai', providerPlan = {}, evaluation = null, text = '', action = '', target = '', evidenceRefs = [], metadata = {} } = {}) {
1310
+ const providerConfig = providerPlan?.config || providerPlan || {};
1311
+ const result = recordCoachPhase({
1312
+ phase,
1313
+ requestId: firstLedgerString(turn.requestId, body.requestId, body.request_id, body?.metadata?.requestId, body?.metadata?.request_id),
1314
+ sessionId: turn.sessionId,
1315
+ surface: surfaceForRuntimeCoach(body, providerStyle),
1316
+ lane: 'managed_aria_provider',
1317
+ action,
1318
+ target,
1319
+ text: text || turn.userMessage || '',
1320
+ harnessPacketHash: hashLedgerValue(turn.packet || {}),
1321
+ roleProfile: roleProfileForTurn(body, turn),
1322
+ requiredSkillIds: turn.requiredSkillPlan?.requiredSkillIds || [],
1323
+ loadedSkillIds: loadedSkillIdsForTurn(body, turn),
1324
+ missingSkillIds: turn.missingSkillIds || [],
1325
+ evidenceRefs,
1326
+ validation: evaluation?.validation || null,
1327
+ layer3: evaluation?.layer3 || null,
1328
+ qualityGateStatus: evaluation?.blocked ? 'blocked' : evaluation ? 'passed' : 'pending',
1329
+ complianceGateStatus: providerPlan?.ok === false ? 'blocked_provider_config' : 'coach_bound',
1330
+ metadata: {
1331
+ provider_style: providerStyle,
1332
+ upstream_provider: providerConfig.provider || null,
1333
+ upstream_model: providerConfig.model || null,
1334
+ selected_alias: firstLedgerString(body.model, body?.llm?.model, body?.providerConfig?.model, providerConfig.model),
1335
+ pre_receipt_id: turn.preReceipt?.receiptId || null,
1336
+ mid_receipt_id: turn.midReceipt?.receiptId || null,
1337
+ required_skill_reasons: turn.requiredSkillPlan?.reasons || [],
1338
+ ...metadata,
1339
+ },
1340
+ });
1341
+ return {
1342
+ phase,
1343
+ coachEventId: result.record.coach_event_id,
1344
+ decision: result.decision,
1345
+ permitted: result.permitted,
1346
+ ledgerPath: result.ledgerPath,
1347
+ reasons: result.record.reasons || [],
1348
+ record: result.record,
1349
+ clientMessage: result.clientMessage,
1350
+ };
1351
+ }
1352
+
1353
+ function coachRecordRefs(records = []) {
1354
+ return records
1355
+ .map((record) => record?.coachEventId ? `coach:${record.coachEventId}` : null)
1356
+ .filter(Boolean);
1357
+ }
1358
+
1359
+ function applyCoachRepairDecision(evaluation, coachResult) {
1360
+ if (!evaluation || coachResult?.decision !== 'repair_once' || evaluation.blocked) return evaluation;
1361
+ const coachBlockers = (coachResult.reasons || []).map((reason) => `coach: ${reason}`);
1362
+ const toolGateBlockers = uniqueStrings([...(evaluation.toolGateBlockers || []), ...coachBlockers]);
1363
+ return {
1364
+ ...evaluation,
1365
+ blocked: true,
1366
+ toolGateBlockers,
1367
+ finalText: buildToolGateBlockMessage(toolGateBlockers),
1368
+ coachDecision: coachResult,
1369
+ };
1370
+ }
1371
+
910
1372
  function readHiveFileLeaseState() {
911
1373
  const state = readJsonFile(HIVE_FILE_LEASES_PATH, { leases: [] });
912
1374
  return {
@@ -1267,17 +1729,48 @@ function getAutonomyView(state, limit = 50) {
1267
1729
  };
1268
1730
  }
1269
1731
 
1270
- function resolveApiKey(req, body) {
1271
- const headerKey = req.headers['x-aria-api-key'];
1272
- if (typeof headerKey === 'string' && headerKey.trim()) return headerKey.trim();
1273
- if (typeof body.apiKey === 'string' && body.apiKey.trim()) return body.apiKey.trim();
1274
- return readBearer(req) || process.env.ARIA_API_KEY || process.env.ARIA_MASTER_TOKEN || null;
1732
+ function resolveApiKey(req, body, options = {}) {
1733
+ const tokenScope = coerceNonEmptyString(body.tokenScope) ||
1734
+ coerceNonEmptyString(body.clientId) ||
1735
+ coerceNonEmptyString(body.tenantId) ||
1736
+ coerceNonEmptyString(req.headers['x-aria-client-id']) ||
1737
+ coerceNonEmptyString(req.headers['x-aria-tenant-id']);
1738
+ const baseUrl = typeof body.baseUrl === 'string' && body.baseUrl.trim()
1739
+ ? body.baseUrl.trim()
1740
+ : DEFAULT_HARNESS_URL;
1741
+ const headerKey = readHeader(req, 'x-aria-api-key').trim();
1742
+ if (headerKey) {
1743
+ resolveAriaAuthToken({ explicitToken: headerKey, baseUrl, tokenScope });
1744
+ return headerKey;
1745
+ }
1746
+
1747
+ const bodyKey = coerceNonEmptyString(body.ariaApiKey) ||
1748
+ coerceNonEmptyString(body.ariaHarnessToken) ||
1749
+ coerceNonEmptyString(body.harnessToken) ||
1750
+ coerceNonEmptyString(body.apiKey);
1751
+ if (bodyKey) {
1752
+ resolveAriaAuthToken({ explicitToken: bodyKey, baseUrl, tokenScope });
1753
+ return bodyKey;
1754
+ }
1755
+
1756
+ if (options.allowBearerAuth !== false) {
1757
+ const bearerToken = readBearer(req);
1758
+ if (bearerToken) {
1759
+ resolveAriaAuthToken({ explicitToken: bearerToken, baseUrl, tokenScope });
1760
+ return bearerToken;
1761
+ }
1762
+ }
1763
+
1764
+ return resolveAriaAuthToken({ baseUrl, tokenScope }).token || null;
1275
1765
  }
1276
1766
 
1277
- function createClient(req, body) {
1278
- const apiKey = resolveApiKey(req, body);
1767
+ function createClient(req, body, options = {}) {
1768
+ const apiKey = resolveApiKey(req, body, options);
1279
1769
  if (!apiKey) {
1280
- throw new Error('Missing Aria API key. Supply x-aria-api-key, Authorization: Bearer <token>, ARIA_API_KEY, or ARIA_MASTER_TOKEN.');
1770
+ const credentialHint = options.allowBearerAuth === false
1771
+ ? 'Supply x-aria-api-key, body.ariaApiKey, ARIA_HARNESS_TOKEN, ARIA_API_KEY, or ARIA_MASTER_TOKEN. Provider Authorization headers are not accepted as Aria control-plane credentials on /v1 compatibility routes.'
1772
+ : 'Supply x-aria-api-key, Authorization: Bearer <token>, ARIA_HARNESS_TOKEN, ARIA_API_KEY, or ARIA_MASTER_TOKEN.';
1773
+ throw new Error(`Missing Aria API key. ${credentialHint}`);
1281
1774
  }
1282
1775
  ensureNotRevoked(apiKey);
1283
1776
 
@@ -1320,7 +1813,10 @@ async function heartbeatUpstream(req, body, client, apiKey) {
1320
1813
 
1321
1814
  const ownerBypassAllowed =
1322
1815
  readOwnerToken() &&
1323
- apiKey === readOwnerToken() &&
1816
+ (apiKey === readOwnerToken() ||
1817
+ apiKey === process.env.ARIA_HARNESS_TOKEN ||
1818
+ apiKey === process.env.ARIA_API_KEY ||
1819
+ apiKey === process.env.ARIA_MASTER_TOKEN) &&
1324
1820
  body.allowOwnerBypass !== false &&
1325
1821
  process.env.ARIA_RUNTIME_ALLOW_OWNER_BYPASS !== '0';
1326
1822
 
@@ -1333,6 +1829,7 @@ async function heartbeatUpstream(req, body, client, apiKey) {
1333
1829
  'Content-Type': 'application/json',
1334
1830
  },
1335
1831
  body: JSON.stringify(heartbeatBody),
1832
+ signal: AbortSignal.timeout(5_000),
1336
1833
  });
1337
1834
 
1338
1835
  const payload = await response.json().catch(() => ({}));
@@ -1428,12 +1925,21 @@ async function heartbeatUpstream(req, body, client, apiKey) {
1428
1925
  return lease;
1429
1926
  }
1430
1927
 
1431
- async function ensureLease(req, body, client) {
1432
- const apiKey = resolveApiKey(req, body);
1928
+ async function ensureLease(req, body, client, options = {}) {
1929
+ const apiKey = resolveApiKey(req, body, options);
1433
1930
  if (!apiKey) {
1434
1931
  throw new Error('Missing Aria API key for heartbeat validation');
1435
1932
  }
1436
1933
 
1934
+ // Owner machine fast-path: if the owner token file is present on disk,
1935
+ // this is the owner's machine. Skip upstream heartbeat entirely.
1936
+ const ownerToken = readOwnerToken();
1937
+ if (ownerToken) {
1938
+ const keyHash = hashKey(ownerToken);
1939
+ const cached = leaseCache.get(keyHash) || null;
1940
+ return cached || synthesizeOwnerLease(ownerToken, 'owner machine fast-path');
1941
+ }
1942
+
1437
1943
  const keyHash = hashKey(apiKey);
1438
1944
  const cached = leaseCache.get(keyHash) || loadEncryptedLease(apiKey);
1439
1945
  ensureOfflineBundleSeeded(apiKey, cached);
@@ -1503,6 +2009,48 @@ function findVerifiedState(text) {
1503
2009
  return /(?:verified|confirmed|observed|tested|health[- ]check|response code|exit code|pod image|digest)/i.test(String(text || ''));
1504
2010
  }
1505
2011
 
2012
+ function hasSuccessfulEvidenceText(...values) {
2013
+ return values.some((value) =>
2014
+ /\b(?:ok|passed|pass|success|succeeded|exit\s*=?\s*0|0\s*failures?|healthy)\b/i.test(String(value || ''))
2015
+ );
2016
+ }
2017
+
2018
+ function hasToolVerificationRef(refs) {
2019
+ if (!Array.isArray(refs)) return false;
2020
+ return refs.some((ref) =>
2021
+ hasSuccessfulEvidenceText(ref?.preview, ref?.metadata?.commandResult, ref?.metadata?.status)
2022
+ );
2023
+ }
2024
+
2025
+ function normalizePostEvidence(text, evidence = {}) {
2026
+ const source = evidence && typeof evidence === 'object' ? evidence : {};
2027
+ const hasVerifiedState =
2028
+ source.hasVerifiedState === true ||
2029
+ source.verified === true ||
2030
+ source.verification === true ||
2031
+ source.validation_run === true ||
2032
+ source.validationRun === true ||
2033
+ source.validation?.ok === true ||
2034
+ source.test?.ok === true ||
2035
+ findVerifiedState(text) ||
2036
+ hasSuccessfulEvidenceText(
2037
+ source.commandResult,
2038
+ source.outputPreview,
2039
+ source.verification,
2040
+ source.validation,
2041
+ source.testResult
2042
+ ) ||
2043
+ hasToolVerificationRef(source.tool_refs || source.toolRefs);
2044
+
2045
+ return {
2046
+ ...source,
2047
+ hasVerifiedState,
2048
+ layer3Pass: source.layer3Pass ?? source.layer3_pass,
2049
+ remoteValidationSeverity:
2050
+ source.remoteValidationSeverity ?? source.remote_validation_severity ?? source.validationSeverity ?? source.validation_severity,
2051
+ };
2052
+ }
2053
+
1506
2054
  const APPLIED_COGNITION_BLOCK_RX = /<applied_cognition>([\s\S]*?)<\/applied_cognition>/i;
1507
2055
 
1508
2056
  function validateAppliedCognitionContract(text) {
@@ -1544,6 +2092,25 @@ function mergeAppliedCognitionValidation(validation, applied) {
1544
2092
  };
1545
2093
  }
1546
2094
 
2095
+ function isAppliedCognitionOnlyValidationBlock(validation) {
2096
+ const violations = Array.isArray(validation?.violations) ? validation.violations : [];
2097
+ return validation?.severity === 'block' &&
2098
+ violations.length > 0 &&
2099
+ violations.every((violation) => /missing\s+<applied_cognition>\s+contract/i.test(String(violation || '')));
2100
+ }
2101
+
2102
+ function allowTrivialAppliedCognitionValidation(validation, requireCognitionBlock) {
2103
+ if (requireCognitionBlock || !isAppliedCognitionOnlyValidationBlock(validation)) return validation;
2104
+ return {
2105
+ ...validation,
2106
+ passed: true,
2107
+ severity: 'pass',
2108
+ violations: [],
2109
+ gateTriggers: [...new Set([...(validation.gateTriggers || []), 'applied-cognition-trivial-turn-waived'])],
2110
+ appliedCognition: { ok: true, waived: 'trivial-turn' },
2111
+ };
2112
+ }
2113
+
1547
2114
  function toTelemetryEvent(payload, source = 'aria-mounted-runtime') {
1548
2115
  return {
1549
2116
  event_type: payload.event_type || 'runtime.cognition.turn',
@@ -1684,6 +2251,131 @@ function buildMinimalInjection(packet, task, aegisLearnings = null) {
1684
2251
  };
1685
2252
  }
1686
2253
 
2254
+ function providerIntentText(body = {}, userMessage = '') {
2255
+ const parts = [userMessage];
2256
+ for (const value of [
2257
+ body.prompt,
2258
+ body.input,
2259
+ body.message,
2260
+ body.ariaPlannedApproach,
2261
+ body.ariaRationale,
2262
+ body?.metadata?.intent,
2263
+ body?.metadata?.mode,
2264
+ body?.metadata?.surface,
2265
+ ]) {
2266
+ if (typeof value === 'string' && value.trim()) parts.push(value.trim());
2267
+ }
2268
+ return parts.filter(Boolean).join('\n');
2269
+ }
2270
+
2271
+ function classifyRuntimeRequiredSkills(body = {}, userMessage = '', turnClass = null) {
2272
+ const text = providerIntentText(body, userMessage);
2273
+ const lower = text.toLowerCase();
2274
+ const required = new Set(['mizan', 'aria-aristotle-pre-phase']);
2275
+ const reasons = ['all managed provider requests require mizan balance and pre-generation cognition'];
2276
+
2277
+ if (/\b(?:edit|write|patch|apply_patch|repo|repository|codebase|runtime-src|source|implementation|implement|fix|debug|refactor|build|compile|test|hook|plugin|connector|quality runtime|provider gateway)\b/i.test(text)) {
2278
+ required.add('aria-repo-doctrine');
2279
+ required.add('aria-forge-guardrails');
2280
+ reasons.push('repository/runtime implementation intent requires repo doctrine and forge guardrails');
2281
+ }
2282
+ if (/\b(?:deploy|kubectl|helm|terraform|docker\s+push|rollout|release|k8s|cluster|infra|infrastructure)\b/i.test(text)) {
2283
+ required.add('aria-harness-deploy');
2284
+ required.add('aria-harness-no-stripping');
2285
+ required.add('aria-harness-output-discipline');
2286
+ required.add('aria-forge-guardrails');
2287
+ reasons.push('deploy or infrastructure intent requires deploy, no-stripping, output discipline, and guardrails');
2288
+ }
2289
+ if (/\b(?:remove|delete|strip|drop|omit|disable|bypass|skip|stub|mock|fake|placeholder|temporary|quick scaffold|band-aid|downgrade|weaken)\b/i.test(text)) {
2290
+ required.add('aria-harness-no-stripping');
2291
+ required.add('aria-forge-guardrails');
2292
+ reasons.push('contract-reduction language requires no-stripping and guardrails');
2293
+ }
2294
+ if (/\b(?:done|complete|completed|ready|verified|fixed|shipped|production-ready|first-class|release-ready|client-facing|status report|summary)\b/i.test(text)) {
2295
+ required.add('aria-harness-output-discipline');
2296
+ reasons.push('completion/readiness/status language requires output discipline');
2297
+ }
2298
+ if (/\b(?:architecture|architect|system design|tradeoff|decision|adr|migration plan|design doc)\b/i.test(text)) {
2299
+ required.add('ghazali-8lens');
2300
+ required.add('predictor');
2301
+ reasons.push('architecture or decision intent requires multi-lens validation and next-step prediction');
2302
+ }
2303
+ if (/\b(?:test|tests|testing|coverage|verify|verification|smoke|e2e|regression)\b/i.test(text)) {
2304
+ required.add('predictor');
2305
+ reasons.push('verification intent requires next-step prediction and evidence framing');
2306
+ }
2307
+
2308
+ const explicitSkills = [
2309
+ ...(Array.isArray(body.requiredSkills) ? body.requiredSkills : []),
2310
+ ...(Array.isArray(body?.metadata?.requiredSkills) ? body.metadata.requiredSkills : []),
2311
+ ];
2312
+ for (const skill of explicitSkills) {
2313
+ if (typeof skill === 'string' && skill.trim()) required.add(skill.trim());
2314
+ }
2315
+
2316
+ return {
2317
+ requiredSkillIds: [...required].sort(),
2318
+ reasons,
2319
+ intentHash: hashLedgerValue(text || 'empty-intent'),
2320
+ turnClass: turnClass || null,
2321
+ matchedTextPreview: text.slice(0, 500),
2322
+ };
2323
+ }
2324
+
2325
+ function resolveSkillPath(skillId) {
2326
+ const safeId = String(skillId || '').replace(/[^a-zA-Z0-9_.-]/g, '');
2327
+ if (!safeId) return '';
2328
+ for (const root of SKILL_SEARCH_ROOTS) {
2329
+ const candidate = join(root, safeId, 'SKILL.md');
2330
+ if (existsSync(candidate)) return candidate;
2331
+ }
2332
+ return '';
2333
+ }
2334
+
2335
+ function loadRuntimeRequiredSkills(skillIds = []) {
2336
+ const loaded = [];
2337
+ const missing = [];
2338
+ for (const skillId of skillIds) {
2339
+ const path = resolveSkillPath(skillId);
2340
+ if (!path) {
2341
+ missing.push(skillId);
2342
+ continue;
2343
+ }
2344
+ const content = readFileSync(path, 'utf8');
2345
+ const body = content.length > SKILL_BODY_MAX_CHARS
2346
+ ? `${content.slice(0, SKILL_BODY_MAX_CHARS)}\n\n[aria-runtime: skill body truncated at ${SKILL_BODY_MAX_CHARS} chars]`
2347
+ : content;
2348
+ loaded.push({
2349
+ id: skillId,
2350
+ path,
2351
+ hash: hashLedgerValue(content),
2352
+ chars: content.length,
2353
+ body,
2354
+ });
2355
+ }
2356
+ return { loaded, missing };
2357
+ }
2358
+
2359
+ function buildForcedSkillPromptBlock(skillLoad = null) {
2360
+ if (!skillLoad || !Array.isArray(skillLoad.loaded) || skillLoad.loaded.length === 0) return '';
2361
+ const sections = [
2362
+ '--- ARIA RUNTIME FORCED SKILL LOAD ---',
2363
+ 'The following skill bodies were loaded by Aria Runtime before the upstream provider call. Apply them as binding instructions, not optional references.',
2364
+ ];
2365
+ for (const skill of skillLoad.loaded) {
2366
+ sections.push(
2367
+ `<skill_content name="${skill.id}" hash="${skill.hash}" source="${skill.path}">`,
2368
+ skill.body,
2369
+ '</skill_content>'
2370
+ );
2371
+ }
2372
+ if (skillLoad.missing.length > 0) {
2373
+ sections.push(`missing_skill_ids: ${skillLoad.missing.join(', ')}`);
2374
+ }
2375
+ sections.push('--- /ARIA RUNTIME FORCED SKILL LOAD ---');
2376
+ return sections.join('\n');
2377
+ }
2378
+
1687
2379
  function isOwnerBypassRequest(req, body, apiKey = null) {
1688
2380
  const ownerToken = readOwnerToken();
1689
2381
  const candidate = apiKey || resolveApiKey(req, body) || '';
@@ -1728,9 +2420,9 @@ function buildOwnerBypassPacket(message, reason = 'owner-local-bypass') {
1728
2420
  };
1729
2421
  }
1730
2422
 
1731
- async function loadRuntimePacket(req, body, client, packetRequest, message) {
2423
+ async function loadRuntimePacket(req, body, client, packetRequest, message, options = {}) {
1732
2424
  if (body.packet) return enrichPacketWithCodebaseSnapshot(body.packet);
1733
- const apiKey = resolveApiKey(req, body);
2425
+ const apiKey = resolveApiKey(req, body, options);
1734
2426
  ensureOfflineBundleSeeded(apiKey, leaseCache.get(hashKey(apiKey)) || loadEncryptedLease(apiKey));
1735
2427
  try {
1736
2428
  const wrapped = await client.getHarnessPacket(packetRequest || {});
@@ -1759,7 +2451,7 @@ async function loadRuntimePacket(req, body, client, packetRequest, message) {
1759
2451
  return enrichPacketWithCodebaseSnapshot(fallbackPacket);
1760
2452
  }
1761
2453
  }
1762
- if (!isOwnerBypassRequest(req, body)) {
2454
+ if (!isOwnerBypassRequest(req, body, apiKey)) {
1763
2455
  throw error;
1764
2456
  }
1765
2457
  return enrichPacketWithCodebaseSnapshot(buildOwnerBypassPacket(
@@ -1772,11 +2464,15 @@ async function loadRuntimePacket(req, body, client, packetRequest, message) {
1772
2464
  function enrichPacketWithCodebaseSnapshot(packet) {
1773
2465
  if (!packet || typeof packet !== 'object') return packet;
1774
2466
  const codebase = compactCodebaseSnapshot();
2467
+ const governanceRecovery = readCurrentGovernanceRecovery();
2468
+ const recoveryPrompt = formatGovernanceRecoveryForPrompt(governanceRecovery);
1775
2469
  return {
1776
2470
  ...packet,
2471
+ harness: [recoveryPrompt, packet.harness].filter(Boolean).join('\n'),
1777
2472
  runtime: {
1778
2473
  ...(packet.runtime && typeof packet.runtime === 'object' ? packet.runtime : {}),
1779
2474
  codebase_awareness: codebase,
2475
+ governance_recovery: governanceRecovery,
1780
2476
  },
1781
2477
  };
1782
2478
  }
@@ -1802,8 +2498,17 @@ function buildMizanPacketRequest(body = {}) {
1802
2498
  };
1803
2499
  }
1804
2500
 
1805
- async function buildRuntimeTurnContext(req, body, client) {
2501
+ async function buildRuntimeTurnContext(req, body, client, options = {}) {
1806
2502
  const sessionId = deriveSessionId(req, body, body.provider === 'anthropic' ? 'anthropic' : 'openai');
2503
+ const requestId = firstLedgerString(
2504
+ body.requestId,
2505
+ body.request_id,
2506
+ body?.metadata?.requestId,
2507
+ body?.metadata?.request_id,
2508
+ readHeader(req, 'x-request-id'),
2509
+ readHeader(req, 'x-aria-request-id'),
2510
+ `req_${randomUUID()}`
2511
+ );
1807
2512
  const userId = deriveUserId(req, body);
1808
2513
  const userMessage = Array.isArray(body.messages)
1809
2514
  ? (body.provider === 'anthropic' ? extractAnthropicUserMessage(body.messages) : extractOpenAIUserMessage(body.messages))
@@ -1814,6 +2519,7 @@ async function buildRuntimeTurnContext(req, body, client) {
1814
2519
  : 'Bind to the harness packet, reason through Aristotle and Noor cognitives, avoid unsupported state claims, and emit only what survives validation.';
1815
2520
  const packetRequest = {
1816
2521
  sessionId,
2522
+ requestId,
1817
2523
  userId,
1818
2524
  platform: body.platform || body.client || 'mounted-runtime',
1819
2525
  system: 'aria-mounted-runtime',
@@ -1821,7 +2527,7 @@ async function buildRuntimeTurnContext(req, body, client) {
1821
2527
  message: userMessage,
1822
2528
  ...(body.packetRequest && typeof body.packetRequest === 'object' ? body.packetRequest : {}),
1823
2529
  };
1824
- const packet = await loadRuntimePacket(req, body, client, packetRequest, userMessage);
2530
+ const packet = await loadRuntimePacket(req, body, client, packetRequest, userMessage, options);
1825
2531
  const runtimeId = ensureRuntimeMeta().runtimeId;
1826
2532
  const turnClass = classifyTurn({
1827
2533
  message: userMessage,
@@ -1830,6 +2536,8 @@ async function buildRuntimeTurnContext(req, body, client) {
1830
2536
  plannedApproach,
1831
2537
  domains: body.ariaDomains,
1832
2538
  });
2539
+ const requiredSkillPlan = classifyRuntimeRequiredSkills(body, userMessage, turnClass);
2540
+ const skillLoad = loadRuntimeRequiredSkills(requiredSkillPlan.requiredSkillIds);
1833
2541
  const preBundle = evaluateMizanPre(packet, {
1834
2542
  sessionId,
1835
2543
  userId,
@@ -1856,13 +2564,15 @@ async function buildRuntimeTurnContext(req, body, client) {
1856
2564
  });
1857
2565
  const aegisLearnings = await client.getAegisLearnings(12).catch(() => null);
1858
2566
  const basePrompt = client.buildSystemPrompt(buildMinimalInjection(packet, userMessage || 'Aria runtime turn', aegisLearnings));
2567
+ const forcedSkillPromptBlock = buildForcedSkillPromptBlock(skillLoad);
1859
2568
  const cognitionDirective = buildRuntimeCognitionDirective(packet, {
1860
2569
  preResult: preBundle.result,
1861
2570
  midResult: midBundle.result,
1862
2571
  });
1863
- const ariaSystemPrompt = [basePrompt, '', cognitionDirective].join('\n');
2572
+ const ariaSystemPrompt = [basePrompt, '', forcedSkillPromptBlock, '', cognitionDirective].filter(Boolean).join('\n');
1864
2573
  return {
1865
2574
  sessionId,
2575
+ requestId,
1866
2576
  userId,
1867
2577
  userMessage,
1868
2578
  plannedApproach,
@@ -1870,6 +2580,11 @@ async function buildRuntimeTurnContext(req, body, client) {
1870
2580
  packetBypassed: packet?.version === 'owner-local-bypass',
1871
2581
  turnClass,
1872
2582
  operatorPlan: buildOperatorPlan(turnClass, 'pre'),
2583
+ requiredSkillPlan,
2584
+ loadedSkills: skillLoad.loaded,
2585
+ loadedSkillIds: skillLoad.loaded.map((skill) => skill.id),
2586
+ loadedSkillHashes: skillLoad.loaded.map((skill) => skill.hash),
2587
+ missingSkillIds: skillLoad.missing,
1873
2588
  preResult: preBundle.result,
1874
2589
  preBundle,
1875
2590
  preReceipt: preBundle.receipt,
@@ -2310,6 +3025,40 @@ function buildReadableAriaEnvelope(extra = {}, debug = false) {
2310
3025
  : [],
2311
3026
  } : null;
2312
3027
 
3028
+ const recovery = Array.isArray(extra.recoveryAttempts) ? {
3029
+ attempted: extra.recoveryAttempts.length > 0,
3030
+ attempts: extra.recoveryAttempts.map((attempt) => ({
3031
+ attempt: attempt.attempt,
3032
+ mode: attempt.mode,
3033
+ success: Boolean(attempt.success),
3034
+ error: attempt.error || null,
3035
+ validationSeverity: attempt.validationSeverity || null,
3036
+ layer3Pass: attempt.layer3Pass ?? null,
3037
+ postQuality: attempt.postQuality ?? null,
3038
+ reasons: Array.isArray(attempt.reasons) ? attempt.reasons.slice(0, 5) : [],
3039
+ })),
3040
+ } : null;
3041
+
3042
+ const runtimeLedger = extra.runtimeLedger ? {
3043
+ path: extra.runtimeLedger.ledgerPath || null,
3044
+ records: Array.isArray(extra.runtimeLedger.records)
3045
+ ? extra.runtimeLedger.records.map((record) => ({
3046
+ phase: record.phase,
3047
+ id: record.ledgerRecordId || record.ledger_record_id || null,
3048
+ }))
3049
+ : [],
3050
+ } : null;
3051
+
3052
+ const coachKernel = extra.coachKernel ? {
3053
+ records: Array.isArray(extra.coachKernel.records)
3054
+ ? extra.coachKernel.records.map((record) => ({
3055
+ phase: record.phase,
3056
+ id: record.coachEventId || record.coach_event_id || record.record?.coach_event_id || null,
3057
+ decision: record.decision || record.record?.decision || null,
3058
+ }))
3059
+ : [],
3060
+ } : null;
3061
+
2313
3062
  const envelope = {
2314
3063
  blocked: Boolean(extra.blocked),
2315
3064
  control_plane: 'aria-mounted-runtime',
@@ -2330,6 +3079,9 @@ function buildReadableAriaEnvelope(extra = {}, debug = false) {
2330
3079
  cognition,
2331
3080
  doctrine,
2332
3081
  tool_gate,
3082
+ recovery,
3083
+ runtime_ledger: runtimeLedger,
3084
+ coach_kernel: coachKernel,
2333
3085
  };
2334
3086
 
2335
3087
  if (debug) {
@@ -2640,10 +3392,12 @@ async function persistTurnArtifacts(req, body, client, apiKey, turn) {
2640
3392
  pre: turn.preResult,
2641
3393
  mid: turn.midResult,
2642
3394
  post: turn.postResult,
3395
+ recovery_attempts: turn.recoveryAttempts || [],
2643
3396
  evolution_principles: evolutionPrinciples,
2644
3397
  aegis_patterns: aegisPatterns,
2645
3398
  runtime_url: DEFAULT_RUNTIME_URL,
2646
3399
  runtime_id: ensureRuntimeMeta().runtimeId,
3400
+ governance_recovery: readCurrentGovernanceRecovery(),
2647
3401
  },
2648
3402
  };
2649
3403
 
@@ -2671,7 +3425,10 @@ async function persistTurnArtifacts(req, body, client, apiKey, turn) {
2671
3425
  sessionId: turn.sessionId,
2672
3426
  message: turn.userMessage,
2673
3427
  response: turn.finalText,
3428
+ governance_recovery: readCurrentGovernanceRecovery(),
3429
+ recovery_attempts: turn.recoveryAttempts || [],
2674
3430
  }, TELEMETRY_LIMIT),
3431
+ governanceRecovery: readCurrentGovernanceRecovery(),
2675
3432
  }));
2676
3433
 
2677
3434
  try {
@@ -2690,6 +3447,7 @@ async function persistTurnArtifacts(req, body, client, apiKey, turn) {
2690
3447
  model: turn.providerMeta.model,
2691
3448
  finish_reason: turn.providerMeta.finishReason,
2692
3449
  success: turn.success,
3450
+ governance_recovery: readCurrentGovernanceRecovery(),
2693
3451
  },
2694
3452
  readability: cognitionStrings.readable,
2695
3453
  });
@@ -2822,22 +3580,7 @@ async function persistTurnArtifacts(req, body, client, apiKey, turn) {
2822
3580
  }
2823
3581
  }
2824
3582
 
2825
- async function handleProviderProxy(req, body, client, providerStyle) {
2826
- const apiKey = resolveApiKey(req, body);
2827
- const startedAt = Date.now();
2828
- const turn = await buildRuntimeTurnContext(req, body, client);
2829
- if (turn.preResult.fitrahVetoed) {
2830
- const refusal = buildPhaseBlockMessage(turn.preResult, 'pre');
2831
- const extra = { blocked: true, phase: 'pre', pre: turn.preResult, mid: turn.midResult };
2832
- return providerStyle === 'anthropic'
2833
- ? anthropicResponseEnvelope(refusal, { model: body.model || 'aria-runtime', finishReason: 'end_turn' }, extra, body?.ariaDebug === true)
2834
- : openAiResponseEnvelope(body, refusal, { model: body.model || 'aria-runtime', finishReason: 'stop', usage: null }, extra);
2835
- }
2836
-
2837
- const providerMeta = providerStyle === 'anthropic'
2838
- ? await callProviderForAnthropic(body, turn.ariaSystemPrompt)
2839
- : await callProviderForOpenAI(body, turn.ariaSystemPrompt);
2840
-
3583
+ function recordProviderUsage(body, turn, providerMeta) {
2841
3584
  try {
2842
3585
  recordTokenUsage({
2843
3586
  tenantId: body?.metadata?.jti || body?.jti || 'owner-local',
@@ -2851,11 +3594,19 @@ async function handleProviderProxy(req, body, client, providerStyle) {
2851
3594
  requestType: body?.metadata?.requestType || 'chat',
2852
3595
  });
2853
3596
  } catch {}
3597
+ }
3598
+
3599
+ async function callProviderByStyle(providerStyle, body, systemPrompt) {
3600
+ return providerStyle === 'anthropic'
3601
+ ? callProviderForAnthropic(body, systemPrompt)
3602
+ : callProviderForOpenAI(body, systemPrompt);
3603
+ }
2854
3604
 
2855
- let candidateText = providerMeta.text || '';
2856
- const toolIntents = extractProviderToolIntents(providerStyle, providerMeta);
2857
- const requiresReadableCognition = isNonTrivialAssistantTurn(candidateText, toolIntents);
2858
- const cognitionContract = extractVisibleCognitionContract(candidateText);
3605
+ async function evaluateProviderCandidate(req, body, client, apiKey, turn, providerStyle, providerMeta, initialText) {
3606
+ let candidateText = typeof initialText === 'string' ? initialText : providerMeta.text || '';
3607
+ let toolIntents = extractProviderToolIntents(providerStyle, providerMeta);
3608
+ let requiresReadableCognition = isNonTrivialAssistantTurn(candidateText, toolIntents);
3609
+ let requireCognitionBlock = body.requireCognitionBlock ?? requiresReadableCognition;
2859
3610
 
2860
3611
  let validation = {
2861
3612
  passed: true,
@@ -2879,6 +3630,9 @@ async function handleProviderProxy(req, body, client, providerStyle) {
2879
3630
  }
2880
3631
  if (validation?.severity === 'block' && validation?.rewritten) {
2881
3632
  candidateText = validation.rewritten;
3633
+ toolIntents = [];
3634
+ requiresReadableCognition = isNonTrivialAssistantTurn(candidateText, toolIntents);
3635
+ requireCognitionBlock = body.requireCognitionBlock ?? requiresReadableCognition;
2882
3636
  try {
2883
3637
  validation = await client.validateOutput(candidateText, turn.sessionId);
2884
3638
  } catch (error) {
@@ -2893,13 +3647,14 @@ async function handleProviderProxy(req, body, client, providerStyle) {
2893
3647
  };
2894
3648
  }
2895
3649
  }
3650
+ validation = allowTrivialAppliedCognitionValidation(validation, requireCognitionBlock);
2896
3651
  }
2897
3652
 
2898
3653
  const layer3 = await runLayer3(req, {
2899
3654
  text: candidateText || (toolIntents.length > 0 ? `<cognition>\n</cognition>` : ''),
2900
3655
  packet: turn.packet,
2901
3656
  fetchPacket: false,
2902
- requireCognitionBlock: body.requireCognitionBlock ?? requiresReadableCognition,
3657
+ requireCognitionBlock,
2903
3658
  }, client);
2904
3659
  const doctrineHits = candidateText.trim() ? collectDoctrineTriggerHits(candidateText) : [];
2905
3660
  const doctrineBlockers = doctrineHits
@@ -2942,7 +3697,6 @@ async function handleProviderProxy(req, body, client, providerStyle) {
2942
3697
  parentReceiptId: turn.midReceipt?.receiptId || turn.preReceipt?.receiptId || null,
2943
3698
  });
2944
3699
  const postResult = postBundle.result;
2945
-
2946
3700
  const blocked =
2947
3701
  validation.severity === 'block' ||
2948
3702
  !layer3.pass ||
@@ -2954,44 +3708,421 @@ async function handleProviderProxy(req, body, client, providerStyle) {
2954
3708
  : blocked
2955
3709
  ? buildPhaseBlockMessage(postResult, 'post')
2956
3710
  : candidateText;
2957
- await persistTurnArtifacts(req, body, client, apiKey, {
2958
- ...turn,
2959
- postResult,
2960
- postBundle,
2961
- postReceipt: postBundle.receipt,
2962
- postContract: postBundle.contract,
3711
+
3712
+ return {
3713
+ candidateText,
3714
+ toolIntents,
3715
+ requireCognitionBlock,
3716
+ cognitionContract: extractVisibleCognitionContract(candidateText),
2963
3717
  validation,
2964
3718
  layer3,
3719
+ doctrineHits,
3720
+ doctrineBlockers,
3721
+ toolGateBlockers,
3722
+ postBundle,
3723
+ postResult,
3724
+ blocked,
3725
+ finalText,
3726
+ };
3727
+ }
3728
+
3729
+ async function handleProviderProxy(req, body, client, providerStyle, options = {}) {
3730
+ const apiKey = resolveApiKey(req, body, options);
3731
+ const startedAt = Date.now();
3732
+ const turn = await buildRuntimeTurnContext(req, body, client, options);
3733
+ const ledgerRecords = [];
3734
+ const coachRecords = [];
3735
+ const providerPlan = resolveProviderPlanForLedger(body);
3736
+ coachRecords.push(recordRuntimeCoachPhase({
3737
+ phase: 'pre_turn',
3738
+ body,
3739
+ turn,
3740
+ providerStyle,
3741
+ providerPlan,
3742
+ text: turn.userMessage,
3743
+ evidenceRefs: [`packet:${hashLedgerValue(turn.packet || {})}`],
3744
+ metadata: { request_start_ms: startedAt },
3745
+ }));
3746
+ coachRecords.push(recordRuntimeCoachPhase({
3747
+ phase: 'pre_cognition',
3748
+ body,
3749
+ turn,
3750
+ providerStyle,
3751
+ providerPlan,
3752
+ text: turn.plannedApproach,
3753
+ evidenceRefs: [`pre_receipt:${turn.preReceipt?.receiptId || 'pending'}`],
3754
+ }));
3755
+ coachRecords.push(recordRuntimeCoachPhase({
3756
+ phase: 'post_cognition',
3757
+ body,
3758
+ turn,
3759
+ providerStyle,
3760
+ providerPlan,
3761
+ text: turn.ariaSystemPrompt,
3762
+ evidenceRefs: [
3763
+ `pre_receipt:${turn.preReceipt?.receiptId || 'missing'}`,
3764
+ `mid_receipt:${turn.midReceipt?.receiptId || 'missing'}`,
3765
+ ...turn.loadedSkillHashes.map((hash) => `skill_hash:${hash}`),
3766
+ ],
3767
+ metadata: {
3768
+ loaded_skill_count: turn.loadedSkillIds.length,
3769
+ missing_skill_count: turn.missingSkillIds.length,
3770
+ },
3771
+ }));
3772
+ if (turn.preResult.fitrahVetoed) {
3773
+ const refusal = buildPhaseBlockMessage(turn.preResult, 'pre');
3774
+ coachRecords.push(recordRuntimeCoachPhase({
3775
+ phase: 'claim_or_release',
3776
+ body,
3777
+ turn,
3778
+ providerStyle,
3779
+ providerPlan,
3780
+ text: refusal,
3781
+ evidenceRefs: [`pre_receipt:${turn.preReceipt?.receiptId || 'missing'}`],
3782
+ metadata: { blocked: true, requireCognitionBlock: false, requireAppliedCognition: false, reason: 'pre_generation_fitrah_veto' },
3783
+ }));
3784
+ const preBlockRecord = appendManagedRuntimeLedger(buildManagedRuntimeLedgerRecord({
3785
+ phase: 'pre_generation_veto',
3786
+ body,
3787
+ turn,
3788
+ providerStyle,
3789
+ providerPlan,
3790
+ releaseDecision: 'hard_block',
3791
+ blockers: turn.preResult?.notes || ['pre_generation_fitrah_veto'],
3792
+ evidenceRefs: coachRecordRefs(coachRecords),
3793
+ }));
3794
+ ledgerRecords.push({ phase: 'pre_generation_veto', ...preBlockRecord });
3795
+ const extra = {
3796
+ blocked: true,
3797
+ phase: 'pre',
3798
+ pre: turn.preResult,
3799
+ mid: turn.midResult,
3800
+ runtimeLedger: { ledgerPath: MANAGED_RUNTIME_LEDGER_PATH, records: ledgerRecords },
3801
+ coachKernel: { records: coachRecords },
3802
+ };
3803
+ return providerStyle === 'anthropic'
3804
+ ? anthropicResponseEnvelope(refusal, { model: body.model || 'aria-runtime', finishReason: 'end_turn' }, extra, body?.ariaDebug === true)
3805
+ : openAiResponseEnvelope(body, refusal, { model: body.model || 'aria-runtime', finishReason: 'stop', usage: null }, extra);
3806
+ }
3807
+
3808
+ const preGenerationCoach = recordRuntimeCoachPhase({
3809
+ phase: 'pre_generation',
3810
+ body,
3811
+ turn,
3812
+ providerStyle,
3813
+ providerPlan,
3814
+ text: turn.userMessage,
3815
+ evidenceRefs: [
3816
+ `pre_receipt:${turn.preReceipt?.receiptId || 'missing'}`,
3817
+ `mid_receipt:${turn.midReceipt?.receiptId || 'missing'}`,
3818
+ ...coachRecordRefs(coachRecords),
3819
+ ],
3820
+ });
3821
+ coachRecords.push(preGenerationCoach);
3822
+ if (preGenerationCoach.decision === 'hard_block') {
3823
+ const refusal = formatCoachClientBlock(preGenerationCoach);
3824
+ const preBlockRecord = appendManagedRuntimeLedger(buildManagedRuntimeLedgerRecord({
3825
+ phase: 'pre_generation_veto',
3826
+ body,
3827
+ turn,
3828
+ providerStyle,
3829
+ providerPlan,
3830
+ releaseDecision: 'hard_block',
3831
+ blockers: preGenerationCoach.reasons,
3832
+ evidenceRefs: coachRecordRefs(coachRecords),
3833
+ }));
3834
+ ledgerRecords.push({ phase: 'pre_generation_veto', ...preBlockRecord });
3835
+ const extra = {
3836
+ blocked: true,
3837
+ phase: 'coach-pre-generation',
3838
+ pre: turn.preResult,
3839
+ mid: turn.midResult,
3840
+ runtimeLedger: { ledgerPath: MANAGED_RUNTIME_LEDGER_PATH, records: ledgerRecords },
3841
+ coachKernel: { records: coachRecords },
3842
+ };
3843
+ return providerStyle === 'anthropic'
3844
+ ? anthropicResponseEnvelope(refusal, { model: body.model || 'aria-runtime', finishReason: 'end_turn' }, extra, body?.ariaDebug === true)
3845
+ : openAiResponseEnvelope(body, refusal, { model: body.model || 'aria-runtime', finishReason: 'stop', usage: null }, extra);
3846
+ }
3847
+
3848
+ const preProviderRecord = appendManagedRuntimeLedger(buildManagedRuntimeLedgerRecord({
3849
+ phase: 'pre_provider_call',
3850
+ body,
3851
+ turn,
3852
+ providerStyle,
3853
+ providerPlan,
3854
+ releaseDecision: 'pending_provider_call',
3855
+ evidenceRefs: [
3856
+ `pre_receipt:${turn.preReceipt?.receiptId || 'missing'}`,
3857
+ `mid_receipt:${turn.midReceipt?.receiptId || 'missing'}`,
3858
+ `coach:${preGenerationCoach.coachEventId}`,
3859
+ ],
3860
+ }));
3861
+ ledgerRecords.push({ phase: 'pre_provider_call', ...preProviderRecord });
3862
+
3863
+ let providerMeta;
3864
+ try {
3865
+ providerMeta = await callProviderByStyle(providerStyle, body, turn.ariaSystemPrompt);
3866
+ } catch (error) {
3867
+ const failureRecord = appendManagedRuntimeLedger(buildManagedRuntimeLedgerRecord({
3868
+ phase: 'provider_call_failed',
3869
+ body,
3870
+ turn,
3871
+ providerStyle,
3872
+ providerPlan,
3873
+ releaseDecision: 'provider_call_failed',
3874
+ blockers: [error instanceof Error ? error.message : String(error)],
3875
+ evidenceRefs: [preProviderRecord.ledgerRecordId],
3876
+ }));
3877
+ ledgerRecords.push({ phase: 'provider_call_failed', ...failureRecord });
3878
+ throw error;
3879
+ }
3880
+ recordProviderUsage(body, turn, providerMeta);
3881
+ let hardCoachBlock = null;
3882
+ let evaluation = await evaluateProviderCandidate(req, body, client, apiKey, turn, providerStyle, providerMeta, providerMeta.text || '');
3883
+ const postGenerationCoach = recordRuntimeCoachPhase({
3884
+ phase: 'post_generation',
3885
+ body,
3886
+ turn,
3887
+ providerStyle,
3888
+ providerPlan: { ok: true, config: { ...providerPlan.config, provider: providerMeta.provider || providerPlan.config?.provider, model: providerMeta.model || providerPlan.config?.model }, error: providerPlan.error },
3889
+ evaluation,
3890
+ text: evaluation.candidateText,
3891
+ evidenceRefs: [preProviderRecord.ledgerRecordId, `coach:${preGenerationCoach.coachEventId}`],
3892
+ metadata: {
3893
+ provider_finish_reason: providerMeta.finishReason || null,
3894
+ requireCognitionBlock: evaluation.requireCognitionBlock,
3895
+ requireAppliedCognition: evaluation.requireCognitionBlock,
3896
+ },
3897
+ });
3898
+ coachRecords.push(postGenerationCoach);
3899
+ if (postGenerationCoach.decision === 'hard_block') {
3900
+ hardCoachBlock = postGenerationCoach;
3901
+ } else {
3902
+ evaluation = applyCoachRepairDecision(evaluation, postGenerationCoach);
3903
+ }
3904
+ const evaluatedRecord = appendManagedRuntimeLedger(buildManagedRuntimeLedgerRecord({
3905
+ phase: 'provider_candidate_evaluated',
3906
+ body,
3907
+ turn,
3908
+ providerStyle,
3909
+ providerPlan: { ok: true, config: { ...providerPlan.config, provider: providerMeta.provider || providerPlan.config?.provider, model: providerMeta.model || providerPlan.config?.model }, error: providerPlan.error },
3910
+ evaluation,
3911
+ releaseDecision: hardCoachBlock ? 'hard_block_pending' : evaluation.blocked ? 'repair_pending' : 'candidate_passed',
3912
+ blockers: hardCoachBlock ? hardCoachBlock.reasons : failureSummaryFromEvaluation(evaluation),
3913
+ evidenceRefs: [preProviderRecord.ledgerRecordId, `coach:${postGenerationCoach.coachEventId}`],
3914
+ }));
3915
+ ledgerRecords.push({ phase: 'provider_candidate_evaluated', ...evaluatedRecord });
3916
+
3917
+ const recoveryAttempts = [];
3918
+ const maxRecoveryAttempts = evaluation.blocked && !hardCoachBlock ? recoveryAttemptCount(body) : 0;
3919
+ for (let attempt = 1; !hardCoachBlock && evaluation.blocked && attempt <= maxRecoveryAttempts; attempt += 1) {
3920
+ const mode = attempt === 1 ? 'self_reauthor' : 'architect_recovery';
3921
+ try {
3922
+ const recoveryBody = buildRecoveryBody(body, turn, evaluation.candidateText, evaluation, attempt, mode, providerStyle);
3923
+ const repairPreCoach = recordRuntimeCoachPhase({
3924
+ phase: 'pre_generation',
3925
+ body: recoveryBody,
3926
+ turn,
3927
+ providerStyle,
3928
+ providerPlan: resolveProviderPlanForLedger(recoveryBody),
3929
+ evaluation,
3930
+ text: turn.userMessage,
3931
+ evidenceRefs: [evaluatedRecord.ledgerRecordId, ...coachRecordRefs(coachRecords)],
3932
+ metadata: { repair_attempt: attempt, repair_mode: mode },
3933
+ });
3934
+ coachRecords.push(repairPreCoach);
3935
+ if (repairPreCoach.decision === 'hard_block') {
3936
+ hardCoachBlock = repairPreCoach;
3937
+ break;
3938
+ }
3939
+ const repairPreRecord = appendManagedRuntimeLedger(buildManagedRuntimeLedgerRecord({
3940
+ phase: 'repair_before_provider_call',
3941
+ body: recoveryBody,
3942
+ turn,
3943
+ providerStyle,
3944
+ providerPlan: resolveProviderPlanForLedger(recoveryBody),
3945
+ evaluation,
3946
+ repairAttempts: recoveryAttempts,
3947
+ releaseDecision: 'repair_provider_call_pending',
3948
+ blockers: failureSummaryFromEvaluation(evaluation),
3949
+ evidenceRefs: [evaluatedRecord.ledgerRecordId, `coach:${repairPreCoach.coachEventId}`],
3950
+ extra: { repair_attempt: attempt, repair_mode: mode },
3951
+ }));
3952
+ ledgerRecords.push({ phase: 'repair_before_provider_call', ...repairPreRecord });
3953
+ const recoveryMeta = await callProviderByStyle(providerStyle, recoveryBody, turn.ariaSystemPrompt);
3954
+ recordProviderUsage(recoveryBody, turn, recoveryMeta);
3955
+ const recoveryEvaluation = await evaluateProviderCandidate(
3956
+ req,
3957
+ recoveryBody,
3958
+ client,
3959
+ apiKey,
3960
+ turn,
3961
+ providerStyle,
3962
+ recoveryMeta,
3963
+ recoveryMeta.text || '',
3964
+ );
3965
+ const repairPostCoach = recordRuntimeCoachPhase({
3966
+ phase: 'post_generation',
3967
+ body: recoveryBody,
3968
+ turn,
3969
+ providerStyle,
3970
+ providerPlan: { ok: true, config: { ...resolveProviderPlanForLedger(recoveryBody).config, provider: recoveryMeta.provider, model: recoveryMeta.model }, error: null },
3971
+ evaluation: recoveryEvaluation,
3972
+ text: recoveryEvaluation.candidateText,
3973
+ evidenceRefs: [repairPreRecord.ledgerRecordId, `coach:${repairPreCoach.coachEventId}`],
3974
+ metadata: { repair_attempt: attempt, repair_mode: mode, requireCognitionBlock: recoveryEvaluation.requireCognitionBlock, requireAppliedCognition: recoveryEvaluation.requireCognitionBlock },
3975
+ });
3976
+ coachRecords.push(repairPostCoach);
3977
+ const finalRecoveryEvaluation = repairPostCoach.decision === 'hard_block'
3978
+ ? {
3979
+ ...recoveryEvaluation,
3980
+ blocked: true,
3981
+ toolGateBlockers: uniqueStrings([
3982
+ ...(recoveryEvaluation.toolGateBlockers || []),
3983
+ ...repairPostCoach.reasons.map((reason) => `coach: ${reason}`),
3984
+ ]),
3985
+ finalText: formatCoachClientBlock(repairPostCoach),
3986
+ coachDecision: repairPostCoach,
3987
+ }
3988
+ : applyCoachRepairDecision(recoveryEvaluation, repairPostCoach);
3989
+ if (repairPostCoach.decision === 'hard_block') {
3990
+ hardCoachBlock = repairPostCoach;
3991
+ }
3992
+ recoveryAttempts.push(recoveryAttemptRecord(attempt, mode, finalRecoveryEvaluation));
3993
+ const repairEvaluationRecord = appendManagedRuntimeLedger(buildManagedRuntimeLedgerRecord({
3994
+ phase: 'repair_candidate_evaluated',
3995
+ body: recoveryBody,
3996
+ turn,
3997
+ providerStyle,
3998
+ providerPlan: { ok: true, config: { ...resolveProviderPlanForLedger(recoveryBody).config, provider: recoveryMeta.provider, model: recoveryMeta.model }, error: null },
3999
+ evaluation: finalRecoveryEvaluation,
4000
+ repairAttempts: recoveryAttempts,
4001
+ releaseDecision: hardCoachBlock ? 'hard_block_pending' : finalRecoveryEvaluation.blocked ? 'repair_failed_or_next_attempt_pending' : 'repair_passed',
4002
+ blockers: hardCoachBlock ? hardCoachBlock.reasons : failureSummaryFromEvaluation(finalRecoveryEvaluation),
4003
+ evidenceRefs: [repairPreRecord.ledgerRecordId, `coach:${repairPostCoach.coachEventId}`],
4004
+ extra: { repair_attempt: attempt, repair_mode: mode },
4005
+ }));
4006
+ ledgerRecords.push({ phase: 'repair_candidate_evaluated', ...repairEvaluationRecord });
4007
+ providerMeta = recoveryMeta;
4008
+ evaluation = finalRecoveryEvaluation;
4009
+ } catch (error) {
4010
+ recoveryAttempts.push(recoveryAttemptRecord(attempt, mode, null, error));
4011
+ const repairErrorRecord = appendManagedRuntimeLedger(buildManagedRuntimeLedgerRecord({
4012
+ phase: 'repair_provider_call_failed',
4013
+ body,
4014
+ turn,
4015
+ providerStyle,
4016
+ providerPlan,
4017
+ repairAttempts: recoveryAttempts,
4018
+ releaseDecision: 'repair_error',
4019
+ blockers: [error instanceof Error ? error.message : String(error)],
4020
+ extra: { repair_attempt: attempt, repair_mode: mode },
4021
+ }));
4022
+ ledgerRecords.push({ phase: 'repair_provider_call_failed', ...repairErrorRecord });
4023
+ }
4024
+ }
4025
+
4026
+ let blocked = evaluation.blocked || Boolean(hardCoachBlock);
4027
+ let finalText = hardCoachBlock
4028
+ ? formatCoachClientBlock(hardCoachBlock)
4029
+ : blocked && recoveryAttempts.length > 0
4030
+ ? buildProviderRecoveryBlockMessage(evaluation, recoveryAttempts)
4031
+ : evaluation.finalText;
4032
+ const preOutputCoach = recordRuntimeCoachPhase({
4033
+ phase: 'pre_output',
4034
+ body,
4035
+ turn,
4036
+ providerStyle,
4037
+ providerPlan: { ok: true, config: { ...providerPlan.config, provider: providerMeta.provider || providerPlan.config?.provider, model: providerMeta.model || providerPlan.config?.model }, error: providerPlan.error },
4038
+ evaluation,
4039
+ text: finalText,
4040
+ evidenceRefs: [...ledgerRecords.map((record) => record.ledgerRecordId).filter(Boolean), ...coachRecordRefs(coachRecords)],
4041
+ metadata: { blocked, requireCognitionBlock: evaluation.requireCognitionBlock, requireAppliedCognition: evaluation.requireCognitionBlock },
4042
+ });
4043
+ coachRecords.push(preOutputCoach);
4044
+ const releaseCoach = recordRuntimeCoachPhase({
4045
+ phase: 'claim_or_release',
4046
+ body,
4047
+ turn,
4048
+ providerStyle,
4049
+ providerPlan: { ok: true, config: { ...providerPlan.config, provider: providerMeta.provider || providerPlan.config?.provider, model: providerMeta.model || providerPlan.config?.model }, error: providerPlan.error },
4050
+ evaluation,
4051
+ text: finalText,
4052
+ evidenceRefs: [
4053
+ ...ledgerRecords.map((record) => record.ledgerRecordId).filter(Boolean),
4054
+ `coach:${preOutputCoach.coachEventId}`,
4055
+ ...coachRecordRefs(coachRecords),
4056
+ ],
4057
+ metadata: { blocked, release_decision: blocked ? 'blocked_after_recovery' : 'released_to_client', requireCognitionBlock: evaluation.requireCognitionBlock, requireAppliedCognition: evaluation.requireCognitionBlock },
4058
+ });
4059
+ coachRecords.push(releaseCoach);
4060
+ if (!blocked && releaseCoach.decision !== 'allow' && releaseCoach.decision !== 'warn_operator_only') {
4061
+ blocked = true;
4062
+ hardCoachBlock = releaseCoach;
4063
+ finalText = formatCoachClientBlock(releaseCoach);
4064
+ }
4065
+
4066
+ await persistTurnArtifacts(req, body, client, apiKey, {
4067
+ ...turn,
4068
+ postResult: evaluation.postResult,
4069
+ postBundle: evaluation.postBundle,
4070
+ postReceipt: evaluation.postBundle.receipt,
4071
+ postContract: evaluation.postBundle.contract,
4072
+ validation: evaluation.validation,
4073
+ layer3: evaluation.layer3,
4074
+ recoveryAttempts,
2965
4075
  finalText,
2966
4076
  durationMs: Date.now() - startedAt,
2967
4077
  success: !blocked,
2968
- error: blocked ? 'runtime blocked output' : null,
4078
+ error: blocked ? 'runtime blocked output after recovery contract' : null,
2969
4079
  providerMeta,
2970
4080
  });
4081
+ const finalLedgerRecord = appendManagedRuntimeLedger(buildManagedRuntimeLedgerRecord({
4082
+ phase: 'final_release_decision',
4083
+ body,
4084
+ turn,
4085
+ providerStyle,
4086
+ providerPlan: { ok: true, config: { ...providerPlan.config, provider: providerMeta.provider || providerPlan.config?.provider, model: providerMeta.model || providerPlan.config?.model }, error: providerPlan.error },
4087
+ evaluation,
4088
+ repairAttempts: recoveryAttempts,
4089
+ releaseDecision: blocked ? 'blocked_after_recovery' : 'released_to_client',
4090
+ blockers: blocked ? uniqueStrings([...failureSummaryFromEvaluation(evaluation), ...(hardCoachBlock?.reasons || [])]) : [],
4091
+ evidenceRefs: [...ledgerRecords.map((record) => record.ledgerRecordId).filter(Boolean), ...coachRecordRefs(coachRecords)],
4092
+ }));
4093
+ ledgerRecords.push({ phase: 'final_release_decision', ...finalLedgerRecord });
2971
4094
 
2972
4095
  const extra = {
2973
4096
  blocked,
2974
4097
  pre: turn.preResult,
2975
4098
  mid: turn.midResult,
2976
- post: postResult,
4099
+ post: evaluation.postResult,
2977
4100
  receipts: {
2978
4101
  pre: turn.preReceipt,
2979
4102
  mid: turn.midReceipt,
2980
- post: postBundle.receipt,
4103
+ post: evaluation.postBundle.receipt,
2981
4104
  },
2982
4105
  turnClass: turn.turnClass,
2983
4106
  operatorPlan: turn.operatorPlan,
2984
- validation,
2985
- layer3,
2986
- cognitionContract,
4107
+ validation: evaluation.validation,
4108
+ layer3: evaluation.layer3,
4109
+ cognitionContract: evaluation.cognitionContract,
4110
+ recoveryAttempts,
2987
4111
  doctrine: {
2988
- blocked: doctrineBlockers.length > 0,
2989
- hits: doctrineHits,
4112
+ blocked: evaluation.doctrineBlockers.length > 0,
4113
+ hits: evaluation.doctrineHits,
2990
4114
  },
2991
4115
  toolGate: {
2992
- blocked: toolGateBlockers.length > 0,
2993
- blockers: toolGateBlockers,
2994
- intents: toolIntents,
4116
+ blocked: evaluation.toolGateBlockers.length > 0,
4117
+ blockers: evaluation.toolGateBlockers,
4118
+ intents: evaluation.toolIntents,
4119
+ },
4120
+ runtimeLedger: {
4121
+ ledgerPath: MANAGED_RUNTIME_LEDGER_PATH,
4122
+ records: ledgerRecords,
4123
+ },
4124
+ coachKernel: {
4125
+ records: coachRecords,
2995
4126
  },
2996
4127
  };
2997
4128
  return providerStyle === 'anthropic'
@@ -3282,7 +4413,11 @@ function runtimeManifest() {
3282
4413
  endpoints: [
3283
4414
  'GET /health',
3284
4415
  'GET /mount',
4416
+ 'GET /coach/state',
3285
4417
  'POST /heartbeat',
4418
+ 'POST /coach/event',
4419
+ 'POST /coach/phase',
4420
+ 'POST /coach/state',
3286
4421
  'POST /mizan/plan',
3287
4422
  'POST /mizan/pre',
3288
4423
  'POST /mizan/mid',
@@ -3308,6 +4443,11 @@ function runtimeManifest() {
3308
4443
  'POST /garden/turn',
3309
4444
  'POST /garden/search',
3310
4445
  'POST /record-discovery',
4446
+ 'POST /task-project-ledger/event',
4447
+ 'POST /task-project-ledger/status',
4448
+ 'POST /task-project-ledger/claim',
4449
+ 'POST /openclaw/task-ledger/snapshot',
4450
+ 'POST /openclaw/task-flow',
3311
4451
  'POST /verify-claim',
3312
4452
  'POST /telemetry/turn',
3313
4453
  'POST /telemetry/state',
@@ -3330,7 +4470,7 @@ function runtimeManifest() {
3330
4470
  ARIA_FORGE_SERVICE_URL: DEFAULT_FORGE_SERVICE_URL,
3331
4471
  ARIA_QDRANT_URL: DEFAULT_QDRANT_URL,
3332
4472
  OPENAI_BASE_URL: `${DEFAULT_RUNTIME_URL}/v1`,
3333
- ANTHROPIC_BASE_URL: `${DEFAULT_RUNTIME_URL}/v1`,
4473
+ ANTHROPIC_BASE_URL: DEFAULT_RUNTIME_URL,
3334
4474
  },
3335
4475
  commands: {
3336
4476
  start: 'aria-runtime',
@@ -3355,6 +4495,423 @@ function runtimeManifest() {
3355
4495
  };
3356
4496
  }
3357
4497
 
4498
+ let taskProjectLedgerModulePromise = null;
4499
+ let openClawRuntimeModulePromise = null;
4500
+
4501
+ async function loadTaskProjectLedgerModule() {
4502
+ if (taskProjectLedgerModulePromise) return taskProjectLedgerModulePromise;
4503
+ const candidates = [
4504
+ join(__dirname, 'task-project-ledger.mjs'),
4505
+ join(__dirname, '..', 'opencode-plugins', 'harness-context', 'task-project-ledger.mjs'),
4506
+ join(__dirname, '..', 'assets', 'opencode-plugins', 'harness-context', 'task-project-ledger.mjs'),
4507
+ ];
4508
+ taskProjectLedgerModulePromise = (async () => {
4509
+ for (const candidate of candidates) {
4510
+ if (!existsSync(candidate)) continue;
4511
+ const mod = await import(`file://${candidate}`);
4512
+ if (typeof mod.updateTaskProjectLedger === 'function') return mod;
4513
+ }
4514
+ throw new Error(`task/project ledger helper missing from candidates: ${candidates.join(', ')}`);
4515
+ })();
4516
+ return taskProjectLedgerModulePromise;
4517
+ }
4518
+
4519
+ function parseMaybeJson(text) {
4520
+ const raw = String(text || '').trim();
4521
+ if (!raw) return null;
4522
+ try {
4523
+ return JSON.parse(raw);
4524
+ } catch {
4525
+ return raw.slice(0, 4000);
4526
+ }
4527
+ }
4528
+
4529
+ function firstTrimmedString(...values) {
4530
+ for (const value of values) {
4531
+ if (typeof value === 'string' && value.trim()) return value.trim();
4532
+ if (typeof value === 'number' && Number.isFinite(value)) return String(value);
4533
+ }
4534
+ return '';
4535
+ }
4536
+
4537
+ function nullableTrimmedString(value) {
4538
+ if (value === null) return null;
4539
+ return firstTrimmedString(value) || undefined;
4540
+ }
4541
+
4542
+ function requireTrimmedString(value, label) {
4543
+ const normalized = firstTrimmedString(value);
4544
+ if (!normalized) throw new Error(`${label} is required`);
4545
+ return normalized;
4546
+ }
4547
+
4548
+ function coerceExpectedRevision(value) {
4549
+ const revision = Number(value);
4550
+ if (!Number.isInteger(revision) || revision < 0) {
4551
+ throw new Error('expectedRevision must be a non-negative integer');
4552
+ }
4553
+ return revision;
4554
+ }
4555
+
4556
+ function normalizeOpenClawStatus(value, allowed, fallback) {
4557
+ const normalized = firstTrimmedString(value);
4558
+ if (!normalized) return fallback;
4559
+ if (!allowed.includes(normalized)) throw new Error(`Unsupported OpenClaw flow status: ${normalized}`);
4560
+ return normalized;
4561
+ }
4562
+
4563
+ function openClawRuntimeModuleCandidates() {
4564
+ const candidates = [...OPENCLAW_RUNTIME_MODULE_CANDIDATES];
4565
+ try {
4566
+ const pkgPath = require.resolve('openclaw/package.json');
4567
+ candidates.push(join(dirname(pkgPath), 'dist', 'plugins', 'runtime', 'index.js'));
4568
+ } catch {}
4569
+ return [...new Set(candidates.filter(Boolean))];
4570
+ }
4571
+
4572
+ async function loadOpenClawRuntimeModule() {
4573
+ if (openClawRuntimeModulePromise) return openClawRuntimeModulePromise;
4574
+ openClawRuntimeModulePromise = (async () => {
4575
+ const candidates = openClawRuntimeModuleCandidates();
4576
+ for (const candidate of candidates) {
4577
+ if (!existsSync(candidate)) continue;
4578
+ const mod = await import(pathToFileURL(candidate).href);
4579
+ if (typeof mod.createPluginRuntime === 'function') {
4580
+ return { modulePath: candidate, createPluginRuntime: mod.createPluginRuntime };
4581
+ }
4582
+ }
4583
+ throw new Error(
4584
+ `OpenClaw plugin runtime module missing from candidates: ${candidates.join(', ')}. Set OPENCLAW_RUNTIME_MODULE to the installed OpenClaw dist/plugins/runtime/index.js path.`,
4585
+ );
4586
+ })();
4587
+ return openClawRuntimeModulePromise;
4588
+ }
4589
+
4590
+ function resolveOpenClawOwnerKey(request = {}, body = {}, context = {}) {
4591
+ return firstTrimmedString(
4592
+ request.ownerKey,
4593
+ request.sessionKey,
4594
+ request.requesterSessionKey,
4595
+ body.ownerKey,
4596
+ body.sessionKey,
4597
+ body.sessionId,
4598
+ context.ownerKey,
4599
+ );
4600
+ }
4601
+
4602
+ function ownerKeyForTaskProjectIdentity(identity = {}) {
4603
+ if (!identity.projectHash || !identity.taskHash) return '';
4604
+ return `aria-ledger:${identity.projectHash}:${identity.taskHash}`;
4605
+ }
4606
+
4607
+ async function executeOpenClawTaskFlowAction(request = {}, context = {}) {
4608
+ const { modulePath, createPluginRuntime } = await loadOpenClawRuntimeModule();
4609
+ const ownerKey = requireTrimmedString(
4610
+ resolveOpenClawOwnerKey(request, {}, context),
4611
+ 'OpenClaw ownerKey or sessionKey',
4612
+ );
4613
+ const runtime = createPluginRuntime();
4614
+ const bindSession = runtime?.tasks?.flow?.bindSession || runtime?.taskFlow?.bindSession;
4615
+ if (typeof bindSession !== 'function') {
4616
+ throw new Error('OpenClaw plugin runtime does not expose tasks.flow.bindSession');
4617
+ }
4618
+ const flowRuntime = bindSession({
4619
+ sessionKey: ownerKey,
4620
+ requesterOrigin: request.requesterOrigin,
4621
+ });
4622
+ const action = requireTrimmedString(request.action || context.action || 'create_flow', 'OpenClaw action');
4623
+ const base = {
4624
+ ok: true,
4625
+ mode: 'owned_openclaw_task_flow',
4626
+ api: 'openclaw.plugin-runtime.tasks.flow',
4627
+ modulePath,
4628
+ action,
4629
+ ownerKey,
4630
+ };
4631
+
4632
+ if (action === 'create_flow') {
4633
+ const flow = flowRuntime.createManaged({
4634
+ controllerId: firstTrimmedString(request.controllerId, context.controllerId) || 'aria/task-project-ledger',
4635
+ goal: requireTrimmedString(request.goal || context.goal, 'OpenClaw flow goal'),
4636
+ status: normalizeOpenClawStatus(
4637
+ request.status,
4638
+ ['queued', 'running', 'waiting', 'blocked'],
4639
+ request.status === undefined ? 'running' : undefined,
4640
+ ),
4641
+ notifyPolicy: normalizeOpenClawStatus(
4642
+ request.notifyPolicy,
4643
+ ['done_only', 'state_changes', 'silent'],
4644
+ undefined,
4645
+ ),
4646
+ currentStep: nullableTrimmedString(request.currentStep),
4647
+ stateJson: request.stateJson,
4648
+ waitJson: request.waitJson,
4649
+ });
4650
+ return { ...base, created: true, flow };
4651
+ }
4652
+
4653
+ if (action === 'get_flow') {
4654
+ const flow = flowRuntime.get(requireTrimmedString(request.flowId, 'OpenClaw flowId'));
4655
+ return { ...base, found: Boolean(flow), flow: flow || null };
4656
+ }
4657
+
4658
+ if (action === 'list_flows') {
4659
+ return { ...base, flows: flowRuntime.list() };
4660
+ }
4661
+
4662
+ if (action === 'find_latest_flow') {
4663
+ const flow = flowRuntime.findLatest();
4664
+ return { ...base, found: Boolean(flow), flow: flow || null };
4665
+ }
4666
+
4667
+ if (action === 'resolve_flow') {
4668
+ const flow = flowRuntime.resolve(requireTrimmedString(request.token, 'OpenClaw flow lookup token'));
4669
+ return { ...base, found: Boolean(flow), flow: flow || null };
4670
+ }
4671
+
4672
+ if (action === 'get_task_summary') {
4673
+ const taskSummary = flowRuntime.getTaskSummary(requireTrimmedString(request.flowId, 'OpenClaw flowId'));
4674
+ return { ...base, found: Boolean(taskSummary), taskSummary: taskSummary || null };
4675
+ }
4676
+
4677
+ if (action === 'set_waiting') {
4678
+ const result = flowRuntime.setWaiting({
4679
+ flowId: requireTrimmedString(request.flowId, 'OpenClaw flowId'),
4680
+ expectedRevision: coerceExpectedRevision(request.expectedRevision),
4681
+ currentStep: nullableTrimmedString(request.currentStep),
4682
+ stateJson: request.stateJson,
4683
+ waitJson: request.waitJson,
4684
+ blockedTaskId: nullableTrimmedString(request.blockedTaskId),
4685
+ blockedSummary: nullableTrimmedString(request.blockedSummary),
4686
+ });
4687
+ return { ...base, result, flow: result.applied ? result.flow : result.current || null };
4688
+ }
4689
+
4690
+ if (action === 'resume_flow' || action === 'advance_flow') {
4691
+ const result = flowRuntime.resume({
4692
+ flowId: requireTrimmedString(request.flowId, 'OpenClaw flowId'),
4693
+ expectedRevision: coerceExpectedRevision(request.expectedRevision),
4694
+ status: normalizeOpenClawStatus(request.status, ['queued', 'running'], 'running'),
4695
+ currentStep: nullableTrimmedString(request.currentStep),
4696
+ stateJson: request.stateJson,
4697
+ });
4698
+ return { ...base, result, flow: result.applied ? result.flow : result.current || null };
4699
+ }
4700
+
4701
+ if (action === 'finish_flow') {
4702
+ const result = flowRuntime.finish({
4703
+ flowId: requireTrimmedString(request.flowId, 'OpenClaw flowId'),
4704
+ expectedRevision: coerceExpectedRevision(request.expectedRevision),
4705
+ stateJson: request.stateJson,
4706
+ });
4707
+ return { ...base, result, flow: result.applied ? result.flow : result.current || null };
4708
+ }
4709
+
4710
+ if (action === 'fail_flow') {
4711
+ const result = flowRuntime.fail({
4712
+ flowId: requireTrimmedString(request.flowId, 'OpenClaw flowId'),
4713
+ expectedRevision: coerceExpectedRevision(request.expectedRevision),
4714
+ stateJson: request.stateJson,
4715
+ blockedTaskId: nullableTrimmedString(request.blockedTaskId),
4716
+ blockedSummary: nullableTrimmedString(request.blockedSummary),
4717
+ });
4718
+ return { ...base, result, flow: result.applied ? result.flow : result.current || null };
4719
+ }
4720
+
4721
+ if (action === 'request_cancel') {
4722
+ const result = flowRuntime.requestCancel({
4723
+ flowId: requireTrimmedString(request.flowId, 'OpenClaw flowId'),
4724
+ expectedRevision: coerceExpectedRevision(request.expectedRevision),
4725
+ });
4726
+ return { ...base, result, flow: result.applied ? result.flow : result.current || null };
4727
+ }
4728
+
4729
+ if (action === 'cancel_flow') {
4730
+ const result = await flowRuntime.cancel({
4731
+ flowId: requireTrimmedString(request.flowId, 'OpenClaw flowId'),
4732
+ cfg: request.cfg || {},
4733
+ });
4734
+ return { ...base, result, flow: result.flow || null, tasks: result.tasks || [] };
4735
+ }
4736
+
4737
+ if (action === 'run_task') {
4738
+ const taskRuntime = firstTrimmedString(request.runtime) || 'cli';
4739
+ if (!['subagent', 'acp', 'cli', 'cron'].includes(taskRuntime)) {
4740
+ throw new Error(`Unsupported OpenClaw task runtime: ${taskRuntime}`);
4741
+ }
4742
+ const result = flowRuntime.runTask({
4743
+ flowId: requireTrimmedString(request.flowId, 'OpenClaw flowId'),
4744
+ runtime: taskRuntime,
4745
+ sourceId: nullableTrimmedString(request.sourceId),
4746
+ childSessionKey: nullableTrimmedString(request.childSessionKey),
4747
+ parentTaskId: nullableTrimmedString(request.parentTaskId),
4748
+ agentId: nullableTrimmedString(request.agentId),
4749
+ runId: nullableTrimmedString(request.runId),
4750
+ label: nullableTrimmedString(request.label),
4751
+ task: requireTrimmedString(request.task, 'OpenClaw task'),
4752
+ preferMetadata: request.preferMetadata === true,
4753
+ notifyPolicy: normalizeOpenClawStatus(
4754
+ request.notifyPolicy,
4755
+ ['done_only', 'state_changes', 'silent'],
4756
+ undefined,
4757
+ ),
4758
+ status: normalizeOpenClawStatus(request.status, ['queued', 'running'], undefined),
4759
+ startedAt: Number.isFinite(Number(request.startedAt)) ? Number(request.startedAt) : undefined,
4760
+ lastEventAt: Number.isFinite(Number(request.lastEventAt)) ? Number(request.lastEventAt) : undefined,
4761
+ progressSummary: nullableTrimmedString(request.progressSummary),
4762
+ });
4763
+ return { ...base, result, flow: result.flow || null, task: result.task || null };
4764
+ }
4765
+
4766
+ throw new Error(`Unsupported OpenClaw task-flow action: ${action}`);
4767
+ }
4768
+
4769
+ function resolveOpenClawBin() {
4770
+ for (const candidate of OPENCLAW_BIN_CANDIDATES) {
4771
+ if (candidate.includes('/') && existsSync(candidate)) return candidate;
4772
+ if (!candidate.includes('/')) return candidate;
4773
+ }
4774
+ return 'openclaw';
4775
+ }
4776
+
4777
+ function runOpenClawJson(args, timeoutMs = 7000) {
4778
+ const bin = resolveOpenClawBin();
4779
+ const child = spawnSync(bin, args, {
4780
+ encoding: 'utf8',
4781
+ timeout: timeoutMs,
4782
+ maxBuffer: 1024 * 1024,
4783
+ });
4784
+ const stdout = String(child.stdout || '').trim();
4785
+ const stderr = String(child.stderr || '').trim();
4786
+ const jsonSource = stdout || (child.status === 0 ? stderr : '');
4787
+ return {
4788
+ ok: child.status === 0,
4789
+ status: child.status,
4790
+ signal: child.signal || null,
4791
+ bin,
4792
+ args,
4793
+ json: child.status === 0 ? parseMaybeJson(jsonSource) : null,
4794
+ jsonStream: stdout ? 'stdout' : jsonSource ? 'stderr' : null,
4795
+ stderr: (child.error ? `${child.error.message}\n` : '') + (stdout ? stderr.slice(0, 1000) : ''),
4796
+ };
4797
+ }
4798
+
4799
+ function collectOpenClawTaskLedgerSnapshot(body = {}) {
4800
+ const includeTasks = body.includeTasks !== false;
4801
+ const includeAudit = body.includeAudit !== false;
4802
+ const includeFlows = body.includeFlows !== false;
4803
+ const commands = {};
4804
+ if (includeTasks) commands.tasksList = runOpenClawJson(['tasks', 'list', '--json']);
4805
+ if (includeAudit) commands.tasksAudit = runOpenClawJson(['tasks', 'audit', '--json']);
4806
+ if (includeFlows) commands.flowList = runOpenClawJson(['tasks', 'flow', 'list', '--json']);
4807
+ const commandValues = Object.values(commands);
4808
+ return {
4809
+ mode: 'openclaw_cli_task_flow_snapshot',
4810
+ observedAt: new Date().toISOString(),
4811
+ available: commandValues.length > 0 && commandValues.some((result) => result.ok),
4812
+ bin: resolveOpenClawBin(),
4813
+ commands,
4814
+ caveat: 'This route is a read-only CLI snapshot; managed flow mutations use /openclaw/task-flow through the OpenClaw plugin runtime Task Flow API.',
4815
+ };
4816
+ }
4817
+
4818
+ function summarizeTaskProjectLedger(ledger, paths = {}) {
4819
+ return {
4820
+ ledgerId: ledger?.ledgerId || null,
4821
+ status: ledger?.status || null,
4822
+ projectRef: ledger?.identity?.projectHint || null,
4823
+ taskRef: ledger?.identity?.taskHint || null,
4824
+ readinessClaimAllowed: ledger?.controls?.readinessClaimAllowed === true,
4825
+ missingReadinessGates: ledger?.controls?.missingReadinessGates || [],
4826
+ openBlockerCount: Array.isArray(ledger?.openBlockers) ? ledger.openBlockers.length : 0,
4827
+ eventCount: Array.isArray(ledger?.phaseEvents) ? ledger.phaseEvents.length : 0,
4828
+ evidenceCount: Array.isArray(ledger?.evidence) ? ledger.evidence.length : 0,
4829
+ ledgerPath: paths.ledgerPath || null,
4830
+ };
4831
+ }
4832
+
4833
+ async function handleTaskProjectLedgerRoute(url, body = {}) {
4834
+ const ledgerMod = await loadTaskProjectLedgerModule();
4835
+ const platform = typeof body.platform === 'string' && body.platform.trim() ? body.platform.trim() : 'owner_runtime';
4836
+ const phase = typeof body.phase === 'string' && body.phase.trim() ? body.phase.trim() : 'post_turn';
4837
+ const source = typeof body.source === 'string' && body.source.trim() ? body.source.trim() : 'owner-runtime-ledger-route';
4838
+ const event = body.event && typeof body.event === 'object' ? body.event : body;
4839
+
4840
+ if (url.pathname === '/openclaw/task-flow') {
4841
+ return executeOpenClawTaskFlowAction(body);
4842
+ }
4843
+
4844
+ if (url.pathname === '/openclaw/task-ledger/snapshot') {
4845
+ return { ok: true, openclaw: collectOpenClawTaskLedgerSnapshot(body) };
4846
+ }
4847
+
4848
+ if (url.pathname === '/task-project-ledger/status') {
4849
+ const identity = ledgerMod.resolveTaskProjectIdentity(event, process.env);
4850
+ const paths = ledgerMod.taskProjectLedgerPaths(identity, process.env);
4851
+ const ledger = existsSync(paths.ledgerPath) ? JSON.parse(readFileSync(paths.ledgerPath, 'utf8')) : null;
4852
+ return {
4853
+ ok: true,
4854
+ exists: Boolean(ledger),
4855
+ ledger: ledger ? summarizeTaskProjectLedger(ledger, paths) : null,
4856
+ paths,
4857
+ };
4858
+ }
4859
+
4860
+ let evidence = body.evidence && typeof body.evidence === 'object' ? { ...body.evidence } : {};
4861
+ if (body.openclawBridge === true || body.collectOpenClaw === true) {
4862
+ evidence.openclaw = collectOpenClawTaskLedgerSnapshot(body.openclaw || {});
4863
+ }
4864
+ if (body.openclawFlow && typeof body.openclawFlow === 'object') {
4865
+ const identity = ledgerMod.resolveTaskProjectIdentity(event, process.env);
4866
+ const ownerKey = resolveOpenClawOwnerKey(body.openclawFlow, body, {
4867
+ ownerKey: ownerKeyForTaskProjectIdentity(identity),
4868
+ });
4869
+ evidence.openclawFlow = await executeOpenClawTaskFlowAction(body.openclawFlow, {
4870
+ ownerKey,
4871
+ controllerId: 'aria/task-project-ledger',
4872
+ goal: firstTrimmedString(body.openclawFlow.goal, event.goal, event.prompt, event.message, event.text, 'Aria task/project ledger flow'),
4873
+ });
4874
+ }
4875
+
4876
+ const result = ledgerMod.updateTaskProjectLedger({
4877
+ platform,
4878
+ phase,
4879
+ source,
4880
+ event,
4881
+ evidence,
4882
+ env: process.env,
4883
+ });
4884
+
4885
+ if (url.pathname === '/task-project-ledger/claim') {
4886
+ const text = typeof body.text === 'string' ? body.text : '';
4887
+ const evaluation = ledgerMod.evaluateTaskProjectClaim({ text, ledger: result.ledger });
4888
+ if (!evaluation.ok) {
4889
+ ledgerMod.recordBlockedTaskProjectClaim({
4890
+ ledger: result.ledger,
4891
+ paths: result.paths,
4892
+ text,
4893
+ evaluation,
4894
+ });
4895
+ }
4896
+ return {
4897
+ ok: true,
4898
+ permitted: evaluation.ok,
4899
+ claim: evaluation.claim,
4900
+ missingReadinessGates: evaluation.missingReadinessGates,
4901
+ openBlockerCount: evaluation.openBlockers.length,
4902
+ ledger: summarizeTaskProjectLedger(result.ledger, result.paths),
4903
+ openclawFlow: evidence.openclawFlow || null,
4904
+ };
4905
+ }
4906
+
4907
+ return {
4908
+ ok: true,
4909
+ ledger: summarizeTaskProjectLedger(result.ledger, result.paths),
4910
+ openclaw: evidence.openclaw || null,
4911
+ openclawFlow: evidence.openclawFlow || null,
4912
+ };
4913
+ }
4914
+
3358
4915
  async function runLayer3(req, body, client) {
3359
4916
  if (typeof body.text !== 'string' || !body.text.trim()) {
3360
4917
  throw new Error('full-chain requires a non-empty text field');
@@ -3377,7 +4934,7 @@ async function runLayer3(req, body, client) {
3377
4934
  const doctrineFailures = doctrineHits.map((hit) => ({
3378
4935
  severity: String(hit.severity || 'block').toLowerCase() === 'block' ? 'block' : 'warn',
3379
4936
  kind: 'drift_trigger',
3380
- detail: `${hit.trigger} (${hit.memory || 'doctrine_trigger_map.json'}): ${hit.message || hit.teaching || 'Doctrine trigger matched.'}`,
4937
+ detail: `${hit.trigger || hit.triggerId || 'doctrine_trigger'} (${hit.memory || 'doctrine_trigger_map.json'}): ${hit.message || hit.teaching || 'Doctrine trigger matched.'}`,
3381
4938
  }));
3382
4939
  const allFailures = [...result.failures, ...doctrineFailures];
3383
4940
  const hardFailures = allFailures.filter((failure) => failure.severity === 'block');
@@ -3619,6 +5176,14 @@ async function handleRoute(req, res) {
3619
5176
  });
3620
5177
  }
3621
5178
 
5179
+ if (req.method === 'GET' && url.pathname === '/coach/state') {
5180
+ return json(res, 200, {
5181
+ ok: true,
5182
+ phases: COACH_PHASES,
5183
+ state: readCoachState(),
5184
+ });
5185
+ }
5186
+
3622
5187
  if (req.method === 'GET' && url.pathname === '/mount') {
3623
5188
  return json(res, 200, runtimeManifest().mount);
3624
5189
  }
@@ -3634,11 +5199,21 @@ async function handleRoute(req, res) {
3634
5199
  return json(res, 400, { ok: false, error: `Invalid JSON body: ${error.message}` });
3635
5200
  }
3636
5201
 
5202
+ const providerPath = normalizeProviderCompatibilityPath(url.pathname);
5203
+ const providerCompatibilityRoute = isProviderCompatibilityPath(providerPath);
5204
+ const ariaAuthOptions = { allowBearerAuth: true };
5205
+ const taskProjectLedgerRoute =
5206
+ url.pathname === '/task-project-ledger/event' ||
5207
+ url.pathname === '/task-project-ledger/status' ||
5208
+ url.pathname === '/task-project-ledger/claim' ||
5209
+ url.pathname === '/openclaw/task-ledger/snapshot' ||
5210
+ url.pathname === '/openclaw/task-flow';
5211
+
3637
5212
  let client;
3638
5213
  try {
3639
5214
  // HQ routes use their own auth middleware — skip the global API key gate
3640
- if (!url.pathname.startsWith('/hq/')) {
3641
- client = createClient(req, body);
5215
+ if (!url.pathname.startsWith('/hq/') && !taskProjectLedgerRoute) {
5216
+ client = createClient(req, body, ariaAuthOptions);
3642
5217
  }
3643
5218
  } catch (error) {
3644
5219
  return json(res, 401, { ok: false, error: error.message });
@@ -3650,7 +5225,34 @@ async function handleRoute(req, res) {
3650
5225
  return json(res, 200, { ok: true, lease });
3651
5226
  }
3652
5227
 
3653
- await ensureLease(req, body, client);
5228
+ if (taskProjectLedgerRoute) {
5229
+ const ledgerResult = await handleTaskProjectLedgerRoute(url, body);
5230
+ return json(res, ledgerResult.permitted === false ? 409 : 200, ledgerResult);
5231
+ }
5232
+
5233
+ await ensureLease(req, body, client, ariaAuthOptions);
5234
+
5235
+ if (url.pathname === '/coach/event' || url.pathname === '/coach/phase') {
5236
+ const result = recordCoachPhase(body);
5237
+ return json(res, 200, {
5238
+ ok: true,
5239
+ permitted: result.permitted,
5240
+ decision: result.decision,
5241
+ ledgerPath: result.ledgerPath,
5242
+ statePath: result.statePath,
5243
+ record: result.record,
5244
+ state: result.state,
5245
+ clientMessage: result.clientMessage,
5246
+ });
5247
+ }
5248
+
5249
+ if (url.pathname === '/coach/state') {
5250
+ return json(res, 200, {
5251
+ ok: true,
5252
+ phases: COACH_PHASES,
5253
+ state: readCoachState({ includeState: body.includeState === true }),
5254
+ });
5255
+ }
3654
5256
 
3655
5257
  if (url.pathname === '/mizan/plan') {
3656
5258
  const turnClass = classifyTurn(body.context || body);
@@ -3716,7 +5318,8 @@ async function handleRoute(req, res) {
3716
5318
  const apiKey = resolveApiKey(req, body);
3717
5319
  const packetRequest = buildMizanPacketRequest(body);
3718
5320
  const packet = body.packet || await loadRuntimePacket(req, body, client, packetRequest, packetRequest.message || body.text || '');
3719
- const bundle = evaluateMizanPost(body.text || '', body.evidence || {}, packet, body.context || body, {
5321
+ const text = body.text || '';
5322
+ const bundle = evaluateMizanPost(text, normalizePostEvidence(text, body.evidence || {}), packet, body.context || body, {
3720
5323
  sessionId: body.sessionId || body.context?.sessionId || deriveSessionId(req, body, 'mizan-post'),
3721
5324
  runtimeId: ensureRuntimeMeta().runtimeId,
3722
5325
  parentReceiptId: body.parentReceiptId || body.receiptId || null,
@@ -3733,19 +5336,19 @@ async function handleRoute(req, res) {
3733
5336
  });
3734
5337
  }
3735
5338
 
3736
- if (url.pathname === '/v1/chat/completions') {
3737
- const response = await handleProviderProxy(req, body, client, 'openai');
5339
+ if (providerPath === '/v1/chat/completions') {
5340
+ const response = await handleProviderProxy(req, body, client, 'openai', ariaAuthOptions);
3738
5341
  return json(res, 200, response);
3739
5342
  }
3740
5343
 
3741
- if (url.pathname === '/v1/responses' || url.pathname === '/responses') {
5344
+ if (providerPath === '/v1/responses' || providerPath === '/responses') {
3742
5345
  const responseBody = responsesRequestToOpenAIBody(body);
3743
- const completion = await handleProviderProxy(req, responseBody, client, 'openai');
5346
+ const completion = await handleProviderProxy(req, responseBody, client, 'openai', ariaAuthOptions);
3744
5347
  return json(res, 200, openAiCompletionToResponsesEnvelope(body, completion));
3745
5348
  }
3746
5349
 
3747
- if (url.pathname === '/v1/messages') {
3748
- const response = await handleProviderProxy(req, body, client, 'anthropic');
5350
+ if (providerPath === '/v1/messages') {
5351
+ const response = await handleProviderProxy(req, body, client, 'anthropic', ariaAuthOptions);
3749
5352
  return json(res, 200, response);
3750
5353
  }
3751
5354
 
@@ -3884,6 +5487,17 @@ async function handleRoute(req, res) {
3884
5487
  }
3885
5488
  }
3886
5489
 
5490
+ if (
5491
+ url.pathname === '/task-project-ledger/event' ||
5492
+ url.pathname === '/task-project-ledger/status' ||
5493
+ url.pathname === '/task-project-ledger/claim' ||
5494
+ url.pathname === '/openclaw/task-ledger/snapshot' ||
5495
+ url.pathname === '/openclaw/task-flow'
5496
+ ) {
5497
+ const ledgerResult = await handleTaskProjectLedgerRoute(url, body);
5498
+ return json(res, ledgerResult.permitted === false ? 409 : 200, ledgerResult);
5499
+ }
5500
+
3887
5501
  if (url.pathname === '/packet' || url.pathname === '/api/harness/codex') {
3888
5502
  const packet = await loadRuntimePacket(req, body, client, body.packetRequest || body, body.message || '');
3889
5503
  return json(res, 200, { ok: true, packet });
@@ -3933,6 +5547,28 @@ async function handleRoute(req, res) {
3933
5547
  throw new Error('validate-output requires text and sessionId');
3934
5548
  }
3935
5549
 
5550
+ if (isAriaControlOutput(body.text)) {
5551
+ const response = {
5552
+ ok: true,
5553
+ pass: true,
5554
+ validation: {
5555
+ passed: true,
5556
+ severity: 'pass',
5557
+ violations: [],
5558
+ gateTriggers: ['aria-control-output'],
5559
+ },
5560
+ };
5561
+ if (body.runLayer3 !== false) {
5562
+ response.layer3 = {
5563
+ pass: true,
5564
+ summary: 'control output bypassed assistant prose validation',
5565
+ failures: [],
5566
+ doctrine: { hits: [] },
5567
+ };
5568
+ }
5569
+ return json(res, 200, response);
5570
+ }
5571
+
3936
5572
  let validation;
3937
5573
  try {
3938
5574
  validation = await client.validateOutput(body.text, body.sessionId);
@@ -3948,6 +5584,7 @@ async function handleRoute(req, res) {
3948
5584
  };
3949
5585
  }
3950
5586
  validation = mergeAppliedCognitionValidation(validation, validateAppliedCognitionContract(body.text));
5587
+ validation = allowTrivialAppliedCognitionValidation(validation, body.requireCognitionBlock !== false);
3951
5588
  const response = { ok: true, validation };
3952
5589
 
3953
5590
  if (body.runLayer3 !== false) {