{"record":{"id":"6f1941aefda3727c","repo":"siyuan-note/siyuan","slug":"unregistered-api-contract-s-s","errorCode":null,"errorMessage":"unregistered API contract: %s %s","messagePattern":"unregistered API contract: (.+?) (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/apicontract/broadcast_protocol.go","lineNumber":76,"sourceCode":"\tfor _, allowed := range schema.Raw.UpgradeErrorStatuses {\n\t\tif status == allowed && media == \"text/plain\" {\n\t\t\treturn true, nil\n\t\t}\n\t}\n\treturn false, nil\n}\n\n// ValidateRawSSEEvent 校验事件协议；事件数据是原始字节，不进行 JSON 解码或 Base64 转码。\nfunc (b *Bundle) ValidateRawSSEEvent(method, path, name, id string, retry uint, payload []byte) error {\n\tfor _, endpoint := range b.Endpoints {\n\t\tif endpoint.Method == method && endpoint.Path == path {\n\t\t\tif endpoint.SSE == nil || endpoint.SSE.Raw == nil {\n\t\t\t\treturn fmt.Errorf(\"endpoint does not declare raw SSE events\")\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn fmt.Errorf(\"unregistered API contract: %s %s\", method, path)\n}\n\nfunc (b *Bundle) ValidateRawWebSocketFrame(method, path string, incoming bool, frameType int, payload []byte) error {\n\tfor _, endpoint := range b.Endpoints {\n\t\tif endpoint.Method == method && endpoint.Path == path {\n\t\t\tif endpoint.WebSocket == nil || endpoint.WebSocket.Raw == nil {\n\t\t\t\treturn fmt.Errorf(\"endpoint does not declare raw WebSocket frames\")\n\t\t\t}\n\t\t\tfor _, allowed := range endpoint.WebSocket.Raw.Frames {\n\t\t\t\tif frameType == allowed {\n\t\t\t\t\tif frameType >= 8 && len(payload) > 125 {\n\t\t\t\t\t\treturn fmt.Errorf(\"WebSocket control frame exceeds 125 bytes\")\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"undeclared raw WebSocket frame: %d\", frameType)\n\t\t}","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/apicontract/broadcast_protocol.go#L58-L94","documentation":"ValidateRawSSEEvent iterates the bundle's registered endpoints and, when no endpoint matches the given method and path, reports the pair as an unregistered API contract. This is the not-found path of the raw SSE validation lookup and echoes the method and path that failed to match.","triggerScenarios":"Calling bundle.ValidateRawSSEEvent with a method or path that is not in b.Endpoints — misspelled path, wrong HTTP method (POST vs GET), or the endpoint was never added to the bundle.","commonSituations":"Path typos or missing trailing/leading slash; endpoint renamed or removed in a contract update while callers kept the old route; tests written before the endpoint was registered; forgetting BuildBundle registration.","solutions":["Register the endpoint in the contract and rebuild the bundle","Correct the method and path strings to the registered values (check exact spelling and slashes)","List b.Endpoints or the contract definition to confirm the route exists","Update stale test/client code after a route rename"],"exampleFix":"// before\nbundle.ValidateRawSSEEvent(\"POST\", \"/api/blocks/raw-sse\", ...)\n// after\nbundle.ValidateRawSSEEvent(\"GET\", \"/api/blocks/raw-sse\", ...)","handlingStrategy":"validation","validationCode":"const registered = bundle.Endpoints.some(e => e.Method === method && e.Path === path);\nif (!registered) throw new Error(\"route not in bundle: \" + method + \" \" + path);","typeGuard":null,"tryCatchPattern":"try { bundle.ValidateRawSSEEvent(method, path, ...); } catch (e) { if (String(e).startsWith(\"unregistered API contract\")) { await rebuildBundle(); retry once; } else throw e; }","preventionTips":["Derive method/path constants from the contract definition, not literals","Rebuild the bundle after any route rename or addition","Add a startup check that all called routes are registered","Watch for slash/case mismatches in paths"],"tags":["api-contract","routing","unregistered-endpoint"],"backgroundTag":"resource-not-found","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"}