Release Notes
0.10.0 is published — five packages, and the fifth one's first release.
DatabentoDotNet.Extensions.Hosting reaches nuget.org for the first time, so its surface gets the
same unpromised window on the feed that 0.9.0 and 0.9.1 gave the core four. Nothing here carries
a SemVer promise yet; 1.0 is where that starts. This page holds the versioning policy, where
releases live, and the narrative for each one.
Last updated against master, 2026-09-02.
Where releases will live
These are the canonical sources, in this order:
- GitHub Releases — the release of record. Generated from a tag, listing the issues closed since the previous one.
- NuGet —
DatabentoDotNet.Dbn,.Live,.Historical,.Referenceand.Extensions.Hosting, versioned together. - This page — the narrative: what changed for you, what to do about it, and the upgrade notes that do not fit a changelog line.
This page is not the changelog. A mechanical list of changes belongs in the repository, in the
commit that makes the change, where a reviewer sees it move. What belongs here is the part a
changelog is bad at: why a breaking change was made, and what to do about it. See the
documentation policy in CLAUDE.md for the general rule.
Recommended, not yet done: the repository has no
CHANGELOG.md. Adding one in Keep a Changelog format, maintained in the pull request that makes each change, would give the mechanical list a home that reviewers see. That needs an issue first, per the repository's own workflow.
Versioning policy
Semantic versioning, with the usual pre-1.0 caveat: the minor version is where breaking changes go until 1.0.
0.x— now. The public API will change. Pin an exact version. Breaking changes are labelledbreaking-changeon their issue and tracked closely.0.9.0— the beta (#74),0.9.1(#85) the same code with corrected package metadata. Parity withdatabento-rsis met, so the code condition for 1.0 is satisfied; what is not yet known is whether the public surface is the right shape, because nothing has built against this library in anger. The beta is what buys that evidence — and it delivered, once: designing the hosting package against this library found thatHistoricalClienthad noHttpMessageHandlerseam (#86).0.10.0— the fifth package's turn (#102).DatabentoDotNet.Extensions.Hostingon the feed, unpromised, for the same reason and by the same mechanism.1.0is reserved for full parity withdatabento-rs— live, historical, and reference data — not for "it works". Parity turned out to be the cheaper half. The expensive half is the promise, which is why it waits on evidence rather than on the code.- After 1.0, ordinary semver: breaking changes wait for a major.
Why 0.10.0 and not 0.9.2. Everything in it is additive, so the rule at the top of this
section would permit a patch. It is still the wrong number: 0.9.1's own release commit fixed what
a patch means here — "diffing v0.9.0..HEAD across src/**/*.cs yields no non-comment line" —
and this release is a new package id and about 3,300 new source lines. A consumer reading 0.9.1 →
0.9.2 would get no signal that a fifth package now exists, and for many of them the version string
is the only thing they read.
Why 0.9.0 and not 0.9.0-beta. A -beta suffix makes it a prerelease, and NuGet hides
prereleases from an ordinary dotnet add package unless the caller opts in. That friction lands on
exactly the people whose feedback the release exists to collect. 0.x already tells both SemVer
tooling and human readers to expect change, so the suffix would buy a signal that is already there
and charge for it in adoption.
The public API baseline moves at 1.0, not before. PublicAPI.Shipped.txt should list a surface
we have undertaken not to break; PublicAPI.Unshipped.txt holds everything else. The dividing line
is not published — every 0.x here reaches nuget.org, all five of them as of 0.10.0 — but
promised. Freezing a surface in a file named Shipped while it is still being contested would
assert the opposite of what these releases are for. All five files are empty today.
The version is set in
Directory.Build.props
and all packages ship together at the same version. A consumer taking .Live and .Dbn at
different versions is not a configuration anyone tests.
Which packages ship is a separate list, in
.github/workflows/publish.yml.
Those two stopped being the same question the moment a fifth package existed: everything builds and
packs at the release version, and the workflow publishes what PACKAGES names, discards what HELD
names, and fails outright on a packed package named by neither. HELD is empty at 0.10.0.
0.10.0 — 2 September 2026
Five packages (#102). DatabentoDotNet.Extensions.Hosting reaches nuget.org for the first
time: IServiceCollection registration for the historical, reference and live clients,
IConfiguration binding, and a live session running as a BackgroundService with bounded
reconnection, an opt-in health check and metrics — allocating nothing per record, the same guarantee
the core four carry.
dotnet add package DatabentoDotNet.Extensions.Hosting --version 0.10.0
Published 2 September 2026, all five packages, tagged v0.10.0 and released by the
release: published trigger (run 33571981378). Verified against the artefacts pulled back off the
feed rather than a local pack — the standard #71 set — including that every push in the log
returned Created rather than a skipped duplicate, and that all five PDBs come back from
symbols.nuget.org.
It took two attempts, and the first one is worth reading if you maintain a release pipeline.
DatabentoDotNet.Live pushed cleanly and DatabentoDotNet.Extensions.Hosting came back 403 — does not have permission to access the specified package: nuget.org's Trusted Publishing policy scoped
the workflow's token to the four package IDs that already existed, and the fifth had never existed.
Nothing checkable on a developer machine can find that, because the first publish of a new package
ID is a question only the registry can answer. The policy was widened and the run repeated; the
retry skipped the already-published Live and completed the other four, which is the partial-failure
path --skip-duplicate and the pre-flight were built for and the first time it has actually run.
Start with the hosting guide. You supply the host —
this package references Microsoft.Extensions.Hosting.Abstractions and never the host itself, so it
plugs into your Program.cs rather than bringing one.
Upgrading from 0.9.1
Change the version. Nothing was removed, renamed or retyped in any of the four packages you already have, and the only addition to them is described below.
The one change to the core four
HistoricalClient gained Handler and DisposesHandler (#86) — a settable
HttpMessageHandler and a flag saying who disposes it. Four public members, additive.
It is here because a singleton HistoricalClient in a host that stays up for weeks keeps talking to
whatever hist.databento.com resolved to on its first request; reaching it through
IHttpClientFactory is what rotates that, and nothing before this provided a way in.
This is also the whole return on the beta. 0.9.0 existed to find out whether the public
surface was the right shape, and designing the hosting package against the library from a
consumer's position is what answered: one gap, filled by addition, nothing withdrawn. Two releases
of exposure produced exactly one change. That is a good result and it is the evidence 1.0 was
waiting for.
Why the fifth package is 0.x rather than 1.1.0
ROADMAP.md §8 had reserved
it for 1.1.0, on the ground that shipping five packages under one SemVer promise would guarantee
this one's surface before anything had built against it. That ground is correct and it is why the
number moved rather than survived.
0.x carries no promise — so the objection was never about a 0.x release, and none had been in
prospect when 1.1.0 was chosen. What the assumption cost was the evidence window itself: under the
1.1.0 plan, this package's window was the stretch when it was not installable, usable only from
a project reference. The core four were given something categorically better — on the feed,
unpromised, available to anyone who wanted them. Reserving a worse window for the fifth package,
in the name of giving it a window, was the argument contradicting itself.
So this release gives it the same mechanism. Pin the exact version, and if something in this package's API is awkward to call, an issue now is far cheaper than a major version later. That is what the window is for, and it is the only way it gets spent.
PublicAPI.Shipped.txt stays empty in all five packages, which is unchanged rather than overlooked.
That file records a surface undertaken not to break, and 0.10.0 undertakes nothing.
What the release pipeline learned
A third defect in publish.yml, of the same species as the two #71 found: a silent pass where a
failure was wanted. dotnet pack at the root packs every shipping project, so it had been
emitting five .nupkg since M6 — but the push was a nupkg/*.nupkg glob while the PACKAGES list
driving both the no-op pre-flight and the post-push verification still named four. The glob was
publishing more than the gates ever checked. It happened to be the right thing to publish this time.
Nobody had decided that.
PACKAGES now names five, and a partition step ahead of the push fails the run on a packed package
named by neither PACKAGES nor HELD. So a sixth package arrives as a decision somebody records,
rather than as one that silently ships or silently does not.
0.9.1 — 31 August 2026
A documentation patch, and nothing else (#85). The same code as 0.9.0 — you can upgrade
without reading further, and if you are not upgrading you are missing nothing that runs.
dotnet add package DatabentoDotNet.Live --version 0.9.1
Published 31 August 2026, all four packages, tagged v0.9.1 and released by the release: published
trigger (run 33416260992). Verified against the artefacts pulled back off the feed rather than a
local pack — the standard #71 set — including that every push in the log returned Created rather
than a skipped duplicate, which a green tick alone does not establish.
Nothing changed, and that is checkable
git diff v0.9.0..v0.9.1 -- 'src/**/*.cs' contains no non-comment line, and all four
PublicAPI.Unshipped.txt files are byte-identical to v0.9.0. The 3,801-member surface is exactly
where it was. 0.9.1 promises nothing 0.9.0 did not.
So why publish at all
Because two things reach you only by being inside a package, and a published package cannot be edited.
The package pages linked to a wiki that no longer exists. #82 moved the guides onto this site
and retired the wiki. Every source file was corrected in the same commit — but 0.9.0 had already
been packed, and nuspec metadata is frozen at pack time. So all four 0.9.0 pages on nuget.org
carried four wiki URLs each, in the README body and in the release-notes link, with no way to
correct them in place. The 0.9.1 pages link here instead.
Those 0.9.0 links are degraded rather than dead — with the wiki disabled GitHub redirects every
/wiki/* path to the repository home page — which is why this is a patch release and not a hotfix.
The XML documentation gained worked examples. #78 added <example> blocks across all four
packages. Those ship inside the .xml in the .nupkg, which is what your editor reads, so on
0.9.0 they reach the API reference on this site and not IntelliSense at the call
site. Upgrading is what closes that gap.
"Project website" now opens this site
PackageProjectUrl was the GitHub repository. From 0.9.1 it is
https://jerbersoft.github.io/databentodotnet/, which is what NuGet renders as Project website.
"Source repository" beside it is unchanged and still goes to GitHub, so nothing is lost from the
page.
This reverses a decision recorded on #68, which chose the repository URL partly because it
redirects through a rename and partly because — quoting it — "the wiki is the landing page this
would otherwise have needed". The second reason stopped being true when #82 built this site. The
first is still real, and a github.io URL is genuinely weaker than a repository URL, since renaming
the repository would break it without a redirect. It is reversed anyway, because each package README
already hardcodes several links to this site and those freeze into the package page the same way —
the risk was taken already, so pointing "Project website" here costs nothing new.
Upgrading
Change the version. There is nothing else to do.
<PackageReference Include="DatabentoDotNet.Live" Version="[0.9.1]" />
Still pinned exactly, and still for the reason 0.9.0 gave: this is a beta, the public API can
change before 1.0, and #68 is where that lands.
0.9.0 — 30 August 2026
The beta. All four packages, tagged v0.9.0, published by the release: published trigger
(run 33303094547). This is the first version meant to be built against.
dotnet add package DatabentoDotNet.Live
What is in it
Everything. The library is code complete against databento-rs: all nineteen historical endpoints,
the live client, all four reference endpoints, and the DBN codec underneath them. 1,868 tests, zero
warnings, a public API locked by an analyzer, and Native AOT verified by publishing and running a
native binary. The two constructs that do not port one-to-one are recorded decisions rather than
gaps — next_record cannot be async around a ref struct, so it is the
FillBufferAsync/TryNextRecord pair, and there is deliberately no record encoder.
Why it is not 1.0
Parity was the condition 1.0 was reserved for, and parity is met. It turned out to be the cheaper half. The expensive half is the promise: 1.0 undertakes not to break a 3,801-member public surface, and until this release nothing had built against the library in anger, so there was no evidence the surface was the right shape. 0.9.0 is what buys that evidence. If something in the API is awkward to call, an issue now costs far less than a major version later.
0.9.0 and not 0.9.0-beta, deliberately — see Versioning policy above.
Packaging, fixed
0.1.0-alpha shipped with no projectUrl, no readme, no icon and no releaseNotes — #71
read all four of its nuspecs and found them absent, and NuGet had been logging warn : Readme missing on every push. All four are present now, and each package carries a LICENSE file as well
(#72), since Apache-2.0 §4(a) asks that recipients of a derivative work receive a copy and the
.nupkg is the distribution.
Each package has its own README rather than a copy of the repository's, whose relative links resolve on github.com and 404 on nuget.org. Their code samples were compiled against the real assemblies rather than proofread, which caught three wrong ones before they became permanent on a package page.
Verified, not assumed
Against the published artefacts, downloaded back off the feed:
- All four
.nupkgcarry the metadata above and theLICENSE/README.md/icon.pngfiles. - All four install from a clean feed into a fresh project with
NUGET_PACKAGESpointed at an empty directory, compile, and run. - The resolved closure is exactly eight packages — the four of ours plus NodaTime,
ZstdSharp.Port,Microsoft.Extensions.Logging.Abstractions, and theDependencyInjection.Abstractionsthat the last of those declares. No analyzer or build-only package leaked. - All four PDBs come back from
symbols.nuget.org.
The release pipeline learned two things
Both defects in publish.yml were fixed before this release, and both earned themselves on it.
The version is now read off the packed artefact — the log opens Packed version: 0.9.0 where a
hardcoded 0.1.0-alpha used to sit, which at any other version would have operated on a version the
run did not produce and still gone green.
A run that would publish nothing now fails, via a pre-flight against the feed. --skip-duplicate
reports success when a package already exists, and a skipped primary push skips its .snupkg too,
so run 33280134279 was green having published nothing at all. --skip-duplicate stays, because it
is what lets a partial failure be retried; the pre-flight is what tells the two apart.
A third defect surfaced while fixing those. The final step, named "List packages on NuGet.org",
POSTed to /api/v2/package/{id}/{version} with the API key — an endpoint that relists a version
rather than listing anything. With its hardcoded version it would have quietly relisted an old
prerelease on every future release. It is now a read-only check that the versions this run published
actually reached the feed, and that check took five minutes to go green (09:04:23 → 09:09:25,
.Live last). A single post-push assertion would have failed this release spuriously. Do not shorten
the retry loop.
0.1.0-alpha — 29 August 2026
Release ·
tag v0.1.0-alpha.1 · commit 700145c
The first published version: DatabentoDotNet.Dbn, .Live, .Historical and .Reference, all
four at 0.1.0-alpha. It contains milestones 0 through 4 — the codec, live streaming, the
historical client and reference data.
Alpha means the surface can still change. Pin the exact version. The public API is locked by an analyzer (#63) so that any change to it is a diff somebody reads, but locked is not the same as frozen: the lock reports changes, it does not forbid them. 1.0 is where that promise hardens.
What was verified after publishing rather than assumed (#71): all four install from a clean
feed into a fresh project and compile against their public API; the symbol packages are on the
symbol server and the PDBs download; SourceLink resolves to the commit; the XML documentation ships
inside each package; and no analyzer or build-only package leaks into a consumer's dependency graph.
One finding — Microsoft.Extensions.DependencyInjection.Abstractions reaches consumers of
.Historical and .Reference, because Microsoft.Extensions.Logging.Abstractions declares it and
on net10.0 it is that package's only dependency. Not removable without withdrawing the public
LoggerFactory.
Known gap, tracked as #72: the packages assert
Apache-2.0and the repository has noLICENSEfile, so the README's licence badges link to a 404 and GitHub reports no licence at all. Being fixed before 1.0.
Milestone 4 — Reference data ✅
15 of 16 issues closed, milestone
Security master, corporate actions, and adjustment factors, over streaming zstd-JSONL.
ReferenceClientwith the three sub-clients, sharingHistoricalClientas its transport (#48)security_master.get_rangeandget_last(#54),corporate_actions.get_range(#55) andlist_events/list_enums(#56),adjustment_factors.get_range(#53)- An optional end to the range, which
DateTimeRangecould not express and so did not (#49) - Nineteen enums — twelve closed with fixed wire codes (#50), and seven open ones that must carry a code they do not recognise rather than reject it (#51)
- The 730-member code tables are generated, from the vendored
list_enumsoutput, by a script in the repository rather than by hand (#58, #59) - Streaming zstd-JSONL, and the client-side sort a stream genuinely cannot do (#52)
Still open: #57, the opt-in tests against the real reference API — blocked on a reference-data
subscription, which this account does not hold.
Milestone 3 — Historical ✅
18 issues, milestone
The full historical HTTPS client: metadata, symbology, timeseries and batch.
HistoricalClientwith Basic auth, URL construction, and structured error and warning handling (#35)metadata.*— all ten discovery and billing endpoints, includingget_cost, which prices the exact request you are about to send (#36)symbology.resolve(#37),timeseries.get_rangeandget_range_to_file(#38), andbatch.*with job submission, listing, and resumable parallel download (#39)DateRangeandDateTimeRangein NodaTime, with their two distinct wire renderings (#33)MockHistoricalGateway(#34), and opt-in tests against the real API with a second gate on the billable ones (#40, #44)- A real bug the mock could never have found (#45):
get_dataset_conditionreadsend_dateas inclusive whileDateRangemodels it as exclusive. The mock had agreed with the client about it for as long as both existed, because the same reading of the documentation produced both. Fixing it taught the lesson twice — the obvious shared fix would have brokenlist_datasets, which turned out to be genuinely half-open, so the endpoint being changed was probed rather than the one next to it (#46).
#32 moved public types between assemblies. Code that referenced DatabentoDotNet.Live's
Symbols or ApiKey needs DatabentoDotNet.Dbn instead. The namespace is unchanged, so for most
callers this is a project reference, not a source change.
Milestone 2 — Live streaming ✅
17 issues, milestone
A complete live-gateway client: connect, CRAM handshake, subscribe, start a session, and read records with no allocation per record.
LiveClientwith the full session lifecycle —ConnectAsync,AuthenticateAsync,SubscribeAsync,StartAsync,ReconnectAsync,ResubscribeAsync,CloseAsync(#19, #20, #21, #22, #23)- Two record loops.
FillBufferAsync+TryNextRecordfor zero-copy,RecordsAsync()for anawait foreachover heap copies (#22) - Subscriptions with 500-symbol chunking,
is_lastframing, and client-side validation that rejects a bad combination before anything reaches the socket (#21) - Heartbeats, read timeouts, and slow-reader behaviour, with
EffectiveReadTimeoutexposed so the derived budget can be read back (#23) - NodaTime throughout, enforced by an analyzer that fails the build on any BCL date/time type (#17)
RecordRef.IndexTs— the correct per-schema index timestamp, so a symbol lookup cannot silently key onts_event(#14)ISymbolIndex— resolve a symbol for any decoded record without knowing which map is answering (#13)- The async read seam decided before the socket loop was written:
SpaceMemory()over aMemoryManager<byte>, notSystem.IO.Pipelines(#15) MockLiveGateway, ported from upstream's harness and landed before the client (#18)- Opt-in real-gateway tests with two independent gates, so no test starts a billable session without its own opt-in (#25)
- The zero-allocation guarantee is measured, not asserted-to — including a test that the measurement itself notices a deliberate allocation (#28)
Milestone 1 — DBN codec ✅
8 issues, milestone
- Twenty-one record structs, every one with its
WireSizeasserted against thestatic_assertvalues indatabento-cpp(#3) - Enums and publisher tables, with numeric validators for
Publisher,Dataset, andVenue(#2, #11) - Metadata decode and encode (#4)
- The incremental decoder —
AlignedBufferover aulong[]for guaranteed 8-byte alignment, and the state machine over it (#5) TsSymbolMapandPitSymbolMapforinstrument_id↔ symbol resolution (#6)- The
net11.0target dropped until .NET 11 is GA, because it was compiled nowhere (#16)
Conformance: every .dbn, .dbn.zst, and .dbn.frag fixture in the vendored corpus (71 files
from databento/dbn 0.68.0) decodes, and yields the record counts upstream reports.
Milestone 0 — Foundation ✅
1 issue — solution layout, CI across Linux, macOS, and Windows, and packaging (#1).
In progress — Milestone 5, polish and 1.0
23 of 25 issues closed, milestone
Landed: the public API lock (#63), Native AOT verified by publishing and running a native binary rather than by the analyzers alone (#64), four runnable samples (#66), the verification of the published packages (#71), and this page (#73).
A documentation site was built (#67), cut to the API reference (#69) and retired (#70) inside
a single evening, then resurfaced. #82 settled it the other way: the site is the documentation.
The wiki's ten guides moved into docs/, the wiki was retired, and there is still exactly one copy
of each fact — which is the rule all three of those issues were actually arguing about. What the
first evening got wrong was not that rule but the assumption that a second surface must mean a
second copy.
The API reference is still generated from the XML doc comments dotnet pack ships inside each
package, so it reaches IntelliSense at the call site and cannot drift from the code. The site
renders those comments; it does not restate them.
Also landed: the LICENSE file (#72) — the repository asserted Apache-2.0 in four published
packages while containing no copy of it, and GitHub's API reported "license": null. The text is now
in the repository and inside every package, alongside the SPDX expression rather than instead of
it.
The 0.9.0 beta shipped (#74) — see its section above. Its one task that no commit could do is
done too: the DatabentoDotNet ID prefix is reserved, granted 2026-08-31 and exclusive to owner
jerbersoft. CLAUDE.md's naming rule exists because Databento.* is the vendor's and unreserved;
ours is now not, and all four packages carry the reserved-prefix indicator on nuget.org.
0.9.1 followed a day later (#85) and is the same code — it exists because retiring the wiki left
the four 0.9.0 package pages pointing at it, and a package page is frozen at pack time. That is
worth stating as the general rule it is: anything a consumer reads from inside the package —
the README, the XML documentation, projectUrl, releaseNotes — can only be corrected by
publishing again. The release checklist below now names the four src/*/README.md explicitly for
that reason; it previously said to grep docs/, which does not reach them.
The live end-to-end latency benchmark is measured (#65). Against EQUS.MINI trades on eight
liquid US equities on 2026-08-31: 2,240 records over five minutes, of which this library's own
share — decoding a record and handing it to the caller — is 7.7 µs at p50 and 27 µs at p99. That
is the figure a consumer can act on, and it reproduced to 0.1 µs across two runs on different samples
because both of its stamps come from one stopwatch: no epoch is read, so no clock offset can enter.
The row that spans two machines' clocks came back negative through its median, and chasing that found a better measurement (#83). A gateway-to-caller figure subtracts our wall clock from Databento's, so it carries the distance between the two clocks' zeros — 63 ms on the day. That is not a bug: one-way delay between unsynchronised clocks is not observable at all. A round trip is, so #83 times the TCP handshake on our own stopwatch alone and gets 74.6 ms, or 37.3 ms one way, against ~40 ms from the offset-corrected figure — two independent routes to the same answer, one of which reads no clock. It is free to run, because a handshake completes long before a session starts.
The practical consequence for anyone reading the report: the gateway-to-caller row is a property of how far you sit from the venue, not of this library. ROADMAP.md §7 has both tables.
The release pipeline learned a fourth thing, and this one it learned by being unable to answer a
question (#103). 0.10.0's first run got a 403 partway through the push because nuget.org's
Trusted Publishing policy was scoped to the four package ids that already existed. The obvious fix —
ask the registry whether the credential may push each id before sending anything — has no mechanism:
the endpoint that looks like it should answer returns 404 for an id that does not exist yet, before
it evaluates any permission. So a new package id is now something the release declares, and the
declaration is pushed first so that being wrong about it costs a failed run rather than a half-
published version. The gates themselves moved out of the workflow into tools/publish-preflight.sh,
where they can be run — and are, on every CI push — without publishing anything.
Still open:
- #68 —
0.x→1.0.0. Its mechanism and metadata are done and 0.9.0 proved both on a real release; what is left is the promise, and the evidence for it can only come from the beta.
In progress — Milestone 6, hosting extensions
9 issues, milestone — that page is
the live count, and this line deliberately does not restate it: the Fixes #N trailers on this
work close several the moment it merges, which would falsify any number written here.
A fifth package, DatabentoDotNet.Extensions.Hosting, registers the historical, reference and live
clients on IServiceCollection, binds IConfiguration to an options model, and runs a live session
as a BackgroundService with bounded reconnection, an opt-in health check, and metrics — allocating
nothing per record, the same guarantee the core four already carry. The guide, the package README,
and a fifth sample landed with it (#93).
It shipped at 0.10.0, ahead of 1.0 rather than after it (#102) — see that release's
section at the top of this page. This paragraph used to say 1.1.0, on the ground that locking five
packages to one SemVer promise on day one would guarantee this package's surface before anything had
built against it. That ground is untouched and it is the ground the number was changed on: 0.x
carries no promise, so it was never the thing the objection was about. 1.1.0 had been chosen only
because the next release was assumed to be 1.0.0. PublicAPI.Shipped.txt for this package is
still empty, for exactly the reason it always was.
The core four are otherwise unchanged. The one exception landed in 1.0 itself, ahead of this
package rather than inside it: an HttpMessageHandler seam on HistoricalClient — a settable
Handler and a DisposesHandler flag — so that a singleton HistoricalClient living in a
long-lived host can be reached through IHttpClientFactory's connection-pool rotation, which
nothing before it provided. That is core surface because a singleton client outliving the process
that first resolved a hostname is a problem HistoricalClient has on its own, independent of
whether anything ever hosts it. ReferenceClient needed no equivalent change:
ReferenceClient(HistoricalClient) already existed, for exactly this — a consumer holding both
clients who wants one connection pool — before this consumer existed to use it.
See ROADMAP.md §8 for the
full design and docs/plans/m6-hosting-extensions-plan.md
for how it decomposes into tasks.
The release checklist
For whoever cuts the first one. Not automated yet.
Every issue in the milestone is closed, and the milestone itself is closed.
dotnet buildanddotnet testare green on all three CI platforms, with zero warnings —TreatWarningsAsErrorsmeans a warning is already a failure.Version set in
Directory.Build.props. DropVersionSuffixfor a stable release. And checkPACKAGES,HELDandFIRST_PUBLISHinpublish.yml— three decisions, all separate from the version. Everything shipping packs at the version above; only whatPACKAGESnames is pushed. The workflow fails on a packed package named by neitherPACKAGESnorHELD, so a new one cannot silently ship — but it can silently not ship if it is parked inHELDand forgotten, and nothing catches that but this line.FIRST_PUBLISHnames ids that have never been published at any version, and it is the one entry here with work attached to it that is not in this repository. A first publish needs the nuget.org Trusted Publishing policy widened to cover the new id first, by hand, at https://www.nuget.org/account/trustedpublishing — a policy scoped to the ids that already exist returns 403 partway through the push, which is how0.10.0went out in two runs. The workflow refuses an undeclared new id, and refuses a declared id that is already on the feed, so the list cannot go stale in either direction. It cannot check the policy itself; nothing can.Benchmarks run and the throughput and allocated-bytes numbers recorded, so a later regression has something to be a regression from.
dotnet pack -c Release, and the resulting.nupkginspected — it should carry the.snupkgsymbol package and SourceLink metadata.Tag
v0.x.y, push the tag, and write the GitHub Release against it. Publishing a release runspublish.yml.Confirm the run actually published. Two things learned cutting
0.1.0-alpha:dotnet nuget pushsends each package's adjacent.snupkgon its own, so symbols need no separate step — but--skip-duplicatemeans a re-run where every package already exists reports success having pushed nothing, symbols included, because a skipped primary push skips its symbol package too. A green tick is not evidence a version reached the feed. Read the log.The push is one invocation per package now rather than a
nupkg/*.nupkgglob, in an order the pre-flight works out: anything inFIRST_PUBLISHgoes first, so a permission failure on a new package id happens before anything else is live. The symbol pairing is unaffected — it is derived from each.nupkgpath, not from having been globbed.Install each package into a throwaway project from a clean feed and compile against it, with
NUGET_PACKAGESpointed at an empty directory so nothing resolves from the local build. #71 has the method.Update this page with the narrative and any upgrade notes — and every other page that names a version. Grep the whole repository for the old version number rather than editing the page you happen to be thinking about, and do it before step 5, not after step 8.
Three groups state the current version, and they are not all in
docs/:docs/index.md,docs/guides/getting-started.md,docs/guides/faq.md— these were still saying0.1.0-alphaafter0.9.0went out.README.mdandCONTRIBUTING.md.src/*/README.md, all five. These are the ones that matter most and are easiest to miss: they arePackageReadmeFile, so they are the nuget.org package pages, and once packed they are frozen.0.9.1exists because these four were left pointing at the retired wiki when0.9.0was packed, and a wrong package page can only be superseded, never edited. A grep scoped todocs/— which is what this step used to say — does not reach them.
Leave the sections of this page describing past releases alone. They are a historical record; the figures in the
0.9.0section are what was true of0.9.0.Verify against the published artefacts, not the local pack: download each
.nupkgback off the feed and read its nuspec, install into a throwaway project withNUGET_PACKAGESpointed at an empty directory, and fetch the PDBs withdotnet-symbol --server-path https://symbols.nuget.org/download/symbols/. The default server is Microsoft's and will report every PDB as Not Found, which looks exactly like a failed symbol push.
The licence half of this is fixed; the lesson it taught is not. This page recommended a
LICENSEfile before anything was published,0.1.0-alphashipped without one anyway, and four packages spent their first days asserting Apache-2.0 against a repository whose licence badges linked to a 404 and whose GitHub API entry read"license": null. What changed it was #72 — an issue. That is the whole difference: a recommendation with nothing tracking it does not stop a release, which is why the checklist above is a checklist and not a paragraph.Both follow-ons now exist too.
SECURITY.md(#75) andCONTRIBUTING.md(#76) were the other two files GitHub surfaces in its own UI, and they were flagged here in the same breath as the licence — untracked, and therefore still missing when0.9.0shipped. They landed the way the licence did: an issue first. The security one mattered most, because four public packages with no stated private channel means a finder's reasonable default is a public issue, which discloses the flaw to every consumer before a fix exists.
CODE_OF_CONDUCT.mdfollowed (#77) — Contributor Covenant 2.1, verbatim, because GitHub detects that file by matching known text exactly as it does a licence. The family is complete: GitHub's community profile now reports all four, where it reported one before0.9.0.The pattern is the point, and it repeated four times: each of these was written down as a recommendation, none of them shipped, and every one of them landed within an hour of getting an issue number. Recommendations do not stop releases. Issues do.
See also
ROADMAP.md— what each milestone contains, and every design decision with its reasoning- Milestones — live progress bars
CLAUDE.md— why the changelog belongs in the repo and this page does not