@aria_asi/cli 0.2.37 → 0.2.39
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/README.md +140 -0
- package/bin/aria.js +8 -4
- package/dist/aria-connector/src/auth.d.ts +1 -0
- package/dist/aria-connector/src/auth.d.ts.map +1 -1
- package/dist/aria-connector/src/auth.js +26 -1
- package/dist/aria-connector/src/auth.js.map +1 -1
- package/dist/aria-connector/src/connectors/codex.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/codex.js +0 -42
- package/dist/aria-connector/src/connectors/codex.js.map +1 -1
- package/dist/aria-connector/src/setup-wizard.d.ts.map +1 -1
- package/dist/aria-connector/src/setup-wizard.js +41 -1
- package/dist/aria-connector/src/setup-wizard.js.map +1 -1
- package/dist/aria-connector/src/types.d.ts +6 -0
- package/dist/aria-connector/src/types.d.ts.map +1 -1
- package/dist/assets/hooks/aria-pre-tool-gate.mjs +53 -0
- package/dist/assets/hooks/aria-pre-tool-use.mjs +75 -0
- package/dist/assets/hooks/lib/first-class-coach.mjs +3 -3
- package/dist/cli-0.2.38.tgz +0 -0
- package/dist/runtime/coach-kernel.mjs +66 -5
- package/dist/runtime/gated-ledger.mjs +237 -0
- package/dist/runtime/hooks/aria-pre-tool-gate.mjs +53 -0
- package/dist/runtime/hooks/aria-pre-tool-use.mjs +75 -0
- package/dist/runtime/hooks/lib/first-class-coach.mjs +3 -3
- package/dist/runtime/manifest.json +1 -1
- package/dist/runtime/quality-enforcer.mjs +257 -0
- package/dist/runtime/sdk/BUNDLED.json +1 -1
- package/dist/runtime/service.mjs +119 -0
- package/dist/sdk/BUNDLED.json +1 -1
- package/hooks/aria-pre-tool-gate.mjs +53 -0
- package/hooks/aria-pre-tool-use.mjs +75 -0
- package/hooks/lib/first-class-coach.mjs +3 -3
- package/package.json +1 -1
- package/runtime-src/coach-kernel.mjs +66 -5
- package/runtime-src/gated-ledger.mjs +237 -0
- package/runtime-src/quality-enforcer.mjs +257 -0
- package/runtime-src/service.mjs +119 -0
- package/scripts/install-client.sh +32 -2
- package/src/auth.ts +25 -1
- package/src/connectors/codex.ts +0 -42
- package/src/setup-wizard.ts +43 -1
- package/src/types.ts +6 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Runtime Quality Enforcer — First-Class Doctrine Rails
|
|
4
|
+
*
|
|
5
|
+
* Hard-blocks any output that contains internal gate labels, placeholders,
|
|
6
|
+
* or collapse-text. No gate label ever reaches a user surface again.
|
|
7
|
+
*
|
|
8
|
+
* Invariants:
|
|
9
|
+
* 1. HARD regex blocks — catch-all for any internal machinery leaking
|
|
10
|
+
* 2. Minimum substance check — no empty/trivial responses
|
|
11
|
+
* 3. Recovery contract — blocked → rewrite prompt → retry (max 2) → safe fallback
|
|
12
|
+
* 4. Coach kernel notified of every violation for pattern learning
|
|
13
|
+
* 5. Quality violation ledger records every enforcement action
|
|
14
|
+
* 6. Safe fallbacks guaranteed per kernel — deterministic, never empty
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
18
|
+
import { appendFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
19
|
+
import { homedir } from 'node:os';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
|
|
22
|
+
// ── Paths ──────────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
const HOME = homedir();
|
|
25
|
+
const STATE_DIR = join(HOME, '.aria', 'runtime', 'state');
|
|
26
|
+
const QUALITY_LEDGER_PATH = join(STATE_DIR, 'quality-violations.jsonl');
|
|
27
|
+
const COACH_STATE_PATH = join(STATE_DIR, 'coach-state.json');
|
|
28
|
+
|
|
29
|
+
// ── Hard Doctrine Rails ────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
const HARD_BLOCK_PATTERNS = [
|
|
32
|
+
{ pattern: /\bpersonal_mouth_[a-z_]+\b/i, label: 'gate_label:personal_mouth' },
|
|
33
|
+
{ pattern: /\bcode_no_tests\b/i, label: 'gate_label:code_no_tests' },
|
|
34
|
+
{ pattern: /\bcode_fake_implementation\b/i, label: 'gate_label:fake_impl' },
|
|
35
|
+
{ pattern: /\bcode_type_safety\b/i, label: 'gate_label:type_safety' },
|
|
36
|
+
{ pattern: /\bip_infrastructure\b/i, label: 'gate_label:ip_leak' },
|
|
37
|
+
{ pattern: /\b8lens_[a-z_]+\b/i, label: 'gate_label:8lens' },
|
|
38
|
+
{ pattern: /\bvoice_cold_[a-z_]+\b/i, label: 'gate_label:voice_cold' },
|
|
39
|
+
{ pattern: /\bharness_output_gate_block\b/i, label: 'gate_label:output_block' },
|
|
40
|
+
{ pattern: /\bauto_fix:\s/i, label: 'gate_label:auto_fix' },
|
|
41
|
+
{ pattern: /I need to pause and reconsider\.?/i, label: 'gate_label:collapse_placeholder' },
|
|
42
|
+
{ pattern: /\bpersonal_mouth_harness_shallow_[a-z_]+\b/i, label: 'gate_label:shallow' },
|
|
43
|
+
{ pattern: /\bpersonal_mouth_unsupported_internal_[a-z_]+\b/i, label: 'gate_label:internal_claim' },
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const MINIMUM_CHARS = 40;
|
|
47
|
+
|
|
48
|
+
const SAFE_FALLBACKS = {
|
|
49
|
+
emotional_presence: "I'm here. Tell me what's with you right now.",
|
|
50
|
+
architect: "I need more context to give a proper architecture answer. What specific system or decision are you working on?",
|
|
51
|
+
repair: "I can see the issue — let me trace the root cause. Can you share the specific error or surface that's broken?",
|
|
52
|
+
action: "Action kernel received — confirmation required before proceeding. What would you like to execute?",
|
|
53
|
+
research: "Let me gather the relevant information. What specific topic or question should I research?",
|
|
54
|
+
default: "Let me try again — that last response wasn't right. What were you asking about?",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// ── Violation Ledger ──────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
function ensureStateDir() {
|
|
60
|
+
if (!existsSync(STATE_DIR)) mkdirSync(STATE_DIR, { recursive: true, mode: 0o700 });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function logViolation(violation) {
|
|
64
|
+
ensureStateDir();
|
|
65
|
+
try {
|
|
66
|
+
appendFileSync(QUALITY_LEDGER_PATH, `${JSON.stringify(violation)}\n`, { encoding: 'utf8' });
|
|
67
|
+
return true;
|
|
68
|
+
} catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function notifyCoach(violation) {
|
|
74
|
+
ensureStateDir();
|
|
75
|
+
try {
|
|
76
|
+
const event = {
|
|
77
|
+
at: new Date().toISOString(),
|
|
78
|
+
type: 'quality_violation',
|
|
79
|
+
violationId: violation.violationId,
|
|
80
|
+
kernel: violation.kernel,
|
|
81
|
+
violation: violation.violation,
|
|
82
|
+
textPreview: violation.textPreview,
|
|
83
|
+
recoveryAttempts: violation.recoveryAttempts,
|
|
84
|
+
finalOutcome: violation.finalOutcome,
|
|
85
|
+
};
|
|
86
|
+
if (existsSync(COACH_STATE_PATH)) {
|
|
87
|
+
// Append to coach state for offline learning
|
|
88
|
+
appendFileSync(COACH_STATE_PATH, `${JSON.stringify(event)}\n`, { encoding: 'utf8' });
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
// Non-fatal — coach learning is best-effort
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function violationHash(text) {
|
|
96
|
+
return createHash('sha256').update(String(text)).digest('hex').slice(0, 16);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ── Core Enforcement ──────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
export function checkQuality(text) {
|
|
102
|
+
if (typeof text !== 'string' || text.length === 0) {
|
|
103
|
+
return { allowed: false, reasons: ['empty_output'] };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const reasons = [];
|
|
107
|
+
for (const { pattern, label } of HARD_BLOCK_PATTERNS) {
|
|
108
|
+
if (pattern.test(text)) {
|
|
109
|
+
reasons.push(label);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (text.trim().length < MINIMUM_CHARS) {
|
|
114
|
+
reasons.push('below_minimum_chars');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return { allowed: reasons.length === 0, reasons };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function enforceQualityWithRecovery(
|
|
121
|
+
text,
|
|
122
|
+
kernel = 'default',
|
|
123
|
+
options = {},
|
|
124
|
+
) {
|
|
125
|
+
const sessionId = options.sessionId || 'runtime';
|
|
126
|
+
const rewriteFn = options.rewriteFn || null;
|
|
127
|
+
const maxAttempts = Math.min(2, Math.max(0, Number(options.maxRecoveryAttempts || 2)));
|
|
128
|
+
|
|
129
|
+
const initial = checkQuality(text);
|
|
130
|
+
if (initial.allowed) {
|
|
131
|
+
return {
|
|
132
|
+
finalText: text,
|
|
133
|
+
enforced: false,
|
|
134
|
+
attempts: 0,
|
|
135
|
+
violations: [],
|
|
136
|
+
logged: false,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const violations = [...initial.reasons];
|
|
141
|
+
let currentText = text;
|
|
142
|
+
let attempts = 0;
|
|
143
|
+
let repaired = false;
|
|
144
|
+
|
|
145
|
+
// Recovery loop: ask the model to repair its own output
|
|
146
|
+
while (attempts < maxAttempts && rewriteFn && !repaired) {
|
|
147
|
+
attempts += 1;
|
|
148
|
+
try {
|
|
149
|
+
const repairedText = await rewriteFn(
|
|
150
|
+
`Your previous response was blocked by quality enforcement for these reasons: ${initial.reasons.join(', ')}. ` +
|
|
151
|
+
`Rewrite the answer to remove all internal labels, gate phrases, and placeholder text. ` +
|
|
152
|
+
`Original context: ${text.slice(0, 200)}`
|
|
153
|
+
);
|
|
154
|
+
const repairCheck = checkQuality(repairedText);
|
|
155
|
+
if (repairCheck.allowed) {
|
|
156
|
+
currentText = repairedText;
|
|
157
|
+
repaired = true;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
violations.push(...repairCheck.reasons);
|
|
161
|
+
} catch {
|
|
162
|
+
// Recovery attempt failed — continue to next attempt or fallback
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Safe fallback if all recovery attempts failed
|
|
167
|
+
const finalText = repaired
|
|
168
|
+
? currentText
|
|
169
|
+
: (SAFE_FALLBACKS[kernel] || SAFE_FALLBACKS.default);
|
|
170
|
+
|
|
171
|
+
// Log the violation
|
|
172
|
+
const violation = {
|
|
173
|
+
violationId: randomUUID(),
|
|
174
|
+
sessionId,
|
|
175
|
+
kernel,
|
|
176
|
+
violations,
|
|
177
|
+
textPreview: String(text).slice(0, 200),
|
|
178
|
+
textHash: violationHash(text),
|
|
179
|
+
recoveryAttempts: attempts,
|
|
180
|
+
finalOutcome: repaired ? 'repaired' : 'safe_fallback',
|
|
181
|
+
at: new Date().toISOString(),
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const logged = logViolation(violation);
|
|
185
|
+
notifyCoach(violation);
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
finalText,
|
|
189
|
+
enforced: true,
|
|
190
|
+
attempts,
|
|
191
|
+
violations,
|
|
192
|
+
logged,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ── Safe Mouth Proxy ──────────────────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Drop-in guard for mounted-runtime provider pipelines.
|
|
200
|
+
* Accepts a providerMeta text and guarantees the finalText is safe.
|
|
201
|
+
*/
|
|
202
|
+
export async function guardProviderOutput(providerMeta, kernel, sessionId) {
|
|
203
|
+
const result = await enforceQualityWithRecovery(
|
|
204
|
+
providerMeta.text,
|
|
205
|
+
kernel,
|
|
206
|
+
{ sessionId },
|
|
207
|
+
);
|
|
208
|
+
return {
|
|
209
|
+
...providerMeta,
|
|
210
|
+
text: result.finalText,
|
|
211
|
+
quality: {
|
|
212
|
+
enforced: result.enforced,
|
|
213
|
+
attempts: result.attempts,
|
|
214
|
+
violations: result.violations,
|
|
215
|
+
logged: result.logged,
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ── Coach Notification Bridge ─────────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Notifies the coach kernel of a new quality violation pattern.
|
|
224
|
+
* Called by the quality enforcer after every enforcement action.
|
|
225
|
+
*/
|
|
226
|
+
export function getCoachQualitySummary() {
|
|
227
|
+
ensureStateDir();
|
|
228
|
+
try {
|
|
229
|
+
if (!existsSync(QUALITY_LEDGER_PATH)) {
|
|
230
|
+
return { ok: true, violationCount: 0, recentPatterns: [] };
|
|
231
|
+
}
|
|
232
|
+
const lines = require('node:fs').readFileSync(QUALITY_LEDGER_PATH, 'utf8').trim().split('\n').filter(Boolean);
|
|
233
|
+
const violations = lines.map((line) => {
|
|
234
|
+
try { return JSON.parse(line); } catch { return null; }
|
|
235
|
+
}).filter(Boolean);
|
|
236
|
+
|
|
237
|
+
// Count by violation type
|
|
238
|
+
const byType = {};
|
|
239
|
+
for (const v of violations) {
|
|
240
|
+
for (const reason of (v.violations || [])) {
|
|
241
|
+
byType[reason] = (byType[reason] || 0) + 1;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
ok: true,
|
|
247
|
+
violationCount: violations.length,
|
|
248
|
+
recentPatterns: Object.entries(byType)
|
|
249
|
+
.sort(([, a], [, b]) => b - a)
|
|
250
|
+
.slice(0, 10)
|
|
251
|
+
.map(([pattern, count]) => ({ pattern, count })),
|
|
252
|
+
lastViolation: violations[violations.length - 1] || null,
|
|
253
|
+
};
|
|
254
|
+
} catch {
|
|
255
|
+
return { ok: false, violationCount: 0, recentPatterns: [] };
|
|
256
|
+
}
|
|
257
|
+
}
|
package/dist/runtime/service.mjs
CHANGED
|
@@ -42,8 +42,11 @@ import {
|
|
|
42
42
|
formatCoachClientBlock,
|
|
43
43
|
readCoachState,
|
|
44
44
|
recordCoachPhase,
|
|
45
|
+
triggerMissingSkills,
|
|
45
46
|
} from './coach-kernel.mjs';
|
|
46
47
|
import { resolveAriaAuthToken } from './auth-token.mjs';
|
|
48
|
+
import { check, enforceWithRecovery as enforceQualityWithRecovery } from './quality-enforcer.mjs';
|
|
49
|
+
import { enforceGates } from './gated-ledger.mjs';
|
|
47
50
|
|
|
48
51
|
const require = createRequire(import.meta.url);
|
|
49
52
|
const { runFullChain } = require('./vendor/aria-gate-runtime/index.js');
|
|
@@ -3726,6 +3729,15 @@ async function evaluateProviderCandidate(req, body, client, apiKey, turn, provid
|
|
|
3726
3729
|
};
|
|
3727
3730
|
}
|
|
3728
3731
|
|
|
3732
|
+
function deriveEffectiveKernel(turn, body) {
|
|
3733
|
+
const msg = body?.message || body?.prompt || body?.input || '';
|
|
3734
|
+
if (turn?.turnClass === 'repair' || /repair|fix|debug|broken|bug|error|failing|crash|recover/i.test(msg)) return 'repair';
|
|
3735
|
+
if (turn?.turnClass === 'architect' || /architecture|design|system|pipeline|tradeoff|ADR/i.test(msg)) return 'architect';
|
|
3736
|
+
if (turn?.turnClass === 'action' || /deploy|execute|run|build|ship|rollout|restart|push/i.test(msg)) return 'action';
|
|
3737
|
+
if (turn?.turnClass === 'research' || /research|search|find|recall|retrieve|look.up|investigate/i.test(msg)) return 'research';
|
|
3738
|
+
return 'emotional_presence';
|
|
3739
|
+
}
|
|
3740
|
+
|
|
3729
3741
|
async function handleProviderProxy(req, body, client, providerStyle, options = {}) {
|
|
3730
3742
|
const apiKey = resolveApiKey(req, body, options);
|
|
3731
3743
|
const startedAt = Date.now();
|
|
@@ -3819,6 +3831,44 @@ async function handleProviderProxy(req, body, client, providerStyle, options = {
|
|
|
3819
3831
|
],
|
|
3820
3832
|
});
|
|
3821
3833
|
coachRecords.push(preGenerationCoach);
|
|
3834
|
+
// ── Coach auto-trigger: load missing skills instead of blocking ──
|
|
3835
|
+
if (preGenerationCoach.decision === 'auto_trigger_skills' && turn.missingSkillIds?.length > 0) {
|
|
3836
|
+
const skillResult = await triggerMissingSkills(
|
|
3837
|
+
turn.missingSkillIds,
|
|
3838
|
+
SKILL_SEARCH_ROOTS,
|
|
3839
|
+
);
|
|
3840
|
+
const loadedCoach = recordRuntimeCoachPhase({
|
|
3841
|
+
phase: 'pre_generation',
|
|
3842
|
+
body,
|
|
3843
|
+
turn: { ...turn, loadedSkillIds: [...(turn.loadedSkillIds || []), ...skillResult.loaded], missingSkillIds: skillResult.stillMissing },
|
|
3844
|
+
providerStyle,
|
|
3845
|
+
providerPlan,
|
|
3846
|
+
text: turn.userMessage,
|
|
3847
|
+
evidenceRefs: [`skills_loaded:${skillResult.loaded.length}`, `skills_still_missing:${skillResult.stillMissing.length}`],
|
|
3848
|
+
metadata: { autoLoadedSkills: skillResult.loaded },
|
|
3849
|
+
});
|
|
3850
|
+
coachRecords.push(loadedCoach);
|
|
3851
|
+
if (loadedCoach.decision === 'hard_block') {
|
|
3852
|
+
const refusal = formatCoachClientBlock(loadedCoach);
|
|
3853
|
+
const autoBlockRecord = appendManagedRuntimeLedger(buildManagedRuntimeLedgerRecord({
|
|
3854
|
+
phase: 'pre_generation_skills_block',
|
|
3855
|
+
body, turn, providerStyle, providerPlan,
|
|
3856
|
+
releaseDecision: 'hard_block',
|
|
3857
|
+
blockers: loadedCoach.reasons,
|
|
3858
|
+
evidenceRefs: coachRecordRefs(coachRecords),
|
|
3859
|
+
}));
|
|
3860
|
+
ledgerRecords.push({ phase: 'pre_generation_skills_block', ...autoBlockRecord });
|
|
3861
|
+
return providerStyle === 'anthropic'
|
|
3862
|
+
? anthropicResponseEnvelope(refusal, { model: body.model || 'aria-runtime', finishReason: 'end_turn' }, {}, body?.ariaDebug === true)
|
|
3863
|
+
: openAiResponseEnvelope(body, refusal, { model: body.model || 'aria-runtime', finishReason: 'stop', usage: null }, {});
|
|
3864
|
+
}
|
|
3865
|
+
// Skills loaded — update turn for the rest of the pipeline
|
|
3866
|
+
turn.loadedSkillIds = [...(turn.loadedSkillIds || []), ...skillResult.loaded];
|
|
3867
|
+
turn.missingSkillIds = skillResult.stillMissing;
|
|
3868
|
+
if (skillResult.loadedBodies.length > 0) {
|
|
3869
|
+
turn.skillBodies = [...(turn.skillBodies || []), ...skillResult.loadedBodies.map((s) => s.body)];
|
|
3870
|
+
}
|
|
3871
|
+
}
|
|
3822
3872
|
if (preGenerationCoach.decision === 'hard_block') {
|
|
3823
3873
|
const refusal = formatCoachClientBlock(preGenerationCoach);
|
|
3824
3874
|
const preBlockRecord = appendManagedRuntimeLedger(buildManagedRuntimeLedgerRecord({
|
|
@@ -3877,6 +3927,17 @@ async function handleProviderProxy(req, body, client, providerStyle, options = {
|
|
|
3877
3927
|
ledgerRecords.push({ phase: 'provider_call_failed', ...failureRecord });
|
|
3878
3928
|
throw error;
|
|
3879
3929
|
}
|
|
3930
|
+
|
|
3931
|
+
const providerQualityResult = await enforceQualityWithRecovery(
|
|
3932
|
+
providerMeta.text || '',
|
|
3933
|
+
deriveEffectiveKernel(turn, body),
|
|
3934
|
+
{ sessionId: turn.sessionId || body.sessionId },
|
|
3935
|
+
);
|
|
3936
|
+
if (providerQualityResult.enforced) {
|
|
3937
|
+
providerMeta.text = providerQualityResult.finalText;
|
|
3938
|
+
providerMeta.qualityEnforced = true;
|
|
3939
|
+
}
|
|
3940
|
+
|
|
3880
3941
|
recordProviderUsage(body, turn, providerMeta);
|
|
3881
3942
|
let hardCoachBlock = null;
|
|
3882
3943
|
let evaluation = await evaluateProviderCandidate(req, body, client, apiKey, turn, providerStyle, providerMeta, providerMeta.text || '');
|
|
@@ -4125,11 +4186,55 @@ async function handleProviderProxy(req, body, client, providerStyle, options = {
|
|
|
4125
4186
|
records: coachRecords,
|
|
4126
4187
|
},
|
|
4127
4188
|
};
|
|
4189
|
+
const finalQualityResult = await enforceQualityWithRecovery(
|
|
4190
|
+
finalText,
|
|
4191
|
+
deriveEffectiveKernel(turn, body),
|
|
4192
|
+
{ sessionId: turn.sessionId || body.sessionId },
|
|
4193
|
+
);
|
|
4194
|
+
if (finalQualityResult.enforced) {
|
|
4195
|
+
finalText = finalQualityResult.finalText;
|
|
4196
|
+
}
|
|
4197
|
+
|
|
4198
|
+
// ── Gated Ledger: final enforcement before release ──
|
|
4199
|
+
const gated = await enforceGates(finalText, {
|
|
4200
|
+
kernel: deriveEffectiveKernel(turn, body),
|
|
4201
|
+
sessionId: turn.sessionId || body.sessionId,
|
|
4202
|
+
});
|
|
4203
|
+
if (gated.enforced) {
|
|
4204
|
+
extra.gatedLedger = {
|
|
4205
|
+
enforced: true,
|
|
4206
|
+
gates: gated.gates,
|
|
4207
|
+
doctrineTriggers: gated.doctrineTriggers,
|
|
4208
|
+
recordId: gated.record.recordId,
|
|
4209
|
+
};
|
|
4210
|
+
}
|
|
4211
|
+
finalText = gated.finalText;
|
|
4212
|
+
|
|
4128
4213
|
return providerStyle === 'anthropic'
|
|
4129
4214
|
? anthropicResponseEnvelope(finalText, providerMeta, extra, body?.ariaDebug === true)
|
|
4130
4215
|
: openAiResponseEnvelope(body, finalText, providerMeta, extra);
|
|
4131
4216
|
}
|
|
4132
4217
|
|
|
4218
|
+
function extractProviderResponseText(response) {
|
|
4219
|
+
if (!response || typeof response !== 'object') return null;
|
|
4220
|
+
const choices = response.choices;
|
|
4221
|
+
if (Array.isArray(choices) && choices.length > 0) {
|
|
4222
|
+
const content = choices[0]?.message?.content;
|
|
4223
|
+
return typeof content === 'string' ? content : null;
|
|
4224
|
+
}
|
|
4225
|
+
return null;
|
|
4226
|
+
}
|
|
4227
|
+
|
|
4228
|
+
function extractAnthropicResponseText(response) {
|
|
4229
|
+
if (!response || typeof response !== 'object') return null;
|
|
4230
|
+
const content = response.content;
|
|
4231
|
+
if (Array.isArray(content)) {
|
|
4232
|
+
return content.map(c => (c && c.text ? c.text : '')).join(' ').trim() || null;
|
|
4233
|
+
}
|
|
4234
|
+
if (typeof content === 'string') return content;
|
|
4235
|
+
return null;
|
|
4236
|
+
}
|
|
4237
|
+
|
|
4133
4238
|
async function handleForgeSynthesis(req, body, client) {
|
|
4134
4239
|
const apiKey = resolveApiKey(req, body);
|
|
4135
4240
|
const startedAt = Date.now();
|
|
@@ -5338,6 +5443,13 @@ async function handleRoute(req, res) {
|
|
|
5338
5443
|
|
|
5339
5444
|
if (providerPath === '/v1/chat/completions') {
|
|
5340
5445
|
const response = await handleProviderProxy(req, body, client, 'openai', ariaAuthOptions);
|
|
5446
|
+
const responseText = extractProviderResponseText(response);
|
|
5447
|
+
if (responseText) {
|
|
5448
|
+
const qScan = check(responseText);
|
|
5449
|
+
if (!qScan.allowed) {
|
|
5450
|
+
console.warn(`[quality-enforcer] Gate labels detected in OpenAI response after handleProviderProxy. ${qScan.reasons.join(', ')}`);
|
|
5451
|
+
}
|
|
5452
|
+
}
|
|
5341
5453
|
return json(res, 200, response);
|
|
5342
5454
|
}
|
|
5343
5455
|
|
|
@@ -5349,6 +5461,13 @@ async function handleRoute(req, res) {
|
|
|
5349
5461
|
|
|
5350
5462
|
if (providerPath === '/v1/messages') {
|
|
5351
5463
|
const response = await handleProviderProxy(req, body, client, 'anthropic', ariaAuthOptions);
|
|
5464
|
+
const responseText = extractAnthropicResponseText(response);
|
|
5465
|
+
if (responseText) {
|
|
5466
|
+
const qScan = check(responseText);
|
|
5467
|
+
if (!qScan.allowed) {
|
|
5468
|
+
console.warn(`[quality-enforcer] Gate labels detected in Anthropic response after handleProviderProxy. ${qScan.reasons.join(', ')}`);
|
|
5469
|
+
}
|
|
5470
|
+
}
|
|
5352
5471
|
return json(res, 200, response);
|
|
5353
5472
|
}
|
|
5354
5473
|
|
package/dist/sdk/BUNDLED.json
CHANGED
|
@@ -1068,6 +1068,59 @@ if (emergencyGateOff.off) {
|
|
|
1068
1068
|
|
|
1069
1069
|
const toolName = event.tool_name ?? event.toolName ?? '';
|
|
1070
1070
|
const toolInput = event.tool_input ?? event.toolInput ?? {};
|
|
1071
|
+
|
|
1072
|
+
// Coach Kernel routing — single source of truth, run before all hook-native checks.
|
|
1073
|
+
try {
|
|
1074
|
+
const _coachUrl = `${HOME}/.aria/runtime/runtime.env`;
|
|
1075
|
+
const _coachBase = existsSync(_coachUrl)
|
|
1076
|
+
? String(readFileSync(_coachUrl, 'utf8')).match(/ARIA_RUNTIME_URL=(http:\/\/[^ \n]+)/)?.[1] || 'http://127.0.0.1:4319'
|
|
1077
|
+
: 'http://127.0.0.1:4319';
|
|
1078
|
+
const _coachToken = (() => {
|
|
1079
|
+
const tp = `${HOME}/.aria/owner-token`;
|
|
1080
|
+
if (existsSync(tp)) return readFileSync(tp, 'utf8').trim();
|
|
1081
|
+
const lp = `${HOME}/.aria/license.json`;
|
|
1082
|
+
if (existsSync(lp)) {
|
|
1083
|
+
try { const lt = JSON.parse(readFileSync(lp, 'utf8')); return lt.token || lt.harnessToken || ''; } catch { return ''; }
|
|
1084
|
+
}
|
|
1085
|
+
return process.env.ARIA_API_KEY || process.env.ARIA_MASTER_TOKEN || '';
|
|
1086
|
+
})();
|
|
1087
|
+
const _coachHeaders = { 'Content-Type': 'application/json' };
|
|
1088
|
+
if (_coachToken) _coachHeaders.Authorization = `Bearer ${_coachToken}`;
|
|
1089
|
+
const _cmd = String(toolInput?.command || '');
|
|
1090
|
+
const _coachPayload = {
|
|
1091
|
+
phase: 'pre_tool',
|
|
1092
|
+
requestId: `claude-pre-tool:${Date.now()}`,
|
|
1093
|
+
sessionId: String(toolInput?.session_id || process.env.HOOK_SESSION_ID || 'claude-unknown').slice(0, 80),
|
|
1094
|
+
surface: 'claude-hooks',
|
|
1095
|
+
lane: 'claude_native_hooks',
|
|
1096
|
+
action: (() => {
|
|
1097
|
+
const t = _cmd.toLowerCase();
|
|
1098
|
+
if (/\b(?:kubectl\s+(?:apply|set|rollout|delete|create|replace|scale)|helm\s+(?:upgrade|install|uninstall)|terraform\s+(?:apply|destroy)|docker\s+(?:push|build\s+.*--push)|deploy)\b/i.test(t)) return 'deploy';
|
|
1099
|
+
if (/\b(?:rm\s+-[rRfF]+\S*|sudo\s+|systemctl\s+(?:stop|disable|mask|kill)|kill\s+-[9K]|pkill\s+-[9K]|chmod\s+777|git\s+(?:push\s+--force|reset\s+--hard)|docker\s+rm\s+-f)\b/i.test(t)) return 'delete';
|
|
1100
|
+
return '';
|
|
1101
|
+
})(),
|
|
1102
|
+
target: JSON.stringify(toolInput).slice(0, 2000),
|
|
1103
|
+
text: _cmd.slice(0, 1000),
|
|
1104
|
+
metadata: { source: 'claude-pre-tool-gate', toolName },
|
|
1105
|
+
};
|
|
1106
|
+
const _coachResp = await fetch(`${_coachBase}/coach/phase`, {
|
|
1107
|
+
method: 'POST', headers: _coachHeaders, body: JSON.stringify(_coachPayload),
|
|
1108
|
+
signal: AbortSignal.timeout(2000),
|
|
1109
|
+
});
|
|
1110
|
+
if (_coachResp.ok) {
|
|
1111
|
+
const _coachBody = await _coachResp.json();
|
|
1112
|
+
if (_coachBody?.permitted === false && _coachBody?.decision === 'hard_block') {
|
|
1113
|
+
audit('block-coach-authoritative', `reasons=${(_coachBody.reasons||[]).join(',')}`);
|
|
1114
|
+
console.log(JSON.stringify({
|
|
1115
|
+
decision: 'block',
|
|
1116
|
+
reason: ['Aria Coach blocked this action before execution.', '', `Reason: ${(_coachBody.reasons||['coach_policy']).slice(0,3).join('; ')}`, '', _coachBody.clientMessage || 'Remove the high-risk condition and retry.'].join('\n'),
|
|
1117
|
+
hookSpecificOutput: { hookEventName: 'PreToolUse', coach_decision: _coachBody.decision, coach_reasons: _coachBody.reasons },
|
|
1118
|
+
}));
|
|
1119
|
+
process.exit(2);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
} catch { /* Coach unreachable — fall through to hook-native checks */ }
|
|
1123
|
+
|
|
1071
1124
|
const transcriptPath = event.transcript_path ?? event.transcriptPath;
|
|
1072
1125
|
|
|
1073
1126
|
// Gate every action tool — every tool that mutates state must go through
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getHarnessClient,
|
|
4
|
+
inferSessionId,
|
|
5
|
+
classifyAction,
|
|
6
|
+
summarizeTarget,
|
|
7
|
+
readEventFromStdin,
|
|
8
|
+
loadTurnState,
|
|
9
|
+
makeEvidenceRef,
|
|
10
|
+
recordCoachPhase,
|
|
11
|
+
saveTurnState,
|
|
12
|
+
formatCodexRecoveryBlock,
|
|
13
|
+
emitJson,
|
|
14
|
+
} from './lib/runtime-client.mjs';
|
|
15
|
+
|
|
16
|
+
const event = readEventFromStdin();
|
|
17
|
+
const sessionId = inferSessionId(event);
|
|
18
|
+
const action = classifyAction(event);
|
|
19
|
+
const target = summarizeTarget(event);
|
|
20
|
+
const state = loadTurnState(sessionId);
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
if (!state?.preReceiptId && !state?.userText) {
|
|
24
|
+
emitJson({
|
|
25
|
+
decision: 'block',
|
|
26
|
+
reason: formatCodexRecoveryBlock({
|
|
27
|
+
surface: 'codex-pre-tool',
|
|
28
|
+
reason: 'this turn has no pre-turn Mizan receipt',
|
|
29
|
+
next: '6. Re-submit the prompt so cognition is established before tool use, then request the tool again.',
|
|
30
|
+
}),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const toolName = String(event?.tool_name || event?.toolName || '').trim() || null;
|
|
34
|
+
const requestRef = makeEvidenceRef('codex_tool_request', { action, toolName, target }, { sessionId });
|
|
35
|
+
const coach = await recordCoachPhase('pre_tool', {
|
|
36
|
+
requestId: state?.traceId || sessionId,
|
|
37
|
+
sessionId,
|
|
38
|
+
text: target,
|
|
39
|
+
action,
|
|
40
|
+
target,
|
|
41
|
+
evidenceRefs: [requestRef],
|
|
42
|
+
metadata: { source: 'codex-pre-tool-hook', toolName, requireVerify: action === 'deploy' || action === 'delete' },
|
|
43
|
+
});
|
|
44
|
+
if (coach?.permitted === false) {
|
|
45
|
+
emitJson({
|
|
46
|
+
decision: 'block',
|
|
47
|
+
reason: formatCodexRecoveryBlock({
|
|
48
|
+
surface: 'codex-pre-tool-coach',
|
|
49
|
+
reason: coach.clientMessage || 'Coach Kernel denied ' + action,
|
|
50
|
+
next: '6. Add the required evidence/cognition contract, then request the tool again.',
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
const tools = Array.isArray(state?.tools) ? state.tools.slice(-24) : [];
|
|
55
|
+
tools.push({
|
|
56
|
+
at: new Date().toISOString(),
|
|
57
|
+
action,
|
|
58
|
+
toolName,
|
|
59
|
+
target,
|
|
60
|
+
evidenceRef: makeEvidenceRef('tool_request', { action, toolName, target }, { sessionId }),
|
|
61
|
+
});
|
|
62
|
+
saveTurnState(sessionId, {
|
|
63
|
+
tools,
|
|
64
|
+
lastEvent: 'PreToolUse',
|
|
65
|
+
});
|
|
66
|
+
process.exit(0);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
emitJson({
|
|
69
|
+
decision: 'block',
|
|
70
|
+
reason: formatCodexRecoveryBlock({
|
|
71
|
+
surface: 'codex-pre-tool-hook',
|
|
72
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
73
|
+
}),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
@@ -243,10 +243,10 @@ function summarizeRuntimeToolTarget(event = {}) {
|
|
|
243
243
|
|
|
244
244
|
function inferRuntimeAction(event = {}, phase = '') {
|
|
245
245
|
const haystack = `${phase}\n${summarizeRuntimeToolTarget(event)}\n${stableJson(event).slice(0, 6000)}`;
|
|
246
|
-
if (/\b(?:kubectl\s+(?:apply|set|rollout|delete)|helm\s+upgrade|terraform\s+apply|docker\s+push|deploy)\b/i.test(haystack)) return 'deploy';
|
|
247
|
-
if (/\b(?:rm\s+-[rRfF]
|
|
246
|
+
if (/\b(?:kubectl\s+(?:apply|set|rollout|delete|create|replace|scale)|helm\s+(?:upgrade|install|uninstall)|terraform\s+(?:apply|destroy)|docker\s+(?:push|build\s+.*--push)|deploy)\b/i.test(haystack)) return 'deploy';
|
|
247
|
+
if (/\b(?:rm\s+-[rRfF]+\S*|drop\s+(?:table|database|schema|collection|index)|git\s+(?:reset\s+--hard|push\s+--force|push\s+--delete)|sudo\s+|systemctl\s+(?:stop|disable|mask|kill)|kill\s+-[9K]|pkill\s+-[9K]|chmod\s+777|docker\s+rm\s+-f)\b/i.test(haystack)) return 'delete';
|
|
248
|
+
if (/\b(?:--no-verify|--no-gpg-sign)\b/i.test(haystack)) return 'delete';
|
|
248
249
|
if (phase === 'stop' || phase === 'pre_emit') return 'release';
|
|
249
|
-
if (phase === 'pre_tool' || phase === 'post_tool') return 'write';
|
|
250
250
|
return '';
|
|
251
251
|
}
|
|
252
252
|
|