Correlated Isolation (Spaces)¶
A space partitions one imposter's stubs and state by a correlation id (the flow id), so parallel test runs sharing a port don't see each other's stubs, scenario state, or recorded requests.
How a request's flow id is resolved¶
_rift.flowState.flowIdSource decides which flow (space) a request belongs to:
flowIdSource |
Resolution |
|---|---|
"imposter_port" (default) |
The imposter port — one shared space. |
"header:X-Mock-Space" |
The value of that request header (case-insensitive); falls back to the port if the header is absent. |
A stub's optional space field scopes it to one flow id. Stubs without a space are global and
match any caller. Space-scoped stubs are considered only when the request's resolved flow id equals
their space.
Example — one port, isolated tenants¶
{
"port": 4510,
"protocol": "http",
"recordRequests": true,
"_rift": {
"flowState": {
"ttlSeconds": 300,
"flowIdSource": "header:X-Mock-Space"
}
},
"stubs": [
{
"space": "alice",
"predicates": [{ "equals": { "path": "/data" } }],
"responses": [{ "is": { "statusCode": 200, "body": { "owner": "alice" } } }]
},
{
"space": "bob",
"predicates": [{ "equals": { "path": "/data" } }],
"responses": [{ "is": { "statusCode": 200, "body": { "owner": "bob" } } }]
},
{
"predicates": [{ "equals": { "path": "/health" } }],
"responses": [{ "is": { "statusCode": 200, "body": "OK" } }]
}
]
}
curl -H 'X-Mock-Space: alice' http://localhost:4510/data # {"owner":"alice"}
curl -H 'X-Mock-Space: bob' http://localhost:4510/data # {"owner":"bob"}
curl http://localhost:4510/health # OK (global stub)
Recorded requests, scoped to a space¶
With recordRequests: true, GET /imposters/{port}/savedRequests accepts a match=flow_id=<value>
query parameter (see the API Reference) to read or clear only
one space's requests, leaving other spaces and the imposter itself untouched:
# Read only alice's recorded requests
curl 'http://localhost:2525/imposters/4510/savedRequests?match=flow_id=alice'
# Clear only alice's recorded requests (bob's and the port's own are unaffected)
curl -X DELETE 'http://localhost:2525/imposters/4510/savedRequests?match=flow_id=alice'
Managing spaces at runtime¶
Instead of declaring space inline, you can add stubs to a space through the admin API and tear the
whole space down in one call (its scoped stubs, recorded requests, and scenario state):
curl -X POST http://localhost:2525/imposters/4510/spaces/alice/stubs \
-d '{"predicates":[{"equals":{"path":"/data"}}],"responses":[{"is":{"statusCode":200,"body":"scoped"}}]}'
curl http://localhost:2525/imposters/4510/spaces/alice # inspect the space
curl -X DELETE http://localhost:2525/imposters/4510/spaces/alice # teardown
The space route takes the bare stub, not the {"stub": …} envelope POST /imposters/{port}/stubs
uses; a body with none of the stub fields is refused with 400 (see the
API reference).
Spaces are addressed by a known flow id — there is no bare GET /imposters/{port}/spaces route to
list or discover which spaces currently exist under an imposter. If you need an inventory of active
flow ids, track them on the caller side (or derive them from recorded requests / stub space
fields); the admin API only ever answers "what does this flow id look like."
Spaces build on the same store as Flow State and Scenarios, which are likewise partitioned by flow id.
Embedding over the C-ABI¶
Everything above is also reachable with zero loopback HTTP from an embedded Rift: the FFI exposes
rift_space_add_stub, rift_space_list_stubs, rift_space_delete, and rift_space_recorded,
each mirroring the corresponding admin-HTTP handler exactly. See
FFI (C-ABI) for signatures and ownership rules.