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
Tracking map of deepening candidates from the 2026-08-21 architecture review (post-0.2.0 IO stub generator pipeline, Proxy domain catalog, Feature runtimes, IDE edits).
This map does not ship code. Each item is grilled later as its own child (/grilling), then filed as one GitHub issue + PR per solution module (AGENTS.md). Do not implement from this map.
Related prior map: #212 (bug hunt; items 1–2 already grilled).
Notes
Lens: shallow modules, leakage across a seam, and confirmed bugs. Vocabulary: module, interface, depth, seam, adapter, leverage, locality.
Hot spot:0.2.0 extracted IoProxyGeneratorPipeline / ProxyRegistrationEmitter / ProxyDomainTable. Remaining friction is catalog wrappers, Parser walks, R3 vs Reactive subscribe clones, RestAPI ownership flags, IDE completion/code fixes, and eight identical *Service.For factories.
Do not re-open:ADR-001 (no third reactive backend); ADR-002 (no new Feature from this map).
Problem.ProxyDomainTable is the real table (markers, OBS*001/004, default boundary attributes). Four shallow modules wrap or re-list it. Runtime / Reactive package IDs live in ObservablesPackageReferenceMappings, not the table. Stub generators still hard-code "Observables.Redis.RedisAttribute" instead of reading the catalog. Adding Redis meant editing the catalog and a second package map.
Deletion test.ProxyDomainCatalog / ObservablesMemberDiagnosticIds / BoundaryAttributeDefaults are pass-through (just move). Folding empty-interface ids, package ids, and assembly names into one catalog concentrates.
Known invariant bugs.EmptyRestApiInterface (OBS3007) text says “HTTP method attributes”; EmptyProxyInterfaceAnalyzer only looks for [RestApi]. ProxyDomainTable.RestApi has empty method/property attributes.
Deepening shape (for grilling, not a spec). One Proxy domain catalog interface; analyzers and code fixes become adapters. Do not invent a second table.
Problem. Completion stores insert text in a property named InsertText and never implements GetChangeAsync; Roslyn inserts DisplayText. RestApiMemberCompletionProvider has no Feature marker gate, so HTTP verbs appear on SignalR/Mqtt interfaces (analyzers ship with every Feature package). ProjectFileWriter does File.WriteAllText on project.FilePath and returns the unchanged Solution (NoWarn=RS1035). OBS*005 offers “Add .Reactive” on an R3 generator, which walks the user into OBS0001.
Deletion test.CompletionItemFactory is just move. One IDE-edit module that owns GetChangeAsync, Feature gating, and workspace text concentrates.
Deepening shape (for grilling, not a spec). Catalog is the seam; completion and csproj edits are adapters. Tests hit that interface, not the filesystem.
Latent note from #212 residue.WebSocketConnect / WebSocketClose have no string ctor; catalog FormatAttribute would emit WebSocketConnect("Name")] if InsertText were wired.
Item 3 — Deepen Feature subscribe/dispose (per Feature, not Shared)
Module: each IO Feature (SignalR, Mqtt, WebSocket, Grpc, Sse, Nats, Postgres, Redis) — not Shared Strength: Strong · local-substitutable Blocked by decision:#215 (no Shared pump module) Open children already filed:#216 … #223
Problem. Two adapters (R3 *Observable vs SystemReactive*Adapter) already justify a Feature seam, but protocol (subscribe, map, cancel, unsubscribe) is copied. Reactive still uses _ = Pump() / _ = RunAsync(), linked CTS never Disposed, cancel often becomes OnError, MQTT never UnsubscribeAsync and can SubscribeAsync after dispose.
Deletion test. Deleting one adapter just moves the pump. Concentrating protocol inside the Feature (R3 and Reactive as thin adapters) is the remaining depth. A cross-Feature Shared pump was already rejected.
Grill later. Whether each open bug ticket (#216–#223) is enough, or a Feature-local protocol module (like SignalR HubConnectionArgs) should absorb R3+Reactive. Do not file a Shared pump ticket unless #215 is superseded.
Item 4 — Deepen RestAPI response ownership (leftovers after #214)
Problem. Generated code still passes isApiResponse; SendAsync still keeps a mutable disposeResponse beside ShouldDisposeResponse(typeof(TBody)). Classifier treats any generic named ApiResponse / IApiResponse as the RestAPI wrapper (name, no namespace). ParseNonHttpMethod can emit ReturnTypeInfo.Return for Observable<T> members the classifier would reject. RestService.For still falls through to Type.GetType + Activator unlike other Features’ GeneratedFactories.
Out of this item (still #212 leftovers, grill separately if needed). QueryUriFormat default 0; silent GET for custom HTTP attributes; AuthenticatedHttpClientHandler token getter.
Deepening shape (for grilling, not a spec).RestApiBridge owns lifetime; generated code stops passing dispose knowledge. Keep Task / IApiResponse classification in RestAPI (ROADMAP C1).
Item 5 — Push the IO stub generator pipeline into the Parser walk
Problem.0.2.0 deepened incremental wiring only. Eight Parsers still clone: GroupBy(SyntaxTree) → GetSemanticModel → marker scan → nullability → public members → HasAttribute. SignalR production Parser throws on AssemblyName == "GeneratorTests" + IInternalErrorProbe. OBS7004 is defined and never reported. IoProxyGeneratorPipeline / ObservableReturnTypeParser have no Shared tests (coverage is Feature snapshots). Stub generators do not read the Proxy domain catalog (marker strings can drift from item 1).
Deletion test. Extracting HasAttribute alone is just move. Pipeline walking marked interfaces, with Feature adapters only classifying members, concentrates. Templating per-Feature DiagnosticDescriptors was already rejected (docs/design/shproj-dedup-plan.md).
Keep Events off this pipeline (call-site driven; real seam). Keep RestAPI on CreateSyntaxProvider (C2).
Files:HubService.cs, SseService.cs, RedisService.cs, MqttService.cs, GrpcService.cs, NatsService.cs, PostgresService.cs, WebSocketService.cs (~2.6KB each). RestAPI RestService is not the same shape (two-arg factory, reflection fallback).
Problem. Eight modules whose interface is the implementation (RegisterGeneratedFactory + ConcurrentDictionary + For<T> + AOT attributes).
Deletion test. Deleting one *Service reappears at user call sites — each Feature interface earns its keep. The clone of the implementation does not. Eight adapters would justify a Core factory seam.
Callout. ROADMAP D1 deferred filling Observables.Core. #212 listed Core as out of scope (“empty project is not a user-facing bug”). Grill before reopening D1; do not silently put types in Core.
Destination
Tracking map of deepening candidates from the 2026-08-21 architecture review (post-
0.2.0IO stub generator pipeline, Proxy domain catalog, Feature runtimes, IDE edits).This map does not ship code. Each item is grilled later as its own child (
/grilling), then filed as one GitHub issue + PR per solution module (AGENTS.md). Do not implement from this map.Related prior map: #212 (bug hunt; items 1–2 already grilled).
Notes
Lens: shallow modules, leakage across a seam, and confirmed bugs. Vocabulary: module, interface, depth, seam, adapter, leverage, locality.
Hot spot:
0.2.0extractedIoProxyGeneratorPipeline/ProxyRegistrationEmitter/ProxyDomainTable. Remaining friction is catalog wrappers, Parser walks, R3 vs Reactive subscribe clones, RestAPI ownership flags, IDE completion/code fixes, and eight identical*Service.Forfactories.Do not re-open: ADR-001 (no third reactive backend); ADR-002 (no new Feature from this map).
Do not re-open without a new decision: #215 — no Shared subscribe-pump module. Per-Feature Reactive bugs are https://github.com/Skymly/Observables/issues/216–https://github.com/Skymly/Observables/issues/223.
Do not migrate RestAPI onto
ForAttributeWithMetadataName(ROADMAP C2). RestAPI stays onCreateSyntaxProvider.Suggested child order (grill, then file)
InsertTextnever applied; RestAPI verbs on every Feature; csproj writer bypasses workspaceSolutionisApiResponse/disposeResponseflags still leak; classifier matchesIApiResponseby nameHasAttribute/ nullability /IInternalErrorProbe*Service.ForfactoriesEach grilling session on a child should run
/grilling. If a deepened module is named after a concept not inCONTEXT.md, update the glossary there.Item 1 — Collapse Feature identity into the Proxy domain catalog
Module: Shared
Strength: Strong · in-process
Files:
Observables.Shared/Observables.Roslyn.Shared/ProxyDomainTable.csObservables.Shared/Observables.Analyzers/ProxyDomainCatalog.csObservables.Shared/Observables.CodeFixes/ObservablesMemberDiagnosticIds.csObservables.Shared/Observables.CodeFixes/BoundaryAttributeDefaults.csObservables.Shared/Observables.CodeFixes/ObservablesPackageReferenceMappings.csProblem.
ProxyDomainTableis the real table (markers, OBS*001/004, default boundary attributes). Four shallow modules wrap or re-list it. Runtime / Reactive package IDs live inObservablesPackageReferenceMappings, not the table. Stub generators still hard-code"Observables.Redis.RedisAttribute"instead of reading the catalog. Adding Redis meant editing the catalog and a second package map.Deletion test.
ProxyDomainCatalog/ObservablesMemberDiagnosticIds/BoundaryAttributeDefaultsare pass-through (just move). Folding empty-interface ids, package ids, and assembly names into one catalog concentrates.Known invariant bugs.
EmptyRestApiInterface(OBS3007) text says “HTTP method attributes”;EmptyProxyInterfaceAnalyzeronly looks for[RestApi].ProxyDomainTable.RestApihas empty method/property attributes.Deepening shape (for grilling, not a spec). One Proxy domain catalog interface; analyzers and code fixes become adapters. Do not invent a second table.
Item 2 — Deepen completion and package code fixes
Module: Shared
Strength: Strong · local-substitutable
Files:
Observables.Shared/Observables.Analyzers/CompletionItemFactory.csObservables.Shared/Observables.Analyzers/BoundaryAttributeCompletionProvider.csObservables.Shared/Observables.Analyzers/RestApiMemberCompletionProvider.csObservables.Shared/Observables.CodeFixes/ProjectFileWriter.csObservables.Shared/Observables.CodeFixes/AddRuntimePackageReferenceCodeFixProvider.csObservables.Shared/Observables.CodeFixes/SwitchToReactivePackageCodeFixProvider.csProblem. Completion stores insert text in a property named
InsertTextand never implementsGetChangeAsync; Roslyn insertsDisplayText.RestApiMemberCompletionProviderhas no Feature marker gate, so HTTP verbs appear on SignalR/Mqtt interfaces (analyzers ship with every Feature package).ProjectFileWriterdoesFile.WriteAllTextonproject.FilePathand returns the unchangedSolution(NoWarn=RS1035). OBS*005 offers “Add.Reactive” on an R3 generator, which walks the user into OBS0001.Deletion test.
CompletionItemFactoryis just move. One IDE-edit module that ownsGetChangeAsync, Feature gating, and workspace text concentrates.Deepening shape (for grilling, not a spec). Catalog is the seam; completion and csproj edits are adapters. Tests hit that interface, not the filesystem.
Latent note from #212 residue.
WebSocketConnect/WebSocketClosehave no string ctor; catalogFormatAttributewould emitWebSocketConnect("Name")]if InsertText were wired.Item 3 — Deepen Feature subscribe/dispose (per Feature, not Shared)
Module: each IO Feature (SignalR, Mqtt, WebSocket, Grpc, Sse, Nats, Postgres, Redis) — not Shared
Strength: Strong · local-substitutable
Blocked by decision: #215 (no Shared pump module)
Open children already filed: #216 … #223
Files (examples):
Observables.SignalR/Observables.SignalR/SignalRObservable.csObservables.SignalR/Observables.SignalR.Reactive/SystemReactiveSignalRAdapter.csObservables.Redis/Observables.Redis/RedisObservable.csObservables.Redis/Observables.Redis.Reactive/SystemReactiveRedisAdapter.csObservables.Mqtt/Observables.Mqtt.Reactive/SystemReactiveMqttAdapter.csObservables.Nats/Observables.Nats.Reactive/SystemReactiveNatsAdapter.csProblem. Two adapters (R3
*ObservablevsSystemReactive*Adapter) already justify a Feature seam, but protocol (subscribe, map, cancel, unsubscribe) is copied. Reactive still uses_ = Pump()/_ = RunAsync(), linked CTS neverDisposed, cancel often becomesOnError, MQTT neverUnsubscribeAsyncand canSubscribeAsyncafter dispose.Deletion test. Deleting one adapter just moves the pump. Concentrating protocol inside the Feature (R3 and Reactive as thin adapters) is the remaining depth. A cross-Feature Shared pump was already rejected.
Grill later. Whether each open bug ticket (#216–#223) is enough, or a Feature-local protocol module (like SignalR
HubConnectionArgs) should absorb R3+Reactive. Do not file a Shared pump ticket unless #215 is superseded.Item 4 — Deepen RestAPI response ownership (leftovers after #214)
Module: RestAPI
Strength: Worth exploring · local-substitutable
Shipped: #213 / PR #214 (IApiResponse ownership + cancel + factory coerce)
Files:
Observables.RestAPI/Observables.RestAPI/RestApiBridge.csObservables.RestAPI/Observables.RestAPI/ApiResponse.csObservables.RestAPI/Observables.RestAPI.SourceGenerators.Shared/RestApiReturnTypeClassifier.csObservables.RestAPI/Observables.RestAPI.SourceGenerators.Shared/Emitter.csObservables.RestAPI/Observables.RestAPI.SourceGenerators.Shared/Parser.csProblem. Generated code still passes
isApiResponse;SendAsyncstill keeps a mutabledisposeResponsebesideShouldDisposeResponse(typeof(TBody)). Classifier treats any generic namedApiResponse/IApiResponseas the RestAPI wrapper (name, no namespace).ParseNonHttpMethodcan emitReturnTypeInfo.ReturnforObservable<T>members the classifier would reject.RestService.Forstill falls through toType.GetType+Activatorunlike other Features’GeneratedFactories.Out of this item (still #212 leftovers, grill separately if needed). QueryUriFormat default
0; silent GET for custom HTTP attributes;AuthenticatedHttpClientHandlertoken getter.Deepening shape (for grilling, not a spec).
RestApiBridgeowns lifetime; generated code stops passing dispose knowledge. Keep Task /IApiResponseclassification in RestAPI (ROADMAP C1).Item 5 — Push the IO stub generator pipeline into the Parser walk
Module: Shared
Strength: Worth exploring · in-process
Files:
Observables.Shared/Observables.SourceGenerators.Shared/IoProxyGeneratorPipeline.csObservables.{SignalR,Mqtt,WebSocket,Grpc,Sse,Nats,Postgres,Redis}.SourceGenerators.Shared/Parser.csProblem.
0.2.0deepened incremental wiring only. Eight Parsers still clone:GroupBy(SyntaxTree)→GetSemanticModel→ marker scan → nullability → public members →HasAttribute. SignalR production Parser throws onAssemblyName == "GeneratorTests"+IInternalErrorProbe. OBS7004 is defined and never reported.IoProxyGeneratorPipeline/ObservableReturnTypeParserhave no Shared tests (coverage is Feature snapshots). Stub generators do not read the Proxy domain catalog (marker strings can drift from item 1).Deletion test. Extracting
HasAttributealone is just move. Pipeline walking marked interfaces, with Feature adapters only classifying members, concentrates. Templating per-FeatureDiagnosticDescriptorswas already rejected (docs/design/shproj-dedup-plan.md).Keep Events off this pipeline (call-site driven; real seam). Keep RestAPI on
CreateSyntaxProvider(C2).Item 6 — Collapse
*Service.ForfactoriesModule: Shared (implementation); public interface stays
Observables.<Feature>.*Service.For<T>Strength: Worth exploring · in-process
Files:
HubService.cs,SseService.cs,RedisService.cs,MqttService.cs,GrpcService.cs,NatsService.cs,PostgresService.cs,WebSocketService.cs(~2.6KB each). RestAPIRestServiceis not the same shape (two-arg factory, reflection fallback).Problem. Eight modules whose interface is the implementation (
RegisterGeneratedFactory+ConcurrentDictionary+For<T>+ AOT attributes).Deletion test. Deleting one
*Servicereappears at user call sites — each Feature interface earns its keep. The clone of the implementation does not. Eight adapters would justify a Core factory seam.Callout. ROADMAP D1 deferred filling
Observables.Core. #212 listed Core as out of scope (“empty project is not a user-facing bug”). Grill before reopening D1; do not silently put types in Core.Decisions so far
Not yet specified
.Reactive” on an R3 generator vs OBS0001 (tested product behavior; fights the Add-Reactive code fix in item 2).Out of scope
IoProxyGeneratorPipeline/ForAttributeWithMetadataName— C2.Acceptance (map)