{"record":{"id":"34bd7720c254c346","repo":"siyuan-note/siyuan","slug":"invalid-plugin-service-protocol-variants","errorCode":null,"errorMessage":"invalid plugin service protocol variants","messagePattern":"invalid plugin service protocol variants","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/apicontract/plugin_service_protocol.go","lineNumber":128,"sourceCode":"\t}\n\tif err := validatePluginServiceStatus(response.pluginServiceMode, response.httpStatus); err != nil {\n\t\tpanic(err)\n\t}\n\treturn response.httpStatus\n}\n\nfunc validatePluginServiceDefinition(definition Definition) error {\n\tif (definition.Output == PluginServiceOutput) != (definition.PluginService != nil) {\n\t\treturn fmt.Errorf(\"plugin service output requires a protocol declaration\")\n\t}\n\tif definition.PluginService == nil {\n\t\treturn nil\n\t}\n\tif definition.Data != reflect.TypeFor[PluginServiceContent]() || definition.SSE != nil || definition.Proxy != nil || definition.WebSocket != nil || definition.DataOnError || definition.ErrorStatus != 0 {\n\t\treturn fmt.Errorf(\"invalid plugin service response options\")\n\t}\n\tif !reflect.DeepEqual(definition.PluginService, PluginServiceOptions().PluginService) {\n\t\treturn fmt.Errorf(\"invalid plugin service protocol variants\")\n\t}\n\treturn nil\n}\n\nfunc validatePluginServiceStatus(mode PluginServiceMode, status int) error {\n\tif status < 100 || status > 999 {\n\t\treturn fmt.Errorf(\"invalid plugin service HTTP status: %d\", status)\n\t}\n\tknown := false\n\tfor _, variant := range PluginServiceOptions().PluginService.Variants {\n\t\tif variant.Mode == mode {\n\t\t\tknown = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif !known {\n\t\treturn fmt.Errorf(\"unknown plugin service mode: %s\", mode)\n\t}","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/apicontract/plugin_service_protocol.go#L110-L146","documentation":"The PluginService protocol declaration attached to a plugin service endpoint must be byte-for-byte equal (via reflect.DeepEqual) to the canonical definition returned by PluginServiceOptions(). Because the variant list, admission statuses, WebSocket frame names, and SSE schema define the wire contract published in the bundle, arbitrary custom definitions are rejected so the declared protocol stays consistent across bundles.","triggerScenarios":"BuildBundle validates a plugin-service endpoint whose PluginService field was hand-constructed or modified (extra/missing variant, changed AdmissionStatuses, altered SSEEvent schema) instead of copied from PluginServiceOptions().","commonSituations":"Trimming the variants list to 'only the modes I use', reordering variants (DeepEqual is order-sensitive for slices), bumping the canonical options in a library update while a stale cached definition is embedded, or hand-writing the definition from the JSON schema.","solutions":["Set definition.PluginService directly from PluginServiceOptions(): &PluginServiceOptions().PluginService","Remove any hand edits (extra variants, reordered entries, changed statuses) so DeepEqual succeeds","After a library upgrade, re-copy from PluginServiceOptions() rather than keeping a stored definition"],"exampleFix":"// before\nsvc := apicontract.PluginServiceOptions().PluginService\nsvc.Variants = svc.Variants[:3] // keep only JSON modes\ndefinition.PluginService = &svc\n// after\ndefinition.PluginService = &apicontract.PluginServiceOptions().PluginService","handlingStrategy":"validation","validationCode":"if definition.PluginService != nil &&\n    !reflect.DeepEqual(*definition.PluginService, apicontract.PluginServiceOptions().PluginService) {\n    return errors.New(\"plugin service definition must equal PluginServiceOptions() exactly\")\n}","typeGuard":null,"tryCatchPattern":"if err := apicontract.BuildBundle(endpoints); err != nil {\n    if strings.Contains(err.Error(), \"protocol variants\") { log.Fatalf(\"stale plugin service definition: %v\", err) }\n    return err\n}","preventionTips":["Always copy the declaration from PluginServiceOptions(); never trim or reorder variants","Re-copy the definition after every library upgrade","Compare against PluginServiceOptions() in a test to catch drift early"],"tags":["bundle-validation","schema-validation","plugin-service","deep-equal"],"backgroundTag":"schema-validation-failed","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}