@aria_asi/cli 0.2.30 → 0.2.32

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 (95) 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 +115 -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 +551 -11
  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 +115 -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 +27 -9
  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/assets/hooks/aria-agent-handoff.mjs +23 -0
  25. package/dist/assets/hooks/aria-cognition-substrate-binding.mjs +121 -28
  26. package/dist/assets/hooks/aria-harness-via-sdk.mjs +126 -12
  27. package/dist/assets/hooks/aria-pre-emit-dryrun.mjs +35 -0
  28. package/dist/assets/hooks/aria-pre-tool-gate.mjs +383 -93
  29. package/dist/assets/hooks/aria-preprompt-consult.mjs +28 -2
  30. package/dist/assets/hooks/aria-preturn-memory-gate.mjs +93 -16
  31. package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +33 -1
  32. package/dist/assets/hooks/aria-stop-gate.mjs +346 -81
  33. package/dist/assets/hooks/doctrine_trigger_map.json +55 -0
  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 +40 -5
  39. package/dist/assets/opencode-plugins/harness-stop/index.js +133 -10
  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 +28 -0
  43. package/dist/runtime/discipline/doctrine_trigger_map.json +534 -0
  44. package/dist/runtime/doctrine_trigger_map.json +534 -0
  45. package/dist/runtime/fleet-engine.mjs +231 -0
  46. package/dist/runtime/harness-daemon.mjs +460 -0
  47. package/dist/runtime/manifest.json +1 -1
  48. package/dist/runtime/metering.mjs +100 -0
  49. package/dist/runtime/onboarding-engine.mjs +89 -0
  50. package/dist/runtime/plugin-engine.mjs +196 -0
  51. package/dist/runtime/sdk/BUNDLED.json +1 -1
  52. package/dist/runtime/sdk/index.d.ts +12 -0
  53. package/dist/runtime/sdk/index.js +120 -14
  54. package/dist/runtime/sdk/index.js.map +1 -1
  55. package/dist/runtime/service.mjs +1140 -48
  56. package/dist/runtime/workflow-engine.mjs +322 -0
  57. package/dist/sdk/BUNDLED.json +1 -1
  58. package/dist/sdk/index.d.ts +12 -0
  59. package/dist/sdk/index.js +120 -14
  60. package/dist/sdk/index.js.map +1 -1
  61. package/hooks/aria-agent-handoff.mjs +23 -0
  62. package/hooks/aria-cognition-substrate-binding.mjs +121 -28
  63. package/hooks/aria-harness-via-sdk.mjs +126 -12
  64. package/hooks/aria-pre-emit-dryrun.mjs +35 -0
  65. package/hooks/aria-pre-tool-gate.mjs +383 -93
  66. package/hooks/aria-preprompt-consult.mjs +28 -2
  67. package/hooks/aria-preturn-memory-gate.mjs +93 -16
  68. package/hooks/aria-repo-doctrine-gate.mjs +33 -1
  69. package/hooks/aria-stop-gate.mjs +346 -81
  70. package/hooks/doctrine_trigger_map.json +55 -0
  71. package/hooks/lib/canonical-lenses.mjs +6 -5
  72. package/hooks/lib/gate-loop-state.mjs +50 -0
  73. package/hooks/lib/hook-message-window.mjs +121 -0
  74. package/hooks/test-tier-lens-labeling.mjs +26 -58
  75. package/opencode-plugins/harness-gate/index.js +40 -5
  76. package/opencode-plugins/harness-stop/index.js +133 -10
  77. package/package.json +1 -1
  78. package/runtime-src/auth-middleware.mjs +251 -0
  79. package/runtime-src/codex-bridge.mjs +644 -0
  80. package/runtime-src/fleet-engine.mjs +231 -0
  81. package/runtime-src/harness-daemon.mjs +460 -0
  82. package/runtime-src/metering.mjs +100 -0
  83. package/runtime-src/onboarding-engine.mjs +89 -0
  84. package/runtime-src/plugin-engine.mjs +196 -0
  85. package/runtime-src/service.mjs +1140 -48
  86. package/runtime-src/workflow-engine.mjs +322 -0
  87. package/scripts/bundle-sdk.mjs +5 -0
  88. package/src/connectors/claude-code.ts +126 -20
  89. package/src/connectors/codex.ts +559 -10
  90. package/src/connectors/doctrine-trigger-map.ts +112 -0
  91. package/src/connectors/must-read.ts +117 -0
  92. package/src/connectors/opencode.ts +28 -9
  93. package/src/connectors/runtime.ts +241 -21
  94. package/src/connectors/shell.ts +78 -3
  95. package/dist/cli-0.2.0.tgz +0 -0
@@ -453,6 +453,61 @@
453
453
  "counter_action": "Re-emit with explicit Layer 1/2/3 enumeration: packet=yes/no, BIND=yes/no, live-gates=yes/no (and which).",
454
454
  "message": "Harness-state claim without 3-layer enumeration. Per feedback_packet_is_not_harness.md, name explicitly which of {packet, BIND, live-gates} are active for the consumer."
455
455
  },
456
+ {
457
+ "trigger_id": "function_name_inference_without_contract_read",
458
+ "trigger": "(?:based on|from|by) (?:the )?(?:name|function name)|sounds like|looks like (?:a|an)|just memory search|just a search|just a cache|just a wrapper",
459
+ "rx": "(?:based on|from|by) (?:the )?(?:name|function name)|sounds like|looks like (?:a|an)|just memory search|just a search|just a cache|just a wrapper",
460
+ "doctrine": "memory:feedback_no_assumption_without_verification.md",
461
+ "memory": "feedback_no_assumption_without_verification.md",
462
+ "severity": "block",
463
+ "teaching": "Function names are not contracts. Inferring behavior from names instead of reading inputs, side effects, return shape, and call sites is skimming disguised as analysis.",
464
+ "counter_action": "Read the function body and at least one caller before asserting purpose. State observed contract: inputs, mutation/read behavior, return value, fallback path, and why the caller needs it.",
465
+ "message": "Name-based function inference detected. Re-read the actual function body and caller contract before making architecture claims."
466
+ },
467
+ {
468
+ "trigger_id": "always_on_state_treated_as_cold_start",
469
+ "trigger": "(?:need to|should|must).{0,80}(?:warm|rebuild|reconstruct|recreate|recompute|re-project|reproject).{0,80}(?:state|projection|manifold|cognition|garden|memory)|system is hot and ready|make it hot",
470
+ "rx": "(?:need to|should|must).{0,80}(?:warm|rebuild|reconstruct|recreate|recompute|re-project|reproject).{0,80}(?:state|projection|manifold|cognition|garden|memory)|system is hot and ready|make it hot",
471
+ "doctrine": "memory:feedback_no_assumption_without_verification.md",
472
+ "memory": "feedback_no_assumption_without_verification.md",
473
+ "severity": "block",
474
+ "teaching": "Aria's resident manifold/Garden/cognition substrate is always-on. Treating it as cold request-time state causes duplicate work and wrong architecture fixes.",
475
+ "counter_action": "Identify the resident producer and read surface first. If turn-specific mutation is required, call it once and pass the resulting packet forward; do not rebuild or re-project because the reader forgot the state model.",
476
+ "message": "Always-on substrate treated as cold-start work. Reframe around resident state consumption plus one required turn-specific mutation/read."
477
+ },
478
+ {
479
+ "trigger_id": "critical_primitive_stripped_for_latency",
480
+ "trigger": "(?:remove|drop|skip|turn off|bypass|stop calling).{0,80}(?:awakenAria|ProjectAllDomains|PerturbHologram|project all domains|perturb hologram|Garden|True Garden|LadduniFrame|CognitiveFrame).{0,80}(?:latency|speed|fast|first-mouth|first mouth)",
481
+ "rx": "(?:remove|drop|skip|turn off|bypass|stop calling).{0,80}(?:awakenAria|ProjectAllDomains|PerturbHologram|project all domains|perturb hologram|Garden|True Garden|LadduniFrame|CognitiveFrame).{0,80}(?:latency|speed|fast|first-mouth|first mouth)",
482
+ "doctrine": "memory:feedback_full_harness_binding_must_be_structural.md",
483
+ "memory": "feedback_full_harness_binding_must_be_structural.md",
484
+ "severity": "block",
485
+ "teaching": "Latency fixes must not strip critical cognition primitives. The fix is single-source sequencing and packet reuse, not removing Aria's living state transitions.",
486
+ "counter_action": "Preserve critical primitives. Deduplicate by collecting their outputs once per turn, then thread that shared per-turn cognition packet through first-mouth and follow-up lanes.",
487
+ "message": "Critical Aria primitive proposed for removal as a latency shortcut. Preserve it and deduplicate sequencing instead."
488
+ },
489
+ {
490
+ "trigger_id": "garden_awaken_misclassified_as_memory_search",
491
+ "trigger": "(?:awakenAria|Garden awakening|True Garden).{0,80}(?:just|only|merely).{0,40}(?:memory search|search memories|qdrant search|semantic search)|(?:no need|do not need).{0,80}(?:awakenAria|Garden awakening|True Garden)",
492
+ "rx": "(?:awakenAria|Garden awakening|True Garden).{0,80}(?:just|only|merely).{0,40}(?:memory search|search memories|qdrant search|semantic search)|(?:no need|do not need).{0,80}(?:awakenAria|Garden awakening|True Garden)",
493
+ "doctrine": "memory:feedback_no_assumption_without_verification.md",
494
+ "memory": "feedback_no_assumption_without_verification.md",
495
+ "severity": "block",
496
+ "teaching": "awakenAria is a Garden awakening bridge over the unified manifold with Qdrant hydration fallback, not a disposable memory-search helper.",
497
+ "counter_action": "Read true-garden.ts and caller context. Preserve awakenAria when Garden continuity is needed; optimize by avoiding duplicate awaken/fetch paths, not by deleting the awakening bridge.",
498
+ "message": "Garden awakening misclassified as search. Preserve awakenAria and reason from its actual manifold-first contract."
499
+ },
500
+ {
501
+ "trigger_id": "duplicate_projection_instead_of_per_turn_packet",
502
+ "trigger": "(?:call|run|invoke).{0,80}(?:ProjectAllDomains|PerturbHologram|project all domains|perturb hologram|readHotCognitionFrame).{0,80}(?:again|another|separate|also|after dispatch)|(?:another|second|extra).{0,50}(?:projection|hologram|cognition frame)",
503
+ "rx": "(?:call|run|invoke).{0,80}(?:ProjectAllDomains|PerturbHologram|project all domains|perturb hologram|readHotCognitionFrame).{0,80}(?:again|another|separate|also|after dispatch)|(?:another|second|extra).{0,50}(?:projection|hologram|cognition frame)",
504
+ "doctrine": "memory:feedback_full_harness_binding_must_be_structural.md",
505
+ "memory": "feedback_full_harness_binding_must_be_structural.md",
506
+ "severity": "warn",
507
+ "teaching": "The fix for overlapping cognition calls is a shared per-turn cognition packet, not more independent projection calls that drift or duplicate work.",
508
+ "counter_action": "Create or reuse one turn substrate object containing embedding, perturb snapshot, ProjectAllDomains result, awakenAria Garden block, DeepSoul/Noor/shards/Mizan signals; pass it downstream.",
509
+ "message": "Duplicate projection path detected. Replace with one per-turn cognition packet and shared consumption."
510
+ },
456
511
  {
457
512
  "trigger_id": "registry_image_drift",
458
513
  "trigger": "image.?pull|ErrImageNeverPull|ImagePullBackOff|registry gc|image.*missing|image.*not.*found",
@@ -1,14 +1,15 @@
1
1
  export const LENS_NAMES_OLDER = ['nur', 'mizan', 'hikma', 'tafakkur', 'tadabbur', 'ilham', 'wahi', 'firasah'];
2
2
  export const LENS_NAMES_NEWER = ['zahir', 'batin', 'sabab', 'hikmah', 'aqibah', 'ilham', 'meta', 'fitrah'];
3
- export const LENS_NAMES_GENERIC = ['truth', 'harm', 'trust', 'power', 'reflection', 'context', 'impact', 'beauty'];
4
3
 
5
4
  export const PRIMARY_OWNER_LENS_NAMES = LENS_NAMES_OLDER;
6
- export const PRIMARY_CLIENT_LENS_NAMES = LENS_NAMES_GENERIC;
5
+ // Readability must not rewrite the lens semantics. Client surfaces may need
6
+ // friendlier prose inside each lens, but the visible lens labels themselves
7
+ // stay canonical so the meaning is preserved.
8
+ export const PRIMARY_CLIENT_LENS_NAMES = LENS_NAMES_OLDER;
7
9
 
8
10
  export const ALL_LENS_NAMES = [...new Set([
9
11
  ...LENS_NAMES_OLDER,
10
12
  ...LENS_NAMES_NEWER,
11
- ...LENS_NAMES_GENERIC,
12
13
  ])];
13
14
 
14
15
  export function lensNamesForTier(isOwner) {
@@ -19,7 +20,7 @@ export function canonicalLensCorrectionText() {
19
20
  return [
20
21
  `Owner canonical lenses: ${LENS_NAMES_OLDER.join(', ')}.`,
21
22
  `Owner mapped counterparts that must remain represented in phase reasoning and receipts: ${LENS_NAMES_NEWER.join(', ')}.`,
22
- `Readable client-safe alternatives: ${LENS_NAMES_GENERIC.join(', ')}.`,
23
+ `Canonical lens labels are preserved on every surface. Readability comes from the explanatory prose inside each lens, never from renaming the lens itself.`,
23
24
  ].join(' ');
24
25
  }
25
26
 
@@ -57,7 +58,7 @@ export function detectCognitionLenses(text, {
57
58
  }
58
59
 
59
60
  const matchedSet =
60
- [LENS_NAMES_OLDER, LENS_NAMES_NEWER, LENS_NAMES_GENERIC]
61
+ [LENS_NAMES_OLDER, LENS_NAMES_NEWER]
61
62
  .find((candidateSet) => candidateSet.every((name) => names.includes(name))) || null;
62
63
 
63
64
  return { count: names.length, names, matchedSet };
@@ -0,0 +1,50 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+
4
+ const MAX_RECORDS = 300;
5
+
6
+ export function registerGateBlock({
7
+ gate,
8
+ sessionId,
9
+ signature,
10
+ statePath,
11
+ threshold = 2,
12
+ windowMs = 10 * 60 * 1000,
13
+ }) {
14
+ const now = Date.now();
15
+ let rows = [];
16
+ try {
17
+ if (existsSync(statePath)) {
18
+ const raw = JSON.parse(readFileSync(statePath, 'utf8'));
19
+ if (Array.isArray(raw)) rows = raw;
20
+ }
21
+ } catch {}
22
+
23
+ const freshRows = rows.filter((row) =>
24
+ row &&
25
+ typeof row.ts === 'number' &&
26
+ typeof row.sessionId === 'string' &&
27
+ typeof row.gate === 'string' &&
28
+ typeof row.signature === 'string' &&
29
+ (now - row.ts) <= windowMs,
30
+ );
31
+
32
+ const priorMatches = freshRows.filter((row) =>
33
+ row.sessionId === sessionId &&
34
+ row.gate === gate &&
35
+ row.signature === signature,
36
+ );
37
+
38
+ freshRows.push({ ts: now, sessionId, gate, signature });
39
+
40
+ try {
41
+ mkdirSync(dirname(statePath), { recursive: true });
42
+ writeFileSync(statePath, JSON.stringify(freshRows.slice(-MAX_RECORDS), null, 2) + '\n');
43
+ } catch {}
44
+
45
+ return {
46
+ loopDetected: priorMatches.length >= threshold,
47
+ priorCount: priorMatches.length,
48
+ totalCount: priorMatches.length + 1,
49
+ };
50
+ }
@@ -0,0 +1,121 @@
1
+ function withGlobalRegex(regex) {
2
+ if (!(regex instanceof RegExp)) return null;
3
+ const flags = regex.flags.includes('g') ? regex.flags : `${regex.flags}g`;
4
+ return new RegExp(regex.source, flags);
5
+ }
6
+
7
+ export function normalizeRole(entry) {
8
+ if (entry?.message?.role) return entry.message.role;
9
+ if (entry?.role) return entry.role;
10
+ if (entry?.type === 'assistant' || entry?.type === 'user') return entry.type;
11
+ return null;
12
+ }
13
+
14
+ export function normalizeContent(entry) {
15
+ return entry?.message?.content ?? entry?.content ?? [];
16
+ }
17
+
18
+ export function extractTextFromContent(content) {
19
+ if (Array.isArray(content)) {
20
+ return content
21
+ .filter((block) => block && block.type === 'text')
22
+ .map((block) => (typeof block.text === 'string' ? block.text : ''))
23
+ .filter(Boolean)
24
+ .join('\n');
25
+ }
26
+ return typeof content === 'string' ? content : '';
27
+ }
28
+
29
+ export function isToolResultOnlyContent(content) {
30
+ return (
31
+ Array.isArray(content) &&
32
+ content.length > 0 &&
33
+ content.every((block) => block && block.type === 'tool_result')
34
+ );
35
+ }
36
+
37
+ export function isMostlySystemReminder(text, systemReminderRx, threshold = 0.6) {
38
+ if (!text || !(systemReminderRx instanceof RegExp)) return false;
39
+ const reminderRx = withGlobalRegex(systemReminderRx);
40
+ if (!reminderRx) return false;
41
+ const matches = text.match(reminderRx) || [];
42
+ if (matches.length === 0) return false;
43
+ const reminderChars = matches.reduce((sum, match) => sum + match.length, 0);
44
+ return reminderChars / Math.max(1, text.length) >= threshold;
45
+ }
46
+
47
+ export function collectRecentAssistantTextsFromMessages(messages, {
48
+ compactSummaryRx = null,
49
+ hardLookbackCap = 50,
50
+ systemReminderRx = null,
51
+ systemReminderThreshold = 0.6,
52
+ userBoundariesToCross = 1,
53
+ } = {}) {
54
+ if (!Array.isArray(messages) || messages.length === 0) return [];
55
+ const recentAssistantTexts = [];
56
+ let scanned = 0;
57
+ let userBoundariesCrossed = 0;
58
+
59
+ for (let i = messages.length - 1; i >= 0 && scanned < hardLookbackCap; i--) {
60
+ const entry = messages[i];
61
+ const role = normalizeRole(entry);
62
+ const content = normalizeContent(entry);
63
+ if (role === 'user') {
64
+ if (isToolResultOnlyContent(content)) continue;
65
+ const textContent = extractTextFromContent(content);
66
+ if (isMostlySystemReminder(textContent, systemReminderRx, systemReminderThreshold)) {
67
+ continue;
68
+ }
69
+ userBoundariesCrossed++;
70
+ if (userBoundariesCrossed > userBoundariesToCross) break;
71
+ continue;
72
+ }
73
+ if (role !== 'assistant') continue;
74
+ scanned++;
75
+ const text = extractTextFromContent(content);
76
+ if (!text) continue;
77
+ if (compactSummaryRx instanceof RegExp && compactSummaryRx.test(text) && text.length > 4000) {
78
+ continue;
79
+ }
80
+ recentAssistantTexts.push(text);
81
+ }
82
+
83
+ return recentAssistantTexts.reverse();
84
+ }
85
+
86
+ export function collectTurnWindowFromMessages(messages, {
87
+ systemReminderRx = null,
88
+ systemReminderThreshold = 0.6,
89
+ } = {}) {
90
+ if (!Array.isArray(messages) || messages.length === 0) {
91
+ return { assistantText: '', lastUserMessage: '' };
92
+ }
93
+
94
+ const textChunks = [];
95
+ let lastUserMessage = '';
96
+
97
+ for (let i = messages.length - 1; i >= 0; i--) {
98
+ const entry = messages[i];
99
+ const role = normalizeRole(entry);
100
+ const content = normalizeContent(entry);
101
+
102
+ if (role === 'user') {
103
+ if (isToolResultOnlyContent(content)) continue;
104
+ const textContent = extractTextFromContent(content);
105
+ if (isMostlySystemReminder(textContent, systemReminderRx, systemReminderThreshold)) {
106
+ continue;
107
+ }
108
+ lastUserMessage = textContent || lastUserMessage;
109
+ break;
110
+ }
111
+
112
+ if (role !== 'assistant') continue;
113
+ const text = extractTextFromContent(content);
114
+ if (text) textChunks.push(text);
115
+ }
116
+
117
+ return {
118
+ assistantText: textChunks.reverse().join('\n\n'),
119
+ lastUserMessage,
120
+ };
121
+ }
@@ -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,14 @@ 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|truth|harm|trust|power|reflection|context|impact|beauty)\s*:\s*.{20,}/im;
45
+ const INLINE_LENS_NAMES = [
46
+ 'nur', 'mizan', 'hikma', 'tafakkur', 'tadabbur', 'ilham', 'wahi', 'firasah',
47
+ 'truth', 'harm', 'trust', 'power', 'reflection', 'context', 'impact', 'beauty',
48
+ ];
49
+ const INLINE_LENS_RX = new RegExp(
50
+ `^\\s*#\\s*(?:${INLINE_LENS_NAMES.join('|')})\\s*:\\s*.{20,}`,
51
+ 'gim',
52
+ );
45
53
  const VERIFY_BLOCK_RX = /<verify>[\s\S]*?target\s*:[\s\S]*?verified\s*:[\s\S]*?axiom\s*:[\s\S]*?<\/verify>/i;
46
54
  const TRIVIAL_BASH_RX = /^\s*(?:ls|cat|head|tail|grep|find|echo|wc|stat|which|pwd|date|file|du|df|ss|ps)\s/;
47
55
  const SHORT_BASH_LIMIT = 30;
@@ -51,6 +59,26 @@ let _clientError = null;
51
59
  let _lastMizanReceipt = null;
52
60
  let _lastActionSummary = '';
53
61
 
62
+ function receiptPath(sessionId) {
63
+ return join(RECEIPT_DIR, `${String(sessionId || 'opencode').replace(/[^a-zA-Z0-9_-]/g, '_')}.json`);
64
+ }
65
+
66
+ function persistReceipt(sessionId, payload) {
67
+ try {
68
+ mkdirSync(RECEIPT_DIR, { recursive: true, mode: 0o755 });
69
+ writeFileSync(receiptPath(sessionId), JSON.stringify(payload, null, 2) + '\n');
70
+ } catch {}
71
+ }
72
+
73
+ function countInlineCognitionLenses(text) {
74
+ const seen = new Set();
75
+ for (const match of String(text || '').matchAll(INLINE_LENS_RX)) {
76
+ const lens = match[0].match(/^\s*#\s*([a-z_ -]+)\s*:/i)?.[1]?.trim().toLowerCase();
77
+ if (lens) seen.add(lens);
78
+ }
79
+ return seen.size;
80
+ }
81
+
54
82
  function resolveToken() {
55
83
  if (process.env.ARIA_HARNESS_TOKEN) return process.env.ARIA_HARNESS_TOKEN;
56
84
  if (process.env.ARIA_API_KEY) return process.env.ARIA_API_KEY;
@@ -151,9 +179,6 @@ export default async function HarnessGatePlugin(ctx) {
151
179
  // Trivial reads pass
152
180
  if (toolName === 'Bash' && TRIVIAL_BASH_RX.test(cmd) && cmd.length < 200) return;
153
181
  if (toolName === 'Bash' && cmd.length < SHORT_BASH_LIMIT) return;
154
- // Inline cognition in bash comments passes
155
- if (toolName === 'Bash' && INLINE_LENS_RX.test(cmd)) return;
156
-
157
182
  const destructive = DESTRUCTIVE_PATTERNS.find(({ rx }) => rx.test(cmd));
158
183
  const deploy = DEPLOY_PATTERNS.find(({ rx }) => rx.test(cmd));
159
184
  const isFileMutation = ['Edit', 'Write', 'NotebookEdit'].includes(toolName) && filePath;
@@ -182,6 +207,16 @@ export default async function HarnessGatePlugin(ctx) {
182
207
  },
183
208
  });
184
209
  _lastMizanReceipt = pre.receipt || null;
210
+ if (_lastMizanReceipt) {
211
+ persistReceipt(sessionId, {
212
+ updatedAt: new Date().toISOString(),
213
+ sessionId,
214
+ receipt: _lastMizanReceipt,
215
+ preResult: pre.result || null,
216
+ preContract: pre.contract || null,
217
+ preSummary: pre.summary || null,
218
+ });
219
+ }
185
220
  _lastActionSummary = cmdPreview;
186
221
  if (pre.receipt?.blocked || pre.result?.fitrahVetoed || pre.result?.reAuthorSignal) {
187
222
  throw new Error(