2026-05-10-fix-issues-272-273-274
Fix Issues #272 / #273 / #274 — XDR doc/OpenAPI/RBAC alignment
Date: 2026-05-10 Status: shipped Slug: issues-272-273-274-xdr-doc-fixes Branch: feature/issues-272-273-274-xdr-doc-fixes Closes: #272, #273, #274
Summary
Fixes the three issues filed by Perplexity's audit of PR #271. The runtime code is correct — every fix landed in this PR is on the documentation / OpenAPI / code-comment side of the contract.
Issue #272 (HIGH) — risk-clear route shape
Bug I introduced: docs/openapi.yaml documented POST /v1/admin/xdr/risk-clear as a separate path. That route does not exist.
Actual handler: DELETE /v1/admin/xdr/risk-push with the tenant_id + agent_id in the request body. SDKs (pushXdrRisk, clearXdrRisk in both TS and Python) already use _deleteWithBody("/v1/admin/xdr/risk-push", ...) — only the OpenAPI + docs prose had the wrong shape.
Fix: Replaced the fictional risk-clear operation with a delete entry under the existing /v1/admin/xdr/risk-push path. Updated the curl example in xdr-pipeline.mdx and observability.mdx to use curl -X DELETE with the body.
Issue #273 (HIGH) — schema drift on push/clear
Bugs:
sourcewas optional in OpenAPI; it is required inXdrRiskPushSchema
(z.string().min(1).max(128)).
tenant_idandagent_idhad no UUID validation (Zod uses.uuid()).ttl_secondshad no max constraint (Zod uses.max(2_592_000)= 30d).- I invented an
indicatorfield that doesn't exist. - Response shapes did not match handler
c.json(...)returns.
Fix:
sourcemarked required;minLength: 1,maxLength: 128.tenant_idandagent_idset toformat: uuid.ttl_secondscapped at2592000(nodefault, since the handler uses
?? 86400 rather than a Zod default — documented explicitly).
- Removed the fictional
indicatorfield. - Push response:
{ ok, agent_id, tenant_id, risk_score, source, ttl_seconds, set_at?, dry_run?, would_set? }
matching c.json({ ok: true, agent_id, tenant_id, risk_score, ... }).
- Clear response:
{ ok, agent_id, tenant_id, dry_run?, would_delete? }
matching c.json({ ok: true, agent_id, tenant_id }).
- Added
403 feature_disabledresponse on both operations (handler returns
this when XDR_BIDIRECTIONAL_ENABLED != "1").
- Added
?dry_run=truequery parameter on both.
Issue #274 (MEDIUM) — destination + RBAC + feature-flag drift
Bugs:
- OpenAPI create/update bodies omitted
event_typesandbatch, both
accepted by CreateDestinationSchema / UpdateDestinationSchema.
XdrDestinationresponse schema omitted six fields:event_types,
batch, and the four stats.* fields (last_delivered_at, delivery_count, error_count, last_error) — see formatDestination in src/api/routes/xdr.ts.
XDR_BIDIRECTIONAL_ENABLEDenv-var feature flag undocumented.?dry_run=truequery parameter undocumented.- Doc prose claimed permissions
xdr.risk.push/xdr.risk.clearexist;
RBAC actually requires platform.admin (per src/api/middleware/rbac.ts:379-380).
Fix:
- Added
event_typesandbatchto create + update OpenAPI bodies. - Expanded
XdrDestinationschema with the six missing fields and the
new DestinationBatchConfig component.
- Documented the feature flag in
xdr-pipeline.mdx,observability.mdx,
and the OpenAPI descriptions for risk-push / risk-clear.
- Documented
?dry_run=truesemantics in all three places. - Replaced
xdr.risk.push/xdr.risk.clearpermission references with
platform.admin everywhere.
Cortex auth — code/comment correction
Perplexity flagged that xdr-pipeline.mdx said SHA256(api_key + nonce + ts) while the prior cortex.ts file header comment claimed "HMAC-SHA256". The implementation in cortex.ts:56 uses createHash("sha256") — a plain SHA256 hash, not an HMAC.
The docs were already accurate (matching the implementation). The code's file-header comment was wrong. Fixed:
src/observability/xdr/adapters/cortex.ts:7— comment now reads "per-request
SHA256 hash signature ... NOT an HMAC."
docs/concepts/xdr-pipeline.mdx— adapter table column reads "Per-request
SHA256 hash" instead of "HMAC-SHA256"; the explanatory paragraph below now says "per-request hashing scheme" and explicitly notes "not an HMAC."
Files
Modified
docs/openapi.yaml—risk-pushPOST schema (source required, UUID, TTL cap, dry_run, 403); newrisk-pushDELETE entry; deletedrisk-clearoperation; XdrDestination + create/update bodies expanded; newDestinationBatchConfigcomponentdocs/concepts/xdr-pipeline.mdx— risk-push/clear curl examples + auth/feature-flag/dry-run notes; Cortex adapter row + paragraph correcteddocs/api-reference/observability.mdx— same risk-push/clear corrections + auth/feature-flag/dry-run notessrc/observability/xdr/adapters/cortex.ts— file header comment corrected (SHA256, not HMAC-SHA256)site/public/openapi.yaml— synced from docs/openapi.yamlsite/public/llms-full.txt+site/public/routes.json— auto-regenerated bypnpm buildsrc/api/static-assets.generated.ts— auto-regenerated (runtime/openapi.jsonnow serves the corrected spec)
New
docs/ship-log/2026-05-10-fix-issues-272-273-274.md— this file
Verification
python3 -c "import yaml; yaml.safe_load(open('docs/openapi.yaml'))"— parses- Programmatic checks against the parsed YAML:
risk-pushhas[post, delete]methods ✅risk-pushPOST body required =[tenant_id, agent_id, risk_score, source]✅XdrDestinationhasstats,event_types,batchproperties ✅risk-clearpath no longer present ✅DestinationBatchConfigcomponent present ✅pnpm tsgo— exit 0pnpm exec oxfmt --check/oxlint— cleanpnpm test:fast— 7755/0 (no logic change, only doc + comment edits)
Open follow-up — automation target
The user's audit closing point: generate or parity-test OpenAPI from Hono route registrations + Zod schemas. This PR fixes the symptoms; the structural fix is a CI gate that compares the two surfaces. Sketch:
- Walk
src/api/routes/*/.ts, extract everyapp.{get,post,put,delete}(path, ...). - For each, find the
zBody(SomeSchema)middleware and reflect the schema. - Compare against
docs/openapi.yaml:
- Missing handler for an OpenAPI path → fail
- Missing OpenAPI entry for a registered handler → fail
- Required-field set mismatch (Zod vs OpenAPI) → fail
- Method set mismatch → fail
- RBAC permission referenced in docs but not in
RBAC_RULES→ fail
This is filed as a task for a follow-up PR (not in scope here).
Lockstep
- TS SDK / Python SDK — no surface change (already matched code in #271)
- MCP tools — no surface change
- Ship log — this file