@aria_asi/cli 0.2.29 → 0.2.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/aria-connector/src/connectors/claude-code.d.ts.map +1 -1
  2. package/dist/aria-connector/src/connectors/claude-code.js +88 -20
  3. package/dist/aria-connector/src/connectors/claude-code.js.map +1 -1
  4. package/dist/aria-connector/src/connectors/codex.d.ts.map +1 -1
  5. package/dist/aria-connector/src/connectors/codex.js +526 -2
  6. package/dist/aria-connector/src/connectors/codex.js.map +1 -1
  7. package/dist/aria-connector/src/connectors/doctrine-trigger-map.d.ts +7 -0
  8. package/dist/aria-connector/src/connectors/doctrine-trigger-map.d.ts.map +1 -0
  9. package/dist/aria-connector/src/connectors/doctrine-trigger-map.js +87 -0
  10. package/dist/aria-connector/src/connectors/doctrine-trigger-map.js.map +1 -0
  11. package/dist/aria-connector/src/connectors/must-read.d.ts +4 -0
  12. package/dist/aria-connector/src/connectors/must-read.d.ts.map +1 -0
  13. package/dist/aria-connector/src/connectors/must-read.js +111 -0
  14. package/dist/aria-connector/src/connectors/must-read.js.map +1 -0
  15. package/dist/aria-connector/src/connectors/opencode.d.ts.map +1 -1
  16. package/dist/aria-connector/src/connectors/opencode.js +2 -0
  17. package/dist/aria-connector/src/connectors/opencode.js.map +1 -1
  18. package/dist/aria-connector/src/connectors/runtime.d.ts.map +1 -1
  19. package/dist/aria-connector/src/connectors/runtime.js +231 -19
  20. package/dist/aria-connector/src/connectors/runtime.js.map +1 -1
  21. package/dist/aria-connector/src/connectors/shell.d.ts.map +1 -1
  22. package/dist/aria-connector/src/connectors/shell.js +76 -3
  23. package/dist/aria-connector/src/connectors/shell.js.map +1 -1
  24. package/dist/aria-connector/src/self-update.d.ts +2 -1
  25. package/dist/aria-connector/src/self-update.d.ts.map +1 -1
  26. package/dist/aria-connector/src/self-update.js +84 -8
  27. package/dist/aria-connector/src/self-update.js.map +1 -1
  28. package/dist/assets/hooks/aria-cognition-substrate-binding.mjs +53 -34
  29. package/dist/assets/hooks/aria-harness-via-sdk.mjs +126 -12
  30. package/dist/assets/hooks/aria-pre-tool-gate.mjs +185 -76
  31. package/dist/assets/hooks/aria-preturn-memory-gate.mjs +63 -14
  32. package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +2 -0
  33. package/dist/assets/hooks/aria-stop-gate.mjs +225 -52
  34. package/dist/assets/hooks/lib/canonical-lenses.mjs +6 -5
  35. package/dist/assets/hooks/lib/gate-loop-state.mjs +50 -0
  36. package/dist/assets/hooks/lib/hook-message-window.mjs +121 -0
  37. package/dist/assets/hooks/test-tier-lens-labeling.mjs +26 -58
  38. package/dist/assets/opencode-plugins/harness-gate/index.js +24 -2
  39. package/dist/assets/opencode-plugins/harness-stop/index.js +94 -5
  40. package/dist/runtime/auth-middleware.mjs +251 -0
  41. package/dist/runtime/codex-bridge.mjs +644 -0
  42. package/dist/runtime/discipline/CLAUDE.md +12 -0
  43. package/dist/runtime/discipline/doctrine_trigger_map.json +479 -0
  44. package/dist/runtime/doctrine_trigger_map.json +479 -0
  45. package/dist/runtime/fleet-engine.mjs +231 -0
  46. package/dist/runtime/harness-daemon.mjs +433 -0
  47. package/dist/runtime/local-phase.mjs +18 -0
  48. package/dist/runtime/manifest.json +1 -1
  49. package/dist/runtime/metering.mjs +100 -0
  50. package/dist/runtime/onboarding-engine.mjs +89 -0
  51. package/dist/runtime/plugin-engine.mjs +196 -0
  52. package/dist/runtime/sdk/BUNDLED.json +1 -1
  53. package/dist/runtime/sdk/index.d.ts +7 -0
  54. package/dist/runtime/sdk/index.js +120 -14
  55. package/dist/runtime/sdk/index.js.map +1 -1
  56. package/dist/runtime/service.mjs +1464 -67
  57. package/dist/runtime/vendor/aria-gate-runtime/index.d.ts +1 -1
  58. package/dist/runtime/vendor/aria-gate-runtime/index.d.ts.map +1 -1
  59. package/dist/runtime/vendor/aria-gate-runtime/index.js +16 -1
  60. package/dist/runtime/vendor/aria-gate-runtime/index.js.map +1 -1
  61. package/dist/runtime/workflow-engine.mjs +322 -0
  62. package/dist/sdk/BUNDLED.json +1 -1
  63. package/dist/sdk/index.d.ts +7 -0
  64. package/dist/sdk/index.js +120 -14
  65. package/dist/sdk/index.js.map +1 -1
  66. package/hooks/aria-cognition-substrate-binding.mjs +53 -34
  67. package/hooks/aria-harness-via-sdk.mjs +126 -12
  68. package/hooks/aria-pre-tool-gate.mjs +185 -76
  69. package/hooks/aria-preturn-memory-gate.mjs +63 -14
  70. package/hooks/aria-repo-doctrine-gate.mjs +2 -0
  71. package/hooks/aria-stop-gate.mjs +225 -52
  72. package/hooks/lib/canonical-lenses.mjs +6 -5
  73. package/hooks/lib/gate-loop-state.mjs +50 -0
  74. package/hooks/lib/hook-message-window.mjs +121 -0
  75. package/hooks/test-tier-lens-labeling.mjs +26 -58
  76. package/opencode-plugins/harness-gate/index.js +24 -2
  77. package/opencode-plugins/harness-stop/index.js +94 -5
  78. package/package.json +2 -2
  79. package/runtime-src/auth-middleware.mjs +251 -0
  80. package/runtime-src/codex-bridge.mjs +644 -0
  81. package/runtime-src/fleet-engine.mjs +231 -0
  82. package/runtime-src/harness-daemon.mjs +433 -0
  83. package/runtime-src/local-phase.mjs +18 -0
  84. package/runtime-src/metering.mjs +100 -0
  85. package/runtime-src/onboarding-engine.mjs +89 -0
  86. package/runtime-src/plugin-engine.mjs +196 -0
  87. package/runtime-src/service.mjs +1464 -67
  88. package/runtime-src/workflow-engine.mjs +322 -0
  89. package/scripts/bundle-sdk.mjs +5 -0
  90. package/src/connectors/claude-code.ts +98 -20
  91. package/src/connectors/codex.ts +534 -1
  92. package/src/connectors/doctrine-trigger-map.ts +112 -0
  93. package/src/connectors/must-read.ts +113 -0
  94. package/src/connectors/opencode.ts +3 -0
  95. package/src/connectors/runtime.ts +241 -21
  96. package/src/connectors/shell.ts +78 -3
  97. package/src/self-update.ts +89 -8
  98. package/dist/cli-0.2.0.tgz +0 -0
@@ -1,13 +1,12 @@
1
1
  #!/usr/bin/env node
2
- // Smoke test: Phase 11 #59 — tier-aware lens labeling
2
+ // Smoke test: Phase 11 #59 — canonical lens labeling
3
3
  //
4
4
  // Verifies that aria-pre-tool-gate.mjs and aria-stop-gate.mjs:
5
5
  // 1. Show canonical Arabic lens names (nur, mizan, etc.) when the harness
6
6
  // packet has isHamza/hamza=true (OWNER tier).
7
- // 2. Show neutral generic labels (perception, balance, etc.) when the
8
- // harness packet is client-surface (CLIENT tier).
7
+ // 2. Preserve those same canonical labels on client-surface execution too.
9
8
  // 3. Gate enforcement (block on insufficient cognition) fires in both
10
- // tiers label swap does not change gate substance.
9
+ // tiers without renaming the lenses.
11
10
  //
12
11
  // Usage: node hooks/test-tier-lens-labeling.mjs
13
12
  // Exit: 0 = all assertions passed, 1 = failure
@@ -24,9 +23,8 @@ const HOME = process.env.HOME || '/tmp';
24
23
  const CLAUDE_DIR = `${HOME}/.claude`;
25
24
  const PACKET_PATH = `${CLAUDE_DIR}/.aria-harness-last-packet.json`;
26
25
 
27
- // ── Canonical vs generic label sets ────────────────────────────────────────
26
+ // ── Canonical label set ────────────────────────────────────────────────────
28
27
  const CANONICAL = ['nur', 'mizan', 'hikma', 'tafakkur', 'tadabbur', 'ilham', 'wahi', 'firasah'];
29
- const GENERIC = ['perception', 'balance', 'wisdom', 'reflection', 'foresight', 'insight', 'revelation', 'discernment'];
30
28
 
31
29
  // ── Assertion helpers ───────────────────────────────────────────────────────
32
30
  let passed = 0;
@@ -225,21 +223,17 @@ console.log('Test 1: aria-pre-tool-gate — OWNER tier shows canonical lens name
225
223
  blockReason.slice(0, 400),
226
224
  );
227
225
 
228
- // Must NOT mention generic labels no IP exposure for owner
229
- // (owner should only see canonical; absence of generic is nice-to-verify
230
- // but not a hard requirement since some generic words may appear in prose)
231
- // Instead check the first lens label is canonical not generic:
226
+ // The first visible lens label must be canonical.
232
227
  const firstCanonicalIdx = blockReason.indexOf(`${CANONICAL[0]}:`);
233
- const firstGenericIdx = blockReason.indexOf(`${GENERIC[0]}:`);
234
228
  assert(
235
- 'T1: first lens label is canonical (nur:), not generic (perception:)',
236
- firstCanonicalIdx >= 0 && (firstGenericIdx < 0 || firstCanonicalIdx < firstGenericIdx),
237
- `firstCanonicalIdx=${firstCanonicalIdx} firstGenericIdx=${firstGenericIdx}`,
229
+ 'T1: first lens label is canonical (nur:)',
230
+ firstCanonicalIdx >= 0,
231
+ `firstCanonicalIdx=${firstCanonicalIdx}`,
238
232
  );
239
233
  }
240
234
 
241
- // ── Test 2: pre-tool-gate, CLIENT tier → generic labels in block reason ──
242
- console.log('\nTest 2: aria-pre-tool-gate — CLIENT tier shows generic lens labels');
235
+ // ── Test 2: pre-tool-gate, CLIENT tier → canonical labels in block reason ──
236
+ console.log('\nTest 2: aria-pre-tool-gate — CLIENT tier keeps canonical lens labels');
243
237
  {
244
238
  writeClientPacket();
245
239
  writeEmptyTranscript(tmpTranscript);
@@ -259,32 +253,15 @@ console.log('\nTest 2: aria-pre-tool-gate — CLIENT tier shows generic lens lab
259
253
  blockReason = result.stdout || '';
260
254
  }
261
255
 
262
- // Must mention at least one generic label
263
- const hasGeneric = GENERIC.some((name) => blockReason.includes(`${name}:`));
256
+ const hasCanonical = CANONICAL.some((name) => blockReason.includes(`${name}:`));
264
257
  assert(
265
- 'T2: block reason mentions generic label (e.g. perception:)',
266
- hasGeneric,
258
+ 'T2: block reason mentions canonical label (e.g. nur:)',
259
+ hasCanonical,
267
260
  blockReason.slice(0, 400),
268
261
  );
269
-
270
- // Must NOT expose canonical Arabic names
271
- const exposesCanonical = CANONICAL.some((name) =>
272
- blockReason.includes(`${name}:`),
273
- );
274
- assert(
275
- 'T2: block reason does NOT expose canonical Arabic lens names',
276
- !exposesCanonical,
277
- `canonical leak: ${CANONICAL.filter((n) => blockReason.includes(`${n}:`)).join(', ')} in: ${blockReason.slice(0, 300)}`,
278
- );
279
-
280
- // Check doctrine memory filename is neutralized (no feedback_no_flag_without_fix.md for client)
281
- const noDocFileLeak = !blockReason.includes('feedback_no_flag_without_fix.md');
282
- // (This specific file only appears in discovery-unresolved path; just verify
283
- // that the block reason itself also neutralizes EIGHT_LENS_DOCTRINE references if any)
284
- // For this test we just assert the generic label set is present.
285
262
  assert(
286
- 'T2: first generic label (perception:) appears in block reason',
287
- blockReason.includes(`${GENERIC[0]}:`),
263
+ 'T2: first visible lens label is canonical (nur:)',
264
+ blockReason.includes(`${CANONICAL[0]}:`),
288
265
  blockReason.slice(0, 400),
289
266
  );
290
267
  }
@@ -318,8 +295,8 @@ console.log('\nTest 3: aria-stop-gate — OWNER tier shows canonical lens names'
318
295
  );
319
296
  }
320
297
 
321
- // ── Test 4: stop-gate, CLIENT tier → generic labels, no canonical leak ──
322
- console.log('\nTest 4: aria-stop-gate — CLIENT tier shows generic labels, no canonical leak');
298
+ // ── Test 4: stop-gate, CLIENT tier → canonical labels ──────────────────────
299
+ console.log('\nTest 4: aria-stop-gate — CLIENT tier keeps canonical lens labels');
323
300
  {
324
301
  writeClientPacket();
325
302
  writeNoCognitionStopTranscript(tmpTranscriptStop);
@@ -339,23 +316,16 @@ console.log('\nTest 4: aria-stop-gate — CLIENT tier shows generic labels, no c
339
316
  blockReason = result.stdout || '';
340
317
  }
341
318
 
342
- const hasGeneric = GENERIC.some((name) => blockReason.includes(`${name}:`));
319
+ const hasCanonical = CANONICAL.some((name) => blockReason.includes(`${name}:`));
343
320
  assert(
344
- 'T4: stop-gate block reason mentions generic label',
345
- hasGeneric,
321
+ 'T4: stop-gate block reason mentions canonical label',
322
+ hasCanonical,
346
323
  blockReason.slice(0, 400),
347
324
  );
348
-
349
- const exposesCanonical = CANONICAL.some((name) => blockReason.includes(`${name}:`));
350
- assert(
351
- 'T4: stop-gate block reason does NOT expose canonical lens names',
352
- !exposesCanonical,
353
- `canonical leak: ${CANONICAL.filter((n) => blockReason.includes(`${n}:`)).join(', ')}`,
354
- );
355
325
  }
356
326
 
357
- // ── Test 5: packet missing → defaults to CLIENT tier (fail-safe) ──────────
358
- console.log('\nTest 5: missing packet cache → defaults to CLIENT tier (fail-safe)');
327
+ // ── Test 5: packet missing → defaults to canonical labels (fail-safe) ─────
328
+ console.log('\nTest 5: missing packet cache → defaults to canonical labels');
359
329
  {
360
330
  // Remove the packet cache
361
331
  try { unlinkSync(PACKET_PATH); } catch {}
@@ -376,13 +346,11 @@ console.log('\nTest 5: missing packet cache → defaults to CLIENT tier (fail-sa
376
346
  blockReason = result.stdout || '';
377
347
  }
378
348
 
379
- // With no packet, should default to generic (client-safe fail direction)
380
- const hasGeneric = GENERIC.some((name) => blockReason.includes(`${name}:`));
381
- const exposesCanonical = CANONICAL.some((name) => blockReason.includes(`${name}:`));
349
+ const hasCanonical = CANONICAL.some((name) => blockReason.includes(`${name}:`));
382
350
  assert(
383
- 'T5: defaults to generic labels when packet is absent',
384
- hasGeneric && !exposesCanonical,
385
- `generic=${hasGeneric} canonicalLeak=${exposesCanonical} reason=${blockReason.slice(0, 300)}`,
351
+ 'T5: defaults to canonical labels when packet is absent',
352
+ hasCanonical,
353
+ `canonical=${hasCanonical} reason=${blockReason.slice(0, 300)}`,
386
354
  );
387
355
  }
388
356
 
@@ -2,7 +2,7 @@
2
2
  * Aria Harness Gate — pre-tool cognition enforcement for OpenCode.
3
3
  * Routes through HTTPHarnessClient SDK for substrate-backed validation.
4
4
  */
5
- import { existsSync, readFileSync } from 'node:fs';
5
+ import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
6
6
  import { homedir } from 'node:os';
7
7
  import { join } from 'node:path';
8
8
 
@@ -15,6 +15,7 @@ const SDK_CANDIDATES = [
15
15
  const OWNER_TOKEN_PATH = join(HOME, '.aria', 'owner-token');
16
16
  const LICENSE_PATH = join(HOME, '.aria', 'license.json');
17
17
  const RUNTIME_URL = (process.env.ARIA_RUNTIME_URL || 'http://127.0.0.1:4319').replace(/\/+$/, '');
18
+ const RECEIPT_DIR = join(HOME, '.opencode', 'aria-mizan-receipts');
18
19
 
19
20
  const DESTRUCTIVE_PATTERNS = [
20
21
  { rx: /(?:^|[;&|]\s*|\$\(\s*|`\s*)sudo\s+\S/, name: 'sudo' },
@@ -41,7 +42,7 @@ const DEPLOY_PATTERNS = [
41
42
  { rx: /\bdocker\s+build\b.*--push\b/, name: 'docker-build-push' },
42
43
  ];
43
44
 
44
- const INLINE_LENS_RX = /^\s*#\s*(?:nur|mizan|hikma|tafakkur|tadabbur|ilham|wahi|firasah|perception|balance|wisdom|reflection|foresight|insight|revelation|discernment)\s*:\s*.{20,}/im;
45
+ const INLINE_LENS_RX = /^\s*#\s*(?:nur|mizan|hikma|tafakkur|tadabbur|ilham|wahi|firasah|truth|harm|trust|power|reflection|context|impact|beauty)\s*:\s*.{20,}/im;
45
46
  const VERIFY_BLOCK_RX = /<verify>[\s\S]*?target\s*:[\s\S]*?verified\s*:[\s\S]*?axiom\s*:[\s\S]*?<\/verify>/i;
46
47
  const TRIVIAL_BASH_RX = /^\s*(?:ls|cat|head|tail|grep|find|echo|wc|stat|which|pwd|date|file|du|df|ss|ps)\s/;
47
48
  const SHORT_BASH_LIMIT = 30;
@@ -51,6 +52,17 @@ let _clientError = null;
51
52
  let _lastMizanReceipt = null;
52
53
  let _lastActionSummary = '';
53
54
 
55
+ function receiptPath(sessionId) {
56
+ return join(RECEIPT_DIR, `${String(sessionId || 'opencode').replace(/[^a-zA-Z0-9_-]/g, '_')}.json`);
57
+ }
58
+
59
+ function persistReceipt(sessionId, payload) {
60
+ try {
61
+ mkdirSync(RECEIPT_DIR, { recursive: true, mode: 0o755 });
62
+ writeFileSync(receiptPath(sessionId), JSON.stringify(payload, null, 2) + '\n');
63
+ } catch {}
64
+ }
65
+
54
66
  function resolveToken() {
55
67
  if (process.env.ARIA_HARNESS_TOKEN) return process.env.ARIA_HARNESS_TOKEN;
56
68
  if (process.env.ARIA_API_KEY) return process.env.ARIA_API_KEY;
@@ -182,6 +194,16 @@ export default async function HarnessGatePlugin(ctx) {
182
194
  },
183
195
  });
184
196
  _lastMizanReceipt = pre.receipt || null;
197
+ if (_lastMizanReceipt) {
198
+ persistReceipt(sessionId, {
199
+ updatedAt: new Date().toISOString(),
200
+ sessionId,
201
+ receipt: _lastMizanReceipt,
202
+ preResult: pre.result || null,
203
+ preContract: pre.contract || null,
204
+ preSummary: pre.summary || null,
205
+ });
206
+ }
185
207
  _lastActionSummary = cmdPreview;
186
208
  if (pre.receipt?.blocked || pre.result?.fitrahVetoed || pre.result?.reAuthorSignal) {
187
209
  throw new Error(
@@ -2,7 +2,7 @@
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 } from 'node:fs';
5
+ import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
6
6
  import { homedir } from 'node:os';
7
7
  import { join } from 'node:path';
8
8
 
@@ -15,10 +15,11 @@ const SDK_CANDIDATES = [
15
15
  const OWNER_TOKEN_PATH = join(HOME, '.aria', 'owner-token');
16
16
  const LICENSE_PATH = join(HOME, '.aria', 'license.json');
17
17
  const RUNTIME_URL = (process.env.ARIA_RUNTIME_URL || 'http://127.0.0.1:4319').replace(/\/+$/, '');
18
+ const RECEIPT_DIR = join(HOME, '.opencode', 'aria-mizan-receipts');
18
19
 
19
20
  const LENS_NAMES = [
20
21
  'nur', 'mizan', 'hikma', 'tafakkur', 'tadabbur', 'ilham', 'wahi', 'firasah',
21
- 'perception', 'balance', 'wisdom', 'reflection', 'foresight', 'insight', 'revelation', 'discernment',
22
+ 'truth', 'harm', 'trust', 'power', 'reflection', 'context', 'impact', 'beauty',
22
23
  ];
23
24
 
24
25
  const COGNITION_BLOCK_RX = /<cognition>([\s\S]*?)<\/cognition>/i;
@@ -33,6 +34,27 @@ let _client = null;
33
34
  let _clientError = null;
34
35
  let _lastMizanReceipt = null;
35
36
 
37
+ function receiptPath(sessionId) {
38
+ return join(RECEIPT_DIR, `${String(sessionId || 'opencode').replace(/[^a-zA-Z0-9_-]/g, '_')}.json`);
39
+ }
40
+
41
+ function loadReceiptState(sessionId) {
42
+ try {
43
+ const target = receiptPath(sessionId);
44
+ if (!existsSync(target)) return null;
45
+ return JSON.parse(readFileSync(target, 'utf8'));
46
+ } catch {
47
+ return null;
48
+ }
49
+ }
50
+
51
+ function persistReceiptState(sessionId, payload) {
52
+ try {
53
+ mkdirSync(RECEIPT_DIR, { recursive: true, mode: 0o755 });
54
+ writeFileSync(receiptPath(sessionId), JSON.stringify(payload, null, 2) + '\n');
55
+ } catch {}
56
+ }
57
+
36
58
  function resolveToken() {
37
59
  if (process.env.ARIA_HARNESS_TOKEN) return process.env.ARIA_HARNESS_TOKEN;
38
60
  if (process.env.ARIA_API_KEY) return process.env.ARIA_API_KEY;
@@ -100,6 +122,7 @@ async function runtimeValidateOutput(text, sessionId) {
100
122
  }
101
123
 
102
124
  async function runtimeMizanPost(text, sessionId, context = {}) {
125
+ const existing = loadReceiptState(sessionId);
103
126
  const token = resolveToken();
104
127
  if (!token) throw new Error('no token');
105
128
  const response = await fetch(`${RUNTIME_URL}/mizan/post`, {
@@ -115,7 +138,7 @@ async function runtimeMizanPost(text, sessionId, context = {}) {
115
138
  sessionId,
116
139
  ...context,
117
140
  },
118
- parentReceiptId: _lastMizanReceipt?.receiptId || null,
141
+ parentReceiptId: existing?.receipt?.receiptId || _lastMizanReceipt?.receiptId || null,
119
142
  }),
120
143
  });
121
144
  if (!response.ok) {
@@ -125,6 +148,24 @@ async function runtimeMizanPost(text, sessionId, context = {}) {
125
148
  return response.json();
126
149
  }
127
150
 
151
+ async function runtimeDecisionLog(payload) {
152
+ const token = resolveToken();
153
+ if (!token) throw new Error('no token');
154
+ const response = await fetch(`${RUNTIME_URL}/decision/log`, {
155
+ method: 'POST',
156
+ headers: {
157
+ 'Content-Type': 'application/json',
158
+ Authorization: `Bearer ${token}`,
159
+ },
160
+ body: JSON.stringify(payload),
161
+ });
162
+ const body = await response.json().catch(() => ({}));
163
+ if (!response.ok) {
164
+ throw new Error(body?.error || `runtime decision/log failed ${response.status}`);
165
+ }
166
+ return body;
167
+ }
168
+
128
169
  function detectCognitionLenses(text) {
129
170
  if (!text) return { count: 0, names: [] };
130
171
  const block = text.match(COGNITION_BLOCK_RX);
@@ -172,6 +213,18 @@ export default async function HarnessStopPlugin(ctx) {
172
213
  plannedApproach: 'OpenCode stop gate output review',
173
214
  });
174
215
  _lastMizanReceipt = mizan.receipt || _lastMizanReceipt;
216
+ if (_lastMizanReceipt) {
217
+ const existing = loadReceiptState(sessionId) || {};
218
+ persistReceiptState(sessionId, {
219
+ ...existing,
220
+ updatedAt: new Date().toISOString(),
221
+ sessionId,
222
+ postReceipt: _lastMizanReceipt,
223
+ postResult: mizan.result || null,
224
+ postContract: mizan.contract || null,
225
+ postSummary: mizan.summary || null,
226
+ });
227
+ }
175
228
  if (mizan.receipt?.blocked || mizan.result?.fitrahVetoed || mizan.result?.reAuthorSignal) {
176
229
  process.stderr.write(
177
230
  `[harness-stop] MIZAN POST BLOCK — ${(mizan.result?.notes || ['post-phase blocked']).slice(0, 4).join(' | ')}\n`
@@ -213,10 +266,18 @@ export default async function HarnessStopPlugin(ctx) {
213
266
 
214
267
  // Local fallback gate
215
268
  // Scan drift triggers
216
- const triggerMapPath = `${HOME}/.claude/hooks/doctrine_trigger_map.json`;
269
+ const triggerMapPaths = [
270
+ `${HOME}/.aria/runtime/discipline/doctrine_trigger_map.json`,
271
+ `${HOME}/.aria/runtime/doctrine_trigger_map.json`,
272
+ `${HOME}/.opencode/doctrine_trigger_map.json`,
273
+ `${HOME}/.codex/doctrine_trigger_map.json`,
274
+ `${HOME}/.claude/hooks/doctrine_trigger_map.json`,
275
+ `${HOME}/.claude/projects/-home-hamzaibrahim1/memory/doctrine_trigger_map.json`,
276
+ ];
217
277
  let driftHits = [];
218
278
  try {
219
- if (existsSync(triggerMapPath)) {
279
+ const triggerMapPath = triggerMapPaths.find((candidate) => existsSync(candidate));
280
+ if (triggerMapPath) {
220
281
  const triggerMap = JSON.parse(readFileSync(triggerMapPath, 'utf8'));
221
282
  const lower = text.toLowerCase();
222
283
  for (const t of triggerMap.triggers || []) {
@@ -236,6 +297,34 @@ export default async function HarnessStopPlugin(ctx) {
236
297
  `[harness-stop] LOCAL GATE — cognition=${cog.count}/${REQUIRED_LENSES} drift=${driftHits.length}\n`
237
298
  );
238
299
  }
300
+
301
+ try {
302
+ const existing = loadReceiptState(sessionId);
303
+ await runtimeDecisionLog({
304
+ decision_type: 'turn_action',
305
+ category: 'agentic_execution',
306
+ context: `opencode stop-gate turn (chars=${text.length})`,
307
+ decision: 'turn completed',
308
+ reasoning: cog.count > 0
309
+ ? `Cognition lenses applied: ${cog.names.join(', ')}.`
310
+ : 'No explicit cognition block in turn.',
311
+ outcome: 'pending',
312
+ outcome_details: {
313
+ expected: null,
314
+ immediate_actual: null,
315
+ anchors: [],
316
+ },
317
+ expected_outcome: null,
318
+ metadata: {
319
+ pre_receipt_id: existing?.receipt?.receiptId || null,
320
+ post_receipt_id: _lastMizanReceipt?.receiptId || null,
321
+ },
322
+ source: 'opencode-stop-gate-runtime',
323
+ model_used: process.env.OPENCODE_MODEL || 'opencode',
324
+ });
325
+ } catch (e) {
326
+ process.stderr.write(`[harness-stop] decision/log unavailable: ${e.message}\n`);
327
+ }
239
328
  },
240
329
  };
241
330
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@aria_asi/cli",
3
- "version": "0.2.29",
4
- "description": "Aria Smart CLI the world's first harness-powered terminal companion",
3
+ "version": "0.2.31",
4
+ "description": "Aria Smart CLI \u2014 the world's first harness-powered terminal companion",
5
5
  "bin": {
6
6
  "aria": "./bin/aria.js"
7
7
  },
@@ -0,0 +1,251 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
3
+ import { randomBytes, scryptSync, timingSafeEqual } from 'node:crypto';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const STATE_DIR = join(__dirname, '..', 'state');
8
+ const KEYS_PATH = join(STATE_DIR, 'api-keys.json');
9
+ const USERS_PATH = join(STATE_DIR, 'users.json');
10
+ const SESSIONS_PATH = join(STATE_DIR, 'sessions.json');
11
+ const SCRYPT_KEY_LENGTH = 32;
12
+ const SCRYPT_COST = 16384;
13
+ const SESSION_TTL_MS = 24 * 60 * 60 * 1000;
14
+
15
+ const OWNER_USERNAME = 'admin';
16
+ const OWNER_PASSWORD_HASH = hashPassword('Loveala613#');
17
+
18
+ function ensureDir(p) {
19
+ if (!existsSync(p)) mkdirSync(p, { recursive: true });
20
+ }
21
+
22
+ function loadJson(path, fallback = []) {
23
+ if (!existsSync(path)) return fallback;
24
+ try {
25
+ return JSON.parse(readFileSync(path, 'utf-8'));
26
+ } catch {
27
+ return fallback;
28
+ }
29
+ }
30
+
31
+ function saveJson(path, data) {
32
+ ensureDir(dirname(path));
33
+ writeFileSync(path, JSON.stringify(data, null, 2));
34
+ }
35
+
36
+ function hashPassword(password) {
37
+ return scryptSync(password, 'aria-hq-user-salt-v2', SCRYPT_COST, 8, 1, SCRYPT_KEY_LENGTH).toString('hex');
38
+ }
39
+
40
+ function hashKey(key) {
41
+ return scryptSync(key, 'aria-hq-api-key-salt', SCRYPT_COST, 8, 1, SCRYPT_KEY_LENGTH).toString('hex');
42
+ }
43
+
44
+ // ─── API Key Management ───────────────────────────────────────
45
+
46
+ function loadKeys() {
47
+ return loadJson(KEYS_PATH, []);
48
+ }
49
+
50
+ function saveKeys(keys) {
51
+ saveJson(KEYS_PATH, keys);
52
+ }
53
+
54
+ export function generateApiKey(tenantId) {
55
+ const raw = randomBytes(32).toString('hex');
56
+ const key = `aria_${raw.slice(0, 8)}_${raw.slice(8)}`;
57
+ const keyHash = hashKey(key);
58
+ const keys = loadKeys();
59
+ const existing = keys.findIndex(k => k.tenantId === tenantId);
60
+ const entry = {
61
+ tenantId,
62
+ keyHash,
63
+ keyPrefix: key.slice(0, 15),
64
+ createdAt: new Date().toISOString(),
65
+ };
66
+ if (existing >= 0) keys[existing] = entry; else keys.push(entry);
67
+ saveKeys(keys);
68
+ return key;
69
+ }
70
+
71
+ export function validateApiKey(key) {
72
+ if (!key || typeof key !== 'string') return { valid: false, tenantId: null };
73
+ const keys = loadKeys();
74
+ const keyHash = hashKey(key);
75
+ for (const entry of keys) {
76
+ try {
77
+ if (timingSafeEqual(Buffer.from(keyHash), Buffer.from(entry.keyHash))) {
78
+ return { valid: true, tenantId: entry.tenantId };
79
+ }
80
+ } catch {
81
+ continue;
82
+ }
83
+ }
84
+ return { valid: false, tenantId: null };
85
+ }
86
+
87
+ export function rotateApiKey(tenantId) {
88
+ return generateApiKey(tenantId);
89
+ }
90
+
91
+ export function revokeApiKey(tenantId) {
92
+ const keys = loadKeys();
93
+ const filtered = keys.filter(k => k.tenantId !== tenantId);
94
+ saveKeys(filtered);
95
+ return filtered.length < keys.length;
96
+ }
97
+
98
+ // ─── User Management ──────────────────────────────────────────
99
+
100
+ export function registerUser(email, password, tenantId) {
101
+ if (!email || !password || !tenantId) {
102
+ return { ok: false, error: 'email, password, and tenantId required' };
103
+ }
104
+ const users = loadJson(USERS_PATH, []);
105
+ if (users.find(u => u.email === email.toLowerCase().trim())) {
106
+ return { ok: false, error: 'User already exists' };
107
+ }
108
+ if (users.find(u => u.tenantId === tenantId)) {
109
+ return { ok: false, error: 'Tenant already has a registered user' };
110
+ }
111
+ const user = {
112
+ id: randomBytes(16).toString('hex'),
113
+ email: email.toLowerCase().trim(),
114
+ passwordHash: hashPassword(password),
115
+ role: 'client',
116
+ tenantId,
117
+ createdAt: new Date().toISOString(),
118
+ lastLogin: null,
119
+ };
120
+ users.push(user);
121
+ saveJson(USERS_PATH, users);
122
+ return { ok: true, user: { id: user.id, email: user.email, role: user.role, tenantId: user.tenantId } };
123
+ }
124
+
125
+ export function loginUser(usernameOrEmail, password) {
126
+ if (usernameOrEmail === OWNER_USERNAME) {
127
+ try {
128
+ if (timingSafeEqual(Buffer.from(hashPassword(password)), Buffer.from(OWNER_PASSWORD_HASH))) {
129
+ const session = createSession({ userId: 'owner', role: 'owner', tenantId: null, email: OWNER_USERNAME });
130
+ return { ok: true, session, user: { id: 'owner', email: OWNER_USERNAME, role: 'owner', tenantId: null } };
131
+ }
132
+ } catch {
133
+ return { ok: false, error: 'Invalid credentials' };
134
+ }
135
+ return { ok: false, error: 'Invalid credentials' };
136
+ }
137
+
138
+ const users = loadJson(USERS_PATH, []);
139
+ const user = users.find(u => u.email === usernameOrEmail.toLowerCase().trim());
140
+ if (!user) return { ok: false, error: 'No account found with that email' };
141
+ try {
142
+ if (timingSafeEqual(Buffer.from(hashPassword(password)), Buffer.from(user.passwordHash))) {
143
+ const session = createSession({ userId: user.id, role: user.role, tenantId: user.tenantId, email: user.email });
144
+ user.lastLogin = new Date().toISOString();
145
+ saveJson(USERS_PATH, users);
146
+ return { ok: true, session, user: { id: user.id, email: user.email, role: user.role, tenantId: user.tenantId } };
147
+ }
148
+ } catch {
149
+ return { ok: false, error: 'Invalid password' };
150
+ }
151
+ return { ok: false, error: 'Invalid password' };
152
+ }
153
+
154
+ // ─── Session Management ───────────────────────────────────────
155
+
156
+ function createSession(identity) {
157
+ const token = `hq_${randomBytes(32).toString('hex')}`;
158
+ const session = {
159
+ token,
160
+ userId: identity.userId,
161
+ role: identity.role,
162
+ tenantId: identity.tenantId,
163
+ email: identity.email,
164
+ createdAt: new Date().toISOString(),
165
+ expiresAt: new Date(Date.now() + SESSION_TTL_MS).toISOString(),
166
+ };
167
+ const sessions = loadJson(SESSIONS_PATH, []);
168
+ sessions.push(session);
169
+ saveJson(SESSIONS_PATH, sessions);
170
+ return { token, role: identity.role, tenantId: identity.tenantId, email: identity.email };
171
+ }
172
+
173
+ export function validateSession(token) {
174
+ if (!token || typeof token !== 'string') return { valid: false };
175
+ const sessions = loadJson(SESSIONS_PATH, []);
176
+ const session = sessions.find(s => s.token === token);
177
+ if (!session) return { valid: false };
178
+ if (new Date(session.expiresAt) < new Date()) {
179
+ revokeSession(token);
180
+ return { valid: false };
181
+ }
182
+ return {
183
+ valid: true,
184
+ userId: session.userId,
185
+ role: session.role,
186
+ tenantId: session.tenantId,
187
+ email: session.email,
188
+ };
189
+ }
190
+
191
+ export function revokeSession(token) {
192
+ const sessions = loadJson(SESSIONS_PATH, []);
193
+ const filtered = sessions.filter(s => s.token !== token);
194
+ saveJson(SESSIONS_PATH, filtered);
195
+ }
196
+
197
+ // ─── Owner Tenant Listing ─────────────────────────────────────
198
+
199
+ export function listAllTenants() {
200
+ const users = loadJson(USERS_PATH, []);
201
+ const results = [];
202
+ for (const user of users) {
203
+ if (user.role !== 'client') continue;
204
+ results.push({
205
+ tenantId: user.tenantId,
206
+ email: user.email,
207
+ createdAt: user.createdAt,
208
+ lastLogin: user.lastLogin,
209
+ });
210
+ }
211
+ return results;
212
+ }
213
+
214
+ // ─── Auth Middleware ───────────────────────────────────────────
215
+
216
+ const PUBLIC_ROUTES = new Set([
217
+ '/hq/auth/login',
218
+ '/hq/auth/register',
219
+ '/hq/onboarding/chat',
220
+ '/hq/onboarding/status',
221
+ '/hq/subscription/tier',
222
+ ]);
223
+
224
+ export function hqAuthMiddleware(pathname, req) {
225
+ if (PUBLIC_ROUTES.has(pathname)) return { authorized: true, tenantId: null };
226
+
227
+ const authHeader = req.headers['authorization'] || req.headers['x-api-key'] || '';
228
+ const token = authHeader.replace(/^Bearer\s+/i, '').trim();
229
+
230
+ if (!token) {
231
+ return { authorized: false, error: 'Authentication required. Use Authorization: Bearer <token> header.', tenantId: null };
232
+ }
233
+
234
+ if (token.startsWith('hq_')) {
235
+ const session = validateSession(token);
236
+ if (session.valid) {
237
+ return {
238
+ authorized: true,
239
+ tenantId: session.tenantId,
240
+ role: session.role,
241
+ userId: session.userId,
242
+ email: session.email,
243
+ };
244
+ }
245
+ return { authorized: false, error: 'Session expired or invalid.', tenantId: null };
246
+ }
247
+
248
+ const result = validateApiKey(token);
249
+ if (!result.valid) return { authorized: false, error: 'Invalid API key or session token.', tenantId: null };
250
+ return { authorized: true, tenantId: result.tenantId };
251
+ }