@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.
- package/dist/aria-connector/src/connectors/claude-code.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/claude-code.js +88 -20
- package/dist/aria-connector/src/connectors/claude-code.js.map +1 -1
- package/dist/aria-connector/src/connectors/codex.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/codex.js +526 -2
- package/dist/aria-connector/src/connectors/codex.js.map +1 -1
- package/dist/aria-connector/src/connectors/doctrine-trigger-map.d.ts +7 -0
- package/dist/aria-connector/src/connectors/doctrine-trigger-map.d.ts.map +1 -0
- package/dist/aria-connector/src/connectors/doctrine-trigger-map.js +87 -0
- package/dist/aria-connector/src/connectors/doctrine-trigger-map.js.map +1 -0
- package/dist/aria-connector/src/connectors/must-read.d.ts +4 -0
- package/dist/aria-connector/src/connectors/must-read.d.ts.map +1 -0
- package/dist/aria-connector/src/connectors/must-read.js +111 -0
- package/dist/aria-connector/src/connectors/must-read.js.map +1 -0
- package/dist/aria-connector/src/connectors/opencode.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/opencode.js +2 -0
- package/dist/aria-connector/src/connectors/opencode.js.map +1 -1
- package/dist/aria-connector/src/connectors/runtime.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/runtime.js +231 -19
- package/dist/aria-connector/src/connectors/runtime.js.map +1 -1
- package/dist/aria-connector/src/connectors/shell.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/shell.js +76 -3
- package/dist/aria-connector/src/connectors/shell.js.map +1 -1
- package/dist/aria-connector/src/self-update.d.ts +2 -1
- package/dist/aria-connector/src/self-update.d.ts.map +1 -1
- package/dist/aria-connector/src/self-update.js +84 -8
- package/dist/aria-connector/src/self-update.js.map +1 -1
- package/dist/assets/hooks/aria-cognition-substrate-binding.mjs +53 -34
- package/dist/assets/hooks/aria-harness-via-sdk.mjs +126 -12
- package/dist/assets/hooks/aria-pre-tool-gate.mjs +185 -76
- package/dist/assets/hooks/aria-preturn-memory-gate.mjs +63 -14
- package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +2 -0
- package/dist/assets/hooks/aria-stop-gate.mjs +225 -52
- package/dist/assets/hooks/lib/canonical-lenses.mjs +6 -5
- package/dist/assets/hooks/lib/gate-loop-state.mjs +50 -0
- package/dist/assets/hooks/lib/hook-message-window.mjs +121 -0
- package/dist/assets/hooks/test-tier-lens-labeling.mjs +26 -58
- package/dist/assets/opencode-plugins/harness-gate/index.js +24 -2
- package/dist/assets/opencode-plugins/harness-stop/index.js +94 -5
- package/dist/runtime/auth-middleware.mjs +251 -0
- package/dist/runtime/codex-bridge.mjs +644 -0
- package/dist/runtime/discipline/CLAUDE.md +12 -0
- package/dist/runtime/discipline/doctrine_trigger_map.json +479 -0
- package/dist/runtime/doctrine_trigger_map.json +479 -0
- package/dist/runtime/fleet-engine.mjs +231 -0
- package/dist/runtime/harness-daemon.mjs +433 -0
- package/dist/runtime/local-phase.mjs +18 -0
- package/dist/runtime/manifest.json +1 -1
- package/dist/runtime/metering.mjs +100 -0
- package/dist/runtime/onboarding-engine.mjs +89 -0
- package/dist/runtime/plugin-engine.mjs +196 -0
- package/dist/runtime/sdk/BUNDLED.json +1 -1
- package/dist/runtime/sdk/index.d.ts +7 -0
- package/dist/runtime/sdk/index.js +120 -14
- package/dist/runtime/sdk/index.js.map +1 -1
- package/dist/runtime/service.mjs +1464 -67
- package/dist/runtime/vendor/aria-gate-runtime/index.d.ts +1 -1
- package/dist/runtime/vendor/aria-gate-runtime/index.d.ts.map +1 -1
- package/dist/runtime/vendor/aria-gate-runtime/index.js +16 -1
- package/dist/runtime/vendor/aria-gate-runtime/index.js.map +1 -1
- package/dist/runtime/workflow-engine.mjs +322 -0
- package/dist/sdk/BUNDLED.json +1 -1
- package/dist/sdk/index.d.ts +7 -0
- package/dist/sdk/index.js +120 -14
- package/dist/sdk/index.js.map +1 -1
- package/hooks/aria-cognition-substrate-binding.mjs +53 -34
- package/hooks/aria-harness-via-sdk.mjs +126 -12
- package/hooks/aria-pre-tool-gate.mjs +185 -76
- package/hooks/aria-preturn-memory-gate.mjs +63 -14
- package/hooks/aria-repo-doctrine-gate.mjs +2 -0
- package/hooks/aria-stop-gate.mjs +225 -52
- package/hooks/lib/canonical-lenses.mjs +6 -5
- package/hooks/lib/gate-loop-state.mjs +50 -0
- package/hooks/lib/hook-message-window.mjs +121 -0
- package/hooks/test-tier-lens-labeling.mjs +26 -58
- package/opencode-plugins/harness-gate/index.js +24 -2
- package/opencode-plugins/harness-stop/index.js +94 -5
- package/package.json +2 -2
- package/runtime-src/auth-middleware.mjs +251 -0
- package/runtime-src/codex-bridge.mjs +644 -0
- package/runtime-src/fleet-engine.mjs +231 -0
- package/runtime-src/harness-daemon.mjs +433 -0
- package/runtime-src/local-phase.mjs +18 -0
- package/runtime-src/metering.mjs +100 -0
- package/runtime-src/onboarding-engine.mjs +89 -0
- package/runtime-src/plugin-engine.mjs +196 -0
- package/runtime-src/service.mjs +1464 -67
- package/runtime-src/workflow-engine.mjs +322 -0
- package/scripts/bundle-sdk.mjs +5 -0
- package/src/connectors/claude-code.ts +98 -20
- package/src/connectors/codex.ts +534 -1
- package/src/connectors/doctrine-trigger-map.ts +112 -0
- package/src/connectors/must-read.ts +113 -0
- package/src/connectors/opencode.ts +3 -0
- package/src/connectors/runtime.ts +241 -21
- package/src/connectors/shell.ts +78 -3
- package/src/self-update.ts +89 -8
- 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 —
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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:)
|
|
236
|
-
firstCanonicalIdx >= 0
|
|
237
|
-
`firstCanonicalIdx=${firstCanonicalIdx}
|
|
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 →
|
|
242
|
-
console.log('\nTest 2: aria-pre-tool-gate — CLIENT tier
|
|
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
|
-
|
|
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
|
|
266
|
-
|
|
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
|
|
287
|
-
blockReason.includes(`${
|
|
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 →
|
|
322
|
-
console.log('\nTest 4: aria-stop-gate — CLIENT tier
|
|
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
|
|
319
|
+
const hasCanonical = CANONICAL.some((name) => blockReason.includes(`${name}:`));
|
|
343
320
|
assert(
|
|
344
|
-
'T4: stop-gate block reason mentions
|
|
345
|
-
|
|
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
|
|
358
|
-
console.log('\nTest 5: missing packet cache → defaults to
|
|
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
|
-
|
|
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
|
|
384
|
-
|
|
385
|
-
`
|
|
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|
|
|
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
|
-
'
|
|
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
|
|
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
|
-
|
|
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
|
}
|
|
@@ -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
|
+
}
|