Skip to content

Spec 017 phase 4: tranche 2a compile repairs — 234 blocks build, ≤ 60 met, #4443 and #4444 filed - #193

Merged
iancooper merged 21 commits into
masterfrom
spec/017-phase4
Sep 28, 2026
Merged

iancooper merged 21 commits into
masterfrom
spec/017-phase4

Conversation

@iancooper

Copy link
Copy Markdown
Member

Summary

Phase 4 of spec 017 repairs the 22 tranche 2a pages, the Outbox and Inbox pages with one hard block each. Every page is now whole: each C# block on it either builds against Brighter 10.7.0, or is listed with its reason in tasks.md § Blocks that stay FAILED. The only listed block is ReplayOnSeenReference.md #1. It uses CausationId, which exists only on Brighter master (P2-2).

This PR changes the published site, so it needs your sign-off to merge. After the merge, may I delete the head ref spec/017-phase4?

Defects found by running the blocks, repaired everywhere they occur

git diff --name-only origin/master..HEAD -- contents lists 28 pages:

  • 21 of the 22 tranche pages;
  • 2 pages changed by recurrence repairs;
  • 5 pages changed on your rulings: four for #4335 and SweeperCircuitBreaking.md.

Inbox pages

  • InMemoryInbox.md said entries are kept until the process restarts. In fact an entry expires 5 minutes after it is written, and past EntryLimit the store compacts. The page also showed a global Warn configuration beside an attribute whose default of Throw wins over it. I ran both claims with controls, and the page now describes the actual behaviour.
  • Inbox configuration blocks had three compile errors:
    • a ConfigureServices lambda that was never opened, on 13 lines across 8 pages;
    • opt. used inside a lambda whose parameter is options;
    • a ; inside an object initialiser.
  • [UseInboxAsync] was placed on a class, where it is not valid.
  • One outbox handler called await Post(…, cancellationToken), but Post is synchronous and takes no cancellation token.

Outbox pages

  • The EF Core outbox pages, DapperOutbox.md and DynamoOutbox.md showed methods without their class. They are now shown whole.

Distributed lock pages

  • All six pages now compile and needed no content change. On MSSQL, MySQL and Postgres in Docker I confirmed that a lock is released when its holder's session dies.

Azure Blob archive and sweeper circuit breaking

  • AzureBlobArchiveProvider.md had eight defects in its one block. For example:

    • AzCliCredential does not exist; the class is AzureCliCredential.
    • The options type has no parameterless constructor.
    • BlobContainerUri is a Uri, not a string.
    • BatchSize is actually ArchiveBatchSize.

    The block is rewritten against 10.7.0. The page gains an options table and the two packages the provider does not bring in.

  • UsingSweeperCircuitBreaking.md:

    • The custom breaker used a CircuitBreakerState type that nothing declares. It also used a plain Dictionary, which threw "Collection was modified" when trips and cooldowns ran concurrently.
    • The "distributed" breaker could never report its tripped topics, because IDistributedCache cannot list its keys. It is now a complete breaker on a Redis sorted set, run against Redis with two connections.
  • SweeperCircuitBreaking.md, repaired on your ruling:

    • The page set the sweep interval through OutboxSweeperOptions.SweepInterval, which does not exist. The real setting is UseOutboxSweeper(o => o.TimerInterval = …).
    • The cooldown formula was one sweep short. A tripped topic is retried after (CooldownCount + 1) × TimerInterval. I confirmed this with a real sweeper and a failing producer.
    • The page claimed circuit breaking works with every Outbox. The DynamoDB and Spanner Outboxes ignore tripped topics; I ran both against DynamoDB Local and the Spanner emulator, with SQLite and MongoDB as controls. The page now has a table of which Outboxes honour a trip.
    • The page also called UseMongoDbOutbox, which does not exist.
    • The page contradicted itself about explicit clearing. In fact ClearOutbox and ClearOutboxAsync send messages for tripped topics. A failed ClearOutboxAsync trips the topic; a failed ClearOutbox does not.

Upstream issues, handled on your rulings:

Every block that asserts behaviour was run against the released packages, with a control. The tables are in tasks.md § Phase 4 as executed.

Gates

# Gate Result
1 linkcheck 165 files, 0 broken
2 pagelint 0 errors; warnings 658 → 616: 36 fewer on the tranche pages, which was the predicted floor, and 6 fewer on two pages outside the tranche
3/4/7 shape / redirects / --verify unchanged: 161 / 77 / 161
5 versioncheck 0 stale of 18, across 5 pages
6 optioncheck 0 mismatches, 59 tables, 519 rows
8 symbolcheck 0 findings, 22 entries, 3 silenced
9 blockcheck 983: 234 BUILT (was 189; predicted 204–230), 733 FAILED, 16 SKIPPED; 35 scaffold units, 0 violations; 542 reference assemblies

Gate 9 is 4 above the ceiling. Five blocks moved on pages outside the tranche: two by recurrence and three on your ruling. One block the ceiling counted, TickerQScheduler.md #2, still fails because of the statements wrapper. attr_mismatch.py holds at 7.

The pin grows by three packages, to 98: Npgsql.EntityFrameworkCore.PostgreSQL, TickerQ.Dashboard and TickerQ.EntityFrameworkCore. I measured it on its own first.

The AC2 diff against c7329bb prints 135 lines:

  • 134 are FAILED -> BUILT: 89 from phases 2 and 3, and this phase's 45.
  • The 135th is SchedulingAMessage.md Update documentation #10, which § Splits already records.

Pages with nothing BUILT: 74 → 57. The ≤ 60 target is met.

Outside contents/, the PR changes:

  • tools/README.md: rows 2 and 9, and a paragraph for phase 4;
  • the blockcheck baseline, refs.csproj and pages.tsv (36 → 53 pages mapped);
  • 5 new scaffold units and 4 changed ones;
  • spec/017-compile_repairs/tasks.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy

iancooper and others added 21 commits September 27, 2026 18:36
From master d8633b1, all nine gates at tools/README.md's figures. The probe
reproduces the phase 4 table (40 FAILED, 18 reachable, 22 hard, 6 BUILT).
blockcheck BUILT 189 -> 204..230; pagelint 658 -> 640..622; pages with
nothing BUILT 74 -> 66..58. The pin grows by three (Npgsql EF Core, two
TickerQ packages); SqlServer, Sqlite and Pomelo are already transitive.

22 hard blocks read: 15 parse, 7 other. Six parse blocks carry a defect
beside the placeholder; their recurrences grepped (the unopened
ConfigureServices lambda: 13 on 8 pages, 7 off-tranche).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
…ckages

98 PackageReferences, 542 reference assemblies. Measured alone: one block
moves, TickerQScheduler.md #3, FAILED -> BUILT. #2 now fails only on
Program, the statements wrapper's limitation (Q2), not on the pin.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
… #4335 stated as run

The thirteen outbox and inbox pages of tranche 2a build whole: using
directives, four units, the unopened ConfigureServices lambda repaired at
all 13 recurrences on 8 pages, the free ConfigureServices put in a Startup
class, the handler bodies given the class the samples declare.

Defects, each verified at 10.7.0: [UseInboxAsync] on a class (CS0592);
Post awaited with a cancellationToken it does not take; a ';' inside an
object initialiser; the InMemory Inbox said to keep entries until restart,
where they expire after EntryTimeToLive; the page's Warn configuration beside
an attribute whose default Throw wins. A global InboxConfiguration is ignored
without AddProducers (BrighterCommand/Brighter#4335, fixed on master, in no
release): stated once on Inbox Support, linked from the nine inbox pages.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
Twenty-two on the outbox and inbox pages, BrighterBasicConfiguration.md #3
and #4 by the lambda recurrence; MSSQLOutbox.md and PostgresOutbox.md #1
re-admitted with RelationalOutboxContext.cs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
…three

Each lock page's two blocks take their using directives, and the Outbox
placeholder becomes a value the new DistributedLockProviderContext unit
supplies: opt.Outbox = outbox; // your … Outbox. No defect: every prose claim
matches 10.7.0, and the session-scoped release on MS SQL, MySQL and Postgres
was run against real servers with a control.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
UsingSweeperCircuitBreaking.md: using directives on blocks 2 and 3, and
`services` in the page's unit. Block 4 named a CircuitBreakerState no
package or block declares, and shared a Dictionary between TripTopic and
CoolDown; it now declares its state and uses a ConcurrentDictionary. Block
5 left CoolDown and TrippedTopics unwritten, and an IDistributedCache
cannot list what is tripped; it is now whole, on a Redis sorted set.

AzureBlobArchiveProvider.md: the block is rewritten against 10.7.0 —
AzureCliCredential, not AzCliCredential; the options' constructor and Uri;
UseOutboxArchiver's TTransaction; ArchiveBatchSize; a TimeSpan MinimumAge;
the option assignments through `options.`. The page gains its opening
sentence, the two packages the provider does not bring in, and its options.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
…h nothing built

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
…cooldown right

SweeperCircuitBreaking.md set the sweep interval through
options.OutboxSweeper = new OutboxSweeperOptions { SweepInterval = … },
which does not exist: UseOutboxSweeper takes a TimedOutboxSweeperOptions,
whose TimerInterval is an int of seconds. Blocks 2 and 7 now configure it
there, and build.

The cooldown formula was also one sweep short. A tripped topic sits out
CooldownCount sweeps and is retried on the next, so the time until retry
is (CooldownCount + 1) × TimerInterval — run against a real sweeper with a
failing producer. The step list and UsingSweeperCircuitBreaking.md's
comments now say so.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
…ling; 233 built

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
…earing does

SweeperCircuitBreaking.md said circuit breaking "works with all Brighter
Outbox implementations", beside a UseMongoDbOutbox that does not exist.
The sweeper passes TrippedTopics to the Outbox, and at 10.7.0 the
DynamoDB Outbox (V3 and V4) ignores it and Spanner's query drops it — run
against DynamoDB Local and the Spanner emulator, with SQLite and MongoDB as
controls. The section is now a table of which Outboxes honour it, and the
MongoDB block registers the Outbox as MongoDBOutbox.md does.

The page also said both that explicit clearing is "NOT subject to circuit
breaking" and that ClearOutboxAsync "respects circuit breaker state". Run:
an explicit clear sends a tripped topic's messages, and a failed
ClearOutboxAsync trips the topic where a failed ClearOutbox does not. The
bulk section now shows UseBulk on the sweeper, which honours trips.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
…age's rows with its unit

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
…learing claims; 234 built

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
…4444

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
@iancooper
iancooper merged commit 976e0e0 into master Sep 28, 2026
10 checks passed
@iancooper
iancooper deleted the spec/017-phase4 branch September 28, 2026 05:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant