Skip to content
48 changes: 29 additions & 19 deletions specifications/SPEC_PLATFORM_SERVICE.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ The Platform Module shall:
- **[FR-11]** Download and verify file integrity using CRC32C checksums for run artifacts
- **[FR-12]** Generate signed URLs for secure Google Cloud Storage access
- **[FR-13]** Provide user and organization information retrieval with sensitive data masking options
- **[FR-14]** Support external token providers to bypass internal OAuth 2.0 flows for machine-to-machine, service account, or custom token lifecycle scenarios.

### 1.3 Non-Functional Requirements

Expand All @@ -44,7 +45,6 @@ The Platform Module shall:
### 1.4 Constraints and Limitations

- OAuth 2.0 dependency: Requires external Auth0 service for authentication, creating external dependency
- Browser dependency: Interactive flow requires web browser availability, limiting headless deployment options
- Network dependency: Requires internet connectivity for initial authentication and token validation
- Platform-specific: Designed specifically for Aignostics Platform API integration

Expand All @@ -58,6 +58,7 @@ The Platform Module shall:
platform/
├── _service.py # Core service implementation with health monitoring
├── _client.py # API client factory and configuration management
├── _api.py # Authenticated API wrapper (_AuthenticatedApi, _AuthenticatedResource)
├── _authentication.py # OAuth flows and token management
├── _cli.py # Command-line interface for user operations
├── _settings.py # Environment-specific configuration management
Expand Down Expand Up @@ -88,7 +89,7 @@ platform/

- **Factory Pattern**: `Client.get_api_client()` creates configured API clients based on environment settings
- **Service Layer Pattern**: Business logic encapsulated in service classes with clean separation from API details
- **Strategy Pattern**: Multiple authentication flows (Authorization Code vs Device Flow) selected based on environment capabilities
- **Strategy Pattern**: Multiple authentication flows (Authorization Code vs Device Flow) selected based on environment capabilities; external token provider as a fully independent alternative strategy
- **Template Method Pattern**: Base authentication flow with specific implementations for different OAuth grant types

---
Expand All @@ -107,10 +108,10 @@ platform/

### 3.2 Outputs

| Output Type | Destination | Format/Type | Success Criteria | Code Location |
| ---------------- | ------------------- | ---------------------- | --------------------------------------------------- | ---------------------------------------------------- |
| JWT Access Token | Token cache/memory | String | Valid JWT with required claims and unexpired | `_authentication.py::get_token()` return value |
| API Client | Client applications | PublicApi object | Authenticated and configured for target environment | `_client.py::Client.get_api_client()` factory method |
| Output Type | Destination | Format/Type | Success Criteria | Code Location |
| ---------------- | ------------------- | ---------------------------- | --------------------------------------------------- | ---------------------------------------------------- |
| JWT Access Token | Token cache/memory | String | Valid JWT with required claims and unexpired | `_authentication.py::get_token()` return value |
| API Client | Client applications | `Client` object | Authenticated and configured for target environment | `_client.py::Client.__init__()` constructor |
Copy link
Copy Markdown
Collaborator Author

@akunft akunft Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@olivermeyer IMO, the type is correct: The user gets a client object, not the authenticatedAPI. If we want to align with the other code locations by using a factory class method, we would need to change the factory (I would not do that).

Unless you have major concerns, I would leave it as is

| User Information | CLI/Application | UserInfo/Me objects | Complete user and organization data | `_service.py::Service.get_user_info()` method |
| Health Status | Monitoring systems | Health object | Accurate service and dependency status | `_service.py::Service.health()` method |
| Downloaded Files | Local filesystem | Binary/structured data | Verified checksums and complete downloads | `_utils.py` download functions and `ApplicationRun` |
Expand Down Expand Up @@ -171,7 +172,9 @@ UserInfo:

```mermaid
graph TD
A[User Request] --> B{Token Cached?}
A[User Request] --> X{External Token Provider?}
X -->|Yes| I[Create API Client with External Provider]
X -->|No| B{Token Cached?}
B -->|Yes| C[Use Cached Token]
B -->|No| D[OAuth Authentication]

Expand Down Expand Up @@ -202,7 +205,11 @@ graph TD
class Client:
"""Main client for interacting with the Aignostics Platform API."""

def __init__(self, cache_token: bool = True) -> None:
def __init__(
self,
cache_token: bool = True,
token_provider: Callable[[], str] | None = None,
) -> None:
"""Initializes authenticated API client with resource accessors."""

def me(self) -> Me:
Expand All @@ -215,7 +222,10 @@ class Client:
"""Creates ApplicationRun instance for existing run."""

@staticmethod
def get_api_client(cache_token: bool = True) -> PublicApi:
def get_api_client(
cache_token: bool = True,
token_provider: Callable[[], str] | None = None,
) -> _AuthenticatedApi: # internal subclass of PublicApi; exposes token_provider attribute
"""Creates authenticated API client with proper configuration."""
```

Expand All @@ -242,10 +252,10 @@ class Service(BaseService):
```

```python
class Applications:
class Applications(_AuthenticatedResource):
"""Resource class for managing applications."""

def __init__(self, api: PublicApi) -> None:
def __init__(self, api: _AuthenticatedApi) -> None:
"""Initializes the Applications resource with the API client."""

def list(self) -> Iterator[Application]:
Expand All @@ -257,11 +267,11 @@ class Applications:
```

```python
class Versions:
class Versions(_AuthenticatedResource):
"""Resource class for managing application versions."""

def __init__(self, api: PublicApi) -> None:
"""Initializes the Versions resource with the API client."""
# Constructor inherited from _AuthenticatedResource
# def __init__(self, api: _AuthenticatedApi) -> None:

def list(self, application: Application | str) -> Iterator[ApplicationVersion]:
"""Find all versions for a specific application."""
Expand All @@ -277,11 +287,11 @@ class Versions:
```

```python
class Runs:
class Runs(_AuthenticatedResource):
"""Resource class for managing application runs."""

def __init__(self, api: PublicApi) -> None:
"""Initializes the Runs resource with the API client."""
# Constructor inherited from _AuthenticatedResource
# def __init__(self, api: _AuthenticatedApi) -> None:

def create(self, application_version: str, items: list[ItemCreationRequest]) -> ApplicationRun:
"""Creates a new application run."""
Expand All @@ -297,10 +307,10 @@ class Runs:
```

```python
class ApplicationRun:
class ApplicationRun(_AuthenticatedResource):
"""Represents a single application run."""

def __init__(self, api: PublicApi, application_run_id: str) -> None:
def __init__(self, api: _AuthenticatedApi, application_run_id: str) -> None:
"""Initializes an ApplicationRun instance."""

@classmethod
Expand Down
Loading
Loading