Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 40 additions & 24 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,35 +1,51 @@
name: CI
on:
pull_request:
branches: [main]
push:
branches: ['**']

branches: [main]
permissions:
contents: read
packages: read

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
node:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: ["20.19.0", "22.23.1", "24.18.0"]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: ${{ matrix.node }}
cache: npm
- run: npm ci --ignore-scripts
- run: npm run build
- run: npm run verify:types
- run: npm run lint
- run: npm run lint:boundary
- run: npm test
- run: npm audit --audit-level=high --omit=dev
- run: npm run verify:package
- name: Pack artifact
if: matrix.node == '20.19.0'
run: |
npm pack --ignore-scripts
sha256sum quantum-l9-llm-router-*.tgz > package.sha256
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: matrix.node == '20.19.0'
with:
persist-credentials: false
- uses: actions/setup-node@v4
name: package-contract
path: |
quantum-l9-llm-router-*.tgz
package.sha256
if-no-files-found: error
artifact-roundtrip:
needs: node
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
with:
node-version: '20'
cache: 'npm'
registry-url: 'https://npm.pkg.github.com'
scope: '@quantum-l9'
- run: npm ci
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build (tsc -> dist/)
run: npm run build
- name: Typecheck
run: npm run verify:types
name: package-contract
path: artifacts/downloaded
- run: cd artifacts/downloaded && sha256sum -c package.sha256
20 changes: 10 additions & 10 deletions .github/workflows/l9-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ concurrency:

env:
# Pin Core to the immutable release commit (or replace with the v2 tag once published).
L9_CORE_REF: "54a2f2fc8d060674d544fab14388bb5eff6b8e78"
L9_CORE_REF: "f88116503430aa18992b70d8d31063e34ff97ef1"
# Match the event: pr_fast for pull_request, merge for push, nightly/release/
# supply_chain as appropriate. Must be allowed_events in execution-profiles.yaml.
L9_PROFILE: "pr_fast"
Expand Down Expand Up @@ -64,7 +64,7 @@ jobs:

- id: gov
name: Resolve governance (Core)
uses: Quantum-L9/l9-ci-core/.github/actions/resolve-governance@54a2f2fc8d060674d544fab14388bb5eff6b8e78
uses: Quantum-L9/l9-ci-core/.github/actions/resolve-governance@f88116503430aa18992b70d8d31063e34ff97ef1
with:
profile: ${{ env.L9_PROFILE }}
provider: semgrep
Expand All @@ -85,7 +85,7 @@ jobs:
if: steps.gov.outputs.enabled == 'true'
run: |
set -euo pipefail
python -m pip install --upgrade pip semgrep
python -m pip install 'pip==26.1.2' 'semgrep==1.170.1'
mkdir -p "artifacts/raw/semgrep/${L9_MATRIX_ID}"
# Node.js repo ruleset — JS/TS only (no p/python; this repo is TypeScript).
semgrep scan \
Expand All @@ -100,11 +100,11 @@ jobs:
- id: sdk
name: Provision immutable SDK
if: steps.gov.outputs.enabled == 'true'
uses: Quantum-L9/l9-ci-core/.github/actions/provision-sdk@54a2f2fc8d060674d544fab14388bb5eff6b8e78
uses: Quantum-L9/l9-ci-core/.github/actions/provision-sdk@f88116503430aa18992b70d8d31063e34ff97ef1

- name: Normalize provider report
if: steps.gov.outputs.enabled == 'true'
uses: Quantum-L9/l9-ci-core/.github/actions/invoke-sdk@54a2f2fc8d060674d544fab14388bb5eff6b8e78
uses: Quantum-L9/l9-ci-core/.github/actions/invoke-sdk@f88116503430aa18992b70d8d31063e34ff97ef1
with:
executable: ${{ steps.sdk.outputs.executable }}
operation: semgrep-normalize
Expand All @@ -119,14 +119,14 @@ jobs:

- name: Validate canonical bundle
if: steps.gov.outputs.enabled == 'true'
uses: Quantum-L9/l9-ci-core/.github/actions/validate-bundle@54a2f2fc8d060674d544fab14388bb5eff6b8e78
uses: Quantum-L9/l9-ci-core/.github/actions/validate-bundle@f88116503430aa18992b70d8d31063e34ff97ef1
with:
executable: ${{ steps.sdk.outputs.executable }}
bundle: .l9/runtime/${{ env.L9_MATRIX_ID }}/finding-bundle.json

- name: Project agent-review payload
if: steps.gov.outputs.enabled == 'true'
uses: Quantum-L9/l9-ci-core/.github/actions/invoke-sdk@54a2f2fc8d060674d544fab14388bb5eff6b8e78
uses: Quantum-L9/l9-ci-core/.github/actions/invoke-sdk@f88116503430aa18992b70d8d31063e34ff97ef1
with:
executable: ${{ steps.sdk.outputs.executable }}
operation: bundle-project-agent-payload
Expand All @@ -137,7 +137,7 @@ jobs:
- id: route
name: Route artifacts
if: steps.gov.outputs.enabled == 'true'
uses: Quantum-L9/l9-ci-core/.github/actions/route-artifacts@54a2f2fc8d060674d544fab14388bb5eff6b8e78
uses: Quantum-L9/l9-ci-core/.github/actions/route-artifacts@f88116503430aa18992b70d8d31063e34ff97ef1
with:
provider: semgrep
matrix-id: ${{ env.L9_MATRIX_ID }}
Expand All @@ -148,7 +148,7 @@ jobs:

- name: Build artifact manifest
if: steps.gov.outputs.enabled == 'true'
uses: Quantum-L9/l9-ci-core/.github/actions/build-artifact-manifest@54a2f2fc8d060674d544fab14388bb5eff6b8e78
uses: Quantum-L9/l9-ci-core/.github/actions/build-artifact-manifest@f88116503430aa18992b70d8d31063e34ff97ef1
with:
provider: semgrep
matrix-id: ${{ env.L9_MATRIX_ID }}
Expand All @@ -174,7 +174,7 @@ jobs:
name: Publish analysis (Core)
needs: analyze
if: needs.analyze.outputs.enabled == 'true'
uses: Quantum-L9/l9-ci-core/.github/workflows/publish-analysis.yml@54a2f2fc8d060674d544fab14388bb5eff6b8e78
uses: Quantum-L9/l9-ci-core/.github/workflows/publish-analysis.yml@f88116503430aa18992b70d8d31063e34ff97ef1
permissions:
actions: read
checks: write
Expand Down
54 changes: 10 additions & 44 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,56 +1,22 @@
name: Publish Package
name: Publish
on:
push:
branches: [main]
paths: ['package.json']
workflow_dispatch:

tags: ['v*']
permissions:
contents: read
packages: write

concurrency:
group: publish-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
registry-url: 'https://npm.pkg.github.com'
scope: '@quantum-l9'
- run: npm ci
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: npm run build

# Gate publish on a version bump: npm refuses to republish an existing
# name@version, so manual dispatch or any non-bump push to package.json
# would otherwise fail the job. Skip cleanly when this version already
# exists on the registry.
- name: Check whether this version is already published
id: published
run: |
VERSION="$(node -p "require('./package.json').version")"
if npm view "@quantum-l9/llm-router@${VERSION}" version >/dev/null 2>&1; then
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "@quantum-l9/llm-router@${VERSION} is already published — skipping publish."
else
echo "exists=false" >> "$GITHUB_OUTPUT"
echo "@quantum-l9/llm-router@${VERSION} not found — will publish."
fi
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Publish
if: steps.published.outputs.exists == 'false'
run: npm publish
node-version: 24.18.0
cache: npm
registry-url: https://npm.pkg.github.com
- run: npm ci --ignore-scripts
- run: npm run verify:all
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
26 changes: 26 additions & 0 deletions .github/workflows/supply-chain.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Supply Chain
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
sbom:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 24.18.0
cache: npm
- run: npm ci --ignore-scripts
- run: npm sbom --sbom-format cyclonedx > sbom.cdx.json
- run: sha256sum sbom.cdx.json > sbom.sha256
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sbom
path: |
sbom.cdx.json
sbom.sha256
if-no-files-found: error
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
node_modules/
dist/
*.log
*.tgz
artifacts/
coverage/
85 changes: 85 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Architecture

`@quantum-l9/llm-router` is a reusable TypeScript routing library. `L9LLMRouter` is the supported production execution surface and the only component that composes routing, budget, resilience, and provider dispatch.

## Runtime flow

```text
validated execution task
-> effective image set merged into task
-> pure route resolution
-> request identity and timestamp
-> atomic process-local budget reservation
-> provider-family-safe downgrade
-> per-provider circuit permit
-> provider dispatch with explicit cancellation and timeout
-> aggregate execution accounting
-> budget reconciliation and audit log
```

Route resolution is pure. Request IDs and timestamps are added afterward and do not participate in routing equivalence.

## Module ownership

```text
src/types.ts public legacy contracts
src/schemas.ts runtime validation for public legacy input
src/matrices/* deterministic model and search resolution
src/pricing.ts canonical OpenRouter price table
src/budget/* process-local admission and spend accounting
src/circuit-breaker.ts process-local provider health control
src/provider-errors.ts typed failure classification and redaction
src/providers/* provider I/O and SDK transport isolation
src/vision/* vision configuration and task planning
src/index.ts composition root and supported execution API
src/control-plane/* internal Phase 1 contract kernel
```

## Provider boundary

Provider clients live under `src/providers/`. Production modules outside `src/index.ts` and `src/providers/` may not import them. ESLint and a programmatic probe enforce the rule.

Existing provider subpath exports remain available during the 1.x line for compatibility. They are deprecated because direct use bypasses budget and circuit controls. Their removal requires a major version.

## Budget state

The built-in budget tracker owns committed spend and active reservations. Admission evaluates both, so concurrent requests inside one JavaScript process cannot all pass against the same unreserved ceiling.

Critical tasks retain the documented override but still reserve and record cost. Reservation identifiers must be non-empty and unique. Client and direct tracker configuration is runtime validated.

The tracker is not a distributed ledger. Multiple processes require an external atomic persistence adapter, which is outside this repository's current scope.

## Circuit state

The circuit breaker owns independent state per provider.

- Closed calls receive ordinary permits.
- The failure threshold opens the circuit.
- The cooldown permits exactly one half-open probe.
- Retryable network, timeout, rate-limit, and server failures count.
- Client, cancellation, budget, policy, and local validation failures do not count.
- Late results from calls acquired before a circuit opened cannot overwrite newer state.

## Provider execution

The OpenAI SDK is isolated behind `OpenAIChatTransport`. SDK retries are disabled. The router controls fallback order explicitly.

OpenRouter fallbacks advance only after retryable failures. Non-retryable client failures and cancellation terminate immediately.

Perplexity consensus executes configured variations in parallel. The selected content remains one successful candidate, while budget-facing cost and token accounting aggregate all successful variations.

## Image safety

Image execution accepts public HTTPS URLs and bounded supported image data URIs. Local, private, loopback, link-local, reserved, and local-domain targets are rejected before dispatch. Option-supplied images are validated and included in route selection before budget reservation.

## Control Plane Phase 1

The Control Plane kernel owns strict runtime contracts, canonical JSON, deterministic identity, immutable builders, policy interfaces, and provider-adapter interfaces. It does not own provider clients, network access, Gate ingress, TransportPacket authority, Graphiti, Neo4j, promotion, mutable global state, or legacy cutover.

The internal barrel `src/control-plane/index.ts` is a deliberate module boundary but is absent from package exports. The one-line pass-through type module was removed because it had no independent responsibility.

Route identity excludes request-specific values and explanatory prose, including route, budget, and provider-health reasons. Complete content hashing still protects those fields.

## Runtime support

The package preserves a Node 20.19.0 compatibility floor for the 1.x line. CI validates the floor plus maintained Node 22 and Node 24 LTS lines. Publish and supply-chain jobs run on Node 24 LTS.
Loading
Loading