Repository navigation
docs: add ADR 0001 for the FSharp.Azure.Cosmos.Quotations architecture - #34
Open
xperiandri wants to merge 29 commits into
Open
xperiandri wants to merge 29 commits into
xperiandri wants to merge 29 commits into
Conversation
Contributor
There was a problem hiding this comment.
Copilot wasn't able to review any files in this pull request. Check if the Files changed in this pull request are included in default exclusions.
This was referenced Oct 2, 2026
Merged
xperiandri
force-pushed
the
docs/quotations-architecture-adr
branch
from
October 3, 2026 01:38
e1f35eb to
8d9ffe6
Compare
This was referenced Oct 5, 2026
xperiandri
force-pushed
the
docs/quotations-architecture-adr
branch
from
October 10, 2026 00:06
ff19ea3 to
9767e66
Compare
This was referenced Oct 10, 2026
xperiandri
force-pushed
the
docs/quotations-architecture-adr
branch
2 times, most recently
from
October 10, 2026 08:26
7557194 to
6820cc6
Compare
4 of 6 tasks
xperiandri
force-pushed
the
docs/quotations-architecture-adr
branch
5 times, most recently
from
October 11, 2026 09:03
7b47902 to
3182e04
Compare
This was referenced Oct 11, 2026
Records the agreed architecture of the FSharp.Azure.Cosmos.Quotations package family as an Architecture Decision Record under a new documentation category, followed by the full design document. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a Delivery row to the decision table: Phase 0 lands as one pull request per step from main, and this record lives in docsSrc/ADRs and is updated in the same pull request as the decision it records. Strike the "branch in flight" risk: the maintainer decided on 2026-10-02 that origin/copilot/support-throughput-bucket-operation-again is not needed, so Phase 0 starts from main and the extraction of tests/Cosmos.Tests.Infrastructure has nothing to wait for. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… 0001 The record gated IcedTasks on a Phase 0 Debug-build probe and left the partition cost of a container as an open probe. Both have now run, and the record has to say what they found so that later steps do not rely on the old assumptions. The IcedTasks probe fails in Debug: with SDK 10.0.401 a Debug build compiles no resumable state machine, and in the dynamic path cancellableTask, cancellableValueTask and coldTask share one state machine between all starts of a value. valueTask and valueTaskUnit pass everywhere, and Release passes everything. Library code therefore keeps task, and the two follow-up questions (scoping the gate per builder, reporting the defect upstream) are added as open items with defaults. The IcedTasks package facts are also corrected from the 0.11.9 nuspec: net9.0 and both netstandard targets, FSharp.Core 6.0.4 (9.0.300 for the net9.0 build), and Microsoft.Bcl.AsyncInterfaces for netstandard2.0. On the Windows emulator a container costs one partition and a database none, an emulator can hold fewer containers than the default budget, and the budget and the leftover sweep are per process, with the sweep limited to old fsac-test- databases so concurrent runs stay safe. Whether the default budget should drop below 25 is added as an open item. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…k misuse Microsoft's ValueTask rules restrict one instance; the probes start the cancellable function value twice and await each returned instance once, and the failure reproduces with the Task-returning cancellableTask and coldTask. IcedTasks itself documents these builders as multi-start. Also record the answers on reporting it upstream and on the emulator partition budget. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…code The maintainer scoped the IcedTasks gate per builder: valueTask and valueTaskUnit pass every probe in Debug and Release and replace the feed iterator's hand-rolled ValueTask wrapper first, while the cold and cancellable builders stay out of library code until their probes pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FSharp.Collections.ParallelSeq wraps Parallel LINQ in an F# API. It fits bulk, CPU-bound work over many independent items (the precompilation tool, plan-cache warm-up, test tooling), not the per-query hot path, and each use has to be justified by a benchmark first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#39 moved the emulator fixture into tests/Cosmos.Tests.Infrastructure and added the partition budget, the database identifier and the leftover sweep, so the ADR's paths into tests/Cosmos.Tests and its list of what the repository still lacked were out of date. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Our TheAngryByrd/IcedTasks#67 fixed the multi-start defect the Phase 0 probes found and shipped in IcedTasks 0.11.10-beta001. With it every probe passes in Debug and Release, so the gate now covers valueTask, valueTaskUnit, cancellableTask and cancellableValueTask, and the risk is resolved except for the prerelease dependency, recorded as an open point: a stable release of this library waits for IcedTasks 0.11.10. The execution section no longer says that Phase 0 did not reach the gate: it names the builders the gate allows and records that #41 converted both hand-rolled wrappers. The Phase 0 result describes the 0.11.9 outcome in the past tense and counts the 30 probes over the four builders they cover; there are no coldTask probes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#44 asks the Windows and the vNext emulator how they evaluate the constructs the translator relies on. The record now states their answers where it waited for them: the comparison table behind guard elimination, ?? keeping null, INTDIV and INTMOD truncating toward zero, a returned infinity failing the query, a parameter as case flag, predicate and indexer, STRINGEQUALS over non-strings, case folding per emulator, object equality without property order, and ORDER BY over missing values. A Phase 0 result paragraph lists what is settled and what is still open. Two rules change. The re-nesting table is written from the results: TOP, ORDER BY and OFFSET LIMIT are rejected in every subquery, while a filter outside a GROUP BY subquery stands in for HAVING, so a where over the projected groups is no longer rejected. The ObjectToArray lowering for map members used kv IN ObjectToArray(...), which is a syntax error, and now iterates a subquery instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On the Windows emulator the spatial functions accept GeoJSON parameters, the form planned for captured geometry values, and ST_DISTANCE measures in metres on the WGS-84 ellipsoid. The vNext emulator has no spatial functions: one in the projection fails with 500, and a spatial filter gets an answer the SDK cannot read, so spatial tests need the Windows lane. With that, no query-semantics probe of Phase 0 is left open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The maintainer decided on 2026-10-10 that neither FSharp.Azure.Cosmos nor the Quotations package declares Newtonsoft.Json: the build check of the SDK already asks every consumer to reference it and leaves the version to the consumer. The package table and the package list lose the dependency, and open question 16 is answered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Hedgehog MSTest adapter of #43 is proposed upstream as hedgehogqa/fsharp-hedgehog#487. The record notes how the proposal differs from the in-repository copy (net8.0 and F# 8, upstream's documentation in the shared files, VSTest), that MSTest 4.2 is the adapter's lowest version, and that one test of #43 stops compiling on MSTest 4.5.1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The maintainer's reviews of 2026-10-10 changed several things the record describes, and it now says so where each belongs. The Hedgehog MSTest adapter (#43): the in-repository copy follows the coding guidelines of this repository, while the upstream pull request keeps upstream's style in a copy of its own, so a change of behaviour goes into both; an invocation that MSTest could not run stops the run instead of being shrunk; the cancellation token is checked after every invocation, whatever its outcome, which the automatic review of #50 asked for; Odd and Even stay inside their range; DateOnly and TimeOnly get generators, upstream through hedgehogqa/fsharp-hedgehog#488. The open question of the adapter's code style is answered, formatting and nullness checking included: the two projects are formatted with Fantomas and build with nullness checking on, and only the verbatim port keeps upstream's settings. Collections (section 4): the shared collection functions of #47 that return voption and struct tuples, tested with properties in #50, and the rule of one module per pipeline of #49. Build (section 5): why FAKE's DotNet.test cannot run the test applications, and fsprojects/FAKE#2903, which adds DotNet.testMTP. The spec model (sections 6 and 16): EquatableArray and the ImmutableArray patterns are #48 and the model itself #42; the validator returns an ImmutableArray of struct errors. Golden baselines (section 13): a literal type provider was evaluated on the question asked in #46 and rejected, with the reasons. Tests (section 22): test category attributes sorted by name, in #51 with an instruction scoped to those files, Pascal-case assertion functions and ImmutableArray for shared test data. Two more answers of the same day: the Windows emulator lane of #45 runs weekly instead of nightly (section 2), and the shared collection functions of #47 are in the namespace FSharp.Azure.Cosmos instead of the global one (section 4). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Section 4 said that an F# record or union holding an ImmutableArray loses structural equality. It does not: the equality the compiler generates goes through IStructuralEquatable, which the array implements, and compares the elements. What is true is narrower. The array's own Equals, which EqualityComparer.Default calls, compares references, so a struct tuple, a HashSet with the default comparer and a C# caller see two equal arrays as different. The structural path boxes every element, hashes only the last eight, throws on compare for different lengths, and a default array never equals an empty one. The section now gives these as the reasons for EquatableArray, as the documentation comment of the type in #48 already does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri
force-pushed
the
docs/quotations-architecture-adr
branch
from
October 11, 2026 11:03
3182e04 to
176dffd
Compare
2 of 6 tasks
The benchmarks of #56 give the cost model of section 11 its first numbers. A quotation literal costs 24 to 75 µs per evaluation inside FSharp.Core, which unpickles the tree every time; the shape key, the plan lookup and the QueryDefinition together cost about 1 µs; a value costs a fraction of a microsecond by reflection and hundreds of times more compiled; a request to the emulator takes 1.4 to 2.1 ms. The record states what follows for the design: the plan cache does not remove the cost of an inline quotation, compiled queries do, and the precompilation tool of Phase 5 stays undecided until the translation itself can be measured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 of 6 tasks
#57 makes the SDK's parser the judge of the printer. The record states how it departs from the plan and why: the SDK's generated sources are copied instead of generated again, so no Java is needed; the conversion is F# and sits in a project that never references the SDK, because the SDK assembly has internal types of the same names as the generated parser; and the runtime package stays below 4.10. It also keeps what the parser taught: a sign directly before a digit belongs to the number, and three distinctions of a tree are not in its text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 of 6 tasks
#58 is the first stage of the translator: it brings a quotation into its normal form. The record states where the implementation departs from the design and why: one walk instead of rounds, with recursion found exactly and two limits; substitution with care for a variable that is bound at two nested places, which Expr.Substitute gets wrong; the mutable copy of a struct that the compiler binds; what stays although it has a reflected definition; and the passes that wait for the registry. It also records what the stage costs and where the time goes in FSharp.Core, with the proposal that follows for the plan cache, and how the tests are built. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 of 6 tasks
#59 computes the parts of a quotation that read no variable. The record states what the step found: 37 operators of FSharp.Core that reflection cannot call and the functions that stand in for them, the checked operators whose witnesses are unchecked, how enumeration values are computed, why the precomputed forms of the FSharp.Reflection functions are used, and how an exception of the caller's code is reported. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 of 6 tasks
xperiandri
force-pushed
the
docs/quotations-architecture-adr
branch
4 times, most recently
from
October 11, 2026 18:18
8965bd9 to
d98e2ad
Compare
3 of 6 tasks
The result of the third step of phase 1 (#60): which values of a quotation are parameters, what its shape key holds, and where the implementation departs from section 11. A literal is a part of the key, a value that the quotation builds is not computed, a branch that is not taken is not computed, and the key is the key of the normal form. Also what the review of #59 changed in the two steps before: the normalizer does not read the branch that a constant condition does not choose, and the evaluator computes the members of a nullable, raises NullReferenceException for a function that is null and computes a coercion as the cast it is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cost model says that a quotation literal is unpickled on every evaluation. That is reported to dotnet/fsharp as issue 20764 now, so the ADR names the issue where it states the cost. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri
force-pushed
the
docs/quotations-architecture-adr
branch
from
October 11, 2026 19:40
d98e2ad to
6ace353
Compare
The fourth step of phase 1 (#61) has the first two members of IJsonLayout and one implementation, JsonLayout.ofSerializerOptions. The record says where it departs from section 10: the serializer is asked for its contract instead of having its configuration read again, so one function describes System.Text.Json with and without FSharp.SystemTextJson; plain System.Text.Json options are described, not refused; and what the two serializers were found to write for a member without a value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What the reviews of #58 to #60 on the evening of 2026-10-11 changed: - the normalizer inlines an inline member of a type, whose definition the compiler keeps for the member itself and not for its form with witnesses, and applies a definition only to arguments it takes; - the evaluator computes a node once, for one part and for a caller that keeps what was computed; - the parameterizer tells a function by a class made of the type of functions too, and Query.constant takes only a value that a literal can be. The counts of tests and of planted defects follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ADR 0001 Hedgehog 2.0.4 cannot generate the two types, and hedgehogqa/fsharp-hedgehog#488 adds the generators upstream. Until a release has them, the adapter of this repository registers the same two for the parameters of a property (#43). The record of the adapter says so, and when the stand-in goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri
force-pushed
the
docs/quotations-architecture-adr
branch
from
October 11, 2026 21:06
6ace353 to
354ef05
Compare
The normalizer reports the outermost construct that a query cannot express also when the rewriting fails inside it, and an inner binding of a variable hides an assignment from the outer one (#58). The layout reads the obsolete setting that ignores null, which both serializers still follow (#61). The counts of tests and planted defects follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every call of a function with a reflected definition gets nodes of its own (#58). The type that a value is declared with decides whether it is a literal, so a nullable and an option are none (#60). In the layout an override has no condition of the declaration it overrides, a property of a record that is not public is not written, and a value whose converter writes null itself is not described (#61). The counts of tests and planted defects follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A receiver that is null is reported after the arguments of the call are computed (#59). A record whose converter an attribute of the application makes is not described, and a read-only member with a rule that code has set is not taken to be left out (#61). One node at two places stays one value and one slot: the record says why, and what would change it. Found while the next step was built, and fixed in #61: a read-only member is a collection, which the serializer writes all the same, by the converter that writes it, and that can be one the member names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Proposed Changes
Adds the agreed architecture of the planned
FSharp.Azure.Cosmos.Quotationspackage family as an Architecture Decision Record:docsSrc/ADRs/0001-Quotations-Architecture.md, published by fsdocs under a new "Architecture Decision Records" category.The record opens with the usual ADR header (status, context, decision, consequences), followed by the full design: translating native F# quotations into Cosmos DB NoSQL SQL over the public
QueryDefinitionpipeline, an own SQL AST with validator and printer, the null/undefined and optional-value semantics, the pluggable translator model, System.Text.Json + FSharp.SystemTextJson only, the test architecture and the phased roadmap. Implementation follows in one pull request per Phase 0 step; each later pull request updates this record together with the decision it changes.Types of changes
Documentation only.
Checklist
build.cmd BuildDocswith fsdocs--strictpasses)Further comments
Examples taken from a private application were replaced by neutral names and descriptions.
🤖 Generated with Claude Code