You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(flex-chain): guarded repair tool that restores hours and re-walks the chain - #909
A tool for repairing historic flex balances with the product's own chain code (sub-project 3 of the 2026-09 flex drift programme). Design: docs/superpowers/specs/2026-09-27-flex-chain-repair-tool-design.md, plan: docs/superpowers/plans/2026-09-27-flex-chain-repair-tool.md.
FlexChainRepair.RepairWorkerAsync handles one worker inside one transaction:
locks the worker's rows;
guards every restore line against the version and hours seen at dump time;
restores the hours;
carries the chain with FlexChainRecompute.RunForwardAsync from the first row;
checks every balance against the expected values, two-sided and with a continuity check.
It commits only with apply set and every check passing. A dry run (the default) always rolls back.
FlexChainRepair.RevertAsync puts back one worker's rows from the before-image, only where a row is still at the version the repair left.
FlexChainRepairCsv reads and writes the restore manifest, the expected balances and the before-image (invariant culture).
A thin console under tools/FlexChainRepair is not in the .sln and not packed.
No change to RunForwardAsync, CarryChain, DayLock or any entity.
Tests
FlexChainRepairTests (21) and FlexChainRepairCsvTests (6) run against MariaDB in CI. They cover:
apply and dry run;
the guard: stale version, changed hours, another worker's row, a removed row, duplicate lines;
an open shift restored to 0, and a break healed by the walk alone;
expected mismatches and missing rows, and continuity for rows without an expected entry;
locked days and version history;
revert, including skipping rows changed after the repair.
…e-walks the chain
RepairWorkerAsync locks the worker's rows, checks every restore line
against the version and hours seen at dump time, restores the hours,
carries the chain with RunForwardAsync, and compares every balance
with the expected values (two-sided, plus continuity). It commits only
on apply with every check passing; a dry run always rolls back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDsVLaH7SqGi1bFk8nAtP1
RevertAsync writes each captured row back only where the row is still
at the version the repair left, under a row lock, dry run by default.
One worker per call.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDsVLaH7SqGi1bFk8nAtP1
- revert skips a row whose worker differs from the before-image's
- re-check the reconciled boundary under the row lock
- a worker with no live rows still fails its restore lines / expected entries
- CSV rejects NaN and infinities, naming line and column
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDsVLaH7SqGi1bFk8nAtP1
Dry run by default; --apply requires --confirm-database to match the
connection's database. One fresh context per worker. The before-image of
each applied worker is written and flushed to disk before anything else,
and an apply run stops at the first worker whose state is unknown.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDsVLaH7SqGi1bFk8nAtP1
- reject a NaN, infinite or negative tolerance
- a lost commit acknowledgement counts as landed only for this worker's
rows written during this call
- console checks every expected/restore Id against its row's worker,
and refuses a walked worker with no expected balances
- a value flag followed by another flag is an invocation error, so
`--out --apply` can never turn into a dry run
- unreadable input files exit 2; README adds a pre-apply snapshot step
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDsVLaH7SqGi1bFk8nAtP1
This implementation-plan bullet is now inconsistent with the shipped console: --apply stops after the first worker exception, and revert invokes RevertAsync once per worker group. Keeping the plan's "loop continues"/single-call description can mislead future changes and operators about the safety boundary; update it to match the current per-worker stop behavior and grouping.
…against the oracle
A live row with no expected balance and an Id at or below the highest
expected Id existed when the oracle was built, so it is a mismatch
("Uncovered") instead of passing on continuity alone. The console checks
the same across the whole file before any worker runs, which also catches
a file missing a worker's newest rows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDsVLaH7SqGi1bFk8nAtP1
…e Ids up front
RepairWorkerAsync refuses NaN/infinite restore or expected values before
touching the database (every check is `abs(a - b) > limit`, which a NaN
passes). The console rejects duplicate restore Ids before any worker runs,
so a malformed manifest cannot surface only after earlier workers committed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDsVLaH7SqGi1bFk8nAtP1
This implementation plan still says a thrown worker is caught and the loop continues, but the delivered console intentionally stops on --apply (stop = apply in Program.cs) because the commit may have landed without an acknowledgement. Update this operational description to distinguish dry-run continuation from apply stop; otherwise the plan gives conflicting guidance for handling an uncertain worker outcome.
…tes up front
RepairWorkerAsync refuses an expected entry filed under another row's key
or another worker. The console rejects duplicate before-image Ids before
any revert group runs. The plan now describes the delivered stop-on-error
behaviour of an apply run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDsVLaH7SqGi1bFk8nAtP1
Re the round-5 overview's "previously missed" note on the plan's worker-failure wording (no inline thread): valid — fixed in e1a26f2. The plan now says a dry run continues past a thrown worker while an --apply run stops there, matching the delivered console.
Add CI build and tests for FlexChainRepair project
tools/FlexChainRepair/FlexChainRepair.csproj:10
This executable is intentionally outside Microting.TimePlanningBase.sln, but the repository CI only runs dotnet build at the solution root and tests Microting.TimePlanningBase.Tests; it never builds or exercises this project. Consequently a compile/API regression in the repair command can merge undetected. Add an explicit CI build (and at least argument/preflight tests) for tools/FlexChainRepair/FlexChainRepair.csproj while keeping it out of the solution.
…air tool in CI
RevertAsync refuses NaN/infinite captured values before touching the
database, since they are written back verbatim. CI now builds
tools/FlexChainRepair (kept out of the solution) so a compile break in
the console cannot merge unnoticed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDsVLaH7SqGi1bFk8nAtP1
Re the round-6 overview's "previously missed" note (CI never builds tools/FlexChainRepair): valid — fixed in 20b3668. All three workflows now run dotnet build tools/FlexChainRepair/FlexChainRepair.csproj --configuration Release after the solution build, keeping the tool out of the .sln. Argument/pre-flight tests for the console are set aside: the console holds no repair logic (that is all in the library, with 33 repair tests and 8 CSV tests), and its argument handling and every pre-flight were exercised end to end against a local copy of real data — dry run, apply, idempotent re-run, revert to identical data, and each refusal path (truncated oracle, wrong-worker lines, duplicate Ids, --out --apply).
The repair walk picked any non-removed AssignedSite, unordered, resigned
ones included. It now uses the non-removed, not-resigned site with the
lowest Id — the same definition the console walks and the oracle uses —
so a resigned site's one-minute settings cannot shape the chain.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDsVLaH7SqGi1bFk8nAtP1
The pre-flight refused a run when an active worker had no expected
balances, even when that worker has no PlanRegistrations rows at all —
nothing to walk or check (the library already treats it as a no-op).
Only workers with live rows must now be covered by the oracle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDsVLaH7SqGi1bFk8nAtP1
The opening safety statement says that neither command writes anything during a dry run, but both RunRepair and RunRevert create and populate report CSVs under --out even when --apply is absent (and RunRepair also creates an empty before-image.csv). This can mislead operators about the files a dry run will create; clarify that dry runs roll back database changes but still write reports, or change the implementation to avoid those files.
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
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.
Summary
A tool for repairing historic flex balances with the product's own chain code (sub-project 3 of the 2026-09 flex drift programme). Design:
docs/superpowers/specs/2026-09-27-flex-chain-repair-tool-design.md, plan:docs/superpowers/plans/2026-09-27-flex-chain-repair-tool.md.FlexChainRepair.RepairWorkerAsynchandles one worker inside one transaction:FlexChainRecompute.RunForwardAsyncfrom the first row;FlexChainRepair.RevertAsyncputs back one worker's rows from the before-image, only where a row is still at the version the repair left.FlexChainRepairCsvreads and writes the restore manifest, the expected balances and the before-image (invariant culture).tools/FlexChainRepairis not in the.slnand not packed.No change to
RunForwardAsync,CarryChain,DayLockor any entity.Tests
FlexChainRepairTests(21) andFlexChainRepairCsvTests(6) run against MariaDB in CI. They cover:🤖 Generated with Claude Code
https://claude.ai/code/session_01VDsVLaH7SqGi1bFk8nAtP1