@aria_asi/cli 0.2.33 → 0.2.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aria-connector/src/connectors/codex.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/codex.js +47 -0
- package/dist/aria-connector/src/connectors/codex.js.map +1 -1
- package/dist/assets/hooks/aria-harness-via-sdk.mjs +16 -3
- package/dist/assets/hooks/aria-pre-tool-gate.mjs +41 -1
- package/dist/assets/hooks/aria-stop-gate.mjs +42 -1
- package/dist/assets/hooks/doctrine_trigger_map.json +43 -0
- package/dist/assets/hooks/lib/skill-autoload-gate.mjs +14 -1
- package/dist/assets/opencode-plugins/harness-context/index.js +1 -1
- package/dist/assets/opencode-plugins/harness-gate/index.js +49 -9
- package/dist/assets/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +14 -1
- package/dist/assets/opencode-plugins/harness-stop/index.js +201 -166
- package/dist/assets/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +14 -1
- package/dist/runtime/codex-bridge.mjs +1 -1
- package/dist/runtime/discipline/CLAUDE.md +2 -2
- package/dist/runtime/discipline/doctrine_trigger_map.json +43 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-harness-onboarding/SKILL.md +3 -3
- package/dist/runtime/doctrine_trigger_map.json +43 -0
- package/dist/runtime/hooks/aria-agent-handoff.mjs +247 -0
- package/dist/runtime/hooks/aria-agent-ledger-merge.mjs +164 -0
- package/dist/runtime/hooks/aria-architect-fallback.mjs +267 -0
- package/dist/runtime/hooks/aria-cognition-substrate-binding.mjs +761 -0
- package/dist/runtime/hooks/aria-discovery-record.mjs +101 -0
- package/dist/runtime/hooks/aria-harness-via-sdk.mjs +544 -0
- package/dist/runtime/hooks/aria-import-resolution-gate.mjs +330 -0
- package/dist/runtime/hooks/aria-outcome-record.mjs +84 -0
- package/dist/runtime/hooks/aria-pre-emit-dryrun.mjs +329 -0
- package/dist/runtime/hooks/aria-pre-text-gate.mjs +112 -0
- package/dist/runtime/hooks/aria-pre-tool-gate.mjs +2482 -0
- package/dist/runtime/hooks/aria-preprompt-consult.mjs +464 -0
- package/dist/runtime/hooks/aria-preturn-memory-gate.mjs +647 -0
- package/dist/runtime/hooks/aria-repo-doctrine-gate.mjs +429 -0
- package/dist/runtime/hooks/aria-stop-gate.mjs +1882 -0
- package/dist/runtime/hooks/aria-trigger-autolearn.mjs +229 -0
- package/dist/runtime/hooks/aria-userprompt-abandon-detect.mjs +192 -0
- package/dist/runtime/hooks/doctrine_trigger_map.json +577 -0
- package/dist/runtime/hooks/lib/canonical-lenses.mjs +65 -0
- package/dist/runtime/hooks/lib/domain-output-quality.mjs +103 -0
- package/dist/runtime/hooks/lib/gate-audit.mjs +43 -0
- package/dist/runtime/hooks/lib/gate-loop-state.mjs +50 -0
- package/dist/runtime/hooks/lib/hook-message-window.mjs +121 -0
- package/dist/runtime/hooks/lib/skill-autoload-gate.mjs +14 -0
- package/dist/runtime/hooks/test-aria-preturn-memory-gate.mjs +245 -0
- package/dist/runtime/hooks/test-tier-lens-labeling.mjs +367 -0
- package/dist/runtime/manifest.json +2 -2
- package/dist/runtime/sdk/BUNDLED.json +2 -2
- package/dist/runtime/sdk/index.d.ts +39 -0
- package/dist/runtime/sdk/index.js +117 -0
- package/dist/runtime/sdk/index.js.map +1 -1
- package/dist/runtime/sdk/runWithGovernance.d.ts +16 -0
- package/dist/runtime/sdk/runWithGovernance.js +54 -0
- package/dist/runtime/sdk/runWithGovernance.js.map +1 -0
- package/dist/sdk/BUNDLED.json +2 -2
- package/dist/sdk/index.d.ts +39 -0
- package/dist/sdk/index.js +117 -0
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/runWithGovernance.d.ts +16 -0
- package/dist/sdk/runWithGovernance.js +54 -0
- package/dist/sdk/runWithGovernance.js.map +1 -0
- package/hooks/aria-harness-via-sdk.mjs +16 -3
- package/hooks/aria-pre-tool-gate.mjs +41 -1
- package/hooks/aria-stop-gate.mjs +42 -1
- package/hooks/doctrine_trigger_map.json +43 -0
- package/hooks/lib/skill-autoload-gate.mjs +14 -1
- package/opencode-plugins/harness-context/index.js +1 -1
- package/opencode-plugins/harness-gate/index.js +49 -9
- package/opencode-plugins/harness-gate/lib/skill-autoload-gate.js +14 -1
- package/opencode-plugins/harness-stop/index.js +201 -166
- package/opencode-plugins/harness-stop/lib/skill-autoload-gate.js +14 -1
- package/package.json +12 -5
- package/runtime-src/codex-bridge.mjs +1 -1
- package/scripts/bundle-sdk.mjs +2 -0
- package/scripts/self-test-harness-gates.mjs +79 -0
- package/src/connectors/codex.ts +47 -0
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
6
6
|
import { createHash } from 'node:crypto';
|
|
7
|
+
import { spawnSync } from 'node:child_process';
|
|
7
8
|
import { homedir } from 'node:os';
|
|
8
9
|
import { join } from 'node:path';
|
|
9
10
|
import { analyzeDomainOutputQuality, extractCodeBlocks } from './lib/domain-output-quality.js';
|
|
@@ -17,6 +18,7 @@ const SDK_CANDIDATES = [
|
|
|
17
18
|
];
|
|
18
19
|
const OWNER_TOKEN_PATH = join(HOME, '.aria', 'owner-token');
|
|
19
20
|
const LICENSE_PATH = join(HOME, '.aria', 'license.json');
|
|
21
|
+
const GOVERNANCE_GATE_PATH = join(HOME, '.aria', 'bin', 'aria-governance-gate');
|
|
20
22
|
const RUNTIME_URL = (process.env.ARIA_RUNTIME_URL || 'http://127.0.0.1:4319').replace(/\/+$/, '');
|
|
21
23
|
const RECEIPT_DIR = join(HOME, '.opencode', 'aria-mizan-receipts');
|
|
22
24
|
|
|
@@ -49,6 +51,25 @@ function isGateBlock(error) {
|
|
|
49
51
|
return BLOCK_PREFIX_RX.test(String(error?.message || error || ''));
|
|
50
52
|
}
|
|
51
53
|
|
|
54
|
+
function runUniversalGovernanceGate(payload) {
|
|
55
|
+
if (!existsSync(GOVERNANCE_GATE_PATH)) return null;
|
|
56
|
+
const child = spawnSync(GOVERNANCE_GATE_PATH, {
|
|
57
|
+
input: `${JSON.stringify(payload)}\n`,
|
|
58
|
+
encoding: 'utf8',
|
|
59
|
+
maxBuffer: 1024 * 1024,
|
|
60
|
+
});
|
|
61
|
+
const stdout = String(child.stdout || '').trim();
|
|
62
|
+
let result = null;
|
|
63
|
+
try { result = stdout ? JSON.parse(stdout) : null; } catch {}
|
|
64
|
+
if (child.status !== 0 || result?.ok === false || result?.decision === 'block') {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`=== ARIA UNIVERSAL GOVERNANCE GATE BLOCK ===\n\n` +
|
|
67
|
+
`${stdout || child.stderr || 'aria-governance-gate blocked this output.'}`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
|
|
52
73
|
let _client = null;
|
|
53
74
|
let _clientError = null;
|
|
54
75
|
let _lastMizanReceipt = null;
|
|
@@ -233,192 +254,206 @@ function detectCognitionLenses(text) {
|
|
|
233
254
|
export default async function HarnessStopPlugin(ctx) {
|
|
234
255
|
process.stderr.write('[harness-stop] Active — SDK-backed text-emission gate\n');
|
|
235
256
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
},
|
|
240
|
-
|
|
241
|
-
event: async ({ event }) => {
|
|
242
|
-
if (event.type !== 'message.updated') return;
|
|
243
|
-
const parts = event.properties?.parts || [];
|
|
244
|
-
const text = parts
|
|
245
|
-
.filter(p => p.type === 'text')
|
|
246
|
-
.map(p => p.text || '')
|
|
247
|
-
.join('\n');
|
|
257
|
+
async function validateText(text, eventContext = {}) {
|
|
258
|
+
if (!text || text.length < NON_TRIVIAL_MIN_CHARS) return;
|
|
259
|
+
if (TRIVIAL_ACK_RX.test(text.trim())) return;
|
|
248
260
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
261
|
+
const cog = detectCognitionLenses(text);
|
|
262
|
+
const sessionId = eventContext.sessionID || process.env.ARIA_SESSION_ID || process.env.OPENCODE_SESSION_ID || 'opencode';
|
|
263
|
+
runUniversalGovernanceGate({
|
|
264
|
+
sessionId,
|
|
265
|
+
sourceRuntime: 'opencode',
|
|
266
|
+
surface: 'opencode-harness-stop',
|
|
267
|
+
text,
|
|
268
|
+
isOutputCloseout: true,
|
|
269
|
+
evidence: makeEvidenceRef('opencode_assistant_output_candidate', text.slice(0, 8000), { sessionId }),
|
|
270
|
+
});
|
|
271
|
+
const skillGate = evaluateSkillGate({
|
|
272
|
+
sessionId,
|
|
273
|
+
surface: 'opencode-harness-stop',
|
|
274
|
+
text: JSON.stringify(eventContext || {}) + '\n' + text,
|
|
275
|
+
isOutputCloseout: true,
|
|
276
|
+
autoLoadAvailable: false,
|
|
277
|
+
});
|
|
278
|
+
if (!skillGate.ok) {
|
|
279
|
+
throw new Error(
|
|
280
|
+
formatBlockReason(
|
|
281
|
+
'=== ARIA SKILL AUTOLOAD GATE BLOCK ===',
|
|
282
|
+
`${formatSkillGateBlock(skillGate)}\nRequired next step: load ${skillGate.missingSkills.join(', ')} before re-emitting this output.`,
|
|
283
|
+
)
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
const outputRef = makeEvidenceRef('opencode_assistant_output', text.slice(0, 8000), { sessionId });
|
|
287
|
+
try {
|
|
288
|
+
const mizan = await runtimeMizanPost(text.slice(0, 8000), sessionId, {
|
|
289
|
+
message: text.slice(0, 1000),
|
|
290
|
+
plannedApproach: 'OpenCode stop gate output review',
|
|
291
|
+
outputRef,
|
|
292
|
+
}, {
|
|
293
|
+
output_ref: outputRef,
|
|
294
|
+
cognition_lens_count: cog.count,
|
|
260
295
|
});
|
|
261
|
-
|
|
296
|
+
_lastMizanReceipt = mizan.receipt || _lastMizanReceipt;
|
|
297
|
+
if (_lastMizanReceipt) {
|
|
298
|
+
const existing = loadReceiptState(sessionId) || {};
|
|
299
|
+
persistReceiptState(sessionId, {
|
|
300
|
+
...existing,
|
|
301
|
+
updatedAt: new Date().toISOString(),
|
|
302
|
+
sessionId,
|
|
303
|
+
postReceipt: _lastMizanReceipt,
|
|
304
|
+
postResult: mizan.result || null,
|
|
305
|
+
postContract: mizan.contract || null,
|
|
306
|
+
postSummary: mizan.summary || null,
|
|
307
|
+
outputRef,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
if (mizan.receipt?.blocked || mizan.result?.fitrahVetoed || mizan.result?.reAuthorSignal) {
|
|
311
|
+
const details = (mizan.result?.notes || ['post-phase blocked']).slice(0, 4).join(' | ');
|
|
262
312
|
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
|
-
)
|
|
313
|
+
formatBlockReason('=== ARIA MIZAN POST BLOCK ===', details)
|
|
267
314
|
);
|
|
268
315
|
}
|
|
269
|
-
|
|
316
|
+
} catch (e) {
|
|
317
|
+
if (isGateBlock(e)) throw e;
|
|
318
|
+
process.stderr.write(`[harness-stop] mizan/post unavailable: ${e.message}\n`);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const client = await getClient();
|
|
322
|
+
if (client) {
|
|
270
323
|
try {
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
_lastMizanReceipt = mizan.receipt || _lastMizanReceipt;
|
|
280
|
-
if (_lastMizanReceipt) {
|
|
281
|
-
const existing = loadReceiptState(sessionId) || {};
|
|
282
|
-
persistReceiptState(sessionId, {
|
|
283
|
-
...existing,
|
|
284
|
-
updatedAt: new Date().toISOString(),
|
|
285
|
-
sessionId,
|
|
286
|
-
postReceipt: _lastMizanReceipt,
|
|
287
|
-
postResult: mizan.result || null,
|
|
288
|
-
postContract: mizan.contract || null,
|
|
289
|
-
postSummary: mizan.summary || null,
|
|
290
|
-
outputRef,
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
if (mizan.receipt?.blocked || mizan.result?.fitrahVetoed || mizan.result?.reAuthorSignal) {
|
|
294
|
-
const details = (mizan.result?.notes || ['post-phase blocked']).slice(0, 4).join(' | ');
|
|
324
|
+
const result = await runtimeValidateOutput(
|
|
325
|
+
text.slice(0, 8000),
|
|
326
|
+
sessionId,
|
|
327
|
+
).catch(() => client.validateOutput(
|
|
328
|
+
text.slice(0, 8000),
|
|
329
|
+
sessionId,
|
|
330
|
+
));
|
|
331
|
+
if (result.severity === 'block') {
|
|
295
332
|
throw new Error(
|
|
296
|
-
formatBlockReason(
|
|
333
|
+
formatBlockReason(
|
|
334
|
+
'=== ARIA OUTPUT GATE BLOCK ===',
|
|
335
|
+
`${result.violations.length} violations: ${result.violations.join('; ').slice(0, 500)}`,
|
|
336
|
+
)
|
|
337
|
+
);
|
|
338
|
+
} else if (result.severity === 'warn') {
|
|
339
|
+
throw new Error(
|
|
340
|
+
formatBlockReason(
|
|
341
|
+
'=== ARIA OUTPUT GATE BLOCK ===',
|
|
342
|
+
`SDK returned warn for ${result.violations.length} violation(s): ${(result.violations || []).join('; ').slice(0, 500)}. Warnings are fail-closed on owner-facing output.`,
|
|
343
|
+
)
|
|
297
344
|
);
|
|
298
345
|
}
|
|
346
|
+
if (result.gateTriggers?.length) {
|
|
347
|
+
process.stderr.write(`[harness-stop] SDK triggers: ${result.gateTriggers.join(', ')}\n`);
|
|
348
|
+
}
|
|
349
|
+
return;
|
|
299
350
|
} catch (e) {
|
|
300
351
|
if (isGateBlock(e)) throw e;
|
|
301
|
-
process.stderr.write(`[harness-stop]
|
|
352
|
+
process.stderr.write(`[harness-stop] SDK validateOutput failed: ${e.message} — falling through to local gate\n`);
|
|
302
353
|
}
|
|
354
|
+
}
|
|
303
355
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
)
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
327
|
-
// Log gate triggers
|
|
328
|
-
if (result.gateTriggers?.length) {
|
|
329
|
-
process.stderr.write(`[harness-stop] SDK triggers: ${result.gateTriggers.join(', ')}\n`);
|
|
330
|
-
}
|
|
331
|
-
return;
|
|
332
|
-
} catch (e) {
|
|
333
|
-
if (isGateBlock(e)) throw e;
|
|
334
|
-
process.stderr.write(`[harness-stop] SDK validateOutput failed: ${e.message} — falling through to local gate\n`);
|
|
356
|
+
const triggerMapPaths = [
|
|
357
|
+
`${HOME}/.aria/runtime/discipline/doctrine_trigger_map.json`,
|
|
358
|
+
`${HOME}/.aria/runtime/doctrine_trigger_map.json`,
|
|
359
|
+
`${HOME}/.opencode/doctrine_trigger_map.json`,
|
|
360
|
+
`${HOME}/.codex/doctrine_trigger_map.json`,
|
|
361
|
+
`${HOME}/.claude/hooks/doctrine_trigger_map.json`,
|
|
362
|
+
`${HOME}/.claude/projects/-home-hamzaibrahim1/memory/doctrine_trigger_map.json`,
|
|
363
|
+
];
|
|
364
|
+
let driftHits = [];
|
|
365
|
+
try {
|
|
366
|
+
const triggerMapPath = triggerMapPaths.find((candidate) => existsSync(candidate));
|
|
367
|
+
if (triggerMapPath) {
|
|
368
|
+
const triggerMap = JSON.parse(readFileSync(triggerMapPath, 'utf8'));
|
|
369
|
+
const lower = text.toLowerCase();
|
|
370
|
+
for (const t of triggerMap.triggers || []) {
|
|
371
|
+
try {
|
|
372
|
+
const rx = new RegExp(t.trigger, 'i');
|
|
373
|
+
if (rx.test(lower)) {
|
|
374
|
+
const memCited = t.memory && lower.includes(t.memory.replace(/\.md$/, '').toLowerCase());
|
|
375
|
+
if (!memCited) driftHits.push(t.trigger);
|
|
376
|
+
}
|
|
377
|
+
} catch {}
|
|
335
378
|
}
|
|
336
379
|
}
|
|
380
|
+
} catch {}
|
|
337
381
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
`${HOME}/.claude/projects/-home-hamzaibrahim1/memory/doctrine_trigger_map.json`,
|
|
347
|
-
];
|
|
348
|
-
let driftHits = [];
|
|
349
|
-
try {
|
|
350
|
-
const triggerMapPath = triggerMapPaths.find((candidate) => existsSync(candidate));
|
|
351
|
-
if (triggerMapPath) {
|
|
352
|
-
const triggerMap = JSON.parse(readFileSync(triggerMapPath, 'utf8'));
|
|
353
|
-
const lower = text.toLowerCase();
|
|
354
|
-
for (const t of triggerMap.triggers || []) {
|
|
355
|
-
try {
|
|
356
|
-
const rx = new RegExp(t.trigger, 'i');
|
|
357
|
-
if (rx.test(lower)) {
|
|
358
|
-
const memCited = t.memory && lower.includes(t.memory.replace(/\.md$/, '').toLowerCase());
|
|
359
|
-
if (!memCited) driftHits.push(t.trigger);
|
|
360
|
-
}
|
|
361
|
-
} catch {}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
} catch {}
|
|
382
|
+
if (cog.count < REQUIRED_LENSES || driftHits.length >= 2) {
|
|
383
|
+
throw new Error(
|
|
384
|
+
formatBlockReason(
|
|
385
|
+
'=== ARIA LOCAL OUTPUT BLOCK ===',
|
|
386
|
+
`cognition=${cog.count}/${REQUIRED_LENSES}; drift=${driftHits.length}`,
|
|
387
|
+
)
|
|
388
|
+
);
|
|
389
|
+
}
|
|
365
390
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
391
|
+
if (DECISION_SIGNAL_RX.test(text) && !APPLIED_COGNITION_RX.test(text)) {
|
|
392
|
+
throw new Error(
|
|
393
|
+
formatBlockReason(
|
|
394
|
+
'=== ARIA LOCAL OUTPUT BLOCK ===',
|
|
395
|
+
'decision-bearing output lacks required applied_cognition binding fields',
|
|
396
|
+
)
|
|
397
|
+
);
|
|
398
|
+
}
|
|
374
399
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
)
|
|
381
|
-
)
|
|
382
|
-
|
|
400
|
+
const domainQuality = analyzeDomainOutputQuality(text, { codeBlocks: extractCodeBlocks(text) });
|
|
401
|
+
if (domainQuality.blockers.length > 0) {
|
|
402
|
+
throw new Error(
|
|
403
|
+
formatBlockReason(
|
|
404
|
+
'=== ARIA LOCAL OUTPUT BLOCK ===',
|
|
405
|
+
`domain output QA (${domainQuality.domains.join(', ') || 'general'}): ${domainQuality.blockers.join('; ')}`,
|
|
406
|
+
)
|
|
407
|
+
);
|
|
408
|
+
}
|
|
383
409
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
410
|
+
try {
|
|
411
|
+
const existing = loadReceiptState(sessionId);
|
|
412
|
+
await runtimeDecisionLog({
|
|
413
|
+
decision_type: 'turn_action',
|
|
414
|
+
category: 'agentic_execution',
|
|
415
|
+
context: `opencode stop-gate turn (chars=${text.length})`,
|
|
416
|
+
decision: 'turn completed',
|
|
417
|
+
reasoning: cog.count > 0
|
|
418
|
+
? `Cognition lenses applied: ${cog.names.join(', ')}.`
|
|
419
|
+
: 'No explicit cognition block in turn.',
|
|
420
|
+
outcome: 'pending',
|
|
421
|
+
outcome_details: {
|
|
422
|
+
expected: null,
|
|
423
|
+
immediate_actual: null,
|
|
424
|
+
anchors: [],
|
|
425
|
+
},
|
|
426
|
+
expected_outcome: null,
|
|
427
|
+
metadata: {
|
|
428
|
+
pre_receipt_id: existing?.receipt?.receiptId || null,
|
|
429
|
+
post_receipt_id: _lastMizanReceipt?.receiptId || null,
|
|
430
|
+
output_ref: outputRef,
|
|
431
|
+
},
|
|
432
|
+
source: 'opencode-stop-gate-runtime',
|
|
433
|
+
model_used: process.env.OPENCODE_MODEL || 'opencode',
|
|
434
|
+
});
|
|
435
|
+
} catch (e) {
|
|
436
|
+
process.stderr.write(`[harness-stop] decision/log unavailable: ${e.message}\n`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
393
439
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
metadata: {
|
|
412
|
-
pre_receipt_id: existing?.receipt?.receiptId || null,
|
|
413
|
-
post_receipt_id: _lastMizanReceipt?.receiptId || null,
|
|
414
|
-
output_ref: outputRef,
|
|
415
|
-
},
|
|
416
|
-
source: 'opencode-stop-gate-runtime',
|
|
417
|
-
model_used: process.env.OPENCODE_MODEL || 'opencode',
|
|
418
|
-
});
|
|
419
|
-
} catch (e) {
|
|
420
|
-
process.stderr.write(`[harness-stop] decision/log unavailable: ${e.message}\n`);
|
|
421
|
-
}
|
|
440
|
+
return {
|
|
441
|
+
'session.idle': async (event) => {
|
|
442
|
+
process.stderr.write('[harness-stop] session idle heartbeat — text-emission gate registered\n');
|
|
443
|
+
},
|
|
444
|
+
|
|
445
|
+
event: async ({ event }) => {
|
|
446
|
+
if (event.type !== 'message.updated') return;
|
|
447
|
+
const parts = event.properties?.parts || [];
|
|
448
|
+
const text = parts
|
|
449
|
+
.filter(p => p.type === 'text')
|
|
450
|
+
.map(p => p.text || '')
|
|
451
|
+
.join('\n');
|
|
452
|
+
await validateText(text, { event });
|
|
453
|
+
},
|
|
454
|
+
|
|
455
|
+
'experimental.text.complete': async (input, output) => {
|
|
456
|
+
await validateText(String(output?.text || ''), input || {});
|
|
422
457
|
},
|
|
423
458
|
};
|
|
424
459
|
}
|
|
@@ -1 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
const RECEIPT_ROOT = process.env.ARIA_SKILL_RECEIPT_DIR || join(tmpdir(), 'aria-skill-receipts');
|
|
5
|
+
const ALIASES = new Map([['deploy', 'aria-harness-deploy'], ['output', 'aria-harness-output-discipline'], ['repo', 'aria-repo-doctrine'], ['forge', 'aria-forge-guardrails']]);
|
|
6
|
+
const RX = { deploy: /deploy-service\.sh|kubectl\s+(?:apply|set|rollout|delete|scale)|helm\s+upgrade|terraform\s+apply|docker\s+push/i, mutationTool: /^(?:edit|write|notebookedit|patch|apply_patch)$/i, mutation: /apply_patch|write file|edit file|modify|delete file|migration|handler|route|runtime|hook|plugin|\btest\b|smoke script/i, strip: /remove|delete|strip|drop|omit|disable|bypass|skip|stub|mock|fake|placeholder|temporary|quick scaffold|band-aid/i, readiness: /production-ready|ready for production|works in general|general readiness|client packages?|npm packages?|SDKs?|runtimes?|harnesses?|release-ready|ship-ready/i, narrow: /single flow|one flow|narrow e2e|covered flow|specific path|widget flow/i, completion: /done|complete|completed|ready|verified|fixed|shipped|implemented|production-ready/i, badProof: /but|except|caveat|remaining|not yet|still|separate|later|blocked|skipped|unresolved|follow-up|failed|failing|error|red|not run|could not verify|untested|no proof|missing proof|without proof/i, advisory: /non-blocking|warning only|warn only|advisory|fall through|falls through|fail open|soft fail|logged and continue|quality gate warning/i, success: /(?:verified|passed|success|successful|green|ok)\s*[:=\-].{0,120}(?:npm|node|playwright|jest|vitest|build|test|lint|typecheck|curl|kubectl|self-test|e2e|probe|smoke)|(?:npm|node|playwright|jest|vitest|build|test|lint|typecheck|curl|kubectl).{0,120}(?:passed|success|successful|green|exit\s*0)/i, resubmit: /re-?submission|resubmit/i, rewrite: /re-?write|rewrite|fix/i, retest: /re-?test|retest|rerun/i, aria: /ARIA console|Aria console|\/chat|aria-pipeline-mcp|aria_chat|escalat(?:e|ion).{0,80}ARIA/i };
|
|
7
|
+
function normalizeSkillName(skill) { return ALIASES.get(String(skill || '').trim()) || String(skill || '').trim(); }
|
|
8
|
+
function sessionDir(sessionId) { return join(RECEIPT_ROOT, encodeURIComponent(String(sessionId || 'unknown'))); }
|
|
9
|
+
function readReceiptSkills(sessionId) { const dir = sessionDir(sessionId); if (!existsSync(dir)) return new Set(); const skills = new Set(); for (const name of readdirSync(dir)) { if (!name.endsWith('.json')) continue; try { const receipt = JSON.parse(readFileSync(join(dir, name), 'utf8')); if (receipt?.skill) skills.add(normalizeSkillName(receipt.skill)); } catch {} } return skills; }
|
|
10
|
+
function readInlineSkills(text) { const skills = new Set(); const value = String(text || ''); for (const match of value.matchAll(/<skill_content\s+name=["']([^"']+)["']/gi)) skills.add(normalizeSkillName(match[1])); return skills; }
|
|
11
|
+
export function recordSkillLoaded({ sessionId, skill, surface = 'unknown', metadata = {} } = {}) { const normalized = normalizeSkillName(skill); if (!normalized) throw new Error('recordSkillLoaded requires a skill name'); const dir = sessionDir(sessionId); mkdirSync(dir, { recursive: true }); const receipt = { skill: normalized, surface, metadata, recordedAt: new Date().toISOString() }; writeFileSync(join(dir, `${encodeURIComponent(normalized)}.json`), `${JSON.stringify(receipt, null, 2)}\n`); return receipt; }
|
|
12
|
+
export function classifyRequiredSkills({ text = '', action = '', toolName = '', filePath = '', isDeploy = false, isMutation = false, isOutputCloseout = false } = {}) { const combined = [text, action, toolName, filePath].filter(Boolean).join('\n'); const required = new Set(); const reasons = []; const recoveryMissing = []; if (isDeploy || RX.deploy.test(combined)) { required.add('aria-harness-deploy'); required.add('aria-forge-guardrails'); reasons.push('deploy/shared-infrastructure action requires fail-closed deploy and forge guardrails'); } if (isMutation || RX.mutationTool.test(toolName)) { required.add('aria-repo-doctrine'); reasons.push('repository/runtime mutation requires repo doctrine'); } if (RX.strip.test(combined)) { required.add('aria-harness-no-stripping'); reasons.push('strip/remove/bypass language requires no-stripping gate'); } if (isOutputCloseout && RX.completion.test(combined)) { required.add('aria-harness-output-discipline'); reasons.push('owner-facing completion/readiness claim requires output discipline'); if (!RX.success.test(combined)) recoveryMissing.push('successful proof from a concrete command/probe'); } if (RX.readiness.test(combined)) { required.add('architecture-decision'); required.add('testing-strategy'); required.add('aria-forge-guardrails'); required.add('aria-harness-output-discipline'); reasons.push('broad production/package/SDK/runtime readiness claim requires architecture, testing, and forge guardrails'); } if (RX.readiness.test(combined) && RX.narrow.test(combined)) { required.add('testing-strategy'); required.add('aria-forge-guardrails'); reasons.push('narrow e2e proof cannot support broad readiness claim without readiness-matrix discipline'); } if (RX.completion.test(combined) && RX.badProof.test(combined)) { required.add('aria-harness-output-discipline'); required.add('aria-forge-guardrails'); reasons.push('completion claim with unresolved or failed proof requires recovery cycle'); if (!RX.resubmit.test(combined)) recoveryMissing.push('re-submission'); if (!RX.rewrite.test(combined)) recoveryMissing.push('re-write'); if (!RX.retest.test(combined)) recoveryMissing.push('re-test'); if (!RX.aria.test(combined)) recoveryMissing.push('ARIA console escalation'); } if (RX.advisory.test(combined)) { required.add('aria-forge-guardrails'); required.add('aria-harness-output-discipline'); reasons.push('advisory/fail-open gate language requires fail-closed hardening discipline'); } return { requiredSkills: [...required].sort(), reasons, recoveryMissing }; }
|
|
13
|
+
export function evaluateSkillGate(options = {}) { const classified = classifyRequiredSkills(options); const text = [options.text, options.action].filter(Boolean).join('\n'); const loaded = new Set([...readReceiptSkills(options.sessionId), ...readInlineSkills(text)]); const missingSkills = classified.requiredSkills.filter((skill) => !loaded.has(skill)); const recoveryMissing = classified.recoveryMissing || []; return { ok: missingSkills.length === 0 && recoveryMissing.length === 0, surface: options.surface || 'unknown', sessionId: options.sessionId || 'unknown', requiredSkills: classified.requiredSkills, loadedSkills: [...loaded].sort(), missingSkills, recoveryMissing, reasons: classified.reasons, autoLoadAvailable: options.autoLoadAvailable === true }; }
|
|
14
|
+
export function formatSkillGateBlock(result = {}) { const missing = Array.isArray(result.missingSkills) ? result.missingSkills : []; const recovery = Array.isArray(result.recoveryMissing) ? result.recoveryMissing : []; const reasons = Array.isArray(result.reasons) ? result.reasons : []; return ['=== ARIA SKILL AUTOLOAD GATE BLOCK ===', `surface: ${result.surface || 'unknown'}`, `missing_skills: ${missing.length ? missing.join(', ') : '(none)'}`, `missing_recovery_cycle: ${recovery.length ? recovery.join(', ') : '(none)'}`, `required_skills: ${(result.requiredSkills || []).join(', ') || '(none)'}`, reasons.length ? `reasons: ${reasons.join(' | ')}` : 'reasons: no classifier reason recorded', 'counter_action: re-submit, re-write, re-test, and escalate through ARIA console until successful proof exists. Do not downgrade this to an advisory warning.'].join('\n'); }
|
|
@@ -6,7 +6,7 @@ import { homedir } from 'node:os';
|
|
|
6
6
|
import { delimiter, dirname, resolve } from 'node:path';
|
|
7
7
|
import { spawn, spawnSync } from 'node:child_process';
|
|
8
8
|
import { createRequire } from 'node:module';
|
|
9
|
-
import { evaluateSkillGate, formatSkillGateBlock } from '
|
|
9
|
+
import { evaluateSkillGate, formatSkillGateBlock } from './hooks/lib/skill-autoload-gate.mjs';
|
|
10
10
|
|
|
11
11
|
const require = createRequire(import.meta.url);
|
|
12
12
|
const { WebSocketServer, WebSocket } = require('ws');
|
|
@@ -18,7 +18,7 @@ If compaction, gate deadlock, or repeated drift happens, restart from that order
|
|
|
18
18
|
|
|
19
19
|
## What this SDK is
|
|
20
20
|
|
|
21
|
-
`@
|
|
21
|
+
`@aria_asi/harness-http-client` is the Aria harness SDK. It binds a process
|
|
22
22
|
(worker, autonomous loop, dispatched artifact) to Aria's three-layer
|
|
23
23
|
enforcement substrate so the process cannot drift from Aria's doctrine,
|
|
24
24
|
axioms, frames, and memory.
|
|
@@ -221,7 +221,7 @@ The Stop-gate scans output for these patterns. When matched, the response is rej
|
|
|
221
221
|
### 1. Build a harness-bound LLM call
|
|
222
222
|
|
|
223
223
|
```ts
|
|
224
|
-
import { getBoundHarnessAndPrompt, bindingContext } from '@
|
|
224
|
+
import { getBoundHarnessAndPrompt, bindingContext } from '@aria_asi/harness-http-client';
|
|
225
225
|
import { runLayer3Gates } from '@aria/gate-runtime';
|
|
226
226
|
|
|
227
227
|
const { prompt: harnessContext, packet } = await getBoundHarnessAndPrompt(
|
|
@@ -508,6 +508,49 @@
|
|
|
508
508
|
"counter_action": "Create or reuse one turn substrate object containing embedding, perturb snapshot, ProjectAllDomains result, awakenAria Garden block, DeepSoul/Noor/shards/Mizan signals; pass it downstream.",
|
|
509
509
|
"message": "Duplicate projection path detected. Replace with one per-turn cognition packet and shared consumption."
|
|
510
510
|
},
|
|
511
|
+
{
|
|
512
|
+
"trigger_id": "premature_task_closeout",
|
|
513
|
+
"trigger": "(?:done|complete|completed|ready|verified|fixed).{0,120}(?:but|except|caveat|remaining|not yet|still|separate|later|blocked|skipped)",
|
|
514
|
+
"rx": "(?:done|complete|completed|ready|verified|fixed).{0,120}(?:but|except|caveat|remaining|not yet|still|separate|later|blocked|skipped)",
|
|
515
|
+
"doctrine": "memory:feedback_no_premature_task_closeout.md",
|
|
516
|
+
"memory": "feedback_no_premature_task_closeout.md",
|
|
517
|
+
"severity": "block",
|
|
518
|
+
"teaching": "A task is not complete while material blockers remain. Completion claims must match the verified scope.",
|
|
519
|
+
"counter_action": "Do not close the task. Fix the blocker now, or create an owner-visible durable task with the exact failing surface and verification gap.",
|
|
520
|
+
"message": "Premature closeout detected: completion language coexists with unresolved blockers. Fix or durably track before emitting."
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
"trigger_id": "narrow_e2e_overclaim",
|
|
524
|
+
"trigger": "(?:production-ready|ready for production|works in general|client packages?|npm packages?|SDKs?|runtimes?|harnesses?).{0,220}(?:deal|single flow|one flow|widget|one service|specific path|covered flow)",
|
|
525
|
+
"rx": "(?:production-ready|ready for production|works in general|client packages?|npm packages?|SDKs?|runtimes?|harnesses?).{0,220}(?:deal|single flow|one flow|widget|one service|specific path|covered flow)",
|
|
526
|
+
"doctrine": "memory:feedback_narrow_e2e_overclaim.md",
|
|
527
|
+
"memory": "feedback_narrow_e2e_overclaim.md",
|
|
528
|
+
"severity": "block",
|
|
529
|
+
"teaching": "A narrow e2e does not prove general production readiness for SDKs, npm packages, runtimes, clients, or harnesses.",
|
|
530
|
+
"counter_action": "Run the general readiness matrix or explicitly limit the claim to the verified surface. Do not imply general readiness from one app flow.",
|
|
531
|
+
"message": "Narrow proof overclaim detected. General readiness requires the client/package/runtime/harness matrix."
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
"trigger_id": "advisory_gate_not_gate",
|
|
535
|
+
"trigger": "(?:non-blocking|warn(?:ing)? only|advisory|falls? through|fail open|soft fail|log(?:ged)? and continue|quality gate warning)",
|
|
536
|
+
"rx": "(?:non-blocking|warn(?:ing)? only|advisory|falls? through|fail open|soft fail|log(?:ged)? and continue|quality gate warning)",
|
|
537
|
+
"doctrine": "memory:feedback_advisory_gate_is_not_gate.md",
|
|
538
|
+
"memory": "feedback_advisory_gate_is_not_gate.md",
|
|
539
|
+
"severity": "block",
|
|
540
|
+
"teaching": "A gate that only warns or falls through is not a gate. Enforcement must fail closed where quality or doctrine is required.",
|
|
541
|
+
"counter_action": "Convert advisory paths to blocking errors, add a self-test that proves rejection, and install the hardened gate into each consumer surface.",
|
|
542
|
+
"message": "Advisory gate bypass detected. Convert to fail-closed enforcement and prove it with a blocking self-test."
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
"trigger_id": "start_new_session_as_gate_fix",
|
|
546
|
+
"trigger": "(?:start|open|begin).{0,40}(?:new|fresh).{0,30}session.{0,120}(?:fix|gate|harness|enforcement)",
|
|
547
|
+
"rx": "(?:start|open|begin).{0,40}(?:new|fresh).{0,30}session.{0,120}(?:fix|gate|harness|enforcement)",
|
|
548
|
+
"doctrine": "memory:feedback_advisory_gate_is_not_gate.md",
|
|
549
|
+
"memory": "feedback_advisory_gate_is_not_gate.md",
|
|
550
|
+
"severity": "block",
|
|
551
|
+
"teaching": "A new session is not an enforcement fix. It only reloads whatever gate contract exists; broken gates remain broken.",
|
|
552
|
+
"counter_action": "Fix the runtime/plugin/hook contract, reinstall it, and run a live bad-action/bad-output self-test that proves blocking."
|
|
553
|
+
},
|
|
511
554
|
{
|
|
512
555
|
"trigger_id": "registry_image_drift",
|
|
513
556
|
"trigger": "image.?pull|ErrImageNeverPull|ImagePullBackOff|registry gc|image.*missing|image.*not.*found",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: aria-harness-onboarding
|
|
3
|
-
description: Use when starting a new project that imports @
|
|
3
|
+
description: Use when starting a new project that imports @aria_asi/harness-http-client OR when an agent first encounters the SDK in a repo. Loads the foundational 3-layer harness model (packet/BIND/gate-runtime), the doctrine reference list, and points to the four sibling skills (aria-harness-deploy, aria-harness-substrate-binding, aria-harness-no-stripping, aria-harness-output-discipline). Read this once per project to inherit the discipline.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Aria Harness Onboarding
|
|
@@ -9,7 +9,7 @@ You are about to author code that uses the Aria harness SDK. This skill loads th
|
|
|
9
9
|
|
|
10
10
|
## What the SDK is
|
|
11
11
|
|
|
12
|
-
`@
|
|
12
|
+
`@aria_asi/harness-http-client` binds a process (worker, autonomous loop, dispatched artifact) to Aria's three-layer enforcement substrate so the process cannot drift from Aria's doctrine, axioms, frames, and memory.
|
|
13
13
|
|
|
14
14
|
## The three layers — every consumer enforces all three
|
|
15
15
|
|
|
@@ -36,7 +36,7 @@ This onboarding skill is the entry point. Four sibling skills auto-trigger on sp
|
|
|
36
36
|
## Build a harness-bound LLM call (the basic pattern)
|
|
37
37
|
|
|
38
38
|
```ts
|
|
39
|
-
import { getBoundHarnessAndPrompt, bindingContext } from '@
|
|
39
|
+
import { getBoundHarnessAndPrompt, bindingContext } from '@aria_asi/harness-http-client';
|
|
40
40
|
import { runLayer3Gates } from '@aria/gate-runtime';
|
|
41
41
|
|
|
42
42
|
const { prompt: harnessContext, packet } = await getBoundHarnessAndPrompt(
|
|
@@ -508,6 +508,49 @@
|
|
|
508
508
|
"counter_action": "Create or reuse one turn substrate object containing embedding, perturb snapshot, ProjectAllDomains result, awakenAria Garden block, DeepSoul/Noor/shards/Mizan signals; pass it downstream.",
|
|
509
509
|
"message": "Duplicate projection path detected. Replace with one per-turn cognition packet and shared consumption."
|
|
510
510
|
},
|
|
511
|
+
{
|
|
512
|
+
"trigger_id": "premature_task_closeout",
|
|
513
|
+
"trigger": "(?:done|complete|completed|ready|verified|fixed).{0,120}(?:but|except|caveat|remaining|not yet|still|separate|later|blocked|skipped)",
|
|
514
|
+
"rx": "(?:done|complete|completed|ready|verified|fixed).{0,120}(?:but|except|caveat|remaining|not yet|still|separate|later|blocked|skipped)",
|
|
515
|
+
"doctrine": "memory:feedback_no_premature_task_closeout.md",
|
|
516
|
+
"memory": "feedback_no_premature_task_closeout.md",
|
|
517
|
+
"severity": "block",
|
|
518
|
+
"teaching": "A task is not complete while material blockers remain. Completion claims must match the verified scope.",
|
|
519
|
+
"counter_action": "Do not close the task. Fix the blocker now, or create an owner-visible durable task with the exact failing surface and verification gap.",
|
|
520
|
+
"message": "Premature closeout detected: completion language coexists with unresolved blockers. Fix or durably track before emitting."
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
"trigger_id": "narrow_e2e_overclaim",
|
|
524
|
+
"trigger": "(?:production-ready|ready for production|works in general|client packages?|npm packages?|SDKs?|runtimes?|harnesses?).{0,220}(?:deal|single flow|one flow|widget|one service|specific path|covered flow)",
|
|
525
|
+
"rx": "(?:production-ready|ready for production|works in general|client packages?|npm packages?|SDKs?|runtimes?|harnesses?).{0,220}(?:deal|single flow|one flow|widget|one service|specific path|covered flow)",
|
|
526
|
+
"doctrine": "memory:feedback_narrow_e2e_overclaim.md",
|
|
527
|
+
"memory": "feedback_narrow_e2e_overclaim.md",
|
|
528
|
+
"severity": "block",
|
|
529
|
+
"teaching": "A narrow e2e does not prove general production readiness for SDKs, npm packages, runtimes, clients, or harnesses.",
|
|
530
|
+
"counter_action": "Run the general readiness matrix or explicitly limit the claim to the verified surface. Do not imply general readiness from one app flow.",
|
|
531
|
+
"message": "Narrow proof overclaim detected. General readiness requires the client/package/runtime/harness matrix."
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
"trigger_id": "advisory_gate_not_gate",
|
|
535
|
+
"trigger": "(?:non-blocking|warn(?:ing)? only|advisory|falls? through|fail open|soft fail|log(?:ged)? and continue|quality gate warning)",
|
|
536
|
+
"rx": "(?:non-blocking|warn(?:ing)? only|advisory|falls? through|fail open|soft fail|log(?:ged)? and continue|quality gate warning)",
|
|
537
|
+
"doctrine": "memory:feedback_advisory_gate_is_not_gate.md",
|
|
538
|
+
"memory": "feedback_advisory_gate_is_not_gate.md",
|
|
539
|
+
"severity": "block",
|
|
540
|
+
"teaching": "A gate that only warns or falls through is not a gate. Enforcement must fail closed where quality or doctrine is required.",
|
|
541
|
+
"counter_action": "Convert advisory paths to blocking errors, add a self-test that proves rejection, and install the hardened gate into each consumer surface.",
|
|
542
|
+
"message": "Advisory gate bypass detected. Convert to fail-closed enforcement and prove it with a blocking self-test."
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
"trigger_id": "start_new_session_as_gate_fix",
|
|
546
|
+
"trigger": "(?:start|open|begin).{0,40}(?:new|fresh).{0,30}session.{0,120}(?:fix|gate|harness|enforcement)",
|
|
547
|
+
"rx": "(?:start|open|begin).{0,40}(?:new|fresh).{0,30}session.{0,120}(?:fix|gate|harness|enforcement)",
|
|
548
|
+
"doctrine": "memory:feedback_advisory_gate_is_not_gate.md",
|
|
549
|
+
"memory": "feedback_advisory_gate_is_not_gate.md",
|
|
550
|
+
"severity": "block",
|
|
551
|
+
"teaching": "A new session is not an enforcement fix. It only reloads whatever gate contract exists; broken gates remain broken.",
|
|
552
|
+
"counter_action": "Fix the runtime/plugin/hook contract, reinstall it, and run a live bad-action/bad-output self-test that proves blocking."
|
|
553
|
+
},
|
|
511
554
|
{
|
|
512
555
|
"trigger_id": "registry_image_drift",
|
|
513
556
|
"trigger": "image.?pull|ErrImageNeverPull|ImagePullBackOff|registry gc|image.*missing|image.*not.*found",
|