@aria_asi/cli 0.2.32 → 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/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 +51 -0
- 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 +91 -24
- package/dist/aria-connector/src/setup-wizard.js.map +1 -1
- package/dist/assets/hooks/aria-harness-via-sdk.mjs +10 -5
- package/dist/assets/hooks/aria-pre-tool-gate.mjs +19 -0
- package/dist/assets/hooks/aria-stop-gate.mjs +27 -2
- 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 +67 -3
- 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 +61 -1
- 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/harness-daemon.mjs +50 -2
- package/dist/runtime/manifest.json +1 -1
- package/dist/runtime/sdk/BUNDLED.json +1 -1
- package/dist/runtime/sdk/index.d.ts +9 -0
- package/dist/runtime/sdk/index.js +23 -1
- package/dist/runtime/sdk/index.js.map +1 -1
- package/dist/runtime/service.mjs +339 -10
- package/dist/sdk/BUNDLED.json +1 -1
- package/dist/sdk/index.d.ts +9 -0
- package/dist/sdk/index.js +23 -1
- package/dist/sdk/index.js.map +1 -1
- package/hooks/aria-harness-via-sdk.mjs +10 -5
- package/hooks/aria-pre-tool-gate.mjs +19 -0
- package/hooks/aria-stop-gate.mjs +27 -2
- 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 +67 -3
- 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 +61 -1
- 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 +50 -2
- package/runtime-src/service.mjs +339 -10
- package/src/connectors/codebase-awareness.ts +141 -77
- package/src/connectors/codex.ts +51 -0
- package/src/setup-wizard.ts +105 -25
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Routes through the canonical SDK for outcome/garden/ledger writes.
|
|
4
4
|
*/
|
|
5
5
|
import { existsSync, readFileSync } from 'node:fs';
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
6
7
|
import { homedir } from 'node:os';
|
|
7
8
|
import { join } from 'node:path';
|
|
8
9
|
|
|
@@ -14,6 +15,7 @@ const SDK_CANDIDATES = [
|
|
|
14
15
|
];
|
|
15
16
|
const OWNER_TOKEN_PATH = join(HOME, '.aria', 'owner-token');
|
|
16
17
|
const LICENSE_PATH = join(HOME, '.aria', 'license.json');
|
|
18
|
+
const RUNTIME_URL = (process.env.ARIA_RUNTIME_URL || 'http://127.0.0.1:4319').replace(/\/+$/, '');
|
|
17
19
|
|
|
18
20
|
let _client = null;
|
|
19
21
|
let _clientError = null;
|
|
@@ -46,6 +48,32 @@ function resolveSdkPath() {
|
|
|
46
48
|
return SDK_CANDIDATES.find((candidate) => existsSync(candidate)) || '';
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
function makeEvidenceRef(kind, value, metadata = {}) {
|
|
52
|
+
const raw = typeof value === 'string' ? value : JSON.stringify(value ?? null);
|
|
53
|
+
const sha256 = createHash('sha256').update(raw).digest('hex');
|
|
54
|
+
return {
|
|
55
|
+
evidenceId: `ev_${sha256.slice(0, 16)}`,
|
|
56
|
+
kind,
|
|
57
|
+
at: new Date().toISOString(),
|
|
58
|
+
sha256,
|
|
59
|
+
preview: raw.slice(0, 500),
|
|
60
|
+
metadata,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function releaseHiveLease(sessionId, files) {
|
|
65
|
+
const token = resolveToken();
|
|
66
|
+
if (!token || !Array.isArray(files) || files.length === 0) return;
|
|
67
|
+
await fetch(`${RUNTIME_URL}/hive/release`, {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
headers: {
|
|
70
|
+
'Content-Type': 'application/json',
|
|
71
|
+
Authorization: `Bearer ${token}`,
|
|
72
|
+
},
|
|
73
|
+
body: JSON.stringify({ sessionId, files }),
|
|
74
|
+
}).catch(() => {});
|
|
75
|
+
}
|
|
76
|
+
|
|
49
77
|
async function getClient() {
|
|
50
78
|
if (_client) return _client;
|
|
51
79
|
if (_clientError) return null;
|
|
@@ -78,6 +106,15 @@ export default async function HarnessOutcomePlugin(ctx) {
|
|
|
78
106
|
: String(input.args?.file_path ?? input.args?.notebook_path ?? '').slice(0, 200) || 'unknown';
|
|
79
107
|
const success = !output?.error;
|
|
80
108
|
const sessionId = process.env.ARIA_SESSION_ID || 'opencode';
|
|
109
|
+
const filePath = toolName !== 'Bash' ? String(input.args?.file_path ?? input.args?.notebook_path ?? '') : '';
|
|
110
|
+
await releaseHiveLease(sessionId, filePath ? [filePath] : []);
|
|
111
|
+
const outcomeRef = makeEvidenceRef('opencode_tool_outcome', {
|
|
112
|
+
toolName,
|
|
113
|
+
actionKind,
|
|
114
|
+
actionTarget,
|
|
115
|
+
success,
|
|
116
|
+
output: output ?? null,
|
|
117
|
+
}, { sessionId });
|
|
81
118
|
|
|
82
119
|
// Try SDK first
|
|
83
120
|
const client = await getClient();
|
|
@@ -86,6 +123,7 @@ export default async function HarnessOutcomePlugin(ctx) {
|
|
|
86
123
|
await client.recordDiscovery({
|
|
87
124
|
text: `${actionKind}: ${actionTarget} — ${success ? 'ok' : 'failed'}`,
|
|
88
125
|
kind: success ? 'observation' : 'defect',
|
|
126
|
+
evidence: JSON.stringify(outcomeRef),
|
|
89
127
|
source: 'opencode-outcome-plugin',
|
|
90
128
|
resolution_status: success ? 'resolved' : 'open',
|
|
91
129
|
sessionId,
|
|
@@ -122,6 +160,7 @@ export default async function HarnessOutcomePlugin(ctx) {
|
|
|
122
160
|
actionKind,
|
|
123
161
|
actionTarget,
|
|
124
162
|
actionShape: { tool: toolName, success },
|
|
163
|
+
evidenceRef: outcomeRef,
|
|
125
164
|
}),
|
|
126
165
|
}).catch(() => {});
|
|
127
166
|
},
|
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
* Routes text through Mizan validateOutput() for substrate-backed QC.
|
|
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 { analyzeDomainOutputQuality, extractCodeBlocks } from './lib/domain-output-quality.js';
|
|
10
|
+
import { evaluateSkillGate, formatSkillGateBlock } from './lib/skill-autoload-gate.js';
|
|
8
11
|
|
|
9
12
|
const HOME = homedir();
|
|
10
13
|
const SDK_CANDIDATES = [
|
|
@@ -71,6 +74,19 @@ function persistReceiptState(sessionId, payload) {
|
|
|
71
74
|
} catch {}
|
|
72
75
|
}
|
|
73
76
|
|
|
77
|
+
function makeEvidenceRef(kind, value, metadata = {}) {
|
|
78
|
+
const raw = typeof value === 'string' ? value : JSON.stringify(value ?? null);
|
|
79
|
+
const sha256 = createHash('sha256').update(raw).digest('hex');
|
|
80
|
+
return {
|
|
81
|
+
evidenceId: `ev_${sha256.slice(0, 16)}`,
|
|
82
|
+
kind,
|
|
83
|
+
at: new Date().toISOString(),
|
|
84
|
+
sha256,
|
|
85
|
+
preview: raw.slice(0, 500),
|
|
86
|
+
metadata,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
74
90
|
function resolveToken() {
|
|
75
91
|
if (process.env.ARIA_HARNESS_TOKEN) return process.env.ARIA_HARNESS_TOKEN;
|
|
76
92
|
if (process.env.ARIA_API_KEY) return process.env.ARIA_API_KEY;
|
|
@@ -137,7 +153,7 @@ async function runtimeValidateOutput(text, sessionId) {
|
|
|
137
153
|
return payload.validation || payload.result || payload;
|
|
138
154
|
}
|
|
139
155
|
|
|
140
|
-
async function runtimeMizanPost(text, sessionId, context = {}) {
|
|
156
|
+
async function runtimeMizanPost(text, sessionId, context = {}, evidence = {}) {
|
|
141
157
|
const existing = loadReceiptState(sessionId);
|
|
142
158
|
const token = resolveToken();
|
|
143
159
|
if (!token) throw new Error('no token');
|
|
@@ -150,10 +166,22 @@ async function runtimeMizanPost(text, sessionId, context = {}) {
|
|
|
150
166
|
body: JSON.stringify({
|
|
151
167
|
sessionId,
|
|
152
168
|
text,
|
|
169
|
+
evidence,
|
|
153
170
|
context: {
|
|
154
171
|
sessionId,
|
|
172
|
+
actor: 'opencode',
|
|
173
|
+
system: 'opencode',
|
|
174
|
+
platform: 'opencode',
|
|
175
|
+
surface: 'opencode-harness-stop',
|
|
155
176
|
...context,
|
|
156
177
|
},
|
|
178
|
+
packetRequest: {
|
|
179
|
+
stage: 'preflight',
|
|
180
|
+
actor: 'opencode',
|
|
181
|
+
system: 'opencode',
|
|
182
|
+
platform: 'opencode',
|
|
183
|
+
message: String(context.message || text || '').slice(0, 1000),
|
|
184
|
+
},
|
|
157
185
|
parentReceiptId: existing?.receipt?.receiptId || _lastMizanReceipt?.receiptId || null,
|
|
158
186
|
}),
|
|
159
187
|
});
|
|
@@ -223,10 +251,30 @@ export default async function HarnessStopPlugin(ctx) {
|
|
|
223
251
|
|
|
224
252
|
const cog = detectCognitionLenses(text);
|
|
225
253
|
const sessionId = process.env.ARIA_SESSION_ID || 'opencode';
|
|
254
|
+
const skillGate = evaluateSkillGate({
|
|
255
|
+
sessionId,
|
|
256
|
+
surface: 'opencode-harness-stop',
|
|
257
|
+
text: JSON.stringify(event || {}) + '\n' + text,
|
|
258
|
+
isOutputCloseout: true,
|
|
259
|
+
autoLoadAvailable: false,
|
|
260
|
+
});
|
|
261
|
+
if (!skillGate.ok) {
|
|
262
|
+
throw new Error(
|
|
263
|
+
formatBlockReason(
|
|
264
|
+
'=== ARIA SKILL AUTOLOAD GATE BLOCK ===',
|
|
265
|
+
`${formatSkillGateBlock(skillGate)}\nRequired next step: load ${skillGate.missingSkills.join(', ')} before re-emitting this output.`,
|
|
266
|
+
)
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
const outputRef = makeEvidenceRef('opencode_assistant_output', text.slice(0, 8000), { sessionId });
|
|
226
270
|
try {
|
|
227
271
|
const mizan = await runtimeMizanPost(text.slice(0, 8000), sessionId, {
|
|
228
272
|
message: text.slice(0, 1000),
|
|
229
273
|
plannedApproach: 'OpenCode stop gate output review',
|
|
274
|
+
outputRef,
|
|
275
|
+
}, {
|
|
276
|
+
output_ref: outputRef,
|
|
277
|
+
cognition_lens_count: cog.count,
|
|
230
278
|
});
|
|
231
279
|
_lastMizanReceipt = mizan.receipt || _lastMizanReceipt;
|
|
232
280
|
if (_lastMizanReceipt) {
|
|
@@ -239,6 +287,7 @@ export default async function HarnessStopPlugin(ctx) {
|
|
|
239
287
|
postResult: mizan.result || null,
|
|
240
288
|
postContract: mizan.contract || null,
|
|
241
289
|
postSummary: mizan.summary || null,
|
|
290
|
+
outputRef,
|
|
242
291
|
});
|
|
243
292
|
}
|
|
244
293
|
if (mizan.receipt?.blocked || mizan.result?.fitrahVetoed || mizan.result?.reAuthorSignal) {
|
|
@@ -332,6 +381,16 @@ export default async function HarnessStopPlugin(ctx) {
|
|
|
332
381
|
);
|
|
333
382
|
}
|
|
334
383
|
|
|
384
|
+
const domainQuality = analyzeDomainOutputQuality(text, { codeBlocks: extractCodeBlocks(text) });
|
|
385
|
+
if (domainQuality.blockers.length > 0) {
|
|
386
|
+
throw new Error(
|
|
387
|
+
formatBlockReason(
|
|
388
|
+
'=== ARIA LOCAL OUTPUT BLOCK ===',
|
|
389
|
+
`domain output QA (${domainQuality.domains.join(', ') || 'general'}): ${domainQuality.blockers.join('; ')}`,
|
|
390
|
+
)
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
335
394
|
try {
|
|
336
395
|
const existing = loadReceiptState(sessionId);
|
|
337
396
|
await runtimeDecisionLog({
|
|
@@ -352,6 +411,7 @@ export default async function HarnessStopPlugin(ctx) {
|
|
|
352
411
|
metadata: {
|
|
353
412
|
pre_receipt_id: existing?.receipt?.receiptId || null,
|
|
354
413
|
post_receipt_id: _lastMizanReceipt?.receiptId || null,
|
|
414
|
+
output_ref: outputRef,
|
|
355
415
|
},
|
|
356
416
|
source: 'opencode-stop-gate-runtime',
|
|
357
417
|
model_used: process.env.OPENCODE_MODEL || 'opencode',
|
|
@@ -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';
|
package/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
4
5
|
import { homedir } from 'node:os';
|
|
5
6
|
import { delimiter, dirname, resolve } from 'node:path';
|
|
6
7
|
import { spawn, spawnSync } from 'node:child_process';
|
|
7
8
|
import { createRequire } from 'node:module';
|
|
9
|
+
import { evaluateSkillGate, formatSkillGateBlock } from '../../../ops/claude-hooks/lib/skill-autoload-gate.mjs';
|
|
8
10
|
|
|
9
11
|
const require = createRequire(import.meta.url);
|
|
10
12
|
const { WebSocketServer, WebSocket } = require('ws');
|
|
@@ -75,6 +77,24 @@ function sleep(ms) {
|
|
|
75
77
|
return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
|
|
76
78
|
}
|
|
77
79
|
|
|
80
|
+
function stableEvidenceString(value) {
|
|
81
|
+
if (typeof value === 'string') return value;
|
|
82
|
+
try { return JSON.stringify(value); } catch { return String(value); }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function makeEvidenceRef(kind, value, metadata = {}) {
|
|
86
|
+
const raw = stableEvidenceString(value);
|
|
87
|
+
const sha256 = createHash('sha256').update(raw).digest('hex');
|
|
88
|
+
return {
|
|
89
|
+
evidenceId: `ev_${sha256.slice(0, 16)}`,
|
|
90
|
+
kind,
|
|
91
|
+
at: new Date().toISOString(),
|
|
92
|
+
sha256,
|
|
93
|
+
preview: raw.slice(0, 500),
|
|
94
|
+
metadata,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
78
98
|
function readRuntimeToken() {
|
|
79
99
|
const envToken = process.env.ARIA_HARNESS_TOKEN || process.env.ARIA_API_KEY || process.env.OPENAI_API_KEY || process.env.ARIA_MASTER_TOKEN;
|
|
80
100
|
if (envToken) return envToken;
|
|
@@ -123,9 +143,10 @@ function ensureTurnState(threadId, turnId) {
|
|
|
123
143
|
userText: '',
|
|
124
144
|
preReceiptId: null,
|
|
125
145
|
agentText: '',
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
146
|
+
bufferedAgentNotifications: [],
|
|
147
|
+
firstAgentItemId: null,
|
|
148
|
+
traceId: `trace_${randomUUID()}`,
|
|
149
|
+
};
|
|
129
150
|
turnState.set(key, state);
|
|
130
151
|
}
|
|
131
152
|
return state;
|
|
@@ -186,6 +207,16 @@ async function validateTurnText(threadId, turnId) {
|
|
|
186
207
|
if (!text) {
|
|
187
208
|
return { ok: false, reason: 'No assistant text exists for this turn yet. Codex must emit readable cognition before action.' };
|
|
188
209
|
}
|
|
210
|
+
const skillGate = evaluateSkillGate({
|
|
211
|
+
sessionId: `codex:${threadId}:${turnId}`,
|
|
212
|
+
surface: 'codex-bridge-output',
|
|
213
|
+
text: [state.userText, text].join('\n'),
|
|
214
|
+
isOutputCloseout: true,
|
|
215
|
+
autoLoadAvailable: false,
|
|
216
|
+
});
|
|
217
|
+
if (!skillGate.ok) {
|
|
218
|
+
return { ok: false, reason: formatSkillGateBlock(skillGate), result: { skillGate } };
|
|
219
|
+
}
|
|
189
220
|
const result = await postRuntime('/validate-output', {
|
|
190
221
|
text,
|
|
191
222
|
sessionId: `codex:${threadId}:${turnId}`,
|
|
@@ -198,6 +229,7 @@ async function validateTurnText(threadId, turnId) {
|
|
|
198
229
|
stage: 'codex-turn',
|
|
199
230
|
actor: 'codex-bridge',
|
|
200
231
|
system: 'codex-bridge',
|
|
232
|
+
traceId: state.traceId,
|
|
201
233
|
},
|
|
202
234
|
});
|
|
203
235
|
const pass = result?.pass === true && result?.validation?.passed !== false;
|
|
@@ -210,11 +242,29 @@ async function validateTurnText(threadId, turnId) {
|
|
|
210
242
|
};
|
|
211
243
|
}
|
|
212
244
|
|
|
213
|
-
async function checkActionAgainstRuntime(action, target, threadId, turnId) {
|
|
245
|
+
async function checkActionAgainstRuntime(action, target, threadId, turnId, metadata = {}) {
|
|
246
|
+
const state = ensureTurnState(threadId, turnId);
|
|
247
|
+
const skillGate = evaluateSkillGate({
|
|
248
|
+
sessionId: `codex:${threadId}:${turnId}`,
|
|
249
|
+
surface: 'codex-bridge-action',
|
|
250
|
+
text: target,
|
|
251
|
+
action: target,
|
|
252
|
+
toolName: action,
|
|
253
|
+
isDeploy: action === 'deploy',
|
|
254
|
+
isMutation: action === 'write',
|
|
255
|
+
autoLoadAvailable: false,
|
|
256
|
+
});
|
|
257
|
+
if (!skillGate.ok) {
|
|
258
|
+
return { ok: false, reason: formatSkillGateBlock(skillGate), result: { skillGate } };
|
|
259
|
+
}
|
|
214
260
|
const result = await postRuntime('/check-action', {
|
|
215
261
|
action,
|
|
216
262
|
target,
|
|
217
263
|
sessionId: `codex:${threadId}:${turnId}`,
|
|
264
|
+
actor: 'codex',
|
|
265
|
+
roleProfile: process.env.CODEX_ARIA_ROLE_PROFILE || process.env.ARIA_ROLE_PROFILE || null,
|
|
266
|
+
verifyText: state.agentText || '',
|
|
267
|
+
...metadata,
|
|
218
268
|
});
|
|
219
269
|
if (result?.allowed === false) {
|
|
220
270
|
return {
|
|
@@ -244,6 +294,8 @@ async function recordMizanPre(threadId, turnId) {
|
|
|
244
294
|
surface: 'codex-bridge',
|
|
245
295
|
platform: 'codex',
|
|
246
296
|
userText: state.userText,
|
|
297
|
+
traceId: state.traceId,
|
|
298
|
+
evidenceRefs: [makeEvidenceRef('user_input', state.userText, { threadId, turnId, traceId: state.traceId })],
|
|
247
299
|
},
|
|
248
300
|
});
|
|
249
301
|
state.preReceiptId = result?.receipt?.receiptId || null;
|
|
@@ -262,6 +314,8 @@ async function recordMizanPost(threadId, turnId, pass, summary) {
|
|
|
262
314
|
evidence: {
|
|
263
315
|
validated_output: pass,
|
|
264
316
|
bridge: 'codex',
|
|
317
|
+
trace_id: state.traceId,
|
|
318
|
+
output_ref: makeEvidenceRef('assistant_output', state.agentText || summary, { threadId, turnId, traceId: state.traceId }),
|
|
265
319
|
},
|
|
266
320
|
context: {
|
|
267
321
|
sessionId: `codex:${threadId}:${turnId}`,
|
|
@@ -269,6 +323,7 @@ async function recordMizanPost(threadId, turnId, pass, summary) {
|
|
|
269
323
|
platform: 'codex',
|
|
270
324
|
userText: state.userText,
|
|
271
325
|
summary,
|
|
326
|
+
traceId: state.traceId,
|
|
272
327
|
},
|
|
273
328
|
});
|
|
274
329
|
} catch (error) {
|
|
@@ -285,6 +340,8 @@ async function recordMizanPost(threadId, turnId, pass, summary) {
|
|
|
285
340
|
details: {
|
|
286
341
|
threadId,
|
|
287
342
|
turnId,
|
|
343
|
+
traceId: state.traceId,
|
|
344
|
+
outputRef: makeEvidenceRef('assistant_output', state.agentText || summary, { threadId, turnId, traceId: state.traceId }),
|
|
288
345
|
},
|
|
289
346
|
});
|
|
290
347
|
} catch (error) {
|
|
@@ -336,7 +393,9 @@ async function handleApprovalRequest(upstream, downstream, message) {
|
|
|
336
393
|
cwd: params.cwd || null,
|
|
337
394
|
commandActions: params.commandActions || params.parsedCmd || null,
|
|
338
395
|
}).slice(0, 1500);
|
|
339
|
-
const actionCheck = await checkActionAgainstRuntime(action, target, threadId, turnId
|
|
396
|
+
const actionCheck = await checkActionAgainstRuntime(action, target, threadId, turnId, {
|
|
397
|
+
requireVerify: action === 'delete' || action === 'deploy',
|
|
398
|
+
});
|
|
340
399
|
if (!actionCheck.ok) {
|
|
341
400
|
const reason = `Aria runtime denied ${action}: ${actionCheck.reason}`;
|
|
342
401
|
upstream.send(JSON.stringify(makeGuardianWarning(threadId, reason)));
|
|
@@ -360,8 +419,9 @@ async function handleApprovalRequest(upstream, downstream, message) {
|
|
|
360
419
|
}
|
|
361
420
|
|
|
362
421
|
if (method === 'item/fileChange/requestApproval' || method === 'applyPatchApproval') {
|
|
363
|
-
const target = params.grantRoot || params.reason || params.itemId || 'file-change';
|
|
364
|
-
const
|
|
422
|
+
const target = params.grantRoot || params.path || params.filePath || params.reason || params.itemId || 'file-change';
|
|
423
|
+
const files = [params.path, params.filePath, params.grantRoot].filter((value) => typeof value === 'string' && value.trim());
|
|
424
|
+
const actionCheck = await checkActionAgainstRuntime('write', String(target), threadId, turnId, { files });
|
|
365
425
|
if (!actionCheck.ok) {
|
|
366
426
|
const reason = `Aria runtime denied file change: ${actionCheck.reason}`;
|
|
367
427
|
upstream.send(JSON.stringify(makeGuardianWarning(threadId, reason)));
|
|
@@ -390,7 +450,10 @@ async function handleApprovalRequest(upstream, downstream, message) {
|
|
|
390
450
|
permissions: params.permissions || null,
|
|
391
451
|
reason: params.reason || null,
|
|
392
452
|
}).slice(0, 1500);
|
|
393
|
-
const
|
|
453
|
+
const files = Array.isArray(params.permissions?.fileSystem?.entries)
|
|
454
|
+
? params.permissions.fileSystem.entries.filter((value) => typeof value === 'string' && value.trim())
|
|
455
|
+
: [];
|
|
456
|
+
const actionCheck = await checkActionAgainstRuntime('write', target, threadId, turnId, { files });
|
|
394
457
|
if (!actionCheck.ok) {
|
|
395
458
|
const reason = `Aria runtime denied permissions request: ${actionCheck.reason}`;
|
|
396
459
|
upstream.send(JSON.stringify(makeGuardianWarning(threadId, reason)));
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { createServer } from 'node:http';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
4
5
|
import { createRequire } from 'node:module';
|
|
5
6
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
6
7
|
import { fileURLToPath } from 'node:url';
|
|
@@ -35,6 +36,7 @@ const LOCAL_FIRST_PRINCIPLE = 'Truth over deception. No harm. Sacred trust. Powe
|
|
|
35
36
|
|
|
36
37
|
let cachedPacketEnvelope = loadCachedPacketEnvelope();
|
|
37
38
|
let refreshInFlight = null;
|
|
39
|
+
let refreshInFlightKey = '';
|
|
38
40
|
let lastRefreshError = null;
|
|
39
41
|
let lastRefreshStartedAt = null;
|
|
40
42
|
let lastRefreshCompletedAt = cachedPacketEnvelope?.timestamp || null;
|
|
@@ -99,6 +101,46 @@ function sanitizePacketEnvelope(raw) {
|
|
|
99
101
|
};
|
|
100
102
|
}
|
|
101
103
|
|
|
104
|
+
function stableIdentityValue(value) {
|
|
105
|
+
return String(value || '').trim().toLowerCase();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function requestCacheKey(body = {}, apiKey = '') {
|
|
109
|
+
const identity = {
|
|
110
|
+
stage: stableIdentityValue(body.stage || body.packetRequest?.stage),
|
|
111
|
+
actor: stableIdentityValue(body.actor || body.packetRequest?.actor),
|
|
112
|
+
system: stableIdentityValue(body.system || body.packetRequest?.system),
|
|
113
|
+
platform: stableIdentityValue(body.platform || body.packetRequest?.platform),
|
|
114
|
+
roleProfile: stableIdentityValue(body.roleProfile || body.role_profile || body.packetRequest?.roleProfile || body.packetRequest?.role_profile),
|
|
115
|
+
token: apiKey ? createHash('sha256').update(apiKey).digest('hex').slice(0, 16) : '',
|
|
116
|
+
};
|
|
117
|
+
return createHash('sha256').update(JSON.stringify(identity)).digest('hex');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function extractPacketField(packet, field) {
|
|
121
|
+
if (!packet || typeof packet !== 'object') return '';
|
|
122
|
+
const direct = packet[field];
|
|
123
|
+
if (typeof direct === 'string' && direct.trim()) return stableIdentityValue(direct);
|
|
124
|
+
for (const source of [packet.adapter, packet.harness]) {
|
|
125
|
+
if (typeof source !== 'string') continue;
|
|
126
|
+
const match = source.match(new RegExp(`(?:^|\\n)\\s*${field}\\s*=\\s*([^\\n\\s]+)`, 'i'));
|
|
127
|
+
if (match?.[1]) return stableIdentityValue(match[1]);
|
|
128
|
+
}
|
|
129
|
+
return '';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function cachedPacketMatchesRequest(envelope, body = {}) {
|
|
133
|
+
const packet = envelope?.packet;
|
|
134
|
+
if (!packet || typeof packet !== 'object') return false;
|
|
135
|
+
for (const field of ['stage', 'actor', 'system']) {
|
|
136
|
+
const expected = stableIdentityValue(body[field] || body.packetRequest?.[field]);
|
|
137
|
+
if (!expected) continue;
|
|
138
|
+
const actual = extractPacketField(packet, field);
|
|
139
|
+
if (actual && actual !== expected) return false;
|
|
140
|
+
}
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
102
144
|
function loadCachedPacketEnvelope() {
|
|
103
145
|
const candidates = [LOCAL_PACKET_CACHE_PATH, ...LEGACY_PACKET_CACHE_CANDIDATES];
|
|
104
146
|
for (const candidate of candidates) {
|
|
@@ -220,10 +262,15 @@ async function fetchJsonWithRetry(url, init, attempts = 3) {
|
|
|
220
262
|
}
|
|
221
263
|
|
|
222
264
|
async function refreshPacket(body = {}, apiKey = '') {
|
|
223
|
-
|
|
265
|
+
const cacheKey = requestCacheKey(body, apiKey);
|
|
266
|
+
if (refreshInFlight && refreshInFlightKey === cacheKey) return refreshInFlight;
|
|
267
|
+
if (refreshInFlight) {
|
|
268
|
+
await refreshInFlight.catch(() => {});
|
|
269
|
+
}
|
|
224
270
|
if (isLoopedUpstream(UPSTREAM_HARNESS_URL)) {
|
|
225
271
|
throw new Error(`upstream harness URL loops back to local daemon: ${UPSTREAM_HARNESS_URL}`);
|
|
226
272
|
}
|
|
273
|
+
refreshInFlightKey = cacheKey;
|
|
227
274
|
lastRefreshStartedAt = new Date().toISOString();
|
|
228
275
|
lastRefreshError = null;
|
|
229
276
|
refreshInFlight = (async () => {
|
|
@@ -258,6 +305,7 @@ async function refreshPacket(body = {}, apiKey = '') {
|
|
|
258
305
|
throw error;
|
|
259
306
|
} finally {
|
|
260
307
|
refreshInFlight = null;
|
|
308
|
+
refreshInFlightKey = '';
|
|
261
309
|
}
|
|
262
310
|
}
|
|
263
311
|
|
|
@@ -268,7 +316,7 @@ function queuePacketRefresh(body, apiKey) {
|
|
|
268
316
|
}
|
|
269
317
|
|
|
270
318
|
async function resolvePacketEnvelope(packetRequest = {}, apiKey = '', message = '') {
|
|
271
|
-
if (cachedPacketEnvelope) {
|
|
319
|
+
if (cachedPacketEnvelope && cachedPacketMatchesRequest(cachedPacketEnvelope, packetRequest)) {
|
|
272
320
|
queuePacketRefresh(packetRequest, apiKey);
|
|
273
321
|
return cachedPacketEnvelope;
|
|
274
322
|
}
|