idi-ftm2j-shared
Shared AWS infrastructure for the FTM2J terminal ecosystem. Two independent Pulumi stacks — deploy bootstrap first, then shared.
pulumi-bootstrap — GitHub Actions OIDC
Provisions the account-level OIDC identity provider and the two IAM roles that all dsi-clinic repos use to authenticate with AWS from GitHub Actions.
Run locally. This stack must be deployed from a workstation with AWS credentials — it creates the very roles that CI uses, so CI cannot deploy it itself.
cd pulumi-bootstrap
pulumi login s3://idi-ftm2j-dev-pulumi-state/ftm2j-shared/bootstrap # Config passphrase in Bitwarden
pulumi stack select dev
pulumi preview
pulumi up
Roles created (per repository):
| Role | Assumed by | Access |
|---|---|---|
checks |
Pull requests, manual workflow_dispatch runs |
Read-only (pulumi preview) |
deploy |
Pushes to main, dev, release/** |
Full deploy (pulumi up) |
Each repository gets its own checks + deploy role pair, trust-scoped to
repo:dsi-clinic/<repo> so a repo's workflows can assume only its own roles. The
stack loops over the idi:repos list (defaulted in
infra/config.py); to onboard a new repo,
add its name to that list and re-deploy this stack, then read its ARNs from the
checks_role_arns / deploy_role_arns stack outputs. See
docs/onboarding-a-processor.md for the full
processor checklist.
pulumi — Shared Infrastructure
Provisions the AWS resources shared across all FTM2J processor pipelines. Individual processor stacks reference these outputs rather than creating their own copies.
cd pulumi
pulumi stack select dev
pulumi preview
pulumi up
Resources:
| Resource | Description |
|---|---|
| S3 bucket | Pipeline input, output, and failure storage. Encrypted at rest; retained on stack destroy to prevent data loss. |
| S3 VPC gateway endpoint | Routes S3 traffic over the private AWS network, avoiding internet egress from ECS tasks. |
| SQS dead-letter queue | Captures EventBridge Scheduler invocation failures for inspection and replay. |
Stack outputs consumed by downstream processor stacks:
processor_bucket_name
processor_bucket_arn
s3_endpoint_id
s3_endpoint_arn
dlq_url
dlq_arn
deploy.ymlis path-filtered: version/publish jobs only run whensrc/**orpyproject.tomlchanged; the Pulumi deploy job only runs whenpulumi-shared/**changed.
development + contributing
Install all dependency groups (includes dev tools: pytest, ruff):
uv sync --all-groups
tests
uv run pytest
linting + formatting
uv run ruff check . # lint
uv run ruff format . # format
code style
| Rule | Value |
|---|---|
| Line length | 100 characters |
| Docstring convention | Google (pydocstyle) |
| Type annotations | Required on all public functions and classes |
| String quotes | Double-quoted (ruff Q ruleset) |
branching strategy + versioning
Onboarding a processor repo onto the shared CI/CD flow? See the end-to-end checklist in docs/onboarding-a-processor.md (workflow callers, branch protection, deploy key, value routing).
Two-branch model with short-lived issue branches.
long-lived branches
| Branch | Purpose | Version style | Deploy target |
|---|---|---|---|
dev |
Integration | X.Y.Z-alphaN (pre-release) |
dev stack |
main |
Production | X.Y.Z (stable) |
prod stack |
Both branches are protected. All changes occur via pull request.
short-lived branches
issue-<number>-<slug>— feature, bug-fix, and chore work.- Branch from
dev, PR back todev. - While the PR is open, only
checks.ymlruns (lint, tests, security, Pulumi preview). Pushes to the issue branch do not bump the version or deploy. - On merge, the push to
devtriggersdeploy.yml: bumps the alpha version and deploys thedevstack. - Note: It is best to create branches with this naming convention as you will be able to manually deploy these branches for testing in the
devstack. See (#manual-deploys)
- Branch from
- Hotfix — urgent production fix.
- Branch from
mainasissue-<number>-hotfix-<slug>, PR back tomain. - After release, merge
mainback intodev(see Syncing main back into dev).
- Branch from
ci/cd pipelines
Validation and deployment are split across two workflows:
checks.yml— runs on every PR, required before merge. Lint, tests, security scan, Pulumi preview.deploy.yml— runs on push todevormain(i.e. after a merge). Computes/bumps version, tags, releases, deploys Pulumi, publishes to PyPI (mainonly, if the repo includes a package).
versioning
The committed version in pyproject.toml is always a stable release. Alpha versions are never committed — they are computed inside the dev deploy run and used only for that run's tag/release. Only main writes a version back to the repo.
| Trigger | What happens | Example |
|---|---|---|
Push to dev |
Alpha computed in-workflow (not committed): next-patch base + run number + short SHA | 0.1.6a123+abc1234 |
Push to main |
uv version --bump patch bumps the committed stable version, committed by the deploy bot |
0.1.5 → 0.1.6 |
The alpha base is uv version --bump patch --dry-run --short (the next stable target), with a<run-number> for ordering and +<short-sha> for traceability. The +<sha> local segment is fine because alphas are never published to PyPI. Because the committed version is already stable, main uses --bump patch (not --bump stable, which would be a no-op).
Loop prevention. The bump (main) and sync-back (dev) commits are committed as idi-deploy-bot, and deploy.yml's version/deploy-pulumi jobs skip whenever the run's head commit was committed by that bot. This breaks the deploy→commit→deploy cycle without [skip ci] — which is deliberately avoided because it suppresses all workflows for the commit (including the required PR checks, silently blocking dev→main PRs) and trips on the literal string appearing anywhere in a message.
Each successful deploy:
mainonly: commits the bumpedpyproject.toml+uv.lock(as the deploy bot, so it doesn't re-triggerdeploy.yml).devcommits nothing.- Pushes a
vX.Y.Z[aN][+sha]git tag. - Creates a GitHub Release — pre-release on
dev, stable onmain. - On
main: builds the wheel/sdist and publishes to PyPI (if the repo ships a package), then thesync-devjob mergesmainback intodev(see below).
development cycle
1. dev → issue → alpha release
PR
issue-123-add-feature ────────────────────────────────► dev
▲ │
│ branch │ push triggers deploy.yml
│ ▼
dev ◄──────────────────────────────────── 0.1.6a123+abc1234, ...
merge deployed to dev stack
(alpha not committed)
git switch dev && git pullgit switch -c issue-123-add-feature- Commit, push, open PR targeting
dev.checks.ymlruns. - Merge the PR (squash recommended). The push to
devtriggersdeploy.yml:- Computes an alpha version in-workflow (e.g.
0.1.6a123+abc1234) — nothing is committed back todev. - Tags, creates a pre-release, deploys the
devPulumi stack, publishes the image. PyPI publish is skipped.
- Computes an alpha version in-workflow (e.g.
- Each merge into
devproduces a fresh alpha keyed to its run number and commit SHA. They all target the same next-patch base (e.g.0.1.6a124+def5678,0.1.6a125+...) until a stable release onmainadvances the base.
2. dev → main → stable release
dev (0.1.6a*) ───────── PR ─────────► main
▲ │ push triggers deploy.yml
│ ▼
◄────────────────────────────────── 0.1.6 (stable)
sync-dev job deployed to prod stack
(automatic) published to PyPI
- When
devis ready to ship, open a PR fromdev→main.checks.ymlruns against theprodPulumi stack preview. (Because alphas are no longer committed todev, the PR head is a normal commit and the required checks run.) - Review and merge. Do not squash — preserve history so release notes capture every change. A merge commit is fine.
- The push to
maintriggersdeploy.yml:uv version --bump patchadvances the committed stable version (0.1.5→0.1.6) and commits it tomainas the deploy bot.- Tags
v0.1.6, creates a stable GitHub Release, deploys theprodPulumi stack, publishes to PyPI (if applicable).
3. syncing main back into dev
This is automatic: after a stable release, the sync-dev job in deploy.yml merges main back into dev (a direct push, committed by the deploy bot) so dev's pyproject.toml reflects the released stable version. Because the push is from idi-deploy-bot, deploy.yml's guards skip it and it doesn't re-trigger Deploy on dev.
The
sync-devpush requiresDEPLOY_KEYto be allow-listed indev's branch protection.
If you ever need to sync manually (e.g. a hotfix landed directly on main):
git switch main && git pull
git switch dev && git pull
git merge main # bring in the stable bump commit + any hotfixes
git push
The next push to dev then produces an alpha targeting the following patch (e.g. 0.1.7a*) above the just-released 0.1.6. On a pyproject.toml conflict, keep main's stable version.
manual deploys
deploy.yml accepts workflow_dispatch:
- From
devit deploys thedevstack. - From
mainit deploys theprodstack.
Use this to redeploy Pulumi without a code change (e.g. after rotating a secret). Version/publish jobs stay gated on src/** changes.
summary
devis the only place new work lands; every merge produces an alpha.maincuts stable releases from whatever alphadevis on.- After every release on
main, mergemainback intodev.
branch protection rules
Branch protection, merge-method, deploy-key, and required-check conventions are documented once in docs/onboarding-a-processor.md (§2 Repo settings / branch protection, §3 Deploy key setup) — the same rulesets apply to this repo and every processor.
Release files for idi-ftm2j-shared 0.1.16
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| idi_ftm2j_shared-0.1.16.tar.gz | 130.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| idi_ftm2j_shared-0.1.16-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 153.9 kB