fauxbus 0.1.2__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.
@@ -0,0 +1,13 @@
1
+ # Podman reads this too (falls back to .dockerignore when no
2
+ # .containerignore exists) — one file covers both runtimes.
3
+ .git
4
+ .worktrees
5
+ .venv
6
+ venv
7
+ __pycache__
8
+ *.pyc
9
+ .pytest_cache
10
+ .ruff_cache
11
+ dist
12
+ build
13
+ *.egg-info
@@ -0,0 +1,17 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .venv/
7
+ venv/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ .coverage
11
+
12
+ # Internal review artifacts — findings land here for triage, not for
13
+ # publication. A security review is a working document: it names
14
+ # weaknesses before they are fixed, which is precisely the thing not to
15
+ # hand a stranger. What survives triage becomes a commit, an issue, or
16
+ # a SPEC entry — those are the public record.
17
+ .reviews/
@@ -0,0 +1,170 @@
1
+ # Fauxbus CI — the conformance tether, mechanized.
2
+ #
3
+ # SPEC.md promises "one test suite, two backends: Fauxbus on every CI run,
4
+ # the real Globus service env-gated before each release." This file is the
5
+ # first half of that promise. The second half arrives with the recording
6
+ # session (see RELEASING.md for the honest posture until then).
7
+ #
8
+ # Fauxbus is a library, not a service — no deploy step. What ships is a
9
+ # wheel and an image, so CI builds and boots both.
10
+ #
11
+ # THE TRUST ARC (a lesson worth keeping): build #1 failed because
12
+ # untrusted repos may not mount `volumes` — correctly, since this config
13
+ # lives in the repo and a config that could mount /var/run/docker.sock
14
+ # would hand the host to anyone who can push a branch. The refusal was
15
+ # the security model working. An admin has since marked the repo
16
+ # trusted, which is a statement about who can push here, not about
17
+ # Woodpecker relaxing: the socket mount below is exactly as dangerous as
18
+ # it always was, and belongs only in repos where every committer could
19
+ # already root the host by other means.
20
+ #
21
+ # Pip cache mounted at /opt/cache/pip, per house convention.
22
+
23
+ # Tags get their own pipeline too: a tag re-verifies the exact tagged
24
+ # commit, and `event: tag` is what the parked publish-pypi step at the
25
+ # bottom will hang on once restored.
26
+ when:
27
+ - event: push
28
+ - event: tag
29
+
30
+ labels:
31
+ host: xdocker01
32
+
33
+ matrix:
34
+ PYTHON:
35
+ - "3.11"
36
+ - "3.12"
37
+ - "3.13"
38
+
39
+ steps:
40
+ # `ruff check` only — deliberately no `ruff format --check`. pyproject
41
+ # ignores E501 at line-length 100, which is the author saying line breaks
42
+ # are a human call in this codebase; a formatter gate would overrule that.
43
+ - name: lint
44
+ image: python:${PYTHON}-slim
45
+ commands:
46
+ - pip install --cache-dir /cache/pip -e ".[dev]"
47
+ - ruff check src/ tests/
48
+ volumes:
49
+ - /opt/cache/pip:/cache/pip
50
+
51
+ # FAUXBUS_REQUIRE_CONFORMANCE is the point of this step, not decoration.
52
+ # tests/test_sdk_conformance.py guards its import with `importorskip` so a
53
+ # bare checkout still runs; that same guard means a CI image missing
54
+ # globus-sdk would drop every SDK test and still go green. The env var
55
+ # turns that skip into a collection error, so "the SDK is the contract"
56
+ # stays a fact about this pipeline rather than a claim in a README.
57
+ - name: test
58
+ image: python:${PYTHON}-slim
59
+ environment:
60
+ FAUXBUS_REQUIRE_CONFORMANCE: "1"
61
+ commands:
62
+ - pip install --cache-dir /cache/pip -e ".[dev,conformance]"
63
+ - python -c "import globus_sdk; print('SDK under test:', globus_sdk.__version__)"
64
+ - pytest -v --tb=short
65
+ volumes:
66
+ - /opt/cache/pip:/cache/pip
67
+
68
+ # An editable install never exercises the wheel path, and the wheel is
69
+ # what the container image and every consumer actually installs. Build
70
+ # it here so a packaging break fails CI instead of surfacing at
71
+ # someone's `pipx install`.
72
+ - name: build-wheel
73
+ image: python:${PYTHON}-slim
74
+ commands:
75
+ - pip install --cache-dir /cache/pip build
76
+ - python -m build --wheel
77
+ - pip install --cache-dir /cache/pip dist/*.whl
78
+ - fauxbus --help
79
+ volumes:
80
+ - /opt/cache/pip:/cache/pip
81
+ depends_on:
82
+ - test
83
+
84
+ # The image is shipped documentation: examples/compose.yaml tells
85
+ # consumers to build it, and root-cellar's deploy module actually does.
86
+ # This step proves the Containerfile builds, boots, and answers the
87
+ # same unauthenticated /_fauxbus/ probe the HEALTHCHECK uses — the
88
+ # thing compose's `depends_on: service_healthy` hangs on. Gated to one
89
+ # matrix cell: the image pins its own Python, so building it three
90
+ # times would test Woodpecker, not Fauxbus.
91
+ #
92
+ # Names key on CI_PIPELINE_NUMBER, not the commit SHA: a push to main
93
+ # and a fleet branch can carry the SAME sha and run concurrently, and
94
+ # two `docker run --name fauxbus-ci-<sha>` would collide. The
95
+ # pipeline number is unique by construction.
96
+ - name: build-image
97
+ image: docker:cli
98
+ when:
99
+ matrix:
100
+ PYTHON: "3.13"
101
+ environment:
102
+ # The Containerfile uses RUN --mount, a BuildKit feature; be
103
+ # explicit rather than trusting the daemon's default.
104
+ DOCKER_BUILDKIT: "1"
105
+ commands:
106
+ - docker build -f Containerfile -t fauxbus:ci-${CI_PIPELINE_NUMBER} .
107
+ - docker run -d --name fauxbus-ci-${CI_PIPELINE_NUMBER} fauxbus:ci-${CI_PIPELINE_NUMBER}
108
+ - |
109
+ for i in $(seq 1 30); do
110
+ docker exec fauxbus-ci-${CI_PIPELINE_NUMBER} python -c \
111
+ "import urllib.request; urllib.request.urlopen('http://127.0.0.1:9800/_fauxbus/', timeout=1)" \
112
+ && break
113
+ [ "$i" = "30" ] && { docker logs fauxbus-ci-${CI_PIPELINE_NUMBER}; exit 1; }
114
+ sleep 1
115
+ done
116
+ # The SBOM must ride in the image and name what it claims to name
117
+ # — an image that lost its bill of materials fails the build, not
118
+ # a compliance review three months later.
119
+ - |
120
+ docker exec fauxbus-ci-${CI_PIPELINE_NUMBER} python -c \
121
+ "import json; d = json.load(open('/usr/share/fauxbus/sbom.json')); names = sorted(c['name'] for c in d['components']); assert 'fauxbus' in names, names; print('SBOM components:', names)"
122
+ - docker rm -f fauxbus-ci-${CI_PIPELINE_NUMBER}
123
+ - docker rmi fauxbus:ci-${CI_PIPELINE_NUMBER} || true
124
+ volumes:
125
+ - /var/run/docker.sock:/var/run/docker.sock
126
+ depends_on:
127
+ - test
128
+
129
+ # ---------------------------------------------------------------------------
130
+ # PARKED PENDING PUBLICATION — the PyPI publish step, written 2026-08-05
131
+ # while we were thinking of it, so tag time never has to improvise.
132
+ # The name is free (checked: fauxbus 404s on PyPI) and the v0.1.0
133
+ # artifacts pass `twine check`; this step is the automation for v0.2
134
+ # onward. ONE condition left — was two:
135
+ #
136
+ # 1. A project-scoped PyPI token exists as the Woodpecker secret
137
+ # `pypi_token`. (PyPI's OIDC trusted publishing doesn't cover
138
+ # Woodpecker, so token it is.) Project-scoped implies the first,
139
+ # manual upload already happened — RELEASING.md has the sequence.
140
+ # 2. DONE 2026-08-07 — pyproject's homepage/issues URLs now point at
141
+ # github.com/atmarx/fauxbus, which is public with issues enabled.
142
+ # (Was: a package that tells strangers to file issues behind the
143
+ # VPN is a bad look.)
144
+ #
145
+ # The `event: tag` this hangs on is already live in the top-level when.
146
+ #
147
+ # - name: publish-pypi
148
+ # image: python:${PYTHON}-slim
149
+ # when:
150
+ # event: tag
151
+ # matrix:
152
+ # PYTHON: "3.13"
153
+ # environment:
154
+ # TWINE_USERNAME: __token__
155
+ # TWINE_PASSWORD:
156
+ # from_secret: pypi_token
157
+ # commands:
158
+ # - pip install --cache-dir /cache/pip build twine
159
+ # - python -m build
160
+ # # The tag names the version; the build had better agree. A tag
161
+ # # pointing at a .dev0 commit must die here, not on PyPI —
162
+ # # uploads are burn-once.
163
+ # - test -f "dist/fauxbus-${CI_COMMIT_TAG#v}.tar.gz" || { echo "tag ${CI_COMMIT_TAG} does not match built version:"; ls dist/; exit 1; }
164
+ # - twine check dist/*
165
+ # - twine upload --non-interactive dist/*
166
+ # volumes:
167
+ # - /opt/cache/pip:/cache/pip
168
+ # depends_on:
169
+ # - test
170
+ # ---------------------------------------------------------------------------
@@ -0,0 +1,58 @@
1
+ # Fauxbus — a wire-level fake of the Globus API.
2
+ #
3
+ # Two stages: build the wheel, install only the wheel. Fauxbus has zero
4
+ # runtime dependencies, so the final image is the Python base plus one
5
+ # pure-stdlib package — no supply chain rides along into your dev stack.
6
+ #
7
+ # Named Containerfile (the OCI-neutral spelling). Docker wants
8
+ # `-f Containerfile`, or in compose:
9
+ # build: { context: ., dockerfile: Containerfile }
10
+
11
+ FROM python:3.13-slim AS build
12
+ WORKDIR /src
13
+ COPY pyproject.toml README.md LICENSE ./
14
+ COPY src ./src
15
+ RUN pip wheel --no-deps --no-cache-dir --wheel-dir /dist .
16
+
17
+ # The SBOM is generated from a CLEAN venv holding only the wheel we just
18
+ # built: pointing the generator at its own environment would list the
19
+ # generator and its dependencies, polluting the receipt. For a
20
+ # zero-dependency package the bill of materials is short on purpose —
21
+ # it is the machine-readable proof of the "no supply chain rides along"
22
+ # claim, license metadata included (CycloneDX reads MIT straight out of
23
+ # the wheel).
24
+ RUN pip install --no-cache-dir cyclonedx-bom \
25
+ && python -m venv /sbomenv \
26
+ && /sbomenv/bin/pip install --no-cache-dir /dist/*.whl \
27
+ && cyclonedx-py environment /sbomenv/bin/python --of json -o /dist/sbom.json
28
+
29
+ FROM python:3.13-slim
30
+
31
+ LABEL org.opencontainers.image.title="fauxbus" \
32
+ org.opencontainers.image.description="A wire-level fake of the Globus API for E2E testing. Independent test tool, not affiliated with Globus." \
33
+ org.opencontainers.image.source="https://github.com/atmarx/fauxbus" \
34
+ org.opencontainers.image.licenses="MIT"
35
+
36
+ ENV PYTHONUNBUFFERED=1
37
+
38
+ RUN --mount=type=bind,from=build,source=/dist,target=/dist \
39
+ pip install --no-cache-dir /dist/*.whl
40
+
41
+ # The image carries its own bill of materials — transparency travels
42
+ # with the artifact, not with a wiki page about the artifact.
43
+ COPY --from=build /dist/sbom.json /usr/share/fauxbus/sbom.json
44
+
45
+ # Unprivileged, uid matching the port — memorable, and stable for anyone
46
+ # who needs to reason about mounted-file ownership.
47
+ RUN useradd --system --uid 9800 --no-create-home fauxbus
48
+ COPY --chmod=755 container/entrypoint.sh /usr/local/bin/fauxbus-entrypoint
49
+ USER fauxbus
50
+
51
+ EXPOSE 9800
52
+
53
+ # The control-plane index answers without auth — the natural liveness
54
+ # probe, and what makes `depends_on: service_healthy` work in compose.
55
+ HEALTHCHECK --interval=5s --timeout=2s --start-period=2s --retries=3 \
56
+ CMD ["python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:9800/_fauxbus/', timeout=1)"]
57
+
58
+ ENTRYPOINT ["fauxbus-entrypoint"]
fauxbus-0.1.2/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Andrew Marx
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
fauxbus-0.1.2/PKG-INFO ADDED
@@ -0,0 +1,163 @@
1
+ Metadata-Version: 2.4
2
+ Name: fauxbus
3
+ Version: 0.1.2
4
+ Summary: A wire-level fake of the Globus API for E2E testing
5
+ Project-URL: Homepage, https://github.com/atmarx/fauxbus
6
+ Project-URL: Issues, https://github.com/atmarx/fauxbus/issues
7
+ Author: Andrew Marx
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: fake,globus,http,integration-tests,testing
11
+ Classifier: Development Status :: 2 - Pre-Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Software Development :: Testing
19
+ Requires-Python: >=3.11
20
+ Provides-Extra: conformance
21
+ Requires-Dist: globus-sdk<5,>=4.8.1; extra == 'conformance'
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest<9,>=8; extra == 'dev'
24
+ Requires-Dist: ruff<1,>=0.5; extra == 'dev'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # Fauxbus
28
+
29
+ A wire-level fake of the Globus API for E2E testing.
30
+
31
+ ## The problem
32
+
33
+ Globus is the data fabric of research computing: it moves datasets
34
+ between universities, national labs, and HPC centers, and its API is how
35
+ institutions automate that world — group rosters that gate who can reach
36
+ which collection, permissions, transfer pipelines. If you run research
37
+ infrastructure, sooner or later you have code calling this API, and that
38
+ code matters more than most: it decides who can touch the data.
39
+
40
+ Testing that code is where it gets uncomfortable. Globus has no
41
+ sandbox, no emulator, no local mode. The SDK ships a testing module,
42
+ but it is in-process canned responses — by its own docs "a best
43
+ approximation of API responses" — which serves unit tests well and
44
+ cannot back a socket. So an integration test has two options: a real
45
+ tenancy (real credentials in CI, real groups with real people in them,
46
+ rate limits, shared mutable state, network weather), or nothing. A lot
47
+ of shops choose nothing. The layer that gates access to research data
48
+ ends up the least-tested code in the pipeline, precisely because it is
49
+ the riskiest thing to point a test at.
50
+
51
+ Fauxbus is a third option: a standalone HTTP server that imitates the
52
+ Globus API at the wire level. Point your `globus-sdk` client at
53
+ `http://localhost:9800` instead of the real service and run your
54
+ existing suite — same requests, same response shapes, same status
55
+ codes. Your code never learns it isn't talking to Globus. And because
56
+ the whole point of a fake is control, failure is a feature you schedule:
57
+ a 429 on exactly the request where your retry logic needs exercise, an
58
+ identity that is already in the group, a batch action that partially
59
+ fails the way the real service partially fails.
60
+
61
+ **Status: early but real.** The complete `GroupsClient` surface of
62
+ globus-sdk 4.8.1 is implemented — all fourteen methods, all eleven
63
+ batch membership verbs — and the conformance suite drives every one of
64
+ the fourteen through the actual globus-sdk, unmodified, including the
65
+ SDK's retry layer recovering through an injected 429. Response shapes
66
+ not yet backed by a recording are marked `PROVISIONAL` in code and
67
+ tracked as conformance obligations. Nothing is stable yet — see
68
+ [SPEC.md](SPEC.md) for the full design.
69
+
70
+ ## Fake, not mock
71
+
72
+ Your code doesn't know Fauxbus is there. No monkeypatching, no fixture
73
+ dicts shaped like your guess about what Globus returns. The client
74
+ library you ship is the client library you test against — Fauxbus
75
+ imitates what `globus-sdk` actually sends and expects, pinned to a
76
+ stated SDK version and derived from real recorded responses, never
77
+ guesswork.
78
+
79
+ If canned responses inside a Python unit test are all you need, use the
80
+ SDK's own `globus_sdk.testing` — that's the in-process layer, and its
81
+ fixtures are Fauxbus's ground truth. Fauxbus is for the layer it
82
+ architecturally can't reach: a real socket your whole stack can talk
83
+ to, with state behind it (think moto next to botocore's `Stubber`).
84
+
85
+ ## Quick start
86
+
87
+ ```sh
88
+ # not on PyPI yet — install straight from the repo
89
+ pipx install git+https://github.com/atmarx/fauxbus
90
+ fauxbus serve --port 9800
91
+ ```
92
+
93
+ Point `globus_sdk.GroupsClient(...)` at that port instead of the real
94
+ service host and run your existing test suite. Other Globus clients
95
+ will meet honest `501`s until their surfaces exist — see below.
96
+
97
+ ## In a compose stack
98
+
99
+ The repo ships a `Containerfile`. Zero runtime dependencies means the
100
+ image is the Python base plus one stdlib-only wheel — nothing else rides
101
+ along. Mount a state document at `/seed.json` and the world exists
102
+ before the first test connects:
103
+
104
+ ```yaml
105
+ services:
106
+ fauxbus:
107
+ build: { context: ., dockerfile: Containerfile }
108
+ ports: ["9800:9800"]
109
+ volumes: ["./seed.json:/seed.json:ro"]
110
+ ```
111
+
112
+ The image ships a healthcheck on the control-plane index, so
113
+ `depends_on: { fauxbus: { condition: service_healthy } }` does the
114
+ waiting for you. A runnable example — sibling-service pattern, seed
115
+ file included, both round-trip tested in CI — is in
116
+ [`examples/`](examples/).
117
+
118
+ ## The control plane
119
+
120
+ Everything test-harness-y lives under `/_fauxbus/`, a prefix no Globus
121
+ API will ever claim — the imitated surface stays pure. `GET
122
+ /_fauxbus/state` dumps everything in the world for assertions; `POST
123
+ /_fauxbus/reset` returns the world to its canonical seed (or to empty)
124
+ between tests — mutate freely, reset comes home. Full control-plane
125
+ surface is in [SPEC.md](SPEC.md).
126
+
127
+ It is unauthenticated by design — it imitates a service that answers
128
+ the network, and harnesses are often sibling containers. Which means
129
+ anyone who can reach the port can rewrite the world and arm forged
130
+ responses; for a fake, the sharpest risk isn't a crash but a lie, since
131
+ tests that trust it would go green when they should go red. Run it
132
+ where you'd run any test fixture. On a shared host,
133
+ `--control-loopback-only` refuses `/_fauxbus/` from non-loopback peers
134
+ without touching the imitated surface. The reasoning, and the two
135
+ things that flag deliberately doesn't solve, are in
136
+ [SPEC.md](SPEC.md#control-plane-reachability).
137
+
138
+ ## Loud 501s, no fiction
139
+
140
+ Fauxbus only implements endpoints a real consumer needs. Anything it
141
+ doesn't imitate yet returns a loud `501` with a pointer to file an
142
+ issue — never a silent wrong answer pretending to be Globus. If you
143
+ hit one, that's the fastest way to get it prioritized.
144
+
145
+ ## The source is meant to be read
146
+
147
+ Comments narrate the design decisions where they live — why uuid5 and
148
+ never uuid4, why one lock, why 404 instead of 403, why failure
149
+ injection can't touch the control plane — at a depth meant for someone
150
+ learning how to *build* a wire-level fake, not just use one.
151
+
152
+ ## Not affiliated with Globus
153
+
154
+ Fauxbus is an independent test tool, not affiliated with or endorsed by
155
+ Globus or the University of Chicago. "Globus" is their trademark; this
156
+ project imitates the API's behavior for local testing only — the name
157
+ is a confession, not an infringement. It's *faux*.
158
+
159
+ ## License
160
+
161
+ MIT. Fauxbus contains no Globus code: it imitates publicly documented
162
+ wire behavior, grounded against the Apache-2.0 `globus-sdk` as a
163
+ test-time dependency only.
@@ -0,0 +1,137 @@
1
+ # Fauxbus
2
+
3
+ A wire-level fake of the Globus API for E2E testing.
4
+
5
+ ## The problem
6
+
7
+ Globus is the data fabric of research computing: it moves datasets
8
+ between universities, national labs, and HPC centers, and its API is how
9
+ institutions automate that world — group rosters that gate who can reach
10
+ which collection, permissions, transfer pipelines. If you run research
11
+ infrastructure, sooner or later you have code calling this API, and that
12
+ code matters more than most: it decides who can touch the data.
13
+
14
+ Testing that code is where it gets uncomfortable. Globus has no
15
+ sandbox, no emulator, no local mode. The SDK ships a testing module,
16
+ but it is in-process canned responses — by its own docs "a best
17
+ approximation of API responses" — which serves unit tests well and
18
+ cannot back a socket. So an integration test has two options: a real
19
+ tenancy (real credentials in CI, real groups with real people in them,
20
+ rate limits, shared mutable state, network weather), or nothing. A lot
21
+ of shops choose nothing. The layer that gates access to research data
22
+ ends up the least-tested code in the pipeline, precisely because it is
23
+ the riskiest thing to point a test at.
24
+
25
+ Fauxbus is a third option: a standalone HTTP server that imitates the
26
+ Globus API at the wire level. Point your `globus-sdk` client at
27
+ `http://localhost:9800` instead of the real service and run your
28
+ existing suite — same requests, same response shapes, same status
29
+ codes. Your code never learns it isn't talking to Globus. And because
30
+ the whole point of a fake is control, failure is a feature you schedule:
31
+ a 429 on exactly the request where your retry logic needs exercise, an
32
+ identity that is already in the group, a batch action that partially
33
+ fails the way the real service partially fails.
34
+
35
+ **Status: early but real.** The complete `GroupsClient` surface of
36
+ globus-sdk 4.8.1 is implemented — all fourteen methods, all eleven
37
+ batch membership verbs — and the conformance suite drives every one of
38
+ the fourteen through the actual globus-sdk, unmodified, including the
39
+ SDK's retry layer recovering through an injected 429. Response shapes
40
+ not yet backed by a recording are marked `PROVISIONAL` in code and
41
+ tracked as conformance obligations. Nothing is stable yet — see
42
+ [SPEC.md](SPEC.md) for the full design.
43
+
44
+ ## Fake, not mock
45
+
46
+ Your code doesn't know Fauxbus is there. No monkeypatching, no fixture
47
+ dicts shaped like your guess about what Globus returns. The client
48
+ library you ship is the client library you test against — Fauxbus
49
+ imitates what `globus-sdk` actually sends and expects, pinned to a
50
+ stated SDK version and derived from real recorded responses, never
51
+ guesswork.
52
+
53
+ If canned responses inside a Python unit test are all you need, use the
54
+ SDK's own `globus_sdk.testing` — that's the in-process layer, and its
55
+ fixtures are Fauxbus's ground truth. Fauxbus is for the layer it
56
+ architecturally can't reach: a real socket your whole stack can talk
57
+ to, with state behind it (think moto next to botocore's `Stubber`).
58
+
59
+ ## Quick start
60
+
61
+ ```sh
62
+ # not on PyPI yet — install straight from the repo
63
+ pipx install git+https://github.com/atmarx/fauxbus
64
+ fauxbus serve --port 9800
65
+ ```
66
+
67
+ Point `globus_sdk.GroupsClient(...)` at that port instead of the real
68
+ service host and run your existing test suite. Other Globus clients
69
+ will meet honest `501`s until their surfaces exist — see below.
70
+
71
+ ## In a compose stack
72
+
73
+ The repo ships a `Containerfile`. Zero runtime dependencies means the
74
+ image is the Python base plus one stdlib-only wheel — nothing else rides
75
+ along. Mount a state document at `/seed.json` and the world exists
76
+ before the first test connects:
77
+
78
+ ```yaml
79
+ services:
80
+ fauxbus:
81
+ build: { context: ., dockerfile: Containerfile }
82
+ ports: ["9800:9800"]
83
+ volumes: ["./seed.json:/seed.json:ro"]
84
+ ```
85
+
86
+ The image ships a healthcheck on the control-plane index, so
87
+ `depends_on: { fauxbus: { condition: service_healthy } }` does the
88
+ waiting for you. A runnable example — sibling-service pattern, seed
89
+ file included, both round-trip tested in CI — is in
90
+ [`examples/`](examples/).
91
+
92
+ ## The control plane
93
+
94
+ Everything test-harness-y lives under `/_fauxbus/`, a prefix no Globus
95
+ API will ever claim — the imitated surface stays pure. `GET
96
+ /_fauxbus/state` dumps everything in the world for assertions; `POST
97
+ /_fauxbus/reset` returns the world to its canonical seed (or to empty)
98
+ between tests — mutate freely, reset comes home. Full control-plane
99
+ surface is in [SPEC.md](SPEC.md).
100
+
101
+ It is unauthenticated by design — it imitates a service that answers
102
+ the network, and harnesses are often sibling containers. Which means
103
+ anyone who can reach the port can rewrite the world and arm forged
104
+ responses; for a fake, the sharpest risk isn't a crash but a lie, since
105
+ tests that trust it would go green when they should go red. Run it
106
+ where you'd run any test fixture. On a shared host,
107
+ `--control-loopback-only` refuses `/_fauxbus/` from non-loopback peers
108
+ without touching the imitated surface. The reasoning, and the two
109
+ things that flag deliberately doesn't solve, are in
110
+ [SPEC.md](SPEC.md#control-plane-reachability).
111
+
112
+ ## Loud 501s, no fiction
113
+
114
+ Fauxbus only implements endpoints a real consumer needs. Anything it
115
+ doesn't imitate yet returns a loud `501` with a pointer to file an
116
+ issue — never a silent wrong answer pretending to be Globus. If you
117
+ hit one, that's the fastest way to get it prioritized.
118
+
119
+ ## The source is meant to be read
120
+
121
+ Comments narrate the design decisions where they live — why uuid5 and
122
+ never uuid4, why one lock, why 404 instead of 403, why failure
123
+ injection can't touch the control plane — at a depth meant for someone
124
+ learning how to *build* a wire-level fake, not just use one.
125
+
126
+ ## Not affiliated with Globus
127
+
128
+ Fauxbus is an independent test tool, not affiliated with or endorsed by
129
+ Globus or the University of Chicago. "Globus" is their trademark; this
130
+ project imitates the API's behavior for local testing only — the name
131
+ is a confession, not an infringement. It's *faux*.
132
+
133
+ ## License
134
+
135
+ MIT. Fauxbus contains no Globus code: it imitates publicly documented
136
+ wire behavior, grounded against the Apache-2.0 `globus-sdk` as a
137
+ test-time dependency only.
@@ -0,0 +1,91 @@
1
+ # Releasing fauxbus
2
+
3
+ The ritual, written down. The point of a checklist is that a release
4
+ cut under time pressure walks the same path as one cut with all the
5
+ time in the world.
6
+
7
+ ## 0. The tether posture, stated honestly
8
+
9
+ SPEC's conformance tether has two legs: Fauxbus on every CI run (live
10
+ since v0.2.5), and the real Globus service, env-gated, before each
11
+ release. The second leg needs a sacrificial tenancy, and **none
12
+ exists anywhere yet** — dev deliberately has no Globus tenancy (that
13
+ absence is why fauxbus is load-bearing for its first consumer).
14
+
15
+ So, until the first recording session is possible:
16
+
17
+ - Every release ships with the **PROVISIONAL inventory** in its release
18
+ notes. Generate it: `grep -rn PROVISIONAL src/`. The batch
19
+ membership response document is the standing #1.
20
+ - The moment a sacrificial tenancy exists, the recording session
21
+ becomes **release-blocking for every release after it becomes
22
+ possible**. A gate nobody can run is worse than an honest gap —
23
+ but a gate somebody could run and didn't is just a gap.
24
+
25
+ ## 1. Green, current, coordinated
26
+
27
+ - [ ] CI green on `main` — lint, conformance-enforced suite, and wheel
28
+ across 3.11/3.12/3.13, plus the image build-and-boot step.
29
+ - [ ] SPEC.md changelog carries an entry for this version.
30
+ - [ ] **The re-pin handshake.** Downstream consumers may pin fauxbus
31
+ in more than one place (the first consumer pins it twice: once
32
+ as an application dependency, once in its deploy stack). Hand
33
+ consumers the new tag so every pin moves **in one commit** —
34
+ never let pins drift, because a half-repinned consumer tests two
35
+ different fakes and believes it tested one.
36
+
37
+ ## 2. Version and tag
38
+
39
+ - [ ] `src/fauxbus/__init__.py`: drop the `.dev0` from `__version__`
40
+ (e.g. `0.1.0.dev0` → `0.1.0`). **That one line is the whole
41
+ bump** — pyproject reads it at build time. It did not use to be:
42
+ the version lived in two files, this checklist named only one of
43
+ them, and v0.1.0 and v0.1.1 both shipped reporting `0.1.0.dev0`
44
+ to `--version`, the banner, and `GET /_fauxbus/`. A checklist
45
+ that names one of two sources of truth is a checklist that walks
46
+ you confidently past the bug.
47
+ - [ ] Confirm the artifact agrees before you tag: `python -m build`
48
+ then check `dist/` is named for the version you meant. (CI's
49
+ tagged pipeline enforces this too, but after the tag exists.)
50
+ - [ ] Commit `Release vX.Y.Z`, tag `vX.Y.Z`, push commit and tag.
51
+ - [ ] Immediately follow with a bump to the next `.dev0` so `main` is
52
+ never ambiguous about whether it is a release.
53
+
54
+ ## 3. Artifacts
55
+
56
+ - The wheel: CI builds it on every push; the tagged pipeline's wheel is
57
+ the release artifact.
58
+ - The image: built from the tag via the shipped `Containerfile`. It
59
+ carries its own CycloneDX SBOM at `/usr/share/fauxbus/sbom.json`,
60
+ generated at image build from a clean install of the wheel and
61
+ asserted by CI — for a zero-dependency package, the SBOM is the
62
+ receipt for the packaging claim. A bare wheel install can mint the
63
+ same receipt: `cyclonedx-py environment <venv-python>`.
64
+ - Publication: **PyPI — decided 2026-08-05, not yet armed.** The name
65
+ is free and the v0.1.0 artifacts pass `twine check`. The sequence,
66
+ in order, none of it improvised at tag time:
67
+ 1. **First upload is manual**, from a clean tag build:
68
+ `git archive vX.Y.Z | tar -x -C /tmp/rel`, then `python -m build`
69
+ and `twine upload dist/*` from there. Manual because the first
70
+ upload is what creates the PyPI project a token can be scoped to.
71
+ 2. **Scope a token** to the project; store it as the Woodpecker
72
+ secret `pypi_token`.
73
+ 3. **Restore the parked `publish-pypi` step** at the bottom of
74
+ `.woodpecker.yml` (restore conditions annotated there). Tags
75
+ publish automatically from then on, with a guard that dies if the
76
+ tag's version disagrees with the built artifacts.
77
+ 4. **Then, and only then, flip the docs**: README's install line
78
+ becomes the real `pipx install fauxbus`, and the blog draft's
79
+ "not on PyPI yet" caveat goes. Docs never promise a package
80
+ that isn't live.
81
+
82
+ Prerequisite for all of it: public homepage/issues URLs in
83
+ pyproject (the mirror decision). **Satisfied 2026-08-07** — they
84
+ point at `github.com/atmarx/fauxbus`, which is public and has issues
85
+ enabled. A container registry remains a decision not yet taken.
86
+
87
+ ## 4. Tell the people
88
+
89
+ - Announce the release where consumers actually look, with the
90
+ PROVISIONAL inventory and the re-pin pointer. Consumers learn about
91
+ releases from the announcement, not from watching the repo.