{"record":{"id":"5d008455d611f416","repo":"rustfs/rustfs","slug":"s3-hook-extension-extension-id-is-missing-capabi","errorCode":null,"errorMessage":"s3 hook extension {extension_id} is missing capability {capability}","messagePattern":"s3 hook extension (.+?) is missing capability (.+?)","errorType":"exception","errorClass":"S3HookRegistryError","httpStatus":null,"severity":"error","filePath":"crates/targets/src/runtime/s3_hooks.rs","lineNumber":31,"sourceCode":"// limitations under the License.\n\nuse std::collections::BTreeMap;\n\nuse rustfs_extension_schema::{\n    ExtensionContractError, ExtensionKind, ExtensionSchema, S3_POST_AUTH_HOOK_CAPABILITY, S3HookContract, S3HookPoint,\n    validate_s3_hook_contract,\n};\nuse thiserror::Error;\n\n#[derive(Debug, Error, PartialEq, Eq)]\npub enum S3HookRegistryError {\n    #[error(transparent)]\n    InvalidContract(#[from] ExtensionContractError),\n\n    #[error(\"extension {extension_id} is {kind:?}, not an S3 hook\")]\n    UnsupportedExtensionKind { extension_id: String, kind: ExtensionKind },\n\n    #[error(\"s3 hook extension {extension_id} is missing capability {capability}\")]\n    MissingCapability { extension_id: String, capability: &'static str },\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct S3HookRegistration {\n    pub extension_id: String,\n    pub hook_point: S3HookPoint,\n}\n\n#[derive(Debug, Default, Clone, PartialEq, Eq)]\npub struct S3HookRegistry {\n    registrations: BTreeMap<S3HookPoint, Vec<S3HookRegistration>>,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub struct S3HookContext<'a> {\n    pub authenticated_principal: &'a str,\n    pub bucket: &'a str,","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/rustfs/rustfs/blob/35af688cd9d41b4346fbe27dcf7250ba72046c1f/crates/targets/src/runtime/s3_hooks.rs#L13-L49","documentation":"Returned by S3HookRegistry::register_schema (crates/targets/src/runtime/s3_hooks.rs) when the schema kind is S3Hook but its capabilities list does not contain S3_POST_AUTH_HOOK_CAPABILITY (\"s3.hook.post_auth.v1\"). Only schemas declaring this exact capability may register post-auth hook points.","triggerScenarios":"register_schema(&schema, &contract) with kind == ExtensionKind::S3Hook but capabilities missing the exact string s3.hook.post_auth.v1 - typically a hand-written capability string with a typo or wrong version suffix.","commonSituations":"External hook plugin manifests authored by hand; capability renamed or version-bumped in rustfs-extension-schema while the manifest still uses the old string; capabilities list left empty when converting a builtin example into an external plugin.","solutions":["Add the capability string s3.hook.post_auth.v1 to the schema's capabilities list","Build it from the S3_POST_AUTH_HOOK_CAPABILITY constant instead of a literal","Verify the manifest against the current contract version in rustfs-extension-schema"],"exampleFix":"// before\ncapabilities: vec![hook_cap.into()], // hook_cap == s3.hook.post_auth\n\n// after\nuse rustfs_extension_schema::S3_POST_AUTH_HOOK_CAPABILITY;\ncapabilities: vec![S3_POST_AUTH_HOOK_CAPABILITY.to_string()],","handlingStrategy":"validation","validationCode":"use rustfs_extension_schema::{S3_POST_AUTH_HOOK_CAPABILITY, ExtensionSchema};\n\nfn declares_post_auth_hook(schema: &ExtensionSchema) -> bool {\n    schema.capabilities.iter().any(|c| c.as_str() == S3_POST_AUTH_HOOK_CAPABILITY)\n}\n\nif !declares_post_auth_hook(&schema) {\n    return Err(format!(\"schema {} lacks the S3 post-auth hook capability\", schema.extension_id));\n}","typeGuard":"fn declares_post_auth_hook(schema: &ExtensionSchema) -> bool {\n    schema.capabilities.iter().any(|c| c.as_str() == S3_POST_AUTH_HOOK_CAPABILITY)\n}","tryCatchPattern":"match s3_hooks.register_schema(&schema, &contract) {\n    Err(S3HookRegistryError::MissingCapability { extension_id, capability }) => {\n        tracing::warn!(event = \"extension.register\", result = \"missing_capability\", extension_id = %extension_id, capability, \"manifest must declare {capability}\");\n    }\n    other => other?,\n}","preventionTips":["Build capability lists from S3_POST_AUTH_HOOK_CAPABILITY, not literals","Validate hook manifests for the exact capability string before the server starts registering them","Add a regression test: S3Hook-kind schema without the capability is rejected"],"tags":["rust","extension-registry","capability-declaration","s3-hooks"],"backgroundTag":"missing-capability-declaration","analyzedSha":"35af688cd9d41b4346fbe27dcf7250ba72046c1f","analyzedAt":"2026-08-20T21:57:04.799Z","contentChangedAt":"2026-08-20T21:57:04.799Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}