Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ config :logger, :default_formatter,
config :phoenix, :json_library, Jason

# A BasicHTTP location's token is a bearer capability, so a params log
# names it filtered, beside Phoenix's own default.
# names it filtered, beside Phoenix's own default. Phoenix filters every
# param whose key contains a listed string, so "token" also redacts any
# other token-named param in those logs, the CSRF token (`_csrf_token`)
# among them.
config :phoenix, :filter_parameters, ["password", "token"]

# OpenTelemetry. `opentelemetry_statifier` brings only the API, so the SDK's
Expand Down
51 changes: 39 additions & 12 deletions docs/guides/basichttp-front.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,39 @@ which drives the whole of it through the controller.
Anyone who holds a location can post events to that execution, and the
router authenticates nothing beyond possession of it (ruled by the
operator, 2026-09-30). The hold hands its location to the one desk its
request names and to nobody else. No request line or dispatch log carries
it: the endpoint's `Plug.Telemetry` logs no request line under
`/basichttp` (`StatifierExamplesWeb.Endpoint.log_level/1`), the route is
`log: false`, and `:filter_parameters` names `token`. Ecto's query log at
`:debug` prints bound parameters, and the router binds the token to look
a location up and to store it, so a host keeps `:debug` out of
production; at `:info`, the production level here, no query is logged.
A host serves
the base URL over TLS and rotates a location that may have leaked with
`StatifierRouter.BasicHTTP.rotate_location/2`, after which the old one
answers 404.
request names. No request line or dispatch log carries it: the endpoint's
`Plug.Telemetry` logs no request line under `/basichttp`
(`StatifierExamplesWeb.Endpoint.log_level/1`), the route is
`log: false`, and `:filter_parameters` names `token`.

The location is also stored, and whoever can read where it is stored
holds the capability as surely as the desk does. The router's location
table holds the token, which the front looks the location up by. The
location is part of the execution's persisted state: `_ioprocessors`
names it, and the execution's `position_blob` carries it in the clear,
because `StatifierExamples.Persistence` stores the blob as `:binary`.
And the `StatifierExamples.HoldDesk.DeskPost` job that carries the POST
holds it in its arguments, in a job row this app never prunes (see the
end of "The outbound send runs in the executor").

A host keeps `:debug` out of production; at `:info`, the production level
here, no query is logged. `statifier_router` 0.10.0 runs its own three
statements that bind the token with Ecto's `log: false`, so the router's
query log no longer prints it, but other statements still bind it.
`statifier_persistence` writes the position on the create and on every
step, and Ecto's `:debug` query log prints those writes with the blob cut
short by its inspect limit: the token is unreadable in the line, not
absent from its parameters. Ecto's query telemetry event carries every
statement's bound parameters whatever the `log` option says, so a handler
on it receives the position writes and the desk post job's insert, whose
own log line Oban suppresses. The router's
`docs/adr/0002-addressing.md`, the Note of 2026-10-02 on the location
token and the query log, records the router's half of this and the
persisted state.

A host serves the base URL over TLS and rotates a location that may have
leaked with `StatifierRouter.BasicHTTP.rotate_location/2`, after which
the old one answers 404.

## The pins

Expand Down Expand Up @@ -135,7 +157,12 @@ database.

The job's arguments carry the planned POST as it was planned, body
included, so the `reply_to` location the body hands the desk is written to
the jobs table with it, and stays there until the host prunes the job.
the jobs table with it. This app configures no Oban pruner, and Oban
prunes nothing unless one is configured, so the row and the location in
it stay, after the job completes or is cancelled, until a host deletes
them; anyone who can read the jobs table holds the capability (see "A
location is a bearer capability" above). A host that prunes sets Oban's
`:pruner`.

## The front

Expand Down
29 changes: 26 additions & 3 deletions lib/statifier_examples/hold_desk.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,32 @@ defmodule StatifierExamples.HoldDesk do
**A location is a bearer capability** (ruled by the operator,
2026-09-30): anyone who holds it can post events to that execution, and
the router authenticates nothing beyond possession of it. This module
hands the location to the desk the hold request names and to nobody
else, and never logs it; `StatifierExamplesWeb.BasicHTTPController`
says what keeps it out of the request log.
hands the location to the one desk the hold request names and writes
no log line of its own that carries it;
`StatifierExamplesWeb.BasicHTTPController` says what keeps it out of
the request log. Whoever can read this app's database holds the
capability too: the router's location table holds the token; the
location is part of the execution's persisted state, in
`_ioprocessors` inside the execution's `position_blob`, in the clear
(`StatifierExamples.Persistence` stores the blob as `:binary`); and the
arguments of the `StatifierExamples.HoldDesk.DeskPost` job that carries
the POST hold it, in a job row this app never prunes.

**The `:debug` limit.** `statifier_router` 0.10.0 runs its own three
statements that bind the token - the front's lookup, the location
insert at create and the rotation upsert - with Ecto's `log: false`,
so the router's query log no longer prints it. The token still reaches
a `:debug` repo through statements that are not the router's.
`statifier_persistence` binds the execution's position on the create
and on every step, and Ecto's query log prints those writes with the
blob cut short by its inspect limit, which makes the token unreadable
in the line but not absent from the parameters. And Ecto's query
telemetry event carries every statement's bound parameters whatever
the `log` option says, so a handler on it receives the position writes
and the desk post job's insert, whose own log line Oban suppresses. So
this app keeps `:debug` out of production, as the router's ADR-0002
advises in its Note on the location token and the query log, and
rotates a location that may have leaked.

`config/0` is a router configuration of its own, separate from
`StatifierExamples.RoutedWorkflow.config/0`: only this configuration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ defmodule StatifierExamplesWeb.BasicHTTPController do
nothing else. No request line or dispatch log carries it:
`StatifierExamplesWeb.Endpoint.log_level/1` skips the request line for
`/basichttp`, the route is `log: false`, and `:filter_parameters` names
`token`. At `:debug` Ecto's query log prints bound parameters, and the
router's location lookup binds the token, so a host keeps `:debug` out
of production.
`token`. The router's own lookup no longer prints the token at `:debug`
(`statifier_router` 0.10.0 runs it with Ecto's `log: false`);
`StatifierExamples.HoldDesk` says what still carries the token at that
level, and why a host keeps `:debug` out of production.
"""

use StatifierExamplesWeb, :controller
Expand Down
Loading