--- name: sdk-implementation-guide description: >- Operational guide for implementing a new Envilder runtime SDK. Covers folder structure, API surface (Tier 1 + Tier 2), provider contract, naming conventions, sync/async model, internal vs public separation, vanity imports, and testing skeleton. Use when creating Go, Java, PHP, Rust, or any future SDK. --- # SDK Implementation Guide Step-by-step guide for building a new Envilder runtime SDK. Complements ADR-0003 (architecture pattern) and ADR-0005 (integration tiers). For website wiring and publishing, see the `sdk-release-checklist` skill. ## When to Use - Implementing a new runtime SDK (Go, Java, PHP, Rust, etc.) - Reviewing an SDK PR for pattern compliance - Onboarding a contributor to SDK development ## Prerequisites Before starting, read: - [ADR-0003: SDK Architecture Pattern](../../../docs/adr/0003-sdk-architecture-pattern.md) - [ADR-0005: SDK Integration Tiers](../../../docs/adr/0005-sdk-integration-tiers.md) ## 1. Folder Structure ```txt src/sdks/{runtime}/ ├── domain/ │ ├── ISecretProvider ← Port interface/protocol │ ├── MapFileConfig ← Config parsed from $config │ ├── EnvilderOptions ← Runtime overrides │ ├── ParsedMapFile ← Config + variable mappings │ └── SecretProviderType ← Enum (aws, azure) ├── application/ │ ├── Envilder ← Public facade (Tier 1 + Tier 2) │ ├── EnvilderClient ← Core resolver │ ├── MapFileParser ← JSON parsing logic │ └── SecretValidation ← Opt-in validate function ├── infrastructure/ │ ├── SecretProviderFactory ← Internal, not exported │ ├── aws/ │ │ └── AwsSsmSecretProvider │ └── azure/ │ └── AzureKeyVaultSecretProvider └── integrations/ ← Tier 3 (future, community-driven) └── {framework}/ ``` Tests mirror the structure under `tests/sdks/{runtime}/`. > **Note:** This is the logical structure. Each runtime adapts to its own > conventions (e.g., Node.js uses `src/` subfolder, Python uses package name > as root directory). ## 2. Mandatory API Surface ### Tier 1: Static Facade (one-liner) ```txt Envilder.load("map.json") → injects into process env Envilder.resolveFile("map.json") → returns resolved key-value pairs ``` ### Tier 2: Fluent Builder ```txt Envilder.fromMapFile("map.json") .withProvider("azure") .withVaultUrl("https://vault.azure.net") .withProfile("my-profile") // AWS only .inject() // injects into env // OR .resolve() // returns key-value pairs ``` ### Environment Routing (both tiers) ```txt Envilder.load("production", { "production": "prod-map.json", "staging": "stg-map.json" }) ``` ## 3. Provider Contract | Aspect | Decision | | ------ | -------- | | Missing secrets | Return null/None/nil (Option in Rust) or omit from result collection: never throw | | Validation | Opt-in `validateSecrets()` post-resolution | | Cross-provider validation | profile + Azure → error; vaultUrl + AWS → error | | Options override config | Runtime `EnvilderOptions` > `$config` from map file | | Pull-only | SDKs do not support push mode | ### Sync vs Async per Runtime | Runtime | Model | Rationale | | ------- | ----- | --------- | | .NET | Both (sync + async methods) | `IConfigurationBuilder` needs sync | | Python | Sync only | boto3 is natively synchronous | | Node.js | Async only | AWS SDK v3 is async, natural fit | | Go | Sync (with context.Context and goroutines for batch) | Idiomatic Go | | Java/Kotlin | Both (sync + async/CompletableFuture) | Spring uses both patterns | | PHP | Sync only | PHP is single-threaded by default | | Rust | Async (tokio) | AWS SDK for Rust is async | ### Batch vs Individual | Runtime | Strategy | Rationale | | ------- | -------- | --------- | | Node.js | Batch (`GetParameters`, max 10) | Avoids waterfall awaits | | .NET | Individual (`GetParameter`) | Simpler error handling per secret | | Python | Individual (`get_parameter`) | Matches boto3 API | | Go | Batch preferred | Reduce round trips | | Java | Batch preferred | AWS SDK supports batch | | PHP | Individual | Simpler for first iteration | | Rust | Batch preferred | Async batch is natural | ## 4. Internal vs Public Separation The `SecretProviderFactory` is always **internal** (not part of public API): | Runtime | Mechanism | | ------- | --------- | | .NET | `internal` keyword + `InternalsVisibleTo` for tests | | Python | `_` prefix (`_SecretProviderFactory`) + `__all__` whitelist | | Node.js | Not re-exported from barrel `index.ts` | | Go | Unexported (`secretProviderFactory`, lowercase) | | Java/Kotlin | Package-private or `internal` (Kotlin) | | PHP | Prefixed `_` or `#[Internal]` attribute / `@internal` docblock | | Rust | `pub(crate)` visibility | ## 5. Naming Conventions ### Package/Module Names | Runtime | Package name | Import | | ------- | ------------ | ------ | | .NET | `Envilder` (NuGet) | `using Envilder;` | | Python | `envilder` (PyPI) | `from envilder import Envilder` | | Node.js | `@envilder/sdk` (npm) | `import { Envilder } from '@envilder/sdk'` | | Go | `envilder.com/go` (vanity) | `import "envilder.com/go"` | | Java/Kotlin | `com.envilder:envilder` (Maven) | `import com.envilder.Envilder;` | | PHP | `envilder/envilder` (Packagist) | `use Envilder\Envilder;` | | Rust | `envilder` (crates.io) | `use envilder::Envilder;` | ### Class/Struct Naming - Facade class is always `Envilder` (consistent cross-SDK brand) - Client class is always `EnvilderClient` - Provider interface: `ISecretProvider` (or language equivalent: Protocol, trait, interface) ## 6. Vanity Imports (Go) Go module path is `envilder.com/go`. This requires a vanity import HTML at `https://envilder.com/go?go-get=1`: ```html ``` Serve from the Envilder website (`src/website/public/go/index.html`). ## 7. Testing Skeleton Every SDK must have: - [ ] Unit tests for `MapFileParser` - [ ] Unit tests for `EnvilderClient` (mock `ISecretProvider`) - [ ] Unit tests for `Envilder` facade (mock client/provider) - [ ] Unit tests for `SecretValidation` - [ ] Unit tests for cross-provider validation - [ ] Acceptance tests with LocalStack (AWS SSM) - [ ] Acceptance tests with Lowkey Vault (Azure Key Vault) Follow `sdk-acceptance-testing` skill for container wrapper patterns. ## 8. Implementation Order (per SDK) 1. Domain types (`MapFileConfig`, `EnvilderOptions`, `SecretProviderType`, `ISecretProvider`) 2. `MapFileParser` + tests 3. `EnvilderClient` + tests 4. `AwsSsmSecretProvider` + acceptance tests 5. `AzureKeyVaultSecretProvider` + acceptance tests 6. `Envilder` facade (Tier 1 + Tier 2) + tests 7. `SecretValidation` + tests 8. Cross-provider validation + tests 9. README, changelog, website wiring (→ `sdk-release-checklist` skill) ## 9. Checklist Before Submitting PR - [ ] All unit tests pass - [ ] All acceptance tests pass (Docker required) - [ ] `SecretProviderFactory` is not publicly exported - [ ] Tier 1 (`load`) and Tier 2 (`fromMapFile().inject()`) work end-to-end - [ ] Missing secrets silently omitted (no exceptions from provider) - [ ] `validateSecrets()` throws on empty/missing values - [ ] Cross-provider errors: profile+Azure→error, vaultUrl+AWS→error - [ ] Options override `$config` values - [ ] Environment routing works - [ ] Follows language-idiomatic conventions (naming, async model, visibility)