@bytesbrains/cruise-worker 0.0.0-stage → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,196 @@
1
+ -- Five frontier rows, the xAI rows re-cited, and a Mistral re-host retired
2
+ -- (#534, #559 — the model board's 2026-09-24 and 2026-10-01 follow-ups).
3
+ --
4
+ -- This migration catalogues and prices. It staffs no lane: a member nobody has
5
+ -- measured makes the lane's capability `null` (`src/lanes.ts`), so restaffing
6
+ -- the coding lanes with Opus 5.5 and GPT-6.1 Sol is a later migration, after a
7
+ -- conformance run has written their flags — the 0077 → 0088 order.
8
+ --
9
+ -- Every figure fetched 2026-10-02 UTC from the host's own pages, cited per row.
10
+ -- Not the board: both issues say so, and #79 / #122 say why.
11
+ --
12
+ -- ## Left out, and why
13
+ --
14
+ -- **`gpt-6-sol`** (#534). OpenAI's starting points replaced it with
15
+ -- `gpt-6.1-sol` on 2026-09-29 (#559) at the same headline rate, so cataloguing
16
+ -- it a week later would add a row whose only future is a disable.
17
+ --
18
+ -- **Gemini 2.5, Gemini 3.1 Flash-Lite, Claude Sonnet 4.5, Groq's Llama rows**
19
+ -- (#534, #559). None is in this catalogue, so the access limits, shutdown dates
20
+ -- and enterprise gating the board reported reach nothing here. The Groq rows
21
+ -- held are the two gpt-oss models (0053), which the gating does not name.
22
+ --
23
+ -- **Gemini 3.8 Flash's window** (#559). The board corrected its rounding to
24
+ -- 1M / 64K; 0049 already holds the page's 1,048,576 / 65,536.
25
+ --
26
+ -- **Perplexity Sonar** (#559). 0058 priced `perplexity/sonar*` per request, but
27
+ -- no `models` or `upstreams` row was ever written, so those components price a
28
+ -- model nothing can route. The end of Sonar's Chat Completions support on
29
+ -- 2026-09-27 changes nothing that serves; it does mean the next person to add
30
+ -- Perplexity starts from the Agent API, not from 0058's rows.
31
+
32
+ -- ── OpenAI ────────────────────────────────────────────────────────────────────
33
+ -- https://developers.openai.com/api/docs/models/gpt-6.1-sol
34
+ -- https://developers.openai.com/api/docs/models/gpt-6-luna
35
+ --
36
+ -- Both: window 1,050,000, max output 128,000, and the long-context rule 0049
37
+ -- and 0077 already hold — prompts over 272K input tokens pay 2x input and
38
+ -- cache and 1.5x output for the whole request, so `prompt_tokens_from` is
39
+ -- 272001. Batch / Flex are not written, for 0049's reason.
40
+ --
41
+ -- gpt-6.1-sol $2 / $10, cached $0.10 — a 5% cache read, which is #559's
42
+ -- "deeper discount". Effort accepts low, medium, high, xhigh,
43
+ -- max: **`none` and `minimal` are refused upstream**. Cruise
44
+ -- passes `reasoning_effort` through unchanged
45
+ -- (`src/chat-openai-responses.ts`), so a caller sending `none`
46
+ -- gets OpenAI's 400 — a property of the request, not of a lane,
47
+ -- and not something conformance can measure for every caller.
48
+ -- gpt-6-luna $0.10 / $0.50, cached $0.01. Effort accepts `none`.
49
+ --
50
+ -- Both match `isOpenAIReasoningModel` (`^(gpt-6|gpt-5\.6)`), so their tool calls
51
+ -- take the `/v1/responses` adapter #545 built for gpt-6-astra with no code here.
52
+ -- `data_retention` is `retained` on the same guide 0049 and 0077 cite.
53
+ INSERT INTO models (id, upstream_id, upstream_model, max_context, max_output,
54
+ data_retention, data_retention_source, data_retention_as_of) VALUES
55
+ ('openai/gpt-6.1-sol', 'openai', 'gpt-6.1-sol', 1050000, 128000,
56
+ 'retained', 'https://developers.openai.com/api/docs/guides/your-data', '2026-10-02'),
57
+ ('openai/gpt-6-luna', 'openai', 'gpt-6-luna', 1050000, 128000,
58
+ 'retained', 'https://developers.openai.com/api/docs/guides/your-data', '2026-10-02');
59
+
60
+ INSERT INTO price_observations
61
+ (observed_at, model, effective_from, prompt_tokens_from, peak_utc,
62
+ input_micros_per_mtok, output_micros_per_mtok, cached_input_micros_per_mtok, source, note)
63
+ VALUES
64
+ ('2026-10-02 00:00:00', 'openai/gpt-6.1-sol', '2026-10-02', 0, '',
65
+ 2000000, 10000000, 100000, 'https://developers.openai.com/api/docs/models/gpt-6.1-sol',
66
+ 'First observation: GPT-6.1 Sol (#559, 0090).'),
67
+ ('2026-10-02 00:00:00', 'openai/gpt-6.1-sol', '2026-10-02', 272001, '',
68
+ 4000000, 15000000, 200000, 'https://developers.openai.com/api/docs/models/gpt-6.1-sol',
69
+ 'Long-context tier: >272K input tokens, 2x input/cache and 1.5x output for the whole request.'),
70
+ ('2026-10-02 00:00:00', 'openai/gpt-6-luna', '2026-10-02', 0, '',
71
+ 100000, 500000, 10000, 'https://developers.openai.com/api/docs/models/gpt-6-luna',
72
+ 'First observation: GPT-6 Luna (#534, 0090).'),
73
+ ('2026-10-02 00:00:00', 'openai/gpt-6-luna', '2026-10-02', 272001, '',
74
+ 200000, 750000, 20000, 'https://developers.openai.com/api/docs/models/gpt-6-luna',
75
+ 'Long-context tier: >272K input tokens, 2x input/cache and 1.5x output for the whole request.');
76
+
77
+ -- ── Anthropic ─────────────────────────────────────────────────────────────────
78
+ -- https://platform.claude.com/docs/en/models/opus-5-5/overview
79
+ -- https://platform.claude.com/docs/en/models/sonnet-5-5/overview
80
+ --
81
+ -- Both: window 1M, max output 128K, no long-context tier on either page.
82
+ -- `cached_input` is the cache *read* rate, as in 0053; the write rates have a
83
+ -- component (0058) and no reader yet.
84
+ --
85
+ -- claude-opus-5-5 $4 / $20, cache read $0.20 — 5% of input, which the
86
+ -- page flags as this model's exception to the usual 10%.
87
+ -- claude-sonnet-5-5 $2 / $10, cache read $0.20.
88
+ --
89
+ -- **What both narrow, and which of it Cruise sees** (#534, #559, and the pages):
90
+ --
91
+ -- - Forced tool use — `tool_choice` `any` or a named `tool` — returns 400.
92
+ -- Conformance sends no `tool_choice`, so `tools = 1` will be measured true
93
+ -- and a lane will promise tools to a caller whose forced request then 400s
94
+ -- and falls back. That is a gap in the suite, not in this row, and has its
95
+ -- own issue rather than a guess written here.
96
+ -- - Thinking cannot be disabled (Opus 5.5; Sonnet 5.5's floor is
97
+ -- `between_tools`). `src/messages-translate.ts` already drops `thinking`
98
+ -- on the way in, so Cruise never asks for it off.
99
+ -- - Thinking blocks are tied to the producing model and account. The same
100
+ -- file returns no `thinking` blocks to the caller, so no history replays
101
+ -- one across tenant keys.
102
+ -- - Sonnet 5.5 refuses a non-default `temperature`, `top_p` or `top_k` with
103
+ -- 400. Callers that send one get the host's answer, as with any 400.
104
+ --
105
+ -- `retained` on the privacy page 0053 quoted, unchanged for these rows.
106
+ INSERT INTO models (id, upstream_id, upstream_model, max_context, max_output,
107
+ data_retention, data_retention_source, data_retention_as_of) VALUES
108
+ ('anthropic/claude-opus-5-5', 'anthropic', 'claude-opus-5-5', 1000000, 128000,
109
+ 'retained', 'https://privacy.claude.com/en/articles/7996868-is-my-data-used-for-model-training', '2026-10-02'),
110
+ ('anthropic/claude-sonnet-5-5', 'anthropic', 'claude-sonnet-5-5', 1000000, 128000,
111
+ 'retained', 'https://privacy.claude.com/en/articles/7996868-is-my-data-used-for-model-training', '2026-10-02');
112
+
113
+ INSERT INTO price_observations
114
+ (observed_at, model, effective_from, prompt_tokens_from, peak_utc,
115
+ input_micros_per_mtok, output_micros_per_mtok, cached_input_micros_per_mtok, source, note)
116
+ VALUES
117
+ ('2026-10-02 00:00:00', 'anthropic/claude-opus-5-5', '2026-10-02', 0, '',
118
+ 4000000, 20000000, 200000, 'https://platform.claude.com/docs/en/models/opus-5-5/overview',
119
+ 'First observation: Claude Opus 5.5 (#534, 0090). Cache read is 0.05x base on this model, which the page states explicitly.'),
120
+ ('2026-10-02 00:00:00', 'anthropic/claude-sonnet-5-5', '2026-10-02', 0, '',
121
+ 2000000, 10000000, 200000, 'https://platform.claude.com/docs/en/models/sonnet-5-5/overview',
122
+ 'First observation: Claude Sonnet 5.5 (#559, 0090).');
123
+
124
+ -- Web search at $10 per 1,000, as 0058 wrote for every Claude row it held —
125
+ -- re-read on the tool's page the same day.
126
+ INSERT INTO price_components
127
+ (observed_at, model, effective_from, component, variant, micros, per, source, note)
128
+ VALUES
129
+ ('2026-10-02 00:00:00', 'anthropic/claude-opus-5-5', '2026-10-02', 'search', '', 10000000, 1000,
130
+ 'https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-search-tool', 'First observation (0090).'),
131
+ ('2026-10-02 00:00:00', 'anthropic/claude-sonnet-5-5', '2026-10-02', 'search', '', 10000000, 1000,
132
+ 'https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-search-tool', 'First observation (0090).');
133
+
134
+ -- ── xAI ───────────────────────────────────────────────────────────────────────
135
+ -- https://docs.x.ai/developers/pricing — where `docs.x.ai/docs/models`, 0053's
136
+ -- source, now lives (#534).
137
+ --
138
+ -- `grok-4.7`: window 500k, same rates as `grok-4.6`, the same "< 200k" /
139
+ -- "≥ 200k" boundary 0053 wrote as `prompt_tokens_from` 200000. xAI offers no
140
+ -- Batch API for it, which Cruise does not use. No max output stated, so NULL
141
+ -- and `checkLimits` derives one (0023). `retained` on the page 0061 read for
142
+ -- every xAI row, re-read today: stored 30 days for abuse review, *"never trains
143
+ -- on your API inputs or outputs without your explicit permission."*
144
+ INSERT INTO models (id, upstream_id, upstream_model, max_context, max_output,
145
+ data_retention, data_retention_source, data_retention_as_of) VALUES
146
+ ('grok/grok-4.7', 'grok', 'grok-4.7', 500000, NULL,
147
+ 'retained', 'https://docs.x.ai/developers/faq/security', '2026-10-02');
148
+
149
+ -- The four rows 0053 sourced to the old URL, re-read on the new page: **every
150
+ -- figure unchanged**. A re-observation rather than an edit, since
151
+ -- `price_observations` is append-only (0048) — the old rows keep the URL they
152
+ -- were read from, and these say where the same numbers are read today.
153
+ INSERT INTO price_observations
154
+ (observed_at, model, effective_from, prompt_tokens_from, peak_utc,
155
+ input_micros_per_mtok, output_micros_per_mtok, cached_input_micros_per_mtok, source, note)
156
+ VALUES
157
+ ('2026-10-02 00:00:00', 'grok/grok-4.7', '2026-10-02', 0, '',
158
+ 2000000, 6000000, 500000, 'https://docs.x.ai/developers/pricing', 'First observation: Grok 4.7 (#534, 0090).'),
159
+ ('2026-10-02 00:00:00', 'grok/grok-4.7', '2026-10-02', 200000, '',
160
+ 4000000, 12000000, 1000000, 'https://docs.x.ai/developers/pricing', 'Long-prompt tier, stated as prompts at or above 200k tokens.'),
161
+ ('2026-10-02 00:00:00', 'grok/grok-4.6', '2026-10-02', 0, '',
162
+ 2000000, 6000000, 500000, 'https://docs.x.ai/developers/pricing', 'Re-read at the moved URL (#534, 0090); unchanged.'),
163
+ ('2026-10-02 00:00:00', 'grok/grok-4.6', '2026-10-02', 200000, '',
164
+ 4000000, 12000000, 1000000, 'https://docs.x.ai/developers/pricing', 'Re-read at the moved URL (#534, 0090); unchanged.'),
165
+ ('2026-10-02 00:00:00', 'grok/grok-4.5', '2026-10-02', 0, '',
166
+ 2000000, 6000000, 300000, 'https://docs.x.ai/developers/pricing', 'Re-read at the moved URL (#534, 0090); unchanged.'),
167
+ ('2026-10-02 00:00:00', 'grok/grok-4.5', '2026-10-02', 200000, '',
168
+ 4000000, 12000000, 600000, 'https://docs.x.ai/developers/pricing', 'Re-read at the moved URL (#534, 0090); unchanged.'),
169
+ ('2026-10-02 00:00:00', 'grok/grok-4.3', '2026-10-02', 0, '',
170
+ 1250000, 2500000, 200000, 'https://docs.x.ai/developers/pricing', 'Re-read at the moved URL (#534, 0090); unchanged.'),
171
+ ('2026-10-02 00:00:00', 'grok/grok-4.3', '2026-10-02', 200000, '',
172
+ 2500000, 5000000, 400000, 'https://docs.x.ai/developers/pricing', 'Re-read at the moved URL (#534, 0090); unchanged.'),
173
+ ('2026-10-02 00:00:00', 'grok/grok-build-0.1', '2026-10-02', 0, '',
174
+ 1000000, 2000000, 200000, 'https://docs.x.ai/developers/pricing', 'Re-read at the moved URL (#534, 0090); unchanged.'),
175
+ ('2026-10-02 00:00:00', 'grok/grok-build-0.1', '2026-10-02', 200000, '',
176
+ 2000000, 4000000, 400000, 'https://docs.x.ai/developers/pricing', 'Re-read at the moved URL (#534, 0090); unchanged.');
177
+
178
+ -- ── Mistral's GLM 5.2 re-host ─────────────────────────────────────────────────
179
+ -- Mistral deprecated its hosted `zai-glm-5-2` on 2026-09-29 and retires it on
180
+ -- 2026-10-31 (#559; https://docs.mistral.ai/getting-started/models/). It has
181
+ -- never served here either: every request to it has been a 403 on a Labs
182
+ -- entitlement this account lacks (0041, and `src/doctor-upstreams.ts`). No
183
+ -- migration put it in a lane or a fallback chain; a chain written since through
184
+ -- `fallback:set` is data in that database, and a disabled member is skipped.
185
+ --
186
+ -- Disabled, never deleted: the ledger may name it. Mistral's replacement, GLM
187
+ -- 5.3 as a third-party model, is not added — Cruise already reaches GLM 5.3 on
188
+ -- Workers AI (0040), and a second re-host of another lab's weights is the thing
189
+ -- `upstreams.md` says to add only when nothing else carries the model. The
190
+ -- price row stays, for 0062's reason: append-only, and unreachable is not free.
191
+ UPDATE models
192
+ SET enabled = 0,
193
+ measured_at = NULL,
194
+ measured_by = NULL,
195
+ measurement_source = NULL
196
+ WHERE id = 'mistral/zai-glm-5-2';
@@ -0,0 +1,43 @@
1
+ -- Claude Opus 5.5 takes Opus 5's seat in the four coding lanes (#534).
2
+ --
3
+ -- 0090 catalogued it; conformance run 36993727932 (dispatched 2026-10-02 UTC,
4
+ -- narrowed to 0090's five rows) measured it on both environments: every check
5
+ -- passed — chat, usage, tools, parallel tools, streaming, json_schema, vision,
6
+ -- truncation. Measured before staffed, which is the 0077 → 0088 order: an
7
+ -- unmeasured member would make each lane's capability `null` (`src/lanes.ts`).
8
+ --
9
+ -- A swap, not an addition: same position, same `quality_rank` 1. Anthropic
10
+ -- holds two rank-1 seats in each lane already (Fable 5.1 and Opus 5), and Opus
11
+ -- 5.5 is the newer model at a lower price ($4 / $20 against $5 / $25), so
12
+ -- keeping Opus 5 beside it would add a member no allocation should prefer.
13
+ -- Opus 5 stays catalogued and routable when pinned — Anthropic lists it as
14
+ -- active — and disable-never-delete is about rows, not seats.
15
+ --
16
+ -- **GPT-6.1 Sol is not staffed.** The same run passed it on seven checks and
17
+ -- failed vision on both environments (asked the colour of a red image, it said
18
+ -- "Brown" on production and "Orange" on staging). A lane's capability is the
19
+ -- intersection of its members, so seating it would take vision off every coding
20
+ -- lane — screenshots through Claude Code included. `gpt-5.6-sol` keeps its seat
21
+ -- until a re-measurement says otherwise.
22
+ --
23
+ -- What Opus 5.5 narrows and this lane inherits: forced `tool_choice` returns
24
+ -- 400, which Fable 5.1 already does in every one of these lanes, and which
25
+ -- conformance cannot see (#570). Thinking blocks are never returned to callers
26
+ -- (`src/messages-translate.ts`), so its model-tied blocks do not replay.
27
+ --
28
+ -- Two statements for 0089's reason: `lane_members` is keyed (lane_id,
29
+ -- model_id) and the control plane can rewrite a lane's members
30
+ -- (`src/lanes-write.ts`), so an environment where Opus 5.5 was already added
31
+ -- to one of these lanes would fail a bare UPDATE and stop the migration.
32
+ -- OR IGNORE skips that row and the DELETE takes Opus 5 out either way.
33
+ --
34
+ -- Selection policy is unchanged.
35
+
36
+ UPDATE OR IGNORE lane_members
37
+ SET model_id = 'anthropic/claude-opus-5-5'
38
+ WHERE lane_id IN ('bb/agentic-coding', 'bb/builder', 'bb/hunter', 'bb/code-review')
39
+ AND model_id = 'anthropic/claude-opus-5';
40
+
41
+ DELETE FROM lane_members
42
+ WHERE lane_id IN ('bb/agentic-coding', 'bb/builder', 'bb/hunter', 'bb/code-review')
43
+ AND model_id = 'anthropic/claude-opus-5';
@@ -0,0 +1,83 @@
1
+ -- DeepSeek's peak does not apply on Chinese public holidays (#569).
2
+ --
3
+ -- Read 2026-10-02 UTC at https://api-docs.deepseek.com/quick_start/pricing:
4
+ --
5
+ -- Peak: "01:00 - 04:00 and 06:00 - 10:00 UTC, Monday through Friday,
6
+ -- excluding Chinese public holidays."
7
+ -- Off-peak: "All other hours are off-peak, including weekends and Chinese
8
+ -- public holidays in full."
9
+ --
10
+ -- 0039 taught `peak_utc` the weekdays. It cannot say "excluding", so a weekday
11
+ -- holiday was reserved and billed at 2x — the weekend bug 0039 fixed, in the
12
+ -- shape of a calendar. Found by the model board's 2026-09-24 pass (#534) and
13
+ -- confirmed on the page, during Golden Week, which was being over-billed as it
14
+ -- was read. How the exemption is applied, and why a UTC date is the right key
15
+ -- for these windows, is on `src/peak-calendar.ts`.
16
+ --
17
+ -- ## Which days
18
+ --
19
+ -- DeepSeek does not list them. The list is the State Council's own notice,
20
+ -- 国务院办公厅关于2026年部分节假日安排的通知 (国办发明电〔2025〕7号, 2025-11-04),
21
+ -- https://www.gov.cn/zhengce/zhengceku/202511/content_7047091.htm, read
22
+ -- 2026-10-02 UTC. Every 放假 (rest) day it names is written, weekends included:
23
+ -- they decide nothing under `mon-fri@`, and leaving them out would make this
24
+ -- table a partial copy of the notice rather than the notice.
25
+ --
26
+ -- Two readings, both taken literally and both stated so the first invoice that
27
+ -- disagrees is a correction rather than a surprise:
28
+ --
29
+ -- - **"Public holidays" is the whole 放假 period**, swapped days included
30
+ -- (10-06 and 10-07 are rest days only because 09-20 and 10-10 are worked).
31
+ -- The notice names them as one break and DeepSeek says "in full"; reading
32
+ -- only the statutory days would bill a day the country is off at peak.
33
+ -- - **Make-up workdays (上班) are not added as peak.** 2026's fall on a Sunday
34
+ -- and a Saturday (01-04, 02-14, 02-28, 05-09, 09-20, 10-10), and the page
35
+ -- says Monday through Friday. A Saturday worked in China is still a
36
+ -- Saturday on DeepSeek's page.
37
+ --
38
+ -- Earlier 2026 dates are written too. Nothing re-prices a settled request — the
39
+ -- ledger keeps what it charged — so they change no bill; they are here so the
40
+ -- table answers "was this a holiday" for the whole year, not only from today.
41
+ --
42
+ -- **2027 is not here.** Its notice is published around November; a row written
43
+ -- before it exists would be a guess. Until a migration adds it, 2027's holidays
44
+ -- bill at peak — over-counting, the safe direction for a cap, and the same
45
+ -- drift this migration closes. The next notice is a calendar item, not an
46
+ -- incident.
47
+
48
+ CREATE TABLE peak_exempt_days (
49
+ upstream_id TEXT NOT NULL REFERENCES upstreams(id),
50
+ day TEXT NOT NULL CHECK (day GLOB '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]'),
51
+ note TEXT NOT NULL,
52
+ source TEXT NOT NULL,
53
+ observed_at TEXT NOT NULL,
54
+ PRIMARY KEY (upstream_id, day)
55
+ );
56
+
57
+ INSERT INTO peak_exempt_days (upstream_id, day, note, source, observed_at)
58
+ WITH notice(day, note) AS (VALUES
59
+ ('2026-01-01', '元旦 New Year'), ('2026-01-02', '元旦 New Year'),
60
+ ('2026-01-03', '元旦 New Year'),
61
+ ('2026-02-15', '春节 Spring Festival'), ('2026-02-16', '春节 Spring Festival'),
62
+ ('2026-02-17', '春节 Spring Festival'), ('2026-02-18', '春节 Spring Festival'),
63
+ ('2026-02-19', '春节 Spring Festival'), ('2026-02-20', '春节 Spring Festival'),
64
+ ('2026-02-21', '春节 Spring Festival'), ('2026-02-22', '春节 Spring Festival'),
65
+ ('2026-02-23', '春节 Spring Festival'),
66
+ ('2026-04-04', '清明节 Qingming'), ('2026-04-05', '清明节 Qingming'),
67
+ ('2026-04-06', '清明节 Qingming'),
68
+ ('2026-05-01', '劳动节 Labour Day'), ('2026-05-02', '劳动节 Labour Day'),
69
+ ('2026-05-03', '劳动节 Labour Day'), ('2026-05-04', '劳动节 Labour Day'),
70
+ ('2026-05-05', '劳动节 Labour Day'),
71
+ ('2026-06-19', '端午节 Dragon Boat'), ('2026-06-20', '端午节 Dragon Boat'),
72
+ ('2026-06-21', '端午节 Dragon Boat'),
73
+ ('2026-09-25', '中秋节 Mid-Autumn'), ('2026-09-26', '中秋节 Mid-Autumn'),
74
+ ('2026-09-27', '中秋节 Mid-Autumn'),
75
+ ('2026-10-01', '国庆节 National Day'), ('2026-10-02', '国庆节 National Day'),
76
+ ('2026-10-03', '国庆节 National Day'), ('2026-10-04', '国庆节 National Day'),
77
+ ('2026-10-05', '国庆节 National Day'), ('2026-10-06', '国庆节 National Day'),
78
+ ('2026-10-07', '国庆节 National Day')
79
+ )
80
+ SELECT 'deepseek', day, note,
81
+ 'https://www.gov.cn/zhengce/zhengceku/202511/content_7047091.htm',
82
+ '2026-10-02 00:00:00'
83
+ FROM notice;
@@ -0,0 +1,26 @@
1
+ -- Whether a model answers a forced `tool_choice`, measured apart from `tools` (#570).
2
+ --
3
+ -- Claude Fable 5.1 (measured 2026-09-12, `src/chat-anthropic-structured.ts`),
4
+ -- Opus 5.5 and Sonnet 5.5 (their own model pages) call a tool when allowed and
5
+ -- answer 400 when `tool_choice` forces one: `"required"` or a named function,
6
+ -- which reach Anthropic as `any` / `tool`. `tool_call` sends no `tool_choice`,
7
+ -- so all three measured `tools = 1`, and Fable and Opus 5.5 are rank-1 members
8
+ -- of all four coding lanes (0091). A caller forcing a call got a wasted chain
9
+ -- slot through a lane and the 400 itself when pinned, with nothing in
10
+ -- `/v1/models` to say so.
11
+ --
12
+ -- A column like the five 0007 made, tri-state and written only by
13
+ -- conformance (`tool_choice_forced`, `src/conformance.ts`). NULL until
14
+ -- measured, and NULL **fails closed**: the gate refuses a request that forces
15
+ -- `tool_choice` for a model nobody has checked (`src/capabilities.ts`).
16
+ --
17
+ -- ## Which is why the order of the rollout is load-bearing
18
+ --
19
+ -- Every row starts NULL. Deploy the Worker that reads this before a
20
+ -- conformance run has measured the column, and every forced request on every
21
+ -- model is refused. So: this migration, then a conformance run (the script
22
+ -- writes the flag; the deployed Worker passes `tool_choice` through and the
23
+ -- conformance key is exempt from the gate), then the Worker. The column alone
24
+ -- is inert under the Worker that predates it.
25
+
26
+ ALTER TABLE models ADD COLUMN tool_choice_forced INTEGER CHECK (tool_choice_forced IN (0, 1));
@@ -0,0 +1,115 @@
1
+ -- Claude Haiku 5.5 catalogued, and Sonnet 5.5's cache read re-priced
2
+ -- (#591 — the model board's 2026-10-08 follow-ups).
3
+ --
4
+ -- Catalogues and prices; staffs no lane, for 0090's reason: an unmeasured
5
+ -- member makes a lane's capability `null` (`src/lanes.ts`). Haiku 5.5 is the
6
+ -- issue's candidate for `bb/chat-assistant` and `bb/extraction`, and that is a
7
+ -- later migration, after conformance has written its flags — the 0077 → 0088
8
+ -- order.
9
+ --
10
+ -- Every figure fetched 2026-10-08 UTC from Anthropic's own pages, cited per
11
+ -- row. Not the board: #591 says so, and #79 / #122 say why.
12
+ --
13
+ -- ## Left out, and why
14
+ --
15
+ -- **Claude Haiku 4.5 going Legacy.** Its page reads "Active (legacy)",
16
+ -- retirement "Not sooner than October 15, 2026", and no retirement date has
17
+ -- been announced. Nothing to write until one is: no migration put
18
+ -- `anthropic/claude-haiku-4-5-20251001` in a lane or a fallback chain, and its
19
+ -- prices are unchanged ($1 / $5, cache read $0.10). When a date is announced it
20
+ -- is 0090's GLM shape — disable on the date, never delete.
21
+ --
22
+ -- **OpenAI's Decisions API and Perplexity's `pplx-decider-v1.1-27b`.** Both
23
+ -- are candidates for `typed-decisions`, which is not a job in
24
+ -- `docs/vocabulary.md`, and both are a separate endpoint rather than a chat
25
+ -- completion — a route and a vocabulary decision, not a catalogue row. The
26
+ -- Decisions API also bills apart from `openai/gpt-6-luna` on Responses, so
27
+ -- 0090's row prices nothing it would serve.
28
+ --
29
+ -- **meraGPT `state-decider-1`, Perplexity's Agent API presets.** Neither host
30
+ -- has an `upstreams` row here (0090 on Perplexity), so a price change reaches
31
+ -- nothing that routes.
32
+
33
+ -- ── Claude Haiku 5.5 ──────────────────────────────────────────────────────────
34
+ -- https://platform.claude.com/docs/en/models/haiku-5-5/overview
35
+ --
36
+ -- Released 2026-10-07. Window 1M, max output 128K, no dated id: the API id is
37
+ -- `claude-haiku-5-5` and the page names no alias.
38
+ --
39
+ -- **Priced by prompt length, for the whole request** — the first Claude row
40
+ -- here that is. Every rate on the page is stated twice, "for prompts up to
41
+ -- 100,000 tokens" and "for prompts over 100,000 tokens", so the second row
42
+ -- starts at `prompt_tokens_from` 100001, as 0090 wrote "over 272K" as 272001:
43
+ --
44
+ -- ≤ 100K $0.10 / $0.50, cache read $0.01
45
+ -- > 100K $0.50 / $2.50, cache read $0.05 — 5x on every line
46
+ --
47
+ -- At a 5x step, `resolvePrice` choosing the short row for a long prompt is an
48
+ -- 80% under-count, so the test pins both sides of the boundary. Cache writes ($0.125 / $0.20 by TTL, and 5x
49
+ -- above 100K) have no column, as 0053 records.
50
+ --
51
+ -- **What it narrows, and which of it Cruise sees:**
52
+ --
53
+ -- - A non-default `temperature`, `top_p` or `top_k` returns 400 — the
54
+ -- Sonnet 5.5 rule 0090 recorded. Callers sending one get the host's answer.
55
+ -- - Manual extended thinking (`budget_tokens`) returns 400 and adaptive
56
+ -- thinking is on by default. Cruise sends no `budget_tokens` to Anthropic
57
+ -- anywhere in `src/`, and `src/messages-translate.ts` drops `thinking` on
58
+ -- the way in, so neither reaches the host from us.
59
+ -- - Thinking blocks work only in the producing account. The same file
60
+ -- returns none to the caller, so no history replays one across keys.
61
+ -- - Forced `tool_choice` is not stated either way on this page. 0093 made it
62
+ -- a measured column, NULL until conformance writes it, and NULL fails
63
+ -- closed — so the row cannot over-promise it.
64
+ -- - Its tokenizer counts the same text as ~30% more tokens than Haiku 4.5.
65
+ -- Cruise bills from the host's reported usage, so that is a price fact, not
66
+ -- a metering bug, and a lane comparison by headline rate understates it.
67
+ --
68
+ -- `retained` on the privacy page 0053 and 0090 quoted, unchanged for this row.
69
+ INSERT INTO models (id, upstream_id, upstream_model, max_context, max_output,
70
+ data_retention, data_retention_source, data_retention_as_of) VALUES
71
+ ('anthropic/claude-haiku-5-5', 'anthropic', 'claude-haiku-5-5', 1000000, 128000,
72
+ 'retained', 'https://privacy.claude.com/en/articles/7996868-is-my-data-used-for-model-training', '2026-10-08');
73
+
74
+ INSERT INTO price_observations
75
+ (observed_at, model, effective_from, prompt_tokens_from, peak_utc,
76
+ input_micros_per_mtok, output_micros_per_mtok, cached_input_micros_per_mtok, source, note)
77
+ VALUES
78
+ ('2026-10-08 00:00:00', 'anthropic/claude-haiku-5-5', '2026-10-08', 0, '',
79
+ 100000, 500000, 10000, 'https://platform.claude.com/docs/en/models/haiku-5-5/overview',
80
+ 'First observation: Claude Haiku 5.5 (#591, 0094). Prompts up to 100,000 tokens.'),
81
+ ('2026-10-08 00:00:00', 'anthropic/claude-haiku-5-5', '2026-10-08', 100001, '',
82
+ 500000, 2500000, 50000, 'https://platform.claude.com/docs/en/models/haiku-5-5/overview',
83
+ 'Long-prompt tier: prompts over 100,000 tokens pay 5x on every line, for the whole request.');
84
+
85
+ -- Web search at $10 per 1,000, as 0058 and 0090 wrote for every Claude row,
86
+ -- re-read on the tool's page today.
87
+ INSERT INTO price_components
88
+ (observed_at, model, effective_from, component, variant, micros, per, source, note)
89
+ VALUES
90
+ ('2026-10-08 00:00:00', 'anthropic/claude-haiku-5-5', '2026-10-08', 'search', '', 10000000, 1000,
91
+ 'https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-search-tool', 'First observation (0094).');
92
+
93
+ -- ── Claude Sonnet 5.5: cache read halved ──────────────────────────────────────
94
+ -- https://platform.claude.com/docs/en/release-notes/overview, 2026-10-07:
95
+ -- *"We've lowered the price of prompt cache reads on Claude Sonnet 5.5 from
96
+ -- $0.20 USD to $0.10 USD per million tokens: 0.05x the base input price
97
+ -- instead of 0.1x. Cache writes and all other prices are unchanged."*
98
+ --
99
+ -- The model's own page now prints $0.10 too, so the two agree — #591 reported
100
+ -- the pricing table still showing $0.20, and the release note is cited because
101
+ -- it carries the date. A new observation effective that day rather than an
102
+ -- edit: `price_observations` is append-only (0048), and requests served
103
+ -- 2026-10-02 → 06 were billed at $0.20, which was the price then.
104
+ --
105
+ -- Until this deploys, a cache read on this row is billed $0.10 / MTok over
106
+ -- the host. No migration has put it in a lane (0091 staffed Opus 5.5, not
107
+ -- Sonnet), so only a request naming it could have paid that; the ledger says
108
+ -- whether one did.
109
+ INSERT INTO price_observations
110
+ (observed_at, model, effective_from, prompt_tokens_from, peak_utc,
111
+ input_micros_per_mtok, output_micros_per_mtok, cached_input_micros_per_mtok, source, note)
112
+ VALUES
113
+ ('2026-10-08 00:00:00', 'anthropic/claude-sonnet-5-5', '2026-10-07', 0, '',
114
+ 2000000, 10000000, 100000, 'https://platform.claude.com/docs/en/release-notes/overview',
115
+ 'Cache read lowered to $0.10 (0.05x base) on 2026-10-07; all other rates unchanged (#591, 0094).');
@@ -0,0 +1,43 @@
1
+ -- Request compression: the project's setting and the ledger's two figures
2
+ -- (#593). Spec: docs/compression.md.
3
+ --
4
+ -- ## `projects.compress`
5
+ --
6
+ -- Whether this project's requests have their tool outputs compressed. On the
7
+ -- project rather than the lane: a `bb/` lane serves every tenant, so a lane
8
+ -- operator choosing a lossy rewrite of other tenants' prompts is the widening
9
+ -- the cache keeps away from callers, moved to a different party; and the
10
+ -- saving lands on the project's bill, so the project is who buys it.
11
+ --
12
+ -- **0 for every project, existing and new.** Compression is lossy and off at
13
+ -- every level by default. A project is switched on through
14
+ -- `PUT /admin/projects/{project}/compression`, audited like a budget. It also
15
+ -- does nothing while the platform's `COMPRESS_MODE` is `off`, which is the
16
+ -- kill switch and the production default.
17
+ --
18
+ -- ## `ledger.compress_bytes_before` / `compress_bytes_after`
19
+ --
20
+ -- The UTF-8 length of the body sent to the served candidate, built from the
21
+ -- caller's tool outputs and from the compressed ones. Measured, not
22
+ -- estimated: the provider's billed input tokens are the *after* figure, and
23
+ -- the counterfactual bill was never measured, so the byte delta is the fact
24
+ -- and any money figure is derived from it and labelled so.
25
+ --
26
+ -- **NULL when compression did not run** — off at any level, a conformance
27
+ -- key, a request with no tool output, or a compressor that threw. Equal when
28
+ -- it ran and saved nothing. That keeps "off" and "ran and saved 0" two
29
+ -- different rows, which #593 asked for by name. No default, so every existing
30
+ -- row reads NULL, which is what it was.
31
+ --
32
+ -- ## Order
33
+ --
34
+ -- This, then the Worker. The Worker that predates the columns never reads
35
+ -- them; the Worker that reads them before this migration fails every key
36
+ -- lookup on the missing `projects.compress` column.
37
+
38
+ ALTER TABLE projects ADD COLUMN compress INTEGER NOT NULL DEFAULT 0 CHECK (compress IN (0, 1));
39
+
40
+ ALTER TABLE ledger ADD COLUMN compress_bytes_before INTEGER
41
+ CHECK (compress_bytes_before IS NULL OR compress_bytes_before >= 0);
42
+ ALTER TABLE ledger ADD COLUMN compress_bytes_after INTEGER
43
+ CHECK (compress_bytes_after IS NULL OR compress_bytes_after >= 0);
@@ -0,0 +1,36 @@
1
+ -- A tenant lane can make compression its default (#608, follow-up to #593).
2
+ -- Spec: docs/compression.md, "The flag".
3
+ --
4
+ -- ## Why a lane, after 0095 said the project
5
+ --
6
+ -- 0095 put the setting on the project and left the lane for when a job turned
7
+ -- out to need it. The dogfood run (#593 slice 6) is that case: the saving
8
+ -- depends on the job, not the project. Agent tool loops send `role: "tool"`
9
+ -- messages full of logs and JSON; review traffic sends diffs as user text and
10
+ -- must never be shortened. The lane is where a job is named, so "compress by
11
+ -- default on `<tenant>/coding`" is the natural way to say it.
12
+ --
13
+ -- ## Tenant lanes only, and the schema says so
14
+ --
15
+ -- 0095's reason against a lane setting still holds for `bb/`: a platform lane
16
+ -- serves every tenant, so whoever runs it would be choosing a lossy rewrite of
17
+ -- other tenants' prompts. On `<tenant>/<slug>` the tenant owns both the lane
18
+ -- and every project that can call it, so the choice stays inside one tenant.
19
+ -- `updateLane` refuses it on a global lane with a 400; the CHECK is the same
20
+ -- rule where a write that skipped the route cannot get past it. Create needs
21
+ -- no check of its own: `laneIdFor` refuses any `bb/` id with a 403 before a
22
+ -- create reaches the insert.
23
+ --
24
+ -- ## 0 for every lane, existing and new
25
+ --
26
+ -- Lossy, so off by default at every level, as 0095. A lane switched on does
27
+ -- nothing while `COMPRESS_MODE` is `off`, and `x-cruise-compress: off` and a
28
+ -- conformance key still win over it (`decideCompression`).
29
+ --
30
+ -- ## Order
31
+ --
32
+ -- This, then the Worker. The Worker that predates the column never reads it;
33
+ -- the Worker that reads it before this migration fails every lane lookup.
34
+
35
+ ALTER TABLE lanes ADD COLUMN compress INTEGER NOT NULL DEFAULT 0
36
+ CHECK (compress IN (0, 1) AND (compress = 0 OR tenant_id IS NOT NULL));
@@ -0,0 +1,34 @@
1
+ -- A tenant-wide compression default (#614, follow-up to #593 and #608).
2
+ -- Spec: docs/compression.md, "The flag".
3
+ --
4
+ -- ## What it is
5
+ --
6
+ -- The starting state of every project and lane the tenant creates from now
7
+ -- on. 0095 put the switch on the project and 0096 on the tenant's lanes; this
8
+ -- is the one switch for the whole account, so a tenant that wants compression
9
+ -- everywhere does not flip it once per project and once per lane.
10
+ --
11
+ -- ## Stamped at creation, never read live
12
+ --
13
+ -- The way `default_selection_policy` works (0066, #288). `createProject` reads
14
+ -- the column inside its own insert and `createLane` reads it when a create
15
+ -- names no `compress`, and the concrete value goes in the new row. Nothing on
16
+ -- the request path consults this column. Turning it on therefore switches on
17
+ -- no existing project or lane: compression is lossy, and one setting quietly
18
+ -- rewriting every project's traffic is the widening this repository refuses
19
+ -- everywhere else. Existing ones are changed one by one, by name, through the
20
+ -- routes that already exist.
21
+ --
22
+ -- ## 0 for every tenant, existing and new
23
+ --
24
+ -- Off by default at every level, as 0095 and 0096. A tenant provisioned after
25
+ -- this reads 0 by the column default, so `tenants.ts` needs no change.
26
+ --
27
+ -- ## Order
28
+ --
29
+ -- This, then the Worker. The Worker that predates the column never reads it;
30
+ -- the Worker that reads it before this migration fails every settings read
31
+ -- and every project create.
32
+
33
+ ALTER TABLE tenants ADD COLUMN default_compress INTEGER NOT NULL DEFAULT 0
34
+ CHECK (default_compress IN (0, 1));
@@ -0,0 +1,47 @@
1
+ -- The run a request belonged to, on the ledger (#617).
2
+ --
3
+ -- ## Why
4
+ --
5
+ -- A caller already names its run in `x-cruise-session` (#394) — an agent tenant's
6
+ -- builder sends `agent-<runId>` on every request — and until now the name
7
+ -- reached KV as an affinity pin and nothing else. So a question about one run
8
+ -- ("what did compression save on run 412?", agents#860) could only
9
+ -- be answered by matching timestamps against the run's log, which two
10
+ -- concurrent runs on one project defeat. A column answers it exactly, and the
11
+ -- compression figures (0095) are the first reader.
12
+ --
13
+ -- ## `ledger.session_digest`
14
+ --
15
+ -- `sha256(x-cruise-session)` in lowercase hex, never the value. The affinity
16
+ -- key hashes it for the reason `src/affinity.ts` gives — a run id is a caller
17
+ -- secret of sorts and must not sit in storage in the clear — and the ledger
18
+ -- outlives a one-hour pin by years, so the reason is stronger here, not
19
+ -- weaker. Unsalted, unlike `account`, on purpose: whoever holds the run id
20
+ -- must be able to find its rows (`npm run ledger -- --session <id>` hashes
21
+ -- what it is given), and a salt only the Worker holds would make the column
22
+ -- write-only. A digest of a guessable id is confirmable by someone with the
23
+ -- ledger and the guess, which is the same exposure `cache_digest` (0072)
24
+ -- accepted.
25
+ --
26
+ -- Recorded whenever the header was sent and valid, lane or pinned model: the
27
+ -- header is ignored for *routing* on a pinned model, but the run is still the
28
+ -- run. NULL when no header was sent, and on every existing row — no default.
29
+ --
30
+ -- ## Index
31
+ --
32
+ -- Partial, because most traffic sends no session and those rows should cost
33
+ -- the index nothing. Digest first: the one who asks holds a run id, not
34
+ -- necessarily the project it ran under. The rollup groups by project, so a
35
+ -- run id two tenants happen to share reads as two lines, never one sum.
36
+ --
37
+ -- ## Order
38
+ --
39
+ -- This, then the Worker. The Worker before this never names the column; the
40
+ -- one after it fails every ledger insert without it.
41
+
42
+ ALTER TABLE ledger ADD COLUMN session_digest TEXT
43
+ CHECK (session_digest IS NULL OR (length(session_digest) = 64 AND session_digest NOT GLOB '*[^0-9a-f]*'));
44
+
45
+ CREATE INDEX IF NOT EXISTS idx_ledger_session
46
+ ON ledger (session_digest, project_id)
47
+ WHERE session_digest IS NOT NULL;