From 46b5543cb58c9c2621e2cb9fd4b0ba46ccf679e8 Mon Sep 17 00:00:00 2001 From: Eyejoker Date: Sat, 30 May 2026 00:08:28 +0900 Subject: [PATCH] refactor: centralize host evidence actions (#194) --- runners/agent-runner/src/host-evidence.ts | 64 ++++------------ runners/shared/src/evidence-actions.ts | 78 ++++++++++++++++++++ runners/shared/src/index.ts | 17 +++++ runners/shared/test/evidence-actions.test.ts | 41 ++++++++++ src/db-evidence.ts | 22 ++---- src/deploy-evidence.ts | 43 +++++------ src/github-evidence.ts | 28 +++---- src/host-evidence.ts | 43 ++--------- 8 files changed, 193 insertions(+), 143 deletions(-) create mode 100644 runners/shared/src/evidence-actions.ts create mode 100644 runners/shared/test/evidence-actions.test.ts diff --git a/runners/agent-runner/src/host-evidence.ts b/runners/agent-runner/src/host-evidence.ts index 88449b9..6981280 100644 --- a/runners/agent-runner/src/host-evidence.ts +++ b/runners/agent-runner/src/host-evidence.ts @@ -1,54 +1,22 @@ import fs from 'fs'; import path from 'path'; +import { + ARTIFACT_EVIDENCE_KINDS, + DB_EVIDENCE_ACTIONS, + DEPLOY_EVIDENCE_ACTIONS, + GITHUB_EVIDENCE_ACTIONS, + HOST_EVIDENCE_ACTIONS, + type HostEvidenceAction, +} from 'ejclaw-runners-shared'; -export const HOST_EVIDENCE_ACTIONS = [ - 'ejclaw_service_status', - 'ejclaw_service_logs', - 'ejclaw_role_runtime_config', - 'ejclaw_deploy_state', - 'ejclaw_artifact_metadata', - 'db_paired_task_status', - 'db_paired_task_flow', - 'db_recent_paired_failures', - 'db_recent_scheduled_tasks', - 'db_scheduled_task_runs', - 'github_pr_status', - 'github_pr_diff_stat', - 'github_run_status', - 'github_run_jobs', - 'github_workflow_file', -] as const; - -export const DB_EVIDENCE_ACTIONS = [ - 'db_paired_task_status', - 'db_paired_task_flow', - 'db_recent_paired_failures', - 'db_recent_scheduled_tasks', - 'db_scheduled_task_runs', -] as const; - -export const DEPLOY_EVIDENCE_ACTIONS = [ - 'ejclaw_deploy_state', - 'ejclaw_artifact_metadata', -] as const; - -export const GITHUB_EVIDENCE_ACTIONS = [ - 'github_pr_status', - 'github_pr_diff_stat', - 'github_run_status', - 'github_run_jobs', - 'github_workflow_file', -] as const; - -export const ARTIFACT_EVIDENCE_KINDS = [ - 'build_outputs', - 'dashboard_dist', - 'runner_dist', - 'android_debug_apk', - 'attachments_dir', -] as const; - -export type HostEvidenceAction = (typeof HOST_EVIDENCE_ACTIONS)[number]; +export { + ARTIFACT_EVIDENCE_KINDS, + DB_EVIDENCE_ACTIONS, + DEPLOY_EVIDENCE_ACTIONS, + GITHUB_EVIDENCE_ACTIONS, + HOST_EVIDENCE_ACTIONS, + type HostEvidenceAction, +}; export interface HostEvidenceResponse { requestId: string; diff --git a/runners/shared/src/evidence-actions.ts b/runners/shared/src/evidence-actions.ts new file mode 100644 index 0000000..037b9e6 --- /dev/null +++ b/runners/shared/src/evidence-actions.ts @@ -0,0 +1,78 @@ +export const DB_EVIDENCE_ACTIONS = [ + 'db_paired_task_status', + 'db_paired_task_flow', + 'db_recent_paired_failures', + 'db_recent_scheduled_tasks', + 'db_scheduled_task_runs', +] as const; + +export const DEPLOY_EVIDENCE_ACTIONS = [ + 'ejclaw_deploy_state', + 'ejclaw_artifact_metadata', +] as const; + +export const GITHUB_EVIDENCE_ACTIONS = [ + 'github_pr_status', + 'github_pr_diff_stat', + 'github_run_status', + 'github_run_jobs', + 'github_workflow_file', +] as const; + +export const HOST_EVIDENCE_ACTIONS = [ + 'ejclaw_service_status', + 'ejclaw_service_logs', + 'ejclaw_role_runtime_config', + ...DEPLOY_EVIDENCE_ACTIONS, + ...DB_EVIDENCE_ACTIONS, + ...GITHUB_EVIDENCE_ACTIONS, +] as const; + +export const ARTIFACT_EVIDENCE_KINDS = [ + 'build_outputs', + 'dashboard_dist', + 'runner_dist', + 'android_debug_apk', + 'attachments_dir', +] as const; + +export type DbEvidenceAction = (typeof DB_EVIDENCE_ACTIONS)[number]; +export type DeployEvidenceAction = (typeof DEPLOY_EVIDENCE_ACTIONS)[number]; +export type GitHubEvidenceAction = (typeof GITHUB_EVIDENCE_ACTIONS)[number]; +export type HostEvidenceAction = (typeof HOST_EVIDENCE_ACTIONS)[number]; +export type ArtifactEvidenceKind = (typeof ARTIFACT_EVIDENCE_KINDS)[number]; + +function includesEvidenceValue( + values: T, + value: unknown, +): value is T[number] { + return typeof value === 'string' && values.includes(value as T[number]); +} + +export function isDbEvidenceAction(value: unknown): value is DbEvidenceAction { + return includesEvidenceValue(DB_EVIDENCE_ACTIONS, value); +} + +export function isDeployEvidenceAction( + value: unknown, +): value is DeployEvidenceAction { + return includesEvidenceValue(DEPLOY_EVIDENCE_ACTIONS, value); +} + +export function isGitHubEvidenceAction( + value: unknown, +): value is GitHubEvidenceAction { + return includesEvidenceValue(GITHUB_EVIDENCE_ACTIONS, value); +} + +export function isHostEvidenceAction( + value: unknown, +): value is HostEvidenceAction { + return includesEvidenceValue(HOST_EVIDENCE_ACTIONS, value); +} + +export function isArtifactEvidenceKind( + value: unknown, +): value is ArtifactEvidenceKind { + return includesEvidenceValue(ARTIFACT_EVIDENCE_KINDS, value); +} diff --git a/runners/shared/src/index.ts b/runners/shared/src/index.ts index 523795f..c8fe8dd 100644 --- a/runners/shared/src/index.ts +++ b/runners/shared/src/index.ts @@ -10,6 +10,23 @@ export { PAIRED_ROOM_ROLES, type PairedRoomRole, } from './paired-room-role.js'; +export { + ARTIFACT_EVIDENCE_KINDS, + DB_EVIDENCE_ACTIONS, + DEPLOY_EVIDENCE_ACTIONS, + GITHUB_EVIDENCE_ACTIONS, + HOST_EVIDENCE_ACTIONS, + isArtifactEvidenceKind, + isDbEvidenceAction, + isDeployEvidenceAction, + isGitHubEvidenceAction, + isHostEvidenceAction, + type ArtifactEvidenceKind, + type DbEvidenceAction, + type DeployEvidenceAction, + type GitHubEvidenceAction, + type HostEvidenceAction, +} from './evidence-actions.js'; export { extractMarkdownImageAttachments, extractMediaAttachments, diff --git a/runners/shared/test/evidence-actions.test.ts b/runners/shared/test/evidence-actions.test.ts new file mode 100644 index 0000000..3a87a46 --- /dev/null +++ b/runners/shared/test/evidence-actions.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from 'vitest'; + +import { + ARTIFACT_EVIDENCE_KINDS, + DB_EVIDENCE_ACTIONS, + DEPLOY_EVIDENCE_ACTIONS, + GITHUB_EVIDENCE_ACTIONS, + HOST_EVIDENCE_ACTIONS, + isArtifactEvidenceKind, + isDbEvidenceAction, + isDeployEvidenceAction, + isGitHubEvidenceAction, + isHostEvidenceAction, +} from '../src/evidence-actions.js'; + +describe('evidence action constants', () => { + it('keeps host evidence actions as the composed allowlist', () => { + expect(HOST_EVIDENCE_ACTIONS).toEqual([ + 'ejclaw_service_status', + 'ejclaw_service_logs', + 'ejclaw_role_runtime_config', + ...DEPLOY_EVIDENCE_ACTIONS, + ...DB_EVIDENCE_ACTIONS, + ...GITHUB_EVIDENCE_ACTIONS, + ]); + }); + + it('recognizes scoped evidence actions', () => { + expect(isHostEvidenceAction('github_run_jobs')).toBe(true); + expect(isDbEvidenceAction('db_recent_scheduled_tasks')).toBe(true); + expect(isDeployEvidenceAction('ejclaw_artifact_metadata')).toBe(true); + expect(isGitHubEvidenceAction('github_workflow_file')).toBe(true); + expect(isHostEvidenceAction('cat /etc/shadow')).toBe(false); + }); + + it('recognizes artifact evidence kinds', () => { + expect(ARTIFACT_EVIDENCE_KINDS).toContain('runner_dist'); + expect(isArtifactEvidenceKind('runner_dist')).toBe(true); + expect(isArtifactEvidenceKind('../secret')).toBe(false); + }); +}); diff --git a/src/db-evidence.ts b/src/db-evidence.ts index 4e50fd9..44d9379 100644 --- a/src/db-evidence.ts +++ b/src/db-evidence.ts @@ -1,19 +1,16 @@ import type { Database } from 'bun:sqlite'; +import { + DB_EVIDENCE_ACTIONS, + isDbEvidenceAction, + type DbEvidenceAction, +} from 'ejclaw-runners-shared'; import { parseGitHubCiMetadata } from './github-ci.js'; import { extractWatchCiTarget } from './task-watch-status.js'; type SqlBinding = string | number | bigint | boolean | null | Uint8Array; -export const DB_EVIDENCE_ACTIONS = [ - 'db_paired_task_status', - 'db_paired_task_flow', - 'db_recent_paired_failures', - 'db_recent_scheduled_tasks', - 'db_scheduled_task_runs', -] as const; - -export type DbEvidenceAction = (typeof DB_EVIDENCE_ACTIONS)[number]; +export { DB_EVIDENCE_ACTIONS, isDbEvidenceAction, type DbEvidenceAction }; export interface DbEvidenceRequest { action: DbEvidenceAction; @@ -33,13 +30,6 @@ const DEFAULT_ROW_LIMIT = 20; const MAX_ROW_LIMIT = 100; const TASK_ID_PATTERN = /^[A-Za-z0-9._:@/-]{1,200}$/; -export function isDbEvidenceAction(value: unknown): value is DbEvidenceAction { - return ( - typeof value === 'string' && - DB_EVIDENCE_ACTIONS.includes(value as DbEvidenceAction) - ); -} - export function normalizeDbEvidenceMinutes(value?: number): number { if (!Number.isFinite(value)) { return DEFAULT_RECENT_MINUTES; diff --git a/src/deploy-evidence.ts b/src/deploy-evidence.ts index 3da2e61..9c9aec9 100644 --- a/src/deploy-evidence.ts +++ b/src/deploy-evidence.ts @@ -2,22 +2,22 @@ import { createHash } from 'crypto'; import { execFile } from 'child_process'; import fs from 'fs'; import path from 'path'; +import { + ARTIFACT_EVIDENCE_KINDS, + DEPLOY_EVIDENCE_ACTIONS, + isArtifactEvidenceKind, + isDeployEvidenceAction, + type ArtifactEvidenceKind, + type DeployEvidenceAction, +} from 'ejclaw-runners-shared'; -export const DEPLOY_EVIDENCE_ACTIONS = [ - 'ejclaw_deploy_state', - 'ejclaw_artifact_metadata', -] as const; - -export const ARTIFACT_EVIDENCE_KINDS = [ - 'build_outputs', - 'dashboard_dist', - 'runner_dist', - 'android_debug_apk', - 'attachments_dir', -] as const; - -export type DeployEvidenceAction = (typeof DEPLOY_EVIDENCE_ACTIONS)[number]; -export type ArtifactEvidenceKind = (typeof ARTIFACT_EVIDENCE_KINDS)[number]; +export { + ARTIFACT_EVIDENCE_KINDS, + DEPLOY_EVIDENCE_ACTIONS, + isDeployEvidenceAction, + type ArtifactEvidenceKind, + type DeployEvidenceAction, +}; export interface DeployEvidenceRequest { action: DeployEvidenceAction; @@ -35,21 +35,12 @@ const COMMAND_MAX_BUFFER = 1024 * 1024; const MAX_DIR_ENTRIES = 5_000; const MAX_LATEST_FILES = 12; -export function isDeployEvidenceAction( - value: unknown, -): value is DeployEvidenceAction { - return ( - typeof value === 'string' && - DEPLOY_EVIDENCE_ACTIONS.includes(value as DeployEvidenceAction) - ); -} - export function normalizeArtifactEvidenceKind( value?: string, ): ArtifactEvidenceKind { if (!value) return 'build_outputs'; - if (ARTIFACT_EVIDENCE_KINDS.includes(value as ArtifactEvidenceKind)) { - return value as ArtifactEvidenceKind; + if (isArtifactEvidenceKind(value)) { + return value; } throw new Error(`Unsupported artifact evidence kind: ${value}`); } diff --git a/src/github-evidence.ts b/src/github-evidence.ts index 4918571..94dde49 100644 --- a/src/github-evidence.ts +++ b/src/github-evidence.ts @@ -1,14 +1,15 @@ import { execFile } from 'child_process'; +import { + GITHUB_EVIDENCE_ACTIONS, + isGitHubEvidenceAction, + type GitHubEvidenceAction, +} from 'ejclaw-runners-shared'; -export const GITHUB_EVIDENCE_ACTIONS = [ - 'github_pr_status', - 'github_pr_diff_stat', - 'github_run_status', - 'github_run_jobs', - 'github_workflow_file', -] as const; - -export type GitHubEvidenceAction = (typeof GITHUB_EVIDENCE_ACTIONS)[number]; +export { + GITHUB_EVIDENCE_ACTIONS, + isGitHubEvidenceAction, + type GitHubEvidenceAction, +}; export interface GitHubEvidenceRequest { action: GitHubEvidenceAction; @@ -33,15 +34,6 @@ const COMMAND_TIMEOUT_MS = 10_000; const COMMAND_MAX_BUFFER = 2 * 1024 * 1024; const MAX_OUTPUT_CHARS = 24_000; -export function isGitHubEvidenceAction( - value: unknown, -): value is GitHubEvidenceAction { - return ( - typeof value === 'string' && - GITHUB_EVIDENCE_ACTIONS.includes(value as GitHubEvidenceAction) - ); -} - export function normalizeGitHubRepo(value?: string): string { const repo = value?.trim(); if (!repo || repo.includes('..') || !REPO_PATTERN.test(repo)) { diff --git a/src/host-evidence.ts b/src/host-evidence.ts index ee5b4e2..a6c20ac 100644 --- a/src/host-evidence.ts +++ b/src/host-evidence.ts @@ -23,37 +23,24 @@ import { WEB_DASHBOARD, } from './config.js'; import type { AgentType } from './types.js'; +import { + HOST_EVIDENCE_ACTIONS, + isHostEvidenceAction, + type HostEvidenceAction, +} from 'ejclaw-runners-shared'; import { collectArtifactMetadata, collectDeployState, - DEPLOY_EVIDENCE_ACTIONS, - type DeployEvidenceAction, } from './deploy-evidence.js'; -import { - DB_EVIDENCE_ACTIONS, - isDbEvidenceAction, - type DbEvidenceAction, - runDbEvidenceRequest, -} from './db-evidence.js'; +import { isDbEvidenceAction, runDbEvidenceRequest } from './db-evidence.js'; import { requireDatabase } from './db/runtime-database.js'; import { - GITHUB_EVIDENCE_ACTIONS, isGitHubEvidenceAction, runGitHubEvidenceCommand, - type GitHubEvidenceAction, } from './github-evidence.js'; import { resolveGroupIpcPath } from './group-folder.js'; -export const HOST_EVIDENCE_ACTIONS = [ - 'ejclaw_service_status', - 'ejclaw_service_logs', - 'ejclaw_role_runtime_config', - ...DEPLOY_EVIDENCE_ACTIONS, - ...DB_EVIDENCE_ACTIONS, - ...GITHUB_EVIDENCE_ACTIONS, -] as const; - -export type HostEvidenceAction = (typeof HOST_EVIDENCE_ACTIONS)[number]; +export { HOST_EVIDENCE_ACTIONS, isHostEvidenceAction, type HostEvidenceAction }; export interface HostEvidenceRequest { requestId: string; @@ -74,12 +61,7 @@ export interface HostEvidenceRequest { export interface HostEvidenceResult { ok: boolean; - action: - | HostEvidenceAction - | DbEvidenceAction - | DeployEvidenceAction - | GitHubEvidenceAction - | 'ejclaw_room_runtime'; + action: HostEvidenceAction | 'ejclaw_room_runtime'; command: string; stdout: string; stderr: string; @@ -104,15 +86,6 @@ const COMMAND_TIMEOUT_MS = 5_000; const COMMAND_MAX_BUFFER = 1024 * 1024; const PROJECT_ROOT = process.cwd(); -export function isHostEvidenceAction( - value: unknown, -): value is HostEvidenceAction { - return ( - typeof value === 'string' && - HOST_EVIDENCE_ACTIONS.includes(value as HostEvidenceAction) - ); -} - export function clampHostEvidenceTailLines(value?: number): number { if (!Number.isFinite(value)) { return DEFAULT_LOG_TAIL_LINES;