@mastra/factory 0.13.0-alpha.6 → 0.13.0-alpha.7
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/README.md +35 -0
- package/dist/boards/index.d.ts +4 -3
- package/dist/boards/index.d.ts.map +1 -1
- package/dist/boards/index.js +2 -1
- package/dist/boards/index.js.map +1 -1
- package/dist/boards/registry.d.ts +16 -0
- package/dist/boards/registry.d.ts.map +1 -0
- package/dist/boards/registry.js +27 -0
- package/dist/boards/registry.js.map +1 -0
- package/dist/boards/review.js +1 -1
- package/dist/boards/work.js +2 -2
- package/dist/factory.d.ts +6 -1
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +10 -0
- package/dist/factory.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/integrations/github/default-rules.d.ts +194 -0
- package/dist/integrations/github/default-rules.d.ts.map +1 -0
- package/dist/integrations/github/default-rules.js +230 -0
- package/dist/integrations/github/default-rules.js.map +1 -0
- package/dist/integrations/github/integration.d.ts +4 -0
- package/dist/integrations/github/integration.d.ts.map +1 -1
- package/dist/integrations/github/integration.js +6 -0
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/rules.d.ts +3 -1
- package/dist/integrations/github/rules.d.ts.map +1 -1
- package/dist/integrations/github/rules.js +1 -2
- package/dist/integrations/github/rules.js.map +1 -1
- package/dist/integrations/linear/rules.js +1 -1
- package/dist/integrations/platform/github/integration.d.ts +4 -0
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +6 -0
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/integrations/slack/slack.d.ts +3 -1
- package/dist/integrations/slack/slack.d.ts.map +1 -1
- package/dist/integrations/slack/slack.js +19 -1
- package/dist/integrations/slack/slack.js.map +1 -1
- package/dist/routes/surface.d.ts +3 -0
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +2 -1
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/work-items.d.ts +3 -0
- package/dist/routes/work-items.d.ts.map +1 -1
- package/dist/routes/work-items.js +48 -15
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/defaults.d.ts.map +1 -1
- package/dist/rules/defaults.js +1 -229
- package/dist/rules/defaults.js.map +1 -1
- package/dist/rules/index.d.ts +2 -2
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +2 -2
- package/dist/rules/processor.d.ts +2 -0
- package/dist/rules/processor.d.ts.map +1 -1
- package/dist/rules/processor.js +9 -7
- package/dist/rules/processor.js.map +1 -1
- package/dist/rules/resolve.d.ts +4 -4
- package/dist/rules/resolve.d.ts.map +1 -1
- package/dist/rules/resolve.js +4 -6
- package/dist/rules/resolve.js.map +1 -1
- package/dist/rules/transition-service.d.ts +2 -0
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +16 -10
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/rules/types.d.ts +3 -8
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/types.js +1 -1
- package/dist/rules/types.js.map +1 -1
- package/dist/rules/validation.d.ts.map +1 -1
- package/dist/rules/validation.js +1 -9
- package/dist/rules/validation.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +11 -1
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +22 -2
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/factory-skills/configure-factory-rules/SKILL.md +12 -4
- package/package.json +6 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transition-service.js","names":["#rules","#storage","#timeoutMs","#onTerminalStage","#terminalCleanupTimeoutMs","#onAccepted","#commitRejection","#commit"],"sources":["../../src/rules/transition-service.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport { allowsBuiltInBoardTransition, builtInBoard } from '../boards/index.js';\nimport type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { resolveFactoryStageRules } from './resolve.js';\nimport type {\n FactoryCommitDecision,\n FactoryRuleActor,\n FactoryRuleBoard,\n FactoryRuleCausalEntry,\n FactoryRuleRejectionCode,\n FactoryRuleStage,\n FactoryTriageType,\n FactoryRules,\n FactoryStageRuleContext,\n FactoryTransitionResult,\n} from './types.js';\nimport {\n externallyAuthoredWorkItem,\n factoryRuleSourceForWorkItem,\n isFactoryRuleStage,\n isWorkingFactoryRuleStage,\n workItemSource,\n} from './types.js';\nimport {\n MAX_FACTORY_RULE_CAUSAL_DEPTH,\n validateFactoryRuleDecision,\n validateFactoryRuleDecisions,\n} from './validation.js';\n\nconst RULE_TIMEOUT_MS = 5_000;\nconst MAX_REJECTION_REASON = 512;\nconst TERMINAL_STAGES: ReadonlySet<FactoryRuleStage> = new Set(['done', 'canceled']);\n/** Longest a committed transition waits for terminal resource cleanup. Cleanup\n * reattaches remote sandboxes, so a hung provider call must not leave the\n * already-committed transition request pending; past this bound the cleanup\n * keeps running in the background as pure best-effort. */\nconst TERMINAL_CLEANUP_TIMEOUT_MS = 30_000;\n\nexport interface FactoryTransitionRequest {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n board: FactoryRuleBoard;\n stage: FactoryRuleStage;\n expectedRevision: number;\n actor: FactoryRuleActor;\n ingress: { type: 'human' | 'agent' | 'toolResult' | 'github' | 'rule'; identity: string; transitionId?: string };\n cause: string;\n causalChain?: readonly FactoryRuleCausalEntry[];\n /** Internal materialization path: evaluate only the destination onEnter leaf even when already at that stage. */\n initialEntry?: boolean;\n /** Re-runs the stage's entry rules when the item already holds that stage, to restart work the entry invalidated. */\n reenter?: boolean;\n /** Structured verdict required from a bound triage-agent terminal request. */\n triageType?: FactoryTriageType;\n}\n\nexport interface FactoryTransitionServiceOptions {\n rules: FactoryRules;\n storage: WorkItemsStorage;\n timeoutMs?: number;\n /**\n * Called after a transition commits into a terminal stage (`done` /\n * `canceled`) — the point where the item's sessions stop receiving runs, so\n * resources they hold (e.g. sandboxes) can be released for reuse. Awaited,\n * but failures are swallowed: releasing resources must never break or roll\n * back the committed transition.\n */\n onTerminalStage?: (args: {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n stage: FactoryRuleStage;\n revision: number;\n }) => Promise<void> | void;\n /** Upper bound on how long a committed transition waits for\n * `onTerminalStage` before returning (default 30s). The cleanup continues\n * in the background past the bound. */\n terminalCleanupTimeoutMs?: number;\n /**\n * Called after a transition first records a person's acceptance of a\n * non-bug item (see `WorkItemRow.acceptedAt`). Fire-and-forget: failures\n * are swallowed, the committed transition never depends on it.\n */\n onAccepted?: (args: {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n item: WorkItemRow;\n }) => Promise<void> | void;\n}\n\nfunction rejection(\n transitionId: string,\n itemId: string,\n code: FactoryRuleRejectionCode,\n reason: string,\n): FactoryTransitionResult {\n return { status: 'rejected', transitionId, itemId, code, reason: reason.slice(0, MAX_REJECTION_REASON) };\n}\n\nfunction actorId(actor: FactoryRuleActor): string {\n switch (actor.type) {\n case 'human':\n case 'system':\n return actor.id;\n case 'agent':\n return `agent:${actor.bindingId}`;\n case 'github':\n return `github:${actor.login}`;\n }\n}\n\nexport function currentStage(stages: readonly string[]): FactoryRuleStage | undefined {\n if (stages.length !== 1) return undefined;\n const stage = stages[0];\n return isFactoryRuleStage(stage) ? stage : undefined;\n}\n\nexport function roleForStage(board: FactoryRuleBoard, stage: FactoryRuleStage): string {\n if (board === 'review') return 'review';\n if (stage === 'triage') return 'triage';\n if (stage === 'planning') return 'plan';\n return 'work';\n}\n\ninterface TransitionConsentOptions {\n autonomy?: 'arm' | 'disarm';\n consentedBy?: string;\n accept?: boolean;\n}\n\n// Entering a resting lane disarms whoever rests it; only a person's move into a working lane arms.\nfunction transitionConsent(stage: FactoryRuleStage, humanMove: boolean): 'arm' | 'disarm' | undefined {\n if (!isWorkingFactoryRuleStage(stage)) return 'disarm';\n return humanMove ? 'arm' : undefined;\n}\n\n// An event arriving as data (GitHub, sweeps) never pre-approves the runs its transition queues.\nfunction bearsConsent(actor: FactoryRuleActor): boolean {\n return actor.type === 'human' || actor.type === 'agent';\n}\n\n// Rides the transition's own revision-checked commit, so a stale or rejected commit flips nothing.\nfunction consentEffect(request: FactoryTransitionRequest, humanMove: boolean): TransitionConsentOptions {\n const autonomy = transitionConsent(request.stage, humanMove);\n return bearsConsent(request.actor) ? { autonomy, consentedBy: actorId(request.actor) } : { autonomy };\n}\n\ntype RunStartDecision = Extract<FactoryCommitDecision, { type: 'invokeSkill' | 'sendMessage' }>;\n\nfunction startsRun(decision: FactoryCommitDecision): decision is RunStartDecision {\n return decision.type === 'invokeSkill' || (decision.type === 'sendMessage' && decision.prepareBinding === true);\n}\n\n// Answering a recorded run start, or the role's own mid-run agent, with a run would start a second one.\nfunction runAlreadyUnderway(request: FactoryTransitionRequest, decision: RunStartDecision): boolean {\n if (request.cause === 'run_start') return true;\n return request.actor.type === 'agent' && request.actor.role === decision.role;\n}\n\nfunction stageTransitionMessage(fromStage: FactoryRuleStage, toStage: FactoryRuleStage): string {\n return `This work was moved from the ${fromStage} stage to the ${toStage} stage.`;\n}\n\nfunction isTriageAgent(actor: FactoryRuleActor): actor is Extract<FactoryRuleActor, { type: 'agent' }> {\n return actor.type === 'agent' && actor.role === 'triage';\n}\n\nfunction isHumanTransition(request: FactoryTransitionRequest): boolean {\n return request.actor.type === 'human' && request.ingress.type === 'human';\n}\n\nfunction requiresHumanApproval(triageType: FactoryTriageType | null | undefined): boolean {\n return triageType !== undefined && triageType !== null && triageType !== 'bug';\n}\n\nfunction isAtRest(stage: FactoryRuleStage): boolean {\n return stage === 'intake' || stage === 'triage';\n}\n\nfunction entersWork(stage: FactoryRuleStage): boolean {\n return stage === 'planning' || stage === 'execute';\n}\n\n// A person moving a card into Planning/Execute is the approval gesture —\n// recorded once, so later agent hops need no second nod. Not limited to moves\n// out of rest: a card accepted before acceptance was recorded still gets its\n// stamp (and its label reconciled) the next time a person moves it forward.\nfunction acceptsItem(request: FactoryTransitionRequest): boolean {\n return isHumanTransition(request) && entersWork(request.stage);\n}\n\nfunction ruleFailure(error: unknown): { code: FactoryRuleRejectionCode; reason: string } {\n return {\n code: 'rule_error',\n reason: error instanceof Error ? `Factory rule failed: ${error.message}` : 'Factory rule failed.',\n };\n}\n\nasync function withRuleTimeout<T>(operation: Promise<T>, timeoutMs: number): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error('FACTORY_RULE_TIMEOUT')), timeoutMs);\n });\n try {\n return await Promise.race([operation, timeout]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\nexport class FactoryTransitionService {\n readonly #rules: FactoryRules;\n readonly #storage: WorkItemsStorage;\n readonly #timeoutMs: number;\n readonly #onTerminalStage: FactoryTransitionServiceOptions['onTerminalStage'];\n readonly #terminalCleanupTimeoutMs: number;\n readonly #onAccepted: FactoryTransitionServiceOptions['onAccepted'];\n\n constructor(options: FactoryTransitionServiceOptions) {\n this.#rules = options.rules;\n this.#storage = options.storage;\n this.#timeoutMs = options.timeoutMs ?? RULE_TIMEOUT_MS;\n this.#onTerminalStage = options.onTerminalStage;\n this.#onAccepted = options.onAccepted;\n this.#terminalCleanupTimeoutMs = options.terminalCleanupTimeoutMs ?? TERMINAL_CLEANUP_TIMEOUT_MS;\n }\n\n get ruleSetVersion(): string {\n return this.#rules.version;\n }\n\n async transition(request: FactoryTransitionRequest): Promise<FactoryTransitionResult> {\n const replay = await this.#storage.getTransitionResultByIngress(\n request.orgId,\n request.factoryProjectId,\n request.ingress.identity,\n );\n if (replay) return replay as unknown as FactoryTransitionResult;\n\n const transitionId = request.ingress.transitionId ?? randomUUID();\n const item = await this.#storage.get({ orgId: request.orgId, id: request.workItemId });\n if (!item) {\n return this.#commitRejection(request, transitionId, 'invalid_transition', 'Work item not found.');\n }\n\n if (request.causalChain && request.causalChain.length > MAX_FACTORY_RULE_CAUSAL_DEPTH) {\n return this.#commitRejection(\n request,\n transitionId,\n 'causal_depth_exceeded',\n 'Factory rule causal depth exceeded.',\n );\n }\n const itemSource = workItemSource(item.externalSource);\n const source = factoryRuleSourceForWorkItem(itemSource);\n if ((request.board === 'review') !== (source === 'pullRequest')) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n 'The work item does not belong to the requested board.',\n );\n }\n const fromStage = currentStage(item.stages);\n if (!fromStage) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n 'The work item does not have one canonical Factory stage.',\n );\n }\n if (!allowsBuiltInBoardTransition(request.board, fromStage, request.stage)) {\n const board = builtInBoard(request.board);\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n `The ${board.title} board does not allow moving from ${fromStage} to ${request.stage}.`,\n );\n }\n\n if (isTriageAgent(request.actor) && request.triageType === undefined) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n 'Triage transitions must report a structured triage classification.',\n );\n }\n if (item.triageType && request.triageType && item.triageType !== request.triageType) {\n return this.#commitRejection(\n request,\n transitionId,\n 'forbidden',\n 'The persisted triage classification cannot be changed by a later transition.',\n );\n }\n // The gate stands at the exit of rest. A non-bug card already in\n // Planning/Execute can only have been put there by a person, so an agent\n // carrying it further (plan → build) is not asked for a second nod even\n // when the acceptance stamp predates its recording.\n const triageType = item.triageType ?? request.triageType;\n if (\n requiresHumanApproval(triageType) &&\n entersWork(request.stage) &&\n isAtRest(fromStage) &&\n !isHumanTransition(request) &&\n !item.acceptedAt\n ) {\n return this.#commitRejection(\n request,\n transitionId,\n 'approval_required',\n 'A maintainer must move this non-bug work item into Planning or Execute from the Factory UI.',\n );\n }\n\n // The card's own content can steer a bound agent; on a card authored\n // outside the write-access circle, leaving rest takes a person's gesture.\n if (\n request.actor.type === 'agent' &&\n !isWorkingFactoryRuleStage(fromStage) &&\n isWorkingFactoryRuleStage(request.stage) &&\n externallyAuthoredWorkItem(item)\n ) {\n return this.#commitRejection(\n request,\n transitionId,\n 'approval_required',\n 'This card comes from outside the write-access circle; a person must resume it from the Factory board.',\n );\n }\n\n // The coordinator's own self-move at run start would otherwise inject a second run's kickoff.\n const humanMove = request.actor.type === 'human' && fromStage !== request.stage && request.cause !== 'run_start';\n\n const contextBase = {\n tenant: { orgId: request.orgId, projectId: request.factoryProjectId },\n actor: request.actor,\n ingress: { type: request.ingress.type, id: request.ingress.identity },\n cause: request.cause,\n causalChain: request.causalChain ?? [],\n ruleSetVersion: this.#rules.version,\n item: {\n id: item.id,\n source: itemSource,\n sourceKey: item.externalSource\n ? `${item.externalSource.integrationId}:${item.externalSource.type}:${item.externalSource.externalId}`\n : null,\n parentWorkItemId: item.parentWorkItemId,\n title: item.title,\n url: item.externalSource?.url ?? null,\n stages: [...item.stages],\n acceptedAt: item.acceptedAt,\n metadata: item.metadata,\n },\n board: request.board,\n itemRevision: item.revision,\n source,\n fromStage,\n toStage: request.stage,\n } satisfies Omit<FactoryStageRuleContext, 'stage'>;\n\n let evaluation:\n | { outcome: 'accepted'; decisions: Record<string, unknown>[] }\n | { outcome: 'rejected'; code: string; reason: string };\n try {\n evaluation = await withRuleTimeout(\n (async () => {\n const decisions: FactoryCommitDecision[] = [];\n for (const rule of resolveFactoryStageRules(this.#rules, {\n board: request.board,\n source,\n fromStage,\n toStage: request.stage,\n initialEntry: request.initialEntry,\n reenter: request.reenter,\n })) {\n const context: FactoryStageRuleContext = Object.freeze({\n ...contextBase,\n stage: rule.phase === 'exit' ? fromStage : request.stage,\n });\n const raw = await rule.handler(context);\n if (raw === undefined) continue;\n const decision = validateFactoryRuleDecision(raw, context.causalChain.length);\n if (decision.type === 'reject') {\n return { outcome: 'rejected' as const, code: decision.code, reason: decision.reason };\n }\n if (startsRun(decision) && runAlreadyUnderway(request, decision)) continue;\n decisions.push(decision);\n }\n const validated = validateFactoryRuleDecisions(decisions);\n if (humanMove) {\n const message = stageTransitionMessage(fromStage, request.stage);\n const skill = validated.find(decision => decision.type === 'invokeSkill');\n if (skill) {\n skill.precedingMessage = message;\n } else {\n validated.unshift({\n type: 'sendMessage',\n idempotencyKey: `factory-stage:${transitionId}`,\n message,\n priority: 'urgent',\n idleBehavior: 'wake',\n // Parking a card says stop: no seat is right by construction, so\n // the notice goes to whichever session is live — or nobody.\n ...(isWorkingFactoryRuleStage(request.stage)\n ? { role: roleForStage(request.board, request.stage), prepareBinding: true }\n : {}),\n });\n }\n }\n return {\n outcome: 'accepted' as const,\n decisions: validateFactoryRuleDecisions(validated) as unknown as Record<string, unknown>[],\n };\n })(),\n this.#timeoutMs,\n );\n } catch (error) {\n const failed =\n error instanceof Error && error.message === 'FACTORY_RULE_TIMEOUT'\n ? { code: 'timeout' as const, reason: 'Factory rule evaluation timed out.' }\n : ruleFailure(error);\n evaluation = { outcome: 'rejected', ...failed };\n }\n return this.#commit(\n request,\n transitionId,\n evaluation,\n evaluation.outcome === 'accepted'\n ? { ...consentEffect(request, humanMove), accept: acceptsItem(request) && !item.acceptedAt }\n : {},\n );\n }\n\n async #commitRejection(\n request: FactoryTransitionRequest,\n transitionId: string,\n code: FactoryRuleRejectionCode,\n reason: string,\n ): Promise<FactoryTransitionResult> {\n return this.#commit(request, transitionId, { outcome: 'rejected', code, reason });\n }\n\n async #commit(\n request: FactoryTransitionRequest,\n transitionId: string,\n evaluation:\n | { outcome: 'accepted'; decisions: Record<string, unknown>[] }\n | { outcome: 'rejected'; code: string; reason: string },\n options: TransitionConsentOptions = {},\n ): Promise<FactoryTransitionResult> {\n const committed = await this.#storage.commitTransition({\n autonomy: options.autonomy,\n consentedBy: options.consentedBy,\n ...(options.accept ? { accept: true } : {}),\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n workItemId: request.workItemId,\n expectedRevision: request.expectedRevision,\n destinationStage: request.stage,\n actorId: actorId(request.actor),\n ingress: { identity: request.ingress.identity, triggerType: request.ingress.type, transitionId },\n ruleSetVersion: this.#rules.version,\n causalChain: [...(request.causalChain ?? [])],\n evaluation,\n ...(isTriageAgent(request.actor) && request.triageType ? { triageType: request.triageType } : {}),\n });\n if (committed.status === 'missing') {\n return rejection(transitionId, request.workItemId, 'invalid_transition', 'Work item not found.');\n }\n const result = committed.result as unknown as FactoryTransitionResult;\n if (\n this.#onAccepted &&\n options.accept &&\n committed.status === 'committed' &&\n result.status === 'accepted' &&\n committed.item?.acceptedAt\n ) {\n const item = committed.item;\n void Promise.resolve(\n this.#onAccepted({\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n workItemId: request.workItemId,\n item,\n }),\n ).catch(error => {\n console.warn(`[factory] acceptance hook failed for work item ${request.workItemId}:`, error);\n });\n }\n if (this.#onTerminalStage && result.status === 'accepted' && TERMINAL_STAGES.has(result.stage)) {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n const cleanup = Promise.resolve(\n this.#onTerminalStage({\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n workItemId: request.workItemId,\n stage: result.stage,\n revision: result.revision,\n }),\n );\n // A late rejection after the timeout wins the race must not surface\n // as an unhandled rejection.\n cleanup.catch(() => {});\n await Promise.race([\n cleanup,\n new Promise<void>(resolve => {\n timer = setTimeout(resolve, this.#terminalCleanupTimeoutMs);\n }),\n ]);\n } catch {\n // Resource release is best-effort — never fail a committed transition.\n } finally {\n clearTimeout(timer);\n }\n }\n return result;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"transition-service.js","names":["#rules","#boards","#storage","#timeoutMs","#onTerminalStage","#terminalCleanupTimeoutMs","#onAccepted","#commitRejection","#commit"],"sources":["../../src/rules/transition-service.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nimport { createBoardRegistry } from '../boards/index.js';\nimport type { BoardRegistry } from '../boards/index.js';\nimport type { WorkItemRow, WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { resolveFactoryStageRules } from './resolve.js';\nimport type {\n FactoryCommitDecision,\n FactoryRuleActor,\n FactoryRuleBoard,\n FactoryRuleCausalEntry,\n FactoryRuleRejectionCode,\n FactoryRuleStage,\n FactoryTriageType,\n FactoryRules,\n FactoryStageRuleContext,\n FactoryTransitionResult,\n} from './types.js';\nimport {\n externallyAuthoredWorkItem,\n factoryRuleSourceForWorkItem,\n isFactoryRuleStage,\n isWorkingFactoryRuleStage,\n workItemSource,\n} from './types.js';\nimport {\n MAX_FACTORY_RULE_CAUSAL_DEPTH,\n validateFactoryRuleDecision,\n validateFactoryRuleDecisions,\n} from './validation.js';\n\nconst RULE_TIMEOUT_MS = 5_000;\nconst MAX_REJECTION_REASON = 512;\nconst TERMINAL_STAGES: ReadonlySet<FactoryRuleStage> = new Set(['done', 'canceled']);\n/** Longest a committed transition waits for terminal resource cleanup. Cleanup\n * reattaches remote sandboxes, so a hung provider call must not leave the\n * already-committed transition request pending; past this bound the cleanup\n * keeps running in the background as pure best-effort. */\nconst TERMINAL_CLEANUP_TIMEOUT_MS = 30_000;\n\nexport interface FactoryTransitionRequest {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n board: FactoryRuleBoard;\n stage: FactoryRuleStage;\n expectedRevision: number;\n actor: FactoryRuleActor;\n ingress: { type: 'human' | 'agent' | 'toolResult' | 'github' | 'rule'; identity: string; transitionId?: string };\n cause: string;\n causalChain?: readonly FactoryRuleCausalEntry[];\n /** Internal materialization path: evaluate only the destination onEnter leaf even when already at that stage. */\n initialEntry?: boolean;\n /** Re-runs the stage's entry rules when the item already holds that stage, to restart work the entry invalidated. */\n reenter?: boolean;\n /** Structured verdict required from a bound triage-agent terminal request. */\n triageType?: FactoryTriageType;\n}\n\nexport interface FactoryTransitionServiceOptions {\n rules: FactoryRules;\n storage: WorkItemsStorage;\n boards?: BoardRegistry;\n timeoutMs?: number;\n /**\n * Called after a transition commits into a terminal stage (`done` /\n * `canceled`) — the point where the item's sessions stop receiving runs, so\n * resources they hold (e.g. sandboxes) can be released for reuse. Awaited,\n * but failures are swallowed: releasing resources must never break or roll\n * back the committed transition.\n */\n onTerminalStage?: (args: {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n stage: FactoryRuleStage;\n revision: number;\n }) => Promise<void> | void;\n /** Upper bound on how long a committed transition waits for\n * `onTerminalStage` before returning (default 30s). The cleanup continues\n * in the background past the bound. */\n terminalCleanupTimeoutMs?: number;\n /**\n * Called after a transition first records a person's acceptance of a\n * non-bug item (see `WorkItemRow.acceptedAt`). Fire-and-forget: failures\n * are swallowed, the committed transition never depends on it.\n */\n onAccepted?: (args: {\n orgId: string;\n factoryProjectId: string;\n workItemId: string;\n item: WorkItemRow;\n }) => Promise<void> | void;\n}\n\nfunction rejection(\n transitionId: string,\n itemId: string,\n code: FactoryRuleRejectionCode,\n reason: string,\n): FactoryTransitionResult {\n return { status: 'rejected', transitionId, itemId, code, reason: reason.slice(0, MAX_REJECTION_REASON) };\n}\n\nfunction actorId(actor: FactoryRuleActor): string {\n switch (actor.type) {\n case 'human':\n case 'system':\n return actor.id;\n case 'agent':\n return `agent:${actor.bindingId}`;\n case 'github':\n return `github:${actor.login}`;\n }\n}\n\nexport function currentStage(stages: readonly string[]): FactoryRuleStage | undefined {\n if (stages.length !== 1) return undefined;\n const stage = stages[0];\n return isFactoryRuleStage(stage) ? stage : undefined;\n}\n\nexport function roleForStage(board: FactoryRuleBoard, stage: FactoryRuleStage): string {\n if (board === 'review') return 'review';\n if (stage === 'triage') return 'triage';\n if (stage === 'planning') return 'plan';\n return 'work';\n}\n\ninterface TransitionConsentOptions {\n autonomy?: 'arm' | 'disarm';\n consentedBy?: string;\n accept?: boolean;\n}\n\n// Entering a resting lane disarms whoever rests it; only a person's move into a working lane arms.\nfunction transitionConsent(stage: FactoryRuleStage, humanMove: boolean): 'arm' | 'disarm' | undefined {\n if (!isWorkingFactoryRuleStage(stage)) return 'disarm';\n return humanMove ? 'arm' : undefined;\n}\n\n// An event arriving as data (GitHub, sweeps) never pre-approves the runs its transition queues.\nfunction bearsConsent(actor: FactoryRuleActor): boolean {\n return actor.type === 'human' || actor.type === 'agent';\n}\n\n// Rides the transition's own revision-checked commit, so a stale or rejected commit flips nothing.\nfunction consentEffect(request: FactoryTransitionRequest, humanMove: boolean): TransitionConsentOptions {\n const autonomy = transitionConsent(request.stage, humanMove);\n return bearsConsent(request.actor) ? { autonomy, consentedBy: actorId(request.actor) } : { autonomy };\n}\n\ntype RunStartDecision = Extract<FactoryCommitDecision, { type: 'invokeSkill' | 'sendMessage' }>;\n\nfunction startsRun(decision: FactoryCommitDecision): decision is RunStartDecision {\n return decision.type === 'invokeSkill' || (decision.type === 'sendMessage' && decision.prepareBinding === true);\n}\n\n// Answering a recorded run start, or the role's own mid-run agent, with a run would start a second one.\nfunction runAlreadyUnderway(request: FactoryTransitionRequest, decision: RunStartDecision): boolean {\n if (request.cause === 'run_start') return true;\n return request.actor.type === 'agent' && request.actor.role === decision.role;\n}\n\nfunction stageTransitionMessage(fromStage: FactoryRuleStage, toStage: FactoryRuleStage): string {\n return `This work was moved from the ${fromStage} stage to the ${toStage} stage.`;\n}\n\nfunction isTriageAgent(actor: FactoryRuleActor): actor is Extract<FactoryRuleActor, { type: 'agent' }> {\n return actor.type === 'agent' && actor.role === 'triage';\n}\n\nfunction isHumanTransition(request: FactoryTransitionRequest): boolean {\n return request.actor.type === 'human' && request.ingress.type === 'human';\n}\n\nfunction requiresHumanApproval(triageType: FactoryTriageType | null | undefined): boolean {\n return triageType !== undefined && triageType !== null && triageType !== 'bug';\n}\n\nfunction isAtRest(stage: FactoryRuleStage): boolean {\n return stage === 'intake' || stage === 'triage';\n}\n\nfunction entersWork(stage: FactoryRuleStage): boolean {\n return stage === 'planning' || stage === 'execute';\n}\n\n// A person moving a card into Planning/Execute is the approval gesture —\n// recorded once, so later agent hops need no second nod. Not limited to moves\n// out of rest: a card accepted before acceptance was recorded still gets its\n// stamp (and its label reconciled) the next time a person moves it forward.\nfunction acceptsItem(request: FactoryTransitionRequest): boolean {\n return isHumanTransition(request) && entersWork(request.stage);\n}\n\nfunction ruleFailure(error: unknown): { code: FactoryRuleRejectionCode; reason: string } {\n return {\n code: 'rule_error',\n reason: error instanceof Error ? `Factory rule failed: ${error.message}` : 'Factory rule failed.',\n };\n}\n\nasync function withRuleTimeout<T>(operation: Promise<T>, timeoutMs: number): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error('FACTORY_RULE_TIMEOUT')), timeoutMs);\n });\n try {\n return await Promise.race([operation, timeout]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\nexport class FactoryTransitionService {\n readonly #rules: FactoryRules;\n readonly #boards: BoardRegistry;\n readonly #storage: WorkItemsStorage;\n readonly #timeoutMs: number;\n readonly #onTerminalStage: FactoryTransitionServiceOptions['onTerminalStage'];\n readonly #terminalCleanupTimeoutMs: number;\n readonly #onAccepted: FactoryTransitionServiceOptions['onAccepted'];\n\n constructor(options: FactoryTransitionServiceOptions) {\n this.#rules = options.rules;\n this.#boards = options.boards ?? createBoardRegistry();\n this.#storage = options.storage;\n this.#timeoutMs = options.timeoutMs ?? RULE_TIMEOUT_MS;\n this.#onTerminalStage = options.onTerminalStage;\n this.#onAccepted = options.onAccepted;\n this.#terminalCleanupTimeoutMs = options.terminalCleanupTimeoutMs ?? TERMINAL_CLEANUP_TIMEOUT_MS;\n }\n\n get ruleSetVersion(): string {\n return this.#rules.version;\n }\n\n async transition(request: FactoryTransitionRequest): Promise<FactoryTransitionResult> {\n const replay = await this.#storage.getTransitionResultByIngress(\n request.orgId,\n request.factoryProjectId,\n request.ingress.identity,\n );\n if (replay) return replay as unknown as FactoryTransitionResult;\n\n const transitionId = request.ingress.transitionId ?? randomUUID();\n const item = await this.#storage.get({ orgId: request.orgId, id: request.workItemId });\n if (!item) {\n return this.#commitRejection(request, transitionId, 'invalid_transition', 'Work item not found.');\n }\n\n if (request.causalChain && request.causalChain.length > MAX_FACTORY_RULE_CAUSAL_DEPTH) {\n return this.#commitRejection(\n request,\n transitionId,\n 'causal_depth_exceeded',\n 'Factory rule causal depth exceeded.',\n );\n }\n const itemSource = workItemSource(item.externalSource);\n const source = factoryRuleSourceForWorkItem(itemSource);\n const legacyBoard = source === 'pullRequest' ? 'review' : 'work';\n if (item.board === null && !this.#boards.has(legacyBoard)) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n 'This legacy work item has no assigned board. Assign an installed board and phase through the work-item PATCH endpoint before transitioning it.',\n );\n }\n const itemBoard = item.board ?? legacyBoard;\n if (request.board !== itemBoard) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n `The work item belongs to board \"${itemBoard}\", not \"${request.board}\".`,\n );\n }\n if ((itemBoard === 'review' && source !== 'pullRequest') || (itemBoard === 'work' && source === 'pullRequest')) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n 'The work item does not belong to the requested board.',\n );\n }\n const board = this.#boards.get(request.board);\n if (!board) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n `Board \"${request.board}\" is not installed.`,\n );\n }\n const fromStage = item.stages.length === 1 ? item.stages[0] : undefined;\n if (!fromStage || !Object.prototype.hasOwnProperty.call(board.phases, fromStage)) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n 'The work item does not have one canonical phase on the requested board.',\n );\n }\n if (\n !Object.prototype.hasOwnProperty.call(board.phases, request.stage) ||\n !board.allowsTransition(fromStage, request.stage)\n ) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n `The ${board.title} board does not allow moving from ${fromStage} to ${request.stage}.`,\n );\n }\n\n if (isTriageAgent(request.actor) && request.triageType === undefined) {\n return this.#commitRejection(\n request,\n transitionId,\n 'invalid_transition',\n 'Triage transitions must report a structured triage classification.',\n );\n }\n if (item.triageType && request.triageType && item.triageType !== request.triageType) {\n return this.#commitRejection(\n request,\n transitionId,\n 'forbidden',\n 'The persisted triage classification cannot be changed by a later transition.',\n );\n }\n // The gate stands at the exit of rest. A non-bug card already in\n // Planning/Execute can only have been put there by a person, so an agent\n // carrying it further (plan → build) is not asked for a second nod even\n // when the acceptance stamp predates its recording.\n const triageType = item.triageType ?? request.triageType;\n if (\n requiresHumanApproval(triageType) &&\n entersWork(request.stage) &&\n isAtRest(fromStage) &&\n !isHumanTransition(request) &&\n !item.acceptedAt\n ) {\n return this.#commitRejection(\n request,\n transitionId,\n 'approval_required',\n 'A maintainer must move this non-bug work item into Planning or Execute from the Factory UI.',\n );\n }\n\n // The card's own content can steer a bound agent; on a card authored\n // outside the write-access circle, leaving rest takes a person's gesture.\n if (\n request.actor.type === 'agent' &&\n !isWorkingFactoryRuleStage(fromStage) &&\n isWorkingFactoryRuleStage(request.stage) &&\n externallyAuthoredWorkItem(item)\n ) {\n return this.#commitRejection(\n request,\n transitionId,\n 'approval_required',\n 'This card comes from outside the write-access circle; a person must resume it from the Factory board.',\n );\n }\n\n // The coordinator's own self-move at run start would otherwise inject a second run's kickoff.\n const humanMove = request.actor.type === 'human' && fromStage !== request.stage && request.cause !== 'run_start';\n\n const contextBase = {\n tenant: { orgId: request.orgId, projectId: request.factoryProjectId },\n actor: request.actor,\n ingress: { type: request.ingress.type, id: request.ingress.identity },\n cause: request.cause,\n causalChain: request.causalChain ?? [],\n ruleSetVersion: this.#rules.version,\n item: {\n id: item.id,\n source: itemSource,\n sourceKey: item.externalSource\n ? `${item.externalSource.integrationId}:${item.externalSource.type}:${item.externalSource.externalId}`\n : null,\n parentWorkItemId: item.parentWorkItemId,\n title: item.title,\n url: item.externalSource?.url ?? null,\n stages: [...item.stages],\n acceptedAt: item.acceptedAt,\n metadata: item.metadata,\n },\n board: request.board,\n itemRevision: item.revision,\n source,\n fromStage,\n toStage: request.stage,\n } satisfies Omit<FactoryStageRuleContext, 'stage'>;\n\n let evaluation:\n | { outcome: 'accepted'; decisions: Record<string, unknown>[] }\n | { outcome: 'rejected'; code: string; reason: string };\n try {\n evaluation = await withRuleTimeout(\n (async () => {\n const decisions: FactoryCommitDecision[] = [];\n for (const rule of resolveFactoryStageRules(\n this.#rules,\n {\n board: request.board,\n source,\n fromStage,\n toStage: request.stage,\n initialEntry: request.initialEntry,\n reenter: request.reenter,\n },\n this.#boards,\n )) {\n const context: FactoryStageRuleContext = Object.freeze({\n ...contextBase,\n stage: rule.phase === 'exit' ? fromStage : request.stage,\n });\n const raw = await rule.handler(context);\n if (raw === undefined) continue;\n const decision = validateFactoryRuleDecision(raw, context.causalChain.length);\n if (decision.type === 'reject') {\n return { outcome: 'rejected' as const, code: decision.code, reason: decision.reason };\n }\n if (startsRun(decision) && runAlreadyUnderway(request, decision)) continue;\n decisions.push(decision);\n }\n const validated = validateFactoryRuleDecisions(decisions);\n if (humanMove) {\n const message = stageTransitionMessage(fromStage, request.stage);\n const skill = validated.find(decision => decision.type === 'invokeSkill');\n if (skill) {\n skill.precedingMessage = message;\n } else {\n validated.unshift({\n type: 'sendMessage',\n idempotencyKey: `factory-stage:${transitionId}`,\n message,\n priority: 'urgent',\n idleBehavior: 'wake',\n // Parking a card says stop: no seat is right by construction, so\n // the notice goes to whichever session is live — or nobody.\n ...(isWorkingFactoryRuleStage(request.stage)\n ? { role: roleForStage(request.board, request.stage), prepareBinding: true }\n : {}),\n });\n }\n }\n return {\n outcome: 'accepted' as const,\n decisions: validateFactoryRuleDecisions(validated) as unknown as Record<string, unknown>[],\n };\n })(),\n this.#timeoutMs,\n );\n } catch (error) {\n const failed =\n error instanceof Error && error.message === 'FACTORY_RULE_TIMEOUT'\n ? { code: 'timeout' as const, reason: 'Factory rule evaluation timed out.' }\n : ruleFailure(error);\n evaluation = { outcome: 'rejected', ...failed };\n }\n return this.#commit(\n request,\n transitionId,\n evaluation,\n evaluation.outcome === 'accepted'\n ? { ...consentEffect(request, humanMove), accept: acceptsItem(request) && !item.acceptedAt }\n : {},\n );\n }\n\n async #commitRejection(\n request: FactoryTransitionRequest,\n transitionId: string,\n code: FactoryRuleRejectionCode,\n reason: string,\n ): Promise<FactoryTransitionResult> {\n return this.#commit(request, transitionId, { outcome: 'rejected', code, reason });\n }\n\n async #commit(\n request: FactoryTransitionRequest,\n transitionId: string,\n evaluation:\n | { outcome: 'accepted'; decisions: Record<string, unknown>[] }\n | { outcome: 'rejected'; code: string; reason: string },\n options: TransitionConsentOptions = {},\n ): Promise<FactoryTransitionResult> {\n const committed = await this.#storage.commitTransition({\n autonomy: options.autonomy,\n consentedBy: options.consentedBy,\n ...(options.accept ? { accept: true } : {}),\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n workItemId: request.workItemId,\n expectedRevision: request.expectedRevision,\n destinationStage: request.stage,\n actorId: actorId(request.actor),\n ingress: { identity: request.ingress.identity, triggerType: request.ingress.type, transitionId },\n ruleSetVersion: this.#rules.version,\n causalChain: [...(request.causalChain ?? [])],\n evaluation,\n ...(isTriageAgent(request.actor) && request.triageType ? { triageType: request.triageType } : {}),\n });\n if (committed.status === 'missing') {\n return rejection(transitionId, request.workItemId, 'invalid_transition', 'Work item not found.');\n }\n const result = committed.result as unknown as FactoryTransitionResult;\n if (\n this.#onAccepted &&\n options.accept &&\n committed.status === 'committed' &&\n result.status === 'accepted' &&\n committed.item?.acceptedAt\n ) {\n const item = committed.item;\n void Promise.resolve(\n this.#onAccepted({\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n workItemId: request.workItemId,\n item,\n }),\n ).catch(error => {\n console.warn(`[factory] acceptance hook failed for work item ${request.workItemId}:`, error);\n });\n }\n if (this.#onTerminalStage && result.status === 'accepted' && TERMINAL_STAGES.has(result.stage)) {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n const cleanup = Promise.resolve(\n this.#onTerminalStage({\n orgId: request.orgId,\n factoryProjectId: request.factoryProjectId,\n workItemId: request.workItemId,\n stage: result.stage,\n revision: result.revision,\n }),\n );\n // A late rejection after the timeout wins the race must not surface\n // as an unhandled rejection.\n cleanup.catch(() => {});\n await Promise.race([\n cleanup,\n new Promise<void>(resolve => {\n timer = setTimeout(resolve, this.#terminalCleanupTimeoutMs);\n }),\n ]);\n } catch {\n // Resource release is best-effort — never fail a committed transition.\n } finally {\n clearTimeout(timer);\n }\n }\n return result;\n }\n}\n"],"mappings":"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"}
|
package/dist/rules/types.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ export declare function externallyAuthoredWorkItem(item: {
|
|
|
22
22
|
metadata: Record<string, unknown> | null;
|
|
23
23
|
}): boolean;
|
|
24
24
|
export declare const FACTORY_RULE_STAGES: readonly ["intake", "triage", "planning", "execute", "review", "done", "canceled"];
|
|
25
|
-
export type FactoryRuleStage = (typeof FACTORY_RULE_STAGES)[number];
|
|
25
|
+
export type FactoryRuleStage = (typeof FACTORY_RULE_STAGES)[number] | (string & {});
|
|
26
26
|
export declare const FACTORY_ROLE_STAGES: {
|
|
27
27
|
readonly triage: "triage";
|
|
28
28
|
readonly plan: "planning";
|
|
@@ -41,7 +41,7 @@ export declare function isTerminalFactoryRuleStage(stages: readonly string[]): b
|
|
|
41
41
|
export declare function isWorkingFactoryRuleStage(stage: FactoryRuleStage): boolean;
|
|
42
42
|
export declare function factoryLaneForRole(role: string): FactoryRuleStage | undefined;
|
|
43
43
|
export declare const FACTORY_RULE_BOARDS: readonly ["work", "review"];
|
|
44
|
-
export type FactoryRuleBoard = (typeof FACTORY_RULE_BOARDS)[number];
|
|
44
|
+
export type FactoryRuleBoard = (typeof FACTORY_RULE_BOARDS)[number] | (string & {});
|
|
45
45
|
export declare const FACTORY_RULE_SOURCES: readonly ["issue", "pullRequest", "linearIssue", "manual"];
|
|
46
46
|
export type FactoryRuleSource = (typeof FACTORY_RULE_SOURCES)[number];
|
|
47
47
|
export declare const FACTORY_GITHUB_EVENTS: readonly ["issueOpened", "issueEdited", "issueClosed", "issueCommentCreated", "issueCommentEdited", "issueCommentDeleted", "pullRequestOpened", "pullRequestUpdated", "pullRequestCommentCreated", "pullRequestReviewRequested", "pullRequestReviewSubmitted", "pullRequestMerged", "pullRequestClosed"];
|
|
@@ -219,26 +219,21 @@ export interface FactoryBoardRuleLeaf {
|
|
|
219
219
|
export interface FactoryToolRuleLeaf {
|
|
220
220
|
onResult?: FactoryRuleHandler<FactoryToolResultRuleContext>;
|
|
221
221
|
}
|
|
222
|
-
export interface FactoryGithubRuleLeaf {
|
|
223
|
-
onEvent?: FactoryRuleHandler<FactoryGithubRuleContext>;
|
|
224
|
-
}
|
|
225
222
|
export interface FactoryLinearRuleLeaf {
|
|
226
223
|
onEvent?: FactoryRuleHandler<FactoryLinearRuleContext>;
|
|
227
224
|
}
|
|
228
|
-
export type FactoryBoardRules = Partial<Record<
|
|
225
|
+
export type FactoryBoardRules = Partial<Record<string, Partial<Record<FactoryRuleSource, FactoryBoardRuleLeaf>>>>;
|
|
229
226
|
export interface FactoryRules {
|
|
230
227
|
version: string;
|
|
231
228
|
work: FactoryBoardRules;
|
|
232
229
|
review: FactoryBoardRules;
|
|
233
230
|
tools: Record<string, FactoryToolRuleLeaf>;
|
|
234
|
-
github: Partial<Record<FactoryGithubEventName, FactoryGithubRuleLeaf>>;
|
|
235
231
|
linear: Partial<Record<FactoryLinearEventName, FactoryLinearRuleLeaf>>;
|
|
236
232
|
}
|
|
237
233
|
export interface FactoryRulesOverrides {
|
|
238
234
|
work?: FactoryBoardRules;
|
|
239
235
|
review?: FactoryBoardRules;
|
|
240
236
|
tools?: Record<string, FactoryToolRuleLeaf>;
|
|
241
|
-
github?: Partial<Record<FactoryGithubEventName, FactoryGithubRuleLeaf>>;
|
|
242
237
|
linear?: Partial<Record<FactoryLinearEventName, FactoryLinearRuleLeaf>>;
|
|
243
238
|
}
|
|
244
239
|
export type FactoryRuleRejectionCode = 'forbidden' | 'invalid_transition' | 'missing_binding' | 'stale' | 'timeout' | 'rule_error' | 'causal_depth_exceeded' | 'repeated_transition' | 'approval_required';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/rules/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAC;AAEpF,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG,WAAW,GAAG,cAAc,GAAG,QAAQ,CAAC;AAEtF,oGAAoG;AACpG,eAAO,MAAM,oBAAoB,2BAA2B,CAAC;AAC7D,eAAO,MAAM,kBAAkB,yBAAyB,CAAC;AAIzD,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAClC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACzC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,UAAU,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;CACnC,GAAG,OAAO,CAQV;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI,GAAG,cAAc,CAOpF;AAID,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CAAE,GAAG,OAAO,CAI9G;AAGD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CAAE,GAAG,OAAO,CAE/G;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE;IAC/C,cAAc,EAAE,sBAAsB,GAAG,IAAI,CAAC;IAC9C,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC1C,GAAG,OAAO,CAEV;AAED,eAAO,MAAM,mBAAmB,oFAAqF,CAAC;AACtH,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/rules/types.ts"],"names":[],"mappings":"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"}
|
package/dist/rules/types.js
CHANGED
|
@@ -66,7 +66,7 @@ function isFactoryRuleStage(value) {
|
|
|
66
66
|
}
|
|
67
67
|
function factoryRuleStage(stages) {
|
|
68
68
|
const stage = stages.length === 1 ? stages[0] : void 0;
|
|
69
|
-
return
|
|
69
|
+
return typeof stage === "string" && stage.length > 0 ? stage : void 0;
|
|
70
70
|
}
|
|
71
71
|
function isTerminalFactoryRuleStage(stages) {
|
|
72
72
|
const stage = factoryRuleStage(stages);
|
package/dist/rules/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../../src/rules/types.ts"],"sourcesContent":["import type { ExternalWorkItemSource } from '../storage/domains/work-items/base.js';\n\nexport type WorkItemSource = 'github-issue' | 'github-pr' | 'linear-issue' | 'manual';\n\n/** The source label that holds an issue at rest until a maintainer decides; compared lowercased. */\nexport const NEEDS_APPROVAL_LABEL = 'status: needs approval';\nexport const AUTO_TRIAGED_LABEL = 'status: auto-triaged';\n\n// The label only holds a card at rest: once a person accepted it, or it sits in a working\n// lane only a person could have moved it into, the label is stale until the source catches up.\nexport function needsApproval(item: {\n metadata: Record<string, unknown> | null;\n stages?: readonly string[];\n acceptedAt?: Date | string | null;\n}): boolean {\n const labels = item.metadata?.labels;\n if (!Array.isArray(labels)) return false;\n return (\n labels.some(label => typeof label === 'string' && label.toLowerCase() === NEEDS_APPROVAL_LABEL) &&\n !item.acceptedAt &&\n (item.stages ?? ['intake']).every(stage => stage === 'intake' || stage === 'triage')\n );\n}\n\nexport function workItemSource(source: ExternalWorkItemSource | null): WorkItemSource {\n if (!source) return 'manual';\n if (source.integrationId === 'linear') return 'linear-issue';\n // Only GitHub and Linear have provider-specific rules; anything else (a Slack\n // thread, say) is a plain work item, not a mislabeled GitHub issue.\n if (source.integrationId !== 'github') return 'manual';\n return source.type === 'pull-request' ? 'github-pr' : 'github-issue';\n}\n\n// Authored outside the write-access circle: a missing trust stamp fails closed until\n// the reconcile sweep backfills it, and Factory's own PRs pass through `factoryAuthored`.\nexport function externallyAuthored(item: { source: string; metadata: Record<string, unknown> | null }): boolean {\n if (item.source !== 'github-pr' && item.source !== 'github-issue') return false;\n if (item.metadata?.factoryAuthored === true) return false;\n return item.metadata?.authorTrusted !== true;\n}\n\n// The board mark claims only what GitHub answered: a missing stamp is silence, not an outside contribution.\nexport function knownExternalAuthor(item: { source: string; metadata: Record<string, unknown> | null }): boolean {\n return externallyAuthored(item) && item.metadata?.authorTrusted === false;\n}\n\nexport function externallyAuthoredWorkItem(item: {\n externalSource: ExternalWorkItemSource | null;\n metadata: Record<string, unknown> | null;\n}): boolean {\n return externallyAuthored({ source: workItemSource(item.externalSource), metadata: item.metadata });\n}\n\nexport const FACTORY_RULE_STAGES = ['intake', 'triage', 'planning', 'execute', 'review', 'done', 'canceled'] as const;\nexport type FactoryRuleStage = (typeof FACTORY_RULE_STAGES)[number];\n\n// Each role and the working stage its run holds the card in. Key order is the\n// seat pipeline order — Resume depth derives from it.\nexport const FACTORY_ROLE_STAGES = {\n triage: 'triage',\n plan: 'planning',\n work: 'execute',\n review: 'review',\n} as const satisfies Record<string, FactoryRuleStage>;\nexport type FactoryRole = keyof typeof FACTORY_ROLE_STAGES;\n\nexport function isFactoryRole(value: string): value is FactoryRole {\n return value in FACTORY_ROLE_STAGES;\n}\n\nexport const FACTORY_TRIAGE_TYPES = [\n 'bug',\n 'feature request',\n 'docs',\n 'question/support',\n 'maintenance',\n 'duplicate',\n 'resolved',\n 'invalid',\n 'spam',\n 'out-of-scope',\n 'other',\n] as const;\nexport type FactoryTriageType = (typeof FACTORY_TRIAGE_TYPES)[number];\n\nexport function isFactoryTriageType(value: unknown): value is FactoryTriageType {\n return typeof value === 'string' && FACTORY_TRIAGE_TYPES.some(type => type === value);\n}\n\nexport function isFactoryRuleStage(value: unknown): value is FactoryRuleStage {\n return typeof value === 'string' && FACTORY_RULE_STAGES.some(stage => stage === value);\n}\n\nexport function factoryRuleStage(stages: readonly string[]): FactoryRuleStage | undefined {\n const stage = stages.length === 1 ? stages[0] : undefined;\n return isFactoryRuleStage(stage) ? stage : undefined;\n}\n\nexport function isTerminalFactoryRuleStage(stages: readonly string[]): boolean {\n const stage = factoryRuleStage(stages);\n return stage === 'done' || stage === 'canceled';\n}\n\n/** Working lanes hold cards with a seat engaged; Intake, Done and Canceled rest them. */\nexport function isWorkingFactoryRuleStage(stage: FactoryRuleStage): boolean {\n return stage !== 'intake' && !isTerminalFactoryRuleStage([stage]);\n}\n\n// Consulted only for the Intake exit: roles don't own lanes, so a card already\n// in a working or terminal lane stays put when a run starts.\nexport function factoryLaneForRole(role: string): FactoryRuleStage | undefined {\n return isFactoryRole(role) ? FACTORY_ROLE_STAGES[role] : undefined;\n}\n\nexport const FACTORY_RULE_BOARDS = ['work', 'review'] as const;\nexport type FactoryRuleBoard = (typeof FACTORY_RULE_BOARDS)[number];\n\nexport const FACTORY_RULE_SOURCES = ['issue', 'pullRequest', 'linearIssue', 'manual'] as const;\nexport type FactoryRuleSource = (typeof FACTORY_RULE_SOURCES)[number];\n\nexport const FACTORY_GITHUB_EVENTS = [\n 'issueOpened',\n 'issueEdited',\n 'issueClosed',\n 'issueCommentCreated',\n 'issueCommentEdited',\n 'issueCommentDeleted',\n 'pullRequestOpened',\n 'pullRequestUpdated',\n 'pullRequestCommentCreated',\n 'pullRequestReviewRequested',\n 'pullRequestReviewSubmitted',\n 'pullRequestMerged',\n 'pullRequestClosed',\n] as const;\nexport type FactoryGithubEventName = (typeof FACTORY_GITHUB_EVENTS)[number];\n\nexport const FACTORY_LINEAR_EVENTS = ['issueObserved', 'issueClosed'] as const;\nexport type FactoryLinearEventName = (typeof FACTORY_LINEAR_EVENTS)[number];\n\nexport type FactoryRuleJsonValue =\n | null\n | boolean\n | number\n | string\n | FactoryRuleJsonValue[]\n | { [key: string]: FactoryRuleJsonValue };\n\nexport interface FactoryRuleItemContext {\n id: string;\n source: WorkItemSource;\n sourceKey: string | null;\n parentWorkItemId: string | null;\n title: string;\n url: string | null;\n stages: readonly string[];\n acceptedAt: Date | null;\n /** Intake-stamped facts about the source — repository id, reporter login, labels. */\n metadata: Record<string, unknown> | null;\n}\n\nexport type FactoryRuleActor =\n | { type: 'human'; id: string }\n | { type: 'agent'; bindingId: string; role: string }\n | { type: 'github'; login: string; trusted: boolean; factoryAuthored: boolean }\n | { type: 'system'; id: string };\n\nexport interface FactoryRuleIngressIdentity {\n type: 'human' | 'agent' | 'toolResult' | 'github' | 'linear' | 'rule';\n id: string;\n}\n\nexport interface FactoryRuleCausalEntry {\n ingressId: string;\n decisionType: FactoryCommitDecision['type'];\n}\n\nexport interface FactoryRuleContextBase {\n tenant: { orgId: string; projectId: string };\n actor: FactoryRuleActor;\n ingress: FactoryRuleIngressIdentity;\n cause: string;\n causalChain: readonly FactoryRuleCausalEntry[];\n ruleSetVersion: string;\n}\n\nexport interface FactoryBoundRuleContext extends FactoryRuleContextBase {\n item: FactoryRuleItemContext;\n board: FactoryRuleBoard;\n itemRevision: number;\n}\n\nexport interface FactoryStageRuleContext extends FactoryBoundRuleContext {\n source: FactoryRuleSource;\n stage: FactoryRuleStage;\n fromStage: FactoryRuleStage;\n toStage: FactoryRuleStage;\n}\n\nexport interface FactoryToolResultRuleContext extends FactoryBoundRuleContext {\n toolName: string;\n threadId: string;\n assistantMessageId: string;\n toolCallId: string;\n result: {\n status: 'success' | 'error';\n value: FactoryRuleJsonValue;\n };\n}\n\nexport interface FactoryGithubRuleContext extends FactoryRuleContextBase {\n item?: FactoryRuleItemContext;\n board?: FactoryRuleBoard;\n itemRevision?: number;\n event: FactoryGithubEventName;\n deliveryId: string;\n factory: { createdAt: string };\n repository: { id: number; fullName: string };\n issue?: {\n number: number;\n title: string;\n url: string;\n createdAt?: string;\n updatedAt?: string;\n assignees?: string[];\n labels?: string[];\n state?: 'open' | 'closed';\n /** GitHub close reason: `completed`, `not_planned`, or `duplicate`. */\n stateReason?: string;\n };\n issueChange?: { title: boolean; body: boolean };\n issueComment?: {\n id: number;\n body?: string;\n url?: string;\n author?: string;\n authorType?: string;\n createdAt?: string;\n updatedAt?: string;\n };\n pullRequest?: {\n number: number;\n title: string;\n url: string;\n createdAt?: string;\n state: 'open' | 'closed';\n draft: boolean;\n merged: boolean;\n assignees?: string[];\n requestedReviewers?: string[];\n labels?: string[];\n author?: string;\n factoryAuthored: boolean;\n headBranch: string;\n baseBranch: string;\n };\n /** Present on `pullRequestReviewRequested`: who review was (re-)requested from. */\n reviewRequest?: { reviewer: string; factoryReviewer: boolean };\n /** Present when a PR comment uses Factory's exact review command. */\n reviewCommand?: { command: 'review' | 're-review'; target: string };\n /** Present on `pullRequestReviewSubmitted`: the review that was just posted. */\n review?: { id: number; state: string; url: string };\n}\n\nexport interface FactoryLinearRuleContext extends FactoryRuleContextBase {\n item?: FactoryRuleItemContext;\n board?: FactoryRuleBoard;\n itemRevision?: number;\n event: FactoryLinearEventName;\n issue: {\n id: string;\n identifier: string;\n title: string;\n url: string;\n state: string;\n stateType: string;\n priorityLabel: string;\n assignee: string | null;\n creator: string | null;\n team: string | null;\n labels: readonly string[];\n createdAt: string;\n updatedAt: string;\n };\n}\n\nexport type FactoryRuleHandler<TContext> = (\n context: Readonly<TContext>,\n) => FactoryRuleDecision | void | Promise<FactoryRuleDecision | void>;\n\nexport interface FactoryBoardRuleLeaf {\n onEnter?: FactoryRuleHandler<FactoryStageRuleContext>;\n onExit?: FactoryRuleHandler<FactoryStageRuleContext>;\n}\n\nexport interface FactoryToolRuleLeaf {\n onResult?: FactoryRuleHandler<FactoryToolResultRuleContext>;\n}\n\nexport interface FactoryGithubRuleLeaf {\n onEvent?: FactoryRuleHandler<FactoryGithubRuleContext>;\n}\n\nexport interface FactoryLinearRuleLeaf {\n onEvent?: FactoryRuleHandler<FactoryLinearRuleContext>;\n}\n\nexport type FactoryBoardRules = Partial<\n Record<FactoryRuleStage, Partial<Record<FactoryRuleSource, FactoryBoardRuleLeaf>>>\n>;\n\nexport interface FactoryRules {\n version: string;\n work: FactoryBoardRules;\n review: FactoryBoardRules;\n tools: Record<string, FactoryToolRuleLeaf>;\n github: Partial<Record<FactoryGithubEventName, FactoryGithubRuleLeaf>>;\n linear: Partial<Record<FactoryLinearEventName, FactoryLinearRuleLeaf>>;\n}\n\nexport interface FactoryRulesOverrides {\n work?: FactoryBoardRules;\n review?: FactoryBoardRules;\n tools?: Record<string, FactoryToolRuleLeaf>;\n github?: Partial<Record<FactoryGithubEventName, FactoryGithubRuleLeaf>>;\n linear?: Partial<Record<FactoryLinearEventName, FactoryLinearRuleLeaf>>;\n}\n\nexport type FactoryRuleRejectionCode =\n | 'forbidden'\n | 'invalid_transition'\n | 'missing_binding'\n | 'stale'\n | 'timeout'\n | 'rule_error'\n | 'causal_depth_exceeded'\n | 'repeated_transition'\n | 'approval_required';\n\nexport interface FactoryRuleRejectDecision {\n type: 'reject';\n code: FactoryRuleRejectionCode;\n reason: string;\n}\n\ninterface FactoryCommitDecisionBase {\n idempotencyKey: string;\n}\n\nexport interface FactoryTransitionDecision extends FactoryCommitDecisionBase {\n type: 'transition';\n board: FactoryRuleBoard;\n stage: FactoryRuleStage;\n /**\n * Delivered to the item's active session (waking it if idle) after the\n * transition commits. Skipped when the item has no active run binding, so\n * informational messages never fail the transition.\n */\n message?: { text: string; role?: string };\n /**\n * Runs the stage's entry rules even when the item is already in that stage.\n * A transition to the current stage is normally inert, because most callers\n * are correcting a board into a state it already holds. Re-entry is for the\n * opposite case: the stage's work is in flight and has been invalidated, so\n * it has to start over.\n */\n reenter?: boolean;\n}\n\nexport interface FactoryUpsertLinkedWorkItemDecision extends FactoryCommitDecisionBase {\n type: 'upsertLinkedWorkItem';\n board: FactoryRuleBoard;\n source: WorkItemSource;\n sourceKey: string;\n title: string;\n url: string | null;\n stage: FactoryRuleStage;\n metadata?: Record<string, FactoryRuleJsonValue>;\n}\n\ninterface FactoryInvokeSkillDecisionBase extends FactoryCommitDecisionBase {\n type: 'invokeSkill';\n role: string;\n arguments?: string;\n precedingMessage?: string;\n cancelInFlight?: boolean;\n}\n\n/**\n * Starting an agent run. Most runs activate a skill, because the skill carries\n * the handoff contract later rules match on. A run whose completion is already\n * signalled some other way — Building finishes by opening a pull request, which\n * arrives as its own event — needs no contract, so it can carry a plain prompt\n * instead of an otherwise empty skill.\n */\nexport type FactoryInvokeSkillDecision = FactoryInvokeSkillDecisionBase &\n ({ skillName: string; prompt?: never } | { prompt: string; skillName?: never });\n\nexport interface FactorySendMessageDecision extends FactoryCommitDecisionBase {\n type: 'sendMessage';\n /** Omitted: the card's live session, whichever seat holds it. Required with `prepareBinding`. */\n role?: string;\n message: string;\n priority?: 'medium' | 'high' | 'urgent';\n idleBehavior?: 'persist' | 'wake';\n prepareBinding?: boolean;\n}\n\nexport interface FactoryNotifyDecision extends FactoryCommitDecisionBase {\n type: 'notify';\n title: string;\n body?: string;\n level?: 'info' | 'warning' | 'error';\n}\n\nexport type FactoryCommitDecision =\n | FactoryTransitionDecision\n | FactoryUpsertLinkedWorkItemDecision\n | FactoryInvokeSkillDecision\n | FactorySendMessageDecision\n | FactoryNotifyDecision;\n\nexport type FactoryRuleDecision = FactoryRuleRejectDecision | FactoryCommitDecision;\n\nexport interface FactoryTransitionResultAccepted {\n status: 'accepted';\n transitionId: string;\n itemId: string;\n revision: number;\n stage: FactoryRuleStage;\n decisions: FactoryCommitDecision[];\n}\n\nexport interface FactoryTransitionResultRejected {\n status: 'rejected';\n transitionId: string;\n itemId: string;\n code: FactoryRuleRejectionCode;\n reason: string;\n}\n\nexport type FactoryTransitionResult = FactoryTransitionResultAccepted | FactoryTransitionResultRejected;\n\nexport function factoryRuleSourceForWorkItem(source: WorkItemSource): FactoryRuleSource {\n switch (source) {\n case 'github-issue':\n return 'issue';\n case 'github-pr':\n return 'pullRequest';\n case 'linear-issue':\n return 'linearIssue';\n case 'manual':\n return 'manual';\n }\n}\n"],"mappings":";;AAKA,MAAa,uBAAuB;AACpC,MAAa,qBAAqB;AAIlC,SAAgB,cAAc,MAIlB;CACV,MAAM,SAAS,KAAK,UAAU;CAC9B,IAAI,CAAC,MAAM,QAAQ,MAAM,GAAG,OAAO;CACnC,OACE,OAAO,MAAK,UAAS,OAAO,UAAU,YAAY,MAAM,YAAY,MAAA,wBAA0B,KAC9F,CAAC,KAAK,eACL,KAAK,UAAU,CAAC,QAAQ,EAAA,CAAG,OAAM,UAAS,UAAU,YAAY,UAAU,QAAQ;AAEvF;AAEA,SAAgB,eAAe,QAAuD;CACpF,IAAI,CAAC,QAAQ,OAAO;CACpB,IAAI,OAAO,kBAAkB,UAAU,OAAO;CAG9C,IAAI,OAAO,kBAAkB,UAAU,OAAO;CAC9C,OAAO,OAAO,SAAS,iBAAiB,cAAc;AACxD;AAIA,SAAgB,mBAAmB,MAA6E;CAC9G,IAAI,KAAK,WAAW,eAAe,KAAK,WAAW,gBAAgB,OAAO;CAC1E,IAAI,KAAK,UAAU,oBAAoB,MAAM,OAAO;CACpD,OAAO,KAAK,UAAU,kBAAkB;AAC1C;AAGA,SAAgB,oBAAoB,MAA6E;CAC/G,OAAO,mBAAmB,IAAI,KAAK,KAAK,UAAU,kBAAkB;AACtE;AAEA,SAAgB,2BAA2B,MAG/B;CACV,OAAO,mBAAmB;EAAE,QAAQ,eAAe,KAAK,cAAc;EAAG,UAAU,KAAK;CAAS,CAAC;AACpG;AAEA,MAAa,sBAAsB;CAAC;CAAU;CAAU;CAAY;CAAW;CAAU;CAAQ;AAAU;AAK3G,MAAa,sBAAsB;CACjC,QAAQ;CACR,MAAM;CACN,MAAM;CACN,QAAQ;AACV;AAGA,SAAgB,cAAc,OAAqC;CACjE,OAAO,SAAS;AAClB;AAEA,MAAa,uBAAuB;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAGA,SAAgB,oBAAoB,OAA4C;CAC9E,OAAO,OAAO,UAAU,YAAY,qBAAqB,MAAK,SAAQ,SAAS,KAAK;AACtF;AAEA,SAAgB,mBAAmB,OAA2C;CAC5E,OAAO,OAAO,UAAU,YAAY,oBAAoB,MAAK,UAAS,UAAU,KAAK;AACvF;AAEA,SAAgB,iBAAiB,QAAyD;CACxF,MAAM,QAAQ,OAAO,WAAW,IAAI,OAAO,KAAK,KAAA;CAChD,OAAO,mBAAmB,KAAK,IAAI,QAAQ,KAAA;AAC7C;AAEA,SAAgB,2BAA2B,QAAoC;CAC7E,MAAM,QAAQ,iBAAiB,MAAM;CACrC,OAAO,UAAU,UAAU,UAAU;AACvC;;AAGA,SAAgB,0BAA0B,OAAkC;CAC1E,OAAO,UAAU,YAAY,CAAC,2BAA2B,CAAC,KAAK,CAAC;AAClE;AAIA,SAAgB,mBAAmB,MAA4C;CAC7E,OAAO,cAAc,IAAI,IAAI,oBAAoB,QAAQ,KAAA;AAC3D;AAEA,MAAa,sBAAsB,CAAC,QAAQ,QAAQ;AAGpD,MAAa,uBAAuB;CAAC;CAAS;CAAe;CAAe;AAAQ;AAGpF,MAAa,wBAAwB;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAGA,MAAa,wBAAwB,CAAC,iBAAiB,aAAa;AAkTpE,SAAgB,6BAA6B,QAA2C;CACtF,QAAQ,QAAR;EACE,KAAK,gBACH,OAAO;EACT,KAAK,aACH,OAAO;EACT,KAAK,gBACH,OAAO;EACT,KAAK,UACH,OAAO;CACX;AACF"}
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../src/rules/types.ts"],"sourcesContent":["import type { ExternalWorkItemSource } from '../storage/domains/work-items/base.js';\n\nexport type WorkItemSource = 'github-issue' | 'github-pr' | 'linear-issue' | 'manual';\n\n/** The source label that holds an issue at rest until a maintainer decides; compared lowercased. */\nexport const NEEDS_APPROVAL_LABEL = 'status: needs approval';\nexport const AUTO_TRIAGED_LABEL = 'status: auto-triaged';\n\n// The label only holds a card at rest: once a person accepted it, or it sits in a working\n// lane only a person could have moved it into, the label is stale until the source catches up.\nexport function needsApproval(item: {\n metadata: Record<string, unknown> | null;\n stages?: readonly string[];\n acceptedAt?: Date | string | null;\n}): boolean {\n const labels = item.metadata?.labels;\n if (!Array.isArray(labels)) return false;\n return (\n labels.some(label => typeof label === 'string' && label.toLowerCase() === NEEDS_APPROVAL_LABEL) &&\n !item.acceptedAt &&\n (item.stages ?? ['intake']).every(stage => stage === 'intake' || stage === 'triage')\n );\n}\n\nexport function workItemSource(source: ExternalWorkItemSource | null): WorkItemSource {\n if (!source) return 'manual';\n if (source.integrationId === 'linear') return 'linear-issue';\n // Only GitHub and Linear have provider-specific rules; anything else (a Slack\n // thread, say) is a plain work item, not a mislabeled GitHub issue.\n if (source.integrationId !== 'github') return 'manual';\n return source.type === 'pull-request' ? 'github-pr' : 'github-issue';\n}\n\n// Authored outside the write-access circle: a missing trust stamp fails closed until\n// the reconcile sweep backfills it, and Factory's own PRs pass through `factoryAuthored`.\nexport function externallyAuthored(item: { source: string; metadata: Record<string, unknown> | null }): boolean {\n if (item.source !== 'github-pr' && item.source !== 'github-issue') return false;\n if (item.metadata?.factoryAuthored === true) return false;\n return item.metadata?.authorTrusted !== true;\n}\n\n// The board mark claims only what GitHub answered: a missing stamp is silence, not an outside contribution.\nexport function knownExternalAuthor(item: { source: string; metadata: Record<string, unknown> | null }): boolean {\n return externallyAuthored(item) && item.metadata?.authorTrusted === false;\n}\n\nexport function externallyAuthoredWorkItem(item: {\n externalSource: ExternalWorkItemSource | null;\n metadata: Record<string, unknown> | null;\n}): boolean {\n return externallyAuthored({ source: workItemSource(item.externalSource), metadata: item.metadata });\n}\n\nexport const FACTORY_RULE_STAGES = ['intake', 'triage', 'planning', 'execute', 'review', 'done', 'canceled'] as const;\nexport type FactoryRuleStage = (typeof FACTORY_RULE_STAGES)[number] | (string & {});\n\n// Each role and the working stage its run holds the card in. Key order is the\n// seat pipeline order — Resume depth derives from it.\nexport const FACTORY_ROLE_STAGES = {\n triage: 'triage',\n plan: 'planning',\n work: 'execute',\n review: 'review',\n} as const satisfies Record<string, FactoryRuleStage>;\nexport type FactoryRole = keyof typeof FACTORY_ROLE_STAGES;\n\nexport function isFactoryRole(value: string): value is FactoryRole {\n return value in FACTORY_ROLE_STAGES;\n}\n\nexport const FACTORY_TRIAGE_TYPES = [\n 'bug',\n 'feature request',\n 'docs',\n 'question/support',\n 'maintenance',\n 'duplicate',\n 'resolved',\n 'invalid',\n 'spam',\n 'out-of-scope',\n 'other',\n] as const;\nexport type FactoryTriageType = (typeof FACTORY_TRIAGE_TYPES)[number];\n\nexport function isFactoryTriageType(value: unknown): value is FactoryTriageType {\n return typeof value === 'string' && FACTORY_TRIAGE_TYPES.some(type => type === value);\n}\n\nexport function isFactoryRuleStage(value: unknown): value is FactoryRuleStage {\n return typeof value === 'string' && FACTORY_RULE_STAGES.some(stage => stage === value);\n}\n\nexport function factoryRuleStage(stages: readonly string[]): FactoryRuleStage | undefined {\n const stage = stages.length === 1 ? stages[0] : undefined;\n return typeof stage === 'string' && stage.length > 0 ? stage : undefined;\n}\n\nexport function isTerminalFactoryRuleStage(stages: readonly string[]): boolean {\n const stage = factoryRuleStage(stages);\n return stage === 'done' || stage === 'canceled';\n}\n\n/** Working lanes hold cards with a seat engaged; Intake, Done and Canceled rest them. */\nexport function isWorkingFactoryRuleStage(stage: FactoryRuleStage): boolean {\n return stage !== 'intake' && !isTerminalFactoryRuleStage([stage]);\n}\n\n// Consulted only for the Intake exit: roles don't own lanes, so a card already\n// in a working or terminal lane stays put when a run starts.\nexport function factoryLaneForRole(role: string): FactoryRuleStage | undefined {\n return isFactoryRole(role) ? FACTORY_ROLE_STAGES[role] : undefined;\n}\n\nexport const FACTORY_RULE_BOARDS = ['work', 'review'] as const;\nexport type FactoryRuleBoard = (typeof FACTORY_RULE_BOARDS)[number] | (string & {});\n\nexport const FACTORY_RULE_SOURCES = ['issue', 'pullRequest', 'linearIssue', 'manual'] as const;\nexport type FactoryRuleSource = (typeof FACTORY_RULE_SOURCES)[number];\n\nexport const FACTORY_GITHUB_EVENTS = [\n 'issueOpened',\n 'issueEdited',\n 'issueClosed',\n 'issueCommentCreated',\n 'issueCommentEdited',\n 'issueCommentDeleted',\n 'pullRequestOpened',\n 'pullRequestUpdated',\n 'pullRequestCommentCreated',\n 'pullRequestReviewRequested',\n 'pullRequestReviewSubmitted',\n 'pullRequestMerged',\n 'pullRequestClosed',\n] as const;\nexport type FactoryGithubEventName = (typeof FACTORY_GITHUB_EVENTS)[number];\n\nexport const FACTORY_LINEAR_EVENTS = ['issueObserved', 'issueClosed'] as const;\nexport type FactoryLinearEventName = (typeof FACTORY_LINEAR_EVENTS)[number];\n\nexport type FactoryRuleJsonValue =\n | null\n | boolean\n | number\n | string\n | FactoryRuleJsonValue[]\n | { [key: string]: FactoryRuleJsonValue };\n\nexport interface FactoryRuleItemContext {\n id: string;\n source: WorkItemSource;\n sourceKey: string | null;\n parentWorkItemId: string | null;\n title: string;\n url: string | null;\n stages: readonly string[];\n acceptedAt: Date | null;\n /** Intake-stamped facts about the source — repository id, reporter login, labels. */\n metadata: Record<string, unknown> | null;\n}\n\nexport type FactoryRuleActor =\n | { type: 'human'; id: string }\n | { type: 'agent'; bindingId: string; role: string }\n | { type: 'github'; login: string; trusted: boolean; factoryAuthored: boolean }\n | { type: 'system'; id: string };\n\nexport interface FactoryRuleIngressIdentity {\n type: 'human' | 'agent' | 'toolResult' | 'github' | 'linear' | 'rule';\n id: string;\n}\n\nexport interface FactoryRuleCausalEntry {\n ingressId: string;\n decisionType: FactoryCommitDecision['type'];\n}\n\nexport interface FactoryRuleContextBase {\n tenant: { orgId: string; projectId: string };\n actor: FactoryRuleActor;\n ingress: FactoryRuleIngressIdentity;\n cause: string;\n causalChain: readonly FactoryRuleCausalEntry[];\n ruleSetVersion: string;\n}\n\nexport interface FactoryBoundRuleContext extends FactoryRuleContextBase {\n item: FactoryRuleItemContext;\n board: FactoryRuleBoard;\n itemRevision: number;\n}\n\nexport interface FactoryStageRuleContext extends FactoryBoundRuleContext {\n source: FactoryRuleSource;\n stage: FactoryRuleStage;\n fromStage: FactoryRuleStage;\n toStage: FactoryRuleStage;\n}\n\nexport interface FactoryToolResultRuleContext extends FactoryBoundRuleContext {\n toolName: string;\n threadId: string;\n assistantMessageId: string;\n toolCallId: string;\n result: {\n status: 'success' | 'error';\n value: FactoryRuleJsonValue;\n };\n}\n\nexport interface FactoryGithubRuleContext extends FactoryRuleContextBase {\n item?: FactoryRuleItemContext;\n board?: FactoryRuleBoard;\n itemRevision?: number;\n event: FactoryGithubEventName;\n deliveryId: string;\n factory: { createdAt: string };\n repository: { id: number; fullName: string };\n issue?: {\n number: number;\n title: string;\n url: string;\n createdAt?: string;\n updatedAt?: string;\n assignees?: string[];\n labels?: string[];\n state?: 'open' | 'closed';\n /** GitHub close reason: `completed`, `not_planned`, or `duplicate`. */\n stateReason?: string;\n };\n issueChange?: { title: boolean; body: boolean };\n issueComment?: {\n id: number;\n body?: string;\n url?: string;\n author?: string;\n authorType?: string;\n createdAt?: string;\n updatedAt?: string;\n };\n pullRequest?: {\n number: number;\n title: string;\n url: string;\n createdAt?: string;\n state: 'open' | 'closed';\n draft: boolean;\n merged: boolean;\n assignees?: string[];\n requestedReviewers?: string[];\n labels?: string[];\n author?: string;\n factoryAuthored: boolean;\n headBranch: string;\n baseBranch: string;\n };\n /** Present on `pullRequestReviewRequested`: who review was (re-)requested from. */\n reviewRequest?: { reviewer: string; factoryReviewer: boolean };\n /** Present when a PR comment uses Factory's exact review command. */\n reviewCommand?: { command: 'review' | 're-review'; target: string };\n /** Present on `pullRequestReviewSubmitted`: the review that was just posted. */\n review?: { id: number; state: string; url: string };\n}\n\nexport interface FactoryLinearRuleContext extends FactoryRuleContextBase {\n item?: FactoryRuleItemContext;\n board?: FactoryRuleBoard;\n itemRevision?: number;\n event: FactoryLinearEventName;\n issue: {\n id: string;\n identifier: string;\n title: string;\n url: string;\n state: string;\n stateType: string;\n priorityLabel: string;\n assignee: string | null;\n creator: string | null;\n team: string | null;\n labels: readonly string[];\n createdAt: string;\n updatedAt: string;\n };\n}\n\nexport type FactoryRuleHandler<TContext> = (\n context: Readonly<TContext>,\n) => FactoryRuleDecision | void | Promise<FactoryRuleDecision | void>;\n\nexport interface FactoryBoardRuleLeaf {\n onEnter?: FactoryRuleHandler<FactoryStageRuleContext>;\n onExit?: FactoryRuleHandler<FactoryStageRuleContext>;\n}\n\nexport interface FactoryToolRuleLeaf {\n onResult?: FactoryRuleHandler<FactoryToolResultRuleContext>;\n}\n\nexport interface FactoryLinearRuleLeaf {\n onEvent?: FactoryRuleHandler<FactoryLinearRuleContext>;\n}\n\nexport type FactoryBoardRules = Partial<Record<string, Partial<Record<FactoryRuleSource, FactoryBoardRuleLeaf>>>>;\n\nexport interface FactoryRules {\n version: string;\n work: FactoryBoardRules;\n review: FactoryBoardRules;\n tools: Record<string, FactoryToolRuleLeaf>;\n linear: Partial<Record<FactoryLinearEventName, FactoryLinearRuleLeaf>>;\n}\n\nexport interface FactoryRulesOverrides {\n work?: FactoryBoardRules;\n review?: FactoryBoardRules;\n tools?: Record<string, FactoryToolRuleLeaf>;\n linear?: Partial<Record<FactoryLinearEventName, FactoryLinearRuleLeaf>>;\n}\n\nexport type FactoryRuleRejectionCode =\n | 'forbidden'\n | 'invalid_transition'\n | 'missing_binding'\n | 'stale'\n | 'timeout'\n | 'rule_error'\n | 'causal_depth_exceeded'\n | 'repeated_transition'\n | 'approval_required';\n\nexport interface FactoryRuleRejectDecision {\n type: 'reject';\n code: FactoryRuleRejectionCode;\n reason: string;\n}\n\ninterface FactoryCommitDecisionBase {\n idempotencyKey: string;\n}\n\nexport interface FactoryTransitionDecision extends FactoryCommitDecisionBase {\n type: 'transition';\n board: FactoryRuleBoard;\n stage: FactoryRuleStage;\n /**\n * Delivered to the item's active session (waking it if idle) after the\n * transition commits. Skipped when the item has no active run binding, so\n * informational messages never fail the transition.\n */\n message?: { text: string; role?: string };\n /**\n * Runs the stage's entry rules even when the item is already in that stage.\n * A transition to the current stage is normally inert, because most callers\n * are correcting a board into a state it already holds. Re-entry is for the\n * opposite case: the stage's work is in flight and has been invalidated, so\n * it has to start over.\n */\n reenter?: boolean;\n}\n\nexport interface FactoryUpsertLinkedWorkItemDecision extends FactoryCommitDecisionBase {\n type: 'upsertLinkedWorkItem';\n board: FactoryRuleBoard;\n source: WorkItemSource;\n sourceKey: string;\n title: string;\n url: string | null;\n stage: FactoryRuleStage;\n metadata?: Record<string, FactoryRuleJsonValue>;\n}\n\ninterface FactoryInvokeSkillDecisionBase extends FactoryCommitDecisionBase {\n type: 'invokeSkill';\n role: string;\n arguments?: string;\n precedingMessage?: string;\n cancelInFlight?: boolean;\n}\n\n/**\n * Starting an agent run. Most runs activate a skill, because the skill carries\n * the handoff contract later rules match on. A run whose completion is already\n * signalled some other way — Building finishes by opening a pull request, which\n * arrives as its own event — needs no contract, so it can carry a plain prompt\n * instead of an otherwise empty skill.\n */\nexport type FactoryInvokeSkillDecision = FactoryInvokeSkillDecisionBase &\n ({ skillName: string; prompt?: never } | { prompt: string; skillName?: never });\n\nexport interface FactorySendMessageDecision extends FactoryCommitDecisionBase {\n type: 'sendMessage';\n /** Omitted: the card's live session, whichever seat holds it. Required with `prepareBinding`. */\n role?: string;\n message: string;\n priority?: 'medium' | 'high' | 'urgent';\n idleBehavior?: 'persist' | 'wake';\n prepareBinding?: boolean;\n}\n\nexport interface FactoryNotifyDecision extends FactoryCommitDecisionBase {\n type: 'notify';\n title: string;\n body?: string;\n level?: 'info' | 'warning' | 'error';\n}\n\nexport type FactoryCommitDecision =\n | FactoryTransitionDecision\n | FactoryUpsertLinkedWorkItemDecision\n | FactoryInvokeSkillDecision\n | FactorySendMessageDecision\n | FactoryNotifyDecision;\n\nexport type FactoryRuleDecision = FactoryRuleRejectDecision | FactoryCommitDecision;\n\nexport interface FactoryTransitionResultAccepted {\n status: 'accepted';\n transitionId: string;\n itemId: string;\n revision: number;\n stage: FactoryRuleStage;\n decisions: FactoryCommitDecision[];\n}\n\nexport interface FactoryTransitionResultRejected {\n status: 'rejected';\n transitionId: string;\n itemId: string;\n code: FactoryRuleRejectionCode;\n reason: string;\n}\n\nexport type FactoryTransitionResult = FactoryTransitionResultAccepted | FactoryTransitionResultRejected;\n\nexport function factoryRuleSourceForWorkItem(source: WorkItemSource): FactoryRuleSource {\n switch (source) {\n case 'github-issue':\n return 'issue';\n case 'github-pr':\n return 'pullRequest';\n case 'linear-issue':\n return 'linearIssue';\n case 'manual':\n return 'manual';\n }\n}\n"],"mappings":";;AAKA,MAAa,uBAAuB;AACpC,MAAa,qBAAqB;AAIlC,SAAgB,cAAc,MAIlB;CACV,MAAM,SAAS,KAAK,UAAU;CAC9B,IAAI,CAAC,MAAM,QAAQ,MAAM,GAAG,OAAO;CACnC,OACE,OAAO,MAAK,UAAS,OAAO,UAAU,YAAY,MAAM,YAAY,MAAA,wBAA0B,KAC9F,CAAC,KAAK,eACL,KAAK,UAAU,CAAC,QAAQ,EAAA,CAAG,OAAM,UAAS,UAAU,YAAY,UAAU,QAAQ;AAEvF;AAEA,SAAgB,eAAe,QAAuD;CACpF,IAAI,CAAC,QAAQ,OAAO;CACpB,IAAI,OAAO,kBAAkB,UAAU,OAAO;CAG9C,IAAI,OAAO,kBAAkB,UAAU,OAAO;CAC9C,OAAO,OAAO,SAAS,iBAAiB,cAAc;AACxD;AAIA,SAAgB,mBAAmB,MAA6E;CAC9G,IAAI,KAAK,WAAW,eAAe,KAAK,WAAW,gBAAgB,OAAO;CAC1E,IAAI,KAAK,UAAU,oBAAoB,MAAM,OAAO;CACpD,OAAO,KAAK,UAAU,kBAAkB;AAC1C;AAGA,SAAgB,oBAAoB,MAA6E;CAC/G,OAAO,mBAAmB,IAAI,KAAK,KAAK,UAAU,kBAAkB;AACtE;AAEA,SAAgB,2BAA2B,MAG/B;CACV,OAAO,mBAAmB;EAAE,QAAQ,eAAe,KAAK,cAAc;EAAG,UAAU,KAAK;CAAS,CAAC;AACpG;AAEA,MAAa,sBAAsB;CAAC;CAAU;CAAU;CAAY;CAAW;CAAU;CAAQ;AAAU;AAK3G,MAAa,sBAAsB;CACjC,QAAQ;CACR,MAAM;CACN,MAAM;CACN,QAAQ;AACV;AAGA,SAAgB,cAAc,OAAqC;CACjE,OAAO,SAAS;AAClB;AAEA,MAAa,uBAAuB;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAGA,SAAgB,oBAAoB,OAA4C;CAC9E,OAAO,OAAO,UAAU,YAAY,qBAAqB,MAAK,SAAQ,SAAS,KAAK;AACtF;AAEA,SAAgB,mBAAmB,OAA2C;CAC5E,OAAO,OAAO,UAAU,YAAY,oBAAoB,MAAK,UAAS,UAAU,KAAK;AACvF;AAEA,SAAgB,iBAAiB,QAAyD;CACxF,MAAM,QAAQ,OAAO,WAAW,IAAI,OAAO,KAAK,KAAA;CAChD,OAAO,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ,KAAA;AACjE;AAEA,SAAgB,2BAA2B,QAAoC;CAC7E,MAAM,QAAQ,iBAAiB,MAAM;CACrC,OAAO,UAAU,UAAU,UAAU;AACvC;;AAGA,SAAgB,0BAA0B,OAAkC;CAC1E,OAAO,UAAU,YAAY,CAAC,2BAA2B,CAAC,KAAK,CAAC;AAClE;AAIA,SAAgB,mBAAmB,MAA4C;CAC7E,OAAO,cAAc,IAAI,IAAI,oBAAoB,QAAQ,KAAA;AAC3D;AAEA,MAAa,sBAAsB,CAAC,QAAQ,QAAQ;AAGpD,MAAa,uBAAuB;CAAC;CAAS;CAAe;CAAe;AAAQ;AAGpF,MAAa,wBAAwB;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAGA,MAAa,wBAAwB,CAAC,iBAAiB,aAAa;AA0SpE,SAAgB,6BAA6B,QAA2C;CACtF,QAAQ,QAAR;EACE,KAAK,gBACH,OAAO;EACT,KAAK,aACH,OAAO;EACT,KAAK,gBACH,OAAO;EACT,KAAK,UACH,OAAO;CACX;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/rules/validation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/rules/validation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EAGb,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,6BAA6B,IAAI,CAAC;AA+B/C,qBAAa,0BAA2B,SAAQ,KAAK;IACnD,QAAQ,CAAC,IAAI,0BAA0B;gBAE3B,OAAO,EAAE,MAAM;CAI5B;AAoCD,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,OAAO,EACd,KAAK,SAAI,EACT,IAAI,cAAoB,GACvB,oBAAoB,CAkCtB;AA8BD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,YAAY,CA2BhF;AAQD,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,EAAE,WAAW,SAAI,GAAG,mBAAmB,CA8JhG;AAED,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,EAAE,WAAW,SAAI,GAAG,qBAAqB,EAAE,CAiBjH"}
|
package/dist/rules/validation.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FACTORY_LINEAR_EVENTS, FACTORY_RULE_BOARDS, FACTORY_RULE_SOURCES, FACTORY_RULE_STAGES } from "./types.js";
|
|
2
2
|
//#region src/rules/validation.ts
|
|
3
3
|
const MAX_FACTORY_RULE_CAUSAL_DEPTH = 8;
|
|
4
4
|
const MAX_VERSION_LENGTH = 128;
|
|
@@ -117,7 +117,6 @@ function assertFactoryRules(rules) {
|
|
|
117
117
|
"work",
|
|
118
118
|
"review",
|
|
119
119
|
"tools",
|
|
120
|
-
"github",
|
|
121
120
|
"linear"
|
|
122
121
|
], "Factory rules");
|
|
123
122
|
boundedString(rules.version, "Factory rule version", MAX_VERSION_LENGTH);
|
|
@@ -130,13 +129,6 @@ function assertFactoryRules(rules) {
|
|
|
130
129
|
assertExactKeys(leaf, ["onResult"], `Factory rules.tools.${toolName}`);
|
|
131
130
|
if (leaf.onResult !== void 0 && typeof leaf.onResult !== "function") throw new FactoryRuleValidationError(`Factory rules.tools.${toolName}.onResult must be a function.`);
|
|
132
131
|
}
|
|
133
|
-
if (!isPlainObject(rules.github)) throw new FactoryRuleValidationError("Factory rules.github must be an object.");
|
|
134
|
-
for (const [event, leaf] of Object.entries(rules.github)) {
|
|
135
|
-
enumValue(event, FACTORY_GITHUB_EVENTS, "Factory GitHub event");
|
|
136
|
-
if (!isPlainObject(leaf)) throw new FactoryRuleValidationError(`Factory rules.github.${event} must be an object.`);
|
|
137
|
-
assertExactKeys(leaf, ["onEvent"], `Factory rules.github.${event}`);
|
|
138
|
-
if (leaf.onEvent !== void 0 && typeof leaf.onEvent !== "function") throw new FactoryRuleValidationError(`Factory rules.github.${event}.onEvent must be a function.`);
|
|
139
|
-
}
|
|
140
132
|
if (!isPlainObject(rules.linear)) throw new FactoryRuleValidationError("Factory rules.linear must be an object.");
|
|
141
133
|
for (const [event, leaf] of Object.entries(rules.linear)) {
|
|
142
134
|
enumValue(event, FACTORY_LINEAR_EVENTS, "Factory Linear event");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.js","names":[],"sources":["../../src/rules/validation.ts"],"sourcesContent":["import {\n FACTORY_GITHUB_EVENTS,\n FACTORY_LINEAR_EVENTS,\n FACTORY_RULE_BOARDS,\n FACTORY_RULE_SOURCES,\n FACTORY_RULE_STAGES,\n} from './types.js';\nimport type {\n FactoryBoardRules,\n FactoryCommitDecision,\n FactoryRuleDecision,\n FactoryRuleJsonValue,\n FactoryRules,\n FactoryRuleRejectionCode,\n WorkItemSource,\n} from './types.js';\n\nexport const MAX_FACTORY_RULE_CAUSAL_DEPTH = 8;\n\nconst MAX_VERSION_LENGTH = 128;\nconst MAX_IDEMPOTENCY_KEY_LENGTH = 256;\nconst MAX_REASON_LENGTH = 512;\nconst MAX_TITLE_LENGTH = 512;\nconst MAX_MESSAGE_LENGTH = 8_192;\nconst MAX_ARGUMENTS_LENGTH = 4_096;\nconst MAX_ROLE_LENGTH = 32;\nconst MAX_SKILL_NAME_LENGTH = 128;\nconst MAX_SOURCE_KEY_LENGTH = 256;\nconst MAX_URL_LENGTH = 2_048;\nconst MAX_METADATA_JSON_LENGTH = 16_384;\nconst MAX_JSON_DEPTH = 8;\nconst MAX_JSON_COLLECTION_SIZE = 100;\n\nconst IDENTIFIER_RE = /^[a-z0-9][a-z0-9_-]*$/i;\nconst SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\nconst SENSITIVE_KEY_RE = /(?:authorization|cookie|credential|password|secret|token)/i;\nconst WORK_ITEM_SOURCES: readonly WorkItemSource[] = ['github-issue', 'github-pr', 'linear-issue', 'manual'];\nconst REJECTION_CODES: readonly FactoryRuleRejectionCode[] = [\n 'forbidden',\n 'invalid_transition',\n 'missing_binding',\n 'stale',\n 'timeout',\n 'rule_error',\n 'causal_depth_exceeded',\n 'repeated_transition',\n];\n\nexport class FactoryRuleValidationError extends Error {\n readonly code = 'invalid_factory_rule';\n\n constructor(message: string) {\n super(message);\n this.name = 'FactoryRuleValidationError';\n }\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n\nfunction assertExactKeys(value: Record<string, unknown>, keys: readonly string[], label: string): void {\n const allowed = new Set(keys);\n if (Object.keys(value).some(key => !allowed.has(key))) {\n throw new FactoryRuleValidationError(`${label} contains an unsupported field.`);\n }\n}\n\nfunction boundedString(value: unknown, label: string, max: number, pattern?: RegExp): string {\n if (typeof value !== 'string') throw new FactoryRuleValidationError(`${label} must be a string.`);\n const normalized = value.trim();\n if (normalized.length === 0 || normalized.length > max || (pattern && !pattern.test(normalized))) {\n throw new FactoryRuleValidationError(`${label} is invalid.`);\n }\n return normalized;\n}\n\nfunction optionalBoundedString(value: unknown, label: string, max: number): string | undefined {\n if (value === undefined) return undefined;\n return boundedString(value, label, max);\n}\n\nfunction enumValue<T extends string>(value: unknown, allowed: readonly T[], label: string): T {\n if (typeof value !== 'string' || !allowed.includes(value as T)) {\n throw new FactoryRuleValidationError(`${label} is invalid.`);\n }\n return value as T;\n}\n\nexport function normalizeFactoryRuleJsonValue(\n value: unknown,\n depth = 0,\n seen = new Set<object>(),\n): FactoryRuleJsonValue {\n if (value === null || typeof value === 'boolean' || typeof value === 'string') return value;\n if (typeof value === 'number') {\n if (!Number.isFinite(value)) throw new FactoryRuleValidationError('Rule metadata must contain finite numbers.');\n return value;\n }\n if (depth >= MAX_JSON_DEPTH || (typeof value !== 'object' && !Array.isArray(value))) {\n throw new FactoryRuleValidationError('Rule metadata is not bounded JSON.');\n }\n if (seen.has(value as object)) throw new FactoryRuleValidationError('Rule metadata must not contain cycles.');\n seen.add(value as object);\n try {\n if (Array.isArray(value)) {\n if (value.length > MAX_JSON_COLLECTION_SIZE) {\n throw new FactoryRuleValidationError('Rule metadata contains too many entries.');\n }\n return value.map(entry => normalizeFactoryRuleJsonValue(entry, depth + 1, seen));\n }\n if (!isPlainObject(value)) throw new FactoryRuleValidationError('Rule metadata must use plain objects.');\n const entries = Object.entries(value);\n if (entries.length > MAX_JSON_COLLECTION_SIZE) {\n throw new FactoryRuleValidationError('Rule metadata contains too many fields.');\n }\n const sanitized: Record<string, FactoryRuleJsonValue> = {};\n for (const [key, entry] of entries) {\n const normalizedKey = boundedString(key, 'Rule metadata key', 128, IDENTIFIER_RE);\n sanitized[normalizedKey] = SENSITIVE_KEY_RE.test(normalizedKey)\n ? '[REDACTED]'\n : normalizeFactoryRuleJsonValue(entry, depth + 1, seen);\n }\n return sanitized;\n } finally {\n seen.delete(value as object);\n }\n}\n\nfunction sanitizeMetadata(value: unknown): Record<string, FactoryRuleJsonValue> | undefined {\n if (value === undefined) return undefined;\n const sanitized = normalizeFactoryRuleJsonValue(value);\n if (!isPlainObject(sanitized)) throw new FactoryRuleValidationError('Rule metadata must be an object.');\n if (JSON.stringify(sanitized).length > MAX_METADATA_JSON_LENGTH) {\n throw new FactoryRuleValidationError('Rule metadata is too large.');\n }\n return sanitized;\n}\n\nfunction validateBoardRules(rules: unknown, label: string): asserts rules is FactoryBoardRules {\n if (!isPlainObject(rules)) throw new FactoryRuleValidationError(`${label} must be an object.`);\n for (const [stage, sources] of Object.entries(rules)) {\n enumValue(stage, FACTORY_RULE_STAGES, `${label} stage`);\n if (!isPlainObject(sources)) throw new FactoryRuleValidationError(`${label}.${stage} must be an object.`);\n for (const [source, leaf] of Object.entries(sources)) {\n enumValue(source, FACTORY_RULE_SOURCES, `${label}.${stage} source`);\n if (!isPlainObject(leaf)) throw new FactoryRuleValidationError(`${label}.${stage}.${source} must be an object.`);\n assertExactKeys(leaf, ['onEnter', 'onExit'], `${label}.${stage}.${source}`);\n for (const handler of Object.values(leaf)) {\n if (handler !== undefined && typeof handler !== 'function') {\n throw new FactoryRuleValidationError(`${label}.${stage}.${source} handlers must be functions.`);\n }\n }\n }\n }\n}\n\nexport function assertFactoryRules(rules: unknown): asserts rules is FactoryRules {\n if (!isPlainObject(rules)) throw new FactoryRuleValidationError('Factory rules must be an object.');\n assertExactKeys(rules, ['version', 'work', 'review', 'tools', 'github', 'linear'], 'Factory rules');\n boundedString(rules.version, 'Factory rule version', MAX_VERSION_LENGTH);\n validateBoardRules(rules.work, 'Factory rules.work');\n validateBoardRules(rules.review, 'Factory rules.review');\n\n if (!isPlainObject(rules.tools)) throw new FactoryRuleValidationError('Factory rules.tools must be an object.');\n for (const [toolName, leaf] of Object.entries(rules.tools)) {\n boundedString(toolName, 'Factory tool name', 128, IDENTIFIER_RE);\n if (!isPlainObject(leaf))\n throw new FactoryRuleValidationError(`Factory rules.tools.${toolName} must be an object.`);\n assertExactKeys(leaf, ['onResult'], `Factory rules.tools.${toolName}`);\n if (leaf.onResult !== undefined && typeof leaf.onResult !== 'function') {\n throw new FactoryRuleValidationError(`Factory rules.tools.${toolName}.onResult must be a function.`);\n }\n }\n\n if (!isPlainObject(rules.github)) throw new FactoryRuleValidationError('Factory rules.github must be an object.');\n for (const [event, leaf] of Object.entries(rules.github)) {\n enumValue(event, FACTORY_GITHUB_EVENTS, 'Factory GitHub event');\n if (!isPlainObject(leaf)) throw new FactoryRuleValidationError(`Factory rules.github.${event} must be an object.`);\n assertExactKeys(leaf, ['onEvent'], `Factory rules.github.${event}`);\n if (leaf.onEvent !== undefined && typeof leaf.onEvent !== 'function') {\n throw new FactoryRuleValidationError(`Factory rules.github.${event}.onEvent must be a function.`);\n }\n }\n\n if (!isPlainObject(rules.linear)) throw new FactoryRuleValidationError('Factory rules.linear must be an object.');\n for (const [event, leaf] of Object.entries(rules.linear)) {\n enumValue(event, FACTORY_LINEAR_EVENTS, 'Factory Linear event');\n if (!isPlainObject(leaf)) throw new FactoryRuleValidationError(`Factory rules.linear.${event} must be an object.`);\n assertExactKeys(leaf, ['onEvent'], `Factory rules.linear.${event}`);\n if (leaf.onEvent !== undefined && typeof leaf.onEvent !== 'function') {\n throw new FactoryRuleValidationError(`Factory rules.linear.${event}.onEvent must be a function.`);\n }\n }\n}\n\nfunction commonCommitFields(value: Record<string, unknown>): { idempotencyKey: string } {\n return {\n idempotencyKey: boundedString(value.idempotencyKey, 'Factory decision idempotencyKey', MAX_IDEMPOTENCY_KEY_LENGTH),\n };\n}\n\nexport function validateFactoryRuleDecision(value: unknown, causalDepth = 0): FactoryRuleDecision {\n if (causalDepth > MAX_FACTORY_RULE_CAUSAL_DEPTH) {\n throw new FactoryRuleValidationError('Factory rule causal depth exceeded.');\n }\n if (!isPlainObject(value)) throw new FactoryRuleValidationError('Factory rule decision must be an object.');\n const type = value.type;\n if (typeof type !== 'string') throw new FactoryRuleValidationError('Factory rule decision type is required.');\n\n switch (type) {\n case 'reject': {\n assertExactKeys(value, ['type', 'code', 'reason'], 'Factory reject decision');\n return {\n type,\n code: enumValue(value.code, REJECTION_CODES, 'Factory rejection code'),\n reason: boundedString(value.reason, 'Factory rejection reason', MAX_REASON_LENGTH),\n };\n }\n case 'transition': {\n assertExactKeys(\n value,\n ['type', 'idempotencyKey', 'board', 'stage', 'message', 'reenter'],\n 'Factory transition decision',\n );\n if (value.reenter !== undefined && typeof value.reenter !== 'boolean') {\n throw new FactoryRuleValidationError('Factory transition reenter must be a boolean.');\n }\n let message: { text: string; role?: string } | undefined;\n if (value.message !== undefined) {\n if (!isPlainObject(value.message)) {\n throw new FactoryRuleValidationError('Factory transition message must be an object.');\n }\n assertExactKeys(value.message, ['text', 'role'], 'Factory transition message');\n const role =\n value.message.role === undefined\n ? undefined\n : boundedString(value.message.role, 'Factory transition message role', MAX_ROLE_LENGTH, IDENTIFIER_RE);\n message = {\n text: boundedString(value.message.text, 'Factory transition message text', MAX_MESSAGE_LENGTH),\n ...(role ? { role } : {}),\n };\n }\n return {\n type,\n ...commonCommitFields(value),\n board: enumValue(value.board, FACTORY_RULE_BOARDS, 'Factory transition board'),\n stage: enumValue(value.stage, FACTORY_RULE_STAGES, 'Factory transition stage'),\n ...(message ? { message } : {}),\n ...(value.reenter === true ? { reenter: true } : {}),\n };\n }\n case 'upsertLinkedWorkItem': {\n assertExactKeys(\n value,\n ['type', 'idempotencyKey', 'board', 'source', 'sourceKey', 'title', 'url', 'stage', 'metadata'],\n 'Factory linked work item decision',\n );\n const url = value.url;\n if (url !== null && (typeof url !== 'string' || url.length > MAX_URL_LENGTH || !/^https?:\\/\\//.test(url))) {\n throw new FactoryRuleValidationError('Factory linked work item URL is invalid.');\n }\n const metadata = sanitizeMetadata(value.metadata);\n return {\n type,\n ...commonCommitFields(value),\n board: enumValue(value.board, FACTORY_RULE_BOARDS, 'Factory linked work item board'),\n source: enumValue(value.source, WORK_ITEM_SOURCES, 'Factory linked work item source'),\n sourceKey: boundedString(value.sourceKey, 'Factory linked work item sourceKey', MAX_SOURCE_KEY_LENGTH),\n title: boundedString(value.title, 'Factory linked work item title', MAX_TITLE_LENGTH),\n url,\n stage: enumValue(value.stage, FACTORY_RULE_STAGES, 'Factory linked work item stage'),\n ...(metadata ? { metadata } : {}),\n };\n }\n case 'invokeSkill': {\n assertExactKeys(\n value,\n ['type', 'idempotencyKey', 'role', 'skillName', 'prompt', 'arguments', 'precedingMessage', 'cancelInFlight'],\n 'Factory invoke skill decision',\n );\n // A run activates a skill or carries a prompt, never both: they are two\n // ways to author the same kickoff message, so accepting both would leave\n // the dispatcher picking a winner.\n if ((value.skillName === undefined) === (value.prompt === undefined)) {\n throw new FactoryRuleValidationError('Factory skill invocation needs exactly one of skillName or prompt.');\n }\n const args = optionalBoundedString(value.arguments, 'Factory skill arguments', MAX_ARGUMENTS_LENGTH);\n const precedingMessage = optionalBoundedString(\n value.precedingMessage,\n 'Factory skill preceding message',\n MAX_MESSAGE_LENGTH,\n );\n if (value.cancelInFlight !== undefined && typeof value.cancelInFlight !== 'boolean') {\n throw new FactoryRuleValidationError('Factory skill cancelInFlight must be a boolean.');\n }\n return {\n type,\n ...commonCommitFields(value),\n role: boundedString(value.role, 'Factory skill role', MAX_ROLE_LENGTH, IDENTIFIER_RE),\n ...(value.skillName === undefined\n ? { prompt: boundedString(value.prompt, 'Factory skill prompt', MAX_MESSAGE_LENGTH) }\n : { skillName: boundedString(value.skillName, 'Factory skill name', MAX_SKILL_NAME_LENGTH, SKILL_NAME_RE) }),\n ...(args ? { arguments: args } : {}),\n ...(precedingMessage ? { precedingMessage } : {}),\n ...(value.cancelInFlight === true ? { cancelInFlight: true } : {}),\n };\n }\n case 'sendMessage': {\n assertExactKeys(\n value,\n ['type', 'idempotencyKey', 'role', 'message', 'priority', 'idleBehavior', 'prepareBinding'],\n 'Factory send message decision',\n );\n const priority =\n value.priority === undefined\n ? undefined\n : enumValue(value.priority, ['medium', 'high', 'urgent'] as const, 'Factory message priority');\n const idleBehavior =\n value.idleBehavior === undefined\n ? undefined\n : enumValue(value.idleBehavior, ['persist', 'wake'] as const, 'Factory message idle behavior');\n if (value.prepareBinding !== undefined && typeof value.prepareBinding !== 'boolean') {\n throw new FactoryRuleValidationError('Factory message prepareBinding must be a boolean.');\n }\n if (value.prepareBinding === true && value.role === undefined) {\n throw new FactoryRuleValidationError('Factory message prepareBinding requires a role.');\n }\n const role =\n value.role === undefined\n ? undefined\n : boundedString(value.role, 'Factory message role', MAX_ROLE_LENGTH, IDENTIFIER_RE);\n return {\n type,\n ...commonCommitFields(value),\n ...(role ? { role } : {}),\n message: boundedString(value.message, 'Factory message', MAX_MESSAGE_LENGTH),\n ...(priority ? { priority } : {}),\n ...(idleBehavior ? { idleBehavior } : {}),\n ...(value.prepareBinding === true ? { prepareBinding: true } : {}),\n };\n }\n case 'notify': {\n assertExactKeys(value, ['type', 'idempotencyKey', 'title', 'body', 'level'], 'Factory notify decision');\n const body = optionalBoundedString(value.body, 'Factory notification body', MAX_MESSAGE_LENGTH);\n const level =\n value.level === undefined\n ? undefined\n : enumValue(value.level, ['info', 'warning', 'error'] as const, 'Factory notification level');\n return {\n type,\n ...commonCommitFields(value),\n title: boundedString(value.title, 'Factory notification title', MAX_TITLE_LENGTH),\n ...(body ? { body } : {}),\n ...(level ? { level } : {}),\n };\n }\n default:\n throw new FactoryRuleValidationError('Factory rule decision type is unsupported.');\n }\n}\n\nexport function validateFactoryRuleDecisions(values: readonly unknown[], causalDepth = 0): FactoryCommitDecision[] {\n if (values.length > MAX_JSON_COLLECTION_SIZE) {\n throw new FactoryRuleValidationError('Factory rule produced too many decisions.');\n }\n const decisions: FactoryCommitDecision[] = [];\n for (const value of values) {\n const decision = validateFactoryRuleDecision(value, causalDepth);\n if (decision.type === 'reject') {\n throw new FactoryRuleValidationError('A rejection cannot be persisted with commit decisions.');\n }\n decisions.push(decision);\n }\n const keys = decisions.map(decision => decision.idempotencyKey);\n if (new Set(keys).size !== keys.length) {\n throw new FactoryRuleValidationError('Factory decisions require unique idempotency keys.');\n }\n return decisions;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"validation.js","names":[],"sources":["../../src/rules/validation.ts"],"sourcesContent":["import { FACTORY_LINEAR_EVENTS, FACTORY_RULE_BOARDS, FACTORY_RULE_SOURCES, FACTORY_RULE_STAGES } from './types.js';\nimport type {\n FactoryBoardRules,\n FactoryCommitDecision,\n FactoryRuleDecision,\n FactoryRuleJsonValue,\n FactoryRules,\n FactoryRuleRejectionCode,\n WorkItemSource,\n} from './types.js';\n\nexport const MAX_FACTORY_RULE_CAUSAL_DEPTH = 8;\n\nconst MAX_VERSION_LENGTH = 128;\nconst MAX_IDEMPOTENCY_KEY_LENGTH = 256;\nconst MAX_REASON_LENGTH = 512;\nconst MAX_TITLE_LENGTH = 512;\nconst MAX_MESSAGE_LENGTH = 8_192;\nconst MAX_ARGUMENTS_LENGTH = 4_096;\nconst MAX_ROLE_LENGTH = 32;\nconst MAX_SKILL_NAME_LENGTH = 128;\nconst MAX_SOURCE_KEY_LENGTH = 256;\nconst MAX_URL_LENGTH = 2_048;\nconst MAX_METADATA_JSON_LENGTH = 16_384;\nconst MAX_JSON_DEPTH = 8;\nconst MAX_JSON_COLLECTION_SIZE = 100;\n\nconst IDENTIFIER_RE = /^[a-z0-9][a-z0-9_-]*$/i;\nconst SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\nconst SENSITIVE_KEY_RE = /(?:authorization|cookie|credential|password|secret|token)/i;\nconst WORK_ITEM_SOURCES: readonly WorkItemSource[] = ['github-issue', 'github-pr', 'linear-issue', 'manual'];\nconst REJECTION_CODES: readonly FactoryRuleRejectionCode[] = [\n 'forbidden',\n 'invalid_transition',\n 'missing_binding',\n 'stale',\n 'timeout',\n 'rule_error',\n 'causal_depth_exceeded',\n 'repeated_transition',\n];\n\nexport class FactoryRuleValidationError extends Error {\n readonly code = 'invalid_factory_rule';\n\n constructor(message: string) {\n super(message);\n this.name = 'FactoryRuleValidationError';\n }\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n\nfunction assertExactKeys(value: Record<string, unknown>, keys: readonly string[], label: string): void {\n const allowed = new Set(keys);\n if (Object.keys(value).some(key => !allowed.has(key))) {\n throw new FactoryRuleValidationError(`${label} contains an unsupported field.`);\n }\n}\n\nfunction boundedString(value: unknown, label: string, max: number, pattern?: RegExp): string {\n if (typeof value !== 'string') throw new FactoryRuleValidationError(`${label} must be a string.`);\n const normalized = value.trim();\n if (normalized.length === 0 || normalized.length > max || (pattern && !pattern.test(normalized))) {\n throw new FactoryRuleValidationError(`${label} is invalid.`);\n }\n return normalized;\n}\n\nfunction optionalBoundedString(value: unknown, label: string, max: number): string | undefined {\n if (value === undefined) return undefined;\n return boundedString(value, label, max);\n}\n\nfunction enumValue<T extends string>(value: unknown, allowed: readonly T[], label: string): T {\n if (typeof value !== 'string' || !allowed.includes(value as T)) {\n throw new FactoryRuleValidationError(`${label} is invalid.`);\n }\n return value as T;\n}\n\nexport function normalizeFactoryRuleJsonValue(\n value: unknown,\n depth = 0,\n seen = new Set<object>(),\n): FactoryRuleJsonValue {\n if (value === null || typeof value === 'boolean' || typeof value === 'string') return value;\n if (typeof value === 'number') {\n if (!Number.isFinite(value)) throw new FactoryRuleValidationError('Rule metadata must contain finite numbers.');\n return value;\n }\n if (depth >= MAX_JSON_DEPTH || (typeof value !== 'object' && !Array.isArray(value))) {\n throw new FactoryRuleValidationError('Rule metadata is not bounded JSON.');\n }\n if (seen.has(value as object)) throw new FactoryRuleValidationError('Rule metadata must not contain cycles.');\n seen.add(value as object);\n try {\n if (Array.isArray(value)) {\n if (value.length > MAX_JSON_COLLECTION_SIZE) {\n throw new FactoryRuleValidationError('Rule metadata contains too many entries.');\n }\n return value.map(entry => normalizeFactoryRuleJsonValue(entry, depth + 1, seen));\n }\n if (!isPlainObject(value)) throw new FactoryRuleValidationError('Rule metadata must use plain objects.');\n const entries = Object.entries(value);\n if (entries.length > MAX_JSON_COLLECTION_SIZE) {\n throw new FactoryRuleValidationError('Rule metadata contains too many fields.');\n }\n const sanitized: Record<string, FactoryRuleJsonValue> = {};\n for (const [key, entry] of entries) {\n const normalizedKey = boundedString(key, 'Rule metadata key', 128, IDENTIFIER_RE);\n sanitized[normalizedKey] = SENSITIVE_KEY_RE.test(normalizedKey)\n ? '[REDACTED]'\n : normalizeFactoryRuleJsonValue(entry, depth + 1, seen);\n }\n return sanitized;\n } finally {\n seen.delete(value as object);\n }\n}\n\nfunction sanitizeMetadata(value: unknown): Record<string, FactoryRuleJsonValue> | undefined {\n if (value === undefined) return undefined;\n const sanitized = normalizeFactoryRuleJsonValue(value);\n if (!isPlainObject(sanitized)) throw new FactoryRuleValidationError('Rule metadata must be an object.');\n if (JSON.stringify(sanitized).length > MAX_METADATA_JSON_LENGTH) {\n throw new FactoryRuleValidationError('Rule metadata is too large.');\n }\n return sanitized;\n}\n\nfunction validateBoardRules(rules: unknown, label: string): asserts rules is FactoryBoardRules {\n if (!isPlainObject(rules)) throw new FactoryRuleValidationError(`${label} must be an object.`);\n for (const [stage, sources] of Object.entries(rules)) {\n enumValue(stage, FACTORY_RULE_STAGES, `${label} stage`);\n if (!isPlainObject(sources)) throw new FactoryRuleValidationError(`${label}.${stage} must be an object.`);\n for (const [source, leaf] of Object.entries(sources)) {\n enumValue(source, FACTORY_RULE_SOURCES, `${label}.${stage} source`);\n if (!isPlainObject(leaf)) throw new FactoryRuleValidationError(`${label}.${stage}.${source} must be an object.`);\n assertExactKeys(leaf, ['onEnter', 'onExit'], `${label}.${stage}.${source}`);\n for (const handler of Object.values(leaf)) {\n if (handler !== undefined && typeof handler !== 'function') {\n throw new FactoryRuleValidationError(`${label}.${stage}.${source} handlers must be functions.`);\n }\n }\n }\n }\n}\n\nexport function assertFactoryRules(rules: unknown): asserts rules is FactoryRules {\n if (!isPlainObject(rules)) throw new FactoryRuleValidationError('Factory rules must be an object.');\n assertExactKeys(rules, ['version', 'work', 'review', 'tools', 'linear'], 'Factory rules');\n boundedString(rules.version, 'Factory rule version', MAX_VERSION_LENGTH);\n validateBoardRules(rules.work, 'Factory rules.work');\n validateBoardRules(rules.review, 'Factory rules.review');\n\n if (!isPlainObject(rules.tools)) throw new FactoryRuleValidationError('Factory rules.tools must be an object.');\n for (const [toolName, leaf] of Object.entries(rules.tools)) {\n boundedString(toolName, 'Factory tool name', 128, IDENTIFIER_RE);\n if (!isPlainObject(leaf))\n throw new FactoryRuleValidationError(`Factory rules.tools.${toolName} must be an object.`);\n assertExactKeys(leaf, ['onResult'], `Factory rules.tools.${toolName}`);\n if (leaf.onResult !== undefined && typeof leaf.onResult !== 'function') {\n throw new FactoryRuleValidationError(`Factory rules.tools.${toolName}.onResult must be a function.`);\n }\n }\n\n if (!isPlainObject(rules.linear)) throw new FactoryRuleValidationError('Factory rules.linear must be an object.');\n for (const [event, leaf] of Object.entries(rules.linear)) {\n enumValue(event, FACTORY_LINEAR_EVENTS, 'Factory Linear event');\n if (!isPlainObject(leaf)) throw new FactoryRuleValidationError(`Factory rules.linear.${event} must be an object.`);\n assertExactKeys(leaf, ['onEvent'], `Factory rules.linear.${event}`);\n if (leaf.onEvent !== undefined && typeof leaf.onEvent !== 'function') {\n throw new FactoryRuleValidationError(`Factory rules.linear.${event}.onEvent must be a function.`);\n }\n }\n}\n\nfunction commonCommitFields(value: Record<string, unknown>): { idempotencyKey: string } {\n return {\n idempotencyKey: boundedString(value.idempotencyKey, 'Factory decision idempotencyKey', MAX_IDEMPOTENCY_KEY_LENGTH),\n };\n}\n\nexport function validateFactoryRuleDecision(value: unknown, causalDepth = 0): FactoryRuleDecision {\n if (causalDepth > MAX_FACTORY_RULE_CAUSAL_DEPTH) {\n throw new FactoryRuleValidationError('Factory rule causal depth exceeded.');\n }\n if (!isPlainObject(value)) throw new FactoryRuleValidationError('Factory rule decision must be an object.');\n const type = value.type;\n if (typeof type !== 'string') throw new FactoryRuleValidationError('Factory rule decision type is required.');\n\n switch (type) {\n case 'reject': {\n assertExactKeys(value, ['type', 'code', 'reason'], 'Factory reject decision');\n return {\n type,\n code: enumValue(value.code, REJECTION_CODES, 'Factory rejection code'),\n reason: boundedString(value.reason, 'Factory rejection reason', MAX_REASON_LENGTH),\n };\n }\n case 'transition': {\n assertExactKeys(\n value,\n ['type', 'idempotencyKey', 'board', 'stage', 'message', 'reenter'],\n 'Factory transition decision',\n );\n if (value.reenter !== undefined && typeof value.reenter !== 'boolean') {\n throw new FactoryRuleValidationError('Factory transition reenter must be a boolean.');\n }\n let message: { text: string; role?: string } | undefined;\n if (value.message !== undefined) {\n if (!isPlainObject(value.message)) {\n throw new FactoryRuleValidationError('Factory transition message must be an object.');\n }\n assertExactKeys(value.message, ['text', 'role'], 'Factory transition message');\n const role =\n value.message.role === undefined\n ? undefined\n : boundedString(value.message.role, 'Factory transition message role', MAX_ROLE_LENGTH, IDENTIFIER_RE);\n message = {\n text: boundedString(value.message.text, 'Factory transition message text', MAX_MESSAGE_LENGTH),\n ...(role ? { role } : {}),\n };\n }\n return {\n type,\n ...commonCommitFields(value),\n board: enumValue(value.board, FACTORY_RULE_BOARDS, 'Factory transition board'),\n stage: enumValue(value.stage, FACTORY_RULE_STAGES, 'Factory transition stage'),\n ...(message ? { message } : {}),\n ...(value.reenter === true ? { reenter: true } : {}),\n };\n }\n case 'upsertLinkedWorkItem': {\n assertExactKeys(\n value,\n ['type', 'idempotencyKey', 'board', 'source', 'sourceKey', 'title', 'url', 'stage', 'metadata'],\n 'Factory linked work item decision',\n );\n const url = value.url;\n if (url !== null && (typeof url !== 'string' || url.length > MAX_URL_LENGTH || !/^https?:\\/\\//.test(url))) {\n throw new FactoryRuleValidationError('Factory linked work item URL is invalid.');\n }\n const metadata = sanitizeMetadata(value.metadata);\n return {\n type,\n ...commonCommitFields(value),\n board: enumValue(value.board, FACTORY_RULE_BOARDS, 'Factory linked work item board'),\n source: enumValue(value.source, WORK_ITEM_SOURCES, 'Factory linked work item source'),\n sourceKey: boundedString(value.sourceKey, 'Factory linked work item sourceKey', MAX_SOURCE_KEY_LENGTH),\n title: boundedString(value.title, 'Factory linked work item title', MAX_TITLE_LENGTH),\n url,\n stage: enumValue(value.stage, FACTORY_RULE_STAGES, 'Factory linked work item stage'),\n ...(metadata ? { metadata } : {}),\n };\n }\n case 'invokeSkill': {\n assertExactKeys(\n value,\n ['type', 'idempotencyKey', 'role', 'skillName', 'prompt', 'arguments', 'precedingMessage', 'cancelInFlight'],\n 'Factory invoke skill decision',\n );\n // A run activates a skill or carries a prompt, never both: they are two\n // ways to author the same kickoff message, so accepting both would leave\n // the dispatcher picking a winner.\n if ((value.skillName === undefined) === (value.prompt === undefined)) {\n throw new FactoryRuleValidationError('Factory skill invocation needs exactly one of skillName or prompt.');\n }\n const args = optionalBoundedString(value.arguments, 'Factory skill arguments', MAX_ARGUMENTS_LENGTH);\n const precedingMessage = optionalBoundedString(\n value.precedingMessage,\n 'Factory skill preceding message',\n MAX_MESSAGE_LENGTH,\n );\n if (value.cancelInFlight !== undefined && typeof value.cancelInFlight !== 'boolean') {\n throw new FactoryRuleValidationError('Factory skill cancelInFlight must be a boolean.');\n }\n return {\n type,\n ...commonCommitFields(value),\n role: boundedString(value.role, 'Factory skill role', MAX_ROLE_LENGTH, IDENTIFIER_RE),\n ...(value.skillName === undefined\n ? { prompt: boundedString(value.prompt, 'Factory skill prompt', MAX_MESSAGE_LENGTH) }\n : { skillName: boundedString(value.skillName, 'Factory skill name', MAX_SKILL_NAME_LENGTH, SKILL_NAME_RE) }),\n ...(args ? { arguments: args } : {}),\n ...(precedingMessage ? { precedingMessage } : {}),\n ...(value.cancelInFlight === true ? { cancelInFlight: true } : {}),\n };\n }\n case 'sendMessage': {\n assertExactKeys(\n value,\n ['type', 'idempotencyKey', 'role', 'message', 'priority', 'idleBehavior', 'prepareBinding'],\n 'Factory send message decision',\n );\n const priority =\n value.priority === undefined\n ? undefined\n : enumValue(value.priority, ['medium', 'high', 'urgent'] as const, 'Factory message priority');\n const idleBehavior =\n value.idleBehavior === undefined\n ? undefined\n : enumValue(value.idleBehavior, ['persist', 'wake'] as const, 'Factory message idle behavior');\n if (value.prepareBinding !== undefined && typeof value.prepareBinding !== 'boolean') {\n throw new FactoryRuleValidationError('Factory message prepareBinding must be a boolean.');\n }\n if (value.prepareBinding === true && value.role === undefined) {\n throw new FactoryRuleValidationError('Factory message prepareBinding requires a role.');\n }\n const role =\n value.role === undefined\n ? undefined\n : boundedString(value.role, 'Factory message role', MAX_ROLE_LENGTH, IDENTIFIER_RE);\n return {\n type,\n ...commonCommitFields(value),\n ...(role ? { role } : {}),\n message: boundedString(value.message, 'Factory message', MAX_MESSAGE_LENGTH),\n ...(priority ? { priority } : {}),\n ...(idleBehavior ? { idleBehavior } : {}),\n ...(value.prepareBinding === true ? { prepareBinding: true } : {}),\n };\n }\n case 'notify': {\n assertExactKeys(value, ['type', 'idempotencyKey', 'title', 'body', 'level'], 'Factory notify decision');\n const body = optionalBoundedString(value.body, 'Factory notification body', MAX_MESSAGE_LENGTH);\n const level =\n value.level === undefined\n ? undefined\n : enumValue(value.level, ['info', 'warning', 'error'] as const, 'Factory notification level');\n return {\n type,\n ...commonCommitFields(value),\n title: boundedString(value.title, 'Factory notification title', MAX_TITLE_LENGTH),\n ...(body ? { body } : {}),\n ...(level ? { level } : {}),\n };\n }\n default:\n throw new FactoryRuleValidationError('Factory rule decision type is unsupported.');\n }\n}\n\nexport function validateFactoryRuleDecisions(values: readonly unknown[], causalDepth = 0): FactoryCommitDecision[] {\n if (values.length > MAX_JSON_COLLECTION_SIZE) {\n throw new FactoryRuleValidationError('Factory rule produced too many decisions.');\n }\n const decisions: FactoryCommitDecision[] = [];\n for (const value of values) {\n const decision = validateFactoryRuleDecision(value, causalDepth);\n if (decision.type === 'reject') {\n throw new FactoryRuleValidationError('A rejection cannot be persisted with commit decisions.');\n }\n decisions.push(decision);\n }\n const keys = decisions.map(decision => decision.idempotencyKey);\n if (new Set(keys).size !== keys.length) {\n throw new FactoryRuleValidationError('Factory decisions require unique idempotency keys.');\n }\n return decisions;\n}\n"],"mappings":"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"}
|
|
@@ -380,6 +380,7 @@ export interface WorkItemRow {
|
|
|
380
380
|
id: string;
|
|
381
381
|
orgId: string;
|
|
382
382
|
factoryProjectId: string;
|
|
383
|
+
board: string | null;
|
|
383
384
|
externalSource: ExternalWorkItemSource | null;
|
|
384
385
|
parentWorkItemId: string | null;
|
|
385
386
|
title: string;
|
|
@@ -417,6 +418,7 @@ export interface WorkItemRow {
|
|
|
417
418
|
updatedAt: Date;
|
|
418
419
|
}
|
|
419
420
|
export interface CreateWorkItemInput {
|
|
421
|
+
board?: string;
|
|
420
422
|
externalSource?: ExternalWorkItemSource | null;
|
|
421
423
|
parentWorkItemId?: string | null;
|
|
422
424
|
title: string;
|
|
@@ -425,6 +427,7 @@ export interface CreateWorkItemInput {
|
|
|
425
427
|
metadata?: Record<string, unknown> | null;
|
|
426
428
|
}
|
|
427
429
|
export interface UpdateWorkItemInput {
|
|
430
|
+
board?: string;
|
|
428
431
|
parentWorkItemId?: string | null;
|
|
429
432
|
title?: string;
|
|
430
433
|
stages?: WorkItemStage[];
|
|
@@ -452,6 +455,11 @@ export declare function externalSourceKey(source: ExternalWorkItemSource | null
|
|
|
452
455
|
export declare class WorkItemRelationError extends Error {
|
|
453
456
|
readonly code = "invalid_work_item_relation";
|
|
454
457
|
}
|
|
458
|
+
export declare class WorkItemUpdateConflictError extends Error {
|
|
459
|
+
readonly reason: 'board' | 'revision';
|
|
460
|
+
readonly code = "work_item_update_conflict";
|
|
461
|
+
constructor(reason: 'board' | 'revision');
|
|
462
|
+
}
|
|
455
463
|
export declare function validateParentRelation(projectItems: WorkItemRow[], itemId: string | undefined, parentWorkItemId: string | null): void;
|
|
456
464
|
/**
|
|
457
465
|
* Diff `oldStages` → `newStages` and return the updated history: exited stages
|
|
@@ -674,11 +682,13 @@ export declare class WorkItemsStorage extends FactoryStorageDomain {
|
|
|
674
682
|
userId: string;
|
|
675
683
|
parentWorkItemId: string;
|
|
676
684
|
}): Promise<WorkItemRow | null>;
|
|
677
|
-
update({ orgId, id, userId, patch, }: {
|
|
685
|
+
update({ orgId, id, userId, patch, expectedRevision, expectedBoard, }: {
|
|
678
686
|
orgId: string;
|
|
679
687
|
id: string;
|
|
680
688
|
userId: string;
|
|
681
689
|
patch: UpdateWorkItemInput;
|
|
690
|
+
expectedRevision?: number;
|
|
691
|
+
expectedBoard?: string | null;
|
|
682
692
|
}): Promise<{
|
|
683
693
|
item: WorkItemRow;
|
|
684
694
|
previous: WorkItemPriorState;
|