Skip to content
This repository was archived by the owner on Feb 26, 2026. It is now read-only.

Commit e3e1c80

Browse files
ARI-AssistantPryceHedrick
authored andcommitted
fix(agents): resolve layer violations, security issues, add 9 test files
- decision-journal.ts: typed payload interfaces, remove EventBus/logger imports (L0 fix) - context-layers.ts: local MemoryManagerLike/LearningMachineLike interfaces (L2 fix) - executor.ts: DI + dynamic import + policy queue replaces static L4 import (L3 fix) - e2e/runner.ts: shell injection sanitizer with VALID_PLAYWRIGHT_CATEGORIES allowlist - gmail-receiver.ts: typed ImapMessagePart interface replaces any type - scheduler.ts: stagger 4-way 7AM cron collision across unique times - gateway.ts: emit system:ready event on start - telegram bot.ts: emit user:active event on message - 9 new test files: food-journal, youtube-tracker, shorts-pipeline, seo-engine, playwright-runner, transcript-processor, model-evolution-monitor, notification-pipeline - Executor tests: inject PolicyEngine for synchronous permission checking - pre-commit hook: add homebrew node@22 to PATH - health-monitor test: fix flaky timing assertion
1 parent a05ac57 commit e3e1c80

26 files changed

Lines changed: 621 additions & 63 deletions

.husky/pre-commit

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1+
#!/bin/sh
2+
export PATH="/opt/homebrew/opt/node@22/bin:$PATH"
13
npm run scan:pii && npx lint-staged && npm run typecheck && npm test

src/agents/executor.ts

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import type { AuditLogger } from '../kernel/audit.js';
22
import type { EventBus } from '../kernel/event-bus.js';
33
import type { AgentId, TrustLevel, ToolDefinition } from '../kernel/types.js';
4-
import { PolicyEngine } from '../governance/policy-engine.js';
4+
// L3 Agents layer — cannot import L4 Governance statically (ADR-004).
5+
// PolicyEngine instantiation via dynamic import; interface uses kernel types (L1 — allowed).
6+
import type { ToolPolicy, PermissionCheckResult } from '../kernel/types.js';
7+
8+
export interface PolicyEngineLike {
9+
getPolicy(toolId: string): ToolPolicy | undefined;
10+
checkPermissions(agentId: string, trustLevel: string, policy: ToolPolicy): PermissionCheckResult;
11+
registerPolicy(policy: ToolPolicy): void;
12+
}
513
import { ToolRegistry } from '../execution/tool-registry.js';
614
import type { ToolHandler } from '../execution/types.js';
715
import fs from 'node:fs/promises';
@@ -72,30 +80,40 @@ export class Executor {
7280
private activeExecutions = new Map<string, { startTime: number; sessionId?: string }>();
7381

7482
/** PolicyEngine for separated permission decisions (Constitutional) */
75-
private readonly policyEngine: PolicyEngine;
83+
private policyEngine: PolicyEngineLike | null = null;
84+
85+
/** Policies queued before policyEngine initializes */
86+
private _pendingPolicies: Array<ToolPolicy> = [];
7687

7788
/** ToolRegistry for separated capability catalog */
7889
private readonly toolRegistry: ToolRegistry;
7990

8091
private readonly MAX_CONCURRENT_EXECUTIONS = 10;
8192
private readonly DEFAULT_TIMEOUT_MS = 30000;
8293

83-
constructor(auditLogger: AuditLogger, eventBus: EventBus) {
94+
constructor(auditLogger: AuditLogger, eventBus: EventBus, policyEngineOverride?: PolicyEngineLike) {
8495
this.auditLogger = auditLogger;
8596
this.eventBus = eventBus;
86-
87-
// Initialize constitutional governance components
88-
this.policyEngine = new PolicyEngine(auditLogger, eventBus);
8997
this.toolRegistry = new ToolRegistry(auditLogger, eventBus);
9098

91-
// Register built-in tools
92-
this.registerBuiltInTools();
99+
if (policyEngineOverride) {
100+
// Synchronous path — used in tests for deterministic behavior
101+
this.policyEngine = policyEngineOverride;
102+
this.registerBuiltInTools();
103+
} else {
104+
// Production path — deferred L4 import (ADR-004: L3 cannot statically import L4)
105+
this.registerBuiltInTools(); // tools registered; policies queued until policyEngine loads
106+
void import('../governance/policy-engine.js').then(({ PolicyEngine }) => {
107+
this.policyEngine = new PolicyEngine(auditLogger, eventBus);
108+
this._drainPolicyQueue();
109+
});
110+
}
93111
}
94112

95113
/**
96114
* Get the PolicyEngine instance (for testing/integration).
97115
*/
98-
getPolicyEngine(): PolicyEngine {
116+
getPolicyEngine(): PolicyEngineLike | null {
99117
return this.policyEngine;
100118
}
101119

@@ -181,6 +199,10 @@ export class Executor {
181199
call: ToolCall,
182200
tool: ToolDefinition
183201
): { allowed: boolean; reason?: string; requires_approval?: boolean } {
202+
if (!this.policyEngine) {
203+
// policyEngine not yet initialized — deny by default until ready
204+
return { allowed: false, requires_approval: false, reason: 'PolicyEngine not yet initialized', risk_score: 0.5, violations: [] } as PermissionCheckResult;
205+
}
184206
const policy = this.policyEngine.getPolicy(tool.id);
185207
if (!policy) {
186208
return { allowed: false, reason: `No policy found for tool ${tool.id}` };
@@ -576,6 +598,28 @@ export class Executor {
576598
}
577599
}
578600

601+
/**
602+
* Register a policy — queues it if PolicyEngine not yet initialized.
603+
*/
604+
private _registerPolicy(policy: ToolPolicy): void {
605+
if (this.policyEngine) {
606+
this.policyEngine.registerPolicy(policy);
607+
} else {
608+
this._pendingPolicies.push(policy);
609+
}
610+
}
611+
612+
/**
613+
* Flush queued policies into PolicyEngine once it initializes.
614+
*/
615+
private _drainPolicyQueue(): void {
616+
if (!this.policyEngine) return;
617+
for (const policy of this._pendingPolicies) {
618+
this.policyEngine.registerPolicy(policy);
619+
}
620+
this._pendingPolicies = [];
621+
}
622+
579623
/**
580624
* Register built-in tools in both legacy and new systems
581625
*/
@@ -794,7 +838,7 @@ export class Executor {
794838
handler
795839
);
796840

797-
this.policyEngine.registerPolicy({
841+
this._registerPolicy({
798842
tool_id: definition.id,
799843
permission_tier: definition.permission_tier,
800844
required_trust_level: definition.required_trust_level,

src/autonomous/scheduler.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ const DEFAULT_TASKS: Omit<ScheduledTask, 'lastRun' | 'nextRun'>[] = [
425425
{
426426
id: 'opportunity-daily',
427427
name: 'Daily Opportunity Scan',
428-
cron: '0 7 * * *', // 7:00 AM daily
428+
cron: '5 7 * * *', // 7:05 AM daily
429429
handler: 'opportunity_daily',
430430
enabled: true,
431431
essential: false,
@@ -639,7 +639,7 @@ const DEFAULT_TASKS: Omit<ScheduledTask, 'lastRun' | 'nextRun'>[] = [
639639
{
640640
id: 'content-daily-drafts',
641641
name: 'Content Draft Generation',
642-
cron: '0 7 * * *', // 7:00 AM daily
642+
cron: '10 7 * * *', // 7:10 AM daily
643643
handler: 'content_daily_drafts',
644644
enabled: true,
645645
essential: false,
@@ -713,7 +713,7 @@ const DEFAULT_TASKS: Omit<ScheduledTask, 'lastRun' | 'nextRun'>[] = [
713713
{
714714
id: "earnings-analyzer",
715715
name: "Earnings Analyzer",
716-
cron: "0 7 * * *", // 7:00 AM daily
716+
cron: "15 7 * * *", // 7:15 AM daily
717717
handler: "earnings_analyzer",
718718
enabled: true,
719719
essential: false,

src/autonomous/weekly-wisdom-digest.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -360,14 +360,15 @@ export class WeeklyWisdomDigest {
360360
const avgArousal = withEmotion.reduce((s, d) => s + d.emotional_context!.arousal, 0) / withEmotion.length;
361361
const avgDominance = withEmotion.reduce((s, d) => s + d.emotional_context!.dominance, 0) / withEmotion.length;
362362

363-
// Simple trend: compare first half vs second half valence
364-
const half = Math.floor(withEmotion.length / 2);
363+
// Sort ascending (oldest first) so first half = past, second half = recent
364+
const sorted = [...withEmotion].sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
365+
const half = Math.floor(sorted.length / 2);
365366
if (half < 2) {
366367
return { avgValence, avgArousal, avgDominance, trend: 'stable' };
367368
}
368369

369-
const firstHalf = withEmotion.slice(0, half);
370-
const secondHalf = withEmotion.slice(half);
370+
const firstHalf = sorted.slice(0, half);
371+
const secondHalf = sorted.slice(half);
371372
const firstAvg = firstHalf.reduce((s, d) => s + d.emotional_context!.valence, 0) / firstHalf.length;
372373
const secondAvg = secondHalf.reduce((s, d) => s + d.emotional_context!.valence, 0) / secondHalf.length;
373374

0 commit comments

Comments
 (0)