@bytesbrains/cruise-worker 0.0.0-stage → 0.12.1
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 +202 -0
- package/README.md +37 -2
- package/index.d.ts +14 -0
- package/index.mjs +41 -0
- package/migrations/0001_virtual_keys.sql +102 -0
- package/migrations/0002_seed_projects.sql +16 -0
- package/migrations/0003_budgets.sql +43 -0
- package/migrations/0004_tenant_isolation_and_integer_money.sql +49 -0
- package/migrations/0005_model_prices.sql +42 -0
- package/migrations/0006_budget_reservation.sql +38 -0
- package/migrations/0007_routing_table.sql +93 -0
- package/migrations/0008_measurement_provenance.sql +24 -0
- package/migrations/0009_conformance.sql +37 -0
- package/migrations/0010_conformance_key.sql +20 -0
- package/migrations/0011_correct_workers_ai_price.sql +30 -0
- package/migrations/0012_rate_limits.sql +28 -0
- package/migrations/0013_deepseek_and_mistral.sql +42 -0
- package/migrations/0014_cost_ledger.sql +104 -0
- package/migrations/0015_memberships.sql +33 -0
- package/migrations/0016_latency.sql +23 -0
- package/migrations/0017_ledger_provider_index.sql +8 -0
- package/migrations/0018_fallback_chains.sql +34 -0
- package/migrations/0019_rate_buckets.sql +27 -0
- package/migrations/0020_mistral_price_and_pin.sql +62 -0
- package/migrations/0021_mistral_price_date_is_utc.sql +35 -0
- package/migrations/0022_drop_unenforced_key_budget.sql +32 -0
- package/migrations/0023_model_limits.sql +48 -0
- package/migrations/0024_conditional_prices.sql +84 -0
- package/migrations/0025_catalogue_from_keyed_hosts.sql +172 -0
- package/migrations/0026_lanes.sql +83 -0
- package/migrations/0027_lane_alternatives.sql +36 -0
- package/migrations/0028_lanes_tenant_scope.sql +66 -0
- package/migrations/0029_lane_jobs.sql +146 -0
- package/migrations/0030_lane_requires.sql +38 -0
- package/migrations/0031_tenant_billable.sql +15 -0
- package/migrations/0032_cache_hit_billing.sql +35 -0
- package/migrations/0033_sell_markup.sql +37 -0
- package/migrations/0034_tiers_and_lane_quotas.sql +38 -0
- package/migrations/0035_wallet_scope.sql +68 -0
- package/migrations/0036_credit_grants.sql +53 -0
- package/migrations/0037_reconciliation.sql +45 -0
- package/migrations/0038_balance_service_keys.sql +35 -0
- package/migrations/0039_deepseek_peak_is_weekdays.sql +77 -0
- package/migrations/0040_glm_5_3_on_workers_ai.sql +63 -0
- package/migrations/0041_mistral_sweep_2026_08_29.sql +89 -0
- package/migrations/0042_provision_service_keys.sql +50 -0
- package/migrations/0043_tier_project_quota.sql +32 -0
- package/migrations/0044_audit_tenant_scope.sql +49 -0
- package/migrations/0045_audit_backfill_derivable.sql +42 -0
- package/migrations/0046_measurement_note.sql +24 -0
- package/migrations/0047_ledger_max_tokens.sql +21 -0
- package/migrations/0048_price_observations.sql +140 -0
- package/migrations/0049_openai_and_gemini.sql +239 -0
- package/migrations/0050_meta_direct_upstream.sql +127 -0
- package/migrations/0051_upstream_credential_and_operator.sql +74 -0
- package/migrations/0052_muse_spark_1_3.sql +71 -0
- package/migrations/0053_anthropic_groq_and_xai.sql +172 -0
- package/migrations/0054_tier_service_keys.sql +60 -0
- package/migrations/0055_output_defaults.sql +79 -0
- package/migrations/0056_model_modality.sql +37 -0
- package/migrations/0057_openai_image_models.sql +177 -0
- package/migrations/0058_price_components.sql +230 -0
- package/migrations/0059_speech.sql +151 -0
- package/migrations/0060_embeddings.sql +131 -0
- package/migrations/0061_data_retention_backfill.sql +121 -0
- package/migrations/0062_gemini_embedding_disabled.sql +125 -0
- package/migrations/0063_google_provider_path_prefix.sql +59 -0
- package/migrations/0064_economics_service_keys.sql +51 -0
- package/migrations/0065_lane_selection.sql +66 -0
- package/migrations/0066_tenant_default_selection.sql +42 -0
- package/migrations/0067_graduated_selection.sql +103 -0
- package/migrations/0068_cache_scope.sql +73 -0
- package/migrations/0069_lane_cache_settings.sql +50 -0
- package/migrations/0070_lane_member_quality_rank.sql +98 -0
- package/migrations/0071_key_cache_pool.sql +60 -0
- package/migrations/0072_cache_digest.sql +51 -0
- package/migrations/0073_lane_max_chain.sql +51 -0
- package/migrations/0074_hunter_builder_lanes.sql +149 -0
- package/migrations/0075_hunter_builder_frontline.sql +36 -0
- package/migrations/0076_deepseek_flash.sql +86 -0
- package/migrations/0077_gpt_6_astra.sql +35 -0
- package/migrations/0078_astra_deep_reasoning.sql +20 -0
- package/migrations/0079_gemini_flash_lite_extraction.sql +15 -0
- package/migrations/0080_priced_by.sql +24 -0
- package/migrations/0081_grok_imagine.sql +81 -0
- package/migrations/0082_markup_three_percent.sql +32 -0
- package/migrations/0083_markup_per_tenant_and_tier.sql +57 -0
- package/migrations/0084_coding_lanes_frontier.sql +93 -0
- package/migrations/0085_minute_period_and_demo_charge.sql +77 -0
- package/migrations/0086_project_sentry.sql +41 -0
- package/migrations/0087_lane_router.sql +71 -0
- package/migrations/0088_openai_frontier_coding_lanes.sql +54 -0
- package/migrations/0089_agentic_coding_drops_codestral.sql +44 -0
- package/migrations/0090_board_followups_2026_10.sql +196 -0
- package/migrations/0091_opus_5_5_coding_lanes.sql +43 -0
- package/migrations/0092_deepseek_peak_holidays.sql +83 -0
- package/migrations/0093_tool_choice_forced.sql +26 -0
- package/migrations/0094_board_followups_2026_10_08.sql +115 -0
- package/migrations/0095_request_compression.sql +43 -0
- package/migrations/0096_lane_compress.sql +36 -0
- package/migrations/0097_tenant_default_compress.sql +34 -0
- package/migrations/0098_ledger_session.sql +47 -0
- package/migrations/0099_wallet_low_water_backfill.sql +47 -0
- package/migrations/0100_rag_grounded_answers_lane.sql +92 -0
- package/migrations/0101_perplexity_agent_api.sql +125 -0
- package/migrations/0102_web_search_job_and_flag.sql +137 -0
- package/migrations/0103_openai_web_search_price.sql +44 -0
- package/migrations/0104_web_search_lane.sql +82 -0
- package/migrations/0105_web_search_lane_billed.sql +22 -0
- package/migrations/0106_reservations_journal.sql +64 -0
- package/migrations/0107_session_cap.sql +65 -0
- package/migrations/0108_ledger_attempts.sql +41 -0
- package/migrations/0109_gemini_retention.sql +39 -0
- package/migrations/0110_gemini_4_argon.sql +110 -0
- package/migrations/0111_gemini_4_argon_lanes.sql +80 -0
- package/migrations/0112_ledger_affinity.sql +42 -0
- package/migrations/0113_contributor_lane_namespace.sql +125 -0
- package/migrations/0114_meta_contributor_models.sql +55 -0
- package/migrations/0115_oss_chat_assistant_lane.sql +41 -0
- package/migrations/0116_promo_credits.sql +63 -0
- package/migrations/0117_oss_mirror_lanes.sql +85 -0
- package/migrations/0118_project_management_job.sql +112 -0
- package/migrations/0119_oss_project_management_lane.sql +29 -0
- package/migrations/0120_moonshot_k3_1_pro.sql +107 -0
- package/migrations/0121_jobs.sql +100 -0
- package/migrations/0122_batch_prices.sql +188 -0
- package/migrations/0123_batch_files.sql +50 -0
- package/migrations/0124_batch_run.sql +21 -0
- package/migrations/0125_anthropic_path_prefix.sql +22 -0
- package/package.json +37 -4
- package/worker/284906b51fc0a3173ea7d301234907f30e834f54-cruise-mark-64.png +0 -0
- package/worker/2c39de526314484775fdfc503fac50c437d21e4e-cruise-logo-240.png +0 -0
- package/worker/63fb63e2d8ae6d8b68228768ef6c91ab8f0c0100-cruise-logo-480.png +0 -0
- package/worker/VERSION +1 -0
- package/worker/b23bfbc792e8710d3b74284a0a1b8ab044c18897-logo-dark.png +0 -0
- package/worker/index.js +31451 -0
- package/wrangler.template.jsonc +48 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
-- Give `checkLimits` something to check.
|
|
2
|
+
--
|
|
3
|
+
-- `max_context` and `max_output` have been columns since 0007 and NULL on every
|
|
4
|
+
-- row since. `checkLimits` reads `max_output` and returns ok when it is NULL, so
|
|
5
|
+
-- the output cap has been live code enforcing nothing — a sixth instance of the
|
|
6
|
+
-- pattern this project keeps recording, and a new variant: not a dead column,
|
|
7
|
+
-- but a live gate with no data. It even has a passing test, because the test
|
|
8
|
+
-- seeds the value the production rows lack.
|
|
9
|
+
--
|
|
10
|
+
-- **Every number here was fetched from the host's own page, not from the model's.**
|
|
11
|
+
-- That distinction is the whole of this migration.
|
|
12
|
+
--
|
|
13
|
+
-- naderu.com's model board tracks 44 models with verified prices and context
|
|
14
|
+
-- windows, refreshed weekly, and is a better-disciplined source than anything we
|
|
15
|
+
-- would write. But it is keyed on the **model**, and Cruise is keyed on the
|
|
16
|
+
-- **model at a given upstream**. For closed-weights models those are the same
|
|
17
|
+
-- thing. For open weights they are not, and the gap is not small:
|
|
18
|
+
--
|
|
19
|
+
-- deepseek-v4-flash board 1M / 384K DeepSeek 1M / 384K identical
|
|
20
|
+
-- llama-3.3-70b board 131,072 Workers AI 24,000 5.5x apart
|
|
21
|
+
--
|
|
22
|
+
-- Workers AI serves Llama with a 24,000-token window, roughly a fifth of the
|
|
23
|
+
-- model's native 131k. Importing the board's figure would have had Cruise accept
|
|
24
|
+
-- requests up to 131k against a deployment that takes 24k — and the gate that
|
|
25
|
+
-- exists to catch exactly that would have passed them through. The same shape as
|
|
26
|
+
-- 0020's price error, in the same direction: model-level data applied to a
|
|
27
|
+
-- host-level deployment.
|
|
28
|
+
|
|
29
|
+
-- Workers AI, https://developers.cloudflare.com/workers-ai/models/llama-3.3-70b-instruct-fp8-fast/
|
|
30
|
+
-- The page states a 24,000-token context window. It states a *default* max_tokens
|
|
31
|
+
-- of 256 and no upper bound, so `max_output` stays NULL rather than being
|
|
32
|
+
-- invented — the rule from 0011 and 0020 holds: never write a number you did not
|
|
33
|
+
-- fetch. `checkLimits` now derives a bound from the window instead.
|
|
34
|
+
UPDATE models
|
|
35
|
+
SET max_context = 24000
|
|
36
|
+
WHERE id = 'workers-ai/@cf/meta/llama-3.3-70b-instruct-fp8-fast';
|
|
37
|
+
|
|
38
|
+
-- DeepSeek, https://api-docs.deepseek.com/quick_start/pricing
|
|
39
|
+
-- 1M context, 384K maximum output, both stated.
|
|
40
|
+
UPDATE models
|
|
41
|
+
SET max_context = 1000000, max_output = 384000
|
|
42
|
+
WHERE id = 'deepseek/deepseek-v4-flash';
|
|
43
|
+
|
|
44
|
+
-- Mistral, https://docs.mistral.ai/models/model-cards/mistral-small-4-0-26-03
|
|
45
|
+
-- 256k context stated; maximum output not stated on the card, so left NULL.
|
|
46
|
+
UPDATE models
|
|
47
|
+
SET max_context = 262144
|
|
48
|
+
WHERE id = 'mistral/mistral-small-latest';
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
-- A price that depends on when you ask, and on how much you send.
|
|
2
|
+
--
|
|
3
|
+
-- `model_prices` holds one rate per model per effective date. Two providers on
|
|
4
|
+
-- the board now price by conditions that a flat row cannot express, and one of
|
|
5
|
+
-- them is a provider we route:
|
|
6
|
+
--
|
|
7
|
+
-- DeepSeek, since 16:00 UTC on 2026-08-16: every rate doubles during peak
|
|
8
|
+
-- hours, 01:00-04:00 and 06:00-10:00 UTC. Off-peak is the rate "most of the
|
|
9
|
+
-- day"; peak is exactly 2x. https://api-docs.deepseek.com/news/news260813
|
|
10
|
+
--
|
|
11
|
+
-- Gemini 3.1 Pro and Grok 4.5/4.6: at or above 200,000 input tokens the rate
|
|
12
|
+
-- doubles, for the whole request. Qwen3.7-Plus steps at 256K, MiniMax M3 at
|
|
13
|
+
-- 512K. None is on a keyed upstream yet; all arrive with the first OpenRouter
|
|
14
|
+
-- key, and the shape is the same. https://naderu.com/models-board
|
|
15
|
+
--
|
|
16
|
+
-- So a price row gains two conditions, each with a value meaning "no condition":
|
|
17
|
+
--
|
|
18
|
+
-- prompt_tokens_from the row applies when the request's prompt is at least
|
|
19
|
+
-- this many tokens. 0 is the base tier. Tiers apply to the
|
|
20
|
+
-- whole request, which is how Google and xAI bill it; if a
|
|
21
|
+
-- provider turns out to bill marginally, correcting down is
|
|
22
|
+
-- the cheap direction.
|
|
23
|
+
-- peak_utc comma-separated HH:MM-HH:MM windows, UTC, start inclusive
|
|
24
|
+
-- and end exclusive, during which the row applies. '' is
|
|
25
|
+
-- any hour.
|
|
26
|
+
--
|
|
27
|
+
-- The resolver takes the rows in force, keeps the highest tier at or below the
|
|
28
|
+
-- prompt, and within it prefers a window containing the request's time over the
|
|
29
|
+
-- '' row. Both are '' / 0 for every existing row, so every existing lookup
|
|
30
|
+
-- resolves exactly as before.
|
|
31
|
+
--
|
|
32
|
+
-- **Not** added: a region. No keyed provider prices by geography today, and a
|
|
33
|
+
-- column with nothing to read it has been wrong six times out of six in this
|
|
34
|
+
-- repository. When the first regionally priced upstream is routed, it is one more
|
|
35
|
+
-- condition column on this row and the same resolver.
|
|
36
|
+
--
|
|
37
|
+
-- Primary key changes, so the table is rebuilt. The '' and 0 defaults are what
|
|
38
|
+
-- let the key stay NOT NULL: SQLite admits NULL in a primary key and then cannot
|
|
39
|
+
-- enforce uniqueness across it.
|
|
40
|
+
|
|
41
|
+
CREATE TABLE model_prices_new (
|
|
42
|
+
model TEXT NOT NULL,
|
|
43
|
+
effective_from TEXT NOT NULL,
|
|
44
|
+
prompt_tokens_from INTEGER NOT NULL DEFAULT 0,
|
|
45
|
+
peak_utc TEXT NOT NULL DEFAULT '',
|
|
46
|
+
input_micros_per_mtok INTEGER NOT NULL,
|
|
47
|
+
output_micros_per_mtok INTEGER NOT NULL,
|
|
48
|
+
cached_input_micros_per_mtok INTEGER,
|
|
49
|
+
source TEXT NOT NULL,
|
|
50
|
+
recorded_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
51
|
+
PRIMARY KEY (model, effective_from, prompt_tokens_from, peak_utc)
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
INSERT INTO model_prices_new
|
|
55
|
+
(model, effective_from, input_micros_per_mtok, output_micros_per_mtok,
|
|
56
|
+
cached_input_micros_per_mtok, source, recorded_at)
|
|
57
|
+
SELECT model, effective_from, input_micros_per_mtok, output_micros_per_mtok,
|
|
58
|
+
cached_input_micros_per_mtok, source, recorded_at
|
|
59
|
+
FROM model_prices;
|
|
60
|
+
|
|
61
|
+
DROP TABLE model_prices;
|
|
62
|
+
ALTER TABLE model_prices_new RENAME TO model_prices;
|
|
63
|
+
|
|
64
|
+
-- DeepSeek V4-Flash at the price it has actually charged since 2026-08-16.
|
|
65
|
+
--
|
|
66
|
+
-- The row on record is $0.14 / $0.28 as of 2026-08-14, which was right for two
|
|
67
|
+
-- days. Since 16:00 UTC on the 16th the ledger has under-counted every DeepSeek
|
|
68
|
+
-- request by 1.6x off-peak and up to 4.7x at peak — the failure the budget exists
|
|
69
|
+
-- to prevent, live for six days. The effective date is the calendar day, which
|
|
70
|
+
-- over-counts the sixteen hours before the change took effect; over-counting is
|
|
71
|
+
-- the safe direction and the alternative is a timestamped effective_from for one
|
|
72
|
+
-- row.
|
|
73
|
+
--
|
|
74
|
+
-- Figures from https://api-docs.deepseek.com/quick_start/pricing, fetched
|
|
75
|
+
-- 2026-08-22: off-peak $0.22 in / $0.007 cache hit / $0.66 out; peak $0.44 /
|
|
76
|
+
-- $0.014 / $1.32. Model version DeepSeek-V4-Flash-0731.
|
|
77
|
+
INSERT INTO model_prices
|
|
78
|
+
(model, effective_from, prompt_tokens_from, peak_utc,
|
|
79
|
+
input_micros_per_mtok, output_micros_per_mtok, cached_input_micros_per_mtok, source)
|
|
80
|
+
VALUES
|
|
81
|
+
('deepseek/deepseek-v4-flash', '2026-08-16', 0, '',
|
|
82
|
+
220000, 660000, 7000, 'https://api-docs.deepseek.com/quick_start/pricing'),
|
|
83
|
+
('deepseek/deepseek-v4-flash', '2026-08-16', 0, '01:00-04:00,06:00-10:00',
|
|
84
|
+
440000, 1320000, 14000, 'https://api-docs.deepseek.com/quick_start/pricing');
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
-- Every chat model the three keyed hosts serve, at the price and window each
|
|
2
|
+
-- host states.
|
|
3
|
+
--
|
|
4
|
+
-- Three models were routable. A lane (auto-mode.md) is a choice among
|
|
5
|
+
-- candidates, and with three candidates across eight jobs most lanes would have
|
|
6
|
+
-- had one or none. This is the whole current catalogue of the upstreams we hold
|
|
7
|
+
-- keys for — DeepSeek, Mistral, Workers AI — minus what is retiring, unpriced,
|
|
8
|
+
-- or not a chat model.
|
|
9
|
+
--
|
|
10
|
+
-- Rules, the same as 0011, 0020 and 0023:
|
|
11
|
+
--
|
|
12
|
+
-- Every number is quoted from the host's own page, fetched 2026-08-21 UTC.
|
|
13
|
+
-- A figure the host does not state stays NULL. `max_output` is NULL for all
|
|
14
|
+
-- but four models because no host page states a maximum; `checkLimits`
|
|
15
|
+
-- derives a bound from the window (0023).
|
|
16
|
+
--
|
|
17
|
+
-- Where two host sources disagree, the gate takes the smaller. Mistral's card
|
|
18
|
+
-- says 256k for ministral-3b and the API says 131072; the card says 128k for
|
|
19
|
+
-- codestral and the API says 256000. Refusing a request the host would have
|
|
20
|
+
-- taken is visible and correctable; passing one it would not is neither.
|
|
21
|
+
--
|
|
22
|
+
-- Nothing here is routable yet. A row is admitted by conformance measuring it
|
|
23
|
+
-- (#10), which is the only writer of `measured_at`. Inserting is not
|
|
24
|
+
-- admitting.
|
|
25
|
+
--
|
|
26
|
+
-- `effective_from` is today in UTC, 2026-08-21. The wall clock that wrote this
|
|
27
|
+
-- read 03:00 on the 22nd. See 0020 and 0021.
|
|
28
|
+
--
|
|
29
|
+
-- Excluded, and why:
|
|
30
|
+
-- Workers AI: 17 models marked "Deprecated 5/30/2026" on their pages, a date
|
|
31
|
+
-- already past; 9 beta LoRA-era models with no price stated;
|
|
32
|
+
-- llama-3.1-8b-instruct-fast, which has a page and no price; llama-guard, a
|
|
33
|
+
-- classifier. Mistral: mistral-medium-2505 and -2508 (retire 2026-08-31),
|
|
34
|
+
-- devstral-2512 (retired 2026-07-31), magistral (deprecated), voxtral (audio).
|
|
35
|
+
-- Every `-latest` alias: an alias moving is a rate change with no signal
|
|
36
|
+
-- (0020), so each row pins the dated id the host lists.
|
|
37
|
+
--
|
|
38
|
+
-- Three of the Workers AI rows are the same weights DeepSeek serves under its
|
|
39
|
+
-- own key, at DeepSeek's peak rate all day and without a peak window. Whether
|
|
40
|
+
-- that is cheaper depends on the hour, which is precisely the decision a lane
|
|
41
|
+
-- exists to make and a human never would.
|
|
42
|
+
|
|
43
|
+
-- ── DeepSeek ──────────────────────────────────────────────────────────────────
|
|
44
|
+
-- https://api-docs.deepseek.com/quick_start/pricing — 1M context, 384K maximum
|
|
45
|
+
-- output, both stated for all three; tool calls and JSON output stated for all
|
|
46
|
+
-- three; image input only on the vision model (guides/vision). Peak 01:00-04:00
|
|
47
|
+
-- and 06:00-10:00 UTC at 2x, in force since 16:00 UTC 2026-08-16 (news260813).
|
|
48
|
+
|
|
49
|
+
INSERT INTO models (id, upstream_id, upstream_model, max_context, max_output) VALUES
|
|
50
|
+
('deepseek/deepseek-v4-pro', 'deepseek', 'deepseek-v4-pro', 1000000, 384000),
|
|
51
|
+
-- Experimental on the host's own label; images are billed as input tokens at
|
|
52
|
+
-- V4-Flash rates, up to 384 tokens each (news260821).
|
|
53
|
+
('deepseek/deepseek-v4-flash-vision-exp', 'deepseek', 'deepseek-v4-flash-vision-exp', 1000000, 384000);
|
|
54
|
+
|
|
55
|
+
INSERT INTO model_prices
|
|
56
|
+
(model, effective_from, prompt_tokens_from, peak_utc,
|
|
57
|
+
input_micros_per_mtok, output_micros_per_mtok, cached_input_micros_per_mtok, source)
|
|
58
|
+
VALUES
|
|
59
|
+
('deepseek/deepseek-v4-pro', '2026-08-21', 0, '',
|
|
60
|
+
660000, 1980000, 22000, 'https://api-docs.deepseek.com/quick_start/pricing'),
|
|
61
|
+
('deepseek/deepseek-v4-pro', '2026-08-21', 0, '01:00-04:00,06:00-10:00',
|
|
62
|
+
1320000, 3960000, 44000, 'https://api-docs.deepseek.com/quick_start/pricing'),
|
|
63
|
+
('deepseek/deepseek-v4-flash-vision-exp', '2026-08-21', 0, '',
|
|
64
|
+
220000, 660000, 7000, 'https://api-docs.deepseek.com/quick_start/pricing'),
|
|
65
|
+
('deepseek/deepseek-v4-flash-vision-exp', '2026-08-21', 0, '01:00-04:00,06:00-10:00',
|
|
66
|
+
440000, 1320000, 14000, 'https://api-docs.deepseek.com/quick_start/pricing');
|
|
67
|
+
|
|
68
|
+
-- ── Mistral ───────────────────────────────────────────────────────────────────
|
|
69
|
+
-- Prices: https://docs.mistral.ai/inference/pricing (cached input is 10% of
|
|
70
|
+
-- input for every listed model). Windows: each model's card, which states a
|
|
71
|
+
-- rounded figure ("256k"), read against the API's max_context_length, which
|
|
72
|
+
-- states an exact one; the smaller wins where they differ. Max output is stated
|
|
73
|
+
-- on two cards only.
|
|
74
|
+
--
|
|
75
|
+
-- `mistral-medium-2604` is the API's id; the card is titled Mistral Medium 3.5
|
|
76
|
+
-- and never prints "2604". The API's own alias list maps the two
|
|
77
|
+
-- (mistral-medium-2604 ↔ mistral-medium-3-5), which is the host saying so.
|
|
78
|
+
|
|
79
|
+
INSERT INTO models (id, upstream_id, upstream_model, max_context, max_output) VALUES
|
|
80
|
+
-- https://docs.mistral.ai/models/mistral-large-3-25-12
|
|
81
|
+
('mistral/mistral-large-2512', 'mistral', 'mistral-large-2512', 262144, NULL),
|
|
82
|
+
-- https://docs.mistral.ai/models/mistral-medium-3-5-26-04
|
|
83
|
+
('mistral/mistral-medium-2604', 'mistral', 'mistral-medium-2604', 262144, NULL),
|
|
84
|
+
-- https://docs.mistral.ai/models/ministral-3-3b-25-12 — card 256k, API 131072
|
|
85
|
+
('mistral/ministral-3b-2512', 'mistral', 'ministral-3b-2512', 131072, NULL),
|
|
86
|
+
-- https://docs.mistral.ai/models/ministral-3-8b-25-12
|
|
87
|
+
('mistral/ministral-8b-2512', 'mistral', 'ministral-8b-2512', 262144, NULL),
|
|
88
|
+
-- https://docs.mistral.ai/models/ministral-3-14b-25-12
|
|
89
|
+
('mistral/ministral-14b-2512', 'mistral', 'ministral-14b-2512', 262144, NULL),
|
|
90
|
+
-- https://docs.mistral.ai/models/codestral-25-08 — card 128k, API 256000
|
|
91
|
+
('mistral/codestral-2508', 'mistral', 'codestral-2508', 131072, NULL),
|
|
92
|
+
-- https://docs.mistral.ai/models/zai-glm-5-2 — 1M context, 128k max output.
|
|
93
|
+
-- Third-party weights, public preview, on the host's label.
|
|
94
|
+
('mistral/zai-glm-5-2', 'mistral', 'zai-glm-5-2', 1048576, 128000),
|
|
95
|
+
-- https://docs.mistral.ai/models/leanstral-1-5 — 256k, 128k max output.
|
|
96
|
+
-- Public preview and free, which a price-first lane will prefer on sight; the
|
|
97
|
+
-- quality data that should temper that is exactly what conformance and the
|
|
98
|
+
-- ledger exist to collect.
|
|
99
|
+
('mistral/labs-leanstral-1-5', 'mistral', 'labs-leanstral-1-5', 262144, 128000);
|
|
100
|
+
|
|
101
|
+
INSERT INTO model_prices
|
|
102
|
+
(model, effective_from, input_micros_per_mtok, output_micros_per_mtok,
|
|
103
|
+
cached_input_micros_per_mtok, source)
|
|
104
|
+
VALUES
|
|
105
|
+
('mistral/mistral-large-2512', '2026-08-21', 500000, 1500000, 50000, 'https://docs.mistral.ai/inference/pricing'),
|
|
106
|
+
('mistral/mistral-medium-2604', '2026-08-21', 1500000, 7500000, 150000, 'https://docs.mistral.ai/inference/pricing'),
|
|
107
|
+
('mistral/ministral-3b-2512', '2026-08-21', 100000, 100000, 10000, 'https://docs.mistral.ai/inference/pricing'),
|
|
108
|
+
('mistral/ministral-8b-2512', '2026-08-21', 150000, 150000, 15000, 'https://docs.mistral.ai/inference/pricing'),
|
|
109
|
+
('mistral/ministral-14b-2512', '2026-08-21', 200000, 200000, 20000, 'https://docs.mistral.ai/inference/pricing'),
|
|
110
|
+
('mistral/codestral-2508', '2026-08-21', 300000, 900000, 30000, 'https://docs.mistral.ai/inference/pricing'),
|
|
111
|
+
('mistral/zai-glm-5-2', '2026-08-21', 1400000, 4400000, 140000, 'https://docs.mistral.ai/inference/pricing'),
|
|
112
|
+
('mistral/labs-leanstral-1-5', '2026-08-21', 0, 0, 0, 'https://docs.mistral.ai/inference/pricing');
|
|
113
|
+
|
|
114
|
+
-- ── Workers AI ────────────────────────────────────────────────────────────────
|
|
115
|
+
-- Prices: https://developers.cloudflare.com/workers-ai/platform/pricing/, which
|
|
116
|
+
-- prints three decimals where the model pages round to two (0011 took the
|
|
117
|
+
-- pricing page for the same reason). Windows: each model's page at
|
|
118
|
+
-- https://developers.cloudflare.com/workers-ai/models/<slug>/. No page states a
|
|
119
|
+
-- maximum output. Five are "paid billing only" on the pricing page.
|
|
120
|
+
|
|
121
|
+
INSERT INTO models (id, upstream_id, upstream_model, max_context) VALUES
|
|
122
|
+
('workers-ai/@cf/deepseek-ai/deepseek-r1-distill-qwen-32b', 'workers-ai', '@cf/deepseek-ai/deepseek-r1-distill-qwen-32b', 80000),
|
|
123
|
+
('workers-ai/@cf/deepseek-ai/deepseek-v4-flash-0731', 'workers-ai', '@cf/deepseek-ai/deepseek-v4-flash-0731', 1048576),
|
|
124
|
+
('workers-ai/@cf/deepseek-ai/deepseek-v4-pro-0813', 'workers-ai', '@cf/deepseek-ai/deepseek-v4-pro-0813', 1048576),
|
|
125
|
+
('workers-ai/@cf/google/gemma-4-26b-a4b-it', 'workers-ai', '@cf/google/gemma-4-26b-a4b-it', 256000),
|
|
126
|
+
('workers-ai/@cf/aisingapore/gemma-sea-lion-v4-27b-it', 'workers-ai', '@cf/aisingapore/gemma-sea-lion-v4-27b-it', 128000),
|
|
127
|
+
('workers-ai/@cf/zai-org/glm-4.7-flash', 'workers-ai', '@cf/zai-org/glm-4.7-flash', 131072),
|
|
128
|
+
('workers-ai/@cf/zai-org/glm-5.2', 'workers-ai', '@cf/zai-org/glm-5.2', 262144),
|
|
129
|
+
('workers-ai/@cf/openai/gpt-oss-120b', 'workers-ai', '@cf/openai/gpt-oss-120b', 128000),
|
|
130
|
+
('workers-ai/@cf/openai/gpt-oss-20b', 'workers-ai', '@cf/openai/gpt-oss-20b', 128000),
|
|
131
|
+
('workers-ai/@cf/ibm-granite/granite-4.0-h-micro', 'workers-ai', '@cf/ibm-granite/granite-4.0-h-micro', 131000),
|
|
132
|
+
('workers-ai/@cf/moonshotai/kimi-k2.6', 'workers-ai', '@cf/moonshotai/kimi-k2.6', 262144),
|
|
133
|
+
('workers-ai/@cf/moonshotai/kimi-k2.7-code', 'workers-ai', '@cf/moonshotai/kimi-k2.7-code', 262144),
|
|
134
|
+
('workers-ai/@cf/meta/llama-3.1-8b-instruct-fp8', 'workers-ai', '@cf/meta/llama-3.1-8b-instruct-fp8', 32000),
|
|
135
|
+
('workers-ai/@cf/meta/llama-3.2-11b-vision-instruct', 'workers-ai', '@cf/meta/llama-3.2-11b-vision-instruct', 128000),
|
|
136
|
+
('workers-ai/@cf/meta/llama-3.2-1b-instruct', 'workers-ai', '@cf/meta/llama-3.2-1b-instruct', 60000),
|
|
137
|
+
('workers-ai/@cf/meta/llama-3.2-3b-instruct', 'workers-ai', '@cf/meta/llama-3.2-3b-instruct', 80000),
|
|
138
|
+
('workers-ai/@cf/meta/llama-4-scout-17b-16e-instruct', 'workers-ai', '@cf/meta/llama-4-scout-17b-16e-instruct', 131000),
|
|
139
|
+
('workers-ai/@cf/mistralai/mistral-small-3.1-24b-instruct', 'workers-ai', '@cf/mistralai/mistral-small-3.1-24b-instruct', 128000),
|
|
140
|
+
('workers-ai/@cf/nvidia/nemotron-3-120b-a12b', 'workers-ai', '@cf/nvidia/nemotron-3-120b-a12b', 256000),
|
|
141
|
+
('workers-ai/@cf/qwen/qwen2.5-coder-32b-instruct', 'workers-ai', '@cf/qwen/qwen2.5-coder-32b-instruct', 32768),
|
|
142
|
+
('workers-ai/@cf/qwen/qwen3-30b-a3b-fp8', 'workers-ai', '@cf/qwen/qwen3-30b-a3b-fp8', 32768),
|
|
143
|
+
('workers-ai/@cf/qwen/qwen3.8-27b', 'workers-ai', '@cf/qwen/qwen3.8-27b', 262144),
|
|
144
|
+
('workers-ai/@cf/qwen/qwq-32b', 'workers-ai', '@cf/qwen/qwq-32b', 24000);
|
|
145
|
+
|
|
146
|
+
INSERT INTO model_prices
|
|
147
|
+
(model, effective_from, input_micros_per_mtok, output_micros_per_mtok,
|
|
148
|
+
cached_input_micros_per_mtok, source)
|
|
149
|
+
VALUES
|
|
150
|
+
('workers-ai/@cf/deepseek-ai/deepseek-r1-distill-qwen-32b', '2026-08-21', 497000, 4881000, NULL, 'https://developers.cloudflare.com/workers-ai/platform/pricing/'),
|
|
151
|
+
('workers-ai/@cf/deepseek-ai/deepseek-v4-flash-0731', '2026-08-21', 440000, 1320000, 14000, 'https://developers.cloudflare.com/workers-ai/platform/pricing/'),
|
|
152
|
+
('workers-ai/@cf/deepseek-ai/deepseek-v4-pro-0813', '2026-08-21', 1320000, 3960000, 44000, 'https://developers.cloudflare.com/workers-ai/platform/pricing/'),
|
|
153
|
+
('workers-ai/@cf/google/gemma-4-26b-a4b-it', '2026-08-21', 100000, 300000, NULL, 'https://developers.cloudflare.com/workers-ai/platform/pricing/'),
|
|
154
|
+
('workers-ai/@cf/aisingapore/gemma-sea-lion-v4-27b-it', '2026-08-21', 351000, 555000, NULL, 'https://developers.cloudflare.com/workers-ai/platform/pricing/'),
|
|
155
|
+
('workers-ai/@cf/zai-org/glm-4.7-flash', '2026-08-21', 60000, 400000, NULL, 'https://developers.cloudflare.com/workers-ai/platform/pricing/'),
|
|
156
|
+
('workers-ai/@cf/zai-org/glm-5.2', '2026-08-21', 1400000, 4400000, 260000, 'https://developers.cloudflare.com/workers-ai/platform/pricing/'),
|
|
157
|
+
('workers-ai/@cf/openai/gpt-oss-120b', '2026-08-21', 350000, 750000, NULL, 'https://developers.cloudflare.com/workers-ai/platform/pricing/'),
|
|
158
|
+
('workers-ai/@cf/openai/gpt-oss-20b', '2026-08-21', 200000, 300000, NULL, 'https://developers.cloudflare.com/workers-ai/platform/pricing/'),
|
|
159
|
+
('workers-ai/@cf/ibm-granite/granite-4.0-h-micro', '2026-08-21', 17000, 112000, NULL, 'https://developers.cloudflare.com/workers-ai/platform/pricing/'),
|
|
160
|
+
('workers-ai/@cf/moonshotai/kimi-k2.6', '2026-08-21', 950000, 4000000, 160000, 'https://developers.cloudflare.com/workers-ai/platform/pricing/'),
|
|
161
|
+
('workers-ai/@cf/moonshotai/kimi-k2.7-code', '2026-08-21', 950000, 4000000, 190000, 'https://developers.cloudflare.com/workers-ai/platform/pricing/'),
|
|
162
|
+
('workers-ai/@cf/meta/llama-3.1-8b-instruct-fp8', '2026-08-21', 152000, 287000, NULL, 'https://developers.cloudflare.com/workers-ai/platform/pricing/'),
|
|
163
|
+
('workers-ai/@cf/meta/llama-3.2-11b-vision-instruct', '2026-08-21', 49000, 676000, NULL, 'https://developers.cloudflare.com/workers-ai/platform/pricing/'),
|
|
164
|
+
('workers-ai/@cf/meta/llama-3.2-1b-instruct', '2026-08-21', 27000, 201000, NULL, 'https://developers.cloudflare.com/workers-ai/platform/pricing/'),
|
|
165
|
+
('workers-ai/@cf/meta/llama-3.2-3b-instruct', '2026-08-21', 51000, 335000, NULL, 'https://developers.cloudflare.com/workers-ai/platform/pricing/'),
|
|
166
|
+
('workers-ai/@cf/meta/llama-4-scout-17b-16e-instruct', '2026-08-21', 270000, 850000, NULL, 'https://developers.cloudflare.com/workers-ai/platform/pricing/'),
|
|
167
|
+
('workers-ai/@cf/mistralai/mistral-small-3.1-24b-instruct', '2026-08-21', 351000, 555000, NULL, 'https://developers.cloudflare.com/workers-ai/platform/pricing/'),
|
|
168
|
+
('workers-ai/@cf/nvidia/nemotron-3-120b-a12b', '2026-08-21', 500000, 1500000, NULL, 'https://developers.cloudflare.com/workers-ai/platform/pricing/'),
|
|
169
|
+
('workers-ai/@cf/qwen/qwen2.5-coder-32b-instruct', '2026-08-21', 660000, 1000000, NULL, 'https://developers.cloudflare.com/workers-ai/platform/pricing/'),
|
|
170
|
+
('workers-ai/@cf/qwen/qwen3-30b-a3b-fp8', '2026-08-21', 51000, 335000, NULL, 'https://developers.cloudflare.com/workers-ai/platform/pricing/'),
|
|
171
|
+
('workers-ai/@cf/qwen/qwen3.8-27b', '2026-08-21', 450000, 3200000, NULL, 'https://developers.cloudflare.com/workers-ai/platform/pricing/'),
|
|
172
|
+
('workers-ai/@cf/qwen/qwq-32b', '2026-08-21', 660000, 1000000, NULL, 'https://developers.cloudflare.com/workers-ai/platform/pricing/');
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
-- Lanes: a model id that names a job, allocated to a concrete model at request
|
|
2
|
+
-- time.
|
|
3
|
+
--
|
|
4
|
+
-- auto-mode.md designed this as `auto/code:saver`. It ships as `bb/<job>`, a
|
|
5
|
+
-- row in `/v1/models` like any other, because that is the form any
|
|
6
|
+
-- OpenAI-compatible client can consume with no new code: the model is an opaque
|
|
7
|
+
-- string in one field, and nothing parses it. The consuming side (agents#574)
|
|
8
|
+
-- treats any row carrying `x-cruise.lane: true` as a lane and renders its
|
|
9
|
+
-- members.
|
|
10
|
+
--
|
|
11
|
+
-- A lane is data, not a constant, for the reason this repository keeps
|
|
12
|
+
-- recording: adding a model to a lane is a data change, the same as adding a
|
|
13
|
+
-- model. `lane_members` is the candidate set — not a priority. Allocation is by
|
|
14
|
+
-- policy: the cheapest member that passes every hard gate for *this* request
|
|
15
|
+
-- (`src/lanes.ts`). `position` keeps the listing stable and means nothing else.
|
|
16
|
+
--
|
|
17
|
+
-- Members may be unmeasured. Conformance admits them to the lane the same way
|
|
18
|
+
-- it admits them to `/v1/models`; a lane lists and allocates only what is
|
|
19
|
+
-- routable for the asking key.
|
|
20
|
+
--
|
|
21
|
+
-- `role` is the board's job vocabulary (naderu.com/models-board), so the word
|
|
22
|
+
-- on the board and the word in the API are the same word.
|
|
23
|
+
|
|
24
|
+
CREATE TABLE lanes (
|
|
25
|
+
id TEXT PRIMARY KEY CHECK (id LIKE 'bb/%'),
|
|
26
|
+
role TEXT NOT NULL,
|
|
27
|
+
description TEXT NOT NULL,
|
|
28
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
29
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
CREATE TABLE lane_members (
|
|
33
|
+
lane_id TEXT NOT NULL REFERENCES lanes(id),
|
|
34
|
+
model_id TEXT NOT NULL REFERENCES models(id),
|
|
35
|
+
position INTEGER NOT NULL,
|
|
36
|
+
PRIMARY KEY (lane_id, model_id)
|
|
37
|
+
);
|
|
38
|
+
CREATE INDEX idx_lane_members_lane ON lane_members (lane_id, position);
|
|
39
|
+
|
|
40
|
+
-- The first lanes. Members are the models measured on staging on 2026-08-21
|
|
41
|
+
-- that fit the job; the five strongest Workers AI models sit behind the paid
|
|
42
|
+
-- plan and join when that changes.
|
|
43
|
+
|
|
44
|
+
INSERT INTO lanes (id, role, description) VALUES
|
|
45
|
+
('bb/security-review', 'code-review',
|
|
46
|
+
'Reads a diff or a repository and reasons about what is wrong with it. Tool calling and a large window; quality over price.'),
|
|
47
|
+
('bb/agentic-coding', 'agentic-coding',
|
|
48
|
+
'Long tool-use loops over a repository. Output tokens dominate the bill.'),
|
|
49
|
+
('bb/extraction', 'bulk-extraction',
|
|
50
|
+
'Classification and extraction across many rows. Cheapest that clears the bar.'),
|
|
51
|
+
('bb/cheap-fast', 'lowest-latency',
|
|
52
|
+
'Interactive turns where a small model is enough.'),
|
|
53
|
+
('bb/long-context', 'long-context',
|
|
54
|
+
'Whole documents and codebases in one prompt.');
|
|
55
|
+
|
|
56
|
+
INSERT INTO lane_members (lane_id, model_id, position) VALUES
|
|
57
|
+
('bb/security-review', 'deepseek/deepseek-v4-pro', 1),
|
|
58
|
+
('bb/security-review', 'mistral/mistral-large-2512', 2),
|
|
59
|
+
('bb/security-review', 'mistral/mistral-medium-2604', 3),
|
|
60
|
+
('bb/security-review', 'workers-ai/@cf/openai/gpt-oss-120b', 4),
|
|
61
|
+
('bb/security-review', 'deepseek/deepseek-v4-flash', 5),
|
|
62
|
+
|
|
63
|
+
('bb/agentic-coding', 'deepseek/deepseek-v4-pro', 1),
|
|
64
|
+
('bb/agentic-coding', 'mistral/mistral-medium-2604', 2),
|
|
65
|
+
('bb/agentic-coding', 'mistral/codestral-2508', 3),
|
|
66
|
+
('bb/agentic-coding', 'workers-ai/@cf/qwen/qwen3.8-27b', 4),
|
|
67
|
+
('bb/agentic-coding', 'deepseek/deepseek-v4-flash', 5),
|
|
68
|
+
|
|
69
|
+
('bb/extraction', 'deepseek/deepseek-v4-flash', 1),
|
|
70
|
+
('bb/extraction', 'mistral/mistral-small-latest', 2),
|
|
71
|
+
('bb/extraction', 'mistral/ministral-8b-2512', 3),
|
|
72
|
+
('bb/extraction', 'workers-ai/@cf/zai-org/glm-4.7-flash', 4),
|
|
73
|
+
('bb/extraction', 'workers-ai/@cf/ibm-granite/granite-4.0-h-micro', 5),
|
|
74
|
+
|
|
75
|
+
('bb/cheap-fast', 'mistral/ministral-3b-2512', 1),
|
|
76
|
+
('bb/cheap-fast', 'workers-ai/@cf/meta/llama-3.2-3b-instruct', 2),
|
|
77
|
+
('bb/cheap-fast', 'workers-ai/@cf/meta/llama-3.2-1b-instruct', 3),
|
|
78
|
+
('bb/cheap-fast', 'workers-ai/@cf/ibm-granite/granite-4.0-h-micro', 4),
|
|
79
|
+
|
|
80
|
+
('bb/long-context', 'deepseek/deepseek-v4-flash', 1),
|
|
81
|
+
('bb/long-context', 'deepseek/deepseek-v4-pro', 2),
|
|
82
|
+
('bb/long-context', 'mistral/mistral-medium-2604', 3),
|
|
83
|
+
('bb/long-context', 'mistral/ministral-14b-2512', 4);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
-- What each other member would have cost, for the usage that occurred.
|
|
2
|
+
--
|
|
3
|
+
-- A lane allocates the cheapest member passing the request's gates (0026). The
|
|
4
|
+
-- ledger records what that member cost. It does not record what the lane
|
|
5
|
+
-- *saved*, and a number that is not recorded per request cannot be reported
|
|
6
|
+
-- later without being reconstructed — from prices that will have changed and
|
|
7
|
+
-- candidate sets that will have changed with them.
|
|
8
|
+
--
|
|
9
|
+
-- So each lane request writes one row per member that could have served it,
|
|
10
|
+
-- the served one included at rank 1, priced at that member's rate at that hour
|
|
11
|
+
-- for the tokens the request actually used. A report then answers "what did
|
|
12
|
+
-- the lane save against the dearest member", "against the member a human
|
|
13
|
+
-- would have pinned", or "against the median" from rows that were true when
|
|
14
|
+
-- they were written.
|
|
15
|
+
--
|
|
16
|
+
-- The approximation, stated once: the alternative's cost is priced on the
|
|
17
|
+
-- *served* member's token counts. A different model tokenises differently and
|
|
18
|
+
-- would have written a different completion. This is the cost of the same
|
|
19
|
+
-- work at the other member's rate, which is the number a report can defend;
|
|
20
|
+
-- it is not a claim about what the other member would have done.
|
|
21
|
+
--
|
|
22
|
+
-- `position` is the allocation rank: 1 served, 2 would have served had 1
|
|
23
|
+
-- failed, and so on. A member the gates rejected has no row — it could not
|
|
24
|
+
-- have served, so it is not an alternative.
|
|
25
|
+
|
|
26
|
+
CREATE TABLE lane_alternatives (
|
|
27
|
+
ledger_id TEXT NOT NULL REFERENCES ledger(id) ON DELETE CASCADE,
|
|
28
|
+
model TEXT NOT NULL,
|
|
29
|
+
position INTEGER NOT NULL,
|
|
30
|
+
cost_micros INTEGER NOT NULL,
|
|
31
|
+
-- 0 when the member had no price in force, in which case cost_micros is 0
|
|
32
|
+
-- and the row is excluded from savings rather than counted as free.
|
|
33
|
+
priced INTEGER NOT NULL,
|
|
34
|
+
PRIMARY KEY (ledger_id, model)
|
|
35
|
+
);
|
|
36
|
+
CREATE INDEX idx_lane_alternatives_ledger ON lane_alternatives (ledger_id);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
-- A lane belongs to a tenant, or to everyone.
|
|
2
|
+
--
|
|
3
|
+
-- Every lane so far is ours and global: `bb/extraction` is the same for every
|
|
4
|
+
-- tenant and changing it is a migration. The value of a lane to a user is that
|
|
5
|
+
-- it is config they can change at runtime (#66), which needs a lane to have an
|
|
6
|
+
-- owner. This is the schema half; the routes come in #68 and nothing
|
|
7
|
+
-- user-visible changes here.
|
|
8
|
+
--
|
|
9
|
+
-- Namespace. `bb/` stays ours. A tenant's lanes are prefixed with the tenant id
|
|
10
|
+
-- — `acme/review` — which is what the key already carries, so a lane id says
|
|
11
|
+
-- who owns it. Tenants have no slug, so the id is the namespace and must stay
|
|
12
|
+
-- immutable.
|
|
13
|
+
--
|
|
14
|
+
-- The CHECK compares the prefix exactly, with substr() and `=`, not LIKE.
|
|
15
|
+
-- LIKE is case-insensitive and reads `%` and `_` as wildcards, so under LIKE a
|
|
16
|
+
-- tenant id of `a%` or `Acme` would satisfy the check for `acme/review` —
|
|
17
|
+
-- another tenant's namespace. Raised in review of PR #73. What the CHECK still
|
|
18
|
+
-- cannot enforce is that a tenant id is not also an upstream id (`deepseek/...`
|
|
19
|
+
-- is a model): the route that creates tenants must refuse those and a strict
|
|
20
|
+
-- charset, and #68 records that. The request path is defensive regardless — a
|
|
21
|
+
-- lane lookup that misses falls through to the model lookup, so a prefix can
|
|
22
|
+
-- never hide a model.
|
|
23
|
+
--
|
|
24
|
+
-- SQLite cannot alter a CHECK, and `lane_members` references `lanes`, so both
|
|
25
|
+
-- are rebuilt: new tables, copy, drop the old in child-then-parent order, and
|
|
26
|
+
-- rename — which rewrites the foreign key in `lane_members_new` to follow the
|
|
27
|
+
-- parent's new name.
|
|
28
|
+
|
|
29
|
+
CREATE TABLE lanes_new (
|
|
30
|
+
id TEXT PRIMARY KEY,
|
|
31
|
+
-- NULL is a global lane, visible to every tenant and writable by none.
|
|
32
|
+
tenant_id TEXT REFERENCES tenants(id),
|
|
33
|
+
role TEXT NOT NULL,
|
|
34
|
+
description TEXT NOT NULL,
|
|
35
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
36
|
+
created_by TEXT NOT NULL DEFAULT 'migration',
|
|
37
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
38
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
39
|
+
CHECK (
|
|
40
|
+
(tenant_id IS NULL AND substr(id, 1, 3) = 'bb/') OR
|
|
41
|
+
(tenant_id IS NOT NULL AND substr(id, 1, length(tenant_id) + 1) = tenant_id || '/'
|
|
42
|
+
AND substr(id, 1, 3) <> 'bb/')
|
|
43
|
+
)
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
INSERT INTO lanes_new (id, tenant_id, role, description, enabled, created_at, updated_at)
|
|
47
|
+
SELECT id, NULL, role, description, enabled, created_at, created_at FROM lanes;
|
|
48
|
+
|
|
49
|
+
CREATE TABLE lane_members_new (
|
|
50
|
+
lane_id TEXT NOT NULL REFERENCES lanes_new(id),
|
|
51
|
+
model_id TEXT NOT NULL REFERENCES models(id),
|
|
52
|
+
position INTEGER NOT NULL,
|
|
53
|
+
PRIMARY KEY (lane_id, model_id)
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
INSERT INTO lane_members_new (lane_id, model_id, position)
|
|
57
|
+
SELECT lane_id, model_id, position FROM lane_members;
|
|
58
|
+
|
|
59
|
+
DROP INDEX idx_lane_members_lane;
|
|
60
|
+
DROP TABLE lane_members;
|
|
61
|
+
DROP TABLE lanes;
|
|
62
|
+
ALTER TABLE lanes_new RENAME TO lanes;
|
|
63
|
+
ALTER TABLE lane_members_new RENAME TO lane_members;
|
|
64
|
+
|
|
65
|
+
CREATE INDEX idx_lane_members_lane ON lane_members (lane_id, position);
|
|
66
|
+
CREATE INDEX idx_lanes_tenant ON lanes (tenant_id, enabled);
|