Adds the async fan-out producer path from Plan §2.8 / §M5.d so primary
Signals + DecisionTransitions rows can be projected onto their index
tables without blocking the hot ingest loop.
Components:
- Constants.QueueNames.TelemetryIndexReconcile (azure-queue-name-safe,
all lowercase + hyphens).
- IndexReconcileEnvelope (Shared DTO, flat shape with nullable fields
per SourceKind="Signal"/"DecisionTransition"). Avoids polymorphic
Newtonsoft deserialization — see M5.c.3 finding.
- IndexReconcileEnvelopeFactory pure static helper (FromSignal /
FromDecisionTransition / BuildBatch) — state-less projection.
- IIndexReconcileProducer (Shared interface) contract: no-op when
the feature flag is off; never rethrows underlying-queue failures.
- AzureQueueIndexReconcileProducer (Functions) — Base64-encoded JSON
messages, lazy CreateIfNotExistsAsync guarded by Interlocked flag,
Managed-Identity-or-connection-string construction mirroring
BlobStorageService / TableStorageService.
- IngestTelemetryFunction ctor gains IIndexReconcileProducer; after
primary Signals + DecisionTransitions commit it builds envelopes
and enqueues them. Event path is untouched (events don't feed the
index tables per §2.8).
- Program.cs registers the producer as a singleton.
Feature gate (AdminConfiguration.EnableIndexDualWrite, default false):
- New bool field in AdminConfiguration.
- TableConfigRepository Store + Map extension (per
feedback_table_storage_serialization).
- Producer checks config each call via AdminConfigurationService's
5-min memory cache. Flag off → primary behaviour is bit-exact.
Fault tolerance: queue-side exceptions (network, throttling, missing
queue before first CreateIfNotExists) are logged per envelope and
swallowed. The primary row is the source of truth; the 2h reconcile
timer (M5.d.4) is the safety-net recovery path.
Tests (+11 in Functions):
- 5 IndexReconcileEnvelopeFactoryTests: Signal/DecisionTransition
projection, dead-end preservation, batch ordering, null/empty.
- 3 IndexReconcileEnvelopeSerializationTests: Newtonsoft round-trip
(Signal, full DecisionTransition, minimal-envelope tolerance).
- 3 AdminConfigurationEnableIndexDualWriteTests: default-false on
new config + CreateDefault, persists when set.
Deferred to later sub-milestones:
- M5.d.3: queue-triggered consumer that maps envelopes to 0–3 index
rows using M5.d.1 TableIndexRepository.
- M5.d.4: 2h reconcile timer + release-gate dual-write verification
tests, then flip EnableIndexDualWrite=true in prod admin config.
Full Functions suite: 672 green (prev 661 + 11 new). Full solution
build green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>