Audit Hardening Implementation Plan (2026-07-07)
For agentic workers: executing-plans / TDD. Spec:
docs/superpowers/specs/2026-07-07-audit-hardening-design.md.
Order: T1 embedder headers → T2 403 public → T3 staff degradation → T4 per-turn trace id → T5 verify+docs.
Mirrors (internal-agent ↔ public-agent) stay byte-identical. TDD each task.
T1 — Embedder tenant headers (Python ×2)
Files: dash/embedder.py, tests/test_embedder.py (extend, file-path loader).
- [ ] RED: gateway branch embedder carries X-Tenant-Id: ahu-chatbot + X-Priority: interactive on client.default_headers; legacy branch has no gateway headers
- [ ] GREEN: client_params={"default_headers": gateway_default_headers("interactive")} (import via file-path-safe from dash.gateway import …; module-load in tests mirrors test_gateway.py)
- [ ] Mirror + commit
T2 — 403 degradation, public (TS)
Files: llm/client.ts, orchestrate.ts, tests/orchestrator/llm-client.test.ts, orchestrate.test.ts.
- [ ] RED: 403 body {"error":{"code":"EXTERNAL_UPSTREAM_FORBIDDEN"}} → LlmHttpError.code; orchestrate 403 → SSE error "Layanan AI sedang tidak tersedia untuk permintaan ini.", not orchestrator failure; ComposeUpstreamError(403) same
- [ ] GREEN: parse code in post() (json try/catch on the body text), extend catch branches
- [ ] Commit
T3 — Staff-facing friendly degradation (TS, shared package)
Files: packages/streams/src/friendly-errors.ts (new), native-provider.ts, packages/streams/tests/friendly-errors.test.ts (new), native-provider.test.ts (extend if it covers error mapping).
- [ ] RED: mapping fn — 429/"Too Many Requests"/503/"Service Unavailable"/"rate limit" → busy copy; 403/EXTERNAL_UPSTREAM_FORBIDDEN → unavailable copy; unrelated → null. NativeProvider: RunError with 429-shaped message emits friendly copy; non-ok HTTP 503 emits friendly copy
- [ ] GREEN: implement + wire both sites; keep raw in console.error; document agno 2.4.7 constraints (no X-Queue-Events opt-in; openai SDK bounded retry w/ Retry-After)
- [ ] Commit
T4 — Per-turn X-Request-Id (Python ×2 + TS)
Files: dash/gateway.py (+ContextVar, GatewayHeaders, RequestIdMiddleware), app/main.py (add_middleware), tests/test_gateway.py (extend), apps/public-web/src/lib/orchestrator/tools/data.ts, orchestrate.ts (pass turn id), tests/orchestrator/data-tool.test.ts, packages/streams/src/native-provider.ts (+header), native-provider.test.ts.
- [ ] RED (py): dict(gateway_default_headers()) has no X-Request-Id; with request_id_var.set("r-1") it does; real openai _build_request picks up A→B→absent across contextvar changes; ASGI middleware sets var from header (fake ASGI app)
- [ ] GREEN (py): implement; keep existing test expectations via dict(...)
- [ ] RED (ts): DataTool constructed with requestId sends X-Request-Id on the run POST; orchestrate passes ${sessionId}-t${turn}; NativeProvider sends X-Request-Id: ${sessionId}-t${history.length}
- [ ] GREEN (ts): implement
- [ ] Mirror + commit
T5 — Verify + docs
- [ ]
corepack pnpm -r typecheck && -r test && node scripts/check-conventions.mjs; agent DB-free pytest both apps; mirrors diff-empty - [ ] Completion notes (report: files changed, agno per-run-header finding = FEASIBLE via lazy mapping, interpretations); upload .md; commit