feat(FN-4723): complete Step 1 — add worktrunk transition classifier

Fusion-Task-Id: FN-4723
Fusion-Task-Lineage: b79b42a0-c65d-4e02-938f-d1ffabc272ee
This commit is contained in:
Fusion (runfusion.ai)
2026-05-16 06:49:44 -07:00
committed by gsxdsm
parent 6afd2737ad
commit 43e3fbf60d
4 changed files with 41 additions and 2 deletions

View File

@@ -1,6 +1,10 @@
import { describe, expect, it } from "vitest";
import { DEFAULT_GLOBAL_SETTINGS, DEFAULT_PROJECT_SETTINGS } from "../settings-schema.js";
import { resolveWorktrunkSettings, validateWorktrunkSettings } from "../worktrunk-settings.js";
import {
resolveWorktrunkSettings,
requiresWorktrunkInstallVerification,
validateWorktrunkSettings,
} from "../worktrunk-settings.js";
describe("settings defaults invariants", () => {
it("keeps worktrunk default off in global and project defaults", () => {
@@ -25,4 +29,15 @@ describe("settings defaults invariants", () => {
it("does not implicitly enable worktrunk when validating undefined", () => {
expect(validateWorktrunkSettings(undefined)).toEqual({});
});
it("flags off→on transition from fresh defaults", () => {
const freshProject = resolveWorktrunkSettings(DEFAULT_GLOBAL_SETTINGS.worktrunk, DEFAULT_PROJECT_SETTINGS.worktrunk);
expect(freshProject.enabled).toBe(false);
expect(
requiresWorktrunkInstallVerification({
current: freshProject,
next: { ...freshProject, enabled: true },
}),
).toBe(true);
});
});

View File

@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
import {
resolveWorktrunkSettings,
requiresWorktrunkInstallVerification,
validateWorktrunkSettings,
} from "../worktrunk-settings.js";
@@ -40,6 +41,14 @@ describe("worktrunk-settings", () => {
});
});
it("classifies enable transitions that require install verification", () => {
expect(requiresWorktrunkInstallVerification({ current: { enabled: false }, next: { enabled: true } })).toBe(true);
expect(requiresWorktrunkInstallVerification({ current: { enabled: true }, next: { enabled: true } })).toBe(false);
expect(requiresWorktrunkInstallVerification({ current: { enabled: true }, next: { enabled: false } })).toBe(false);
expect(requiresWorktrunkInstallVerification({ current: undefined, next: undefined })).toBe(false);
expect(requiresWorktrunkInstallVerification({ current: undefined, next: { enabled: true } })).toBe(true);
});
it("validator rejects invalid values", () => {
expect(() => validateWorktrunkSettings({ onFailure: "ignore" })).toThrow(
"worktrunk.onFailure must be one of",

View File

@@ -1,7 +1,12 @@
export { COLUMNS, DEFAULT_COLUMN, isColumn, normalizeColumn, COLUMN_LABELS, COLUMN_DESCRIPTIONS, VALID_TRANSITIONS, DEFAULT_SETTINGS, DEFAULT_GLOBAL_SETTINGS, DEFAULT_PROJECT_SETTINGS, GLOBAL_SETTINGS_KEYS, PROJECT_SETTINGS_KEYS, isGlobalSettingsKey, isProjectSettingsKey, resolvePersistAgentThinkingLog, THINKING_LEVELS, THEME_MODES, COLOR_THEMES, WORKFLOW_STEP_TEMPLATES, AGENT_PERMISSIONS, PERMANENT_AGENT_ACTION_CATEGORIES, AGENT_PERMISSION_POLICY_ACTION_CATEGORIES, AGENT_PROVISIONING_APPROVAL_MODES, SANDBOX_PROVISIONING_APPROVAL_MODES, AGENT_PERMISSION_POLICY_PRESET_IDS, LEGACY_AGENT_PERMISSION_POLICY_ACTION_CATEGORY_ALIASES, APPROVAL_REQUEST_STATUSES, APPROVAL_REQUEST_AUDIT_EVENT_TYPES, normalizeApprovalRequestActionCategory, isValidApprovalRequestTransition, agentToConfigSnapshot, diffConfigSnapshots, isEphemeralAgent, hasAgentIdentity, CheckoutConflictError, DEFAULT_HEARTBEAT_PROCEDURE_PATH, getDefaultHeartbeatProcedurePath, EXECUTION_MODES, DEFAULT_EXECUTION_MODE, TASK_PRIORITIES, DEFAULT_TASK_PRIORITY, HIGH_FANOUT_BLOCKER_TODO_THRESHOLD, STALE_HIGH_FANOUT_BLOCKER_AGE_THRESHOLD_MS, DASHBOARD_USER_ID, normalizeMessageParticipant, validateMessageMetadata, validateDockerNodeConfig, sanitizeDockerNodeConfigForResponse, normalizeMergeConflictStrategy, normalizeMergeStrategyOverlapBehavior, normalizePostMergeAuditMode, POST_MERGE_AUDIT_MODES, normalizeMergeAuditAutoRecovery, MERGE_AUDIT_AUTO_RECOVERY_MODES, normalizeAutoRecovery, AUTO_RECOVERY_MODES, buildResearchDocumentKey, REPO_OVERRIDE_RE, SHARED_STATE_SNAPSHOT_VERSION } from "./types.js";
export type { Column, IssueInfo, IssueState, TaskSourceIssue, PrInfo, PrStatus, Task, TaskTokenUsage, TaskAttachment, TaskComment, TaskCommentInput, TaskDocument, TaskDocumentRevision, TaskDocumentCreateInput, TaskDocumentWithTask, TaskCreateInput, MeshReplicatedTaskCreatePayload, MeshReplicatedTaskApplyResult, TaskSource, SourceType, TaskDetail, RetrySummary, InboxTask, TodoList, TodoItem, TodoListCreateInput, TodoListUpdateInput, TodoItemCreateInput, TodoItemUpdateInput, TodoListWithItems, AgentLogEntry, AgentLogType, AgentRole, BoardConfig, DistributedTaskIdReserveInput, DistributedTaskIdReserveResult, DistributedTaskIdCommitInput, DistributedTaskIdCommitResult, DistributedTaskIdAbortInput, DistributedTaskIdAbortResult, DistributedTaskIdStateInput, DistributedTaskIdStateResult, AutostashOrphanRecord, AutostashOutcome, MergeDetails, MergeResult, MergeConflictStrategy, CanonicalMergeConflictStrategy, MergeStrategyOverlapBehavior, PostMergeAuditMode, MergeAuditAutoRecoveryMode, AutoRecoveryMode, AutoRecoveryFailureClass, AutoRecoverySettings, DirectMergeCommitStrategy, Settings, GlobalSettings, ProjectSettings, WebSearchBackend, ResearchEnabledSources, ResearchGlobalDefaults, ResearchProjectLimits, ResearchProjectSettings, SandboxBackendName, SandboxFailureMode, SandboxPolicy, SandboxProjectSettings, EvalFollowUpPolicy, EvalProjectSettings, ResolvedEvalSettings, SettingsScope, DaemonTokenSettings, TaskStep, StepStatus, TaskLogEntry, RunMutationContext, ActivityLogEntry, ActivityEventType, ThinkingLevel, ThemeMode, ColorTheme, ExecutionMode, TaskPriority, UnavailableNodePolicy, PlanningQuestion, PlanningSummary, PlanningResponse, PlanningQuestionType, ArchivedTaskEntry, BatchStatusRequest, BatchStatusResponse, BatchStatusEntry, BatchStatusResult, GithubIssueAction, ModelPreset, WorkflowStep, WorkflowStepMode, WorkflowStepGateMode, WorkflowStepPhase, WorkflowStepInput, WorkflowStepResult, WorkflowStepTemplate, Agent, OrgTreeNode, AgentState, AgentDetail, AgentCreateInput, AgentUpdateInput, AgentApiKey, AgentApiKeyCreateResult, AgentCapability, AgentPromptTemplate, AgentPromptsConfig, AgentPermission, PermanentAgentActionCategory, PermanentAgentSensitiveActionCategory, PermanentAgentGatingContext, AgentPermissionPolicy, AgentPermissionPolicyRules, AgentPermissionPolicyActionCategory, AgentProvisioningApprovalMode, SandboxProvisioningApprovalMode, LegacyAgentPermissionPolicyActionCategory, ApprovalRequestActionCategoryInput, ApprovalRequestActionCategory, AgentPermissionPolicyDisposition, AgentPermissionPolicyPresetId, ApprovalRequestStatus, ApprovalRequestAuditEventType, ApprovalRequestActorSnapshot, ApprovalRequestTargetAction, ApprovalRequestAuditEvent, ApprovalRequest, ApprovalRequestCreateInput, ApprovalRequestDecisionInput, ApprovalRequestCompletionInput, ApprovalRequestListInput, TaskAssignSource, AgentAccessState, AgentHeartbeatConfig, AgentBudgetConfig, AgentBudgetStatus, InstructionsBundleConfig, MessageResponseMode, AgentHeartbeatEvent, AgentHeartbeatRun, BlockedStateSnapshot, HeartbeatInvocationSource, AgentTaskSession, AgentRating, AgentRatingSummary, AgentRatingInput, AgentConfigSnapshot, RevisionFieldDiff, AgentConfigRevision, AgentStats, ReflectionTrigger, ReflectionMetrics, AgentReflection, AgentPerformanceSummary, NtfyNotificationEvent, NotificationEvent, NotificationPayload, NotificationProviderConfig, CustomProvider, SteeringComment, ParticipantType, MessageType, Message, MessageCreateInput, MessageFilter, MessageMetadata, MessageReplyReference, Mailbox, CheckoutLease, RunAuditDomain, RunAuditEvent, RunAuditEventInput, RunAuditEventFilter, AgentMemoryInclusionMode, HeartbeatPromptTemplate, HeartbeatScopeDisciplineMode, WorktrunkSettings, WorktrunkOnFailure } from "./types.js";
export { AGENT_VALID_TRANSITIONS } from "./types.js";
export { resolveWorktrunkSettings, validateWorktrunkSettings, DEFAULT_WORKTRUNK_SETTINGS } from "./worktrunk-settings.js";
export {
resolveWorktrunkSettings,
requiresWorktrunkInstallVerification,
validateWorktrunkSettings,
DEFAULT_WORKTRUNK_SETTINGS,
} from "./worktrunk-settings.js";
export {
resolveAgentMemoryInclusionMode,
type AgentMemoryInclusionModeSource,

View File

@@ -38,6 +38,16 @@ export function resolveWorktrunkSettings(
};
}
/** Strict validator used by GlobalSettingsStore writes and CLI parsing. */
export function requiresWorktrunkInstallVerification(params: {
current: WorktrunkSettings | undefined;
next: WorktrunkSettings | undefined;
}): boolean {
const currentEnabled = params.current?.enabled ?? DEFAULT_WORKTRUNK_SETTINGS.enabled;
const nextEnabled = params.next?.enabled ?? DEFAULT_WORKTRUNK_SETTINGS.enabled;
return currentEnabled !== true && nextEnabled === true;
}
/** Strict validator used by GlobalSettingsStore writes and CLI parsing. */
export function validateWorktrunkSettings(value: unknown): WorktrunkSettings {
if (value === undefined || value === null) {