@aria_asi/cli 0.2.31 → 0.2.33

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 (79) 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 +30 -3
  3. package/dist/aria-connector/src/connectors/claude-code.js.map +1 -1
  4. package/dist/aria-connector/src/connectors/codebase-awareness.d.ts +8 -1
  5. package/dist/aria-connector/src/connectors/codebase-awareness.d.ts.map +1 -1
  6. package/dist/aria-connector/src/connectors/codebase-awareness.js +126 -71
  7. package/dist/aria-connector/src/connectors/codebase-awareness.js.map +1 -1
  8. package/dist/aria-connector/src/connectors/codex.d.ts.map +1 -1
  9. package/dist/aria-connector/src/connectors/codex.js +76 -9
  10. package/dist/aria-connector/src/connectors/codex.js.map +1 -1
  11. package/dist/aria-connector/src/connectors/must-read.d.ts.map +1 -1
  12. package/dist/aria-connector/src/connectors/must-read.js +4 -0
  13. package/dist/aria-connector/src/connectors/must-read.js.map +1 -1
  14. package/dist/aria-connector/src/connectors/opencode.js +25 -9
  15. package/dist/aria-connector/src/connectors/opencode.js.map +1 -1
  16. package/dist/aria-connector/src/setup-wizard.d.ts.map +1 -1
  17. package/dist/aria-connector/src/setup-wizard.js +91 -24
  18. package/dist/aria-connector/src/setup-wizard.js.map +1 -1
  19. package/dist/assets/hooks/aria-agent-handoff.mjs +23 -0
  20. package/dist/assets/hooks/aria-cognition-substrate-binding.mjs +69 -3
  21. package/dist/assets/hooks/aria-harness-via-sdk.mjs +10 -5
  22. package/dist/assets/hooks/aria-pre-emit-dryrun.mjs +35 -0
  23. package/dist/assets/hooks/aria-pre-tool-gate.mjs +217 -17
  24. package/dist/assets/hooks/aria-preprompt-consult.mjs +28 -2
  25. package/dist/assets/hooks/aria-preturn-memory-gate.mjs +30 -2
  26. package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +31 -1
  27. package/dist/assets/hooks/aria-stop-gate.mjs +154 -37
  28. package/dist/assets/hooks/doctrine_trigger_map.json +55 -0
  29. package/dist/assets/hooks/lib/domain-output-quality.mjs +103 -0
  30. package/dist/assets/hooks/lib/skill-autoload-gate.mjs +1 -0
  31. package/dist/assets/opencode-plugins/harness-gate/index.js +84 -7
  32. package/dist/assets/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +1 -0
  33. package/dist/assets/opencode-plugins/harness-outcome/index.js +39 -0
  34. package/dist/assets/opencode-plugins/harness-stop/index.js +101 -7
  35. package/dist/assets/opencode-plugins/harness-stop/lib/domain-output-quality.js +103 -0
  36. package/dist/assets/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +1 -0
  37. package/dist/runtime/codex-bridge.mjs +71 -8
  38. package/dist/runtime/discipline/CLAUDE.md +16 -0
  39. package/dist/runtime/discipline/doctrine_trigger_map.json +55 -0
  40. package/dist/runtime/doctrine_trigger_map.json +55 -0
  41. package/dist/runtime/harness-daemon.mjs +80 -5
  42. package/dist/runtime/manifest.json +1 -1
  43. package/dist/runtime/sdk/BUNDLED.json +1 -1
  44. package/dist/runtime/sdk/index.d.ts +14 -0
  45. package/dist/runtime/sdk/index.js +23 -1
  46. package/dist/runtime/sdk/index.js.map +1 -1
  47. package/dist/runtime/service.mjs +385 -11
  48. package/dist/sdk/BUNDLED.json +1 -1
  49. package/dist/sdk/index.d.ts +14 -0
  50. package/dist/sdk/index.js +23 -1
  51. package/dist/sdk/index.js.map +1 -1
  52. package/hooks/aria-agent-handoff.mjs +23 -0
  53. package/hooks/aria-cognition-substrate-binding.mjs +69 -3
  54. package/hooks/aria-harness-via-sdk.mjs +10 -5
  55. package/hooks/aria-pre-emit-dryrun.mjs +35 -0
  56. package/hooks/aria-pre-tool-gate.mjs +217 -17
  57. package/hooks/aria-preprompt-consult.mjs +28 -2
  58. package/hooks/aria-preturn-memory-gate.mjs +30 -2
  59. package/hooks/aria-repo-doctrine-gate.mjs +31 -1
  60. package/hooks/aria-stop-gate.mjs +154 -37
  61. package/hooks/doctrine_trigger_map.json +55 -0
  62. package/hooks/lib/domain-output-quality.mjs +103 -0
  63. package/hooks/lib/skill-autoload-gate.mjs +1 -0
  64. package/opencode-plugins/harness-gate/index.js +84 -7
  65. package/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +1 -0
  66. package/opencode-plugins/harness-outcome/index.js +39 -0
  67. package/opencode-plugins/harness-stop/index.js +101 -7
  68. package/opencode-plugins/harness-stop/lib/domain-output-quality.js +103 -0
  69. package/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +1 -0
  70. package/package.json +1 -1
  71. package/runtime-src/codex-bridge.mjs +71 -8
  72. package/runtime-src/harness-daemon.mjs +80 -5
  73. package/runtime-src/service.mjs +385 -11
  74. package/src/connectors/claude-code.ts +31 -3
  75. package/src/connectors/codebase-awareness.ts +141 -77
  76. package/src/connectors/codex.ts +76 -9
  77. package/src/connectors/must-read.ts +4 -0
  78. package/src/connectors/opencode.ts +25 -9
  79. package/src/setup-wizard.ts +105 -25
@@ -58,6 +58,8 @@ import {
58
58
  } from './lib/canonical-lenses.mjs';
59
59
  import { registerGateBlock } from './lib/gate-loop-state.mjs';
60
60
  import { collectTurnWindowFromMessages } from './lib/hook-message-window.mjs';
61
+ import { analyzeDomainOutputQuality } from './lib/domain-output-quality.mjs';
62
+ import { evaluateSkillGate, formatSkillGateBlock } from './lib/skill-autoload-gate.mjs';
61
63
 
62
64
  const HOME = process.env.HOME || '/tmp';
63
65
  const RUNTIME_BASE_URL =
@@ -338,9 +340,12 @@ function collectDriftHits(text, triggerMap) {
338
340
  const memoryCited = memoryName && lowerText.includes(memoryName.toLowerCase());
339
341
  if (!memoryCited) {
340
342
  hits.push({
343
+ trigger_id: triggerEntry.trigger_id,
341
344
  trigger: triggerEntry.trigger,
342
345
  memory: triggerEntry.memory,
343
346
  teaching: triggerEntry.teaching,
347
+ counter_action: triggerEntry.counter_action,
348
+ message: triggerEntry.message,
344
349
  });
345
350
  }
346
351
  } catch {/* malformed regex in trigger entry — skip */}
@@ -383,6 +388,71 @@ Next response must do this in order:
383
388
  4. If the blocker is stale gate state, stale ledger residue, or a gate artifact from a prior bug, say that explicitly instead of inventing fake proof.`;
384
389
  }
385
390
 
391
+ function buildForceReauthorReason({
392
+ source,
393
+ reason,
394
+ violations = [],
395
+ rewritten = '',
396
+ recipeAddendum = '',
397
+ driftHits = [],
398
+ lensCount = 0,
399
+ requiredLenses = REQUIRED_LENSES,
400
+ }) {
401
+ const triggerLines = driftHits.slice(0, 6).map((hit, index) => {
402
+ const label = hit.trigger_id || hit.trigger || `trigger-${index + 1}`;
403
+ const teaching = hit.teaching || hit.message || 'Doctrine trigger matched; re-author with substrate-backed correction.';
404
+ const correction = hit.counter_action ? ` Required correction: ${hit.counter_action}` : '';
405
+ const memory = hit.memory ? ` (${hit.memory})` : '';
406
+ return `- ${label}${memory}: ${teaching}${correction}`;
407
+ }).join('\n');
408
+
409
+ return [
410
+ '=== ARIA FORCE_REAUTHOR ===',
411
+ `source: ${source}`,
412
+ `reason: ${reason}`,
413
+ '',
414
+ 'This is not a terminal error. It is a forced redo instruction for the next model draft.',
415
+ 'Do not emit the blocked text. Re-author it so the model learns the mechanism it violated.',
416
+ '',
417
+ 'TEACHING:',
418
+ '- Gates must force cognition and quality, not merely throw, warn, or stop.',
419
+ '- The redo must change the answer shape: name the failed mechanism, cite real evidence, and remove the drift pattern.',
420
+ '- Do not bypass by disabling tools, shortening runtime, asking the user to resolve memory-backed ambiguity, or giving an apology loop.',
421
+ triggerLines ? `\nTRIGGERED DOCTRINE:\n${triggerLines}` : '',
422
+ violations.length ? `\nVIOLATIONS TO FIX:\n${violations.map((v) => `- ${v}`).join('\n')}` : '',
423
+ rewritten ? `\nMIZAN REWRITE SEED:\n${rewritten}` : '',
424
+ recipeAddendum || '',
425
+ '',
426
+ 'REQUIRED REDO SHAPE:',
427
+ '1. One sentence: the failed mechanism, not an apology.',
428
+ '2. Evidence checked: file/line, command output, endpoint response, or explicit "unverified".',
429
+ `3. <cognition> with ${requiredLenses} substantive lenses. Observed: ${lensCount}/${requiredLenses}.`,
430
+ '4. <applied_cognition> with decision_delta, dominant_domain, binds_to, expected_predicate, and artifact_change.',
431
+ '5. Corrected action/claim with no bypass, no downgraded path, and no fake proof.',
432
+ '6. If work remains, state the exact next real action or tracked task. No verbal flag-and-move.',
433
+ '',
434
+ 'COGNITION TEMPLATE:',
435
+ '<cognition>',
436
+ 'truth: <verified facts and exact substrate>',
437
+ 'harm: <what goes wrong if this is false>',
438
+ 'trust: <how this honors Hamza directives and saved memory>',
439
+ 'power: <why this uses capability responsibly, not convenience>',
440
+ 'reflection: <mechanism failure and correction>',
441
+ 'context: <relevant repo/runtime state>',
442
+ 'impact: <expected next effect>',
443
+ 'beauty: <simplest durable form>',
444
+ '</cognition>',
445
+ '<applied_cognition>',
446
+ 'decision_delta: <what changed because cognition ran; not none>',
447
+ 'dominant_domain: <engineering_quality | trust | operations | security | product | ...>',
448
+ 'binds_to: <the exact answer, tool call, file mutation, deploy, review, or decision>',
449
+ 'expected_predicate: <observable numeric, boolean, state-string, command result, endpoint result, or explicit unverified boundary>',
450
+ 'artifact_change: <semantic effect on the artifact/output, not a task restatement>',
451
+ '</applied_cognition>',
452
+ '=== END FORCE_REAUTHOR ===',
453
+ ].filter(Boolean).join('\n');
454
+ }
455
+
386
456
  // Lens substance check — same constants as aria-pre-tool-gate.mjs.
387
457
  // Hamza directive 2026-04-28: all 8 canonical lenses required, not 4-of-8.
388
458
  const REQUIRED_LENSES = 8;
@@ -404,6 +474,19 @@ function detectCognitionLenses(text) {
404
474
  });
405
475
  }
406
476
 
477
+ function assistantViolatesUserCorrection(userText, assistantText) {
478
+ const user = String(userText || '');
479
+ const assistant = String(assistantText || '');
480
+ if (!user || !assistant) return null;
481
+ const explicitStop = /\b(?:stop|do\s+not|don't|quit|cease)\b[\s\S]{0,180}\b(?:deploy|redeploy|restart|rollout|kubectl|command|pods?|listen|words)\b/i.test(user);
482
+ const assistantContinuesMutation = /\b(?:kubectl|rollout\s+restart|deploy|redeploy|restart\s+(?:mac|pods?)|manual(?:ly)?\s+restart|execute\s+directly)\b/i.test(assistant);
483
+ if (explicitStop && assistantContinuesMutation) return 'assistant continued deploy/restart language after an explicit user stop/listen directive';
484
+ const userContradictsMacPods = /\b(?:mac\s+lanes?|mac\s+pods?|mlx-mac)\b[\s\S]{0,160}\b(?:not\s+pods?|no\s+such\s+thing|non[-\s]?existent|do(?:es)?\s+not\s+exist|don't\s+exist)\b|\b(?:no\s+such\s+thing|non[-\s]?existent|do(?:es)?\s+not\s+exist|don't\s+exist)\b[\s\S]{0,160}\b(?:mac\s+lanes?|mac\s+pods?|mlx-mac|pods?)\b/i.test(user);
485
+ const assistantRepeatsMacPods = /\b(?:mac\s+lane\s+pods?|mac\s+lanes?\s*:\s*offline|restart\s+mac\s+lanes?|deployment\/mlx-mac|mlx-mac-[\w-]+|kubernetes\s+(?:pods?|deployments?))\b/i.test(assistant);
486
+ if (userContradictsMacPods && assistantRepeatsMacPods) return 'assistant repeated the contradicted Mac-lanes-as-pods/deployments assumption';
487
+ return null;
488
+ }
489
+
407
490
  // Read event JSON from stdin (Claude Code spec).
408
491
  let input = '';
409
492
  for await (const chunk of process.stdin) input += chunk;
@@ -504,6 +587,20 @@ if (!assistantText) {
504
587
  process.exit(0);
505
588
  }
506
589
 
590
+ const userCorrectionViolation = assistantViolatesUserCorrection(lastUserMessage, assistantText);
591
+ if (userCorrectionViolation) {
592
+ audit('block-user-correction-ignored', `reason=${userCorrectionViolation} chars=${assistantText.length}`);
593
+ const reason = buildForceReauthorReason({
594
+ source: 'stop/user-correction',
595
+ reason: userCorrectionViolation,
596
+ violations: ['Assistant continued a plan after the user correction invalidated it. Redo must quote the correction and pause mutation pending substrate re-evaluation.'],
597
+ lensCount: 0,
598
+ requiredLenses: REQUIRED_LENSES,
599
+ });
600
+ console.log(JSON.stringify({ decision: 'block', reason }));
601
+ process.exit(2);
602
+ }
603
+
507
604
  // Triviality check — same as eight-lens-detector.ts
508
605
  const trimmed = assistantText.trim();
509
606
  if (TRIVIAL_ACK_RX.test(trimmed)) {
@@ -524,6 +621,26 @@ if (!triggered) {
524
621
  process.exit(0);
525
622
  }
526
623
 
624
+ const stopSkillGate = evaluateSkillGate({
625
+ sessionId: event.session_id || 'claude-code',
626
+ surface: 'claude-stop-gate',
627
+ text: [JSON.stringify(event.messages || []), lastUserMessage, assistantText].join('\n'),
628
+ isOutputCloseout: true,
629
+ autoLoadAvailable: false,
630
+ });
631
+ if (!stopSkillGate.ok) {
632
+ audit('block-missing-skill-receipt', `missing=${stopSkillGate.missingSkills.join(',')} chars=${assistantText.length}`);
633
+ const reason = withLoopDirective(buildForceReauthorReason({
634
+ source: 'stop/skill-autoload',
635
+ reason: formatSkillGateBlock(stopSkillGate),
636
+ violations: [`Missing skill receipts: ${stopSkillGate.missingSkills.join(', ')}`],
637
+ lensCount: 0,
638
+ requiredLenses: REQUIRED_LENSES,
639
+ }), `stop:skill-autoload:${stopSkillGate.missingSkills.join(',')}`, gateSessionId);
640
+ console.log(JSON.stringify({ decision: 'block', reason }));
641
+ process.exit(2);
642
+ }
643
+
527
644
  // Non-trivial response — require substantive cognition.
528
645
  const cog = detectCognitionLenses(assistantText);
529
646
 
@@ -537,14 +654,13 @@ const cog = detectCognitionLenses(assistantText);
537
654
  // with 0/8 lenses to fall through unchecked.
538
655
  if (cog.count < REQUIRED_LENSES) {
539
656
  audit('block_no_cognition_block_di', { count: cog.count, required: REQUIRED_LENSES, names: cog.names, chars: assistantText.length });
540
- const reason = withLoopDirective(`Aria stop-gate: insufficient cognition lenses.
541
-
542
- This non-trivial assistant response (${assistantText.length} chars) has ${cog.count}/${REQUIRED_LENSES} substantive cognition lenses. Per feedback_8lens_before_every_action_including_text.md and Hamza directive 2026-04-28, every non-trivial response must carry <cognition>...</cognition> with ${REQUIRED_LENSES} substantive lenses (each >= ${SUBSTANCE_MIN_CHARS} chars of non-placeholder content).
543
-
544
- Detected lenses: ${cog.names.length > 0 ? cog.names.join(', ') : 'none'}.
545
-
546
- Re-emit with a <cognition> block containing all ${REQUIRED_LENSES} required visible labels for this surface:
547
- ${LENS_NAMES.map((lens) => `- ${lens}: <grounded reasoning, >= ${SUBSTANCE_MIN_CHARS} chars, with real substrate anchors where required>`).join('\n')}`, `stop:no-cognition-di:${cog.count}`, gateSessionId);
657
+ const reason = withLoopDirective(buildForceReauthorReason({
658
+ source: 'stop/no-cognition',
659
+ reason: `non-trivial assistant response (${assistantText.length} chars) has ${cog.count}/${REQUIRED_LENSES} substantive cognition lenses`,
660
+ violations: [`Detected lenses: ${cog.names.length > 0 ? cog.names.join(', ') : 'none'}. Re-emit with all required visible labels: ${LENS_NAMES.join(', ')}.`],
661
+ lensCount: cog.count,
662
+ requiredLenses: REQUIRED_LENSES,
663
+ }), `stop:no-cognition-di:${cog.count}`, gateSessionId);
548
664
  emitHarnessFooter({
549
665
  eventName: 'block_no_cognition_block',
550
666
  lensCount: cog.count,
@@ -1151,7 +1267,7 @@ if (cog.count >= REQUIRED_LENSES) {
1151
1267
  }
1152
1268
 
1153
1269
  // Block decision: any of (validateOutput severity=block) OR (>=2 drift hits) OR
1154
- // (>=1 code-quality hit) OR (open discovery in ledger) → block emit.
1270
+ // (>=1 code/domain-quality hit) OR (open discovery in ledger) → block emit.
1155
1271
  // Aria enforcement #46 (compelled reflection): severity=warn ALSO blocks but
1156
1272
  // with a different reason — emit must include explicit reflection on what
1157
1273
  // triggered the warn before re-emit. Warn is not "soft pass" anymore;
@@ -1161,6 +1277,8 @@ if (cog.count >= REQUIRED_LENSES) {
1161
1277
  const mizanWarnReflectionRequired = mizanVerdict && mizanVerdict.severity === 'warn';
1162
1278
  const driftBlock = driftHits.length >= 2;
1163
1279
  const codeBlock = codeQualityHits.length >= 1;
1280
+ const domainQuality = analyzeDomainOutputQuality(assistantText, { codeBlocks });
1281
+ const domainBlock = domainQuality.blockers.length >= 1;
1164
1282
 
1165
1283
  // Reflection-already-present check: if the assistant text already contains
1166
1284
  // an explicit <reflection>...</reflection> block OR a "reflection:" line
@@ -1319,13 +1437,14 @@ if (cog.count >= REQUIRED_LENSES) {
1319
1437
  }
1320
1438
 
1321
1439
  const implCouplingBlock = implCouplingHits.length > 0;
1322
- if (mizanBlock || driftBlock || codeBlock || discoveryBlock || compelReflection || phaseReportMissing || substrateBlock || implCouplingBlock) {
1440
+ if (mizanBlock || driftBlock || codeBlock || domainBlock || discoveryBlock || compelReflection || phaseReportMissing || substrateBlock || implCouplingBlock) {
1323
1441
  const violations = [];
1324
1442
  if (mizanBlock) violations.push(`Mizan: ${(mizanVerdict.violations || []).join(', ')}`);
1325
1443
  if (implCouplingBlock) violations.push(`Cognition impl-coupling (#88): ${implCouplingHits.join(' | ')}. Each canonical lens in cognition must dictate a specific implementation choice (file_path:line_range pair tied to a decision). Re-emit cognition that names file paths + line ranges + decision text per lens, OR a verify/fixing block where lenses cite specific artifact changes.`);
1326
1444
  if (compelReflection) violations.push(`Mizan severity=warn — compelled reflection required (per Aria enforcement #46). Triggers: ${(mizanVerdict.gateTriggers || mizanVerdict.violations || ['unspecified']).join(', ')}. Re-emit with an explicit <reflection>...</reflection> block (or 'reflection:' line) addressing what triggered the warn and why your re-draft handles it. Reflection is NOT lens-cognition repeated — it's a focused self-audit on the specific Mizan triggers above.`);
1327
1445
  if (driftBlock) violations.push(`Drift triggers (${driftHits.length}): ${driftHits.map((h) => `"${h.trigger}" → ${h.memory}`).join(' | ')}`);
1328
1446
  if (codeBlock) violations.push(`Code quality: ${codeQualityHits.join('; ')}`);
1447
+ if (domainBlock) violations.push(`Domain output QA (${domainQuality.domains.join(', ') || 'general'}): ${domainQuality.blockers.join('; ')}. Rewrite with the missing domain-specific safeguards instead of generic prose.`);
1329
1448
  if (discoveryBlock) violations.push(`Discovery-binding ledger has ${ledgerOpenCount} OPEN discoveries (per ${docRef('feedback_no_flag_without_fix.md', 'atomic-discovery-rule')}, discoveries are atomic with their fixes — fix in the same turn or create a TaskCreate before continuing). Recent open: ${ledgerOpenSamples.map((s) => `"${s.slice(0, 80)}"`).join(' | ')}. Resolve each by either (a) fixing it inline in this turn, or (b) creating a TaskCreate with the discovery's full context (file path, line number, what's broken, why), then editing ${LEDGER_PATH} to set status=resolved.`);
1330
1449
  if (phaseReportMissing) {
1331
1450
  const phaseList = (activePlan?.phases || []).map((p) => `${p.id}:${p.summary?.slice(0, 60) || ''}`).join(' | ');
@@ -1414,7 +1533,16 @@ if (cog.count >= REQUIRED_LENSES) {
1414
1533
  }
1415
1534
  })();
1416
1535
 
1417
- const reason = withLoopDirective(`Aria Stop-gate output-quality block. Cognition passed (${cog.count}/${REQUIRED_LENSES}) but output failed quality gates:\n\n${violations.join('\n\n')}${rewritten ? `\n\nMizan rewrite suggestion:\n${rewritten}` : ''}\n\nRe-draft addressing the violations above. No process-level disable path — gates are unconditional from the gated process per Hamza directive 2026-04-27.${recipeAddendum}`, `stop:output-qc:${violations.join('|').slice(0, 240)}`, gateSessionId);
1536
+ const reason = withLoopDirective(buildForceReauthorReason({
1537
+ source: 'stop/output-quality',
1538
+ reason: `cognition passed (${cog.count}/${REQUIRED_LENSES}) but output failed quality gates`,
1539
+ violations,
1540
+ rewritten,
1541
+ recipeAddendum,
1542
+ driftHits,
1543
+ lensCount: cog.count,
1544
+ requiredLenses: REQUIRED_LENSES,
1545
+ }), `stop:output-qc:${violations.join('|').slice(0, 240)}`, gateSessionId);
1418
1546
 
1419
1547
  audit(`block-output-qc`, `mizan=${mizanBlock?'y':'n'} warn-reflect=${compelReflection?'y':'n'} drift=${driftHits.length} code=${codeQualityHits.length} discoveries-open=${ledgerOpenCount} impl-coupling=${implCouplingHits.length}`);
1420
1548
  emitHarnessFooter({
@@ -1557,31 +1685,20 @@ const dalioHasMeasurablePredicate = dalioExpectedText
1557
1685
 
1558
1686
  // Block stop if non-trivial action taken AND expected block is missing
1559
1687
  if (hadNonTrivialAction && (!dalioExpectedMatch || !dalioHasMeasurablePredicate)) {
1560
- const missingReason = withLoopDirective(dalioExpectedMatch
1561
- ? `Aria Stop-gate: action taken in this turn had an <expected> block, but it contains only qualitative drift phrases without a measurable predicate. Qualitative drift is not accountability — it defeats the Dalio feedback loop.
1562
-
1563
- Your <expected> block must contain at least one measurable predicate:
1564
- Numeric: exit_code==0, count>=1, error_rate=0%, latency<200ms
1565
- • Boolean: exit=0, status=healthy, rc=0, file=exists
1566
- • State-string: "status=running", "200 OK", "no_error"
1567
-
1568
- REJECTED phrases: "better", "improved", "should work", "more reliable", "cleaner"
1569
-
1570
- Re-emit with a corrected <expected> block. Per doctrine:dalio_expected_required — no bypass path.`
1571
- : `Aria Stop-gate: a non-trivial action (${lastActionSummary.slice(0, 120)}) was taken in this turn but no <expected> block was found in the assistant text.
1572
-
1573
- Per doctrine:dalio_expected_required, every non-trivial action must declare what measurable outcome it expects BEFORE the action fires. This is Dalio Loop Layer 1 — without it, outcome comparison is impossible and learning collapses.
1574
-
1575
- Add to your assistant text:
1576
-
1577
- <expected>
1578
- predicate: <exact measurable assertion — e.g. "exit_code==0", "status=running", "count=3 of 3">
1579
- measurable_type: numeric | boolean | state_string
1580
- threshold: <optional>
1581
- eval_window_minutes: <optional>
1582
- </expected>
1583
-
1584
- No bypass — pre-tool-gate enforces this BEFORE the action; stop-gate enforces it AFTER. Both gates are now wired.`, `stop:dalio-expected:${hadNonTrivialAction}:${!!dalioExpectedMatch}:${dalioHasMeasurablePredicate}`, gateSessionId);
1688
+ const missingReason = withLoopDirective(buildForceReauthorReason({
1689
+ source: 'stop/dalio-expected',
1690
+ reason: dalioExpectedMatch
1691
+ ? 'action had an <expected> block, but it was qualitative instead of measurable'
1692
+ : `non-trivial action (${lastActionSummary.slice(0, 120)}) had no <expected> block`,
1693
+ violations: [
1694
+ dalioExpectedMatch
1695
+ ? 'Rejected qualitative expected phrases: better, improved, should work, more reliable, cleaner. Expected outcome must be numeric, boolean, or state-string.'
1696
+ : 'Missing <expected> block after non-trivial action. Dalio feedback loop cannot compare outcome without a measurable predicate.',
1697
+ 'Required block: <expected> predicate: "exit_code==0" | "status=running" | "count=3 of 3"; measurable_type: numeric | boolean | state_string; threshold/eval_window optional.',
1698
+ ],
1699
+ lensCount: cog.count,
1700
+ requiredLenses: REQUIRED_LENSES,
1701
+ }), `stop:dalio-expected:${hadNonTrivialAction}:${!!dalioExpectedMatch}:${dalioHasMeasurablePredicate}`, gateSessionId);
1585
1702
 
1586
1703
  audit('block-dalio-expected-missing', `hadNonTrivialAction=${hadNonTrivialAction} expectedPresent=${!!dalioExpectedMatch} measurable=${dalioHasMeasurablePredicate}`);
1587
1704
  emitHarnessFooter({
@@ -1720,5 +1837,5 @@ emitHarnessFooter({
1720
1837
  codeCount: 0,
1721
1838
  implCouplingCount: 0,
1722
1839
  });
1723
- console.log(JSON.stringify({ decision: 'block', reason }));
1840
+ console.log(JSON.stringify({ decision: 'block', reason: buildForceReauthorReason({ source: 'stop/lens-missing', reason, lensCount: cog.count, requiredLenses: REQUIRED_LENSES }) }));
1724
1841
  process.exit(2);
@@ -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",
@@ -0,0 +1,103 @@
1
+ // Domain-aware output QA for Aria stop gates.
2
+ // Deterministic local checks complement remote Mizan; they do not replace it.
3
+
4
+ const DOMAIN_RULES = [
5
+ {
6
+ domain: 'code',
7
+ signal: /```|\b(?:function|class|interface|type|import|export|npm test|typecheck|eslint|jest|vitest|tsx?|jsx?|\.ts|\.tsx|\.js|\.py)\b/i,
8
+ },
9
+ {
10
+ domain: 'ui',
11
+ signal: /\b(?:ui|ux|frontend|react|component|page|screen|modal|form|button|layout|tailwind|css|html|mobile|responsive|accessib|aria-label|keyboard|focus state|dark mode)\b/i,
12
+ },
13
+ {
14
+ domain: 'beauty',
15
+ signal: /\b(?:beauty|beautiful|polish|visual|aesthetic|elegant|layout|typography|spacing|hierarchy|composition|brand|design language|modern|clean)\b/i,
16
+ },
17
+ {
18
+ domain: 'security',
19
+ signal: /\b(?:security|auth|token|secret|credential|password|jwt|oauth|csrf|xss|sql injection|permission|role|cors|sanitize|encrypt|webhook signature)\b/i,
20
+ },
21
+ {
22
+ domain: 'ops',
23
+ signal: /\b(?:deploy|rollout|rollback|kubernetes|kubectl|docker|image|pod|health check|slo|alert|log|metric|trace|env var|migration|release)\b/i,
24
+ },
25
+ {
26
+ domain: 'product',
27
+ signal: /\b(?:user flow|customer|workflow|conversion|pricing|onboarding|checkout|activation|retention|business|persona|job-to-be-done|acceptance criteria)\b/i,
28
+ },
29
+ {
30
+ domain: 'writing',
31
+ signal: /\b(?:summary|explain|docs|readme|copy|email|post|article|message|final answer|status report|release notes)\b/i,
32
+ },
33
+ ];
34
+
35
+ function hasAny(text, patterns) {
36
+ return patterns.some((pattern) => pattern.test(text));
37
+ }
38
+
39
+ function lineHits(text, rx, label) {
40
+ const hits = [];
41
+ const lines = String(text || '').split('\n');
42
+ for (let i = 0; i < lines.length; i++) {
43
+ if (rx.test(lines[i])) hits.push(`${label} at line ${i + 1}`);
44
+ }
45
+ return hits;
46
+ }
47
+
48
+ export function extractCodeBlocks(text) {
49
+ return [...String(text || '').matchAll(/```[a-z0-9_-]*\n([\s\S]*?)```/gi)].map((m) => m[1] || '');
50
+ }
51
+
52
+ export function analyzeDomainOutputQuality(text, options = {}) {
53
+ const source = String(text || '');
54
+ const lower = source.toLowerCase();
55
+ const codeBlocks = options.codeBlocks || extractCodeBlocks(source);
56
+ const domains = DOMAIN_RULES.filter((rule) => rule.signal.test(source)).map((rule) => rule.domain);
57
+ const blockers = [];
58
+ const warnings = [];
59
+
60
+ if (domains.includes('code')) {
61
+ for (const hit of lineHits(source, /\b(?:TODO|FIXME|XXX|implementation pending|not implemented|coming soon|placeholder)\b/i, 'code placeholder semantics')) blockers.push(hit);
62
+ for (const block of codeBlocks) {
63
+ if (/@ts-expect-error|@ts-ignore/.test(block)) blockers.push('code: type suppression instead of fixing the type contract');
64
+ if (/catch\s*\([^)]*\)\s*\{\s*(?:return\s+(?:''|""|null|undefined|\[\]|\{\})|\}\s*$|\/\/[^\n]*$)/m.test(block)) blockers.push('code: silent or empty catch block hides runtime failure');
65
+ if (/console\.log\(/.test(block) && !/\/\/\s*(?:debug|log)/i.test(block)) warnings.push('code: console.log appears in shipped code without debug intent');
66
+ }
67
+ }
68
+
69
+ if (domains.includes('ui')) {
70
+ if (!hasAny(source, [/\b(?:mobile|responsive|breakpoint|small screen|desktop)\b/i])) blockers.push('ui: UI/design output must address desktop and mobile responsiveness');
71
+ if (!hasAny(source, [/\b(?:accessib|aria-|keyboard|focus|screen reader|semantic html|label)\b/i])) blockers.push('ui: UI/design output must address accessibility, focus, labels, or semantic structure');
72
+ if (/\b(?:button|input|form|modal|menu|dialog)\b/i.test(source) && !/\b(?:focus|keyboard|aria-|label|escape|tab order)\b/i.test(source)) blockers.push('ui: interactive elements need keyboard/focus/accessibility behavior');
73
+ }
74
+
75
+ if (domains.includes('beauty')) {
76
+ if (/\b(?:clean|modern|beautiful|polished|nice|sleek)\b/i.test(source) && !hasAny(source, [/\b(?:spacing|typography|contrast|hierarchy|rhythm|composition|palette|motion|density|visual language)\b/i])) blockers.push('beauty: aesthetic claim lacks concrete visual language such as spacing, typography, contrast, hierarchy, palette, or composition');
77
+ }
78
+
79
+ if (domains.includes('security')) {
80
+ if (/\b(?:token|secret|credential|password|api key|jwt)\b/i.test(source) && !/\b(?:redact|mask|env|secret store|do not log|rotate|least privilege)\b/i.test(source)) blockers.push('security: secrets/tokens require redaction, env/secret-store handling, no logging, or rotation guidance');
81
+ if (/\b(?:auth|permission|role|admin|tenant)\b/i.test(source) && !/\b(?:authorize|authorization|least privilege|tenant isolation|deny by default|role check)\b/i.test(source)) warnings.push('security: auth/permission output should state authorization and isolation checks');
82
+ }
83
+
84
+ if (domains.includes('ops')) {
85
+ if (/\b(?:deploy|rollout|release|migration|kubectl|docker push)\b/i.test(source) && !/\b(?:rollback|health|smoke|verify|readiness|observability|monitor)\b/i.test(source)) blockers.push('ops: deploy/release output must include rollback plus health/smoke/readiness verification');
86
+ if (/\b(?:env var|config|secret)\b/i.test(source) && !/\b(?:scope|owner|runtime|restart|reload|secret)\b/i.test(source)) warnings.push('ops: runtime config changes should name scope and reload/restart expectations');
87
+ }
88
+
89
+ if (domains.includes('product')) {
90
+ if (!/\b(?:user|customer|operator|admin|persona|workflow|acceptance criteria|success metric|job-to-be-done)\b/i.test(source)) warnings.push('product: product output should bind to a user/workflow and success criterion');
91
+ }
92
+
93
+ if (domains.includes('writing')) {
94
+ if (/\b(?:done|fixed|verified|published|deployed|passed|complete)\b/i.test(lower) && !/\b(?:verified|observed|passed|evidence|output|registry|status|unverified|not verified)\b/i.test(lower)) blockers.push('writing: completion claim needs observed evidence or explicit unverified boundary');
95
+ if (/\b(?:should work|probably|presumably|i assume)\b/i.test(source)) blockers.push('writing: uncertainty must be stated as an evidence boundary, not an assumption');
96
+ }
97
+
98
+ return {
99
+ domains: [...new Set(domains)],
100
+ blockers: [...new Set(blockers)],
101
+ warnings: [...new Set(warnings)],
102
+ };
103
+ }
@@ -0,0 +1 @@
1
+ export * from '../../../../ops/claude-hooks/lib/skill-autoload-gate.mjs';
@@ -3,8 +3,10 @@
3
3
  * Routes through HTTPHarnessClient SDK for substrate-backed validation.
4
4
  */
5
5
  import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
6
+ import { createHash } from 'node:crypto';
6
7
  import { homedir } from 'node:os';
7
8
  import { join } from 'node:path';
9
+ import { evaluateSkillGate, formatSkillGateBlock } from './lib/skill-autoload-gate.js';
8
10
 
9
11
  const HOME = homedir();
10
12
  const SDK_CANDIDATES = [
@@ -42,7 +44,14 @@ const DEPLOY_PATTERNS = [
42
44
  { rx: /\bdocker\s+build\b.*--push\b/, name: 'docker-build-push' },
43
45
  ];
44
46
 
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;
47
+ const INLINE_LENS_NAMES = [
48
+ 'nur', 'mizan', 'hikma', 'tafakkur', 'tadabbur', 'ilham', 'wahi', 'firasah',
49
+ 'truth', 'harm', 'trust', 'power', 'reflection', 'context', 'impact', 'beauty',
50
+ ];
51
+ const INLINE_LENS_RX = new RegExp(
52
+ `^\\s*#\\s*(?:${INLINE_LENS_NAMES.join('|')})\\s*:\\s*.{20,}`,
53
+ 'gim',
54
+ );
46
55
  const VERIFY_BLOCK_RX = /<verify>[\s\S]*?target\s*:[\s\S]*?verified\s*:[\s\S]*?axiom\s*:[\s\S]*?<\/verify>/i;
47
56
  const TRIVIAL_BASH_RX = /^\s*(?:ls|cat|head|tail|grep|find|echo|wc|stat|which|pwd|date|file|du|df|ss|ps)\s/;
48
57
  const SHORT_BASH_LIMIT = 30;
@@ -63,6 +72,28 @@ function persistReceipt(sessionId, payload) {
63
72
  } catch {}
64
73
  }
65
74
 
75
+ function makeEvidenceRef(kind, value, metadata = {}) {
76
+ const raw = typeof value === 'string' ? value : JSON.stringify(value ?? null);
77
+ const sha256 = createHash('sha256').update(raw).digest('hex');
78
+ return {
79
+ evidenceId: `ev_${sha256.slice(0, 16)}`,
80
+ kind,
81
+ at: new Date().toISOString(),
82
+ sha256,
83
+ preview: raw.slice(0, 500),
84
+ metadata,
85
+ };
86
+ }
87
+
88
+ function countInlineCognitionLenses(text) {
89
+ const seen = new Set();
90
+ for (const match of String(text || '').matchAll(INLINE_LENS_RX)) {
91
+ const lens = match[0].match(/^\s*#\s*([a-z_ -]+)\s*:/i)?.[1]?.trim().toLowerCase();
92
+ if (lens) seen.add(lens);
93
+ }
94
+ return seen.size;
95
+ }
96
+
66
97
  function resolveToken() {
67
98
  if (process.env.ARIA_HARNESS_TOKEN) return process.env.ARIA_HARNESS_TOKEN;
68
99
  if (process.env.ARIA_API_KEY) return process.env.ARIA_API_KEY;
@@ -111,7 +142,7 @@ async function getClient() {
111
142
  return _client;
112
143
  }
113
144
 
114
- async function runtimeCheckAction(action, target) {
145
+ async function runtimeCheckAction(action, target, metadata = {}) {
115
146
  const token = resolveToken();
116
147
  if (!token) throw new Error('no token');
117
148
  const response = await fetch(`${RUNTIME_URL}/check-action`, {
@@ -120,7 +151,7 @@ async function runtimeCheckAction(action, target) {
120
151
  'Content-Type': 'application/json',
121
152
  Authorization: `Bearer ${token}`,
122
153
  },
123
- body: JSON.stringify({ action, target }),
154
+ body: JSON.stringify({ action, target, ...metadata }),
124
155
  });
125
156
  if (!response.ok) {
126
157
  const body = await response.text().catch(() => response.statusText);
@@ -163,9 +194,6 @@ export default async function HarnessGatePlugin(ctx) {
163
194
  // Trivial reads pass
164
195
  if (toolName === 'Bash' && TRIVIAL_BASH_RX.test(cmd) && cmd.length < 200) return;
165
196
  if (toolName === 'Bash' && cmd.length < SHORT_BASH_LIMIT) return;
166
- // Inline cognition in bash comments passes
167
- if (toolName === 'Bash' && INLINE_LENS_RX.test(cmd)) return;
168
-
169
197
  const destructive = DESTRUCTIVE_PATTERNS.find(({ rx }) => rx.test(cmd));
170
198
  const deploy = DEPLOY_PATTERNS.find(({ rx }) => rx.test(cmd));
171
199
  const isFileMutation = ['Edit', 'Write', 'NotebookEdit'].includes(toolName) && filePath;
@@ -178,6 +206,25 @@ export default async function HarnessGatePlugin(ctx) {
178
206
  const label = destructive?.name || deploy?.name || `${toolName}:${filePath?.split('/').pop() || ''}`;
179
207
  const action = toolName === 'Bash' ? 'bash' : 'edit';
180
208
  const target = toolName === 'Bash' ? cmd.slice(0, 200) : filePath.slice(0, 200);
209
+ const skillGate = evaluateSkillGate({
210
+ sessionId,
211
+ surface: 'opencode-harness-gate',
212
+ text: JSON.stringify(input || {}),
213
+ action: cmd,
214
+ toolName,
215
+ filePath,
216
+ isDeploy: Boolean(deploy),
217
+ isMutation: Boolean(isFileMutation),
218
+ autoLoadAvailable: false,
219
+ });
220
+ if (!skillGate.ok) {
221
+ throw new Error(
222
+ `=== ARIA SKILL AUTOLOAD GATE: ${label} ===\n\n` +
223
+ `${formatSkillGateBlock(skillGate)}\n\n` +
224
+ `Required next step: call the skill loader for ${skillGate.missingSkills.join(', ')} before retrying this tool.`
225
+ );
226
+ }
227
+ const actionRef = makeEvidenceRef('opencode_tool_request', { toolName, action, target }, { sessionId, label });
181
228
  const rationale =
182
229
  destructive ? `High-risk action ${label} requested in OpenCode and must satisfy Mizan truth, protection, and quality before execution.`
183
230
  : deploy ? `Deployment action ${label} requested in OpenCode and must satisfy Mizan survivability before execution.`
@@ -188,10 +235,21 @@ export default async function HarnessGatePlugin(ctx) {
188
235
  sessionId,
189
236
  context: {
190
237
  sessionId,
238
+ actor: 'opencode',
239
+ system: 'opencode',
240
+ platform: 'opencode',
241
+ surface: 'opencode-harness-gate',
191
242
  message: cmdPreview,
192
243
  intendedAction: target || cmdPreview,
193
244
  rationale,
194
245
  },
246
+ packetRequest: {
247
+ stage: 'preflight',
248
+ actor: 'opencode',
249
+ system: 'opencode',
250
+ platform: 'opencode',
251
+ message: cmdPreview,
252
+ },
195
253
  });
196
254
  _lastMizanReceipt = pre.receipt || null;
197
255
  if (_lastMizanReceipt) {
@@ -202,6 +260,7 @@ export default async function HarnessGatePlugin(ctx) {
202
260
  preResult: pre.result || null,
203
261
  preContract: pre.contract || null,
204
262
  preSummary: pre.summary || null,
263
+ actionRef,
205
264
  });
206
265
  }
207
266
  _lastActionSummary = cmdPreview;
@@ -234,8 +293,25 @@ export default async function HarnessGatePlugin(ctx) {
234
293
 
235
294
  if (client) {
236
295
  try {
237
- const check = await runtimeCheckAction(action, target).catch(() => client.checkAction(action, target));
296
+ const check = await runtimeCheckAction(action, target, {
297
+ sessionId,
298
+ actor: 'opencode',
299
+ roleProfile: process.env.OPENCODE_ARIA_ROLE_PROFILE || process.env.ARIA_ROLE_PROFILE || null,
300
+ files: filePath ? [filePath] : [],
301
+ requireVerify: Boolean(destructive || deploy),
302
+ verifyText: String(input.args?.verifyText || input.args?.verifyBlock || input.args?.verify || ''),
303
+ }).catch(() => client.checkAction(action, target));
238
304
  if (!check.allowed) {
305
+ if (check.queued) {
306
+ throw new Error(
307
+ `=== ARIA TOOL LANE QUEUED: ${label} ===\n\n` +
308
+ `Immediate execution paused; action was queued instead of failed.\n` +
309
+ `Reason: ${check.reason || 'tool lane contention'}\n` +
310
+ `Job: ${check.queue?.jobId || 'unknown'} (${check.queue?.status || 'queued'})\n` +
311
+ `Queue depth: ${check.queue?.queueDepth ?? 'unknown'}\n\n` +
312
+ `Recovery: retry after overlapping Hive lease expires, or let the tool-lane worker claim the queued job.`
313
+ );
314
+ }
239
315
  throw new Error(
240
316
  `=== ARIA SD GATE: ${label} ===\n\n` +
241
317
  `Blocked by substrate gate: ${check.reason || 'no reason provided'}\n` +
@@ -245,6 +321,7 @@ export default async function HarnessGatePlugin(ctx) {
245
321
  }
246
322
  return;
247
323
  } catch (e) {
324
+ if (e.message.startsWith('=== ARIA TOOL LANE QUEUED')) throw e;
248
325
  if (e.message.startsWith('=== ARIA SD GATE')) throw e;
249
326
  // SDK unreachable — fall through to local gate below
250
327
  process.stderr.write(`[harness-gate] SDK checkAction failed: ${e.message} — falling through to local gate\n`);
@@ -0,0 +1 @@
1
+ export * from '../../../../../ops/claude-hooks/lib/skill-autoload-gate.mjs';