Skip to content

Spec 017 phase 5: tranche 2b and the recorded falsehoods — 295 blocks build, both targets met - #194

Merged
iancooper merged 27 commits into
masterfrom
spec/017-phase5
Sep 28, 2026
Merged

iancooper merged 27 commits into
masterfrom
spec/017-phase5

Conversation

@iancooper

Copy link
Copy Markdown
Member

Summary

Phase 5 of spec 017 repairs the 16 tranche 2b pages, which are the remaining pages with one hard block each, and fixes the recorded falsehoods (P0-7): ITimerProvider, the unshown Order, and the sync attributes on async handlers. Every tranche page is now whole: each C# block on it either builds against Brighter 10.7.0 / Darker 4.1.1, is skipped with an accepted reason, or is listed with its reason in tasks.md § Blocks that stay FAILED.

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-phase5?

git diff --name-only origin/master..HEAD -- contents lists 53 pages: 15 of the 16 tranche pages (AggregationQueryPatterns.md was made whole by its scaffold unit alone), and 38 pages outside the tranche, changed by P0-7, by repairing recurrences, and on your rulings.

Defects found by running the blocks, repaired everywhere they occur

Commands, pipelines and Darker

  • BuildingAPipeline.md used .Successor =, but the method is SetSuccessor(). The page also said a manual chain runs from the registry alone; in fact a factory must return the wired instance.
  • [UseResiliencePipeline] was stacked on one method (CS0579) on 5 pages. The attribute is not repeatable, so the strategies are now layered in one pipeline. PolicyRetryAndCircuitBreaker.md described Polly's strategy order backwards: the first strategy added is the outermost, so the example's 10 s timeout covered every retry together.
  • Handlers declared without public never ran, because AutoFromAssemblies() scans only public types. They are now public on 7 pages (your ruling).
  • Darker's query processor was said to default to Transient. It is Singleton, and resolves handlers from the root provider. Also corrected: Darker's handler scan rules, and Activator.CreateInstance as a factory.
  • "Cannot use AutoFromAssemblies" with an Agreement Dispatcher was wrong on three pages. The scan works; this was run with a control.
  • Monitoring.md is rewritten for V10 (your ruling). Two upstream defects found while running it are stated on the page.

Transports, external bus and observability

  • PostgreSQLMessageBroker.md had 11 defects. For example:
    • A consumer whose channelName differed from the publication's Topic received nothing.
    • PublishAsync was said to send to the broker; it writes no row.
    • A delay was said to set visible_timeout.
    • [ClaimCheck] was shown with parameters it does not have.
    • A subscription without messagePumpType throws. This last defect recurred on non-generic subscriptions on 5 more pages.
  • Tracing never recorded a Brighter span. AddSource(…) alone is not enough; AddBrighterInstrumentation() is needed. Telemetry.md is rewritten, including its span names, attributes and propagation headers as captured on the wire. ConfiguringOpenTelemetry.md now points the OTLP exporter at Jaeger instead of using the deprecated Jaeger exporter.
  • CloudEvents header names and the content mode are now as captured on the wire. Extension properties reach the message only through CloudEventJsonMessageMapper<>.
  • S3LuggageStore.md said ACLs default to Private; they are null.
  • The Control API page showed V9's status JSON and described subscription names as topics.

P0-7 and what stood beside it

  • ITimerProvider does not exist: 4 → 0. The InMemory scheduler's time seam is InMemorySchedulerFactory.TimeProvider, and the fake-clock example is run with FakeTimeProvider. The page's Paramore.Brighter.InMemoryScheduler package does not exist either.
  • Order is now shown in CQRSWithBrighterAndDarker.md. Showing it surfaced a CS0029 (Id assigned to a Guid key) and a CancellationToken passed where PublishAsync takes a RequestContext.
  • Six sync attributes on HandleAsync → the async form. These pipelines throw ConfigurationException when built. attr_mismatch.py now finds exactly one hit, the deliberate Before (error) example at PipelineValidation.md:250, whose prose now says it is wrong. The published After (fixed) example on that page itself reported an error. It now reports nothing, run with ValidatePipelines().
  • PublishAsync swept wherever it meant the bus (your ruling). It dispatches to handlers in this process only.
  • The Avro mapper in DefaultMessageMappers.md is rewritten against Confluent's API (your ruling). This is the one pin change besides FakeTimeProvider: 98 → 100 packages, each measured alone, and no verdict moved.
  • Sync and async take different mappers. ReactorAndProactor.md said mappers have no async variants. MessageMappers.md now shows IAmAMessageMapperAsync<T> and which call uses which. A pump or a producer call given only the other kind of mapper silently uses the default mapper instead; all four producer calls and both pumps were run.
  • V10MigrationGuide.md § 4 built a registry Brighter never reads, and § 5 listed IRequestContext members that do not exist. InstrumentationOptions was said to be "added in 10.7.0"; it ships after 10.7.0 and is now marked as not yet released. Replay validation is marked the same way on three pages.
  • UsingTheContextBag.md's examples now post with the context they fill. Bag keys act only on the context passed to the call that makes the message (run, with a control).

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

Upstream issues, filed in this phase on your word: BrighterCommand/Brighter#4453 and #4454 (monitoring), #4458 (AWS CloudEvents souce), and #4465 (Control API wrong-case 500). Each is stated on its page.

Gates

# Gate Result
1 linkcheck 165 files, 0 broken
2 pagelint 0 errors; warnings 616 → 537. 35 fewer are on the tranche pages, inside the predicted band; 19 on the P0-7 pages; 25 on 13 other pages, from recurrences and rulings
3/4/7 shape / redirects / --verify unchanged: 161 / 77 / 161
5 versioncheck 0 stale of 18
6 optioncheck 0 mismatches, 59 tables, 519 rows
8 symbolcheck 0 findings, 22 entries, 3 silenced
9 blockcheck 990 blocks: 295 BUILT (234 before), 678 FAILED, 17 SKIPPED; baseline 295; 45 units, 0 violations; 545 reference assemblies
  • pagelint --changed origin/master exits 0.
  • The AC2 diff against c7329bb prints 207 lines. 194 are FAILED -> BUILT; the other 13 are the accepted skip, or insertions and splits recorded in § Splits. The diff was checked with controls in both directions.
  • Both spec targets are met. BUILT is 295 against a target of ≥ 250. Pages with nothing BUILT are 41 against a target of ≤ 60, and 57 before this phase.
  • BUILT is 1 above 5.1's predicted ceiling of 294, and the pin grew against a prediction of no change. Both are reconciled in § Task 5.6.

🤖 Generated with Claude Code

https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy

iancooper and others added 27 commits September 28, 2026 08:17
…ard block

Nine gates at 976e0e0, all exit 0. BUILT 234 -> 246..294; the floor is 4 short
of >= 250. Pages with nothing BUILT 57 -> at most 51, as low as 45. pagelint
616 -> 582..573 before P0-7. attr_mismatch 7 -> 1 (PipelineValidation.md:250);
ITimerProvider 4 -> 0.

The tranche reads 15 hard, not 16: task 2.4's UseEndpoints repair left
BrighterControlAPI.md #1 needing only an `app` stub. Hard blocks read as 5 parse,
10 other (six defects, verified at 10.7.0 / Darker 4.1.1), recurrences run.
The [RetryableQuery] ledger grep said 9 lines; any second argument finds 20.

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

Ten tranche pages repaired, and the recurrences their defects reach on eight more:

- BuildingAPipeline.md: the pre-V10 logging handler rewritten against 10.7.0; `.Successor =`
  is `SetSuccessor()`; how a custom generic pipeline handler is registered, run with controls
- AgreementDispatcherRouting.md, AgreementDispatcher.md, FAQ.md: "cannot use
  AutoFromAssemblies" is false — pass the route's handlers in `excludeDynamicHandlerTypes`
- DarkerConfigurationReference.md, DarkerBasicConfiguration.md: the query processor defaults to
  Singleton, not Transient, and the unscoped failure is a root-provider resolution, not a
  disposed DbContext; `Activator.CreateInstance` cast to Darker's factory types (and on
  ImplementAQueryHandler.md)
- [RetryableQuery]'s second argument is the one policy the decorator runs: QueryPipeline.md,
  QueryPatterns.md, CQRSWithBrighterAndDarker.md, ImplementAQueryHandler.md,
  DarkerAndBrighterPipelines.md; QueryPipelinePolicies.md shows a retry wrapped round a breaker
- HowConfiguringTheDispatcherWorks.md: V10's MessageMapperRegistry; RmqSubscription<T>
  defaults to Reactor
- The Darker query-pattern pages, CQRSUseCasesAndPatterns.md: usings, whole blocks, and six
  scaffold units for the types the pages never show

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
28 rows at 1cafef9, and three re-admissions: ParameterizedQueryPatterns.md #3, #5 and
ProjectionQueryPatterns.md #4, BUILT before and now compiled with DarkerQueryPatternsContext.cs.
--report: exit 0, 985 blocks: 262 BUILT, 706 FAILED, 17 SKIPPED; 40 units, 0 violations.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
BUILT 234 -> 262; pagelint 616 -> 599; pages with nothing BUILT 57 -> 49. Nine defects and the
carried [RetryableQuery] row in the ledger, twelve blocks listed as staying FAILED, one split, and
four findings off the tranche put to the maintainer.

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

- Monitoring.md rewritten for V10: MonitorConfiguration and a control bus sender in the
  container, [Monitor] on the handler, the message format captured from a run, and two 10.7.0
  defects stated (a throwing handler's exception is replaced; [MonitorAsync] cannot send through
  ControlBusSenderFactory's sender)
- HowConfiguringTheDispatcherWorks.md: Topshelf retired; the Dispatcher run from a console app
  until Ctrl+C, End() letting the pump finish its message
- Handlers declared without `public`: 18 on 6 pages made public, and
  CommandProcessorConfigurationReference.md says the scan finds only public handlers
- DarkerBasicConfiguration.md: Darker's scan takes exported types; names and nesting in a public
  class do not matter
- Found by --explain on the blocks those touched: [UseResiliencePipeline] stacked on one method
  (CS0579) on five pages, now one composed pipeline; Polly v8's first strategy is the outermost,
  so MyComprehensivePipeline ran its timeout around all its retries; a FeatureSwitches.md handler
  awaited without `async`

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
HowConfiguringTheDispatcherWorks.md #3 and Monitoring.md #1, #2. --report: exit 0, 985 blocks:
265 BUILT, 703 FAILED, 17 SKIPPED.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
BUILT 262 -> 265; pagelint 599 -> 585; pages with nothing BUILT 49 -> 48. Seven defect rows,
two of them upstream and stated, filing put to the maintainer. The Topshelf row leaves
§ Blocks that stay FAILED: the block builds.

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
… repaired

Work in progress, not yet gated: baseline rows, tasks.md records, the
S3 ACL fix and the carried 5.1 items are still to come.

- PostgreSQLMessageBroker.md: consumer channelName must equal the
  publication Topic; PostAsync, not PublishAsync, reaches the table;
  scheduled messages go through the scheduler, visible_timeout delays
  only a requeue; outbox deposit takes the transaction provider;
  claim check on a mapper; messagePumpType on every subscription; SQL
  no longer names a created_at column
- PostgreSQLBrokerTradeOffs.md: JSONB/JSON fence split; message size
  measured (50 MB round-trips, 150 MB rejected); SQS 1 MiB
- CloudEventsReference.md, CloudEventsSupport.md: header names as
  captured from RabbitMQ and Kafka; the mapper chooses the content
  mode; Kafka partition key from the request context; souce, #4458
- Telemetry.md, ConfiguringOpenTelemetry.md: spans need a registered
  tracer (AddBrighterInstrumentation); tables rewritten from captured
  spans; Jaeger exporter replaced by OTLP
- S3LuggageStore.md: link label, IHttpClientFactory, list nesting
- Units for BrighterControlAPI, CloudEventsReference, PostgreSQL
  (both pages) and Telemetry

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

- S3LuggageStore.md, HandlingLargeMessages.md: ACLs has no default; a
  missing bucket with none set throws ConfigurationException (run on
  LocalStack, with a control that sets ACLs and creates the bucket)
- BrighterControlAPI.md: list un-indented; response captured from a V10
  Dispatcher; availableTopics/topicName are subscription names; PATCH
  takes the subscription name, case-sensitive; custom base route run
- KafkaConfiguration.md, MessageMappers.md: MessageBody takes a
  ContentType, not a string (as the KafkaSchemaRegistry sample does)
- Routing.md, V10MigrationGuide.md, NullableReferenceTypes.md: mappers
  gain the member they were missing
- Command/Event subclasses with no base constructor call given one;
  V10MigrationGuide's default-mapper example posts rather than publishes
  (run: PostAsync puts one JSON message on the bus, PublishAsync none)
- Unit S3LuggageStoreContext.cs

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

24 blocks appended; PostgreSQLMessageBroker.md #11 and Telemetry.md #4
re-admitted with their units; Telemetry.md #1 and #4's rows removed, the
blocks now at #2 and #5 after the insertion above them. 288 required.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
- FAQ.md #7: MapToMessage returned nothing and MapToRequest was missing;
  the mapper now maps both ways, [RetrieveClaim] on the way back
- PostgreSQLMessageBroker.md, PostgresOutbox.md: the EF Core transaction
  provider brings the target framework's EF Core, so Npgsql's EF provider
  must match (9.x on net10.0 → MissingMethodException, run in session 101)

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

BUILT 265 → 288; pagelint 585 → 562; pages with nothing BUILT 48 → 44.
The maintainer's five rulings; 23 defect ledger rows; seven blocks that
stay FAILED on the pin; a split and two insertions; the Jaeger block
removed. Three questions put to the maintainer. 31 of 42 tasks ticked.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
99 PackageReferences, 543 reference assemblies. FakeTimeProvider is what
InMemoryScheduler.md's rewritten ITimerProvider block (task 5.4) sets as
the scheduler's clock. Measured alone, no page changed: 987 blocks, 288
BUILT, and no verdict moves.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
100 PackageReferences, 545 reference assemblies (Avro.dll and the
serdes). The Avro serializers DefaultMessageMappers.md's rewritten mapper
calls (task 5.4, the maintainer's ruling on the Avro block), at 2.15.0,
the Confluent release Paramore.Brighter.MessagingGateway.Kafka 10.7.0
depends on. Measured alone, no page changed: 987 blocks, 288 BUILT, and
no verdict moves.

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

InMemoryScheduler.md: ITimerProvider 4 -> 0. The scheduler creates its
timers from InMemorySchedulerFactory.TimeProvider; block #5 is now a
FakeTimeProvider test, run with controls (advance 5m -> handled before
Advance returns; none, 4:59 and 2.5s wall clock -> not). The package it
named, Paramore.Brighter.InMemoryScheduler, is a NuGet 404.

CQRSWithBrighterAndDarker.md: the Order write model is shown (#7, BUILT).
Behind it, Id = command.Id was CS0029 against the read side's Guid key,
and #2 passed a CancellationToken positionally as RequestContext (CS1503).

BrighterSchedulerSupport.md: the scheduled overloads listed the request
before the delay and dropped RequestContext and args; now as 10.7.0.

DefaultMessageMappers.md: the Avro mapper rewritten against Confluent's
API (maintainer's ruling) as an async mapper constrained to
ISpecificRecord, run end to end against a schema registry. A sync mapper
is bypassed by PostAsync, and as the default a non-Avro type throws.

PublishAsync sweep (maintainer's ruling): three places relied on
PublishAsync reaching a mapper or a bus, now PostAsync; one practice
that published dummy events to warm caches removed. CloudEvents
extension properties are written only by CloudEventJsonMessageMapper,
measured, and said so on three pages.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
Five rows: InMemoryScheduler.md #5, CQRSWithBrighterAndDarker.md #7,
BrighterSchedulerSupport.md #1, DefaultMessageMappers.md #4 and #5.
--report exit 0: 989 blocks, 293 BUILT, 679 FAILED, 17 SKIPPED;
baseline 293, 0 findings.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
Filed on the maintainer's word (2026-09-28) and linked where the page
tells readers to match the subscription name's case.

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

§ Phase 5 as executed gains 5.4's entry: the maintainer's three rulings,
two pin growths measured alone, the behaviour runs and their controls,
BUILT 288 -> 293, pagelint 562 -> 553, pages with nothing BUILT 44 -> 42.
Four § Blocks that stay FAILED rows, two § Splits rows, one § Blocks
removed row, ten § Defect ledger rows. Task 5.4 ticked: 32 of 42.

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

Five sync attributes on async handlers, each accepted by the compiler and
each a ConfigurationException when the pipeline is built, now the async
form: HowServiceActivatorWorks.md (UseInboxAsync), PipelineValidation.md
x2, PolicyRetryAndCircuitBreaker.md, ReactorAndProactor.md and
V10MigrationGuide.md (UseResiliencePipelineAsync). attr_mismatch.py 7 -> 1:
the deliberate Before (error) example, now wrong in prose as well.

PipelineValidation.md: the step-order examples also named an argument
before a positional one, and the Before comments called step 0 inner,
against the page's own rule. The published After (fixed) reported an
error under ValidatePipelines; run, it now reports none, the Before one
warning. The example warning message now spells attribute names as the
validator does.

V10MigrationGuide.md: the pipeline registry went to the obsolete
PolicyRegistry (a type mismatch) and was built with a generic builder
Brighter never reads; now ResiliencePipelineRegistry, from
AddBrighterDefault (without it, ConfigurationException, run). Section 5
listed IRequestContext members 10.7.0 does not have (PartitionKey,
CustomHeaders, Guid Id, ISpan) and omitted five it does; now as 10.7.0,
with partition key and headers set through the Bag, run with a control.
IRequestContext.InstrumentationOptions said "added in 10.7.0"; it is
after 10.7.0, and now marked Not in a released package yet.

ReactorAndProactor.md: said mappers have no async variants and advised
Task.Run wrappers. A Proactor maps with IAmAMessageMapperAsync<T>, and a
pump given only the other kind of mapper uses the default one silently,
run both ways with controls.

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
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
…ontext you post with

By the maintainer's ruling, the three items 5.5 found and left.

PipelineValidation.md: the Replay rule, its two example messages and the
Replay Without Causation Tracking section describe a feature after
10.7.0 (no causation code in the tag); now marked Not in a released
package yet. The deliberate :250 example keeps its line.

MessageMappers.md: the mapper's home page never mentioned
IAmAMessageMapperAsync<T>. A new section shows one and says which path
uses which kind: Post, DepositPost and a Reactor map with the sync
mapper; PostAsync, DepositPostAsync and a Proactor with the async one;
a type with only the other kind goes through the default mapper,
silently. Run for all four calls.

UsingTheContextBag.md: five examples set the partition key, headers or
CloudEvents extensions on a handler's own Context and posted nothing,
or passed them to SendAsync. Run: the keys act only on the context
passed to the call that makes the message; a handler posting without
one gets a fresh context, and a caller's context does not carry through.
Each example now posts with the context it fills, and the page says why.

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

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

The Replay recurrence grep found two more pages naming OnceOnlyAction.Replay
as available with no marker: DispatcherConfigurationReference.md
(ActionOnExists) and BrighterOutboxSupport.md (the Sweeper). Both now say
it ships after 10.7.0. UsingTheContextBag.md #4 wrote the PartitionKey
object to Context under an example that now fills its own context.

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
… 2 and 9

All nine gates exit 0 against 5.1's prediction; BUILT 295 (1 over the
ceiling, reconciled), pages with nothing BUILT 41, pin 98 -> 100.
Two Splits rows and 5.2's unit count corrected to what was measured.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Am29wrQUS81NVh8dUyNuy
@iancooper
iancooper merged commit 960ce6d into master Sep 28, 2026
10 checks passed
@iancooper
iancooper deleted the spec/017-phase5 branch September 28, 2026 19:58
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