mirror of
https://github.com/azaion/annotations.git
synced 2026-06-21 08:31:06 +00:00
docs: Step 4 testability refactor — list-of-changes + 2 task specs
autodev existing-code Step 4 (Code Testability Revision) — invoked
refactor skill in guided mode. Phase 0 (baseline) + Phase 1 (discovery
+ validation) + Phase 2 (analysis + task decomposition) artifacts.
list-of-changes.md identifies two surgical fixes required before the
67-scenario blackbox suite (already specified in _docs/02_document/
tests/) can run against the SUT:
C01 — env-gate JWKS RequireHttps on ASPNETCORE_ENVIRONMENT=E2ETest
(architecture.md Open Risks Section 6 prescribes this; the
mock issuer in e2e/docker-compose.test.yml serves plain HTTP)
C02 — DNS-resolve RABBITMQ_HOST in FailsafeProducer.ProcessQueue
(IPAddress.Parse currently throws on every drain cycle when
host is a service name; latent production-relevant bug, not
just a test-env issue)
Two task specs in _docs/02_tasks/todo/ (3 story points total).
Independent — no inter-task dependency.
Tracker: local — Atlassian MCP reported errored at task-creation
time. Deferred Jira writes (epic + 2 tickets) recorded in
_docs/_process_leftovers/2026-05-14_testability-tracker.md for
replay when MCP is restored.
Items explicitly deferred to Step 8 Refactor are enumerated in
list-of-changes.md "Deferred to Step 8 Refactor" — including the
FailsafeProducer static helper (F3), the JWKS GetAwaiter().GetResult()
hot path, RB-05/06/08 backlog items, and the MediaService ffprobe
empty-catch.
State: Step 4 in_progress, sub_step 3 (phase-2-task-decomposition).
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
# Task Dependencies Table
|
||||||
|
|
||||||
|
Tracks ordering and inter-task dependencies for all task specs in `_docs/02_tasks/todo/`. Updated by the decompose / refactor / new-task skills whenever a task is added or completed.
|
||||||
|
|
||||||
|
## Current Tasks (cycle 1 — testability refactor)
|
||||||
|
|
||||||
|
| # | Task File | Component | Complexity | Depends on | Notes |
|
||||||
|
|---|-----------|-----------|-----------|------------|-------|
|
||||||
|
| 01 | `01_refactor_jwks_https_env_gate.md` | `06_platform` → Auth (`src/Auth/JwtExtensions.cs`) | 1 | None | C01 from `_docs/04_refactoring/01-testability-refactoring/list-of-changes.md` |
|
||||||
|
| 02 | `02_refactor_rabbitmq_host_dns_resolution.md` | `02_annotations-realtime-sync` (`src/Services/FailsafeProducer.cs`) | 2 | None | C02 from `_docs/04_refactoring/01-testability-refactoring/list-of-changes.md` |
|
||||||
|
|
||||||
|
## Independent Tasks
|
||||||
|
|
||||||
|
Tasks 01 and 02 touch disjoint files and have no inter-dependency. They may be implemented in parallel or in either order; the implement skill is free to batch them together.
|
||||||
|
|
||||||
|
## Tracker Status
|
||||||
|
|
||||||
|
`tracker: local` (per `_docs/_autodev_state.md`). Tracker writes are recorded in `_docs/_process_leftovers/2026-05-14_testability-tracker.md` for replay when Atlassian MCP is restored. On replay, each `PENDING_*` task ID in the file headers must be renamed to its assigned `AZ-NNNN` Jira issue key.
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# Refactor: gate JWKS HTTPS requirement on `ASPNETCORE_ENVIRONMENT=E2ETest`
|
||||||
|
|
||||||
|
**Task**: PENDING_refactor_jwks_https_env_gate
|
||||||
|
**Name**: JWKS HTTPS env gate
|
||||||
|
**Description**: Gate the JWKS document retriever's `RequireHttps` flag on the ASP.NET Core environment name so the e2e test harness (which serves the mock issuer over plain HTTP on the test-only docker network) can fetch the public key set without weakening production HTTPS enforcement.
|
||||||
|
**Complexity**: 1 point
|
||||||
|
**Dependencies**: None
|
||||||
|
**Component**: `06_platform` → Auth (`src/Auth/JwtExtensions.cs`)
|
||||||
|
**Tracker**: pending (tracker MCP unavailable at task-creation time — see `_docs/_process_leftovers/2026-05-14_testability-tracker.md`)
|
||||||
|
**Epic**: pending — `01-testability-refactoring`
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
The JWKS retriever in `AddJwtAuth` is constructed with `new HttpDocumentRetriever { RequireHttps = true }`. This is correct for production (where `JWT_JWKS_URL` is `https://admin.azaion.com/.well-known/jwks.json`), but blocks the documented blackbox test harness, which serves a per-test ES256 public key over plain HTTP at `http://e2e-issuer:8080/.well-known/jwks.json`. With the constant true, the service throws on every first JWKS fetch and ~60 of the 67 test scenarios in `_docs/02_document/tests/` cannot exercise the real validation path.
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
|
||||||
|
- When `ASPNETCORE_ENVIRONMENT` is `E2ETest`, the JWKS retriever accepts plain-HTTP JWKS URLs.
|
||||||
|
- For any other environment name (Development, Staging, Production, unset), the JWKS retriever continues to require HTTPS exactly as today.
|
||||||
|
- No change to issuer / audience / algorithm pinning / signature / lifetime validation. The relaxation is strictly about the *transport* used to fetch the public-key document.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
### Included
|
||||||
|
|
||||||
|
- `src/Auth/JwtExtensions.cs` — `AddJwtAuth` method.
|
||||||
|
- The way `RequireHttps` is set on the `HttpDocumentRetriever` argument passed to `ConfigurationManager<JsonWebKeySet>`.
|
||||||
|
- Use of `IHostEnvironment` (already injected into `IServiceCollection` via the ASP.NET Core host) — either by adding the env name as a parameter on `AddJwtAuth`, or by resolving it from `IConfiguration`/the `ASPNETCORE_ENVIRONMENT` env var inline. Either approach is acceptable; smaller-diff option preferred.
|
||||||
|
|
||||||
|
### Excluded
|
||||||
|
|
||||||
|
- Any change to `TokenValidationParameters` (issuer/audience/lifetime/algorithm/signature).
|
||||||
|
- Any change to the `IssuerSigningKeyResolver` lambda.
|
||||||
|
- Any change to `JwksRetriever`.
|
||||||
|
- Any change to policy registration (`ANN` / `DATASET` / `ADM`).
|
||||||
|
- Adding new env vars or configuration keys.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
**AC-1: HTTPS still enforced outside the test environment**
|
||||||
|
Given `ASPNETCORE_ENVIRONMENT` is unset, `Development`, `Staging`, or `Production`,
|
||||||
|
When `AddJwtAuth` runs and `JWT_JWKS_URL` is `http://anywhere/jwks.json`,
|
||||||
|
Then the service refuses to fetch JWKS over plain HTTP (existing `Microsoft.IdentityModel` behavior is preserved; the retriever throws `InvalidOperationException` for non-HTTPS URLs).
|
||||||
|
|
||||||
|
**AC-2: HTTPS relaxed under E2ETest only**
|
||||||
|
Given `ASPNETCORE_ENVIRONMENT=E2ETest`,
|
||||||
|
When `AddJwtAuth` runs and `JWT_JWKS_URL` is `http://e2e-issuer:8080/.well-known/jwks.json`,
|
||||||
|
Then the JWKS document is fetched successfully over plain HTTP and used to populate the signing-key cache.
|
||||||
|
|
||||||
|
**AC-3: Validation semantics unchanged**
|
||||||
|
Given any environment,
|
||||||
|
When a token presents valid `iss`, `aud`, `exp`, ES256 signature, and `permissions` claim,
|
||||||
|
Then the token is accepted exactly as today.
|
||||||
|
|
||||||
|
**AC-4: Forgery / tamper / cross-policy attacks still rejected**
|
||||||
|
Given the harness from AC-2,
|
||||||
|
When a token is presented with `alg=HS256`, with an expired `exp`, with a wrong `iss` or `aud`, or with a tampered payload,
|
||||||
|
Then the request is rejected with 401 — same behavior as today.
|
||||||
|
|
||||||
|
## Non-Functional Requirements
|
||||||
|
|
||||||
|
**Compatibility**
|
||||||
|
- No env var renames; no breaking change to operators' deployment configs.
|
||||||
|
- No public method signature change on `AddJwtAuth` that breaks the single existing caller (`Program.cs:49`).
|
||||||
|
|
||||||
|
**Reliability**
|
||||||
|
- The env-name read must be deterministic at startup (read once during `AddJwtAuth` invocation; do not re-read per request).
|
||||||
|
|
||||||
|
## Unit Tests
|
||||||
|
|
||||||
|
| AC Ref | What to Test | Required Outcome |
|
||||||
|
|--------|--------------|------------------|
|
||||||
|
| AC-1 | Constructing `HttpDocumentRetriever` under `EnvironmentName="Production"` | `RequireHttps == true` |
|
||||||
|
| AC-2 | Constructing `HttpDocumentRetriever` under `EnvironmentName="E2ETest"` | `RequireHttps == false` |
|
||||||
|
|
||||||
|
(Unit tests above are illustrative — Step 6 will write the executable test code; this task only adjusts source.)
|
||||||
|
|
||||||
|
## Blackbox Tests
|
||||||
|
|
||||||
|
| AC Ref | Initial Data/Conditions | What to Test | Expected Behavior | NFR References |
|
||||||
|
|--------|------------------------|--------------|-------------------|----------------|
|
||||||
|
| AC-2 | Docker e2e stack with `ASPNETCORE_ENVIRONMENT=E2ETest` and the mock issuer on HTTP | `POST /annotations` with a runner-minted ES256 token whose public key is published by the mock issuer | HTTP 200, body matches `AnnotationDto` (this is FT-P-01 from `blackbox-tests.md`) | — |
|
||||||
|
| AC-3 | Same env | FT-P-12 (Bearer happy path) | HTTP 200 | — |
|
||||||
|
| AC-4 | Same env | NFT-SEC-01..10 (signature mismatch / expired / wrong iss / wrong aud / alg confusion / tamper) | HTTP 401 every time | — |
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- The integration with `Microsoft.IdentityModel.Protocols.ConfigurationManager<JsonWebKeySet>` must be preserved; only the constructor argument to `HttpDocumentRetriever` changes.
|
||||||
|
- ASP.NET Core convention: read environment via `IHostEnvironment` rather than `Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")` directly where possible.
|
||||||
|
|
||||||
|
## Risks & Mitigation
|
||||||
|
|
||||||
|
**Risk 1: Wrong environment-name comparison style**
|
||||||
|
- *Risk*: A case-sensitive equality check could miss `E2ETest` vs `e2etest` if a future operator types the env name differently.
|
||||||
|
- *Mitigation*: Use `string.Equals(envName, "E2ETest", StringComparison.OrdinalIgnoreCase)` — matches the existing `CorsConfigurationValidator.EnsureSafeForEnvironment` pattern.
|
||||||
|
|
||||||
|
**Risk 2: Accidental Production opt-in**
|
||||||
|
- *Risk*: An operator could set `ASPNETCORE_ENVIRONMENT=E2ETest` in production to silence an HTTPS-only error.
|
||||||
|
- *Mitigation*: Documented as test-only in `architecture.md`. `Program.cs`'s `CorsConfigurationValidator` already runs an environment-aware safety check on a related axis; a future Step 8 hardening item can add a similar "Production refuses E2ETest token over HTTP" guard if desired. Out of scope for this task.
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
# Refactor: resolve RabbitMQ broker host via DNS in `FailsafeProducer`
|
||||||
|
|
||||||
|
**Task**: PENDING_refactor_rabbitmq_host_dns_resolution
|
||||||
|
**Name**: RabbitMQ host DNS resolution
|
||||||
|
**Description**: Replace `IPAddress.Parse(config.Host)` with a hostname-aware resolver (literal-IP shortcut + `Dns.GetHostAddressesAsync` fallback) so the `FailsafeProducer` outbox-drain loop can reach the broker when `RABBITMQ_HOST` is a DNS service name — which is the documented test configuration and the production-normal configuration for any container deployment.
|
||||||
|
**Complexity**: 2 points
|
||||||
|
**Dependencies**: None
|
||||||
|
**Component**: `02_annotations-realtime-sync` → `FailsafeProducer` (`src/Services/FailsafeProducer.cs`)
|
||||||
|
**Tracker**: pending (tracker MCP unavailable at task-creation time — see `_docs/_process_leftovers/2026-05-14_testability-tracker.md`)
|
||||||
|
**Epic**: pending — `01-testability-refactoring`
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
`FailsafeProducer.ProcessQueue` builds the RabbitMQ Stream endpoint with `new IPEndPoint(IPAddress.Parse(config.Host), config.Port)`. `IPAddress.Parse` throws `FormatException` on any string that is not a literal IPv4 / IPv6 address. The test stack (`e2e/docker-compose.test.yml:82`) sets `RABBITMQ_HOST=rabbitmq` (a docker-compose service name); the in-class default in `RabbitMqConfig` is also `"rabbitmq"`. Every drain cycle today throws on the first line of `ProcessQueue`; the outer catch logs and backs off 10 s, and the outbox never drains. Five test scenarios depend on the drain path working: FT-P-09, NFT-RES-01, NFT-RES-06, NFT-RES-LIM-03, NFT-PERF-OUTBOX-DRAIN-01.
|
||||||
|
|
||||||
|
Beyond tests, this is a latent **production-relevant** logic bug: any deployment that uses container DNS, Kubernetes service names, or any non-IP value in `RABBITMQ_HOST` has the same broken drain. The bug is masked because outbox row inserts (synchronous, via `AnnotationService` → static `EnqueueAsync`) keep returning 200; only consumers of the stream see the absence.
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
|
||||||
|
- When `RABBITMQ_HOST` is a literal IP, behavior is unchanged.
|
||||||
|
- When `RABBITMQ_HOST` is a DNS hostname, the producer resolves it via `System.Net.Dns` and connects to the resulting IP.
|
||||||
|
- The DNS resolution participates in the existing retry envelope — a DNS failure surfaces through the same `catch (Exception ex)` + 10 s back-off path that exception-throws use today, so operator-visible behavior on broker-unreachable is preserved.
|
||||||
|
- No change to MessagePack payload, gzip compression, queue-table delete, or any aspect of `DrainQueue`.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
### Included
|
||||||
|
|
||||||
|
- `src/Services/FailsafeProducer.cs` — `ProcessQueue` method, specifically the construction of the `IPEndPoint` for `StreamSystemConfig.Endpoints`.
|
||||||
|
- Cancellation-token propagation through the new resolve call (use the existing `ct`).
|
||||||
|
|
||||||
|
### Excluded
|
||||||
|
|
||||||
|
- Any change to `EnqueueAsync` (static helper).
|
||||||
|
- Any change to `DrainQueue` (serialization / publish / delete).
|
||||||
|
- Any change to `ExecuteAsync`'s outer retry envelope.
|
||||||
|
- Any change to `RabbitMqConfig` shape — same env-var contract.
|
||||||
|
- Switching from `IPEndPoint` to `DnsEndPoint` (the library accepts both, but `DnsEndPoint` path is untested in this codebase; pick the smaller-diff option).
|
||||||
|
- Caching resolved IPs across drain cycles (broker may rotate; resolve per cycle is fine and matches the connect-per-cycle pattern).
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
**AC-1: Literal IP host still works**
|
||||||
|
Given `RABBITMQ_HOST=127.0.0.1` (or any literal IPv4/IPv6),
|
||||||
|
When `ProcessQueue` enters its first iteration,
|
||||||
|
Then the producer connects to the broker without any DNS lookup.
|
||||||
|
|
||||||
|
**AC-2: DNS hostname now works**
|
||||||
|
Given `RABBITMQ_HOST=rabbitmq` (or any non-IP hostname),
|
||||||
|
When `ProcessQueue` enters its first iteration in an environment where the name resolves,
|
||||||
|
Then the producer connects to the broker and the outbox drain executes.
|
||||||
|
|
||||||
|
**AC-3: Unresolvable hostname surfaces through existing retry envelope**
|
||||||
|
Given `RABBITMQ_HOST=does-not-resolve.invalid`,
|
||||||
|
When `ProcessQueue` enters its first iteration,
|
||||||
|
Then the resolve call throws (e.g., `SocketException`), the outer `catch` in `ExecuteAsync` logs the exception, and the loop backs off 10 seconds — same surface behavior as a `FormatException` today.
|
||||||
|
|
||||||
|
**AC-4: Cancellation honored during resolution**
|
||||||
|
Given a cancellation request mid-resolve,
|
||||||
|
When `ProcessQueue` is in the resolution call,
|
||||||
|
Then `OperationCanceledException` propagates and `ExecuteAsync`'s `catch (OperationCanceledException) when (ct.IsCancellationRequested)` exits the outer loop cleanly.
|
||||||
|
|
||||||
|
**AC-5: Wire format / consumers unaffected**
|
||||||
|
Given any successful drain after the fix,
|
||||||
|
When a stream consumer reads from `azaion-annotations`,
|
||||||
|
Then the message body (MessagePack-encoded `AnnotationQueueMessage` / `AnnotationBulkQueueMessage`, gzip-compressed) is byte-for-byte identical to what the unchanged `DrainQueue` produced before this task.
|
||||||
|
|
||||||
|
## Non-Functional Requirements
|
||||||
|
|
||||||
|
**Performance**
|
||||||
|
- DNS resolution adds at most one network round-trip per drain cycle (every ~10 s). Negligible.
|
||||||
|
- No per-request DNS lookups; the resolution is in `ProcessQueue`, which runs once per outer retry cycle.
|
||||||
|
|
||||||
|
**Compatibility**
|
||||||
|
- No env-var rename; `RABBITMQ_HOST` semantics expanded from "IP literal" to "IP literal OR DNS hostname".
|
||||||
|
- No change to consumers (admin's `AnnotationSyncWorker`, AI Training consumer).
|
||||||
|
|
||||||
|
**Reliability**
|
||||||
|
- Resolution failure path uses the same retry envelope as the previous parsing failure path; no new failure modes are introduced.
|
||||||
|
|
||||||
|
## Unit Tests
|
||||||
|
|
||||||
|
| AC Ref | What to Test | Required Outcome |
|
||||||
|
|--------|--------------|------------------|
|
||||||
|
| AC-1 | Helper that maps `"127.0.0.1"` to `IPAddress` | returns `IPAddress.Loopback` without performing a DNS lookup |
|
||||||
|
| AC-2 | Helper that maps `"localhost"` to `IPAddress` | returns the first address from `Dns.GetHostAddresses("localhost")` |
|
||||||
|
| AC-3 | Helper invoked with `"does-not-resolve.invalid"` | throws (caller-side handling lives in `ExecuteAsync`'s catch) |
|
||||||
|
|
||||||
|
(Step 6 produces executable test code; these guide the implementation.)
|
||||||
|
|
||||||
|
## Blackbox Tests
|
||||||
|
|
||||||
|
| AC Ref | Initial Data/Conditions | What to Test | Expected Behavior | NFR References |
|
||||||
|
|--------|------------------------|--------------|-------------------|----------------|
|
||||||
|
| AC-2, AC-5 | Docker e2e stack with `RABBITMQ_HOST=rabbitmq`, broker up | FT-P-08 (outbox row insert) + FT-P-09 (stream message arrives) | row appears, drained within ~10–30 s, consumer receives MessagePack+gzip message matching the documented schema | — |
|
||||||
|
| AC-3 | Docker e2e stack, broker stopped via `rabbitmqctl stop_app` | NFT-RES-01 (broker outage) | SUT does not crash, `/health` stays 200, outbox preserves the row, recovery delivers the deferred message within 60 s of broker `start_app` | — |
|
||||||
|
| AC-5 | Docker e2e stack at sustained 5 RPS | NFT-PERF-OUTBOX-DRAIN-01 | max queue depth ≤ 100 rows | — |
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
|
||||||
|
- Keep using `RabbitMQ.Stream.Client.StreamSystemConfig.Endpoints` with `IPEndPoint` entries (matches existing examples and the rest of the call).
|
||||||
|
- Resolution must accept a `CancellationToken`; use the `ct` already in scope.
|
||||||
|
- No `IPAddress.Parse` calls on `config.Host` anywhere in the diff (the bug being fixed).
|
||||||
|
|
||||||
|
## Risks & Mitigation
|
||||||
|
|
||||||
|
**Risk 1: DNS round-trip on the drain hot loop**
|
||||||
|
- *Risk*: Sub-second DNS lookup on every drain cycle might surprise operators expecting a pure local-IP fast path.
|
||||||
|
- *Mitigation*: Drain cycle is already ~10 s spacing; one DNS lookup per cycle is negligible. If profiling later reveals a problem, a literal-IP fast path (already in this design — `TryParse` before `Dns`) means IP-literal deployments are not affected at all.
|
||||||
|
|
||||||
|
**Risk 2: Resolved IP becomes stale across cycles**
|
||||||
|
- *Risk*: A broker IP change between cycles would not be picked up if we cached.
|
||||||
|
- *Mitigation*: Don't cache. Resolve every cycle (same cost; matches connect-every-cycle pattern).
|
||||||
|
|
||||||
|
**Risk 3: Multi-address records (round-robin DNS)**
|
||||||
|
- *Risk*: `GetHostAddressesAsync` returns N addresses; picking only the first ignores load balancing.
|
||||||
|
- *Mitigation*: For testability scope, "first address" is fine — broker LBs are a Step 8 concern. Documented here, not implemented.
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Refactoring Roadmap — 01-testability-refactoring
|
||||||
|
|
||||||
|
## Weak Points Assessment
|
||||||
|
|
||||||
|
| # | Location | Description | Impact | Proposed Solution | Status |
|
||||||
|
|---|----------|-------------|--------|-------------------|--------|
|
||||||
|
| 1 | `src/Auth/JwtExtensions.cs:33` | `HttpDocumentRetriever { RequireHttps = true }` hardcoded; blocks the e2e mock-issuer harness that serves HTTP | Blocks ~60 of 67 documented test scenarios from running | C01 (env-gated `RequireHttps`) | Selected |
|
||||||
|
| 2 | `src/Services/FailsafeProducer.cs:56` | `IPAddress.Parse(config.Host)` throws on DNS hostnames; outbox drain never runs in any deployment using container DNS | Blocks 5 outbox/resilience/perf test scenarios; latent **production-relevant** logic bug | C02 (DNS resolution before `IPEndPoint`) | Selected |
|
||||||
|
|
||||||
|
## Gap Analysis — acceptance criteria vs. current state
|
||||||
|
|
||||||
|
The Step 4 testability scope is the gap. The full AC list (in `_docs/00_problem/acceptance_criteria.md`) is implementation-tracked by other steps. The relevant gaps for this run:
|
||||||
|
|
||||||
|
| AC | Gap | Closed by |
|
||||||
|
|----|-----|-----------|
|
||||||
|
| AC-F-50 (Bearer token verification) | Cannot be exercised against real JWKS-fetch behavior in test harness | C01 |
|
||||||
|
| AC-F-12 (Outbox drain → stream) | Cannot be exercised at all today | C02 |
|
||||||
|
| AC-F-10 (SSE delivery < 1s) | Independent of these changes; works today | (none) |
|
||||||
|
|
||||||
|
## Phased Roadmap
|
||||||
|
|
||||||
|
**Phase 1 — Critical Fixes (this run)**: C01 + C02. Selected. Approved by user.
|
||||||
|
|
||||||
|
**Phase 2 — Major Improvements**: deferred to Step 8 Refactor; enumerated under "Deferred to Step 8 Refactor" in `list-of-changes.md`.
|
||||||
|
|
||||||
|
**Phase 3 — Enhancements**: out of scope.
|
||||||
|
|
||||||
|
## Hardening Tracks
|
||||||
|
|
||||||
|
For testability runs, hardening tracks (Tech Debt / Performance / Security review) are explicitly **out of scope** per existing-code flow Step 4 ("smallest set of changes ... deeper structural improvements belong in Step 8"). The user already constrained scope to C01+C02 via the Phase 1 approval — re-opening the scope here would violate Step 4's discipline.
|
||||||
|
|
||||||
|
**Selected hardening tracks**: None (option E).
|
||||||
|
|
||||||
|
## Applicability Gate
|
||||||
|
|
||||||
|
Every roadmap item is `Selected`. No items in `Rejected` / `Experimental only` / `Needs user decision` states. BLOCKING applicability gate cleared.
|
||||||
|
|
||||||
|
## Constraint Fit Summary
|
||||||
|
|
||||||
|
| Recommendation | Constraint fit | Mismatches | Evidence | Status |
|
||||||
|
|---------------|---------------|-----------|----------|--------|
|
||||||
|
| C01 | Preserves SW-05, AC-F-50, NFT-SEC-01..10, ENV-02; aligns with architecture.md Open Risks §6 | None | `research_findings.md` references | Selected |
|
||||||
|
| C02 | Preserves SW-03, AC-F-12, AC-N-03, ENV-01; matches module-layout Component 02 ownership | None | `research_findings.md` references | Selected |
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# Research Findings — 01-testability-refactoring
|
||||||
|
|
||||||
|
**Scope**: minimal — testability run with 2 surgical changes. No replacement library / SDK / framework is proposed; therefore the per-mode API capability verification (mandatory for replacements) is **not applicable** to this run. All API decisions stay inside the BCL and existing dependencies (`Microsoft.IdentityModel.Protocols`, `RabbitMQ.Stream.Client`, `System.Net.Dns`).
|
||||||
|
|
||||||
|
## Current State Analysis
|
||||||
|
|
||||||
|
### C01 area — JWKS retrieval
|
||||||
|
|
||||||
|
- **Pattern**: `ConfigurationManager<JsonWebKeySet>` + `HttpDocumentRetriever`. This is the recommended path in `Microsoft.IdentityModel.Protocols` 8.x for non-OIDC JWKS endpoints; the project pins the version in `Azaion.Annotations.csproj`. No alternative pattern is needed.
|
||||||
|
- **Strength**: caching + automatic refresh on the documented schedule; key-rotation friendly.
|
||||||
|
- **Weakness**: `HttpDocumentRetriever.RequireHttps` is a constructor-time bool. No env-aware factory in the BCL or in `Microsoft.IdentityModel.*`. The smallest change is to read `IHostEnvironment.EnvironmentName` (or `ASPNETCORE_ENVIRONMENT` directly) at the call site and pass the resulting bool.
|
||||||
|
|
||||||
|
### C02 area — RabbitMQ stream endpoint construction
|
||||||
|
|
||||||
|
- **Pattern**: `RabbitMQ.Stream.Client.StreamSystem.Create(StreamSystemConfig)` with `Endpoints = IList<EndPoint>`. The `EndPoint` base class accepts `IPEndPoint` (used today) and `DnsEndPoint`. The library's documented examples all use `IPEndPoint`.
|
||||||
|
- **Strength**: works deterministically when given a literal IP.
|
||||||
|
- **Weakness**: hostnames must be resolved by the caller. `IPAddress.Parse` throws on hostnames — wrong API choice for the documented `RABBITMQ_HOST` value space.
|
||||||
|
- **Alternatives considered**:
|
||||||
|
- **(rejected)** Replace `IPEndPoint` with `DnsEndPoint` and let `RabbitMQ.Stream.Client` resolve internally — uncertain whether the library handles this path on every transport; introduces unverified behavior. Testability rule: smallest known-correct change wins.
|
||||||
|
- **(selected)** Resolve via `Dns.GetHostAddressesAsync(...)` when the value is not an IP, keep `IPEndPoint`. Identical behavior for IP-literal callers, mechanical fix for hostname callers.
|
||||||
|
|
||||||
|
## Prioritized Recommendations
|
||||||
|
|
||||||
|
| Recommendation | Pinned mode / config | Constraint fit | Evidence | Status |
|
||||||
|
|----------------|---------------------|---------------|----------|--------|
|
||||||
|
| C01 — env-gate `RequireHttps` | `HttpDocumentRetriever { RequireHttps = environment != "E2ETest" }` | Preserves SW-05, AC-F-50, NFT-SEC-01..10; aligns with `architecture.md` Open Risks §6 | Listed in `_docs/02_document/architecture.md` Open Risks §6, `_docs/02_document/tests/test-data.md` "Bearer token harness" step 2 | Selected |
|
||||||
|
| C02 — DNS-resolve `config.Host` before `IPEndPoint` | `IPAddress.TryParse(host, out ip) ? ip : (await Dns.GetHostAddressesAsync(host, ct)).First()` | Preserves SW-03, AC-F-12, AC-N-03, ENV-01; module-layout Component 02 owner; no wire-format change | `RabbitMQ.Stream.Client` example code uses `IPEndPoint`; `System.Net.Dns.GetHostAddressesAsync` is the standard hostname-resolution call in .NET 10 | Selected |
|
||||||
|
|
||||||
|
**Replacement library/SDK count**: 0. **MVE files required**: 0. **`context7` calls required**: 0 (no replacement; both changes use APIs already in the dependency closure and stay inside the documented call patterns).
|
||||||
|
|
||||||
|
## Restrictions × Recommendation Sub-Matrix (light)
|
||||||
|
|
||||||
|
A full Restrictions × Candidate-Mode walk is mandatory only for replacement recommendations. Both selected items keep the existing dependencies; for completeness, the binding restrictions are mapped here.
|
||||||
|
|
||||||
|
| Restriction (from `_docs/00_problem/restrictions.md`) | C01 effect | C02 effect |
|
||||||
|
|-------------------------------------------------------|-----------|-----------|
|
||||||
|
| HW-01 ARM64 only | N/A — no native code | N/A |
|
||||||
|
| HW-02 writable dirs | N/A | N/A |
|
||||||
|
| SW-01 .NET 10 | uses BCL `IHostEnvironment` already injected | uses BCL `System.Net.Dns` |
|
||||||
|
| SW-03 RabbitMQ streams plugin | N/A | unchanged — same client, same wire format |
|
||||||
|
| SW-05 JWT verifier-only ES256 over JWKS | verification semantics unchanged | N/A |
|
||||||
|
| ENV-01 env vars required | unchanged — same vars | unchanged — `RABBITMQ_HOST` semantics preserved |
|
||||||
|
| ENV-02 service on port 8080 HTTP, no in-image TLS | aligned — the SUT itself is HTTP, this change only affects the issuer transport requirement | N/A |
|
||||||
|
| ENV-06 CORS gated by validator | unchanged | N/A |
|
||||||
|
| OP-01 per-instance SSE state | N/A | N/A |
|
||||||
|
| OP-02 no outbox row leasing | N/A | unchanged — same drain pattern |
|
||||||
|
|
||||||
|
No ❌ or ❓ cells. Both recommendations are `Selected`.
|
||||||
|
|
||||||
|
## Quick Wins vs. Strategic Improvements
|
||||||
|
|
||||||
|
Both C01 and C02 are quick wins (1-2 hour implementation each). Strategic improvements were deferred to `list-of-changes.md` "Deferred to Step 8 Refactor" section — the user already approved that constraint.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- `_docs/02_document/architecture.md` Open Risks §6
|
||||||
|
- `_docs/02_document/tests/test-data.md` "Bearer token harness"
|
||||||
|
- `_docs/02_document/tests/environment.md` services table
|
||||||
|
- `_docs/02_document/modules/auth-identity.md`
|
||||||
|
- `_docs/02_document/modules/rabbitmq-stream-sync.md`
|
||||||
|
- `_docs/02_document/architecture_compliance_baseline.md`
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# Baseline Metrics — 01-testability-refactoring
|
||||||
|
|
||||||
|
**Run**: 01-testability-refactoring
|
||||||
|
**Date**: 2026-05-14
|
||||||
|
**Mode**: guided (testability)
|
||||||
|
**Scope**: minimal-surgical refactor to make the documented test suite runnable. Many baseline categories are **N/A** by design because no executable test suite exists yet (this run produces the *prerequisites* for the suite to be implemented in Step 6).
|
||||||
|
|
||||||
|
## Goals (from `list-of-changes.md` Summary)
|
||||||
|
|
||||||
|
1. Unblock authenticated-endpoint tests by gating the JWKS retriever's HTTPS requirement on `ASPNETCORE_ENVIRONMENT=E2ETest`.
|
||||||
|
2. Unblock outbox-drain tests by fixing the `IPAddress.Parse` assumption in `FailsafeProducer` so DNS hostnames (the documented and operationally normal case) resolve correctly.
|
||||||
|
|
||||||
|
Both goals derive directly from `_docs/02_document/tests/` and `_docs/02_document/architecture.md` Open Risks §6.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
This run completes when:
|
||||||
|
|
||||||
|
- C01 and C02 are applied to source and build cleanly under `dotnet build src/`.
|
||||||
|
- The refactored code preserves every existing behavior outside the documented testability surface (verified by code review in Phase 6, not by an executing test suite that does not yet exist).
|
||||||
|
- `architecture.md` Open Risks §6 is updated (entry retired, replaced by the implemented gating).
|
||||||
|
- A `testability_changes_summary.md` is written and presented to the user.
|
||||||
|
|
||||||
|
## Baseline Metrics
|
||||||
|
|
||||||
|
| Metric Category | Captured? | Value / Reason |
|
||||||
|
|----------------|-----------|----------------|
|
||||||
|
| Coverage (overall / unit / blackbox / critical paths) | **N/A** | No executable test suite exists in this repo today. `_docs/02_document/tests/` contains 67 *test specifications* but zero implemented tests. This is precisely why Step 4 (testability) precedes Step 6 (Implement Tests). |
|
||||||
|
| Cyclomatic complexity (avg + top 5) | Captured (rough) | `src/Auth/JwtExtensions.cs` — 1 method (`AddJwtAuth`), cyclomatic ~3 (one inline resolver lambda with a kid-null branch). `src/Services/FailsafeProducer.cs` — `DrainQueue` is the hot function, cyclomatic ~7 (foreach + nested foreach + 3 branches for operation type). No top-5 needed for a 2-file scope. |
|
||||||
|
| LOC (target files) | Captured | `JwtExtensions.cs` = 89 LOC. `FailsafeProducer.cs` = 206 LOC. |
|
||||||
|
| Tech debt ratio | **N/A** | No SonarQube / dotnet-counters baseline in this repo. The architecture compliance baseline (`_docs/02_document/architecture_compliance_baseline.md`) is the qualitative substitute — verdict PASS_WITH_WARNINGS, 0 Critical, 0 High, 1 Medium, 2 Low. |
|
||||||
|
| Total / critical / major code smells | Captured (qualitative) | From baseline: F1 (Medium — DatasetService writes annotation table — out of scope here, RB-08), F2 (Low — ClassesController bypasses service — out of scope, RB-06), F3 (Low — `FailsafeProducer.EnqueueAsync` static — accepted tech debt). None in the C01/C02 affected lines. |
|
||||||
|
| Response times P50/P95/P99 | **N/A** | No load harness yet; perf tests are spec'd in `_docs/02_document/tests/performance-tests.md` but not implemented. Step 15 (Performance Test) is where these get measured. |
|
||||||
|
| CPU / Memory baseline | **N/A** | Same as above. |
|
||||||
|
| Throughput | **N/A** | Same as above. |
|
||||||
|
| Dependency count (target files) | Captured | `JwtExtensions.cs` deps: `Microsoft.AspNetCore.Authentication.JwtBearer`, `Microsoft.IdentityModel.Protocols`, `Microsoft.IdentityModel.Tokens` (all pinned in `src/Azaion.Annotations.csproj`). `FailsafeProducer.cs` deps: `LinqToDB`, `RabbitMQ.Stream.Client`, `MessagePack`, `System.Net` (BCL). No new dependencies added by C01/C02. |
|
||||||
|
| Outdated dependencies | **N/A** | Out of scope for testability. |
|
||||||
|
| Security vulnerabilities | **N/A** | Step 14 (Security Audit) — separate concern. The change does not introduce new attack surface (C01 narrows by env, C02 same threat model). |
|
||||||
|
| Build time | Captured | Not measured today; not material to a 2-file change. Phase 6 will run `dotnet build` and confirm it remains green. |
|
||||||
|
| Test execution time | **N/A** | No tests yet. |
|
||||||
|
| Deployment time | **N/A** | Out of scope. |
|
||||||
|
|
||||||
|
## Functionality Inventory — affected surface
|
||||||
|
|
||||||
|
| Endpoint / Background work | File(s) | Affected by | Behavioral change visible to a consumer? |
|
||||||
|
|----------------------------|---------|-------------|-----------------------------------------|
|
||||||
|
| All `[Authorize]`-protected endpoints (everything except `/health`) | `src/Auth/JwtExtensions.cs` | C01 | In `ASPNETCORE_ENVIRONMENT=E2ETest`, the SUT will fetch JWKS over HTTP. In all other environments (Development, Production), behavior is identical — `RequireHttps=true` remains the default. |
|
||||||
|
| `FailsafeProducer` (`BackgroundService`) drain loop | `src/Services/FailsafeProducer.cs` | C02 | When `RABBITMQ_HOST` is a literal IP: no behavioral change. When `RABBITMQ_HOST` is a DNS hostname: the producer now connects (previous behavior: throws `FormatException` every 10 s, outbox never drains). |
|
||||||
|
| `AnnotationService.CreateAnnotation` → `FailsafeProducer.EnqueueAsync` (static, synchronous outbox insert) | `src/Services/AnnotationService.cs:102` | not affected | No change. |
|
||||||
|
| Public API surface (DTOs, OpenAPI shape) | `src/DTOs/`, controllers | not affected | No change. |
|
||||||
|
|
||||||
|
## Reproducibility
|
||||||
|
|
||||||
|
- Source state captured by git commit at the start of this run (next commit after `_docs/_autodev_state.md` step-4 in_progress write).
|
||||||
|
- Both files have local line numbers documented in `list-of-changes.md` for unambiguous before/after diffing.
|
||||||
|
- No external tooling required beyond `dotnet build` and a `grep` over the two affected files post-change.
|
||||||
|
|
||||||
|
## Self-verification
|
||||||
|
|
||||||
|
- [x] RUN_DIR created with prefix `01-testability-refactoring` (no prior `NN-*` folders in `_docs/04_refactoring/`).
|
||||||
|
- [x] Goals documented; map 1:1 to `list-of-changes.md` entries.
|
||||||
|
- [x] Acceptance criteria stated and measurable.
|
||||||
|
- [x] Metric categories captured OR explicitly marked N/A with reason.
|
||||||
|
- [x] Functionality inventory covers every public-API impact of C01 and C02.
|
||||||
|
- [x] Measurements are reproducible from the listed files + a clean checkout.
|
||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
# Discovery — Component: Auth & Identity (scoped to C01)
|
||||||
|
|
||||||
|
**Component**: `06_platform` → Auth & Identity subsystem
|
||||||
|
**Source files in scope**: `src/Auth/JwtExtensions.cs`
|
||||||
|
**Component spec reference**: `_docs/02_document/modules/auth-identity.md`
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
JWT validation for API authorization policies (`ANN`, `DATASET`, `ADM`). Annotations is a **verifier-only** service — all token minting is the admin service's responsibility.
|
||||||
|
|
||||||
|
## Affected API / behavior
|
||||||
|
|
||||||
|
- `JwtExtensions.AddJwtAuth(IServiceCollection, IConfiguration)` — wires the JWT bearer scheme. The line affected by C01 is the `HttpDocumentRetriever` construction (line 33). No method signature changes. No DI graph changes. The `TokenValidationParameters` block (issuer, audience, lifetime, ES256 alg pinning, signed-tokens requirement) is untouched by this change.
|
||||||
|
|
||||||
|
## Coupling map (affected only)
|
||||||
|
|
||||||
|
```
|
||||||
|
Program.cs
|
||||||
|
└─ builder.Services.AddJwtAuth(builder.Configuration) ← caller of the affected code
|
||||||
|
|
||||||
|
Auth/JwtExtensions.cs (AddJwtAuth)
|
||||||
|
├─ ConfigurationResolver.ResolveRequiredOrThrow ← unaffected
|
||||||
|
├─ new ConfigurationManager<JsonWebKeySet>( ← container, unaffected
|
||||||
|
│ jwksUrl,
|
||||||
|
│ new JwksRetriever(),
|
||||||
|
│ new HttpDocumentRetriever { RequireHttps = true } ← C01 changes this constant to env-gated
|
||||||
|
│ )
|
||||||
|
└─ services.AddAuthentication(...).AddJwtBearer(...) ← unaffected
|
||||||
|
```
|
||||||
|
|
||||||
|
## C01 — input file claims vs. code reality
|
||||||
|
|
||||||
|
| Claim in `list-of-changes.md` C01 | Verification against `src/Auth/JwtExtensions.cs` | Status |
|
||||||
|
|----------------------------------|--------------------------------------------------|--------|
|
||||||
|
| `RequireHttps = true` on line 33 | Confirmed at line 33 (`new HttpDocumentRetriever { RequireHttps = true }`). | ✓ |
|
||||||
|
| No `IHostEnvironment` parameter on `AddJwtAuth` today | Confirmed — signature is `AddJwtAuth(IServiceCollection services, IConfiguration configuration)`. Adding an environment-name parameter (or reading `Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")` inline) does not change the public method shape. | ✓ |
|
||||||
|
| `Program.cs:53` already uses `builder.Environment.EnvironmentName` for the CORS validator | Confirmed (`CorsConfigurationValidator.EnsureSafeForEnvironment(allowedOrigins, allowAnyOrigin, builder.Environment.EnvironmentName)`). | ✓ |
|
||||||
|
| Test stack sets `ASPNETCORE_ENVIRONMENT=E2ETest` | Confirmed in `e2e/docker-compose.test.yml` line 76 (`ASPNETCORE_ENVIRONMENT: E2ETest`). | ✓ |
|
||||||
|
| Open Risks §6 in `architecture.md` flags this exact change | Confirmed — `_docs/02_document/architecture.md` Open Risks §6 reads: "JWKS HTTPS-only retrieval blocks plain-HTTP test harness; resolution is `ASPNETCORE_ENVIRONMENT=E2ETest` + relaxed `RequireHttps` for tests, never in production." | ✓ |
|
||||||
|
| `test-data.md` "Bearer token harness" §2 prescribes the same fix | Confirmed verbatim. | ✓ |
|
||||||
|
|
||||||
|
All claims hold; no contradictions to surface to the user.
|
||||||
|
|
||||||
|
## Issues discovered during scoped analysis (additional to the input file)
|
||||||
|
|
||||||
|
None within the C01 scope. The `IssuerSigningKeyResolver` uses `.GetAwaiter().GetResult()` (sync-over-async on the auth hot path) — already enumerated under "Deferred to Step 8 Refactor" in `list-of-changes.md`; the test suite does not depend on substituting it, so no change is required for testability.
|
||||||
|
|
||||||
|
## Architecture Vision check
|
||||||
|
|
||||||
|
`_docs/02_document/architecture.md` Architecture Vision § "Verifier-only auth, no token issuance in annotations":
|
||||||
|
- C01 does NOT change verification semantics — algorithm pinning, signature, lifetime, audience, and issuer all remain enforced.
|
||||||
|
- C01 changes only the *transport requirement* for fetching the public-key document from a non-production issuer URL.
|
||||||
|
- No contradiction.
|
||||||
|
|
||||||
|
## Module-layout check
|
||||||
|
|
||||||
|
`_docs/02_document/module-layout.md` Component 06 (`06_platform`) → Auth: the affected file `src/Auth/JwtExtensions.cs` is the documented owner; no boundary crossing.
|
||||||
|
|
||||||
|
## Public API impact
|
||||||
|
|
||||||
|
None — `AddJwtAuth` signature unchanged; no DTOs, OpenAPI shapes, or HTTP responses affected.
|
||||||
+75
@@ -0,0 +1,75 @@
|
|||||||
|
# Discovery — Component: Realtime Sync / Failsafe Producer (scoped to C02)
|
||||||
|
|
||||||
|
**Component**: `02 annotations-realtime-sync`
|
||||||
|
**Source files in scope**: `src/Services/FailsafeProducer.cs`
|
||||||
|
**Component spec reference**: `_docs/02_document/modules/rabbitmq-stream-sync.md`
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Outbox drain + RabbitMQ Stream producer (`BackgroundService`). Reads `annotations_queue_records`, serializes payloads (MessagePack + gzip), publishes to the `azaion-annotations` stream, then deletes drained rows.
|
||||||
|
|
||||||
|
## Affected API / behavior
|
||||||
|
|
||||||
|
- `FailsafeProducer.ProcessQueue(CancellationToken)` — line 54-76 — currently constructs `StreamSystem` via:
|
||||||
|
```csharp
|
||||||
|
Endpoints = [new IPEndPoint(IPAddress.Parse(config.Host), config.Port)]
|
||||||
|
```
|
||||||
|
Affected by C02. No other call site uses `IPAddress.Parse` against `config.Host`.
|
||||||
|
- The `FailsafeProducer` constructor (line 24-29) takes `IServiceScopeFactory`, `PathResolver`, `RabbitMqConfig`, `ILogger`. **Unchanged by C02.**
|
||||||
|
- The static `FailsafeProducer.EnqueueAsync` (line 195) — synchronous outbox row insert called from `AnnotationService` — does NOT use the broker connection and is unaffected.
|
||||||
|
|
||||||
|
## Coupling map (affected only)
|
||||||
|
|
||||||
|
```
|
||||||
|
Program.cs
|
||||||
|
└─ builder.Services.AddSingleton(rabbitMqConfig) ← unaffected
|
||||||
|
└─ builder.Services.AddHostedService<FailsafeProducer> ← unaffected
|
||||||
|
|
||||||
|
Services/FailsafeProducer.cs
|
||||||
|
├─ ExecuteAsync ← unaffected (loop / retry envelope)
|
||||||
|
├─ ProcessQueue ← C02 changes this line
|
||||||
|
│ ├─ IPAddress.Parse(config.Host) ← REPLACED by env-resolve
|
||||||
|
│ └─ StreamSystem.Create / Producer.Create ← unchanged
|
||||||
|
├─ DrainQueue ← unaffected (queue read / msg build / publish / delete)
|
||||||
|
└─ EnqueueAsync (static) ← unaffected
|
||||||
|
```
|
||||||
|
|
||||||
|
## C02 — input file claims vs. code reality
|
||||||
|
|
||||||
|
| Claim in `list-of-changes.md` C02 | Verification against `src/Services/FailsafeProducer.cs` | Status |
|
||||||
|
|----------------------------------|---------------------------------------------------------|--------|
|
||||||
|
| `IPAddress.Parse(config.Host)` on line 56 | Confirmed at line 56. | ✓ |
|
||||||
|
| `IPAddress.Parse` throws `FormatException` for non-IP strings | Verified against .NET BCL contract for `IPAddress.Parse(string)`. | ✓ |
|
||||||
|
| `config.Host` is populated from `RABBITMQ_HOST` env var | Confirmed at `Program.cs:40` (`Environment.GetEnvironmentVariable("RABBITMQ_HOST") ?? "127.0.0.1"`). | ✓ |
|
||||||
|
| Test stack sets `RABBITMQ_HOST=rabbitmq` (DNS hostname) | Confirmed in `e2e/docker-compose.test.yml` line 82. | ✓ |
|
||||||
|
| Test-environment fallback default in `RabbitMqConfig` class is `"rabbitmq"` | Confirmed in `FailsafeProducer.cs:17` (`public string Host { get; set; } = "rabbitmq"`). This means even ignoring `Program.cs`, the *default* triggers the bug. | ✓ |
|
||||||
|
| `BackgroundService` catches exceptions in `ExecuteAsync` and backs off 10 s | Confirmed at lines 44-48. | ✓ |
|
||||||
|
| Outbox insert (`EnqueueAsync`) is synchronous from the request thread and unaffected | Confirmed at line 195; called from `AnnotationService.cs:102`. | ✓ |
|
||||||
|
| `IPEndPoint` ctor requires `IPAddress`, not hostname | Verified against `RabbitMQ.Stream.Client` API surface (`StreamSystemConfig.Endpoints` is `IList<EndPoint>`; `IPEndPoint` is the standard-library type the existing code uses; `RabbitMQ.Stream.Client` accepts any `System.Net.EndPoint`, so a `DnsEndPoint` is a theoretical alternative — but every example in the client repo uses `IPEndPoint`, and the call is wrapped in a sync `IPEndPoint` constructor today, so the smallest-change path is to keep `IPEndPoint` and resolve the hostname ourselves). | ✓ |
|
||||||
|
|
||||||
|
All claims hold; no contradictions to surface to the user.
|
||||||
|
|
||||||
|
## Issues discovered during scoped analysis (additional to the input file)
|
||||||
|
|
||||||
|
1. **`IServiceScopeFactory.CreateScope()` is called inside `DrainQueue` to fetch a scoped `AppDataConnection`** (line 80). This is fine — it follows the documented `BackgroundService` pattern. Not in scope.
|
||||||
|
2. **`catch { }` at line 138 swallows image-read failures** — already enumerated under "Deferred to Step 8 Refactor" in `list-of-changes.md` (RB-05 tracks the proper logging + metric). No change here.
|
||||||
|
3. **`ProcessQueue` creates a new `StreamSystem` on every entry** — i.e., on every retry. With C02 applied, this remains the behavior — broker reconnects per outage cycle. Acceptable; matches the documented "broker recovers, drain resumes" behavior in NFT-RES-01. No additional change.
|
||||||
|
|
||||||
|
## Architecture Vision check
|
||||||
|
|
||||||
|
`_docs/02_document/architecture.md` Architecture Vision § "Lifecycle observability via outbox + stream":
|
||||||
|
- C02 is required to *honor* this vision in any environment where `RABBITMQ_HOST` is a hostname. Today the producer silently never drains in such environments.
|
||||||
|
- The fix preserves the documented flow (outbox row → batch read → MessagePack serialize → gzip → publish → delete row).
|
||||||
|
- No contradiction; this change is squarely aligned with the vision.
|
||||||
|
|
||||||
|
## Module-layout check
|
||||||
|
|
||||||
|
`_docs/02_document/module-layout.md` Component 02 (`02_annotations-realtime-sync`) → `FailsafeProducer` is the documented owner; the static `EnqueueAsync` helper is part of the Component 02 Public API (per F3 in the baseline) and is unchanged. C02 is internal to the component.
|
||||||
|
|
||||||
|
## Public API impact
|
||||||
|
|
||||||
|
None — `FailsafeProducer` is a `BackgroundService`; its `ExecuteAsync` is called by the host. The static `EnqueueAsync` (the only external surface) is unchanged. No DTOs, no HTTP shapes, no MessagePack wire format affected.
|
||||||
|
|
||||||
|
## Wire-format / stream contract check
|
||||||
|
|
||||||
|
C02 changes *how the producer reaches the broker*, not what it sends. Verified by reading `DrainQueue` (lines 78-181): the `MessagePackSerializer.Serialize(...)` calls, `Producer.Send(messages, CompressionType.Gzip)`, and the queue-table delete are all downstream of the line C02 touches and are untouched. Consumers (admin's `AnnotationSyncWorker`, AI Training consumer) see identical messages.
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# Logical Flow Analysis — 01-testability-refactoring (scoped)
|
||||||
|
|
||||||
|
**Scope**: only the two flows whose code is touched by C01 and C02.
|
||||||
|
**Method**: each documented flow walked through actual code line-by-line; classified per phases/01-discovery.md guidance (logic bug / performance waste / design contradiction / documentation drift).
|
||||||
|
|
||||||
|
## Flow 1 — Bearer-token verification
|
||||||
|
|
||||||
|
**Documented in**: `_docs/02_document/diagrams/flows/` (auth-related sequence), `_docs/02_document/modules/auth-identity.md`, `_docs/02_document/tests/test-data.md` "Bearer token harness", `_docs/02_document/architecture.md` Open Risks §6.
|
||||||
|
|
||||||
|
**Walk-through**:
|
||||||
|
|
||||||
|
1. Request arrives at any `[Authorize]` controller (e.g., `POST /annotations`).
|
||||||
|
2. ASP.NET Core auth middleware invokes the configured `JwtBearer` scheme.
|
||||||
|
3. The scheme reads `TokenValidationParameters` from `JwtExtensions.AddJwtAuth` — these are correct (alg pinned, lifetime/issuer/audience validated, signature required).
|
||||||
|
4. For signature verification, `IssuerSigningKeyResolver` (line 52-63) calls `jwksConfigManager.GetConfigurationAsync(...)`.
|
||||||
|
5. On first call, `ConfigurationManager<JsonWebKeySet>` fetches the JWKS document using `HttpDocumentRetriever`. Today: `RequireHttps = true`. The mock issuer in the e2e stack serves `http://e2e-issuer:8080/.well-known/jwks.json` — a plain-HTTP URL. The retriever throws `InvalidOperationException("The URL must use HTTPS")`.
|
||||||
|
6. Token validation surfaces the exception as a 401/500 in unpredictable ways (depends on the framework's error envelope path), and **no real validation logic was ever exercised**.
|
||||||
|
|
||||||
|
**Findings**:
|
||||||
|
|
||||||
|
- **Documentation drift — NONE.** The architecture doc and test-data doc both already specify the expected behavior and the fix (env-gated `RequireHttps`).
|
||||||
|
- **Logic bug — None in production.** In production the JWKS URL IS HTTPS, so the current code works. The bug is environment-specific: it only blocks the test harness.
|
||||||
|
- **Design contradiction** — the test harness assumes HTTP-only JWKS service is acceptable, but the SUT enforces HTTPS unconditionally. C01 resolves this by reading the environment name and relaxing the requirement only under `E2ETest`.
|
||||||
|
- **Silent data loss** — N/A.
|
||||||
|
|
||||||
|
**Classification**: documented design contradiction. Resolution: C01.
|
||||||
|
|
||||||
|
**Loop / boundary check**: not applicable (no loops in the auth-init code path).
|
||||||
|
|
||||||
|
## Flow 2 — Outbox drain → RabbitMQ stream
|
||||||
|
|
||||||
|
**Documented in**: `_docs/02_document/diagrams/flows/flow_failsafe_drain.md`, `_docs/02_document/modules/rabbitmq-stream-sync.md`, `_docs/02_document/architecture.md` (ADR-008 transactional outbox).
|
||||||
|
|
||||||
|
**Walk-through**:
|
||||||
|
|
||||||
|
1. `AnnotationService.CreateAnnotation` (line 102) calls `FailsafeProducer.EnqueueAsync(db, id, QueueOperation.Created)` — synchronous DB insert into `annotations_queue_records`. **No broker dependency.** Always succeeds when DB is up.
|
||||||
|
2. The host's `BackgroundService` invokes `FailsafeProducer.ExecuteAsync` shortly after startup (`Task.Delay(5s)` at line 32).
|
||||||
|
3. `ExecuteAsync` enters its loop and calls `ProcessQueue`.
|
||||||
|
4. `ProcessQueue` constructs `StreamSystem`:
|
||||||
|
```csharp
|
||||||
|
Endpoints = [new IPEndPoint(IPAddress.Parse(config.Host), config.Port)]
|
||||||
|
```
|
||||||
|
With `config.Host = "rabbitmq"`, `IPAddress.Parse` throws **`FormatException: An invalid IP address was specified.`**.
|
||||||
|
5. Control returns up the stack. `ExecuteAsync`'s outer `catch (Exception ex)` at line 44 catches it, logs `ex.Message`, and `await Task.Delay(TimeSpan.FromSeconds(10), ct)`.
|
||||||
|
6. The loop restarts → step 4 → same exception → same 10s back-off. **The drain never executes.**
|
||||||
|
|
||||||
|
**Findings**:
|
||||||
|
|
||||||
|
- **Silent data loss — YES (production-relevant).** With `RABBITMQ_HOST` set to any non-IP value (typical: docker-compose service name, Kubernetes service DNS, or any deployment using container DNS), the outbox grows monotonically and is never published to consumers. The error IS logged, but unless logs are alerted on, it manifests as "stream consumers see no traffic". This is a **logic bug**, not a documentation drift — the documented flow assumes the drain works.
|
||||||
|
- **Documentation drift — NONE.** The flow diagram and module spec describe correct behavior; the implementation has a latent bug.
|
||||||
|
- **Design contradiction — NONE.** The fix is mechanical.
|
||||||
|
- **Performance waste** — every 10 seconds the producer does the work to start an exception, log it, and back off. Trivial compared to the real production impact (no messages publish).
|
||||||
|
- **Why not caught by `architecture_compliance_baseline.md`**: the baseline checks structural properties (layering, public-API respect, cycles, duplicate symbols, cross-cutting concerns). API-level correctness ("is `IPAddress.Parse` the right API for this input?") is not in Phase 7's mandate.
|
||||||
|
- **Why not caught by `_docs/02_document/00_discovery.md`**: discovery documents *intent*, not implementation correctness against the BCL contract.
|
||||||
|
- **Why surfaced now**: the test harness uses a service-name hostname, which forces exercise of the bug. Without the test harness, the bug remained latent.
|
||||||
|
|
||||||
|
**Classification**: logic bug. Resolution: C02.
|
||||||
|
|
||||||
|
**Loop / boundary check**:
|
||||||
|
- The retry loop in `ExecuteAsync` correctly catches `OperationCanceledException` and propagates cancellation (line 40-42).
|
||||||
|
- The drain loop in `ProcessQueue` (line 65-69) correctly checks `ct.IsCancellationRequested`.
|
||||||
|
- The `DrainQueue` foreach is correct — it processes every queue record, deletes drained rows in bulk (line 176-180).
|
||||||
|
- No silent-drop edge cases inside the drain itself.
|
||||||
|
|
||||||
|
The bug is strictly in the *transition from `RabbitMqConfig.Host` to a `System.Net.IPEndPoint`*.
|
||||||
|
|
||||||
|
## Cross-flow check
|
||||||
|
|
||||||
|
C01 and C02 are **independent**:
|
||||||
|
- C01 affects only `AddJwtAuth` (a one-shot, at startup).
|
||||||
|
- C02 affects only `FailsafeProducer.ProcessQueue` (a `BackgroundService` cold-path).
|
||||||
|
- No shared symbol; no ordering dependency.
|
||||||
|
|
||||||
|
Both changes preserve the documented Architecture Vision and module-layout boundaries (see component reports).
|
||||||
|
|
||||||
|
## Contradictions surfaced to user — NONE
|
||||||
|
|
||||||
|
Both input-file entries are consistent with the code reality. No changes recommended outside the input file. No need to escalate before the Phase 1 BLOCKING gate.
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# List of Changes
|
||||||
|
|
||||||
|
**Run**: 01-testability-refactoring
|
||||||
|
**Mode**: guided
|
||||||
|
**Source**: autodev-testability-analysis
|
||||||
|
**Date**: 2026-05-14
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Two surgical changes are required before the blackbox test suite (67 scenarios, `_docs/02_document/tests/`) can run against the SUT. Both are bounded fixes that respect the existing-code Step 4 allowed-changes envelope (env-driven config gating; no algorithm/business-logic change). One change (C01) is the previously-documented HTTPS-only JWKS retriever — explicitly flagged in `architecture.md` Open Risks §6 and `test-data.md` → "Bearer token harness" step 2. The other (C02) is a latent `IPAddress.Parse` bug in `FailsafeProducer` that throws on every drain cycle when `RABBITMQ_HOST` is a DNS hostname (which it is in the documented test environment and in production docker-compose).
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
### C01: Relax JWKS HTTPS requirement under `ASPNETCORE_ENVIRONMENT=E2ETest`
|
||||||
|
|
||||||
|
- **File(s)**: `src/Auth/JwtExtensions.cs`
|
||||||
|
- **Problem**: `AddJwtAuth` instantiates `new HttpDocumentRetriever { RequireHttps = true }` (line 33). The blackbox test stack runs a mock JWKS issuer over **plain HTTP** at `http://e2e-issuer:8080/.well-known/jwks.json` (see `_docs/02_document/tests/environment.md` services table and `test-data.md` → "Bearer token harness"). With `RequireHttps=true` the SUT throws on the very first JWKS fetch, so every authenticated request fails at boot or at first request — masking the real auth-validation behavior the tests are designed to exercise (FT-P-12/-13, FT-N-09..11, NFT-SEC-01..10, plus every other authenticated endpoint test). The Open Risks §6 entry in `architecture.md` and the test-data harness section already prescribe the fix: gate `RequireHttps` on `ASPNETCORE_ENVIRONMENT == "E2ETest"`, defaulting to `true` everywhere else (development, production).
|
||||||
|
- **Change**: Replace the constant `RequireHttps = true` with a value derived from `IHostEnvironment.EnvironmentName`. When the environment name is `E2ETest`, set `RequireHttps = false`; otherwise keep `true`. The environment name is the existing ASP.NET Core hook (`builder.Environment.EnvironmentName`) and is already passed to `CorsConfigurationValidator.EnsureSafeForEnvironment(...)` in `Program.cs:53`, so there is precedent for environment-gated behavior.
|
||||||
|
- **Rationale**: Smallest possible change to enable the documented test harness without weakening production. The relaxation is one-flag, gated on a string the operator never sets in production, and named explicitly to match the docker-compose stack (`ASPNETCORE_ENVIRONMENT: E2ETest` in `e2e/docker-compose.test.yml:76`).
|
||||||
|
- **Constraint Fit**:
|
||||||
|
- **SW-05** (JWT verifier-only, ES256 over admin's JWKS, alg pinned) — unchanged. Algorithm pinning, signature validation, lifetime and audience checks are untouched.
|
||||||
|
- **AC-F-50** (Bearer token verification) — unchanged; the relaxation affects only the *transport* of the JWKS document, not validation rules.
|
||||||
|
- **NFT-SEC-09** (CORS allow-list) — independent; CORS validator already environment-aware.
|
||||||
|
- **Architecture Vision** in `architecture.md` and **module-layout.md** Component 06 ownership of Auth — unchanged.
|
||||||
|
- **ENV-02** ("Service on port 8080 HTTP, no in-image TLS") — directly aligned; the SUT has never been TLS-terminated, so requiring HTTPS for the *issuer side* of JWKS retrieval was always a test-environment friction point.
|
||||||
|
- Open Risks §6 in `architecture.md` is explicitly retired by this change.
|
||||||
|
- **Risk**: low. One conditional read of `EnvironmentName`; no change to validation parameters or signing-key resolution.
|
||||||
|
- **Dependencies**: None.
|
||||||
|
|
||||||
|
### C02: Resolve RabbitMQ broker host through DNS in `FailsafeProducer`
|
||||||
|
|
||||||
|
- **File(s)**: `src/Services/FailsafeProducer.cs`
|
||||||
|
- **Problem**: `ProcessQueue` builds the stream connection with `Endpoints = [new IPEndPoint(IPAddress.Parse(config.Host), config.Port)]` (line 56). `IPAddress.Parse` throws `FormatException` for any non-IP string. In the documented test environment, `RABBITMQ_HOST=rabbitmq` is a docker-compose service name (`_docs/02_document/tests/environment.md` and `e2e/docker-compose.test.yml:82`). The same pattern is used in any realistic production deployment that uses service discovery or container DNS. Today every drain attempt throws on the first line of `ProcessQueue`, the BackgroundService's outer `catch` logs the exception and backs off 10 s, and the outbox **never drains**. This is invisible at runtime if no one is reading the logs, but it blocks every test that asserts the drain path: FT-P-09 (stream message round-trip), NFT-RES-01 (broker outage + recovery), NFT-RES-06 (consumer reconnect), NFT-RES-LIM-03 (outbox depth under outage), NFT-PERF-OUTBOX-DRAIN-01 (drain rate). FT-P-08 (outbox row inserted by `EnqueueAsync` synchronously from the request thread) still passes — the bug is isolated to the drain path.
|
||||||
|
- **Change**: Before constructing the `IPEndPoint`, resolve `config.Host` to an `IPAddress`. If `IPAddress.TryParse(config.Host, out var ip)` succeeds (caller supplied a literal IP), use that. Otherwise call `Dns.GetHostAddressesAsync(config.Host, ct)` and use the first returned address. Behavior is unchanged for callers who already passed an IP literal; callers who pass a hostname now connect instead of throwing.
|
||||||
|
- **Rationale**: This is the minimum change to make the existing tests runnable — `RabbitMQ.Stream.Client`'s `IPEndPoint` API takes an `IPAddress`, not a hostname, so the resolution has to happen somewhere. The host name is read from `RABBITMQ_HOST` env var by `Program.cs:40`; changing only the *parsing* assumption preserves the env-var contract and the operator-visible config surface. No algorithm, business logic, schema, queue layout, or wire format changes.
|
||||||
|
- **Constraint Fit**:
|
||||||
|
- **SW-03** (RabbitMQ streams plugin) — unchanged.
|
||||||
|
- **AC-F-12** (Outbox drain → stream) and **AC-N-03** (queue depth bounded) — these become *actually testable* rather than passing by virtue of the bug never being exercised.
|
||||||
|
- **ENV-01** (env vars required: `RABBITMQ_HOST` etc.) — unchanged; same env-var contract.
|
||||||
|
- **Architecture Vision** § "Lifecycle observability via outbox + stream" — unchanged; if anything, this change is *required* to honor it in real deployments.
|
||||||
|
- **module-layout.md** Component 02 (`02 annotations-realtime-sync`) — `FailsafeProducer` is the documented owner; change is internal to the component.
|
||||||
|
- **Risk**: medium. The change reaches into `BackgroundService` execution, but the surface is one line of resolution wrapped in the existing `try/catch` envelope. Risk is "medium" rather than "low" because DNS failures in production (broker unreachable) now fail at hostname resolution rather than at `IPAddress.Parse`; the outer catch handles both identically (log + back off + retry), so the operator-visible behavior is the same.
|
||||||
|
- **Dependencies**: None.
|
||||||
|
|
||||||
|
## Deferred to Step 8 Refactor
|
||||||
|
|
||||||
|
The following testability-adjacent items were considered and **rejected** for this run because they exceed the surgical envelope or are already accepted as technical debt:
|
||||||
|
|
||||||
|
- **`FailsafeProducer.EnqueueAsync` is a static method that performs DB I/O** (F3 in `architecture_compliance_baseline.md`). Accepted as tech debt per stakeholder review; no test depends on substituting this implementation.
|
||||||
|
- **`FailsafeProducer.DrainQueue` line 138 `catch { }` on missing-image read** — RB-05 covers this. NFT-RES-05 was authored to test today's behavior; it does not require the change.
|
||||||
|
- **`JwtExtensions.IssuerSigningKeyResolver` uses `.GetAwaiter().GetResult()` on JWKS fetch** — sync-over-async on the auth hot path. Real concern, but tests do not depend on it; defer to Step 8.
|
||||||
|
- **`ClassesController` direct `AppDataConnection` injection** (F2 in baseline). RB-06 tracks the service-layer move; FT-P-14 reads the endpoint as-is and passes.
|
||||||
|
- **`DatasetService` direct mutation of `annotations.status`** (F1, Medium). RB-08 tracks the routing through `AnnotationService`; AC-F-05/-06/-07 tests are skipped until RB-01+RB-08 land — already documented in `traceability-matrix.md`.
|
||||||
|
- **Hardcoded `/data/...` defaults in `PathResolver` and `DatabaseMigrator.directory_settings`** — these are *seed defaults*; the test docker-compose mounts volumes at exactly those paths, and FT-P-15 exercises the runtime override via `PUT /settings/directories`. No change needed for testability.
|
||||||
|
- **`MediaService.ExtractDuration` swallows `ffprobe` failures** — coderule.mdc violation, but no test asserts on `duration`. Defer.
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# Autodev State
|
||||||
|
|
||||||
|
## Current Step
|
||||||
|
flow: existing-code
|
||||||
|
step: 4
|
||||||
|
name: Code Testability Revision
|
||||||
|
status: in_progress
|
||||||
|
sub_step:
|
||||||
|
phase: 3
|
||||||
|
name: phase-2-task-decomposition
|
||||||
|
detail: ""
|
||||||
|
retry_count: 0
|
||||||
|
cycle: 1
|
||||||
|
tracker: local
|
||||||
|
|
||||||
|
## Completed Steps
|
||||||
|
- step: 1
|
||||||
|
name: Document
|
||||||
|
status: completed
|
||||||
|
- step: 2
|
||||||
|
name: Architecture Baseline Scan
|
||||||
|
status: completed
|
||||||
|
outcome: "PASS_WITH_WARNINGS — 0 Critical, 0 High, 1 Medium (RB-08 logical coupling), 2 Low (RB-06 ClassesController, accepted-debt FailsafeProducer.EnqueueAsync)"
|
||||||
|
- step: 3
|
||||||
|
name: Test Spec
|
||||||
|
status: completed
|
||||||
|
outcome: "67 scenarios authored across 6 test-spec files; coverage 88% (40/45 active items, 6 RB-deferred, 5 truly uncovered with documented reasons); Docker-only execution; scripts/run-tests.sh + scripts/run-performance-tests.sh + e2e/docker-compose.test.yml + e2e/seed/run.sh produced and syntactically valid"
|
||||||
|
|
||||||
|
## Mid-step adjustments
|
||||||
|
- 2026-05-14: targeted auth + CORS re-sync triggered by codebase drift discovered at Step 4 entry.
|
||||||
|
- Detected: AuthController + TokenService removed; JwtExtensions switched from HS256 symmetric to ES256 over admin's JWKS; ConfigurationResolver and CorsConfigurationValidator added in src/Infrastructure/.
|
||||||
|
- User-chosen path: Option A — targeted re-sync, then continue to Step 4 proper.
|
||||||
|
- Files touched (19): _docs/02_document/architecture.md, module-layout.md (already aligned), system-flows.md, glossary.md, FINAL_report.md, 04_verification_log.md, architecture_compliance_baseline.md, 00_discovery.md, modules/auth-identity.md (already aligned), modules/composition-program.md (already aligned), deployment/environment_strategy.md (already aligned); _docs/00_problem/problem.md, restrictions.md, acceptance_criteria.md, security_approach.md (already aligned), input_data/data_parameters.md, input_data/expected_results/results_report.md; _docs/01_solution/solution.md; _docs/02_document/tests/blackbox-tests.md, security-tests.md, traceability-matrix.md, test-data.md, environment.md; e2e/docker-compose.test.yml; e2e/seed/run.sh.
|
||||||
|
- ADR-002 and ADR-006 marked RETIRED. SEC-01, SEC-02, SEC-03 marked Closed. Refactor Backlog unaffected.
|
||||||
|
- One new testability open risk recorded in architecture.md (Open Risks §6): JWKS HTTPS-only retrieval blocks plain-HTTP test harness; resolution is `ASPNETCORE_ENVIRONMENT=E2ETest` + relaxed `RequireHttps` for tests, never in production.
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# Deferred Tracker Writes — 2026-05-14 (testability refactor)
|
||||||
|
|
||||||
|
**Created**: 2026-05-14T19:15+03:00
|
||||||
|
**Trigger**: Atlassian MCP (Jira) reported `errored` status during `/autodev` Step 4 → refactor skill Phase 2c (Create Epic) and 2d (Task Decomposition).
|
||||||
|
**User decision**: skipped the structured tracker gate; autodev proceeded in `tracker: local` mode and recorded these deferred writes for replay.
|
||||||
|
**Replay target**: Jira at `denyspopov.atlassian.net`, project key `AZ`.
|
||||||
|
|
||||||
|
## Pending Writes
|
||||||
|
|
||||||
|
### 1. Epic — `01-testability-refactoring`
|
||||||
|
|
||||||
|
- **Type**: Epic
|
||||||
|
- **Summary**: `01-testability-refactoring` — gate JWKS HTTPS + fix RabbitMQ host resolution
|
||||||
|
- **Description**: Source of truth lives in `_docs/04_refactoring/01-testability-refactoring/list-of-changes.md`. This epic groups the two surgical changes needed to make the documented test suite (`_docs/02_document/tests/`) runnable against the SUT.
|
||||||
|
- **Story points**: 3 (sum of child tasks)
|
||||||
|
- **Project**: AZ
|
||||||
|
- **Target status**: To Do
|
||||||
|
|
||||||
|
### 2. Story / Task — JWKS HTTPS env gate (C01)
|
||||||
|
|
||||||
|
- **Type**: Story (or Task — match existing project convention)
|
||||||
|
- **Summary**: `[refactor] Gate JWKS HTTPS requirement on ASPNETCORE_ENVIRONMENT=E2ETest`
|
||||||
|
- **Description**: full body lives in `_docs/02_tasks/todo/01_refactor_jwks_https_env_gate.md` — copy verbatim into the Description field on replay.
|
||||||
|
- **Story points**: 1
|
||||||
|
- **Epic link**: the epic created in write 1
|
||||||
|
- **Project**: AZ
|
||||||
|
- **Target status**: To Do
|
||||||
|
- **Replay action**: after the ticket is created, rename the file to `_docs/02_tasks/todo/{AZ-NNNN}_refactor_jwks_https_env_gate.md` and update the `**Task**:` and `**Tracker**:` headers inside the file.
|
||||||
|
|
||||||
|
### 3. Story / Task — RabbitMQ host DNS resolution (C02)
|
||||||
|
|
||||||
|
- **Type**: Story (or Task — match existing project convention)
|
||||||
|
- **Summary**: `[refactor] Resolve RABBITMQ_HOST via DNS in FailsafeProducer`
|
||||||
|
- **Description**: full body lives in `_docs/02_tasks/todo/02_refactor_rabbitmq_host_dns_resolution.md` — copy verbatim into the Description field on replay.
|
||||||
|
- **Story points**: 2
|
||||||
|
- **Epic link**: the epic created in write 1
|
||||||
|
- **Project**: AZ
|
||||||
|
- **Target status**: To Do
|
||||||
|
- **Replay action**: after the ticket is created, rename the file to `_docs/02_tasks/todo/{AZ-NNNN}_refactor_rabbitmq_host_dns_resolution.md` and update the `**Task**:` and `**Tracker**:` headers inside the file.
|
||||||
|
|
||||||
|
## Replay Procedure
|
||||||
|
|
||||||
|
When Atlassian MCP is restored:
|
||||||
|
|
||||||
|
1. From the workspace root, invoke the autodev (or run leftovers replay manually):
|
||||||
|
- Create the epic per write 1.
|
||||||
|
- For each task write, create the issue with the documented Summary, Description (verbatim from the task file), Story points, and Epic link.
|
||||||
|
2. Update the task files in `_docs/02_tasks/todo/`:
|
||||||
|
- Rename each file to use the assigned `AZ-NNNN` key.
|
||||||
|
- Update the `**Task**:` header line.
|
||||||
|
- Update the `**Tracker**:` line from `pending` to the assigned ID.
|
||||||
|
- Update the `**Epic**:` line from `pending — 01-testability-refactoring` to the assigned epic ID.
|
||||||
|
3. Update `_docs/02_tasks/_dependencies_table.md` to reference the assigned IDs.
|
||||||
|
4. Update `_docs/_autodev_state.md`: change `tracker: local` to `tracker: jira` once all replays complete.
|
||||||
|
5. Delete this leftovers file.
|
||||||
|
|
||||||
|
## Blocker Reason
|
||||||
|
|
||||||
|
Atlassian MCP server is reporting `errored` per `/Users/obezdienie001/.cursor/projects/Users-obezdienie001-dev-azaion-suite-annotations/mcps/user-atlassian-mcp/STATUS.md`. Likely cause: auth token expired or MCP server restart pending. Resolution path: re-authenticate from Cursor Settings → MCP Servers → user-atlassian-mcp.
|
||||||
|
|
||||||
|
## Hard Gates Bypassed? — No
|
||||||
|
|
||||||
|
Per `tracker.mdc` Leftovers Mechanism, only **non-user-input** blockers may be deferred. The user-input gates that *cannot* be deferred (scope, approval, choice between alternatives, irreversible actions) were all answered before reaching this write step:
|
||||||
|
|
||||||
|
- C01 + C02 list-of-changes approved by user via the Phase 1 BLOCKING gate (option A).
|
||||||
|
- The tracker mode (local vs. Jira) was surfaced to the user; the user skipped the structured choice and let autodev continue with the information available, which the orchestrator interpreted as "proceed in local mode and record leftovers" — consistent with the user's prior explicit approval of the list-of-changes scope.
|
||||||
|
|
||||||
|
This file is appropriate for the leftovers mechanism — no user clarification is required to replay these writes; only the MCP being restored.
|
||||||
Reference in New Issue
Block a user