@aria_asi/cli 0.2.32 → 0.2.34

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 (93) hide show
  1. package/dist/aria-connector/src/connectors/codebase-awareness.d.ts +8 -1
  2. package/dist/aria-connector/src/connectors/codebase-awareness.d.ts.map +1 -1
  3. package/dist/aria-connector/src/connectors/codebase-awareness.js +126 -71
  4. package/dist/aria-connector/src/connectors/codebase-awareness.js.map +1 -1
  5. package/dist/aria-connector/src/connectors/codex.d.ts.map +1 -1
  6. package/dist/aria-connector/src/connectors/codex.js +98 -0
  7. package/dist/aria-connector/src/connectors/codex.js.map +1 -1
  8. package/dist/aria-connector/src/setup-wizard.d.ts.map +1 -1
  9. package/dist/aria-connector/src/setup-wizard.js +91 -24
  10. package/dist/aria-connector/src/setup-wizard.js.map +1 -1
  11. package/dist/assets/hooks/aria-harness-via-sdk.mjs +26 -8
  12. package/dist/assets/hooks/aria-pre-tool-gate.mjs +60 -1
  13. package/dist/assets/hooks/aria-stop-gate.mjs +69 -3
  14. package/dist/assets/hooks/doctrine_trigger_map.json +43 -0
  15. package/dist/assets/hooks/lib/domain-output-quality.mjs +103 -0
  16. package/dist/assets/hooks/lib/skill-autoload-gate.mjs +14 -0
  17. package/dist/assets/opencode-plugins/harness-context/index.js +1 -1
  18. package/dist/assets/opencode-plugins/harness-gate/index.js +114 -10
  19. package/dist/assets/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +14 -0
  20. package/dist/assets/opencode-plugins/harness-outcome/index.js +39 -0
  21. package/dist/assets/opencode-plugins/harness-stop/index.js +234 -139
  22. package/dist/assets/opencode-plugins/harness-stop/lib/domain-output-quality.js +103 -0
  23. package/dist/assets/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +14 -0
  24. package/dist/runtime/codex-bridge.mjs +71 -8
  25. package/dist/runtime/discipline/CLAUDE.md +2 -2
  26. package/dist/runtime/discipline/doctrine_trigger_map.json +43 -0
  27. package/dist/runtime/discipline/skills/aria-harness/aria-harness-onboarding/SKILL.md +3 -3
  28. package/dist/runtime/doctrine_trigger_map.json +43 -0
  29. package/dist/runtime/harness-daemon.mjs +50 -2
  30. package/dist/runtime/hooks/aria-agent-handoff.mjs +247 -0
  31. package/dist/runtime/hooks/aria-agent-ledger-merge.mjs +164 -0
  32. package/dist/runtime/hooks/aria-architect-fallback.mjs +267 -0
  33. package/dist/runtime/hooks/aria-cognition-substrate-binding.mjs +761 -0
  34. package/dist/runtime/hooks/aria-discovery-record.mjs +101 -0
  35. package/dist/runtime/hooks/aria-harness-via-sdk.mjs +544 -0
  36. package/dist/runtime/hooks/aria-import-resolution-gate.mjs +330 -0
  37. package/dist/runtime/hooks/aria-outcome-record.mjs +84 -0
  38. package/dist/runtime/hooks/aria-pre-emit-dryrun.mjs +329 -0
  39. package/dist/runtime/hooks/aria-pre-text-gate.mjs +112 -0
  40. package/dist/runtime/hooks/aria-pre-tool-gate.mjs +2482 -0
  41. package/dist/runtime/hooks/aria-preprompt-consult.mjs +464 -0
  42. package/dist/runtime/hooks/aria-preturn-memory-gate.mjs +647 -0
  43. package/dist/runtime/hooks/aria-repo-doctrine-gate.mjs +429 -0
  44. package/dist/runtime/hooks/aria-stop-gate.mjs +1882 -0
  45. package/dist/runtime/hooks/aria-trigger-autolearn.mjs +229 -0
  46. package/dist/runtime/hooks/aria-userprompt-abandon-detect.mjs +192 -0
  47. package/dist/runtime/hooks/doctrine_trigger_map.json +577 -0
  48. package/dist/runtime/hooks/lib/canonical-lenses.mjs +65 -0
  49. package/dist/runtime/hooks/lib/domain-output-quality.mjs +103 -0
  50. package/dist/runtime/hooks/lib/gate-audit.mjs +43 -0
  51. package/dist/runtime/hooks/lib/gate-loop-state.mjs +50 -0
  52. package/dist/runtime/hooks/lib/hook-message-window.mjs +121 -0
  53. package/dist/runtime/hooks/lib/skill-autoload-gate.mjs +14 -0
  54. package/dist/runtime/hooks/test-aria-preturn-memory-gate.mjs +245 -0
  55. package/dist/runtime/hooks/test-tier-lens-labeling.mjs +367 -0
  56. package/dist/runtime/manifest.json +2 -2
  57. package/dist/runtime/sdk/BUNDLED.json +2 -2
  58. package/dist/runtime/sdk/index.d.ts +48 -0
  59. package/dist/runtime/sdk/index.js +140 -1
  60. package/dist/runtime/sdk/index.js.map +1 -1
  61. package/dist/runtime/sdk/runWithGovernance.d.ts +16 -0
  62. package/dist/runtime/sdk/runWithGovernance.js +54 -0
  63. package/dist/runtime/sdk/runWithGovernance.js.map +1 -0
  64. package/dist/runtime/service.mjs +339 -10
  65. package/dist/sdk/BUNDLED.json +2 -2
  66. package/dist/sdk/index.d.ts +48 -0
  67. package/dist/sdk/index.js +140 -1
  68. package/dist/sdk/index.js.map +1 -1
  69. package/dist/sdk/runWithGovernance.d.ts +16 -0
  70. package/dist/sdk/runWithGovernance.js +54 -0
  71. package/dist/sdk/runWithGovernance.js.map +1 -0
  72. package/hooks/aria-harness-via-sdk.mjs +26 -8
  73. package/hooks/aria-pre-tool-gate.mjs +60 -1
  74. package/hooks/aria-stop-gate.mjs +69 -3
  75. package/hooks/doctrine_trigger_map.json +43 -0
  76. package/hooks/lib/domain-output-quality.mjs +103 -0
  77. package/hooks/lib/skill-autoload-gate.mjs +14 -0
  78. package/opencode-plugins/harness-context/index.js +1 -1
  79. package/opencode-plugins/harness-gate/index.js +114 -10
  80. package/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +14 -0
  81. package/opencode-plugins/harness-outcome/index.js +39 -0
  82. package/opencode-plugins/harness-stop/index.js +234 -139
  83. package/opencode-plugins/harness-stop/lib/domain-output-quality.js +103 -0
  84. package/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +14 -0
  85. package/package.json +12 -5
  86. package/runtime-src/codex-bridge.mjs +71 -8
  87. package/runtime-src/harness-daemon.mjs +50 -2
  88. package/runtime-src/service.mjs +339 -10
  89. package/scripts/bundle-sdk.mjs +2 -0
  90. package/scripts/self-test-harness-gates.mjs +79 -0
  91. package/src/connectors/codebase-awareness.ts +141 -77
  92. package/src/connectors/codex.ts +98 -0
  93. package/src/setup-wizard.ts +105 -25
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
4
+ import { createHash, randomUUID } from 'node:crypto';
4
5
  import { homedir } from 'node:os';
5
6
  import { delimiter, dirname, resolve } from 'node:path';
6
7
  import { spawn, spawnSync } from 'node:child_process';
7
8
  import { createRequire } from 'node:module';
9
+ import { evaluateSkillGate, formatSkillGateBlock } from './hooks/lib/skill-autoload-gate.mjs';
8
10
 
9
11
  const require = createRequire(import.meta.url);
10
12
  const { WebSocketServer, WebSocket } = require('ws');
@@ -75,6 +77,24 @@ function sleep(ms) {
75
77
  return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
76
78
  }
77
79
 
80
+ function stableEvidenceString(value) {
81
+ if (typeof value === 'string') return value;
82
+ try { return JSON.stringify(value); } catch { return String(value); }
83
+ }
84
+
85
+ function makeEvidenceRef(kind, value, metadata = {}) {
86
+ const raw = stableEvidenceString(value);
87
+ const sha256 = createHash('sha256').update(raw).digest('hex');
88
+ return {
89
+ evidenceId: `ev_${sha256.slice(0, 16)}`,
90
+ kind,
91
+ at: new Date().toISOString(),
92
+ sha256,
93
+ preview: raw.slice(0, 500),
94
+ metadata,
95
+ };
96
+ }
97
+
78
98
  function readRuntimeToken() {
79
99
  const envToken = process.env.ARIA_HARNESS_TOKEN || process.env.ARIA_API_KEY || process.env.OPENAI_API_KEY || process.env.ARIA_MASTER_TOKEN;
80
100
  if (envToken) return envToken;
@@ -123,9 +143,10 @@ function ensureTurnState(threadId, turnId) {
123
143
  userText: '',
124
144
  preReceiptId: null,
125
145
  agentText: '',
126
- bufferedAgentNotifications: [],
127
- firstAgentItemId: null,
128
- };
146
+ bufferedAgentNotifications: [],
147
+ firstAgentItemId: null,
148
+ traceId: `trace_${randomUUID()}`,
149
+ };
129
150
  turnState.set(key, state);
130
151
  }
131
152
  return state;
@@ -186,6 +207,16 @@ async function validateTurnText(threadId, turnId) {
186
207
  if (!text) {
187
208
  return { ok: false, reason: 'No assistant text exists for this turn yet. Codex must emit readable cognition before action.' };
188
209
  }
210
+ const skillGate = evaluateSkillGate({
211
+ sessionId: `codex:${threadId}:${turnId}`,
212
+ surface: 'codex-bridge-output',
213
+ text: [state.userText, text].join('\n'),
214
+ isOutputCloseout: true,
215
+ autoLoadAvailable: false,
216
+ });
217
+ if (!skillGate.ok) {
218
+ return { ok: false, reason: formatSkillGateBlock(skillGate), result: { skillGate } };
219
+ }
189
220
  const result = await postRuntime('/validate-output', {
190
221
  text,
191
222
  sessionId: `codex:${threadId}:${turnId}`,
@@ -198,6 +229,7 @@ async function validateTurnText(threadId, turnId) {
198
229
  stage: 'codex-turn',
199
230
  actor: 'codex-bridge',
200
231
  system: 'codex-bridge',
232
+ traceId: state.traceId,
201
233
  },
202
234
  });
203
235
  const pass = result?.pass === true && result?.validation?.passed !== false;
@@ -210,11 +242,29 @@ async function validateTurnText(threadId, turnId) {
210
242
  };
211
243
  }
212
244
 
213
- async function checkActionAgainstRuntime(action, target, threadId, turnId) {
245
+ async function checkActionAgainstRuntime(action, target, threadId, turnId, metadata = {}) {
246
+ const state = ensureTurnState(threadId, turnId);
247
+ const skillGate = evaluateSkillGate({
248
+ sessionId: `codex:${threadId}:${turnId}`,
249
+ surface: 'codex-bridge-action',
250
+ text: target,
251
+ action: target,
252
+ toolName: action,
253
+ isDeploy: action === 'deploy',
254
+ isMutation: action === 'write',
255
+ autoLoadAvailable: false,
256
+ });
257
+ if (!skillGate.ok) {
258
+ return { ok: false, reason: formatSkillGateBlock(skillGate), result: { skillGate } };
259
+ }
214
260
  const result = await postRuntime('/check-action', {
215
261
  action,
216
262
  target,
217
263
  sessionId: `codex:${threadId}:${turnId}`,
264
+ actor: 'codex',
265
+ roleProfile: process.env.CODEX_ARIA_ROLE_PROFILE || process.env.ARIA_ROLE_PROFILE || null,
266
+ verifyText: state.agentText || '',
267
+ ...metadata,
218
268
  });
219
269
  if (result?.allowed === false) {
220
270
  return {
@@ -244,6 +294,8 @@ async function recordMizanPre(threadId, turnId) {
244
294
  surface: 'codex-bridge',
245
295
  platform: 'codex',
246
296
  userText: state.userText,
297
+ traceId: state.traceId,
298
+ evidenceRefs: [makeEvidenceRef('user_input', state.userText, { threadId, turnId, traceId: state.traceId })],
247
299
  },
248
300
  });
249
301
  state.preReceiptId = result?.receipt?.receiptId || null;
@@ -262,6 +314,8 @@ async function recordMizanPost(threadId, turnId, pass, summary) {
262
314
  evidence: {
263
315
  validated_output: pass,
264
316
  bridge: 'codex',
317
+ trace_id: state.traceId,
318
+ output_ref: makeEvidenceRef('assistant_output', state.agentText || summary, { threadId, turnId, traceId: state.traceId }),
265
319
  },
266
320
  context: {
267
321
  sessionId: `codex:${threadId}:${turnId}`,
@@ -269,6 +323,7 @@ async function recordMizanPost(threadId, turnId, pass, summary) {
269
323
  platform: 'codex',
270
324
  userText: state.userText,
271
325
  summary,
326
+ traceId: state.traceId,
272
327
  },
273
328
  });
274
329
  } catch (error) {
@@ -285,6 +340,8 @@ async function recordMizanPost(threadId, turnId, pass, summary) {
285
340
  details: {
286
341
  threadId,
287
342
  turnId,
343
+ traceId: state.traceId,
344
+ outputRef: makeEvidenceRef('assistant_output', state.agentText || summary, { threadId, turnId, traceId: state.traceId }),
288
345
  },
289
346
  });
290
347
  } catch (error) {
@@ -336,7 +393,9 @@ async function handleApprovalRequest(upstream, downstream, message) {
336
393
  cwd: params.cwd || null,
337
394
  commandActions: params.commandActions || params.parsedCmd || null,
338
395
  }).slice(0, 1500);
339
- const actionCheck = await checkActionAgainstRuntime(action, target, threadId, turnId);
396
+ const actionCheck = await checkActionAgainstRuntime(action, target, threadId, turnId, {
397
+ requireVerify: action === 'delete' || action === 'deploy',
398
+ });
340
399
  if (!actionCheck.ok) {
341
400
  const reason = `Aria runtime denied ${action}: ${actionCheck.reason}`;
342
401
  upstream.send(JSON.stringify(makeGuardianWarning(threadId, reason)));
@@ -360,8 +419,9 @@ async function handleApprovalRequest(upstream, downstream, message) {
360
419
  }
361
420
 
362
421
  if (method === 'item/fileChange/requestApproval' || method === 'applyPatchApproval') {
363
- const target = params.grantRoot || params.reason || params.itemId || 'file-change';
364
- const actionCheck = await checkActionAgainstRuntime('write', String(target), threadId, turnId);
422
+ const target = params.grantRoot || params.path || params.filePath || params.reason || params.itemId || 'file-change';
423
+ const files = [params.path, params.filePath, params.grantRoot].filter((value) => typeof value === 'string' && value.trim());
424
+ const actionCheck = await checkActionAgainstRuntime('write', String(target), threadId, turnId, { files });
365
425
  if (!actionCheck.ok) {
366
426
  const reason = `Aria runtime denied file change: ${actionCheck.reason}`;
367
427
  upstream.send(JSON.stringify(makeGuardianWarning(threadId, reason)));
@@ -390,7 +450,10 @@ async function handleApprovalRequest(upstream, downstream, message) {
390
450
  permissions: params.permissions || null,
391
451
  reason: params.reason || null,
392
452
  }).slice(0, 1500);
393
- const actionCheck = await checkActionAgainstRuntime('write', target, threadId, turnId);
453
+ const files = Array.isArray(params.permissions?.fileSystem?.entries)
454
+ ? params.permissions.fileSystem.entries.filter((value) => typeof value === 'string' && value.trim())
455
+ : [];
456
+ const actionCheck = await checkActionAgainstRuntime('write', target, threadId, turnId, { files });
394
457
  if (!actionCheck.ok) {
395
458
  const reason = `Aria runtime denied permissions request: ${actionCheck.reason}`;
396
459
  upstream.send(JSON.stringify(makeGuardianWarning(threadId, reason)));
@@ -18,7 +18,7 @@ If compaction, gate deadlock, or repeated drift happens, restart from that order
18
18
 
19
19
  ## What this SDK is
20
20
 
21
- `@aria/harness-http-client` is the Aria harness SDK. It binds a process
21
+ `@aria_asi/harness-http-client` is the Aria harness SDK. It binds a process
22
22
  (worker, autonomous loop, dispatched artifact) to Aria's three-layer
23
23
  enforcement substrate so the process cannot drift from Aria's doctrine,
24
24
  axioms, frames, and memory.
@@ -221,7 +221,7 @@ The Stop-gate scans output for these patterns. When matched, the response is rej
221
221
  ### 1. Build a harness-bound LLM call
222
222
 
223
223
  ```ts
224
- import { getBoundHarnessAndPrompt, bindingContext } from '@aria/harness-http-client';
224
+ import { getBoundHarnessAndPrompt, bindingContext } from '@aria_asi/harness-http-client';
225
225
  import { runLayer3Gates } from '@aria/gate-runtime';
226
226
 
227
227
  const { prompt: harnessContext, packet } = await getBoundHarnessAndPrompt(
@@ -508,6 +508,49 @@
508
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
509
  "message": "Duplicate projection path detected. Replace with one per-turn cognition packet and shared consumption."
510
510
  },
511
+ {
512
+ "trigger_id": "premature_task_closeout",
513
+ "trigger": "(?:done|complete|completed|ready|verified|fixed).{0,120}(?:but|except|caveat|remaining|not yet|still|separate|later|blocked|skipped)",
514
+ "rx": "(?:done|complete|completed|ready|verified|fixed).{0,120}(?:but|except|caveat|remaining|not yet|still|separate|later|blocked|skipped)",
515
+ "doctrine": "memory:feedback_no_premature_task_closeout.md",
516
+ "memory": "feedback_no_premature_task_closeout.md",
517
+ "severity": "block",
518
+ "teaching": "A task is not complete while material blockers remain. Completion claims must match the verified scope.",
519
+ "counter_action": "Do not close the task. Fix the blocker now, or create an owner-visible durable task with the exact failing surface and verification gap.",
520
+ "message": "Premature closeout detected: completion language coexists with unresolved blockers. Fix or durably track before emitting."
521
+ },
522
+ {
523
+ "trigger_id": "narrow_e2e_overclaim",
524
+ "trigger": "(?:production-ready|ready for production|works in general|client packages?|npm packages?|SDKs?|runtimes?|harnesses?).{0,220}(?:deal|single flow|one flow|widget|one service|specific path|covered flow)",
525
+ "rx": "(?:production-ready|ready for production|works in general|client packages?|npm packages?|SDKs?|runtimes?|harnesses?).{0,220}(?:deal|single flow|one flow|widget|one service|specific path|covered flow)",
526
+ "doctrine": "memory:feedback_narrow_e2e_overclaim.md",
527
+ "memory": "feedback_narrow_e2e_overclaim.md",
528
+ "severity": "block",
529
+ "teaching": "A narrow e2e does not prove general production readiness for SDKs, npm packages, runtimes, clients, or harnesses.",
530
+ "counter_action": "Run the general readiness matrix or explicitly limit the claim to the verified surface. Do not imply general readiness from one app flow.",
531
+ "message": "Narrow proof overclaim detected. General readiness requires the client/package/runtime/harness matrix."
532
+ },
533
+ {
534
+ "trigger_id": "advisory_gate_not_gate",
535
+ "trigger": "(?:non-blocking|warn(?:ing)? only|advisory|falls? through|fail open|soft fail|log(?:ged)? and continue|quality gate warning)",
536
+ "rx": "(?:non-blocking|warn(?:ing)? only|advisory|falls? through|fail open|soft fail|log(?:ged)? and continue|quality gate warning)",
537
+ "doctrine": "memory:feedback_advisory_gate_is_not_gate.md",
538
+ "memory": "feedback_advisory_gate_is_not_gate.md",
539
+ "severity": "block",
540
+ "teaching": "A gate that only warns or falls through is not a gate. Enforcement must fail closed where quality or doctrine is required.",
541
+ "counter_action": "Convert advisory paths to blocking errors, add a self-test that proves rejection, and install the hardened gate into each consumer surface.",
542
+ "message": "Advisory gate bypass detected. Convert to fail-closed enforcement and prove it with a blocking self-test."
543
+ },
544
+ {
545
+ "trigger_id": "start_new_session_as_gate_fix",
546
+ "trigger": "(?:start|open|begin).{0,40}(?:new|fresh).{0,30}session.{0,120}(?:fix|gate|harness|enforcement)",
547
+ "rx": "(?:start|open|begin).{0,40}(?:new|fresh).{0,30}session.{0,120}(?:fix|gate|harness|enforcement)",
548
+ "doctrine": "memory:feedback_advisory_gate_is_not_gate.md",
549
+ "memory": "feedback_advisory_gate_is_not_gate.md",
550
+ "severity": "block",
551
+ "teaching": "A new session is not an enforcement fix. It only reloads whatever gate contract exists; broken gates remain broken.",
552
+ "counter_action": "Fix the runtime/plugin/hook contract, reinstall it, and run a live bad-action/bad-output self-test that proves blocking."
553
+ },
511
554
  {
512
555
  "trigger_id": "registry_image_drift",
513
556
  "trigger": "image.?pull|ErrImageNeverPull|ImagePullBackOff|registry gc|image.*missing|image.*not.*found",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: aria-harness-onboarding
3
- description: Use when starting a new project that imports @aria/harness-http-client OR when an agent first encounters the SDK in a repo. Loads the foundational 3-layer harness model (packet/BIND/gate-runtime), the doctrine reference list, and points to the four sibling skills (aria-harness-deploy, aria-harness-substrate-binding, aria-harness-no-stripping, aria-harness-output-discipline). Read this once per project to inherit the discipline.
3
+ description: Use when starting a new project that imports @aria_asi/harness-http-client OR when an agent first encounters the SDK in a repo. Loads the foundational 3-layer harness model (packet/BIND/gate-runtime), the doctrine reference list, and points to the four sibling skills (aria-harness-deploy, aria-harness-substrate-binding, aria-harness-no-stripping, aria-harness-output-discipline). Read this once per project to inherit the discipline.
4
4
  ---
5
5
 
6
6
  # Aria Harness Onboarding
@@ -9,7 +9,7 @@ You are about to author code that uses the Aria harness SDK. This skill loads th
9
9
 
10
10
  ## What the SDK is
11
11
 
12
- `@aria/harness-http-client` binds a process (worker, autonomous loop, dispatched artifact) to Aria's three-layer enforcement substrate so the process cannot drift from Aria's doctrine, axioms, frames, and memory.
12
+ `@aria_asi/harness-http-client` binds a process (worker, autonomous loop, dispatched artifact) to Aria's three-layer enforcement substrate so the process cannot drift from Aria's doctrine, axioms, frames, and memory.
13
13
 
14
14
  ## The three layers — every consumer enforces all three
15
15
 
@@ -36,7 +36,7 @@ This onboarding skill is the entry point. Four sibling skills auto-trigger on sp
36
36
  ## Build a harness-bound LLM call (the basic pattern)
37
37
 
38
38
  ```ts
39
- import { getBoundHarnessAndPrompt, bindingContext } from '@aria/harness-http-client';
39
+ import { getBoundHarnessAndPrompt, bindingContext } from '@aria_asi/harness-http-client';
40
40
  import { runLayer3Gates } from '@aria/gate-runtime';
41
41
 
42
42
  const { prompt: harnessContext, packet } = await getBoundHarnessAndPrompt(
@@ -508,6 +508,49 @@
508
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
509
  "message": "Duplicate projection path detected. Replace with one per-turn cognition packet and shared consumption."
510
510
  },
511
+ {
512
+ "trigger_id": "premature_task_closeout",
513
+ "trigger": "(?:done|complete|completed|ready|verified|fixed).{0,120}(?:but|except|caveat|remaining|not yet|still|separate|later|blocked|skipped)",
514
+ "rx": "(?:done|complete|completed|ready|verified|fixed).{0,120}(?:but|except|caveat|remaining|not yet|still|separate|later|blocked|skipped)",
515
+ "doctrine": "memory:feedback_no_premature_task_closeout.md",
516
+ "memory": "feedback_no_premature_task_closeout.md",
517
+ "severity": "block",
518
+ "teaching": "A task is not complete while material blockers remain. Completion claims must match the verified scope.",
519
+ "counter_action": "Do not close the task. Fix the blocker now, or create an owner-visible durable task with the exact failing surface and verification gap.",
520
+ "message": "Premature closeout detected: completion language coexists with unresolved blockers. Fix or durably track before emitting."
521
+ },
522
+ {
523
+ "trigger_id": "narrow_e2e_overclaim",
524
+ "trigger": "(?:production-ready|ready for production|works in general|client packages?|npm packages?|SDKs?|runtimes?|harnesses?).{0,220}(?:deal|single flow|one flow|widget|one service|specific path|covered flow)",
525
+ "rx": "(?:production-ready|ready for production|works in general|client packages?|npm packages?|SDKs?|runtimes?|harnesses?).{0,220}(?:deal|single flow|one flow|widget|one service|specific path|covered flow)",
526
+ "doctrine": "memory:feedback_narrow_e2e_overclaim.md",
527
+ "memory": "feedback_narrow_e2e_overclaim.md",
528
+ "severity": "block",
529
+ "teaching": "A narrow e2e does not prove general production readiness for SDKs, npm packages, runtimes, clients, or harnesses.",
530
+ "counter_action": "Run the general readiness matrix or explicitly limit the claim to the verified surface. Do not imply general readiness from one app flow.",
531
+ "message": "Narrow proof overclaim detected. General readiness requires the client/package/runtime/harness matrix."
532
+ },
533
+ {
534
+ "trigger_id": "advisory_gate_not_gate",
535
+ "trigger": "(?:non-blocking|warn(?:ing)? only|advisory|falls? through|fail open|soft fail|log(?:ged)? and continue|quality gate warning)",
536
+ "rx": "(?:non-blocking|warn(?:ing)? only|advisory|falls? through|fail open|soft fail|log(?:ged)? and continue|quality gate warning)",
537
+ "doctrine": "memory:feedback_advisory_gate_is_not_gate.md",
538
+ "memory": "feedback_advisory_gate_is_not_gate.md",
539
+ "severity": "block",
540
+ "teaching": "A gate that only warns or falls through is not a gate. Enforcement must fail closed where quality or doctrine is required.",
541
+ "counter_action": "Convert advisory paths to blocking errors, add a self-test that proves rejection, and install the hardened gate into each consumer surface.",
542
+ "message": "Advisory gate bypass detected. Convert to fail-closed enforcement and prove it with a blocking self-test."
543
+ },
544
+ {
545
+ "trigger_id": "start_new_session_as_gate_fix",
546
+ "trigger": "(?:start|open|begin).{0,40}(?:new|fresh).{0,30}session.{0,120}(?:fix|gate|harness|enforcement)",
547
+ "rx": "(?:start|open|begin).{0,40}(?:new|fresh).{0,30}session.{0,120}(?:fix|gate|harness|enforcement)",
548
+ "doctrine": "memory:feedback_advisory_gate_is_not_gate.md",
549
+ "memory": "feedback_advisory_gate_is_not_gate.md",
550
+ "severity": "block",
551
+ "teaching": "A new session is not an enforcement fix. It only reloads whatever gate contract exists; broken gates remain broken.",
552
+ "counter_action": "Fix the runtime/plugin/hook contract, reinstall it, and run a live bad-action/bad-output self-test that proves blocking."
553
+ },
511
554
  {
512
555
  "trigger_id": "registry_image_drift",
513
556
  "trigger": "image.?pull|ErrImageNeverPull|ImagePullBackOff|registry gc|image.*missing|image.*not.*found",
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { createServer } from 'node:http';
4
+ import { createHash } from 'node:crypto';
4
5
  import { createRequire } from 'node:module';
5
6
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
6
7
  import { fileURLToPath } from 'node:url';
@@ -35,6 +36,7 @@ const LOCAL_FIRST_PRINCIPLE = 'Truth over deception. No harm. Sacred trust. Powe
35
36
 
36
37
  let cachedPacketEnvelope = loadCachedPacketEnvelope();
37
38
  let refreshInFlight = null;
39
+ let refreshInFlightKey = '';
38
40
  let lastRefreshError = null;
39
41
  let lastRefreshStartedAt = null;
40
42
  let lastRefreshCompletedAt = cachedPacketEnvelope?.timestamp || null;
@@ -99,6 +101,46 @@ function sanitizePacketEnvelope(raw) {
99
101
  };
100
102
  }
101
103
 
104
+ function stableIdentityValue(value) {
105
+ return String(value || '').trim().toLowerCase();
106
+ }
107
+
108
+ function requestCacheKey(body = {}, apiKey = '') {
109
+ const identity = {
110
+ stage: stableIdentityValue(body.stage || body.packetRequest?.stage),
111
+ actor: stableIdentityValue(body.actor || body.packetRequest?.actor),
112
+ system: stableIdentityValue(body.system || body.packetRequest?.system),
113
+ platform: stableIdentityValue(body.platform || body.packetRequest?.platform),
114
+ roleProfile: stableIdentityValue(body.roleProfile || body.role_profile || body.packetRequest?.roleProfile || body.packetRequest?.role_profile),
115
+ token: apiKey ? createHash('sha256').update(apiKey).digest('hex').slice(0, 16) : '',
116
+ };
117
+ return createHash('sha256').update(JSON.stringify(identity)).digest('hex');
118
+ }
119
+
120
+ function extractPacketField(packet, field) {
121
+ if (!packet || typeof packet !== 'object') return '';
122
+ const direct = packet[field];
123
+ if (typeof direct === 'string' && direct.trim()) return stableIdentityValue(direct);
124
+ for (const source of [packet.adapter, packet.harness]) {
125
+ if (typeof source !== 'string') continue;
126
+ const match = source.match(new RegExp(`(?:^|\\n)\\s*${field}\\s*=\\s*([^\\n\\s]+)`, 'i'));
127
+ if (match?.[1]) return stableIdentityValue(match[1]);
128
+ }
129
+ return '';
130
+ }
131
+
132
+ function cachedPacketMatchesRequest(envelope, body = {}) {
133
+ const packet = envelope?.packet;
134
+ if (!packet || typeof packet !== 'object') return false;
135
+ for (const field of ['stage', 'actor', 'system']) {
136
+ const expected = stableIdentityValue(body[field] || body.packetRequest?.[field]);
137
+ if (!expected) continue;
138
+ const actual = extractPacketField(packet, field);
139
+ if (actual && actual !== expected) return false;
140
+ }
141
+ return true;
142
+ }
143
+
102
144
  function loadCachedPacketEnvelope() {
103
145
  const candidates = [LOCAL_PACKET_CACHE_PATH, ...LEGACY_PACKET_CACHE_CANDIDATES];
104
146
  for (const candidate of candidates) {
@@ -220,10 +262,15 @@ async function fetchJsonWithRetry(url, init, attempts = 3) {
220
262
  }
221
263
 
222
264
  async function refreshPacket(body = {}, apiKey = '') {
223
- if (refreshInFlight) return refreshInFlight;
265
+ const cacheKey = requestCacheKey(body, apiKey);
266
+ if (refreshInFlight && refreshInFlightKey === cacheKey) return refreshInFlight;
267
+ if (refreshInFlight) {
268
+ await refreshInFlight.catch(() => {});
269
+ }
224
270
  if (isLoopedUpstream(UPSTREAM_HARNESS_URL)) {
225
271
  throw new Error(`upstream harness URL loops back to local daemon: ${UPSTREAM_HARNESS_URL}`);
226
272
  }
273
+ refreshInFlightKey = cacheKey;
227
274
  lastRefreshStartedAt = new Date().toISOString();
228
275
  lastRefreshError = null;
229
276
  refreshInFlight = (async () => {
@@ -258,6 +305,7 @@ async function refreshPacket(body = {}, apiKey = '') {
258
305
  throw error;
259
306
  } finally {
260
307
  refreshInFlight = null;
308
+ refreshInFlightKey = '';
261
309
  }
262
310
  }
263
311
 
@@ -268,7 +316,7 @@ function queuePacketRefresh(body, apiKey) {
268
316
  }
269
317
 
270
318
  async function resolvePacketEnvelope(packetRequest = {}, apiKey = '', message = '') {
271
- if (cachedPacketEnvelope) {
319
+ if (cachedPacketEnvelope && cachedPacketMatchesRequest(cachedPacketEnvelope, packetRequest)) {
272
320
  queuePacketRefresh(packetRequest, apiKey);
273
321
  return cachedPacketEnvelope;
274
322
  }
@@ -0,0 +1,247 @@
1
+ #!/usr/bin/env node
2
+ // aria-agent-handoff.mjs — PreToolUse hook for the Agent tool.
3
+ // Writes a handoff JSON before a sub-agent spawns so it inherits the
4
+ // parent's owner/client-tier identity + ledger path instead of starting fresh.
5
+ //
6
+ // Owner tier → ~/.claude/aria-agent-harness-handoff.json
7
+ // Client tier → /var/lib/aria-licensee/{jti}/handoff.json
8
+ //
9
+ // Tier is determined by whether a JTI is present in the license file at
10
+ // ~/.aria/license.json. If JTI is present → client tier. If absent (owner
11
+ // JWT at ~/.aria/owner-token) → owner tier.
12
+ //
13
+ // Tier protection: client handoffs NEVER write to ~/.claude/ and NEVER
14
+ // carry master tokens. Client paths are scoped to /var/lib/aria-licensee/{jti}/.
15
+ // Default-on per Hamza doctrine — no env-flag gate to disable.
16
+ //
17
+ // Doctrine: feedback_aria_does_work.md (sub-agents inherit harness)
18
+ // feedback_implementation_coupled_cognition.md (handoff is the impl)
19
+ // feedback_no_flag_without_fix.md (no silent bypasses)
20
+
21
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, appendFileSync } from 'node:fs';
22
+ import { dirname, join } from 'node:path';
23
+ import { homedir } from 'node:os';
24
+ import { createRequire } from 'node:module';
25
+
26
+ const HOME = homedir();
27
+ const LOG = `${HOME}/.claude/aria-pre-tool-gate.log`;
28
+ const PACKET_CACHE = `${HOME}/.claude/.aria-harness-last-packet.json`;
29
+ const LICENSE_PATH = `${HOME}/.aria/license.json`;
30
+ const OWNER_TOKEN_PATH = `${HOME}/.aria/owner-token`;
31
+ const HANDOFF_TTL_MS = 5 * 60 * 1000;
32
+ const SHARED_RUNTIME_URL = (process.env.ARIA_RUNTIME_URL || 'http://127.0.0.1:4319').replace(/\/+$/, '');
33
+ const SHARED_SDK_ROOT = `${HOME}/.aria/sdk`;
34
+
35
+ function audit(msg) {
36
+ try {
37
+ if (!existsSync(dirname(LOG))) mkdirSync(dirname(LOG), { recursive: true });
38
+ appendFileSync(LOG, `${new Date().toISOString()} [agent-handoff] ${msg}\n`);
39
+ } catch {}
40
+ }
41
+
42
+ let input = '';
43
+ for await (const chunk of process.stdin) input += chunk;
44
+
45
+ let event;
46
+ try { event = JSON.parse(input); }
47
+ catch { process.exit(0); }
48
+
49
+ const toolName = event.tool_name ?? event.toolName ?? '';
50
+ if (toolName !== 'Agent' && toolName !== 'agent') process.exit(0);
51
+
52
+ const sessionId =
53
+ event.session_id ?? event.sessionId ??
54
+ (event.transcript_path
55
+ ? event.transcript_path.split('/').pop()?.replace(/\.[^.]+$/, '')
56
+ : null) ??
57
+ 'unknown';
58
+
59
+ // ── Tier detection ──────────────────────────────────────────────────────────
60
+ // Read license.json to detect client tier (has jti). If only owner-token
61
+ // exists → owner tier. No license/owner-token → treat as owner tier (dev).
62
+
63
+ let jti = null;
64
+ let isClientTier = false;
65
+ let licenseToken = '';
66
+
67
+ try {
68
+ if (existsSync(LICENSE_PATH)) {
69
+ const lic = JSON.parse(readFileSync(LICENSE_PATH, 'utf8'));
70
+ jti = lic.jti ?? null;
71
+ licenseToken = lic.token ?? lic.license ?? '';
72
+ isClientTier = Boolean(jti);
73
+ }
74
+ } catch (err) {
75
+ audit(`warn: license.json read failed: ${(err?.message || err).toString().slice(0, 120)}`);
76
+ }
77
+
78
+ // For owner tier, read harness token from env or owner-token file.
79
+ let harnessToken = '';
80
+ if (isClientTier) {
81
+ // Client tier: their own license JWT, never master token.
82
+ harnessToken = licenseToken;
83
+ } else {
84
+ harnessToken =
85
+ process.env.ARIA_HARNESS_TOKEN ||
86
+ process.env.ARIA_API_KEY ||
87
+ process.env.ARIA_MASTER_TOKEN ||
88
+ '';
89
+ if (!harnessToken && existsSync(OWNER_TOKEN_PATH)) {
90
+ try { harnessToken = readFileSync(OWNER_TOKEN_PATH, 'utf8').trim(); } catch {}
91
+ }
92
+ }
93
+
94
+ const harnessUrl =
95
+ process.env.ARIA_HIVE_RUNTIME_URL ||
96
+ process.env.ARIA_HARNESS_BASE_URL ||
97
+ process.env.ARIA_HARNESS_URL ||
98
+ 'https://harness.ariasos.com';
99
+
100
+ // ── OwnerTier signal resolution ──────────────────────────────────────────────
101
+ let ownerTier = {
102
+ hamza: !isClientTier,
103
+ trustedExec: false,
104
+ roleProfile: isClientTier ? 'client_tier_worker' : 'general_worker',
105
+ };
106
+
107
+ if (!isClientTier) {
108
+ try {
109
+ if (existsSync(PACKET_CACHE)) {
110
+ const cached = JSON.parse(readFileSync(PACKET_CACHE, 'utf8'));
111
+ const signals = cached?.contractGate?.signals ?? {};
112
+ ownerTier.hamza = signals.hamza === true || signals.hamza === 'true';
113
+ ownerTier.trustedExec = (cached?.harness || '').includes('trusted_exec_policy=allowed');
114
+ const adapterStr = cached?.adapter || cached?.harness || '';
115
+ const roleMatch = adapterStr.match(/role_profile\s*=\s*(\S+)/);
116
+ if (roleMatch) ownerTier.roleProfile = roleMatch[1];
117
+ }
118
+ } catch (err) {
119
+ audit(`warn: packet cache read failed: ${(err?.message || err).toString().slice(0, 120)}`);
120
+ }
121
+ }
122
+
123
+ // ── Path resolution (tier-scoped) ───────────────────────────────────────────
124
+ const safeSession = String(sessionId).replace(/[^a-zA-Z0-9_-]/g, '_');
125
+
126
+ let handoffPath;
127
+ let parentLedgerPath;
128
+
129
+ if (isClientTier) {
130
+ const base = `/var/lib/aria-licensee/${jti}`;
131
+ handoffPath = `${base}/handoff.json`;
132
+ parentLedgerPath = `${base}/aria-discoveries-${safeSession}.jsonl`;
133
+ } else {
134
+ handoffPath = `${HOME}/.claude/aria-agent-harness-handoff.json`;
135
+ parentLedgerPath = `${HOME}/.claude/aria-discoveries-${safeSession}.jsonl`;
136
+ }
137
+
138
+ // ── Harness packet fetch for sub-agent substrate binding ────────────────────
139
+ // Layer 1: after writing identity handoff, fetch the harness packet from
140
+ // /api/harness/codex so the sub-agent can load COGNITION (not just identity).
141
+ // Fail-soft: any fetch failure logs a warning and leaves the handoff identity-only.
142
+ // Never blocks the spawn — packet is substrate enrichment, not a gate.
143
+ //
144
+ // Tier-aware packet paths:
145
+ // Owner: ~/.claude/aria-agent-harness-packet.json
146
+ // Client: /var/lib/aria-licensee/{jti}/aria-agent-harness-packet.json
147
+ let packetPath = null;
148
+ if (isClientTier) {
149
+ packetPath = `/var/lib/aria-licensee/${jti}/aria-agent-harness-packet.json`;
150
+ } else {
151
+ packetPath = `${HOME}/.claude/aria-agent-harness-packet.json`;
152
+ }
153
+
154
+ async function fetchAndWriteHarnessPacket() {
155
+ if (!harnessToken || !harnessUrl) {
156
+ audit('warn: skipping packet fetch — no harnessToken or harnessUrl');
157
+ return null;
158
+ }
159
+ try {
160
+ const body = JSON.stringify({
161
+ stage: 'agent-spawn',
162
+ actor: 'harness-http-client',
163
+ system: 'claude-coding-agent',
164
+ surface: 'platform:harness-http-client',
165
+ isHamza: ownerTier.hamza,
166
+ sessionId: sessionId,
167
+ mode: 'subagent',
168
+ });
169
+ const resp = await fetch(`${harnessUrl}/api/harness/codex`, {
170
+ method: 'POST',
171
+ headers: {
172
+ 'Content-Type': 'application/json',
173
+ 'Authorization': `Bearer ${harnessToken}`,
174
+ },
175
+ body,
176
+ });
177
+ if (!resp.ok) {
178
+ audit(`warn: harness packet fetch HTTP ${resp.status} — identity-only handoff`);
179
+ return null;
180
+ }
181
+ const data = await resp.json();
182
+ mkdirSync(dirname(packetPath), { recursive: true });
183
+ writeFileSync(packetPath, JSON.stringify(data, null, 2));
184
+ audit(`wrote harness packet tier=${isClientTier ? 'client' : 'owner'} path=${packetPath}`);
185
+ return packetPath;
186
+ } catch (err) {
187
+ audit(`warn: harness packet fetch failed: ${(err?.message || err).toString().slice(0, 200)} — identity-only handoff`);
188
+ return null;
189
+ }
190
+ }
191
+
192
+ const resolvedPacketPath = await fetchAndWriteHarnessPacket();
193
+
194
+ const handoff = {
195
+ writtenAt: new Date().toISOString(),
196
+ parentSessionId: sessionId,
197
+ harnessToken,
198
+ harnessUrl,
199
+ runtimeUrl: SHARED_RUNTIME_URL,
200
+ sharedSdkRoot: SHARED_SDK_ROOT,
201
+ ownerTier,
202
+ parentLedgerPath,
203
+ cognitionMode: 'mizan-pre-mid-post',
204
+ requiredSkillPacks: ['aria-harness', 'aria-cognition'],
205
+ requiredLenses: ['nur', 'mizan', 'hikma', 'tafakkur', 'tadabbur', 'ilham', 'wahi', 'firasah'],
206
+ cognitionOperatingMethod: {
207
+ purpose: 'Use Aria cognition to improve the assigned work, not to satisfy formatting after the fact.',
208
+ loop: [
209
+ 'perceive real substrate before acting',
210
+ 'infer constraints and missing evidence',
211
+ 'shape the next tool/input/output from those constraints',
212
+ 'predict the observable result before claiming success',
213
+ 'return evidence, residual risk, and exact artifact impact to the parent',
214
+ ],
215
+ requiredReturnShape: {
216
+ substrateUsed: ['axiom/frame/memory/packet anchors actually loaded'],
217
+ decisionDelta: 'what changed in the work because cognition ran',
218
+ artifactImpact: 'specific files, findings, commands, or prose semantics affected',
219
+ evidence: 'observed tool output, file state, endpoint result, or explicit unverified boundary',
220
+ unresolvedRisk: 'remaining risk or none',
221
+ },
222
+ prohibitedPatterns: [
223
+ 'decorative cognition that does not alter the work',
224
+ 'summary-only sub-agent result without evidence',
225
+ 'asking the parent/user for choices that substrate can resolve',
226
+ 'claiming completion from intent rather than observation',
227
+ ],
228
+ },
229
+ ttlMs: HANDOFF_TTL_MS,
230
+ // harnessPacketPath is set only if packet was successfully fetched;
231
+ // absent means sub-agent should fall back to calling /api/harness/codex directly.
232
+ ...(resolvedPacketPath ? { harnessPacketPath: resolvedPacketPath } : {}),
233
+ };
234
+
235
+ try {
236
+ mkdirSync(dirname(handoffPath), { recursive: true });
237
+ writeFileSync(handoffPath, JSON.stringify(handoff, null, 2));
238
+ audit(
239
+ `wrote handoff tier=${isClientTier ? 'client' : 'owner'} jti=${jti ?? 'none'} ` +
240
+ `session=${sessionId} hamza=${ownerTier.hamza} role=${ownerTier.roleProfile} ` +
241
+ `packetPath=${resolvedPacketPath ?? 'none'}`,
242
+ );
243
+ } catch (err) {
244
+ audit(`ERROR: handoff write failed: ${(err?.message || err).toString().slice(0, 200)}`);
245
+ }
246
+
247
+ process.exit(0);