@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.
- package/dist/aria-connector/src/connectors/claude-code.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/claude-code.js +30 -3
- package/dist/aria-connector/src/connectors/claude-code.js.map +1 -1
- package/dist/aria-connector/src/connectors/codebase-awareness.d.ts +8 -1
- package/dist/aria-connector/src/connectors/codebase-awareness.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/codebase-awareness.js +126 -71
- package/dist/aria-connector/src/connectors/codebase-awareness.js.map +1 -1
- package/dist/aria-connector/src/connectors/codex.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/codex.js +76 -9
- package/dist/aria-connector/src/connectors/codex.js.map +1 -1
- package/dist/aria-connector/src/connectors/must-read.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/must-read.js +4 -0
- package/dist/aria-connector/src/connectors/must-read.js.map +1 -1
- package/dist/aria-connector/src/connectors/opencode.js +25 -9
- package/dist/aria-connector/src/connectors/opencode.js.map +1 -1
- package/dist/aria-connector/src/setup-wizard.d.ts.map +1 -1
- package/dist/aria-connector/src/setup-wizard.js +91 -24
- package/dist/aria-connector/src/setup-wizard.js.map +1 -1
- package/dist/assets/hooks/aria-agent-handoff.mjs +23 -0
- package/dist/assets/hooks/aria-cognition-substrate-binding.mjs +69 -3
- package/dist/assets/hooks/aria-harness-via-sdk.mjs +10 -5
- package/dist/assets/hooks/aria-pre-emit-dryrun.mjs +35 -0
- package/dist/assets/hooks/aria-pre-tool-gate.mjs +217 -17
- package/dist/assets/hooks/aria-preprompt-consult.mjs +28 -2
- package/dist/assets/hooks/aria-preturn-memory-gate.mjs +30 -2
- package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +31 -1
- package/dist/assets/hooks/aria-stop-gate.mjs +154 -37
- package/dist/assets/hooks/doctrine_trigger_map.json +55 -0
- package/dist/assets/hooks/lib/domain-output-quality.mjs +103 -0
- package/dist/assets/hooks/lib/skill-autoload-gate.mjs +1 -0
- package/dist/assets/opencode-plugins/harness-gate/index.js +84 -7
- package/dist/assets/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +1 -0
- package/dist/assets/opencode-plugins/harness-outcome/index.js +39 -0
- package/dist/assets/opencode-plugins/harness-stop/index.js +101 -7
- package/dist/assets/opencode-plugins/harness-stop/lib/domain-output-quality.js +103 -0
- package/dist/assets/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +1 -0
- package/dist/runtime/codex-bridge.mjs +71 -8
- package/dist/runtime/discipline/CLAUDE.md +16 -0
- package/dist/runtime/discipline/doctrine_trigger_map.json +55 -0
- package/dist/runtime/doctrine_trigger_map.json +55 -0
- package/dist/runtime/harness-daemon.mjs +80 -5
- package/dist/runtime/manifest.json +1 -1
- package/dist/runtime/sdk/BUNDLED.json +1 -1
- package/dist/runtime/sdk/index.d.ts +14 -0
- package/dist/runtime/sdk/index.js +23 -1
- package/dist/runtime/sdk/index.js.map +1 -1
- package/dist/runtime/service.mjs +385 -11
- package/dist/sdk/BUNDLED.json +1 -1
- package/dist/sdk/index.d.ts +14 -0
- package/dist/sdk/index.js +23 -1
- package/dist/sdk/index.js.map +1 -1
- package/hooks/aria-agent-handoff.mjs +23 -0
- package/hooks/aria-cognition-substrate-binding.mjs +69 -3
- package/hooks/aria-harness-via-sdk.mjs +10 -5
- package/hooks/aria-pre-emit-dryrun.mjs +35 -0
- package/hooks/aria-pre-tool-gate.mjs +217 -17
- package/hooks/aria-preprompt-consult.mjs +28 -2
- package/hooks/aria-preturn-memory-gate.mjs +30 -2
- package/hooks/aria-repo-doctrine-gate.mjs +31 -1
- package/hooks/aria-stop-gate.mjs +154 -37
- package/hooks/doctrine_trigger_map.json +55 -0
- package/hooks/lib/domain-output-quality.mjs +103 -0
- package/hooks/lib/skill-autoload-gate.mjs +1 -0
- package/opencode-plugins/harness-gate/index.js +84 -7
- package/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +1 -0
- package/opencode-plugins/harness-outcome/index.js +39 -0
- package/opencode-plugins/harness-stop/index.js +101 -7
- package/opencode-plugins/harness-stop/lib/domain-output-quality.js +103 -0
- package/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +1 -0
- package/package.json +1 -1
- package/runtime-src/codex-bridge.mjs +71 -8
- package/runtime-src/harness-daemon.mjs +80 -5
- package/runtime-src/service.mjs +385 -11
- package/src/connectors/claude-code.ts +31 -3
- package/src/connectors/codebase-awareness.ts +141 -77
- package/src/connectors/codex.ts +76 -9
- package/src/connectors/must-read.ts +4 -0
- package/src/connectors/opencode.ts +25 -9
- package/src/setup-wizard.ts +105 -25
package/hooks/aria-stop-gate.mjs
CHANGED
|
@@ -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(
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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(
|
|
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(
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
:
|
|
1572
|
-
|
|
1573
|
-
|
|
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
|
|
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
|
|
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';
|