From e4b55a495573b640f224356d1b72ea8fb39af13a Mon Sep 17 00:00:00 2001 From: tomohiro takada <263370648+leagames0221-sys@users.noreply.github.com> Date: Tue, 26 May 2026 01:15:21 +0900 Subject: [PATCH] fix(readme): note /api/kb/budget is disabled-by-default in production MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The clickable link surfaces a structured 404 `{"code":"DISABLED"}` response in production by design (ADR-0046 § Trade-offs — production opacity defaults). Add an inline disclaimer so a reviewer clicking the link sees the expected response shape and links it back to the ADR rationale rather than reading it as a dead endpoint. Co-Authored-By: Claude Opus 4.7 (1M context) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d8e324f..4ca82ad 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ craftstack/ - **OpenAPI 3.1 contract** at [`apps/collab/src/openapi.ts`](apps/collab/src/openapi.ts). Browsable in-app at (server-rendered, inside the strict CSP, zero external CDN), served as raw JSON at . Hand-written so the spec **is** the contract ([ADR-0035](docs/adr/0035-hand-written-openapi-as-the-contract.md)). `pnpm --filter collab generate:api-types` emits a fully-typed `paths` interface into [`src/openapi-types.ts`](apps/collab/src/openapi-types.ts) via `openapi-typescript` - **Release hygiene** — human-readable [CHANGELOG.md](CHANGELOG.md) per Keep-a-Changelog, signed tags, GitHub Releases, and a **CycloneDX 1.5 SBOM** auto-generated and attached to every `v*` release (see [`.github/workflows/sbom.yml`](.github/workflows/sbom.yml)) for supply-chain inspection - **Undo / redo on card moves** — `Ctrl-Z` / `⌘-Z` reverses the last drag, `Ctrl-Shift-Z` / `⌘-Shift-Z` re-applies it. Bounded 25-entry LIFO stack, replays against the existing optimistic-lock-protected `/api/cards/:id/move` endpoint so concurrent-edit rejection behaves exactly like a fresh drag. Pure state-machine module (6 Vitest cases) in [ADR-0036](docs/adr/0036-move-undo-redo-client-only.md) -- **Cost safety by construction** — every service the project touches (Vercel, Neon, Gemini via AI Studio, Pusher, Resend, GitHub Actions, Upstash, Sentry) is on a free tier that **caps out to zero cost** rather than auto-scaling to the attacker's credit card. In-code defense-in-depth: per-IP + global daily/monthly budget on `/api/kb/ask` **and** `/api/kb/ingest` (Knowlex parity, see [ADR-0043](docs/adr/0043-knowlex-ops-cost-ci-eval.md)), per-user rate limits on authenticated reads, three-layer cap on invitation emails. The guarantee is enforced, not declared: [`scripts/check-free-tier-compliance.mjs`](scripts/check-free-tier-compliance.mjs) runs as a **PR-blocking `free-tier-compliance` gate** in [`ci.yml`](.github/workflows/ci.yml) that fails merges introducing paid-plan `vercel.json`, billable SDKs, or leaked secret patterns. A single-flag kill switch `EMERGENCY_STOP=1` short-circuits every write + AI endpoint on the next request (runbook §9); its counterpart observability endpoint [`/api/kb/budget`](https://craftstack-knowledge.vercel.app/api/kb/budget) mirrors `/api/kb/stats` to expose the current used/cap state. STRIDE threat model covers this attack shape explicitly as `C-01..C-06`. Decision record: [ADR-0046](docs/adr/0046-zero-cost-by-construction.md). Credit-card-free signup walk-through in [`docs/FREE_TIER_ONBOARDING.md`](docs/FREE_TIER_ONBOARDING.md); cost-attack threat model in [`COST_SAFETY.md`](COST_SAFETY.md) +- **Cost safety by construction** — every service the project touches (Vercel, Neon, Gemini via AI Studio, Pusher, Resend, GitHub Actions, Upstash, Sentry) is on a free tier that **caps out to zero cost** rather than auto-scaling to the attacker's credit card. In-code defense-in-depth: per-IP + global daily/monthly budget on `/api/kb/ask` **and** `/api/kb/ingest` (Knowlex parity, see [ADR-0043](docs/adr/0043-knowlex-ops-cost-ci-eval.md)), per-user rate limits on authenticated reads, three-layer cap on invitation emails. The guarantee is enforced, not declared: [`scripts/check-free-tier-compliance.mjs`](scripts/check-free-tier-compliance.mjs) runs as a **PR-blocking `free-tier-compliance` gate** in [`ci.yml`](.github/workflows/ci.yml) that fails merges introducing paid-plan `vercel.json`, billable SDKs, or leaked secret patterns. A single-flag kill switch `EMERGENCY_STOP=1` short-circuits every write + AI endpoint on the next request (runbook §9); its counterpart observability endpoint [`/api/kb/budget`](https://craftstack-knowledge.vercel.app/api/kb/budget) mirrors `/api/kb/stats` to expose the current used/cap state (disabled-by-default in production per ADR-0046 § Trade-offs; returns a structured 404 `{"code":"DISABLED"}` until `ENABLE_OBSERVABILITY_API=1` is set on the server). STRIDE threat model covers this attack shape explicitly as `C-01..C-06`. Decision record: [ADR-0046](docs/adr/0046-zero-cost-by-construction.md). Credit-card-free signup walk-through in [`docs/FREE_TIER_ONBOARDING.md`](docs/FREE_TIER_ONBOARDING.md); cost-attack threat model in [`COST_SAFETY.md`](COST_SAFETY.md) - **Error-capture pipeline with demo mode** — both apps boot `@sentry/nextjs` via Next's `instrumentation.ts` + `instrumentation-client.ts` hooks; server and browser errors, unhandled rejections, and every `error.tsx` boundary flow through a unified `lib/observability.ts` seam. When `SENTRY_DSN` is configured the captures ship upstream; when it's not, they land in an in-memory ring buffer surfaced at `/api/observability/captures`, so a reviewer can prove the pipeline works end-to-end **without signing up for Sentry**. Rationale in [ADR-0044](docs/adr/0044-knowlex-openapi-a11y-sentry-v0.4.0.md) (wiring) and [ADR-0045](docs/adr/0045-observability-demo-mode.md) (demo-mode dual-backend) - **Knowlex RAG regression stack** — `retrieve.integration.test.ts` exercises the real pgvector kNN path against a `pgvector/pgvector:pg16` service container in CI, asserting "returns every row when `k ≥ corpus size`" — the exact regression a misconfigured ivfflat(lists, probes) silently produces ([ADR-0041](docs/adr/0041-knowlex-ivfflat-to-hnsw.md) documents the production diagnosis). `scripts/bench-retrieve.ts` reports min / p50 / p95 / p99 / max latency over N=1000 / M=100 probes. `scripts/eval.ts` seeds a self-contained **10-doc / 30-question golden set v4** (21 OR-mode + 6 AND-mode + 3 adversarial) and scores substring-faithfulness + citation-coverage + refusal correctness against the live deploy — full measurement methodology in [ADR-0042](docs/adr/0042-knowlex-test-observability-stack.md) / [ADR-0043](docs/adr/0043-knowlex-ops-cost-ci-eval.md) / [ADR-0049 § 7th arc](docs/adr/0049-rag-eval-client-retry-contract.md) (substring-OR scoring + 12 expanded refusal markers) and [`docs/eval/README.md`](docs/eval/README.md) - **Live deploy smoke, scheduled** — [`.github/workflows/smoke.yml`](.github/workflows/smoke.yml) runs Playwright against both production URLs every 6 h (plus on `workflow_dispatch` and on main pushes after a 90-second Vercel-settle sleep). Knowlex smoke asserts `indexType === "hnsw"` so an accidental ivfflat rollback trips the workflow, not production users