-
Notifications
You must be signed in to change notification settings - Fork 24
Expand file tree
/
Copy pathCargo.toml
More file actions
1127 lines (1116 loc) · 61.4 KB
/
Copy pathCargo.toml
File metadata and controls
1127 lines (1116 loc) · 61.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
[workspace]
members = [
"crates/billing",
"crates/app",
"crates/server",
"crates/api-github",
"crates/api-partner-console",
"crates/api-onboarding",
"crates/app-core",
"crates/auth",
"crates/authz",
"crates/server-authz",
"crates/cameras",
"crates/core",
"crates/entity",
"crates/git",
"crates/integration/looker",
"crates/integration/slack-client",
"crates/metric-monitoring",
"crates/integration/unifi",
"crates/migration",
"crates/oxy-compile",
"crates/integration/omni",
"crates/observability",
"crates/telemetry",
"crates/oltp",
"crates/project",
"crates/semantic",
"crates/shared",
"crates/simulation",
"crates/test-utils",
"crates/infrastructure/llm/anthropic",
"crates/infrastructure/llm/ollama",
"crates/infrastructure/llm/gemini",
"crates/infrastructure/llm/openai",
"crates/infrastructure/llm/oxy-llm",
"crates/infrastructure/semantic",
"crates/agentic/analytics",
"crates/agentic/airway",
"crates/agentic/builder",
"crates/agentic/core",
"crates/agentic/llm",
"crates/agentic/connector",
"crates/agentic/http",
"crates/agentic/pipeline",
"crates/agentic/runtime",
"crates/agentic/semantic",
"crates/agentic/automation",
"crates/airform",
"crates/airhouse",
"crates/platform",
"crates/workspace-fs",
"web-app",
]
exclude = [
"docs",
"internal-docs",
"tests",
"scripts",
"assets",
"oss",
"json-schemas",
"semantics",
"sdk",
# Dev-only dynamic-linking shim. Excluded (NOT a member) so `--workspace`
# test/lint/build never compile its ~1.4 GB dylib; it is built solely when
# `oxy-server` enables the `dev-dynamic` feature (a path dep pulls it in).
"crates/app-dylib",
]
# The default build target is the binary crate (oxy-server), so `cargo build` /
# `cargo run` at the workspace root drive the `oxy` binary.
default-members = ["crates/server"]
resolver = "2"
[workspace.dependencies]
# Internal workspace crates
agentic-airway = { path = "crates/agentic/airway" }
agentic-analytics = { path = "crates/agentic/analytics" }
agentic-builder = { path = "crates/agentic/builder" }
agentic-connector = { path = "crates/agentic/connector" }
agentic-core = { path = "crates/agentic/core" }
agentic-http = { path = "crates/agentic/http" }
agentic-llm = { path = "crates/agentic/llm" }
agentic-pipeline = { path = "crates/agentic/pipeline" }
agentic-runtime = { path = "crates/agentic/runtime" }
agentic-semantic = { path = "crates/agentic/semantic" }
agentic-automation = { path = "crates/agentic/automation" }
airhouse = { path = "crates/airhouse" }
entity = { path = "crates/entity" }
migration = { path = "crates/migration" }
omni = { path = "crates/integration/omni" }
oxy = { path = "crates/core" }
oxy-airlayer-compat = { path = "crates/infrastructure/semantic" }
oxy-anthropic = { path = "crates/infrastructure/llm/anthropic" }
oxy-auth = { path = "crates/auth" }
oxy-app-core = { path = "crates/app-core" }
oxy-authz = { path = "crates/authz" }
oxy-server-authz = { path = "crates/server-authz" }
oxy-billing = { path = "crates/billing" }
oxy-cameras = { path = "crates/cameras" }
oxy-compile = { path = "crates/oxy-compile" }
oxy-gemini = { path = "crates/infrastructure/llm/gemini" }
oxy-git = { path = "crates/git" }
oxy-llm = { path = "crates/infrastructure/llm/oxy-llm" }
oxy-looker = { path = "crates/integration/looker" }
oxy-slack-client = { path = "crates/integration/slack-client" }
oxy-metric-monitoring = { path = "crates/metric-monitoring" }
oxy-observability = { path = "crates/observability" }
oxy-telemetry = { path = "crates/telemetry" }
oxy-ollama = { path = "crates/infrastructure/llm/ollama" }
oxy-airform = { path = "crates/airform" }
oxy-openai = { path = "crates/infrastructure/llm/openai" }
oxy-oltp = { path = "crates/oltp" }
oxy-platform = { path = "crates/platform" }
oxy-project = { path = "crates/project" }
oxy-semantic = { path = "crates/semantic" }
oxy-shared = { path = "crates/shared" }
oxy-test-utils = { path = "crates/test-utils" }
oxy-unifi = { path = "crates/integration/unifi" }
oxy-workspace-fs = { path = "crates/workspace-fs" }
# External dependencies
async-openai = { version = "0.41.3" }
duckdb = { version = "=1.10501.0", features = ["bundled"] }
axum = { version = "0.8.9", features = ["macros", "http2"] }
axum-server = { version = "0.8.0", features = ["tls-rustls"] }
# Not `"*"`. A wildcard accepts *any* release, so a routine `cargo update`
# would pull a future breaking chrono (0.5, 1.0) into the build with no
# semver gate — the lockfile is the only thing that was holding this at 0.4.
chrono = "0.4"
# 0.8.6 carried IANA tzdata frozen in Feb 2024. Zone rules decide where a
# `.monitor.yml` / `reconcile.yml` bucket boundary falls, so stale rules read
# as an anomaly that isn't real, or hide one that is.
#
# `cargo tree` still shows chrono-tz 0.8.6 below this: airlayer declares it.
# That is not a second stale bucketing path — re-checked at the rev pinned
# below (`afe6daf`): airlayer still has zero `chrono_tz` call sites, and its timezone
# handling (`sql_generator::time_col_expr`) emits `dialect.convert_tz(expr, tz)`,
# passing the zone NAME into generated SQL. The warehouse's tzdata governs
# there, not ours, so the fix above is complete.
#
# That is a claim about someone else's crate at one revision, so it is pinned to
# one deliberately: if a future airlayer bump starts applying zone rules
# in-process, this comment goes stale silently and the bug reopens on exactly
# the path that produces bucket labels. Re-check it when moving that rev.
chrono-tz = "0.10"
croner = "3"
indexmap = { version = "2.14.0", features = ["serde"] }
log = { version = "0.4" }
minijinja = { version = "2.23.0" }
regex = { version = "1.13.1" }
sea-orm = { version = "2.0.1", features = [
"sqlx-postgres",
"runtime-tokio-rustls",
"macros",
"with-chrono",
# No `postgres-use-serial-pk`. It used to be here to keep `.auto_increment()`
# emitting `serial` instead of 2.0's `GENERATED BY DEFAULT AS IDENTITY`, so a
# fresh migration run matched the deployed databases. That made a column's type
# depend on a cargo feature declared two files away — drop the feature and three
# tables silently change shape, with nothing failing until a restore.
#
# The three sites now spell `serial`/`bigserial` out at the call site, so the
# DDL is the same with or without the feature and there is nothing left for it
# to do. `migration_ddl_is_not_feature_dependent` asserts exactly that.
] }
sea-orm-migration = { version = "2.0.1", features = [
"sqlx-postgres",
"runtime-tokio-rustls",
"with-uuid",
] }
sentry = { version = "0.49.1", features = [
"tower-axum-matched-path",
"rustls",
"tracing",
] }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0.151" }
serde_yaml = { version = "0.9.34" }
sqlparser = { version = "0.62" }
tempfile = { version = "3.27.0" }
thiserror = { version = "2.0.20" }
tokio = { version = "1.53", features = ["full"] }
tracing = { version = "0.1.44" }
uuid = { version = "1.24", features = ["serde", "v4"] }
aes-gcm = "0.11.0"
aho-corasick = "1.1"
# Pinned to a main SHA carrying the whole opportunity-sizing line of work
# (airlayer PRs #78–#81 plus the composite-rate-basis follow-up, all merged).
# Together, these are what make the world-model Opportunities panel say
# something true:
#
# - Sum-like measures are sized on a per-unit rate via a declared count
# denominator (refusing outright when there isn't one), so a segment is never
# flagged as headroom merely for being small.
# - Segment discovery drops surrogate-id dimensions and follows foreign entities
# one hop, so the scan sees the joined view's real dimensions —
# stores.store_name / .region — not just the fact view's FKs.
# - `opportunity` takes a caller-supplied `scope` filter set, so the instance
# panel sizes "within this instance" instead of the whole population.
# - Each segment is gated on evidence: a Welch test on the rate gap, with a bar
# that answers for every comparison the scan makes (~100 once foreign entities
# are followed) rather than the segments of the dimension in hand, and that
# rises to offset the benchmark being the selected best of those same
# segments. A scan of ~20 dimensions can no longer mint a "lever" just by
# testing enough of them. Dimensions can also declare `segmentable: false`, so
# non-levers (gender, address lines, measure-backing numerics) are never
# ranked as upside at all.
#
# The gate is opt-in via `augment_layer_for_opportunity`, which MUST be called
# after building the metric tree and before building the engine — see
# server/api/metric_tree.rs, where the ordering is load-bearing and commented.
# Without it, sizing silently reverts to reporting gaps it cannot demonstrate.
#
# - Composites referencing a single foreign view isolate into per-view CTEs, so
# a finer-grain numerator can pair with a coarser-grain count in one query —
# the per-unit rate the opportunity drill decomposes. A `type: custom`
# composite root is sized on the same rate basis as its component children,
# so concentration reports each child's GAP share, not its SIZE share.
# - The evidence gate handles a filtered-sum numerator (a per-parent-unit rate),
# and its significance test can be asked to spend a smaller alpha budget than
# 5% — both needed once the drill runs multi-level scans. A segment the gate
# could not evaluate is reported as such (`gated: false`).
# - opportunity_drill: recursively decomposes the top opportunity() gap through
# component edges and dimension partitions, inheriting the root's benchmark
# population unchanged at every level and gating each dimension split on
# significance at a Šidák-composed per-level alpha budget. It skips
# tautological splits, collapses aliased dimensions before counting the
# comparison family, gates for its whole candidate family, and applies
# hierarchy-aware pruning.
# - The drill installs its synthetic per-value measures through a shared
# Arc<RwLock<SemanticLayer>> (SharedLayer) with scoped locks, so a real SQL
# executor holding the same handle can compile them mid-recursion.
# - `augment_layer_for_opportunity` adds dispersion support for eligible
# composites (flattened STDDEV_SAMP), and `supports_rate_basis(layer, target)`
# is the single authoritative answer to "will the drill size this target on a
# per-unit rate" — replacing the two places oxy used to re-derive that
# predicate itself and get it wrong. The same answer is serialized per node as
# `MetricNode.drillable` so the UI can gate on it directly.
#
# Also carries the time-dimension timezone fix (PR #85): every site that emits
# a time-dimension column — the SELECT/GROUP BY buckets, the date_range WHERE
# bounds, the fan-out spine and the shift scan window — now routes through one
# tz-aware helper. Before it, `QueryRequest.timezone` reached only the SELECT
# bucket, so a filter-only query (granularity: None, which is every reconcile
# health check) got a timezone that did nothing, and a fan-out query bucketed
# rows on the local calendar while clipping them on the UTC one.
#
# And the follow-up that fix left open: that helper converted a time dimension
# whenever the request carried a timezone, whatever its declared type. A DATE
# column has no time-of-day and no UTC semantics, so converting it is wrong —
# and on ClickHouse it is fatal, not merely wrong: `toTimeZone` accepts only
# DateTime/DateTime64 and raises `Code: 43 ILLEGAL_TYPE_OF_ARGUMENT` on a Date
# argument. Every monitor here that sets `timezone:` and buckets a `type: date`
# business-date column (labor_daily, sales_daily, qb_cogs_by_store) failed its
# scan outright. The conversion is now gated on `DimensionType::Datetime`, on
# the same single helper, so the bucket and the bound still cannot disagree.
#
# 06ba5e5 (the pin before last) is an ancestor of this SHA, so nothing above is
# lost by moving to it.
#
# Adds `direction` + `contribution` + `passthrough` to DriverAttribution, so the
# anomaly explain panel can tell a driver that *caused* a move from one that
# offset it (a `direction: negative` driver that fell pushes the target up) —
# and both of those from one that only moved because its base did (discount
# dollars tracking sales volume). Stacked on e8353db above, which it keeps.
#
# Now airlayer main (PRs #86 and #87 merged), not the branch head 67115e6 this
# was developed against. 67115e6 is an ancestor, so nothing above is lost, and
# the nine commits since carry the fix that makes the feature work on the
# anomaly that motivated it: the passthrough drift test was symmetric, so
# gross-is-a-passthrough-of-discounts passed the same gate as the reverse. Both
# rows got a split, both sorted into the mechanical group, and the explain
# returned nothing that explained the move — strictly worse than before the
# feature. A base must now contain its driver (|ratio| < 1 in both periods),
# and candidates include the target's component children rather than only its
# declared sibling drivers, since `net = gross - discounts` is what discounts
# actually ride on and nobody declares that as a driver edge.
#
# Serialized shapes are unchanged across the bump — DriverAttribution,
# DriverContribution and PassthroughSplit keep their fields, so the TS mirrors
# in web-app/src/types/metricTree.ts and sdk/typescript/src/metricTree.ts still
# match. `contribution` is still emitted on a passthrough row (airlayer's own
# CLI now prints `mechanical` for those rather than the raw value, matching how
# `group()` files them — the same precedence the web panel applies).
# The entries below are a HISTORY of what each bump added, oldest first. Only
# the last one describes the pin in force; every "branch head" named before it
# is an ancestor of the current rev and needs no separate bump. The one thing
# to carry forward is at the bottom: the pin in force is still a branch head.
#
# `feat/reachable-values-filtered` (https://github.com/oxy-hq/airlayer/pull/89)
# added `reachable_values_filtered` (scope-narrowed baseline fetch) for
# metric-tree scenario simulation — `reachable_values` keeps its exact
# signature and delegates to it with an empty scope, producing a byte-identical
# QueryRequest (asserted by a test in that PR).
#
# `runtime-coefficient-fitting` added `engine::metric_tree_fit` — within-panel
# lagged OLS that sizes a driver edge declaring no `coefficient:` from the same
# window the baseline already queries, with a |t| >= 2.0 refusal gate. Purely
# additive: no existing signature or serialized shape changes, so the TS
# mirrors in web-app/src/types/metricTree.ts and
# sdk/typescript/src/metricTree.ts stay valid (the new `fitted[]` is an added
# optional field, not a changed one).
#
# Now also at bca26bb: a driver edge is fitted AND propagated under its
# declared `form:`, which until then was metadata — the fit regressed raw
# levels and `predict` multiplied, so `log-log` yielded a level slope labelled
# an elasticity. `FittedDriver` gains `form` and `n_nonpositive`; both default
# on deserialize, so an older payload still round-trips as the linear fit it
# was. Two behaviour changes to know about: a non-linear form with no `values`
# now reports `unquantifiable` instead of a silently-linear number, and
# `explain`'s `estimated_target_impact` can be absent for an unsizable form as
# well as for a missing coefficient.
#
# Now at 535c546: the four hand-written `form:` cases collapse into ONE
# representation (`engine::response` — a basis of transformed regressors under a
# link on the target), so a new shape is a table row rather than six edits. Adds
# `form: quadratic`, the first shape that can TURN AROUND, and with it
# `coefficients: Vec<f64>` on both `Driver` and `FittedDriver` — a scalar reads as
# a one-element vector, so no existing `.view.yml` or serialized fit changes.
#
# Three things to know, all covered in internal-docs/driver-response-standardization.md:
# * `FittedDriver` gains `coefficients`/`se_terms`/`t_stats`, plus `moments` and
# `domain`. The moments are load-bearing, not diagnostics: the fit is per row
# and a lever is a window aggregate, and a curved response cannot cross that
# gap without them (the naive alternative is 42,905x out with the sign
# flipped). They must survive the baseline -> predict round trip.
# * Log-link propagation is now EXACT where it was first-order, so existing
# numbers move slightly by design: +10% at an elasticity of 0.4 gives 38,860
# rather than 40,000, and the old shortcut was 12.4% out by +50%.
# * `MetricTree` gains `warnings` (additive, skipped when empty) — a driver that
# declares both `coefficient:` and `coefficients:`, or a wrong-width vector,
# is refused with a reason instead of silently going qualitative.
#
# And at 3f0e793: `form:` is now OPTIONAL. `Driver.form` is `Option<DriverForm>`
# — left out, the engine infers the shape from history alongside the magnitude;
# declared, it pins the shape and skips the search. Candidates are compared by
# AIC in y-space (the Jacobian is what makes a `ln y` model comparable with a
# `y` model), `linear` is the null, and a curve must beat it by 10 AIC AND clear
# the per-term gate to be adopted. `FittedDriver` gains `form_source` and the
# candidate scores so an inferred shape is auditable.
#
# And at 422aa7f: the per-shape vertex solver is gone. `FittedDriver.profile`
# carries the response SAMPLED as (lever, delta) pairs over the range the fit has
# evidence for, so peak / break-even / saturation are read off the curve instead
# of solved per basis — and the client needs no shape vocabulary at all. Fill it
# with `FittedDriver::with_profile(target)` wherever the target's aggregate is
# known; the fit itself cannot, since a log link needs that value.
#
# Now 06b0761 (branch `feat/more-driver-shapes`), which cashes the "a shape is a
# row" claim four times over: `cubic`, `sqrt`, `inverse` and
# `linear-log-quadratic`, each one enum variant plus one `ResponseSpec`, with the
# fit and the forecast unchanged. All four aggregate exactly, so all four are
# inferable. Note the selection change that came with them: the richer shapes
# NEST the simpler ones and can only ever score better, so the 10-AIC margin now
# applies BETWEEN candidates too — within that band the fewest-term shape wins,
# equal widths settled by the candidate list's order. Without it, adding `cubic`
# silently re-labelled a quadratic edge on a 2.2-point margin.
#
# Then `feat/user-grain-time-dimensions` (6ed55f0), branched off that head:
# time_dimensions are now supported on the user-grain CTE path, which used to
# refuse them outright. That path is chosen per MEASURE — any non-additive
# measure on a multiplied view routes there — so one such measure made every
# bucketed query across the batch fail at SQL generation. The scenario
# projection asks for that shape by definition (a measure series broken out by
# day/week/month across views), so it could not draw a curve for a tree
# containing an `avg`. The commit also re-applies `date_range` inside the CTEs
# and the spine, which assemble their own WHERE and had been dropping it: the
# window silently became "everything". 6ed55f0 then adds the time dimension's
# own view to the join planning for each CTE — projecting a bucket column is not
# enough if the view it comes from was never joined in, which surfaced as
# "not reachable from view X" for a pair the entity graph connects fine.
#
# 67a3acb (airlayer PR #90, branched off 6ed55f0) makes `aggregate_delta` take
# the target's `AggregateSpace`. A fitted response aggregates through the basis
# moments, which are SUMS — so what it produces is a change in the target's
# TOTAL, and adding that to a target whose window value is a MEAN is out by the
# row count. `sales_per_guest -> avg_order_value` fitted `coefficient 1.00` over
# n=2,005 rows and moved a 27.50 average to 8.3k. Identity links now convert;
# log links are proportional and need none; a ratio or a median refuses.
# THE BRANCH HAD SILENTLY FALLEN BEHIND MAIN, which is what `afe6daf` fixes by
# merging main back in. The claim that stood here — "every rev named above is
# an ancestor of it" — was false, and stating it is what let the gap sit
# unnoticed: `feat/more-driver-shapes` was cut BEFORE PRs #86 and #87 merged,
# so 19 commits of main were missing. Two of them mattered:
#
# * `e8353db fix(sql): only timezone-convert a datetime time dimension`.
# Losing it re-broke every non-UTC query over a `type: date` dimension —
# silently a day out on Postgres, `Code: 43 ILLEGAL_TYPE_OF_ARGUMENT` on
# ClickHouse (`toTimeZone` takes DateTime/DateTime64 only), which is what a
# scenario projection panel surfaced as "the warehouse rejected the query".
# * PR #86's `direction` / `contribution` / `passthrough` on
# `DriverAttribution` — described above as if in force, but the pinned
# struct had none of those fields, so the explain panel's driver
# classification was inert and `agentic-analytics`' prompt described a
# `passthrough` object the engine never emitted. Nothing failed to compile,
# because both consumers made the fields optional. Restored by the merge,
# so that panel starts populating again.
#
# `afe6daf` and then `4ad34c1` merged airlayer main back into that branch and
# added three commits over it: `346c2eb` is a clippy-only cleanup, `7f755c6`
# fixes `aggregate_delta` treating a rate response's constant term as a per-row
# factor instead of the whole delta, and `ddaed78` stops a fit's `t_stat` (and
# its per-term siblings) from ever landing on a literal `f64::INFINITY` —
# `serde_json` has no JSON literal for it and serializes it as `null`, so the
# metric-tree `predict` endpoint's `PredictRequest.coefficients: Vec<FittedDriver>`
# 400'd deserializing its own baseline fit's output back in whenever a fit had
# zero residual standard error. It's now `b.signum() * f64::MAX`: still clears
# `MIN_FIT_T`, still signed, and an ordinary finite JSON number.
#
# NOW AT 3199baa, which merges airlayer main INTO that branch — main having
# moved twice underneath it while this PR was open:
#
# * PR #96 (ca3e72c) stops generate_reagg_sql / generate_warehouse_reagg_sql
# wrapping the local-preagg re-aggregation query in GROUP BY + SUM/COUNT/...
# when the requested dimensions (and time dimension, at its stored
# granularity) already match the rollup's exact grain — one row per group
# already, so re-aggregating was a no-op that still forced a full
# scan+aggregate, and GROUP BY is blocking, so it cannot honor LIMIT
# cheaply even then. Generated SQL only; nothing stored moves.
# * PR #99 (e89d1d0, merged as 077aeef) resolves `{{view.member}}` /
# `{{member}}` refs in a rollup's CTAS and folds `Measure.filters` into the
# stored partials. THIS ONE CHANGES WHAT A BUILT ROLLUP CONTAINS: a
# filtered measure previously stored the unfiltered total and served it
# under the Pre-aggregated badge, and `compute_rollup_hash` covers only
# member NAMES, granularity and time dimension — not exprs or filters — so
# the artifact keeps its name and a rebuild is not implied by the fix.
# `PREAGG_BUILDER_GENERATION` in crates/app/src/server/preagg_executor.rs
# is the invalidation lever, and main already bumped it for this pin; every
# workspace rebuilds once on its next cycle.
#
# AND NOW ON MAIN, at last. Everything above was developed as a CHAIN of stacked
# branches — `feat/more-driver-shapes` -> `feat/user-grain-time-dimensions` ->
# `fix/aggregate-delta-target-space` — and airlayer PR #90 merged into
# `feat/user-grain-time-dimensions` rather than into main, so for the whole life
# of this PR the pin was a branch head and none of the stack was on main. PR
# #100 (https://github.com/oxy-hq/airlayer/pull/100) landed the lot as `e0bca28`,
# which is what this line now names: an ordinary commit on airlayer main, not a
# rev that can be force-pushed out from under the lockfile.
#
# #100 carried one commit that was not in what this pinned before it merged —
# `3189f0e`, ten defects found in review. Four of them are visible from Oxy:
#
# * `aggregate_delta_from_total` had no `([Log], Identity)` arm, so a DECLARED
# `form: linear-log` could never be sized — `fittable_edges` excludes an edge
# that declares a coefficient, so it never acquired moments either, and the
# refusal was unconditional. Such an edge now sizes. A declaration states the
# aggregate response directly, so unlike a fit it carries no row-count factor.
# * A delta-only `predict` refused every log-form driver with a bare zero. The
# refusal itself is right — an elasticity applied as a level slope is wrong by
# target/driver — but it now names its cause, so the panel can say which
# levels are missing instead of showing an unexplained nothing.
# * `apply_fitted_coefficients` wrote `edge.form` BEFORE the width check that
# can reject the fit, leaving an inferred shape on an edge with no
# coefficients. Oxy calls this directly in the `predict` handler.
# * `n_nonpositive` read 0 on every successful inferred fit, hiding exactly the
# narrowing of `n` the field exists to disclose.
#
# Plus, on the SQL side, the user-grain CTE path now compiles a fanned-out CTE in
# two halves (a DISTINCT over source key + projected dims, then a rejoin that
# aggregates) rather than silently multiplying an all-additive CTE across a
# one-to-many hop, and it no longer discards ORDER BY and measure filters while
# still emitting LIMIT. A time dimension with neither granularity nor date_range
# is refused on that path as it already was on the main one.
#
# Now b16548d (airlayer PR #101), three commits on: a ClickHouse rollup CTAS
# makes the grouping key the MergeTree SORTING key, and MergeTree refuses a
# nullable sorting key by default — so a view whose dimensions arrived as
# `Nullable(String)`, which is most of what an ELT pipeline loads, could not
# have a rollup built at all:
#
# Code: 44. DB::Exception: Sorting key contains nullable columns, but merge
# tree setting `allow_nullable_key` is disabled. (ILLEGAL_COLUMN)
#
# The CTAS now carries `SETTINGS allow_nullable_key = 1`. Nothing stored changes
# shape and no rebuild is implied: a rollup that previously built still builds
# identically, and one that previously failed now exists. A NULL group stays its
# own row rather than folding into the type default, and the key still prunes.
#
#
# Now a729c18 (airlayer PR #102), two commits on b16548d and READ-SIDE ONLY:
# `date_trunc` is bound over the stored VARCHAR time column so a coarser-than-
# stored request stops falling back to the warehouse, and an `InDateRange` filter
# declines the rollup instead of being erased from the WHERE. Nothing a build
# WRITES changes, so `PREAGG_BUILDER_GENERATION` in
# crates/app/src/server/preagg_executor.rs needs no bump — it was last moved for
# #99, and nothing in #100-#102 writes a rollup.
#
# This branch pinned `6bc560e4` for the same two commits before merging main:
# that was #102's pre-rebase branch copy, whose tree is byte-identical to
# a729c18's (`git diff 6bc560e4 a729c18` is empty). a729c18 is the one that
# landed on airlayer main, so it is the one that survives branch deletion.
# NOW AT 34802d4 — airlayer main, and 30 commits on. PRs #103, #104 and #105,
# essentially all of it pre-aggregation. `a729c18` is an ancestor, so this is a
# clean fast-forward with nothing reverted. Four things to know:
#
# * **Every rollup hash moves, and that is the invalidation.** `0b4cf10` folds
# a `definition_fingerprint` into `compute_rollup_hash` — view name and
# `table:`/`sql:`, each dimension's `expr`, and per measure
# `name:type:expr:filters`. The artifact is named `{view}__{hash}__{date}`,
# so every existing rollup gets a NEW name: old Parquet is orphaned rather
# than overwritten, and `RollupFreshness` is re-keyed on
# `(view_name, rollup_name, rollup_hash)` (`ceeac04`) so a moved hash can
# match no prior verdict and cannot be judged fresh. Expect one full rebuild
# after deploy, and a cold preagg tier until it finishes.
# This is ALSO why `PREAGG_BUILDER_GENERATION` is NOT bumped for this pin —
# see its doc in crates/app/src/server/preagg_generation.rs, which this bump
# corrects: the "the artifact keeps its name" premise it rested on no longer
# holds. Independently, `generate_build_sql` emits an unchanged CTAS for any
# rollup that previously built (`396e84b` only turns silently-dropped
# undeclared members into hard build errors), so nothing needs the lever.
#
# * **Three API breaks and one silent one.** `check_coverage` takes a third
# `live: Option<&LiveRollups>`; `RollupFreshness` gains `view_name` +
# `rollup_name`; `DrillConfig` gains `dialect`. The silent one is
# `BuildPlan`, which gains `migrations` + `prelude_len`: it still COMPILES
# against a caller that runs only `plan.statements`, but an existing
# deployment's `__manifest` then never acquires `refresh_key_value` /
# `refresh_key_checked_at` and every upsert fails. `execute_build_plan` in
# crates/agentic/semantic/src/preagg.rs now runs prelude -> migrations
# (best-effort, they are expected to fail once applied) -> the rest.
#
# * **`None` and an empty `LiveRollups` are opposites.** `None` means "don't
# check liveness" and keeps the old name-only matching; an EMPTY set declines
# every rollup (`719228c`). Oxy passes a real set everywhere the semantic
# layer is in scope; the analytics solver passes `None` deliberately, because
# its `engine` is the vendor abstraction and carries no airlayer view list.
#
# * **One upstream fragility found landing this, worth knowing before the next
# bump.** `aa4bf15` wraps a rollup's stored time bucket in
# `NULLIF(col, '')`. That is only valid over VARCHAR: DuckDB resolves NULLIF
# to the column's own type, so on a DATE/TIMESTAMP bucket it casts `''` to
# TIMESTAMP and throws on the first row. `LocalRollupEntry` does not record
# the stored type, so the type-agnostic form is the fix
# (`CAST(NULLIF(CAST(col AS VARCHAR), '') AS TIMESTAMP)`). **Production is
# not affected** — `write_result_to_parquet` builds Parquet from `CellValue`
# rows whose only non-numeric variant is `Text`, so every bucket reaches disk
# as a string, and airlayer's own writer agrees. It surfaced only because
# `preagg_equivalence_tests`' fixture copied the CTAS table straight to
# Parquet and kept its temporal type — a divergence from the writer it claims
# to imitate, now fixed there rather than worked around.
#
# * **Read-side hit rate moves in both directions.** Relative date ranges
# ("last 30 days") were declining EVERY rollup and now resolve (`ac88ebc`) —
# the commonest shape, so expect a large net increase. Against that, bounds
# must now land on a bucket boundary or the rollup is declined rather than
# silently widened (`3f5455c`, `99429e5`, `d98af15`); week-grain rollups
# serve no bounded query at all, since Monday-vs-Sunday start is not recorded
# in the manifest (`9717eee`, flagged upstream as a known consequence); and
# `segments`, `motif`, `ungrouped` and `timezone`+time_dimension are refused
# outright (`7c6815e`) where segments previously returned UNFILTERED totals.
# Bounds are now half-open, fixing a lexicographic VARCHAR compare that
# dropped the last bucket of every range (89 of 90 days). Limit-less queries
# now get `effective_limit` on the cache path; one previously returned the
# whole rollup while reporting `LIMIT 10000`.
#
# NOW AT b141d8d — airlayer **tag v0.4.0**, 19 commits on and the first pin that
# names a release rather than a main-branch commit. `34802d4` is an ancestor, so
# this is a clean fast-forward with nothing reverted. Nothing here touches the
# semantic model, the query planner or pre-aggregation — `PREAGG_BUILDER_GENERATION`
# in crates/app/src/server/preagg_generation.rs stays put, and no rollup hash moves.
# Two things to know:
#
# * **BigQuery grows service-account auth (#106), and it is additive.**
# `BigQueryConnection` gains `key_file` / `key_file_var` / `key_json` /
# `key_json_var` plus a private `#[serde(skip)] token_cache`, so airlayer
# mints and refreshes its own tokens instead of requiring a pre-minted
# `access_token` that dies in ~an hour. `get_access_token` still prefers an
# explicitly configured token, so an existing `access_token_var` connection
# behaves exactly as before. The new private field would break a struct
# literal, but oxy constructs no `BigQueryConnection` anywhere (it reaches
# BigQuery through its own connector), so the break cannot reach us. Worth
# knowing for the `access_token_var` note in product-context.md: the
# "read-only token keeps a pipeline out of the rotator role" reasoning is
# about QuickBooks, not BigQuery, and is unaffected.
#
# * **Everything else is dependabot and CI.** tokio-postgres 0.7.16→0.7.18,
# postgres-protocol 0.6.10→0.6.12, quinn-proto 0.11.14→0.11.16, postcss in
# `sdk/`; plus per-warehouse gating of the cloud test jobs. No airlayer
# source outside `src/executor/bigquery.rs`, `src/executor/mod.rs` and the
# two CLI prompt/template files changed.
#
# Before bumping this line again, re-check with
# `git log <new-rev>..origin/main --oneline` and prefer the next TAG over a bare
# main commit — the entries above are a history of what each bump ADDED, and
# nothing in them would have caught a subtraction. That is how `afe6daf` came to
# be needed, and how an earlier bump came to have two of main's pre-aggregation
# fixes to carry.
# NOW AT f0bacc8 — airlayer main, 60 commits on from the v0.4.0 tag. Not a tag
# this time: the standing advice above is to prefer the next TAG, and there
# isn't one — v0.4.0 is still the newest, and f0bacc8 is `origin/main` HEAD
# exactly. `b141d8d` is an ancestor, so nothing is reverted.
#
# Carried FOR: peer cohorts (airlayer PR #116) — `cohorts:` on an entity in a
# `.view.yml`, resolved by `engine::cohort::resolve_cohort` into per-subject
# baselines and gaps. Surfaced by `POST /projects/{id}/semantic/cohort`.
#
# * **`Measure` gains two fields**, which is the only MECHANICAL break: the
# `__oxy_row_count` literal in crates/infrastructure/semantic/src/lib.rs
# needed `direction` and `default_cohort`. `direction` is
# `skip_serializing_if` on its `HigherIsBetter` default, so passing
# `Default::default()` keeps that injected measure serializing
# byte-identically. **No rollup hash moves** — airlayer's own
# `definition_fingerprint_ignores_measure_direction` and
# `..._ignores_default_cohort` tests pin that, so
# `PREAGG_BUILDER_GENERATION` in crates/app/src/server/preagg_generation.rs
# stays at 1 and nothing rebuilds.
#
# * **`opportunity` and `opportunity_drill` changed shape, and this one is
# BEHAVIOURAL — read before trusting an opportunity number across this
# bump.** Both gained `statistic: BenchmarkStatistic` and
# `min_support: usize`; drill also moved `executor` last, after `config`.
# Upstream DELETED the adaptive `pick_benchmark` (best_peer for
# few-segment dimensions, p75 once percentile estimation was meaningful)
# and made the statistic an explicit argument, so **no single value
# reproduces the old behaviour**. oxy now passes the constants
# `OPPORTUNITY_STATISTIC` (= `P75`) and `OPPORTUNITY_MIN_SUPPORT` (= `1`)
# from crates/app/src/server/api/metric_tree.rs — P75 as the closest match,
# and 1 because the pre-bump code had no support floor at all and
# upstream's new default of 2 would introduce refusals nobody asked for.
# Small-cardinality dimensions that used to benchmark against their best
# peer will now report different upside. This is a forced choice carried by
# the bump, not a product decision — it wants one.
#
# * **Other opportunity behaviour that moved underneath us**, all upstream:
# `opportunity` now honours `MeasureDirection`, so for a `lower_is_better`
# measure the segments sized as having upside are the opposite ones;
# additive composites with filtered leaves now refuse to size rather than
# returning a wrong number; a new `min_support` floor adds `"empty"` as a
# fourth `benchmark_basis` value. Note web-app's
# WorldModelSegmentGroups.tsx reads `benchmark_basis` as a binary
# `=== "p75" ? … : "best peer"`, so an `"empty"` basis would render as
# "best peer" — pre-existing shape, newly reachable.
#
# * Nothing in the range touches timezone/DATE handling or pre-aggregation
# selection. Checked, because both have bitten this pin before.
#
# rev is a bare main commit; there is no tag on it (tags can be moved; the rev
# cannot).
airlayer = { git = "https://github.com/oxy-hq/airlayer", rev = "f0bacc8c3d0e3d23e6368bb70aa3a70a92116135" }
# Anomaly detection: ETS forecasting + MSTL decomposition. augurs-ets gives
# Holt-Winters family (with auto model selection); augurs-mstl handles
# multi-seasonality (daily + weekly + yearly). Pulled in by oxy-metric-monitoring.
augurs-core = "0.10.2"
augurs-ets = "0.10.2"
augurs-mstl = "0.10.2"
anyhow = "1.0.104"
apalis = "0.7.4"
apalis-core = "0.7.4"
apalis-sql = "0.7.4"
argon2 = "0.5"
# Held at 58 — NOT a stale pin. duckdb (`=1.10501.0` below) declares `arrow ^58`,
# connectorx 0.4.5 declares `^54`, and df-interchange's newest feature is
# `arrow_58`. arrow 59 exists but nothing in that trio can reach it, and arrow
# types cross every one of those boundaries. Re-check when duckdb moves.
arrow = { version = "=58.3.0", default-features = false }
assert_cmd = "2.2.2"
async-std = "1.13.2"
async-stream = "0.3.6"
async-trait = "0.1.92"
aws-config = "1"
aws-credential-types = "1"
aws-sdk-s3 = "1"
aws-sdk-sesv2 = "1"
aws-smithy-http-client = "1"
aws-sigv4 = "1"
backoff = "0.4.0"
base64 = "0.23"
bcrypt = "0.19.3"
# Held at 0.20 — bollard 0.21 exists, but testcontainers 0.27 (see below) needs
# `^0.20`, and each bollard `=`-pins its own bollard-stubs prerelease, so the two
# cannot coexist in one graph. Moves when testcontainers does.
bollard = "0.20.2"
brotli = "8"
chrono-english = "0.1.8"
clap = "4.6.6"
clickhouse = "0.15.1"
colored = "3.1.1"
connectorx = "0.4.5"
constant_time_eq = "0.5.0"
csv = "1.4.0"
dashmap = "6"
deser-incomplete = "0.1.2"
df-interchange = "0.3.3"
dirs = "6.0.0"
dotenv = "0.15.0"
email_address = "0.2.9"
enum_dispatch = "0.3.13"
fehler = "1.0.0"
flate2 = "1"
futures = "0.3.33"
futures-core = "0.3"
fxhash = "0.2.1"
garde = "0.23.0"
# Held at 0.25 — 0.28 exists, but connectorx 0.4.5 declares `^0.25` and
# `connector/connectorx/bigquery.rs` implements connectorx's BigQuery source
# against it. Two versions in the graph do not meet at connectorx's
# `From<BQError>`, so this must track connectorx, not crates.io.
gcp-bigquery-client = "0.25.1"
glob = "0.3.4"
governor = "0.10"
handlebars = "6"
headless_chrome = "1.0"
hex = "0.4.3"
hmac = "0.13.0"
home = "0.5"
http = "1.5"
human-panic = "2.0.8"
humantime = "2.4.0"
include_dir = "0.7"
indicatif = "0.18"
indoc = "2.0.7"
itertools = "0.15.0"
jsonwebtoken = "11"
lazy_static = "1.5.0"
# MIME assembly only — no transport. Oxy hands the composed message to SES v2
# as `Content.Raw`, so every SMTP/TLS/pool feature is dead weight here.
lettre = { version = "0.11.23", default-features = false, features = ["builder"] }
libc = "0.2"
linfa = "0.8.1"
linfa-reduction = "0.8.1"
lru = "0.18"
# Held at 0.16 — 0.17 exists, but linfa 0.8.1 (its newest release) declares `^0.16`.
ndarray = "0.16.1"
once_cell = "1.21.4"
parking_lot = "0.12"
# Must match `arrow` exactly; see the note there for why 58 is the ceiling.
parquet = "=58.3.0"
predicates = "3.1.4"
pulldown-cmark = "0.13"
rand = "0.10"
rayon = "1"
# 0.13 renamed `rustls-tls` to `rustls`, and split `form` and `query` into their
# own features — 0.12 had neither, so `.form()`/`.query()` were always available.
# Every crate calling them now declares the feature itself; they were compiling
# only because something else in the graph unified it on, which a future graph
# change would have silently broken.
#
# On TLS, be precise about what this line does and does not decide. 0.13 made
# `default-tls` mean *rustls* (0.12's meant native-tls), and `rustls` pulls
# rustls-platform-verifier — the OS trust store — where 0.12's `rustls-tls`
# bundled webpki-roots. But native-tls is ALSO in the graph, and not from here:
# `self_update`'s default features pull `reqwest/native-tls`, and `sentry`'s
# `transport` pulls `reqwest/native-tls-no-alpn`. Both connectors are therefore
# compiled, and this line alone does not determine which one a client picks —
# nothing in `crates/` calls `.use_rustls_tls()`. That predates this bump (0.12's
# own default-tls was native-tls and we never set `default-features = false`), so
# it is not a regression, but it does mean the OpenSSL dependency the
# tokio-postgres-rustls note below hopes to avoid is present regardless.
# This bump does NOT take 0.12 out of the graph. 0.12.x stays, pulled by
# airway, gcp-bigquery-client, object_store, reqwest-middleware, reqwest-retry
# and snowflake-api — so the build compiles two reqwest majors, and with them
# both TLS stacks. Not a defect (the crates above are on their own release
# clocks, and gcp-bigquery-client is pinned for a separate reason noted in the
# held-back list), but "we are on reqwest 0.13" is only true of the crates in
# this workspace, not of the dependency graph.
reqwest = { version = "0.13", features = ["rustls", "multipart"] }
# V8-isolate runtime for Oxy Functions (custom-app `functions/*.ts`).
# See internal-docs/customer-apps-functions.md.
deno_core = "0.410"
# deno_core's `op2` macro requires fallible ops to return an error type
# implementing `deno_error::JsErrorClass` (plain `anyhow::Error` does not
# qualify); `JsErrorBox` is that type. Kept on the line deno_core pins — 0.7.x
# for deno_core 0.410.
deno_error = "0.7"
rkyv = "0.8.18"
# Held at 0.10 — DELIBERATE, not lag. rmcp 1.0 deleted the SSE *server*
# transport (`transport-sse-server`), which is what `oxy mcp-sse` serves; 3.x
# only offers Streamable HTTP. Moving majors therefore retires an endpoint MCP
# clients are configured against — a product decision, not a dependency bump.
rmcp = "0.10.0"
# 0.39, not 0.40: rusqlite 0.40 wants libsqlite3-sys 0.38, sqlx-sqlite 0.9 caps
# at `<0.38`, and libsqlite3-sys carries `links = "sqlite3"` so only one version
# may exist in the graph at all.
rusqlite = "0.39"
rustc_version_runtime = "0.3.0"
rustls = "0.23.43"
# Held at 0.8 — DELIBERATE. schemars 1.0 replaced the whole `schemars::schema`
# tree (SchemaObject/InstanceType/SingleOrVec) with a serde_json::Value wrapper.
# That is ~46 compile errors across 16 files, but the cost is not the errors: the
# hand-written `JsonSchema` impls in config/model.rs exist to emit a specific
# OpenAI-compatible shape, `schema_type_converter.rs` walks SchemaObject
# directly, `ClickHouse.filters` carries SchemaObject as a *config* field, and
# `json-schemas/*.json` (published, and fetched by the IDE's YAML validation)
# would be regenerated onto draft 2020-12. Worth its own PR with its own review.
schemars = "0.8.22"
scopeguard = "1.2.0"
secrecy = "0.10.3"
self_update = "0.44.0"
semver = "1.0"
serde_arrow = "0.15.0"
serde_jcs = "0.2.0"
serde_urlencoded = "0.7"
serde_with = "3.22.0"
serial_test = "4.0.1"
sha1 = "0.11.0"
sha2 = "0.11.0"
short-uuid = "0.2.1"
slugify = "0.1.0"
snowflake-api = { git = "https://github.com/oxy-hq/snowflake-rs.git", tag = "v0.13.2-browser-auth" }
sqlformat = { git = "https://github.com/shssoichiro/sqlformat-rs.git", rev = "80255c7" }
# Must stay on the same major as the sqlx that sea-orm re-exports: the RDS IAM
# path hands a hand-built `sqlx::PgPool` to
# `SqlxPostgresConnector::from_sqlx_postgres_pool`, so two sqlx versions in the
# graph would be a type mismatch, not just duplicate code.
sqlx = "0.9"
statrs = "0.19"
strip-ansi-escapes = "=0.2.1"
strsim = "0.11"
syntect = "5.3"
tabled = "0.21.0"
terminal-light = "1.9.0"
terminal_size = "0.4.4"
# Integration-test container harness — used as a dev-dependency by 6 crates.
# Version centralized here; each crate opts into the features (postgres, mysql,
# clickhouse, …) it actually spins up.
# Held at 0.27 — 0.28 exists, but testcontainers-modules 0.15 (its newest
# release) declares `^0.27`. This pin is also what holds bollard at 0.20.
testcontainers = { version = "0.27" }
testcontainers-modules = { version = "0.15" }
tokio-postgres = "0.7"
# tokio-postgres TLS connector backed by rustls. Matches the workspace's
# existing rustls-based Postgres TLS stack (sqlx uses runtime-tokio + tls-rustls;
# sqlx 0.9 split the combined runtime-tokio-rustls feature in two), avoiding a
# parallel OpenSSL dependency.
tokio-postgres-rustls = "0.14"
tokio-stream = "0.1.19"
# Mozilla root certificate bundle, for the task-router's LISTEN connection.
# Whether an RDS cert verifies against it depends on the CA generation
# (`rds-ca-rsa2048-g1` chains to Amazon Root CA 1, which is here; `rds-ca-2019`
# does not) — so don't read this line as "RDS always verifies". The Postgres
# connector sidesteps the question entirely: see `NoCertVerification` in
# `crates/agentic/connector/src/postgres.rs`.
#
# Note there are two webpki-roots in the graph: this 1.x, and 0.26 held by
# `tokio-tungstenite`'s `rustls-tls-webpki-roots` feature below. Harmless — they
# are separate bundles used by separate stacks, not a duplicate of one another —
# but "the Mozilla bundle" is no longer unambiguous when reading this line.
webpki-roots = "1.0"
tar = "0.4"
tokio-tungstenite = { version = "0.30", features = ["rustls-tls-webpki-roots"] }
tokio-util = { version = "0.7.19", features = ["rt"] }
tower = "0.5.3"
# Bumping this to 0.7 does NOT remove 0.6 from the build: reqwest depends on
# tower-http 0.6 internally, so both versions compile until reqwest moves. The
# duplicate costs compile time and binary size but nothing else — reqwest's use
# is entirely internal, so the two never meet at a type boundary.
tower-http = "0.7.0"
tower-serve-static = "0.1.2"
tqdm = "0.8.0"
tracing-appender = "0.2.5"
tracing-log = "0.2.0"
tracing-subscriber = "0.3.23"
# Platform telemetry (crates/telemetry): OTLP export to the cluster OTel
# collector → ClickStack / HyperDX. The four opentelemetry-* crates move in
# lockstep (0.32) and tracing-opentelemetry is one minor ahead of them (0.33
# pairs with 0.32) — bump all five together. `http-proto` +
# `reqwest-blocking-client` is the exporter transport on purpose: the SDK's
# batch processors run on their own thread, and the blocking reqwest client is
# the one transport that needs no async runtime there (the gRPC/tonic one
# would also drag a second tonic feature-set into the graph).
opentelemetry = "0.32"
opentelemetry_sdk = "0.32"
opentelemetry-otlp = { version = "0.32", default-features = false, features = [
"http-proto",
"reqwest-blocking-client",
"trace",
"logs",
"internal-logs",
] }
opentelemetry-appender-tracing = "0.32"
opentelemetry-http = "0.32"
tracing-opentelemetry = { version = "0.33", default-features = false, features = [
"tracing-log",
] }
url = "2.5.8"
urlencoding = "2.1.3"
utoipa = { git = "https://github.com/haitrr/utoipa.git", rev = "776f86e" }
utoipa-axum = { git = "https://github.com/haitrr/utoipa.git", rev = "776f86e" }
utoipa-swagger-ui = { git = "https://github.com/haitrr/utoipa.git", rev = "776f86e" }
wildcard = "0.3.0"
wiremock = "0.6"
xxhash-rust = "0.8"
# Airform — dbt-compatible SQL transformation engine
airform-core = { git = "https://github.com/oxy-hq/airform.git", rev = "3e3e8288692381a989444eef874900e6db4475b1" }
# `postgres` feature is required for the real Airhouse / Postgres
# destinations (without it airway compiles a stub that errors at run
# time) — note that shape: airway's features gate *runtime stubs*, not
# compilation, so dropping one it needs would pass `cargo check` and fail
# on a live run. Airway's full list is `default = ["duckdb", "cli"]`
# (verified at rev 8cd1213), so naming `duckdb` + `postgres` below is
# exactly the old set minus `cli` — nothing else is silently dropped.
# `duckdb` is named explicitly rather than inherited from
# `default-features`, because airway's defaults also carry `cli`, which
# exists to gate `tracing-subscriber` and the subscriber its binary
# installs. A library host installs its own; inheriting it compiled that
# plus its transitive crates only to throw them away. Airway keeps `cli`
# default-on deliberately — dropping it there would make `cargo build`
# stop emitting the `airway` binary with no error, since cargo skips a
# target whose `required-features` are unmet silently — so the opt-out
# belongs on this side. `duckdb` is still required: the `http_file`
# source arm builds on it.
# Pinned to the 0.1.24 tag (`dc84d7be`). Four changes oxy consumes:
#
# * #105 `EndpointConfig::contract` — a per-endpoint
# `Option<SourceContract>` so the ~24 REST-backed connectors can
# declare one. **Singular, and a field on the endpoint** — not a
# name-keyed map on `RestApiConfig`. Upstream chose the inline field
# so a contract rides its endpoint through the `--resources` filter;
# a side map would not be filtered with it, and every unselected
# resource would then read as an orphan. The consequence oxy depends
# on is that an orphan is *unrepresentable* for `rest_api` (the key
# is always the endpoint's own name), which is why
# `preview::orphan_verdicts` can still set `not_fixable_here`
# unconditionally. An undeclared `rest_api` endpoint is now a fixable
# declaration in the pipeline's own `.airway.yml`, which is why the
# admin policy preview no longer flags the kind `not_fixable_here`.
# * #107 a run where EVERY resource failed returns `Err` instead of
# `Ok` + `completed_with_errors`. Oxy maps that to a failed run, which
# is what re-surfaces the "Reconnect QuickBooks" affordance on an
# `invalid_grant`.
# * #108 QuickBooks cascades the document `Id` into nested child tables
# (`invoice_id`, `bill_id`, `purchase_order_id`,
# `inventory_adjustment_id`, `item_id`). **Additive columns only** —
# nothing renamed. A consumer joining a child to its parent still
# needs `_aw_parent_id = parent._aw_id` as well; the propagated key
# alone over-counts across re-ingests, since children load
# append-only.
# * #111 `cursor_lag_floor_secs` on `GlobalConfig`, wired through the
# `airway_deployment_config` tier.
#
# Supersedes 0.1.23 (version-guarded `Merge`/`Replacing` writes,
# per-resource `SourceContract`s, the `ContractPolicy` + `Environment`
# admission checks), 0.1.22, 0.1.21 and 0.1.20 — all still carried.
#
# `tag`, now that upstream pushes them: 0.1.23 shipped only as a
# `release/0.1.23` branch, so that one had to be a `rev` to stay
# immutable. A tag is immutable enough for this and reads better in a
# diff; a *branch* pin would not be, and must never be used here.
#
# 0.1.30 adds `SourceConnector::column_hints`, which is why the ubereats
# source needed this bump: without it a connector cannot declare a column's
# type, and an all-null column does not materialize at all — leaving the
# landed table's shape dependent on which file loaded first.
#
# It also carries the **netsuite** connector, which `source_factory` now has
# an arm for. Two changes arrived at this pin from different directions and
# both are consumed: #2937 for `column_hints`, this one for netsuite.
#
# Note the tag is NOT the `release/0.1.30` branch tip this was briefly
# pinned to (`608c9e5`): the release merged as #127 and picked up the
# netsuite work along the way, so `0.1.30` is a superset, +1514 lines.
# Everything ubereats consumes is byte-identical between the two —
# `ubereats.rs`, `connector/mod.rs`, `source/mod.rs`, `schema/inference.rs`
# and `filesystem.rs` are unchanged, and the `SourceConnector` method list
# is identical.
#
# ---
#
# 0.1.31 (`4423cd3f`) carried two bugs this feature hit head-on:
#
# * airway#133 — the file sources built their object store with
# `object_store::parse_url`, which reads NO environment: endpoint, static
# keys and region were all dropped, and the client went to IMDS instead.
# Not local-only — the region defaulting to `us-east-1` fails a us-west-2
# bucket's signature, so the extract could not have worked in dev either.
# * airway#134 — the Postgres destination filled a `$1`..`$N` template by
# ascending string replacement, and `$1` is a prefix of `$10`, so every
# table of ten or more columns was corrupted. Ours has 54.
#
# 0.1.32 adds one netsuite fix (#136) on top. It is not confined to netsuite
# though — it also rewrites `schema/inference.rs`, which ubereats goes
# through: a declared `primary_key` / `merge_key` set is now AUTHORITATIVE,
# clearing a key that is no longer declared, where before it could only add
# one. That only changes behaviour when the declared set NARROWS between
# batches, which is the netsuite `inventory_balance` case. Ubereats declares
# a constant `["_row_uid"]` on every call, so there is nothing to clear and
# the change is inert here — checked rather than assumed, and re-verified end
# to end on this pin.
#
# 0.1.33 is the first bump in a while with NO blast radius outside its own
# source. `git diff --name-only 0.1.32 0.1.33` is `netsuite.rs`, `CHANGELOG.md`,
# `Cargo.toml`, `Cargo.lock` — no shared module, so nothing to reason about for
# ubereats, toast or quickbooks. It carries two netsuite changes:
#
# * airway#138 — `items` stops being cursored and becomes a full snapshot.
# A cursor on `lastmodifieddate` only fetches rows that CHANGED inside the
# window, and items go quiet once they settle, so 43 of BMG's 671 listed
# items had never landed at all. ~5,300 rows, six page-reads. No backfill
# or schema reset is needed: `Replace` re-reads the whole table, so the
# first run on this pin closes the gap by itself.
# * airway#139 — declares `partition_keys` / `sort_keys` for the first time
# on this connector: `transaction_lines` partitioned by `location` (11.16M
# rows over 17 locations, and every FBA query filters on it), sorted by
# `item`; `transactions` sorted by `trandate`.
#