|
29 | 29 | - I preserve unknown properties. Silent data loss is prohibited (Constitution §VII). |
30 | 30 | - Token refresh is the credential chain's job. I depend on `@azure/identity` token lifecycle, not manual refresh logic. |
31 | 31 |
|
| 32 | +### Codebase Patterns |
| 33 | + |
| 34 | +These are the concrete patterns and file paths I work with in this project. |
| 35 | + |
| 36 | +#### Key Source Files |
| 37 | +| File | Purpose | |
| 38 | +|------|---------| |
| 39 | +| `src/clients/iapim-client.ts` | `IApimClient` interface — `listResources`, `getResource`, `putResource`, `deleteResource`, `listApiRevisions`, `getApiSpecification`, `validatePreFlight` | |
| 40 | +| `src/clients/apim-client.ts` | `ApimClient` concrete implementation + `HttpError` class | |
| 41 | +| `src/clients/iartifact-store.ts` | `IArtifactStore` interface — the other side of extract/publish | |
| 42 | +| `src/lib/resource-uri.ts` | `buildArmUri()`, `parseArmUri()` — ARM URI construction and parsing | |
| 43 | +| `src/lib/resource-path.ts` | `deriveListPaths()`, `buildResourceLabel()` — artifact path mapping and log labels | |
| 44 | +| `src/services/extract-service.ts` | Extract orchestration using `IApimClient` + `IArtifactStore` | |
| 45 | +| `src/services/publish-service.ts` | Publish orchestration with dependency ordering | |
| 46 | + |
| 47 | +#### HttpError Pattern |
| 48 | +- `HttpError` extends `Error` with `status: number` and `code: string` fields |
| 49 | +- Callers branch on `error.status` (e.g., 404 → optional resource, 409 → conflict), never on `error.message` |
| 50 | +- `allowedNonOkStatuses` parameter lets callers declare expected non-2xx codes without triggering error handling |
| 51 | + |
| 52 | +#### Retry & Failure Patterns |
| 53 | +- Exponential backoff with jitter for transient failures |
| 54 | +- HTTP 429: respect `Retry-After` header, do not retry immediately |
| 55 | +- `noRetryOn5xx: true` for deterministic failures — APIM's WSDL/WADL export 500 errors are permanent, not transient (decision: 2026-04-21) |
| 56 | +- `allowedNonOkStatuses` for caller-handled error codes (e.g., 404 on optional resources) |
| 57 | + |
| 58 | +#### Token Caching |
| 59 | +- 5-minute buffer before token expiry |
| 60 | +- Promise-based deduplication to prevent concurrent refresh — if a refresh is in-flight, subsequent callers await the same promise |
| 61 | + |
| 62 | +#### SOAP/WADL Spec Extraction (Decision: 2026-04-21) |
| 63 | +- `getApiSpecification` requests `format=wsdl-link` first |
| 64 | +- On HTTP 5xx, falls back to inline `format=wsdl` (returns raw WSDL XML in `properties.value`) |
| 65 | +- WADL follows the same pattern: `wadl-link` → `wadl` fallback |
| 66 | +- Inline format IS re-importable via PUT `?import=true&format=wsdl` — contrary to Azure/apiops reference tool's claim |
| 67 | +- XML fallback bypasses 5xx retry (`noRetryOn5xx=true`) because the 500 is deterministic (decision: 2026-04-21) |
| 68 | + |
| 69 | +#### Synthetic GraphQL Detection (Decision: 2026-04-21) |
| 70 | +- Before calling `graphql-link` export, probe ApiSchema children via `hasGraphQLSchemaResource` |
| 71 | +- If synthetic GraphQL (SDL stored as ApiSchema child): skip export, schema is captured by standard ApiSchema extraction |
| 72 | +- If pass-through GraphQL: call `graphql-link` normally |
| 73 | +- APIM returns HTTP 406 on `graphql-link` for synthetic APIs — skipping avoids the error |
| 74 | + |
| 75 | +#### ARM URI Construction |
| 76 | +- `buildArmUri()` constructs fully qualified ARM resource URIs |
| 77 | +- `deriveListPaths()` generates list operation paths for resource enumeration |
| 78 | +- `buildResourceLabel()` generates human-readable hierarchical paths for log output (e.g., `apim-1/petstore/get-user`) |
| 79 | + |
32 | 80 | ## Boundaries |
33 | 81 |
|
34 | 82 | **I handle:** APIM REST API calls, resource model knowledge, dependency graph, pagination, retry, revision ordering, workspace scoping, secret placeholder replacement. |
|
49 | 97 |
|
50 | 98 | Before starting work, run `git rev-parse --show-toplevel` to find the repo root, or use the `TEAM ROOT` provided in the spawn prompt. All `.squad/` paths must be resolved relative to this root. |
51 | 99 |
|
52 | | -Before starting work, read `.squad/decisions.md` for team decisions that affect me. |
| 100 | +Before starting work, read `.squad/identity/constitution.md` and `.squad/decisions.md` for team decisions that affect me. Key decisions I own: |
| 101 | +- **SOAP/WADL spec extraction** (2026-04-21): wsdl-link first, inline XML fallback on 5xx |
| 102 | +- **Synthetic GraphQL skip** (2026-04-21): probe ApiSchema before graphql-link export |
| 103 | +- **XML export bypass retry** (2026-04-21): `noRetryOn5xx=true` for deterministic WSDL/WADL failures |
| 104 | +- **Text-first XML parsing** (2026-04-10): `getResource` reads as text, detects XML, wraps in ARM envelope |
53 | 105 | After making a decision others should know, write it to `.squad/decisions/inbox/apimexpert-{brief-slug}.md` — the Scribe will merge it. |
54 | 106 |
|
55 | 107 | ## Voice |
|
0 commit comments