castrum 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +322 -59
- package/castrum.linux-x64-gnu.node +0 -0
- package/dist/index.d.ts +3161 -0
- package/dist/index.js +7306 -0
- package/docs/ARCHITECTURE.md +415 -0
- package/docs/BENCHMARKS.md +307 -0
- package/docs/ENVIRONMENT.md +79 -0
- package/docs/GETTING_STARTED.md +209 -0
- package/docs/INGRESS.md +745 -0
- package/docs/REPO_MAP.md +259 -0
- package/docs/adr/0001-two-wire-formats.md +39 -0
- package/docs/adr/0002-zero-dom-fast-schema.md +38 -0
- package/docs/adr/0003-bun-builtins-delegation.md +42 -0
- package/docs/bun-builtins-decision-matrix.md +108 -0
- package/index.ts +51 -8
- package/package.json +73 -20
- package/src/data/json-rows.ts +13 -13
- package/src/ingress/body.ts +149 -0
- package/src/ingress/constants.ts +220 -48
- package/src/ingress/context.ts +136 -0
- package/src/ingress/decode/baked-result.ts +121 -0
- package/src/ingress/decode/fast-result.ts +160 -0
- package/src/ingress/decode/packed-sections.ts +56 -0
- package/src/ingress/decode/result-base.ts +172 -0
- package/src/ingress/errors.ts +72 -0
- package/src/ingress/fast.ts +152 -1021
- package/src/ingress/handlers.ts +563 -1001
- package/src/ingress/headers/baked-templates.ts +102 -0
- package/src/ingress/headers/cors.ts +44 -0
- package/src/ingress/headers/fast-templates.ts +238 -0
- package/src/ingress/headers/hsts.ts +83 -0
- package/src/ingress/health.ts +67 -0
- package/src/ingress/index.ts +130 -405
- package/src/ingress/metrics.ts +132 -0
- package/src/ingress/options.ts +197 -0
- package/src/ingress/packing/gather-raw-headers.ts +75 -0
- package/src/ingress/packing/header-packing.ts +75 -0
- package/src/ingress/packing/input-packer.ts +150 -0
- package/src/ingress/packing/scratch.ts +50 -0
- package/src/ingress/packing/select-headers.ts +96 -0
- package/src/ingress/response/error-bodies.ts +75 -0
- package/src/ingress/response/terminal.ts +59 -0
- package/src/ingress/routes/common.ts +38 -0
- package/src/ingress/routes/delete.ts +14 -0
- package/src/ingress/routes/echo.ts +107 -0
- package/src/ingress/routes/fallback.ts +23 -0
- package/src/ingress/routes/head.ts +37 -0
- package/src/ingress/routes/index.ts +10 -0
- package/src/ingress/routes/json-write.ts +153 -0
- package/src/ingress/routes/options.ts +36 -0
- package/src/ingress/routes/read.ts +45 -0
- package/src/ingress/server-node.ts +376 -0
- package/src/ingress/server.ts +443 -0
- package/src/ingress/shared.ts +132 -0
- package/src/ingress/status.ts +97 -0
- package/src/ingress/types.ts +157 -0
- package/src/integration/batch.ts +58 -0
- package/src/integration/index.ts +29 -0
- package/src/integration/pipeline.ts +217 -0
- package/src/integration/streaming.ts +79 -0
- package/src/integration/websocket.ts +92 -0
- package/src/loader/batch.ts +133 -0
- package/src/loader/cost.ts +104 -0
- package/src/loader/index.ts +930 -0
- package/src/loader/ops.ts +657 -0
- package/src/native/ffi.ts +1541 -0
- package/src/native/index.ts +8 -207
- package/src/native/loader.ts +194 -0
- package/src/native/types.ts +598 -0
- package/src/rust-ffi/addon.ts +10 -0
- package/src/rust-ffi/batch.ts +431 -0
- package/src/rust-ffi/client.ts +88 -0
- package/src/rust-ffi/context.ts +164 -0
- package/src/rust-ffi/index.ts +40 -541
- package/src/rust-ffi/options.ts +113 -0
- package/src/rust-ffi/packed.ts +253 -0
- package/src/rust-ffi/proven.ts +42 -0
- package/src/rust-ffi/scalar/crypto.ts +265 -0
- package/src/rust-ffi/scalar/factories.ts +233 -0
- package/src/rust-ffi/scalar/hashing.ts +72 -0
- package/src/rust-ffi/scalar/http.ts +206 -0
- package/src/rust-ffi/scalar/index.ts +31 -0
- package/src/rust-ffi/scalar/interface.ts +450 -0
- package/src/rust-ffi/scalar/json.ts +45 -0
- package/src/rust-ffi/scalar/payload.ts +124 -0
- package/src/rust-ffi/text.ts +95 -0
- package/src/selection.json +194 -0
- package/src/selection.ts +77 -0
- package/src/shared/adaptive.ts +68 -0
- package/src/shared/bench-classify.ts +209 -0
- package/src/shared/buffer-pool.ts +209 -0
- package/src/shared/bytes.ts +35 -5
- package/src/shared/env.ts +20 -0
- package/src/shared/log.ts +74 -0
- package/src/shared/metrics.ts +247 -0
- package/src/shared/packed.ts +350 -154
- package/src/shared/proven.ts +596 -0
- package/src/shared/request-id.ts +67 -0
- package/src/shared/response.ts +104 -0
- package/src/shared/runtime.ts +34 -0
- package/src/shared/trace.ts +81 -0
- package/src/shared/uuid.ts +31 -0
- package/Cargo.toml +0 -73
- package/bench.ts +0 -3
- package/rust/batch.rs +0 -195
- package/rust/cookie_parser.rs +0 -70
- package/rust/cors.rs +0 -263
- package/rust/hashing.rs +0 -164
- package/rust/headers.rs +0 -234
- package/rust/hmac_sha256.rs +0 -113
- package/rust/http_parser.rs +0 -132
- package/rust/ingress.rs +0 -616
- package/rust/ingress_constants.rs +0 -89
- package/rust/ip_trust.rs +0 -185
- package/rust/json_ops.rs +0 -66
- package/rust/json_patch_ops.rs +0 -25
- package/rust/json_schema.rs +0 -151
- package/rust/json_ser.rs +0 -429
- package/rust/lib.rs +0 -48
- package/rust/method.rs +0 -141
- package/rust/mime_lookup.rs +0 -76
- package/rust/output.rs +0 -238
- package/rust/proxy.rs +0 -90
- package/rust/query_parser.rs +0 -141
- package/rust/random_token.rs +0 -52
- package/rust/rate_limit.rs +0 -203
- package/rust/terminal.rs +0 -95
- package/rust/unit_tests.rs +0 -843
- package/rust/url_codec.rs +0 -133
- package/rust/util.rs +0 -537
- package/rust/validation.rs +0 -107
- package/rust/websocket.rs +0 -23
- package/src/baseline/index.ts +0 -12
- package/src/baseline/tasks/cookie.ts +0 -22
- package/src/baseline/tasks/hashing.ts +0 -18
- package/src/baseline/tasks/hmac.ts +0 -38
- package/src/baseline/tasks/http.ts +0 -62
- package/src/baseline/tasks/json-patch.ts +0 -12
- package/src/baseline/tasks/json.ts +0 -50
- package/src/baseline/tasks/mime.ts +0 -5
- package/src/baseline/tasks/query.ts +0 -14
- package/src/baseline/tasks/token.ts +0 -15
- package/src/baseline/tasks/url.ts +0 -11
- package/src/baseline/tasks/validation.ts +0 -19
- package/src/baseline/tasks/websocket.ts +0 -14
- package/src/bench/assert.ts +0 -70
- package/src/bench/checks.ts +0 -227
- package/src/bench/checksum.ts +0 -27
- package/src/bench/comparisons.ts +0 -70
- package/src/bench/concurrent-worker.ts +0 -135
- package/src/bench/fixtures.ts +0 -233
- package/src/bench/index.ts +0 -2
- package/src/bench/measure.ts +0 -177
- package/src/bench/now.ts +0 -3
- package/src/bench/report.ts +0 -79
- package/src/bench/run.ts +0 -57
- package/src/bench/tasks/complex.ts +0 -232
- package/src/bench/tasks/concurrent.ts +0 -213
- package/src/bench/tasks/cookie.ts +0 -44
- package/src/bench/tasks/hashing.ts +0 -33
- package/src/bench/tasks/hmac.ts +0 -36
- package/src/bench/tasks/http.ts +0 -50
- package/src/bench/tasks/index.ts +0 -52
- package/src/bench/tasks/json-patch.ts +0 -21
- package/src/bench/tasks/json.ts +0 -33
- package/src/bench/tasks/mime.ts +0 -21
- package/src/bench/tasks/query.ts +0 -44
- package/src/bench/tasks/stress.ts +0 -142
- package/src/bench/tasks/token.ts +0 -20
- package/src/bench/tasks/url.ts +0 -34
- package/src/bench/tasks/validation.ts +0 -57
- package/src/bench/tasks/websocket.ts +0 -21
- package/src/bench/types.ts +0 -60
- package/src/ingress/packed-input.ts +0 -59
- package/src/shared/json.ts +0 -18
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# Benchmarks Guide
|
|
2
|
+
|
|
3
|
+
This document describes the benchmark framework, how to run benchmarks, and how to interpret results.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
The benchmark system compares the Rust implementations against pure JavaScript/Bun baseline implementations. It measures throughput (ops/sec), latency (mean/percentiles), and memory allocation. Under Bun the `rust:*` side runs through the PRIMARY `bun:ffi` transport (NAPI is the fallback); the CPU bench warns — or hard-fails with `CASTRUM_BENCH_FFI=1` — if the ffi transport is not live.
|
|
10
|
+
|
|
11
|
+
### Benchmark Types
|
|
12
|
+
|
|
13
|
+
| Type | Description |
|
|
14
|
+
|------|-------------|
|
|
15
|
+
| **Unit benchmarks** | Individual function comparisons (hashing, validation, JSON, etc.) |
|
|
16
|
+
| **HTTP benchmarks** | Full HTTP server comparisons (Bun vs Elysia vs Ingress) |
|
|
17
|
+
| **Batch benchmarks** | Throughput for batch operations |
|
|
18
|
+
| **Stress benchmarks** | Performance under load |
|
|
19
|
+
| **Concurrency benchmarks** | Performance with multiple concurrent requests |
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Running Benchmarks
|
|
24
|
+
|
|
25
|
+
### Unit Benchmarks
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Run all unit benchmarks
|
|
29
|
+
bun bench.ts
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Performance-proven surface & auto-annotated JSDoc
|
|
33
|
+
|
|
34
|
+
The CPU benchmark persists a machine-readable report to `bench/results/cpu/latest.json`
|
|
35
|
+
(+ timestamped snapshots). Three commands consume it; `bun run check:docs` runs all
|
|
36
|
+
three as the one-stop "informed decision" pipeline:
|
|
37
|
+
|
|
38
|
+
| Command | What it does |
|
|
39
|
+
|---------|--------------|
|
|
40
|
+
| `bun run check` | Runs the CPU benchmark and writes `bench/results/cpu/latest.json` (report-only — **never mutates source**). |
|
|
41
|
+
| `bun run check:annotate` | Rewrites the `@performance` / `@deprecated` / `@remarks` JSDoc on the public `rust.*` declarations (`src/rust-ffi/scalar/interface.ts`, `batch.ts`, `client.ts`) from the report. Idempotent — generated lines carry the `[check:annotate]` marker; hand-written JSDoc is preserved. `--dry-run` previews changes. |
|
|
42
|
+
| `bun run check:proven` | Audits the `PROVEN_SURFACE` registry against the report and flags `REGRESSION`, `PARITY-DRIFT`, and `PROMOTABLE`. `--fail` exits 1 on regressions. |
|
|
43
|
+
| `bun run check:docs` | `check` + `check:annotate` + `check:proven` — the full pipeline. |
|
|
44
|
+
|
|
45
|
+
**Automatic annotation (opt-in):** `CASTRUM_BENCH_ANNOTATE=1 bun run check`
|
|
46
|
+
auto-runs the annotator right after the benchmark, so the JSDoc always reflects
|
|
47
|
+
the latest run. It is opt-in because it mutates tracked source files.
|
|
48
|
+
|
|
49
|
+
**How the annotations work (data-driven):**
|
|
50
|
+
|
|
51
|
+
- Each function's **score** is embedded in the `@performance` line, e.g.
|
|
52
|
+
`JSON valid: ~3.2x faster than the JS baseline (8/8 tasks won)` or
|
|
53
|
+
`CRC32: ~4.1x slower than the JS baseline (2/3 tasks failed)`. A function's
|
|
54
|
+
benchmark set is the canonical comparison plus all its `_`-suffixed variants
|
|
55
|
+
(`_large`, `_batch`, `_concurrent_*`, `_stress`, pooled `_into` overloads)
|
|
56
|
+
that no other registry entry claims.
|
|
57
|
+
- Task bands (see `src/shared/bench-classify.ts`): **won** = `ratio >= 1`
|
|
58
|
+
(Rust at least as fast), **failed** = `ratio < 1 / 1.35` (clear loss), the
|
|
59
|
+
middle is a **tie** that counts toward neither.
|
|
60
|
+
- `@deprecated` is **hybrid**:
|
|
61
|
+
- A clear **majority** of a function's benchmarks failing in the latest run
|
|
62
|
+
auto-deprecates it even when the static registry says `proven`/`parity` — a
|
|
63
|
+
`@remarks Auto-deprecated: N/M benchmarks failed ...` note records the drift.
|
|
64
|
+
- A single good run **never** removes an existing `@deprecated` (static
|
|
65
|
+
classifications reflect the shipped release build, not one local run). A
|
|
66
|
+
majority win on a static `not-competitive` function only adds a
|
|
67
|
+
`@remarks Promotion candidate ...` note.
|
|
68
|
+
- Classifications are **build-dependent** (debug/SIMD builds distort ratios). The
|
|
69
|
+
annotator warns when the report carries no `buildFlavor`; confirm on a release
|
|
70
|
+
build (`bun run build`) or local perf build (`bun run build:perf`).
|
|
71
|
+
|
|
72
|
+
### Bun built-ins diagnostic set ("don't reinvent the wheel")
|
|
73
|
+
|
|
74
|
+
`bun run check` also races castrum ops against **Bun's native built-ins**
|
|
75
|
+
(`Bun.hash`, `Bun.password`, `Bun.CryptoHasher`, `Bun.gzipSync`,
|
|
76
|
+
`Bun.randomUUIDv7`) as a diagnostic set:
|
|
77
|
+
|
|
78
|
+
- Task names use the **`diag:`** prefix (not `native:`/`rust:`) so
|
|
79
|
+
`check-proven`'s aggregation (which matches `PROVEN_SURFACE` rustTask names
|
|
80
|
+
and their `_`-suffixed variants) never picks them up — they are informational.
|
|
81
|
+
- Sources: `src/baseline/tasks/bun-builtins.ts` (Bun-only baselines, guarded
|
|
82
|
+
with `typeof Bun` so the module still loads under Node),
|
|
83
|
+
`src/bench/tasks/bun-builtins.ts`, comparisons in `src/bench/comparisons.ts`.
|
|
84
|
+
- **Runtime honesty guard**: a Bun API must be verified to actually execute real
|
|
85
|
+
work before its result is trusted. The first run produced false "wins"
|
|
86
|
+
(brotli 156x, validators 3-5x) because Bun 1.4 has **no**
|
|
87
|
+
`Bun.brotliCompressSync` and **no** `Bun.validators` — those pairs were
|
|
88
|
+
removed, not reported.
|
|
89
|
+
- Results and the keep/delegate/add decisions live in
|
|
90
|
+
`docs/bun-builtins-decision-matrix.md`.
|
|
91
|
+
|
|
92
|
+
### HTTP Benchmarks
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Run all HTTP scenarios
|
|
96
|
+
bun run bench:http
|
|
97
|
+
|
|
98
|
+
# Run specific scenario
|
|
99
|
+
bun run bench:http:smoke # Light load, quick health check
|
|
100
|
+
bun run bench:http:stress # High concurrency, short duration
|
|
101
|
+
bun run bench:http:heavy # Heavy JSON payloads
|
|
102
|
+
bun run bench:http:crud # CRUD operations
|
|
103
|
+
bun run bench:http:spike # Burst traffic patterns
|
|
104
|
+
bun run bench:http:soak # Long duration (endurance)
|
|
105
|
+
bun run bench:http:storm # Very high concurrency burst
|
|
106
|
+
bun run bench:http:boundary # Edge case payloads
|
|
107
|
+
|
|
108
|
+
# Compare specific frameworks
|
|
109
|
+
bun run bench:http:elysia # Run only Elysia vs baseline
|
|
110
|
+
bun run bench:http:ingress # Run only Ingress vs baseline
|
|
111
|
+
bun run bench:http:bun-only # Run only raw Bun vs baseline
|
|
112
|
+
|
|
113
|
+
# Run all heavy scenarios
|
|
114
|
+
bun run bench:http:all-heavy
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## HTTP Benchmark Scenarios
|
|
120
|
+
|
|
121
|
+
Each scenario targets a specific performance aspect:
|
|
122
|
+
|
|
123
|
+
### 01-smoke
|
|
124
|
+
- **Goal**: Quick health check (health, GET, POST mix)
|
|
125
|
+
- **Duration**: ~10 seconds
|
|
126
|
+
- **Concurrency**: up to 50
|
|
127
|
+
|
|
128
|
+
### 02-load
|
|
129
|
+
- **Goal**: Sustained mixed load (warm-up → sustain → cool-down)
|
|
130
|
+
- **Duration**: ~120 seconds
|
|
131
|
+
- **Concurrency**: up to 1,000
|
|
132
|
+
- **Traffic**: GET/POST mix with cookies, health
|
|
133
|
+
|
|
134
|
+
### 03-stress
|
|
135
|
+
- **Goal**: Ramp to maximum throughput
|
|
136
|
+
- **Duration**: ~100 seconds (5 × 20s ramp phases)
|
|
137
|
+
- **Concurrency**: up to 10,000
|
|
138
|
+
|
|
139
|
+
### 04-spike
|
|
140
|
+
- **Goal**: Burst resilience (baseline → spike → recovery)
|
|
141
|
+
- **Duration**: ~100 seconds
|
|
142
|
+
- **Concurrency**: up to 8,000 (spikes up to 5,000 req/s)
|
|
143
|
+
|
|
144
|
+
### 05-soak
|
|
145
|
+
- **Goal**: Endurance/stability
|
|
146
|
+
- **Duration**: ~600 seconds (10 minutes)
|
|
147
|
+
- **Concurrency**: up to 1,000
|
|
148
|
+
|
|
149
|
+
### 06-edge-cases
|
|
150
|
+
- **Goal**: Malformed / edge-case payloads
|
|
151
|
+
- **Duration**: ~30 seconds
|
|
152
|
+
- **Concurrency**: up to 200
|
|
153
|
+
- **Traffic**: invalid JSON, schema failures, 404/405/415/422, Unicode, oversize query
|
|
154
|
+
|
|
155
|
+
### 07-cors-preflight
|
|
156
|
+
- **Goal**: CORS preflight storm (allowed + disallowed origins)
|
|
157
|
+
- **Duration**: ~30 seconds
|
|
158
|
+
- **Concurrency**: up to 500
|
|
159
|
+
|
|
160
|
+
### 08-rate-limit
|
|
161
|
+
- **Goal**: Rate limiting (under limit → over limit → recovery)
|
|
162
|
+
- **Duration**: ~40 seconds
|
|
163
|
+
- **Concurrency**: up to 1,000
|
|
164
|
+
|
|
165
|
+
### 09-large-payload
|
|
166
|
+
- **Goal**: Large payload handling
|
|
167
|
+
- **Duration**: ~30 seconds
|
|
168
|
+
- **Concurrency**: up to 100
|
|
169
|
+
- **Payload**: large binary + large JSON arrays
|
|
170
|
+
|
|
171
|
+
### 10-mixed-realistic
|
|
172
|
+
- **Goal**: Realistic mixed traffic (origins, cookies, auth)
|
|
173
|
+
- **Duration**: ~60 seconds (ramp)
|
|
174
|
+
- **Concurrency**: up to 5,000
|
|
175
|
+
|
|
176
|
+
### 11-concurrent-burst
|
|
177
|
+
- **Goal**: High-concurrency bursts with pauses
|
|
178
|
+
- **Duration**: ~35 seconds
|
|
179
|
+
- **Concurrency**: up to 8,000
|
|
180
|
+
|
|
181
|
+
### 12-slowloris
|
|
182
|
+
- **Goal**: Slow clients
|
|
183
|
+
- **Duration**: ~60 seconds
|
|
184
|
+
- **Concurrency**: up to 500
|
|
185
|
+
|
|
186
|
+
### Heavy JSON scenarios (`13`–`20`)
|
|
187
|
+
|
|
188
|
+
`13-heavy-json-nested` … `20-validation-storm` stress schema validation with nested, array-heavy, wide, CRUD-mix, spike, soak, large-body, and storm payloads. Run them with `bun run bench:http:heavy`.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Interpreting Results
|
|
193
|
+
|
|
194
|
+
### Report Format
|
|
195
|
+
|
|
196
|
+
Results are saved to `bench/results/<framework>/<scenario>.bench.md` and include:
|
|
197
|
+
|
|
198
|
+
```md
|
|
199
|
+
# Benchmark Report: bun / 01-smoke
|
|
200
|
+
|
|
201
|
+
| Metric | Value |
|
|
202
|
+
|--------|-------|
|
|
203
|
+
| Duration | 10.0s |
|
|
204
|
+
| Requests | 125,432 |
|
|
205
|
+
| Throughput | 12,543 req/s |
|
|
206
|
+
| Mean Latency | 0.79ms |
|
|
207
|
+
| p50 Latency | 0.72ms |
|
|
208
|
+
| p95 Latency | 1.13ms |
|
|
209
|
+
| p99 Latency | 2.45ms |
|
|
210
|
+
| Max Latency | 15.67ms |
|
|
211
|
+
| Errors | 0 |
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Comparison Reports
|
|
215
|
+
|
|
216
|
+
When comparing multiple frameworks, differences are expressed as:
|
|
217
|
+
|
|
218
|
+
- **Speedup**: `2.45x` (Rust is 2.45x faster than JS)
|
|
219
|
+
- **Slowdown**: `0.82x` (Rust is 0.82x the speed of JS, i.e., 18% slower)
|
|
220
|
+
- **% Improvement**: `+145%` throughput increase
|
|
221
|
+
|
|
222
|
+
### Key Metrics
|
|
223
|
+
|
|
224
|
+
| Metric | What It Measures |
|
|
225
|
+
|--------|-----------------|
|
|
226
|
+
| **Throughput** | Requests per second — higher is better |
|
|
227
|
+
| **Mean Latency** | Average response time — lower is better |
|
|
228
|
+
| **p50/p95/p99** | Percentile latencies — lower is better |
|
|
229
|
+
| **Max Latency** | Worst-case response time — should be bounded |
|
|
230
|
+
| **Error Rate** | Percentage of failed requests — should be 0% |
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Benchmark Architecture
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
bench.ts
|
|
238
|
+
└─ Loads benchmark tasks from src/bench/tasks/ (30 tasks)
|
|
239
|
+
├── hashing.ts Compare Rust fnv1a64 vs JS TextEncoder + hash
|
|
240
|
+
├── validation.ts Compare Rust validation vs validator library
|
|
241
|
+
├── json.ts Compare Rust sonic-rs vs JSON.parse
|
|
242
|
+
├── http.ts Compare Rust httparse vs manual
|
|
243
|
+
├── complex.ts Combined operations
|
|
244
|
+
├── stress.ts Concurrent load tests
|
|
245
|
+
└── ... (aead, accept, compress, cookie-sign, csrf, encoding, etag,
|
|
246
|
+
form, json-schema, jwt, media-type, multipart, password,
|
|
247
|
+
streaming, template, url-join, ... — full list in docs/REPO_MAP.md)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Each task runs:
|
|
251
|
+
1. **Warmup**: 100ms to JIT-compile JavaScript
|
|
252
|
+
2. **Measurement**: Fixed duration (default 2000ms) in a tight loop
|
|
253
|
+
3. **Operations counted**: Number of function calls completed
|
|
254
|
+
4. **Result**: Ops/sec calculated from wall-clock time
|
|
255
|
+
|
|
256
|
+
### Benchmark controls
|
|
257
|
+
|
|
258
|
+
| Env / flag | Effect |
|
|
259
|
+
|------------|--------|
|
|
260
|
+
| `CASTRUM_BENCH_BATCH_SIZE` | Batch size for sub-µs operations (default `64`). Batching amortizes the timer/measurement overhead for very fast ops. |
|
|
261
|
+
| `CASTRUM_BENCH_ANNOTATE=1` | After `bun run check`, auto-rewrite the `rust.*` JSDoc annotations from the fresh report (see "Performance-proven surface & auto-annotated JSDoc"). |
|
|
262
|
+
| `HTTP_NO_SHAPE=1` | The HTTP load generator (`bench/load.ts`) skips response-shape `JSON.parse` for pure-throughput runs. |
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## HTTP Server Architecture
|
|
267
|
+
|
|
268
|
+
```
|
|
269
|
+
bench/servers/
|
|
270
|
+
├── bun-server.ts # Raw Bun.serve with manual routing
|
|
271
|
+
├── elysia-server.ts # Elysia framework
|
|
272
|
+
├── ingress-server.ts # Ingress (this project) optimized handler
|
|
273
|
+
└── shared.ts # Shared route handlers
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Each server implements the same routes for fair comparison:
|
|
277
|
+
|
|
278
|
+
- `GET /` — Plain text hello
|
|
279
|
+
- `GET /json` — JSON response
|
|
280
|
+
- `POST /json` — Echo JSON body
|
|
281
|
+
- `GET /id/:id` — Path parameter
|
|
282
|
+
- `POST /users` — User creation
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Environment
|
|
287
|
+
|
|
288
|
+
All benchmarks should be run on a dedicated machine for reproducible results:
|
|
289
|
+
|
|
290
|
+
- **CPU**: Modern x86_64 (Intel/AMD) or Apple Silicon
|
|
291
|
+
- **Memory**: 8GB+ RAM
|
|
292
|
+
- **OS**: Linux (recommended) or macOS
|
|
293
|
+
- **Bun**: Latest stable
|
|
294
|
+
- **Rust**: Latest stable (via rustup)
|
|
295
|
+
|
|
296
|
+
### Turbo Mode (Linux)
|
|
297
|
+
|
|
298
|
+
For consistent results on Linux, disable CPU frequency scaling:
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
sudo cpupower frequency-set --governor performance
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Run benchmarks, then restore:
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
sudo cpupower frequency-set --governor powersave
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Environment Variables Reference
|
|
2
|
+
|
|
3
|
+
This document lists every environment variable read by the package and its
|
|
4
|
+
example servers. All values are read once at module load; invalid values are
|
|
5
|
+
warned about and replaced with the documented default.
|
|
6
|
+
|
|
7
|
+
## Ingress server (`bench/servers/ingress-server.ts`)
|
|
8
|
+
|
|
9
|
+
| Variable | Type | Default | Description |
|
|
10
|
+
|----------|------|---------|-------------|
|
|
11
|
+
| `INGRESS_TRUST_PROXY` | bool | `false` | **Opt-in.** When `true`, honors `X-Forwarded-For` / `X-Real-IP` for client-IP resolution. Keep **off** unless the server sits behind a trusted edge that strips these headers, otherwise clients can spoof arbitrary IPs for IP-based rate limiting. |
|
|
12
|
+
| `INGRESS_REQUEST_ID_HEADER` | bool | `false` | Emit an `x-request-id` response header per request. The request ID is always generated internally; this only controls whether it is echoed to clients. |
|
|
13
|
+
| `INGRESS_UNSAFE_ZERO_COPY` | bool | `false` | Enable zero-copy response bodies. **Not recommended** unless per-response output buffers are implemented; leave off for safety (bodies are copied by default). |
|
|
14
|
+
| `INGRESS_OUTPUT_BUF_BYTES` | int | `131072` | Size of the Rust output buffer (min `65536`). If cookies + query + metadata JSON exceed this, `FLAG_BODY_TRUNCATED` is set and the request fails closed. |
|
|
15
|
+
| `INGRESS_SECURITY_HEADERS` | bool | `true` | Emit the precomputed security headers (CSP, HSTS, X-Frame-Options, etc.). Disable only behind an edge that already sets them. |
|
|
16
|
+
| `INGRESS_REUSE_PORT` | bool | `false` | Enable `SO_REUSEPORT` for multi-process deployments (spawn N processes, each with a distinct `INGRESS_WORKER_ID`). |
|
|
17
|
+
| `INGRESS_LOG_REQUESTS` | bool | `false` | Write one JSON line per request to **stderr** (`{ts, kind, requestId, method, path, status, errorCode?, rateLimited?, ...}`). |
|
|
18
|
+
| `INGRESS_METRICS` | bool | `false` | Write a periodic aggregate summary to **stderr** every 10s (`{ts, kind, total, rps, status, rateLimited, internalErrors}`). |
|
|
19
|
+
| `INGRESS_HOST` | string | `0.0.0.0` | Bind address. |
|
|
20
|
+
| `INGRESS_IDLE_TIMEOUT` | int | `30` | Idle keep-alive timeout in seconds (min `1`). |
|
|
21
|
+
| `INGRESS_WORKER_ID` | int | `0` | Worker identifier (0–65535), mixed into request-ID generation to reduce collision risk across processes. |
|
|
22
|
+
|
|
23
|
+
Boolean flags accept `1`/`true`/`yes`/`on` for true and `0`/`false`/`no`/`off`
|
|
24
|
+
for false; anything else is warned about and falls back to the default.
|
|
25
|
+
|
|
26
|
+
## Rust FFI thread pool
|
|
27
|
+
|
|
28
|
+
| Variable | Type | Default | Description |
|
|
29
|
+
|----------|------|---------|-------------|
|
|
30
|
+
| `CASTRUM_RAYON_THREADS` / `RUST_RAYON_THREADS` (legacy `RUST_BENCH_RAYON_THREADS`) | int | `max(1, cores-1)` | Rayon thread pool size. The pool is process-wide and initialized **once** — the first use wins. With lazy init, call `rust.configure({ rayonThreads })` (or set this env) before the first batch operation. |
|
|
31
|
+
| `CASTRUM_MAX_RAYON_THREADS` (legacy `RUST_BENCH_MAX_RAYON_THREADS`) | int | `cores` | Hard cap on rayon threads (native). |
|
|
32
|
+
| `CASTRUM_PIN_CORES` (legacy `RUST_BENCH_PIN_CORES`) | (presence) | — | When set (Linux), pin rayon worker threads to distinct cores. |
|
|
33
|
+
|
|
34
|
+
## Native transport selection (`src/native/ffi.ts`)
|
|
35
|
+
|
|
36
|
+
`bun:ffi` is the PRIMARY transport under Bun; NAPI is the fallback (Node,
|
|
37
|
+
`CASTRUM_FFI_MODE=napi`, or a failed bind-time self-test). `rust.transport()`
|
|
38
|
+
reports the resolved transport (`"ffi"` / `"napi"`); `rust.ffiActive()` reports
|
|
39
|
+
whether the ffi transport is live.
|
|
40
|
+
|
|
41
|
+
| Variable | Type | Default | Description |
|
|
42
|
+
|----------|------|---------|-------------|
|
|
43
|
+
| `CASTRUM_FFI_MODE` (legacy `RUST_FFI_MODE`) | string | `auto` | Native transport selection: `auto` (default) — use `bun:ffi` on Bun, silently fall back to napi on bind/self-test failure (and always on Node); `ffi` — force `bun:ffi` on Bun and THROW if it cannot bind or the self-test fails (for benches/CI that must run the primary transport; invalid on Node); `napi` — never bind, every call goes through the napi addon (exercises the fallback on Bun). |
|
|
44
|
+
|
|
45
|
+
## Native addon loader (`src/native/loader.ts`)
|
|
46
|
+
|
|
47
|
+
| Variable | Type | Default | Description |
|
|
48
|
+
|----------|------|---------|-------------|
|
|
49
|
+
| `CASTRUM_DEBUG` (legacy `RUST_BENCH_DEBUG`) | (presence) | — | Log the resolved addon path and exported keys at load time. |
|
|
50
|
+
| `CASTRUM_NATIVE_LIBRARY_PATH` / `NAPI_RS_NATIVE_LIBRARY_PATH` | path | — | Explicit path (file or directory) to the native `.node` artifact, overriding normal resolution. Useful for bundled/electron layouts. |
|
|
51
|
+
|
|
52
|
+
## Library logging (`src/shared/log.ts`)
|
|
53
|
+
|
|
54
|
+
| Variable | Type | Default | Description |
|
|
55
|
+
|----------|------|---------|-------------|
|
|
56
|
+
| `CASTRUM_LOG_LEVEL` | string | `info` | Threshold for the built-in structured logger (`debug`/`info`/`warn`/`error`/`silent`). Enabled per handler via the `structuredLog: true` runtime option; emits one JSON line per request/error to stderr (`{ts, level, event, requestId, method, path, status, durationMs, ...}`). |
|
|
57
|
+
|
|
58
|
+
## Security guidance
|
|
59
|
+
|
|
60
|
+
- **Proxy trust**: `trustProxy: true` is deprecated — it trusts EVERY hop of
|
|
61
|
+
`X-Forwarded-For`/`X-Real-IP`, so clients can forge their IP and bypass IP-based
|
|
62
|
+
rate limiting. Prefer `trustedProxies: { enabled: true, networks: [...] }` and only
|
|
63
|
+
enable proxy trust behind a trusted edge that strips these headers.
|
|
64
|
+
- **Rate limiting is per-process, not distributed**: with N worker processes
|
|
65
|
+
(`reusePort`), an IP's effective limit is multiplied by N. The shared-limiter config
|
|
66
|
+
registry is bounded (LRU, max 16 distinct configs) to prevent unbounded memory growth.
|
|
67
|
+
- **Request bodies**: `createIngressServer` applies a server-level `maxRequestBodySize`
|
|
68
|
+
default of 16 MiB, and JSON-write routes stream-read with `maxBodyBytes` enforced
|
|
69
|
+
while reading (an oversized body is rejected as soon as the limit is crossed, never
|
|
70
|
+
fully buffered). Route/async handlers apply a non-zero body-read deadline by default
|
|
71
|
+
(`bodyTimeoutMs`, default **30 s** — set `bodyTimeoutMs: 0` to disable) that guards
|
|
72
|
+
against slowloris/trickling bodies.
|
|
73
|
+
- **Secrets (JWT/AEAD)**: keys and secrets are caller-owned buffers; treat them as
|
|
74
|
+
sensitive (never log them, rotate regularly). The AEAD **batch** APIs derive a unique
|
|
75
|
+
nonce per item, but callers MUST use a fresh base nonce for every batch call — never
|
|
76
|
+
reuse a base nonce with the same key.
|
|
77
|
+
- **Multipart**: `multipart_parse` accepts an optional `limits` object
|
|
78
|
+
(`max_parts`, `max_field_count`, `max_part_bytes`, `max_total_bytes`) with generous
|
|
79
|
+
defaults to bound worst-case parse cost.
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Getting Started with castrum
|
|
2
|
+
|
|
3
|
+
A walkthrough for a first-time contributor or intern. By the end you'll know
|
|
4
|
+
how to import the package, use the `rust.*` primitives, stand up an HTTP
|
|
5
|
+
server, run the benchmarks, and run the test suite. For the full file map see
|
|
6
|
+
[`REPO_MAP.md`](./REPO_MAP.md).
|
|
7
|
+
|
|
8
|
+
## Prerequisites
|
|
9
|
+
|
|
10
|
+
- [Bun](https://bun.sh) ≥ 1.1 (primary runtime)
|
|
11
|
+
- [Rust](https://rustup.rs) — stable + `cargo` (to build/test the addon). The
|
|
12
|
+
repo pins a toolchain in `rust-toolchain.toml` (`channel = "1.97.1"`); install
|
|
13
|
+
it with `rustup` so local builds match CI.
|
|
14
|
+
- Node.js ≥ 20.3 (optional — only for the Node compatibility path)
|
|
15
|
+
|
|
16
|
+
> **Building on different OSes**: `bun run build` (napi) works on Linux, macOS,
|
|
17
|
+
> and Windows (WSL recommended on Windows). `bun run build:perf` shells out to
|
|
18
|
+
> `bash scripts/build-perf.sh` and is **Linux-only** — it is a local max-perf
|
|
19
|
+
> build, never for publish. If `bash` is unavailable, use plain `bun run build`.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
bun install
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 1. Import the package
|
|
26
|
+
|
|
27
|
+
The entry point is `index.ts` (Bun resolves it via the `bun` export condition).
|
|
28
|
+
Node users get the compiled `dist/index.js`.
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { rust } from "castrum";
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 2. Use the Rust-accelerated primitives
|
|
35
|
+
|
|
36
|
+
The flat `rust.*` namespace wraps every native function:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { rust } from "castrum";
|
|
40
|
+
|
|
41
|
+
rust.crc32(new TextEncoder().encode("hello")); // number
|
|
42
|
+
rust.fnv1a64(bytes); // bigint
|
|
43
|
+
rust.jsonValid(bytes); // boolean
|
|
44
|
+
rust.validateEmail(emailBytes); // boolean
|
|
45
|
+
|
|
46
|
+
// Text helpers (string in / string out)
|
|
47
|
+
rust.text.mimeFromExtension(".js"); // "text/javascript"
|
|
48
|
+
rust.text.urlEncode("a b"); // "a%20b"
|
|
49
|
+
|
|
50
|
+
// Batch helpers (array of byte arrays → packed bitset/count)
|
|
51
|
+
rust.batch.jsonValid([docA, docB, docC]); // Uint8Array bitset
|
|
52
|
+
rust.batch.fnv1a64([a, b]); // BigUint64Array
|
|
53
|
+
rust.batch.etag([a, b]); // Uint8Array[]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Most functions accept `Uint8Array` (or strings via the `text.` namespace).
|
|
57
|
+
Everything is **synchronous** — there is no async/await in the FFI surface.
|
|
58
|
+
|
|
59
|
+
> **Transport**: under Bun these calls run through `bun:ffi` (the PRIMARY
|
|
60
|
+
> transport — see `rust.transport()` / `rust.ffiActive()`); Node and any
|
|
61
|
+
> `CASTRUM_FFI_MODE=napi` fall back to the napi addon. Surfaces with no C-ABI
|
|
62
|
+
> export (instances, `rust.batch.*`/`packed.*`, `text.*`, structured scalars)
|
|
63
|
+
> always use the napi addon.
|
|
64
|
+
|
|
65
|
+
> **Tip**: `rust` is a singleton created with sensible defaults. If you need
|
|
66
|
+
> isolated state (own rayon threads, own HMAC cache), call
|
|
67
|
+
> `createRust({ rayonThreads: 4 })`.
|
|
68
|
+
|
|
69
|
+
### Higher-order data loader (`loader`)
|
|
70
|
+
|
|
71
|
+
For workloads that call the same op many times, `loader` gives you a hot,
|
|
72
|
+
pre-bound function with automatic bulk-vs-scalar dispatch, DataLoader-style
|
|
73
|
+
coalescing and a bounded LRU cache — over **44 ops** including paired and
|
|
74
|
+
schema-validator ops:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import { loader } from "castrum";
|
|
78
|
+
|
|
79
|
+
const isEmail = loader("validateEmail");
|
|
80
|
+
isEmail(emailBytes); // scalar → boolean
|
|
81
|
+
isEmail([a, b, c]); // bulk → ONE packed call
|
|
82
|
+
await isEmail.load(a); // coalesced + cached
|
|
83
|
+
|
|
84
|
+
loader.run("jsonPatch", docs, patches); // paired ops (companion array)
|
|
85
|
+
loader.schema(validator)(docs); // schema bitset (+ .count())
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
See the README "Higher-order data loader" section for the full op table and
|
|
89
|
+
`createLoader` options.
|
|
90
|
+
|
|
91
|
+
## 3. The `proven` performance surface
|
|
92
|
+
|
|
93
|
+
Not every `rust.*` function beats the JS baseline on every CPU. `proven`
|
|
94
|
+
exposes the **full** `rust.*` surface, and each function's JSDoc records its
|
|
95
|
+
measured performance vs the JS baseline — functions that are slower carry
|
|
96
|
+
`@deprecated`:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { proven, PROVEN_SURFACE } from "castrum";
|
|
100
|
+
|
|
101
|
+
proven.fnv1a64(bytes); // JSDoc: ~11x faster than the JS baseline
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`PROVEN_SURFACE` is the registry (pure data); `scripts/check-proven.ts`
|
|
105
|
+
audits it against the CPU benchmark so it can't drift, and
|
|
106
|
+
`scripts/annotate-performance.ts` (`bun run check:annotate`) refreshes the
|
|
107
|
+
per-function JSDoc from the latest report.
|
|
108
|
+
|
|
109
|
+
## 4. Stand up an HTTP server (the ingress pipeline)
|
|
110
|
+
|
|
111
|
+
The quickest path is the **pre-baked** route handlers + the server builder:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { createIngressHandler, createIngressServer } from "castrum";
|
|
115
|
+
|
|
116
|
+
const ingress = createIngressHandler({
|
|
117
|
+
parseCookies: true,
|
|
118
|
+
parseQuery: true,
|
|
119
|
+
cors: { allowOrigin: ["https://app.example.com"] },
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// createIngressServer is SYNCHRONOUS — it wraps Bun.serve over the routes.
|
|
123
|
+
// Each route maps HTTP methods to the pre-baked handlers (see below):
|
|
124
|
+
const server = createIngressServer({
|
|
125
|
+
port: 3000,
|
|
126
|
+
routes: {
|
|
127
|
+
"/health": { read: ingress }, // GET + HEAD
|
|
128
|
+
"/api/users": { read: ingress, write: ingress }, // + POST/PUT/PATCH (+OPTIONS)
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
console.log("listening on", server.port);
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
- `read: ingress` — GET (+ HEAD) returns the ingress body JSON.
|
|
136
|
+
- `write: ingress` — POST/PUT/PATCH validates + echoes JSON.
|
|
137
|
+
- `echo: ingress`, `cookies: ingress`, and a top-level `fallback` are the other
|
|
138
|
+
route options. The underlying factories (`readHandler`, `jsonWriteHandler`,
|
|
139
|
+
`echoHandler`, `headHandler`, `fallbackHandler`) are also exported directly
|
|
140
|
+
if you'd rather wire methods by hand.
|
|
141
|
+
- **Bun-only**: `createIngressServer`. **Node**: use `createIngressServerNode`
|
|
142
|
+
with the same `routes` spec (and `await srv.ready` for the port).
|
|
143
|
+
|
|
144
|
+
The wire format is `{"ok":true,...,"requestId":...}` on success and
|
|
145
|
+
`{"ok":false,"error":{code,message}}` on errors, with `ratelimit-*` headers.
|
|
146
|
+
|
|
147
|
+
> There is also a lower-level **fast path** (`createIngressFast` +
|
|
148
|
+
> `createIngressSync`/`createIngress`). Prefer it only when you need the
|
|
149
|
+
> absolute lowest overhead and are comfortable calling `run()` yourself with a
|
|
150
|
+
> synchronous callback.
|
|
151
|
+
|
|
152
|
+
## 5. Run the benchmarks
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# CPU benchmark (correctness checks + comparisons) — also gates `proven`
|
|
156
|
+
bun run check
|
|
157
|
+
|
|
158
|
+
# HTTP benchmark across all servers (bun / elysia / ingress)
|
|
159
|
+
bun run bench:http
|
|
160
|
+
|
|
161
|
+
# Fast HTTP smoke (the CI wire-format guard)
|
|
162
|
+
bun run bench:http:smoke
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Results land in `bench/results/` (gitignored).
|
|
166
|
+
|
|
167
|
+
## 6. Run the tests
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
# Everything at once (TS unit + Rust unit + typecheck + lint)
|
|
171
|
+
bun run test:all
|
|
172
|
+
|
|
173
|
+
bun test # TypeScript unit tests (test/unit/**)
|
|
174
|
+
bun run test:rust # Rust unit tests (cargo test)
|
|
175
|
+
bun run typecheck # TypeScript typecheck
|
|
176
|
+
bun run lint:ci # Biome lint gate (what CI enforces)
|
|
177
|
+
bun run test:node # Node.js integration tests (node --test) — run `bun run build:js` first
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## 7. Make your first change
|
|
181
|
+
|
|
182
|
+
1. Find where the code lives with [`REPO_MAP.md`](./REPO_MAP.md).
|
|
183
|
+
2. Edit the TypeScript (`src/`) and/or Rust (`rust/`).
|
|
184
|
+
3. If you changed Rust, rebuild the addon: `bun run build:debug`.
|
|
185
|
+
4. Run `bun test`, `bun run typecheck`, and `cargo test`.
|
|
186
|
+
5. If you touched any server or `handlers.ts`, run `bun run bench:http:smoke`.
|
|
187
|
+
6. If you changed a public function, update the `PROVEN_SURFACE` registry
|
|
188
|
+
(`src/shared/proven.ts`) and run `bun run check:proven:fail` on a release
|
|
189
|
+
build (`bun run build`).
|
|
190
|
+
7. Format + lint: `bun run format` and `bun run lint`.
|
|
191
|
+
|
|
192
|
+
## Common gotchas
|
|
193
|
+
|
|
194
|
+
- The **two ingress paths** have different wire formats — don't unify them
|
|
195
|
+
(see REPO_MAP §4.2 and AGENTS.md).
|
|
196
|
+
- `tsconfig.json` typechecks `index.ts`, `bench.ts`, and `src/` (so `bench/` IS
|
|
197
|
+
covered by `bun run typecheck`); `test/` + `bench/` are covered by
|
|
198
|
+
`bun run typecheck:test` (`-p tsconfig.test.json`).
|
|
199
|
+
- `src/ingress/constants.ts` dlopens the addon at import time — that's
|
|
200
|
+
intentional.
|
|
201
|
+
- The native addon must be rebuilt (`bun run build:debug`) before tests that
|
|
202
|
+
touch Rust changes will see them.
|
|
203
|
+
|
|
204
|
+
## Next steps
|
|
205
|
+
|
|
206
|
+
- [`REPO_MAP.md`](./REPO_MAP.md) — where everything is and how it connects
|
|
207
|
+
- [`INGRESS.md`](./INGRESS.md) — the full pre-baked ingress API
|
|
208
|
+
- [`ARCHITECTURE.md`](./ARCHITECTURE.md) — deep-dive internals
|
|
209
|
+
- [`AGENTS.md`](../AGENTS.md) — agent/contributor guidance
|