mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-22 09:41:14 +00:00
[AZ-487] JWT validation baseline (HS256, all endpoints)
Adds Microsoft.AspNetCore.Authentication.JwtBearer 8.0.21 and the SatelliteProvider.Api.Authentication.AddSatelliteJwt extension that validates HS256 tokens against a shared JWT_SECRET (>=32 bytes, fail fast at startup). Every minimal-API endpoint now carries .RequireAuthorization(); the middleware chain is UseExceptionHandler -> UseHttpsRedirection -> UseCors -> UseAuthentication -> UseAuthorization -> endpoints. Swagger UI gets a Bearer security definition so the Authorize button works. Test infrastructure: JwtTokenFactory (unit) and JwtTestHelpers (integration) mint deterministic tokens against the same secret; the integration test runner attaches a default Bearer token to its shared HttpClient so existing tests continue to exercise protected endpoints. JwtIntegrationTests adds AC-1..AC-4 and AC-7 (Swagger advertises Bearer) end-to-end; AuthenticationServiceCollectionExtensionsTests covers AC-5 (missing/empty/short secret fail-fast) plus env-var precedence; JwtTokenFactoryTests covers AC-6 (claims pass through the JwtSecurityTokenHandler.ValidateToken path JwtBearer uses). docker-compose and scripts/run-tests.sh now propagate JWT_SECRET to the api and integration-tests containers, with a >=32-byte guard. .env.example documents the required keys; .env stays gitignored. Code review verdict: PASS_WITH_WARNINGS (2 Low findings surfaced in _docs/03_implementation/reviews/batch_01_cycle2_review.md). Cross-component coordination: gps-denied-onboard and the mission planner UI must attach Bearer tokens before this lands in dev. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -22,7 +22,14 @@ The three Layer-3 service components are compile-time siblings: each only refere
|
||||
- Single-instance deployment, no horizontal scaling requirements (`inferred-from: Channel-based queue, no distributed state`)
|
||||
- Append-by-source tile storage — multiple producers (Google Maps, UAV upload, future SatAR, …) can each persist a row per `(latitude, longitude, tile_zoom, tile_size_meters)` cell. Reads return the most-recent row across sources, ordered by `captured_at DESC` with deterministic `(updated_at DESC, id DESC)` tie-breaks. The single-row-per-cell-per-source invariant is enforced by the 5-column unique index `idx_tiles_unique_location_source` introduced in migration 013 (AZ-484). The `tiles.version` column is vestigial since AZ-357 dropped year-based cache invalidation in favour of cell-level overwrite. (`inferred-from: tiles table + AZ-484/AZ-357 migrations + tile-storage contract v1.0.0`)
|
||||
- Fire-and-forget async processing with status polling (`inferred-from: queue + background service + status endpoint`)
|
||||
- No authentication layer — designed as an internal/trusted network service (`inferred-from: no auth middleware in Program.cs`)
|
||||
- JWT-validated callers only — every HTTP endpoint requires a valid HS256-signed Bearer token, validated locally against a shared `JWT_SECRET` per the suite-level auth contract (`suite/_docs/10_auth.md`). Issuer/audience are intentionally not validated yet; signature + lifetime + ≥32-byte key are. Per-endpoint permission claims (e.g. `permissions: ["GPS"]` on the UAV upload) layer on top of this baseline.
|
||||
|
||||
**Authentication & Authorization** (AZ-487):
|
||||
- Validation library: `Microsoft.AspNetCore.Authentication.JwtBearer` 8.0.21 (matches the rest of the ASP.NET Core 8 package set).
|
||||
- Signing key: read from the `JWT_SECRET` environment variable (preferred) or the `Jwt:Secret` configuration key. Startup fails fast if the resolved secret is unset, empty, or shorter than 32 bytes (HMAC-SHA256 minimum per RFC 2104 §3).
|
||||
- Token contract: `ValidateIssuerSigningKey = true`, `ValidateLifetime = true`, `RequireSignedTokens = true`, `RequireExpirationTime = true`, `ValidateIssuer/Audience = false`, `ClockSkew = 30s`. The 5-minute JwtBearer default is intentionally tightened.
|
||||
- Authorization model: every endpoint registered in `Program.cs` is decorated with `.RequireAuthorization()`. AZ-488 adds `permissions`-claim policies on top of this baseline (UAV upload requires `GPS`).
|
||||
- Test infrastructure: `JwtTokenFactory` (unit tests) and `JwtTestHelpers` (integration tests) mint deterministic tokens against the same `JWT_SECRET`; the integration test runner attaches a default Bearer token to its shared `HttpClient` so legacy non-auth tests continue to exercise the protected endpoints unchanged.
|
||||
|
||||
**Planned features** (confirmed by user, currently stubs):
|
||||
- MGRS endpoint — tile access via Military Grid Reference System coordinates
|
||||
@@ -132,16 +139,16 @@ The N-source storage contract is authoritative in `_docs/02_document/contracts/d
|
||||
|
||||
## 7. Security Architecture
|
||||
|
||||
**Authentication**: None (internal service, no auth layer)
|
||||
**Authentication**: HS256 JWT Bearer tokens (AZ-487). Signing key from `JWT_SECRET` env var (≥ 32 bytes, validated at startup). `Microsoft.AspNetCore.Authentication.JwtBearer` validates signature, lifetime, and signing key; issuer and audience are intentionally not validated (suite contract does not specify expected values). ClockSkew tightened from JwtBearer default (5 min) to 30 s. Tokens are minted by the centralized Admin API per `suite/_docs/10_auth.md`.
|
||||
|
||||
**Authorization**: None (all endpoints are open)
|
||||
**Authorization**: Every endpoint requires authentication via `.RequireAuthorization()`. Permission-claim enforcement (e.g. `permissions: ["GPS"]`) is added per-endpoint where needed — AZ-488 introduces it on `POST /api/satellite/upload`. Other endpoints accept any authenticated principal.
|
||||
|
||||
**Data protection**:
|
||||
- At rest: No encryption (tiles stored as plain JPEG files)
|
||||
- In transit: HTTPS for Google Maps calls; API itself on HTTP
|
||||
- Secrets management: Google Maps session token in appsettings / env vars
|
||||
- In transit: HTTPS for Google Maps calls; API itself runs HTTP behind Kestrel (TLS termination is a deployment-layer concern)
|
||||
- Secrets management: `JWT_SECRET` and `GOOGLE_MAPS_API_KEY` from environment variables / `.env` (gitignored); `.env.example` documents the required keys. Production deployments MUST supply both via the host environment, never via the appsettings files.
|
||||
|
||||
**Audit logging**: Serilog writes to file; logs exceptions and processing state transitions
|
||||
**Audit logging**: Serilog writes to file; logs exceptions and processing state transitions. 401/403 responses are emitted by the JwtBearer middleware via the `WWW-Authenticate` header; no body leakage of internal details.
|
||||
|
||||
## 8. Key Architectural Decisions
|
||||
|
||||
|
||||
@@ -36,12 +36,14 @@ Application entry point. Configures DI container, sets up middleware, defines mi
|
||||
6. Hosted services: `RegionProcessingService`, `RouteProcessingService`
|
||||
7. CORS policy: `TilesCors` — configured origins from `CorsConfig:AllowedOrigins`, falls back to allow-any
|
||||
8. JSON options: camelCase, case-insensitive
|
||||
9. **JWT authentication (AZ-487)**: `AddSatelliteJwt(builder.Configuration)` (extension in `SatelliteProvider.Api.Authentication`) registers `JwtBearer` with `TokenValidationParameters` set per the suite auth contract (signature + lifetime, no issuer/audience validation, 30 s clock skew, ≥ 32-byte HMAC key). Followed by `AddAuthorization()`.
|
||||
|
||||
### Startup
|
||||
1. Database migration via `DatabaseMigrator.RunMigrations()` — throws on failure
|
||||
2. Creates tiles and ready directories
|
||||
3. Swagger enabled in Development mode
|
||||
4. HTTPS redirection, CORS applied
|
||||
4. Middleware chain (order matters): `UseExceptionHandler` → `UseHttpsRedirection` → `UseCors("TilesCors")` → `UseAuthentication` → `UseAuthorization` → endpoint mapping.
|
||||
5. Every `MapGet`/`MapPost` endpoint is decorated with `.RequireAuthorization()`; the framework returns 401 before the handler runs for any anonymous, expired, or invalid-signature request.
|
||||
|
||||
### ServeTile Handler
|
||||
1. Checks `IMemoryCache` for tile bytes (1h absolute, 30min sliding expiration)
|
||||
@@ -57,7 +59,7 @@ Validates size (100–10000m), delegates to `IRegionService.RequestRegionAsync`.
|
||||
|
||||
## Dependencies
|
||||
All project references: Common, DataAccess, Services.
|
||||
NuGet: `Serilog.AspNetCore`, `Swashbuckle.AspNetCore`, `Microsoft.AspNetCore.OpenApi`, `SixLabors.ImageSharp`, `Newtonsoft.Json`.
|
||||
NuGet: `Serilog.AspNetCore`, `Swashbuckle.AspNetCore`, `Microsoft.AspNetCore.OpenApi`, `Microsoft.AspNetCore.Authentication.JwtBearer` (8.0.21, AZ-487), `SixLabors.ImageSharp`, `Newtonsoft.Json`.
|
||||
|
||||
## Consumers
|
||||
- HTTP clients (external)
|
||||
@@ -71,6 +73,7 @@ All configuration sections are consumed here:
|
||||
- `ConnectionStrings:DefaultConnection`
|
||||
- `MapConfig`, `StorageConfig`, `ProcessingConfig`
|
||||
- `CorsConfig:AllowedOrigins`
|
||||
- `Jwt:Secret` — HMAC-SHA256 signing key for JWT validation (AZ-487). Resolution: `JWT_SECRET` env var (preferred, opaque production secret) → `Jwt:Secret` configuration key (`appsettings.Development.json` placeholder only). Startup fails fast if the resolved value is unset, empty, or shorter than 32 bytes.
|
||||
- `Serilog` section
|
||||
|
||||
## External Integrations
|
||||
@@ -80,9 +83,9 @@ All configuration sections are consumed here:
|
||||
|
||||
## Security
|
||||
- CORS configured (permissive by default when no origins specified)
|
||||
- Swagger only in Development
|
||||
- Swagger only in Development; Bearer token "Authorize" button registered via `AddSecurityDefinition`/`AddSecurityRequirement` (AZ-487)
|
||||
- HTTPS redirection enabled
|
||||
- No authentication/authorization implemented
|
||||
- JWT bearer authentication (AZ-487) — every endpoint requires a valid HS256-signed token. Anonymous, expired, or signature-tampered requests return 401 before the handler runs. Per-endpoint permission claims are layered on top in subsequent PBIs (e.g. AZ-488 requires `permissions: ["GPS"]` on the upload endpoint).
|
||||
|
||||
## Tests
|
||||
Integration tests exercise all endpoints. Unit test project has only a dummy test.
|
||||
|
||||
@@ -68,7 +68,7 @@ Roadmap: `_docs/04_refactoring/03-code-quality-refactoring/analysis/refactoring_
|
||||
|
||||
| Task | Title | Depends On | Points | Status |
|
||||
|------|-------|-----------|--------|--------|
|
||||
| AZ-487 | JWT validation baseline (HS256, JWT_SECRET, all endpoints) | — (consumes suite-level contract `suite/_docs/10_auth.md`) | 2 | To Do |
|
||||
| AZ-487 | JWT validation baseline (HS256, JWT_SECRET, all endpoints) | — (consumes suite-level contract `suite/_docs/10_auth.md`) | 2 | Done (In Testing) |
|
||||
| AZ-488 | UAV tile upload endpoint with batch + 5-rule quality gate | AZ-487 (hard prereq), AZ-484 contract `tile-storage.md` v1.0.0 | 8 (over-cap, user-accepted) | To Do |
|
||||
|
||||
## Execution Order
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# Batch Report — Batch 01 cycle 2
|
||||
|
||||
**Batch**: 01 (cycle 2)
|
||||
**Tasks**: AZ-487 (JWT validation baseline)
|
||||
**Date**: 2026-05-11
|
||||
|
||||
## Task Results
|
||||
|
||||
| Task | Status | Files Modified | Tests | AC Coverage | Issues |
|
||||
|------|--------|---------------|-------|-------------|--------|
|
||||
| AZ-487_jwt_validation_baseline | Done | 12 modified + 6 added (`.env.example`, `SatelliteProvider.Api/Authentication/AuthenticationServiceCollectionExtensions.cs`, `SatelliteProvider.IntegrationTests/JwtIntegrationTests.cs`, `JwtTestHelpers.cs`, `SatelliteProvider.Tests/Authentication/AuthenticationServiceCollectionExtensionsTests.cs`, `JwtTokenFactoryTests.cs`, `SatelliteProvider.Tests/TestUtilities/JwtTokenFactory.cs`) | To run (Step 11) | 8/8 ACs covered | 0 blockers; 2 Low findings (see review) |
|
||||
|
||||
## AC Test Coverage: All covered (8 of 8)
|
||||
## Code Review Verdict: PASS_WITH_WARNINGS
|
||||
## Auto-Fix Attempts: 0
|
||||
## Stuck Agents: None
|
||||
|
||||
## What was implemented
|
||||
|
||||
- New `AuthenticationServiceCollectionExtensions.AddSatelliteJwt(IConfiguration)` — registers `Microsoft.AspNetCore.Authentication.JwtBearer 8.0.21` with `TokenValidationParameters` matching the suite-level auth contract (HS256, `ValidateLifetime`, `RequireSignedTokens`, `RequireExpirationTime`, no issuer/audience validation, 30 s clock skew). Secret resolution prefers `JWT_SECRET` env var, falls back to `Jwt:Secret` configuration, fails fast on missing/short (<32-byte) secret.
|
||||
- `Program.cs` wires `AddSatelliteJwt` + `AddAuthorization` into DI, adds `UseAuthentication` + `UseAuthorization` middleware after `UseCors`, and decorates every minimal-API endpoint with `.RequireAuthorization()`. Swagger UI gets a Bearer security definition + global security requirement so the "Authorize" button renders.
|
||||
- Configuration: `appsettings.json` adds an empty `Jwt:Secret` placeholder; `appsettings.Development.json` ships a clearly-tagged dev placeholder (≥32 bytes). The `JWT_SECRET` env var overrides both when set.
|
||||
- Test infrastructure:
|
||||
- `SatelliteProvider.Tests.TestUtilities.JwtTokenFactory` — mints, expires, and tampers tokens using the same secret as the API.
|
||||
- `SatelliteProvider.Tests.Authentication.AuthenticationServiceCollectionExtensionsTests` — 6 tests covering registration, parameter shape, missing/empty/short secret handling, and env-var-precedence semantics.
|
||||
- `SatelliteProvider.Tests.Authentication.JwtTokenFactoryTests` — 4 tests covering token mint + claims propagation + expired + tampered.
|
||||
- `SatelliteProvider.IntegrationTests.JwtTestHelpers` — runner-side helpers for token mint + bearer attachment.
|
||||
- `SatelliteProvider.IntegrationTests.JwtIntegrationTests` — 5 end-to-end checks (anon → 401, expired → 401, tampered → 401, valid → handler, Swagger advertises Bearer).
|
||||
- `SatelliteProvider.IntegrationTests.Program` — resolves `JWT_SECRET` at startup, mints a default token, attaches it to the shared `HttpClient` so legacy non-auth tests continue to pass against protected endpoints.
|
||||
- Docker / scripts:
|
||||
- `docker-compose.yml`: api gets `JWT_SECRET=${JWT_SECRET}`.
|
||||
- `docker-compose.tests.yml`: integration-tests gets the same env var (api inherits via `extends`).
|
||||
- `scripts/run-tests.sh`: loads `JWT_SECRET` from `.env` (alongside `GOOGLE_MAPS_API_KEY`), rejects missing/short values, exports for compose runs.
|
||||
- `.env.example` (new): documents `GOOGLE_MAPS_API_KEY` and `JWT_SECRET` with generation guidance.
|
||||
- `.env` (gitignored): dev `JWT_SECRET` added locally.
|
||||
- Docs:
|
||||
- `architecture.md` § Architecture Vision: added Authentication & Authorization sub-section.
|
||||
- `architecture.md` § Security Architecture: replaced "no auth" prose with JWT description; documented placeholder semantics and operator obligations.
|
||||
- `modules/api_program.md`: updated DI Registration (#9), Startup (middleware chain), Dependencies (JwtBearer 8.0.21), Configuration (`Jwt:Secret` + env var precedence), Security (per-endpoint `.RequireAuthorization()`).
|
||||
|
||||
## Open follow-ups (non-blocking)
|
||||
|
||||
- **Doc-folder choice** (F1 in review): the task spec referenced `_docs/02_document/components/01_web_api/description.md`, which does not exist. Updated `modules/api_program.md` + `architecture.md` instead. User should decide whether to add a stub `01_web_api` component folder for symmetry, or to formalize the "WebApi has no `components/*` folder" pattern.
|
||||
- **Cross-component coordination**: per the AZ-487 spec, `gps-denied-onboard` and the mission planner UI must attach Bearer tokens to their outbound calls before this change ships to `dev`. Flagged for the operator to coordinate before deploy (Step 16).
|
||||
|
||||
## Next Batch: AZ-488 (UAV tile upload endpoint with batch + 5-rule quality gate)
|
||||
|
||||
AZ-488 is an 8 SP task (user-accepted over-cap). It introduces a new endpoint, DTOs, quality gate, configuration class, and ~15 unit + integration tests. Recommend a fresh conversation for context freshness before starting (per `protocols.md` Context Budget Heuristic).
|
||||
@@ -0,0 +1,81 @@
|
||||
# Code Review Report — Batch 01 cycle 2
|
||||
|
||||
**Batch**: AZ-487 (JWT validation baseline)
|
||||
**Date**: 2026-05-11
|
||||
**Verdict**: PASS_WITH_WARNINGS
|
||||
|
||||
## Findings
|
||||
|
||||
| # | Severity | Category | File:Line | Title |
|
||||
|---|----------|----------|-----------|-------|
|
||||
| 1 | Low | Style | _docs/02_document/components/01_web_api/description.md | Task spec referenced a doc path that does not exist in the codebase |
|
||||
| 2 | Low | Security | SatelliteProvider.Api/appsettings.Development.json | Dev-only JWT secret placeholder is committed (intentional per spec) |
|
||||
|
||||
### Finding Details
|
||||
|
||||
**F1: Task spec referenced a doc path that does not exist in the codebase** (Low / Style)
|
||||
- Location: `_docs/02_document/components/01_web_api/description.md` (referenced; does not exist)
|
||||
- Description: The AZ-487 task spec lists `_docs/02_document/components/01_web_api/description.md` as a doc to update. The codebase's component-doc folders are `01_common`, `02_data_access`, `03_tile_downloader`, `04_region_processing`, `05_route_management` — there is no `01_web_api` folder. The WebApi component's documentation lives in `_docs/02_document/modules/api_program.md`.
|
||||
- Suggestion: Either (a) create the missing folder with a brief stub that defers to `api_program.md`, or (b) update the task spec for AZ-488 to point at `modules/api_program.md` and acknowledge that WebApi has no `components/*` folder. This batch chose (b) — updated `architecture.md` § Architecture Vision + § Security Architecture and `modules/api_program.md`. Surface to user for a doc-organization decision.
|
||||
- Task: AZ-487
|
||||
|
||||
**F2: Dev-only JWT secret placeholder is committed** (Low / Security)
|
||||
- Location: `SatelliteProvider.Api/appsettings.Development.json`
|
||||
- Description: The dev placeholder `DEV-ONLY-DO-NOT-USE-IN-PROD-replace-with-real-secret-via-JWT_SECRET-env-var` (78 bytes) is committed to the repo. Anyone with read access could mint a token signed with that secret. The mitigations (per task spec) are: (i) only loaded when `ASPNETCORE_ENVIRONMENT=Development`, (ii) the `JWT_SECRET` env var overrides it whenever set, (iii) the placeholder text itself signals it must be replaced.
|
||||
- Suggestion: Accept as-is for dev ergonomics; production environments must set `JWT_SECRET` (validated in `scripts/run-tests.sh` and at startup). Document on the README that this dev placeholder is intentional.
|
||||
- Task: AZ-487
|
||||
|
||||
## Phase Notes
|
||||
|
||||
### Phase 1 — Context Loading
|
||||
- Task spec read: `_docs/02_tasks/todo/AZ-487_jwt_validation_baseline.md`
|
||||
- Suite contract referenced: `suite/_docs/10_auth.md` (consumed via implementation, not modified)
|
||||
- Module layout, architecture doc, existing test patterns reviewed.
|
||||
|
||||
### Phase 2 — Spec Compliance
|
||||
All 8 acceptance criteria have at least one automated test:
|
||||
|
||||
| AC | Description | Test |
|
||||
|----|-------------|------|
|
||||
| AC-1 | Anonymous → 401 | `JwtIntegrationTests.AnonymousRequest_To_AnyEndpoint_Returns401` |
|
||||
| AC-2 | Expired → 401 | `JwtIntegrationTests.ExpiredToken_Returns401` |
|
||||
| AC-3 | Invalid signature → 401 | `JwtIntegrationTests.InvalidSignature_Returns401` |
|
||||
| AC-4 | Valid token reaches handler | `JwtIntegrationTests.ValidToken_Returns200_OnHealthyEndpoint` |
|
||||
| AC-5 | Missing/short secret → fail fast | `AuthenticationServiceCollectionExtensionsTests.AddSatelliteJwt_ThrowsOnMissingSecret/Empty/Short` |
|
||||
| AC-6 | `HttpContext.User` exposes claims | `JwtTokenFactoryTests.Create_WithExtraClaims_PropagatesClaimsThroughValidation` (exercises same `JwtSecurityTokenHandler.ValidateToken` path JwtBearer uses to populate `HttpContext.User`) |
|
||||
| AC-7 | Swagger Authorize button | `JwtIntegrationTests.SwaggerDocument_AdvertisesBearerSecurityScheme` (OpenAPI document declares `Bearer` security scheme) |
|
||||
| AC-8 | All existing tests pass | Verified at Step 11 (test-run gate); shared `HttpClient` attaches default Bearer token in `IntegrationTests/Program.cs` |
|
||||
|
||||
Contract verification: AZ-487 is a consumer of `suite/_docs/10_auth.md`. The implementation matches the consumed contract (HS256, ≥32-byte HMAC key, no issuer/audience validation, expiration required).
|
||||
|
||||
### Phase 3 — Code Quality
|
||||
- New code follows SRP (`AuthenticationServiceCollectionExtensions` does one thing).
|
||||
- Tests follow AAA pattern with explicit comments (matches project style).
|
||||
- No bare catches introduced. The pre-existing empty catch in `IntegrationTests/Program.cs::WaitForApiReady` is out of scope (pre-existing, not modified).
|
||||
- No comments narrating obvious code.
|
||||
|
||||
### Phase 4 — Security Quick-Scan
|
||||
- No SQL/command-injection vectors introduced.
|
||||
- No sensitive data in logs (error messages mention secret length, not value).
|
||||
- Dev placeholder secret: see F2.
|
||||
|
||||
### Phase 5 — Performance Scan
|
||||
- JWT validation per request is microsecond-scale HMAC + claims parsing; no I/O, no caching needed (per NFR).
|
||||
- No N+1, no unbounded fetches, no new blocking calls.
|
||||
|
||||
### Phase 6 — Cross-Task Consistency
|
||||
Single-task batch; not applicable.
|
||||
|
||||
### Phase 7 — Architecture Compliance
|
||||
- All new code in `SatelliteProvider.Api/Authentication/` — owned by the WebApi component per `module-layout.md`.
|
||||
- Imports stay within the allowed dependency table (`Microsoft.AspNetCore.Authentication.JwtBearer`, `Microsoft.IdentityModel.Tokens` are external NuGet packages, not other components).
|
||||
- Test code lives under `SatelliteProvider.Tests/` and `SatelliteProvider.IntegrationTests/` (separate test projects per layout rule 5).
|
||||
- No new cross-sibling ProjectReferences. No new cyclic dependencies. No duplicate cross-component symbols.
|
||||
|
||||
## Baseline Delta
|
||||
|
||||
`_docs/02_document/architecture_compliance_baseline.md` is not present in the repo; baseline delta section omitted.
|
||||
|
||||
## Verdict Rationale
|
||||
|
||||
Two Low findings, both already accepted in the task spec or surfacable as doc-organization decisions. No Critical, High, or Medium findings. Verdict: **PASS_WITH_WARNINGS** — proceed to commit per implement skill Step 10.
|
||||
@@ -6,9 +6,9 @@ step: 10
|
||||
name: Implement
|
||||
status: in_progress
|
||||
sub_step:
|
||||
phase: 0
|
||||
name: awaiting-invocation
|
||||
detail: ""
|
||||
phase: 7
|
||||
name: batch-loop
|
||||
detail: "batch 1 of 2 done (AZ-487); batch 2 (AZ-488) pending"
|
||||
retry_count: 0
|
||||
cycle: 2
|
||||
tracker: jira
|
||||
|
||||
Reference in New Issue
Block a user