@objectstack/plugin-approvals 5.1.0 → 6.0.0
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +56 -0
- package/dist/index.d.mts +33 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +82 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +82 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -5
- package/src/approval-service.test.ts +109 -0
- package/src/approval-service.ts +88 -3
- package/src/approvals-plugin.ts +14 -0
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/approval-service.ts","../src/action-executor.ts","../src/approvals-plugin.ts","../src/lifecycle-hooks.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * @objectstack/plugin-approvals\n *\n * Multi-step approval engine for ObjectStack.\n * Persists sys_approval_process / sys_approval_request / sys_approval_action\n * and drives the cycle: submit → review → approve/reject → effects.\n */\n\nexport {\n SysApprovalProcess,\n SysApprovalRequest,\n SysApprovalAction,\n} from '@objectstack/platform-objects/audit';\nexport {\n ApprovalService,\n type ApprovalEngine,\n type ApprovalClock,\n type ApprovalServiceOptions,\n} from './approval-service.js';\nexport {\n ApprovalsServicePlugin,\n type ApprovalsPluginOptions,\n} from './approvals-plugin.js';\nexport type {\n IApprovalService,\n ApprovalProcessRow,\n ApprovalRequestRow,\n ApprovalActionRow,\n ApprovalDecisionInput,\n ApprovalDecisionResult,\n ApprovalStatus,\n DefineApprovalProcessInput,\n SubmitApprovalInput,\n} from '@objectstack/spec/contracts';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ApprovalProcessSchema } from '@objectstack/spec/automation';\nimport type {\n IApprovalService,\n ApprovalProcessRow,\n ApprovalRequestRow,\n ApprovalActionRow,\n ApprovalDecisionInput,\n ApprovalDecisionResult,\n ApprovalStatus,\n DefineApprovalProcessInput,\n SubmitApprovalInput,\n SharingExecutionContext,\n} from '@objectstack/spec/contracts';\nimport { executeActions, type ApprovalTrigger, type FetchLike } from './action-executor.js';\n\n/**\n * Narrow engine surface — keeps the service testable without booting\n * a real ObjectQL kernel.\n */\nexport interface ApprovalEngine {\n find(object: string, options?: any): Promise<any[]>;\n insert(object: string, data: any, options?: any): Promise<any>;\n update(object: string, idOrData: any, dataOrOptions?: any, options?: any): Promise<any>;\n delete(object: string, options?: any): Promise<any>;\n}\n\nexport interface ApprovalClock { now(): Date }\n\nconst SYSTEM_CTX = { isSystem: true, roles: [], permissions: [] } as const;\n\nfunction uid(prefix: string): string {\n const g: any = globalThis as any;\n if (g.crypto?.randomUUID) return `${prefix}_${g.crypto.randomUUID()}`;\n return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction parseJson<T = any>(raw: unknown, fallback: T): T {\n if (raw == null || raw === '') return fallback;\n if (typeof raw === 'string') {\n try { return JSON.parse(raw) as T; } catch { return fallback; }\n }\n return raw as T;\n}\n\nfunction csvSplit(raw: unknown): string[] {\n if (!raw) return [];\n if (Array.isArray(raw)) return raw.map(String).filter(Boolean);\n return String(raw).split(',').map(s => s.trim()).filter(Boolean);\n}\n\nfunction rowFromProcess(row: any): ApprovalProcessRow {\n return {\n id: String(row.id),\n name: String(row.name ?? ''),\n label: String(row.label ?? ''),\n object_name: String(row.object_name ?? ''),\n description: row.description ?? undefined,\n active: row.active !== false,\n definition: parseJson(row.definition_json, {}),\n created_at: row.created_at ?? undefined,\n updated_at: row.updated_at ?? undefined,\n };\n}\n\nfunction rowFromRequest(row: any): ApprovalRequestRow {\n return {\n id: String(row.id),\n organization_id: row.organization_id ?? undefined,\n process_name: String(row.process_name ?? ''),\n object_name: String(row.object_name ?? ''),\n record_id: String(row.record_id ?? ''),\n submitter_id: row.submitter_id ?? undefined,\n submitter_comment: row.submitter_comment ?? undefined,\n status: (row.status as ApprovalStatus) ?? 'pending',\n current_step: row.current_step ?? undefined,\n current_step_index: row.current_step_index ?? undefined,\n pending_approvers: csvSplit(row.pending_approvers),\n payload: parseJson(row.payload_json, undefined),\n completed_at: row.completed_at ?? undefined,\n created_at: row.created_at ?? undefined,\n updated_at: row.updated_at ?? undefined,\n } as any;\n}\n\nfunction rowFromAction(row: any): ApprovalActionRow {\n return {\n id: String(row.id),\n request_id: String(row.request_id),\n step_name: row.step_name ?? undefined,\n step_index: row.step_index ?? undefined,\n action: row.action,\n actor_id: row.actor_id ?? undefined,\n comment: row.comment ?? undefined,\n created_at: row.created_at ?? undefined,\n };\n}\n\n// Note: legacy synchronous `resolveApprovers` removed in M10.17.1 — replaced\n// by the async `expandApprovers` member which routes through the team/dept\n// graph tables (with prefixed-literal fallback for back-compat).\n\nexport interface ApprovalServiceOptions {\n engine: ApprovalEngine;\n clock?: ApprovalClock;\n logger?: { info?: (msg: any, ...rest: any[]) => void; warn?: (msg: any, ...rest: any[]) => void; error?: (msg: any, ...rest: any[]) => void; debug?: (msg: any, ...rest: any[]) => void };\n /** Optional fetch impl for `webhook` actions; defaults to global. */\n fetch?: FetchLike;\n /** Webhook timeout in ms; default 5000. */\n webhookTimeoutMs?: number;\n /**\n * Called after the process registry changes (defineProcess / deleteProcess).\n * The plugin uses this to re-bind lifecycle hooks for auto-trigger / lock.\n */\n onRegistryChange?: () => void | Promise<void>;\n}\n\nexport class ApprovalService implements IApprovalService {\n private readonly engine: ApprovalEngine;\n private readonly clock: ApprovalClock;\n private readonly logger?: ApprovalServiceOptions['logger'];\n private readonly fetchImpl?: FetchLike;\n private readonly webhookTimeoutMs?: number;\n private readonly onRegistryChange?: () => void | Promise<void>;\n\n constructor(opts: ApprovalServiceOptions) {\n this.engine = opts.engine;\n this.clock = opts.clock ?? { now: () => new Date() };\n this.logger = opts.logger;\n this.fetchImpl = opts.fetch;\n this.webhookTimeoutMs = opts.webhookTimeoutMs;\n this.onRegistryChange = opts.onRegistryChange;\n }\n\n /** Allow the plugin to attach a hook re-binding callback after construction. */\n setRegistryChangeHandler(handler: () => void | Promise<void>): void {\n (this as any).onRegistryChange = handler;\n }\n\n /**\n * Expand the approvers on a step into user IDs by querying the graph\n * tables for `team:` / `department:` / `role:` / `manager:` approver\n * types. Falls back to a prefixed literal (`type:value`) when graph\n * lookups produce nothing — so existing test fixtures and approver\n * flows that rely on substring matching keep working.\n *\n * **Graph semantics (M10.17.1):**\n * - `team` → flat members of `sys_team` (better-auth; no BFS)\n * - `department` → recursive BFS of `sys_department.parent_department_id`\n * → members of every descendant via `sys_department_member`\n * - `role` → users with `sys_member.role = value` in tenant\n * - `manager` → `sys_user.manager_id` of `record[value] ?? record.owner_id`\n * - `field` → literal user id stored in `record[value]`\n * - `user` → literal value\n */\n private async expandApprovers(step: any, record?: any, organizationId?: string | null): Promise<string[]> {\n if (!step || !Array.isArray(step.approvers)) return [];\n const out: string[] = [];\n for (const a of step.approvers) {\n if (!a) continue;\n if (a.type === 'user') { out.push(String(a.value)); continue; }\n if (a.type === 'field' && record) { out.push(String((record as any)[a.value] ?? '')); continue; }\n try {\n if (a.type === 'team') {\n const users = await this.expandTeamUsers(String(a.value));\n if (users.length) { for (const u of users) out.push(u); continue; }\n } else if (a.type === 'department' || a.type === 'dept') {\n const users = await this.expandDepartmentUsers(String(a.value), organizationId);\n if (users.length) { for (const u of users) out.push(u); continue; }\n } else if (a.type === 'role') {\n const users = await this.expandRoleUsers(String(a.value), organizationId);\n if (users.length) { for (const u of users) out.push(u); continue; }\n } else if (a.type === 'manager' && record) {\n const subject = (record as any)[a.value] ?? (record as any).owner_id;\n if (subject) {\n const mgr = await this.lookupManager(String(subject));\n if (mgr) { out.push(mgr); continue; }\n }\n }\n } catch { /* fall through */ }\n out.push(`${a.type}:${a.value}`);\n }\n return out.filter(Boolean);\n }\n\n /** Flat team — `sys_team` is better-auth's collaboration grouping (no hierarchy). */\n private async expandTeamUsers(teamId: string): Promise<string[]> {\n if (!teamId) return [];\n let rows: any[] = [];\n try {\n rows = await this.engine.find('sys_team_member', {\n filter: { team_id: teamId },\n fields: ['user_id'],\n limit: 10000,\n context: SYSTEM_CTX,\n } as any);\n } catch { rows = []; }\n return Array.from(new Set((rows ?? []).map((r: any) => String(r.user_id ?? '')).filter(Boolean)));\n }\n\n /** Recursive department — walks `sys_department.parent_department_id`. */\n private async expandDepartmentUsers(departmentId: string, organizationId?: string | null): Promise<string[]> {\n if (!departmentId) return [];\n // Seed sanity check: skip if dept doesn't exist or is inactive within tenant.\n try {\n const seed = await this.engine.find('sys_department', {\n filter: organizationId\n ? { id: departmentId, organization_id: organizationId }\n : { id: departmentId },\n fields: ['id', 'active'],\n limit: 1,\n context: SYSTEM_CTX,\n } as any);\n const seedRow: any = Array.isArray(seed) ? seed[0] : null;\n if (!seedRow || seedRow.active === false) return [];\n } catch { return []; }\n\n const seen = new Set<string>([departmentId]);\n const queue: string[] = [departmentId];\n while (queue.length) {\n const parent = queue.shift()!;\n let kids: any[] = [];\n try {\n const filter: any = { parent_department_id: parent, active: { $ne: false } };\n if (organizationId) filter.organization_id = organizationId;\n kids = await this.engine.find('sys_department', { filter, fields: ['id'], limit: 1000, context: SYSTEM_CTX } as any);\n } catch { kids = []; }\n for (const k of kids ?? []) {\n const kid = String((k as any).id ?? '');\n if (kid && !seen.has(kid)) { seen.add(kid); queue.push(kid); }\n }\n }\n let rows: any[] = [];\n try {\n rows = await this.engine.find('sys_department_member', {\n filter: { department_id: { $in: Array.from(seen) } },\n fields: ['user_id'],\n limit: 10000,\n context: SYSTEM_CTX,\n } as any);\n } catch { rows = []; }\n return Array.from(new Set((rows ?? []).map((r: any) => String(r.user_id ?? '')).filter(Boolean)));\n }\n\n private async expandRoleUsers(roleName: string, organizationId?: string | null): Promise<string[]> {\n if (!roleName) return [];\n const filter: any = { role: roleName };\n if (organizationId) filter.organization_id = organizationId;\n let rows: any[] = [];\n try {\n rows = await this.engine.find('sys_member', { filter, fields: ['user_id'], limit: 10000, context: SYSTEM_CTX } as any);\n } catch { rows = []; }\n return Array.from(new Set((rows ?? []).map((r: any) => String(r.user_id ?? '')).filter(Boolean)));\n }\n\n private async lookupManager(userId: string): Promise<string | null> {\n try {\n const rows = await this.engine.find('sys_user', {\n filter: { id: userId }, fields: ['id', 'manager_id'], limit: 1, context: SYSTEM_CTX,\n } as any);\n const row: any = Array.isArray(rows) ? rows[0] : null;\n return row?.manager_id ? String(row.manager_id) : null;\n } catch { return null; }\n }\n\n\n private async notifyRegistryChanged(): Promise<void> {\n const cb = this.onRegistryChange ?? ((this as any).onRegistryChange as (() => void | Promise<void>) | undefined);\n if (!cb) return;\n try { await cb(); }\n catch (err: any) { this.logger?.warn?.('[approvals] onRegistryChange handler failed', { error: err?.message }); }\n }\n\n /** Mirror request status onto `process.approvalStatusField` if configured. */\n private async syncStatusField(process: ApprovalProcessRow, request: ApprovalRequestRow): Promise<void> {\n const field = (process.definition as any)?.approvalStatusField;\n if (!field) return;\n try {\n await this.engine.update(\n process.object_name,\n { id: request.record_id, [field]: request.status },\n { context: SYSTEM_CTX },\n );\n } catch (err: any) {\n this.logger?.warn?.(`[approvals] syncStatusField failed: ${err?.message ?? err}`);\n }\n }\n\n /** Convenience wrapper that funnels every action invocation through the executor. */\n private async runActions(\n actions: any[] | undefined | null,\n trigger: ApprovalTrigger,\n process: ApprovalProcessRow,\n request: ApprovalRequestRow,\n step: any | undefined,\n actorId: string | null | undefined,\n comment: string | null | undefined,\n ): Promise<void> {\n if (!actions || actions.length === 0) return;\n await executeActions(actions, {\n trigger,\n process: { ...process, object: process.object_name },\n request,\n step,\n actorId: actorId ?? null,\n comment: comment ?? null,\n }, {\n engine: this.engine,\n logger: this.logger,\n fetch: this.fetchImpl,\n webhookTimeoutMs: this.webhookTimeoutMs,\n });\n }\n\n // ── Process definitions ──────────────────────────────────────\n\n async defineProcess(input: DefineApprovalProcessInput, _context: SharingExecutionContext): Promise<ApprovalProcessRow> {\n if (!input.name) throw new Error('VALIDATION_FAILED: name is required');\n if (!input.label) throw new Error('VALIDATION_FAILED: label is required');\n if (!input.object) throw new Error('VALIDATION_FAILED: object is required');\n if (!input.definition) throw new Error('VALIDATION_FAILED: definition is required');\n\n const parsed = ApprovalProcessSchema.safeParse(input.definition);\n if (!parsed.success) {\n const msg = parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; ');\n throw new Error(`VALIDATION_FAILED: ${msg}`);\n }\n\n const now = this.clock.now().toISOString();\n const payload: any = {\n name: input.name,\n label: input.label,\n object_name: input.object,\n description: input.description ?? null,\n active: input.active !== false,\n definition_json: JSON.stringify(parsed.data),\n updated_at: now,\n };\n\n // Upsert by name.\n const existing = await this.engine.find('sys_approval_process', {\n where: { name: input.name }, limit: 1, context: SYSTEM_CTX,\n });\n if (Array.isArray(existing) && existing[0]) {\n const id = existing[0].id;\n await this.engine.update('sys_approval_process', { id, ...payload }, { context: SYSTEM_CTX });\n const row = rowFromProcess({ ...existing[0], ...payload, id });\n await this.notifyRegistryChanged();\n return row;\n }\n\n const id = input.id ?? uid('apv');\n const row = { id, ...payload, created_at: now };\n await this.engine.insert('sys_approval_process', row, { context: SYSTEM_CTX });\n const out = rowFromProcess(row);\n await this.notifyRegistryChanged();\n return out;\n }\n\n async listProcesses(\n filter: { object?: string; activeOnly?: boolean } | undefined,\n _context: SharingExecutionContext,\n ): Promise<ApprovalProcessRow[]> {\n const f: any = {};\n if (filter?.object) f.object_name = filter.object;\n if (filter?.activeOnly) f.active = true;\n const rows = await this.engine.find('sys_approval_process', {\n where: f, limit: 500, orderBy: [{ field: 'updated_at', direction: 'desc' }], context: SYSTEM_CTX,\n });\n return Array.isArray(rows) ? rows.map(rowFromProcess) : [];\n }\n\n async getProcess(idOrName: string, _context: SharingExecutionContext): Promise<ApprovalProcessRow | null> {\n if (!idOrName) return null;\n let rows = await this.engine.find('sys_approval_process', {\n where: { id: idOrName }, limit: 1, context: SYSTEM_CTX,\n });\n if (!Array.isArray(rows) || !rows[0]) {\n rows = await this.engine.find('sys_approval_process', {\n where: { name: idOrName }, limit: 1, context: SYSTEM_CTX,\n });\n }\n return Array.isArray(rows) && rows[0] ? rowFromProcess(rows[0]) : null;\n }\n\n async deleteProcess(idOrName: string, context: SharingExecutionContext): Promise<void> {\n if (!idOrName) throw new Error('VALIDATION_FAILED: idOrName is required');\n const proc = await this.getProcess(idOrName, context);\n if (!proc) return;\n await this.engine.delete('sys_approval_process', { where: { id: proc.id }, context: SYSTEM_CTX });\n await this.notifyRegistryChanged();\n }\n\n // ── Requests ─────────────────────────────────────────────────\n\n async submit(input: SubmitApprovalInput, context: SharingExecutionContext): Promise<ApprovalRequestRow> {\n if (!input.object) throw new Error('VALIDATION_FAILED: object is required');\n if (!input.recordId) throw new Error('VALIDATION_FAILED: recordId is required');\n\n // Find active process for the object (or by name when supplied).\n let process: ApprovalProcessRow | null = null;\n if (input.processName) {\n process = await this.getProcess(input.processName, context);\n if (process && !process.active) {\n throw new Error(`NO_ACTIVE_PROCESS: process '${input.processName}' is not active`);\n }\n } else {\n const list = await this.listProcesses({ object: input.object, activeOnly: true }, context);\n process = list[0] ?? null;\n }\n if (!process) {\n throw new Error(`NO_ACTIVE_PROCESS: no active approval process for object '${input.object}'`);\n }\n\n // De-duplicate: only one pending request per (object, record).\n const existing = await this.engine.find('sys_approval_request', {\n where: { object_name: input.object, record_id: input.recordId, status: 'pending' },\n limit: 1, context: SYSTEM_CTX,\n });\n if (Array.isArray(existing) && existing[0]) {\n throw new Error(`DUPLICATE_REQUEST: a pending approval already exists for ${input.object}/${input.recordId}`);\n }\n\n const steps: any[] = process.definition?.steps ?? [];\n if (steps.length === 0) {\n throw new Error('VALIDATION_FAILED: process definition has no steps');\n }\n const step0 = steps[0];\n const ctxOrg = (context as any)?.organizationId ?? (context as any)?.tenantId ?? null;\n const approvers = await this.expandApprovers(step0, input.payload, ctxOrg);\n\n const now = this.clock.now().toISOString();\n const id = uid('areq');\n const row: any = {\n id,\n process_name: process.name,\n object_name: input.object,\n record_id: input.recordId,\n submitter_id: input.submitterId ?? context.userId ?? null,\n submitter_comment: input.comment ?? null,\n status: 'pending',\n current_step: step0.name,\n current_step_index: 0,\n pending_approvers: approvers.join(','),\n payload_json: input.payload != null ? JSON.stringify(input.payload) : null,\n organization_id: ctxOrg,\n created_at: now,\n updated_at: now,\n };\n await this.engine.insert('sys_approval_request', row, { context: SYSTEM_CTX });\n\n // Audit: submit.\n await this.engine.insert('sys_approval_action', {\n id: uid('aact'),\n request_id: id,\n organization_id: ctxOrg,\n step_name: step0.name,\n step_index: 0,\n action: 'submit',\n actor_id: input.submitterId ?? context.userId ?? null,\n comment: input.comment ?? null,\n created_at: now,\n }, { context: SYSTEM_CTX });\n\n const requestRow = rowFromRequest(row);\n\n // Phase B: status mirror + onSubmit actions.\n await this.syncStatusField(process, requestRow);\n const definition: any = process.definition ?? {};\n await this.runActions(\n definition.onSubmit,\n 'submit',\n process,\n requestRow,\n step0,\n input.submitterId ?? context.userId ?? null,\n input.comment ?? null,\n );\n\n return requestRow;\n }\n\n async listRequests(\n filter: {\n object?: string;\n recordId?: string;\n status?: ApprovalStatus | ApprovalStatus[];\n approverId?: string;\n submitterId?: string;\n } | undefined,\n context: SharingExecutionContext,\n ): Promise<ApprovalRequestRow[]> {\n const f: any = {};\n if (filter?.object) f.object_name = filter.object;\n if (filter?.recordId) f.record_id = filter.recordId;\n if (filter?.submitterId) f.submitter_id = filter.submitterId;\n // Tenant isolation: when a caller context carries a tenant identifier\n // (organizationId / tenantId), scope the query to that tenant. SYSTEM\n // callers (no tenant) see all rows. This prevents the bespoke endpoint\n // from leaking other-tenant rows since we deliberately query with\n // SYSTEM_CTX to bypass RLS on the engine (we need CSV substring match\n // on pending_approvers which RLS can't model cleanly).\n const tenantOrg = (context as any)?.organizationId ?? (context as any)?.tenantId;\n if (tenantOrg) f.organization_id = tenantOrg;\n // Status: when array, post-filter; when single, push into engine filter.\n let statusFilter: ApprovalStatus[] | undefined;\n if (Array.isArray(filter?.status)) statusFilter = filter!.status as ApprovalStatus[];\n else if (filter?.status) f.status = filter.status;\n\n const rows = await this.engine.find('sys_approval_request', {\n where: f, limit: 500, orderBy: [{ field: 'updated_at', direction: 'desc' }], context: SYSTEM_CTX,\n });\n let list = Array.isArray(rows) ? rows.map(rowFromRequest) : [];\n if (statusFilter) list = list.filter(r => statusFilter!.includes(r.status));\n if (filter?.approverId) {\n const target = filter.approverId;\n list = list.filter(r => (r.pending_approvers ?? []).includes(target));\n }\n return list;\n }\n\n async getRequest(requestId: string, context: SharingExecutionContext): Promise<ApprovalRequestRow | null> {\n if (!requestId) return null;\n const where: any = { id: requestId };\n const tenantOrg = (context as any)?.organizationId ?? (context as any)?.tenantId;\n if (tenantOrg) where.organization_id = tenantOrg;\n const rows = await this.engine.find('sys_approval_request', {\n where, limit: 1, context: SYSTEM_CTX,\n });\n return Array.isArray(rows) && rows[0] ? rowFromRequest(rows[0]) : null;\n }\n\n async approve(requestId: string, input: ApprovalDecisionInput, context: SharingExecutionContext): Promise<ApprovalDecisionResult> {\n const req = await this.getRequest(requestId, context);\n if (!req) throw new Error(`REQUEST_NOT_FOUND: ${requestId}`);\n if (req.status !== 'pending') throw new Error(`INVALID_STATE: request is ${req.status}`);\n if (!input?.actorId) throw new Error('VALIDATION_FAILED: actorId is required');\n\n if (!context.isSystem && !(req.pending_approvers ?? []).includes(input.actorId)) {\n throw new Error(`FORBIDDEN: actor '${input.actorId}' is not a pending approver`);\n }\n\n const process = await this.getProcess(req.process_name, context);\n if (!process) throw new Error(`PROCESS_NOT_FOUND: ${req.process_name}`);\n const steps: any[] = process.definition?.steps ?? [];\n const stepIndex = req.current_step_index ?? 0;\n const step = steps[stepIndex];\n if (!step) throw new Error(`INVALID_STATE: step index ${stepIndex} out of range`);\n\n const now = this.clock.now().toISOString();\n // Audit row first so unanimous tally sees it.\n await this.engine.insert('sys_approval_action', {\n id: uid('aact'),\n request_id: req.id,\n organization_id: (req as any).organization_id ?? null,\n step_name: step.name,\n step_index: stepIndex,\n action: 'approve',\n actor_id: input.actorId,\n comment: input.comment ?? null,\n created_at: now,\n }, { context: SYSTEM_CTX });\n\n // Unanimous: only advance once every original approver has approved at this step_index.\n if (step.behavior === 'unanimous') {\n const original = await this.expandApprovers(step, req.payload, (req as any).organization_id ?? null);\n const acts = await this.engine.find('sys_approval_action', {\n where: { request_id: req.id, step_index: stepIndex, action: 'approve' },\n limit: 500, context: SYSTEM_CTX,\n });\n const approved = new Set<string>((acts ?? []).map((a: any) => String(a.actor_id ?? '')).filter(Boolean));\n const stillPending = original.filter(a => !approved.has(a));\n if (stillPending.length > 0) {\n // Update pending_approvers to those who haven't voted yet.\n await this.engine.update('sys_approval_request', {\n id: req.id,\n pending_approvers: stillPending.join(','),\n updated_at: now,\n }, { context: SYSTEM_CTX });\n const fresh = await this.getRequest(req.id, context);\n return { request: fresh!, finalized: false };\n }\n }\n\n // Advance the request — either to next step or to finalized=approved.\n if (stepIndex + 1 >= steps.length) {\n await this.engine.update('sys_approval_request', {\n id: req.id,\n status: 'approved',\n pending_approvers: null,\n completed_at: now,\n updated_at: now,\n }, { context: SYSTEM_CTX });\n const fresh = await this.getRequest(req.id, context);\n // Phase B: step.onApprove + process.onFinalApprove + status mirror.\n await this.runActions((step as any)?.onApprove, 'step_approve', process, fresh!, step, input.actorId, input.comment);\n await this.syncStatusField(process, fresh!);\n await this.runActions((process.definition as any)?.onFinalApprove, 'final_approve', process, fresh!, step, input.actorId, input.comment);\n return { request: fresh!, finalized: true };\n }\n\n const nextStep = steps[stepIndex + 1];\n const nextApprovers = await this.expandApprovers(nextStep, req.payload, (req as any).organization_id ?? null);\n await this.engine.update('sys_approval_request', {\n id: req.id,\n current_step: nextStep.name,\n current_step_index: stepIndex + 1,\n pending_approvers: nextApprovers.join(','),\n updated_at: now,\n }, { context: SYSTEM_CTX });\n const fresh = await this.getRequest(req.id, context);\n // Phase B: step.onApprove fires when transitioning out of this step.\n await this.runActions((step as any)?.onApprove, 'step_approve', process, fresh!, step, input.actorId, input.comment);\n return { request: fresh!, finalized: false };\n }\n\n async reject(requestId: string, input: ApprovalDecisionInput, context: SharingExecutionContext): Promise<ApprovalDecisionResult> {\n const req = await this.getRequest(requestId, context);\n if (!req) throw new Error(`REQUEST_NOT_FOUND: ${requestId}`);\n if (req.status !== 'pending') throw new Error(`INVALID_STATE: request is ${req.status}`);\n if (!input?.actorId) throw new Error('VALIDATION_FAILED: actorId is required');\n if (!context.isSystem && !(req.pending_approvers ?? []).includes(input.actorId)) {\n throw new Error(`FORBIDDEN: actor '${input.actorId}' is not a pending approver`);\n }\n\n const process = await this.getProcess(req.process_name, context);\n if (!process) throw new Error(`PROCESS_NOT_FOUND: ${req.process_name}`);\n const steps: any[] = process.definition?.steps ?? [];\n const stepIndex = req.current_step_index ?? 0;\n const step = steps[stepIndex];\n\n const now = this.clock.now().toISOString();\n await this.engine.insert('sys_approval_action', {\n id: uid('aact'),\n request_id: req.id,\n organization_id: (req as any).organization_id ?? null,\n step_name: step?.name,\n step_index: stepIndex,\n action: 'reject',\n actor_id: input.actorId,\n comment: input.comment ?? null,\n created_at: now,\n }, { context: SYSTEM_CTX });\n\n if (step?.rejectionBehavior === 'back_to_previous' && stepIndex > 0) {\n const prev = steps[stepIndex - 1];\n const prevApprovers = await this.expandApprovers(prev, req.payload, (req as any).organization_id ?? null);\n await this.engine.update('sys_approval_request', {\n id: req.id,\n current_step: prev.name,\n current_step_index: stepIndex - 1,\n pending_approvers: prevApprovers.join(','),\n updated_at: now,\n }, { context: SYSTEM_CTX });\n const fresh = await this.getRequest(req.id, context);\n // Phase B: step-level onReject fires on non-final rejection too.\n await this.runActions((step as any)?.onReject, 'step_reject', process, fresh!, step, input.actorId, input.comment);\n return { request: fresh!, finalized: false };\n }\n\n await this.engine.update('sys_approval_request', {\n id: req.id,\n status: 'rejected',\n pending_approvers: null,\n completed_at: now,\n updated_at: now,\n }, { context: SYSTEM_CTX });\n const fresh = await this.getRequest(req.id, context);\n // Phase B: step.onReject + process.onFinalReject + status mirror.\n await this.runActions((step as any)?.onReject, 'step_reject', process, fresh!, step, input.actorId, input.comment);\n await this.syncStatusField(process, fresh!);\n await this.runActions((process.definition as any)?.onFinalReject, 'final_reject', process, fresh!, step, input.actorId, input.comment);\n return { request: fresh!, finalized: true };\n }\n\n async recall(requestId: string, input: ApprovalDecisionInput, context: SharingExecutionContext): Promise<ApprovalDecisionResult> {\n const req = await this.getRequest(requestId, context);\n if (!req) throw new Error(`REQUEST_NOT_FOUND: ${requestId}`);\n if (req.status !== 'pending') throw new Error(`INVALID_STATE: request is ${req.status}`);\n if (!input?.actorId) throw new Error('VALIDATION_FAILED: actorId is required');\n if (!context.isSystem && req.submitter_id && req.submitter_id !== input.actorId) {\n throw new Error(`FORBIDDEN: only the submitter can recall this request`);\n }\n\n const now = this.clock.now().toISOString();\n await this.engine.insert('sys_approval_action', {\n id: uid('aact'),\n request_id: req.id,\n organization_id: (req as any).organization_id ?? null,\n step_name: req.current_step,\n step_index: req.current_step_index,\n action: 'recall',\n actor_id: input.actorId,\n comment: input.comment ?? null,\n created_at: now,\n }, { context: SYSTEM_CTX });\n\n await this.engine.update('sys_approval_request', {\n id: req.id,\n status: 'recalled',\n pending_approvers: null,\n completed_at: now,\n updated_at: now,\n }, { context: SYSTEM_CTX });\n const fresh = await this.getRequest(req.id, context);\n // Phase B: process.onRecall + status mirror.\n const process = await this.getProcess(req.process_name, context);\n if (process) {\n await this.syncStatusField(process, fresh!);\n await this.runActions((process.definition as any)?.onRecall, 'recall', process, fresh!, undefined, input.actorId, input.comment);\n }\n return { request: fresh!, finalized: true };\n }\n\n async listActions(requestId: string, context: SharingExecutionContext): Promise<ApprovalActionRow[]> {\n if (!requestId) return [];\n // Tenant gate: ensure the caller can see the parent request before\n // returning its action history. Skipping this would leak history rows\n // across tenants the same way the unscoped list-requests path did.\n const req = await this.getRequest(requestId, context);\n if (!req) return [];\n const rows = await this.engine.find('sys_approval_action', {\n where: { request_id: requestId },\n limit: 500,\n orderBy: [{ field: 'created_at', direction: 'asc' }],\n context: SYSTEM_CTX,\n });\n return Array.isArray(rows) ? rows.map(rowFromAction) : [];\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Approval Action Executor — M11.C15.B\n *\n * Pure dispatcher that runs the `ApprovalAction` items declared on\n * `ApprovalProcess.onSubmit / onFinalApprove / onFinalReject / onRecall`\n * and `ApprovalStep.onApprove / onReject`.\n *\n * Supported action types:\n * - `field_update` — write `config.field = config.value` on the\n * business record (under SYSTEM_CTX so the lock hook is bypassed).\n * `config.value` may be a literal or `\"$status\"` / `\"$now\"` /\n * `\"$actor\"` / `\"$comment\"` token resolved against the runtime\n * context.\n * - `inbox_notify` — insert one `sys_notification` row per target.\n * `config.to` may be `'submitter' | 'pending_approvers'` or an\n * explicit `string[]` of user ids. `config.title` / `config.body`\n * interpolate `{record_id}`, `{object}`, `{status}`, `{step}`,\n * `{actor}`, `{comment}`.\n * - `webhook` — POST `config.body` (JSON) to `config.url`,\n * fire-and-forget (caller awaits with timeout). Headers default\n * to `Content-Type: application/json`. Failures are logged, not\n * thrown, so a flaky receiver can't deadlock the approval flow.\n *\n * Unimplemented (logged + skipped):\n * - `email_alert` — needs SMTP transport, later milestone.\n * - `script` — needs sandboxed runner, later milestone.\n * - `connector_action` — needs connector registry, later milestone.\n */\n\nimport type { ApprovalEngine } from './approval-service.js';\n\nconst SYSTEM_CTX = { isSystem: true, roles: [], permissions: [] } as const;\n\nexport interface ActionLogger {\n info?: (msg: string, meta?: any) => void;\n warn?: (msg: string, meta?: any) => void;\n error?: (msg: string, meta?: any) => void;\n debug?: (msg: string, meta?: any) => void;\n}\n\nconst noopLogger: Required<ActionLogger> = {\n info: () => {}, warn: () => {}, error: () => {}, debug: () => {},\n};\n\n/** Possible trigger points; passed to executors for tokenization. */\nexport type ApprovalTrigger =\n | 'submit'\n | 'step_approve'\n | 'final_approve'\n | 'step_reject'\n | 'final_reject'\n | 'recall';\n\nexport interface ExecutionContext {\n /** The trigger that caused these actions to fire. */\n trigger: ApprovalTrigger;\n /** Approval process row (parsed `definition`). */\n process: any;\n /** Approval request row (post-transition). */\n request: any;\n /** Current step config (when applicable). */\n step?: any;\n /** Business record (optional — looked up on demand if needed). */\n record?: any;\n /** Actor whose decision triggered the action; for `submit` this is the submitter. */\n actorId?: string | null;\n /** Comment passed with the decision. */\n comment?: string | null;\n}\n\n/** Default fetch implementation — overridable for tests. */\nexport type FetchLike = (\n input: any,\n init?: any,\n) => Promise<{ ok: boolean; status: number; statusText: string }>;\n\nexport interface ExecuteActionsOptions {\n engine: ApprovalEngine;\n logger?: ActionLogger;\n fetch?: FetchLike;\n /** Maximum webhook duration in ms; default 5000. */\n webhookTimeoutMs?: number;\n}\n\nconst DEFAULT_WEBHOOK_TIMEOUT_MS = 5000;\n\n/** Public entry point — run an ordered list of actions. */\nexport async function executeActions(\n actions: any[] | undefined | null,\n ctx: ExecutionContext,\n opts: ExecuteActionsOptions,\n): Promise<void> {\n if (!Array.isArray(actions) || actions.length === 0) return;\n const log = { ...noopLogger, ...(opts.logger ?? {}) };\n for (const a of actions) {\n try {\n await runOne(a, ctx, opts, log);\n } catch (err: any) {\n // Approval actions must not crash the transition — log + continue.\n log.error?.(`[approvals] action '${a?.type ?? '<unknown>'}' failed: ${err?.message ?? err}`, {\n action: a, trigger: ctx.trigger, request_id: ctx.request?.id,\n });\n }\n }\n}\n\nasync function runOne(\n action: any,\n ctx: ExecutionContext,\n opts: ExecuteActionsOptions,\n log: Required<ActionLogger>,\n): Promise<void> {\n if (!action || typeof action !== 'object') return;\n switch (action.type) {\n case 'field_update': return runFieldUpdate(action, ctx, opts, log);\n case 'inbox_notify': return runInboxNotify(action, ctx, opts, log);\n case 'webhook': return runWebhook(action, ctx, opts, log);\n case 'email_alert':\n case 'script':\n case 'connector_action':\n log.warn?.(`[approvals] action type '${action.type}' is not implemented yet — skipping`, {\n action_name: action.name, trigger: ctx.trigger,\n });\n return;\n default:\n log.warn?.(`[approvals] unknown action type '${action.type}' — skipping`);\n }\n}\n\n// ── field_update ──────────────────────────────────────────────────\n\nasync function runFieldUpdate(\n action: any,\n ctx: ExecutionContext,\n opts: ExecuteActionsOptions,\n log: Required<ActionLogger>,\n): Promise<void> {\n const cfg = action.config ?? {};\n const field: string | undefined = cfg.field;\n if (!field) {\n log.warn?.('[approvals] field_update missing config.field');\n return;\n }\n const value = resolveValueToken(cfg.value, ctx);\n const object = ctx.process?.object_name ?? ctx.process?.object;\n const recordId = ctx.request?.record_id;\n if (!object || !recordId) {\n log.warn?.('[approvals] field_update missing object/record context');\n return;\n }\n await opts.engine.update(\n object,\n { id: recordId, [field]: value },\n { context: SYSTEM_CTX },\n );\n log.debug?.(`[approvals] field_update ${object}/${recordId} set ${field}`, { value });\n}\n\n/** Resolve `$status`, `$now`, `$actor`, `$comment` or literal value. */\nfunction resolveValueToken(raw: unknown, ctx: ExecutionContext): unknown {\n if (typeof raw !== 'string') return raw;\n switch (raw) {\n case '$status': return ctx.request?.status ?? null;\n case '$now': return new Date().toISOString();\n case '$actor': return ctx.actorId ?? null;\n case '$comment': return ctx.comment ?? null;\n case '$step': return ctx.request?.current_step ?? null;\n case '$request_id': return ctx.request?.id ?? null;\n default: return raw;\n }\n}\n\n// ── inbox_notify ──────────────────────────────────────────────────\n\nfunction interpolate(template: string, ctx: ExecutionContext): string {\n if (typeof template !== 'string') return template as any;\n return template\n .replace(/\\{record_id\\}/g, String(ctx.request?.record_id ?? ''))\n .replace(/\\{object\\}/g, String(ctx.process?.object_name ?? ctx.process?.object ?? ''))\n .replace(/\\{status\\}/g, String(ctx.request?.status ?? ''))\n .replace(/\\{step\\}/g, String(ctx.request?.current_step ?? ''))\n .replace(/\\{actor\\}/g, String(ctx.actorId ?? ''))\n .replace(/\\{comment\\}/g, String(ctx.comment ?? ''))\n .replace(/\\{process\\}/g, String(ctx.process?.name ?? ''));\n}\n\nasync function runInboxNotify(\n action: any,\n ctx: ExecutionContext,\n opts: ExecuteActionsOptions,\n log: Required<ActionLogger>,\n): Promise<void> {\n const cfg = action.config ?? {};\n const recipients = resolveRecipients(cfg.to, ctx);\n if (recipients.length === 0) {\n log.debug?.('[approvals] inbox_notify resolved no recipients — skipping');\n return;\n }\n const title = interpolate(cfg.title ?? 'Approval update', ctx);\n const body = interpolate(cfg.body ?? '', ctx);\n // sys_notification.type is a select with a fixed enum; 'system' is the\n // safe default. Callers may override via cfg.notificationType but must\n // pick a value the schema accepts.\n const type = String(cfg.notificationType ?? 'system');\n const rawLink = cfg.link\n ? interpolate(String(cfg.link), ctx)\n : `/console/system/approvals?requestId=${encodeURIComponent(ctx.request?.id ?? '')}`;\n // sys_notification.url is a URL field — only forward absolute URLs.\n // Relative deep-links (`/system/approvals`) get stripped to satisfy\n // validation; the recipient can still navigate via the source linkage.\n const url = /^https?:\\/\\//i.test(rawLink) ? rawLink : null;\n const now = new Date().toISOString();\n\n for (const recipient of recipients) {\n try {\n await opts.engine.insert(\n 'sys_notification',\n {\n id: `notif_${cryptoRandom()}`,\n recipient_id: String(recipient),\n type,\n title,\n body,\n url,\n is_read: false,\n source_object: ctx.process?.object_name ?? ctx.process?.object ?? null,\n source_id: ctx.request?.record_id ?? null,\n created_at: now,\n updated_at: now,\n },\n { context: SYSTEM_CTX },\n );\n } catch (err: any) {\n // Notification persistence is best-effort.\n log.warn?.(`[approvals] inbox_notify insert failed for ${recipient}: ${err?.message ?? err}`);\n }\n }\n}\n\nfunction resolveRecipients(to: unknown, ctx: ExecutionContext): string[] {\n if (Array.isArray(to)) return to.map(String).filter(Boolean);\n if (typeof to === 'string') {\n if (to === 'submitter') return ctx.request?.submitter_id ? [String(ctx.request.submitter_id)] : [];\n if (to === 'pending_approvers') {\n const list = ctx.request?.pending_approvers ?? [];\n if (Array.isArray(list)) return list.map(String).filter(Boolean);\n if (typeof list === 'string') return list.split(',').map(s => s.trim()).filter(Boolean);\n return [];\n }\n // Fall through: literal user id.\n return [to];\n }\n return [];\n}\n\nfunction cryptoRandom(): string {\n const g: any = globalThis as any;\n if (g.crypto?.randomUUID) return g.crypto.randomUUID();\n return `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 12)}`;\n}\n\n// ── webhook ──────────────────────────────────────────────────────\n\nasync function runWebhook(\n action: any,\n ctx: ExecutionContext,\n opts: ExecuteActionsOptions,\n log: Required<ActionLogger>,\n): Promise<void> {\n const cfg = action.config ?? {};\n const url: string | undefined = cfg.url;\n if (!url) {\n log.warn?.('[approvals] webhook missing config.url');\n return;\n }\n const fetchImpl: FetchLike = opts.fetch ?? (globalThis as any).fetch;\n if (!fetchImpl) {\n log.warn?.('[approvals] webhook skipped — no fetch implementation available');\n return;\n }\n const timeoutMs = opts.webhookTimeoutMs ?? DEFAULT_WEBHOOK_TIMEOUT_MS;\n const headers = { 'Content-Type': 'application/json', ...(cfg.headers ?? {}) };\n const payload = {\n trigger: ctx.trigger,\n request: ctx.request,\n step: ctx.step ? { name: ctx.step.name, index: ctx.request?.current_step_index } : null,\n actor_id: ctx.actorId ?? null,\n comment: ctx.comment ?? null,\n process_name: ctx.process?.name,\n object: ctx.process?.object_name ?? ctx.process?.object,\n ...(cfg.body && typeof cfg.body === 'object' ? cfg.body : {}),\n };\n // Manual timeout — works in Node 18+ without AbortController dependency.\n const controller = (globalThis as any).AbortController ? new (globalThis as any).AbortController() : null;\n const timer = setTimeout(() => controller?.abort(), timeoutMs);\n try {\n const res = await fetchImpl(url, {\n method: cfg.method ?? 'POST',\n headers,\n body: JSON.stringify(payload),\n signal: controller?.signal,\n });\n if (!res.ok) {\n log.warn?.(`[approvals] webhook ${url} → ${res.status} ${res.statusText}`);\n }\n } catch (err: any) {\n log.warn?.(`[approvals] webhook ${url} failed: ${err?.message ?? err}`);\n } finally {\n clearTimeout(timer);\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport {\n SysApprovalProcess,\n SysApprovalRequest,\n SysApprovalAction,\n} from '@objectstack/platform-objects/audit';\nimport { ApprovalService, type ApprovalEngine } from './approval-service.js';\nimport { bindProcessHooks, unbindAllHooks } from './lifecycle-hooks.js';\n\nexport interface ApprovalsPluginOptions {\n /** Disable runtime registration (schemas still register). */\n disableService?: boolean;\n /**\n * Disable Phase B auto-trigger / lock hooks. Schema definition stays\n * intact; only the engine-level wiring is suppressed. Useful when a\n * caller wants the manual API only (e.g. tests).\n */\n disableAutoHooks?: boolean;\n}\n\n/**\n * ApprovalsServicePlugin — registers sys_approval_{process,request,action},\n * the `approvals` service, and Phase B lifecycle hooks (auto-trigger,\n * record lock, status mirror). SLA escalation dispatcher is a later\n * milestone.\n */\nexport class ApprovalsServicePlugin implements Plugin {\n name = 'com.objectstack.service.approvals';\n version = '1.0.0';\n type = 'standard';\n dependencies = ['com.objectstack.engine.objectql'];\n\n private readonly options: ApprovalsPluginOptions;\n private service?: ApprovalService;\n private engine?: any;\n private logger?: any;\n\n constructor(options: ApprovalsPluginOptions = {}) {\n this.options = options;\n }\n\n async init(ctx: PluginContext): Promise<void> {\n ctx.getService<{ register(m: any): void }>('manifest').register({\n id: 'com.objectstack.service.approvals',\n name: 'Approvals Service',\n version: '1.0.0',\n type: 'plugin',\n scope: 'system',\n defaultDatasource: 'cloud',\n namespace: 'sys',\n objects: [SysApprovalProcess, SysApprovalRequest, SysApprovalAction],\n });\n ctx.logger.info('ApprovalsServicePlugin: schemas registered');\n }\n\n async start(ctx: PluginContext): Promise<void> {\n if (this.options.disableService) return;\n let engine: any = null;\n try { engine = ctx.getService<any>('objectql'); }\n catch { try { engine = ctx.getService<any>('data'); } catch { /* ignore */ } }\n if (!engine) {\n ctx.logger.warn('ApprovalsServicePlugin: no ObjectQL engine — service NOT registered');\n return;\n }\n this.engine = engine;\n this.logger = ctx.logger;\n\n this.service = new ApprovalService({\n engine: engine as ApprovalEngine,\n logger: ctx.logger,\n });\n\n if (!this.options.disableAutoHooks) {\n // Re-bind hooks on every registry mutation.\n this.service.setRegistryChangeHandler(() => this.rebindHooks());\n // Initial bind happens once the kernel is ready so the AppPlugin's\n // declarative process seeder has already populated sys_approval_process.\n const hookOn = (ctx as any).hook ?? (ctx as any).on;\n if (typeof hookOn === 'function') {\n try {\n hookOn.call(ctx, 'kernel:ready', async () => { await this.rebindHooks(); });\n } catch {\n // Fall through to immediate bind (no kernel:ready event).\n await this.rebindHooks();\n }\n } else {\n await this.rebindHooks();\n }\n }\n\n ctx.registerService('approvals', this.service);\n ctx.logger.info('ApprovalsServicePlugin: service registered');\n }\n\n private async rebindHooks(): Promise<void> {\n if (!this.engine || !this.service) return;\n try {\n unbindAllHooks(this.engine);\n const processes = await this.service.listProcesses({ activeOnly: true }, { isSystem: true, roles: [], permissions: [] } as any);\n bindProcessHooks(this.engine, this.service, processes, this.logger);\n } catch (err: any) {\n this.logger?.warn?.('[approvals] rebindHooks failed', { error: err?.message });\n }\n }\n\n async stop(_ctx: PluginContext): Promise<void> {\n if (this.engine) {\n try { unbindAllHooks(this.engine); } catch { /* ignore */ }\n }\n }\n}\n\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Lifecycle Hooks — Phase B auto-takeover.\n *\n * For each active ApprovalProcess we bind three hooks on its target object:\n *\n * 1. `afterInsert` — evaluate `entryCriteria` against the new record;\n * if truthy and no pending request exists, auto-submit one.\n * 2. `afterUpdate` — same as above but for updates that newly satisfy\n * criteria (e.g. amount edited above threshold).\n * 3. `beforeUpdate` — when `lockRecord=true`, block edits to a record\n * that has a pending request, EXCEPT when the only fields being\n * changed are the configured `approvalStatusField` (so the engine's\n * own status mirror is not blocked).\n *\n * All hooks are registered with `packageId: 'plugin-approvals:auto'` so\n * that re-bind on `defineProcess`/`deleteProcess` can call\n * `engine.unregisterHooksByPackage(...)` first.\n */\n\nimport { ExpressionEngine } from '@objectstack/formula';\nimport type { Expression } from '@objectstack/spec';\nimport type { ApprovalProcessRow } from '@objectstack/spec/contracts';\nimport type { ApprovalService } from './approval-service.js';\n\nexport const APPROVALS_HOOK_PACKAGE = 'plugin-approvals:auto';\n\nconst SYSTEM_CTX = { isSystem: true, roles: [], permissions: [] } as const;\n\ninterface MinimalEngine {\n registerHook(event: string, handler: (ctx: any) => any | Promise<any>, options?: {\n object?: string | string[];\n priority?: number;\n packageId?: string;\n }): void;\n unregisterHooksByPackage(packageId: string): number;\n find<T = any>(object: string, args: any, opts?: any): Promise<T[]>;\n}\n\ninterface MinimalLogger {\n debug?: (msg: any, ...rest: any[]) => void;\n info?: (msg: any, ...rest: any[]) => void;\n warn?: (msg: any, ...rest: any[]) => void;\n error?: (msg: any, ...rest: any[]) => void;\n}\n\n/**\n * Evaluate an entry criteria expression against a record. Returns `true`\n * when no criteria is set (matches everything). Returns `false` on\n * evaluation failure (fail-closed — better to skip than auto-submit on a\n * broken expression).\n */\nfunction evaluateCriteria(criteria: unknown, record: Record<string, unknown>, logger?: MinimalLogger): boolean {\n if (criteria == null || criteria === '' ) return true;\n let expr: Expression;\n if (typeof criteria === 'string') {\n expr = { dialect: 'cel', source: criteria };\n } else if (typeof criteria === 'object' && (criteria as any).dialect) {\n expr = criteria as Expression;\n } else {\n return true;\n }\n if (!expr.source || !expr.source.trim()) return true;\n const r = ExpressionEngine.evaluate<boolean>(expr, { record });\n if (!r.ok) {\n logger?.warn?.('[approvals] entryCriteria evaluation failed; skipping auto-submit', {\n source: expr.source,\n error: r.error.message,\n });\n return false;\n }\n return Boolean(r.value);\n}\n\n/** Does this record already have a pending approval request? */\nasync function hasPendingRequest(\n engine: MinimalEngine,\n objectName: string,\n recordId: string,\n): Promise<boolean> {\n try {\n const rows = await engine.find('sys_approval_request', {\n where: { object_name: objectName, record_id: String(recordId), status: 'pending' },\n limit: 1,\n } as any);\n return Array.isArray(rows) && rows.length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Bind auto-trigger + lock hooks for the supplied active processes.\n * Caller is responsible for calling `unbindAll` first if re-binding.\n */\nexport function bindProcessHooks(\n engine: MinimalEngine,\n service: ApprovalService,\n processes: ApprovalProcessRow[],\n logger?: MinimalLogger,\n): void {\n // Group processes by object so we can register one hook per object\n // and fan out internally — keeps the engine's hook map compact.\n const byObject = new Map<string, ApprovalProcessRow[]>();\n for (const p of processes) {\n if (!(p as any).active && !(p as any).is_active) continue;\n if (!p.object_name) continue;\n const list = byObject.get(p.object_name) ?? [];\n list.push(p);\n byObject.set(p.object_name, list);\n }\n\n for (const [objectName, procs] of byObject.entries()) {\n // ---- auto-trigger (afterInsert) ----\n engine.registerHook('afterInsert', async (ctx: any) => {\n try {\n const record = (ctx?.result ?? ctx?.input?.data ?? {}) as Record<string, unknown>;\n const id = String((record as any)?.id ?? '');\n if (!id) return;\n for (const proc of procs) {\n await tryAutoSubmit(engine, service, proc, objectName, id, record, ctx, logger);\n }\n } catch (err: any) {\n logger?.warn?.('[approvals] afterInsert auto-trigger failed', { error: err?.message });\n }\n }, { object: objectName, packageId: APPROVALS_HOOK_PACKAGE, priority: 200 });\n\n // ---- auto-trigger (afterUpdate) ----\n engine.registerHook('afterUpdate', async (ctx: any) => {\n // Ignore engine self-writes (status mirror, field_update from\n // post-actions, etc) — otherwise post-finalize updates would loop\n // a fresh approval on every state change.\n if ((ctx?.session as any)?.isSystem) return;\n try {\n const result = (ctx?.result ?? {}) as Record<string, unknown>;\n const id = String((ctx?.input?.id ?? (result as any)?.id ?? '') as string);\n if (!id) return;\n // result may be { affected: 1 } for some drivers; merge previous+input.data as the\n // best-effort record snapshot for criteria evaluation.\n const record: Record<string, unknown> = {\n ...(ctx?.previous ?? {}),\n ...((result as any)?.id ? result : {}),\n ...((ctx?.input?.data ?? {}) as Record<string, unknown>),\n id,\n };\n for (const proc of procs) {\n await tryAutoSubmit(engine, service, proc, objectName, id, record, ctx, logger);\n }\n } catch (err: any) {\n logger?.warn?.('[approvals] afterUpdate auto-trigger failed', { error: err?.message });\n }\n }, { object: objectName, packageId: APPROVALS_HOOK_PACKAGE, priority: 200 });\n\n // ---- record lock (beforeUpdate) ----\n const lockProcs = procs.filter((p) => (p.definition as any)?.lockRecord !== false);\n if (lockProcs.length === 0) continue;\n engine.registerHook('beforeUpdate', async (ctx: any) => {\n const id = String((ctx?.input?.id ?? '') as string);\n if (!id) return;\n const data = (ctx?.input?.data ?? {}) as Record<string, unknown>;\n const changedFields = Object.keys(data).filter((k) => k !== 'id' && k !== 'updated_at');\n if (changedFields.length === 0) return;\n\n // Allow engine self-writes (status mirror, field_update from actions, etc).\n if ((ctx?.session as any)?.isSystem) return;\n\n // Allow when every changed field is an approval status mirror.\n const mirrorFields = new Set<string>();\n for (const p of lockProcs) {\n const f = (p.definition as any)?.approvalStatusField;\n if (typeof f === 'string' && f) mirrorFields.add(f);\n }\n const onlyMirror = changedFields.every((f) => mirrorFields.has(f));\n if (onlyMirror) return;\n\n // Allow admin override: roles include 'admin'.\n const roles = (ctx?.session?.roles ?? []) as string[];\n if (Array.isArray(roles) && roles.includes('admin')) return;\n\n const pending = await hasPendingRequest(engine, objectName, id);\n if (!pending) return;\n\n const err: any = new Error('RECORD_LOCKED: record is locked while an approval is in progress');\n err.code = 'RECORD_LOCKED';\n err.statusCode = 409;\n throw err;\n }, { object: objectName, packageId: APPROVALS_HOOK_PACKAGE, priority: 50 });\n }\n\n logger?.info?.('[approvals] lifecycle hooks bound', {\n objects: Array.from(byObject.keys()),\n processCount: processes.length,\n });\n}\n\n/** Unregister every hook the auto-trigger module ever registered. */\nexport function unbindAllHooks(engine: MinimalEngine): number {\n return engine.unregisterHooksByPackage(APPROVALS_HOOK_PACKAGE);\n}\n\nasync function tryAutoSubmit(\n engine: MinimalEngine,\n service: ApprovalService,\n process: ApprovalProcessRow,\n objectName: string,\n recordId: string,\n record: Record<string, unknown>,\n ctx: any,\n logger?: MinimalLogger,\n): Promise<void> {\n try {\n const criteria = (process.definition as any)?.entryCriteria;\n const passes = evaluateCriteria(criteria, record, logger);\n if (!passes) return;\n if (await hasPendingRequest(engine, objectName, recordId)) return;\n // Guard: if the record's mirror status field is already a terminal\n // state (approved / rejected / recalled), do NOT auto-submit again —\n // otherwise every post-finalize edit would loop a fresh approval.\n const statusField = (process.definition as any)?.approvalStatusField;\n if (statusField) {\n const current = (record as any)?.[statusField];\n if (current === 'approved' || current === 'rejected' || current === 'recalled') return;\n }\n\n const submitterId = (ctx?.session?.userId ?? null) as string | null;\n const submitterOrg = (ctx?.session?.tenantId ?? ctx?.session?.organizationId ?? null) as string | null;\n await service.submit({\n object: objectName,\n recordId,\n processName: process.name,\n payload: record,\n submitterId,\n }, { ...SYSTEM_CTX, userId: submitterId ?? undefined, organizationId: submitterOrg ?? undefined, tenantId: submitterOrg ?? undefined } as any);\n\n logger?.info?.('[approvals] auto-submitted approval', {\n process: process.name,\n object: objectName,\n record: recordId,\n });\n } catch (err: any) {\n if (err?.code === 'DUPLICATE_REQUEST') return;\n logger?.warn?.('[approvals] auto-submit failed', {\n process: process.name,\n object: objectName,\n record: recordId,\n error: err?.message ?? String(err),\n });\n }\n}\n"],"mappings":"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","names":["import_audit","SYSTEM_CTX","id","row","fresh","SYSTEM_CTX"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/approval-service.ts","../src/action-executor.ts","../src/approvals-plugin.ts","../src/lifecycle-hooks.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * @objectstack/plugin-approvals\n *\n * Multi-step approval engine for ObjectStack.\n * Persists sys_approval_process / sys_approval_request / sys_approval_action\n * and drives the cycle: submit → review → approve/reject → effects.\n */\n\nexport {\n SysApprovalProcess,\n SysApprovalRequest,\n SysApprovalAction,\n} from '@objectstack/platform-objects/audit';\nexport {\n ApprovalService,\n type ApprovalEngine,\n type ApprovalClock,\n type ApprovalServiceOptions,\n} from './approval-service.js';\nexport {\n ApprovalsServicePlugin,\n type ApprovalsPluginOptions,\n} from './approvals-plugin.js';\nexport type {\n IApprovalService,\n ApprovalProcessRow,\n ApprovalRequestRow,\n ApprovalActionRow,\n ApprovalDecisionInput,\n ApprovalDecisionResult,\n ApprovalStatus,\n DefineApprovalProcessInput,\n SubmitApprovalInput,\n} from '@objectstack/spec/contracts';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ApprovalProcessSchema } from '@objectstack/spec/automation';\nimport type {\n IApprovalService,\n ApprovalProcessRow,\n ApprovalRequestRow,\n ApprovalActionRow,\n ApprovalDecisionInput,\n ApprovalDecisionResult,\n ApprovalStatus,\n DefineApprovalProcessInput,\n SubmitApprovalInput,\n SharingExecutionContext,\n} from '@objectstack/spec/contracts';\nimport type { MetadataRepository } from '@objectstack/metadata-core';\nimport { executeActions, type ApprovalTrigger, type FetchLike } from './action-executor.js';\n\n/**\n * Narrow engine surface — keeps the service testable without booting\n * a real ObjectQL kernel.\n */\nexport interface ApprovalEngine {\n find(object: string, options?: any): Promise<any[]>;\n insert(object: string, data: any, options?: any): Promise<any>;\n update(object: string, idOrData: any, dataOrOptions?: any, options?: any): Promise<any>;\n delete(object: string, options?: any): Promise<any>;\n}\n\nexport interface ApprovalClock { now(): Date }\n\nconst SYSTEM_CTX = { isSystem: true, roles: [], permissions: [] } as const;\n\nfunction uid(prefix: string): string {\n const g: any = globalThis as any;\n if (g.crypto?.randomUUID) return `${prefix}_${g.crypto.randomUUID()}`;\n return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n}\n\nfunction parseJson<T = any>(raw: unknown, fallback: T): T {\n if (raw == null || raw === '') return fallback;\n if (typeof raw === 'string') {\n try { return JSON.parse(raw) as T; } catch { return fallback; }\n }\n return raw as T;\n}\n\nfunction csvSplit(raw: unknown): string[] {\n if (!raw) return [];\n if (Array.isArray(raw)) return raw.map(String).filter(Boolean);\n return String(raw).split(',').map(s => s.trim()).filter(Boolean);\n}\n\nfunction rowFromProcess(row: any): ApprovalProcessRow {\n return {\n id: String(row.id),\n name: String(row.name ?? ''),\n label: String(row.label ?? ''),\n object_name: String(row.object_name ?? ''),\n description: row.description ?? undefined,\n active: row.active !== false,\n definition: parseJson(row.definition_json, {}),\n created_at: row.created_at ?? undefined,\n updated_at: row.updated_at ?? undefined,\n };\n}\n\nfunction rowFromRequest(row: any): ApprovalRequestRow {\n return {\n id: String(row.id),\n organization_id: row.organization_id ?? undefined,\n process_name: String(row.process_name ?? ''),\n process_hash: row.process_hash ?? undefined,\n object_name: String(row.object_name ?? ''),\n record_id: String(row.record_id ?? ''),\n submitter_id: row.submitter_id ?? undefined,\n submitter_comment: row.submitter_comment ?? undefined,\n status: (row.status as ApprovalStatus) ?? 'pending',\n current_step: row.current_step ?? undefined,\n current_step_index: row.current_step_index ?? undefined,\n pending_approvers: csvSplit(row.pending_approvers),\n payload: parseJson(row.payload_json, undefined),\n completed_at: row.completed_at ?? undefined,\n created_at: row.created_at ?? undefined,\n updated_at: row.updated_at ?? undefined,\n } as any;\n}\n\nfunction rowFromAction(row: any): ApprovalActionRow {\n return {\n id: String(row.id),\n request_id: String(row.request_id),\n step_name: row.step_name ?? undefined,\n step_index: row.step_index ?? undefined,\n action: row.action,\n actor_id: row.actor_id ?? undefined,\n comment: row.comment ?? undefined,\n created_at: row.created_at ?? undefined,\n };\n}\n\n// Note: legacy synchronous `resolveApprovers` removed in M10.17.1 — replaced\n// by the async `expandApprovers` member which routes through the team/dept\n// graph tables (with prefixed-literal fallback for back-compat).\n\nexport interface ApprovalServiceOptions {\n engine: ApprovalEngine;\n clock?: ApprovalClock;\n logger?: { info?: (msg: any, ...rest: any[]) => void; warn?: (msg: any, ...rest: any[]) => void; error?: (msg: any, ...rest: any[]) => void; debug?: (msg: any, ...rest: any[]) => void };\n /** Optional fetch impl for `webhook` actions; defaults to global. */\n fetch?: FetchLike;\n /** Webhook timeout in ms; default 5000. */\n webhookTimeoutMs?: number;\n /**\n * Called after the process registry changes (defineProcess / deleteProcess).\n * The plugin uses this to re-bind lifecycle hooks for auto-trigger / lock.\n */\n onRegistryChange?: () => void | Promise<void>;\n /**\n * Optional metadata repository for execution-pinned process resolution\n * (ADR-0009). When provided:\n *\n * - `submit()` records the process body's sha256 on the request row.\n * - `approve` / `reject` / `recall` resolve the pinned body via\n * `MetadataRepository.getByHash` so process upgrades don't affect\n * in-flight requests.\n *\n * When omitted, the service reads the current process from the\n * `sys_approval_process` projection (pre-ADR-0009 behaviour).\n */\n metadataRepo?: MetadataRepository;\n}\n\nexport class ApprovalService implements IApprovalService {\n private readonly engine: ApprovalEngine;\n private readonly clock: ApprovalClock;\n private readonly logger?: ApprovalServiceOptions['logger'];\n private readonly fetchImpl?: FetchLike;\n private readonly webhookTimeoutMs?: number;\n private readonly onRegistryChange?: () => void | Promise<void>;\n private readonly metadataRepo?: MetadataRepository;\n\n constructor(opts: ApprovalServiceOptions) {\n this.engine = opts.engine;\n this.clock = opts.clock ?? { now: () => new Date() };\n this.logger = opts.logger;\n this.fetchImpl = opts.fetch;\n this.webhookTimeoutMs = opts.webhookTimeoutMs;\n this.onRegistryChange = opts.onRegistryChange;\n this.metadataRepo = opts.metadataRepo;\n }\n\n /** Allow the plugin to attach a hook re-binding callback after construction. */\n setRegistryChangeHandler(handler: () => void | Promise<void>): void {\n (this as any).onRegistryChange = handler;\n }\n\n /**\n * Expand the approvers on a step into user IDs by querying the graph\n * tables for `team:` / `department:` / `role:` / `manager:` approver\n * types. Falls back to a prefixed literal (`type:value`) when graph\n * lookups produce nothing — so existing test fixtures and approver\n * flows that rely on substring matching keep working.\n *\n * **Graph semantics (M10.17.1):**\n * - `team` → flat members of `sys_team` (better-auth; no BFS)\n * - `department` → recursive BFS of `sys_department.parent_department_id`\n * → members of every descendant via `sys_department_member`\n * - `role` → users with `sys_member.role = value` in tenant\n * - `manager` → `sys_user.manager_id` of `record[value] ?? record.owner_id`\n * - `field` → literal user id stored in `record[value]`\n * - `user` → literal value\n */\n private async expandApprovers(step: any, record?: any, organizationId?: string | null): Promise<string[]> {\n if (!step || !Array.isArray(step.approvers)) return [];\n const out: string[] = [];\n for (const a of step.approvers) {\n if (!a) continue;\n if (a.type === 'user') { out.push(String(a.value)); continue; }\n if (a.type === 'field' && record) { out.push(String((record as any)[a.value] ?? '')); continue; }\n try {\n if (a.type === 'team') {\n const users = await this.expandTeamUsers(String(a.value));\n if (users.length) { for (const u of users) out.push(u); continue; }\n } else if (a.type === 'department' || a.type === 'dept') {\n const users = await this.expandDepartmentUsers(String(a.value), organizationId);\n if (users.length) { for (const u of users) out.push(u); continue; }\n } else if (a.type === 'role') {\n const users = await this.expandRoleUsers(String(a.value), organizationId);\n if (users.length) { for (const u of users) out.push(u); continue; }\n } else if (a.type === 'manager' && record) {\n const subject = (record as any)[a.value] ?? (record as any).owner_id;\n if (subject) {\n const mgr = await this.lookupManager(String(subject));\n if (mgr) { out.push(mgr); continue; }\n }\n }\n } catch { /* fall through */ }\n out.push(`${a.type}:${a.value}`);\n }\n return out.filter(Boolean);\n }\n\n /** Flat team — `sys_team` is better-auth's collaboration grouping (no hierarchy). */\n private async expandTeamUsers(teamId: string): Promise<string[]> {\n if (!teamId) return [];\n let rows: any[] = [];\n try {\n rows = await this.engine.find('sys_team_member', {\n filter: { team_id: teamId },\n fields: ['user_id'],\n limit: 10000,\n context: SYSTEM_CTX,\n } as any);\n } catch { rows = []; }\n return Array.from(new Set((rows ?? []).map((r: any) => String(r.user_id ?? '')).filter(Boolean)));\n }\n\n /** Recursive department — walks `sys_department.parent_department_id`. */\n private async expandDepartmentUsers(departmentId: string, organizationId?: string | null): Promise<string[]> {\n if (!departmentId) return [];\n // Seed sanity check: skip if dept doesn't exist or is inactive within tenant.\n try {\n const seed = await this.engine.find('sys_department', {\n filter: organizationId\n ? { id: departmentId, organization_id: organizationId }\n : { id: departmentId },\n fields: ['id', 'active'],\n limit: 1,\n context: SYSTEM_CTX,\n } as any);\n const seedRow: any = Array.isArray(seed) ? seed[0] : null;\n if (!seedRow || seedRow.active === false) return [];\n } catch { return []; }\n\n const seen = new Set<string>([departmentId]);\n const queue: string[] = [departmentId];\n while (queue.length) {\n const parent = queue.shift()!;\n let kids: any[] = [];\n try {\n const filter: any = { parent_department_id: parent, active: { $ne: false } };\n if (organizationId) filter.organization_id = organizationId;\n kids = await this.engine.find('sys_department', { filter, fields: ['id'], limit: 1000, context: SYSTEM_CTX } as any);\n } catch { kids = []; }\n for (const k of kids ?? []) {\n const kid = String((k as any).id ?? '');\n if (kid && !seen.has(kid)) { seen.add(kid); queue.push(kid); }\n }\n }\n let rows: any[] = [];\n try {\n rows = await this.engine.find('sys_department_member', {\n filter: { department_id: { $in: Array.from(seen) } },\n fields: ['user_id'],\n limit: 10000,\n context: SYSTEM_CTX,\n } as any);\n } catch { rows = []; }\n return Array.from(new Set((rows ?? []).map((r: any) => String(r.user_id ?? '')).filter(Boolean)));\n }\n\n private async expandRoleUsers(roleName: string, organizationId?: string | null): Promise<string[]> {\n if (!roleName) return [];\n const filter: any = { role: roleName };\n if (organizationId) filter.organization_id = organizationId;\n let rows: any[] = [];\n try {\n rows = await this.engine.find('sys_member', { filter, fields: ['user_id'], limit: 10000, context: SYSTEM_CTX } as any);\n } catch { rows = []; }\n return Array.from(new Set((rows ?? []).map((r: any) => String(r.user_id ?? '')).filter(Boolean)));\n }\n\n private async lookupManager(userId: string): Promise<string | null> {\n try {\n const rows = await this.engine.find('sys_user', {\n filter: { id: userId }, fields: ['id', 'manager_id'], limit: 1, context: SYSTEM_CTX,\n } as any);\n const row: any = Array.isArray(rows) ? rows[0] : null;\n return row?.manager_id ? String(row.manager_id) : null;\n } catch { return null; }\n }\n\n\n private async notifyRegistryChanged(): Promise<void> {\n const cb = this.onRegistryChange ?? ((this as any).onRegistryChange as (() => void | Promise<void>) | undefined);\n if (!cb) return;\n try { await cb(); }\n catch (err: any) { this.logger?.warn?.('[approvals] onRegistryChange handler failed', { error: err?.message }); }\n }\n\n /**\n * Look up the HEAD checksum of an approval process from the metadata repo\n * (ADR-0009). Returns null when no repo is wired, no metadata exists for\n * the name, or the lookup fails — callers MUST treat null as \"do not pin\"\n * and fall back to the projection table.\n */\n private async resolveProcessHash(processName: string, organizationId?: string | null): Promise<string | null> {\n if (!this.metadataRepo) return null;\n if (!processName) return null;\n const orgRef = { org: organizationId || 'system', type: 'approval' as const, name: processName };\n try {\n const head = await this.metadataRepo.get(orgRef);\n return head?.hash ?? null;\n } catch (err: any) {\n this.logger?.debug?.('[approvals] metadataRepo.get failed', { name: processName, error: err?.message });\n return null;\n }\n }\n\n /**\n * Resolve the approval process for an in-flight request, honouring\n * ADR-0009 execution pinning when a `process_hash` is recorded.\n *\n * Resolution order:\n * 1. If `req.process_hash` AND `metadataRepo` are set, try\n * `getByHash` — return a row whose `definition` is the pinned body.\n * 2. Otherwise (or on lookup failure) fall back to the current\n * projection via `getProcess(req.process_name)`.\n */\n private async loadProcessForRequest(req: ApprovalRequestRow, context: SharingExecutionContext): Promise<ApprovalProcessRow | null> {\n const hash = req.process_hash;\n if (hash && this.metadataRepo) {\n const orgId = (req as any).organization_id ?? null;\n const orgRef = { org: orgId || 'system', type: 'approval' as const, name: req.process_name };\n try {\n const pinned = await this.metadataRepo.getByHash(orgRef, hash);\n if (pinned?.body) {\n // Use the pinned body for the definition; pull identity/state\n // fields from the current projection if available so display\n // labels and active-flag stay fresh. Synthesize if absent.\n const current = await this.getProcess(req.process_name, context);\n const body: any = pinned.body;\n return {\n id: current?.id ?? `pinned_${hash.slice(7, 19)}`,\n name: req.process_name,\n label: body.label ?? current?.label ?? req.process_name,\n object_name: req.object_name,\n description: body.description ?? current?.description,\n active: current?.active ?? true,\n definition: body,\n created_at: current?.created_at,\n updated_at: current?.updated_at,\n };\n }\n this.logger?.warn?.('[approvals] pinned process body not found; falling back to current', {\n request: req.id, process: req.process_name, hash,\n });\n } catch (err: any) {\n this.logger?.warn?.('[approvals] getByHash failed; falling back to current', {\n request: req.id, error: err?.message,\n });\n }\n }\n return this.getProcess(req.process_name, context);\n }\n\n /** Mirror request status onto `process.approvalStatusField` if configured. */\n private async syncStatusField(process: ApprovalProcessRow, request: ApprovalRequestRow): Promise<void> {\n const field = (process.definition as any)?.approvalStatusField;\n if (!field) return;\n try {\n await this.engine.update(\n process.object_name,\n { id: request.record_id, [field]: request.status },\n { context: SYSTEM_CTX },\n );\n } catch (err: any) {\n this.logger?.warn?.(`[approvals] syncStatusField failed: ${err?.message ?? err}`);\n }\n }\n\n /** Convenience wrapper that funnels every action invocation through the executor. */\n private async runActions(\n actions: any[] | undefined | null,\n trigger: ApprovalTrigger,\n process: ApprovalProcessRow,\n request: ApprovalRequestRow,\n step: any | undefined,\n actorId: string | null | undefined,\n comment: string | null | undefined,\n ): Promise<void> {\n if (!actions || actions.length === 0) return;\n await executeActions(actions, {\n trigger,\n process: { ...process, object: process.object_name },\n request,\n step,\n actorId: actorId ?? null,\n comment: comment ?? null,\n }, {\n engine: this.engine,\n logger: this.logger,\n fetch: this.fetchImpl,\n webhookTimeoutMs: this.webhookTimeoutMs,\n });\n }\n\n // ── Process definitions ──────────────────────────────────────\n\n async defineProcess(input: DefineApprovalProcessInput, _context: SharingExecutionContext): Promise<ApprovalProcessRow> {\n if (!input.name) throw new Error('VALIDATION_FAILED: name is required');\n if (!input.label) throw new Error('VALIDATION_FAILED: label is required');\n if (!input.object) throw new Error('VALIDATION_FAILED: object is required');\n if (!input.definition) throw new Error('VALIDATION_FAILED: definition is required');\n\n const parsed = ApprovalProcessSchema.safeParse(input.definition);\n if (!parsed.success) {\n const msg = parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; ');\n throw new Error(`VALIDATION_FAILED: ${msg}`);\n }\n\n const now = this.clock.now().toISOString();\n const payload: any = {\n name: input.name,\n label: input.label,\n object_name: input.object,\n description: input.description ?? null,\n active: input.active !== false,\n definition_json: JSON.stringify(parsed.data),\n updated_at: now,\n };\n\n // Upsert by name.\n const existing = await this.engine.find('sys_approval_process', {\n where: { name: input.name }, limit: 1, context: SYSTEM_CTX,\n });\n if (Array.isArray(existing) && existing[0]) {\n const id = existing[0].id;\n await this.engine.update('sys_approval_process', { id, ...payload }, { context: SYSTEM_CTX });\n const row = rowFromProcess({ ...existing[0], ...payload, id });\n await this.notifyRegistryChanged();\n return row;\n }\n\n const id = input.id ?? uid('apv');\n const row = { id, ...payload, created_at: now };\n await this.engine.insert('sys_approval_process', row, { context: SYSTEM_CTX });\n const out = rowFromProcess(row);\n await this.notifyRegistryChanged();\n return out;\n }\n\n async listProcesses(\n filter: { object?: string; activeOnly?: boolean } | undefined,\n _context: SharingExecutionContext,\n ): Promise<ApprovalProcessRow[]> {\n const f: any = {};\n if (filter?.object) f.object_name = filter.object;\n if (filter?.activeOnly) f.active = true;\n const rows = await this.engine.find('sys_approval_process', {\n where: f, limit: 500, orderBy: [{ field: 'updated_at', direction: 'desc' }], context: SYSTEM_CTX,\n });\n return Array.isArray(rows) ? rows.map(rowFromProcess) : [];\n }\n\n async getProcess(idOrName: string, _context: SharingExecutionContext): Promise<ApprovalProcessRow | null> {\n if (!idOrName) return null;\n let rows = await this.engine.find('sys_approval_process', {\n where: { id: idOrName }, limit: 1, context: SYSTEM_CTX,\n });\n if (!Array.isArray(rows) || !rows[0]) {\n rows = await this.engine.find('sys_approval_process', {\n where: { name: idOrName }, limit: 1, context: SYSTEM_CTX,\n });\n }\n return Array.isArray(rows) && rows[0] ? rowFromProcess(rows[0]) : null;\n }\n\n async deleteProcess(idOrName: string, context: SharingExecutionContext): Promise<void> {\n if (!idOrName) throw new Error('VALIDATION_FAILED: idOrName is required');\n const proc = await this.getProcess(idOrName, context);\n if (!proc) return;\n await this.engine.delete('sys_approval_process', { where: { id: proc.id }, context: SYSTEM_CTX });\n await this.notifyRegistryChanged();\n }\n\n // ── Requests ─────────────────────────────────────────────────\n\n async submit(input: SubmitApprovalInput, context: SharingExecutionContext): Promise<ApprovalRequestRow> {\n if (!input.object) throw new Error('VALIDATION_FAILED: object is required');\n if (!input.recordId) throw new Error('VALIDATION_FAILED: recordId is required');\n\n // Find active process for the object (or by name when supplied).\n let process: ApprovalProcessRow | null = null;\n if (input.processName) {\n process = await this.getProcess(input.processName, context);\n if (process && !process.active) {\n throw new Error(`NO_ACTIVE_PROCESS: process '${input.processName}' is not active`);\n }\n } else {\n const list = await this.listProcesses({ object: input.object, activeOnly: true }, context);\n process = list[0] ?? null;\n }\n if (!process) {\n throw new Error(`NO_ACTIVE_PROCESS: no active approval process for object '${input.object}'`);\n }\n\n // De-duplicate: only one pending request per (object, record).\n const existing = await this.engine.find('sys_approval_request', {\n where: { object_name: input.object, record_id: input.recordId, status: 'pending' },\n limit: 1, context: SYSTEM_CTX,\n });\n if (Array.isArray(existing) && existing[0]) {\n throw new Error(`DUPLICATE_REQUEST: a pending approval already exists for ${input.object}/${input.recordId}`);\n }\n\n const steps: any[] = process.definition?.steps ?? [];\n if (steps.length === 0) {\n throw new Error('VALIDATION_FAILED: process definition has no steps');\n }\n const step0 = steps[0];\n const ctxOrg = (context as any)?.organizationId ?? (context as any)?.tenantId ?? null;\n const approvers = await this.expandApprovers(step0, input.payload, ctxOrg);\n\n const now = this.clock.now().toISOString();\n const id = uid('areq');\n const processHash = await this.resolveProcessHash(process.name, ctxOrg);\n const row: any = {\n id,\n process_name: process.name,\n process_hash: processHash,\n object_name: input.object,\n record_id: input.recordId,\n submitter_id: input.submitterId ?? context.userId ?? null,\n submitter_comment: input.comment ?? null,\n status: 'pending',\n current_step: step0.name,\n current_step_index: 0,\n pending_approvers: approvers.join(','),\n payload_json: input.payload != null ? JSON.stringify(input.payload) : null,\n organization_id: ctxOrg,\n created_at: now,\n updated_at: now,\n };\n await this.engine.insert('sys_approval_request', row, { context: SYSTEM_CTX });\n\n // Audit: submit.\n await this.engine.insert('sys_approval_action', {\n id: uid('aact'),\n request_id: id,\n organization_id: ctxOrg,\n step_name: step0.name,\n step_index: 0,\n action: 'submit',\n actor_id: input.submitterId ?? context.userId ?? null,\n comment: input.comment ?? null,\n created_at: now,\n }, { context: SYSTEM_CTX });\n\n const requestRow = rowFromRequest(row);\n\n // Phase B: status mirror + onSubmit actions.\n await this.syncStatusField(process, requestRow);\n const definition: any = process.definition ?? {};\n await this.runActions(\n definition.onSubmit,\n 'submit',\n process,\n requestRow,\n step0,\n input.submitterId ?? context.userId ?? null,\n input.comment ?? null,\n );\n\n return requestRow;\n }\n\n async listRequests(\n filter: {\n object?: string;\n recordId?: string;\n status?: ApprovalStatus | ApprovalStatus[];\n approverId?: string;\n submitterId?: string;\n } | undefined,\n context: SharingExecutionContext,\n ): Promise<ApprovalRequestRow[]> {\n const f: any = {};\n if (filter?.object) f.object_name = filter.object;\n if (filter?.recordId) f.record_id = filter.recordId;\n if (filter?.submitterId) f.submitter_id = filter.submitterId;\n // Tenant isolation: when a caller context carries a tenant identifier\n // (organizationId / tenantId), scope the query to that tenant. SYSTEM\n // callers (no tenant) see all rows. This prevents the bespoke endpoint\n // from leaking other-tenant rows since we deliberately query with\n // SYSTEM_CTX to bypass RLS on the engine (we need CSV substring match\n // on pending_approvers which RLS can't model cleanly).\n const tenantOrg = (context as any)?.organizationId ?? (context as any)?.tenantId;\n if (tenantOrg) f.organization_id = tenantOrg;\n // Status: when array, post-filter; when single, push into engine filter.\n let statusFilter: ApprovalStatus[] | undefined;\n if (Array.isArray(filter?.status)) statusFilter = filter!.status as ApprovalStatus[];\n else if (filter?.status) f.status = filter.status;\n\n const rows = await this.engine.find('sys_approval_request', {\n where: f, limit: 500, orderBy: [{ field: 'updated_at', direction: 'desc' }], context: SYSTEM_CTX,\n });\n let list = Array.isArray(rows) ? rows.map(rowFromRequest) : [];\n if (statusFilter) list = list.filter(r => statusFilter!.includes(r.status));\n if (filter?.approverId) {\n const target = filter.approverId;\n list = list.filter(r => (r.pending_approvers ?? []).includes(target));\n }\n return list;\n }\n\n async getRequest(requestId: string, context: SharingExecutionContext): Promise<ApprovalRequestRow | null> {\n if (!requestId) return null;\n const where: any = { id: requestId };\n const tenantOrg = (context as any)?.organizationId ?? (context as any)?.tenantId;\n if (tenantOrg) where.organization_id = tenantOrg;\n const rows = await this.engine.find('sys_approval_request', {\n where, limit: 1, context: SYSTEM_CTX,\n });\n return Array.isArray(rows) && rows[0] ? rowFromRequest(rows[0]) : null;\n }\n\n async approve(requestId: string, input: ApprovalDecisionInput, context: SharingExecutionContext): Promise<ApprovalDecisionResult> {\n const req = await this.getRequest(requestId, context);\n if (!req) throw new Error(`REQUEST_NOT_FOUND: ${requestId}`);\n if (req.status !== 'pending') throw new Error(`INVALID_STATE: request is ${req.status}`);\n if (!input?.actorId) throw new Error('VALIDATION_FAILED: actorId is required');\n\n if (!context.isSystem && !(req.pending_approvers ?? []).includes(input.actorId)) {\n throw new Error(`FORBIDDEN: actor '${input.actorId}' is not a pending approver`);\n }\n\n const process = await this.loadProcessForRequest(req, context);\n if (!process) throw new Error(`PROCESS_NOT_FOUND: ${req.process_name}`);\n const steps: any[] = process.definition?.steps ?? [];\n const stepIndex = req.current_step_index ?? 0;\n const step = steps[stepIndex];\n if (!step) throw new Error(`INVALID_STATE: step index ${stepIndex} out of range`);\n\n const now = this.clock.now().toISOString();\n // Audit row first so unanimous tally sees it.\n await this.engine.insert('sys_approval_action', {\n id: uid('aact'),\n request_id: req.id,\n organization_id: (req as any).organization_id ?? null,\n step_name: step.name,\n step_index: stepIndex,\n action: 'approve',\n actor_id: input.actorId,\n comment: input.comment ?? null,\n created_at: now,\n }, { context: SYSTEM_CTX });\n\n // Unanimous: only advance once every original approver has approved at this step_index.\n if (step.behavior === 'unanimous') {\n const original = await this.expandApprovers(step, req.payload, (req as any).organization_id ?? null);\n const acts = await this.engine.find('sys_approval_action', {\n where: { request_id: req.id, step_index: stepIndex, action: 'approve' },\n limit: 500, context: SYSTEM_CTX,\n });\n const approved = new Set<string>((acts ?? []).map((a: any) => String(a.actor_id ?? '')).filter(Boolean));\n const stillPending = original.filter(a => !approved.has(a));\n if (stillPending.length > 0) {\n // Update pending_approvers to those who haven't voted yet.\n await this.engine.update('sys_approval_request', {\n id: req.id,\n pending_approvers: stillPending.join(','),\n updated_at: now,\n }, { context: SYSTEM_CTX });\n const fresh = await this.getRequest(req.id, context);\n return { request: fresh!, finalized: false };\n }\n }\n\n // Advance the request — either to next step or to finalized=approved.\n if (stepIndex + 1 >= steps.length) {\n await this.engine.update('sys_approval_request', {\n id: req.id,\n status: 'approved',\n pending_approvers: null,\n completed_at: now,\n updated_at: now,\n }, { context: SYSTEM_CTX });\n const fresh = await this.getRequest(req.id, context);\n // Phase B: step.onApprove + process.onFinalApprove + status mirror.\n await this.runActions((step as any)?.onApprove, 'step_approve', process, fresh!, step, input.actorId, input.comment);\n await this.syncStatusField(process, fresh!);\n await this.runActions((process.definition as any)?.onFinalApprove, 'final_approve', process, fresh!, step, input.actorId, input.comment);\n return { request: fresh!, finalized: true };\n }\n\n const nextStep = steps[stepIndex + 1];\n const nextApprovers = await this.expandApprovers(nextStep, req.payload, (req as any).organization_id ?? null);\n await this.engine.update('sys_approval_request', {\n id: req.id,\n current_step: nextStep.name,\n current_step_index: stepIndex + 1,\n pending_approvers: nextApprovers.join(','),\n updated_at: now,\n }, { context: SYSTEM_CTX });\n const fresh = await this.getRequest(req.id, context);\n // Phase B: step.onApprove fires when transitioning out of this step.\n await this.runActions((step as any)?.onApprove, 'step_approve', process, fresh!, step, input.actorId, input.comment);\n return { request: fresh!, finalized: false };\n }\n\n async reject(requestId: string, input: ApprovalDecisionInput, context: SharingExecutionContext): Promise<ApprovalDecisionResult> {\n const req = await this.getRequest(requestId, context);\n if (!req) throw new Error(`REQUEST_NOT_FOUND: ${requestId}`);\n if (req.status !== 'pending') throw new Error(`INVALID_STATE: request is ${req.status}`);\n if (!input?.actorId) throw new Error('VALIDATION_FAILED: actorId is required');\n if (!context.isSystem && !(req.pending_approvers ?? []).includes(input.actorId)) {\n throw new Error(`FORBIDDEN: actor '${input.actorId}' is not a pending approver`);\n }\n\n const process = await this.loadProcessForRequest(req, context);\n if (!process) throw new Error(`PROCESS_NOT_FOUND: ${req.process_name}`);\n const steps: any[] = process.definition?.steps ?? [];\n const stepIndex = req.current_step_index ?? 0;\n const step = steps[stepIndex];\n\n const now = this.clock.now().toISOString();\n await this.engine.insert('sys_approval_action', {\n id: uid('aact'),\n request_id: req.id,\n organization_id: (req as any).organization_id ?? null,\n step_name: step?.name,\n step_index: stepIndex,\n action: 'reject',\n actor_id: input.actorId,\n comment: input.comment ?? null,\n created_at: now,\n }, { context: SYSTEM_CTX });\n\n if (step?.rejectionBehavior === 'back_to_previous' && stepIndex > 0) {\n const prev = steps[stepIndex - 1];\n const prevApprovers = await this.expandApprovers(prev, req.payload, (req as any).organization_id ?? null);\n await this.engine.update('sys_approval_request', {\n id: req.id,\n current_step: prev.name,\n current_step_index: stepIndex - 1,\n pending_approvers: prevApprovers.join(','),\n updated_at: now,\n }, { context: SYSTEM_CTX });\n const fresh = await this.getRequest(req.id, context);\n // Phase B: step-level onReject fires on non-final rejection too.\n await this.runActions((step as any)?.onReject, 'step_reject', process, fresh!, step, input.actorId, input.comment);\n return { request: fresh!, finalized: false };\n }\n\n await this.engine.update('sys_approval_request', {\n id: req.id,\n status: 'rejected',\n pending_approvers: null,\n completed_at: now,\n updated_at: now,\n }, { context: SYSTEM_CTX });\n const fresh = await this.getRequest(req.id, context);\n // Phase B: step.onReject + process.onFinalReject + status mirror.\n await this.runActions((step as any)?.onReject, 'step_reject', process, fresh!, step, input.actorId, input.comment);\n await this.syncStatusField(process, fresh!);\n await this.runActions((process.definition as any)?.onFinalReject, 'final_reject', process, fresh!, step, input.actorId, input.comment);\n return { request: fresh!, finalized: true };\n }\n\n async recall(requestId: string, input: ApprovalDecisionInput, context: SharingExecutionContext): Promise<ApprovalDecisionResult> {\n const req = await this.getRequest(requestId, context);\n if (!req) throw new Error(`REQUEST_NOT_FOUND: ${requestId}`);\n if (req.status !== 'pending') throw new Error(`INVALID_STATE: request is ${req.status}`);\n if (!input?.actorId) throw new Error('VALIDATION_FAILED: actorId is required');\n if (!context.isSystem && req.submitter_id && req.submitter_id !== input.actorId) {\n throw new Error(`FORBIDDEN: only the submitter can recall this request`);\n }\n\n const now = this.clock.now().toISOString();\n await this.engine.insert('sys_approval_action', {\n id: uid('aact'),\n request_id: req.id,\n organization_id: (req as any).organization_id ?? null,\n step_name: req.current_step,\n step_index: req.current_step_index,\n action: 'recall',\n actor_id: input.actorId,\n comment: input.comment ?? null,\n created_at: now,\n }, { context: SYSTEM_CTX });\n\n await this.engine.update('sys_approval_request', {\n id: req.id,\n status: 'recalled',\n pending_approvers: null,\n completed_at: now,\n updated_at: now,\n }, { context: SYSTEM_CTX });\n const fresh = await this.getRequest(req.id, context);\n // Phase B: process.onRecall + status mirror.\n const process = await this.loadProcessForRequest(req, context);\n if (process) {\n await this.syncStatusField(process, fresh!);\n await this.runActions((process.definition as any)?.onRecall, 'recall', process, fresh!, undefined, input.actorId, input.comment);\n }\n return { request: fresh!, finalized: true };\n }\n\n async listActions(requestId: string, context: SharingExecutionContext): Promise<ApprovalActionRow[]> {\n if (!requestId) return [];\n // Tenant gate: ensure the caller can see the parent request before\n // returning its action history. Skipping this would leak history rows\n // across tenants the same way the unscoped list-requests path did.\n const req = await this.getRequest(requestId, context);\n if (!req) return [];\n const rows = await this.engine.find('sys_approval_action', {\n where: { request_id: requestId },\n limit: 500,\n orderBy: [{ field: 'created_at', direction: 'asc' }],\n context: SYSTEM_CTX,\n });\n return Array.isArray(rows) ? rows.map(rowFromAction) : [];\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Approval Action Executor — M11.C15.B\n *\n * Pure dispatcher that runs the `ApprovalAction` items declared on\n * `ApprovalProcess.onSubmit / onFinalApprove / onFinalReject / onRecall`\n * and `ApprovalStep.onApprove / onReject`.\n *\n * Supported action types:\n * - `field_update` — write `config.field = config.value` on the\n * business record (under SYSTEM_CTX so the lock hook is bypassed).\n * `config.value` may be a literal or `\"$status\"` / `\"$now\"` /\n * `\"$actor\"` / `\"$comment\"` token resolved against the runtime\n * context.\n * - `inbox_notify` — insert one `sys_notification` row per target.\n * `config.to` may be `'submitter' | 'pending_approvers'` or an\n * explicit `string[]` of user ids. `config.title` / `config.body`\n * interpolate `{record_id}`, `{object}`, `{status}`, `{step}`,\n * `{actor}`, `{comment}`.\n * - `webhook` — POST `config.body` (JSON) to `config.url`,\n * fire-and-forget (caller awaits with timeout). Headers default\n * to `Content-Type: application/json`. Failures are logged, not\n * thrown, so a flaky receiver can't deadlock the approval flow.\n *\n * Unimplemented (logged + skipped):\n * - `email_alert` — needs SMTP transport, later milestone.\n * - `script` — needs sandboxed runner, later milestone.\n * - `connector_action` — needs connector registry, later milestone.\n */\n\nimport type { ApprovalEngine } from './approval-service.js';\n\nconst SYSTEM_CTX = { isSystem: true, roles: [], permissions: [] } as const;\n\nexport interface ActionLogger {\n info?: (msg: string, meta?: any) => void;\n warn?: (msg: string, meta?: any) => void;\n error?: (msg: string, meta?: any) => void;\n debug?: (msg: string, meta?: any) => void;\n}\n\nconst noopLogger: Required<ActionLogger> = {\n info: () => {}, warn: () => {}, error: () => {}, debug: () => {},\n};\n\n/** Possible trigger points; passed to executors for tokenization. */\nexport type ApprovalTrigger =\n | 'submit'\n | 'step_approve'\n | 'final_approve'\n | 'step_reject'\n | 'final_reject'\n | 'recall';\n\nexport interface ExecutionContext {\n /** The trigger that caused these actions to fire. */\n trigger: ApprovalTrigger;\n /** Approval process row (parsed `definition`). */\n process: any;\n /** Approval request row (post-transition). */\n request: any;\n /** Current step config (when applicable). */\n step?: any;\n /** Business record (optional — looked up on demand if needed). */\n record?: any;\n /** Actor whose decision triggered the action; for `submit` this is the submitter. */\n actorId?: string | null;\n /** Comment passed with the decision. */\n comment?: string | null;\n}\n\n/** Default fetch implementation — overridable for tests. */\nexport type FetchLike = (\n input: any,\n init?: any,\n) => Promise<{ ok: boolean; status: number; statusText: string }>;\n\nexport interface ExecuteActionsOptions {\n engine: ApprovalEngine;\n logger?: ActionLogger;\n fetch?: FetchLike;\n /** Maximum webhook duration in ms; default 5000. */\n webhookTimeoutMs?: number;\n}\n\nconst DEFAULT_WEBHOOK_TIMEOUT_MS = 5000;\n\n/** Public entry point — run an ordered list of actions. */\nexport async function executeActions(\n actions: any[] | undefined | null,\n ctx: ExecutionContext,\n opts: ExecuteActionsOptions,\n): Promise<void> {\n if (!Array.isArray(actions) || actions.length === 0) return;\n const log = { ...noopLogger, ...(opts.logger ?? {}) };\n for (const a of actions) {\n try {\n await runOne(a, ctx, opts, log);\n } catch (err: any) {\n // Approval actions must not crash the transition — log + continue.\n log.error?.(`[approvals] action '${a?.type ?? '<unknown>'}' failed: ${err?.message ?? err}`, {\n action: a, trigger: ctx.trigger, request_id: ctx.request?.id,\n });\n }\n }\n}\n\nasync function runOne(\n action: any,\n ctx: ExecutionContext,\n opts: ExecuteActionsOptions,\n log: Required<ActionLogger>,\n): Promise<void> {\n if (!action || typeof action !== 'object') return;\n switch (action.type) {\n case 'field_update': return runFieldUpdate(action, ctx, opts, log);\n case 'inbox_notify': return runInboxNotify(action, ctx, opts, log);\n case 'webhook': return runWebhook(action, ctx, opts, log);\n case 'email_alert':\n case 'script':\n case 'connector_action':\n log.warn?.(`[approvals] action type '${action.type}' is not implemented yet — skipping`, {\n action_name: action.name, trigger: ctx.trigger,\n });\n return;\n default:\n log.warn?.(`[approvals] unknown action type '${action.type}' — skipping`);\n }\n}\n\n// ── field_update ──────────────────────────────────────────────────\n\nasync function runFieldUpdate(\n action: any,\n ctx: ExecutionContext,\n opts: ExecuteActionsOptions,\n log: Required<ActionLogger>,\n): Promise<void> {\n const cfg = action.config ?? {};\n const field: string | undefined = cfg.field;\n if (!field) {\n log.warn?.('[approvals] field_update missing config.field');\n return;\n }\n const value = resolveValueToken(cfg.value, ctx);\n const object = ctx.process?.object_name ?? ctx.process?.object;\n const recordId = ctx.request?.record_id;\n if (!object || !recordId) {\n log.warn?.('[approvals] field_update missing object/record context');\n return;\n }\n await opts.engine.update(\n object,\n { id: recordId, [field]: value },\n { context: SYSTEM_CTX },\n );\n log.debug?.(`[approvals] field_update ${object}/${recordId} set ${field}`, { value });\n}\n\n/** Resolve `$status`, `$now`, `$actor`, `$comment` or literal value. */\nfunction resolveValueToken(raw: unknown, ctx: ExecutionContext): unknown {\n if (typeof raw !== 'string') return raw;\n switch (raw) {\n case '$status': return ctx.request?.status ?? null;\n case '$now': return new Date().toISOString();\n case '$actor': return ctx.actorId ?? null;\n case '$comment': return ctx.comment ?? null;\n case '$step': return ctx.request?.current_step ?? null;\n case '$request_id': return ctx.request?.id ?? null;\n default: return raw;\n }\n}\n\n// ── inbox_notify ──────────────────────────────────────────────────\n\nfunction interpolate(template: string, ctx: ExecutionContext): string {\n if (typeof template !== 'string') return template as any;\n return template\n .replace(/\\{record_id\\}/g, String(ctx.request?.record_id ?? ''))\n .replace(/\\{object\\}/g, String(ctx.process?.object_name ?? ctx.process?.object ?? ''))\n .replace(/\\{status\\}/g, String(ctx.request?.status ?? ''))\n .replace(/\\{step\\}/g, String(ctx.request?.current_step ?? ''))\n .replace(/\\{actor\\}/g, String(ctx.actorId ?? ''))\n .replace(/\\{comment\\}/g, String(ctx.comment ?? ''))\n .replace(/\\{process\\}/g, String(ctx.process?.name ?? ''));\n}\n\nasync function runInboxNotify(\n action: any,\n ctx: ExecutionContext,\n opts: ExecuteActionsOptions,\n log: Required<ActionLogger>,\n): Promise<void> {\n const cfg = action.config ?? {};\n const recipients = resolveRecipients(cfg.to, ctx);\n if (recipients.length === 0) {\n log.debug?.('[approvals] inbox_notify resolved no recipients — skipping');\n return;\n }\n const title = interpolate(cfg.title ?? 'Approval update', ctx);\n const body = interpolate(cfg.body ?? '', ctx);\n // sys_notification.type is a select with a fixed enum; 'system' is the\n // safe default. Callers may override via cfg.notificationType but must\n // pick a value the schema accepts.\n const type = String(cfg.notificationType ?? 'system');\n const rawLink = cfg.link\n ? interpolate(String(cfg.link), ctx)\n : `/console/system/approvals?requestId=${encodeURIComponent(ctx.request?.id ?? '')}`;\n // sys_notification.url is a URL field — only forward absolute URLs.\n // Relative deep-links (`/system/approvals`) get stripped to satisfy\n // validation; the recipient can still navigate via the source linkage.\n const url = /^https?:\\/\\//i.test(rawLink) ? rawLink : null;\n const now = new Date().toISOString();\n\n for (const recipient of recipients) {\n try {\n await opts.engine.insert(\n 'sys_notification',\n {\n id: `notif_${cryptoRandom()}`,\n recipient_id: String(recipient),\n type,\n title,\n body,\n url,\n is_read: false,\n source_object: ctx.process?.object_name ?? ctx.process?.object ?? null,\n source_id: ctx.request?.record_id ?? null,\n created_at: now,\n updated_at: now,\n },\n { context: SYSTEM_CTX },\n );\n } catch (err: any) {\n // Notification persistence is best-effort.\n log.warn?.(`[approvals] inbox_notify insert failed for ${recipient}: ${err?.message ?? err}`);\n }\n }\n}\n\nfunction resolveRecipients(to: unknown, ctx: ExecutionContext): string[] {\n if (Array.isArray(to)) return to.map(String).filter(Boolean);\n if (typeof to === 'string') {\n if (to === 'submitter') return ctx.request?.submitter_id ? [String(ctx.request.submitter_id)] : [];\n if (to === 'pending_approvers') {\n const list = ctx.request?.pending_approvers ?? [];\n if (Array.isArray(list)) return list.map(String).filter(Boolean);\n if (typeof list === 'string') return list.split(',').map(s => s.trim()).filter(Boolean);\n return [];\n }\n // Fall through: literal user id.\n return [to];\n }\n return [];\n}\n\nfunction cryptoRandom(): string {\n const g: any = globalThis as any;\n if (g.crypto?.randomUUID) return g.crypto.randomUUID();\n return `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 12)}`;\n}\n\n// ── webhook ──────────────────────────────────────────────────────\n\nasync function runWebhook(\n action: any,\n ctx: ExecutionContext,\n opts: ExecuteActionsOptions,\n log: Required<ActionLogger>,\n): Promise<void> {\n const cfg = action.config ?? {};\n const url: string | undefined = cfg.url;\n if (!url) {\n log.warn?.('[approvals] webhook missing config.url');\n return;\n }\n const fetchImpl: FetchLike = opts.fetch ?? (globalThis as any).fetch;\n if (!fetchImpl) {\n log.warn?.('[approvals] webhook skipped — no fetch implementation available');\n return;\n }\n const timeoutMs = opts.webhookTimeoutMs ?? DEFAULT_WEBHOOK_TIMEOUT_MS;\n const headers = { 'Content-Type': 'application/json', ...(cfg.headers ?? {}) };\n const payload = {\n trigger: ctx.trigger,\n request: ctx.request,\n step: ctx.step ? { name: ctx.step.name, index: ctx.request?.current_step_index } : null,\n actor_id: ctx.actorId ?? null,\n comment: ctx.comment ?? null,\n process_name: ctx.process?.name,\n object: ctx.process?.object_name ?? ctx.process?.object,\n ...(cfg.body && typeof cfg.body === 'object' ? cfg.body : {}),\n };\n // Manual timeout — works in Node 18+ without AbortController dependency.\n const controller = (globalThis as any).AbortController ? new (globalThis as any).AbortController() : null;\n const timer = setTimeout(() => controller?.abort(), timeoutMs);\n try {\n const res = await fetchImpl(url, {\n method: cfg.method ?? 'POST',\n headers,\n body: JSON.stringify(payload),\n signal: controller?.signal,\n });\n if (!res.ok) {\n log.warn?.(`[approvals] webhook ${url} → ${res.status} ${res.statusText}`);\n }\n } catch (err: any) {\n log.warn?.(`[approvals] webhook ${url} failed: ${err?.message ?? err}`);\n } finally {\n clearTimeout(timer);\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport {\n SysApprovalProcess,\n SysApprovalRequest,\n SysApprovalAction,\n} from '@objectstack/platform-objects/audit';\nimport { ApprovalService, type ApprovalEngine } from './approval-service.js';\nimport { bindProcessHooks, unbindAllHooks } from './lifecycle-hooks.js';\n\nexport interface ApprovalsPluginOptions {\n /** Disable runtime registration (schemas still register). */\n disableService?: boolean;\n /**\n * Disable Phase B auto-trigger / lock hooks. Schema definition stays\n * intact; only the engine-level wiring is suppressed. Useful when a\n * caller wants the manual API only (e.g. tests).\n */\n disableAutoHooks?: boolean;\n}\n\n/**\n * ApprovalsServicePlugin — registers sys_approval_{process,request,action},\n * the `approvals` service, and Phase B lifecycle hooks (auto-trigger,\n * record lock, status mirror). SLA escalation dispatcher is a later\n * milestone.\n */\nexport class ApprovalsServicePlugin implements Plugin {\n name = 'com.objectstack.service.approvals';\n version = '1.0.0';\n type = 'standard';\n dependencies = ['com.objectstack.engine.objectql'];\n\n private readonly options: ApprovalsPluginOptions;\n private service?: ApprovalService;\n private engine?: any;\n private logger?: any;\n\n constructor(options: ApprovalsPluginOptions = {}) {\n this.options = options;\n }\n\n async init(ctx: PluginContext): Promise<void> {\n ctx.getService<{ register(m: any): void }>('manifest').register({\n id: 'com.objectstack.service.approvals',\n name: 'Approvals Service',\n version: '1.0.0',\n type: 'plugin',\n scope: 'system',\n defaultDatasource: 'cloud',\n namespace: 'sys',\n objects: [SysApprovalProcess, SysApprovalRequest, SysApprovalAction],\n });\n ctx.logger.info('ApprovalsServicePlugin: schemas registered');\n }\n\n async start(ctx: PluginContext): Promise<void> {\n if (this.options.disableService) return;\n let engine: any = null;\n try { engine = ctx.getService<any>('objectql'); }\n catch { try { engine = ctx.getService<any>('data'); } catch { /* ignore */ } }\n if (!engine) {\n ctx.logger.warn('ApprovalsServicePlugin: no ObjectQL engine — service NOT registered');\n return;\n }\n this.engine = engine;\n this.logger = ctx.logger;\n\n // ADR-0009: try to wire the metadata repository for execution pinning.\n // The approvals service degrades to the projection-table path if no\n // metadata service is registered (e.g. in tests or minimal setups).\n let metadataRepo: any;\n try {\n const meta = ctx.getService<any>('metadata');\n metadataRepo = meta?.getRepository?.();\n } catch { /* metadata plugin not loaded — fall back */ }\n\n this.service = new ApprovalService({\n engine: engine as ApprovalEngine,\n logger: ctx.logger,\n metadataRepo,\n });\n\n if (metadataRepo) {\n ctx.logger.info('ApprovalsServicePlugin: execution pinning enabled (ADR-0009)');\n }\n\n if (!this.options.disableAutoHooks) {\n // Re-bind hooks on every registry mutation.\n this.service.setRegistryChangeHandler(() => this.rebindHooks());\n // Initial bind happens once the kernel is ready so the AppPlugin's\n // declarative process seeder has already populated sys_approval_process.\n const hookOn = (ctx as any).hook ?? (ctx as any).on;\n if (typeof hookOn === 'function') {\n try {\n hookOn.call(ctx, 'kernel:ready', async () => { await this.rebindHooks(); });\n } catch {\n // Fall through to immediate bind (no kernel:ready event).\n await this.rebindHooks();\n }\n } else {\n await this.rebindHooks();\n }\n }\n\n ctx.registerService('approvals', this.service);\n ctx.logger.info('ApprovalsServicePlugin: service registered');\n }\n\n private async rebindHooks(): Promise<void> {\n if (!this.engine || !this.service) return;\n try {\n unbindAllHooks(this.engine);\n const processes = await this.service.listProcesses({ activeOnly: true }, { isSystem: true, roles: [], permissions: [] } as any);\n bindProcessHooks(this.engine, this.service, processes, this.logger);\n } catch (err: any) {\n this.logger?.warn?.('[approvals] rebindHooks failed', { error: err?.message });\n }\n }\n\n async stop(_ctx: PluginContext): Promise<void> {\n if (this.engine) {\n try { unbindAllHooks(this.engine); } catch { /* ignore */ }\n }\n }\n}\n\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Lifecycle Hooks — Phase B auto-takeover.\n *\n * For each active ApprovalProcess we bind three hooks on its target object:\n *\n * 1. `afterInsert` — evaluate `entryCriteria` against the new record;\n * if truthy and no pending request exists, auto-submit one.\n * 2. `afterUpdate` — same as above but for updates that newly satisfy\n * criteria (e.g. amount edited above threshold).\n * 3. `beforeUpdate` — when `lockRecord=true`, block edits to a record\n * that has a pending request, EXCEPT when the only fields being\n * changed are the configured `approvalStatusField` (so the engine's\n * own status mirror is not blocked).\n *\n * All hooks are registered with `packageId: 'plugin-approvals:auto'` so\n * that re-bind on `defineProcess`/`deleteProcess` can call\n * `engine.unregisterHooksByPackage(...)` first.\n */\n\nimport { ExpressionEngine } from '@objectstack/formula';\nimport type { Expression } from '@objectstack/spec';\nimport type { ApprovalProcessRow } from '@objectstack/spec/contracts';\nimport type { ApprovalService } from './approval-service.js';\n\nexport const APPROVALS_HOOK_PACKAGE = 'plugin-approvals:auto';\n\nconst SYSTEM_CTX = { isSystem: true, roles: [], permissions: [] } as const;\n\ninterface MinimalEngine {\n registerHook(event: string, handler: (ctx: any) => any | Promise<any>, options?: {\n object?: string | string[];\n priority?: number;\n packageId?: string;\n }): void;\n unregisterHooksByPackage(packageId: string): number;\n find<T = any>(object: string, args: any, opts?: any): Promise<T[]>;\n}\n\ninterface MinimalLogger {\n debug?: (msg: any, ...rest: any[]) => void;\n info?: (msg: any, ...rest: any[]) => void;\n warn?: (msg: any, ...rest: any[]) => void;\n error?: (msg: any, ...rest: any[]) => void;\n}\n\n/**\n * Evaluate an entry criteria expression against a record. Returns `true`\n * when no criteria is set (matches everything). Returns `false` on\n * evaluation failure (fail-closed — better to skip than auto-submit on a\n * broken expression).\n */\nfunction evaluateCriteria(criteria: unknown, record: Record<string, unknown>, logger?: MinimalLogger): boolean {\n if (criteria == null || criteria === '' ) return true;\n let expr: Expression;\n if (typeof criteria === 'string') {\n expr = { dialect: 'cel', source: criteria };\n } else if (typeof criteria === 'object' && (criteria as any).dialect) {\n expr = criteria as Expression;\n } else {\n return true;\n }\n if (!expr.source || !expr.source.trim()) return true;\n const r = ExpressionEngine.evaluate<boolean>(expr, { record });\n if (!r.ok) {\n logger?.warn?.('[approvals] entryCriteria evaluation failed; skipping auto-submit', {\n source: expr.source,\n error: r.error.message,\n });\n return false;\n }\n return Boolean(r.value);\n}\n\n/** Does this record already have a pending approval request? */\nasync function hasPendingRequest(\n engine: MinimalEngine,\n objectName: string,\n recordId: string,\n): Promise<boolean> {\n try {\n const rows = await engine.find('sys_approval_request', {\n where: { object_name: objectName, record_id: String(recordId), status: 'pending' },\n limit: 1,\n } as any);\n return Array.isArray(rows) && rows.length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Bind auto-trigger + lock hooks for the supplied active processes.\n * Caller is responsible for calling `unbindAll` first if re-binding.\n */\nexport function bindProcessHooks(\n engine: MinimalEngine,\n service: ApprovalService,\n processes: ApprovalProcessRow[],\n logger?: MinimalLogger,\n): void {\n // Group processes by object so we can register one hook per object\n // and fan out internally — keeps the engine's hook map compact.\n const byObject = new Map<string, ApprovalProcessRow[]>();\n for (const p of processes) {\n if (!(p as any).active && !(p as any).is_active) continue;\n if (!p.object_name) continue;\n const list = byObject.get(p.object_name) ?? [];\n list.push(p);\n byObject.set(p.object_name, list);\n }\n\n for (const [objectName, procs] of byObject.entries()) {\n // ---- auto-trigger (afterInsert) ----\n engine.registerHook('afterInsert', async (ctx: any) => {\n try {\n const record = (ctx?.result ?? ctx?.input?.data ?? {}) as Record<string, unknown>;\n const id = String((record as any)?.id ?? '');\n if (!id) return;\n for (const proc of procs) {\n await tryAutoSubmit(engine, service, proc, objectName, id, record, ctx, logger);\n }\n } catch (err: any) {\n logger?.warn?.('[approvals] afterInsert auto-trigger failed', { error: err?.message });\n }\n }, { object: objectName, packageId: APPROVALS_HOOK_PACKAGE, priority: 200 });\n\n // ---- auto-trigger (afterUpdate) ----\n engine.registerHook('afterUpdate', async (ctx: any) => {\n // Ignore engine self-writes (status mirror, field_update from\n // post-actions, etc) — otherwise post-finalize updates would loop\n // a fresh approval on every state change.\n if ((ctx?.session as any)?.isSystem) return;\n try {\n const result = (ctx?.result ?? {}) as Record<string, unknown>;\n const id = String((ctx?.input?.id ?? (result as any)?.id ?? '') as string);\n if (!id) return;\n // result may be { affected: 1 } for some drivers; merge previous+input.data as the\n // best-effort record snapshot for criteria evaluation.\n const record: Record<string, unknown> = {\n ...(ctx?.previous ?? {}),\n ...((result as any)?.id ? result : {}),\n ...((ctx?.input?.data ?? {}) as Record<string, unknown>),\n id,\n };\n for (const proc of procs) {\n await tryAutoSubmit(engine, service, proc, objectName, id, record, ctx, logger);\n }\n } catch (err: any) {\n logger?.warn?.('[approvals] afterUpdate auto-trigger failed', { error: err?.message });\n }\n }, { object: objectName, packageId: APPROVALS_HOOK_PACKAGE, priority: 200 });\n\n // ---- record lock (beforeUpdate) ----\n const lockProcs = procs.filter((p) => (p.definition as any)?.lockRecord !== false);\n if (lockProcs.length === 0) continue;\n engine.registerHook('beforeUpdate', async (ctx: any) => {\n const id = String((ctx?.input?.id ?? '') as string);\n if (!id) return;\n const data = (ctx?.input?.data ?? {}) as Record<string, unknown>;\n const changedFields = Object.keys(data).filter((k) => k !== 'id' && k !== 'updated_at');\n if (changedFields.length === 0) return;\n\n // Allow engine self-writes (status mirror, field_update from actions, etc).\n if ((ctx?.session as any)?.isSystem) return;\n\n // Allow when every changed field is an approval status mirror.\n const mirrorFields = new Set<string>();\n for (const p of lockProcs) {\n const f = (p.definition as any)?.approvalStatusField;\n if (typeof f === 'string' && f) mirrorFields.add(f);\n }\n const onlyMirror = changedFields.every((f) => mirrorFields.has(f));\n if (onlyMirror) return;\n\n // Allow admin override: roles include 'admin'.\n const roles = (ctx?.session?.roles ?? []) as string[];\n if (Array.isArray(roles) && roles.includes('admin')) return;\n\n const pending = await hasPendingRequest(engine, objectName, id);\n if (!pending) return;\n\n const err: any = new Error('RECORD_LOCKED: record is locked while an approval is in progress');\n err.code = 'RECORD_LOCKED';\n err.statusCode = 409;\n throw err;\n }, { object: objectName, packageId: APPROVALS_HOOK_PACKAGE, priority: 50 });\n }\n\n logger?.info?.('[approvals] lifecycle hooks bound', {\n objects: Array.from(byObject.keys()),\n processCount: processes.length,\n });\n}\n\n/** Unregister every hook the auto-trigger module ever registered. */\nexport function unbindAllHooks(engine: MinimalEngine): number {\n return engine.unregisterHooksByPackage(APPROVALS_HOOK_PACKAGE);\n}\n\nasync function tryAutoSubmit(\n engine: MinimalEngine,\n service: ApprovalService,\n process: ApprovalProcessRow,\n objectName: string,\n recordId: string,\n record: Record<string, unknown>,\n ctx: any,\n logger?: MinimalLogger,\n): Promise<void> {\n try {\n const criteria = (process.definition as any)?.entryCriteria;\n const passes = evaluateCriteria(criteria, record, logger);\n if (!passes) return;\n if (await hasPendingRequest(engine, objectName, recordId)) return;\n // Guard: if the record's mirror status field is already a terminal\n // state (approved / rejected / recalled), do NOT auto-submit again —\n // otherwise every post-finalize edit would loop a fresh approval.\n const statusField = (process.definition as any)?.approvalStatusField;\n if (statusField) {\n const current = (record as any)?.[statusField];\n if (current === 'approved' || current === 'rejected' || current === 'recalled') return;\n }\n\n const submitterId = (ctx?.session?.userId ?? null) as string | null;\n const submitterOrg = (ctx?.session?.tenantId ?? ctx?.session?.organizationId ?? null) as string | null;\n await service.submit({\n object: objectName,\n recordId,\n processName: process.name,\n payload: record,\n submitterId,\n }, { ...SYSTEM_CTX, userId: submitterId ?? undefined, organizationId: submitterOrg ?? undefined, tenantId: submitterOrg ?? undefined } as any);\n\n logger?.info?.('[approvals] auto-submitted approval', {\n process: process.name,\n object: objectName,\n record: recordId,\n });\n } catch (err: any) {\n if (err?.code === 'DUPLICATE_REQUEST') return;\n logger?.warn?.('[approvals] auto-submit failed', {\n process: process.name,\n object: objectName,\n record: recordId,\n error: err?.message ?? String(err),\n });\n }\n}\n"],"mappings":"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","names":["import_audit","SYSTEM_CTX","id","row","fresh","SYSTEM_CTX"]}
|
package/dist/index.mjs
CHANGED
|
@@ -241,6 +241,7 @@ function rowFromRequest(row) {
|
|
|
241
241
|
id: String(row.id),
|
|
242
242
|
organization_id: row.organization_id ?? void 0,
|
|
243
243
|
process_name: String(row.process_name ?? ""),
|
|
244
|
+
process_hash: row.process_hash ?? void 0,
|
|
244
245
|
object_name: String(row.object_name ?? ""),
|
|
245
246
|
record_id: String(row.record_id ?? ""),
|
|
246
247
|
submitter_id: row.submitter_id ?? void 0,
|
|
@@ -275,6 +276,7 @@ var ApprovalService = class {
|
|
|
275
276
|
this.fetchImpl = opts.fetch;
|
|
276
277
|
this.webhookTimeoutMs = opts.webhookTimeoutMs;
|
|
277
278
|
this.onRegistryChange = opts.onRegistryChange;
|
|
279
|
+
this.metadataRepo = opts.metadataRepo;
|
|
278
280
|
}
|
|
279
281
|
/** Allow the plugin to attach a hook re-binding callback after construction. */
|
|
280
282
|
setRegistryChangeHandler(handler) {
|
|
@@ -443,6 +445,70 @@ var ApprovalService = class {
|
|
|
443
445
|
this.logger?.warn?.("[approvals] onRegistryChange handler failed", { error: err?.message });
|
|
444
446
|
}
|
|
445
447
|
}
|
|
448
|
+
/**
|
|
449
|
+
* Look up the HEAD checksum of an approval process from the metadata repo
|
|
450
|
+
* (ADR-0009). Returns null when no repo is wired, no metadata exists for
|
|
451
|
+
* the name, or the lookup fails — callers MUST treat null as "do not pin"
|
|
452
|
+
* and fall back to the projection table.
|
|
453
|
+
*/
|
|
454
|
+
async resolveProcessHash(processName, organizationId) {
|
|
455
|
+
if (!this.metadataRepo) return null;
|
|
456
|
+
if (!processName) return null;
|
|
457
|
+
const orgRef = { org: organizationId || "system", type: "approval", name: processName };
|
|
458
|
+
try {
|
|
459
|
+
const head = await this.metadataRepo.get(orgRef);
|
|
460
|
+
return head?.hash ?? null;
|
|
461
|
+
} catch (err) {
|
|
462
|
+
this.logger?.debug?.("[approvals] metadataRepo.get failed", { name: processName, error: err?.message });
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Resolve the approval process for an in-flight request, honouring
|
|
468
|
+
* ADR-0009 execution pinning when a `process_hash` is recorded.
|
|
469
|
+
*
|
|
470
|
+
* Resolution order:
|
|
471
|
+
* 1. If `req.process_hash` AND `metadataRepo` are set, try
|
|
472
|
+
* `getByHash` — return a row whose `definition` is the pinned body.
|
|
473
|
+
* 2. Otherwise (or on lookup failure) fall back to the current
|
|
474
|
+
* projection via `getProcess(req.process_name)`.
|
|
475
|
+
*/
|
|
476
|
+
async loadProcessForRequest(req, context) {
|
|
477
|
+
const hash = req.process_hash;
|
|
478
|
+
if (hash && this.metadataRepo) {
|
|
479
|
+
const orgId = req.organization_id ?? null;
|
|
480
|
+
const orgRef = { org: orgId || "system", type: "approval", name: req.process_name };
|
|
481
|
+
try {
|
|
482
|
+
const pinned = await this.metadataRepo.getByHash(orgRef, hash);
|
|
483
|
+
if (pinned?.body) {
|
|
484
|
+
const current = await this.getProcess(req.process_name, context);
|
|
485
|
+
const body = pinned.body;
|
|
486
|
+
return {
|
|
487
|
+
id: current?.id ?? `pinned_${hash.slice(7, 19)}`,
|
|
488
|
+
name: req.process_name,
|
|
489
|
+
label: body.label ?? current?.label ?? req.process_name,
|
|
490
|
+
object_name: req.object_name,
|
|
491
|
+
description: body.description ?? current?.description,
|
|
492
|
+
active: current?.active ?? true,
|
|
493
|
+
definition: body,
|
|
494
|
+
created_at: current?.created_at,
|
|
495
|
+
updated_at: current?.updated_at
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
this.logger?.warn?.("[approvals] pinned process body not found; falling back to current", {
|
|
499
|
+
request: req.id,
|
|
500
|
+
process: req.process_name,
|
|
501
|
+
hash
|
|
502
|
+
});
|
|
503
|
+
} catch (err) {
|
|
504
|
+
this.logger?.warn?.("[approvals] getByHash failed; falling back to current", {
|
|
505
|
+
request: req.id,
|
|
506
|
+
error: err?.message
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return this.getProcess(req.process_name, context);
|
|
511
|
+
}
|
|
446
512
|
/** Mirror request status onto `process.approvalStatusField` if configured. */
|
|
447
513
|
async syncStatusField(process, request) {
|
|
448
514
|
const field = process.definition?.approvalStatusField;
|
|
@@ -583,9 +649,11 @@ var ApprovalService = class {
|
|
|
583
649
|
const approvers = await this.expandApprovers(step0, input.payload, ctxOrg);
|
|
584
650
|
const now = this.clock.now().toISOString();
|
|
585
651
|
const id = uid("areq");
|
|
652
|
+
const processHash = await this.resolveProcessHash(process.name, ctxOrg);
|
|
586
653
|
const row = {
|
|
587
654
|
id,
|
|
588
655
|
process_name: process.name,
|
|
656
|
+
process_hash: processHash,
|
|
589
657
|
object_name: input.object,
|
|
590
658
|
record_id: input.recordId,
|
|
591
659
|
submitter_id: input.submitterId ?? context.userId ?? null,
|
|
@@ -669,7 +737,7 @@ var ApprovalService = class {
|
|
|
669
737
|
if (!context.isSystem && !(req.pending_approvers ?? []).includes(input.actorId)) {
|
|
670
738
|
throw new Error(`FORBIDDEN: actor '${input.actorId}' is not a pending approver`);
|
|
671
739
|
}
|
|
672
|
-
const process = await this.
|
|
740
|
+
const process = await this.loadProcessForRequest(req, context);
|
|
673
741
|
if (!process) throw new Error(`PROCESS_NOT_FOUND: ${req.process_name}`);
|
|
674
742
|
const steps = process.definition?.steps ?? [];
|
|
675
743
|
const stepIndex = req.current_step_index ?? 0;
|
|
@@ -741,7 +809,7 @@ var ApprovalService = class {
|
|
|
741
809
|
if (!context.isSystem && !(req.pending_approvers ?? []).includes(input.actorId)) {
|
|
742
810
|
throw new Error(`FORBIDDEN: actor '${input.actorId}' is not a pending approver`);
|
|
743
811
|
}
|
|
744
|
-
const process = await this.
|
|
812
|
+
const process = await this.loadProcessForRequest(req, context);
|
|
745
813
|
if (!process) throw new Error(`PROCESS_NOT_FOUND: ${req.process_name}`);
|
|
746
814
|
const steps = process.definition?.steps ?? [];
|
|
747
815
|
const stepIndex = req.current_step_index ?? 0;
|
|
@@ -813,7 +881,7 @@ var ApprovalService = class {
|
|
|
813
881
|
updated_at: now
|
|
814
882
|
}, { context: SYSTEM_CTX2 });
|
|
815
883
|
const fresh = await this.getRequest(req.id, context);
|
|
816
|
-
const process = await this.
|
|
884
|
+
const process = await this.loadProcessForRequest(req, context);
|
|
817
885
|
if (process) {
|
|
818
886
|
await this.syncStatusField(process, fresh);
|
|
819
887
|
await this.runActions(process.definition?.onRecall, "recall", process, fresh, void 0, input.actorId, input.comment);
|
|
@@ -1027,10 +1095,20 @@ var ApprovalsServicePlugin = class {
|
|
|
1027
1095
|
}
|
|
1028
1096
|
this.engine = engine;
|
|
1029
1097
|
this.logger = ctx.logger;
|
|
1098
|
+
let metadataRepo;
|
|
1099
|
+
try {
|
|
1100
|
+
const meta = ctx.getService("metadata");
|
|
1101
|
+
metadataRepo = meta?.getRepository?.();
|
|
1102
|
+
} catch {
|
|
1103
|
+
}
|
|
1030
1104
|
this.service = new ApprovalService({
|
|
1031
1105
|
engine,
|
|
1032
|
-
logger: ctx.logger
|
|
1106
|
+
logger: ctx.logger,
|
|
1107
|
+
metadataRepo
|
|
1033
1108
|
});
|
|
1109
|
+
if (metadataRepo) {
|
|
1110
|
+
ctx.logger.info("ApprovalsServicePlugin: execution pinning enabled (ADR-0009)");
|
|
1111
|
+
}
|
|
1034
1112
|
if (!this.options.disableAutoHooks) {
|
|
1035
1113
|
this.service.setRegistryChangeHandler(() => this.rebindHooks());
|
|
1036
1114
|
const hookOn = ctx.hook ?? ctx.on;
|