{"record":{"id":"32b8cc321c058d56","repo":"agalwood/Motrix","slug":"pluginruntimefault-32b8cc","errorCode":"PluginRuntimeFault","errorMessage":"AuditRoleCannotMutate","messagePattern":"AuditRoleCannotMutate","errorType":"exception","errorClass":"AppError","httpStatus":null,"severity":"error","filePath":"src/core/plugin/hooks/ctx-update.ts","lineNumber":48,"sourceCode":"export const FinalizePatchSchema = z\n  .object({\n    filePath: z.string(),\n  })\n  .strict()\n\nexport interface ValidateOptions {\n  permissions: ReadonlySet<string>\n  role: RoleBand\n  hook: 'beforeCreate' | 'beforeFinalize'\n  saveDir: string\n}\n\nexport function validateHttpPatch(\n  patch: unknown,\n  opts: ValidateOptions\n): z.infer<typeof HttpPatchSchema> {\n  if (opts.role === 'audit')\n    throw new AppError(ErrorCode.PluginRuntimeFault, 'AuditRoleCannotMutate')\n  const parsed = HttpPatchSchema.safeParse(patch)\n  if (!parsed.success)\n    throw new AppError(\n      ErrorCode.PluginRuntimeFault,\n      `CtxUpdateInvalid: ${parsed.error.issues[0]?.message ?? 'invalid patch'}`\n    )\n  if (\n    parsed.data.filename !== undefined &&\n    !opts.permissions.has('fs.task.write')\n  )\n    throw new AppError(\n      ErrorCode.PluginRuntimeFault,\n      'CtxUpdateInvalid: filename requires fs.task.write permission'\n    )\n  return parsed.data\n}\n\nexport function validateFinalizePatch(","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/agalwood/Motrix/blob/1a708ee57746c434e2c67a44bbf0906a976afea4/src/core/plugin/hooks/ctx-update.ts#L30-L66","documentation":"Thrown by validateHttpPatch (and validateFinalizePatch) when opts.role === 'audit'. The audit role is read-only by design: a plugin running in the audit role band must never mutate task context, so any patch submission is rejected before schema parsing even begins. Classified PluginRuntimeFault.","triggerScenarios":"A plugin whose current role band is 'audit' calls the context-update hook (beforeCreate/beforeFinalize) with an HTTP patch. The very first check in validateHttpPatch inspects opts.role and throws immediately.","commonSituations":"A plugin designed for a read/write role is mistakenly assigned or escalated to the audit role; a hook dispatcher routes a mutation request from an audit-role plugin; or a developer tests a mutation path against a plugin configured as auditor.","solutions":["Confirm the plugin's intended role — audit-role plugins must not issue context mutations; route the action through a plugin running in a writable role band instead.","Gate the mutation call on role !== 'audit' before submitting the patch.","If the role assignment is wrong, correct the role-band configuration for that plugin/hook.","For audit-role plugins, restrict their API surface to read-only context access."],"exampleFix":"// before\nif (role === 'audit') validateHttpPatch(patch, { ...opts, role })\n\n// after\nif (role !== 'audit') validateHttpPatch(patch, { ...opts, role })\nelse read-only-context-access()","handlingStrategy":"validation","validationCode":"if (opts.role === 'audit') { /* read-only; do not call validateHttpPatch with a mutation */ }","typeGuard":"function isWritableRole(role: RoleBand): boolean { return role !== 'audit' }","tryCatchPattern":"try { validateHttpPatch(patch, opts) }\ncatch (e) { if (e.message === 'AuditRoleCannotMutate') { /* route to a writable-role plugin */ } else throw e }","preventionTips":["Never assign mutation duties to audit-role plugins.","Gate mutation calls on role !== 'audit'.","Restrict audit-role plugin API surfaces to read-only context access."],"tags":["plugin","hooks","access-control","rbac"],"backgroundTag":null,"analyzedSha":"1a708ee57746c434e2c67a44bbf0906a976afea4","analyzedAt":"2026-08-12T16:18:09.346Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}