Shared Lambda Interface
Plan Metadata
- Plan type:
plan - Parent plan:
N/A - Depends on:
N/A - Status:
documentation
System Intent
- What is being built: Shared API lambda invoker that can be used to apply auth and standardize input and output and simplify core functionality.
- Primary consumer(s): Python API lambda handlers.
- Boundary (black-box scope only): Input event parsing, auth header/token handling, handler invocation contract, and normalized response envelopes including preflight and error behavior.
Stage Gate Tracker
- [x] Stage 1 Mermaid approved
- [x] Stage 2 I/O contracts approved
- [x] Stage 3 pseudocode approved or skipped
1. Mermaid Diagram
Reference: .agent/skills/create-mermaid-diagram/SKILL.md
flowchart TD
A[lambda_handler] -->|event context settings handler| B[invoke_lambda - layers/shared/python/shared/lambda_helpers.py]
B -->|optional CORS preflight path on OPTIONS| C[maybe_handle_cors_preflight - layers/shared/python/shared/lambda_helpers.py]
C -->|continue request lifecycle| D[maybe_require_auth_context - layers/shared/python/shared/lambda_helpers.py]
D -->|parse request payload| E[parse_json_event_body - layers/shared/python/shared/lambda_helpers.py]
E -->|payload and optional auth| F[handler - layers/shared/python/shared/lambda_helpers.py]
F -->|return normalized success payload| G[build_standard_response - layers/shared/python/shared/lambda_helpers.py]
D -->|auth validation failure| H[build_error_response - layers/shared/python/shared/lambda_helpers.py]
F -->|application or unexpected error| H
classDef unchanged fill:#d3d3d3,stroke:#666,stroke-width:1px;
class A,B,C,D,E,F,G,H unchanged; 2. Black-Box Inputs and Outputs
Global Types
Define shared types used in the invoke_lambda integration contract.
LambdaInvokeInput {
event: map<string, any> (API Gateway-style event; body may be object, JSON string, or omitted)
context: any (lambda context)
settings: LambdaSettings
handler: function(payload, auth?) -> object
require_auth_context: boolean (default true)
}
LambdaSettings {
lambda_name: string (required)
}
AuthContext {
user_id: string (required; Cognito subject)
token_use: string (expected access)
claims: map<string, any>
}
SuccessEnvelope {
statusCode: 200
headers: map<string, string>
body: {
success: true
data: object
}
}
TypedErrorEnvelope {
statusCode: number
headers: map<string, string>
body: {
success: false
code: string
message: string
}
}
GenericErrorEnvelope {
statusCode: 500
headers: map<string, string>
body: {
success: false
message: string
}
}
PreflightResponse {
statusCode: 200
headers: map<string, string>
body: ""
}
Flow: main/server/layers/shared/python/shared/lambda_helpers.py.invoke_lambda, main/server/tests/unit/test_invoke_lambda.py, main/server/tests/unit/test_auth_helpers.py
| path-name | input | output/expected state change | path-type | notes | updated |
|---|---|---|---|---|---|
invoke-lambda.integration.preflight-options | LambdaInvokeInput with http_method=OPTIONS | PreflightResponse | subpath | returns CORS headers immediately; handler is not executed | |
invoke-lambda.integration.success-body-dict | require_auth_context=false; event.body is object | SuccessEnvelope | happy path | payload object is passed to handler | |
invoke-lambda.integration.success-body-json-string | require_auth_context=false; event.body is JSON string | SuccessEnvelope | subpath | body string is JSON-decoded before handler call | |
invoke-lambda.integration.success-body-unsupported-type | require_auth_context=false; event.body is non-string and non-object | SuccessEnvelope | subpath | payload is normalized to empty object | |
invoke-lambda.integration.success-event-root | require_auth_context=false; event.body missing | SuccessEnvelope | subpath | root event object is used as payload | |
invoke-lambda.integration.success-single-arg-handler | handler signature is function(payload) | SuccessEnvelope | subpath | runtime calls handler without auth arg when signature has one positional arg | |
invoke-lambda.integration.success-auth-required | require_auth_context=true; valid bearer access token | SuccessEnvelope | happy path | handler receives resolved AuthContext | |
invoke-lambda.integration.error-invalid-json-body | event.body is invalid JSON string | GenericErrorEnvelope statusCode=500 message=<json parse error> | error | parse failure is treated as unexpected exception | |
invoke-lambda.integration.error-auth-missing-header | require_auth_context=true; Authorization missing | TypedErrorEnvelope statusCode=401 code=AUTH_MISSING | error | handler is not executed | |
invoke-lambda.integration.error-auth-invalid-header | require_auth_context=true; Authorization not Bearer <token> | TypedErrorEnvelope statusCode=401 code=AUTH_INVALID | error | malformed auth header | |
invoke-lambda.integration.error-auth-config-missing | require_auth_context=true; missing AWS_REGION or COGNITO_USER_POOL_ID or COGNITO_APP_CLIENT_ID | TypedErrorEnvelope statusCode=401 code=AUTH_CONFIG_MISSING | error | auth settings are required before JWT verification | |
invoke-lambda.integration.error-auth-key-missing | require_auth_context=true; token kid not found in JWKS | TypedErrorEnvelope statusCode=401 code=AUTH_KEY_MISSING | error | token key lookup fails during JWT verification | |
invoke-lambda.integration.error-auth-claims-invalid | require_auth_context=true; token_use/client_id/sub invalid | TypedErrorEnvelope statusCode=401 code=AUTH_INVALID | error | invalid token claims are rejected | |
invoke-lambda.integration.error-auth-jwks-fetch-failure | require_auth_context=true; JWKS endpoint/network failure | GenericErrorEnvelope statusCode=500 message=<fetch error> | error | non-AppError auth dependency failures bubble as generic runtime errors | |
invoke-lambda.integration.error-handler-app-error | handler raises AppError/AuthError | TypedErrorEnvelope status/code/message from exception | error | preserves typed application errors | |
invoke-lambda.integration.error-handler-unexpected | handler raises Exception | GenericErrorEnvelope statusCode=500 message=<exception> | error | unexpected exceptions map to generic 500 envelope | |
invoke-lambda.integration.error-missing-lambda-name | settings.lambda_name missing | ValueError is raised before envelope response | error | configuration failure at wrapper entry |
3. Pseudocode for Critical Flows
- Flow name::
invoke_lambda request lifecycleread settings.lambda_name and fail fast if missing derive request metadata for logging if http_method == OPTIONS then return 200 with CORS headers and empty body parse payload from event body or root event if require_auth_context true then resolve auth context from bearer token inspect handler signature to decide one-arg or two-arg call invoke handler and return success envelope if AppError or AuthError occurs then return typed error envelope if any other exception occurs then return generic 500 envelope - Flow name::
maybe_handle_cors_preflightread LOCAL_CORS_ORIGIN env value if http_method != OPTIONS then continue request lifecycle if http_method == OPTIONS then return { statusCode: 200 headers: { Access-Control-Allow-Origin: LOCAL_CORS_ORIGIN Access-Control-Allow-Methods: DELETE,GET,OPTIONS,POST,PUT Access-Control-Allow-Headers: Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token,X-Encache-Client } body: "" } - Flow name::
maybe_require_auth_contextif require_auth_context is false then return auth_context=None read Authorization header (case-insensitive) require format "Bearer <jwt>" else raise AuthError AUTH_MISSING or AUTH_INVALID require env AWS_REGION, COGNITO_USER_POOL_ID, COGNITO_APP_CLIENT_ID else raise AUTH_CONFIG_MISSING fetch JWKS, select key by kid, verify JWT signature and issuer require token_use=access, client_id matches app client id, and sub exists return AuthContext { user_id=sub, token_use, claims } - Flow name::
auth error mapping - Implementation notes: This plan documents existing behavior in
lambda_helpers.py; downstream API plans should treat these envelopes and auth semantics as shared contracts.
4. Handoff to Related Plan Reconciliation
After all stages are approved, apply .agent/skills/reconcile-plans/SKILL.md to propagate contract updates across linked plans.