441 lines
15 KiB
Markdown
441 lines
15 KiB
Markdown
# Operator trust tooling — design discussion
|
|
|
|
**Status:** Design ratified. Items 1 + 2 ✅ SHIPPED. Item 3
|
|
(orderbook comparison view), Items 4 + 5 (advisory op +
|
|
governance process) remain open.
|
|
|
|
- **Item 1** — `verify.json` generated at build time by
|
|
`scripts/build-verify-json.mjs` (repo root), served as a
|
|
static asset. Captures version + git commit + build
|
|
timestamp + operator tag + hash manifest.
|
|
- **Item 2** — `/about-this-instance` route at
|
|
`apps/web/src/routes/[lang]/about-this-instance/+page.svelte`
|
|
visualizes the verify.json for users. Does not yet
|
|
cross-check against the on-chain release-op
|
|
(intentionally — the cross-check is followup work, this
|
|
page just makes the claim legible).
|
|
- **Item 3** — orderbook comparison view: not shipped.
|
|
- **Item 4** — `morphit_operator_advisory_v1` op: not
|
|
shipped, blocked on the OP-TRUST Q1-Q5 governance
|
|
discussion.
|
|
- **Item 5** — community advisory governance: blocked on
|
|
Q5 maintainer/Agorise decision.
|
|
|
|
**Date:** 2026-04-21 (design); shipped iteratively.
|
|
**Interacts with:** ADR-0013 (operator incentives), ADR-0011
|
|
(dynamic fee model, operator payout path).
|
|
|
|
---
|
|
|
|
## Why this document exists
|
|
|
|
The revisit list carried "operator-shutdown tooling" for a
|
|
while. After writing a first draft, the framing itself
|
|
turned out to be wrong. We can't shut down operators. The
|
|
architecture is specifically designed so that nobody can.
|
|
This document replaces that framing with an honest one:
|
|
**operator trust tooling**. We're not stopping anyone. We're
|
|
helping users see which operators deserve their trust.
|
|
|
|
Morphit's architecture is intentionally federated: anyone
|
|
can stand up an instance (SvelteKit frontend + indexer +
|
|
relay) and serve users. The indexer reads the Blurt chain as
|
|
its source of truth; the frontend renders from the
|
|
indexer's API; the relay broadcasts ops on users' behalf.
|
|
Users choose which operator's frontend they visit.
|
|
|
|
This design is censorship-resistant by construction. It's
|
|
also **permanent**: once the software exists and the chain
|
|
exists, operators exist, and nothing we do can change that.
|
|
If an operator acts in bad faith, we cannot technically or
|
|
legally remove them. We can only inform the users.
|
|
|
|
---
|
|
|
|
## What "stopping" a bad operator actually looks like
|
|
|
|
It's worth being explicit, because the intuition from
|
|
centralized services is wrong.
|
|
|
|
**What we cannot do:**
|
|
|
|
1. **We cannot make their server stop.** We have no
|
|
administrative access. They pay for their own
|
|
hosting.
|
|
2. **We cannot block users from reaching them.** There is
|
|
no central DNS, gateway, or CDN that we operate.
|
|
3. **We cannot stop them from reading the Blurt chain.**
|
|
It's a public blockchain.
|
|
4. **We cannot stop them from collecting operator fees.**
|
|
Once ADR-0013 ships, every operator earns a fee share
|
|
via the same protocol rules. If users post orders
|
|
through their frontend, those fees flow to them
|
|
regardless of our opinion of them.
|
|
5. **We cannot legally compel them.** They're anonymous.
|
|
No KYC, no jurisdiction, no company, no address.
|
|
|
|
**What we can do:**
|
|
|
|
1. **Warn users** via channels we control: `morphit.io`'s
|
|
homepage, Matrix, the `@morphit` Blurt account, the
|
|
signed `morphit_release_v1` op on the chain.
|
|
2. **Publish verification tools** so users can check an
|
|
instance themselves — e.g., the signed bundle hash
|
|
from the release-op lets any user detect an instance
|
|
serving a tampered build.
|
|
3. **Compete.** Build a better instance than the bad
|
|
actor's. Users who care about quality pick the better
|
|
instance.
|
|
|
|
The honest consequence: **a better-looking operator with
|
|
better uptime and better UX should earn more than
|
|
morphit.io**. That's correct. That's the market working.
|
|
The way we protect morphit.io's relevance is by making it
|
|
actually better, not by suppressing competitors.
|
|
|
|
This framing matters because every "shutdown" tool we
|
|
could imagine either (a) doesn't actually shut anything
|
|
down, or (b) creates a central point of control that a
|
|
government could pressure. We build (a)-type tools.
|
|
We refuse to build (b)-type tools.
|
|
|
|
---
|
|
|
|
## Threat model — three tiers of bad operator
|
|
|
|
"Bad operator" covers three categorically different
|
|
behaviors and the appropriate response is different for
|
|
each. Muddling them leads to either tyranny (treating the
|
|
selfish operator as if they were lying) or uselessness
|
|
(treating the lying operator as if they were selfish).
|
|
|
|
### Tier 1 — Selfish operator
|
|
|
|
**Behavior:** Runs a technically correct node but doesn't
|
|
participate in the ecosystem as intended. Examples:
|
|
|
|
- Strips the `operator_tag` field from orders they
|
|
broadcast, so fee shares flow to `@morphit-fees`
|
|
rather than to the operator who actually served the
|
|
user (ADR-0013 interaction).
|
|
- Claims operator status without paying the registration
|
|
fee.
|
|
- Promises 99% uptime and delivers 50%.
|
|
|
|
**User impact:** Low. Users see correct data; the only
|
|
damage is to operator economics. The user doesn't know
|
|
or care.
|
|
|
|
**Appropriate response:** Economic, not policing. The
|
|
market handles Tier 1 without any tooling from us.
|
|
|
|
### Tier 2 — Censoring operator
|
|
|
|
**Behavior:** Silently filters what users see. The
|
|
operator doesn't fabricate anything — they just omit.
|
|
Examples:
|
|
|
|
- Hides orders from accounts they don't like.
|
|
- Hides orders in a particular fiat currency.
|
|
- Hides the cheapest listings to steer traffic toward
|
|
their own.
|
|
|
|
**User impact:** Medium. The user gets **some** of the
|
|
truth but not all of it.
|
|
|
|
**Appropriate response:** Verifiability. Make the
|
|
omission detectable so a user (or a watchdog) can compare
|
|
one operator's view to another's and see the discrepancy.
|
|
This is not enforcement — censoring operators keep running
|
|
— but their filtering becomes visible.
|
|
|
|
### Tier 3 — Lying operator
|
|
|
|
**Behavior:** Actively fabricates data. The operator's
|
|
frontend returns orders that don't exist on-chain, or
|
|
mis-prices orders that do exist, or ships a phishing fork
|
|
of the UI that exfiltrates seed phrases.
|
|
|
|
**User impact:** High. Users lose funds, lose keys, or
|
|
trade based on false data.
|
|
|
|
**Appropriate response:** Truth-amplification. Warn users
|
|
away via every channel we control, and publish tools
|
|
users can run to verify an instance themselves. The
|
|
operator keeps running — to an increasingly empty
|
|
parking lot as users check the warnings.
|
|
|
|
---
|
|
|
|
## Design primitives we already have
|
|
|
|
Before adding anything, catalog what's there:
|
|
|
|
### Release-op trust anchor (ADR-0008, shipped)
|
|
|
|
The `morphit_release_v1` op is signed by `@morphit` with
|
|
the pinned posting pubkey. Its payload includes:
|
|
|
|
- `version` (semver)
|
|
- `hash_manifest` (asset hashes of the frontend bundle)
|
|
- `endpoints` (RPC endpoint rotation set)
|
|
- `signature` (optional additional signature)
|
|
|
|
The `hash_manifest` is the key primitive. Every legitimate
|
|
frontend instance should serve a bundle whose hash matches
|
|
what `@morphit`'s latest release-op published. A user
|
|
visiting a tampered fork can check the hash and see a
|
|
mismatch — in theory. Today this requires a tool that
|
|
doesn't exist yet (see item 1 below).
|
|
|
|
**Gap we need to close:** a user-facing "is this instance
|
|
legitimate" check that uses the release-op hash_manifest.
|
|
|
|
### `releases.invalid_reason` audit trail (shipped this session, Finding J fix)
|
|
|
|
Rejected release ops are now recorded with a specific
|
|
`invalid_reason`. If `@morphit`'s posting key is ever
|
|
compromised, the audit trail distinguishes:
|
|
|
|
- `signer_not_official_account` (phishing / impersonation;
|
|
no real key compromise)
|
|
- `signer_no_single_posting_key` (unusual chain auth
|
|
setup; not necessarily malicious)
|
|
- `pubkey_mismatch` (pinned value differs from what's
|
|
on-chain — signal to rotate keys immediately)
|
|
|
|
A key-compromise event would require a config update to
|
|
all operators: bump the pinned `officialPostingPubkey` to
|
|
a newly-rotated value. Currently there's no chain op that
|
|
signals "the @morphit posting key has been rotated" —
|
|
operators would coordinate out-of-band (Matrix, email).
|
|
|
|
**Gap we need to close:** a key-rotation signaling op or
|
|
a documented out-of-band procedure.
|
|
|
|
### Operator registration (ADR-0013, ✅ shipped 2026-05-02)
|
|
|
|
ADR-0013 was Accepted-and-implemented in early May. We now
|
|
have:
|
|
|
|
- a list of registered operators on-chain
|
|
- a registration fee they've paid
|
|
- a unique operator tag claimed by each
|
|
|
|
Registration is voluntary — an operator can run an
|
|
instance without registering. Registration is how they
|
|
get their fee share, not how they get permission to
|
|
operate.
|
|
|
|
**Gap closed:** ADR-0013 accepted-and-implemented 2026-05-02
|
|
(registration, tag claim, payout pipeline all in place).
|
|
|
|
---
|
|
|
|
## Proposed tooling (minimum viable)
|
|
|
|
### Short-term, no new chain ops (shippable now)
|
|
|
|
**1. Instance-verification endpoint (frontend).**
|
|
|
|
Every frontend instance exposes `/verify.json`:
|
|
|
|
- the current release-op version string
|
|
- the hash of the frontend bundle this instance is serving
|
|
- the git commit hash of the frontend build
|
|
- the operator's claimed tag (null for unregistered
|
|
instances)
|
|
|
|
A user or a watchdog fetches this and compares to the
|
|
release-op value on chain. Mismatches are visible without
|
|
trusting the instance's own claim about itself.
|
|
|
|
**Scope:** ~50 lines of static-build-time generation plus
|
|
one JSON endpoint. No chain changes.
|
|
|
|
**2. "About this instance" page.**
|
|
|
|
A `/about-this-instance` route on every frontend that
|
|
visually presents the `verify.json` data to the user:
|
|
|
|
> "You're on: `morphit.agorise.world`. This instance
|
|
> claims to be running Morphit v2.3.1. The release-op
|
|
> on chain says v2.3.1 hash is `abc123…`; this instance
|
|
> serves `abc123…` ✅"
|
|
|
|
When the hash matches, the check is green. When it
|
|
doesn't, the check is red and the page shows what the
|
|
expected hash is and links to `morphit.io` and
|
|
`morphit.agorise.world` as known-good alternatives.
|
|
|
|
**Scope:** ~200 lines of Svelte + i18n. No chain changes.
|
|
|
|
**3. Comparison view.**
|
|
|
|
The orderbook page gains a "compare with another
|
|
instance" toggle. User enters a second instance URL; the
|
|
page fetches both orderbooks and highlights anything one
|
|
has that the other doesn't.
|
|
|
|
**Scope:** ~400 lines of Svelte + a shared-schema
|
|
expectation that both instances serve the standard
|
|
`/v1/orderbook` API. No chain changes.
|
|
|
|
### Medium-term (needs ADR-0013 first)
|
|
|
|
**4. Signed operator advisory op.**
|
|
|
|
`morphit_operator_advisory_v1` signed by `@morphit`
|
|
(release-op trust anchor: signer must be official
|
|
account, posting pubkey must match pinned value). Payload:
|
|
|
|
```json
|
|
{
|
|
"operator_tag": "<tag or null for origin-based>",
|
|
"origin": "<hostname or null for tag-based>",
|
|
"advisory": "warn" | "do-not-use",
|
|
"reason": "short human-readable string",
|
|
"evidence_url": "optional link to proof"
|
|
}
|
|
```
|
|
|
|
Frontend instances read the latest advisory list and, if
|
|
the current origin or claimed tag is on it, show a
|
|
warning banner:
|
|
|
|
> "⚠️ This instance is on a community advisory list.
|
|
> Reason: `<reason>`. Consider visiting morphit.io or
|
|
> morphit.agorise.world instead."
|
|
|
|
**This is not a shutdown.** The instance keeps running.
|
|
The warning is informational. A lying operator could
|
|
even serve a frontend that hides the advisory banner —
|
|
but users can detect this via item 1's on-chain hash
|
|
check.
|
|
|
|
**Scope after ADR-0013:** ~100 lines of indexer handler,
|
|
a new `operator_advisories` table, the release-op schema
|
|
addition, ~50 lines of frontend check.
|
|
|
|
### Long-term (needs governance process)
|
|
|
|
**5. Community advisory maintenance.**
|
|
|
|
Someone has to decide what goes on the advisory list.
|
|
This is a governance question, not a technical one. The
|
|
very high bar for inclusion: demonstrated Tier 3
|
|
behavior (fabrication, phishing), not disagreement with
|
|
project direction. Requires a documented process with
|
|
evidence requirements and an appeal path.
|
|
|
|
The advisory list should NOT be a tool for suppressing
|
|
Tier 1 freeloaders or Tier 2 censors. Tier 1 is handled
|
|
by the market; Tier 2 is handled by item 3's
|
|
comparison view.
|
|
|
|
---
|
|
|
|
## What we explicitly don't want
|
|
|
|
**Anti-patterns to avoid:**
|
|
|
|
1. **Pre-emptive blocking of unregistered operators.** A
|
|
new operator should be able to stand up an instance
|
|
without asking permission. Requiring prior approval
|
|
destroys the federated property.
|
|
|
|
2. **Central domain-level filtering.** Morphit doesn't
|
|
run DNS for operator instances. Any "shutdown" via
|
|
DNS requires us to run DNS, which is centralization
|
|
we don't want.
|
|
|
|
3. **Irreversible operator advisories.** Every advisory
|
|
must be reversible by a follow-up op from
|
|
`@morphit` — otherwise a compromised key could
|
|
permanently destroy legitimate operators.
|
|
|
|
4. **Trust-on-first-use of arbitrary instance JSON.**
|
|
The `verify.json` endpoint MUST cross-check against
|
|
on-chain release-op data; relying on the instance's
|
|
own claim about itself defeats the point.
|
|
|
|
5. **Any tool framed as "shutdown."** We don't have
|
|
that power. Framing tools as if we did creates
|
|
expectations users will feel betrayed by when the
|
|
bad operator keeps running.
|
|
|
|
---
|
|
|
|
## Operator anonymity — design invariant
|
|
|
|
Operators are anonymous by default. This is a feature:
|
|
|
|
- **Registration requires only a Blurt account.** No
|
|
name, no email, no address. The operator picks a tag
|
|
and pays the registration fee. Done.
|
|
- **The `contact_url` field on operator profiles is
|
|
optional.** An operator can populate it with a Matrix
|
|
handle, a Nostr pubkey, an onion address, or nothing.
|
|
They choose what to expose.
|
|
- **Nothing in the public UI identifies operators
|
|
geographically or personally.** Operators are
|
|
pseudonymous. The `/operators` page shows the tag,
|
|
registration date, on-chain stats, and the
|
|
operator's own chosen contact link (if any). No
|
|
country flags, no city names, no real names.
|
|
- **This invariant also applies to morphit.io itself.**
|
|
The instance is identified as "operated by Agorise,"
|
|
which is a project-level pseudonym. No individual
|
|
person is named anywhere user-visible.
|
|
|
|
If a jurisdiction ever pressures a known person behind
|
|
an operator, that person hasn't been made known by us.
|
|
Operator anonymity is protection against legal coercion.
|
|
|
|
---
|
|
|
|
## Open questions (need maintainer / governance decisions)
|
|
|
|
**OP-TRUST Q1:** What evidence threshold justifies a
|
|
`morphit_operator_advisory_v1` op? Who reviews the
|
|
evidence? Is there an appeal path?
|
|
|
|
**OP-TRUST Q2:** Does the release-op gain an
|
|
`advisories` field now, or do we wait for the first
|
|
concrete incident to force the design?
|
|
|
|
**OP-TRUST Q3:** If `@morphit`'s posting key is
|
|
compromised, what's the recovery path? A hardcoded
|
|
secondary pubkey in `officialPostingPubkey` config
|
|
that operators can roll to? A published recovery
|
|
procedure that assumes out-of-band communication?
|
|
|
|
**OP-TRUST Q4:** Should the short-term tooling (items
|
|
1, 2, 3) ship even if ADR-0013 is still pending? I'd
|
|
argue **yes** — they're independent of the operator-
|
|
incentive design and help Tier 2/3 detection
|
|
regardless.
|
|
|
|
**OP-TRUST Q5:** Who maintains the advisory list?
|
|
Project maintainer? Agorise? A multi-signature
|
|
committee? Once this is more than one person, it's a
|
|
governance structure that needs its own
|
|
consideration.
|
|
|
|
---
|
|
|
|
## Recommended next step
|
|
|
|
Before writing an ADR: **build items 1 and 2**. Both
|
|
are independent of every open question above, both
|
|
help Tier 2/3 detection immediately, and both create
|
|
concrete artifacts that users and operators can
|
|
reference when OP-TRUST Q1-Q5 get discussed.
|
|
|
|
Item 3 is independent but bigger UI work — probably a
|
|
second pass after items 1 and 2 land and we see how
|
|
users engage with them.
|
|
|
|
Items 4 and 5 need ADR-level decisions first. Item 5
|
|
specifically needs a governance process that doesn't
|
|
exist yet, and that's the bigger blocker.
|