@aria_asi/cli 0.2.25 → 0.2.29
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/CLIENT-ONBOARDING.md +282 -0
- package/bin/aria.js +1140 -14
- package/dist/aria-connector/src/auth-commands.d.ts +1 -0
- package/dist/aria-connector/src/auth-commands.d.ts.map +1 -1
- package/dist/aria-connector/src/auth-commands.js +89 -41
- package/dist/aria-connector/src/auth-commands.js.map +1 -1
- package/dist/aria-connector/src/chat.d.ts +3 -0
- package/dist/aria-connector/src/chat.d.ts.map +1 -1
- package/dist/aria-connector/src/chat.js +146 -8
- package/dist/aria-connector/src/chat.js.map +1 -1
- package/dist/aria-connector/src/codebase-scanner.d.ts +2 -2
- package/dist/aria-connector/src/codebase-scanner.d.ts.map +1 -1
- package/dist/aria-connector/src/codebase-scanner.js +1 -1
- package/dist/aria-connector/src/codebase-scanner.js.map +1 -1
- package/dist/aria-connector/src/config.d.ts +12 -0
- package/dist/aria-connector/src/config.d.ts.map +1 -1
- package/dist/aria-connector/src/config.js +2 -0
- package/dist/aria-connector/src/config.js.map +1 -1
- package/dist/aria-connector/src/connectors/claude-code.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/claude-code.js +111 -21
- package/dist/aria-connector/src/connectors/claude-code.js.map +1 -1
- package/dist/aria-connector/src/connectors/codebase-awareness.d.ts +37 -0
- package/dist/aria-connector/src/connectors/codebase-awareness.d.ts.map +1 -0
- package/dist/aria-connector/src/connectors/codebase-awareness.js +335 -0
- package/dist/aria-connector/src/connectors/codebase-awareness.js.map +1 -0
- package/dist/aria-connector/src/connectors/codex.d.ts +3 -0
- package/dist/aria-connector/src/connectors/codex.d.ts.map +1 -0
- package/dist/aria-connector/src/connectors/codex.js +248 -0
- package/dist/aria-connector/src/connectors/codex.js.map +1 -0
- package/dist/aria-connector/src/connectors/cognitive-skills.d.ts +2 -0
- package/dist/aria-connector/src/connectors/cognitive-skills.d.ts.map +1 -0
- package/dist/aria-connector/src/connectors/cognitive-skills.js +47 -0
- package/dist/aria-connector/src/connectors/cognitive-skills.js.map +1 -0
- package/dist/aria-connector/src/connectors/opencode.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/opencode.js +90 -4
- package/dist/aria-connector/src/connectors/opencode.js.map +1 -1
- package/dist/aria-connector/src/connectors/repo-git-hooks.d.ts +3 -0
- package/dist/aria-connector/src/connectors/repo-git-hooks.d.ts.map +1 -0
- package/dist/aria-connector/src/connectors/repo-git-hooks.js +87 -0
- package/dist/aria-connector/src/connectors/repo-git-hooks.js.map +1 -0
- package/dist/aria-connector/src/connectors/repo-guard.d.ts +19 -0
- package/dist/aria-connector/src/connectors/repo-guard.d.ts.map +1 -0
- package/dist/aria-connector/src/connectors/repo-guard.js +509 -0
- package/dist/aria-connector/src/connectors/repo-guard.js.map +1 -0
- package/dist/aria-connector/src/connectors/runtime.d.ts +2 -0
- package/dist/aria-connector/src/connectors/runtime.d.ts.map +1 -0
- package/dist/aria-connector/src/connectors/runtime.js +330 -0
- package/dist/aria-connector/src/connectors/runtime.js.map +1 -0
- package/dist/aria-connector/src/connectors/shell.d.ts.map +1 -1
- package/dist/aria-connector/src/connectors/shell.js +78 -13
- package/dist/aria-connector/src/connectors/shell.js.map +1 -1
- package/dist/aria-connector/src/connectors/syncd.d.ts +27 -0
- package/dist/aria-connector/src/connectors/syncd.d.ts.map +1 -0
- package/dist/aria-connector/src/connectors/syncd.js +405 -0
- package/dist/aria-connector/src/connectors/syncd.js.map +1 -0
- package/dist/aria-connector/src/decisions.d.ts +207 -0
- package/dist/aria-connector/src/decisions.d.ts.map +1 -0
- package/dist/aria-connector/src/decisions.js +291 -0
- package/dist/aria-connector/src/decisions.js.map +1 -0
- package/dist/aria-connector/src/garden-control-plane.d.ts.map +1 -1
- package/dist/aria-connector/src/garden-control-plane.js +74 -17
- package/dist/aria-connector/src/garden-control-plane.js.map +1 -1
- package/dist/aria-connector/src/github-connect.d.ts +18 -0
- package/dist/aria-connector/src/github-connect.d.ts.map +1 -0
- package/dist/aria-connector/src/github-connect.js +117 -0
- package/dist/aria-connector/src/github-connect.js.map +1 -0
- package/dist/aria-connector/src/harness-client.d.ts +15 -0
- package/dist/aria-connector/src/harness-client.d.ts.map +1 -1
- package/dist/aria-connector/src/harness-client.js +106 -3
- package/dist/aria-connector/src/harness-client.js.map +1 -1
- package/dist/aria-connector/src/hive-client.d.ts +30 -0
- package/dist/aria-connector/src/hive-client.d.ts.map +1 -1
- package/dist/aria-connector/src/hive-client.js +124 -5
- package/dist/aria-connector/src/hive-client.js.map +1 -1
- package/dist/aria-connector/src/index.d.ts +13 -2
- package/dist/aria-connector/src/index.d.ts.map +1 -1
- package/dist/aria-connector/src/index.js +10 -1
- package/dist/aria-connector/src/index.js.map +1 -1
- package/dist/aria-connector/src/lib/aristotle-noor-wire.d.ts +102 -0
- package/dist/aria-connector/src/lib/aristotle-noor-wire.d.ts.map +1 -0
- package/dist/aria-connector/src/lib/aristotle-noor-wire.js +231 -0
- package/dist/aria-connector/src/lib/aristotle-noor-wire.js.map +1 -0
- package/dist/aria-connector/src/providers/types.d.ts +5 -0
- package/dist/aria-connector/src/providers/types.d.ts.map +1 -1
- package/dist/aria-connector/src/runtime-proof.d.ts +45 -0
- package/dist/aria-connector/src/runtime-proof.d.ts.map +1 -0
- package/dist/aria-connector/src/runtime-proof.js +340 -0
- package/dist/aria-connector/src/runtime-proof.js.map +1 -0
- package/dist/aria-connector/src/setup-wizard.d.ts.map +1 -1
- package/dist/aria-connector/src/setup-wizard.js +34 -2
- package/dist/aria-connector/src/setup-wizard.js.map +1 -1
- package/dist/assets/hooks/aria-agent-handoff.mjs +224 -0
- package/dist/assets/hooks/aria-agent-ledger-merge.mjs +164 -0
- package/dist/assets/hooks/aria-architect-fallback.mjs +267 -0
- package/dist/assets/hooks/aria-cognition-substrate-binding.mjs +676 -0
- package/dist/assets/hooks/aria-discovery-record.mjs +101 -0
- package/dist/assets/hooks/aria-harness-via-sdk.mjs +412 -0
- package/dist/assets/hooks/aria-import-resolution-gate.mjs +330 -0
- package/dist/assets/hooks/aria-outcome-record.mjs +84 -0
- package/dist/assets/hooks/aria-pre-emit-dryrun.mjs +294 -0
- package/dist/assets/hooks/aria-pre-text-gate.mjs +112 -0
- package/dist/assets/hooks/aria-pre-tool-gate.mjs +2133 -0
- package/dist/assets/hooks/aria-preprompt-consult.mjs +438 -0
- package/dist/assets/hooks/aria-preturn-memory-gate.mjs +570 -0
- package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +397 -0
- package/dist/assets/hooks/aria-stop-gate.mjs +1551 -0
- package/dist/assets/hooks/aria-trigger-autolearn.mjs +229 -0
- package/dist/assets/hooks/aria-userprompt-abandon-detect.mjs +192 -0
- package/dist/assets/hooks/doctrine_trigger_map.json +479 -0
- package/dist/assets/hooks/lib/canonical-lenses.mjs +64 -0
- package/dist/assets/hooks/lib/gate-audit.mjs +43 -0
- package/dist/assets/hooks/test-aria-preturn-memory-gate.mjs +245 -0
- package/dist/assets/hooks/test-tier-lens-labeling.mjs +399 -0
- package/dist/assets/opencode-plugins/harness-context/index.js +60 -0
- package/dist/assets/opencode-plugins/harness-context/inject-context.mjs +179 -0
- package/dist/assets/opencode-plugins/harness-context/package.json +9 -0
- package/dist/assets/opencode-plugins/harness-gate/index.js +248 -0
- package/dist/assets/opencode-plugins/harness-outcome/index.js +129 -0
- package/dist/assets/opencode-plugins/harness-role/index.js +77 -0
- package/dist/assets/opencode-plugins/harness-role/package.json +9 -0
- package/dist/assets/opencode-plugins/harness-stop/index.js +241 -0
- package/dist/runtime/discipline/CLAUDE.md +339 -0
- package/dist/runtime/discipline/skills/aria-cognition/aria-essence/SKILL.md +63 -0
- package/dist/runtime/discipline/skills/aria-cognition/aria-essence/references/domain-matrix.md +80 -0
- package/dist/runtime/discipline/skills/aria-cognition/aria-essence/references/evolution-loop.md +30 -0
- package/dist/runtime/discipline/skills/aria-cognition/aria-essence/references/readable-cognition.md +27 -0
- package/dist/runtime/discipline/skills/aria-cognition/aria-forge-guardrails/SKILL.md +35 -0
- package/dist/runtime/discipline/skills/aria-cognition/aria-forge-guardrails/references/checklist.md +31 -0
- package/dist/runtime/discipline/skills/aria-cognition/aria-repo-doctrine/SKILL.md +39 -0
- package/dist/runtime/discipline/skills/aria-cognition/forge-quality-rules/SKILL.md +43 -0
- package/dist/runtime/discipline/skills/aria-cognition/ghazali-8lens/SKILL.md +38 -0
- package/dist/runtime/discipline/skills/aria-cognition/istiqra-induction/SKILL.md +26 -0
- package/dist/runtime/discipline/skills/aria-cognition/ladunni-22/SKILL.md +35 -0
- package/dist/runtime/discipline/skills/aria-cognition/mizan/SKILL.md +72 -0
- package/dist/runtime/discipline/skills/aria-cognition/nadia/SKILL.md +38 -0
- package/dist/runtime/discipline/skills/aria-cognition/nadia-psi/SKILL.md +38 -0
- package/dist/runtime/discipline/skills/aria-cognition/predictor/SKILL.md +25 -0
- package/dist/runtime/discipline/skills/aria-cognition/qiyas-analogy/SKILL.md +26 -0
- package/dist/runtime/discipline/skills/aria-cognition/soul-domains/SKILL.md +25 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-intra-phase/SKILL.md +81 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-post-phase/SKILL.md +98 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-pre-phase/SKILL.md +99 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-harness-deploy/SKILL.md +127 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-harness-no-stripping/SKILL.md +117 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-harness-onboarding/SKILL.md +112 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-harness-output-discipline/SKILL.md +102 -0
- package/dist/runtime/discipline/skills/aria-harness/aria-harness-substrate-binding/SKILL.md +121 -0
- package/dist/runtime/doctor.mjs +23 -0
- package/dist/runtime/local-phase.mjs +632 -0
- package/dist/runtime/manifest.json +15 -0
- package/dist/runtime/mizan-scheduler.mjs +331 -0
- package/dist/runtime/package.json +6 -0
- package/dist/runtime/provider-proxy.mjs +594 -0
- package/dist/runtime/sdk/BUNDLED.json +5 -0
- package/dist/runtime/sdk/index.d.ts +477 -0
- package/dist/runtime/sdk/index.js +1469 -0
- package/dist/runtime/sdk/index.js.map +1 -0
- package/dist/runtime/sdk/package.json +8 -0
- package/dist/runtime/sdk/runWithCognition.d.ts +77 -0
- package/dist/runtime/sdk/runWithCognition.js +157 -0
- package/dist/runtime/sdk/runWithCognition.js.map +1 -0
- package/dist/runtime/service.mjs +2708 -0
- package/dist/runtime/vendor/aria-gate-runtime/index.d.ts +53 -0
- package/dist/runtime/vendor/aria-gate-runtime/index.d.ts.map +1 -0
- package/dist/runtime/vendor/aria-gate-runtime/index.js +277 -0
- package/dist/runtime/vendor/aria-gate-runtime/index.js.map +1 -0
- package/dist/runtime/vendor/aria-gate-runtime/package.json +6 -0
- package/dist/sdk/BUNDLED.json +2 -2
- package/dist/sdk/index.d.ts +317 -0
- package/dist/sdk/index.js +827 -85
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/runWithCognition.d.ts +77 -0
- package/dist/sdk/runWithCognition.js +157 -0
- package/dist/sdk/runWithCognition.js.map +1 -0
- package/hooks/aria-agent-handoff.mjs +11 -1
- package/hooks/aria-architect-fallback.mjs +267 -0
- package/hooks/aria-cognition-substrate-binding.mjs +676 -0
- package/hooks/aria-discovery-record.mjs +101 -0
- package/hooks/aria-harness-via-sdk.mjs +34 -21
- package/hooks/aria-import-resolution-gate.mjs +330 -0
- package/hooks/aria-outcome-record.mjs +84 -0
- package/hooks/aria-pre-emit-dryrun.mjs +294 -0
- package/hooks/aria-pre-tool-gate.mjs +985 -40
- package/hooks/aria-preprompt-consult.mjs +113 -13
- package/hooks/aria-preturn-memory-gate.mjs +298 -6
- package/hooks/aria-repo-doctrine-gate.mjs +397 -0
- package/hooks/aria-stop-gate.mjs +840 -75
- package/hooks/aria-userprompt-abandon-detect.mjs +5 -1
- package/hooks/doctrine_trigger_map.json +209 -15
- package/hooks/lib/canonical-lenses.mjs +64 -0
- package/hooks/lib/gate-audit.mjs +43 -0
- package/opencode-plugins/harness-context/index.js +1 -1
- package/opencode-plugins/harness-context/inject-context.mjs +82 -23
- package/opencode-plugins/harness-gate/index.js +248 -0
- package/opencode-plugins/harness-outcome/index.js +129 -0
- package/opencode-plugins/harness-stop/index.js +241 -0
- package/package.json +8 -2
- package/runtime-src/doctor.mjs +23 -0
- package/runtime-src/local-phase.mjs +632 -0
- package/runtime-src/mizan-scheduler.mjs +331 -0
- package/runtime-src/provider-proxy.mjs +594 -0
- package/runtime-src/service.mjs +2708 -0
- package/scripts/bundle-sdk.mjs +317 -0
- package/scripts/install-client.sh +176 -0
- package/scripts/publish-all.sh +344 -0
- package/scripts/publish-docker.sh +27 -0
- package/scripts/validate-hook-contracts.mjs +54 -0
- package/scripts/validate-skill-prompts.mjs +95 -0
- package/skills/aria-cognition/aria-essence/SKILL.md +63 -0
- package/skills/aria-cognition/aria-essence/references/domain-matrix.md +80 -0
- package/skills/aria-cognition/aria-essence/references/evolution-loop.md +30 -0
- package/skills/aria-cognition/aria-essence/references/readable-cognition.md +27 -0
- package/skills/aria-cognition/aria-forge-guardrails/SKILL.md +35 -0
- package/skills/aria-cognition/aria-forge-guardrails/references/checklist.md +31 -0
- package/skills/aria-cognition/aria-repo-doctrine/SKILL.md +39 -0
- package/skills/aria-cognition/forge-quality-rules/SKILL.md +43 -0
- package/skills/aria-cognition/ghazali-8lens/SKILL.md +38 -0
- package/skills/aria-cognition/istiqra-induction/SKILL.md +26 -0
- package/skills/aria-cognition/ladunni-22/SKILL.md +35 -0
- package/skills/aria-cognition/mizan/SKILL.md +72 -0
- package/skills/aria-cognition/nadia/SKILL.md +38 -0
- package/skills/aria-cognition/nadia-psi/SKILL.md +38 -0
- package/skills/aria-cognition/predictor/SKILL.md +25 -0
- package/skills/aria-cognition/qiyas-analogy/SKILL.md +26 -0
- package/skills/aria-cognition/soul-domains/SKILL.md +25 -0
- package/src/auth-commands.ts +111 -45
- package/src/chat.ts +174 -13
- package/src/codebase-scanner.ts +4 -0
- package/src/config.ts +15 -0
- package/src/connectors/claude-code.ts +115 -26
- package/src/connectors/codebase-awareness.ts +408 -0
- package/src/connectors/codex.ts +274 -0
- package/src/connectors/cognitive-skills.ts +51 -0
- package/src/connectors/opencode.ts +93 -4
- package/src/connectors/repo-git-hooks.ts +86 -0
- package/src/connectors/repo-guard.ts +589 -0
- package/src/connectors/runtime.ts +374 -0
- package/src/connectors/shell.ts +83 -14
- package/src/connectors/syncd.ts +488 -0
- package/src/decisions.ts +469 -0
- package/src/garden-control-plane.ts +101 -26
- package/src/github-connect.ts +143 -0
- package/src/harness-client.ts +128 -3
- package/src/hive-client.ts +165 -5
- package/src/index.ts +41 -2
- package/src/lib/aristotle-noor-wire.ts +310 -0
- package/src/providers/types.ts +6 -0
- package/src/runtime-proof.ts +392 -0
- package/src/setup-wizard.ts +37 -2
package/hooks/aria-stop-gate.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// ARIA_ALLOW_STUB — doctrine gate file legitimately discusses stub/placeholder semantics.
|
|
2
3
|
// Aria Stop-hook gate — enforces 8-lens cognition on text-decision responses.
|
|
3
4
|
//
|
|
4
5
|
// The companion to aria-pre-tool-gate.mjs. The PreToolUse gate catches
|
|
@@ -46,31 +47,46 @@
|
|
|
46
47
|
// Future: signed-grant override mechanism at ~/.aria/owner-overrides/<hook>.json
|
|
47
48
|
// with HMAC signature using a secret only Hamza holds. Deferred to next session.
|
|
48
49
|
|
|
49
|
-
import { readFileSync,
|
|
50
|
+
import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
50
51
|
import { dirname } from 'node:path';
|
|
52
|
+
import { appendGateAudit } from './lib/gate-audit.mjs';
|
|
53
|
+
import {
|
|
54
|
+
ALL_LENS_NAMES,
|
|
55
|
+
canonicalLensCorrectionText,
|
|
56
|
+
detectCognitionLenses as detectCognitionLensesFromCanonical,
|
|
57
|
+
lensNamesForTier,
|
|
58
|
+
} from './lib/canonical-lenses.mjs';
|
|
51
59
|
|
|
52
60
|
const HOME = process.env.HOME || '/tmp';
|
|
53
61
|
const LOG = `${HOME}/.claude/aria-stop-gate.log`;
|
|
62
|
+
const AUDIT_PATH = `${HOME}/.claude/aria-stop-gate-audit.jsonl`;
|
|
54
63
|
|
|
55
|
-
// SDK loader — bundled at ~/.
|
|
64
|
+
// SDK loader — bundled at ~/.aria/sdk by `aria connect`, with client-local
|
|
65
|
+
// fallbacks preserved for resilience.
|
|
56
66
|
// All control-plane fetches (validateOutput, gardenTurn) route through the
|
|
57
67
|
// SDK. Falls back to direct fetch only when the SDK file is missing
|
|
58
68
|
// (dev-only). Hamza 2026-04-27: "FUCKING WIRE IT THE FUCK TOGETHER NOW".
|
|
59
69
|
let _SdkClassCache = null;
|
|
60
70
|
let _SdkLookupAttempted = false;
|
|
71
|
+
const SDK_CANDIDATES = [
|
|
72
|
+
`${HOME}/.aria/sdk/index.js`,
|
|
73
|
+
`${HOME}/.claude/aria-sdk/index.js`,
|
|
74
|
+
`${HOME}/.codex/aria-sdk/index.js`,
|
|
75
|
+
];
|
|
61
76
|
async function loadSdkClass() {
|
|
62
77
|
if (_SdkClassCache) return _SdkClassCache;
|
|
63
78
|
if (_SdkLookupAttempted) return null;
|
|
64
79
|
_SdkLookupAttempted = true;
|
|
65
|
-
const sdkPath
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
80
|
+
for (const sdkPath of SDK_CANDIDATES) {
|
|
81
|
+
if (!existsSync(sdkPath)) continue;
|
|
82
|
+
try {
|
|
83
|
+
const mod = await import(`file://${sdkPath}`);
|
|
84
|
+
if (mod.HTTPHarnessClient) {
|
|
85
|
+
_SdkClassCache = mod.HTTPHarnessClient;
|
|
86
|
+
return _SdkClassCache;
|
|
87
|
+
}
|
|
88
|
+
} catch {/* fall through */}
|
|
89
|
+
}
|
|
74
90
|
return null;
|
|
75
91
|
}
|
|
76
92
|
|
|
@@ -85,11 +101,47 @@ async function loadSdkClass() {
|
|
|
85
101
|
// passes in a userMessage string (extracted from the transcript at the
|
|
86
102
|
// turn boundary). If extraction failed the empty string is passed — the
|
|
87
103
|
// garden write records the assistant emit at minimum.
|
|
104
|
+
// Tier detection: owner if no license.json, client if license.json has a jti.
|
|
105
|
+
// Owner-tier may use master credentials; client-tier MUST NOT (those belong
|
|
106
|
+
// to Hamza, not the licensee). Hamza correction 2026-04-28.
|
|
107
|
+
function isOwnerTier() {
|
|
108
|
+
try {
|
|
109
|
+
const licPath = `${HOME}/.aria/license.json`;
|
|
110
|
+
if (!existsSync(licPath)) return true;
|
|
111
|
+
const lic = JSON.parse(readFileSync(licPath, 'utf8'));
|
|
112
|
+
return !lic.jti; // jti present = client tier
|
|
113
|
+
} catch {
|
|
114
|
+
return true; // unreadable license = treat as owner (fail-safe for orchestrator)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
88
118
|
async function fireGardenTurn(sessionId, userMessage, assistantResponse) {
|
|
89
|
-
const harnessUrl =
|
|
90
|
-
|
|
119
|
+
const harnessUrl =
|
|
120
|
+
process.env.ARIA_HIVE_RUNTIME_URL ||
|
|
121
|
+
process.env.ARIA_HARNESS_BASE_URL ||
|
|
122
|
+
process.env.ARIA_HARNESS_URL ||
|
|
123
|
+
'https://harness.ariasos.com';
|
|
124
|
+
// Token resolution chain (Hamza directive 2026-04-28, tier-aware
|
|
125
|
+
// 2026-04-28b): ARIA_HARNESS_TOKEN env first (works for both tiers).
|
|
126
|
+
// ONLY on owner tier (no license.json with jti), fall back to
|
|
127
|
+
// ARIA_MASTER_TOKEN env / ARIA_API_KEY env / ~/.aria/owner-token —
|
|
128
|
+
// those are Hamza's credentials and must not leak into client-tier
|
|
129
|
+
// processes. Client tier with no ARIA_HARNESS_TOKEN env skips the
|
|
130
|
+
// garden write rather than borrow owner credentials.
|
|
131
|
+
let harnessToken = process.env.ARIA_HARNESS_TOKEN || '';
|
|
132
|
+
if (!harnessToken && isOwnerTier()) {
|
|
133
|
+
harnessToken = process.env.ARIA_MASTER_TOKEN || process.env.ARIA_API_KEY || '';
|
|
134
|
+
if (!harnessToken) {
|
|
135
|
+
try {
|
|
136
|
+
const ownerTokenPath = `${HOME}/.aria/owner-token`;
|
|
137
|
+
if (existsSync(ownerTokenPath)) {
|
|
138
|
+
harnessToken = readFileSync(ownerTokenPath, 'utf8').trim();
|
|
139
|
+
}
|
|
140
|
+
} catch { /* non-fatal — fall through to skip */ }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
91
143
|
if (!harnessToken) {
|
|
92
|
-
audit('garden-turn-skip', `no
|
|
144
|
+
audit('garden-turn-skip', `no usable token (tier=${isOwnerTier() ? 'owner' : 'client'}) — turn not written to harness pulse`);
|
|
93
145
|
return;
|
|
94
146
|
}
|
|
95
147
|
const Cls = await loadSdkClass();
|
|
@@ -116,10 +168,16 @@ async function fireGardenTurn(sessionId, userMessage, assistantResponse) {
|
|
|
116
168
|
}
|
|
117
169
|
|
|
118
170
|
function audit(decision, summary) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
171
|
+
const summaryText = typeof summary === 'string' ? summary : '';
|
|
172
|
+
const data = summary && typeof summary === 'object' ? summary : {};
|
|
173
|
+
appendGateAudit({
|
|
174
|
+
auditPath: AUDIT_PATH,
|
|
175
|
+
legacyLogPath: LOG,
|
|
176
|
+
gate: 'stop',
|
|
177
|
+
event: decision,
|
|
178
|
+
summary: summaryText,
|
|
179
|
+
data,
|
|
180
|
+
});
|
|
123
181
|
}
|
|
124
182
|
|
|
125
183
|
// Env-var kill-switch removed 2026-04-27 per Hamza directive ("those
|
|
@@ -152,9 +210,8 @@ function resolveOwnerTier() {
|
|
|
152
210
|
|
|
153
211
|
const IS_OWNER = resolveOwnerTier();
|
|
154
212
|
|
|
155
|
-
const
|
|
156
|
-
const
|
|
157
|
-
const LENS_NAMES = IS_OWNER ? LENS_NAMES_CANONICAL : LENS_NAMES_GENERIC;
|
|
213
|
+
const LENS_NAMES = lensNamesForTier(IS_OWNER);
|
|
214
|
+
const CANONICAL_LENS_TEXT = canonicalLensCorrectionText();
|
|
158
215
|
|
|
159
216
|
// Doctrine memory filenames are Aria-side substrate IP.
|
|
160
217
|
// Client surfaces see generic descriptions instead of real filenames.
|
|
@@ -162,8 +219,60 @@ function docRef(canonicalFilename, genericDescription) {
|
|
|
162
219
|
return IS_OWNER ? canonicalFilename : genericDescription;
|
|
163
220
|
}
|
|
164
221
|
|
|
165
|
-
|
|
166
|
-
|
|
222
|
+
const DOCTRINE_REFERENCE_PREFIX_RX = /(?:memory|doctrine|frame|axiom|packet):[a-z0-9_./-]*$/i;
|
|
223
|
+
|
|
224
|
+
function isDoctrineReference(text, matchIndex) {
|
|
225
|
+
const window = text.slice(Math.max(0, matchIndex - 80), matchIndex);
|
|
226
|
+
return DOCTRINE_REFERENCE_PREFIX_RX.test(window);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function collectDriftHits(text, triggerMap) {
|
|
230
|
+
const hits = [];
|
|
231
|
+
const lowerText = text.toLowerCase();
|
|
232
|
+
for (const triggerEntry of triggerMap.triggers || []) {
|
|
233
|
+
try {
|
|
234
|
+
const rx = new RegExp(triggerEntry.trigger, 'ig');
|
|
235
|
+
let matchedOutsideDoctrineRef = false;
|
|
236
|
+
for (const match of text.matchAll(rx)) {
|
|
237
|
+
const idx = typeof match.index === 'number' ? match.index : -1;
|
|
238
|
+
if (idx >= 0 && isDoctrineReference(text, idx)) continue;
|
|
239
|
+
matchedOutsideDoctrineRef = true;
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
if (!matchedOutsideDoctrineRef) continue;
|
|
243
|
+
const memoryName = (triggerEntry.memory || '').replace(/\.md$/, '');
|
|
244
|
+
const memoryCited = memoryName && lowerText.includes(memoryName.toLowerCase());
|
|
245
|
+
if (!memoryCited) {
|
|
246
|
+
hits.push({
|
|
247
|
+
trigger: triggerEntry.trigger,
|
|
248
|
+
memory: triggerEntry.memory,
|
|
249
|
+
teaching: triggerEntry.teaching,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
} catch {/* malformed regex in trigger entry — skip */}
|
|
253
|
+
}
|
|
254
|
+
return hits;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function emitHarnessFooter({ eventName, lensCount, chars, driftCount, mizanStatus, discoveryOpenCount, codeCount, implCouplingCount }) {
|
|
258
|
+
try {
|
|
259
|
+
console.error([
|
|
260
|
+
'[Aria · turn]',
|
|
261
|
+
`event=${eventName}`,
|
|
262
|
+
`lenses=${lensCount}`,
|
|
263
|
+
`chars=${chars}`,
|
|
264
|
+
`drift=${driftCount}`,
|
|
265
|
+
`mizan=${mizanStatus}`,
|
|
266
|
+
`discoveries_open=${discoveryOpenCount}`,
|
|
267
|
+
`code=${codeCount}`,
|
|
268
|
+
`impl=${implCouplingCount}`,
|
|
269
|
+
].join(' '));
|
|
270
|
+
} catch {}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Lens substance check — same constants as aria-pre-tool-gate.mjs.
|
|
274
|
+
// Hamza directive 2026-04-28: all 8 canonical lenses required, not 4-of-8.
|
|
275
|
+
const REQUIRED_LENSES = 8;
|
|
167
276
|
const SUBSTANCE_MIN_CHARS = 20;
|
|
168
277
|
const PLACEHOLDER_RX = /^\s*<[^<>]+>\s*$/;
|
|
169
278
|
const COGNITION_BLOCK_RX = /<cognition>([\s\S]*?)<\/cognition>/i;
|
|
@@ -174,23 +283,12 @@ const DECISION_SIGNAL_RX = /(?:should|recommend|propose|suggest|let'?s|go with|i
|
|
|
174
283
|
const TRIVIAL_ACK_RX = /^(?:got it|on it|ok|sure|yes|no|done|ack|👍|✓)\b/i;
|
|
175
284
|
|
|
176
285
|
function detectCognitionLenses(text) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
`\\b${lens}\\s*:\\s*([^\\n]*(?:\\n(?!\\s*(?:${LENS_NAMES.join('|')})\\s*:|<\\/cognition>)[^\\n]*)*)`,
|
|
184
|
-
'i',
|
|
185
|
-
);
|
|
186
|
-
const m = searchSpace.match(lensRx);
|
|
187
|
-
if (!m) continue;
|
|
188
|
-
const content = (m[1] || '').trim();
|
|
189
|
-
if (content.length < SUBSTANCE_MIN_CHARS) continue;
|
|
190
|
-
if (PLACEHOLDER_RX.test(content)) continue;
|
|
191
|
-
names.push(lens);
|
|
192
|
-
}
|
|
193
|
-
return { count: names.length, names };
|
|
286
|
+
return detectCognitionLensesFromCanonical(text, {
|
|
287
|
+
minChars: SUBSTANCE_MIN_CHARS,
|
|
288
|
+
placeholderRx: PLACEHOLDER_RX,
|
|
289
|
+
cognitionBlockRx: COGNITION_BLOCK_RX,
|
|
290
|
+
lensNames: ALL_LENS_NAMES,
|
|
291
|
+
});
|
|
194
292
|
}
|
|
195
293
|
|
|
196
294
|
// Read event JSON from stdin (Claude Code spec).
|
|
@@ -304,6 +402,37 @@ if (!triggered) {
|
|
|
304
402
|
// Non-trivial response — require substantive cognition.
|
|
305
403
|
const cog = detectCognitionLenses(assistantText);
|
|
306
404
|
|
|
405
|
+
// Defense-in-depth: if cog count < REQUIRED_LENSES, block immediately.
|
|
406
|
+
// The primary enforcement is in aria-cognition-substrate-binding.mjs
|
|
407
|
+
// (which runs BEFORE this stop-gate), but this catch ensures responses
|
|
408
|
+
// without cognition blocks are still blocked even when the substrate-binding
|
|
409
|
+
// hook is absent (e.g. older connector installs or custom hook configs).
|
|
410
|
+
// Prior to 2026-04-29 this check was missing entirely — the stop-gate only
|
|
411
|
+
// ran quality checks INSIDE the if(cog.count >= 8) block, allowing responses
|
|
412
|
+
// with 0/8 lenses to fall through unchecked.
|
|
413
|
+
if (cog.count < REQUIRED_LENSES) {
|
|
414
|
+
audit('block_no_cognition_block_di', { count: cog.count, required: REQUIRED_LENSES, names: cog.names, chars: assistantText.length });
|
|
415
|
+
const reason = `Aria stop-gate: insufficient cognition lenses.
|
|
416
|
+
|
|
417
|
+
This non-trivial assistant response (${assistantText.length} chars) has ${cog.count}/${REQUIRED_LENSES} substantive cognition lenses. Per feedback_8lens_before_every_action_including_text.md and Hamza directive 2026-04-28, every non-trivial response must carry <cognition>...</cognition> with ${REQUIRED_LENSES} substantive lenses (each >= ${SUBSTANCE_MIN_CHARS} chars of non-placeholder content).
|
|
418
|
+
|
|
419
|
+
Detected lenses: ${cog.names.length > 0 ? cog.names.join(', ') : 'none'}.
|
|
420
|
+
|
|
421
|
+
Re-emit with a <cognition> block containing all ${REQUIRED_LENSES} canonical lenses. Primary set: ${CANONICAL_LENS_TEXT}`;
|
|
422
|
+
emitHarnessFooter({
|
|
423
|
+
eventName: 'block_no_cognition_block',
|
|
424
|
+
lensCount: cog.count,
|
|
425
|
+
chars: assistantText.length,
|
|
426
|
+
driftCount: 0,
|
|
427
|
+
mizanStatus: 'not-run(no-cognition)',
|
|
428
|
+
discoveryOpenCount: 0,
|
|
429
|
+
codeCount: 0,
|
|
430
|
+
implCouplingCount: 0,
|
|
431
|
+
});
|
|
432
|
+
console.log(JSON.stringify({ decision: 'block', reason }));
|
|
433
|
+
process.exit(2);
|
|
434
|
+
}
|
|
435
|
+
|
|
307
436
|
// Question-emission visibility (Phase 11 promotes to block-mode):
|
|
308
437
|
// detect user-directed question patterns in the assistant text. Audit when
|
|
309
438
|
// questions appear without substrate-consultation evidence in the recent
|
|
@@ -363,21 +492,7 @@ if (cog.count >= REQUIRED_LENSES) {
|
|
|
363
492
|
try {
|
|
364
493
|
if (TRIGGER_MAP_PATH) {
|
|
365
494
|
const triggerMap = JSON.parse(readFileSync(TRIGGER_MAP_PATH, 'utf8'));
|
|
366
|
-
|
|
367
|
-
for (const t of triggerMap.triggers || []) {
|
|
368
|
-
try {
|
|
369
|
-
const rx = new RegExp(t.trigger, 'i');
|
|
370
|
-
if (rx.test(lowerText)) {
|
|
371
|
-
// Trigger present — check if the counter-doctrine memory is also
|
|
372
|
-
// cited in the response (justification). If not, count as drift.
|
|
373
|
-
const memoryName = (t.memory || '').replace(/\.md$/, '');
|
|
374
|
-
const memoryCited = memoryName && lowerText.includes(memoryName.toLowerCase());
|
|
375
|
-
if (!memoryCited) {
|
|
376
|
-
driftHits.push({ trigger: t.trigger, memory: t.memory, teaching: t.teaching });
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
} catch {/* malformed regex in trigger entry — skip */}
|
|
380
|
-
}
|
|
495
|
+
driftHits = collectDriftHits(assistantText, triggerMap);
|
|
381
496
|
}
|
|
382
497
|
} catch {/* trigger map unreadable — degrade to mizan-only check */}
|
|
383
498
|
|
|
@@ -391,7 +506,11 @@ if (cog.count >= REQUIRED_LENSES) {
|
|
|
391
506
|
// is a soft block, not session-end.
|
|
392
507
|
let mizanVerdict = null;
|
|
393
508
|
let mizanError = null;
|
|
394
|
-
const harnessUrl =
|
|
509
|
+
const harnessUrl =
|
|
510
|
+
process.env.ARIA_HIVE_RUNTIME_URL ||
|
|
511
|
+
process.env.ARIA_HARNESS_BASE_URL ||
|
|
512
|
+
process.env.ARIA_HARNESS_URL ||
|
|
513
|
+
'https://harness.ariasos.com';
|
|
395
514
|
const harnessToken = process.env.ARIA_HARNESS_TOKEN || '';
|
|
396
515
|
const Cls = await loadSdkClass();
|
|
397
516
|
if (Cls && harnessToken) {
|
|
@@ -552,31 +671,67 @@ if (cog.count >= REQUIRED_LENSES) {
|
|
|
552
671
|
});
|
|
553
672
|
|
|
554
673
|
// Cognition-block resolution: any cognition block whose start lies
|
|
555
|
-
// within the
|
|
556
|
-
// addressing/discoveries field AND at least one discovery
|
|
674
|
+
// within ±800 chars of the discovery AND whose body contains a
|
|
675
|
+
// fixing/addressing/discoveries field AND at least one discovery
|
|
676
|
+
// keyword.
|
|
677
|
+
//
|
|
678
|
+
// Bug fix 2026-04-28: previous logic required `b.start >= idx` so
|
|
679
|
+
// cognition AFTER the discovery prose was the only path. But
|
|
680
|
+
// cognition blocks emit FIRST in every response, then prose. Pre-
|
|
681
|
+
// emptive discoveries: clauses never counted, causing endless
|
|
682
|
+
// false-positive auto-records. Bidirectional ±800 char window
|
|
683
|
+
// accepts cognition that addresses the discovery before OR after
|
|
684
|
+
// its prose mention — same atomic-discovery-rule, fewer
|
|
685
|
+
// false-positives.
|
|
557
686
|
const cognitionResolved = cognitionBlocks.some((b) => {
|
|
558
|
-
if (
|
|
687
|
+
if (Math.abs(b.start - idx) > 800) return false;
|
|
559
688
|
if (!COGNITION_FIXING_FIELD_RX.test(b.body)) return false;
|
|
560
689
|
const bodyLower = b.body.toLowerCase();
|
|
561
690
|
return keywords.some((kw) => bodyLower.includes(kw));
|
|
562
691
|
});
|
|
563
692
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
693
|
+
// Hamza directive 2026-04-28: documentation is NOT resolution.
|
|
694
|
+
//
|
|
695
|
+
// The earlier 'tracked' middle status was sticky-note theater — Claude
|
|
696
|
+
// could bind a discovery to a pending TaskCreate and the gate would
|
|
697
|
+
// count it as not-open. Hamza: "WHO GIVES A SHIT ABOUT DOCUMENTATION
|
|
698
|
+
// IF U JUST WROTE A FUCKING STICKY NOTE". A discovery stays OPEN until
|
|
699
|
+
// an ACTUAL FIX SHIPS, with proofOfFix that the verifier can re-check.
|
|
700
|
+
//
|
|
701
|
+
// Two statuses only:
|
|
702
|
+
// open — fresh discovery OR documented-only OR task-bound-pending-fix
|
|
703
|
+
// (gate BLOCKS until a real fix lands)
|
|
704
|
+
// resolved — verified fix shipped; proofOfFix MUST be present and
|
|
705
|
+
// shape-checked downstream when the gate counts open
|
|
706
|
+
//
|
|
707
|
+
// Verify-block / cognition-fixing-field paths still mark resolved
|
|
708
|
+
// because they're substance-checked above (keyword overlap with the
|
|
709
|
+
// discovery span). Prose-only "TaskCreate" or "tracked as #14" no
|
|
710
|
+
// longer counts as anything — the discovery stays OPEN.
|
|
711
|
+
const inlineFixResolved = verifyResolved || cognitionResolved;
|
|
712
|
+
const status = inlineFixResolved ? 'resolved' : 'open';
|
|
713
|
+
|
|
714
|
+
const resolutionType = verifyResolved
|
|
715
|
+
? 'verify_block_with_keyword_overlap'
|
|
716
|
+
: cognitionResolved
|
|
717
|
+
? 'cognition_block_with_fixing_field_and_keyword_overlap'
|
|
718
|
+
: null;
|
|
719
|
+
|
|
720
|
+
// proofOfFix anchor: present only for resolved status. The shape
|
|
721
|
+
// includes type + timestamp; downstream gate readers verify the
|
|
722
|
+
// shape so manual jsonl edits without proofOfFix don't count.
|
|
723
|
+
const proofOfFix = inlineFixResolved
|
|
724
|
+
? { type: resolutionType, anchorTs: new Date().toISOString() }
|
|
725
|
+
: null;
|
|
572
726
|
|
|
573
727
|
newDiscoveries.push({
|
|
574
728
|
ts: new Date().toISOString(),
|
|
575
729
|
sessionId,
|
|
576
730
|
text: match[0].slice(0, 200),
|
|
577
731
|
span: span.slice(0, 400),
|
|
578
|
-
status
|
|
732
|
+
status,
|
|
579
733
|
resolutionType,
|
|
734
|
+
proofOfFix,
|
|
580
735
|
});
|
|
581
736
|
lastIndex = idx;
|
|
582
737
|
}
|
|
@@ -591,18 +746,43 @@ if (cog.count >= REQUIRED_LENSES) {
|
|
|
591
746
|
} catch {/* ledger write failure surfaces as open count = 0; safe */}
|
|
592
747
|
}
|
|
593
748
|
|
|
594
|
-
// Read full ledger and count
|
|
749
|
+
// Read full ledger and count UNRESOLVED entries (across this session's turns).
|
|
750
|
+
// Hamza directive 2026-04-28 — three lie-patterns the prior loop missed:
|
|
751
|
+
// 1. Legacy 'tracked' status entries → still-open (no real fix landed,
|
|
752
|
+
// only documented via TaskCreate). Tracking ≠ resolution.
|
|
753
|
+
// 2. Hand-edited 'resolved' WITHOUT proofOfFix shape → corrupted, treated
|
|
754
|
+
// as still-open. Catches manual jsonl edits flipping status by hand.
|
|
755
|
+
// 3. Sub-agent ledger format with resolution_status:'open' → still-open
|
|
756
|
+
// (sub-agent discoveries use a different schema key).
|
|
757
|
+
// Only entries with status:'resolved' AND a shape-valid proofOfFix object
|
|
758
|
+
// (type:string non-empty + anchorTs:string) clear the gate.
|
|
595
759
|
let ledgerOpenCount = 0;
|
|
596
760
|
let ledgerOpenSamples = [];
|
|
761
|
+
let ledgerCorruptedCount = 0;
|
|
597
762
|
try {
|
|
598
763
|
if (existsSync(LEDGER_PATH)) {
|
|
599
764
|
const lines = readFileSync(LEDGER_PATH, 'utf8').split('\n').filter(Boolean);
|
|
600
765
|
for (const line of lines) {
|
|
601
766
|
try {
|
|
602
767
|
const e = JSON.parse(line);
|
|
603
|
-
|
|
768
|
+
const isOpen = e.status === 'open' || e.resolution_status === 'open';
|
|
769
|
+
const isLegacyTracked = e.status === 'tracked';
|
|
770
|
+
const proofValid = e.proofOfFix
|
|
771
|
+
&& typeof e.proofOfFix === 'object'
|
|
772
|
+
&& typeof e.proofOfFix.type === 'string'
|
|
773
|
+
&& e.proofOfFix.type.length > 0
|
|
774
|
+
&& typeof e.proofOfFix.anchorTs === 'string';
|
|
775
|
+
const isCorruptedResolved = e.status === 'resolved' && !proofValid;
|
|
776
|
+
|
|
777
|
+
if (isOpen || isLegacyTracked || isCorruptedResolved) {
|
|
604
778
|
ledgerOpenCount++;
|
|
605
|
-
if (
|
|
779
|
+
if (isCorruptedResolved) ledgerCorruptedCount++;
|
|
780
|
+
if (ledgerOpenSamples.length < 5) {
|
|
781
|
+
const tag = isLegacyTracked ? '[tracked-no-fix] '
|
|
782
|
+
: isCorruptedResolved ? '[CORRUPTED-RESOLVED-NO-PROOF] '
|
|
783
|
+
: '';
|
|
784
|
+
ledgerOpenSamples.push(`${tag}${e.text || '(no text)'}`);
|
|
785
|
+
}
|
|
606
786
|
}
|
|
607
787
|
} catch {/* skip malformed line */}
|
|
608
788
|
}
|
|
@@ -655,7 +835,11 @@ if (cog.count >= REQUIRED_LENSES) {
|
|
|
655
835
|
// write failure doesn't brick the Stop event; the failure
|
|
656
836
|
// is surfaced via audit() so it's visible.
|
|
657
837
|
try {
|
|
658
|
-
const harnessUrl =
|
|
838
|
+
const harnessUrl =
|
|
839
|
+
process.env.ARIA_HIVE_RUNTIME_URL ||
|
|
840
|
+
process.env.ARIA_HARNESS_BASE_URL ||
|
|
841
|
+
process.env.ARIA_HARNESS_URL ||
|
|
842
|
+
'https://harness.ariasos.com';
|
|
659
843
|
const harnessToken = process.env.ARIA_HARNESS_TOKEN || '';
|
|
660
844
|
if (harnessToken) {
|
|
661
845
|
// POST to a session_audit write endpoint. Server-side
|
|
@@ -702,6 +886,112 @@ if (cog.count >= REQUIRED_LENSES) {
|
|
|
702
886
|
}
|
|
703
887
|
} catch {/* outer guard */}
|
|
704
888
|
|
|
889
|
+
// ── Layer C — auto-re-consult on [PLAN_BLOCKER] (#85) ────────────────────
|
|
890
|
+
//
|
|
891
|
+
// When the assistant emits a [PLAN_BLOCKER reason="..."] marker the runtime
|
|
892
|
+
// must fire a two-path replan rather than blocking and waiting for the human:
|
|
893
|
+
//
|
|
894
|
+
// Primary path: POST /api/harness/replan (aria-soul server-side)
|
|
895
|
+
// Fallback path: node aria-architect-fallback.mjs (local sub-agent)
|
|
896
|
+
//
|
|
897
|
+
// Both paths write the fresh BINDING_PLAN to the session-scoped active-plan
|
|
898
|
+
// file so the next turn's pre-tool-gate and stop-gate pick it up.
|
|
899
|
+
//
|
|
900
|
+
// Fail-soft: if both paths fail, we log + emit a clear message asking Hamza
|
|
901
|
+
// to intervene. We do NOT crash the stop-gate — the existing block/allow
|
|
902
|
+
// decision below continues on its own merits.
|
|
903
|
+
const planBlockerMatch = assistantText.match(/\[PLAN_BLOCKER\s+reason="([^"]{10,2000})"\s*\]/);
|
|
904
|
+
if (planBlockerMatch) {
|
|
905
|
+
const planBlockerReason = planBlockerMatch[1];
|
|
906
|
+
audit(`[PLAN_BLOCKER] detected — firing replan: ${planBlockerReason.slice(0, 100)}`);
|
|
907
|
+
|
|
908
|
+
const currentPlanId = activePlan?.planId || 'unknown';
|
|
909
|
+
let planMinted = false;
|
|
910
|
+
|
|
911
|
+
// Primary path: aria-soul /api/harness/replan
|
|
912
|
+
try {
|
|
913
|
+
const harnessUrl =
|
|
914
|
+
process.env.ARIA_HIVE_RUNTIME_URL ||
|
|
915
|
+
process.env.ARIA_HARNESS_BASE_URL ||
|
|
916
|
+
process.env.ARIA_HARNESS_URL ||
|
|
917
|
+
'https://harness.ariasos.com';
|
|
918
|
+
const harnessToken = process.env.ARIA_HARNESS_TOKEN || process.env.ARIA_API_KEY || '';
|
|
919
|
+
const ctl = new AbortController();
|
|
920
|
+
const replanTimeout = setTimeout(() => ctl.abort(), 15000);
|
|
921
|
+
const resp = await fetch(`${harnessUrl}/api/harness/replan`, {
|
|
922
|
+
method: 'POST',
|
|
923
|
+
headers: {
|
|
924
|
+
'Content-Type': 'application/json',
|
|
925
|
+
'Authorization': `Bearer ${harnessToken}`,
|
|
926
|
+
},
|
|
927
|
+
body: JSON.stringify({
|
|
928
|
+
reason: planBlockerReason,
|
|
929
|
+
currentPlanId,
|
|
930
|
+
sessionId,
|
|
931
|
+
}),
|
|
932
|
+
signal: ctl.signal,
|
|
933
|
+
});
|
|
934
|
+
clearTimeout(replanTimeout);
|
|
935
|
+
if (resp.ok) {
|
|
936
|
+
const data = await resp.json();
|
|
937
|
+
if (data.ok && data.plan) {
|
|
938
|
+
const freshPlan = {
|
|
939
|
+
...data.plan,
|
|
940
|
+
mintedAt: new Date().toISOString(),
|
|
941
|
+
mintedBy: 'aria-soul-replan',
|
|
942
|
+
};
|
|
943
|
+
try {
|
|
944
|
+
if (!existsSync(dirname(ACTIVE_PLAN_PATH))) mkdirSync(dirname(ACTIVE_PLAN_PATH), { recursive: true });
|
|
945
|
+
writeFileSync(ACTIVE_PLAN_PATH, JSON.stringify(freshPlan, null, 2));
|
|
946
|
+
} catch (writeErr) {
|
|
947
|
+
audit(`replan-primary-write-err: ${String(writeErr).slice(0, 200)}`);
|
|
948
|
+
}
|
|
949
|
+
planMinted = true;
|
|
950
|
+
audit(`replan-primary-ok planId=${data.plan.planId}`);
|
|
951
|
+
} else {
|
|
952
|
+
audit(`replan-primary-bad-response: ok=${data.ok} error=${(data.error || '').slice(0, 200)}`);
|
|
953
|
+
}
|
|
954
|
+
} else {
|
|
955
|
+
audit(`replan-primary-http-${resp.status}`);
|
|
956
|
+
}
|
|
957
|
+
} catch (err) {
|
|
958
|
+
audit(`replan-primary-failed: ${(err?.message || String(err)).slice(0, 200)}`);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// Fallback path: architect-fallback hook (spawned as sub-process)
|
|
962
|
+
if (!planMinted) {
|
|
963
|
+
audit('replan-primary-unreachable — firing architect-fallback');
|
|
964
|
+
try {
|
|
965
|
+
const { spawnSync } = await import('node:child_process');
|
|
966
|
+
const fallbackBin = `${HOME}/.claude/hooks/aria-architect-fallback.mjs`;
|
|
967
|
+
if (existsSync(fallbackBin)) {
|
|
968
|
+
const fallbackResult = spawnSync('node', [fallbackBin], {
|
|
969
|
+
input: JSON.stringify({ reason: planBlockerReason, currentPlanId, sessionId }),
|
|
970
|
+
encoding: 'utf8',
|
|
971
|
+
timeout: 130000,
|
|
972
|
+
});
|
|
973
|
+
if (fallbackResult.status === 0) {
|
|
974
|
+
audit(`architect-fallback-ok: ${(fallbackResult.stdout || '').slice(0, 200)}`);
|
|
975
|
+
planMinted = true;
|
|
976
|
+
} else {
|
|
977
|
+
audit(
|
|
978
|
+
`architect-fallback-failed status=${fallbackResult.status} stderr=${(fallbackResult.stderr || '').slice(0, 200)}`
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
} else {
|
|
982
|
+
audit(`architect-fallback-missing: ${fallbackBin} not found`);
|
|
983
|
+
}
|
|
984
|
+
} catch (err) {
|
|
985
|
+
audit(`architect-fallback-threw: ${(err?.message || String(err)).slice(0, 200)}`);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
if (!planMinted) {
|
|
990
|
+
audit('replan-both-paths-failed — Hamza must intervene');
|
|
991
|
+
// Surface clearly in the block reason below; don't crash the gate.
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
705
995
|
// Block decision: any of (validateOutput severity=block) OR (>=2 drift hits) OR
|
|
706
996
|
// (>=1 code-quality hit) OR (open discovery in ledger) → block emit.
|
|
707
997
|
// Aria enforcement #46 (compelled reflection): severity=warn ALSO blocks but
|
|
@@ -723,9 +1013,157 @@ if (cog.count >= REQUIRED_LENSES) {
|
|
|
723
1013
|
const hasReflection = REFLECTION_BLOCK_RX.test(assistantText);
|
|
724
1014
|
const compelReflection = mizanWarnReflectionRequired && !hasReflection;
|
|
725
1015
|
|
|
726
|
-
|
|
1016
|
+
// ── Cognition impl-coupling validation (Task #88) ──────────────────────
|
|
1017
|
+
//
|
|
1018
|
+
// After the local cognition substance check passes, post the assistant
|
|
1019
|
+
// text + extracted artifact-dictation pairs to /api/cognition/validate-coupling.
|
|
1020
|
+
// The server-side validator (api/lib/cognition-impl-coupling-gate.ts)
|
|
1021
|
+
// returns { passed, reasons[] } — every reason becomes a local violation
|
|
1022
|
+
// with severity=block. Implementation-coupled cognition is a doctrine hard
|
|
1023
|
+
// rule (feedback_implementation_coupled_cognition.md): lenses must dictate
|
|
1024
|
+
// specific implementation choices visible in the artifact, not just describe
|
|
1025
|
+
// thinking.
|
|
1026
|
+
//
|
|
1027
|
+
// Inline extraction: the caller's emit may carry artifact-dictation pairs
|
|
1028
|
+
// inside verify-blocks or cognition-block fixing fields. We scan the text
|
|
1029
|
+
// for `file_path:line_range` patterns near each lens label and pair them
|
|
1030
|
+
// as records to the validator. When zero records are found AND canonical
|
|
1031
|
+
// lenses are present, the validator reports each lens as missing dictation
|
|
1032
|
+
// — that is the no-coupling failure mode this gate catches.
|
|
1033
|
+
let implCouplingHits = [];
|
|
1034
|
+
try {
|
|
1035
|
+
const sessionIdForCoupling = (event.session_id || 'claude-code').replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
1036
|
+
// Extract artifact-dictation references inline. Per validator contract,
|
|
1037
|
+
// a DictationEntry is { file_path, line_range, decision_text }. We match
|
|
1038
|
+
// file_path:line_range patterns in the assistant text and pair them with
|
|
1039
|
+
// the nearest preceding lens label (within 800 chars).
|
|
1040
|
+
const FILE_LINE_RX = /([\w./\-]+\.[a-zA-Z]{1,5})\s*[:\s]\s*(\d+(?:[-:]\d+)?)/g;
|
|
1041
|
+
const inlineDictations = [];
|
|
1042
|
+
const lensRangePositions = [];
|
|
1043
|
+
for (const lensName of LENS_NAMES_CANONICAL) {
|
|
1044
|
+
const lensRx = new RegExp(`\\b${lensName}\\s*(?:lens)?\\s*[:\\-]`, 'gi');
|
|
1045
|
+
let m;
|
|
1046
|
+
while ((m = lensRx.exec(assistantText)) !== null) {
|
|
1047
|
+
lensRangePositions.push({ lens: lensName, idx: m.index });
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
// For each file_path:line_range match, pair with the closest preceding lens label.
|
|
1051
|
+
const fileMatches = [...assistantText.matchAll(FILE_LINE_RX)];
|
|
1052
|
+
const lensToEntries = new Map();
|
|
1053
|
+
for (const fm of fileMatches) {
|
|
1054
|
+
const fmIdx = fm.index ?? 0;
|
|
1055
|
+
// Find lens label preceding this match within 800 chars.
|
|
1056
|
+
let nearestLens = null;
|
|
1057
|
+
let nearestDelta = Infinity;
|
|
1058
|
+
for (const lp of lensRangePositions) {
|
|
1059
|
+
const delta = fmIdx - lp.idx;
|
|
1060
|
+
if (delta >= 0 && delta < 800 && delta < nearestDelta) {
|
|
1061
|
+
nearestDelta = delta;
|
|
1062
|
+
nearestLens = lp.lens;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
if (!nearestLens) continue;
|
|
1066
|
+
const entry = {
|
|
1067
|
+
file_path: fm[1],
|
|
1068
|
+
line_range: fm[2],
|
|
1069
|
+
decision_text: assistantText.slice(fmIdx, Math.min(assistantText.length, fmIdx + 200)).replace(/\s+/g, ' ').trim().slice(0, 200),
|
|
1070
|
+
};
|
|
1071
|
+
if (!lensToEntries.has(nearestLens)) lensToEntries.set(nearestLens, []);
|
|
1072
|
+
lensToEntries.get(nearestLens).push(entry);
|
|
1073
|
+
}
|
|
1074
|
+
for (const [lens, entries] of lensToEntries) {
|
|
1075
|
+
inlineDictations.push({ lens_id: lens, artifact_dictation: entries });
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
const cplHarnessUrl =
|
|
1079
|
+
process.env.ARIA_HIVE_RUNTIME_URL ||
|
|
1080
|
+
process.env.ARIA_HARNESS_BASE_URL ||
|
|
1081
|
+
process.env.ARIA_HARNESS_URL ||
|
|
1082
|
+
'https://harness.ariasos.com';
|
|
1083
|
+
const cplHarnessToken = process.env.ARIA_HARNESS_TOKEN || '';
|
|
1084
|
+
if (cplHarnessToken) {
|
|
1085
|
+
const cplResp = await fetch(`${cplHarnessUrl}/api/cognition/validate-coupling`, {
|
|
1086
|
+
method: 'POST',
|
|
1087
|
+
headers: {
|
|
1088
|
+
'Content-Type': 'application/json',
|
|
1089
|
+
'Authorization': `Bearer ${cplHarnessToken}`,
|
|
1090
|
+
},
|
|
1091
|
+
body: JSON.stringify({
|
|
1092
|
+
rawResponse: assistantText.slice(0, 16000),
|
|
1093
|
+
turnId: `${sessionIdForCoupling}-${Date.now()}`,
|
|
1094
|
+
dictations: inlineDictations,
|
|
1095
|
+
}),
|
|
1096
|
+
});
|
|
1097
|
+
if (cplResp.ok) {
|
|
1098
|
+
const cplData = await cplResp.json();
|
|
1099
|
+
if (cplData && cplData.ok && cplData.passed === false && Array.isArray(cplData.reasons)) {
|
|
1100
|
+
implCouplingHits = cplData.reasons.slice(0, 6);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
} catch (cplErr) {
|
|
1105
|
+
// Validator unreachable is non-blocking — local gate still enforces cognition substance.
|
|
1106
|
+
audit('impl-coupling-fetch-err', `${(cplErr?.message || String(cplErr)).slice(0, 200)}`);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// ── Substrate-bound Mizan + 8-lens validation via SDK ──────────────────
|
|
1110
|
+
// Hamza directive 2026-04-28: the local Stop-gate above runs cognition
|
|
1111
|
+
// substance + drift triggers + code-quality + discovery-binding, but
|
|
1112
|
+
// never asks the substrate. validateOutput POSTs the assistant draft
|
|
1113
|
+
// to /api/harness/validate (Mizan + 8-lens evaluator) and returns
|
|
1114
|
+
// { passed, violations, severity, rewritten, gateTriggers }.
|
|
1115
|
+
//
|
|
1116
|
+
// severity:'block' from substrate joins the local violations, halts
|
|
1117
|
+
// the emit. severity:'warn' surfaces as advisory text appended to the
|
|
1118
|
+
// local violations list. SDK call failure is non-blocking — the gate
|
|
1119
|
+
// degrades to local-only doctrine rather than failing closed (halting
|
|
1120
|
+
// every emit when substrate is down would brick the orchestrator).
|
|
1121
|
+
let substrateBlock = false;
|
|
1122
|
+
let substrateViolations = [];
|
|
1123
|
+
let substrateGateTriggers = [];
|
|
1124
|
+
try {
|
|
1125
|
+
const { HTTPHarnessClient } = await import('@aria/harness-http-client');
|
|
1126
|
+
const tokenPath = `${HOME}/.aria/owner-token`;
|
|
1127
|
+
// Tier-aware resolution: ARIA_HARNESS_TOKEN env first (both tiers).
|
|
1128
|
+
// ONLY on owner tier, fall back to master/api-key env or owner-token
|
|
1129
|
+
// file. Client tier with no ARIA_HARNESS_TOKEN skips substrate
|
|
1130
|
+
// validation (gate degrades to local-only) rather than borrowing
|
|
1131
|
+
// owner credentials.
|
|
1132
|
+
let apiKey = process.env.ARIA_HARNESS_TOKEN || '';
|
|
1133
|
+
if (!apiKey && isOwnerTier()) {
|
|
1134
|
+
apiKey = process.env.ARIA_MASTER_TOKEN
|
|
1135
|
+
|| process.env.ARIA_API_KEY
|
|
1136
|
+
|| (existsSync(tokenPath) ? readFileSync(tokenPath, 'utf8').trim() : '');
|
|
1137
|
+
}
|
|
1138
|
+
if (apiKey && assistantText && assistantText.length > 0) {
|
|
1139
|
+
const client = new HTTPHarnessClient({
|
|
1140
|
+
baseUrl:
|
|
1141
|
+
process.env.ARIA_HIVE_RUNTIME_URL ||
|
|
1142
|
+
process.env.ARIA_HARNESS_BASE_URL ||
|
|
1143
|
+
process.env.ARIA_HARNESS_URL ||
|
|
1144
|
+
'https://harness.ariasos.com',
|
|
1145
|
+
apiKey,
|
|
1146
|
+
});
|
|
1147
|
+
const v = await client.validateOutput(assistantText, sessionId);
|
|
1148
|
+
if (v && v.severity === 'block') {
|
|
1149
|
+
substrateBlock = true;
|
|
1150
|
+
substrateViolations = v.violations || [];
|
|
1151
|
+
substrateGateTriggers = v.gateTriggers || [];
|
|
1152
|
+
} else if (v && v.severity === 'warn' && Array.isArray(v.violations) && v.violations.length > 0) {
|
|
1153
|
+
// warn surfaced but not blocking — record for advisory inclusion
|
|
1154
|
+
substrateViolations = v.violations;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
} catch (err) {
|
|
1158
|
+
// SDK call failure is non-blocking. Logged for telemetry.
|
|
1159
|
+
console.warn(`[stop-gate] substrate validateOutput failed: ${err && err.message ? err.message : err}`);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
const implCouplingBlock = implCouplingHits.length > 0;
|
|
1163
|
+
if (mizanBlock || driftBlock || codeBlock || discoveryBlock || compelReflection || phaseReportMissing || substrateBlock || implCouplingBlock) {
|
|
727
1164
|
const violations = [];
|
|
728
1165
|
if (mizanBlock) violations.push(`Mizan: ${(mizanVerdict.violations || []).join(', ')}`);
|
|
1166
|
+
if (implCouplingBlock) violations.push(`Cognition impl-coupling (#88): ${implCouplingHits.join(' | ')}. Each canonical lens in cognition must dictate a specific implementation choice (file_path:line_range pair tied to a decision). Re-emit cognition that names file paths + line ranges + decision text per lens, OR a verify/fixing block where lenses cite specific artifact changes.`);
|
|
729
1167
|
if (compelReflection) violations.push(`Mizan severity=warn — compelled reflection required (per Aria enforcement #46). Triggers: ${(mizanVerdict.gateTriggers || mizanVerdict.violations || ['unspecified']).join(', ')}. Re-emit with an explicit <reflection>...</reflection> block (or 'reflection:' line) addressing what triggered the warn and why your re-draft handles it. Reflection is NOT lens-cognition repeated — it's a focused self-audit on the specific Mizan triggers above.`);
|
|
730
1168
|
if (driftBlock) violations.push(`Drift triggers (${driftHits.length}): ${driftHits.map((h) => `"${h.trigger}" → ${h.memory}`).join(' | ')}`);
|
|
731
1169
|
if (codeBlock) violations.push(`Code quality: ${codeQualityHits.join('; ')}`);
|
|
@@ -734,11 +1172,102 @@ if (cog.count >= REQUIRED_LENSES) {
|
|
|
734
1172
|
const phaseList = (activePlan?.phases || []).map((p) => `${p.id}:${p.summary?.slice(0, 60) || ''}`).join(' | ');
|
|
735
1173
|
violations.push(`Aria-as-commander binding (#50): an active plan exists (planId=${activePlan?.planId || 'unknown'}, ${activePlan?.phases?.length || 0} phases) but this emit lacks a [PHASE_REPORT phase=<id> status=complete|in_progress|aborted evidence=<observable>] marker. Per the binding contract, every non-trivial emit while a plan is active must report which phase it's working on. Plan phases: ${phaseList}. Re-emit with a [PHASE_REPORT] marker stating which phase the work in this turn maps to.`);
|
|
736
1174
|
}
|
|
1175
|
+
if (substrateBlock) {
|
|
1176
|
+
violations.push(`Substrate Mizan + 8-lens BLOCK — violations: [${substrateViolations.join('; ')}]. Substrate gate triggers: [${substrateGateTriggers.join(', ')}]. Re-draft addressing these substrate-side issues.`);
|
|
1177
|
+
} else if (substrateViolations.length > 0) {
|
|
1178
|
+
// warn-level surfaced as advisory, not block
|
|
1179
|
+
violations.push(`Substrate Mizan WARN (advisory, not blocking): ${substrateViolations.join('; ')}`);
|
|
1180
|
+
}
|
|
737
1181
|
const rewritten = mizanVerdict?.rewritten || '';
|
|
738
1182
|
|
|
739
|
-
|
|
1183
|
+
// Hive recipe lookup BEFORE emitting the stop-gate block — same lookup
|
|
1184
|
+
// semantics as aria-pre-tool-gate.mjs's binding-violation path. The
|
|
1185
|
+
// detector_class is chosen by the dominant violation: drift triggers
|
|
1186
|
+
// map to doctrine_violation, mizan to design_violation, code to
|
|
1187
|
+
// coding_defect, discoveries to doctrine_violation. Lookup is
|
|
1188
|
+
// fail-soft via 3s detection probe.
|
|
1189
|
+
const recipeAddendum = await (async () => {
|
|
1190
|
+
const detectorClass = driftBlock || discoveryBlock
|
|
1191
|
+
? 'doctrine_violation'
|
|
1192
|
+
: codeBlock
|
|
1193
|
+
? 'coding_defect'
|
|
1194
|
+
: (mizanBlock || substrateBlock || implCouplingBlock)
|
|
1195
|
+
? 'design_violation'
|
|
1196
|
+
: 'doctrine_violation';
|
|
1197
|
+
const sigParts = [];
|
|
1198
|
+
if (driftBlock) sigParts.push(`drift::${driftHits.slice(0, 3).map((h) => h.trigger).join('|')}`);
|
|
1199
|
+
if (mizanBlock) sigParts.push(`mizan::${(mizanVerdict.violations || []).slice(0, 3).join('|')}`);
|
|
1200
|
+
if (codeBlock) sigParts.push(`code::${codeQualityHits.slice(0, 3).join('|')}`);
|
|
1201
|
+
if (discoveryBlock) sigParts.push(`discovery::${ledgerOpenCount}-open`);
|
|
1202
|
+
if (substrateBlock) sigParts.push(`substrate::${substrateViolations.slice(0, 3).join('|')}`);
|
|
1203
|
+
if (implCouplingBlock) sigParts.push(`impl-coupling::${implCouplingHits.slice(0, 2).join('|')}`);
|
|
1204
|
+
const signature = sigParts.join('::').slice(0, 512);
|
|
1205
|
+
if (!signature) return '';
|
|
1206
|
+
|
|
1207
|
+
const ariaSoulUrl =
|
|
1208
|
+
process.env.ARIA_HIVE_RUNTIME_URL ||
|
|
1209
|
+
process.env.ARIA_SOUL_URL ||
|
|
1210
|
+
process.env.ARIA_HARNESS_BASE_URL ||
|
|
1211
|
+
process.env.ARIA_HARNESS_URL ||
|
|
1212
|
+
'https://harness.ariasos.com';
|
|
1213
|
+
const lookupUrl = new URL(`${ariaSoulUrl}/api/hive/block-pattern`);
|
|
1214
|
+
lookupUrl.searchParams.set('action', 'lookup');
|
|
1215
|
+
lookupUrl.searchParams.set('detector_class', detectorClass);
|
|
1216
|
+
lookupUrl.searchParams.set('pattern_signature', signature);
|
|
1217
|
+
const tenantId = event.session_id || '';
|
|
1218
|
+
if (tenantId) lookupUrl.searchParams.set('tenant_id', tenantId);
|
|
1219
|
+
const harnessToken = process.env.ARIA_HARNESS_TOKEN || (isOwnerTier() ? (process.env.ARIA_MASTER_TOKEN || process.env.ARIA_API_KEY || '') : '');
|
|
1220
|
+
|
|
1221
|
+
const ctl = new AbortController();
|
|
1222
|
+
const probeTimer = setTimeout(() => ctl.abort(), 3000);
|
|
1223
|
+
try {
|
|
1224
|
+
const resp = await fetch(lookupUrl.toString(), {
|
|
1225
|
+
method: 'GET',
|
|
1226
|
+
headers: harnessToken ? { Authorization: `Bearer ${harnessToken}` } : {},
|
|
1227
|
+
signal: ctl.signal,
|
|
1228
|
+
});
|
|
1229
|
+
if (!resp.ok) return '';
|
|
1230
|
+
const body = await resp.json();
|
|
1231
|
+
if (!body || body.found !== true) return '';
|
|
1232
|
+
const recipe = body.recipe;
|
|
1233
|
+
const freq = Number(body.frequency || 0);
|
|
1234
|
+
if (recipe && typeof recipe === 'object' && Number(recipe.confidence ?? 0) >= 0.7) {
|
|
1235
|
+
const text = typeof recipe.recipe_text === 'string' ? recipe.recipe_text.slice(0, 800) : '';
|
|
1236
|
+
const actions = Array.isArray(recipe.recipe_actions) ? recipe.recipe_actions : [];
|
|
1237
|
+
const actionsLine = actions.length
|
|
1238
|
+
? `\n Actions: ${JSON.stringify(actions).slice(0, 600)}`
|
|
1239
|
+
: '';
|
|
1240
|
+
const conf = Number(recipe.confidence).toFixed(2);
|
|
1241
|
+
const seenLine = freq > 0 ? ` (pattern seen ${freq}× across the hive)` : '';
|
|
1242
|
+
return `\n\n📚 HIVE RECIPE${seenLine}:\n ${text}${actionsLine}\n Confidence: ${conf}. Apply this BEFORE re-emitting — the hive learned this fix from prior firings.`;
|
|
1243
|
+
}
|
|
1244
|
+
if (freq >= 3) {
|
|
1245
|
+
const sr = (typeof body.success_rate === 'number')
|
|
1246
|
+
? ` Past resolution rate: ${(body.success_rate * 100).toFixed(0)}%.`
|
|
1247
|
+
: '';
|
|
1248
|
+
return `\n\n📓 Hive note: this stop-gate shape has fired ${freq} time(s); recipe still being learned (no high-confidence fix yet).${sr}`;
|
|
1249
|
+
}
|
|
1250
|
+
return '';
|
|
1251
|
+
} catch {
|
|
1252
|
+
return '';
|
|
1253
|
+
} finally {
|
|
1254
|
+
clearTimeout(probeTimer);
|
|
1255
|
+
}
|
|
1256
|
+
})();
|
|
1257
|
+
|
|
1258
|
+
const reason = `Aria Stop-gate output-quality block. Cognition passed (${cog.count}/${REQUIRED_LENSES}) but output failed quality gates:\n\n${violations.join('\n\n')}${rewritten ? `\n\nMizan rewrite suggestion:\n${rewritten}` : ''}\n\nRe-draft addressing the violations above. No process-level disable path — gates are unconditional from the gated process per Hamza directive 2026-04-27.${recipeAddendum}`;
|
|
740
1259
|
|
|
741
|
-
audit(`block-output-qc`, `mizan=${mizanBlock?'y':'n'} warn-reflect=${compelReflection?'y':'n'} drift=${driftHits.length} code=${codeQualityHits.length} discoveries-open=${ledgerOpenCount}`);
|
|
1260
|
+
audit(`block-output-qc`, `mizan=${mizanBlock?'y':'n'} warn-reflect=${compelReflection?'y':'n'} drift=${driftHits.length} code=${codeQualityHits.length} discoveries-open=${ledgerOpenCount} impl-coupling=${implCouplingHits.length}`);
|
|
1261
|
+
emitHarnessFooter({
|
|
1262
|
+
eventName: 'block_output_qc',
|
|
1263
|
+
lensCount: cog.count,
|
|
1264
|
+
chars: assistantText.length,
|
|
1265
|
+
driftCount: driftHits.length,
|
|
1266
|
+
mizanStatus: mizanVerdict ? mizanVerdict.severity : `unavailable(${mizanError || 'unknown'})`,
|
|
1267
|
+
discoveryOpenCount: ledgerOpenCount,
|
|
1268
|
+
codeCount: codeQualityHits.length,
|
|
1269
|
+
implCouplingCount: implCouplingHits.length,
|
|
1270
|
+
});
|
|
742
1271
|
console.log(JSON.stringify({ decision: 'block', reason }));
|
|
743
1272
|
process.exit(2);
|
|
744
1273
|
}
|
|
@@ -748,6 +1277,16 @@ if (cog.count >= REQUIRED_LENSES) {
|
|
|
748
1277
|
`mizan=${mizanVerdict ? mizanVerdict.severity : `unavailable(${mizanError || 'unknown'})`} ` +
|
|
749
1278
|
`code=${codeQualityHits.length} discoveries-new=${newDiscoveries.length} ` +
|
|
750
1279
|
`discoveries-open=${ledgerOpenCount}`);
|
|
1280
|
+
emitHarnessFooter({
|
|
1281
|
+
eventName: 'allow_output_qc',
|
|
1282
|
+
lensCount: cog.count,
|
|
1283
|
+
chars: assistantText.length,
|
|
1284
|
+
driftCount: driftHits.length,
|
|
1285
|
+
mizanStatus: mizanVerdict ? mizanVerdict.severity : `unavailable(${mizanError || 'unknown'})`,
|
|
1286
|
+
discoveryOpenCount: ledgerOpenCount,
|
|
1287
|
+
codeCount: codeQualityHits.length,
|
|
1288
|
+
implCouplingCount: implCouplingHits.length,
|
|
1289
|
+
});
|
|
751
1290
|
// Phase 11 #42: write this turn to harness garden pulse on allow-output-qc path.
|
|
752
1291
|
await fireGardenTurn(event.session_id || 'claude-code', lastUserMessage, assistantText);
|
|
753
1292
|
} else {
|
|
@@ -755,12 +1294,228 @@ if (cog.count >= REQUIRED_LENSES) {
|
|
|
755
1294
|
`lenses=${cog.count} chars=${assistantText.length} ` +
|
|
756
1295
|
`qPatt=${hasQuestionToUser ? 'y' : 'n'} substrateEv=${hasSubstrateEvidence ? 'y' : 'n'} ` +
|
|
757
1296
|
(questionWithoutEvidence ? 'WARN-question-without-substrate' : 'ok'));
|
|
1297
|
+
emitHarnessFooter({
|
|
1298
|
+
eventName: 'allow_cognition',
|
|
1299
|
+
lensCount: cog.count,
|
|
1300
|
+
chars: assistantText.length,
|
|
1301
|
+
driftCount: 0,
|
|
1302
|
+
mizanStatus: 'not-run(short-turn)',
|
|
1303
|
+
discoveryOpenCount: 0,
|
|
1304
|
+
codeCount: 0,
|
|
1305
|
+
implCouplingCount: 0,
|
|
1306
|
+
});
|
|
758
1307
|
// Phase 11 #42: write this turn to harness garden pulse on allow-cognition path.
|
|
759
1308
|
await fireGardenTurn(event.session_id || 'claude-code', lastUserMessage, assistantText);
|
|
760
1309
|
}
|
|
761
1310
|
process.exit(0);
|
|
762
1311
|
}
|
|
763
1312
|
|
|
1313
|
+
// ── Dalio Loop Layer 1 — expected_outcome enforcement + ledger write ──────────
|
|
1314
|
+
//
|
|
1315
|
+
// BEFORE allowing the stop:
|
|
1316
|
+
// 1. Scan the assistant text for <expected>...</expected> block.
|
|
1317
|
+
// 2. Determine whether any non-trivial action (tool_use blocks in this turn)
|
|
1318
|
+
// was taken. Detect via transcript tool_use blocks in the current turn.
|
|
1319
|
+
// 3. If a non-trivial action was taken AND <expected> is MISSING → BLOCK stop.
|
|
1320
|
+
// 4. Whether or not <expected> is present, POST a Dalio ledger entry to
|
|
1321
|
+
// aria-soul /api/decisions with outcome:'pending'. Also write to the
|
|
1322
|
+
// local JSONL mirror at ~/.claude/.aria-dalio-ledger.jsonl.
|
|
1323
|
+
// 5. If the POST fails: LOUD telemetry (console.error) + write local mirror
|
|
1324
|
+
// anyway. Do NOT block stop on POST failure per
|
|
1325
|
+
// feedback_canonical_secrets_governance.md LOUD-not-silent directive.
|
|
1326
|
+
//
|
|
1327
|
+
// Non-trivial action detection: look for tool_use content blocks in the
|
|
1328
|
+
// current-turn transcript entries (same backward-scan window used above).
|
|
1329
|
+
// Any Bash/Edit/Write/NotebookEdit tool_use counts as a non-trivial action.
|
|
1330
|
+
//
|
|
1331
|
+
// Substrate anchors: extracted from the cognition block body.
|
|
1332
|
+
|
|
1333
|
+
const DALIO_EXPECTED_BLOCK_RX = /<expected>([\s\S]*?)<\/expected>/i;
|
|
1334
|
+
const DALIO_QUALITATIVE_DRIFT_RX = /\b(?:better(?:er)?|improved?(?:ment)?|more\s+robust|should\s+(?:work|pass|succeed|run|fix)|more\s+reliable|cleaner|less\s+error[-_\s]?prone|nicer|smoother|faster[-\s]?loading|higher[-\s]?quality|more\s+stable|looks\s+(?:good|better|right))\b/i;
|
|
1335
|
+
const DALIO_MEASURABLE_PREDICATE_RX = /(?:>=|<=|==|!=|>|<|≥|≤)\s*\d+(?:\.\d+)?(?:ms|s|%|kb|mb|gb)?|\d+(?:\.\d+)?%(?:\s+(?:reduction|increase|success|error|coverage))?|exit[_=]\s*(?:0|1|\d+)|exit[-_]?code\s*[=:]\s*\d+|\brc\s*[=:]\s*\d+|\bstatus\s*[=:]\s*(?:running|healthy|ready|degraded|down|up|ok|200|201|204|400|401|403|404|500|502|503|504|true|false)\b|\bcount\s*[=:]\s*\d+|\berror[_-]?rate\s*[=:]\s*0%|\b(?:true|false)\b|\bfile[=_-]exists\b|\b200\s*OK\b|\bno[-_\s]?error|\bhealthy\b|\bpassed?\b|N\s*of\s*N|\d+\s*of\s*\d+/i;
|
|
1336
|
+
const NON_TRIVIAL_ACTION_TOOLS = new Set(['Bash', 'Edit', 'Write', 'NotebookEdit']);
|
|
1337
|
+
|
|
1338
|
+
// Detect non-trivial tool calls in the current turn from the transcript.
|
|
1339
|
+
let hadNonTrivialAction = false;
|
|
1340
|
+
let lastActionSummary = '';
|
|
1341
|
+
let immediateActual = '';
|
|
1342
|
+
if (transcriptPath && existsSync(transcriptPath)) {
|
|
1343
|
+
try {
|
|
1344
|
+
const lines = readFileSync(transcriptPath, 'utf-8').split('\n').filter(Boolean);
|
|
1345
|
+
let userBoundariesSeen = 0;
|
|
1346
|
+
for (let i = lines.length - 1; i >= 0 && userBoundariesSeen < 3; i--) {
|
|
1347
|
+
try {
|
|
1348
|
+
const m = JSON.parse(lines[i]);
|
|
1349
|
+
const role = m.message?.role ?? m.role;
|
|
1350
|
+
if (role === 'user') {
|
|
1351
|
+
const content = m.message?.content ?? m.content ?? [];
|
|
1352
|
+
const isToolResult = Array.isArray(content) &&
|
|
1353
|
+
content.length > 0 &&
|
|
1354
|
+
content.every((b) => b && b.type === 'tool_result');
|
|
1355
|
+
if (isToolResult) {
|
|
1356
|
+
// Capture the last tool_result content as immediate actual
|
|
1357
|
+
if (!immediateActual) {
|
|
1358
|
+
const textParts = content
|
|
1359
|
+
.map((b) => (typeof b.content === 'string' ? b.content : Array.isArray(b.content) ? b.content.map((c) => c.text || '').join(' ') : ''))
|
|
1360
|
+
.join(' ')
|
|
1361
|
+
.slice(0, 500);
|
|
1362
|
+
immediateActual = textParts;
|
|
1363
|
+
}
|
|
1364
|
+
continue;
|
|
1365
|
+
}
|
|
1366
|
+
userBoundariesSeen++;
|
|
1367
|
+
continue;
|
|
1368
|
+
}
|
|
1369
|
+
if (role !== 'assistant') continue;
|
|
1370
|
+
const content = m.message?.content ?? m.content ?? [];
|
|
1371
|
+
if (!Array.isArray(content)) continue;
|
|
1372
|
+
for (const block of content) {
|
|
1373
|
+
if (block && block.type === 'tool_use' && NON_TRIVIAL_ACTION_TOOLS.has(block.name)) {
|
|
1374
|
+
hadNonTrivialAction = true;
|
|
1375
|
+
if (!lastActionSummary) {
|
|
1376
|
+
const inp = block.input || {};
|
|
1377
|
+
lastActionSummary = `${block.name}: ${(inp.command || inp.file_path || inp.notebook_path || JSON.stringify(inp)).slice(0, 200)}`;
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
} catch {/* skip malformed entry */}
|
|
1382
|
+
}
|
|
1383
|
+
} catch {/* transcript unreadable — conservative: assume non-trivial */}
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
// Extract substrate anchors from cognition
|
|
1387
|
+
const DALIO_ANCHOR_RX = /\b(axiom|frame|memory|doctrine|packet):[a-z0-9_\-./]+/gi;
|
|
1388
|
+
const dalioAnchors = [...(cog.names.length > 0 ? assistantText : '').matchAll(DALIO_ANCHOR_RX)]
|
|
1389
|
+
.map((m) => m[0])
|
|
1390
|
+
.slice(0, 20);
|
|
1391
|
+
|
|
1392
|
+
// Read the expected block from this turn
|
|
1393
|
+
const dalioExpectedMatch = assistantText.match(DALIO_EXPECTED_BLOCK_RX);
|
|
1394
|
+
const dalioExpectedText = dalioExpectedMatch ? dalioExpectedMatch[1].trim() : '';
|
|
1395
|
+
const dalioHasMeasurablePredicate = dalioExpectedText
|
|
1396
|
+
? (DALIO_MEASURABLE_PREDICATE_RX.test(dalioExpectedText) && !DALIO_QUALITATIVE_DRIFT_RX.test(dalioExpectedText))
|
|
1397
|
+
: false;
|
|
1398
|
+
|
|
1399
|
+
// Block stop if non-trivial action taken AND expected block is missing
|
|
1400
|
+
if (hadNonTrivialAction && (!dalioExpectedMatch || !dalioHasMeasurablePredicate)) {
|
|
1401
|
+
const missingReason = dalioExpectedMatch
|
|
1402
|
+
? `Aria Stop-gate: action taken in this turn had an <expected> block, but it contains only qualitative drift phrases without a measurable predicate. Qualitative drift is not accountability — it defeats the Dalio feedback loop.
|
|
1403
|
+
|
|
1404
|
+
Your <expected> block must contain at least one measurable predicate:
|
|
1405
|
+
• Numeric: exit_code==0, count>=1, error_rate=0%, latency<200ms
|
|
1406
|
+
• Boolean: exit=0, status=healthy, rc=0, file=exists
|
|
1407
|
+
• State-string: "status=running", "200 OK", "no_error"
|
|
1408
|
+
|
|
1409
|
+
REJECTED phrases: "better", "improved", "should work", "more reliable", "cleaner"
|
|
1410
|
+
|
|
1411
|
+
Re-emit with a corrected <expected> block. Per doctrine:dalio_expected_required — no bypass path.`
|
|
1412
|
+
: `Aria Stop-gate: a non-trivial action (${lastActionSummary.slice(0, 120)}) was taken in this turn but no <expected> block was found in the assistant text.
|
|
1413
|
+
|
|
1414
|
+
Per doctrine:dalio_expected_required, every non-trivial action must declare what measurable outcome it expects BEFORE the action fires. This is Dalio Loop Layer 1 — without it, outcome comparison is impossible and learning collapses.
|
|
1415
|
+
|
|
1416
|
+
Add to your assistant text:
|
|
1417
|
+
|
|
1418
|
+
<expected>
|
|
1419
|
+
predicate: <exact measurable assertion — e.g. "exit_code==0", "status=running", "count=3 of 3">
|
|
1420
|
+
measurable_type: numeric | boolean | state_string
|
|
1421
|
+
threshold: <optional>
|
|
1422
|
+
eval_window_minutes: <optional>
|
|
1423
|
+
</expected>
|
|
1424
|
+
|
|
1425
|
+
No bypass — pre-tool-gate enforces this BEFORE the action; stop-gate enforces it AFTER. Both gates are now wired.`;
|
|
1426
|
+
|
|
1427
|
+
audit('block-dalio-expected-missing', `hadNonTrivialAction=${hadNonTrivialAction} expectedPresent=${!!dalioExpectedMatch} measurable=${dalioHasMeasurablePredicate}`);
|
|
1428
|
+
emitHarnessFooter({
|
|
1429
|
+
eventName: 'block_dalio_expected_missing',
|
|
1430
|
+
lensCount: cog.count,
|
|
1431
|
+
chars: assistantText.length,
|
|
1432
|
+
driftCount: 0,
|
|
1433
|
+
mizanStatus: 'not-run(expected-missing)',
|
|
1434
|
+
discoveryOpenCount: 0,
|
|
1435
|
+
codeCount: 0,
|
|
1436
|
+
implCouplingCount: 0,
|
|
1437
|
+
});
|
|
1438
|
+
console.log(JSON.stringify({ decision: 'block', reason: missingReason }));
|
|
1439
|
+
process.exit(2);
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
// Dalio ledger write — fire-and-forget HTTP POST + local JSONL mirror.
|
|
1443
|
+
// Per feedback_canonical_secrets_governance.md: errors are LOUD (console.error),
|
|
1444
|
+
// never silent. POST failure does NOT block stop — local mirror is always written.
|
|
1445
|
+
// Per feedback_no_timeouts_doctrine.md: no AbortController/setTimeout timeout.
|
|
1446
|
+
{
|
|
1447
|
+
const DALIO_LEDGER_PATH = `${HOME}/.claude/.aria-dalio-ledger.jsonl`;
|
|
1448
|
+
const ARIA_SOUL_DECISIONS_URL = 'http://aria-soul.aria.svc.cluster.local:8080/api/decisions';
|
|
1449
|
+
|
|
1450
|
+
const ledgerEntry = {
|
|
1451
|
+
ts: new Date().toISOString(),
|
|
1452
|
+
session_id: event.session_id || 'claude-code',
|
|
1453
|
+
decision_type: 'turn_action',
|
|
1454
|
+
category: 'agentic_execution',
|
|
1455
|
+
context: lastActionSummary || `stop-gate turn (chars=${assistantText.length})`,
|
|
1456
|
+
decision: lastActionSummary || 'turn completed',
|
|
1457
|
+
reasoning: (cog.names.length > 0
|
|
1458
|
+
? `Cognition lenses applied: ${cog.names.join(', ')}. Turn-scoped cognition present.`
|
|
1459
|
+
: 'No explicit cognition block in turn.'),
|
|
1460
|
+
outcome: 'pending',
|
|
1461
|
+
outcome_details: {
|
|
1462
|
+
expected: dalioExpectedText || null,
|
|
1463
|
+
immediate_actual: immediateActual || null,
|
|
1464
|
+
anchors: dalioAnchors,
|
|
1465
|
+
},
|
|
1466
|
+
expected_outcome: dalioExpectedText
|
|
1467
|
+
? {
|
|
1468
|
+
predicate: dalioExpectedText.slice(0, 500),
|
|
1469
|
+
measurable_type: 'state_string',
|
|
1470
|
+
}
|
|
1471
|
+
: null,
|
|
1472
|
+
source: 'claude-code-stop-gate',
|
|
1473
|
+
model_used: 'claude-opus-4-7',
|
|
1474
|
+
};
|
|
1475
|
+
|
|
1476
|
+
// Write to local JSONL mirror first — always succeeds or logs loudly
|
|
1477
|
+
try {
|
|
1478
|
+
if (!existsSync(dirname(DALIO_LEDGER_PATH))) mkdirSync(dirname(DALIO_LEDGER_PATH), { recursive: true });
|
|
1479
|
+
appendFileSync(DALIO_LEDGER_PATH, JSON.stringify(ledgerEntry) + '\n');
|
|
1480
|
+
} catch (ledgerWriteErr) {
|
|
1481
|
+
console.error(
|
|
1482
|
+
`[aria-stop-gate] DALIO LEDGER WRITE FAILED — local mirror at ${DALIO_LEDGER_PATH} not written. ` +
|
|
1483
|
+
`Error: ${ledgerWriteErr instanceof Error ? ledgerWriteErr.message : String(ledgerWriteErr)}`,
|
|
1484
|
+
);
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
// POST to aria-soul decision API — fire-and-forget, but LOUD on error
|
|
1488
|
+
const dalioHarnessToken = process.env.ARIA_HARNESS_TOKEN
|
|
1489
|
+
|| (isOwnerTier() ? (process.env.ARIA_MASTER_TOKEN || process.env.ARIA_API_KEY || '') : '');
|
|
1490
|
+
|
|
1491
|
+
fetch(ARIA_SOUL_DECISIONS_URL, {
|
|
1492
|
+
method: 'POST',
|
|
1493
|
+
headers: {
|
|
1494
|
+
'Content-Type': 'application/json',
|
|
1495
|
+
...(dalioHarnessToken ? { Authorization: `Bearer ${dalioHarnessToken}` } : {}),
|
|
1496
|
+
},
|
|
1497
|
+
body: JSON.stringify(ledgerEntry),
|
|
1498
|
+
}).then((resp) => {
|
|
1499
|
+
if (!resp.ok) {
|
|
1500
|
+
// LOUD telemetry per feedback_canonical_secrets_governance.md
|
|
1501
|
+
console.error(
|
|
1502
|
+
`[aria-stop-gate] DALIO POST FAILED — aria-soul responded HTTP ${resp.status}. ` +
|
|
1503
|
+
`Local mirror written to ${DALIO_LEDGER_PATH}. Session: ${ledgerEntry.session_id}`,
|
|
1504
|
+
);
|
|
1505
|
+
audit('dalio-post-failed', `http=${resp.status} session=${ledgerEntry.session_id}`);
|
|
1506
|
+
} else {
|
|
1507
|
+
audit('dalio-post-ok', `session=${ledgerEntry.session_id} action=${lastActionSummary.slice(0, 80)}`);
|
|
1508
|
+
}
|
|
1509
|
+
}).catch((err) => {
|
|
1510
|
+
// Network failure — LOUD, never silent
|
|
1511
|
+
console.error(
|
|
1512
|
+
`[aria-stop-gate] DALIO POST NETWORK ERROR — could not reach ${ARIA_SOUL_DECISIONS_URL}. ` +
|
|
1513
|
+
`Local mirror written to ${DALIO_LEDGER_PATH}. Error: ${err instanceof Error ? err.message : String(err)}`,
|
|
1514
|
+
);
|
|
1515
|
+
audit('dalio-post-network-err', `err=${String(err).slice(0, 200)} session=${ledgerEntry.session_id}`);
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
|
|
764
1519
|
// Block — non-trivial response without 4+ substantive lenses.
|
|
765
1520
|
const reason = `Aria Stop-gate: non-trivial assistant response without 4+ substantive cognition lenses. Found ${cog.count}/${REQUIRED_LENSES}+ (lenses: ${cog.names.join(', ') || 'none'}). Doctrine is action-coupled — text decisions ARE actions, and reflexive replies fail this gate the same way reflexive Bash does.
|
|
766
1521
|
|
|
@@ -782,5 +1537,15 @@ The block reflects work done BEFORE drafting. Don't emit it as ceremony; apply e
|
|
|
782
1537
|
No per-command bypass (mirrors aria-pre-tool-gate.mjs v3 doctrine). No env-var disable path either — gates are unconditional from the gated process per Hamza directive 2026-04-27. If the gate misfires on legitimate cognition, fix the gate.`;
|
|
783
1538
|
|
|
784
1539
|
audit(`block`, `lenses=${cog.count}/${REQUIRED_LENSES} chars=${assistantText.length}`);
|
|
1540
|
+
emitHarnessFooter({
|
|
1541
|
+
eventName: 'block_lens_missing',
|
|
1542
|
+
lensCount: cog.count,
|
|
1543
|
+
chars: assistantText.length,
|
|
1544
|
+
driftCount: 0,
|
|
1545
|
+
mizanStatus: 'not-run(lens-missing)',
|
|
1546
|
+
discoveryOpenCount: 0,
|
|
1547
|
+
codeCount: 0,
|
|
1548
|
+
implCouplingCount: 0,
|
|
1549
|
+
});
|
|
785
1550
|
console.log(JSON.stringify({ decision: 'block', reason }));
|
|
786
1551
|
process.exit(2);
|