@aria_asi/cli 0.2.36 → 0.2.37

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 (195) 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 +310 -10
  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 +33 -8
  53. package/dist/assets/hooks/aria-preprompt-consult.mjs +5 -6
  54. package/dist/assets/hooks/aria-preturn-memory-gate.mjs +5 -0
  55. package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +15 -0
  56. package/dist/assets/hooks/aria-stop-gate.mjs +125 -17
  57. package/dist/assets/hooks/doctrine_trigger_map.json +11 -0
  58. package/dist/assets/hooks/lib/emergency-gateoff-impl.mjs +39 -0
  59. package/dist/assets/hooks/lib/emergency-gateoff.mjs +6 -0
  60. package/dist/assets/hooks/lib/first-class-coach.mjs +755 -0
  61. package/dist/assets/hooks/lib/skill-autoload-gate-impl.mjs +103 -0
  62. package/dist/assets/hooks/lib/skill-autoload-gate.mjs +1 -14
  63. package/dist/assets/opencode-plugins/harness-context/auth-token.mjs +126 -0
  64. package/dist/assets/opencode-plugins/harness-context/inject-context.mjs +62 -22
  65. package/dist/assets/opencode-plugins/harness-context/task-project-ledger.mjs +290 -0
  66. package/dist/assets/opencode-plugins/harness-gate/index.js +87 -27
  67. package/dist/assets/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +1 -14
  68. package/dist/assets/opencode-plugins/harness-outcome/index.js +29 -24
  69. package/dist/assets/opencode-plugins/harness-stop/index.js +229 -68
  70. package/dist/assets/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +1 -14
  71. package/dist/runtime/auth-token.mjs +121 -0
  72. package/dist/runtime/coach-kernel.mjs +371 -0
  73. package/dist/runtime/codex-bridge.mjs +440 -69
  74. package/dist/runtime/discipline/doctrine_trigger_map.json +11 -0
  75. package/dist/runtime/discipline/skills/aria-cognition/aria-essence/SKILL.md +18 -0
  76. package/dist/runtime/discipline/skills/aria-cognition/aria-forge-guardrails/SKILL.md +18 -0
  77. package/dist/runtime/discipline/skills/aria-cognition/aria-repo-doctrine/SKILL.md +18 -0
  78. package/dist/runtime/discipline/skills/aria-cognition/forge-quality-rules/SKILL.md +18 -0
  79. package/dist/runtime/discipline/skills/aria-cognition/ghazali-8lens/SKILL.md +18 -0
  80. package/dist/runtime/discipline/skills/aria-cognition/istiqra-induction/SKILL.md +18 -0
  81. package/dist/runtime/discipline/skills/aria-cognition/ladunni-22/SKILL.md +18 -0
  82. package/dist/runtime/discipline/skills/aria-cognition/mizan/SKILL.md +18 -0
  83. package/dist/runtime/discipline/skills/aria-cognition/nadia/SKILL.md +18 -0
  84. package/dist/runtime/discipline/skills/aria-cognition/nadia-psi/SKILL.md +18 -0
  85. package/dist/runtime/discipline/skills/aria-cognition/predictor/SKILL.md +18 -0
  86. package/dist/runtime/discipline/skills/aria-cognition/qiyas-analogy/SKILL.md +18 -0
  87. package/dist/runtime/discipline/skills/aria-cognition/soul-domains/SKILL.md +18 -0
  88. package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-intra-phase/SKILL.md +18 -0
  89. package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-post-phase/SKILL.md +18 -0
  90. package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-pre-phase/SKILL.md +18 -0
  91. package/dist/runtime/discipline/skills/aria-harness/aria-harness-deploy/SKILL.md +18 -0
  92. package/dist/runtime/discipline/skills/aria-harness/aria-harness-no-stripping/SKILL.md +18 -0
  93. package/dist/runtime/discipline/skills/aria-harness/aria-harness-onboarding/SKILL.md +18 -0
  94. package/dist/runtime/discipline/skills/aria-harness/aria-harness-output-discipline/SKILL.md +18 -0
  95. package/dist/runtime/discipline/skills/aria-harness/aria-harness-substrate-binding/SKILL.md +18 -0
  96. package/dist/runtime/doctrine_trigger_map.json +11 -0
  97. package/dist/runtime/hooks/aria-cognition-substrate-binding.mjs +51 -9
  98. package/dist/runtime/hooks/aria-first-class-coach.mjs +129 -0
  99. package/dist/runtime/hooks/aria-harness-via-sdk.mjs +33 -6
  100. package/dist/runtime/hooks/aria-pre-tool-gate.mjs +33 -8
  101. package/dist/runtime/hooks/aria-preprompt-consult.mjs +5 -6
  102. package/dist/runtime/hooks/aria-preturn-memory-gate.mjs +5 -0
  103. package/dist/runtime/hooks/aria-repo-doctrine-gate.mjs +15 -0
  104. package/dist/runtime/hooks/aria-stop-gate.mjs +125 -17
  105. package/dist/runtime/hooks/doctrine_trigger_map.json +11 -0
  106. package/dist/runtime/hooks/lib/emergency-gateoff-impl.mjs +39 -0
  107. package/dist/runtime/hooks/lib/emergency-gateoff.mjs +6 -0
  108. package/dist/runtime/hooks/lib/first-class-coach.mjs +755 -0
  109. package/dist/runtime/hooks/lib/skill-autoload-gate-impl.mjs +103 -0
  110. package/dist/runtime/hooks/lib/skill-autoload-gate.mjs +1 -14
  111. package/dist/runtime/local-phase.mjs +8 -0
  112. package/dist/runtime/manifest.json +2 -2
  113. package/dist/runtime/provider-proxy.mjs +136 -33
  114. package/dist/runtime/sdk/BUNDLED.json +2 -2
  115. package/dist/runtime/sdk/auth.d.ts +17 -0
  116. package/dist/runtime/sdk/auth.js +158 -0
  117. package/dist/runtime/sdk/auth.js.map +1 -0
  118. package/dist/runtime/sdk/index.d.ts +8 -1
  119. package/dist/runtime/sdk/index.js +15 -1
  120. package/dist/runtime/sdk/index.js.map +1 -1
  121. package/dist/runtime/service.mjs +1711 -74
  122. package/dist/runtime/task-project-ledger.mjs +290 -0
  123. package/dist/sdk/BUNDLED.json +2 -2
  124. package/dist/sdk/auth.d.ts +17 -0
  125. package/dist/sdk/auth.js +158 -0
  126. package/dist/sdk/auth.js.map +1 -0
  127. package/dist/sdk/index.d.ts +8 -1
  128. package/dist/sdk/index.js +15 -1
  129. package/dist/sdk/index.js.map +1 -1
  130. package/hooks/aria-cognition-substrate-binding.mjs +51 -9
  131. package/hooks/aria-first-class-coach.mjs +129 -0
  132. package/hooks/aria-harness-via-sdk.mjs +33 -6
  133. package/hooks/aria-pre-tool-gate.mjs +33 -8
  134. package/hooks/aria-preprompt-consult.mjs +5 -6
  135. package/hooks/aria-preturn-memory-gate.mjs +5 -0
  136. package/hooks/aria-repo-doctrine-gate.mjs +15 -0
  137. package/hooks/aria-stop-gate.mjs +125 -17
  138. package/hooks/doctrine_trigger_map.json +11 -0
  139. package/hooks/lib/emergency-gateoff-impl.mjs +39 -0
  140. package/hooks/lib/emergency-gateoff.mjs +6 -0
  141. package/hooks/lib/first-class-coach.mjs +755 -0
  142. package/hooks/lib/skill-autoload-gate-impl.mjs +103 -0
  143. package/hooks/lib/skill-autoload-gate.mjs +1 -14
  144. package/opencode-plugins/harness-context/auth-token.mjs +126 -0
  145. package/opencode-plugins/harness-context/inject-context.mjs +62 -22
  146. package/opencode-plugins/harness-context/task-project-ledger.mjs +290 -0
  147. package/opencode-plugins/harness-gate/index.js +87 -27
  148. package/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +1 -14
  149. package/opencode-plugins/harness-outcome/index.js +29 -24
  150. package/opencode-plugins/harness-stop/index.js +229 -68
  151. package/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +1 -14
  152. package/package.json +8 -2
  153. package/runtime-src/auth-token.mjs +121 -0
  154. package/runtime-src/coach-kernel.mjs +371 -0
  155. package/runtime-src/codex-bridge.mjs +440 -69
  156. package/runtime-src/local-phase.mjs +8 -0
  157. package/runtime-src/provider-proxy.mjs +136 -33
  158. package/runtime-src/service.mjs +1711 -74
  159. package/scripts/bundle-sdk.mjs +8 -0
  160. package/scripts/check-client-compatibility.mjs +422 -0
  161. package/scripts/check-coach-kernel.mjs +204 -0
  162. package/scripts/check-managed-runtime-ledger.mjs +107 -0
  163. package/scripts/check-opencode-config-contract.mjs +78 -0
  164. package/scripts/check-quality-ledger.mjs +121 -0
  165. package/scripts/self-test-harness-gates.mjs +179 -11
  166. package/scripts/self-test-repo-guard.mjs +38 -0
  167. package/scripts/validate-skill-prompts.mjs +14 -1
  168. package/skills/aria-cognition/aria-essence/SKILL.md +18 -0
  169. package/skills/aria-cognition/aria-forge-guardrails/SKILL.md +18 -0
  170. package/skills/aria-cognition/aria-repo-doctrine/SKILL.md +18 -0
  171. package/skills/aria-cognition/forge-quality-rules/SKILL.md +18 -0
  172. package/skills/aria-cognition/ghazali-8lens/SKILL.md +18 -0
  173. package/skills/aria-cognition/istiqra-induction/SKILL.md +18 -0
  174. package/skills/aria-cognition/ladunni-22/SKILL.md +18 -0
  175. package/skills/aria-cognition/mizan/SKILL.md +18 -0
  176. package/skills/aria-cognition/nadia/SKILL.md +18 -0
  177. package/skills/aria-cognition/nadia-psi/SKILL.md +18 -0
  178. package/skills/aria-cognition/predictor/SKILL.md +18 -0
  179. package/skills/aria-cognition/qiyas-analogy/SKILL.md +18 -0
  180. package/skills/aria-cognition/soul-domains/SKILL.md +18 -0
  181. package/src/auth.ts +136 -1
  182. package/src/chat.ts +13 -8
  183. package/src/config.ts +6 -1
  184. package/src/connectors/claude-code.ts +62 -18
  185. package/src/connectors/codex.ts +308 -10
  186. package/src/connectors/opencode.ts +35 -12
  187. package/src/connectors/repo-guard.ts +117 -172
  188. package/src/connectors/runtime.ts +19 -7
  189. package/src/connectors/shell.ts +12 -8
  190. package/src/harness-client.ts +8 -22
  191. package/src/model-context.ts +6 -0
  192. package/src/providers/types.ts +1 -1
  193. package/src/providers/xai.ts +55 -0
  194. package/src/setup-wizard.ts +1 -0
  195. package/src/types.ts +2 -0
@@ -2,13 +2,19 @@
2
2
  * Aria Harness Stop — text-emission gate via HTTPHarnessClient SDK.
3
3
  * Routes text through Mizan validateOutput() for substrate-backed QC.
4
4
  */
5
- import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
5
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, appendFileSync } from 'node:fs';
6
6
  import { createHash } from 'node:crypto';
7
7
  import { spawnSync } from 'node:child_process';
8
8
  import { homedir } from 'node:os';
9
- import { join } from 'node:path';
9
+ import { dirname, join } from 'node:path';
10
10
  import { analyzeDomainOutputQuality, extractCodeBlocks } from './lib/domain-output-quality.js';
11
11
  import { evaluateSkillGate, formatSkillGateBlock } from './lib/skill-autoload-gate.js';
12
+ import {
13
+ evaluateTaskProjectClaim,
14
+ recordBlockedTaskProjectClaim,
15
+ updateTaskProjectLedger,
16
+ } from '../harness-context/task-project-ledger.mjs';
17
+ import { resolveAriaAuthToken } from '../harness-context/auth-token.mjs';
12
18
 
13
19
  const HOME = homedir();
14
20
  const SDK_CANDIDATES = [
@@ -16,11 +22,12 @@ const SDK_CANDIDATES = [
16
22
  join(HOME, '.codex', 'aria-sdk', 'index.js'),
17
23
  join(HOME, '.claude', 'aria-sdk', 'index.js'),
18
24
  ];
19
- const OWNER_TOKEN_PATH = join(HOME, '.aria', 'owner-token');
20
- const LICENSE_PATH = join(HOME, '.aria', 'license.json');
21
25
  const GOVERNANCE_GATE_PATH = join(HOME, '.aria', 'bin', 'aria-governance-gate');
26
+ const CURRENT_RECOVERY_PATH = join(HOME, '.aria', 'governance-recovery-current.json');
22
27
  const RUNTIME_URL = (process.env.ARIA_RUNTIME_URL || 'http://127.0.0.1:4319').replace(/\/+$/, '');
23
28
  const RECEIPT_DIR = join(HOME, '.opencode', 'aria-mizan-receipts');
29
+ const RECOVERY_LOG_PATH = join(HOME, '.opencode', 'aria-governance-recovery.jsonl');
30
+ const RECOVERY_NOTICE_INTERVAL_MS = Number(process.env.ARIA_RECOVERY_NOTICE_INTERVAL_MS || 30000);
24
31
 
25
32
  const LENS_NAMES = [
26
33
  'nur', 'mizan', 'hikma', 'tafakkur', 'tadabbur', 'ilham', 'wahi', 'firasah',
@@ -34,23 +41,154 @@ const NON_TRIVIAL_MIN_CHARS = 300;
34
41
  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;
35
42
  const TRIVIAL_ACK_RX = /^(?:got it|on it|ok|sure|yes|no|done|ack)\b/i;
36
43
  const PLACEHOLDER_RX = /^\s*<[^<>]+>\s*$/;
37
- const BLOCK_PREFIX_RX = /^=== ARIA (?:MIZAN POST|OUTPUT GATE|LOCAL OUTPUT) BLOCK ===/;
44
+ const BLOCK_PREFIX_RX = /^=== ARIA (?:MIZAN POST|OUTPUT GATE|LOCAL OUTPUT|SKILL AUTOLOAD GATE|UNIVERSAL GOVERNANCE GATE) BLOCK ===/;
38
45
  const APPLIED_COGNITION_RX = /<applied_cognition>[\s\S]*?decision_delta\s*:[\s\S]*?dominant_domain\s*:[\s\S]*?binds_to\s*:[\s\S]*?expected_predicate\s*:[\s\S]*?artifact_change\s*:[\s\S]*?<\/applied_cognition>/i;
46
+ const HANDOFF_MODE_RX = /\b(?:post[_ -]?compact[_ -]?continuation|handoff[_ -]?resume|continuation[_ -]?handoff|post-compact|compact(?:ion)? handoff|post\s+commission)\b/i;
47
+ const HANDOFF_REQUIRED_FIELDS = [
48
+ { key: 'current_objective', label: 'current objective', rx: /\bcurrent\s+objective\b\s*:/i },
49
+ { key: 'known_blockers', label: 'known blockers', rx: /\bknown\s+blockers\b\s*:/i },
50
+ { key: 'next_executable_step', label: 'next executable step', rx: /\bnext\s+executable\s+step\b\s*:/i },
51
+ { key: 'what_not_to_touch', label: 'what not to touch', rx: /\bwhat\s+not\s+to\s+touch\b\s*:/i },
52
+ { key: 'verification_already_run', label: 'verification already run', rx: /\bverification\s+already\s+run\b\s*:/i },
53
+ ];
54
+ const FAKE_COMPLETION_CLAIM_RX = /\b(?:everything\s+is\s+)?(?:done|fixed|ready|complete|completed|shipped|production-ready|works in general)\b/i;
55
+ const PROOF_SIGNAL_RX = /\b(?:passed|exit\s*=?\s*0|status\s*[:=]\s*(?:ok|healthy)|readyReplicas\s*=|\d+\s+passed|green|verified\s*[:=-].{0,80}(?:npm|jest|test|build|typecheck|kubectl|curl|smoke))\b/i;
56
+ const GATEOFF_MARKER_PATH = join(HOME, '.aria', 'gates-off.manual');
57
+ const GATEOFF_ENV_RX = /^(?:1|true|yes|on)$/i;
58
+ const GATEOFF_PHRASE_RX = /\bGATEOFF\b/;
59
+
60
+ function textFromPayload(payload) {
61
+ if (!payload) return '';
62
+ if (typeof payload === 'string') return payload;
63
+ try { return JSON.stringify(payload); } catch { return String(payload); }
64
+ }
39
65
 
40
- function formatBlockReason(prefix, details) {
66
+ function persistEmergencyGateOff(reason = 'phrase') {
67
+ mkdirSync(dirname(GATEOFF_MARKER_PATH), { recursive: true, mode: 0o700 });
68
+ writeFileSync(GATEOFF_MARKER_PATH, JSON.stringify({
69
+ disabled: true,
70
+ reason,
71
+ disabledAt: new Date().toISOString(),
72
+ reenable: 'Manual owner action only: remove this marker and unset ARIA_GATES_OFF/ARIA_HARNESS_GATES_OFF.',
73
+ }, null, 2) + '\n', { mode: 0o600 });
74
+ }
75
+
76
+ function emergencyGateOffDecision(payload = '') {
77
+ if (GATEOFF_ENV_RX.test(String(process.env.ARIA_GATES_OFF || process.env.ARIA_HARNESS_GATES_OFF || ''))) {
78
+ return { off: true, reason: 'env' };
79
+ }
80
+ if (existsSync(GATEOFF_MARKER_PATH)) return { off: true, reason: 'marker' };
81
+ if (GATEOFF_PHRASE_RX.test(textFromPayload(payload))) {
82
+ persistEmergencyGateOff('phrase');
83
+ return { off: true, reason: 'phrase' };
84
+ }
85
+ return { off: false, reason: null };
86
+ }
87
+
88
+ function recoveryTemplate(kind = 'general') {
89
+ const handoff = kind === 'handoff';
90
+ return [
91
+ 'Recovery contract:',
92
+ '1. Do not retry the same blocked text.',
93
+ '2. Rewrite the output using this exact shape:',
94
+ handoff ? 'post_compact_continuation' : '<applied_cognition>',
95
+ handoff ? 'current objective: <one sentence of the still-active task>' : 'decision_delta: <what changed in this turn>',
96
+ handoff ? 'known blockers: <specific blockers, or "none verified" if truly none>' : 'dominant_domain: <code|deploy|security|testing|product|ops|writing>',
97
+ handoff ? 'next executable step: <single command/edit/read/action the next agent should run first>' : 'binds_to: <specific file, command result, endpoint, runtime, or user instruction>',
98
+ handoff ? 'what not to touch: <unrelated dirty files, secrets, protected systems, or deploys to avoid>' : 'expected_predicate: <measurable condition, e.g. exit=0, status=healthy, count=N>',
99
+ handoff ? 'verification already run: <commands/probes already run and exact result, or "not run">' : 'artifact_change: <exact artifact changed or "none">',
100
+ handoff ? '' : '</applied_cognition>',
101
+ '3. If the block was caused by missing proof, run the concrete command/probe first, then re-submit with the result.',
102
+ '4. If the block was caused by a missing skill/doctrine trigger, load the named skill, then re-write and re-test.',
103
+ '5. If two rewrites still block, escalate through ARIA console/MCP with the block text and stop repeating the same output.',
104
+ ].filter(Boolean).join('\n');
105
+ }
106
+
107
+ function formatBlockReason(prefix, details, options = {}) {
41
108
  return [
42
109
  prefix,
43
110
  '',
44
111
  String(details || 'Aria output gate blocked this message.'),
45
112
  '',
46
- 'Required repair: bind cognition to the actual action/output using <applied_cognition> with decision_delta, dominant_domain, binds_to, expected_predicate, and artifact_change.',
113
+ recoveryTemplate(options.kind || 'general'),
47
114
  ].join('\n');
48
115
  }
49
116
 
117
+ function recoveryFingerprint(reason, source = 'opencode-stop') {
118
+ return createHash('sha256')
119
+ .update([source, String(reason || '').replace(/\s+/g, ' ').trim().slice(0, 1200)].join('\0'))
120
+ .digest('hex');
121
+ }
122
+
123
+ function emitRecoverableOutput(reason, { source = 'opencode-stop', sessionId = 'opencode' } = {}) {
124
+ const fingerprint = recoveryFingerprint(reason, source);
125
+ const updatedAt = new Date().toISOString();
126
+ const recovery = {
127
+ schema: 'aria.governance_recovery_current.v1',
128
+ updatedAt,
129
+ deliveryRule: 'This file is injected into the next system prompt. Execute recoveryLoop.nextStep before any completion claim.',
130
+ ok: true,
131
+ decision: 'warn',
132
+ source,
133
+ governanceMode: 'recovery-required',
134
+ recoveryLoop: {
135
+ fingerprint,
136
+ allowedRecoveryAttempts: 1,
137
+ priorRecoveryAttempts: 0,
138
+ remainingRecoveryAttempts: 1,
139
+ nextStep: 'Re-author the blocked output using the recovery contract, run a concrete verification probe when tools are available, then emit the corrected output.',
140
+ architectFallback: 'If Aria consult is unavailable or this same fingerprint repeats, use the strongest available client LLM under the architect harness, produce and execute the recovery plan, then verify.',
141
+ },
142
+ recoveryContract: {
143
+ retry: 'One recovery attempt is allowed for this exact output-gate fingerprint; repeat failure enters architect execution mode.',
144
+ architectMode: 'On repeat, load architecture-decision and named domain skills, define the smallest executable plan, execute the first recovery action, and verify before output.',
145
+ fallbackWhenAriaUnavailable: 'Do not stop or ask the owner to fix it. Bind the strongest available client LLM to the architect harness, give it this JSON contract and failed evidence, execute its recovery plan, then verify.',
146
+ repairRecoveryCycle: ['re-author output according to the gate reason', 'verify corrected claim or state explicit unverified boundary'],
147
+ retest: 'Submit the revised output through the same output gate and run the concrete verification probe named by the recovery plan.',
148
+ blockedReason: reason,
149
+ },
150
+ };
151
+ mkdirSync(dirname(CURRENT_RECOVERY_PATH), { recursive: true, mode: 0o700 });
152
+ writeFileSync(CURRENT_RECOVERY_PATH, `${JSON.stringify(recovery, null, 2)}\n`, { mode: 0o600 });
153
+ mkdirSync(dirname(RECOVERY_LOG_PATH), { recursive: true, mode: 0o700 });
154
+ appendFileSync(RECOVERY_LOG_PATH, `${JSON.stringify(recovery)}\n`, { mode: 0o600 });
155
+
156
+ const noticeKey = `${source}:${fingerprint}`;
157
+ const nowMs = Date.now();
158
+ const lastNoticeAt = _lastRecoveryNoticeBySource.get(noticeKey) || 0;
159
+ if (nowMs - lastNoticeAt >= RECOVERY_NOTICE_INTERVAL_MS) {
160
+ _lastRecoveryNoticeBySource.set(noticeKey, nowMs);
161
+ const compactReason = String(reason || 'recovery required')
162
+ .replace(/\s+/g, ' ')
163
+ .replace(/^=+\s*ARIA\s*/i, '')
164
+ .slice(0, 160);
165
+ process.stderr.write(`[aria-governance] recovery-required source=${source} fingerprint=${fingerprint.slice(0, 12)} reason="${compactReason}" details=${CURRENT_RECOVERY_PATH}\n`);
166
+ }
167
+ return recovery;
168
+ }
169
+
50
170
  function isGateBlock(error) {
51
171
  return BLOCK_PREFIX_RX.test(String(error?.message || error || ''));
52
172
  }
53
173
 
174
+ export function classifyContinuationHandoff(text) {
175
+ const body = String(text || '');
176
+ const isContinuation = HANDOFF_MODE_RX.test(body);
177
+ const missingFields = HANDOFF_REQUIRED_FIELDS
178
+ .filter((field) => !field.rx.test(body))
179
+ .map((field) => field.label);
180
+ const completionClaimLines = body
181
+ .split(/\r?\n/)
182
+ .filter((line) => /\b(?:current\s+objective|summary|status)\b\s*:/i.test(line));
183
+ const hasFakeCompletionClaim = completionClaimLines.some((line) => FAKE_COMPLETION_CLAIM_RX.test(line)) && !PROOF_SIGNAL_RX.test(body);
184
+ return {
185
+ isContinuation,
186
+ ok: isContinuation && missingFields.length === 0 && !hasFakeCompletionClaim,
187
+ missingFields,
188
+ hasFakeCompletionClaim,
189
+ };
190
+ }
191
+
54
192
  function runUniversalGovernanceGate(payload) {
55
193
  if (!existsSync(GOVERNANCE_GATE_PATH)) return null;
56
194
  const child = spawnSync(GOVERNANCE_GATE_PATH, {
@@ -63,16 +201,22 @@ function runUniversalGovernanceGate(payload) {
63
201
  try { result = stdout ? JSON.parse(stdout) : null; } catch {}
64
202
  if (child.status !== 0 || result?.ok === false || result?.decision === 'block') {
65
203
  throw new Error(
66
- `=== ARIA UNIVERSAL GOVERNANCE GATE BLOCK ===\n\n` +
67
- `${stdout || child.stderr || 'aria-governance-gate blocked this output.'}`
204
+ formatBlockReason(
205
+ '=== ARIA UNIVERSAL GOVERNANCE GATE BLOCK ===',
206
+ stdout || child.stderr || 'aria-governance-gate blocked this output.',
207
+ )
68
208
  );
69
209
  }
210
+ if (result?.decision === 'warn' || result?.governanceMode === 'recovery-required' || result?.governanceMode === 'architectural-intervention-required') {
211
+ process.stderr.write(`[aria-governance] ${result.governanceMode || 'recovery-required'} details=${CURRENT_RECOVERY_PATH}\n`);
212
+ }
70
213
  return result;
71
214
  }
72
215
 
73
216
  let _client = null;
74
217
  let _clientError = null;
75
218
  let _lastMizanReceipt = null;
219
+ const _lastRecoveryNoticeBySource = new Map();
76
220
 
77
221
  function receiptPath(sessionId) {
78
222
  return join(RECEIPT_DIR, `${String(sessionId || 'opencode').replace(/[^a-zA-Z0-9_-]/g, '_')}.json`);
@@ -109,22 +253,7 @@ function makeEvidenceRef(kind, value, metadata = {}) {
109
253
  }
110
254
 
111
255
  function resolveToken() {
112
- if (process.env.ARIA_HARNESS_TOKEN) return process.env.ARIA_HARNESS_TOKEN;
113
- if (process.env.ARIA_API_KEY) return process.env.ARIA_API_KEY;
114
- if (process.env.ARIA_MASTER_TOKEN) return process.env.ARIA_MASTER_TOKEN;
115
- try {
116
- if (existsSync(OWNER_TOKEN_PATH)) {
117
- const v = readFileSync(OWNER_TOKEN_PATH, 'utf8').trim();
118
- if (v) return v;
119
- }
120
- } catch {}
121
- try {
122
- if (existsSync(LICENSE_PATH)) {
123
- const j = JSON.parse(readFileSync(LICENSE_PATH, 'utf8'));
124
- if (j.token) return j.token;
125
- }
126
- } catch {}
127
- return '';
256
+ return resolveAriaAuthToken({ baseUrl: resolveBaseUrl() }).token;
128
257
  }
129
258
 
130
259
  function resolveBaseUrl() {
@@ -255,11 +384,51 @@ export default async function HarnessStopPlugin(ctx) {
255
384
  process.stderr.write('[harness-stop] Active — SDK-backed text-emission gate\n');
256
385
 
257
386
  async function validateText(text, eventContext = {}) {
387
+ const sessionId = eventContext.sessionID || process.env.ARIA_SESSION_ID || process.env.OPENCODE_SESSION_ID || 'opencode';
388
+ const handoff = classifyContinuationHandoff(text);
389
+ if (handoff.isContinuation) {
390
+ if (!handoff.ok) {
391
+ const reasons = [
392
+ handoff.missingFields.length ? `missing fields: ${handoff.missingFields.join(', ')}` : '',
393
+ handoff.hasFakeCompletionClaim ? 'fake completion claim without proof' : '',
394
+ ].filter(Boolean).join('; ');
395
+ emitRecoverableOutput(formatBlockReason('=== ARIA LOCAL OUTPUT BLOCK ===', `post-compact continuation handoff malformed: ${reasons}`, { kind: 'handoff' }), { source: 'opencode-stop/handoff', sessionId });
396
+ return;
397
+ }
398
+ return;
399
+ }
400
+ const emergencyGateOff = emergencyGateOffDecision({ text, eventContext });
401
+ if (emergencyGateOff.off) {
402
+ return;
403
+ }
404
+ const ledgerResult = updateTaskProjectLedger({
405
+ platform: 'opencode',
406
+ phase: 'stop',
407
+ source: 'opencode-harness-stop',
408
+ event: { ...eventContext, text, sessionId, cwd: process.cwd() },
409
+ });
410
+ const ledgerClaim = evaluateTaskProjectClaim({ text, ledger: ledgerResult.ledger });
411
+ if (!ledgerClaim.ok) {
412
+ recordBlockedTaskProjectClaim({
413
+ ledger: ledgerResult.ledger,
414
+ paths: ledgerResult.paths,
415
+ text,
416
+ evaluation: ledgerClaim,
417
+ });
418
+ emitRecoverableOutput(formatBlockReason(
419
+ '=== ARIA TASK/PROJECT LEDGER BLOCK ===',
420
+ [
421
+ `ledger_id: ${ledgerResult.ledger.ledgerId}`,
422
+ `missing_readiness_gates: ${ledgerClaim.missingReadinessGates.join(', ') || 'none'}`,
423
+ `open_blockers: ${ledgerClaim.openBlockers.length}`,
424
+ ].join('\n')
425
+ ), { source: 'opencode-stop/task-project-ledger', sessionId });
426
+ return;
427
+ }
258
428
  if (!text || text.length < NON_TRIVIAL_MIN_CHARS) return;
259
429
  if (TRIVIAL_ACK_RX.test(text.trim())) return;
260
430
 
261
431
  const cog = detectCognitionLenses(text);
262
- const sessionId = eventContext.sessionID || process.env.ARIA_SESSION_ID || process.env.OPENCODE_SESSION_ID || 'opencode';
263
432
  runUniversalGovernanceGate({
264
433
  sessionId,
265
434
  sourceRuntime: 'opencode',
@@ -275,13 +444,9 @@ export default async function HarnessStopPlugin(ctx) {
275
444
  isOutputCloseout: true,
276
445
  autoLoadAvailable: false,
277
446
  });
278
- if (!skillGate.ok) {
279
- throw new Error(
280
- formatBlockReason(
281
- '=== ARIA SKILL AUTOLOAD GATE BLOCK ===',
282
- `${formatSkillGateBlock(skillGate)}\nRequired next step: load ${skillGate.missingSkills.join(', ')} before re-emitting this output.`,
283
- )
284
- );
447
+ if (!skillGate.ok && !skillGate.redirectOnly) {
448
+ emitRecoverableOutput(formatBlockReason('=== ARIA SKILL AUTOLOAD GATE BLOCK ===', formatSkillGateBlock(skillGate)), { source: 'opencode-stop/skill-autoload', sessionId });
449
+ return;
285
450
  }
286
451
  const outputRef = makeEvidenceRef('opencode_assistant_output', text.slice(0, 8000), { sessionId });
287
452
  try {
@@ -309,12 +474,14 @@ export default async function HarnessStopPlugin(ctx) {
309
474
  }
310
475
  if (mizan.receipt?.blocked || mizan.result?.fitrahVetoed || mizan.result?.reAuthorSignal) {
311
476
  const details = (mizan.result?.notes || ['post-phase blocked']).slice(0, 4).join(' | ');
312
- throw new Error(
313
- formatBlockReason('=== ARIA MIZAN POST BLOCK ===', details)
314
- );
477
+ emitRecoverableOutput(formatBlockReason('=== ARIA MIZAN POST BLOCK ===', details), { source: 'opencode-stop/mizan-post', sessionId });
478
+ return;
315
479
  }
316
480
  } catch (e) {
317
- if (isGateBlock(e)) throw e;
481
+ if (isGateBlock(e)) {
482
+ emitRecoverableOutput(e.message, { source: 'opencode-stop/mizan-post', sessionId });
483
+ return;
484
+ }
318
485
  process.stderr.write(`[harness-stop] mizan/post unavailable: ${e.message}\n`);
319
486
  }
320
487
 
@@ -329,26 +496,21 @@ export default async function HarnessStopPlugin(ctx) {
329
496
  sessionId,
330
497
  ));
331
498
  if (result.severity === 'block') {
332
- throw new Error(
333
- formatBlockReason(
334
- '=== ARIA OUTPUT GATE BLOCK ===',
335
- `${result.violations.length} violations: ${result.violations.join('; ').slice(0, 500)}`,
336
- )
337
- );
499
+ emitRecoverableOutput(formatBlockReason('=== ARIA OUTPUT GATE BLOCK ===', `${result.violations.length} violations: ${result.violations.join('; ').slice(0, 500)}`), { source: 'opencode-stop/validate-output', sessionId });
500
+ return;
338
501
  } else if (result.severity === 'warn') {
339
- throw new Error(
340
- formatBlockReason(
341
- '=== ARIA OUTPUT GATE BLOCK ===',
342
- `SDK returned warn for ${result.violations.length} violation(s): ${(result.violations || []).join('; ').slice(0, 500)}. Warnings are fail-closed on owner-facing output.`,
343
- )
344
- );
502
+ emitRecoverableOutput(formatBlockReason('=== ARIA OUTPUT GATE BLOCK ===', `SDK returned warn for ${result.violations.length} violation(s): ${(result.violations || []).join('; ').slice(0, 500)}. Warnings require forced recovery on owner-facing output.`), { source: 'opencode-stop/validate-output-warn', sessionId });
503
+ return;
345
504
  }
346
505
  if (result.gateTriggers?.length) {
347
506
  process.stderr.write(`[harness-stop] SDK triggers: ${result.gateTriggers.join(', ')}\n`);
348
507
  }
349
508
  return;
350
509
  } catch (e) {
351
- if (isGateBlock(e)) throw e;
510
+ if (isGateBlock(e)) {
511
+ emitRecoverableOutput(e.message, { source: 'opencode-stop/validate-output', sessionId });
512
+ return;
513
+ }
352
514
  process.stderr.write(`[harness-stop] SDK validateOutput failed: ${e.message} — falling through to local gate\n`);
353
515
  }
354
516
  }
@@ -380,31 +542,19 @@ export default async function HarnessStopPlugin(ctx) {
380
542
  } catch {}
381
543
 
382
544
  if (cog.count < REQUIRED_LENSES || driftHits.length >= 2) {
383
- throw new Error(
384
- formatBlockReason(
385
- '=== ARIA LOCAL OUTPUT BLOCK ===',
386
- `cognition=${cog.count}/${REQUIRED_LENSES}; drift=${driftHits.length}`,
387
- )
388
- );
545
+ emitRecoverableOutput(formatBlockReason('=== ARIA LOCAL OUTPUT BLOCK ===', `cognition=${cog.count}/${REQUIRED_LENSES}; drift=${driftHits.length}`), { source: 'opencode-stop/local-cognition-drift', sessionId });
546
+ return;
389
547
  }
390
548
 
391
549
  if (DECISION_SIGNAL_RX.test(text) && !APPLIED_COGNITION_RX.test(text)) {
392
- throw new Error(
393
- formatBlockReason(
394
- '=== ARIA LOCAL OUTPUT BLOCK ===',
395
- 'decision-bearing output lacks required applied_cognition binding fields',
396
- )
397
- );
550
+ emitRecoverableOutput(formatBlockReason('=== ARIA LOCAL OUTPUT BLOCK ===', 'decision-bearing output lacks required applied_cognition binding fields'), { source: 'opencode-stop/applied-cognition', sessionId });
551
+ return;
398
552
  }
399
553
 
400
554
  const domainQuality = analyzeDomainOutputQuality(text, { codeBlocks: extractCodeBlocks(text) });
401
555
  if (domainQuality.blockers.length > 0) {
402
- throw new Error(
403
- formatBlockReason(
404
- '=== ARIA LOCAL OUTPUT BLOCK ===',
405
- `domain output QA (${domainQuality.domains.join(', ') || 'general'}): ${domainQuality.blockers.join('; ')}`,
406
- )
407
- );
556
+ emitRecoverableOutput(formatBlockReason('=== ARIA LOCAL OUTPUT BLOCK ===', `domain output QA (${domainQuality.domains.join(', ') || 'general'}): ${domainQuality.blockers.join('; ')}`), { source: 'opencode-stop/domain-output-quality', sessionId });
557
+ return;
408
558
  }
409
559
 
410
560
  try {
@@ -432,6 +582,17 @@ export default async function HarnessStopPlugin(ctx) {
432
582
  source: 'opencode-stop-gate-runtime',
433
583
  model_used: process.env.OPENCODE_MODEL || 'opencode',
434
584
  });
585
+ updateTaskProjectLedger({
586
+ platform: 'opencode',
587
+ phase: 'post_turn',
588
+ source: 'opencode-harness-stop',
589
+ event: { ...eventContext, text, sessionId, cwd: process.cwd() },
590
+ evidence: {
591
+ post_turn: true,
592
+ output_ref: outputRef,
593
+ verification: false,
594
+ },
595
+ });
435
596
  } catch (e) {
436
597
  process.stderr.write(`[harness-stop] decision/log unavailable: ${e.message}\n`);
437
598
  }
@@ -1,14 +1 @@
1
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { tmpdir } from 'node:os';
4
- const RECEIPT_ROOT = process.env.ARIA_SKILL_RECEIPT_DIR || join(tmpdir(), 'aria-skill-receipts');
5
- const ALIASES = new Map([['deploy', 'aria-harness-deploy'], ['output', 'aria-harness-output-discipline'], ['repo', 'aria-repo-doctrine'], ['forge', 'aria-forge-guardrails']]);
6
- const RX = { deploy: /deploy-service\.sh|kubectl\s+(?:apply|set|rollout|delete|scale)|helm\s+upgrade|terraform\s+apply|docker\s+push/i, mutationTool: /^(?:edit|write|notebookedit|patch|apply_patch)$/i, mutation: /apply_patch|write file|edit file|modify|delete file|migration|handler|route|runtime|hook|plugin|\btest\b|smoke script/i, strip: /remove|delete|strip|drop|omit|disable|bypass|skip|stub|mock|fake|placeholder|temporary|quick scaffold|band-aid/i, readiness: /production-ready|ready for production|works in general|general readiness|client packages?|npm packages?|SDKs?|runtimes?|harnesses?|release-ready|ship-ready/i, narrow: /single flow|one flow|narrow e2e|covered flow|specific path|widget flow/i, completion: /done|complete|completed|ready|verified|fixed|shipped|implemented|production-ready/i, badProof: /but|except|caveat|remaining|not yet|still|separate|later|blocked|skipped|unresolved|follow-up|failed|failing|error|red|not run|could not verify|untested|no proof|missing proof|without proof/i, advisory: /non-blocking|warning only|warn only|advisory|fall through|falls through|fail open|soft fail|logged and continue|quality gate warning/i, success: /(?:verified|passed|success|successful|green|ok)\s*[:=\-].{0,120}(?:npm|node|playwright|jest|vitest|build|test|lint|typecheck|curl|kubectl|self-test|e2e|probe|smoke)|(?:npm|node|playwright|jest|vitest|build|test|lint|typecheck|curl|kubectl).{0,120}(?:passed|success|successful|green|exit\s*0)/i, resubmit: /re-?submission|resubmit/i, rewrite: /re-?write|rewrite|fix/i, retest: /re-?test|retest|rerun/i, aria: /ARIA console|Aria console|\/chat|aria-pipeline-mcp|aria_chat|escalat(?:e|ion).{0,80}ARIA/i };
7
- function normalizeSkillName(skill) { return ALIASES.get(String(skill || '').trim()) || String(skill || '').trim(); }
8
- function sessionDir(sessionId) { return join(RECEIPT_ROOT, encodeURIComponent(String(sessionId || 'unknown'))); }
9
- function readReceiptSkills(sessionId) { const dir = sessionDir(sessionId); if (!existsSync(dir)) return new Set(); const skills = new Set(); for (const name of readdirSync(dir)) { if (!name.endsWith('.json')) continue; try { const receipt = JSON.parse(readFileSync(join(dir, name), 'utf8')); if (receipt?.skill) skills.add(normalizeSkillName(receipt.skill)); } catch {} } return skills; }
10
- function readInlineSkills(text) { const skills = new Set(); const value = String(text || ''); for (const match of value.matchAll(/<skill_content\s+name=["']([^"']+)["']/gi)) skills.add(normalizeSkillName(match[1])); return skills; }
11
- export function recordSkillLoaded({ sessionId, skill, surface = 'unknown', metadata = {} } = {}) { const normalized = normalizeSkillName(skill); if (!normalized) throw new Error('recordSkillLoaded requires a skill name'); const dir = sessionDir(sessionId); mkdirSync(dir, { recursive: true }); const receipt = { skill: normalized, surface, metadata, recordedAt: new Date().toISOString() }; writeFileSync(join(dir, `${encodeURIComponent(normalized)}.json`), `${JSON.stringify(receipt, null, 2)}\n`); return receipt; }
12
- export function classifyRequiredSkills({ text = '', action = '', toolName = '', filePath = '', isDeploy = false, isMutation = false, isOutputCloseout = false } = {}) { const combined = [text, action, toolName, filePath].filter(Boolean).join('\n'); const required = new Set(); const reasons = []; const recoveryMissing = []; if (isDeploy || RX.deploy.test(combined)) { required.add('aria-harness-deploy'); required.add('aria-forge-guardrails'); reasons.push('deploy/shared-infrastructure action requires fail-closed deploy and forge guardrails'); } if (isMutation || RX.mutationTool.test(toolName)) { required.add('aria-repo-doctrine'); reasons.push('repository/runtime mutation requires repo doctrine'); } if (RX.strip.test(combined)) { required.add('aria-harness-no-stripping'); reasons.push('strip/remove/bypass language requires no-stripping gate'); } if (isOutputCloseout && RX.completion.test(combined)) { required.add('aria-harness-output-discipline'); reasons.push('owner-facing completion/readiness claim requires output discipline'); if (!RX.success.test(combined)) recoveryMissing.push('successful proof from a concrete command/probe'); } if (RX.readiness.test(combined)) { required.add('architecture-decision'); required.add('testing-strategy'); required.add('aria-forge-guardrails'); required.add('aria-harness-output-discipline'); reasons.push('broad production/package/SDK/runtime readiness claim requires architecture, testing, and forge guardrails'); } if (RX.readiness.test(combined) && RX.narrow.test(combined)) { required.add('testing-strategy'); required.add('aria-forge-guardrails'); reasons.push('narrow e2e proof cannot support broad readiness claim without readiness-matrix discipline'); } if (RX.completion.test(combined) && RX.badProof.test(combined)) { required.add('aria-harness-output-discipline'); required.add('aria-forge-guardrails'); reasons.push('completion claim with unresolved or failed proof requires recovery cycle'); if (!RX.resubmit.test(combined)) recoveryMissing.push('re-submission'); if (!RX.rewrite.test(combined)) recoveryMissing.push('re-write'); if (!RX.retest.test(combined)) recoveryMissing.push('re-test'); if (!RX.aria.test(combined)) recoveryMissing.push('ARIA console escalation'); } if (RX.advisory.test(combined)) { required.add('aria-forge-guardrails'); required.add('aria-harness-output-discipline'); reasons.push('advisory/fail-open gate language requires fail-closed hardening discipline'); } return { requiredSkills: [...required].sort(), reasons, recoveryMissing }; }
13
- export function evaluateSkillGate(options = {}) { const classified = classifyRequiredSkills(options); const text = [options.text, options.action].filter(Boolean).join('\n'); const loaded = new Set([...readReceiptSkills(options.sessionId), ...readInlineSkills(text)]); const missingSkills = classified.requiredSkills.filter((skill) => !loaded.has(skill)); const recoveryMissing = classified.recoveryMissing || []; return { ok: missingSkills.length === 0 && recoveryMissing.length === 0, surface: options.surface || 'unknown', sessionId: options.sessionId || 'unknown', requiredSkills: classified.requiredSkills, loadedSkills: [...loaded].sort(), missingSkills, recoveryMissing, reasons: classified.reasons, autoLoadAvailable: options.autoLoadAvailable === true }; }
14
- export function formatSkillGateBlock(result = {}) { const missing = Array.isArray(result.missingSkills) ? result.missingSkills : []; const recovery = Array.isArray(result.recoveryMissing) ? result.recoveryMissing : []; const reasons = Array.isArray(result.reasons) ? result.reasons : []; return ['=== ARIA SKILL AUTOLOAD GATE BLOCK ===', `surface: ${result.surface || 'unknown'}`, `missing_skills: ${missing.length ? missing.join(', ') : '(none)'}`, `missing_recovery_cycle: ${recovery.length ? recovery.join(', ') : '(none)'}`, `required_skills: ${(result.requiredSkills || []).join(', ') || '(none)'}`, reasons.length ? `reasons: ${reasons.join(' | ')}` : 'reasons: no classifier reason recorded', 'counter_action: re-submit, re-write, re-test, and escalate through ARIA console until successful proof exists. Do not downgrade this to an advisory warning.'].join('\n'); }
1
+ export * from '../../../hooks/lib/skill-autoload-gate.mjs';
@@ -0,0 +1,121 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+
6
+ const HOME = homedir();
7
+ const ARIA_DIR = join(HOME, '.aria');
8
+ const OWNER_TOKEN_PATH = join(ARIA_DIR, 'owner-token');
9
+ const LICENSE_PATH = join(ARIA_DIR, 'license.json');
10
+ const CLIENT_TOKEN_DIR = join(ARIA_DIR, 'harness-tokens');
11
+
12
+ function nonEmpty(value) {
13
+ return typeof value === 'string' && value.trim() ? value.trim() : '';
14
+ }
15
+
16
+ function readJson(path) {
17
+ try {
18
+ if (!existsSync(path)) return null;
19
+ const parsed = JSON.parse(readFileSync(path, 'utf8'));
20
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+
26
+ function readText(path) {
27
+ try {
28
+ if (!existsSync(path)) return '';
29
+ return readFileSync(path, 'utf8').trim();
30
+ } catch {
31
+ return '';
32
+ }
33
+ }
34
+
35
+ function sanitizeScope(value) {
36
+ const clean = String(value || '').trim().replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 96);
37
+ return clean || 'default';
38
+ }
39
+
40
+ function scopeFromBaseUrl(baseUrl) {
41
+ try {
42
+ const parsed = new URL(String(baseUrl || ''));
43
+ return `base-${createHash('sha256').update(`${parsed.protocol}//${parsed.host}`).digest('hex').slice(0, 16)}`;
44
+ } catch {
45
+ return 'default';
46
+ }
47
+ }
48
+
49
+ function hasExplicitClientScope(options) {
50
+ return Boolean(nonEmpty(options.clientId) || nonEmpty(options.tokenScope));
51
+ }
52
+
53
+ function inferScope(options, license) {
54
+ const explicit = nonEmpty(options.tokenScope) || nonEmpty(options.clientId);
55
+ if (explicit) return sanitizeScope(explicit);
56
+ const envScope = nonEmpty(process.env.ARIA_HARNESS_CLIENT_ID) || nonEmpty(process.env.ARIA_CLIENT_ID) || nonEmpty(process.env.ARIA_TENANT_ID);
57
+ if (envScope) return sanitizeScope(envScope);
58
+ const licenseScope = nonEmpty(license?.jti) || nonEmpty(license?.sub);
59
+ if (licenseScope) return sanitizeScope(licenseScope);
60
+ return sanitizeScope(scopeFromBaseUrl(nonEmpty(options.baseUrl)));
61
+ }
62
+
63
+ function persistedPath(scope) {
64
+ return join(CLIENT_TOKEN_DIR, `${sanitizeScope(scope)}.json`);
65
+ }
66
+
67
+ function readPersistedToken(scope) {
68
+ const parsed = readJson(persistedPath(scope));
69
+ return nonEmpty(parsed?.token);
70
+ }
71
+
72
+ function persistToken(scope, token, source) {
73
+ if (!token) return false;
74
+ try {
75
+ mkdirSync(CLIENT_TOKEN_DIR, { recursive: true, mode: 0o700 });
76
+ writeFileSync(
77
+ persistedPath(scope),
78
+ `${JSON.stringify({ scope, token, source, updatedAt: new Date().toISOString() }, null, 2)}\n`,
79
+ { encoding: 'utf8', mode: 0o600 },
80
+ );
81
+ return true;
82
+ } catch {
83
+ return false;
84
+ }
85
+ }
86
+
87
+ export function resolveAriaAuthToken(options = {}) {
88
+ const license = readJson(LICENSE_PATH);
89
+ const scope = inferScope(options, license);
90
+ const persist = options.persistToken !== false;
91
+
92
+ const explicit = nonEmpty(options.explicitToken);
93
+ if (explicit) {
94
+ return { token: explicit, source: 'explicit', scope, persisted: persist ? persistToken(scope, explicit, 'explicit') : false };
95
+ }
96
+
97
+ const envSources = [
98
+ ['env:ARIA_HARNESS_TOKEN', process.env.ARIA_HARNESS_TOKEN],
99
+ ['env:ARIA_API_KEY', process.env.ARIA_API_KEY],
100
+ ['env:ARIA_MASTER_TOKEN', process.env.ARIA_MASTER_TOKEN],
101
+ ];
102
+ for (const [source, value] of envSources) {
103
+ const token = nonEmpty(value);
104
+ if (!token) continue;
105
+ return { token, source, scope, persisted: persist ? persistToken(scope, token, source) : false };
106
+ }
107
+
108
+ const stored = readPersistedToken(scope);
109
+ if (stored) return { token: stored, source: 'persisted-client-token', scope, persisted: true };
110
+
111
+ if (!hasExplicitClientScope(options) || process.env.ARIA_ALLOW_OWNER_TOKEN_FOR_CLIENT_SCOPE === 'true') {
112
+ const ownerToken = readText(OWNER_TOKEN_PATH);
113
+ if (ownerToken) return { token: ownerToken, source: 'owner-token-file', scope, persisted: false };
114
+ }
115
+
116
+ const harnessToken = nonEmpty(license?.harnessToken);
117
+ if (harnessToken) return { token: harnessToken, source: 'license:harnessToken', scope, persisted: false };
118
+ const token = nonEmpty(license?.token);
119
+ if (token) return { token, source: 'license:token', scope, persisted: false };
120
+ return { token: '', source: 'missing', scope, persisted: false };
121
+ }