@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,65 @@
1
+ -- A hard cap per run, so one stuck agent cannot spend the whole project (#651).
2
+ --
3
+ -- A project's cap is the only one a request answers to below the wallet, and
4
+ -- the unit a loop runs away in is narrower than that: one agent run. One run
5
+ -- stuck in a retry loop spends the project's day, and every other run in the
6
+ -- project is refused with it. `x-cruise-session` already names a run (#394),
7
+ -- and the ledger already keeps its digest (#617, 0098).
8
+ --
9
+ -- Three changes, each the smallest that carries the cap:
10
+ --
11
+ -- `spend` admits scope 'session'. A run's counter is one row, subject
12
+ -- `<project id>:<sha256(session)>`, period 'lifetime', period_start ''. The
13
+ -- project id is in the subject because a session is a caller's string and
14
+ -- two projects may pick the same one. Lifetime rather than the project's
15
+ -- period, decided on #651: a run that crosses midnight UTC is still the
16
+ -- same run, and a cap that reset there would hand a runaway loop a fresh
17
+ -- allowance at exactly the hour nobody is watching. It is counted on every
18
+ -- request that carries a session, capped or not, inside the reserve's own
19
+ -- statement, so a cap set mid-run already knows what the run has spent and
20
+ -- #646 has a running figure to report. The daily sweep deletes a counter
21
+ -- idle for 30 days (`src/session-budget.ts`). A counter is not an id the
22
+ -- ledger references, so this does not break disable-never-delete: the
23
+ -- ledger keeps the digest on every row and is the record.
24
+ --
25
+ -- `budgets.session_hard_micros` is the project's per-run cap. NULL is no
26
+ -- cap. A request's `x-cruise-session-budget` may narrow it and never widen
27
+ -- it, the rule the cache TTL already follows. On the project row only:
28
+ -- the CHECK below refuses one on a tenant's wallet row, which has no runs.
29
+ --
30
+ -- `reservations.session_subject` names the counter a hold also charged, or
31
+ -- NULL when it charged none. The settle and the sweep read it from the row,
32
+ -- the way they already read `period_start`, so the window the reserve
33
+ -- charged is the one the settle corrects, with no parameter to pass along.
34
+ --
35
+ -- CHECK constraints cannot be altered in SQLite, so `spend` is rebuilt the
36
+ -- way 0085 rebuilt it. Nothing references it by foreign key and it had no
37
+ -- index or trigger to recreate. The two new columns are plain ADD COLUMNs.
38
+ CREATE TABLE spend_new (
39
+ scope TEXT NOT NULL CHECK (scope IN ('project', 'tenant', 'session')),
40
+ subject TEXT NOT NULL,
41
+ period TEXT NOT NULL CHECK (period IN ('minute', 'day', 'month', 'lifetime')),
42
+ -- '' for lifetime. `YYYY-MM-DD HH:MM` for a minute, which is what
43
+ -- strftime('%Y-%m-%d %H:%M', 'now') gives (`src/budget.ts`).
44
+ period_start TEXT NOT NULL,
45
+ spend_micros INTEGER NOT NULL DEFAULT 0,
46
+ request_count INTEGER NOT NULL DEFAULT 0,
47
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
48
+ PRIMARY KEY (scope, subject, period, period_start),
49
+ -- A run is counted for its whole life and only that way.
50
+ CHECK (scope <> 'session' OR (period = 'lifetime' AND period_start = ''))
51
+ );
52
+
53
+ INSERT INTO spend_new (scope, subject, period, period_start, spend_micros, request_count, updated_at)
54
+ SELECT scope, subject, period, period_start, spend_micros, request_count, updated_at
55
+ FROM spend;
56
+ DROP TABLE spend;
57
+ ALTER TABLE spend_new RENAME TO spend;
58
+
59
+ -- What the sweep's prune reads: session counters, oldest first.
60
+ CREATE INDEX idx_spend_session_idle ON spend (updated_at) WHERE scope = 'session';
61
+
62
+ ALTER TABLE budgets ADD COLUMN session_hard_micros INTEGER
63
+ CHECK (session_hard_micros IS NULL OR (session_hard_micros >= 0 AND scope = 'project'));
64
+
65
+ ALTER TABLE reservations ADD COLUMN session_subject TEXT;
@@ -0,0 +1,41 @@
1
+ -- Every attempt a request's chain stepped past, so a rescue can be counted (#647).
2
+ --
3
+ -- When a lane's first member fails before any body and the second answers,
4
+ -- the caller's request would have failed without Cruise. Nothing recorded it:
5
+ -- the ledger keeps one row per request, naming the attempt that was served and
6
+ -- its status, so a request rescued on its second attempt read exactly like one
7
+ -- served on its first. `fallbacks()` in report.ts inferred a chain firing from
8
+ -- `requested_model <> model` and had to exclude lanes, where that difference is
9
+ -- allocation, not failure. So the common case, a lane rescue, was invisible,
10
+ -- and so was a dark upstream that fallback was hiding from doctor.
11
+ --
12
+ -- One row per **failed** attempt, never for the one served: that one is the
13
+ -- ledger row itself, and a request served first time writes nothing here, so
14
+ -- the common path costs no extra row. Written in the same `db.batch` as the
15
+ -- ledger row (`recordLedgerEntry`), as `ledger_components` is, so a request
16
+ -- cannot land with its attempts and not its row or the other way round.
17
+ --
18
+ -- A **rescue** is then a ledger row with a 2xx `http_status` and at least one
19
+ -- row here. Every row here is a failure by construction: the chain only steps
20
+ -- on past an attempt `shouldFallOver` refused or that never answered. A
21
+ -- request whose last candidate failed too has rows here and a failing status,
22
+ -- and is not a rescue: it failed with Cruise as well.
23
+ --
24
+ -- Not priced, ever. A failed attempt is not billed (#647), and there is no
25
+ -- cost column to tempt anyone.
26
+ --
27
+ -- `http_status` is NULL when the attempt never answered at all: a dead
28
+ -- socket, or the deadline passing. Those are failures before any body as
29
+ -- much as a 503 is, and the chain treats them alike.
30
+ --
31
+ -- Mid-stream failures do not fall over (v0.md §4), so they never reach here.
32
+ CREATE TABLE ledger_attempts (
33
+ ledger_id TEXT NOT NULL REFERENCES ledger(id),
34
+ -- 1-based position in the chain, as `Chain.candidates[].position` has it.
35
+ position INTEGER NOT NULL CHECK (position >= 1),
36
+ model TEXT NOT NULL,
37
+ upstream_id TEXT NOT NULL,
38
+ http_status INTEGER,
39
+ upstream_ms INTEGER,
40
+ PRIMARY KEY (ledger_id, position)
41
+ );
@@ -0,0 +1,39 @@
1
+ -- Google AI Studio's retention, now that the key's tier is known: `retained`.
2
+ --
3
+ -- 0049 left every `google-ai-studio` row NULL, and 0061 kept it so, for one
4
+ -- reason: Google's terms follow the **account tier**, a property of the
5
+ -- credential that no page can answer. Free trains on prompts; paid does not.
6
+ -- `test/catalogue.test.ts` pinned NULL with "see 0049 before setting this".
7
+ --
8
+ -- On 2026-10-15 (UTC) the key behind `PROVIDER_KEY_GOOGLE_AI_STUDIO` was
9
+ -- replaced on staging and production, after the old one's credit ran out and
10
+ -- every Google request answered 402. Its holder read the key's plan in AI
11
+ -- Studio → API keys: **Tier 1**, a paid tier on a Cloud Billing account.
12
+ --
13
+ -- https://ai.google.dev/gemini-api/terms, last updated 2026-04-28, read
14
+ -- 2026-10-15. For Paid Services:
15
+ --
16
+ -- "Google doesn't use your prompts (including associated system
17
+ -- instructions, cached content, and files) or responses to improve our
18
+ -- products"
19
+ --
20
+ -- "Google logs prompts and responses for a limited period of time, solely
21
+ -- for detecting and preventing violations"
22
+ --
23
+ -- and "Your access to Gemini API is a 'Paid Service' only when accessing the
24
+ -- API through a Cloud Project ... associated with an active billing account."
25
+ --
26
+ -- Kept for a limited period and not trained on: `retained`, the same answer
27
+ -- and the same reading as xAI's in 0061. Not `none`, because a copy exists.
28
+ --
29
+ -- **This is a fact about the key, not the model.** A key replaced by one on
30
+ -- the free tier makes every row here false while reading exactly like a
31
+ -- measurement, and `no-training-on-data` (0030) would go on passing it. So
32
+ -- whoever next sets `PROVIDER_KEY_GOOGLE_AI_STUDIO` reads its tier again and,
33
+ -- if it is not paid, sets these rows back to NULL. By upstream, as 0061 does,
34
+ -- so a row added to this host later inherits nothing.
35
+ UPDATE models
36
+ SET data_retention = 'retained',
37
+ data_retention_source = 'https://ai.google.dev/gemini-api/terms (Paid Services; key on Tier 1)',
38
+ data_retention_as_of = '2026-10-15'
39
+ WHERE upstream_id = 'google-ai-studio';
@@ -0,0 +1,110 @@
1
+ -- Gemini 4 Argon, catalogued and priced (#677).
2
+ --
3
+ -- Google released `gemini-4-argon` on 2026-10-15 as its flagship reasoning
4
+ -- model, positioned for long-horizon coding and security work. It is the
5
+ -- candidate for Google's seat in the four coding lanes, which
6
+ -- `gemini-3.1-pro-preview` holds today (0084, 0088).
7
+ --
8
+ -- This migration catalogues and prices. It staffs no lane: a member nobody has
9
+ -- measured makes the lane's capability `null` (`src/lanes.ts`), so the swap is
10
+ -- a later migration, after a conformance run has written its flags. That is
11
+ -- the 0090 → 0091 order.
12
+ --
13
+ -- Every figure below was fetched 2026-10-15 UTC from Google's own pages, cited
14
+ -- per row. Not from the launch email the issue quoted, and not from the board
15
+ -- (board#384): #79 and #122 say why.
16
+ --
17
+ -- ## Which surface: compat, so this is a migration and not code
18
+ --
19
+ -- The launch sample calls `client.interactions.create`, and an Interactions-only
20
+ -- model would have needed an adapter (`docs/upstreams.md`). It is not one. On
21
+ -- 2026-10-15 UTC, with a paid Gemini API key:
22
+ --
23
+ -- GET /v1beta/openai/models/gemini-4-argon 200, "Gemini 4 Argon"
24
+ -- GET /v1beta/models/gemini-4-argon 200, supportedGenerationMethods
25
+ -- generateContent, countTokens,
26
+ -- batchGenerateContent
27
+ -- POST /v1beta/openai/chat/completions 200, content "ok", usage reported
28
+ --
29
+ -- The last call is the path Cruise sends Google chat through (0063's
30
+ -- `provider_path_prefix`), so the row below routes with no request-path code.
31
+ --
32
+ -- ## Window and output
33
+ --
34
+ -- https://ai.google.dev/gemini-api/docs/models/gemini-4-argon (last updated
35
+ -- 2026-10-14): context window 1,048,576 "unified input and output token
36
+ -- limit"; output "65,536 (default synchronous) / up to 1,048,576 (with long
37
+ -- decode)". Long decode is not a mode the compat endpoint offers, so the
38
+ -- synchronous figure is the one a Cruise request can get.
39
+ --
40
+ -- The model resource disagrees: `GET /v1beta/models/gemini-4-argon` reports
41
+ -- `outputTokenLimit: 32768`. The endpoint itself does not enforce that number.
42
+ -- `max_tokens` 40,000, 65,536 and 70,000 all answered 200 on the same day, so
43
+ -- 32,768 would refuse work Google accepts. `max_output` is a refusal threshold
44
+ -- (`src/routing.ts`), so the page's figure is written.
45
+ --
46
+ -- ## Price, and the rise already on the page
47
+ --
48
+ -- https://ai.google.dev/gemini-api/docs/pricing (last updated 2026-10-15),
49
+ -- paid tier, per 1M tokens:
50
+ --
51
+ -- through 2026-11-30 from 2026-12-01
52
+ -- input $2.00 $4.00
53
+ -- output (incl. thinking) $10.00 $20.00
54
+ -- context caching $0.10 $0.20
55
+ --
56
+ -- One price per category. Unlike 3.1 Pro, there is **no prompt-length tier**,
57
+ -- so there are no conditional variants to fold into a lane's maximum. The rise
58
+ -- is a second `effective_from` row, the mechanism 0049 used for Gemini 3.8
59
+ -- Flash: `resolvePrice` takes max(effective_from) <= the request's day, so the
60
+ -- price doubles at midnight UTC on 2026-12-01 with no second deploy and nobody
61
+ -- needing to remember.
62
+ --
63
+ -- That matters most for the lanes. At the December rate Argon is $4 / $20, so
64
+ -- once it is seated, a coding lane's price (the maximum across members, never
65
+ -- the average) is no lower than that from 2026-12-01.
66
+ --
67
+ -- Caching is implicit only ("explicit caching is not supported"): Google
68
+ -- decides what is a hit, and the cached rate is the price of one.
69
+ -- Batch, Flex and Priority are not written, for 0049's reason.
70
+ --
71
+ -- Free tier: "not available". This catalogue reaches Google only on the paid
72
+ -- key 0109 read, so that changes nothing here. A free key would get Google's
73
+ -- refusal, which #149 handles.
74
+ --
75
+ -- ## Retention
76
+ --
77
+ -- `retained`, on 0109's reading of the paid-services terms. The model page
78
+ -- says nothing to the contrary. 0109 set it by UPDATE on the rows that existed,
79
+ -- and a row added after inherits nothing, so this row writes it itself. Whoever
80
+ -- next replaces `PROVIDER_KEY_GOOGLE_AI_STUDIO` re-reads the tier for this row
81
+ -- too.
82
+ --
83
+ -- ## What conformance should watch
84
+ --
85
+ -- Thinking is on by default. Without `reasoning_effort`, a one-word prompt
86
+ -- sent directly to Google ran past 60 seconds with no headers. With
87
+ -- `reasoning_effort: "low"`, the same prompt answered in 2.6 seconds. A unary
88
+ -- request has 180 seconds (`UNARY_RESPONSE_TIMEOUT_MS`) and a streamed one 45
89
+ -- seconds to headers, so a check that times out on this row is the default
90
+ -- thinking budget, not the model being down.
91
+ --
92
+ -- Capabilities on the page (function calling, structured outputs, search
93
+ -- grounding; image, video, audio and PDF input) are not written here.
94
+ -- Conformance is their only writer.
95
+
96
+ INSERT INTO models (id, upstream_id, upstream_model, max_context, max_output,
97
+ data_retention, data_retention_source, data_retention_as_of) VALUES
98
+ ('google-ai-studio/gemini-4-argon', 'google-ai-studio', 'gemini-4-argon', 1048576, 65536,
99
+ 'retained', 'https://ai.google.dev/gemini-api/terms (Paid Services; key on Tier 1)', '2026-10-15');
100
+
101
+ INSERT INTO price_observations
102
+ (observed_at, model, effective_from, prompt_tokens_from, peak_utc,
103
+ input_micros_per_mtok, output_micros_per_mtok, cached_input_micros_per_mtok, source, note)
104
+ VALUES
105
+ ('2026-10-15 00:00:00', 'google-ai-studio/gemini-4-argon', '2026-10-15', 0, '',
106
+ 2000000, 10000000, 100000, 'https://ai.google.dev/gemini-api/docs/pricing',
107
+ 'First observation: Gemini 4 Argon (#677, 0110). Introductory rate, stated to hold through 2026-11-30.'),
108
+ ('2026-10-15 00:00:00', 'google-ai-studio/gemini-4-argon', '2026-12-01', 0, '',
109
+ 4000000, 20000000, 200000, 'https://ai.google.dev/gemini-api/docs/pricing',
110
+ 'The rate from 2026-12-01, printed on the same page as the introductory one and dated by it. Not a forecast.');
@@ -0,0 +1,80 @@
1
+ -- Gemini 4 Argon takes Google's seat in the four coding lanes, and joins
2
+ -- bb/deep-reasoning (#677).
3
+ --
4
+ -- 0110 catalogued it unmeasured. Conformance run 38757259408 (dispatched
5
+ -- 2026-10-15 UTC, production, narrowed to this one row) measured it, and the
6
+ -- run's mirror job copied the flags to staging and the demo:
7
+ --
8
+ -- streaming usage tools json_schema vision web_search
9
+ -- gemini-4-argon 1 1 1 1 1 0
10
+ -- gemini-3.1-pro-preview 0 1 1 0 1 0
11
+ --
12
+ -- Measured before staffed: the 0090 → 0091 order. Argon matches or beats 3.1
13
+ -- Pro on every flag, so no lane loses a capability by the swap. Each of the
14
+ -- four coding lanes has 3.1 Pro as a streaming=0 and json_schema=0 member, and
15
+ -- a lane's capability is the intersection of its members (`src/lanes.ts`). With
16
+ -- 3.1 Pro gone, a lane gains streaming or json_schema wherever 3.1 Pro was the
17
+ -- only member without it.
18
+ --
19
+ -- ## The coding lanes: a swap, at the same seat and rank
20
+ --
21
+ -- bb/agentic-coding, bb/builder, bb/hunter and bb/code-review. Same position,
22
+ -- same `quality_rank` 2, as 0091 did for Opus 5.5. Google's seat moves from a
23
+ -- preview to the model Google ships for "long-horizon coding and security
24
+ -- work". Rank 1 stays with Anthropic and OpenAI's frontier rows. Promoting
25
+ -- Argon beside them is a judgement about how it codes, which no measurement
26
+ -- here makes. 3.1 Pro stays catalogued and routable when pinned, because
27
+ -- disable-never-delete is about rows, not seats.
28
+ --
29
+ -- Price: from 2026-12-01 Argon is $4 / $20 (0110). A lane's price is the
30
+ -- maximum across its members, and gpt-6-astra ($10 / $50) is already in all
31
+ -- four, so the swap leaves each lane's quoted price where it is, in December as
32
+ -- now.
33
+ --
34
+ -- ## bb/deep-reasoning: added at rank 1
35
+ --
36
+ -- Google calls Argon "our flagship reasoning model" (models page, read
37
+ -- 2026-10-15). bb/deep-reasoning's only frontier member is gpt-6-astra, at
38
+ -- rank 1, so Argon joins it there as the second lab's frontier reasoner. The
39
+ -- lane has no `lane_requires`, and Argon carries every flag the lane's members
40
+ -- do, so its folded capabilities can only stay or grow. Selection stays
41
+ -- `cheap`: under it Argon is depth behind the cheaper members, not the lane's
42
+ -- first pick. Price is unchanged for the same reason as above, with astra
43
+ -- already the maximum.
44
+ --
45
+ -- ## Not here
46
+ --
47
+ -- **The bb-adm/* lanes.** They belong to a tenant and are staffed through the
48
+ -- control plane. No migration has written to them. bb-adm/code-review already
49
+ -- holds Argon, added there by hand.
50
+ --
51
+ -- **Lanes whose job wants something cheap or small** (chat-assistant,
52
+ -- extraction, summarization, translation, code-completion, rag-grounded-answers,
53
+ -- web-search). A frontier reasoning model at $2–4 / $10–20 is the wrong member
54
+ -- for any of them, and web-search needs `web_search`, which Argon measures 0.
55
+ --
56
+ -- Two statements per swap, for 0089's reason. `lane_members` is keyed
57
+ -- (lane_id, model_id) and the control plane can rewrite a lane's members
58
+ -- (`src/lanes-write.ts`). An environment where Argon was already added to one
59
+ -- of these lanes would fail a bare UPDATE and stop the migration. OR IGNORE
60
+ -- skips that row, and the DELETE takes 3.1 Pro out either way.
61
+ --
62
+ -- Selection policy is unchanged everywhere.
63
+
64
+ UPDATE OR IGNORE lane_members
65
+ SET model_id = 'google-ai-studio/gemini-4-argon'
66
+ WHERE lane_id IN ('bb/agentic-coding', 'bb/builder', 'bb/hunter', 'bb/code-review')
67
+ AND model_id = 'google-ai-studio/gemini-3.1-pro-preview';
68
+
69
+ DELETE FROM lane_members
70
+ WHERE lane_id IN ('bb/agentic-coding', 'bb/builder', 'bb/hunter', 'bb/code-review')
71
+ AND model_id = 'google-ai-studio/gemini-3.1-pro-preview';
72
+
73
+ -- Last position, so the existing order is untouched. OR IGNORE for the same
74
+ -- reason as above. Guarded on the lane existing, so an environment without it
75
+ -- writes nothing rather than failing the foreign key.
76
+ INSERT OR IGNORE INTO lane_members (lane_id, model_id, position, quality_rank)
77
+ SELECT 'bb/deep-reasoning', 'google-ai-studio/gemini-4-argon',
78
+ coalesce((SELECT max(position) FROM lane_members WHERE lane_id = 'bb/deep-reasoning'), 0) + 1,
79
+ 1
80
+ WHERE EXISTS (SELECT 1 FROM lanes WHERE id = 'bb/deep-reasoning');
@@ -0,0 +1,42 @@
1
+ -- What the affinity pin did for a request, on the ledger (#662).
2
+ --
3
+ -- ## Why
4
+ --
5
+ -- A run that sends `x-cruise-session` (#394) is answered with
6
+ -- `x-cruise-affinity: new | pinned | rebound`, and until now that header was
7
+ -- the only place the state existed. #662 found an agent tenant's Builder run,
8
+ -- `agent-38372995550` on 2026-10-13, that served two turns on
9
+ -- `openai/gpt-6-astra` and then three on `kimi-k2.7-code`, breaking the prompt
10
+ -- cache on the switch. The ledger showed no `ledger_attempts` row on any turn,
11
+ -- so it was not a fallover, but it could not say which of the two remaining
12
+ -- causes it was:
13
+ --
14
+ -- rebound the pinned member failed a hard gate or lost headroom, and the
15
+ -- lane redrew on purpose (`src/affinity.ts`, "What does")
16
+ -- new the pin was not found. It lives in KV, which is eventually
17
+ -- consistent across locations, and the switch came 14 seconds
18
+ -- after the pin was written, during three concurrent runs
19
+ --
20
+ -- The two have different fixes, so the column records which one happened.
21
+ -- A `new` on a row whose session already has earlier rows is the lost-pin
22
+ -- signature; a `rebound` is a decision, and the gate that made it is the
23
+ -- next question.
24
+ --
25
+ -- ## `ledger.affinity`
26
+ --
27
+ -- The state `x-cruise-affinity` carried, exactly. NULL when no session was
28
+ -- sent, when the request named a pinned model (the header is ignored for
29
+ -- routing there), and on every existing row — no default, because a guessed
30
+ -- state is worse than none. A cache hit records its state too: the hit was
31
+ -- still admitted through the lane, and a run's turns should read whole.
32
+ --
33
+ -- No index. The question is always about one run, and `idx_ledger_session`
34
+ -- (0098) already finds a run's rows; this column is read off them.
35
+ --
36
+ -- ## Order
37
+ --
38
+ -- This, then the Worker. The Worker before this never names the column; the
39
+ -- one after it fails every ledger insert without it.
40
+
41
+ ALTER TABLE ledger ADD COLUMN affinity TEXT
42
+ CHECK (affinity IS NULL OR affinity IN ('new', 'pinned', 'rebound'));
@@ -0,0 +1,125 @@
1
+ -- A second global lane namespace, `oss/`, for contributor lanes (#692).
2
+ --
3
+ -- A contributor lane holds only models whose hosts train on the traffic they
4
+ -- serve, at the discounted rates that bought the consent. Choosing one is the
5
+ -- consent, so the namespace is the disclosure — a caller cannot reach a
6
+ -- training endpoint without spelling the word. Same visibility and writability
7
+ -- as `bb/`: every tenant sees them, only the operator (and migrations) change
8
+ -- them. No lane is created here; 0115 seats the first one, on the rows 0114
9
+ -- catalogues.
10
+ --
11
+ -- ## Why a rebuild
12
+ --
13
+ -- SQLite cannot widen a CHECK, and the namespace rule lives in two of them:
14
+ -- the global/tenant split from 0028, and the global id-is-job pin from 0029
15
+ -- (as 0102 restated it). So this is 0102's shape, over the schema as it stands
16
+ -- now — which is 0102's exactly, nothing since having touched these three
17
+ -- tables. `lane_requires` constrains nothing new: the contributor rule is
18
+ -- prefix-implied, not a vocabulary slug — the vocabulary is the board's and
19
+ -- has no positive constraint — so it is enforced in the write and the
20
+ -- allocator (`src/lanes-validate.ts`, `src/lanes-allocate.ts`), the way the
21
+ -- web-search gate is job-implied rather than listed (0104).
22
+ --
23
+ -- **The window is real and is stated rather than argued small** (0102's
24
+ -- words, still true): each statement is its own transaction on D1, so between
25
+ -- `DROP TABLE lanes` and the `RENAME` a lane lookup finds no table and fails.
26
+ -- Run it at the quietest hour, and expect a handful of lane requests to 500
27
+ -- if traffic lands inside. Pinned-model requests do not read `lanes` and are
28
+ -- unaffected.
29
+ --
30
+ -- ## What does not change
31
+ --
32
+ -- The `compress` CHECK refuses on `tenant_id IS NULL`, which already covers
33
+ -- the new namespace: a contributor lane serves every tenant, so it cannot
34
+ -- rewrite their tool outputs, for 0096's reason. Lane reads filter on
35
+ -- `tenant_id IS NULL` (`VISIBLE_TO`, `src/lanes.ts`), so `oss/` lanes are
36
+ -- visible everywhere with no query change. Reserving `oss` as a tenant id is
37
+ -- the app's half (`assertTenantId`), as `bb` was — there is no table half to
38
+ -- widen, because there never was one for `bb` either.
39
+
40
+ CREATE TABLE lanes_new (
41
+ id TEXT PRIMARY KEY,
42
+ tenant_id TEXT REFERENCES tenants(id),
43
+ job TEXT NOT NULL CHECK (job IN (
44
+ 'chat-assistant', 'deep-reasoning', 'summarization', 'translation',
45
+ 'content-writing', 'extraction', 'code-completion', 'agentic-coding',
46
+ 'hunter', 'builder', 'web-search',
47
+ 'code-review', 'document-understanding', 'speech', 'rag-grounded-answers',
48
+ 'computer-use', 'content-moderation', 'other')),
49
+ description TEXT NOT NULL,
50
+ enabled INTEGER NOT NULL DEFAULT 1,
51
+ created_by TEXT NOT NULL DEFAULT 'migration',
52
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
53
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
54
+ archived_at TEXT,
55
+ selection TEXT NOT NULL DEFAULT 'cheap'
56
+ CHECK (selection IN ('cheap', 'random', 'weighted')),
57
+ selection_policy TEXT NOT NULL DEFAULT 'cheap'
58
+ CHECK (selection_policy IN ('cheap', 'random', 'weighted', 'graduated')),
59
+ cache_ttl_seconds INTEGER NOT NULL DEFAULT 0
60
+ CHECK (cache_ttl_seconds BETWEEN 0 AND 2592000),
61
+ cache_scope TEXT NOT NULL DEFAULT 'model'
62
+ CHECK (cache_scope IN ('model', 'lane')),
63
+ max_chain INTEGER
64
+ CHECK (max_chain IS NULL OR max_chain BETWEEN 1 AND 3),
65
+ router TEXT
66
+ CHECK (router IS NULL OR router IN ('jev')),
67
+ router_input TEXT NOT NULL DEFAULT 'hint'
68
+ CHECK (router_input IN ('hint', 'prompt')),
69
+ compress INTEGER NOT NULL DEFAULT 0
70
+ CHECK (compress IN (0, 1) AND (compress = 0 OR tenant_id IS NOT NULL)),
71
+ CHECK (
72
+ (tenant_id IS NULL AND (substr(id, 1, 3) = 'bb/' OR substr(id, 1, 4) = 'oss/')) OR
73
+ (tenant_id IS NOT NULL AND substr(id, 1, length(tenant_id) + 1) = tenant_id || '/'
74
+ AND substr(id, 1, 3) <> 'bb/' AND substr(id, 1, 4) <> 'oss/')
75
+ ),
76
+ CHECK (tenant_id IS NOT NULL OR ((id = 'bb/' || job OR id = 'oss/' || job) AND job <> 'other'))
77
+ );
78
+
79
+ INSERT INTO lanes_new (
80
+ id, tenant_id, job, description, enabled, created_by, created_at, updated_at,
81
+ archived_at, selection, selection_policy, cache_ttl_seconds, cache_scope, max_chain,
82
+ router, router_input, compress
83
+ )
84
+ SELECT id, tenant_id, job, description, enabled, created_by, created_at, updated_at,
85
+ archived_at, selection, selection_policy, cache_ttl_seconds, cache_scope, max_chain,
86
+ router, router_input, compress
87
+ FROM lanes;
88
+
89
+ CREATE TABLE lane_members_new (
90
+ lane_id TEXT NOT NULL REFERENCES lanes_new(id),
91
+ model_id TEXT NOT NULL REFERENCES models(id),
92
+ position INTEGER NOT NULL,
93
+ weight REAL NOT NULL DEFAULT 1 CHECK (weight > 0),
94
+ quality_rank INTEGER,
95
+ PRIMARY KEY (lane_id, model_id)
96
+ );
97
+
98
+ INSERT INTO lane_members_new (lane_id, model_id, position, weight, quality_rank)
99
+ SELECT lane_id, model_id, position, weight, quality_rank FROM lane_members;
100
+
101
+ CREATE TABLE lane_requires_new (
102
+ lane_id TEXT NOT NULL REFERENCES lanes_new(id),
103
+ constraint_ TEXT NOT NULL CHECK (constraint_ IN (
104
+ 'long-context', 'tools-required', 'json-schema', 'vision-required',
105
+ 'no-training-on-data',
106
+ 'low-latency', 'high-throughput', 'open-weights', 'data-residency',
107
+ 'audio-required', 'batch-api')),
108
+ value TEXT,
109
+ PRIMARY KEY (lane_id, constraint_)
110
+ );
111
+
112
+ INSERT INTO lane_requires_new (lane_id, constraint_, value)
113
+ SELECT lane_id, constraint_, value FROM lane_requires;
114
+
115
+ DROP INDEX idx_lane_members_lane;
116
+ DROP INDEX idx_lanes_tenant;
117
+ DROP TABLE lane_requires;
118
+ DROP TABLE lane_members;
119
+ DROP TABLE lanes;
120
+ ALTER TABLE lanes_new RENAME TO lanes;
121
+ ALTER TABLE lane_members_new RENAME TO lane_members;
122
+ ALTER TABLE lane_requires_new RENAME TO lane_requires;
123
+
124
+ CREATE INDEX idx_lane_members_lane ON lane_members (lane_id, position);
125
+ CREATE INDEX idx_lanes_tenant ON lanes (tenant_id, enabled);
@@ -0,0 +1,55 @@
1
+ -- Meta's contributor tier, catalogued at last: `muse-spark-1.2-contributor`
2
+ -- and `muse-spark-1.3-contributor` (#692).
3
+ --
4
+ -- 0050 left the 1.2 row out, and 0052 the 1.3, for the same stated reason:
5
+ -- the same model at $0.10 in / $0.20 out against $1.25 / $4.25, bought with
6
+ -- *"permission to use your prompts and completions to train future Meta
7
+ -- models"*, would win every request in any lane it joined — allocation is
8
+ -- price-first — while `no-training-on-data` fires only for a lane that
9
+ -- carries the requirement. Both comments end "waiting on a decision". This is
10
+ -- the decision: contributor rows exist, but only contributor lanes may hold
11
+ -- them. The namespace (0113), the write rule (`validMembers`) and the
12
+ -- per-request gate (`allocate`) admit them to `oss/` lanes and nowhere else,
13
+ -- so the cheapest row in the catalogue can no longer leak into a lane whose
14
+ -- caller never consented.
15
+ --
16
+ -- Unmeasured, like 0110's Argon: conformance measures what the host serves,
17
+ -- and the next scheduled run is the first that can see these rows. Until
18
+ -- then they are catalogued, priced, and unroutable — pinned or in a lane —
19
+ -- because an unmeasured model is not routable, in either namespace.
20
+ --
21
+ -- ## The figures
22
+ --
23
+ -- Rates as 0050 recorded them from Meta's pricing page on 2026-09-02
24
+ -- (https://dev.meta.ai/docs/pricing-rate-limits): $0.10 in, $0.20 out, no
25
+ -- prompt-size tier, no time-of-day tier. The cached-input rate for the
26
+ -- contributor tier was never stated there, so it is NULL — unrecorded, not
27
+ -- the standard tier's $0.15, which would be a guess wearing a figure's
28
+ -- clothes. `observed_at` is when this row observes them, not when 0050 read
29
+ -- the page; the note says where they come from.
30
+ --
31
+ -- `data_retention` is `trains` on the host's own quoted words above, the
32
+ -- third value these columns have held. `as_of` is when eyes were last on the
33
+ -- page — 2026-09-02, seven weeks back. That is honest rather than current:
34
+ -- the training permission is the tier's definition, the thing the discount
35
+ -- buys, so it cannot drift silently without the price rows drifting with it.
36
+ -- 1M-token window and unstated output cap are the generations' own (0050,
37
+ -- 0052), unchanged: a tier is a price and a term, not a different model.
38
+
39
+ INSERT INTO models (id, upstream_id, upstream_model, max_context, max_output,
40
+ data_retention, data_retention_source, data_retention_as_of) VALUES
41
+ ('meta/muse-spark-1.2-contributor', 'meta', 'muse-spark-1.2-contributor', 1048576, NULL,
42
+ 'trains', 'https://dev.meta.ai/docs/pricing-rate-limits', '2026-09-02'),
43
+ ('meta/muse-spark-1.3-contributor', 'meta', 'muse-spark-1.3-contributor', 1048576, NULL,
44
+ 'trains', 'https://dev.meta.ai/docs/pricing-rate-limits', '2026-09-02');
45
+
46
+ INSERT INTO price_observations
47
+ (observed_at, model, effective_from, prompt_tokens_from, peak_utc,
48
+ input_micros_per_mtok, output_micros_per_mtok, cached_input_micros_per_mtok, source, note)
49
+ VALUES
50
+ ('2026-10-21 00:00:00', 'meta/muse-spark-1.2-contributor', '2026-10-21', 0, '',
51
+ 100000, 200000, NULL, 'https://dev.meta.ai/docs/pricing-rate-limits',
52
+ 'First observation: contributor tier catalogued (#692, 0114). Rate as 0050 recorded it on 2026-09-02; cached input unstated.'),
53
+ ('2026-10-21 00:00:00', 'meta/muse-spark-1.3-contributor', '2026-10-21', 0, '',
54
+ 100000, 200000, NULL, 'https://dev.meta.ai/docs/pricing-rate-limits',
55
+ 'First observation: contributor tier catalogued (#692, 0114). Rate as 0050 recorded it on 2026-09-02; cached input unstated.');
@@ -0,0 +1,41 @@
1
+ -- The first contributor lane: `oss/chat-assistant` (#692).
2
+ --
3
+ -- ## One lane, two members, both unmeasured
4
+ --
5
+ -- The vocabulary asks three routable, measured members before a `bb/` lane
6
+ -- exists, so that allocation is a decision. That rule is stated for `bb/`
7
+ -- lanes; `oss/` lanes staff where contributor models exist, and two rows are
8
+ -- the whole market today — the 1.2 and 1.3 contributor tiers 0114
9
+ -- catalogued. A second member from another lab is a catalogue change, not a
10
+ -- staffing failure.
11
+ --
12
+ -- One job, not one per job: `chat-assistant` is the definitionally general
13
+ -- job, and these are general models. Staffing `oss/summarization` or
14
+ -- `oss/translation` beside it would be a guess about what unmeasured models
15
+ -- do well — the flags that answer that arrive with the first conformance run
16
+ -- that sees the rows. More lanes follow measurement, as 0111 followed 0110.
17
+ --
18
+ -- Unmeasured members are a lane built today from models measured tomorrow,
19
+ -- which the write explicitly allows: valid, listed, serving nothing until
20
+ -- conformance admits them. The catalogue row says so honestly — capabilities
21
+ -- null, retention `trains`, price the dearest member's.
22
+ --
23
+ -- ## The terms, on the row
24
+ --
25
+ -- No `lane_requires` row: the contributor rule is prefix-implied, not a
26
+ -- vocabulary slug (0113). Selection stays `cheap` — under it the dearer
27
+ -- generation is depth behind the cheaper, not the lane's first pick, and both
28
+ -- are a tenth of anything in `bb/chat-assistant`. Both members unranked:
29
+ -- placing 1.3 above 1.2 is a judgement about generations no measurement here
30
+ -- makes (0111's reason), and an unranked member cannot serve a downgrade,
31
+ -- which is the safe direction for a lane whose cache is off. The description
32
+ -- carries the consent in plain words, because it is shown to callers beside
33
+ -- the lane in `/v1/models`.
34
+
35
+ INSERT INTO lanes (id, job, description) VALUES
36
+ ('oss/chat-assistant', 'chat-assistant',
37
+ 'General chat on contributor-tier models: the host may train future models on this traffic, at the discounted rates that bought the consent. Choosing this lane is the consent.');
38
+
39
+ INSERT INTO lane_members (lane_id, model_id, position, quality_rank) VALUES
40
+ ('oss/chat-assistant', 'meta/muse-spark-1.3-contributor', 1, NULL),
41
+ ('oss/chat-assistant', 'meta/muse-spark-1.2-contributor', 2, NULL);