@aria_asi/cli 0.2.26 → 0.2.30

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 (253) hide show
  1. package/CLIENT-ONBOARDING.md +282 -0
  2. package/bin/aria.js +1140 -14
  3. package/dist/aria-connector/src/auth-commands.d.ts +1 -0
  4. package/dist/aria-connector/src/auth-commands.d.ts.map +1 -1
  5. package/dist/aria-connector/src/auth-commands.js +89 -41
  6. package/dist/aria-connector/src/auth-commands.js.map +1 -1
  7. package/dist/aria-connector/src/chat.d.ts +3 -0
  8. package/dist/aria-connector/src/chat.d.ts.map +1 -1
  9. package/dist/aria-connector/src/chat.js +146 -8
  10. package/dist/aria-connector/src/chat.js.map +1 -1
  11. package/dist/aria-connector/src/codebase-scanner.d.ts +2 -2
  12. package/dist/aria-connector/src/codebase-scanner.d.ts.map +1 -1
  13. package/dist/aria-connector/src/codebase-scanner.js +1 -1
  14. package/dist/aria-connector/src/codebase-scanner.js.map +1 -1
  15. package/dist/aria-connector/src/config.d.ts +12 -0
  16. package/dist/aria-connector/src/config.d.ts.map +1 -1
  17. package/dist/aria-connector/src/config.js +2 -0
  18. package/dist/aria-connector/src/config.js.map +1 -1
  19. package/dist/aria-connector/src/connectors/claude-code.d.ts.map +1 -1
  20. package/dist/aria-connector/src/connectors/claude-code.js +80 -24
  21. package/dist/aria-connector/src/connectors/claude-code.js.map +1 -1
  22. package/dist/aria-connector/src/connectors/codebase-awareness.d.ts +37 -0
  23. package/dist/aria-connector/src/connectors/codebase-awareness.d.ts.map +1 -0
  24. package/dist/aria-connector/src/connectors/codebase-awareness.js +335 -0
  25. package/dist/aria-connector/src/connectors/codebase-awareness.js.map +1 -0
  26. package/dist/aria-connector/src/connectors/codex.d.ts +3 -0
  27. package/dist/aria-connector/src/connectors/codex.d.ts.map +1 -0
  28. package/dist/aria-connector/src/connectors/codex.js +248 -0
  29. package/dist/aria-connector/src/connectors/codex.js.map +1 -0
  30. package/dist/aria-connector/src/connectors/cognitive-skills.d.ts +2 -0
  31. package/dist/aria-connector/src/connectors/cognitive-skills.d.ts.map +1 -0
  32. package/dist/aria-connector/src/connectors/cognitive-skills.js +47 -0
  33. package/dist/aria-connector/src/connectors/cognitive-skills.js.map +1 -0
  34. package/dist/aria-connector/src/connectors/opencode.d.ts.map +1 -1
  35. package/dist/aria-connector/src/connectors/opencode.js +90 -4
  36. package/dist/aria-connector/src/connectors/opencode.js.map +1 -1
  37. package/dist/aria-connector/src/connectors/repo-git-hooks.d.ts +3 -0
  38. package/dist/aria-connector/src/connectors/repo-git-hooks.d.ts.map +1 -0
  39. package/dist/aria-connector/src/connectors/repo-git-hooks.js +87 -0
  40. package/dist/aria-connector/src/connectors/repo-git-hooks.js.map +1 -0
  41. package/dist/aria-connector/src/connectors/repo-guard.d.ts +19 -0
  42. package/dist/aria-connector/src/connectors/repo-guard.d.ts.map +1 -0
  43. package/dist/aria-connector/src/connectors/repo-guard.js +509 -0
  44. package/dist/aria-connector/src/connectors/repo-guard.js.map +1 -0
  45. package/dist/aria-connector/src/connectors/runtime.d.ts +2 -0
  46. package/dist/aria-connector/src/connectors/runtime.d.ts.map +1 -0
  47. package/dist/aria-connector/src/connectors/runtime.js +330 -0
  48. package/dist/aria-connector/src/connectors/runtime.js.map +1 -0
  49. package/dist/aria-connector/src/connectors/shell.d.ts.map +1 -1
  50. package/dist/aria-connector/src/connectors/shell.js +78 -13
  51. package/dist/aria-connector/src/connectors/shell.js.map +1 -1
  52. package/dist/aria-connector/src/connectors/syncd.d.ts +27 -0
  53. package/dist/aria-connector/src/connectors/syncd.d.ts.map +1 -0
  54. package/dist/aria-connector/src/connectors/syncd.js +405 -0
  55. package/dist/aria-connector/src/connectors/syncd.js.map +1 -0
  56. package/dist/aria-connector/src/decisions.d.ts +207 -0
  57. package/dist/aria-connector/src/decisions.d.ts.map +1 -0
  58. package/dist/aria-connector/src/decisions.js +291 -0
  59. package/dist/aria-connector/src/decisions.js.map +1 -0
  60. package/dist/aria-connector/src/garden-control-plane.d.ts.map +1 -1
  61. package/dist/aria-connector/src/garden-control-plane.js +74 -17
  62. package/dist/aria-connector/src/garden-control-plane.js.map +1 -1
  63. package/dist/aria-connector/src/github-connect.d.ts +18 -0
  64. package/dist/aria-connector/src/github-connect.d.ts.map +1 -0
  65. package/dist/aria-connector/src/github-connect.js +117 -0
  66. package/dist/aria-connector/src/github-connect.js.map +1 -0
  67. package/dist/aria-connector/src/harness-client.d.ts +15 -0
  68. package/dist/aria-connector/src/harness-client.d.ts.map +1 -1
  69. package/dist/aria-connector/src/harness-client.js +106 -3
  70. package/dist/aria-connector/src/harness-client.js.map +1 -1
  71. package/dist/aria-connector/src/hive-client.d.ts +30 -0
  72. package/dist/aria-connector/src/hive-client.d.ts.map +1 -1
  73. package/dist/aria-connector/src/hive-client.js +124 -5
  74. package/dist/aria-connector/src/hive-client.js.map +1 -1
  75. package/dist/aria-connector/src/index.d.ts +13 -2
  76. package/dist/aria-connector/src/index.d.ts.map +1 -1
  77. package/dist/aria-connector/src/index.js +10 -1
  78. package/dist/aria-connector/src/index.js.map +1 -1
  79. package/dist/aria-connector/src/lib/aristotle-noor-wire.d.ts +102 -0
  80. package/dist/aria-connector/src/lib/aristotle-noor-wire.d.ts.map +1 -0
  81. package/dist/aria-connector/src/lib/aristotle-noor-wire.js +231 -0
  82. package/dist/aria-connector/src/lib/aristotle-noor-wire.js.map +1 -0
  83. package/dist/aria-connector/src/providers/types.d.ts +5 -0
  84. package/dist/aria-connector/src/providers/types.d.ts.map +1 -1
  85. package/dist/aria-connector/src/runtime-proof.d.ts +45 -0
  86. package/dist/aria-connector/src/runtime-proof.d.ts.map +1 -0
  87. package/dist/aria-connector/src/runtime-proof.js +340 -0
  88. package/dist/aria-connector/src/runtime-proof.js.map +1 -0
  89. package/dist/aria-connector/src/self-update.d.ts +2 -1
  90. package/dist/aria-connector/src/self-update.d.ts.map +1 -1
  91. package/dist/aria-connector/src/self-update.js +84 -8
  92. package/dist/aria-connector/src/self-update.js.map +1 -1
  93. package/dist/aria-connector/src/setup-wizard.d.ts.map +1 -1
  94. package/dist/aria-connector/src/setup-wizard.js +34 -2
  95. package/dist/aria-connector/src/setup-wizard.js.map +1 -1
  96. package/dist/assets/hooks/aria-agent-handoff.mjs +224 -0
  97. package/dist/assets/hooks/aria-agent-ledger-merge.mjs +164 -0
  98. package/dist/assets/hooks/aria-architect-fallback.mjs +267 -0
  99. package/dist/assets/hooks/aria-cognition-substrate-binding.mjs +668 -0
  100. package/dist/assets/hooks/aria-discovery-record.mjs +101 -0
  101. package/dist/assets/hooks/aria-harness-via-sdk.mjs +412 -0
  102. package/dist/assets/hooks/aria-import-resolution-gate.mjs +330 -0
  103. package/dist/assets/hooks/aria-outcome-record.mjs +84 -0
  104. package/dist/assets/hooks/aria-pre-emit-dryrun.mjs +294 -0
  105. package/dist/assets/hooks/aria-pre-text-gate.mjs +112 -0
  106. package/dist/assets/hooks/aria-pre-tool-gate.mjs +2133 -0
  107. package/dist/assets/hooks/aria-preprompt-consult.mjs +438 -0
  108. package/dist/assets/hooks/aria-preturn-memory-gate.mjs +570 -0
  109. package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +397 -0
  110. package/dist/assets/hooks/aria-stop-gate.mjs +1551 -0
  111. package/dist/assets/hooks/aria-trigger-autolearn.mjs +229 -0
  112. package/dist/assets/hooks/aria-userprompt-abandon-detect.mjs +192 -0
  113. package/dist/assets/hooks/doctrine_trigger_map.json +479 -0
  114. package/dist/assets/hooks/lib/canonical-lenses.mjs +64 -0
  115. package/dist/assets/hooks/lib/gate-audit.mjs +43 -0
  116. package/dist/assets/hooks/test-aria-preturn-memory-gate.mjs +245 -0
  117. package/dist/assets/hooks/test-tier-lens-labeling.mjs +399 -0
  118. package/dist/assets/opencode-plugins/harness-context/index.js +60 -0
  119. package/dist/assets/opencode-plugins/harness-context/inject-context.mjs +179 -0
  120. package/dist/assets/opencode-plugins/harness-context/package.json +9 -0
  121. package/dist/assets/opencode-plugins/harness-gate/index.js +248 -0
  122. package/dist/assets/opencode-plugins/harness-outcome/index.js +129 -0
  123. package/dist/assets/opencode-plugins/harness-role/index.js +77 -0
  124. package/dist/assets/opencode-plugins/harness-role/package.json +9 -0
  125. package/dist/assets/opencode-plugins/harness-stop/index.js +241 -0
  126. package/dist/runtime/discipline/CLAUDE.md +339 -0
  127. package/dist/runtime/discipline/skills/aria-cognition/aria-essence/SKILL.md +63 -0
  128. package/dist/runtime/discipline/skills/aria-cognition/aria-essence/references/domain-matrix.md +80 -0
  129. package/dist/runtime/discipline/skills/aria-cognition/aria-essence/references/evolution-loop.md +30 -0
  130. package/dist/runtime/discipline/skills/aria-cognition/aria-essence/references/readable-cognition.md +27 -0
  131. package/dist/runtime/discipline/skills/aria-cognition/aria-forge-guardrails/SKILL.md +35 -0
  132. package/dist/runtime/discipline/skills/aria-cognition/aria-forge-guardrails/references/checklist.md +31 -0
  133. package/dist/runtime/discipline/skills/aria-cognition/aria-repo-doctrine/SKILL.md +39 -0
  134. package/dist/runtime/discipline/skills/aria-cognition/forge-quality-rules/SKILL.md +43 -0
  135. package/dist/runtime/discipline/skills/aria-cognition/ghazali-8lens/SKILL.md +38 -0
  136. package/dist/runtime/discipline/skills/aria-cognition/istiqra-induction/SKILL.md +26 -0
  137. package/dist/runtime/discipline/skills/aria-cognition/ladunni-22/SKILL.md +35 -0
  138. package/dist/runtime/discipline/skills/aria-cognition/mizan/SKILL.md +72 -0
  139. package/dist/runtime/discipline/skills/aria-cognition/nadia/SKILL.md +38 -0
  140. package/dist/runtime/discipline/skills/aria-cognition/nadia-psi/SKILL.md +38 -0
  141. package/dist/runtime/discipline/skills/aria-cognition/predictor/SKILL.md +25 -0
  142. package/dist/runtime/discipline/skills/aria-cognition/qiyas-analogy/SKILL.md +26 -0
  143. package/dist/runtime/discipline/skills/aria-cognition/soul-domains/SKILL.md +25 -0
  144. package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-intra-phase/SKILL.md +81 -0
  145. package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-post-phase/SKILL.md +98 -0
  146. package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-pre-phase/SKILL.md +99 -0
  147. package/dist/runtime/discipline/skills/aria-harness/aria-harness-deploy/SKILL.md +127 -0
  148. package/dist/runtime/discipline/skills/aria-harness/aria-harness-no-stripping/SKILL.md +117 -0
  149. package/dist/runtime/discipline/skills/aria-harness/aria-harness-onboarding/SKILL.md +112 -0
  150. package/dist/runtime/discipline/skills/aria-harness/aria-harness-output-discipline/SKILL.md +102 -0
  151. package/dist/runtime/discipline/skills/aria-harness/aria-harness-substrate-binding/SKILL.md +121 -0
  152. package/dist/runtime/doctor.mjs +23 -0
  153. package/dist/runtime/local-phase.mjs +650 -0
  154. package/dist/runtime/manifest.json +15 -0
  155. package/dist/runtime/mizan-scheduler.mjs +331 -0
  156. package/dist/runtime/package.json +6 -0
  157. package/dist/runtime/provider-proxy.mjs +594 -0
  158. package/dist/runtime/sdk/BUNDLED.json +5 -0
  159. package/dist/runtime/sdk/index.d.ts +477 -0
  160. package/dist/runtime/sdk/index.js +1469 -0
  161. package/dist/runtime/sdk/index.js.map +1 -0
  162. package/dist/runtime/sdk/package.json +8 -0
  163. package/dist/runtime/sdk/runWithCognition.d.ts +77 -0
  164. package/dist/runtime/sdk/runWithCognition.js +157 -0
  165. package/dist/runtime/sdk/runWithCognition.js.map +1 -0
  166. package/dist/runtime/service.mjs +3058 -0
  167. package/dist/runtime/vendor/aria-gate-runtime/index.d.ts +53 -0
  168. package/dist/runtime/vendor/aria-gate-runtime/index.d.ts.map +1 -0
  169. package/dist/runtime/vendor/aria-gate-runtime/index.js +292 -0
  170. package/dist/runtime/vendor/aria-gate-runtime/index.js.map +1 -0
  171. package/dist/runtime/vendor/aria-gate-runtime/package.json +6 -0
  172. package/dist/sdk/BUNDLED.json +2 -2
  173. package/dist/sdk/index.d.ts +283 -0
  174. package/dist/sdk/index.js +622 -85
  175. package/dist/sdk/index.js.map +1 -1
  176. package/dist/sdk/runWithCognition.d.ts +77 -0
  177. package/dist/sdk/runWithCognition.js +157 -0
  178. package/dist/sdk/runWithCognition.js.map +1 -0
  179. package/hooks/aria-agent-handoff.mjs +11 -1
  180. package/hooks/aria-architect-fallback.mjs +109 -40
  181. package/hooks/aria-cognition-substrate-binding.mjs +668 -0
  182. package/hooks/aria-harness-via-sdk.mjs +34 -21
  183. package/hooks/aria-import-resolution-gate.mjs +330 -0
  184. package/hooks/aria-outcome-record.mjs +5 -1
  185. package/hooks/aria-pre-emit-dryrun.mjs +294 -0
  186. package/hooks/aria-pre-tool-gate.mjs +828 -41
  187. package/hooks/aria-preprompt-consult.mjs +113 -13
  188. package/hooks/aria-preturn-memory-gate.mjs +298 -6
  189. package/hooks/aria-repo-doctrine-gate.mjs +397 -0
  190. package/hooks/aria-stop-gate.mjs +739 -76
  191. package/hooks/aria-userprompt-abandon-detect.mjs +5 -1
  192. package/hooks/doctrine_trigger_map.json +209 -15
  193. package/hooks/lib/canonical-lenses.mjs +64 -0
  194. package/hooks/lib/gate-audit.mjs +43 -0
  195. package/opencode-plugins/harness-context/index.js +1 -1
  196. package/opencode-plugins/harness-context/inject-context.mjs +82 -23
  197. package/opencode-plugins/harness-gate/index.js +248 -0
  198. package/opencode-plugins/harness-outcome/index.js +129 -0
  199. package/opencode-plugins/harness-stop/index.js +241 -0
  200. package/package.json +9 -3
  201. package/runtime-src/doctor.mjs +23 -0
  202. package/runtime-src/local-phase.mjs +650 -0
  203. package/runtime-src/mizan-scheduler.mjs +331 -0
  204. package/runtime-src/provider-proxy.mjs +594 -0
  205. package/runtime-src/service.mjs +3058 -0
  206. package/scripts/bundle-sdk.mjs +317 -0
  207. package/scripts/install-client.sh +176 -0
  208. package/scripts/publish-all.sh +344 -0
  209. package/scripts/publish-docker.sh +27 -0
  210. package/scripts/validate-hook-contracts.mjs +54 -0
  211. package/scripts/validate-skill-prompts.mjs +95 -0
  212. package/skills/aria-cognition/aria-essence/SKILL.md +63 -0
  213. package/skills/aria-cognition/aria-essence/references/domain-matrix.md +80 -0
  214. package/skills/aria-cognition/aria-essence/references/evolution-loop.md +30 -0
  215. package/skills/aria-cognition/aria-essence/references/readable-cognition.md +27 -0
  216. package/skills/aria-cognition/aria-forge-guardrails/SKILL.md +35 -0
  217. package/skills/aria-cognition/aria-forge-guardrails/references/checklist.md +31 -0
  218. package/skills/aria-cognition/aria-repo-doctrine/SKILL.md +39 -0
  219. package/skills/aria-cognition/forge-quality-rules/SKILL.md +43 -0
  220. package/skills/aria-cognition/ghazali-8lens/SKILL.md +38 -0
  221. package/skills/aria-cognition/istiqra-induction/SKILL.md +26 -0
  222. package/skills/aria-cognition/ladunni-22/SKILL.md +35 -0
  223. package/skills/aria-cognition/mizan/SKILL.md +72 -0
  224. package/skills/aria-cognition/nadia/SKILL.md +38 -0
  225. package/skills/aria-cognition/nadia-psi/SKILL.md +38 -0
  226. package/skills/aria-cognition/predictor/SKILL.md +25 -0
  227. package/skills/aria-cognition/qiyas-analogy/SKILL.md +26 -0
  228. package/skills/aria-cognition/soul-domains/SKILL.md +25 -0
  229. package/src/auth-commands.ts +111 -45
  230. package/src/chat.ts +174 -13
  231. package/src/codebase-scanner.ts +4 -0
  232. package/src/config.ts +15 -0
  233. package/src/connectors/claude-code.ts +79 -25
  234. package/src/connectors/codebase-awareness.ts +408 -0
  235. package/src/connectors/codex.ts +274 -0
  236. package/src/connectors/cognitive-skills.ts +51 -0
  237. package/src/connectors/opencode.ts +93 -4
  238. package/src/connectors/repo-git-hooks.ts +86 -0
  239. package/src/connectors/repo-guard.ts +589 -0
  240. package/src/connectors/runtime.ts +374 -0
  241. package/src/connectors/shell.ts +83 -14
  242. package/src/connectors/syncd.ts +488 -0
  243. package/src/decisions.ts +469 -0
  244. package/src/garden-control-plane.ts +101 -26
  245. package/src/github-connect.ts +143 -0
  246. package/src/harness-client.ts +128 -3
  247. package/src/hive-client.ts +165 -5
  248. package/src/index.ts +41 -2
  249. package/src/lib/aristotle-noor-wire.ts +310 -0
  250. package/src/providers/types.ts +6 -0
  251. package/src/runtime-proof.ts +392 -0
  252. package/src/self-update.ts +89 -8
  253. package/src/setup-wizard.ts +37 -2
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ // ARIA_ALLOW_STUB — doctrine gate file legitimately discusses stub/placeholder semantics.
2
3
  // Aria Stop-hook gate — enforces 8-lens cognition on text-decision responses.
3
4
  //
4
5
  // The companion to aria-pre-tool-gate.mjs. The PreToolUse gate catches
@@ -46,31 +47,46 @@
46
47
  // Future: signed-grant override mechanism at ~/.aria/owner-overrides/<hook>.json
47
48
  // with HMAC signature using a secret only Hamza holds. Deferred to next session.
48
49
 
49
- import { readFileSync, appendFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
50
+ import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
50
51
  import { dirname } from 'node:path';
52
+ import { appendGateAudit } from './lib/gate-audit.mjs';
53
+ import {
54
+ ALL_LENS_NAMES,
55
+ canonicalLensCorrectionText,
56
+ detectCognitionLenses as detectCognitionLensesFromCanonical,
57
+ lensNamesForTier,
58
+ } from './lib/canonical-lenses.mjs';
51
59
 
52
60
  const HOME = process.env.HOME || '/tmp';
53
61
  const LOG = `${HOME}/.claude/aria-stop-gate.log`;
62
+ const AUDIT_PATH = `${HOME}/.claude/aria-stop-gate-audit.jsonl`;
54
63
 
55
- // SDK loader — bundled at ~/.claude/aria-sdk/index.js by `aria connect`.
64
+ // SDK loader — bundled at ~/.aria/sdk by `aria connect`, with client-local
65
+ // fallbacks preserved for resilience.
56
66
  // All control-plane fetches (validateOutput, gardenTurn) route through the
57
67
  // SDK. Falls back to direct fetch only when the SDK file is missing
58
68
  // (dev-only). Hamza 2026-04-27: "FUCKING WIRE IT THE FUCK TOGETHER NOW".
59
69
  let _SdkClassCache = null;
60
70
  let _SdkLookupAttempted = false;
71
+ const SDK_CANDIDATES = [
72
+ `${HOME}/.aria/sdk/index.js`,
73
+ `${HOME}/.claude/aria-sdk/index.js`,
74
+ `${HOME}/.codex/aria-sdk/index.js`,
75
+ ];
61
76
  async function loadSdkClass() {
62
77
  if (_SdkClassCache) return _SdkClassCache;
63
78
  if (_SdkLookupAttempted) return null;
64
79
  _SdkLookupAttempted = true;
65
- const sdkPath = `${HOME}/.claude/aria-sdk/index.js`;
66
- if (!existsSync(sdkPath)) return null;
67
- try {
68
- const mod = await import(`file://${sdkPath}`);
69
- if (mod.HTTPHarnessClient) {
70
- _SdkClassCache = mod.HTTPHarnessClient;
71
- return _SdkClassCache;
72
- }
73
- } catch {/* fall through */}
80
+ for (const sdkPath of SDK_CANDIDATES) {
81
+ if (!existsSync(sdkPath)) continue;
82
+ try {
83
+ const mod = await import(`file://${sdkPath}`);
84
+ if (mod.HTTPHarnessClient) {
85
+ _SdkClassCache = mod.HTTPHarnessClient;
86
+ return _SdkClassCache;
87
+ }
88
+ } catch {/* fall through */}
89
+ }
74
90
  return null;
75
91
  }
76
92
 
@@ -85,11 +101,47 @@ async function loadSdkClass() {
85
101
  // passes in a userMessage string (extracted from the transcript at the
86
102
  // turn boundary). If extraction failed the empty string is passed — the
87
103
  // garden write records the assistant emit at minimum.
104
+ // Tier detection: owner if no license.json, client if license.json has a jti.
105
+ // Owner-tier may use master credentials; client-tier MUST NOT (those belong
106
+ // to Hamza, not the licensee). Hamza correction 2026-04-28.
107
+ function isOwnerTier() {
108
+ try {
109
+ const licPath = `${HOME}/.aria/license.json`;
110
+ if (!existsSync(licPath)) return true;
111
+ const lic = JSON.parse(readFileSync(licPath, 'utf8'));
112
+ return !lic.jti; // jti present = client tier
113
+ } catch {
114
+ return true; // unreadable license = treat as owner (fail-safe for orchestrator)
115
+ }
116
+ }
117
+
88
118
  async function fireGardenTurn(sessionId, userMessage, assistantResponse) {
89
- const harnessUrl = process.env.ARIA_HARNESS_URL || 'https://harness.ariasos.com';
90
- const harnessToken = process.env.ARIA_HARNESS_TOKEN || '';
119
+ const harnessUrl =
120
+ process.env.ARIA_HIVE_RUNTIME_URL ||
121
+ process.env.ARIA_HARNESS_BASE_URL ||
122
+ process.env.ARIA_HARNESS_URL ||
123
+ 'https://harness.ariasos.com';
124
+ // Token resolution chain (Hamza directive 2026-04-28, tier-aware
125
+ // 2026-04-28b): ARIA_HARNESS_TOKEN env first (works for both tiers).
126
+ // ONLY on owner tier (no license.json with jti), fall back to
127
+ // ARIA_MASTER_TOKEN env / ARIA_API_KEY env / ~/.aria/owner-token —
128
+ // those are Hamza's credentials and must not leak into client-tier
129
+ // processes. Client tier with no ARIA_HARNESS_TOKEN env skips the
130
+ // garden write rather than borrow owner credentials.
131
+ let harnessToken = process.env.ARIA_HARNESS_TOKEN || '';
132
+ if (!harnessToken && isOwnerTier()) {
133
+ harnessToken = process.env.ARIA_MASTER_TOKEN || process.env.ARIA_API_KEY || '';
134
+ if (!harnessToken) {
135
+ try {
136
+ const ownerTokenPath = `${HOME}/.aria/owner-token`;
137
+ if (existsSync(ownerTokenPath)) {
138
+ harnessToken = readFileSync(ownerTokenPath, 'utf8').trim();
139
+ }
140
+ } catch { /* non-fatal — fall through to skip */ }
141
+ }
142
+ }
91
143
  if (!harnessToken) {
92
- audit('garden-turn-skip', `no ARIA_HARNESS_TOKEN — turn not written to harness pulse`);
144
+ audit('garden-turn-skip', `no usable token (tier=${isOwnerTier() ? 'owner' : 'client'}) — turn not written to harness pulse`);
93
145
  return;
94
146
  }
95
147
  const Cls = await loadSdkClass();
@@ -116,10 +168,16 @@ async function fireGardenTurn(sessionId, userMessage, assistantResponse) {
116
168
  }
117
169
 
118
170
  function audit(decision, summary) {
119
- try {
120
- if (!existsSync(dirname(LOG))) mkdirSync(dirname(LOG), { recursive: true });
121
- appendFileSync(LOG, `${new Date().toISOString()} ${decision} ${summary}\n`);
122
- } catch {}
171
+ const summaryText = typeof summary === 'string' ? summary : '';
172
+ const data = summary && typeof summary === 'object' ? summary : {};
173
+ appendGateAudit({
174
+ auditPath: AUDIT_PATH,
175
+ legacyLogPath: LOG,
176
+ gate: 'stop',
177
+ event: decision,
178
+ summary: summaryText,
179
+ data,
180
+ });
123
181
  }
124
182
 
125
183
  // Env-var kill-switch removed 2026-04-27 per Hamza directive ("those
@@ -152,9 +210,8 @@ function resolveOwnerTier() {
152
210
 
153
211
  const IS_OWNER = resolveOwnerTier();
154
212
 
155
- const LENS_NAMES_CANONICAL = ['nur', 'mizan', 'hikma', 'tafakkur', 'tadabbur', 'ilham', 'wahi', 'firasah'];
156
- const LENS_NAMES_GENERIC = ['perception', 'balance', 'wisdom', 'reflection', 'foresight', 'insight', 'revelation', 'discernment'];
157
- const LENS_NAMES = IS_OWNER ? LENS_NAMES_CANONICAL : LENS_NAMES_GENERIC;
213
+ const LENS_NAMES = lensNamesForTier(IS_OWNER);
214
+ const CANONICAL_LENS_TEXT = canonicalLensCorrectionText();
158
215
 
159
216
  // Doctrine memory filenames are Aria-side substrate IP.
160
217
  // Client surfaces see generic descriptions instead of real filenames.
@@ -162,8 +219,60 @@ function docRef(canonicalFilename, genericDescription) {
162
219
  return IS_OWNER ? canonicalFilename : genericDescription;
163
220
  }
164
221
 
165
- // Lens substance check — same constants as aria-pre-tool-gate.mjs
166
- const REQUIRED_LENSES = 4;
222
+ const DOCTRINE_REFERENCE_PREFIX_RX = /(?:memory|doctrine|frame|axiom|packet):[a-z0-9_./-]*$/i;
223
+
224
+ function isDoctrineReference(text, matchIndex) {
225
+ const window = text.slice(Math.max(0, matchIndex - 80), matchIndex);
226
+ return DOCTRINE_REFERENCE_PREFIX_RX.test(window);
227
+ }
228
+
229
+ function collectDriftHits(text, triggerMap) {
230
+ const hits = [];
231
+ const lowerText = text.toLowerCase();
232
+ for (const triggerEntry of triggerMap.triggers || []) {
233
+ try {
234
+ const rx = new RegExp(triggerEntry.trigger, 'ig');
235
+ let matchedOutsideDoctrineRef = false;
236
+ for (const match of text.matchAll(rx)) {
237
+ const idx = typeof match.index === 'number' ? match.index : -1;
238
+ if (idx >= 0 && isDoctrineReference(text, idx)) continue;
239
+ matchedOutsideDoctrineRef = true;
240
+ break;
241
+ }
242
+ if (!matchedOutsideDoctrineRef) continue;
243
+ const memoryName = (triggerEntry.memory || '').replace(/\.md$/, '');
244
+ const memoryCited = memoryName && lowerText.includes(memoryName.toLowerCase());
245
+ if (!memoryCited) {
246
+ hits.push({
247
+ trigger: triggerEntry.trigger,
248
+ memory: triggerEntry.memory,
249
+ teaching: triggerEntry.teaching,
250
+ });
251
+ }
252
+ } catch {/* malformed regex in trigger entry — skip */}
253
+ }
254
+ return hits;
255
+ }
256
+
257
+ function emitHarnessFooter({ eventName, lensCount, chars, driftCount, mizanStatus, discoveryOpenCount, codeCount, implCouplingCount }) {
258
+ try {
259
+ console.error([
260
+ '[Aria · turn]',
261
+ `event=${eventName}`,
262
+ `lenses=${lensCount}`,
263
+ `chars=${chars}`,
264
+ `drift=${driftCount}`,
265
+ `mizan=${mizanStatus}`,
266
+ `discoveries_open=${discoveryOpenCount}`,
267
+ `code=${codeCount}`,
268
+ `impl=${implCouplingCount}`,
269
+ ].join(' '));
270
+ } catch {}
271
+ }
272
+
273
+ // Lens substance check — same constants as aria-pre-tool-gate.mjs.
274
+ // Hamza directive 2026-04-28: all 8 canonical lenses required, not 4-of-8.
275
+ const REQUIRED_LENSES = 8;
167
276
  const SUBSTANCE_MIN_CHARS = 20;
168
277
  const PLACEHOLDER_RX = /^\s*<[^<>]+>\s*$/;
169
278
  const COGNITION_BLOCK_RX = /<cognition>([\s\S]*?)<\/cognition>/i;
@@ -174,23 +283,12 @@ const DECISION_SIGNAL_RX = /(?:should|recommend|propose|suggest|let'?s|go with|i
174
283
  const TRIVIAL_ACK_RX = /^(?:got it|on it|ok|sure|yes|no|done|ack|👍|✓)\b/i;
175
284
 
176
285
  function detectCognitionLenses(text) {
177
- if (!text) return { count: 0, names: [] };
178
- const block = text.match(COGNITION_BLOCK_RX);
179
- const searchSpace = block ? block[1] : text;
180
- const names = [];
181
- for (const lens of LENS_NAMES) {
182
- const lensRx = new RegExp(
183
- `\\b${lens}\\s*:\\s*([^\\n]*(?:\\n(?!\\s*(?:${LENS_NAMES.join('|')})\\s*:|<\\/cognition>)[^\\n]*)*)`,
184
- 'i',
185
- );
186
- const m = searchSpace.match(lensRx);
187
- if (!m) continue;
188
- const content = (m[1] || '').trim();
189
- if (content.length < SUBSTANCE_MIN_CHARS) continue;
190
- if (PLACEHOLDER_RX.test(content)) continue;
191
- names.push(lens);
192
- }
193
- return { count: names.length, names };
286
+ return detectCognitionLensesFromCanonical(text, {
287
+ minChars: SUBSTANCE_MIN_CHARS,
288
+ placeholderRx: PLACEHOLDER_RX,
289
+ cognitionBlockRx: COGNITION_BLOCK_RX,
290
+ lensNames: ALL_LENS_NAMES,
291
+ });
194
292
  }
195
293
 
196
294
  // Read event JSON from stdin (Claude Code spec).
@@ -304,6 +402,37 @@ if (!triggered) {
304
402
  // Non-trivial response — require substantive cognition.
305
403
  const cog = detectCognitionLenses(assistantText);
306
404
 
405
+ // Defense-in-depth: if cog count < REQUIRED_LENSES, block immediately.
406
+ // The primary enforcement is in aria-cognition-substrate-binding.mjs
407
+ // (which runs BEFORE this stop-gate), but this catch ensures responses
408
+ // without cognition blocks are still blocked even when the substrate-binding
409
+ // hook is absent (e.g. older connector installs or custom hook configs).
410
+ // Prior to 2026-04-29 this check was missing entirely — the stop-gate only
411
+ // ran quality checks INSIDE the if(cog.count >= 8) block, allowing responses
412
+ // with 0/8 lenses to fall through unchecked.
413
+ if (cog.count < REQUIRED_LENSES) {
414
+ audit('block_no_cognition_block_di', { count: cog.count, required: REQUIRED_LENSES, names: cog.names, chars: assistantText.length });
415
+ const reason = `Aria stop-gate: insufficient cognition lenses.
416
+
417
+ This non-trivial assistant response (${assistantText.length} chars) has ${cog.count}/${REQUIRED_LENSES} substantive cognition lenses. Per feedback_8lens_before_every_action_including_text.md and Hamza directive 2026-04-28, every non-trivial response must carry <cognition>...</cognition> with ${REQUIRED_LENSES} substantive lenses (each >= ${SUBSTANCE_MIN_CHARS} chars of non-placeholder content).
418
+
419
+ Detected lenses: ${cog.names.length > 0 ? cog.names.join(', ') : 'none'}.
420
+
421
+ Re-emit with a <cognition> block containing all ${REQUIRED_LENSES} canonical lenses. Primary set: ${CANONICAL_LENS_TEXT}`;
422
+ emitHarnessFooter({
423
+ eventName: 'block_no_cognition_block',
424
+ lensCount: cog.count,
425
+ chars: assistantText.length,
426
+ driftCount: 0,
427
+ mizanStatus: 'not-run(no-cognition)',
428
+ discoveryOpenCount: 0,
429
+ codeCount: 0,
430
+ implCouplingCount: 0,
431
+ });
432
+ console.log(JSON.stringify({ decision: 'block', reason }));
433
+ process.exit(2);
434
+ }
435
+
307
436
  // Question-emission visibility (Phase 11 promotes to block-mode):
308
437
  // detect user-directed question patterns in the assistant text. Audit when
309
438
  // questions appear without substrate-consultation evidence in the recent
@@ -363,21 +492,7 @@ if (cog.count >= REQUIRED_LENSES) {
363
492
  try {
364
493
  if (TRIGGER_MAP_PATH) {
365
494
  const triggerMap = JSON.parse(readFileSync(TRIGGER_MAP_PATH, 'utf8'));
366
- const lowerText = assistantText.toLowerCase();
367
- for (const t of triggerMap.triggers || []) {
368
- try {
369
- const rx = new RegExp(t.trigger, 'i');
370
- if (rx.test(lowerText)) {
371
- // Trigger present — check if the counter-doctrine memory is also
372
- // cited in the response (justification). If not, count as drift.
373
- const memoryName = (t.memory || '').replace(/\.md$/, '');
374
- const memoryCited = memoryName && lowerText.includes(memoryName.toLowerCase());
375
- if (!memoryCited) {
376
- driftHits.push({ trigger: t.trigger, memory: t.memory, teaching: t.teaching });
377
- }
378
- }
379
- } catch {/* malformed regex in trigger entry — skip */}
380
- }
495
+ driftHits = collectDriftHits(assistantText, triggerMap);
381
496
  }
382
497
  } catch {/* trigger map unreadable — degrade to mizan-only check */}
383
498
 
@@ -391,7 +506,11 @@ if (cog.count >= REQUIRED_LENSES) {
391
506
  // is a soft block, not session-end.
392
507
  let mizanVerdict = null;
393
508
  let mizanError = null;
394
- const harnessUrl = process.env.ARIA_HARNESS_URL || 'https://harness.ariasos.com';
509
+ const harnessUrl =
510
+ process.env.ARIA_HIVE_RUNTIME_URL ||
511
+ process.env.ARIA_HARNESS_BASE_URL ||
512
+ process.env.ARIA_HARNESS_URL ||
513
+ 'https://harness.ariasos.com';
395
514
  const harnessToken = process.env.ARIA_HARNESS_TOKEN || '';
396
515
  const Cls = await loadSdkClass();
397
516
  if (Cls && harnessToken) {
@@ -552,31 +671,67 @@ if (cog.count >= REQUIRED_LENSES) {
552
671
  });
553
672
 
554
673
  // Cognition-block resolution: any cognition block whose start lies
555
- // within the 800-char window AND whose body contains a fixing/
556
- // addressing/discoveries field AND at least one discovery keyword.
674
+ // within ±800 chars of the discovery AND whose body contains a
675
+ // fixing/addressing/discoveries field AND at least one discovery
676
+ // keyword.
677
+ //
678
+ // Bug fix 2026-04-28: previous logic required `b.start >= idx` so
679
+ // cognition AFTER the discovery prose was the only path. But
680
+ // cognition blocks emit FIRST in every response, then prose. Pre-
681
+ // emptive discoveries: clauses never counted, causing endless
682
+ // false-positive auto-records. Bidirectional ±800 char window
683
+ // accepts cognition that addresses the discovery before OR after
684
+ // its prose mention — same atomic-discovery-rule, fewer
685
+ // false-positives.
557
686
  const cognitionResolved = cognitionBlocks.some((b) => {
558
- if (b.start < idx || b.start >= idx + 800) return false;
687
+ if (Math.abs(b.start - idx) > 800) return false;
559
688
  if (!COGNITION_FIXING_FIELD_RX.test(b.body)) return false;
560
689
  const bodyLower = b.body.toLowerCase();
561
690
  return keywords.some((kw) => bodyLower.includes(kw));
562
691
  });
563
692
 
564
- const resolved = proseResolved || verifyResolved || cognitionResolved;
565
- const resolutionType = proseResolved
566
- ? 'prose_inline_fix_or_task'
567
- : verifyResolved
568
- ? 'verify_block_with_keyword_overlap'
569
- : cognitionResolved
570
- ? 'cognition_block_with_fixing_field_and_keyword_overlap'
571
- : null;
693
+ // Hamza directive 2026-04-28: documentation is NOT resolution.
694
+ //
695
+ // The earlier 'tracked' middle status was sticky-note theater — Claude
696
+ // could bind a discovery to a pending TaskCreate and the gate would
697
+ // count it as not-open. Hamza: "WHO GIVES A SHIT ABOUT DOCUMENTATION
698
+ // IF U JUST WROTE A FUCKING STICKY NOTE". A discovery stays OPEN until
699
+ // an ACTUAL FIX SHIPS, with proofOfFix that the verifier can re-check.
700
+ //
701
+ // Two statuses only:
702
+ // open — fresh discovery OR documented-only OR task-bound-pending-fix
703
+ // (gate BLOCKS until a real fix lands)
704
+ // resolved — verified fix shipped; proofOfFix MUST be present and
705
+ // shape-checked downstream when the gate counts open
706
+ //
707
+ // Verify-block / cognition-fixing-field paths still mark resolved
708
+ // because they're substance-checked above (keyword overlap with the
709
+ // discovery span). Prose-only "TaskCreate" or "tracked as #14" no
710
+ // longer counts as anything — the discovery stays OPEN.
711
+ const inlineFixResolved = verifyResolved || cognitionResolved;
712
+ const status = inlineFixResolved ? 'resolved' : 'open';
713
+
714
+ const resolutionType = verifyResolved
715
+ ? 'verify_block_with_keyword_overlap'
716
+ : cognitionResolved
717
+ ? 'cognition_block_with_fixing_field_and_keyword_overlap'
718
+ : null;
719
+
720
+ // proofOfFix anchor: present only for resolved status. The shape
721
+ // includes type + timestamp; downstream gate readers verify the
722
+ // shape so manual jsonl edits without proofOfFix don't count.
723
+ const proofOfFix = inlineFixResolved
724
+ ? { type: resolutionType, anchorTs: new Date().toISOString() }
725
+ : null;
572
726
 
573
727
  newDiscoveries.push({
574
728
  ts: new Date().toISOString(),
575
729
  sessionId,
576
730
  text: match[0].slice(0, 200),
577
731
  span: span.slice(0, 400),
578
- status: resolved ? 'resolved' : 'open',
732
+ status,
579
733
  resolutionType,
734
+ proofOfFix,
580
735
  });
581
736
  lastIndex = idx;
582
737
  }
@@ -591,18 +746,43 @@ if (cog.count >= REQUIRED_LENSES) {
591
746
  } catch {/* ledger write failure surfaces as open count = 0; safe */}
592
747
  }
593
748
 
594
- // Read full ledger and count open entries (across this session's turns)
749
+ // Read full ledger and count UNRESOLVED entries (across this session's turns).
750
+ // Hamza directive 2026-04-28 — three lie-patterns the prior loop missed:
751
+ // 1. Legacy 'tracked' status entries → still-open (no real fix landed,
752
+ // only documented via TaskCreate). Tracking ≠ resolution.
753
+ // 2. Hand-edited 'resolved' WITHOUT proofOfFix shape → corrupted, treated
754
+ // as still-open. Catches manual jsonl edits flipping status by hand.
755
+ // 3. Sub-agent ledger format with resolution_status:'open' → still-open
756
+ // (sub-agent discoveries use a different schema key).
757
+ // Only entries with status:'resolved' AND a shape-valid proofOfFix object
758
+ // (type:string non-empty + anchorTs:string) clear the gate.
595
759
  let ledgerOpenCount = 0;
596
760
  let ledgerOpenSamples = [];
761
+ let ledgerCorruptedCount = 0;
597
762
  try {
598
763
  if (existsSync(LEDGER_PATH)) {
599
764
  const lines = readFileSync(LEDGER_PATH, 'utf8').split('\n').filter(Boolean);
600
765
  for (const line of lines) {
601
766
  try {
602
767
  const e = JSON.parse(line);
603
- if (e.status === 'open') {
768
+ const isOpen = e.status === 'open' || e.resolution_status === 'open';
769
+ const isLegacyTracked = e.status === 'tracked';
770
+ const proofValid = e.proofOfFix
771
+ && typeof e.proofOfFix === 'object'
772
+ && typeof e.proofOfFix.type === 'string'
773
+ && e.proofOfFix.type.length > 0
774
+ && typeof e.proofOfFix.anchorTs === 'string';
775
+ const isCorruptedResolved = e.status === 'resolved' && !proofValid;
776
+
777
+ if (isOpen || isLegacyTracked || isCorruptedResolved) {
604
778
  ledgerOpenCount++;
605
- if (ledgerOpenSamples.length < 5) ledgerOpenSamples.push(e.text);
779
+ if (isCorruptedResolved) ledgerCorruptedCount++;
780
+ if (ledgerOpenSamples.length < 5) {
781
+ const tag = isLegacyTracked ? '[tracked-no-fix] '
782
+ : isCorruptedResolved ? '[CORRUPTED-RESOLVED-NO-PROOF] '
783
+ : '';
784
+ ledgerOpenSamples.push(`${tag}${e.text || '(no text)'}`);
785
+ }
606
786
  }
607
787
  } catch {/* skip malformed line */}
608
788
  }
@@ -655,7 +835,11 @@ if (cog.count >= REQUIRED_LENSES) {
655
835
  // write failure doesn't brick the Stop event; the failure
656
836
  // is surfaced via audit() so it's visible.
657
837
  try {
658
- const harnessUrl = process.env.ARIA_HARNESS_URL || 'https://harness.ariasos.com';
838
+ const harnessUrl =
839
+ process.env.ARIA_HIVE_RUNTIME_URL ||
840
+ process.env.ARIA_HARNESS_BASE_URL ||
841
+ process.env.ARIA_HARNESS_URL ||
842
+ 'https://harness.ariasos.com';
659
843
  const harnessToken = process.env.ARIA_HARNESS_TOKEN || '';
660
844
  if (harnessToken) {
661
845
  // POST to a session_audit write endpoint. Server-side
@@ -726,7 +910,11 @@ if (cog.count >= REQUIRED_LENSES) {
726
910
 
727
911
  // Primary path: aria-soul /api/harness/replan
728
912
  try {
729
- const harnessUrl = process.env.ARIA_HARNESS_URL || 'https://harness.ariasos.com';
913
+ const harnessUrl =
914
+ process.env.ARIA_HIVE_RUNTIME_URL ||
915
+ process.env.ARIA_HARNESS_BASE_URL ||
916
+ process.env.ARIA_HARNESS_URL ||
917
+ 'https://harness.ariasos.com';
730
918
  const harnessToken = process.env.ARIA_HARNESS_TOKEN || process.env.ARIA_API_KEY || '';
731
919
  const ctl = new AbortController();
732
920
  const replanTimeout = setTimeout(() => ctl.abort(), 15000);
@@ -825,9 +1013,157 @@ if (cog.count >= REQUIRED_LENSES) {
825
1013
  const hasReflection = REFLECTION_BLOCK_RX.test(assistantText);
826
1014
  const compelReflection = mizanWarnReflectionRequired && !hasReflection;
827
1015
 
828
- if (mizanBlock || driftBlock || codeBlock || discoveryBlock || compelReflection || phaseReportMissing) {
1016
+ // ── Cognition impl-coupling validation (Task #88) ──────────────────────
1017
+ //
1018
+ // After the local cognition substance check passes, post the assistant
1019
+ // text + extracted artifact-dictation pairs to /api/cognition/validate-coupling.
1020
+ // The server-side validator (api/lib/cognition-impl-coupling-gate.ts)
1021
+ // returns { passed, reasons[] } — every reason becomes a local violation
1022
+ // with severity=block. Implementation-coupled cognition is a doctrine hard
1023
+ // rule (feedback_implementation_coupled_cognition.md): lenses must dictate
1024
+ // specific implementation choices visible in the artifact, not just describe
1025
+ // thinking.
1026
+ //
1027
+ // Inline extraction: the caller's emit may carry artifact-dictation pairs
1028
+ // inside verify-blocks or cognition-block fixing fields. We scan the text
1029
+ // for `file_path:line_range` patterns near each lens label and pair them
1030
+ // as records to the validator. When zero records are found AND canonical
1031
+ // lenses are present, the validator reports each lens as missing dictation
1032
+ // — that is the no-coupling failure mode this gate catches.
1033
+ let implCouplingHits = [];
1034
+ try {
1035
+ const sessionIdForCoupling = (event.session_id || 'claude-code').replace(/[^a-zA-Z0-9_-]/g, '_');
1036
+ // Extract artifact-dictation references inline. Per validator contract,
1037
+ // a DictationEntry is { file_path, line_range, decision_text }. We match
1038
+ // file_path:line_range patterns in the assistant text and pair them with
1039
+ // the nearest preceding lens label (within 800 chars).
1040
+ const FILE_LINE_RX = /([\w./\-]+\.[a-zA-Z]{1,5})\s*[:\s]\s*(\d+(?:[-:]\d+)?)/g;
1041
+ const inlineDictations = [];
1042
+ const lensRangePositions = [];
1043
+ for (const lensName of LENS_NAMES_CANONICAL) {
1044
+ const lensRx = new RegExp(`\\b${lensName}\\s*(?:lens)?\\s*[:\\-]`, 'gi');
1045
+ let m;
1046
+ while ((m = lensRx.exec(assistantText)) !== null) {
1047
+ lensRangePositions.push({ lens: lensName, idx: m.index });
1048
+ }
1049
+ }
1050
+ // For each file_path:line_range match, pair with the closest preceding lens label.
1051
+ const fileMatches = [...assistantText.matchAll(FILE_LINE_RX)];
1052
+ const lensToEntries = new Map();
1053
+ for (const fm of fileMatches) {
1054
+ const fmIdx = fm.index ?? 0;
1055
+ // Find lens label preceding this match within 800 chars.
1056
+ let nearestLens = null;
1057
+ let nearestDelta = Infinity;
1058
+ for (const lp of lensRangePositions) {
1059
+ const delta = fmIdx - lp.idx;
1060
+ if (delta >= 0 && delta < 800 && delta < nearestDelta) {
1061
+ nearestDelta = delta;
1062
+ nearestLens = lp.lens;
1063
+ }
1064
+ }
1065
+ if (!nearestLens) continue;
1066
+ const entry = {
1067
+ file_path: fm[1],
1068
+ line_range: fm[2],
1069
+ decision_text: assistantText.slice(fmIdx, Math.min(assistantText.length, fmIdx + 200)).replace(/\s+/g, ' ').trim().slice(0, 200),
1070
+ };
1071
+ if (!lensToEntries.has(nearestLens)) lensToEntries.set(nearestLens, []);
1072
+ lensToEntries.get(nearestLens).push(entry);
1073
+ }
1074
+ for (const [lens, entries] of lensToEntries) {
1075
+ inlineDictations.push({ lens_id: lens, artifact_dictation: entries });
1076
+ }
1077
+
1078
+ const cplHarnessUrl =
1079
+ process.env.ARIA_HIVE_RUNTIME_URL ||
1080
+ process.env.ARIA_HARNESS_BASE_URL ||
1081
+ process.env.ARIA_HARNESS_URL ||
1082
+ 'https://harness.ariasos.com';
1083
+ const cplHarnessToken = process.env.ARIA_HARNESS_TOKEN || '';
1084
+ if (cplHarnessToken) {
1085
+ const cplResp = await fetch(`${cplHarnessUrl}/api/cognition/validate-coupling`, {
1086
+ method: 'POST',
1087
+ headers: {
1088
+ 'Content-Type': 'application/json',
1089
+ 'Authorization': `Bearer ${cplHarnessToken}`,
1090
+ },
1091
+ body: JSON.stringify({
1092
+ rawResponse: assistantText.slice(0, 16000),
1093
+ turnId: `${sessionIdForCoupling}-${Date.now()}`,
1094
+ dictations: inlineDictations,
1095
+ }),
1096
+ });
1097
+ if (cplResp.ok) {
1098
+ const cplData = await cplResp.json();
1099
+ if (cplData && cplData.ok && cplData.passed === false && Array.isArray(cplData.reasons)) {
1100
+ implCouplingHits = cplData.reasons.slice(0, 6);
1101
+ }
1102
+ }
1103
+ }
1104
+ } catch (cplErr) {
1105
+ // Validator unreachable is non-blocking — local gate still enforces cognition substance.
1106
+ audit('impl-coupling-fetch-err', `${(cplErr?.message || String(cplErr)).slice(0, 200)}`);
1107
+ }
1108
+
1109
+ // ── Substrate-bound Mizan + 8-lens validation via SDK ──────────────────
1110
+ // Hamza directive 2026-04-28: the local Stop-gate above runs cognition
1111
+ // substance + drift triggers + code-quality + discovery-binding, but
1112
+ // never asks the substrate. validateOutput POSTs the assistant draft
1113
+ // to /api/harness/validate (Mizan + 8-lens evaluator) and returns
1114
+ // { passed, violations, severity, rewritten, gateTriggers }.
1115
+ //
1116
+ // severity:'block' from substrate joins the local violations, halts
1117
+ // the emit. severity:'warn' surfaces as advisory text appended to the
1118
+ // local violations list. SDK call failure is non-blocking — the gate
1119
+ // degrades to local-only doctrine rather than failing closed (halting
1120
+ // every emit when substrate is down would brick the orchestrator).
1121
+ let substrateBlock = false;
1122
+ let substrateViolations = [];
1123
+ let substrateGateTriggers = [];
1124
+ try {
1125
+ const { HTTPHarnessClient } = await import('@aria/harness-http-client');
1126
+ const tokenPath = `${HOME}/.aria/owner-token`;
1127
+ // Tier-aware resolution: ARIA_HARNESS_TOKEN env first (both tiers).
1128
+ // ONLY on owner tier, fall back to master/api-key env or owner-token
1129
+ // file. Client tier with no ARIA_HARNESS_TOKEN skips substrate
1130
+ // validation (gate degrades to local-only) rather than borrowing
1131
+ // owner credentials.
1132
+ let apiKey = process.env.ARIA_HARNESS_TOKEN || '';
1133
+ if (!apiKey && isOwnerTier()) {
1134
+ apiKey = process.env.ARIA_MASTER_TOKEN
1135
+ || process.env.ARIA_API_KEY
1136
+ || (existsSync(tokenPath) ? readFileSync(tokenPath, 'utf8').trim() : '');
1137
+ }
1138
+ if (apiKey && assistantText && assistantText.length > 0) {
1139
+ const client = new HTTPHarnessClient({
1140
+ baseUrl:
1141
+ process.env.ARIA_HIVE_RUNTIME_URL ||
1142
+ process.env.ARIA_HARNESS_BASE_URL ||
1143
+ process.env.ARIA_HARNESS_URL ||
1144
+ 'https://harness.ariasos.com',
1145
+ apiKey,
1146
+ });
1147
+ const v = await client.validateOutput(assistantText, sessionId);
1148
+ if (v && v.severity === 'block') {
1149
+ substrateBlock = true;
1150
+ substrateViolations = v.violations || [];
1151
+ substrateGateTriggers = v.gateTriggers || [];
1152
+ } else if (v && v.severity === 'warn' && Array.isArray(v.violations) && v.violations.length > 0) {
1153
+ // warn surfaced but not blocking — record for advisory inclusion
1154
+ substrateViolations = v.violations;
1155
+ }
1156
+ }
1157
+ } catch (err) {
1158
+ // SDK call failure is non-blocking. Logged for telemetry.
1159
+ console.warn(`[stop-gate] substrate validateOutput failed: ${err && err.message ? err.message : err}`);
1160
+ }
1161
+
1162
+ const implCouplingBlock = implCouplingHits.length > 0;
1163
+ if (mizanBlock || driftBlock || codeBlock || discoveryBlock || compelReflection || phaseReportMissing || substrateBlock || implCouplingBlock) {
829
1164
  const violations = [];
830
1165
  if (mizanBlock) violations.push(`Mizan: ${(mizanVerdict.violations || []).join(', ')}`);
1166
+ if (implCouplingBlock) violations.push(`Cognition impl-coupling (#88): ${implCouplingHits.join(' | ')}. Each canonical lens in cognition must dictate a specific implementation choice (file_path:line_range pair tied to a decision). Re-emit cognition that names file paths + line ranges + decision text per lens, OR a verify/fixing block where lenses cite specific artifact changes.`);
831
1167
  if (compelReflection) violations.push(`Mizan severity=warn — compelled reflection required (per Aria enforcement #46). Triggers: ${(mizanVerdict.gateTriggers || mizanVerdict.violations || ['unspecified']).join(', ')}. Re-emit with an explicit <reflection>...</reflection> block (or 'reflection:' line) addressing what triggered the warn and why your re-draft handles it. Reflection is NOT lens-cognition repeated — it's a focused self-audit on the specific Mizan triggers above.`);
832
1168
  if (driftBlock) violations.push(`Drift triggers (${driftHits.length}): ${driftHits.map((h) => `"${h.trigger}" → ${h.memory}`).join(' | ')}`);
833
1169
  if (codeBlock) violations.push(`Code quality: ${codeQualityHits.join('; ')}`);
@@ -836,11 +1172,102 @@ if (cog.count >= REQUIRED_LENSES) {
836
1172
  const phaseList = (activePlan?.phases || []).map((p) => `${p.id}:${p.summary?.slice(0, 60) || ''}`).join(' | ');
837
1173
  violations.push(`Aria-as-commander binding (#50): an active plan exists (planId=${activePlan?.planId || 'unknown'}, ${activePlan?.phases?.length || 0} phases) but this emit lacks a [PHASE_REPORT phase=<id> status=complete|in_progress|aborted evidence=<observable>] marker. Per the binding contract, every non-trivial emit while a plan is active must report which phase it's working on. Plan phases: ${phaseList}. Re-emit with a [PHASE_REPORT] marker stating which phase the work in this turn maps to.`);
838
1174
  }
1175
+ if (substrateBlock) {
1176
+ violations.push(`Substrate Mizan + 8-lens BLOCK — violations: [${substrateViolations.join('; ')}]. Substrate gate triggers: [${substrateGateTriggers.join(', ')}]. Re-draft addressing these substrate-side issues.`);
1177
+ } else if (substrateViolations.length > 0) {
1178
+ // warn-level surfaced as advisory, not block
1179
+ violations.push(`Substrate Mizan WARN (advisory, not blocking): ${substrateViolations.join('; ')}`);
1180
+ }
839
1181
  const rewritten = mizanVerdict?.rewritten || '';
840
1182
 
841
- const reason = `Aria Stop-gate output-quality block. Cognition passed (${cog.count}/${REQUIRED_LENSES}) but output failed quality gates:\n\n${violations.join('\n\n')}${rewritten ? `\n\nMizan rewrite suggestion:\n${rewritten}` : ''}\n\nRe-draft addressing the violations above. No process-level disable path gates are unconditional from the gated process per Hamza directive 2026-04-27.`;
1183
+ // Hive recipe lookup BEFORE emitting the stop-gate blocksame lookup
1184
+ // semantics as aria-pre-tool-gate.mjs's binding-violation path. The
1185
+ // detector_class is chosen by the dominant violation: drift triggers
1186
+ // map to doctrine_violation, mizan to design_violation, code to
1187
+ // coding_defect, discoveries to doctrine_violation. Lookup is
1188
+ // fail-soft via 3s detection probe.
1189
+ const recipeAddendum = await (async () => {
1190
+ const detectorClass = driftBlock || discoveryBlock
1191
+ ? 'doctrine_violation'
1192
+ : codeBlock
1193
+ ? 'coding_defect'
1194
+ : (mizanBlock || substrateBlock || implCouplingBlock)
1195
+ ? 'design_violation'
1196
+ : 'doctrine_violation';
1197
+ const sigParts = [];
1198
+ if (driftBlock) sigParts.push(`drift::${driftHits.slice(0, 3).map((h) => h.trigger).join('|')}`);
1199
+ if (mizanBlock) sigParts.push(`mizan::${(mizanVerdict.violations || []).slice(0, 3).join('|')}`);
1200
+ if (codeBlock) sigParts.push(`code::${codeQualityHits.slice(0, 3).join('|')}`);
1201
+ if (discoveryBlock) sigParts.push(`discovery::${ledgerOpenCount}-open`);
1202
+ if (substrateBlock) sigParts.push(`substrate::${substrateViolations.slice(0, 3).join('|')}`);
1203
+ if (implCouplingBlock) sigParts.push(`impl-coupling::${implCouplingHits.slice(0, 2).join('|')}`);
1204
+ const signature = sigParts.join('::').slice(0, 512);
1205
+ if (!signature) return '';
1206
+
1207
+ const ariaSoulUrl =
1208
+ process.env.ARIA_HIVE_RUNTIME_URL ||
1209
+ process.env.ARIA_SOUL_URL ||
1210
+ process.env.ARIA_HARNESS_BASE_URL ||
1211
+ process.env.ARIA_HARNESS_URL ||
1212
+ 'https://harness.ariasos.com';
1213
+ const lookupUrl = new URL(`${ariaSoulUrl}/api/hive/block-pattern`);
1214
+ lookupUrl.searchParams.set('action', 'lookup');
1215
+ lookupUrl.searchParams.set('detector_class', detectorClass);
1216
+ lookupUrl.searchParams.set('pattern_signature', signature);
1217
+ const tenantId = event.session_id || '';
1218
+ if (tenantId) lookupUrl.searchParams.set('tenant_id', tenantId);
1219
+ const harnessToken = process.env.ARIA_HARNESS_TOKEN || (isOwnerTier() ? (process.env.ARIA_MASTER_TOKEN || process.env.ARIA_API_KEY || '') : '');
1220
+
1221
+ const ctl = new AbortController();
1222
+ const probeTimer = setTimeout(() => ctl.abort(), 3000);
1223
+ try {
1224
+ const resp = await fetch(lookupUrl.toString(), {
1225
+ method: 'GET',
1226
+ headers: harnessToken ? { Authorization: `Bearer ${harnessToken}` } : {},
1227
+ signal: ctl.signal,
1228
+ });
1229
+ if (!resp.ok) return '';
1230
+ const body = await resp.json();
1231
+ if (!body || body.found !== true) return '';
1232
+ const recipe = body.recipe;
1233
+ const freq = Number(body.frequency || 0);
1234
+ if (recipe && typeof recipe === 'object' && Number(recipe.confidence ?? 0) >= 0.7) {
1235
+ const text = typeof recipe.recipe_text === 'string' ? recipe.recipe_text.slice(0, 800) : '';
1236
+ const actions = Array.isArray(recipe.recipe_actions) ? recipe.recipe_actions : [];
1237
+ const actionsLine = actions.length
1238
+ ? `\n Actions: ${JSON.stringify(actions).slice(0, 600)}`
1239
+ : '';
1240
+ const conf = Number(recipe.confidence).toFixed(2);
1241
+ const seenLine = freq > 0 ? ` (pattern seen ${freq}× across the hive)` : '';
1242
+ return `\n\n📚 HIVE RECIPE${seenLine}:\n ${text}${actionsLine}\n Confidence: ${conf}. Apply this BEFORE re-emitting — the hive learned this fix from prior firings.`;
1243
+ }
1244
+ if (freq >= 3) {
1245
+ const sr = (typeof body.success_rate === 'number')
1246
+ ? ` Past resolution rate: ${(body.success_rate * 100).toFixed(0)}%.`
1247
+ : '';
1248
+ return `\n\n📓 Hive note: this stop-gate shape has fired ${freq} time(s); recipe still being learned (no high-confidence fix yet).${sr}`;
1249
+ }
1250
+ return '';
1251
+ } catch {
1252
+ return '';
1253
+ } finally {
1254
+ clearTimeout(probeTimer);
1255
+ }
1256
+ })();
1257
+
1258
+ const reason = `Aria Stop-gate output-quality block. Cognition passed (${cog.count}/${REQUIRED_LENSES}) but output failed quality gates:\n\n${violations.join('\n\n')}${rewritten ? `\n\nMizan rewrite suggestion:\n${rewritten}` : ''}\n\nRe-draft addressing the violations above. No process-level disable path — gates are unconditional from the gated process per Hamza directive 2026-04-27.${recipeAddendum}`;
842
1259
 
843
- audit(`block-output-qc`, `mizan=${mizanBlock?'y':'n'} warn-reflect=${compelReflection?'y':'n'} drift=${driftHits.length} code=${codeQualityHits.length} discoveries-open=${ledgerOpenCount}`);
1260
+ audit(`block-output-qc`, `mizan=${mizanBlock?'y':'n'} warn-reflect=${compelReflection?'y':'n'} drift=${driftHits.length} code=${codeQualityHits.length} discoveries-open=${ledgerOpenCount} impl-coupling=${implCouplingHits.length}`);
1261
+ emitHarnessFooter({
1262
+ eventName: 'block_output_qc',
1263
+ lensCount: cog.count,
1264
+ chars: assistantText.length,
1265
+ driftCount: driftHits.length,
1266
+ mizanStatus: mizanVerdict ? mizanVerdict.severity : `unavailable(${mizanError || 'unknown'})`,
1267
+ discoveryOpenCount: ledgerOpenCount,
1268
+ codeCount: codeQualityHits.length,
1269
+ implCouplingCount: implCouplingHits.length,
1270
+ });
844
1271
  console.log(JSON.stringify({ decision: 'block', reason }));
845
1272
  process.exit(2);
846
1273
  }
@@ -850,6 +1277,16 @@ if (cog.count >= REQUIRED_LENSES) {
850
1277
  `mizan=${mizanVerdict ? mizanVerdict.severity : `unavailable(${mizanError || 'unknown'})`} ` +
851
1278
  `code=${codeQualityHits.length} discoveries-new=${newDiscoveries.length} ` +
852
1279
  `discoveries-open=${ledgerOpenCount}`);
1280
+ emitHarnessFooter({
1281
+ eventName: 'allow_output_qc',
1282
+ lensCount: cog.count,
1283
+ chars: assistantText.length,
1284
+ driftCount: driftHits.length,
1285
+ mizanStatus: mizanVerdict ? mizanVerdict.severity : `unavailable(${mizanError || 'unknown'})`,
1286
+ discoveryOpenCount: ledgerOpenCount,
1287
+ codeCount: codeQualityHits.length,
1288
+ implCouplingCount: implCouplingHits.length,
1289
+ });
853
1290
  // Phase 11 #42: write this turn to harness garden pulse on allow-output-qc path.
854
1291
  await fireGardenTurn(event.session_id || 'claude-code', lastUserMessage, assistantText);
855
1292
  } else {
@@ -857,12 +1294,228 @@ if (cog.count >= REQUIRED_LENSES) {
857
1294
  `lenses=${cog.count} chars=${assistantText.length} ` +
858
1295
  `qPatt=${hasQuestionToUser ? 'y' : 'n'} substrateEv=${hasSubstrateEvidence ? 'y' : 'n'} ` +
859
1296
  (questionWithoutEvidence ? 'WARN-question-without-substrate' : 'ok'));
1297
+ emitHarnessFooter({
1298
+ eventName: 'allow_cognition',
1299
+ lensCount: cog.count,
1300
+ chars: assistantText.length,
1301
+ driftCount: 0,
1302
+ mizanStatus: 'not-run(short-turn)',
1303
+ discoveryOpenCount: 0,
1304
+ codeCount: 0,
1305
+ implCouplingCount: 0,
1306
+ });
860
1307
  // Phase 11 #42: write this turn to harness garden pulse on allow-cognition path.
861
1308
  await fireGardenTurn(event.session_id || 'claude-code', lastUserMessage, assistantText);
862
1309
  }
863
1310
  process.exit(0);
864
1311
  }
865
1312
 
1313
+ // ── Dalio Loop Layer 1 — expected_outcome enforcement + ledger write ──────────
1314
+ //
1315
+ // BEFORE allowing the stop:
1316
+ // 1. Scan the assistant text for <expected>...</expected> block.
1317
+ // 2. Determine whether any non-trivial action (tool_use blocks in this turn)
1318
+ // was taken. Detect via transcript tool_use blocks in the current turn.
1319
+ // 3. If a non-trivial action was taken AND <expected> is MISSING → BLOCK stop.
1320
+ // 4. Whether or not <expected> is present, POST a Dalio ledger entry to
1321
+ // aria-soul /api/decisions with outcome:'pending'. Also write to the
1322
+ // local JSONL mirror at ~/.claude/.aria-dalio-ledger.jsonl.
1323
+ // 5. If the POST fails: LOUD telemetry (console.error) + write local mirror
1324
+ // anyway. Do NOT block stop on POST failure per
1325
+ // feedback_canonical_secrets_governance.md LOUD-not-silent directive.
1326
+ //
1327
+ // Non-trivial action detection: look for tool_use content blocks in the
1328
+ // current-turn transcript entries (same backward-scan window used above).
1329
+ // Any Bash/Edit/Write/NotebookEdit tool_use counts as a non-trivial action.
1330
+ //
1331
+ // Substrate anchors: extracted from the cognition block body.
1332
+
1333
+ const DALIO_EXPECTED_BLOCK_RX = /<expected>([\s\S]*?)<\/expected>/i;
1334
+ const DALIO_QUALITATIVE_DRIFT_RX = /\b(?:better(?:er)?|improved?(?:ment)?|more\s+robust|should\s+(?:work|pass|succeed|run|fix)|more\s+reliable|cleaner|less\s+error[-_\s]?prone|nicer|smoother|faster[-\s]?loading|higher[-\s]?quality|more\s+stable|looks\s+(?:good|better|right))\b/i;
1335
+ const DALIO_MEASURABLE_PREDICATE_RX = /(?:>=|<=|==|!=|>|<|≥|≤)\s*\d+(?:\.\d+)?(?:ms|s|%|kb|mb|gb)?|\d+(?:\.\d+)?%(?:\s+(?:reduction|increase|success|error|coverage))?|exit[_=]\s*(?:0|1|\d+)|exit[-_]?code\s*[=:]\s*\d+|\brc\s*[=:]\s*\d+|\bstatus\s*[=:]\s*(?:running|healthy|ready|degraded|down|up|ok|200|201|204|400|401|403|404|500|502|503|504|true|false)\b|\bcount\s*[=:]\s*\d+|\berror[_-]?rate\s*[=:]\s*0%|\b(?:true|false)\b|\bfile[=_-]exists\b|\b200\s*OK\b|\bno[-_\s]?error|\bhealthy\b|\bpassed?\b|N\s*of\s*N|\d+\s*of\s*\d+/i;
1336
+ const NON_TRIVIAL_ACTION_TOOLS = new Set(['Bash', 'Edit', 'Write', 'NotebookEdit']);
1337
+
1338
+ // Detect non-trivial tool calls in the current turn from the transcript.
1339
+ let hadNonTrivialAction = false;
1340
+ let lastActionSummary = '';
1341
+ let immediateActual = '';
1342
+ if (transcriptPath && existsSync(transcriptPath)) {
1343
+ try {
1344
+ const lines = readFileSync(transcriptPath, 'utf-8').split('\n').filter(Boolean);
1345
+ let userBoundariesSeen = 0;
1346
+ for (let i = lines.length - 1; i >= 0 && userBoundariesSeen < 3; i--) {
1347
+ try {
1348
+ const m = JSON.parse(lines[i]);
1349
+ const role = m.message?.role ?? m.role;
1350
+ if (role === 'user') {
1351
+ const content = m.message?.content ?? m.content ?? [];
1352
+ const isToolResult = Array.isArray(content) &&
1353
+ content.length > 0 &&
1354
+ content.every((b) => b && b.type === 'tool_result');
1355
+ if (isToolResult) {
1356
+ // Capture the last tool_result content as immediate actual
1357
+ if (!immediateActual) {
1358
+ const textParts = content
1359
+ .map((b) => (typeof b.content === 'string' ? b.content : Array.isArray(b.content) ? b.content.map((c) => c.text || '').join(' ') : ''))
1360
+ .join(' ')
1361
+ .slice(0, 500);
1362
+ immediateActual = textParts;
1363
+ }
1364
+ continue;
1365
+ }
1366
+ userBoundariesSeen++;
1367
+ continue;
1368
+ }
1369
+ if (role !== 'assistant') continue;
1370
+ const content = m.message?.content ?? m.content ?? [];
1371
+ if (!Array.isArray(content)) continue;
1372
+ for (const block of content) {
1373
+ if (block && block.type === 'tool_use' && NON_TRIVIAL_ACTION_TOOLS.has(block.name)) {
1374
+ hadNonTrivialAction = true;
1375
+ if (!lastActionSummary) {
1376
+ const inp = block.input || {};
1377
+ lastActionSummary = `${block.name}: ${(inp.command || inp.file_path || inp.notebook_path || JSON.stringify(inp)).slice(0, 200)}`;
1378
+ }
1379
+ }
1380
+ }
1381
+ } catch {/* skip malformed entry */}
1382
+ }
1383
+ } catch {/* transcript unreadable — conservative: assume non-trivial */}
1384
+ }
1385
+
1386
+ // Extract substrate anchors from cognition
1387
+ const DALIO_ANCHOR_RX = /\b(axiom|frame|memory|doctrine|packet):[a-z0-9_\-./]+/gi;
1388
+ const dalioAnchors = [...(cog.names.length > 0 ? assistantText : '').matchAll(DALIO_ANCHOR_RX)]
1389
+ .map((m) => m[0])
1390
+ .slice(0, 20);
1391
+
1392
+ // Read the expected block from this turn
1393
+ const dalioExpectedMatch = assistantText.match(DALIO_EXPECTED_BLOCK_RX);
1394
+ const dalioExpectedText = dalioExpectedMatch ? dalioExpectedMatch[1].trim() : '';
1395
+ const dalioHasMeasurablePredicate = dalioExpectedText
1396
+ ? (DALIO_MEASURABLE_PREDICATE_RX.test(dalioExpectedText) && !DALIO_QUALITATIVE_DRIFT_RX.test(dalioExpectedText))
1397
+ : false;
1398
+
1399
+ // Block stop if non-trivial action taken AND expected block is missing
1400
+ if (hadNonTrivialAction && (!dalioExpectedMatch || !dalioHasMeasurablePredicate)) {
1401
+ const missingReason = dalioExpectedMatch
1402
+ ? `Aria Stop-gate: action taken in this turn had an <expected> block, but it contains only qualitative drift phrases without a measurable predicate. Qualitative drift is not accountability — it defeats the Dalio feedback loop.
1403
+
1404
+ Your <expected> block must contain at least one measurable predicate:
1405
+ • Numeric: exit_code==0, count>=1, error_rate=0%, latency<200ms
1406
+ • Boolean: exit=0, status=healthy, rc=0, file=exists
1407
+ • State-string: "status=running", "200 OK", "no_error"
1408
+
1409
+ REJECTED phrases: "better", "improved", "should work", "more reliable", "cleaner"
1410
+
1411
+ Re-emit with a corrected <expected> block. Per doctrine:dalio_expected_required — no bypass path.`
1412
+ : `Aria Stop-gate: a non-trivial action (${lastActionSummary.slice(0, 120)}) was taken in this turn but no <expected> block was found in the assistant text.
1413
+
1414
+ Per doctrine:dalio_expected_required, every non-trivial action must declare what measurable outcome it expects BEFORE the action fires. This is Dalio Loop Layer 1 — without it, outcome comparison is impossible and learning collapses.
1415
+
1416
+ Add to your assistant text:
1417
+
1418
+ <expected>
1419
+ predicate: <exact measurable assertion — e.g. "exit_code==0", "status=running", "count=3 of 3">
1420
+ measurable_type: numeric | boolean | state_string
1421
+ threshold: <optional>
1422
+ eval_window_minutes: <optional>
1423
+ </expected>
1424
+
1425
+ No bypass — pre-tool-gate enforces this BEFORE the action; stop-gate enforces it AFTER. Both gates are now wired.`;
1426
+
1427
+ audit('block-dalio-expected-missing', `hadNonTrivialAction=${hadNonTrivialAction} expectedPresent=${!!dalioExpectedMatch} measurable=${dalioHasMeasurablePredicate}`);
1428
+ emitHarnessFooter({
1429
+ eventName: 'block_dalio_expected_missing',
1430
+ lensCount: cog.count,
1431
+ chars: assistantText.length,
1432
+ driftCount: 0,
1433
+ mizanStatus: 'not-run(expected-missing)',
1434
+ discoveryOpenCount: 0,
1435
+ codeCount: 0,
1436
+ implCouplingCount: 0,
1437
+ });
1438
+ console.log(JSON.stringify({ decision: 'block', reason: missingReason }));
1439
+ process.exit(2);
1440
+ }
1441
+
1442
+ // Dalio ledger write — fire-and-forget HTTP POST + local JSONL mirror.
1443
+ // Per feedback_canonical_secrets_governance.md: errors are LOUD (console.error),
1444
+ // never silent. POST failure does NOT block stop — local mirror is always written.
1445
+ // Per feedback_no_timeouts_doctrine.md: no AbortController/setTimeout timeout.
1446
+ {
1447
+ const DALIO_LEDGER_PATH = `${HOME}/.claude/.aria-dalio-ledger.jsonl`;
1448
+ const ARIA_SOUL_DECISIONS_URL = 'http://aria-soul.aria.svc.cluster.local:8080/api/decisions';
1449
+
1450
+ const ledgerEntry = {
1451
+ ts: new Date().toISOString(),
1452
+ session_id: event.session_id || 'claude-code',
1453
+ decision_type: 'turn_action',
1454
+ category: 'agentic_execution',
1455
+ context: lastActionSummary || `stop-gate turn (chars=${assistantText.length})`,
1456
+ decision: lastActionSummary || 'turn completed',
1457
+ reasoning: (cog.names.length > 0
1458
+ ? `Cognition lenses applied: ${cog.names.join(', ')}. Turn-scoped cognition present.`
1459
+ : 'No explicit cognition block in turn.'),
1460
+ outcome: 'pending',
1461
+ outcome_details: {
1462
+ expected: dalioExpectedText || null,
1463
+ immediate_actual: immediateActual || null,
1464
+ anchors: dalioAnchors,
1465
+ },
1466
+ expected_outcome: dalioExpectedText
1467
+ ? {
1468
+ predicate: dalioExpectedText.slice(0, 500),
1469
+ measurable_type: 'state_string',
1470
+ }
1471
+ : null,
1472
+ source: 'claude-code-stop-gate',
1473
+ model_used: 'claude-opus-4-7',
1474
+ };
1475
+
1476
+ // Write to local JSONL mirror first — always succeeds or logs loudly
1477
+ try {
1478
+ if (!existsSync(dirname(DALIO_LEDGER_PATH))) mkdirSync(dirname(DALIO_LEDGER_PATH), { recursive: true });
1479
+ appendFileSync(DALIO_LEDGER_PATH, JSON.stringify(ledgerEntry) + '\n');
1480
+ } catch (ledgerWriteErr) {
1481
+ console.error(
1482
+ `[aria-stop-gate] DALIO LEDGER WRITE FAILED — local mirror at ${DALIO_LEDGER_PATH} not written. ` +
1483
+ `Error: ${ledgerWriteErr instanceof Error ? ledgerWriteErr.message : String(ledgerWriteErr)}`,
1484
+ );
1485
+ }
1486
+
1487
+ // POST to aria-soul decision API — fire-and-forget, but LOUD on error
1488
+ const dalioHarnessToken = process.env.ARIA_HARNESS_TOKEN
1489
+ || (isOwnerTier() ? (process.env.ARIA_MASTER_TOKEN || process.env.ARIA_API_KEY || '') : '');
1490
+
1491
+ fetch(ARIA_SOUL_DECISIONS_URL, {
1492
+ method: 'POST',
1493
+ headers: {
1494
+ 'Content-Type': 'application/json',
1495
+ ...(dalioHarnessToken ? { Authorization: `Bearer ${dalioHarnessToken}` } : {}),
1496
+ },
1497
+ body: JSON.stringify(ledgerEntry),
1498
+ }).then((resp) => {
1499
+ if (!resp.ok) {
1500
+ // LOUD telemetry per feedback_canonical_secrets_governance.md
1501
+ console.error(
1502
+ `[aria-stop-gate] DALIO POST FAILED — aria-soul responded HTTP ${resp.status}. ` +
1503
+ `Local mirror written to ${DALIO_LEDGER_PATH}. Session: ${ledgerEntry.session_id}`,
1504
+ );
1505
+ audit('dalio-post-failed', `http=${resp.status} session=${ledgerEntry.session_id}`);
1506
+ } else {
1507
+ audit('dalio-post-ok', `session=${ledgerEntry.session_id} action=${lastActionSummary.slice(0, 80)}`);
1508
+ }
1509
+ }).catch((err) => {
1510
+ // Network failure — LOUD, never silent
1511
+ console.error(
1512
+ `[aria-stop-gate] DALIO POST NETWORK ERROR — could not reach ${ARIA_SOUL_DECISIONS_URL}. ` +
1513
+ `Local mirror written to ${DALIO_LEDGER_PATH}. Error: ${err instanceof Error ? err.message : String(err)}`,
1514
+ );
1515
+ audit('dalio-post-network-err', `err=${String(err).slice(0, 200)} session=${ledgerEntry.session_id}`);
1516
+ });
1517
+ }
1518
+
866
1519
  // Block — non-trivial response without 4+ substantive lenses.
867
1520
  const reason = `Aria Stop-gate: non-trivial assistant response without 4+ substantive cognition lenses. Found ${cog.count}/${REQUIRED_LENSES}+ (lenses: ${cog.names.join(', ') || 'none'}). Doctrine is action-coupled — text decisions ARE actions, and reflexive replies fail this gate the same way reflexive Bash does.
868
1521
 
@@ -884,5 +1537,15 @@ The block reflects work done BEFORE drafting. Don't emit it as ceremony; apply e
884
1537
  No per-command bypass (mirrors aria-pre-tool-gate.mjs v3 doctrine). No env-var disable path either — gates are unconditional from the gated process per Hamza directive 2026-04-27. If the gate misfires on legitimate cognition, fix the gate.`;
885
1538
 
886
1539
  audit(`block`, `lenses=${cog.count}/${REQUIRED_LENSES} chars=${assistantText.length}`);
1540
+ emitHarnessFooter({
1541
+ eventName: 'block_lens_missing',
1542
+ lensCount: cog.count,
1543
+ chars: assistantText.length,
1544
+ driftCount: 0,
1545
+ mizanStatus: 'not-run(lens-missing)',
1546
+ discoveryOpenCount: 0,
1547
+ codeCount: 0,
1548
+ implCouplingCount: 0,
1549
+ });
887
1550
  console.log(JSON.stringify({ decision: 'block', reason }));
888
1551
  process.exit(2);