-
Notifications
You must be signed in to change notification settings - Fork 0
Comparing changes
Open a pull request
base repository: TraxSharp/Trax.Effect
base: v1.54.0
head repository: TraxSharp/Trax.Effect
compare: v1.54.2
- 9 commits
- 102 files changed
- 2 contributors
Commits on Sep 16, 2026
-
chore: refresh trax pins (#144)
* chore(deps): refresh cross-repo Trax pins to the latest published versions Trax.Core 1.6.0 -> 1.7.0. Pins are deliberate edits under adr/0002, so they do not move by themselves, and these had drifted far enough that a downstream repo could no longer build against what its own code needed. Regenerated every lockfile with the local feed hidden and 1.99.99 purged from the NuGet cache, so the resolved versions are the ones CI will see rather than the locally packed build. * chore(deps): make the Trax pins readable so Dependabot can bump them The pins were written as $([MSBuild]::ValueOrDefault('$(TraxLocalVersion)', '1.54.0')). That is not a version any tool can parse, so Dependabot skipped every one of them while bumping the plain-versioned packages beside them in the same file: across three repos, ten Dependabot commits touched Directory.Packages.props and none of them moved a Trax pin. The mechanism that makes local development work was blinding the automation that would have kept the pins fresh, which is why some had drifted thirteen releases behind. The pins are now plain literal versions. The local override moves to a PackageVersion Update group that trax-local.props switches on, so pack-local.sh still redirects everything to the packed 1.99.99 and CI, which never sees that file, still restores the exact pins under --locked-mode. No resolved version changes: the lockfiles are untouched. Dependabot also gains an ignore for Trax majors. A major is a deliberate migration, and it keeps the bot away from the unlisted 2.0.0 packages still sitting on nuget.org for trax.core and trax.scheduler, which stay resolvable and would otherwise be a legitimate-looking upgrade target. * ci: regenerate lockfiles on Dependabot pull requests Dependabot edits Directory.Packages.props but does not reliably regenerate the packages.lock.json files beside it. The pull-request build restores with --locked-mode, so a lockfile that disagrees with the manifest fails the PR with NU1004 and the bump stalls until somebody regenerates by hand. Reproduced deliberately: bumping one pin without touching the lockfiles gives exactly that error, and restore --use-lock-file --force-evaluate clears it. This matters more now that the Trax pins are readable, because those bumps will start arriving. Two jobs, per GITHUB_ACTIONS_SECURITY.md triage item 2. `dotnet restore` resolves and downloads third-party packages, and that must not share a job with a write token, so the resolve job holds contents: read and hands the result to the commit job as a patch. The commit job holds the only elevated token in the workflow and runs nothing but git. The patch comes from a job that ran dependency code, so it is treated as untrusted on arrival: git apply is restricted with --include to lockfile paths, and the working tree is re-checked afterwards in case that restriction ever slips. Both were verified to hold, including that --include refuses a patch touching Directory.Packages.props. The guard is the pull request author rather than github.actor, which is what GitHub documents: a human re-running the workflow changes the actor but not the author. persist-credentials is true in the commit job alone, against the convention everywhere else here, because that is the job that pushes.Configuration menu - View commit details
-
Copy full SHA for 3a8e05e - Browse repository at this point
Copy the full SHA 3a8e05eView commit details -
test: bound the SignalR handshake under the hub wait (#147)
The hub tests wait on StartAsync with a 10 second ceiling while HubConnection keeps its 15 second handshake default, so a stalled handshake outlived the assertion and reported as a bare cancellation. The shared fixture and the one test that builds its own connection now set HandshakeTimeout to 5s. ServerTimeout is left alone deliberately: it detects a dead server rather than delaying a wait, and shortening it below the server's keep-alive interval would drop healthy connections mid-test. Trax.Docs/adr/0014.
Configuration menu - View commit details
-
Copy full SHA for 59fb794 - Browse repository at this point
Copy the full SHA 59fb794View commit details -
chore(deps-dev): bump js-yaml from 4.3.1 to 4.3.2 (#141)
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.3.1 to 4.3.2. - [Changelog](https://github.com/nodeca/js-yaml/blob/4.3.2/CHANGELOG.md) - [Commits](nodeca/js-yaml@4.3.1...4.3.2) --- updated-dependencies: - dependency-name: js-yaml dependency-version: 4.3.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Configuration menu - View commit details
-
Copy full SHA for 39faa84 - Browse repository at this point
Copy the full SHA 39faa84View commit details -
ci: harden the Dependabot lockfile workflow (#148)
Two fixes to the commit job, the one holding the write token. The patch is now downloaded before the checkout and into RUNNER_TEMP rather than the workspace. Previously actions/download-artifact ran after the checkout, so a third-party action executed while the push credentials were already in .git/config; now nothing third-party runs in that window. Keeping the patch outside the working tree also means it can never be swept into the commit, so the explicit delete is gone. The pinned SHAs for upload-artifact and download-artifact were correct but their version comments were not, reading v5.0.0 and v6.0.0 against SHAs that are v7.0.1 and v8.0.1 everywhere else here. The SHA is what pins, so this changed nothing at runtime, but a wrong label is worse than none in a pinned- action audit.
Configuration menu - View commit details
-
Copy full SHA for e9e24d9 - Browse repository at this point
Copy the full SHA e9e24d9View commit details
Commits on Sep 17, 2026
-
ci: make the Dependabot heal workflow actually finish the job (#151)
Same change proven on Trax.Mediator, where it healed #84 end to end. The old workflow could never land a commit: it used `git push`, and the org ruleset requires verified signatures, which a runner has no key for. It now writes through the createCommitOnBranch GraphQL mutation, which GitHub signs itself, so the heal lands Verified with no signing key in CI. It also only ever fixed lockfiles. Dependabot rewrites project files on a grouped bump: when the transitive graph resolves one package at different versions across projects, it adds <PackageReference ... VersionOverride="..."> to projects that never referenced it, several pinned to the version being bumped away from. Nothing reverted those, so lockfiles regenerated on top of the injection. The revert is re-derived with git in the committing job rather than trusted from the patch, so the job that runs dependency code still hands over nothing but lockfiles. The regenerating restore now passes -p:NuGetAudit=false. Directory.Build.props promotes NU1901-NU1904 to errors, so an advisory anywhere in the graph aborted the regeneration, which is precisely the state a security bump arrives in. The PR build still audits. It keeps authenticating with GITHUB_TOKEN rather than an App token or a PAT. Either would avoid the "Approve and run" press on the run that follows the write, but both mean a long-lived credential in Dependabot secrets, and an App private key never expires and can mint write tokens for every repo it is installed on. GITHUB_TOKEN stores nothing. On the config side, csharpier and PublicApiGenerator are ignored. Each has to move in one commit with the artefact it invalidates -- the repo-wide format and the PublicApi baselines -- which a bot PR cannot do, so it just leaves the check failing every time. Roslyn moves to its own group so that when its bump does need a human it does not take the rest of the week's updates with it, and the PR limit drops to three.
Configuration menu - View commit details
-
Copy full SHA for 96893b8 - Browse repository at this point
Copy the full SHA 96893b8View commit details -
Bump FluentAssertions and 13 others (#152)
Bumps FluentAssertions from 8.10.0 to 8.11.0 Bumps Microsoft.AspNetCore.SignalR.Client from 10.0.11 to 10.0.12 Bumps Microsoft.AspNetCore.TestHost from 10.0.11 to 10.0.12 Bumps Microsoft.Extensions.Configuration from 10.0.11 to 10.0.12 Bumps Microsoft.Extensions.Configuration.Json from 10.0.11 to 10.0.12 Bumps Microsoft.Extensions.DependencyInjection from 10.0.11 to 10.0.12 Bumps Microsoft.Extensions.Hosting.Abstractions from 10.0.11 to 10.0.12 Bumps Microsoft.Extensions.Logging from 10.0.11 to 10.0.12 Bumps Microsoft.Extensions.Logging.Console from 10.0.11 to 10.0.12 Bumps Microsoft.Extensions.TimeProvider.Testing from 10.9.0 to 10.10.0 Bumps Microsoft.NET.Test.Sdk from 18.9.0 to 18.10.1 Bumps NUnit.Analyzers from 4.14.0 to 4.15.0 Bumps Trax.Core from 1.7.0 to 1.7.1 Bumps Trax.Core.Testing from 1.7.0 to 1.7.1 --- updated-dependencies: - dependency-name: FluentAssertions dependency-version: 8.11.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: nuget-minor-patch - dependency-name: Microsoft.AspNetCore.SignalR.Client dependency-version: 10.0.12 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-minor-patch - dependency-name: Microsoft.AspNetCore.TestHost dependency-version: 10.0.12 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-minor-patch - dependency-name: Microsoft.Extensions.Configuration dependency-version: 10.0.12 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-minor-patch - dependency-name: Microsoft.Extensions.Configuration.Json dependency-version: 10.0.12 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-minor-patch - dependency-name: Microsoft.Extensions.DependencyInjection dependency-version: 10.0.12 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-minor-patch - dependency-name: Microsoft.Extensions.Hosting.Abstractions dependency-version: 10.0.12 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-minor-patch - dependency-name: Microsoft.Extensions.Logging dependency-version: 10.0.12 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-minor-patch - dependency-name: Microsoft.Extensions.Logging.Console dependency-version: 10.0.12 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-minor-patch - dependency-name: Microsoft.Extensions.TimeProvider.Testing dependency-version: 10.10.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: nuget-minor-patch - dependency-name: Microsoft.NET.Test.Sdk dependency-version: 18.10.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: nuget-minor-patch - dependency-name: NUnit.Analyzers dependency-version: 4.15.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: nuget-minor-patch - dependency-name: Trax.Core dependency-version: 1.7.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-minor-patch - dependency-name: Trax.Core.Testing dependency-version: 1.7.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: nuget-minor-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Configuration menu - View commit details
-
Copy full SHA for 75bc65e - Browse repository at this point
Copy the full SHA 75bc65eView commit details -
chore(deps): bump codecov/codecov-action (#149)
Bumps the actions group with 1 update in the / directory: [codecov/codecov-action](https://github.com/codecov/codecov-action). Updates `codecov/codecov-action` from 7.0.0 to 7.1.0 - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](codecov/codecov-action@fb8b358...0b35c9e) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: 7.1.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Configuration menu - View commit details
-
Copy full SHA for e1ad605 - Browse repository at this point
Copy the full SHA e1ad605View commit details -
fix(deps): reference SQLitePCLRaw directly instead of pinning it tran…
…sitively (#153) Trax.Effect.Data.Sqlite pinned SQLitePCLRaw.bundle_e_sqlite3 through a bare PackageVersion, to force the patched native library that fixes CVE-2025-6965. Nothing referenced the package, and Dependabot only updates a PackageVersion it can tie to a PackageReference, so the pin was invisible to it and had sat at 3.0.3 while Trax.Samples, which references it directly, was carried to 3.0.5. A pin that exists to raise a security floor had quietly stopped rising. The reference now lives in Trax.Effect.Data.Sqlite, the project that pulls it, so Dependabot maintains it from here. That also means the floor reaches consumers through the package's own dependencies rather than applying only to builds inside this repo, which is the intended effect for a CVE. NoOrphanPackageVersionTests fails on any future PackageVersion with no matching PackageReference. Records Trax.Docs/adr/0015.
Configuration menu - View commit details
-
Copy full SHA for f6b81bf - Browse repository at this point
Copy the full SHA f6b81bfView commit details
Commits on Sep 24, 2026
-
fix: train outcome recording (#154)
* fix: record a train's outcome on a token the caller cannot cancel ServiceTrain set the terminal state through FinishServiceTrain and then persisted it with EffectRunner.SaveChanges(CancellationToken). That token is cancelled in exactly the case the write exists to record, so the write never landed. The row stayed InProgress with no EndTime until a scheduler's stale-in-progress reaper rewrote it to Failed an hour later, whatever the train had actually done, and a train whose downstream call takes no token finishes its work after the caller gives up only to be recorded as Failed. The three terminal writes now go through SaveOutcome, which persists on CancellationToken.None. ADR 0005 records why that one write does not take the token, because the obvious edit undoes it. * feat: refuse a junction chain that reads the value being processed TrainInput and TrainOutput now throw while a chain is being declared, so a chain cannot vary by the input and the chain read at startup is the chain that runs. Nothing had to change to adopt this: every Junctions() override in the repo was already pure. ServiceTrain no longer overrides RunInternal. Once monad construction moved behind the overridable NewMonad() upstream, the override was identical to the base, and removing it leaves Train.RunInternal with no framework override at all. * test: move zero-junction test trains onto Junctions() A train that returns its input, or Unit, declares a chain of no junctions and ends it with Resolve(). These no longer need RunInternal. * test: move the remaining chainable test trains onto Junctions() Work that a train used to do inline moves into a junction, which is where per-execution decisions belong. Where the output is a property of the input, Extract states that directly rather than needing a junction at all. * test: finish moving test trains onto Junctions() Work moves into junctions, which is where per-execution decisions belong. Three trains keep RunInternal: two return the train instance itself, and one captures the monad to assert on Memory, so both need seeding a chain declaration cannot express. * feat: drop ServiceTrain.Activate Redundant once monad construction moved behind NewMonad(), and it was the last public route to a seeded monad outside a chain declaration. ServiceTrain.Run now goes through RunEither, which is the public entry point RunInternal sits behind. * feat: let a train hold its queue entry unconfirmed until OnQueue commits An OnQueue hook and the work queue row are two writes, and a crash between them used to leave the hook's side-effect with no queued work to consume it. Two ways to close that, depending on where the hook writes. IEnqueueContextAccessor exposes the context the enqueue is about to commit on, so a hook writing Trax's own entities is committed and rolled back with the entry. That does not help a hook writing through its own DbContext: EF can only share a transaction between contexts that share a connection. For that case a train overrides DeferQueuePromotion. Its entry is committed with a null confirmed_at and is not dispatchable, the hook runs, and a second commit promotes it. The two writes still are not atomic — nothing can make them atomic across two databases — but a crash now strands a findable entry instead of an invisible side-effect, and IWorkQueuePromotion.PromoteStaleAsync recovers it. Promotion rather than cancellation, because the deferred run re-executes the chain and the hook is already required to be idempotent. Immediate promotion stays the default, so a train that does not opt in sees no extra round trip. Existing rows are backfilled from created_at. * feat: let a train name the subject its queued work touches Queued entries are claimed by several workers with SKIP LOCKED ordered by priority and age, keyed on nothing, so two mutations for one record can run at the same instant. Against a system that resolves concurrent writes by last-write-wins that is a lost update, and Trax had no way to express that two pieces of work touch the same thing. QueueSubjectKey gives a train one. It is read at enqueue time from a metadata carrying the input, so the key varies per mutation rather than per train, and it returns null by default so every existing train is unaffected. Throwing aborts the enqueue rather than degrading to null, which would drop the guarantee exactly when the caller wanted it. This carries the key and the indexes the dispatch check needs; the dispatcher change that acts on it follows. The two partial indexes are not a tuning afterthought — measured on 200k entries the busy-subject check is 36ms of sequential scanning without them and 0.05ms with them. * feat: refuse to claim a queue entry whose subject already has a run in flight The claim now rejects an entry when another entry with the same subject key is dispatched and its run has not reached a terminal state — the definition of an active execution already used for dormant dependents. That is not sufficient on its own. Two entries for one subject are two different rows, so FOR UPDATE SKIP LOCKED does not make them contend, and while both are still queued neither can see a dispatched sibling to refuse itself. Two dispatchers in the claim window at the same moment would both succeed. LockSubject closes that: on Postgres a transaction-scoped advisory lock on the subject, taken before the claim. It is held only for the claim transaction, which commits before the job is submitted, so no remote call happens under it. LockSubject is default-implemented as a no-op so providers outside this repo keep compiling, and because a single-writer provider does not need one. * feat: record how a failed run failed Registering an IFailureClassifier makes Trax stamp a FailureClass on every failed run, stored on the metadata so it survives the process and can be queried rather than grepped. It is asked where the failure is recorded, which is the only place holding the consumer's own exception object: junctions enrich an exception and return it rather than wrapping it, so a classifier can type-check and read structured error data instead of parsing a message. There is a test pinning that, because if it ever changes every classification silently becomes Unclassified rather than anything failing. A classifier is optional, may return null, and may throw — a throw is logged and leaves the failure unclassified, since it must never be able to mask the failure it was asked about. Cancellation is not a failure and is not classified. Nothing acts on the class yet; retry remains count-based. Recording it first is deliberate. * fix: record a failed run once, and classify failures raised outside a junction A failed run finished the train, saved its outcome and fired OnFailed, then rethrew from inside the try whose catch did all three again. Every failure was written twice and every failure hook, global and per-train, ran twice. The result is now captured first and takes one path. A completed run whose outcome cannot be saved lets that error propagate instead of being rewritten as Failed, since the work did happen. The classifier's answer was only ever written onto the exception's TrainExceptionData, which exists only for a failure inside a junction. A train whose Junctions() threw, or that failed before any junction ran, recorded Unclassified whatever the classifier said. The class is now set on the run directly when nothing more specific was carried, so a class sent back by a remote worker still wins over the calling side's classifier. Also repairs the XML docs on the queue hooks: the DeferQueuePromotion and QueueSubjectKey summaries had been stacked onto QueueSubjectKey, leaving OnQueue and DeferQueuePromotion undocumented, and OnQueue now says that a deferring train's row is committed before its hook runs. * fix: keep entries the claim will refuse out of dispatch capacity The group-fair load, which is the default dispatch path, returned every queued manual entry, including unconfirmed ones and ones whose subject already had a run in flight. The claim refused them, but only after capacity limiting had given each a slot, so a backlog for one subject, or a handful of stranded staged entries, could take every slot on every cycle. Both dialects now filter them out the way the other load path already did. confirmed_at gets a DEFAULT of now() on Postgres. During a rolling deploy an instance still on the previous version inserts rows without the column, and without a default those rows were unconfirmed forever. The dispatchable index now also filters on status, so it stops covering every row ever dispatched. The subject lock moves to the two-key advisory form under a fixed class key, which Postgres keeps apart from the single-key space the leader lock and any consumer locks use. * feat: cancel stranded staged entries by default, and let enqueue contexts flow A crash can leave a deferred entry unconfirmed, and nothing recorded tells apart a hook that succeeded, one that never ran, and one that threw before the entry was removed. Promoting such an entry could run a mutation that was rejected, so IWorkQueuePromotion gains CancelStaleAsync as the default recovery and keeps PromoteStaleAsync for hosts that opt in. Promotion now only confirms an entry that is still queued, so one an operator cancelled while its hook ran stays cancelled. On the in-memory provider, which cannot translate ExecuteUpdate, both fall back to tracked updates instead of throwing after a hook has already run. The enqueue context was a plain field on a scoped service, so two enqueues in one scope, such as a Blazor circuit, threw or saw each other's context. It is an AsyncLocal now: each enqueue sees its own, and one started from inside a hook gets its own and hands the outer one back. * docs: route the terminal write and authorization vocabulary to their decisions AGENTS.md now points work on ServiceTrain's terminal write to effect/0005 and authorization types to effect/0004; neither was routed. The README usage example still overrode RunInternal with Activate, which no longer compiles. Also clears a nullable warning in the JSON effect provider tests. * docs: record that a closed vocabulary Trax persists is a Postgres enum Adding a value to one of these vocabularies breaks a rolling deploy, and it is easy to blame that on the enum column type. The failure is in mapping the value to a C# enum the old process lacks, which text storage would not avoid, so all seven stay Postgres enums. effect/0006 records that, and that a new value ships in a migration and a reader build before anything writes it. PostgresEnumVocabularyTests holds it up: the three places an enum is mapped must name the same enums, and every mapped enum's members must match the labels its migrations create and add. * fix: make the SQLite dispatch SQL match how SQLite stores enums The SQLite context stores enums as their integers, so status holds 0 and train_state holds 0 or 1, but the SQLite claim and candidate queries compared those columns to 'queued', 'dispatched', 'pending' and 'in_progress'. The claim therefore never matched a row, which meant dispatch on SQLite could not claim any work, and the subject check could never see a busy subject. The claim predates this branch; nothing caught it because the only test string-matched the SQL. The literals are now generated from the enums, and new tests run the claim and the candidate load against rows EF wrote. * fix: leave a remote failure unclassified when the worker sent no class When a worker returned a failure without a classification, the calling side asked its own classifier about the rebuilt exception and recorded the answer. That is the re-derivation docs/0020 rejects: the rebuilt exception's type is gone, so a catch-all classifier stamped Permanent on a failure the worker deliberately left alone. A failure rebuilt from a serialized record is now never classified locally; a class the worker did send still wins. * perf: index runs by failure class for the executions filter The executions query can filter on failure_class, which had no index, so a filtered page scanned the metadata table and counted it exactly. A partial index on the classified rows, ordered by id like every other filter's page, covers it; unclassified, the default and the bulk of the table, is left out. * fix: backfill confirmed_at from created_at before setting its default Migration 041 added the column with DEFAULT now(), which filled every existing row with the migration time and left the backfill from created_at nothing to do, contrary to its own comment. The default is now set after the backfill. The migration has not been released, so existing databases are unaffected. Also drops two tests named for a RunInternal override that no longer exists, fixes comments and XML docs that still referred to it, and names both stale-run reapers as releasing a subject. * test: cite the decisions these tests hold up The exemplar tests named by docs/0016, docs/0018, docs/0019 and docs/0020 now carry the adr property and say which decision they enforce, so someone gutting one sees that an ADR depends on it. * docs: route to the new decisions, and say how SQLite stores enums AGENTS.md now routes work queue, enqueue-context and FailureClass storage changes to their decisions, with counts that match. effect/0006 said only that SQLite stores the C# value; it stores the enum's integer, so raw SQL against those columns has to compare integers. * fix: carry the class of a failure raised outside a junction, and keep the failure if recording it fails A classifier's answer was written onto the exception only when a junction had attached TrainExceptionData. A failure raised elsewhere got its class on the local run but nowhere else, so a remote worker reported it with none and the caller recorded Unclassified. The data is now attached when missing, with the fields AddException would derive anyway. If finishing or saving a failed run threw, that exception replaced the train's own failure and no failure hooks ran. The recording error is now logged and the original failure propagates with its hooks. * fix: give SQLite's new columns and indexes integers, and pin TrainState's values SQLite stores enums as integers, but migration 009 defaulted failure_class to the label 'unclassified', so every row that existed before it was neither Unclassified to a query nor anything else, and migration 008's partial indexes filtered on 'dispatched', 'pending' and 'in_progress', which no row holds, so they were never used. Both now use the integers. TrainState's values are pinned explicitly, because SQLite's SQL and indexes now depend on them. * fix: share the enqueue context across instances, and make the failure-class index usable The enqueue context's AsyncLocal lived on each accessor instance, and the accessor is scoped, so a singleton train, or a repository resolved from another scope, read null inside OnQueue. It is static now: every instance sees the enqueue running on the current async flow. The failure-class index was partial on classified rows, but the executions filter sends the class as a parameter, and a generic plan cannot prove a parameter satisfies a partial predicate, so the index went unused. It covers every row now. * docs: record how a run's outcome is written when saving it fails, and SQLite's integers effect/0005 now says a completed run whose outcome cannot be saved propagates the save error instead of being rewritten as Failed, and that a failed run's recording error is logged while the original failure propagates with its hooks. effect/0006 says SQLite's failure_class is an INTEGER defaulting to 0, the subject indexes compare integers, and TrainState's values are pinned for that reason. * fix: set confirmed_at's default before backfilling it in migration 041 DbUp runs the script without a transaction, so an instance still on the previous version could insert after the backfill UPDATE and before SET DEFAULT, leaving a row with a NULL confirmed_at. The dispatcher treats that as staged and skips it, and the stale-staged sweep then cancels it, so accepted work was silently lost during a rolling deploy. The default is now set before the backfill. SET DEFAULT takes an ACCESS EXCLUSIVE lock that waits out in-flight inserts, so every row written without the default is committed before it and caught by the backfill after it. The regression test replays the script statement by statement against a database migrated to 040 and inserts an old-style row between each pair; it fails on the previous ordering. * test: cover the stale-staged sweeps against confirmed backlogs, cancelled entries and SQLite The sweeps were only tested against staged entries on the in-memory provider. A queued entry that is confirmed but an hour old is a normal backlog, and neither CancelStaleAsync nor PromoteStaleAsync may touch it; an old staged entry an operator already cancelled must not be revived by PromoteStaleAsync. SQLite gets its own copy of the promotion tests because it runs the real ExecuteUpdate statements, with an integer status and a TEXT created_at, so the cutoff is a string comparison. That includes a row whose created_at came from the column's datetime('now') default rather than from EF. * test: pin what a train reports when saving its outcome fails Effect ADR 0005 says a completed run whose outcome save throws propagates the save error, is not rewritten as Failed and fires no OnFailed, leaving the row InProgress for the reaper; and that a failed run whose outcome save throws still propagates its own exception and fires OnFailed once. Neither half had a test. A failing effect provider placed ahead of the in-memory one throws once the run reaches the probed terminal state. * test: pin FailureClass's integers and that SQLite stores them SQLite writes FailureClass as its integer and migration 009 defaults the column to 0, so renumbering a member would reinterpret every stored row. The test pins each member's value and checks EF writes Conflict to the column as 2. * test: name the decisions in the outcome-save and FailureClass failure messages OutcomeSaveFailureTests and SqliteFailureClassStorageTests cite effect ADRs 0005 and 0006 in their attribute and docstring; the messages now name them too, so whoever trips one sees the authority and the ADR guard can list them as exemplars. * docs: correct what TrainInput, TrainOutput and WorkQueue's constructor promise TrainInput and TrainOutput throw ChainDeclarationException while a chain is being read, and return default in QueueSubjectKey and OnQueue, which run on an instance with no metadata; the docs said only that they return default before a run. WorkQueue's parameterless constructor leaves ConfirmedAt null, and EF writes that null explicitly, so an entry built that way is saved as staged and never dispatched; the docs now say so and point to WorkQueue.Create. TrainState had two remarks blocks, of which only one renders; they are merged. AGENTS.md summarised effect 0004 in central 0013's words and now uses its own. ADR 0006's overlong lines are wrapped, and 0005 and 0006 name the tests that now pin them. * test: split migration 041 with DbUp's own splitter in the mid-migration writer test The test cut 041 into statements with a hand-rolled split that dropped comment lines and broke on every semicolon, which only matched production while the script avoided quoted or inline semicolons and dollar-quoted bodies. It now uses PostgresqlConnectionManager's SplitScriptIntoCommands, the splitter DatabaseMigrator's PostgresqlDatabase runs, so the statement boundaries an older writer can land between are production's by construction. * test: bound the wait for a train body in the cancelled-outcome tests Both tests awaited the body's start signal with no ceiling, so a run that faulted before its body started hung the suite instead of failing. The wait now races the signal against the run under a 15 second ceiling, rethrows the run's exception when the run ends first, and releases the parked body in finally so no run outlives a failed test. * chore: build against Trax.Core 1.7.2 Core 1.7.2 is the release that carries FailureClass, ChainVerification and the Junctions()-only chain, which this branch builds on. Only Trax.Core and Trax.Core.Testing move; no Microsoft.Extensions floor changed. * fix: backfill confirmed_at only on rows that can still be dispatched Migration 041 rewrote every work_queue row. work_queue keeps every dispatched row until metadata cleanup removes it, and on 2M rows the UPDATE took 17 s, doubled the table and held a row lock on each one, so no other instance could dispatch until it finished. Startup runs migrations synchronously, so a probe that killed the pod mid-UPDATE would roll it back and start over. Only queued and cancelled rows are backfilled now, plus dispatched rows from the last day: a failed dispatch goes straight back to queued without touching confirmed_at, so one in flight across the migration must come out confirmed. Older dispatched rows keep a null nothing reads. ix_work_queue_unconfirmed is restricted to queued rows to match the sweep's predicate, so that history and the staged rows the sweep has cancelled stay out of it. SQLite's 007 does the same with its integer statuses; it has one writer, so it needs no recent-rows allowance. Neither migration has been released. * fix: run each SQLite migration in its own transaction DbUp ran the SQLite scripts without a transaction. SQLite has no ADD COLUMN IF NOT EXISTS, so 007-009 adding a column and then failing, or the process dying before DbUp journaled the script, would fail every later startup on "duplicate column name" and need the database repaired by hand. SQLite DDL is transactional, so a script now lands whole with its journal row or not at all. * feat: build a work queue entry only through WorkQueue.Create An entry built with new WorkQueue { ... } leaves ConfirmedAt null, and EF writes that null, so it was saved as staged, never dispatched, and cancelled by the stale-staged sweep with no error. The parameterless constructor is now protected, still reachable by EF and by System.Text.Json through [JsonConstructor], and PersistentWorkQueue, which only holds the EF configuration, gets a private one so it is not a way around Create. Create also refuses a subject key that is empty or longer than the new WorkQueue.MaxSubjectKeyLength (512). Until now only the mediator checked the key, so an entry built from CreateWorkQueue could carry one the index cannot claim.Configuration menu - View commit details
-
Copy full SHA for de45880 - Browse repository at this point
Copy the full SHA de45880View commit details
This comparison is taking too long to generate.
Unfortunately it looks like we can’t render this comparison for you right now. It might be too big, or there might be something weird with your repository.
You can try running this command locally to see the comparison on your machine:
git diff v1.54.0...v1.54.2