5 Commits
Author SHA1 Message Date
mrambossekandClaude Opus 5 8118e213ae server: upstream trains, observed TTL/DSCP/ECN, rate limits, action ids
Types 0x03/0x04/0x05 land with a bounded columnar train buffer (head kept,
truncation declared) and grant-free multi-part reports - a report row is
smaller than the packet it answers, so $3.4 holds without a grant. The
read loop now collects TTL/TOS cmsgs on Linux, replacing the 0xFF stubs in
the observation block with what the kernel saw; downtrain gained a dscp
parameter, so DSCP survival is measurable in both directions.

Rate limiting ($2.5) exists now: per-credential AND per-source buckets,
429 on the control plane, silent drop on the data plane after the HMAC
gate and before the replay window. UDP ceilings default above the largest
legitimate run - a limit that clips a real measurement produces a
confidently wrong number.

Every granted packet carries its action_id at payload[8:16]; overlapping
actions were unattributable before. Canary DNS logs now honor the stated
24h privacy default. /admin/enroll-tokens answers the spec's JSON shape.
protocol_version 1.0.1 (additive).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 13:04:54 +02:00
mrambossekandClaude Opus 5 082a2314ef protocol: enrollment links carry the public name, not the endpoint
Sharing port 443 between the admin UI and the control plane forces two
hostnames — one port and one name is one certificate, and the two need
different ones. That difference had been leaking into every enrollment
link, so an operator handed out fmr-1.echo-lot.app when the thing they
and their users know is fmr.echo-lot.app.

The link now carries the public name and the app asks GET /v1/discover
where to actually connect. The endpoint is plumbing: it exists to select
a certificate, and nobody needs to see it.

Discovery hands out an address and never a pin. The pin stays in the
link. Fetching it over an ordinary TLS connection would make pinning
worth exactly what the certificate authorities are worth, and pinning is
there to survive one the operator does not control — a root injected by
corporate device management, say, which is unremarkable on the networks
this tool gets pointed at. With the pin pre-shared, an intercepted
discovery can only send a device somewhere the pin will not match: an
outage, not a compromise.

Optional on both sides. A server that does not answer, or a link that
already names the control endpoint, works unchanged — enrollment must not
start failing because a lookup did.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 00:13:27 +02:00
mrambossekandClaude Fable 5 199807a8c9 docs: enrollment link encoding rules in the spec, session log in build-status
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 12:11:37 +02:00
mrambossekandClaude Fable 5 0c5b021b63 compat: SemVer version windows between app and server
server-release / image (push) Successful in 14s
server-test / test (push) Successful in 30s
server-release / release (push) Successful in 30s
Both sides now declare what they will talk to, and enforce it. Two axes kept
deliberately separate, because conflating them is the trap:

  protocol_version  — CAN these builds talk. The correctness axis. Below 1.0.0
                      the minor is the breaking axis, per SemVer §4.
  release window    — MAY they, per policy. [min, max), advertised in the
                      profile, overridable by the operator.

The server refuses out-of-window apps with 426 and a body naming both versions
and the accepted range; the app checks the profile in both directions before a
run rather than discovering mid-measurement that it will be refused.

Three rules that shape the rest:

  - GET /v1/profile is never gated. It is where a refused client learns which
    version it needs; gating it leaves the user with a network error instead of
    an answer, which is precisely the confusion this exists to remove.
  - An unparseable or absent version is "unknown", and is allowed. Development
    builds report "dev", and a client too old to send the header cannot be
    identified anyway.
  - Bounds sit at breaking boundaries, not at releases, so shipping a patch
    never requires editing a range. The app's server minimum is 0.4.2 for a
    stated reason: earlier multi-homed servers mis-addressed granted sends and
    the client measured 100% downstream loss that never happened.

The app's versionCode is now derived from its SemVer instead of being a second
number someone has to remember to bump.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 11:36:34 +02:00
mrambossekandClaude Opus 5 e3840f54fe Initial commit: capability prober + design docs
Monorepo root for Echolot. Contains the no-root capability prober
(Kotlin/Compose, app.echo_lot.prober) and the four design docs that act
as the contract for the production app and the Go server.

LICENSE is deliberately absent — still undecided, see docs/build-status.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 08:54:28 +02:00