Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
65c668c
docs(design): data-protection satellites design bundle (crypto/vault/…
Arcoders Jul 4, 2026
a3b7f25
feat(core,crypto): field-encryption vertical slice — sealV2WithKey se…
Arcoders Jul 4, 2026
e0af9c5
feat(crypto): crypto-shred (I6) + governance erasability gate + two-p…
Arcoders Jul 4, 2026
7ab3dd3
feat(crypto): structural invariant guards (I2/I6/I7/I10) + mark packa…
Arcoders Jul 4, 2026
68ea48f
feat(core,crypto): shared WORM ledger writer — wire crypto shred's tw…
Arcoders Jul 4, 2026
e20b01d
feat(crypto): blind-index search HMAC (I5 mechanism) + guard-5
Arcoders Jul 4, 2026
08a8674
feat(crypto): EncryptedRepository — explicit field-encryption facade …
Arcoders Jul 4, 2026
4ba7d75
test(crypto): real-Postgres integration suite (I4/I6/I7/I10 + WORM + …
Arcoders Jul 4, 2026
3689652
feat(crypto): @encrypted/@searchable model decorators (I5 ergonomic s…
Arcoders Jul 4, 2026
ec04b79
fix(crypto): decorator read/write correctness from adversarial review…
Arcoders Jul 4, 2026
bd5ee44
feat(crypto): key-lifecycle commands — tenant:crypto:rekek (I8) + shr…
Arcoders Jul 4, 2026
bed74e5
feat(crypto): close T5 with a DB-level ciphertext CHECK + structural …
Arcoders Jul 4, 2026
30a9b71
feat(crypto): structural guards invariant-4 (domain separation) + inv…
Arcoders Jul 4, 2026
6c80b2f
feat(crypto): isthmus guard.crypto_* registry + emission (§7) and har…
Arcoders Jul 4, 2026
3ecaf39
feat(crypto): rowscope placement for the wrapped-DEK store (I10) — sc…
Arcoders Jul 4, 2026
7545bff
feat(crypto): graduate the crypto satellite to release candidate (doc…
Arcoders Jul 4, 2026
d046c35
docs(release-notes): pgvector extension lands in a dedicated extensio…
Arcoders Jul 4, 2026
02ef5e1
feat(crypto): enterprise hardening pass — correctness, KMS extensibil…
Arcoders Jul 5, 2026
6ed20e7
test(core): scope no_lenient_decrypt_on_secrets to bare encrypt/decry…
Arcoders Jul 7, 2026
951709e
feat(core): plugin platform Lote A — definePlugin facade + request-pa…
Arcoders Jul 7, 2026
07e218a
refactor(reporting,template): adopt definePlugin and widen the ABI-bo…
Arcoders Jul 7, 2026
83eebc9
test(core): E4/E6 plugin fail-closed matrix + hostile-identifier fuzz…
Arcoders Jul 7, 2026
2ca4b26
feat(core): E8 typed section builders for the plugin surface
Arcoders Jul 7, 2026
1edf8b7
test(core): E7 per-entry api-extractor goldens + /plugin surface hygi…
Arcoders Jul 7, 2026
7faa625
test(crypto): rename shadowed local in worm_ledger spec (no-shadow)
Arcoders Jul 7, 2026
4ed15d8
feat(core): S-0 plugin execution scope primitive (Lote S foundation)
Arcoders Jul 7, 2026
057a581
feat(core): S-1a plugin permission model (declare + consent foundation)
Arcoders Jul 7, 2026
474680f
feat(core): S-1b permission coherence guard + fail-closed install con…
Arcoders Jul 7, 2026
c970ebb
docs(core): S-1c plugin permissions + install consent (closes S-1)
Arcoders Jul 7, 2026
4b2b19b
feat(core): S-2 supply-chain — native-addon install gate + lasagna:he…
Arcoders Jul 7, 2026
4b7c988
feat(core): S-3a read-only routing for untrusted plugins (adapter + c…
Arcoders Jul 7, 2026
8109d66
test(core): S-3b plugin_ro role + read-only firewall red-team (CI-pro…
Arcoders Jul 7, 2026
22b7501
feat(core): S-4 worker sandbox machinery + native-addon boot guard
Arcoders Jul 8, 2026
42450a0
feat(core): S-5 in-process friction — core-access proxies + capabilit…
Arcoders Jul 8, 2026
1dd983d
feat(core): S-6 plugin:doctor + plugin_extension_identifier guard + t…
Arcoders Jul 8, 2026
3758dc0
feat(core): Lote B SEAM-1 — tenant scheduler (definePlugin schedules)
Arcoders Jul 8, 2026
b978678
feat(core): Lote B SEAM-7 — generic provisionExtension() + definePlug…
Arcoders Jul 8, 2026
32fb585
feat(core): Lote C SEAM-5 — data-change hooks (TracksDataChanges mixi…
Arcoders Jul 8, 2026
de614f8
test(core): Lote B/C integration specs (real PG) + satellite-template…
Arcoders Jul 8, 2026
573765e
ci: TEMP — run full CI on crypto-satellite for the plugin-platform re…
Arcoders Jul 8, 2026
b4791ec
ci: wire crypto satellite V8 (unit+integration) into the merged cover…
Arcoders Jul 8, 2026
ed5dadd
fix(security): Wave 0 fail-loud correctness hardening
Arcoders Jul 8, 2026
c1a8fd4
fix(core,ai,crypto,billing,admin): Wave 1 — config-derive the backoff…
Arcoders Jul 8, 2026
b6d83e5
feat(sdk,scripts): Wave 2 — fleet contract alignment (kill phantom fi…
Arcoders Jul 8, 2026
096a30b
refactor(providers,sdk,scripts): Wave 3 — migrate the whole fleet to …
Arcoders Jul 8, 2026
226ac1c
fix(examples): pin the demo Postgres to the pgvector image
Arcoders Jul 8, 2026
c489f9a
refactor(core,ai,crypto,backup): Wave 4 — architecture dedup + /inter…
Arcoders Jul 8, 2026
af78f41
test(satellite-test-kit,crypto,ai): Wave 5 — pin the integration DDL …
Arcoders Jul 8, 2026
4e37973
test(crypto,core,ai,test-kit,examples): Wave 6 (part 1) — real-env cr…
Arcoders Jul 9, 2026
1ab6aeb
test(crypto,core,examples): Wave 6 (part 2) — perf/docs-integrity + g…
Arcoders Jul 9, 2026
2ad205e
fix(core,security): require exact "true" for the SSRF/live-key securi…
Arcoders Jul 9, 2026
317c906
docs(crypto): honest backup limit — correct the shred docstring + add…
Arcoders Jul 9, 2026
aaf830a
test(billing): recalibrate the unit coverage floor to the post-W1 bas…
Arcoders Jul 9, 2026
10f1e43
refactor: Wave 7 — repo-wide TS strictness + docs finish
Arcoders Jul 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 6 additions & 0 deletions .github/SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,9 @@ Reports against the core isolation guarantees (cross-tenant read or write
leakage, resolver hijack, fail-open under dependency outage) are highest
priority. See the [stability matrix](https://arcoders.github.io/Adonisjs-lasagna-saas-tenancy/reference/stability)
for the per-feature labels.

An installed plugin/satellite runs in-process with full privilege by design, so an
**in-process plugin sandbox escape is not a goal** — the in-process trust controls are
friction, and the hard boundary for an untrusted plugin's write is the read-only
Postgres role (S3). The [plugin platform trust boundary](https://arcoders.github.io/Adonisjs-lasagna-saas-tenancy/guides/security#plugin-platform-trust-boundary)
in the security guide states the five-layer model and what each layer does and does not contain.
88 changes: 81 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ name: CI

on:
push:
# TEMP (WS-AI 1.0 branch review): run the full CI matrix on this feature
# branch so the whole suite is exercised on the branch before the single 1.0
# PR is opened. REVERT this extra branch entry before merging to master.
branches: [master, main, LASAGNA-020726/ws-ai-3-vector-store]
# TEMP (plugin-platform A→S→B→C branch review): run the full CI matrix on the
# crypto-satellite branch so the whole plugin-platform stack is exercised on the
# branch before merging. REVERT this extra branch entry (crypto-satellite) before
# merging to master. (The ws-ai-3 entry is a stale leftover from an earlier review.)
branches: [master, main, LASAGNA-020726/ws-ai-3-vector-store, LASAGNA-040726/crypto-satellite]
pull_request:
branches: [master, main]

Expand Down Expand Up @@ -55,8 +56,8 @@ jobs:
- name: Build (core)
run: npm run build

- name: Build satellite packages (sso, billing, admin, backup, websockets, reporting, ai)
run: npm run build:sso && npm run build:billing && npm run build:admin && npm run build:backup && npm run build:websockets && npm run build:reporting && npm run build:ai
- name: Build satellite packages (sso, billing, admin, backup, websockets, reporting, ai, crypto)
run: npm run build:sso && npm run build:billing && npm run build:admin && npm run build:backup && npm run build:websockets && npm run build:reporting && npm run build:ai && npm run build:crypto

# The dev-only satellite-test-kit is imported by core's bin/test.integration.ts
# (and each satellite's), which core's tsconfig typechecks. Build it before the
Expand Down Expand Up @@ -203,6 +204,7 @@ jobs:
npm run test:coverage --workspace @adonisjs-lasagna/websockets
npm run test:coverage --workspace @adonisjs-lasagna/reporting
npm run test:coverage --workspace @adonisjs-lasagna/ai
npm run test:coverage --workspace @adonisjs-lasagna/crypto

# Satellite ABI compatibility (B5): the reference third-party satellite
# is built + tested against the freshly-built core above. Its typecheck
Expand Down Expand Up @@ -294,6 +296,14 @@ jobs:
path: coverage/.v8/ai-unit
if-no-files-found: warn

- name: Upload raw satellite unit coverage (V8) — crypto
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: c8-raw-crypto-unit
path: coverage/.v8/crypto-unit
if-no-files-found: warn

# Full report (unused exports / orphaned files / deps) stays informational.
- name: Knip (unused-code report)
run: npm run knip
Expand Down Expand Up @@ -322,7 +332,7 @@ jobs:
- name: Package contracts (publint + types resolution)
run: |
set -e
for pkg in core sso billing admin backup websockets reporting ai; do
for pkg in core sso billing admin backup websockets reporting ai crypto; do
echo "== @adonisjs-lasagna/$pkg =="
( cd "packages/$pkg" \
&& npx -y publint@0.3.21 \
Expand Down Expand Up @@ -462,6 +472,17 @@ jobs:
# fixture's `rls_probe` connection picks these up.
RLS_DB_USER: rls_ci
RLS_DB_PASSWORD: rls_ci
# Least-privilege SELECT-only role for the S3 plugin read-only proof. The
# fixture's `plugin_ro` connection picks these up; the role is created below
# with `default_transaction_read_only = on` so a write is denied by Postgres.
PLUGIN_RO_DB_USER: plugin_ro
PLUGIN_RO_DB_PASSWORD: plugin_ro
# This job HAS a real Postgres service, so the crypto/AI real-PG proofs are
# mandatory here: they must EXECUTE, never self-skip. The satellite real-PG
# helpers read this flag and turn a would-be self-skip (PG unreachable, role
# lacks CREATEDB) into a hard failure — the fail-loud twin of RLS_DB_USER,
# so a broken/hardened runner can't ship the crypto crown-jewel proofs green.
REQUIRE_REAL_PG: '1'

steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
Expand Down Expand Up @@ -555,6 +576,27 @@ jobs:
GRANT USAGE ON SCHEMA public TO rls_ci;
SQL

# Least-privilege SELECT-only role for the S3 read-only firewall proof. It
# is NOSUPERUSER NOBYPASSRLS and — the point — `default_transaction_read_only
# = on`, so a write is denied by Postgres even when the role is granted
# INSERT. The fixture's `plugin_ro` connection authenticates as it (via
# PLUGIN_RO_DB_USER/PLUGIN_RO_DB_PASSWORD); the proof self-skips when the
# connection resolves to a writable role (the local default).
- name: Create least-privilege plugin read-only role
env:
PGPASSWORD: postgres
run: |
psql -h 127.0.0.1 -U postgres -d multitenancy_test -v ON_ERROR_STOP=1 <<'SQL'
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'plugin_ro') THEN
CREATE ROLE plugin_ro LOGIN PASSWORD 'plugin_ro' NOSUPERUSER NOBYPASSRLS;
END IF;
END $$;
GRANT CONNECT ON DATABASE multitenancy_test TO plugin_ro;
GRANT USAGE ON SCHEMA public TO plugin_ro;
ALTER ROLE plugin_ro SET default_transaction_read_only = on;
SQL

- name: Test (integration) + coverage
run: npm run test:integration:coverage

Expand Down Expand Up @@ -613,6 +655,14 @@ jobs:
- name: Test (satellite integration) + coverage — ai
run: npm run test:integration:coverage --workspace @adonisjs-lasagna/ai

# crypto's integration tier: the wrapped-DEK store + shred + WORM ledger + the
# @encrypted/@searchable decorators against real Postgres via the kit, across
# every placement (schema-pg, a real second database for database-pg, and the
# shared rowscope table with its RLS stub). The database-pg + rowscope specs
# self-skip if the CI role cannot CREATEDB / set the RLS GUC.
- name: Test (satellite integration) + coverage — crypto
run: npm run test:integration:coverage --workspace @adonisjs-lasagna/crypto

# Consumer canary: boot the shared harness from a fresh satellite (the
# reference template) against core's fixture. A kit change that breaks a
# real consumer fails here on an isolated, fast signal instead of buried in
Expand Down Expand Up @@ -703,6 +753,18 @@ jobs:
path: coverage/.v8/ai-integration
if-no-files-found: warn

# crypto integration V8 (written by test:integration:coverage to
# coverage/.v8/crypto-integration in the "— crypto" step above). The
# coverage-report job merges this with crypto's unit V8 so
# check-satellite-coverage.mjs gates a real per-satellite MERGED number.
- name: Upload raw satellite integration coverage (V8) — crypto
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: c8-raw-crypto-integration
path: coverage/.v8/crypto-integration
if-no-files-found: warn

test-e2e-demo:
name: E2E (demo app)
runs-on: ubuntu-latest
Expand Down Expand Up @@ -1118,6 +1180,12 @@ jobs:
name: c8-raw-ai-integration
path: coverage/.v8/all

- name: Download raw satellite integration coverage (V8) — crypto
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: c8-raw-crypto-integration
path: coverage/.v8/all

# websockets integration V8 is produced by the test-e2e-websockets job (a
# different job than test-integration above), now run under c8. This is why
# test-e2e-websockets is in this job's needs:.
Expand Down Expand Up @@ -1173,6 +1241,12 @@ jobs:
name: c8-raw-ai-unit
path: coverage/.v8/all

- name: Download raw satellite unit coverage (V8) — crypto
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: c8-raw-crypto-unit
path: coverage/.v8/all

- name: Aggregate coverage report (unit + integration, remapped to src)
run: npm run coverage:report

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,4 @@ jobs:
publish_pkg "packages/reporting"
publish_pkg "packages/websockets"
publish_pkg "packages/ai"
publish_pkg "packages/crypto"
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ packages/*/temp/
# publish the docs.
packages/ai/ARCHITECTURE.md
packages/ai/SEAMS.md

# Data-protection satellite ARCHITECTURE docs are kept local (the design bundle in design/ is the tracked source).
packages/crypto/ARCHITECTURE.md
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ and runs the full e2e suite against it.
| **Schema isolation** | Each tenant gets its own `tenant_<uuid>` PostgreSQL schema, provisioned and routed automatically. |
| **Circuit breaker** | Opossum wraps every tenant DB call; OPEN state is restored from Redis on restart so a known-down tenant DB fails fast across deploys. One bad schema can't take down the others. |
| **Dependency resilience** | Per-dependency fail-open/fail-closed degradation policy via `ResilienceService`. Emits `DependencyDegraded` for alerting and returns a typed 503 (`DependencyUnavailableException`) when fail-closed. |
| **Lifecycle hooks + 29 typed events** | Declarative `before` / `after` hooks wired into commands and jobs. 19 core (tenant / quota / maintenance / resilience / metrics / guard-audit lifecycle) + 10 billing. |
| **Lifecycle hooks + 30 typed events** | Declarative `before` / `after` hooks wired into commands and jobs. 20 core (tenant / quota / maintenance / resilience / metrics / data-change / guard-audit lifecycle) + 10 billing. |
| **Contextual logging** | `tenantId` rides along through HTTP and queue jobs via `AsyncLocalStorage`. |
| **`tenant:doctor`** | Ten built-in checks (plus `backup_recency` and `backup_encryption` when the backup satellite is installed), `--fix` for auto-recovery, `--json` for CI, `--watch` for a live TUI. |
| **Plans and quotas** | Declarative plans, rolling counters, snapshot usage, an `enforceQuota()` middleware that returns 429 and emits `TenantQuotaExceeded`. |
Expand Down
6 changes: 5 additions & 1 deletion benchmarks/fixture/config/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ import env from '../start/env.js'
import multitenancyConfig from './multitenancy.js'
import { defineConfig } from '@adonisjs/lucid'

// `DB_PASSWORD` is optional in the env schema; Lucid's connection type (with
// exactOptionalPropertyTypes) rejects `password: undefined`, so omit the key
// entirely when it is unset rather than passing an explicit `undefined`.
const dbPassword = env.get('DB_PASSWORD')
const defaultConnectionOptions = {
client: 'pg',
connection: {
host: env.get('DB_HOST'),
port: env.get('DB_PORT'),
user: env.get('DB_USER'),
password: env.get('DB_PASSWORD'),
...(dbPassword !== undefined ? { password: dbPassword } : {}),
database: env.get('DB_DATABASE'),
},
} as const
Expand Down
7 changes: 4 additions & 3 deletions benchmarks/src/db/connection_churn.bench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ async function churnLoad(
for (;;) {
const i = next++
if (i >= totalOps) return
const ref = refs[i % refs.length]
// Modulo of a non-empty `refs` (seeded with M = cap*2 tenants) is always in range.
const ref = refs[i % refs.length]!
const t0 = process.hrtime.bigint()
const conn = await clientFor(driver, db, ref)
// Mix writes into the churn so the insert path (connect + search_path +
Expand Down Expand Up @@ -113,8 +114,8 @@ export async function runConnectionChurn(app: ApplicationService, db: any): Prom
`cap=${cap} single-tenant SELECT (baseline)`,
300,
async () => {
const conn = await clientFor(driver, db, refs[0])
await selectMarker(conn, driver, refs[0])
const conn = await clientFor(driver, db, refs[0]!)
await selectMarker(conn, driver, refs[0]!)
},
{ group: GROUP, meta: { cap } }
)
Expand Down
3 changes: 2 additions & 1 deletion benchmarks/src/db/driver_query.bench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ const COLD = CI_MODE ? 10 : 25
export async function runDriverQuery(app: ApplicationService, db: any): Promise<BenchResult[]> {
const driver = await activeDriver(app)
const { refs } = await seedAll(app, db, { tenants: sizes.db.tenants, rows: sizes.db.rows })
const ref = refs[0]
// `sizes.db.tenants >= 1`, so `seedAll` returns at least one ref.
const ref = refs[0]!
const conn = await clientFor(driver, db, ref)

const seed = await firstId(conn, driver, ref)
Expand Down
7 changes: 4 additions & 3 deletions benchmarks/src/harness/concurrent_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ export async function runConcurrent(opts: ConcurrentOptions): Promise<Concurrent
try {
const res = await fetch(`${baseUrl}${req.path}`, {
method: req.method ?? 'GET',
headers: req.headers,
body: req.body,
...(req.headers !== undefined ? { headers: req.headers } : {}),
...(req.body !== undefined ? { body: req.body } : {}),
signal: controller.signal,
})
result.latencyNs.push(Number(process.hrtime.bigint() - t0))
Expand Down Expand Up @@ -132,5 +132,6 @@ export function percentileNs(samples: number[], p: number): number {
if (samples.length === 0) return 0
const sorted = [...samples].sort((a, b) => a - b)
const idx = Math.min(sorted.length - 1, Math.max(0, Math.ceil((p / 100) * sorted.length) - 1))
return sorted[idx]
// `idx` is clamped to [0, length-1] and `sorted` is non-empty (guarded above).
return sorted[idx]!
}
2 changes: 1 addition & 1 deletion benchmarks/src/harness/provision.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type TenantRef = { id: string; name: string }
/** A valid UUID v4 derived deterministically from an index. */
export function deterministicTenantId(index: number): string {
const h = createHash('sha256').update(`lasagna-bench-tenant-${index}`).digest('hex')
const variant = ((Number.parseInt(h[16], 16) & 0x3) | 0x8).toString(16)
const variant = ((Number.parseInt(h[16]!, 16) & 0x3) | 0x8).toString(16)
return (
`${h.slice(0, 8)}-${h.slice(8, 12)}-4${h.slice(13, 16)}-` +
`${variant}${h.slice(17, 20)}-${h.slice(20, 32)}`
Expand Down
4 changes: 2 additions & 2 deletions benchmarks/src/harness/report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,8 @@ function writeBaseline(name: string, latest: Map<string, ResultFile>): void {
const index: Record<string, Record<string, { opsPerSec: number; nsMedian: number }>> = {}
for (const file of latest.values()) {
for (const e of flatten(file)) {
index[e.key] ??= {}
index[e.key][e.label] = { opsPerSec: e.opsPerSec, nsMedian: e.nsMedian }
const bucket = (index[e.key] ??= {})
bucket[e.label] = { opsPerSec: e.opsPerSec, nsMedian: e.nsMedian }
}
}
const env = [...latest.values()][0]?.env ?? null
Expand Down
9 changes: 6 additions & 3 deletions benchmarks/src/harness/results.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface ResultFile {
suite: string
env: BenchEnv
results: BenchResult[]
meta?: Record<string, unknown>
meta?: Record<string, unknown> | undefined
}

/**
Expand Down Expand Up @@ -74,14 +74,17 @@ export function median(values: number[]): number {
if (values.length === 0) return 0
const s = [...values].sort((a, b) => a - b)
const mid = Math.floor(s.length / 2)
return s.length % 2 ? s[mid] : (s[mid - 1] + s[mid]) / 2
// `s` is non-empty (guarded above); for even length `mid >= 1`, so both
// `s[mid]` and `s[mid - 1]` are in range.
return s.length % 2 ? s[mid]! : (s[mid - 1]! + s[mid]!) / 2
}

/** Inter-quartile range (p75 - p25) of a numeric sample. */
export function iqr(values: number[]): number {
if (values.length < 2) return 0
const s = [...values].sort((a, b) => a - b)
// `s.length >= 2` (guarded above) and the index is clamped to [0, length-1].
const at = (p: number) =>
s[Math.min(s.length - 1, Math.max(0, Math.ceil((p / 100) * s.length) - 1))]
s[Math.min(s.length - 1, Math.max(0, Math.ceil((p / 100) * s.length) - 1))]!
return at(75) - at(25)
}
9 changes: 5 additions & 4 deletions benchmarks/src/harness/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ export interface BenchStats {

export interface BenchResult {
name: string
group?: string
group?: string | undefined
samples: number
opsPerSec: number
/** Per-operation timing, nanoseconds. */
ns: BenchStats
meta?: Record<string, unknown>
meta?: Record<string, unknown> | undefined
}

export interface MicroOptions {
Expand All @@ -48,13 +48,14 @@ function percentile(sortedAsc: number[], p: number): number {
sortedAsc.length - 1,
Math.max(0, Math.ceil((p / 100) * sortedAsc.length) - 1)
)
return sortedAsc[idx]
// `idx` is clamped to [0, length-1] and the array is non-empty (guarded above).
return sortedAsc[idx]!
}

export function summarize(
name: string,
nsPerOpSamples: number[],
opts: { group?: string; meta?: Record<string, unknown> } = {}
opts: { group?: string | undefined; meta?: Record<string, unknown> | undefined } = {}
): BenchResult {
const sorted = [...nsPerOpSamples].sort((a, b) => a - b)
const n = sorted.length
Expand Down
5 changes: 3 additions & 2 deletions benchmarks/src/http/isolation.bench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,12 @@ export async function runIsolationLoad(
workers: concurrency,
totalRequests: requests,
pickRequest: (i) => {
const sent = tenantIds[i % tenantIds.length]
// Modulo indexing of the non-empty seeded `tenantIds` is always in range.
const sent = tenantIds[i % tenantIds.length]!
// Self-test: claim a DIFFERENT tenant than the one we send, so the
// content check is guaranteed to fail. Proves the assertion detects a
// leak rather than rubber-stamping every run.
const expected = selftest ? tenantIds[(i + 1) % tenantIds.length] : sent
const expected = selftest ? tenantIds[(i + 1) % tenantIds.length]! : sent
return { path, headers: { [HEADER]: sent }, meta: { expected } }
},
check: (req, status, body) => {
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/src/memory/catalog_bloat.bench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ async function runSchemaCatalog(app: ApplicationService, db: any): Promise<Bench
// A real, populated probe tenant queried via search_path (the realistic path).
const { refs } = await provisionTenants(app, db, 1)
await seedIdentifiableNotes(app, db, refs, 200)
const probe = refs[0]
const probe = refs[0]! // provisioned exactly one tenant above
const schemaPrefix = getConfig().tenantSchemaPrefix ?? 'tenant_'
const probeSchema = `${schemaPrefix}${probe.id}`
const conn = await driver.connect(probe as any) // search_path = probe's schema
Expand Down
3 changes: 2 additions & 1 deletion benchmarks/src/resilience/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ try {
} finally {
await terminateBenchApp(seedApp)
}
const tenant = tenantIds[0]
// Reaching here means the seed above succeeded, so `tenantIds` has 5 entries.
const tenant = tenantIds[0]!

// 2) Spawn the serving fixture (development → rate-limit middleware runs).
const server = spawn(process.execPath, ['--import', 'tsx', SERVER_ENTRY], {
Expand Down
Loading
Loading