svc-infra 0.1.615__tar.gz → 0.1.617__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of svc-infra might be problematic. Click here for more details.
- {svc_infra-0.1.615 → svc_infra-0.1.617}/PKG-INFO +1 -1
- svc_infra-0.1.617/docs/acceptance-matrix.md +71 -0
- svc_infra-0.1.617/docs/acceptance.md +44 -0
- svc_infra-0.1.617/docs/adr/0002-background-jobs-and-scheduling.md +40 -0
- svc_infra-0.1.617/docs/adr/0003-webhooks-framework.md +24 -0
- svc_infra-0.1.617/docs/adr/0004-tenancy-model.md +42 -0
- svc_infra-0.1.617/docs/adr/0005-data-lifecycle.md +86 -0
- svc_infra-0.1.617/docs/adr/0006-ops-slos-and-metrics.md +47 -0
- svc_infra-0.1.617/docs/adr/0007-docs-and-sdks.md +83 -0
- svc_infra-0.1.617/docs/adr/0008-billing-primitives.md +109 -0
- svc_infra-0.1.617/docs/adr/0009-acceptance-harness.md +40 -0
- svc_infra-0.1.617/docs/api.md +59 -0
- svc_infra-0.1.617/docs/auth.md +11 -0
- svc_infra-0.1.617/docs/cache.md +18 -0
- svc_infra-0.1.617/docs/cli.md +74 -0
- svc_infra-0.1.617/docs/contributing.md +34 -0
- svc_infra-0.1.617/docs/data-lifecycle.md +52 -0
- svc_infra-0.1.617/docs/database.md +14 -0
- svc_infra-0.1.617/docs/docs-and-sdks.md +62 -0
- svc_infra-0.1.617/docs/environment.md +114 -0
- svc_infra-0.1.617/docs/idempotency.md +111 -0
- svc_infra-0.1.617/docs/jobs.md +67 -0
- svc_infra-0.1.617/docs/observability.md +16 -0
- svc_infra-0.1.617/docs/ops.md +33 -0
- svc_infra-0.1.617/docs/rate-limiting.md +121 -0
- svc_infra-0.1.617/docs/repo-review.md +48 -0
- svc_infra-0.1.617/docs/security.md +155 -0
- svc_infra-0.1.617/docs/tenancy.md +35 -0
- svc_infra-0.1.617/docs/webhooks.md +112 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/pyproject.toml +3 -2
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/middleware/ratelimit.py +8 -0
- svc_infra-0.1.617/src/svc_infra/bundled_docs/README.md +5 -0
- svc_infra-0.1.617/src/svc_infra/bundled_docs/__init__.py +1 -0
- svc_infra-0.1.617/src/svc_infra/bundled_docs/getting-started.md +6 -0
- svc_infra-0.1.617/src/svc_infra/cli/cmds/docs/docs_cmds.py +135 -0
- svc_infra-0.1.615/src/svc_infra/cli/cmds/docs/docs_cmds.py +0 -69
- {svc_infra-0.1.615 → svc_infra-0.1.617}/README.md +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/apf_payments/README.md +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/apf_payments/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/apf_payments/alembic.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/apf_payments/models.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/apf_payments/provider/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/apf_payments/provider/aiydan.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/apf_payments/provider/base.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/apf_payments/provider/registry.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/apf_payments/provider/stripe.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/apf_payments/schemas.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/apf_payments/service.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/apf_payments/settings.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/apf_payments/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/apf_payments/router.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/apf_payments/setup.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/auth/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/auth/_cookies.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/auth/add.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/auth/gaurd.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/auth/mfa/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/auth/mfa/models.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/auth/mfa/pre_auth.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/auth/mfa/router.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/auth/mfa/security.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/auth/mfa/utils.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/auth/mfa/verify.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/auth/policy.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/auth/providers.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/auth/routers/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/auth/routers/account.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/auth/routers/apikey_router.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/auth/routers/oauth_router.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/auth/routers/session_router.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/auth/security.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/auth/sender.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/auth/settings.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/auth/state.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/cache/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/cache/add.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/db/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/db/http.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/db/nosql/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/db/nosql/mongo/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/db/nosql/mongo/add.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/db/nosql/mongo/crud_router.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/db/nosql/mongo/health.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/db/sql/README.md +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/db/sql/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/db/sql/add.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/db/sql/crud_router.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/db/sql/health.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/db/sql/session.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/db/sql/users.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/dependencies/ratelimit.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/docs/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/docs/add.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/docs/landing.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/docs/scoped.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/dual/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/dual/dualize.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/dual/protected.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/dual/public.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/dual/router.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/dual/utils.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/dx.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/ease.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/http/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/http/concurrency.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/http/conditional.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/http/deprecation.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/middleware/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/middleware/debug.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/middleware/errors/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/middleware/errors/catchall.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/middleware/errors/exceptions.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/middleware/errors/handlers.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/middleware/idempotency.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/middleware/idempotency_store.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/middleware/optimistic_lock.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/middleware/ratelimit_store.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/middleware/request_id.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/middleware/request_size_limit.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/openapi/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/openapi/apply.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/openapi/conventions.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/openapi/models.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/openapi/mutators.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/openapi/pipeline.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/openapi/responses.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/openapi/security.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/ops/add.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/pagination.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/paths/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/paths/auth.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/paths/generic.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/paths/prefix.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/paths/user.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/routers/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/routers/ping.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/setup.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/tenancy/add.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/api/fastapi/tenancy/context.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/app/README.md +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/app/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/app/env.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/app/logging/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/app/logging/add.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/app/logging/filter.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/app/logging/formats.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/app/root.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/billing/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/billing/models.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/billing/service.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cache/README.md +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cache/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cache/backend.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cache/decorators.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cache/demo.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cache/keys.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cache/recache.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cache/resources.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cache/tags.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cache/ttl.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cache/utils.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cli/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cli/__main__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cli/cmds/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cli/cmds/db/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cli/cmds/db/nosql/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cli/cmds/db/nosql/mongo/README.md +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cli/cmds/db/nosql/mongo/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cli/cmds/db/nosql/mongo/mongo_cmds.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cli/cmds/db/nosql/mongo/mongo_scaffold_cmds.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cli/cmds/db/sql/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cli/cmds/db/sql/alembic_cmds.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cli/cmds/db/sql/sql_export_cmds.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cli/cmds/db/sql/sql_scaffold_cmds.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cli/cmds/dx/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cli/cmds/dx/dx_cmds.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cli/cmds/help.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cli/cmds/jobs/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cli/cmds/jobs/jobs_cmds.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cli/cmds/obs/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cli/cmds/obs/obs_cmds.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cli/cmds/sdk/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cli/cmds/sdk/sdk_cmds.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cli/foundation/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cli/foundation/runner.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/cli/foundation/typer_bootstrap.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/data/add.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/data/backup.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/data/erasure.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/data/fixtures.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/data/retention.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/crud_schema.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/inbox.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/nosql/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/nosql/base.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/nosql/constants.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/nosql/core.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/nosql/indexes.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/nosql/management.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/nosql/mongo/README.md +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/nosql/mongo/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/nosql/mongo/client.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/nosql/mongo/settings.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/nosql/mongo/templates/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/nosql/mongo/templates/documents.py.tmpl +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/nosql/mongo/templates/resources.py.tmpl +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/nosql/mongo/templates/schemas.py.tmpl +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/nosql/repository.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/nosql/resource.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/nosql/scaffold.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/nosql/service.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/nosql/service_with_hooks.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/nosql/types.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/nosql/utils.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/outbox.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/README.md +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/apikey.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/authref.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/base.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/constants.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/core.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/management.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/repository.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/resource.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/scaffold.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/service.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/service_with_hooks.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/templates/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/templates/models_schemas/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/templates/models_schemas/auth/models.py.tmpl +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/templates/models_schemas/auth/schemas.py.tmpl +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/templates/models_schemas/entity/models.py.tmpl +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/templates/models_schemas/entity/schemas.py.tmpl +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/templates/setup/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/templates/setup/alembic.ini.tmpl +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/templates/setup/env_async.py.tmpl +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/templates/setup/env_sync.py.tmpl +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/templates/setup/script.py.mako.tmpl +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/tenant.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/types.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/uniq.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/uniq_hooks.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/utils.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/sql/versioning.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/db/utils.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/dx/add.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/dx/changelog.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/dx/checks.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/jobs/builtins/outbox_processor.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/jobs/builtins/webhook_delivery.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/jobs/easy.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/jobs/loader.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/jobs/queue.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/jobs/redis_queue.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/jobs/scheduler.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/jobs/worker.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/mcp/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/mcp/svc_infra_mcp.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/README.md +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/add.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/cloud_dash.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/grafana/dashboards/http-overview.json +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/metrics/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/metrics/asgi.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/metrics/base.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/metrics/http.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/metrics/sqlalchemy.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/metrics.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/providers/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/providers/compose_cloud/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/providers/compose_cloud/templates/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/providers/compose_cloud/templates/agent.yaml.tmpl +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/providers/compose_cloud/templates/docker-compose.cloud.yml.tmpl +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/providers/grafana/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/providers/grafana/dashboards/00_overview.json +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/providers/grafana/dashboards/10_http.json +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/providers/grafana/dashboards/20_db.json +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/providers/grafana/dashboards/30_runtime.json +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/providers/grafana/dashboards/40_clients.json +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/providers/grafana/dashboards/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/providers/grafana/templates/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/providers/grafana/templates/docker-compose.yml.tmpl +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/providers/grafana/templates/prometheus.yml.tmpl +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/providers/grafana/templates/provisioning/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/providers/grafana/templates/provisioning/dashboards.yml +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/providers/grafana/templates/provisioning/datasource.yml +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/settings.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/templates/grafana_dashboard.json +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/templates/prometheus_rules.yml +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/templates/sidecars/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/templates/sidecars/compose/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/templates/sidecars/compose/agent.yaml +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/templates/sidecars/compose/docker-compose.yml +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/templates/sidecars/fly/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/templates/sidecars/fly/agent.yaml +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/templates/sidecars/fly/fly.toml.fragment +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/templates/sidecars/k8s/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/templates/sidecars/k8s/configmap.yaml +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/templates/sidecars/k8s/deployment.yaml +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/templates/sidecars/railway/Dockerfile +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/templates/sidecars/railway/README.md +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/templates/sidecars/railway/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/obs/templates/sidecars/railway/agent.yaml +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/py.typed +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/security/add.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/security/audit.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/security/audit_service.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/security/headers.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/security/hibp.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/security/jwt_rotation.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/security/lockout.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/security/models.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/security/org_invites.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/security/passwords.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/security/permissions.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/security/session.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/security/signed_cookies.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/utils.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/webhooks/__init__.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/webhooks/add.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/webhooks/fastapi.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/webhooks/router.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/webhooks/service.py +0 -0
- {svc_infra-0.1.615 → svc_infra-0.1.617}/src/svc_infra/webhooks/signing.py +0 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Acceptance Matrix (A-IDs)
|
|
2
|
+
|
|
3
|
+
This document maps Acceptance scenarios (A-IDs) to endpoints, CLIs, fixtures, and seed data. Use it to drive the CI promotion gate and local `make accept` runs.
|
|
4
|
+
|
|
5
|
+
## A0. Harness
|
|
6
|
+
- Stack: docker-compose.test.yml (api, db, redis)
|
|
7
|
+
- Makefile targets: accept, compose_up, wait, seed, down
|
|
8
|
+
- Tests bootstrap: tests/acceptance/conftest.py (BASE_URL), _auth.py, _seed.py, _http.py
|
|
9
|
+
|
|
10
|
+
## A1. Security & Auth
|
|
11
|
+
- A1-01 Register → Verify → Login → /auth/me
|
|
12
|
+
- Endpoints: POST /auth/register, POST /auth/verify, POST /auth/login, GET /auth/me
|
|
13
|
+
- Fixtures: admin, user
|
|
14
|
+
- A1-02 Password policy & breach check
|
|
15
|
+
- Endpoints: POST /auth/register
|
|
16
|
+
- A1-03 Lockout escalation and cooldown
|
|
17
|
+
- Endpoints: POST /auth/login
|
|
18
|
+
- A1-04 RBAC/ABAC enforced
|
|
19
|
+
- Endpoints: GET /admin/*, resource GET with owner guard
|
|
20
|
+
- A1-05 Session list & revoke
|
|
21
|
+
- Endpoints: GET/DELETE /auth/sessions
|
|
22
|
+
- A1-06 API keys lifecycle
|
|
23
|
+
- Endpoints: POST/GET/DELETE /auth/api-keys, usage via Authorization header
|
|
24
|
+
- A1-07 MFA lifecycle
|
|
25
|
+
- Endpoints: /auth/mfa/*
|
|
26
|
+
|
|
27
|
+
## A2. Rate Limiting
|
|
28
|
+
- A2-01 Global limit → 429 with Retry-After
|
|
29
|
+
- A2-02 Per-route & tenant override honored
|
|
30
|
+
- A2-03 Window reset
|
|
31
|
+
|
|
32
|
+
## A3. Idempotency & Concurrency
|
|
33
|
+
- A3-01 Same Idempotency-Key → identical 2xx
|
|
34
|
+
- A3-02 Conflicting payload + same key → 409
|
|
35
|
+
- A3-03 Optimistic lock mismatch → 409; success increments version
|
|
36
|
+
|
|
37
|
+
## A4. Jobs & Scheduling
|
|
38
|
+
- A4-01 Custom job consumed
|
|
39
|
+
- A4-02 Backoff & DLQ
|
|
40
|
+
- A4-03 Cron tick observed
|
|
41
|
+
|
|
42
|
+
## A5. Webhooks
|
|
43
|
+
- A5-01 Producer → delivery (HMAC verified)
|
|
44
|
+
- A5-02 Retry stops on success
|
|
45
|
+
- A5-03 Secret rotation window accepts old+new
|
|
46
|
+
|
|
47
|
+
## A6. Tenancy
|
|
48
|
+
- A6-01 tenant_id injected on create; list scoped
|
|
49
|
+
- A6-02 Cross-tenant → 404/403
|
|
50
|
+
- A6-03 Per-tenant quotas enforced
|
|
51
|
+
|
|
52
|
+
## A7. Data Lifecycle
|
|
53
|
+
- A7-01 Soft delete hides; undelete restores
|
|
54
|
+
- A7-02 GDPR erasure steps with audit
|
|
55
|
+
- A7-03 Retention purge soft→hard
|
|
56
|
+
- A7-04 Backup verification healthy
|
|
57
|
+
|
|
58
|
+
## A8. SLOs & Ops
|
|
59
|
+
- A8-01 Metrics http_server_* and db_pool_* present
|
|
60
|
+
- A8-02 Maintenance mode 503; circuit breaker trips/recover
|
|
61
|
+
- A8-03 Liveness/readiness under DB up/down
|
|
62
|
+
|
|
63
|
+
## A9. OpenAPI & Error Contracts
|
|
64
|
+
- A9-01 /openapi.json valid; examples present
|
|
65
|
+
- A9-02 Problem+JSON conforms
|
|
66
|
+
- A9-03 Spectral + API Doctor pass
|
|
67
|
+
|
|
68
|
+
## A10. CLI & DX
|
|
69
|
+
- A10-01 DB migrate/rollback/seed
|
|
70
|
+
- A10-02 Jobs runner consumes a sample job
|
|
71
|
+
- A10-03 SDK smoke-import and /ping
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Pre-Deploy Acceptance (Promotion Gate)
|
|
2
|
+
|
|
3
|
+
This guide describes the acceptance harness that runs post-build against an ephemeral stack. Artifacts are promoted only if acceptance checks pass.
|
|
4
|
+
|
|
5
|
+
## Stack
|
|
6
|
+
- docker-compose.test.yml: api (uvicorn serving tests.acceptance.app), optional db/redis (via profiles), and a tester container to run pytest inside
|
|
7
|
+
- Makefile targets: accept, compose_up, wait, seed, down
|
|
8
|
+
- Health probes: /healthz (liveness), /readyz (readiness), /startupz (startup)
|
|
9
|
+
|
|
10
|
+
## Workflow
|
|
11
|
+
1. Build image
|
|
12
|
+
2. docker compose up -d (test stack)
|
|
13
|
+
3. CLI DB checks & seed: run `sql-setup-and-migrate`, `sql-current`, `sql-downgrade -1`, `sql-upgrade head` against an ephemeral SQLite DB, then call `sql-seed tests.acceptance._seed:acceptance_seed` (no-op by default)
|
|
14
|
+
4. Run pytest inside tester: docker compose run --rm tester (Makefile wires this)
|
|
15
|
+
5. OpenAPI lint & API Doctor
|
|
16
|
+
6. Teardown
|
|
17
|
+
|
|
18
|
+
## Supply-chain & Matrix (v1 scope)
|
|
19
|
+
- SBOM: generate and upload as artifact; image scan (Trivy/Grype) with severity gate.
|
|
20
|
+
- Provenance: sign/attest images (cosign/SLSA) on best-effort basis.
|
|
21
|
+
- Backend matrix: run acceptance against two stacks via COMPOSE_PROFILES:
|
|
22
|
+
1) in-memory stores (default), 2) Redis + Postgres (COMPOSE_PROFILES=pg-redis).
|
|
23
|
+
|
|
24
|
+
## Additional Acceptance Checks (fast wins)
|
|
25
|
+
- Headers/CORS: assert HSTS, X-Content-Type-Options, Referrer-Policy, X-Frame-Options/SameSite; OPTIONS preflight behavior.
|
|
26
|
+
- Resilience: restart DB/Redis during request; expect breaker trip and recovery.
|
|
27
|
+
- DR drill: restore a tiny SQL dump then run smoke.
|
|
28
|
+
- OpenAPI invariants: no orphan routes; servers block correctness for versions; 100% examples for public JSON; stable operationIds; reject /auth/{id} path via lint rule.
|
|
29
|
+
- CLI contracts: `svc-infra --help` and key subcommands exit 0 and print expected flags.
|
|
30
|
+
|
|
31
|
+
## Local usage
|
|
32
|
+
- make accept (runs the full flow locally)
|
|
33
|
+
- make down (tears down the stack)
|
|
34
|
+
- To run tests manually: docker compose run --rm tester
|
|
35
|
+
- To target a different backend: COMPOSE_PROFILES=pg-redis make accept
|
|
36
|
+
|
|
37
|
+
## Files
|
|
38
|
+
- tests/acceptance/conftest.py: BASE_URL, httpx client, fixtures
|
|
39
|
+
- tests/acceptance/_auth.py: login/register helpers
|
|
40
|
+
- tests/acceptance/_seed.py: seed users/tenants/api keys
|
|
41
|
+
- tests/acceptance/_http.py: HTTP helpers
|
|
42
|
+
|
|
43
|
+
## Scenarios
|
|
44
|
+
See docs/acceptance-matrix.md for A-IDs and mapping to endpoints.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# ADR 0002: Background Jobs & Scheduling
|
|
2
|
+
|
|
3
|
+
Date: 2025-10-15
|
|
4
|
+
|
|
5
|
+
Status: Accepted
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
We need production-grade background job processing and simple scheduling with a one-call setup. The library already includes in-memory queue/scheduler for tests/local. We need a production backend and a minimal runner.
|
|
9
|
+
|
|
10
|
+
## Decision
|
|
11
|
+
- JobQueue protocol defines enqueue/reserve/ack/fail with retry and exponential backoff (base seconds * attempts). Jobs have: id, name, payload, available_at, attempts, max_attempts, backoff_seconds, last_error.
|
|
12
|
+
- Backends:
|
|
13
|
+
- InMemoryJobQueue for tests/local.
|
|
14
|
+
- RedisJobQueue for production using Redis primitives with visibility timeout and atomic operations.
|
|
15
|
+
- Scheduler:
|
|
16
|
+
- InMemoryScheduler providing interval-based scheduling via next_run_at. Cron parsing is out of scope initially; a simple YAML loader can be added later.
|
|
17
|
+
- Runner:
|
|
18
|
+
- A CLI loop `svc-infra jobs run` will tick the scheduler and process jobs in a loop with small sleep/backoff.
|
|
19
|
+
- Configuration:
|
|
20
|
+
- One-call `easy_jobs()` returns (queue, scheduler). Picks backend via `JOBS_DRIVER` env (memory|redis). Redis URL via `REDIS_URL`.
|
|
21
|
+
|
|
22
|
+
## Alternatives Considered
|
|
23
|
+
- Using RQ/Huey/Celery: heavier dependency and less control over API ergonomic goals; we prefer thin primitives aligned with svc-infra patterns.
|
|
24
|
+
- SQL-backed queue first: we will consider later; Redis is sufficient for v1.
|
|
25
|
+
|
|
26
|
+
## Consequences
|
|
27
|
+
- Enables outbox/webhook processors on a reliable queue.
|
|
28
|
+
- Minimal cognitive load: consistent APIs, ENV-driven.
|
|
29
|
+
- Future work: SQL queue, cron YAML loader, metrics, concurrency controls.
|
|
30
|
+
|
|
31
|
+
## Redis Data Model (initial)
|
|
32
|
+
- List `jobs:ready` holds ready job IDs; a ZSET `jobs:delayed` with score=available_at keeps delayed jobs; a HASH per job `job:{id}` stores fields.
|
|
33
|
+
- Reserve uses RPOPLPUSH from `jobs:ready` to `jobs:processing` or BRPOPLPUSH with timeout; sets `visible_at` on job as now+vt and increments `attempts`.
|
|
34
|
+
- Ack removes job from `jobs:processing` and deletes `job:{id}`.
|
|
35
|
+
- Fail increments attempts and computes next available_at = now + backoff_seconds * attempts; moves job to delayed ZSET.
|
|
36
|
+
- A housekeeping step periodically moves due jobs from delayed ZSET to ready list. Reserve also checks ZSET for due jobs opportunistically.
|
|
37
|
+
|
|
38
|
+
## Testing Strategy
|
|
39
|
+
- Unit tests cover enqueue/reserve/ack/fail, visibility timeout behavior, and DLQ after max_attempts.
|
|
40
|
+
- Runner tests cover one iteration loop processing.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# ADR 0003: Webhooks Framework
|
|
2
|
+
|
|
3
|
+
Date: 2025-10-15
|
|
4
|
+
|
|
5
|
+
Status: Accepted
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
Services need a consistent way to publish domain events to external consumers via webhooks, verify inbound signatures, and handle retries with backoff. We already have an outbox pattern, a job queue, and a webhook delivery worker.
|
|
9
|
+
|
|
10
|
+
## Decision
|
|
11
|
+
- Event Schema: minimal fields {topic, payload, version, created_at}. Versioning included to evolve payloads.
|
|
12
|
+
- Signing: HMAC-SHA256 over canonical JSON payload; header `X-Signature` carries hex digest. Future: include timestamp and v1 signature header variant.
|
|
13
|
+
- Outbox → Job Queue: Producer writes events to Outbox; outbox tick enqueues delivery jobs; worker performs HTTP POST with signature.
|
|
14
|
+
- Subscriptions: In-memory subscription store maps topic → {url, secret}. Persistence deferred.
|
|
15
|
+
- Verification: Provide helper for verifying incoming webhook requests by recomputing the HMAC.
|
|
16
|
+
- Retry: Already handled by JobQueue backoff; DLQ after max attempts.
|
|
17
|
+
|
|
18
|
+
## Consequences
|
|
19
|
+
- Clear boundary: producers don't call HTTP directly; they publish to Outbox.
|
|
20
|
+
- Deterministic signing & verification across producer/consumer.
|
|
21
|
+
- Extensibility: timestamped signed headers, secret rotation, persisted subscriptions are future extensions.
|
|
22
|
+
|
|
23
|
+
## Testing
|
|
24
|
+
- Unit tests for verification helper and end-to-end publish→outbox→queue→delivery using in-memory components and a fake HTTP client.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# ADR-0004: Tenancy Model and Enforcement
|
|
2
|
+
|
|
3
|
+
Date: 2025-10-15
|
|
4
|
+
|
|
5
|
+
Status: Proposed
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
The framework needs a consistent, ergonomic multi-tenant story across modules (API scaffolding, SQL/Mongo persistence, auth/security, payments, jobs, webhooks). Existing patterns already reference `tenant_id` in many places (payments models and service, audit/session models, SQL/Mongo scaffolds). However, enforcement and app ergonomics were not unified.
|
|
10
|
+
|
|
11
|
+
## Decision
|
|
12
|
+
|
|
13
|
+
Adopt a default "soft-tenant" isolation model via a `tenant_id` column and centralized enforcement primitives:
|
|
14
|
+
|
|
15
|
+
- Resolution: `resolve_tenant_id` and `require_tenant_id` FastAPI dependencies in `api.fastapi.tenancy.context`. Resolution order: override hook → identity (user/api_key) → `X-Tenant-Id` header → `request.state.tenant_id`.
|
|
16
|
+
- Enforcement in SQL: `TenantSqlService(SqlService)` that scopes list/get/update/delete/search/count with a `where` clause and injects `tenant_id` on create when the model supports it. Repository methods accept optional `where` filters.
|
|
17
|
+
- Router ergonomics: `make_tenant_crud_router_plus_sql` which requires `TenantId` and uses `TenantSqlService` under the hood. This keeps route code simple while enforcing scoping.
|
|
18
|
+
- Extensibility: `set_tenant_resolver` hook to override resolution logic per app; `tenant_field` parameter to support custom column names. Future: schema-per-tenant or db-per-tenant via alternate repository/service implementations.
|
|
19
|
+
|
|
20
|
+
## Alternatives considered
|
|
21
|
+
|
|
22
|
+
1) Enforce tenancy at the ORM layer (SQLAlchemy events/session) – rejected for clarity and testability; we prefer explicit service/dep composition.
|
|
23
|
+
2) Global middleware that rewrites queries – rejected due to SQLAlchemy complexity and opacity.
|
|
24
|
+
3) Only rely on developers to remember filters – rejected due to footguns.
|
|
25
|
+
|
|
26
|
+
## Consequences
|
|
27
|
+
|
|
28
|
+
- Clear default behavior with escape hatches. Minimal changes for consumers using CRUD builders and SqlService.
|
|
29
|
+
- Requires models to include an optional or required `tenant_id` column for scoping.
|
|
30
|
+
- Non-SQL stores should add equivalent wrappers; Mongo scaffolds already include `tenant_id` fields and can mirror these patterns later.
|
|
31
|
+
|
|
32
|
+
## Implementation Notes
|
|
33
|
+
|
|
34
|
+
- New modules: `api.fastapi.tenancy.context`, `db.sql.tenant`. Repository updated to accept `where` filters.
|
|
35
|
+
- CRUD router extended with `make_tenant_crud_router_plus_sql` to require `TenantId`.
|
|
36
|
+
- Tests added: `tests/tenancy/*` for resolution and service scoping.
|
|
37
|
+
|
|
38
|
+
## Open Items
|
|
39
|
+
|
|
40
|
+
- Per-tenant quotas & rate limit overrides (tie into rate limit dependency/middleware via a resolver that returns per-tenant config).
|
|
41
|
+
- Export tenant CLI (dump/import data for a specific tenant).
|
|
42
|
+
- Docs: isolation guidance (column vs schema vs db), migration guidance.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# ADR 0005: Data Lifecycle — Soft Delete, Retention, Erasure, Backups
|
|
2
|
+
|
|
3
|
+
Date: 2025-10-16
|
|
4
|
+
Status: Accepted
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
We need a coherent Data Lifecycle story in svc-infra that covers:
|
|
8
|
+
- Migrations & fixtures: simple way to run DB setup/migrations and load reference data.
|
|
9
|
+
- Soft delete conventions: consistent filtering and model scaffolding support.
|
|
10
|
+
- Retention policies: periodic purging of expired records per model/table.
|
|
11
|
+
- GDPR/PII erasure: queued workflow to scrub user-related data while preserving legal audit.
|
|
12
|
+
- Backups/PITR verification: a job that exercises restore checks or at least validates backup health signals.
|
|
13
|
+
|
|
14
|
+
Existing building blocks:
|
|
15
|
+
- Migrations CLI with end-to-end "setup-and-migrate" and new `sql-seed` command for executing a user-specified seed callable.
|
|
16
|
+
- Code: `src/svc_infra/cli/cmds/db/sql/alembic_cmds.py` (cmd_setup_and_migrate, cmd_seed)
|
|
17
|
+
- Soft delete support in repository and scaffold:
|
|
18
|
+
- Repo filtering: `src/svc_infra/db/sql/repository.py` (soft_delete flags, `deleted_at` timestamp, optional active flag)
|
|
19
|
+
- Model scaffolding: `src/svc_infra/db/sql/scaffold.py` (optional `deleted_at` field)
|
|
20
|
+
- Easy-setup helper to coordinate lifecycle bits:
|
|
21
|
+
- `src/svc_infra/data/add.py` provides a startup hook to auto-migrate and optional callbacks for fixtures, retention jobs, and an erasure job.
|
|
22
|
+
|
|
23
|
+
Gaps:
|
|
24
|
+
- No standardized fixture loader contract beyond the callback surface.
|
|
25
|
+
- No built-in retention policy registry or purge execution job.
|
|
26
|
+
- No opinionated GDPR erasure workflow and audit trail.
|
|
27
|
+
- No backup/PITR verification job implementation.
|
|
28
|
+
|
|
29
|
+
## Decision
|
|
30
|
+
Introduce minimal, composable primitives that keep svc-infra flexible while providing a clear path to production-grade lifecycle.
|
|
31
|
+
|
|
32
|
+
1) Fixture Loader Contract
|
|
33
|
+
- Provide a simple callable signature for deterministic, idempotent fixture loading: `Callable[[], None | Awaitable[None]]`.
|
|
34
|
+
- Document best practices: UPSERT by natural keys, avoid random IDs, guard on existing rows.
|
|
35
|
+
- Expose via `add_data_lifecycle(on_load_fixtures=...)` (already available); add docs and tests.
|
|
36
|
+
|
|
37
|
+
2) Retention Policy Registry
|
|
38
|
+
- Define a registry API that allows services to register per-resource retention rules.
|
|
39
|
+
- Basic shape:
|
|
40
|
+
- `RetentionPolicy(name: str, model: type, where: list[Any] | None, older_than_days: int, soft_delete_field: str = "deleted_at")`
|
|
41
|
+
- A purge function computes a cutoff timestamp and issues DELETE or marks soft-delete fields.
|
|
42
|
+
- Execution model: a periodic job (via jobs scheduler) calls `run_retention_purge(registry)`.
|
|
43
|
+
- Keep SQL-only first; room for NoSQL extensions later.
|
|
44
|
+
|
|
45
|
+
3) GDPR Erasure Workflow
|
|
46
|
+
- Provide a single callable entrypoint `erase_principal(principal_id: str) -> None | Awaitable[None]`.
|
|
47
|
+
- Default strategy: enqueue a job that runs a configurable erasure plan composed of steps (delete/soft-delete/overwrite) across tables.
|
|
48
|
+
- Add an audit log entry per erasure request with outcome and timestamp (reuse `security.audit` helpers if feasible).
|
|
49
|
+
- Keep the plan provider pluggable so apps specify which tables/columns participate.
|
|
50
|
+
|
|
51
|
+
4) Backup/PITR Verification Job
|
|
52
|
+
- Define an interface `verify_backups() -> HealthReport` with a minimal default implementation that:
|
|
53
|
+
- Queries the backup system or driver for last successful backup timestamp and retention window.
|
|
54
|
+
- Emits metrics/logs and returns a structured status.
|
|
55
|
+
- Defer full "restore drill" capability; provide extension hook only.
|
|
56
|
+
|
|
57
|
+
## Interfaces
|
|
58
|
+
- Registry
|
|
59
|
+
- `register_retention(policy: RetentionPolicy) -> None`
|
|
60
|
+
- `run_retention_purge(session_factory, policies: list[RetentionPolicy]) -> PurgeReport`
|
|
61
|
+
- Erasure
|
|
62
|
+
- `erase_principal(principal_id: str, plan: ErasurePlan, session_factory) -> ErasureReport`
|
|
63
|
+
- Fixtures
|
|
64
|
+
- `load_fixtures()` as provided by caller via `add_data_lifecycle`.
|
|
65
|
+
- Backup
|
|
66
|
+
- `verify_backups() -> BackupHealthReport`
|
|
67
|
+
|
|
68
|
+
## Alternatives Considered
|
|
69
|
+
- Heavy-weight DSL for retention and erasure: rejected for now; keep APIs Pythonic and pluggable.
|
|
70
|
+
- Trigger-level soft delete enforcement: skipped to avoid provider lock-in; enforced at repository and query layer.
|
|
71
|
+
- Full restore drill automation: out of scope for v1; introduce later behind provider integrations.
|
|
72
|
+
|
|
73
|
+
## Consequences
|
|
74
|
+
- Minimal surface that doesn't over-constrain adopters; provides default patterns and contracts.
|
|
75
|
+
- Requires additional test scaffolds and example docs to demonstrate usage.
|
|
76
|
+
- SQL-focused initial implementation; other backends can plug via similar interfaces.
|
|
77
|
+
|
|
78
|
+
## Rollout & Testing
|
|
79
|
+
- Add unit/integration tests for fixture loader, retention purge logic, and erasure workflow skeleton.
|
|
80
|
+
- Provide docs in `docs/database.md` with examples and operational guidance.
|
|
81
|
+
|
|
82
|
+
## References
|
|
83
|
+
- `src/svc_infra/db/sql/repository.py` soft-delete handling
|
|
84
|
+
- `src/svc_infra/db/sql/scaffold.py` deleted_at field scaffolding
|
|
85
|
+
- `src/svc_infra/data/add.py` data lifecycle helper
|
|
86
|
+
- `src/svc_infra/cli/cmds/db/sql/alembic_cmds.py` migrations & seed
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# ADR-0006: Ops SLOs, SLIs, and Metrics Naming
|
|
2
|
+
|
|
3
|
+
Date: 2025-10-16
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
Accepted
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
We already expose Prometheus metrics via `svc_infra.obs.add.add_observability`, which mounts the `PrometheusMiddleware` and exports:
|
|
10
|
+
- `http_server_requests_total{method,route,code}`
|
|
11
|
+
- `http_server_request_duration_seconds_bucket{route,method}` + _sum/_count
|
|
12
|
+
- `http_server_inflight_requests{route}`
|
|
13
|
+
- `http_server_response_size_bytes_bucket` + _sum/_count (where available)
|
|
14
|
+
- `http_server_exceptions_total{route,exception}` (where available)
|
|
15
|
+
|
|
16
|
+
We also optionally expose SQLAlchemy pool metrics and instrument `requests`/`httpx`. Logging is configured via `svc_infra.app.logging.setup_logging`.
|
|
17
|
+
|
|
18
|
+
## Decision
|
|
19
|
+
1. Metric naming and labels
|
|
20
|
+
- Keep `http_server_*` naming aligned with Prometheus and OpenTelemetry conventions.
|
|
21
|
+
- Labels: `route` uses normalized FastAPI route pattern (e.g., `/users/{id}`); `method` is uppercase HTTP verb; `code` is the 3-digit status.
|
|
22
|
+
- Add DB pool metrics with `db_pool_*` prefix when bound (labels: `engine`/`pool_name`).
|
|
23
|
+
2. SLIs
|
|
24
|
+
- Request Success Rate: 1 - error_ratio, where errors are 5xx by default; optionally include 429/499 as errors per service config.
|
|
25
|
+
- Request Latency: p50/p90/p99 on `http_server_request_duration_seconds` by `route` and overall.
|
|
26
|
+
- Availability (Probes): uptime of `/_ops/live` and `/_ops/ready` endpoints.
|
|
27
|
+
3. SLOs
|
|
28
|
+
- Default SLOs per service class:
|
|
29
|
+
- Public API: 99.9% success, p99 < 500ms.
|
|
30
|
+
- Internal API/Jobs control plane: 99.5% success, p99 < 1000ms.
|
|
31
|
+
- Error Budget: monthly window; alert on burn rates of 2h (fast) and 24h (slow). Budgets computed from success SLI.
|
|
32
|
+
4. Dashboards & Alerts
|
|
33
|
+
- Provide Grafana JSON dashboard templates referencing the above metrics and labels.
|
|
34
|
+
- Include alert rules for budget burn (fast/slow).
|
|
35
|
+
|
|
36
|
+
## Consequences
|
|
37
|
+
- Developers can rely on consistent metrics and labels for dashboards.
|
|
38
|
+
- SLO targets are explicit and can be overridden per service.
|
|
39
|
+
- Future work: Emit `http_server_exceptions_total` where missing; provide helper to register per-route classes (public/internal/admin) to pick default SLOs.
|
|
40
|
+
|
|
41
|
+
## Alternatives Considered
|
|
42
|
+
- OpenTelemetry SDK direct instrumentation was considered but deferred to keep dependency surface minimal; we keep the naming aligned for easy migration.
|
|
43
|
+
|
|
44
|
+
## References
|
|
45
|
+
- `src/svc_infra/obs/metrics/asgi.py`
|
|
46
|
+
- `src/svc_infra/api/fastapi/ops/add.py`
|
|
47
|
+
- Google SRE Workbook: SLOs and Error Budgets
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# ADR 0007: Docs & SDKs — Research and Design
|
|
2
|
+
|
|
3
|
+
Status: Proposed
|
|
4
|
+
|
|
5
|
+
Date: 2025-10-16
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
We want a production-ready documentation and SDK experience built on our existing FastAPI scaffolding.
|
|
10
|
+
Current capabilities in the codebase:
|
|
11
|
+
|
|
12
|
+
- Docs endpoints and export
|
|
13
|
+
- `add_docs(app, redoc_url, swagger_url, openapi_url, export_openapi_to)` mounts Swagger, ReDoc, and OpenAPI JSON; optional export on startup.
|
|
14
|
+
- `setup_service_api(...)` renders a landing page with per-version doc cards and local-only root docs.
|
|
15
|
+
- `add_prefixed_docs(...)` exposes scoped docs (e.g., for auth/payments) with per-scope OpenAPI, Swagger, ReDoc.
|
|
16
|
+
- OpenAPI conventions and enrichment pipeline
|
|
17
|
+
- Mutators pipeline (`openapi/mutators.py`) with: conventions, normalized Problem schema, pagination params/components, header params, info mutator, and auth scheme installers.
|
|
18
|
+
- Conventions define `Problem` schema and normalize examples.
|
|
19
|
+
- DX checks
|
|
20
|
+
- OpenAPI Problem+JSON lint in `dx/checks.py` and CLI to validate.
|
|
21
|
+
- SDK stub
|
|
22
|
+
- `add_sdk_generation_stub(app, on_generate=...)` exposes a hook endpoint to trigger SDK generation (no hard deps).
|
|
23
|
+
|
|
24
|
+
Gaps for a complete v1 experience:
|
|
25
|
+
|
|
26
|
+
- Enriched OpenAPI with examples and tags is not yet standardized across routers.
|
|
27
|
+
- No built-in SDK generator CLI; only a stub exists. No pinned toolchain or CI integration.
|
|
28
|
+
- No Postman collection generator.
|
|
29
|
+
- No dark-mode toggle/themes for Swagger/ReDoc (landing page supports light/dark).
|
|
30
|
+
- No smoke tests for generated SDKs.
|
|
31
|
+
|
|
32
|
+
## Decision
|
|
33
|
+
|
|
34
|
+
We will standardize the Docs & SDKs approach around the following:
|
|
35
|
+
|
|
36
|
+
1) OpenAPI enrichment
|
|
37
|
+
- Use existing mutators pipeline and add small mutators to:
|
|
38
|
+
- Inject global tags and tag descriptions for major areas (auth, payments, webhooks, ops).
|
|
39
|
+
- Attach minimal `x-codeSamples` for common operations (curl/httpie).
|
|
40
|
+
- Ensure `Problem` schema and example responses are present across 4xx/5xx.
|
|
41
|
+
- Keep pagination and header parameter mutators enabled by default.
|
|
42
|
+
|
|
43
|
+
2) Docs UI
|
|
44
|
+
- Continue with Swagger UI and ReDoc via `add_docs` and `setup_service_api`.
|
|
45
|
+
- Add an optional dark mode toggle for Swagger UI via custom CSS and a query param (design-only; implement later).
|
|
46
|
+
- Keep local-only exposure of root docs; version-specific docs always exposed under their mount path.
|
|
47
|
+
|
|
48
|
+
3) SDK generation pipeline (tools and layout)
|
|
49
|
+
- TypeScript: `openapi-typescript` to generate types (no runtime client) to `clients/typescript/`.
|
|
50
|
+
- Python: `openapi-python-client` to generate a client package to `clients/python/`.
|
|
51
|
+
- Provide a new CLI group `svc-infra sdk` with subcommands:
|
|
52
|
+
- `svc-infra sdk ts --schema openapi.json --out clients/typescript --package @org/service`
|
|
53
|
+
- `svc-infra sdk py --schema openapi.json --out clients/python --package service_sdk`
|
|
54
|
+
- `svc-infra sdk postman --schema openapi.json --out clients/postman_collection.json` (via converter)
|
|
55
|
+
- Pin generator versions in a minimal tool manifest (poetry extras and npm devDeps suggestions in docs) rather than hard deps in core library.
|
|
56
|
+
- Add optional CI steps to generate SDKs on release tags; artifacts uploaded; publishing pipelines documented.
|
|
57
|
+
|
|
58
|
+
4) Postman collection
|
|
59
|
+
- Use the Postman converter (`openapi-to-postmanv2`) to produce `clients/postman_collection.json` from the exported OpenAPI.
|
|
60
|
+
|
|
61
|
+
5) Testing & verification
|
|
62
|
+
- Extend `dx` checks to include: schema export presence, generator dry-run, and minimal smoke tests:
|
|
63
|
+
- TS: typecheck the generated d.ts.
|
|
64
|
+
- Python: `pip install -e` and import a sample client in a quick script.
|
|
65
|
+
- Keep these checks optional (opt-in via CI config) to avoid burdening minimal users.
|
|
66
|
+
|
|
67
|
+
## Consequences
|
|
68
|
+
|
|
69
|
+
- Pros: Clear, tool-agnostic pipeline; no heavy runtime dependencies; easy local and CI usage; versioned artifacts.
|
|
70
|
+
- Cons: Adds extra tooling expectations (node and python generators) for teams that opt in.
|
|
71
|
+
- Risk: Generator/tooling churn; mitigate by pinning versions and providing stubs/fallbacks.
|
|
72
|
+
|
|
73
|
+
## Implementation Notes (planned)
|
|
74
|
+
|
|
75
|
+
- Provide a small `svc_infra/cli/cmds/sdk` module with Typer commands that shell out to the generators if available, with helpful error messages if missing.
|
|
76
|
+
- Document usage in `docs/docs-and-sdks.md` (to be added), including examples and troubleshooting.
|
|
77
|
+
- Keep all new code behind DX/CLI; core library remains free of generator dependencies.
|
|
78
|
+
|
|
79
|
+
## Out of Scope (v1)
|
|
80
|
+
|
|
81
|
+
- Live “try it” consoles beyond Swagger UI.
|
|
82
|
+
- Multi-language example snippets beyond curl/httpie.
|
|
83
|
+
- Automatic publishing to npm/PyPI (documented manual workflows first).
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# ADR 0008: Billing Primitives (Usage, Quotas, Invoicing)
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Proposed — Research and Design complete for v1 scope.
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
We need shared billing primitives to support both usage-based and subscription features across services. Goals:
|
|
10
|
+
- Capture fine-grained usage events with idempotency and tenant isolation.
|
|
11
|
+
- Aggregate usage into billable buckets (hour/day/month) with rollups.
|
|
12
|
+
- Enforce entitlements/quotas at runtime (hard/soft limits).
|
|
13
|
+
- Produce invoice data structures and events; enable later integration with external providers (Stripe, Paddle) without coupling core DX to any vendor.
|
|
14
|
+
|
|
15
|
+
Non-goals for v1: taxes/VAT, complex proration rules, refunds/credits automation, dunning flows, provider-specific webhooks/end-to-end reconciliation.
|
|
16
|
+
|
|
17
|
+
## Decisions
|
|
18
|
+
|
|
19
|
+
1) Internal-first data model with optional provider adapters
|
|
20
|
+
- Persist usage, aggregates, plans, subscriptions, invoices in our SQL layer.
|
|
21
|
+
- Provide interfaces for provider adapters (Stripe later) to map internal invoices/lines and sync state when enabled.
|
|
22
|
+
|
|
23
|
+
2) Usage ingestion API + idempotency
|
|
24
|
+
- FastAPI router exposes POST /_billing/usage capturing events: {tenant_id, metric, amount, at, idempotency_key, metadata}.
|
|
25
|
+
- Enforce request idempotency via existing middleware + usage-event unique index on (tenant_id, metric, idempotency_key).
|
|
26
|
+
- Emit webhook event `billing.usage_recorded` (optional).
|
|
27
|
+
|
|
28
|
+
3) Aggregation job (scheduler)
|
|
29
|
+
- Background job reads new UsageEvent rows, aggregates into UsageAggregate by key (tenant, metric, period_start, period_granularity).
|
|
30
|
+
- Granularities: hour, day, month (config). Maintains running totals; idempotent.
|
|
31
|
+
- Emits `billing.usage_aggregated` webhook.
|
|
32
|
+
|
|
33
|
+
4) Entitlements and quotas
|
|
34
|
+
- Define Plan and PlanEntitlement models (feature flags, quotas per window).
|
|
35
|
+
- Subscriptions bind tenant -> plan, effective_at/ended_at.
|
|
36
|
+
- Runtime enforcement via dependency/decorator: `require_quota("metric", window="day", soft=True)` which raises/records when limit exceeded.
|
|
37
|
+
|
|
38
|
+
5) Invoicing primitives
|
|
39
|
+
- Invoice and InvoiceLine models created for each billing cycle (monthly default). Lines derived from aggregates and static prices.
|
|
40
|
+
- Price model: unit amount, currency, metric reference (for metered), or fixed recurring.
|
|
41
|
+
- Emit `billing.invoice_created` and `billing.invoice_finalized` webhooks; provider adapter can consume and sync out.
|
|
42
|
+
|
|
43
|
+
6) Observability
|
|
44
|
+
- Metrics: `billing_usage_ingest_total`, `billing_aggregate_duration_ms`, `billing_invoice_generated_total`.
|
|
45
|
+
- Logs: aggregation windows processed, invoice cycles.
|
|
46
|
+
|
|
47
|
+
7) Security & tenancy
|
|
48
|
+
- All models include tenant_id; APIs require tenant context. RBAC: billing.read/billing.write for admin/operator roles.
|
|
49
|
+
|
|
50
|
+
## Data Model (SQL)
|
|
51
|
+
|
|
52
|
+
Tables (minimal v1):
|
|
53
|
+
- usage_events(id, tenant_id, metric, amount, at_ts, idempotency_key, metadata_json, created_at)
|
|
54
|
+
- Unique (tenant_id, metric, idempotency_key)
|
|
55
|
+
- usage_aggregates(id, tenant_id, metric, period_start, granularity, total, updated_at)
|
|
56
|
+
- Unique (tenant_id, metric, period_start, granularity)
|
|
57
|
+
- plans(id, key, name, description, created_at)
|
|
58
|
+
- plan_entitlements(id, plan_id, key, limit_per_window, window, created_at)
|
|
59
|
+
- subscriptions(id, tenant_id, plan_id, effective_at, ended_at, created_at)
|
|
60
|
+
- prices(id, key, currency, unit_amount, metric, recurring_interval, created_at)
|
|
61
|
+
- invoices(id, tenant_id, period_start, period_end, status, total_amount, currency, created_at)
|
|
62
|
+
- invoice_lines(id, invoice_id, price_id, metric, quantity, amount, created_at)
|
|
63
|
+
|
|
64
|
+
All tables will be scaffolded with our SQL helpers and tenant mixin, with Alembic templates.
|
|
65
|
+
|
|
66
|
+
## APIs
|
|
67
|
+
|
|
68
|
+
- POST /_billing/usage: record usage events (body as above). Returns 202 with event id.
|
|
69
|
+
- GET /_billing/usage: list usage by metric and window (aggregated).
|
|
70
|
+
- GET /_billing/plans, GET /_billing/subscriptions, POST /_billing/subscriptions.
|
|
71
|
+
- GET /_billing/invoices, GET /_billing/invoices/{id}.
|
|
72
|
+
|
|
73
|
+
Routers mounted under a `/_billing` prefix and hidden behind auth + tenant guard. OpenAPI tags: Billing.
|
|
74
|
+
|
|
75
|
+
## Jobs & Webhooks
|
|
76
|
+
|
|
77
|
+
- Job: `aggregate_usage` runs on schedule; creates/updates UsageAggregate rows.
|
|
78
|
+
- Job: `generate_invoices` runs monthly; emits invoice events and inserts Invoice/InvoiceLine rows.
|
|
79
|
+
- Webhooks: `billing.usage_recorded`, `billing.usage_aggregated`, `billing.invoice_created`, `billing.invoice_finalized` (signed via existing module).
|
|
80
|
+
|
|
81
|
+
## Implementation Plan (Phased)
|
|
82
|
+
|
|
83
|
+
Phase 1 (MVP):
|
|
84
|
+
- Models + migrations; CRUD for Plans/Subs/Prices; Usage ingestion + idempotency; Aggregator job (daily granularity); Basic invoice generator (monthly, fixed price + metered by day sum); Webhooks emitted; Tests for ingestion, aggregation, simple invoice.
|
|
85
|
+
|
|
86
|
+
Phase 2:
|
|
87
|
+
- Granularity options (hourly); soft/hard quota decorator; Read APIs; Observability metrics; Docs.
|
|
88
|
+
|
|
89
|
+
Phase 3 (Provider adapter optional):
|
|
90
|
+
- Stripe adapter skeleton: map internal invoices/lines -> Stripe, idempotent sync; basic webhook handler to update statuses.
|
|
91
|
+
|
|
92
|
+
## Alternatives Considered
|
|
93
|
+
|
|
94
|
+
- Provider-first approach (Stripe-only) rejected for v1 to keep core DX portable and support non-card use-cases.
|
|
95
|
+
- Event-stream aggregation (Kafka) out-of-scope for framework baseline—can be integrated later.
|
|
96
|
+
|
|
97
|
+
## Risks
|
|
98
|
+
|
|
99
|
+
- Complexity creep around proration and taxes—explicitly out-of-scope for v1.
|
|
100
|
+
- Performance on large tenants—mitigated by granular aggregation and indexes.
|
|
101
|
+
|
|
102
|
+
## Testing
|
|
103
|
+
|
|
104
|
+
- Unit tests for ingestion idempotency, aggregation correctness, invoice totals.
|
|
105
|
+
- E2E-ish tests using in-memory queue + sqlite.
|
|
106
|
+
|
|
107
|
+
## Documentation
|
|
108
|
+
|
|
109
|
+
- `docs/billing.md`: usage API, quotas, invoice lifecycle, and Stripe adapter notes.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# ADR 0009: Acceptance Harness & Promotion Gate (A0)
|
|
2
|
+
|
|
3
|
+
Date: 2025-10-17
|
|
4
|
+
Status: Proposed
|
|
5
|
+
Decision: Adopt a post-build acceptance harness that brings up an ephemeral stack (Docker Compose) and gates image promotion on acceptance results.
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
- We need a thin but strict pre-deploy acceptance layer that runs after building images, before promotion.
|
|
9
|
+
- It should validate golden paths across domains and basic operational invariants.
|
|
10
|
+
- It must be easy to run locally and in CI and support a backend matrix (in-memory vs Redis+Postgres).
|
|
11
|
+
- Supply-chain checks (SBOM, image scan, provenance) should be part of the gate.
|
|
12
|
+
|
|
13
|
+
## Decision
|
|
14
|
+
- Introduce A0 Acceptance Harness:
|
|
15
|
+
- Compose stack (api + db + redis), Makefile helpers (accept/up/wait/seed/down).
|
|
16
|
+
- Seed CLI/script to create ADMIN/USER/TENANT fixtures and API key.
|
|
17
|
+
- Acceptance tests under `tests/acceptance` with `@pytest.mark.acceptance` and BASE_URL.
|
|
18
|
+
- CI job `build-and-accept` steps: build → compose up → seed → `pytest -m "acceptance or smoke"` → OpenAPI lint + API Doctor → teardown.
|
|
19
|
+
- Supply-chain: generate SBOM, image scan (Trivy/Grype) with severity threshold; upload SBOM.
|
|
20
|
+
- Provenance: sign/attest images via cosign/SLSA (best-effort for v1).
|
|
21
|
+
- Backend matrix: two jobs (in-memory vs Redis+Postgres).
|
|
22
|
+
|
|
23
|
+
## Alternatives
|
|
24
|
+
- Testcontainers-only approach (simpler per-test spin-up) — good DX but slower; we can adopt later for certain suites.
|
|
25
|
+
- Kubernetes-in-Docker (kind) for near-prod parity — heavier; likely a v2 improvement.
|
|
26
|
+
|
|
27
|
+
## Consequences
|
|
28
|
+
- Slightly longer CI time due to matrix and scans.
|
|
29
|
+
- Clearer promotion safety; early detection of config/env gaps.
|
|
30
|
+
|
|
31
|
+
## Implementation Notes
|
|
32
|
+
- Files to add:
|
|
33
|
+
- `docker-compose.test.yml`
|
|
34
|
+
- `Makefile` targets: `accept`, `compose_up`, `wait`, `seed`, `down`
|
|
35
|
+
- `tests/acceptance/` scaffolding: `conftest.py`, `_seed.py`, `_auth.py`, `_http.py`, first tests (headers/CORS)
|
|
36
|
+
- CI: `.github/workflows/ci.yml` job `build-and-accept`
|
|
37
|
+
- Env contracts:
|
|
38
|
+
- `SQL_URL`, `REDIS_URL` for backend matrix; `APP_ENV=test-accept` for toggles.
|
|
39
|
+
- Evidence:
|
|
40
|
+
- CI run URL, SBOM artifact link, scan report, acceptance summary.
|