@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.
Files changed (137) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +37 -2
  3. package/index.d.ts +14 -0
  4. package/index.mjs +41 -0
  5. package/migrations/0001_virtual_keys.sql +102 -0
  6. package/migrations/0002_seed_projects.sql +16 -0
  7. package/migrations/0003_budgets.sql +43 -0
  8. package/migrations/0004_tenant_isolation_and_integer_money.sql +49 -0
  9. package/migrations/0005_model_prices.sql +42 -0
  10. package/migrations/0006_budget_reservation.sql +38 -0
  11. package/migrations/0007_routing_table.sql +93 -0
  12. package/migrations/0008_measurement_provenance.sql +24 -0
  13. package/migrations/0009_conformance.sql +37 -0
  14. package/migrations/0010_conformance_key.sql +20 -0
  15. package/migrations/0011_correct_workers_ai_price.sql +30 -0
  16. package/migrations/0012_rate_limits.sql +28 -0
  17. package/migrations/0013_deepseek_and_mistral.sql +42 -0
  18. package/migrations/0014_cost_ledger.sql +104 -0
  19. package/migrations/0015_memberships.sql +33 -0
  20. package/migrations/0016_latency.sql +23 -0
  21. package/migrations/0017_ledger_provider_index.sql +8 -0
  22. package/migrations/0018_fallback_chains.sql +34 -0
  23. package/migrations/0019_rate_buckets.sql +27 -0
  24. package/migrations/0020_mistral_price_and_pin.sql +62 -0
  25. package/migrations/0021_mistral_price_date_is_utc.sql +35 -0
  26. package/migrations/0022_drop_unenforced_key_budget.sql +32 -0
  27. package/migrations/0023_model_limits.sql +48 -0
  28. package/migrations/0024_conditional_prices.sql +84 -0
  29. package/migrations/0025_catalogue_from_keyed_hosts.sql +172 -0
  30. package/migrations/0026_lanes.sql +83 -0
  31. package/migrations/0027_lane_alternatives.sql +36 -0
  32. package/migrations/0028_lanes_tenant_scope.sql +66 -0
  33. package/migrations/0029_lane_jobs.sql +146 -0
  34. package/migrations/0030_lane_requires.sql +38 -0
  35. package/migrations/0031_tenant_billable.sql +15 -0
  36. package/migrations/0032_cache_hit_billing.sql +35 -0
  37. package/migrations/0033_sell_markup.sql +37 -0
  38. package/migrations/0034_tiers_and_lane_quotas.sql +38 -0
  39. package/migrations/0035_wallet_scope.sql +68 -0
  40. package/migrations/0036_credit_grants.sql +53 -0
  41. package/migrations/0037_reconciliation.sql +45 -0
  42. package/migrations/0038_balance_service_keys.sql +35 -0
  43. package/migrations/0039_deepseek_peak_is_weekdays.sql +77 -0
  44. package/migrations/0040_glm_5_3_on_workers_ai.sql +63 -0
  45. package/migrations/0041_mistral_sweep_2026_08_29.sql +89 -0
  46. package/migrations/0042_provision_service_keys.sql +50 -0
  47. package/migrations/0043_tier_project_quota.sql +32 -0
  48. package/migrations/0044_audit_tenant_scope.sql +49 -0
  49. package/migrations/0045_audit_backfill_derivable.sql +42 -0
  50. package/migrations/0046_measurement_note.sql +24 -0
  51. package/migrations/0047_ledger_max_tokens.sql +21 -0
  52. package/migrations/0048_price_observations.sql +140 -0
  53. package/migrations/0049_openai_and_gemini.sql +239 -0
  54. package/migrations/0050_meta_direct_upstream.sql +127 -0
  55. package/migrations/0051_upstream_credential_and_operator.sql +74 -0
  56. package/migrations/0052_muse_spark_1_3.sql +71 -0
  57. package/migrations/0053_anthropic_groq_and_xai.sql +172 -0
  58. package/migrations/0054_tier_service_keys.sql +60 -0
  59. package/migrations/0055_output_defaults.sql +79 -0
  60. package/migrations/0056_model_modality.sql +37 -0
  61. package/migrations/0057_openai_image_models.sql +177 -0
  62. package/migrations/0058_price_components.sql +230 -0
  63. package/migrations/0059_speech.sql +151 -0
  64. package/migrations/0060_embeddings.sql +131 -0
  65. package/migrations/0061_data_retention_backfill.sql +121 -0
  66. package/migrations/0062_gemini_embedding_disabled.sql +125 -0
  67. package/migrations/0063_google_provider_path_prefix.sql +59 -0
  68. package/migrations/0064_economics_service_keys.sql +51 -0
  69. package/migrations/0065_lane_selection.sql +66 -0
  70. package/migrations/0066_tenant_default_selection.sql +42 -0
  71. package/migrations/0067_graduated_selection.sql +103 -0
  72. package/migrations/0068_cache_scope.sql +73 -0
  73. package/migrations/0069_lane_cache_settings.sql +50 -0
  74. package/migrations/0070_lane_member_quality_rank.sql +98 -0
  75. package/migrations/0071_key_cache_pool.sql +60 -0
  76. package/migrations/0072_cache_digest.sql +51 -0
  77. package/migrations/0073_lane_max_chain.sql +51 -0
  78. package/migrations/0074_hunter_builder_lanes.sql +149 -0
  79. package/migrations/0075_hunter_builder_frontline.sql +36 -0
  80. package/migrations/0076_deepseek_flash.sql +86 -0
  81. package/migrations/0077_gpt_6_astra.sql +35 -0
  82. package/migrations/0078_astra_deep_reasoning.sql +20 -0
  83. package/migrations/0079_gemini_flash_lite_extraction.sql +15 -0
  84. package/migrations/0080_priced_by.sql +24 -0
  85. package/migrations/0081_grok_imagine.sql +81 -0
  86. package/migrations/0082_markup_three_percent.sql +32 -0
  87. package/migrations/0083_markup_per_tenant_and_tier.sql +57 -0
  88. package/migrations/0084_coding_lanes_frontier.sql +93 -0
  89. package/migrations/0085_minute_period_and_demo_charge.sql +77 -0
  90. package/migrations/0086_project_sentry.sql +41 -0
  91. package/migrations/0087_lane_router.sql +71 -0
  92. package/migrations/0088_openai_frontier_coding_lanes.sql +54 -0
  93. package/migrations/0089_agentic_coding_drops_codestral.sql +44 -0
  94. package/migrations/0090_board_followups_2026_10.sql +196 -0
  95. package/migrations/0091_opus_5_5_coding_lanes.sql +43 -0
  96. package/migrations/0092_deepseek_peak_holidays.sql +83 -0
  97. package/migrations/0093_tool_choice_forced.sql +26 -0
  98. package/migrations/0094_board_followups_2026_10_08.sql +115 -0
  99. package/migrations/0095_request_compression.sql +43 -0
  100. package/migrations/0096_lane_compress.sql +36 -0
  101. package/migrations/0097_tenant_default_compress.sql +34 -0
  102. package/migrations/0098_ledger_session.sql +47 -0
  103. package/migrations/0099_wallet_low_water_backfill.sql +47 -0
  104. package/migrations/0100_rag_grounded_answers_lane.sql +92 -0
  105. package/migrations/0101_perplexity_agent_api.sql +125 -0
  106. package/migrations/0102_web_search_job_and_flag.sql +137 -0
  107. package/migrations/0103_openai_web_search_price.sql +44 -0
  108. package/migrations/0104_web_search_lane.sql +82 -0
  109. package/migrations/0105_web_search_lane_billed.sql +22 -0
  110. package/migrations/0106_reservations_journal.sql +64 -0
  111. package/migrations/0107_session_cap.sql +65 -0
  112. package/migrations/0108_ledger_attempts.sql +41 -0
  113. package/migrations/0109_gemini_retention.sql +39 -0
  114. package/migrations/0110_gemini_4_argon.sql +110 -0
  115. package/migrations/0111_gemini_4_argon_lanes.sql +80 -0
  116. package/migrations/0112_ledger_affinity.sql +42 -0
  117. package/migrations/0113_contributor_lane_namespace.sql +125 -0
  118. package/migrations/0114_meta_contributor_models.sql +55 -0
  119. package/migrations/0115_oss_chat_assistant_lane.sql +41 -0
  120. package/migrations/0116_promo_credits.sql +63 -0
  121. package/migrations/0117_oss_mirror_lanes.sql +85 -0
  122. package/migrations/0118_project_management_job.sql +112 -0
  123. package/migrations/0119_oss_project_management_lane.sql +29 -0
  124. package/migrations/0120_moonshot_k3_1_pro.sql +107 -0
  125. package/migrations/0121_jobs.sql +100 -0
  126. package/migrations/0122_batch_prices.sql +188 -0
  127. package/migrations/0123_batch_files.sql +50 -0
  128. package/migrations/0124_batch_run.sql +21 -0
  129. package/migrations/0125_anthropic_path_prefix.sql +22 -0
  130. package/package.json +37 -4
  131. package/worker/284906b51fc0a3173ea7d301234907f30e834f54-cruise-mark-64.png +0 -0
  132. package/worker/2c39de526314484775fdfc503fac50c437d21e4e-cruise-logo-240.png +0 -0
  133. package/worker/63fb63e2d8ae6d8b68228768ef6c91ab8f0c0100-cruise-logo-480.png +0 -0
  134. package/worker/VERSION +1 -0
  135. package/worker/b23bfbc792e8710d3b74284a0a1b8ab044c18897-logo-dark.png +0 -0
  136. package/worker/index.js +31451 -0
  137. package/wrangler.template.jsonc +48 -0
@@ -0,0 +1,81 @@
1
+ -- Grok Imagine: the first models priced per image (#284), unlocked by `priced_by`
2
+ -- (#240, 0080). Video rows stay out — no `/v1/video/*` route and #220 said nobody
3
+ -- is blocked on them.
4
+ --
5
+ -- ## Measured, 2026-09-13 (UTC)
6
+ --
7
+ -- Direct to `https://api.x.ai/v1/images/generations` with an xAI key:
8
+ --
9
+ -- model + prompt + n + response_format=b64_json|url 200, `data[0].b64_json`
10
+ -- or `.url`, plus `mime_type`;
11
+ -- `usage.cost_in_usd_ticks`
12
+ -- only — no token counts
13
+ -- + aspect_ratio / resolution 200
14
+ -- + quality low|medium|high|auto 200
15
+ -- + size (OpenAI's field) 400, "Argument not supported: size"
16
+ -- + quality=standard (OpenAI's word) 422, unknown variant
17
+ --
18
+ -- So the path is OpenAI-compatible enough for Cruise's images route, but the
19
+ -- body is not a free pass-through: `size` must be stripped and an OpenAI
20
+ -- `quality` value remapped or dropped. That lives in `src/images-grok.ts`, the
21
+ -- same shape as ElevenLabs for speech — a host whose wire is almost OpenAI's
22
+ -- and not quite.
23
+ --
24
+ -- Prices from each model's page on docs.x.ai the same day, matching the ticks
25
+ -- the live responses printed ($0.02 / $0.05 / $0.04 → 20k / 50k / 40k micros):
26
+ --
27
+ -- grok-imagine-image $0.02 / image
28
+ -- grok-imagine-image-quality $0.05 / image
29
+ -- grok-imagine-image-2.0 $0.04 / image
30
+ --
31
+ -- Flat rates, one figure per model — no size×quality grid. The `image`
32
+ -- component's `variant` is therefore `''`, and `/v1/models` publishes that as
33
+ -- the accepted set via the price log (#240). **`model_image_variants` is not
34
+ -- written for these rows**: that table's `output_tokens NOT NULL` is the bound
35
+ -- for a token-priced image model, and a per-image model has no such number.
36
+ -- Making the column nullable was option 1 on #284; not writing the table for
37
+ -- `priced_by = 'component'` is the same fact written once — `priced_by` already
38
+ -- says the bound is the component, not a measured token count.
39
+ --
40
+ -- ## Path on the gateway
41
+ --
42
+ -- Chat stays on `/compat`. Images take the provider path (`src/images.ts`),
43
+ -- which appends `/images/generations` with no version of its own — OpenAI's
44
+ -- gateway provider supplies `/v1`, xAI's API lives under `/v1`, so the
45
+ -- upstream's `provider_path_prefix` is `/v1`. That is the measured direct
46
+ -- shape applied to the gateway leg; a 404 through the gateway after
47
+ -- `PROVIDER_KEY_GROK` is set is the cheap way to learn it was wrong, the same
48
+ -- discovery 0063 made for Google.
49
+ --
50
+ -- `base_url` is deliberately not set: it is per upstream and would yank the
51
+ -- chat rows off `/compat` (0063). `provider_path_prefix` is safe to set: only
52
+ -- `providerPath` in `src/route-run.ts` reads it (images, speech, embeddings).
53
+ -- Chat hard-codes `/compat/chat/completions` in `src/chat.ts` and never
54
+ -- consults this column — same fact 0063 recorded for Google.
55
+ --
56
+ -- Capability flags stay NULL. Conformance is the only writer, and a
57
+ -- component-priced image run measures generation only — not usage tokens, not
58
+ -- a nine-cell grid — because neither is a property of this bill.
59
+ UPDATE upstreams SET provider_path_prefix = '/v1' WHERE id = 'grok';
60
+
61
+ INSERT INTO models (id, upstream_id, upstream_model, modality, priced_by,
62
+ data_retention, data_retention_source, data_retention_as_of) VALUES
63
+ ('grok/grok-imagine-image', 'grok', 'grok-imagine-image', 'image', 'component',
64
+ 'retained', 'https://docs.x.ai/developers/faq/security', '2026-09-05'),
65
+ ('grok/grok-imagine-image-quality', 'grok', 'grok-imagine-image-quality', 'image', 'component',
66
+ 'retained', 'https://docs.x.ai/developers/faq/security', '2026-09-05'),
67
+ ('grok/grok-imagine-image-2.0', 'grok', 'grok-imagine-image-2.0', 'image', 'component',
68
+ 'retained', 'https://docs.x.ai/developers/faq/security', '2026-09-05');
69
+
70
+ INSERT INTO price_components
71
+ (observed_at, model, effective_from, component, variant, micros, per, source, note)
72
+ VALUES
73
+ ('2026-09-13 00:00:00', 'grok/grok-imagine-image', '2026-09-13', 'image', '',
74
+ 20000, 1, 'https://docs.x.ai/developers/models/grok-imagine-image',
75
+ 'First observation: $0.02 per image, flat. Measured response usage.cost_in_usd_ticks = 200000000 on 2026-09-13.'),
76
+ ('2026-09-13 00:00:00', 'grok/grok-imagine-image-quality', '2026-09-13', 'image', '',
77
+ 50000, 1, 'https://docs.x.ai/developers/models/grok-imagine-image-quality',
78
+ 'First observation: $0.05 per image, flat. Measured response usage.cost_in_usd_ticks = 500000000 on 2026-09-13.'),
79
+ ('2026-09-13 00:00:00', 'grok/grok-imagine-image-2.0', '2026-09-13', 'image', '',
80
+ 40000, 1, 'https://docs.x.ai/developers/models/grok-imagine-image-2.0',
81
+ 'First observation: $0.04 per image, flat. Measured response usage.cost_in_usd_ticks = 400000000 on 2026-09-13.');
@@ -0,0 +1,32 @@
1
+ -- The platform default markup falls from 10% to 3% (#463, dashboard epic
2
+ -- dashboard#620).
3
+ --
4
+ -- Why: BytesBrains' published price becomes "a 3% markup on usage", charged
5
+ -- as a request is served. Payment-gateway fees are the dashboard's, shown at the
6
+ -- payment step, and never pass through Cruise — so the markup is the whole of
7
+ -- Cruise's margin and the whole of what the pricing page states. The dashboard's
8
+ -- copy change (dashboard#625) is held until production charges
9
+ -- this, because advertising 3% while charging 10% is a false price.
10
+ --
11
+ -- A new row, not an edit of 0033's: sell_markups is dated like a price, so a
12
+ -- request settled before this date keeps the 10% it was charged, and a
13
+ -- statement or reconciliation re-derived later still finds that basis.
14
+ --
15
+ -- What it changes, and what it does not:
16
+ --
17
+ -- * Only `'*'`. A tenant with a row of its own keeps its own rate — the
18
+ -- resolution in `markupInForce` prefers the tenant's row however dated.
19
+ -- * Only billed tenants. Internal tenants and the conformance runner are
20
+ -- charged list and never read this table.
21
+ -- * The rate is read twice per request, at reservation and at settlement,
22
+ -- each at its own moment. A request reserved before 00:00 UTC on the day
23
+ -- and settled after is held at 10% and charged 3%; the difference is
24
+ -- released, so a cap is never breached by a fall. (A rise would settle
25
+ -- above its hold by the difference — #464's concern, not this row's.)
26
+ -- * A cache hit is the published fraction of list with the markup on top:
27
+ -- 20% of list, then plus 3%.
28
+ --
29
+ -- Dated 2026-09-16 (UTC), agreed for #463 on 2026-09-15. Mid-month, so a
30
+ -- September statement's `markup` field names the rate in force on its last
31
+ -- day — 3% — while its lines carry each row as charged, 10% and 3% both.
32
+ INSERT INTO sell_markups (tenant_id, effective_from, basis_points) VALUES ('*', '2026-09-16', 300);
@@ -0,0 +1,57 @@
1
+ -- A markup per tenant and per tier, set over the wire (#464, dashboard epic
2
+ -- dashboard#620).
3
+ --
4
+ -- Why: 0033 made the markup a dated per-tenant row and said a tier "is a policy
5
+ -- that writes these rows". Nothing ever wrote one — no route, no command — so a
6
+ -- deal for one customer was a migration, and #463's platform default was the
7
+ -- only lever that moved. Staff now set a rate per account and per tier from
8
+ -- the dashboard's admin panel, through `/payments/markup/*`.
9
+ --
10
+ -- Resolution is one order, in `markupInForce`: the tenant's own row, then its
11
+ -- tier's, then the platform's `'*'` — each the latest row not after the day.
12
+ --
13
+ -- 1. `sell_markups.basis_points` may be NULL, on a tenant row only. A NULL row
14
+ -- means "no rate of its own from this date": it supersedes the tenant's
15
+ -- earlier rows and resolution falls through to the tier. Clearing an
16
+ -- override is a new dated row, never a delete, so a month charged at the
17
+ -- override still finds it. `'*'` cannot be cleared — a billed tenant charged
18
+ -- 0% because the default went missing is 0033's reason for seeding one.
19
+ --
20
+ -- SQLite cannot relax NOT NULL or add a table CHECK in place, so the table
21
+ -- is rebuilt. Nothing references it: no view, no trigger, no foreign key.
22
+ --
23
+ -- 2. `tier_markups`, a table of its own rather than `'tier:pro'` rows in
24
+ -- `sell_markups`: a tier id is not a tenant id, and one column meaning two
25
+ -- things is how a lookup for one comes to match the other. Not seeded — a
26
+ -- tier with no row resolves to the platform default, so this migration
27
+ -- changes no charge.
28
+ --
29
+ -- What is not dated: `tenants.tier` holds the tier a tenant is on now. A tenant
30
+ -- that moves tier is charged the new tier's rate from its next request, and a
31
+ -- past month's statement names the rate of the tier held today. Each ledger row
32
+ -- carries what its request was actually charged, so the month's lines are exact
33
+ -- either way.
34
+ CREATE TABLE sell_markups_new (
35
+ tenant_id TEXT NOT NULL,
36
+ effective_from TEXT NOT NULL,
37
+ -- Above list, in basis points: 300 is list plus 3%. NULL clears a tenant's
38
+ -- own rate from this date.
39
+ basis_points INTEGER CHECK (basis_points IS NULL OR basis_points BETWEEN 0 AND 10000),
40
+ recorded_at TEXT NOT NULL DEFAULT (datetime('now')),
41
+ PRIMARY KEY (tenant_id, effective_from),
42
+ CHECK (tenant_id <> '*' OR basis_points IS NOT NULL)
43
+ );
44
+
45
+ INSERT INTO sell_markups_new (tenant_id, effective_from, basis_points, recorded_at)
46
+ SELECT tenant_id, effective_from, basis_points, recorded_at FROM sell_markups;
47
+
48
+ DROP TABLE sell_markups;
49
+ ALTER TABLE sell_markups_new RENAME TO sell_markups;
50
+
51
+ CREATE TABLE tier_markups (
52
+ tier_id TEXT NOT NULL REFERENCES tiers (id),
53
+ effective_from TEXT NOT NULL,
54
+ basis_points INTEGER NOT NULL CHECK (basis_points BETWEEN 0 AND 10000),
55
+ recorded_at TEXT NOT NULL DEFAULT (datetime('now')),
56
+ PRIMARY KEY (tier_id, effective_from)
57
+ );
@@ -0,0 +1,93 @@
1
+ -- Restaff the four coding lanes with each lab's best coding model.
2
+ --
3
+ -- bb/agentic-coding, bb/builder, bb/hunter and bb/code-review had drifted
4
+ -- into a cheap pool: DeepSeek Flash at quality_rank 1 on all four, beside
5
+ -- qwen3.8-27b, gpt-oss-120b and mistral-medium. That clears the ≥3-routable
6
+ -- bar and is not what a caller naming a coding lane is asking for. 0075
7
+ -- left the Claude 5 rows out because `measured_at` was null; they have since
8
+ -- measured clean (below), so the reason is gone.
9
+ --
10
+ -- ## One lab, its best coding model
11
+ --
12
+ -- The team is the strongest tools-capable coding model each lab we buy from
13
+ -- directly (upstreams.md) has in the catalogue. Every member is tools=1 —
14
+ -- all four lanes are `tools-required`, and a tools=0 member never allocates.
15
+ --
16
+ -- Anthropic claude-fable-5-1, claude-opus-5 rank 1
17
+ -- Google gemini-3.1-pro-preview rank 2
18
+ -- xAI grok-4.6 rank 2
19
+ -- Moonshot kimi-k2.7-code (Workers AI) rank 2 (hunter: kimi-k2.6, as 0075)
20
+ -- Zhipu glm-5.3 (Workers AI) rank 3
21
+ -- Mistral codestral-2508 rank 3 (hunter / code-review:
22
+ -- mistral-medium-2604 — coding
23
+ -- polish invents plausible vulns, 0074)
24
+ --
25
+ -- Anthropic carries two at rank 1 on purpose: Fable 5.1 and Opus 5 are the
26
+ -- two strongest coding models in the catalogue, and graduated splits rank-1
27
+ -- peers equally. Sonnet 5 is not added — Opus 5 is above it at the same job.
28
+ --
29
+ -- ## Who is absent, and why
30
+ --
31
+ -- openai/gpt-6-astra measured tools=0: chat-completions refuses function
32
+ -- tools alongside reasoning_effort (0078). The best
33
+ -- OpenAI model cannot join a tools-required lane until
34
+ -- the probe or a Responses path measures tools honestly.
35
+ -- deepseek/deepseek-flash DeepSeek's only live chat row since V4-Pro retired
36
+ -- (0076); a flash model is not a frontier coder.
37
+ -- Returns when a V4.1-Pro ships and is measured.
38
+ -- workers-ai gpt-oss-120b, qwen3.8-27b, gemini-3.8-flash — mid-tier, and each
39
+ -- lab above already has a stronger member here.
40
+ --
41
+ -- ## What to check before applying to an environment
42
+ --
43
+ -- Measured on staging (UTC): claude-* 2026-09-12, grok-4.6 2026-09-10 —
44
+ -- tools, streaming and usage all 1. Production was not read when this was
45
+ -- written. An unmeasured member is refused per request rather than served,
46
+ -- but it nulls the lane's folded capabilities in `/v1/models` (lanes.ts), so
47
+ -- confirm `measured_at` and `tools = 1` on production for those five rows
48
+ -- first.
49
+ --
50
+ -- Streaming: gemini-3.1-pro-preview and kimi-k2.6 measure streaming=0 and
51
+ -- glm-5.3 null, so a `stream: true` request is served from the other members.
52
+ --
53
+ -- data_retention: gemini and grok rows are NULL (#454), so once #450 is
54
+ -- deployed a billed tenant is refused them and drawn from the rest.
55
+ --
56
+ -- Selection policy is unchanged on every lane: builder and hunter stay
57
+ -- `graduated`, agentic-coding and code-review stay as they are.
58
+
59
+ DELETE FROM lane_members
60
+ WHERE lane_id IN ('bb/agentic-coding', 'bb/builder', 'bb/hunter', 'bb/code-review');
61
+
62
+ INSERT INTO lane_members (lane_id, model_id, position, quality_rank) VALUES
63
+ ('bb/agentic-coding', 'anthropic/claude-fable-5-1', 1, 1),
64
+ ('bb/agentic-coding', 'anthropic/claude-opus-5', 2, 1),
65
+ ('bb/agentic-coding', 'google-ai-studio/gemini-3.1-pro-preview', 3, 2),
66
+ ('bb/agentic-coding', 'grok/grok-4.6', 4, 2),
67
+ ('bb/agentic-coding', 'workers-ai/@cf/moonshotai/kimi-k2.7-code', 5, 2),
68
+ ('bb/agentic-coding', 'workers-ai/@cf/zai-org/glm-5.3', 6, 3),
69
+ ('bb/agentic-coding', 'mistral/codestral-2508', 7, 3),
70
+
71
+ ('bb/builder', 'anthropic/claude-fable-5-1', 1, 1),
72
+ ('bb/builder', 'anthropic/claude-opus-5', 2, 1),
73
+ ('bb/builder', 'google-ai-studio/gemini-3.1-pro-preview', 3, 2),
74
+ ('bb/builder', 'grok/grok-4.6', 4, 2),
75
+ ('bb/builder', 'workers-ai/@cf/moonshotai/kimi-k2.7-code', 5, 2),
76
+ ('bb/builder', 'workers-ai/@cf/zai-org/glm-5.3', 6, 3),
77
+ ('bb/builder', 'mistral/codestral-2508', 7, 3),
78
+
79
+ ('bb/hunter', 'anthropic/claude-fable-5-1', 1, 1),
80
+ ('bb/hunter', 'anthropic/claude-opus-5', 2, 1),
81
+ ('bb/hunter', 'google-ai-studio/gemini-3.1-pro-preview', 3, 2),
82
+ ('bb/hunter', 'grok/grok-4.6', 4, 2),
83
+ ('bb/hunter', 'workers-ai/@cf/moonshotai/kimi-k2.6', 5, 2),
84
+ ('bb/hunter', 'workers-ai/@cf/zai-org/glm-5.3', 6, 3),
85
+ ('bb/hunter', 'mistral/mistral-medium-2604', 7, 3),
86
+
87
+ ('bb/code-review', 'anthropic/claude-fable-5-1', 1, 1),
88
+ ('bb/code-review', 'anthropic/claude-opus-5', 2, 1),
89
+ ('bb/code-review', 'google-ai-studio/gemini-3.1-pro-preview', 3, 2),
90
+ ('bb/code-review', 'grok/grok-4.6', 4, 2),
91
+ ('bb/code-review', 'workers-ai/@cf/moonshotai/kimi-k2.7-code', 5, 2),
92
+ ('bb/code-review', 'workers-ai/@cf/zai-org/glm-5.3', 6, 3),
93
+ ('bb/code-review', 'mistral/mistral-medium-2604', 7, 3);
@@ -0,0 +1,77 @@
1
+ -- A budget that trips on the demo, for free, and heals itself (#494).
2
+ --
3
+ -- The hard stop tripping mid-run is the one thing Cruise does that a
4
+ -- gateway does not, and the demo could only show its headers. Every demo
5
+ -- price is zero (demo-catalogue.ts), so `x-cruise-budget-spend` never moved
6
+ -- off 0.0000 and the cap was unreachable. A public sample
7
+ -- (bytesbrains/cruise-agentgateway) had to drop the cap from its v0 for
8
+ -- exactly that reason.
9
+ --
10
+ -- Two columns change, and both are needed:
11
+ --
12
+ -- `period` admits 'minute'. A published key is shared by every stranger
13
+ -- who runs the sample, so a tiny cap on a daily period is taken down for
14
+ -- everyone by the first person to trip it, until midnight UTC. That trap
15
+ -- is named in #494 itself. A minute is the shortest window that still fits
16
+ -- a run of a handful of calls, and it means `retry-after` is never more
17
+ -- than 60 seconds. It is set only on the demo: `budget:set` refuses
18
+ -- `--period minute` for any other `--env`, and the control plane still
19
+ -- accepts day or month only. A minute window is one `spend` row per project
20
+ -- per minute, which is fine for one demo project and not a dial to hand to
21
+ -- customers.
22
+ --
23
+ -- `demo_charge_micros` is a flat charge per served request. Catalogue prices
24
+ -- stay zero on purpose: #327 decided that, `catalogue_parity` compares
25
+ -- against production, and a real price would let a no-max_tokens request
26
+ -- on a dear model reserve more than a demo tenant's $5. So the cost comes
27
+ -- from the project instead of the model. Only the one project that publishes
28
+ -- the cap has a non-zero value, and every other demo tenant still reads
29
+ -- 0.0000. It is honoured only in CREDENTIAL_FREE_ENVIRONMENTS
30
+ -- (`src/demo-charge.ts`). On production the column is never read, so a value
31
+ -- that got there by hand bills nobody.
32
+ --
33
+ -- CHECK constraints cannot be altered in SQLite, so both tables are rebuilt,
34
+ -- the same way 0035 built them. Nothing references either table by foreign
35
+ -- key, and neither has an index or trigger to recreate.
36
+ CREATE TABLE budgets_new (
37
+ scope TEXT NOT NULL CHECK (scope IN ('project', 'tenant')),
38
+ subject TEXT NOT NULL,
39
+ period TEXT NOT NULL DEFAULT 'day' CHECK (period IN ('minute', 'day', 'month', 'lifetime')),
40
+ soft_micros INTEGER,
41
+ hard_micros INTEGER,
42
+ reserve_micros INTEGER NOT NULL DEFAULT 50000,
43
+ overdraft_micros INTEGER NOT NULL DEFAULT 0 CHECK (overdraft_micros >= 0),
44
+ -- Charged on every 2xx, on top of what the usage cost, and reserved with
45
+ -- the bound so that the cap refuses at the reservation rather than
46
+ -- overshooting at settlement. Zero everywhere but the demo's cap project.
47
+ demo_charge_micros INTEGER NOT NULL DEFAULT 0 CHECK (demo_charge_micros >= 0),
48
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
49
+ updated_by TEXT,
50
+ PRIMARY KEY (scope, subject),
51
+ CHECK ((scope = 'tenant') = (period = 'lifetime'))
52
+ );
53
+
54
+ INSERT INTO budgets_new (scope, subject, period, soft_micros, hard_micros, reserve_micros, overdraft_micros, updated_at, updated_by)
55
+ SELECT scope, subject, period, soft_micros, hard_micros, reserve_micros, overdraft_micros, updated_at, updated_by
56
+ FROM budgets;
57
+ DROP TABLE budgets;
58
+ ALTER TABLE budgets_new RENAME TO budgets;
59
+
60
+ CREATE TABLE spend_new (
61
+ scope TEXT NOT NULL CHECK (scope IN ('project', 'tenant')),
62
+ subject TEXT NOT NULL,
63
+ period TEXT NOT NULL CHECK (period IN ('minute', 'day', 'month', 'lifetime')),
64
+ -- '' for lifetime. `YYYY-MM-DD HH:MM` for a minute, which is what
65
+ -- strftime('%Y-%m-%d %H:%M', 'now') gives (`src/budget.ts`).
66
+ period_start TEXT NOT NULL,
67
+ spend_micros INTEGER NOT NULL DEFAULT 0,
68
+ request_count INTEGER NOT NULL DEFAULT 0,
69
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
70
+ PRIMARY KEY (scope, subject, period, period_start)
71
+ );
72
+
73
+ INSERT INTO spend_new (scope, subject, period, period_start, spend_micros, request_count, updated_at)
74
+ SELECT scope, subject, period, period_start, spend_micros, request_count, updated_at
75
+ FROM spend;
76
+ DROP TABLE spend;
77
+ ALTER TABLE spend_new RENAME TO spend;
@@ -0,0 +1,41 @@
1
+ -- A project's own Sentry, as a destination the tenant chose (#506, epic #508).
2
+ --
3
+ -- Why: Cruise's answers that change what a tenant's app gets (a budget crossing
4
+ -- its soft threshold, the hard stop, a wallet running low) reach the app today as
5
+ -- a status code in its own logs, if it logs them. #506 sends them as issues into
6
+ -- the tenant's own Sentry project, next to their errors. This migration is the
7
+ -- half that stores where; nothing sends yet.
8
+ --
9
+ -- Per project, not per tenant: a Cruise project is an app, and so is a Sentry
10
+ -- project, and budgets are per project already. Keyed by project alone, so the
11
+ -- tenant is reached one way only, through `projects.tenant_id` (0004's rule).
12
+ --
13
+ -- A table of its own rather than a column on `projects`, so no reader of
14
+ -- `projects` (the listing, the doctor, a `SELECT *` in a script) can carry the
15
+ -- key along by accident.
16
+ --
17
+ -- The DSN is stored split. `endpoint` is everything but the key:
18
+ -- scheme://host[:port][/prefix]/<sentry project id>. It is what the control
19
+ -- plane returns and what the audit trail records, so a trail can say which
20
+ -- Sentry project a change pointed at without holding the credential.
21
+ -- `public_key` is the part that authorises sending. Sentry calls it public,
22
+ -- because a DSN ships in browser bundles and grants nothing but ingest, but it
23
+ -- is the tenant's credential all the same: never returned, logged or audited.
24
+ --
25
+ -- Not encrypted at rest. The worst a leaked key does is send events into the
26
+ -- tenant's own Sentry project, which rotating the DSN in Sentry ends, and a
27
+ -- key for Cruise to encrypt with would be a new secret per environment
28
+ -- guarding less than it costs. Revisit if a stored credential here ever grants
29
+ -- more than ingest.
30
+ --
31
+ -- Clearing sets both columns NULL rather than deleting the row, so the columns
32
+ -- #506 adds next (the last state sent, per event kind) survive a DSN being
33
+ -- switched off and on.
34
+ CREATE TABLE project_sentry (
35
+ project_id TEXT PRIMARY KEY REFERENCES projects (id),
36
+ endpoint TEXT,
37
+ public_key TEXT,
38
+ updated_by TEXT NOT NULL,
39
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
40
+ CHECK ((endpoint IS NULL) = (public_key IS NULL))
41
+ );
@@ -0,0 +1,71 @@
1
+ -- A lane can ask a router which member should serve, before it draws (#531).
2
+ --
3
+ -- Every selection policy so far (0065, 0067) decides from what Cruise already
4
+ -- knows: price, health, a weight, a declared rank. None of them reads the
5
+ -- request, so a one-line typo fix and a rewrite of the auth middleware reach
6
+ -- the same member of `bb/code-review` with the same odds. #531 asks for the
7
+ -- other thing: hand a router the request and the lane's members, and let it
8
+ -- say which one this request needs. The router it names is Jev (TypeSafe AI),
9
+ -- a model that returns a typed choice and a calibrated probability per option
10
+ -- rather than prose, in a few hundred milliseconds.
11
+ --
12
+ -- ── Why this is not a fifth selection policy ─────────────────────────────────
13
+ --
14
+ -- The obvious shape is `selection_policy = 'jev'`, and it is wrong twice.
15
+ --
16
+ -- **The CHECK.** Three constraints enumerate the policies, SQLite cannot alter
17
+ -- a CHECK, and 0067 records at length why neither a table rebuild nor a column
18
+ -- swap is safe on D1. A fifth value would be a second expand-and-contract over
19
+ -- `lanes`, `ledger` and `tenants` to add one word.
20
+ --
21
+ -- **The meaning.** A router can fail — no credential, a timeout, an answer it
22
+ -- is not confident of, a request with nothing to route on — and a lane that
23
+ -- fails *to choose* must still serve. So the router needs a fallback, and the
24
+ -- fallback is exactly what `selection_policy` already is. Layering the router
25
+ -- over the policy says that in the schema: Jev picks the head when it can, and
26
+ -- the lane's own policy runs when it cannot. A router that replaced the policy
27
+ -- would need its fallback stored somewhere else anyway.
28
+ --
29
+ -- So: two columns beside the policy, both new, both pure adds — the one shape
30
+ -- of schema change 0067 found to have no window.
31
+ --
32
+ -- ── What goes to the router ──────────────────────────────────────────────────
33
+ --
34
+ -- `router_input` exists because TypeSafe is a new party that sees request
35
+ -- data, and how much it sees is a privacy decision the lane's owner makes, not
36
+ -- a default we pick for them (decided on #531).
37
+ --
38
+ -- hint (default) only the caller's `x-cruise-route-hint` header — what the
39
+ -- issue's own example sends: an issue type, an intent, a diff
40
+ -- summary. No prompt text leaves Cruise. A request without a hint
41
+ -- is not routed; the lane's policy draws.
42
+ -- prompt the hint if any, plus the last user message, truncated. What a
43
+ -- stock client that cannot set headers needs, and an explicit
44
+ -- opt-in because it sends prompt content to a sub-processor.
45
+ --
46
+ -- An operator's terms should list it beside the model hosts.
47
+ --
48
+ -- ── The ledger ───────────────────────────────────────────────────────────────
49
+ --
50
+ -- `ledger.router` names the router a lane consulted (NULL when it had none),
51
+ -- `router_outcome` what came of it, and `router_confidence` the probability
52
+ -- Jev put on its choice when it made one. The outcome is recorded on the
53
+ -- fallbacks too, because "the router ran and was ignored 80% of the time" is
54
+ -- the finding that says whether it earns its latency, and it is invisible if
55
+ -- only the successes are kept. The confidence is kept for the same reason
56
+ -- `quality_rank` was declared before it was measured: a threshold is a guess
57
+ -- until the ledger can show what each confidence band went on to complete.
58
+ --
59
+ -- **Deploy order is migrate first, then the Worker**, as for every add.
60
+
61
+ ALTER TABLE lanes ADD COLUMN router TEXT
62
+ CHECK (router IS NULL OR router IN ('jev'));
63
+ ALTER TABLE lanes ADD COLUMN router_input TEXT NOT NULL DEFAULT 'hint'
64
+ CHECK (router_input IN ('hint', 'prompt'));
65
+
66
+ ALTER TABLE ledger ADD COLUMN router TEXT
67
+ CHECK (router IS NULL OR router IN ('jev'));
68
+ ALTER TABLE ledger ADD COLUMN router_outcome TEXT
69
+ CHECK (router_outcome IS NULL OR router_outcome IN
70
+ ('chose', 'low_confidence', 'failed', 'malformed', 'no_input', 'unconfigured', 'bypassed'));
71
+ ALTER TABLE ledger ADD COLUMN router_confidence REAL;
@@ -0,0 +1,54 @@
1
+ -- Support tool calling for OpenAI frontier reasoning models and restaff coding lanes (#545).
2
+ --
3
+ -- Prior to #545, OpenAI's newer reasoning models (gpt-6-astra, gpt-5.6-*) refused
4
+ -- function tools on the legacy /v1/chat/completions endpoint ("use /v1/responses
5
+ -- or set reasoning_effort to 'none'"). With Cruise's /v1/responses route and
6
+ -- translation adapter in place, tool calling is now measured and served natively.
7
+ --
8
+ -- Promotes openai/gpt-6-astra (quality_rank 1) and openai/gpt-5.6-sol (quality_rank 2)
9
+ -- into bb/agentic-coding, bb/builder, bb/hunter, and bb/code-review alongside Claude 5
10
+ -- and Gemini 3.1 Pro now that /v1/responses route & adapter allows honest tool measurement.
11
+
12
+ DELETE FROM lane_members
13
+ WHERE lane_id IN ('bb/agentic-coding', 'bb/builder', 'bb/hunter', 'bb/code-review');
14
+
15
+ INSERT INTO lane_members (lane_id, model_id, position, quality_rank) VALUES
16
+ ('bb/agentic-coding', 'anthropic/claude-fable-5-1', 1, 1),
17
+ ('bb/agentic-coding', 'anthropic/claude-opus-5', 2, 1),
18
+ ('bb/agentic-coding', 'openai/gpt-6-astra', 3, 1),
19
+ ('bb/agentic-coding', 'google-ai-studio/gemini-3.1-pro-preview', 4, 2),
20
+ ('bb/agentic-coding', 'openai/gpt-5.6-sol', 5, 2),
21
+ ('bb/agentic-coding', 'grok/grok-4.6', 6, 2),
22
+ ('bb/agentic-coding', 'workers-ai/@cf/moonshotai/kimi-k2.7-code', 7, 2),
23
+ ('bb/agentic-coding', 'workers-ai/@cf/zai-org/glm-5.3', 8, 3),
24
+ ('bb/agentic-coding', 'mistral/codestral-2508', 9, 3),
25
+
26
+ ('bb/builder', 'anthropic/claude-fable-5-1', 1, 1),
27
+ ('bb/builder', 'anthropic/claude-opus-5', 2, 1),
28
+ ('bb/builder', 'openai/gpt-6-astra', 3, 1),
29
+ ('bb/builder', 'google-ai-studio/gemini-3.1-pro-preview', 4, 2),
30
+ ('bb/builder', 'openai/gpt-5.6-sol', 5, 2),
31
+ ('bb/builder', 'grok/grok-4.6', 6, 2),
32
+ ('bb/builder', 'workers-ai/@cf/moonshotai/kimi-k2.7-code', 7, 2),
33
+ ('bb/builder', 'workers-ai/@cf/zai-org/glm-5.3', 8, 3),
34
+ ('bb/builder', 'mistral/codestral-2508', 9, 3),
35
+
36
+ ('bb/hunter', 'anthropic/claude-fable-5-1', 1, 1),
37
+ ('bb/hunter', 'anthropic/claude-opus-5', 2, 1),
38
+ ('bb/hunter', 'openai/gpt-6-astra', 3, 1),
39
+ ('bb/hunter', 'google-ai-studio/gemini-3.1-pro-preview', 4, 2),
40
+ ('bb/hunter', 'openai/gpt-5.6-sol', 5, 2),
41
+ ('bb/hunter', 'grok/grok-4.6', 6, 2),
42
+ ('bb/hunter', 'workers-ai/@cf/moonshotai/kimi-k2.6', 7, 2),
43
+ ('bb/hunter', 'workers-ai/@cf/zai-org/glm-5.3', 8, 3),
44
+ ('bb/hunter', 'mistral/mistral-medium-2604', 9, 3),
45
+
46
+ ('bb/code-review', 'anthropic/claude-fable-5-1', 1, 1),
47
+ ('bb/code-review', 'anthropic/claude-opus-5', 2, 1),
48
+ ('bb/code-review', 'openai/gpt-6-astra', 3, 1),
49
+ ('bb/code-review', 'google-ai-studio/gemini-3.1-pro-preview', 4, 2),
50
+ ('bb/code-review', 'openai/gpt-5.6-sol', 5, 2),
51
+ ('bb/code-review', 'grok/grok-4.6', 6, 2),
52
+ ('bb/code-review', 'workers-ai/@cf/moonshotai/kimi-k2.7-code', 7, 2),
53
+ ('bb/code-review', 'workers-ai/@cf/zai-org/glm-5.3', 8, 3),
54
+ ('bb/code-review', 'mistral/mistral-medium-2604', 9, 3);
@@ -0,0 +1,44 @@
1
+ -- Take codestral-2508 out of the two lanes Claude Code drives (#549).
2
+ --
3
+ -- On 2026-09-29 (17:59–18:08 UTC) every request Claude Code sent to
4
+ -- `bb/agentic-coding` in production was served by `mistral/codestral-2508` —
5
+ -- five requests, 32,284 completion tokens, all under the lane's `cheap` policy,
6
+ -- which takes the cheapest routable member and codestral is it. What came back:
7
+ -- one sentence repeated to the output limit ("Please confirm if this approach
8
+ -- is acceptable"), and a tool call named
9
+ -- `mcp__plugin_cruise_cruise__list_models{"kind": "all"}`, the arguments fused
10
+ -- onto the name, which Claude Code refused as "No such tool available".
11
+ --
12
+ -- Codestral is Mistral's completion model — fill-in-the-middle, short
13
+ -- contexts — and it measures tools = 1 because the conformance probe asks for
14
+ -- one call to one tool with one sentence of context. A Claude Code turn is a
15
+ -- long system prompt and dozens of tools, which it does not hold together.
16
+ -- 0084 picked it as "Mistral's best coding model"; for completion that is
17
+ -- right, for an agent it is not. It stays in `bb/code-completion`.
18
+ --
19
+ -- Mistral's seat goes to `mistral-medium-2604`, the model hunter and
20
+ -- code-review already hold there (0084), at the same rank and position.
21
+ -- Devstral would be the natural pick and is not in the catalogue.
22
+ --
23
+ -- The tool check now fails a call whose name is not an offered tool or whose
24
+ -- arguments are not a JSON object (`src/conformance-tools.ts`), so the malformed
25
+ -- shape measures tools = 0 for any model that produces it on the probe. The
26
+ -- probe is still one short turn, so that is a floor, not a Claude Code test.
27
+ --
28
+ -- Selection policy is unchanged.
29
+ --
30
+ -- Two statements, not one. `lane_members` is keyed (lane_id, model_id) and the
31
+ -- control plane can rewrite a lane's members (`src/lanes-write.ts`), so an
32
+ -- environment where mistral-medium was already added to either lane would
33
+ -- fail a bare UPDATE on the primary key and stop the migration with codestral
34
+ -- still serving. OR IGNORE skips that row and the DELETE takes codestral out
35
+ -- either way; on the rows 0088 wrote, the DELETE matches nothing.
36
+
37
+ UPDATE OR IGNORE lane_members
38
+ SET model_id = 'mistral/mistral-medium-2604'
39
+ WHERE lane_id IN ('bb/agentic-coding', 'bb/builder')
40
+ AND model_id = 'mistral/codestral-2508';
41
+
42
+ DELETE FROM lane_members
43
+ WHERE lane_id IN ('bb/agentic-coding', 'bb/builder')
44
+ AND model_id = 'mistral/codestral-2508';