updated skills
This commit is contained in:
1031
GUIDELINES.md
1031
GUIDELINES.md
File diff suppressed because it is too large
Load Diff
@@ -1,411 +0,0 @@
|
||||
# sp-stack Full-Stack Library Composer Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Add an `sp-stack` skill that turns a whole-app description into a complete `@suaveplan/*` library set, wiring, and starter code — and relocate the `docs/` tree into the plugin that owns it.
|
||||
|
||||
**Architecture:** Approach C (hybrid). A curated capability manifest (`capabilities.md`) covers the common app capabilities with turnkey, code-minimal picks; the existing `sp` docs-traversal is the long-tail fallback. Both skills live in `document-navigator` and share the relocated `docs/` tree, referenced via `${CLAUDE_PLUGIN_ROOT}/docs/`.
|
||||
|
||||
**Tech Stack:** Claude Code plugin skills (Markdown + YAML frontmatter). No build step, no test runner — verification is path-resolution and content checks via `grep` / `test -f`.
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-05-29-sp-stack-composer-design.md`
|
||||
|
||||
---
|
||||
|
||||
## Chunk 1: Relocate docs/ and rewrite path references
|
||||
|
||||
### Task 1: Move the docs tree into the plugin
|
||||
|
||||
**Files:**
|
||||
- Move: `docs/` (excluding `docs/superpowers/`) → `plugins/document-navigator/docs/`
|
||||
|
||||
> Note: `docs/superpowers/` holds these specs/plans and must STAY at the repo root.
|
||||
> Only the package-mirror categories move.
|
||||
|
||||
- [ ] **Step 1: Snapshot the pre-move reference set (verification baseline)**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
grep -rIl --exclude-dir=.git -e "suaveplan-skills/docs" -e "\.\./docs/" -e " docs/" \
|
||||
plugins GUIDELINES.md .claude-plugin
|
||||
```
|
||||
Expected: `plugins/document-navigator/skills/sp/SKILL.md` and `GUIDELINES.md` only. (These are rewritten in Tasks 2 and 4. If any OTHER file appears, stop and reassess — it is an unplanned reference into the docs tree.)
|
||||
|
||||
- [ ] **Step 2: Confirm what is under docs/ root**
|
||||
|
||||
Run: `ls docs/`
|
||||
Expected: the 18 package categories (`api-stack`, `commerce`, `communication`, `devtools`, `foundation`, `gaming`, `geospatial`, …) plus the root files `README.md` and `_intro.md`, and the `superpowers/` directory. The `git mv` loop in Step 3 moves everything except `superpowers/` (so `README.md` and `_intro.md` move too).
|
||||
|
||||
- [ ] **Step 3: Move every category (not superpowers/) with git mv**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
mkdir -p plugins/document-navigator/docs
|
||||
for entry in docs/*; do
|
||||
base="$(basename "$entry")"
|
||||
[ "$base" = "superpowers" ] && continue
|
||||
git mv "$entry" "plugins/document-navigator/docs/$base"
|
||||
done
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Verify the move landed and superpowers/ stayed**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
test -f plugins/document-navigator/docs/README.md && echo "DOCS-OK"
|
||||
test -f plugins/document-navigator/docs/api-stack/api/api-plugin-payment/README.md && echo "SEED-OK"
|
||||
test -d docs/superpowers && echo "SPECS-STAYED"
|
||||
test ! -e docs/api-stack && echo "ROOT-CLEAN"
|
||||
```
|
||||
Expected: `DOCS-OK`, `SEED-OK`, `SPECS-STAYED`, `ROOT-CLEAN` all print.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "Move docs/ package mirror into document-navigator plugin"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Rewrite docs path references in sp/SKILL.md
|
||||
|
||||
**Files:**
|
||||
- Modify: `plugins/document-navigator/skills/sp/SKILL.md`
|
||||
|
||||
The current file hard-codes `.claude/plugins/suaveplan-skills/docs/` in the Architecture table, File Structure block, and the two `grep` Patterns examples. Replace every occurrence with the portable `${CLAUDE_PLUGIN_ROOT}/docs/`.
|
||||
|
||||
- [ ] **Step 1: Write the failing check**
|
||||
|
||||
Run: `grep -c "suaveplan-skills/docs" plugins/document-navigator/skills/sp/SKILL.md`
|
||||
Expected (before fix): a non-zero count (the stale paths still present).
|
||||
|
||||
- [ ] **Step 2: Replace the docs-tree row in the Architecture table**
|
||||
|
||||
Change:
|
||||
```
|
||||
| Docs tree | `.claude/plugins/suaveplan-skills/docs/` (`.md`-only mirror of `packages/`, maintained externally) |
|
||||
```
|
||||
to:
|
||||
```
|
||||
| Docs tree | `${CLAUDE_PLUGIN_ROOT}/docs/` (`.md`-only mirror of `packages/`, maintained externally) |
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Replace the File Structure block**
|
||||
|
||||
Change the fenced block that begins `.claude/plugins/suaveplan-skills/` so its root is `${CLAUDE_PLUGIN_ROOT}/` and the tree reads:
|
||||
```
|
||||
${CLAUDE_PLUGIN_ROOT}/
|
||||
docs/ # mirrored .md tree (maintained externally)
|
||||
<category>/
|
||||
README.md
|
||||
<subcategory>/
|
||||
README.md
|
||||
<package>/
|
||||
README.md
|
||||
src/
|
||||
<module>.md
|
||||
skills/sp/
|
||||
SKILL.md # this file
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Replace the two grep Patterns examples**
|
||||
|
||||
Change:
|
||||
```bash
|
||||
grep -ri "sms" .claude/plugins/suaveplan-skills/docs/*/README.md
|
||||
grep -ri "twilio" .claude/plugins/suaveplan-skills/docs/
|
||||
```
|
||||
to:
|
||||
```bash
|
||||
grep -ri "sms" ${CLAUDE_PLUGIN_ROOT}/docs/*/README.md
|
||||
grep -ri "twilio" ${CLAUDE_PLUGIN_ROOT}/docs/
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Run the check to verify it passes**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
grep -c "suaveplan-skills/docs" plugins/document-navigator/skills/sp/SKILL.md
|
||||
grep -c "CLAUDE_PLUGIN_ROOT" plugins/document-navigator/skills/sp/SKILL.md
|
||||
```
|
||||
Expected: first command prints `0`; second prints a non-zero count.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add plugins/document-navigator/skills/sp/SKILL.md
|
||||
git commit -m "Point sp skill at \${CLAUDE_PLUGIN_ROOT}/docs"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Update the docs path in GUIDELINES.md
|
||||
|
||||
**Files:**
|
||||
- Modify: `GUIDELINES.md` (§7.2, the `docs/` tree description, ~line 148)
|
||||
|
||||
- [ ] **Step 1: Locate the stale reference**
|
||||
|
||||
Run: `grep -n "suaveplan-skills/docs" GUIDELINES.md`
|
||||
Expected: one hit in §7.2.
|
||||
|
||||
- [ ] **Step 2: Rewrite it to the new repo location**
|
||||
|
||||
Change `.claude/plugins/suaveplan-skills/docs/<category>/<subcategory>/<pkg>/` to `plugins/document-navigator/docs/<category>/<subcategory>/<pkg>/`.
|
||||
|
||||
- [ ] **Step 3: Verify**
|
||||
|
||||
Run: `grep -c "suaveplan-skills/docs" GUIDELINES.md`
|
||||
Expected: `0`.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add GUIDELINES.md
|
||||
git commit -m "Update docs tree path in GUIDELINES §7.2"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Chunk 2: The sp-stack skill and capability manifest
|
||||
|
||||
### Task 4: Author the capability manifest
|
||||
|
||||
**Files:**
|
||||
- Create: `plugins/document-navigator/skills/sp-stack/capabilities.md`
|
||||
|
||||
Each row maps a capability to its turnkey `primary` pick, supporting packages, the
|
||||
`example_doc` path (relative to `docs/`), and external prerequisites. All `example_doc`
|
||||
paths below were verified to exist under the relocated tree.
|
||||
|
||||
- [ ] **Step 1: Create capabilities.md with the v1 seed (15–20 capabilities)**
|
||||
|
||||
```markdown
|
||||
# Capability Manifest
|
||||
|
||||
Curated map of common app capabilities to turnkey `@suaveplan/*` packages. The
|
||||
`sp-stack` skill consults this first; capabilities not listed fall back to the
|
||||
`sp` docs-traversal. `example_doc` paths are relative to `${CLAUDE_PLUGIN_ROOT}/docs/`.
|
||||
|
||||
| capability | layer | primary | supporting | example_doc | external |
|
||||
|---|---|---|---|---|---|
|
||||
| api-host | backend | @suaveplan/api | — | api-stack/api/api/README.md | — |
|
||||
| auth | backend | @suaveplan/api-plugin-auth | @suaveplan/api-auth-better-auth | api-stack/api/api-plugin-auth/README.md | Better-Auth config |
|
||||
| multi-tenancy | backend | @suaveplan/api-plugin-tenant | @suaveplan/api-tenant | api-stack/api/api-plugin-tenant/README.md | — |
|
||||
| payments | backend | @suaveplan/api-plugin-payment | @suaveplan/payment | api-stack/api/api-plugin-payment/README.md | Stripe/Adyen/PayPal account + secret key |
|
||||
| email-campaigns | backend | @suaveplan/campaigns | @suaveplan/suppression, @suaveplan/delivery-tracking | communication/emails/campaigns/README.md | — |
|
||||
| transactional-email | backend | @suaveplan/emailer | @suaveplan/email-templates | communication/emails/emailer/README.md | SMTP creds or Gmail REST token |
|
||||
| file-upload | backend | @suaveplan/api-plugin-file-upload | — | api-stack/api/api-plugin-file-upload/README.md | storage backend |
|
||||
| search | backend | @suaveplan/api-plugin-search | — | api-stack/api/api-plugin-search/README.md | — |
|
||||
| realtime-sse | backend | @suaveplan/api-plugin-sse | @suaveplan/api-sse-bun | api-stack/api/api-plugin-sse/README.md | — |
|
||||
| rate-limiting | backend | @suaveplan/api-plugin-rate-limit | @suaveplan/rate-limiter | api-stack/api/api-plugin-rate-limit/README.md | — |
|
||||
| notifications | backend | @suaveplan/api-plugin-notifications | @suaveplan/notification-channel | api-stack/api/api-plugin-notifications/README.md | APNs key (push) |
|
||||
| webhooks | backend | @suaveplan/api-plugin-webhook | @suaveplan/webhook-client | api-stack/api/api-plugin-webhook/README.md | — |
|
||||
| booking | backend | @suaveplan/booking | — | commerce/booking/booking/README.md | — |
|
||||
| validation | foundation | @suaveplan/validation | — | foundation/data/validation/README.md | — |
|
||||
| pwa | frontend | @suaveplan/pwa | — | foundation/data/pwa/README.md | — |
|
||||
| client-data-hooks | frontend | @suaveplan/react-packer | @suaveplan/packer-browser | foundation/data/react-packer/README.md | React 19 |
|
||||
| cli | devtools | @suaveplan/cli | — | devtools/tooling/cli/README.md | — |
|
||||
| testing | devtools | @suaveplan/test-vite | — | devtools/tooling/test-vite/README.md | — |
|
||||
```
|
||||
|
||||
(`booking` doc path is verified present at `commerce/booking/booking/README.md`.)
|
||||
|
||||
- [ ] **Step 2: Verify every example_doc path resolves**
|
||||
|
||||
Filter to data rows by **content**, not line number (the preamble makes line offsets
|
||||
brittle): keep only lines starting with `|`, drop the header row (the `capability`
|
||||
column is `$2` because the leading `|` makes `$1` empty) and the `|---|` separator
|
||||
(`$6` is only dashes/spaces).
|
||||
|
||||
Run:
|
||||
```bash
|
||||
cd plugins/document-navigator
|
||||
awk -F'|' '/^\|/ && $2 !~ /capability/ && $6 !~ /^[- ]*$/ {gsub(/ /,"",$6); print $6}' \
|
||||
skills/sp-stack/capabilities.md \
|
||||
| grep -v '^$' | grep -v '^—$' \
|
||||
| while read p; do [ -f "docs/$p" ] && echo OK || echo "MISSING $p"; done \
|
||||
| sort | uniq -c
|
||||
cd -
|
||||
```
|
||||
Expected: a single line `18 OK`; no `MISSING` lines. (A `MISSING <path>` line means that
|
||||
manifest row points at a doc that does not exist — fix the path before continuing.)
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add plugins/document-navigator/skills/sp-stack/capabilities.md
|
||||
git commit -m "Add sp-stack capability manifest (v1 seed)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Author the sp-stack SKILL.md
|
||||
|
||||
**Files:**
|
||||
- Create: `plugins/document-navigator/skills/sp-stack/SKILL.md`
|
||||
|
||||
- [ ] **Step 1: Create SKILL.md**
|
||||
|
||||
````markdown
|
||||
---
|
||||
name: sp-stack
|
||||
description: Describe a whole website, service, or app and get the complete @suaveplan/* library set, host wiring, and starter code — selected to minimise the code you write.
|
||||
---
|
||||
|
||||
# Stack Composer for Suaveplan
|
||||
|
||||
Turn a natural-language description of an entire website, service, or app into the full
|
||||
set of `@suaveplan/*` packages needed across all layers, plus the wiring and starter
|
||||
code to compose them. Selection always prefers the highest-level turnkey package so the
|
||||
user writes as little code as possible.
|
||||
|
||||
This skill is the whole-app counterpart to `sp` (which finds a library for a single
|
||||
stated need). Use `sp-stack` when the input describes a product; use `sp` for "which
|
||||
library does X" or "how do I use Y".
|
||||
|
||||
## Inputs
|
||||
|
||||
A product description, e.g. *"a multi-tenant SaaS with email campaigns, Stripe billing,
|
||||
and a booking calendar"*.
|
||||
|
||||
## Sources
|
||||
|
||||
| Source | Path |
|
||||
|--------|------|
|
||||
| Capability manifest | `${CLAUDE_PLUGIN_ROOT}/skills/sp-stack/capabilities.md` |
|
||||
| Docs tree (fallback + examples) | `${CLAUDE_PLUGIN_ROOT}/docs/` |
|
||||
|
||||
## Flow
|
||||
|
||||
1. **Decompose.** Parse the description into a list of capabilities and infer the
|
||||
app-type (service / website / app / game). Map each capability to the manifest
|
||||
vocabulary (auth, multi-tenancy, payments, email-campaigns, file-upload, search,
|
||||
realtime-sse, rate-limiting, notifications, webhooks, booking, validation, pwa,
|
||||
client-data-hooks, cli, testing, …).
|
||||
|
||||
2. **Resolve each capability.**
|
||||
- **Manifest hit** → take `primary` + `supporting`; open its `example_doc` under
|
||||
`${CLAUDE_PLUGIN_ROOT}/docs/` and read the quick-start block verbatim.
|
||||
- **Manifest miss** → fall back to the `sp` docs-traversal: root README →
|
||||
`<category>/README.md` → `<subcategory>/README.md` → package README, ranking
|
||||
turnkey `api-plugin-*` / engine packages above low-level primitives.
|
||||
|
||||
3. **Assemble.** Dedupe packages, order by dependency (turnkey plugins first), group by
|
||||
`layer` (backend / frontend / foundation / devtools).
|
||||
|
||||
4. **Read examples.** Pull the quick-start code verbatim from each chosen package's
|
||||
docs. Never fabricate import paths.
|
||||
|
||||
5. **Emit** the output below.
|
||||
|
||||
## Output
|
||||
|
||||
- **Bill of materials** — grouped by layer, one-line reason per package.
|
||||
- **Install** — a single `bun add @suaveplan/… …`.
|
||||
- **Wiring** — the `createHost({ plugins: [...] })` composition (see
|
||||
`docs/api-stack/api/api/README.md` for the host entry point).
|
||||
- **Starter code** — stitched from the verbatim docs examples.
|
||||
- **Gaps & prerequisites** — capabilities with no `@suaveplan` package (the user writes
|
||||
custom code or brings a third-party lib) and required external services (API keys,
|
||||
datastores) drawn from each manifest row's `external` column.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Only reference packages that exist under `${CLAUDE_PLUGIN_ROOT}/docs/`. Never invent
|
||||
package names.
|
||||
- Use exact `@suaveplan/<name>` identifiers.
|
||||
- Extract code examples verbatim from `.md` files; do not fabricate import paths.
|
||||
- Prefer the turnkey package for a capability (the manifest's `primary`) over
|
||||
hand-assembling low-level primitives.
|
||||
- Be honest about gaps — do not imply coverage that the catalog does not provide.
|
||||
|
||||
## Checklist
|
||||
|
||||
1. Description decomposed into capabilities + app-type.
|
||||
2. Each capability resolved via manifest, or via docs-traversal fallback when absent.
|
||||
3. Packages deduped, dependency-ordered, grouped by layer.
|
||||
4. Code examples pulled verbatim from the docs tree, not invented.
|
||||
5. Gaps and external prerequisites stated explicitly.
|
||||
````
|
||||
|
||||
- [ ] **Step 2: Verify frontmatter and source paths**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
f=plugins/document-navigator/skills/sp-stack/SKILL.md
|
||||
grep -q "^name: sp-stack$" "$f" && echo "NAME-OK"
|
||||
grep -q "^description: " "$f" && echo "DESC-OK"
|
||||
grep -q "CLAUDE_PLUGIN_ROOT}/skills/sp-stack/capabilities.md" "$f" && echo "MANIFEST-REF-OK"
|
||||
test -f plugins/document-navigator/skills/sp-stack/capabilities.md && echo "MANIFEST-EXISTS"
|
||||
```
|
||||
Expected: `NAME-OK`, `DESC-OK`, `MANIFEST-REF-OK`, `MANIFEST-EXISTS` all print.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add plugins/document-navigator/skills/sp-stack/SKILL.md
|
||||
git commit -m "Add sp-stack composer skill"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Narrow the sp skill description
|
||||
|
||||
**Files:**
|
||||
- Modify: `plugins/document-navigator/skills/sp/SKILL.md` (frontmatter `description`)
|
||||
|
||||
So Claude reliably routes single-need lookups to `sp` and whole-app requests to
|
||||
`sp-stack`, sharpen `sp`'s description to the single-capability role.
|
||||
|
||||
- [ ] **Step 1: Replace the description line**
|
||||
|
||||
Change:
|
||||
```
|
||||
description: Navigate the 500+ @suaveplan/* packages to find libraries matching a stated development need. Extracts code examples from the mirrored docs tree.
|
||||
```
|
||||
to:
|
||||
```
|
||||
description: Find the @suaveplan/* package for a single stated need ("which library does X", "how do I use Y") and extract its code examples from the docs tree. For whole-app stacks, use sp-stack.
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the two descriptions are distinct and cross-referenced**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
grep "^description:" plugins/document-navigator/skills/sp/SKILL.md
|
||||
grep "^description:" plugins/document-navigator/skills/sp-stack/SKILL.md
|
||||
grep -q "sp-stack" plugins/document-navigator/skills/sp/SKILL.md && echo "CROSSREF-OK"
|
||||
```
|
||||
Expected: two clearly different description lines print; `CROSSREF-OK` prints.
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add plugins/document-navigator/skills/sp/SKILL.md
|
||||
git commit -m "Narrow sp skill description to single-need lookups"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Final verification
|
||||
|
||||
- [ ] **Step 1: End-to-end path + structure check**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
test -f plugins/document-navigator/docs/README.md && echo "DOCS-MOVED"
|
||||
test -f plugins/document-navigator/skills/sp/SKILL.md && echo "SP-OK"
|
||||
test -f plugins/document-navigator/skills/sp-stack/SKILL.md && echo "SPSTACK-OK"
|
||||
test -f plugins/document-navigator/skills/sp-stack/capabilities.md && echo "MANIFEST-OK"
|
||||
grep -rc "suaveplan-skills/docs" plugins GUIDELINES.md | grep -v ':0' || echo "NO-STALE-PATHS"
|
||||
```
|
||||
Expected: `DOCS-MOVED`, `SP-OK`, `SPSTACK-OK`, `MANIFEST-OK`, `NO-STALE-PATHS` all print.
|
||||
|
||||
- [ ] **Step 2: Manual smoke test (human or driving agent)**
|
||||
|
||||
In a session with the plugin loaded, give `sp-stack` the prompt *"a multi-tenant SaaS
|
||||
with email campaigns, Stripe billing, and a booking calendar"* and confirm the output
|
||||
contains: a layered BOM, a `bun add` line, a `createHost` wiring block, starter code,
|
||||
and a gaps/prerequisites section naming the Stripe key.
|
||||
@@ -1,149 +0,0 @@
|
||||
# sp-stack — Full-Stack Library Composer
|
||||
|
||||
**Date:** 2026-05-29
|
||||
**Status:** Approved (design)
|
||||
**Plugin:** `document-navigator`
|
||||
|
||||
## Problem
|
||||
|
||||
The marketplace exposes two ways into the 472-package `@suaveplan/*` catalog:
|
||||
|
||||
- `library-navigator` — 383 self-contained `sp-<package>` skills, one per library.
|
||||
- `document-navigator/skills/sp` — greps the mirrored `docs/` tree to match a single
|
||||
stated need to a package and pull verbatim code examples.
|
||||
|
||||
Neither answers the question a user actually starts with: *"I want to build a
|
||||
multi-tenant SaaS with email campaigns, Stripe billing, and a booking calendar —
|
||||
which libraries do I need, and how do they fit together?"* Answering that today
|
||||
means many manual `sp` lookups and hand-assembling the result.
|
||||
|
||||
We need a skill that takes a whole-app description and returns the complete library
|
||||
set, the wiring, and starter code — selected to **minimise the code the user writes**
|
||||
(prefer a turnkey `api-plugin-payment` over hand-assembling `payment` + `retry` +
|
||||
`webhook-client`).
|
||||
|
||||
## Goals
|
||||
|
||||
- Map a natural-language app description to the full set of `@suaveplan/*` packages
|
||||
needed, across all 18 categories (backend, frontend, foundation, devtools).
|
||||
- Always prefer the highest-level turnkey package for each capability.
|
||||
- Emit a bill of materials, install command, host wiring, and starter code stitched
|
||||
from **verbatim** docs examples — never fabricated imports.
|
||||
- Surface gaps honestly: capabilities with no `@suaveplan` package, and external
|
||||
prerequisites (API keys, datastores).
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- No automated or batch generation of per-library skills (GUIDELINES.md §1 stands;
|
||||
the capability manifest is a separate hand-curated artifact).
|
||||
- No runtime/scaffolding tool — the skill emits code as text, it does not write a
|
||||
project to disk.
|
||||
- No change to the 383 `library-navigator` skills.
|
||||
|
||||
## Decisions
|
||||
|
||||
1. **Approach C (hybrid).** A curated capability manifest covers the common 80% with
|
||||
precise, code-minimal picks; the existing `sp` docs-traversal is the fallback for
|
||||
the long tail. This is the only approach that both guarantees the "prefer turnkey"
|
||||
behavior and cannot silently miss a library.
|
||||
2. **Housed in `document-navigator`**, sharing the relocated `docs/` tree. The composer
|
||||
is a docs-navigation concern, so co-locating it avoids duplicating 32 MB or relying
|
||||
on cross-plugin paths (which Claude Code has no mechanism for).
|
||||
3. **`docs/` moves under the plugin** so the plugin is self-contained and portable
|
||||
regardless of distribution.
|
||||
|
||||
## Architecture
|
||||
|
||||
### File layout
|
||||
|
||||
```
|
||||
plugins/document-navigator/
|
||||
.claude-plugin/plugin.json
|
||||
docs/ # moved from repo root (32 MB, ~6k .md)
|
||||
skills/
|
||||
sp/SKILL.md # existing — description narrowed (see below)
|
||||
sp-stack/
|
||||
SKILL.md # new composer skill
|
||||
capabilities.md # curated capability manifest
|
||||
```
|
||||
|
||||
### docs/ relocation
|
||||
|
||||
`docs/` moves from the repo root to `plugins/document-navigator/docs/`. Every `docs/`
|
||||
reference in `sp/SKILL.md` is rewritten from the hard-coded
|
||||
`.claude/plugins/suaveplan-skills/docs/` path to `${CLAUDE_PLUGIN_ROOT}/docs/`.
|
||||
|
||||
A repo-root path that only resolves when the entire marketplace is cloned together is
|
||||
replaced by a plugin-relative path that resolves however the plugin is installed.
|
||||
|
||||
**Pre-move verification (planning step):** before relocating ~6k files, grep the repo
|
||||
for any other reference to the root `docs/` path — the 383 `library-navigator` skills,
|
||||
`marketplace.json`, and any build/lint scripts — and confirm none break. Relative links
|
||||
*inside* `docs/` (e.g. `./<category>/README.md`) survive a wholesale move; references
|
||||
*into* `docs/` from outside do not.
|
||||
|
||||
### Capability manifest (`capabilities.md`)
|
||||
|
||||
A small, hand-curated table — the heart of Approach C. One row per common capability:
|
||||
|
||||
| Field | Purpose |
|
||||
|---|---|
|
||||
| `capability` | e.g. `payments`, `multi-tenancy`, `email-campaigns`, `file-upload` |
|
||||
| `layer` | `backend` / `frontend` / `foundation` / `devtools` — drives grouping |
|
||||
| `primary` | the turnkey package, the minimise-code pick (`@suaveplan/api-plugin-payment`) |
|
||||
| `supporting` | deps it pulls in / commonly paired packages |
|
||||
| `example_doc` | path into `docs/` where the verbatim quick-start lives |
|
||||
| `external` | non-suaveplan prerequisites (Stripe keys, SurrealDB, SMTP) |
|
||||
|
||||
The manifest is the only place the "prefer turnkey over low-level primitives" rule is
|
||||
encoded, so selection is deterministic rather than re-derived per request.
|
||||
|
||||
**v1 seed scope:** ship ~15–20 of the most common app capabilities (auth, multi-tenancy,
|
||||
payments, email-campaigns, transactional-email, file-upload, search, realtime/SSE,
|
||||
rate-limiting, notifications, webhooks, validation, PWA, client data hooks, CLI, testing).
|
||||
This bounds the manifest work; everything outside it degrades to the docs-traversal
|
||||
fallback, not a wrong answer. Each seed row should be anchored against a real example
|
||||
phrasing so the `capability` vocabulary maps to how users actually describe needs.
|
||||
|
||||
## Composer flow
|
||||
|
||||
1. Parse the app description into a capability list plus an inferred app-type.
|
||||
2. For each capability:
|
||||
- **Manifest hit** → take `primary` + `supporting`, read its `example_doc` verbatim.
|
||||
- **Manifest miss** → fall back to the `sp` docs-traversal (root → category →
|
||||
subcategory → package) to locate candidates.
|
||||
3. Dedupe packages, order by dependency (turnkey plugins first), group by `layer`.
|
||||
4. Read the verbatim quick-start blocks from the docs tree.
|
||||
5. Emit the output.
|
||||
|
||||
## Output shape
|
||||
|
||||
- **Bill of materials** grouped by layer, with a one-line reason per package.
|
||||
- **Install command** — a single `bun add …`.
|
||||
- **Wiring** — the `createHost({ plugins: [...] })` composition.
|
||||
- **Starter code** — stitched from the verbatim docs examples.
|
||||
- **Gaps & prerequisites** — capabilities with no `@suaveplan` package (write custom
|
||||
or bring third-party) and required external services.
|
||||
|
||||
## Coexistence with `sp`
|
||||
|
||||
Two skills, differentiated sharply by description so Claude selects the right one:
|
||||
|
||||
- **`sp`** — narrowed to *"find the library for a single stated need / how do I use X."*
|
||||
- **`sp-stack`** — *"Describe a whole website/service/app; get the complete library set,
|
||||
wiring, and starter code."*
|
||||
|
||||
## Constraints inherited from existing skills
|
||||
|
||||
- Only reference packages that exist under the docs tree; never invent package names.
|
||||
- Use exact `@suaveplan/<name>` identifiers.
|
||||
- Extract code examples verbatim; never fabricate import paths.
|
||||
|
||||
## Risks
|
||||
|
||||
- **Manifest drift.** New packages need manual addition. Mitigation: the docs-traversal
|
||||
fallback keeps the long tail reachable, so a missing manifest row degrades to the
|
||||
current `sp` behavior rather than a wrong answer.
|
||||
- **Two code paths.** Manifest + fallback. Both are simple, and the fallback is the
|
||||
existing, already-exercised traversal.
|
||||
```
|
||||
@@ -357,9 +357,9 @@ Game controllers, entity-component systems, and game-server logic.
|
||||
|
||||
| Package | Description |
|
||||
|---|---|
|
||||
| [`@suaveplan/controller`](./gaming/games/controller/README.md) | Universal input controller foundation. Ships the unified `ControllerInput` shape (37 normalised fields in `[0, 255]`), `BaseController` abstract base with inje… |
|
||||
| [`@suaveplan/ecs`](./gaming/games/ecs/README.md) | Archetype-based Entity Component System with `Uint32`-backed bitset queries, phase-ordered system scheduling, entity-ID recycling, Zod-validated component sche… |
|
||||
| [`@suaveplan/game-server`](./gaming/games/game-server/README.md) | Universal game-server framework. Migrated from `@suaveplan/game-server` on the reference branch `claude/review-game-engine-arch-Kaykr` (main repo `packages/uni… |
|
||||
| [`@suaveplan/controller`](./gaming/gaming/controller/README.md) | Universal input controller foundation. Ships the unified `ControllerInput` shape (37 normalised fields in `[0, 255]`), `BaseController` abstract base with inje… |
|
||||
| [`@suaveplan/ecs`](./gaming/gaming/ecs/README.md) | Archetype-based Entity Component System with `Uint32`-backed bitset queries, phase-ordered system scheduling, entity-ID recycling, Zod-validated component sche… |
|
||||
| [`@suaveplan/game-server`](./gaming/gaming/game-server/README.md) | Universal game-server framework. Migrated from `@suaveplan/game-server` on the reference branch `claude/review-game-engine-arch-Kaykr` (main repo `packages/uni… |
|
||||
|
||||
### `geospatial`
|
||||
|
||||
|
||||
@@ -106,4 +106,3 @@ Every adapter implementing a writer participant MUST run
|
||||
`tests/conformance/run-2pc-participant.test.ts` file. The harness
|
||||
pins prepare / commit / rollback semantics and the idempotency
|
||||
contract.
|
||||
|
||||
|
||||
@@ -82,4 +82,3 @@ await sink.dispose();
|
||||
## Tier
|
||||
|
||||
Server-tier — depends on `node:fs/promises`.
|
||||
|
||||
|
||||
@@ -76,4 +76,3 @@ queue wait exceeds the bound the record is dropped, the
|
||||
`api.audit.dropped_total{sink,reason}` counter increments, and
|
||||
(subject to a once-per-`warnThrottleMs` throttle) a single warning is
|
||||
logged. Drops are NEVER silent.
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ _(no submodules)_
|
||||
## Purpose
|
||||
|
||||
This module produces the HMAC-SHA-256 tag attached to every audit
|
||||
line. Signing operates on a *canonical* byte representation of the
|
||||
line. Signing operates on a _canonical_ byte representation of the
|
||||
record (keys serialised in a fixed order, `cause` omitted when
|
||||
absent) so the same logical record always produces the same MAC under
|
||||
the same key, regardless of how the source object was constructed.
|
||||
|
||||
@@ -11,7 +11,7 @@ writes. The shape is exactly what design doc §12.5 specifies for the
|
||||
admin / audit pipeline: `ts`, `type`, `principal`, `tenant`,
|
||||
`request_id`, `trace_id`, `subject`, `outcome`, and a conditional
|
||||
`cause`. Every write goes through `parseAuditRecord(...)` so a
|
||||
malformed record raises `AUDIT_STDOUT.INVALID_RECORD` *before* a line
|
||||
malformed record raises `AUDIT_STDOUT.INVALID_RECORD` _before_ a line
|
||||
is written — log pipelines downstream never see a half-finished
|
||||
record.
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ rejected synchronously without consuming the slot.
|
||||
- Boundary Zod validation — `AUDIT_STDOUT.INVALID_RECORD` on a bad
|
||||
record.
|
||||
- Bounded queue — `AUDIT_STDOUT.QUEUE_FULL` on timeout, drop counter
|
||||
+ throttled warning.
|
||||
- throttled warning.
|
||||
- HMAC-SHA-256 over canonical bytes appended as `_hmac` field.
|
||||
- `dispose()` drains the in-flight write and rejects subsequent
|
||||
writes as `closed` drops.
|
||||
|
||||
@@ -57,4 +57,3 @@ The resolver passes `runAuthSessionConformance` (null-only mode) from
|
||||
`AUTH_ANON.INVALID_OPTIONS` — thrown only when the factory is handed a
|
||||
non-object or unknown-key options bag. No per-request error paths exist by
|
||||
design.
|
||||
|
||||
|
||||
@@ -152,4 +152,3 @@ Both surfaces pass `runAuthSessionConformance` from
|
||||
`@suaveplan/api-auth`. Spec-conformance tests live alongside the source
|
||||
under `src/factory/*.spec-conformance.test.ts` and
|
||||
`src/proxy/*.spec-conformance.test.ts`.
|
||||
|
||||
|
||||
@@ -169,4 +169,3 @@ createBetterAuthSession createBetterAuthProvider
|
||||
▼ ▼
|
||||
Principal auth.handler(rewrittenRequest)
|
||||
```
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ provider, not this package.
|
||||
`JWT_BEARER_SCHEME` and `createJwtBearerSecurityScheme(name)` for
|
||||
OpenAPI emission.
|
||||
- [`./error-codes`](src/error-codes/error-codes.md) — `AUTH_JWT_*` codes
|
||||
+ `AuthJwtError` class.
|
||||
- `AuthJwtError` class.
|
||||
- [`./options`](src/options/options.md) — Zod-validated options
|
||||
boundary.
|
||||
|
||||
@@ -132,4 +132,3 @@ returns a non-Principal value.
|
||||
## License
|
||||
|
||||
MIT — see the repository.
|
||||
|
||||
|
||||
@@ -65,4 +65,3 @@ package.
|
||||
## Stage / status
|
||||
|
||||
Concrete providers ship in Stages 6 / 8.
|
||||
|
||||
|
||||
@@ -57,11 +57,11 @@ ownership.register(replacementProvider);
|
||||
## API Reference
|
||||
|
||||
- `class PathOwnership` — runtime registry.
|
||||
- `constructor(options?: PathOwnershipOptions)`
|
||||
- `register(provider): PathOwnershipClaim`
|
||||
- `release(): void`
|
||||
- `current(): ProviderClaim | null`
|
||||
- `hasProvider(): boolean`
|
||||
- `constructor(options?: PathOwnershipOptions)`
|
||||
- `register(provider): PathOwnershipClaim`
|
||||
- `release(): void`
|
||||
- `current(): ProviderClaim | null`
|
||||
- `hasProvider(): boolean`
|
||||
- `interface PathOwnershipOptions { clock? }`
|
||||
- `type OwnershipClock = () => number`
|
||||
- `interface PathOwnershipClaim { providerId; prefix; claimedAt }`
|
||||
|
||||
@@ -4,6 +4,7 @@ Stage-8.12 rolled session-token resolver for `@suaveplan/api`. Issues
|
||||
RS256-signed JWTs with rotating `kid` slots, verifies tokens through the
|
||||
rotator's active + retired-non-revoked key set, projects DB rows onto
|
||||
the `@suaveplan/api-types` `Principal` contract, and contributes cookie
|
||||
|
||||
+ bearer OpenAPI security schemes.
|
||||
|
||||
This milestone ships the **session-resolver half** of the eventual dual
|
||||
@@ -14,20 +15,20 @@ a successor change `api-auth-rolled-provider`.
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [composition](./src/composition/composition.md) — Top-level factory `createRolledAuthAdapter` that wires the four sub-modules (`token-rotator`, `session`, `principal-pro…
|
||||
- [composition](./src/composition/composition.md) — Top-level factory `createRolledAuthAdapter` that wires the four sub-modules (`token-rotator`, `session`, `principal-pro…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — Stable error-code registry for `@suaveplan/api-auth-rolled`. Every code is namespaced under `AUTH_ROLLED.*` so cross-pa…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — Stable error-code registry for `@suaveplan/api-auth-rolled`. Every code is namespaced under `AUTH_ROLLED.*` so cross-pa…
|
||||
- [options](./src/options/options.md) — Defines the configuration shape accepted by `createRolledAuthAdapter` and the Zod schema that validates it. Operators w…
|
||||
- [options](./src/options/options.md) — Defines the configuration shape accepted by `createRolledAuthAdapter` and the Zod schema that validates it. Operators w…
|
||||
- [principal-projection](./src/principal-projection/principal-projection.md) — Projects the package's internal `UserRow` storage shape onto the abstract `Principal` contract published by `@suaveplan…
|
||||
- [principal-projection](./src/principal-projection/principal-projection.md) — Projects the package's internal `UserRow` storage shape onto the abstract `Principal` contract published by `@suaveplan…
|
||||
- [security-schemes](./src/security-schemes/security-schemes.md) — Contributes OpenAPI 3.1 `securitySchemes` entries describing both transports the rolled session resolver accepts — `Aut…
|
||||
- [security-schemes](./src/security-schemes/security-schemes.md) — Contributes OpenAPI 3.1 `securitySchemes` entries describing both transports the rolled session resolver accepts — `Aut…
|
||||
- [session](./src/session/session.md) — The rolling-JWT session resolver — the keystone of the rolled auth adapter. Builds a `SessionResolver` (per `@suaveplan…
|
||||
- [session](./src/session/session.md) — The rolling-JWT session resolver — the keystone of the rolled auth adapter. Builds a `SessionResolver` (per `@suaveplan…
|
||||
- [token-rotator](./src/token-rotator/token-rotator.md) — Owns the rotating-keyset state for the rolled session-token resolver. The active key signs new JWTs; retired-but-non-re…
|
||||
- [token-rotator](./src/token-rotator/token-rotator.md) — Owns the rotating-keyset state for the rolled session-token resolver. The active key signs new JWTs; retired-but-non-re…
|
||||
+ [composition](./src/composition/composition.md) — Top-level factory `createRolledAuthAdapter` that wires the four sub-modules (`token-rotator`, `session`, `principal-pro…
|
||||
+ [composition](./src/composition/composition.md) — Top-level factory `createRolledAuthAdapter` that wires the four sub-modules (`token-rotator`, `session`, `principal-pro…
|
||||
+ [error-codes](./src/error-codes/error-codes.md) — Stable error-code registry for `@suaveplan/api-auth-rolled`. Every code is namespaced under `AUTH_ROLLED.*` so cross-pa…
|
||||
+ [error-codes](./src/error-codes/error-codes.md) — Stable error-code registry for `@suaveplan/api-auth-rolled`. Every code is namespaced under `AUTH_ROLLED.*` so cross-pa…
|
||||
+ [options](./src/options/options.md) — Defines the configuration shape accepted by `createRolledAuthAdapter` and the Zod schema that validates it. Operators w…
|
||||
+ [options](./src/options/options.md) — Defines the configuration shape accepted by `createRolledAuthAdapter` and the Zod schema that validates it. Operators w…
|
||||
+ [principal-projection](./src/principal-projection/principal-projection.md) — Projects the package's internal `UserRow` storage shape onto the abstract `Principal` contract published by `@suaveplan…
|
||||
+ [principal-projection](./src/principal-projection/principal-projection.md) — Projects the package's internal `UserRow` storage shape onto the abstract `Principal` contract published by `@suaveplan…
|
||||
+ [security-schemes](./src/security-schemes/security-schemes.md) — Contributes OpenAPI 3.1 `securitySchemes` entries describing both transports the rolled session resolver accepts — `Aut…
|
||||
+ [security-schemes](./src/security-schemes/security-schemes.md) — Contributes OpenAPI 3.1 `securitySchemes` entries describing both transports the rolled session resolver accepts — `Aut…
|
||||
+ [session](./src/session/session.md) — The rolling-JWT session resolver — the keystone of the rolled auth adapter. Builds a `SessionResolver` (per `@suaveplan…
|
||||
+ [session](./src/session/session.md) — The rolling-JWT session resolver — the keystone of the rolled auth adapter. Builds a `SessionResolver` (per `@suaveplan…
|
||||
+ [token-rotator](./src/token-rotator/token-rotator.md) — Owns the rotating-keyset state for the rolled session-token resolver. The active key signs new JWTs; retired-but-non-re…
|
||||
+ [token-rotator](./src/token-rotator/token-rotator.md) — Owns the rotating-keyset state for the rolled session-token resolver. The active key signs new JWTs; retired-but-non-re…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Why
|
||||
@@ -80,4 +81,3 @@ setInterval(async () => {
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -122,4 +122,3 @@ const host = createTestHost({
|
||||
|
||||
Until then, callers wire the adapter directly via
|
||||
`createStaticPrincipalAdapter()` as shown above.
|
||||
|
||||
|
||||
@@ -93,9 +93,9 @@ const principal = await chain.resolve(new Request("https://example.test"));
|
||||
|
||||
## Versioning
|
||||
|
||||
1.0`. Graduates to `1.0.0` when the Stage 1
|
||||
contract bundle (`api-types`, `api-auth`, `api-tenant-resolver`,
|
||||
`api-secrets`, `api-db-adapter`) all reach 1.0.
|
||||
1.0`. Graduates to`1.0.0`when the Stage 1
|
||||
contract bundle (`api-types`,`api-auth`,`api-tenant-resolver`,
|
||||
`api-secrets`,`api-db-adapter`) all reach 1.0.
|
||||
|
||||
## Dependencies
|
||||
|
||||
@@ -106,4 +106,3 @@ contract bundle (`api-types`, `api-auth`, `api-tenant-resolver`,
|
||||
| `@suaveplan/types` | Cross-package contracts (logger, clock, etc.). |
|
||||
| `tslib` | TypeScript runtime helpers (catalog). |
|
||||
| `zod` | Boundary validation for security-scheme contributions. |
|
||||
|
||||
|
||||
@@ -125,4 +125,3 @@ a no-op.
|
||||
factory.
|
||||
- `DB_ADAPTER.INVALID_OPTIONS` — Zod validation failed; issues attached
|
||||
to `error.context.issues`.
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ The result object exposes:
|
||||
## Features
|
||||
|
||||
- Options are parsed once at factory time; the rest of the API is typed
|
||||
against the *post-parse* `DbAdapterOptions`.
|
||||
against the _post-parse_ `DbAdapterOptions`.
|
||||
- Engine lookup is delegated to `buildEngineAdapter`, so any
|
||||
declaration-merged engine works automatically.
|
||||
- Tenant scoping config (`tenantColumn`, `autoInjectInserts`) flows from
|
||||
|
||||
@@ -24,7 +24,7 @@ payloads.
|
||||
- `parseDbAdapterOptions(input)` — convenience wrapper that converts every Zod
|
||||
failure into a typed `DbAdapterError` with code `INVALID_OPTIONS`. Issues
|
||||
are surfaced through `error.context.issues`.
|
||||
- `engine` is `z.string().min(1)` — the *literal value* check happens against
|
||||
- `engine` is `z.string().min(1)` — the _literal value_ check happens against
|
||||
the open `DatabaseEngineRegistry` so declaration-merged engines pass.
|
||||
- `pool` is an optional sub-schema with its own defaults; absent values are
|
||||
forwarded as `undefined` to the engine layer rather than guessed.
|
||||
|
||||
@@ -10,7 +10,7 @@ This module is the load-bearing safety boundary between a plugin route handler
|
||||
and the underlying admin database surface. Every SELECT/UPDATE/DELETE that the
|
||||
route handler issues is rewritten in flight to carry a `WHERE tenant_id = ?`
|
||||
predicate; every INSERT either carries the tenant column or is rejected (or
|
||||
auto-injected, configurable). The contract is enforced *at the wrapper*, so a
|
||||
auto-injected, configurable). The contract is enforced _at the wrapper_, so a
|
||||
route handler literally cannot read another tenant's rows even if the plugin
|
||||
author is malicious.
|
||||
|
||||
|
||||
@@ -75,4 +75,3 @@ The final merged pragma map is `{ ...DEFAULT_PRAGMAS, ...options.pragmas }`, so
|
||||
## License
|
||||
|
||||
MIT.
|
||||
|
||||
|
||||
@@ -109,4 +109,3 @@ parentPort?.once("message", (boot) => {
|
||||
- [`src/tenant-scoped/tenant-scoped.md`](./src/tenant-scoped/tenant-scoped.md)
|
||||
- [`src/error-codes/error-codes.md`](./src/error-codes/error-codes.md)
|
||||
- [`src/testing/testing.md`](./src/testing/testing.md)
|
||||
|
||||
|
||||
@@ -102,4 +102,3 @@ underlying `z.ZodError` on `cause`.
|
||||
## License
|
||||
|
||||
MIT — Suaveplan Team.
|
||||
|
||||
|
||||
@@ -87,4 +87,3 @@ console.log(result.delivered);
|
||||
- [`payload-codec`](./src/payload-codec/payload-codec.md)
|
||||
- [`error-codes`](./src/error-codes/error-codes.md)
|
||||
- [`testing`](./src/testing/testing.md)
|
||||
|
||||
|
||||
@@ -66,4 +66,3 @@ slimmer dependency closure.
|
||||
Original. Owned by the `api-stack` super-category.
|
||||
Passes the abstract conformance harness with the same invariants Yoga
|
||||
satisfies — see `src/engine/apollo-engine.spec-conformance.test.ts`.
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ boundary.
|
||||
|
||||
The module also re-exports the unmodified `runGraphqlEngineConformance`
|
||||
so downstream packages that want to run the harness against a
|
||||
*different* factory (e.g. a federated subgraph composing Apollo + a
|
||||
_different_ factory (e.g. a federated subgraph composing Apollo + a
|
||||
custom transport) keep a single import surface.
|
||||
|
||||
## Features
|
||||
|
||||
@@ -94,4 +94,3 @@ the `ui` label.
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -104,4 +104,3 @@ bunx --bun turbo lint typecheck test:coverage --filter=@suaveplan/api-graphql-ui
|
||||
|
||||
100% statements / branches / functions / lines required on touched
|
||||
modules (`provider: istanbul`).
|
||||
|
||||
|
||||
@@ -61,4 +61,4 @@ if (cspAllowsInlineScripts(myCspValue)) {
|
||||
Inline-style allowance is documented in the module header so future
|
||||
maintainers don't tighten the policy without understanding why. The
|
||||
predicate operates on the raw header string rather than parsing the CSP
|
||||
grammar — sufficient for the conformance check.
|
||||
grammar — sufficient for the conformance check.
|
||||
|
||||
@@ -87,4 +87,3 @@ const result = await engine.executeQuery({ query: "{ ping }" }, ctx);
|
||||
## License
|
||||
|
||||
MIT — Suaveplan Team.
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ Bridges the `WsBroker` contract from `@suaveplan/api-ws` to GraphQL
|
||||
`Subscription` resolvers via `AsyncIterable<Payload>`. The Yoga engine
|
||||
wires `graphql-ws` over the host's already-mounted WebSocket transport;
|
||||
this module is the per-subscription helper that converts a broker `topic`
|
||||
|
||||
+ `tenant` pair into an iterator the engine can consume.
|
||||
|
||||
Tenant filtering is enforced at the bridge: only `BrokerEnvelope` items
|
||||
@@ -20,10 +21,10 @@ within one tick.
|
||||
|
||||
## Features
|
||||
|
||||
- Returns `AsyncIterable<Payload>` with an explicit `close()` accessor.
|
||||
- Queues payloads when no consumer is awaiting; resolves immediately when
|
||||
+ Returns `AsyncIterable<Payload>` with an explicit `close()` accessor.
|
||||
+ Queues payloads when no consumer is awaiting; resolves immediately when
|
||||
a consumer is parked.
|
||||
- Calls `GraphqlYogaError("SUBSCRIPTION_TRANSPORT_UNAVAILABLE")` when the
|
||||
+ Calls `GraphqlYogaError("SUBSCRIPTION_TRANSPORT_UNAVAILABLE")` when the
|
||||
host hands a `null` broker, satisfying the design doc §10.4 contract.
|
||||
|
||||
## Usage: Basic
|
||||
@@ -59,7 +60,7 @@ const subscription = {
|
||||
|
||||
## API Reference
|
||||
|
||||
- `subscribeBrokerTopic(broker, topic, tenant)` — returns
|
||||
+ `subscribeBrokerTopic(broker, topic, tenant)` — returns
|
||||
`AsyncIterable<Payload> & { close(): Promise<void> }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
@@ -78,4 +78,3 @@ Altair packages land .
|
||||
- `@suaveplan/error` — `defineCodes` + `SuaveplanError` base.
|
||||
- `zod` — subscription protocol envelope validation.
|
||||
- `tslib` — TypeScript helper functions.
|
||||
|
||||
|
||||
@@ -115,4 +115,3 @@ bun add @suaveplan/api-host
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -65,14 +65,14 @@ await registry.dispose();
|
||||
## API Reference
|
||||
|
||||
- `class CapabilityRegistry`
|
||||
- `register(reg)`: void
|
||||
- `lookup<Id>(id): CapabilityRegistration<Id> | undefined`
|
||||
- `require<Id>(id): CapabilityRegistration<Id>`
|
||||
- `has(id): boolean`
|
||||
- `ids(): readonly CapabilityId[]`
|
||||
- `size(): number`
|
||||
- `assertSatisfies(ranges: Record<string, string>): void`
|
||||
- `dispose(): Promise<void>`
|
||||
- `register(reg)`: void
|
||||
- `lookup<Id>(id): CapabilityRegistration<Id> | undefined`
|
||||
- `require<Id>(id): CapabilityRegistration<Id>`
|
||||
- `has(id): boolean`
|
||||
- `ids(): readonly CapabilityId[]`
|
||||
- `size(): number`
|
||||
- `assertSatisfies(ranges: Record<string, string>): void`
|
||||
- `dispose(): Promise<void>`
|
||||
- `defineCapability<const Id>(reg): CapabilityRegistration<Id>`
|
||||
- `CapabilityScope`: `"host" | "plugin" | "request"`
|
||||
- `CapabilityRegistration<Id>`: shape consumed by `register`.
|
||||
|
||||
@@ -73,12 +73,12 @@ for (const name of HOST_COUNTER_NAMES) {
|
||||
- `HOST_COUNTER_NAMES` — `readonly` tuple of every counter name.
|
||||
- `HOST_HISTOGRAM_NAMES` — `readonly` tuple of every histogram name.
|
||||
- `HostInstruments`:
|
||||
- `meter: Meter`
|
||||
- `tenantLabelOptIn: boolean`
|
||||
- `counters: Readonly<Record<HostCounterName, Counter>>`
|
||||
- `histograms: Readonly<Record<HostHistogramName, Histogram>>`
|
||||
- `forPlugin(name): PluginInstruments`
|
||||
- `dispose(): void`
|
||||
- `meter: Meter`
|
||||
- `tenantLabelOptIn: boolean`
|
||||
- `counters: Readonly<Record<HostCounterName, Counter>>`
|
||||
- `histograms: Readonly<Record<HostHistogramName, Histogram>>`
|
||||
- `forPlugin(name): PluginInstruments`
|
||||
- `dispose(): void`
|
||||
- `PluginInstruments`: `{ meter: Meter; pluginName: string }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
@@ -112,7 +112,7 @@ const PIPELINE_STEPS: readonly PipelineStepName[];
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The pipeline runs route-match *first* (technically step 2 in §6) so
|
||||
The pipeline runs route-match _first_ (technically step 2 in §6) so
|
||||
later step gates (`auth`, `csrf`, `rateLimit`, `idempotent`) can
|
||||
consult per-route policy. The §6 ordering of "intake → match" is
|
||||
preserved at the engine adapter; this driver assumes the intake has
|
||||
|
||||
@@ -54,7 +54,7 @@ package adds only the jobs-specific verbs.
|
||||
## Local conformance harness
|
||||
|
||||
The proposal originally referenced `runJobsConformance` from
|
||||
`@suaveplan/job-scheduler/testing`. That harness does not yet exist;
|
||||
`@suaveplan/job-scheduler/testing`. That harness does not yet exist;
|
||||
this package therefore provides its own
|
||||
`JOBS_CONFORMANCE_CASES` + `runJobsConformance` under `./testing`. The
|
||||
surface matches what the upstream harness is intended to be — a
|
||||
@@ -67,4 +67,3 @@ in-repo substitute can be swapped out without changing consumers.
|
||||
bunx turbo lint typecheck test --filter=@suaveplan/api-jobs-worker-proxy
|
||||
bunx turbo test:coverage --filter=@suaveplan/api-jobs-worker-proxy
|
||||
```
|
||||
|
||||
|
||||
@@ -67,8 +67,8 @@ client.registerQueued("send-email", async (payload) => {
|
||||
- `JobsProxyClient extends JobRegistrationHandle` — adds `close()` and a
|
||||
`sizes` accessor reporting registration counts.
|
||||
- `JobsProxyClientOptions`:
|
||||
- `handlerTimeoutMs?: number` — per-dispatch timeout, `0` disables.
|
||||
- `onHandlerError?: (err, ctx) => void` — observer hook.
|
||||
- `handlerTimeoutMs?: number` — per-dispatch timeout, `0` disables.
|
||||
- `onHandlerError?: (err, ctx) => void` — observer hook.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
|
||||
@@ -48,11 +48,11 @@ try {
|
||||
## API Reference
|
||||
|
||||
- `JOBS_PROXY_CODES` — readonly registry of five string codes:
|
||||
- `JOB_NOT_DECLARED` — name absent from manifest whitelist.
|
||||
- `HANDLER_THREW` — worker-side handler threw or rejected.
|
||||
- `PROXY_DISPOSED` — operation attempted after `close()`.
|
||||
- `JOB_TIMEOUT` — dispatch exceeded `handlerTimeoutMs`.
|
||||
- `DUPLICATE_REGISTRATION` — same name registered twice.
|
||||
- `JOB_NOT_DECLARED` — name absent from manifest whitelist.
|
||||
- `HANDLER_THREW` — worker-side handler threw or rejected.
|
||||
- `PROXY_DISPOSED` — operation attempted after `close()`.
|
||||
- `JOB_TIMEOUT` — dispatch exceeded `handlerTimeoutMs`.
|
||||
- `DUPLICATE_REGISTRATION` — same name registered twice.
|
||||
- `JobsProxyCode` — union of every literal value in the registry.
|
||||
- `JobsProxyError(code, message, options?)` — typed error class.
|
||||
|
||||
|
||||
@@ -63,12 +63,12 @@ server.close("plugin-unload");
|
||||
|
||||
- `createJobsProxyServer(port, mainHandle, opts) -> JobsProxyServer`.
|
||||
- `JobsProxyServer`:
|
||||
- `close(reason?: string): void` — idempotent disposer.
|
||||
- `registeredCount: number` — count of accepted registrations.
|
||||
- `close(reason?: string): void` — idempotent disposer.
|
||||
- `registeredCount: number` — count of accepted registrations.
|
||||
- `JobsProxyServerOptions`:
|
||||
- `declaredJobs: ReadonlyArray<{name, kind}>` — manifest whitelist.
|
||||
- `runRpcTimeoutMs?: number` — optional per-tick RPC timeout.
|
||||
- `onDispatchError?: (err, ctx) => void` — telemetry hook.
|
||||
- `declaredJobs: ReadonlyArray<{name, kind}>` — manifest whitelist.
|
||||
- `runRpcTimeoutMs?: number` — optional per-tick RPC timeout.
|
||||
- `onDispatchError?: (err, ctx) => void` — telemetry hook.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
|
||||
@@ -79,4 +79,3 @@ await fsm.transition("startCompleted");
|
||||
| `LIFECYCLE.MUTEX_HELD` | `assertAcquired` called on a null `tryAcquire` result |
|
||||
| `LIFECYCLE.RETRY_BUDGET_EXHAUSTED` | start_failed × 3 OR crashed × 3 in window |
|
||||
| `LIFECYCLE.REINSTALL_REQUIRED_VERSION_DOWNGRADE` | `next.version < prev.version` |
|
||||
|
||||
|
||||
@@ -73,4 +73,4 @@ sections 8 (Hot reload), 8.1a (RollbackJournal), 8.2 (cache busting).
|
||||
|
||||
- Version: `0.1.0`
|
||||
- Wave: Stage 1 (Contracts)
|
||||
- Consumers: `@suaveplan/api-host` (Stage 2) implements the contracts here.
|
||||
- Consumers: `@suaveplan/api-host` (Stage 2) implements the contracts here.
|
||||
|
||||
@@ -88,7 +88,7 @@ export type ModuleResolverFactory<M = unknown> = () => ModuleResolver<M>;
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`resolve` returns the *evaluated namespace*, not a thenable wrapping it —
|
||||
`resolve` returns the _evaluated namespace_, not a thenable wrapping it —
|
||||
the host's lifecycle FSM treats a successful return as a synchronous
|
||||
"module loaded" signal even though the call itself is async. Concrete
|
||||
implementations are free to memoise nothing (the cache-bust strategy in
|
||||
|
||||
@@ -95,4 +95,3 @@ server.close();
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ proxy).
|
||||
`WireLogRecord` with defaults.
|
||||
- `runLoggerProxyConformance(plugin, options?)` — full paired
|
||||
harness: client + server + captured-output array + control sender
|
||||
+ `dispose()`.
|
||||
- `dispose()`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
|
||||
@@ -108,4 +108,3 @@ Every code is namespaced under `MANIFEST.*` and produced by `defineCodes`. The e
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -123,4 +123,3 @@ import { MIGRATION_CODES } from "@suaveplan/api-migrations/error-codes/error-cod
|
||||
|
||||
Server (Bun + Node). Uses `node:crypto` for SHA-256 and `node:fs/promises`
|
||||
for directory scanning and file reads.
|
||||
|
||||
|
||||
@@ -80,4 +80,3 @@ const res = handler();
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -127,4 +127,3 @@ bunx --bun turbo lint typecheck test:coverage --filter=@suaveplan/api-openapi-ui
|
||||
|
||||
100% statements / branches / functions / lines required on touched
|
||||
modules (`provider: istanbul`).
|
||||
|
||||
|
||||
@@ -78,4 +78,3 @@ for (const name of listBundledAssetNames()) {
|
||||
- `OPENAPI_UI_SWAGGER.ASSETS_MISSING` — bundled mode requested without `assetBasePath`, or a bundled asset name is not registered.
|
||||
|
||||
Both surfaces raise at factory-call time (eager validation), so misconfiguration is caught at boot rather than at first request.
|
||||
|
||||
|
||||
@@ -220,4 +220,3 @@ Server-tier (Hono-typed). Runs in Bun + Node.
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -85,4 +85,3 @@ app.post("/users/:id/delete", async (c) => {
|
||||
- `auditService` registered with `ctx.registerService("auditService", ...)` and exposed through the typed `ServiceRegistry` slot.
|
||||
- Hono `ContextVariableMap` augmented with `audit: AuditContext` so handlers fire explicit events without import boilerplate.
|
||||
- Four OpenTelemetry instruments: `audit.requests_recorded_total{outcome,sampled}`, `audit.requests_excluded_total`, `audit.sink_failures_total`, `audit.middleware_duration_ms`.
|
||||
|
||||
|
||||
@@ -192,4 +192,3 @@ createAuthPlugin({
|
||||
- `better-auth ^1.4` (resolves to 1.6.x via the catalog).
|
||||
- `hono`, `@hono/zod-openapi`, `zod` — supplied by the host
|
||||
application; the plugin imports them directly.
|
||||
|
||||
|
||||
@@ -65,4 +65,3 @@ each ships green on its own:
|
||||
`bunx turbo lint typecheck test --filter=@suaveplan/api-plugin-campaigns`
|
||||
plus `bunx turbo test:coverage --filter=@suaveplan/api-plugin-campaigns`
|
||||
at 100/100/100/100 are required for every chunk to land.
|
||||
|
||||
|
||||
@@ -94,4 +94,3 @@ app.onError((err, c) => respondProblemJson(c, err));
|
||||
## License
|
||||
|
||||
MIT — © Suaveplan Team
|
||||
|
||||
|
||||
@@ -108,4 +108,3 @@ Migration metrics are emitted by `@suaveplan/api-migrations` under its own meter
|
||||
## Errors
|
||||
|
||||
Every failure path raises `DatabaseError` carrying one of eight `DATABASE_*` codes: `CONNECTION_FAILED`, `POOL_EXHAUSTED`, `POOL_CLOSED`, `QUERY_FAILED`, `MIGRATION_FAILED`, `LOCK_CONTENDED`, `TENANT_INVALID`, `INVALID_CONFIG`. See [`error-codes/error-codes.md`](./src/error-codes/error-codes.md).
|
||||
|
||||
|
||||
@@ -193,4 +193,3 @@ table and `context.statusCode` mapping.
|
||||
application.
|
||||
- `@suaveplan/api-plugin-auth` — used by `requireAuth()` route gate
|
||||
when `requireAuthForRoutes: true` (default).
|
||||
|
||||
|
||||
@@ -109,4 +109,3 @@ for the full list. Highlights:
|
||||
`0.1.0` — feature parity with upstream `@suaveplan/api-plugin-events`
|
||||
modulo the explicit scope-outs above. 100% line / branch / function /
|
||||
statement coverage. Real SurrealDB testcontainer integration.
|
||||
|
||||
|
||||
@@ -102,4 +102,3 @@ bunx --bun turbo lint typecheck test:coverage --filter=@suaveplan/api-plugin-exa
|
||||
|
||||
100% Istanbul coverage is enforced; the manifest `errors.statusMap`
|
||||
is verified end-to-end against the runtime by the integration suite.
|
||||
|
||||
|
||||
@@ -86,4 +86,3 @@ DELETE /v1/exports/{id} → { success: true }
|
||||
## Scope notes
|
||||
|
||||
This package ships the upstream §2 functional surface (CSV / JSON / SpreadsheetML XML exporters + in-memory job store + four-route Hono group). The upstream §3–§4 enhancements (streaming exporters, S3-backed blob store, auth/permissions/rate-limit/idempotency middleware, OOXML/ZIP writer, scheduled exports, signed download URLs, Parquet/Markdown formats) are deferred to follow-up changes — they require downstream plugins that have not yet landed .
|
||||
|
||||
|
||||
@@ -55,4 +55,4 @@ const result = new XlsxExporter().export(rows, cols, { sheetName: "WideExport" }
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The output is *SpreadsheetML 2006* — a worksheet XML payload, not the ZIP package most consumers think of when they hear "xlsx". This is a deliberate carry-over from the upstream package which chose XML over the `xlsx` npm dependency. The MIME type is set to the OOXML sheet content-type so HTTP consumers can still dispatch by `Content-Type`; the filename suffix is `.xlsx.xml` to signal the format honestly. The sheet name is escaped into the `<worksheet sheetName="…">` attribute even though the bare-worksheet form is not strictly required to carry one — keeping the attribute lets downstream upgrades wrap this document in an OOXML/ZIP package without re-emitting the worksheet. The `columnIndexToLetter` loop uses `Math.floor(n / 26) - 1` so the multi-letter overflow is exactly Excel-compatible: `26 → "AA"`, `51 → "AZ"`, `52 → "BA"`, `676 → "ZA"`, `701 → "ZZ"`, `702 → "AAA"`. The boolean cell uses `1`/`0` (not `true`/`false`) because OOXML requires the integer form.
|
||||
The output is _SpreadsheetML 2006_ — a worksheet XML payload, not the ZIP package most consumers think of when they hear "xlsx". This is a deliberate carry-over from the upstream package which chose XML over the `xlsx` npm dependency. The MIME type is set to the OOXML sheet content-type so HTTP consumers can still dispatch by `Content-Type`; the filename suffix is `.xlsx.xml` to signal the format honestly. The sheet name is escaped into the `<worksheet sheetName="…">` attribute even though the bare-worksheet form is not strictly required to carry one — keeping the attribute lets downstream upgrades wrap this document in an OOXML/ZIP package without re-emitting the worksheet. The `columnIndexToLetter` loop uses `Math.floor(n / 26) - 1` so the multi-letter overflow is exactly Excel-compatible: `26 → "AA"`, `51 → "AZ"`, `52 → "BA"`, `676 → "ZA"`, `701 → "ZZ"`, `702 → "AAA"`. The boolean cell uses `1`/`0` (not `true`/`false`) because OOXML requires the integer form.
|
||||
|
||||
@@ -53,4 +53,4 @@ const inst = getExportInstruments(); // rebinds against the new provider
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The bundle is held in a module-scope `let cached: ExportInstruments | undefined` rather than in a class instance, because the function is the only documented entry point and there is no per-plugin state to keep — every plugin instance in a process shares the same meter. Counters use `add(value, attributes?)`; the `outcome` attribute is set by the call site rather than declared on the instrument because OTel attributes are runtime keys, not instrument metadata. The histogram unit is set to `"ms"` (lower-case) per OTel semantic conventions for latency. Resetting between tests is mandatory: meter handles cache internally inside `@opentelemetry/api`, so without `resetExportInstruments()` a test would emit to whichever provider was installed when the *first* `getExportInstruments()` call ran — usually the global default provider, which loses every metric.
|
||||
The bundle is held in a module-scope `let cached: ExportInstruments | undefined` rather than in a class instance, because the function is the only documented entry point and there is no per-plugin state to keep — every plugin instance in a process shares the same meter. Counters use `add(value, attributes?)`; the `outcome` attribute is set by the call site rather than declared on the instrument because OTel attributes are runtime keys, not instrument metadata. The histogram unit is set to `"ms"` (lower-case) per OTel semantic conventions for latency. Resetting between tests is mandatory: meter handles cache internally inside `@opentelemetry/api`, so without `resetExportInstruments()` a test would emit to whichever provider was installed when the _first_ `getExportInstruments()` call ran — usually the global default provider, which loses every metric.
|
||||
|
||||
@@ -56,4 +56,4 @@ const routes = createExportRoutes({
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The `runPostExport` helper in `post-handler.ts` owns the bulk of the work — the route definitions stay in this file so that the OpenAPI schema is one click away from the route name. Failure paths set `job.status = "failed"` and persist `job.error` *before* returning, so `GET /:id` immediately afterwards sees the failure record (mirroring upstream behaviour). The `downloadUrl` response is `/${jobId}/download` — the actual streaming endpoint is upstream §3 future work, deferred in this migration. The downstream consumer is expected to combine this `downloadUrl` with the `basePath` themselves (e.g. `${basePath}${downloadUrl}`) — the upstream package made the same choice and the route layer cannot know the full mount path. Every route is also registered against an explicit `createRoute(...)` descriptor so an OpenAPI document generated by `OpenAPIHono.getOpenAPIDocument()` includes the export routes with full Zod-derived schemas.
|
||||
The `runPostExport` helper in `post-handler.ts` owns the bulk of the work — the route definitions stay in this file so that the OpenAPI schema is one click away from the route name. Failure paths set `job.status = "failed"` and persist `job.error` _before_ returning, so `GET /:id` immediately afterwards sees the failure record (mirroring upstream behaviour). The `downloadUrl` response is `/${jobId}/download` — the actual streaming endpoint is upstream §3 future work, deferred in this migration. The downstream consumer is expected to combine this `downloadUrl` with the `basePath` themselves (e.g. `${basePath}${downloadUrl}`) — the upstream package made the same choice and the route layer cannot know the full mount path. Every route is also registered against an explicit `createRoute(...)` descriptor so an OpenAPI document generated by `OpenAPIHono.getOpenAPIDocument()` includes the export routes with full Zod-derived schemas.
|
||||
|
||||
@@ -77,4 +77,4 @@ try {
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The two guards run in a specific order: row count is checked *before* the exporter runs so a runaway provider doesn't burn CPU encoding fields the route layer is about to reject. File size is checked *after* encoding because cell-level transforms and XML/JSON overhead can push a "small" row count over the byte cap. The `default:` branch in `getExporter` is reachable only if a caller bypasses the Zod boundary (the schema enumerates `csv|json|xlsx`); the `format as string` cast in the error message is intentional — we want the actual value the caller passed to surface in logs, not the post-narrowing `never`. `provider.fetch` errors are wrapped rather than thrown raw because the route layer needs to distinguish a *bad provider* (502-class) from a *bad request* (400-class), and the `ExportError` code is the contract that drives that branching upstream.
|
||||
The two guards run in a specific order: row count is checked _before_ the exporter runs so a runaway provider doesn't burn CPU encoding fields the route layer is about to reject. File size is checked _after_ encoding because cell-level transforms and XML/JSON overhead can push a "small" row count over the byte cap. The `default:` branch in `getExporter` is reachable only if a caller bypasses the Zod boundary (the schema enumerates `csv|json|xlsx`); the `format as string` cast in the error message is intentional — we want the actual value the caller passed to surface in logs, not the post-narrowing `never`. `provider.fetch` errors are wrapped rather than thrown raw because the route layer needs to distinguish a _bad provider_ (502-class) from a _bad request_ (400-class), and the `ExportError` code is the contract that drives that branching upstream.
|
||||
|
||||
@@ -41,4 +41,4 @@ providers.set("orders", myOrderProvider);
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The augmentation pattern is identical to `@suaveplan/api-plugin-database`'s `registry-augmentation.ts` — both files exist purely to add slots to the shared registry interface defined by `@suaveplan/api-plugin/service-registry`. Without this file, every call site would have to cast the `getService("exportProviders")` return value, defeating the type safety that the `ServiceRegistry` interface was designed to provide. The two slots cover the two distinct concerns the plugin owns: the *registry* of data providers (writable by every consumer that wants to expose a resource for export) and the *store* of jobs (mutated by the route layer, observable by anyone tracking export progress). Exposing both is a deliberate departure from upstream, which exposed them via `ctx.registerService(...)` at runtime but never declared the corresponding types.
|
||||
The augmentation pattern is identical to `@suaveplan/api-plugin-database`'s `registry-augmentation.ts` — both files exist purely to add slots to the shared registry interface defined by `@suaveplan/api-plugin/service-registry`. Without this file, every call site would have to cast the `getService("exportProviders")` return value, defeating the type safety that the `ServiceRegistry` interface was designed to provide. The two slots cover the two distinct concerns the plugin owns: the _registry_ of data providers (writable by every consumer that wants to expose a resource for export) and the _store_ of jobs (mutated by the route layer, observable by anyone tracking export progress). Exposing both is a deliberate departure from upstream, which exposed them via `ctx.registerService(...)` at runtime but never declared the corresponding types.
|
||||
|
||||
@@ -97,4 +97,3 @@ await api.start();
|
||||
- [`schemas/routes/initiate`](./src/schemas/routes/initiate.md) — chunked-init request/response.
|
||||
- [`schemas/routes/status`](./src/schemas/routes/status.md) — chunk / complete / status / abort schemas.
|
||||
- [`types/types`](./src/types/types.md) — public data model.
|
||||
|
||||
|
||||
@@ -105,4 +105,3 @@ This package is functional-parity with upstream `@suaveplan/api-plugin-graphql`
|
||||
## License
|
||||
|
||||
MIT.
|
||||
|
||||
|
||||
@@ -138,4 +138,3 @@ upstream import success/failure).
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -106,4 +106,3 @@ const service = new ApnService({ config, transport });
|
||||
|
||||
See `src/transport/apn-transport/apn-transport.md` for the full
|
||||
contract.
|
||||
|
||||
|
||||
@@ -157,4 +157,3 @@ const plugin = createPaymentPlugin({
|
||||
- [`./src/otel/instruments.md`](./src/otel/instruments.md) — 5 counters + 1 histogram.
|
||||
- [`./src/error-codes/error-codes.md`](./src/error-codes/error-codes.md) — 15 `PAYMENT_PLUGIN_*` codes.
|
||||
- [`./src/types/types.md`](./src/types/types.md) — `PaymentPluginOptions` / `PaymentPluginConfig`.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# @suaveplan/api-plugin-rate-limit - Rate-limit plugin for @suaveplan/api. Wraps @suaveplan/rate-limiter as a definePlugin descriptor: per-route + per-tenant configuration, parameterised path matcher, IP whitelist, tenant multiplier applied to numeric limit, Hono middleware factory, 429 + Retry-After + X-RateLimit-* / RateLimit-* headers, OTel counters, RFC 7807 Problem+JSON body. Registers `rateLimitService` in the host ServiceRegistry.
|
||||
# @suaveplan/api-plugin-rate-limit - Rate-limit plugin for @suaveplan/api. Wraps @suaveplan/rate-limiter as a definePlugin descriptor: per-route + per-tenant configuration, parameterised path matcher, IP whitelist, tenant multiplier applied to numeric limit, Hono middleware factory, 429 + Retry-After + X-RateLimit-*/ RateLimit-* headers, OTel counters, RFC 7807 Problem+JSON body. Registers `rateLimitService` in the host ServiceRegistry.
|
||||
|
||||
Rate-limit plugin for `@suaveplan/api`. Wraps `@suaveplan/rate-limiter`
|
||||
as a `definePlugin` descriptor exposing per-route + per-tenant
|
||||
@@ -130,4 +130,3 @@ readiness).
|
||||
## Status
|
||||
|
||||
`0.1.0` — initial ship. Surface is final for the 0.1.x line.
|
||||
|
||||
|
||||
@@ -107,10 +107,9 @@ await api.start();
|
||||
|
||||
## Status
|
||||
|
||||
`0.1.0` — first migration wave shipping` with full upstream
|
||||
`0.1.0` — first migration wave shipping`with full upstream
|
||||
parity. Persistent backends (`SearchAdapter` implementations against
|
||||
SurrealDB FTS / Typesense / Meilisearch), tenant scoping, auth gates,
|
||||
rate limiting, and the enterprise-audit §4 enhancements (vector search,
|
||||
synonyms, query DSL, saved searches, learning-to-rank) are tracked as
|
||||
follow-up changes.
|
||||
|
||||
|
||||
@@ -98,4 +98,3 @@ The site plugin ships **zero runtime dependencies** on `@suaveplan/api-plugin-ra
|
||||
- See [`docs/middleware-integration.md`](./docs/middleware-integration.md) for the canonical end-to-end wiring example and the recommended layering order (audit OUTERMOST → rate-limit MIDDLE → site routes INNERMOST).
|
||||
- See [`src/integration/middleware-recommendations.md`](./src/integration/middleware-recommendations.md) for the `MIDDLEWARE_RECOMMENDATIONS` constant exposing recommended rate-limit windows and audit categories per route group.
|
||||
- The pattern is proven by [`src/integration/middleware-composition.integration.test.ts`](./src/integration/middleware-composition.integration.test.ts) — a runnable composition asserting site behaviour, rate-limit headers, and audit event emission.
|
||||
|
||||
|
||||
@@ -106,4 +106,3 @@ This package follows the canonical API-plugin pattern pioneered by `@suaveplan/a
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -118,4 +118,3 @@ const app = defineApp({
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -200,4 +200,3 @@ The plugin emits three counters:
|
||||
Counters bind lazily to the global `MeterProvider`; tests
|
||||
re-bind by calling `resetUserInstruments()` after swapping the
|
||||
provider.
|
||||
|
||||
|
||||
@@ -163,4 +163,3 @@ Operators can:
|
||||
- [`services/registry-augmentation`](./src/services/registry-augmentation.md)
|
||||
— typed `ServiceRegistry` slot.
|
||||
- [`types/types`](./src/types/types.md) — public TS surface.
|
||||
|
||||
|
||||
@@ -118,4 +118,3 @@ Status label values: `delivered`, `exhausted`, `not_found`, `inactive`, `invalid
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ ctx.getService("nope"); // type error — not augmented
|
||||
|
||||
`PLUGIN_CODES` defines ten codes (`PLUGIN.NOT_FOUND`, `PLUGIN.ALREADY_REGISTERED`, `PLUGIN.INIT_FAILED`, `PLUGIN.DEP_MISSING`, `PLUGIN.CIRCULAR_DEPENDENCY`, `PLUGIN.INIT_TIMEOUT`, `PLUGIN.SHUTDOWN_FAILED`, `PLUGIN.INVALID_CONFIG`, `PLUGIN.HOOK_FAILED`, `PLUGIN.ABORTED`). Every code is raised by at least one production call site — the spec-conformance test pins this invariant.
|
||||
|
||||
## design decisions
|
||||
## design decisions
|
||||
|
||||
- **Unified lifecycle runtime** — `PluginRegistry` calls `onRegister`/`onInit`/`onReady`/`onClose` directly; the `HookExecutor` rejects registration of these four names. Upstream's dual-runtime hazard is eliminated by construction.
|
||||
- **`initTimeout`/`shutdownTimeout` are enforced** — every hook runs with an `AbortController`-bound `ctx.signal` that fires when the budget is exceeded.
|
||||
@@ -116,4 +116,3 @@ ctx.getService("nope"); // type error — not augmented
|
||||
- **Topological sort is deterministic** — alphabetical tie-break for equal-in-degree nodes makes boot logs reproducible across runs.
|
||||
- **No `applyMigrations`** — migrations live in `@suaveplan/api-migrations`. The plugin registry knows nothing about databases.
|
||||
- **No ALS singletons** (`getCurrentApp`, etc.). Callers thread `ctx` explicitly.
|
||||
|
||||
|
||||
@@ -98,4 +98,3 @@ bunx --bun turbo lint typecheck test:coverage --filter=@suaveplan/api-rate-limit
|
||||
```
|
||||
|
||||
100% statements/branches/functions/lines (Istanbul).
|
||||
|
||||
|
||||
@@ -82,4 +82,3 @@ Reason label values: `"missing-scope" | "rule-deny" | "no-principal"`.
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -59,4 +59,3 @@ await slot.dispose(draining.slotId, { timeoutMs: 1_000 });
|
||||
## Version
|
||||
|
||||
`0.1.0` — initial Stage-4 ship. Graduates to `1.0.0` when the plugin system as a whole is declared production-ready.
|
||||
|
||||
|
||||
@@ -98,12 +98,12 @@ r.registerPipelineRoute({
|
||||
## API Reference
|
||||
|
||||
- `class PipelineRouteTable` —
|
||||
- `register(reg: HonoPipelineRegistration): void`
|
||||
- `unregister(method: string, path: string): void`
|
||||
- `unregisterPlugin(plugin: string): void`
|
||||
- `list(): readonly HonoPipelineRoute[]`
|
||||
- `match(method: string, url: string): HonoPipelineRoute | null`
|
||||
- `size(): number`
|
||||
- `register(reg: HonoPipelineRegistration): void`
|
||||
- `unregister(method: string, path: string): void`
|
||||
- `unregisterPlugin(plugin: string): void`
|
||||
- `list(): readonly HonoPipelineRoute[]`
|
||||
- `match(method: string, url: string): HonoPipelineRoute | null`
|
||||
- `size(): number`
|
||||
- `interface HonoPipelineRoute` — `{ plugin, method, path, options, handler, params, statusMap }`. Structurally compatible with `@suaveplan/api-types/pipeline-router`'s `PipelineRoute`.
|
||||
- `interface HonoPipelineRegistration` — `{ plugin, method, path, handler, options?, statusMap? }`.
|
||||
- `interface HonoPipelineRouteOptions` — narrow subset of the host's `PipelineRouteOptions`: `auth`, `csrf`, `streaming`, `idempotent`, `runtimeValidation`, `auditable`, `rateLimit`, `request`, `responses`.
|
||||
|
||||
@@ -71,4 +71,3 @@ await slot.dispose(draining.slotId, { timeoutMs: 100 });
|
||||
- Foundation tests that need a real `Router`/`PipelineRouter` without a Hono peer.
|
||||
- Plugin tests that previously hand-rolled their own router shim (`api-router-hono` carries the Hono peer-dep cost).
|
||||
- The default `router: "in-memory"` slot in the Stage-5 `@suaveplan/api-host/testing` adapter table.
|
||||
|
||||
|
||||
@@ -75,4 +75,3 @@ const { dispatch } = createMainBridge(channel.port1 as never);
|
||||
const res = await dispatch(new Request("https://api.local/world"));
|
||||
console.log(await res.text()); // "hi /world"
|
||||
```
|
||||
|
||||
|
||||
@@ -95,4 +95,3 @@ import { runRouterConformance } from "@suaveplan/api-router/testing";
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ values via wrapping responses and inspecting the request shape.
|
||||
list; order is preserved.
|
||||
- `next()` enforcement: a middleware that calls `next()` twice in a row
|
||||
has its dispatch rejected. (Multiple sequential `next()` calls across
|
||||
*different* middlewares are correct; only same-middleware repeats fail.)
|
||||
_different_ middlewares are correct; only same-middleware repeats fail.)
|
||||
- Synchronous return values are accepted (wrapped in `Promise.resolve`).
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
@@ -72,7 +72,7 @@ isolated copies and never accidentally share state across branches. The
|
||||
runtime cost is one object-spread per call; modern V8 inlines this away
|
||||
for short builder chains.
|
||||
|
||||
The `body` setter is typed as a *re-parameterising* method — calling
|
||||
The `body` setter is typed as a _re-parameterising_ method — calling
|
||||
`builder.body<T>(value)` returns a `ResponseBuilder<T>`, picking up the
|
||||
new body type. This lets adapters call `.body(zodValidatedShape)` and the
|
||||
inferred `RouterResponse.body` lines up with the validation output. The
|
||||
|
||||
@@ -104,7 +104,7 @@ keeps the steps-7-8 insertion contract honest: middleware only enters
|
||||
through `use()`, and only at the post-validation boundary when supplied
|
||||
by plugins.
|
||||
|
||||
Collision detection compares the *compiled* path key (`":id"` vs.
|
||||
Collision detection compares the _compiled_ path key (`":id"` vs.
|
||||
`":userId"` both normalise to `:`-form). Adapters that need parameter-name
|
||||
preservation can introspect `RouteMatch.path` for the originally registered
|
||||
form.
|
||||
|
||||
@@ -90,8 +90,8 @@ runPipelineRouterConformance({
|
||||
## API Reference
|
||||
|
||||
- `interface PipelineRouterConformanceFactory` —
|
||||
- `label: string`
|
||||
- `build(): { router: PipelineRouter; register: (reg: PipelineRouteRegistration) => void; reload?: () => void }`
|
||||
- `label: string`
|
||||
- `build(): { router: PipelineRouter; register: (reg: PipelineRouteRegistration) => void; reload?: () => void }`
|
||||
- `interface PipelineRouteRegistration` — `{ plugin, method, path, handler, options?, statusMap? }`.
|
||||
- `function runPipelineRouterConformance(factory): void` — declares one
|
||||
`describe()` block with six required `it()` cases.
|
||||
|
||||
@@ -84,4 +84,3 @@ participant whose `actions[name]` is missing fails at install with
|
||||
`0.1.0` — bottom-up rebuild stage. Will graduate to `1.0.0` per the
|
||||
foundation umbrella's Stage 6 acceptance gate (`a multi-step saga in
|
||||
api-plugin-example runs end-to-end including a compensation path`).
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ router.post("/webhook/saga", async (req) => {
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The emitter validates *outbound* payloads before pushing them onto the
|
||||
The emitter validates _outbound_ payloads before pushing them onto the
|
||||
bus. This is deliberate — `bus.emit` is fire-and-forget; if the
|
||||
emitter trusted the caller and pushed a bad payload, every listener
|
||||
would see corrupt data with no callback to recover. By failing at the
|
||||
|
||||
@@ -96,4 +96,3 @@ if (result.outcome === "deny") {
|
||||
Runtime: `@suaveplan/api-types`, `@suaveplan/error`, `@suaveplan/types`,
|
||||
`@suaveplan/opentelemetry`, `@opentelemetry/api`, `tslib`, `zod`. No
|
||||
`@suaveplan/api-*` runtime beyond `api-types`.
|
||||
|
||||
|
||||
@@ -92,4 +92,3 @@ lifecycle delegation (`LIFECYCLE_FAILED`).
|
||||
the spec mandates: route registration, request round-trip, idempotent
|
||||
lifecycle, error propagation with `cause` chain, and capability
|
||||
passthrough including denied capability surfacing `SAND_PERM.DENIED`.
|
||||
|
||||
|
||||
@@ -89,4 +89,3 @@ import { runVmSandboxConformance } from "@suaveplan/api-sandbox-vm/testing";
|
||||
|
||||
runVmSandboxConformance("my-host", makeMyHandle);
|
||||
```
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# @suaveplan/api-sandbox-wasm-host-bridge - Host bridge wrapping a @suaveplan/api-sandbox-wasm WasmSandboxHandle as a @suaveplan/api-host PluginModule. Validates the WASM ABI surface (__suaveplan_alloc / free / handle_request / routes / start / dispose + memory) at bridge-init, marshals SerializedRequest bytes into linear memory via __suaveplan_alloc, invokes __suaveplan_handle_request, reads the packed (responsePtr, responseLen) result, JSON-decodes the SerializedResponse, and unmarshals it back to a host Response. Traps surface as SAND_WASM_BRIDGE.HANDLER_TRAPPED with classified trapKind; denied capabilities surface as SAND_PERM.DENIED.
|
||||
# @suaveplan/api-sandbox-wasm-host-bridge - Host bridge wrapping a @suaveplan/api-sandbox-wasm WasmSandboxHandle as a @suaveplan/api-host PluginModule. Validates the WASM ABI surface (__suaveplan_alloc / free / handle_request / routes / start / dispose + memory) at bridge-init, marshals SerializedRequest bytes into linear memory via__suaveplan_alloc, invokes __suaveplan_handle_request, reads the packed (responsePtr, responseLen) result, JSON-decodes the SerializedResponse, and unmarshals it back to a host Response. Traps surface as SAND_WASM_BRIDGE.HANDLER_TRAPPED with classified trapKind; denied capabilities surface as SAND_PERM.DENIED.
|
||||
|
||||
Host-integration layer that wraps a `@suaveplan/api-sandbox-wasm` `WasmSandboxHandle` as a `@suaveplan/api-host` `PluginModule`. Sandboxed-tier plugins compiled to WebAssembly export the `__suaveplan_*` ABI surface (`memory`, `__suaveplan_alloc`, `__suaveplan_free`, `__suaveplan_handle_request`, `__suaveplan_routes`, `__suaveplan_start`, `__suaveplan_dispose`); this bridge validates that surface, discovers the guest's routes once at bridge-init time, and emits per-route closures that marshal `SerializedRequest` records through the guest's linear memory.
|
||||
|
||||
|
||||
@@ -15,18 +15,19 @@ The split from `bridge.ts` is structural rather than mechanical: every
|
||||
direct-variant import (`abi-direct/wasm-abi`, `abi-direct/memory`,
|
||||
`abi-direct/marshal`) is referenced from this file only, so a future
|
||||
removal of the direct variant collapses to deleting `bridge-direct.ts`
|
||||
|
||||
+ `abi-direct/` and trimming the dispatcher.
|
||||
|
||||
## Features
|
||||
|
||||
- `buildDirectInstall(options)` — accepts a `DirectVariantOptions`,
|
||||
+ `buildDirectInstall(options)` — accepts a `DirectVariantOptions`,
|
||||
returns the bridge's `SandboxedWasmPluginInstall`.
|
||||
- Trap classification, capability-gate routing through
|
||||
+ Trap classification, capability-gate routing through
|
||||
`SAND_PERM.DENIED`, idempotent lifecycle — preserved exactly from
|
||||
the shipped contract.
|
||||
- Instrument registry plumbed with `abi_variant: "direct-i32-i64"` on
|
||||
+ Instrument registry plumbed with `abi_variant: "direct-i32-i64"` on
|
||||
every counter and histogram emission.
|
||||
- Route closures are pure functions of the manifest + ABI exports;
|
||||
+ Route closures are pure functions of the manifest + ABI exports;
|
||||
re-instantiating the bridge against the same sandbox produces
|
||||
identical `PipelineRoute[]` records.
|
||||
|
||||
@@ -81,7 +82,7 @@ try {
|
||||
Internal entrypoint called by the dispatcher. Public consumers should
|
||||
use `createSandboxedWasmPluginModule` from the package root.
|
||||
|
||||
- `options: DirectVariantOptions` — `sandbox`, `manifest`, optional
|
||||
+ `options: DirectVariantOptions` — `sandbox`, `manifest`, optional
|
||||
`instruments`.
|
||||
|
||||
Throws `SandboxWasmBridgeError` with code `INVALID_PLUGIN_SURFACE` if
|
||||
@@ -89,14 +90,14 @@ ABI validation fails BEFORE any guest call.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The function never touches the sandbox's `instance` directly past
|
||||
+ The function never touches the sandbox's `instance` directly past
|
||||
the ABI validator — the typed `WasmAbiExports` view is the single
|
||||
hand-off point.
|
||||
- `freeBuffer` is called in a `finally` block so the request pointer
|
||||
+ `freeBuffer` is called in a `finally` block so the request pointer
|
||||
is released even when the response unmarshal throws. The response
|
||||
pointer is released BEFORE unmarshal runs because Zod parsing may
|
||||
allocate additional bytes that would shadow the original buffer.
|
||||
- Capability denials surface through the canonical
|
||||
+ Capability denials surface through the canonical
|
||||
`SAND_PERM.DENIED` code, not the bridge-local `CAPABILITY_DENIED`.
|
||||
Operators see one code for "permission denied" regardless of which
|
||||
bridge raised it.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user