Freshness diagnostics API, UI trust notes, mission control/stats updates, and deploy scripts.
Made-with: Cursor
This commit is contained in:
530
docs/api/EXPLORER_FRESHNESS_DIAGNOSTICS_CONTRACT.md
Normal file
530
docs/api/EXPLORER_FRESHNESS_DIAGNOSTICS_CONTRACT.md
Normal file
@@ -0,0 +1,530 @@
|
||||
# Explorer Freshness And Diagnostics Contract
|
||||
|
||||
This document defines the minimum public freshness and diagnostics payloads the SolaceScan frontend needs in order to present chain activity, transaction visibility, and snapshot posture without relying on frontend inference.
|
||||
|
||||
It is intended to close the remaining gap between:
|
||||
|
||||
- a frontend that now renders and explains state honestly, and
|
||||
- upstream APIs that still omit authoritative freshness metadata for several critical surfaces.
|
||||
|
||||
## Goal
|
||||
|
||||
The frontend should be able to answer these questions directly from public API fields:
|
||||
|
||||
1. Is the chain head current?
|
||||
2. When was the latest visible transaction indexed?
|
||||
3. What is the latest non-empty block?
|
||||
4. Is the homepage using a live feed, a snapshot, or mixed evidence?
|
||||
5. Which subsystem is stale: RPC, indexing, relay monitoring, or stats?
|
||||
6. Which values are reported directly vs inferred vs unavailable?
|
||||
|
||||
The frontend should not have to infer these from a combination of:
|
||||
|
||||
- `/api/v2/stats`
|
||||
- `/api/v2/main-page/blocks`
|
||||
- `/api/v2/main-page/transactions`
|
||||
- `/explorer-api/v1/track1/bridge/status`
|
||||
|
||||
unless there is no backend alternative.
|
||||
|
||||
## Design Principles
|
||||
|
||||
- Prefer explicit freshness fields over derived heuristics.
|
||||
- Separate chain freshness from indexed-transaction freshness.
|
||||
- Distinguish reported facts from inferred or partial facts.
|
||||
- Make incompleteness first-class.
|
||||
- Keep the contract calm and operational, not alarmist.
|
||||
|
||||
## Proposed Public Endpoints
|
||||
|
||||
Two additions are recommended.
|
||||
|
||||
### 1. Extend `GET /api/v2/stats`
|
||||
|
||||
This endpoint already feeds the homepage summary cards. It should become the authoritative public summary for chain freshness and indexed activity freshness.
|
||||
|
||||
### 2. Extend `GET /explorer-api/v1/track1/bridge/status`
|
||||
|
||||
This endpoint already powers Mission Control. It should expose snapshot/feed posture and subsystem freshness more directly.
|
||||
|
||||
If backend implementation prefers separation, these fields may instead be exposed from a new endpoint:
|
||||
|
||||
`GET /explorer-api/v1/track1/observability/freshness`
|
||||
|
||||
The frontend does not require a separate endpoint as long as the fields below are available from a stable public contract.
|
||||
|
||||
## Required Additions To `/api/v2/stats`
|
||||
|
||||
### Current gaps
|
||||
|
||||
The current `stats` payload gives totals, but it does not reliably expose:
|
||||
|
||||
- latest indexed transaction timestamp
|
||||
- latest non-empty block
|
||||
- authoritative utilization freshness
|
||||
- confidence/completeness metadata
|
||||
|
||||
### Required fields
|
||||
|
||||
```json
|
||||
{
|
||||
"total_blocks": 3873353,
|
||||
"total_transactions": 52391,
|
||||
"total_addresses": 10294,
|
||||
"latest_block": 3873353,
|
||||
"average_block_time": 2000,
|
||||
"gas_prices": {
|
||||
"slow": 0.02,
|
||||
"average": 0.03,
|
||||
"fast": 0.05
|
||||
},
|
||||
"network_utilization_percentage": 0,
|
||||
"transactions_today": 18,
|
||||
|
||||
"freshness": {
|
||||
"chain_head": {
|
||||
"block_number": 3873353,
|
||||
"timestamp": "2026-04-10T21:42:15Z",
|
||||
"age_seconds": 1,
|
||||
"source": "reported",
|
||||
"confidence": "high"
|
||||
},
|
||||
"latest_indexed_transaction": {
|
||||
"hash": "0x...",
|
||||
"block_number": 3858013,
|
||||
"timestamp": "2026-04-10T12:31:05Z",
|
||||
"age_seconds": 33070,
|
||||
"source": "reported",
|
||||
"confidence": "high"
|
||||
},
|
||||
"latest_non_empty_block": {
|
||||
"block_number": 3858013,
|
||||
"timestamp": "2026-04-10T12:31:05Z",
|
||||
"age_seconds": 33070,
|
||||
"distance_from_head": 15340,
|
||||
"source": "reported",
|
||||
"confidence": "high"
|
||||
},
|
||||
"latest_indexed_block": {
|
||||
"block_number": 3873353,
|
||||
"timestamp": "2026-04-10T21:42:15Z",
|
||||
"age_seconds": 1,
|
||||
"source": "reported",
|
||||
"confidence": "high"
|
||||
}
|
||||
},
|
||||
|
||||
"completeness": {
|
||||
"transactions_feed": "complete",
|
||||
"blocks_feed": "complete",
|
||||
"gas_metrics": "partial",
|
||||
"utilization_metrics": "partial"
|
||||
},
|
||||
|
||||
"sampling": {
|
||||
"stats_generated_at": "2026-04-10T21:42:16Z",
|
||||
"stats_window_seconds": 300,
|
||||
"rpc_probe_at": "2026-04-10T21:42:15Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Field Semantics
|
||||
|
||||
### `freshness.chain_head`
|
||||
|
||||
The latest chain head known from the authoritative public RPC or canonical head source.
|
||||
|
||||
This is the answer to:
|
||||
|
||||
- "Is the chain alive?"
|
||||
- "Is head visibility current?"
|
||||
|
||||
### `freshness.latest_indexed_transaction`
|
||||
|
||||
The most recent transaction currently visible in the public indexed transaction feed.
|
||||
|
||||
This is the answer to:
|
||||
|
||||
- "How recent is the latest visible transaction?"
|
||||
|
||||
### `freshness.latest_non_empty_block`
|
||||
|
||||
The most recent indexed block containing one or more transactions.
|
||||
|
||||
This is the answer to:
|
||||
|
||||
- "Are head blocks empty because the chain is quiet?"
|
||||
- "What is the last block with visible activity?"
|
||||
|
||||
### `freshness.latest_indexed_block`
|
||||
|
||||
The latest block successfully indexed into the explorer's public block dataset.
|
||||
|
||||
This disambiguates:
|
||||
|
||||
- current chain head
|
||||
- current explorer indexed head
|
||||
|
||||
### `completeness.*`
|
||||
|
||||
Simple public-facing availability states for each summary subsystem:
|
||||
|
||||
- `complete`
|
||||
- `partial`
|
||||
- `stale`
|
||||
- `unavailable`
|
||||
|
||||
These should not be interpreted as outage severity; they describe data completeness only.
|
||||
|
||||
### `sampling.*`
|
||||
|
||||
Metadata for when the summary itself was generated and what freshness window it represents.
|
||||
|
||||
## Required Additions To Mission Control Payload
|
||||
|
||||
Mission Control currently provides useful relay detail, but the homepage still infers snapshot scope and partial feed posture from surrounding evidence.
|
||||
|
||||
### Required fields
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"status": "degraded",
|
||||
"checked_at": "2026-04-10T21:42:16Z",
|
||||
"mode": {
|
||||
"kind": "snapshot",
|
||||
"updated_at": "2026-04-10T21:42:16Z",
|
||||
"age_seconds": 1,
|
||||
"reason": "live_homepage_stream_not_attached",
|
||||
"scope": "relay_monitoring_homepage_card_only",
|
||||
"source": "reported",
|
||||
"confidence": "high"
|
||||
},
|
||||
"subsystems": {
|
||||
"rpc_head": {
|
||||
"status": "operational",
|
||||
"updated_at": "2026-04-10T21:42:15Z",
|
||||
"age_seconds": 1,
|
||||
"source": "reported",
|
||||
"confidence": "high"
|
||||
},
|
||||
"tx_index": {
|
||||
"status": "stale",
|
||||
"updated_at": "2026-04-10T12:31:05Z",
|
||||
"age_seconds": 33070,
|
||||
"source": "reported",
|
||||
"confidence": "high"
|
||||
},
|
||||
"bridge_relay_monitoring": {
|
||||
"status": "degraded",
|
||||
"updated_at": "2026-04-10T21:42:16Z",
|
||||
"age_seconds": 1,
|
||||
"source": "reported",
|
||||
"confidence": "high"
|
||||
},
|
||||
"stats_summary": {
|
||||
"status": "partial",
|
||||
"updated_at": "2026-04-10T21:42:16Z",
|
||||
"age_seconds": 1,
|
||||
"source": "reported",
|
||||
"confidence": "medium"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Required Enumerations
|
||||
|
||||
These enums should be consistent across public surfaces.
|
||||
|
||||
### Activity interpretation
|
||||
|
||||
- `active`
|
||||
- `quiet`
|
||||
- `sparse_activity`
|
||||
- `fresh_head_stale_tx_visibility`
|
||||
- `limited_observability`
|
||||
|
||||
This value should be emitted only when the backend can support it directly. Otherwise the frontend may continue to derive it as a presentation layer.
|
||||
|
||||
### Data source confidence
|
||||
|
||||
- `high`
|
||||
- `medium`
|
||||
- `low`
|
||||
- `unknown`
|
||||
|
||||
### Data origin
|
||||
|
||||
- `reported`
|
||||
- `inferred`
|
||||
- `sampled`
|
||||
- `unavailable`
|
||||
|
||||
### Completeness
|
||||
|
||||
- `complete`
|
||||
- `partial`
|
||||
- `stale`
|
||||
- `unavailable`
|
||||
|
||||
## Example Payloads
|
||||
|
||||
These examples are intended to accelerate frontend/backend alignment by showing how the contract should represent common live states.
|
||||
|
||||
### Example A: Healthy Live State
|
||||
|
||||
```json
|
||||
{
|
||||
"freshness": {
|
||||
"chain_head": {
|
||||
"block_number": 3874000,
|
||||
"timestamp": "2026-04-10T22:10:14Z",
|
||||
"age_seconds": 1,
|
||||
"source": "reported",
|
||||
"confidence": "high"
|
||||
},
|
||||
"latest_indexed_block": {
|
||||
"block_number": 3874000,
|
||||
"timestamp": "2026-04-10T22:10:14Z",
|
||||
"age_seconds": 1,
|
||||
"source": "reported",
|
||||
"confidence": "high"
|
||||
},
|
||||
"latest_indexed_transaction": {
|
||||
"hash": "0x...",
|
||||
"block_number": 3873998,
|
||||
"timestamp": "2026-04-10T22:10:10Z",
|
||||
"age_seconds": 5,
|
||||
"source": "reported",
|
||||
"confidence": "high"
|
||||
},
|
||||
"latest_non_empty_block": {
|
||||
"block_number": 3873998,
|
||||
"timestamp": "2026-04-10T22:10:10Z",
|
||||
"age_seconds": 5,
|
||||
"distance_from_head": 2,
|
||||
"source": "reported",
|
||||
"confidence": "high"
|
||||
}
|
||||
},
|
||||
"completeness": {
|
||||
"transactions_feed": "complete",
|
||||
"blocks_feed": "complete",
|
||||
"gas_metrics": "complete",
|
||||
"utilization_metrics": "complete"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example B: Quiet Chain But Current
|
||||
|
||||
```json
|
||||
{
|
||||
"freshness": {
|
||||
"chain_head": {
|
||||
"block_number": 3875000,
|
||||
"timestamp": "2026-04-10T23:10:14Z",
|
||||
"age_seconds": 1,
|
||||
"source": "reported",
|
||||
"confidence": "high"
|
||||
},
|
||||
"latest_indexed_block": {
|
||||
"block_number": 3875000,
|
||||
"timestamp": "2026-04-10T23:10:14Z",
|
||||
"age_seconds": 1,
|
||||
"source": "reported",
|
||||
"confidence": "high"
|
||||
},
|
||||
"latest_indexed_transaction": {
|
||||
"hash": "0x...",
|
||||
"block_number": 3874902,
|
||||
"timestamp": "2026-04-10T23:01:42Z",
|
||||
"age_seconds": 512,
|
||||
"source": "reported",
|
||||
"confidence": "high"
|
||||
},
|
||||
"latest_non_empty_block": {
|
||||
"block_number": 3874902,
|
||||
"timestamp": "2026-04-10T23:01:42Z",
|
||||
"age_seconds": 512,
|
||||
"distance_from_head": 98,
|
||||
"source": "reported",
|
||||
"confidence": "high"
|
||||
}
|
||||
},
|
||||
"activity_interpretation": "quiet"
|
||||
}
|
||||
```
|
||||
|
||||
### Example C: Fresh Head, Stale Transaction Visibility
|
||||
|
||||
```json
|
||||
{
|
||||
"freshness": {
|
||||
"chain_head": {
|
||||
"block_number": 3876000,
|
||||
"timestamp": "2026-04-11T00:10:14Z",
|
||||
"age_seconds": 1,
|
||||
"source": "reported",
|
||||
"confidence": "high"
|
||||
},
|
||||
"latest_indexed_block": {
|
||||
"block_number": 3875999,
|
||||
"timestamp": "2026-04-11T00:10:12Z",
|
||||
"age_seconds": 3,
|
||||
"source": "reported",
|
||||
"confidence": "high"
|
||||
},
|
||||
"latest_indexed_transaction": {
|
||||
"hash": "0x...",
|
||||
"block_number": 3860660,
|
||||
"timestamp": "2026-04-10T15:02:10Z",
|
||||
"age_seconds": 32900,
|
||||
"source": "reported",
|
||||
"confidence": "high"
|
||||
},
|
||||
"latest_non_empty_block": {
|
||||
"block_number": 3860660,
|
||||
"timestamp": "2026-04-10T15:02:10Z",
|
||||
"age_seconds": 32900,
|
||||
"distance_from_head": 15340,
|
||||
"source": "reported",
|
||||
"confidence": "high"
|
||||
}
|
||||
},
|
||||
"activity_interpretation": "fresh_head_stale_tx_visibility",
|
||||
"completeness": {
|
||||
"transactions_feed": "stale",
|
||||
"blocks_feed": "complete",
|
||||
"gas_metrics": "partial",
|
||||
"utilization_metrics": "partial"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example D: Snapshot Mode State
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"status": "degraded",
|
||||
"checked_at": "2026-04-11T00:10:15Z",
|
||||
"mode": {
|
||||
"kind": "snapshot",
|
||||
"updated_at": "2026-04-11T00:10:15Z",
|
||||
"age_seconds": 1,
|
||||
"reason": "live_homepage_stream_not_attached",
|
||||
"scope": "relay_monitoring_homepage_card_only",
|
||||
"source": "reported",
|
||||
"confidence": "high"
|
||||
},
|
||||
"subsystems": {
|
||||
"rpc_head": {
|
||||
"status": "operational",
|
||||
"updated_at": "2026-04-11T00:10:14Z",
|
||||
"age_seconds": 1,
|
||||
"source": "reported",
|
||||
"confidence": "high"
|
||||
},
|
||||
"tx_index": {
|
||||
"status": "stale",
|
||||
"updated_at": "2026-04-10T15:02:10Z",
|
||||
"age_seconds": 32900,
|
||||
"source": "reported",
|
||||
"confidence": "high"
|
||||
},
|
||||
"bridge_relay_monitoring": {
|
||||
"status": "degraded",
|
||||
"updated_at": "2026-04-11T00:10:15Z",
|
||||
"age_seconds": 1,
|
||||
"source": "reported",
|
||||
"confidence": "high"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Frontend Usage Rules
|
||||
|
||||
Once the fields above exist, the frontend should follow these rules:
|
||||
|
||||
1. Use backend freshness fields directly where present.
|
||||
2. Stop deriving latest transaction age from the transactions page feed when `freshness.latest_indexed_transaction` is available.
|
||||
3. Stop deriving last non-empty block from recent block scanning when `freshness.latest_non_empty_block` is available.
|
||||
4. Use `mode.kind`, `mode.reason`, and `mode.scope` directly for homepage snapshot messaging.
|
||||
5. Use `source` and `confidence` badges only where they improve trust and do not clutter.
|
||||
|
||||
## Backward-Compatible Rollout Plan
|
||||
|
||||
### Phase A
|
||||
|
||||
Add fields without removing any current keys:
|
||||
|
||||
- extend `/api/v2/stats`
|
||||
- extend bridge status payload with `mode` and `subsystems`
|
||||
|
||||
### Phase B
|
||||
|
||||
Frontend prefers new fields when available and falls back to inference when absent.
|
||||
|
||||
### Phase C
|
||||
|
||||
Once fields are consistently present in production:
|
||||
|
||||
- reduce frontend inference paths
|
||||
- remove duplicate explanatory fallback logic where it is no longer needed
|
||||
|
||||
## Minimum Viable Backend Implementation
|
||||
|
||||
If full rollout is not possible immediately, the minimum high-leverage addition is:
|
||||
|
||||
### `/api/v2/stats`
|
||||
|
||||
- `freshness.chain_head`
|
||||
- `freshness.latest_indexed_transaction`
|
||||
- `freshness.latest_non_empty_block`
|
||||
- `sampling.stats_generated_at`
|
||||
|
||||
### `/explorer-api/v1/track1/bridge/status`
|
||||
|
||||
- `mode.kind`
|
||||
- `mode.updated_at`
|
||||
- `mode.reason`
|
||||
- `mode.scope`
|
||||
|
||||
That alone would materially reduce frontend ambiguity.
|
||||
|
||||
## Why This Contract Matters
|
||||
|
||||
The frontend now presents state honestly enough that the remaining ambiguity is no longer visual. It is contractual.
|
||||
|
||||
Without these fields, the UI must keep inferring:
|
||||
|
||||
- whether the chain is quiet or stale
|
||||
- whether the homepage is in snapshot mode because of relay posture or indexing posture
|
||||
- whether low activity is real or a visibility gap
|
||||
|
||||
With these fields, the product becomes:
|
||||
|
||||
- more trustworthy
|
||||
- easier to evaluate externally
|
||||
- less likely to be misread as broken
|
||||
|
||||
## Summary
|
||||
|
||||
The next backend milestone is not broad API expansion. It is a targeted public freshness contract.
|
||||
|
||||
The public explorer needs explicit answers for:
|
||||
|
||||
- current chain head
|
||||
- current indexed head
|
||||
- latest visible transaction
|
||||
- last non-empty block
|
||||
- snapshot/feed mode
|
||||
- subsystem freshness/completeness
|
||||
|
||||
That is the smallest backend addition with the highest frontend trust impact.
|
||||
278
docs/api/EXPLORER_FRESHNESS_IMPLEMENTATION_CHECKLIST.md
Normal file
278
docs/api/EXPLORER_FRESHNESS_IMPLEMENTATION_CHECKLIST.md
Normal file
@@ -0,0 +1,278 @@
|
||||
# Explorer Freshness Implementation Checklist
|
||||
|
||||
This checklist converts the freshness contract into a backend implementation plan against the current SolaceScan code paths:
|
||||
|
||||
- [stats.go](/home/intlc/projects/proxmox/explorer-monorepo/backend/api/rest/stats.go)
|
||||
- [mission_control.go](/home/intlc/projects/proxmox/explorer-monorepo/backend/api/rest/mission_control.go)
|
||||
|
||||
Use this document as the handoff from frontend trust requirements to backend delivery.
|
||||
|
||||
See also:
|
||||
|
||||
- [EXPLORER_FRESHNESS_DIAGNOSTICS_CONTRACT.md](/home/intlc/projects/proxmox/explorer-monorepo/docs/api/EXPLORER_FRESHNESS_DIAGNOSTICS_CONTRACT.md)
|
||||
- [track-api-contracts.md](/home/intlc/projects/proxmox/explorer-monorepo/docs/api/track-api-contracts.md)
|
||||
|
||||
## Scope
|
||||
|
||||
This checklist covers four buckets:
|
||||
|
||||
1. field ownership and source of truth
|
||||
2. response-shape rollout
|
||||
3. freshness semantics
|
||||
4. confidence and completeness behavior
|
||||
|
||||
## Bucket 1: Field Ownership And Source Of Truth
|
||||
|
||||
| Field | Endpoint | Backend owner | Source of truth | Directly measured or derived | Cadence | Nullable | Frontend dependency |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| `freshness.chain_head.block_number` | `/api/v2/stats` | `stats.go` with RPC helper | authoritative public RPC head | directly measured | per stats request or short cache | no | homepage head freshness, blocks/trust cues |
|
||||
| `freshness.chain_head.timestamp` | `/api/v2/stats` | `stats.go` with RPC helper | authoritative public RPC head block timestamp | directly measured | per stats request or short cache | no | head age, chain visibility |
|
||||
| `freshness.latest_indexed_block.block_number` | `/api/v2/stats` | `stats.go` | explorer DB `MAX(blocks.number)` | directly measured | per stats request | no | distinguish head vs indexed head |
|
||||
| `freshness.latest_indexed_block.timestamp` | `/api/v2/stats` | `stats.go` | explorer DB latest indexed block timestamp | directly measured | per stats request | yes until wired | detail-page trust cues |
|
||||
| `freshness.latest_indexed_transaction.hash` | `/api/v2/stats` | `stats.go` | explorer DB latest indexed tx row | directly measured | per stats request | yes | activity summary |
|
||||
| `freshness.latest_indexed_transaction.block_number` | `/api/v2/stats` | `stats.go` | explorer DB latest indexed tx row | directly measured | per stats request | yes | tx freshness explanation |
|
||||
| `freshness.latest_indexed_transaction.timestamp` | `/api/v2/stats` | `stats.go` | explorer DB latest indexed tx row | directly measured | per stats request | yes | tx age, stale tx visibility |
|
||||
| `freshness.latest_non_empty_block.block_number` | `/api/v2/stats` | `stats.go` | explorer DB latest block where `transaction_count > 0` or equivalent join | derived from indexed block/tx data | per stats request | yes | quiet-chain vs stale-visibility interpretation |
|
||||
| `freshness.latest_non_empty_block.timestamp` | `/api/v2/stats` | `stats.go` | explorer DB latest non-empty block row | derived from indexed block/tx data | per stats request | yes | recent activity framing |
|
||||
| `freshness.latest_non_empty_block.distance_from_head` | `/api/v2/stats` | `stats.go` | computed from chain head minus last non-empty block | derived | per stats request | yes | homepage block-gap explanation |
|
||||
| `completeness.transactions_feed` | `/api/v2/stats` | `stats.go` | comparison of tx freshness vs head freshness | derived | per stats request | no | trust badges |
|
||||
| `completeness.blocks_feed` | `/api/v2/stats` | `stats.go` | indexed block freshness vs chain head freshness | derived | per stats request | no | trust badges |
|
||||
| `completeness.gas_metrics` | `/api/v2/stats` | `stats.go` | gas fields presence and quality | derived | per stats request | no | gas card honesty |
|
||||
| `completeness.utilization_metrics` | `/api/v2/stats` | `stats.go` | utilization field presence and quality | derived | per stats request | no | utilization card honesty |
|
||||
| `sampling.stats_generated_at` | `/api/v2/stats` | `stats.go` | server clock at response generation | directly measured | per response | no | “updated” copy |
|
||||
| `sampling.rpc_probe_at` | `/api/v2/stats` | `stats.go` | latest successful RPC sample timestamp | directly measured or nullable | per stats request | yes | source confidence |
|
||||
| `mode.kind` | `/explorer-api/v1/track1/bridge/status` | `mission_control.go` | mission-control feed mode | directly measured if known, otherwise derived conservatively | per response / SSE tick | no | snapshot/live messaging |
|
||||
| `mode.updated_at` | `/explorer-api/v1/track1/bridge/status` | `mission_control.go` | mission-control snapshot timestamp | directly measured | per response | no | snapshot age |
|
||||
| `mode.reason` | `/explorer-api/v1/track1/bridge/status` | `mission_control.go` | bridge/homepage mode controller | directly measured if available, else nullable | per response | yes | scope explanation |
|
||||
| `mode.scope` | `/explorer-api/v1/track1/bridge/status` | `mission_control.go` | bridge/homepage mode controller | directly measured if available, else nullable | per response | yes | “what is affected?” |
|
||||
| `subsystems.rpc_head.*` | `/explorer-api/v1/track1/bridge/status` | `mission_control.go` | RPC probe result | directly measured | per response | no | mission-control trust cues |
|
||||
| `subsystems.tx_index.*` | `/explorer-api/v1/track1/bridge/status` | `mission_control.go` using stats freshness or shared helper | explorer DB tx freshness | derived from authoritative indexed data | per response / shared cache | yes | homepage stale-tx explanation |
|
||||
| `subsystems.bridge_relay_monitoring.*` | `/explorer-api/v1/track1/bridge/status` | `mission_control.go` | existing relay probe payload | directly measured | per response | no | lane posture |
|
||||
| `subsystems.stats_summary.*` | `/explorer-api/v1/track1/bridge/status` | `mission_control.go` or shared summary helper | stats freshness sample | derived | per response | yes | homepage summary confidence |
|
||||
|
||||
## Bucket 2: Response-Shape Rollout
|
||||
|
||||
### Ship immediately as nullable additions
|
||||
|
||||
These are low-risk additive fields that can be introduced without breaking existing clients.
|
||||
|
||||
- `freshness.latest_indexed_transaction.*`
|
||||
- `freshness.latest_non_empty_block.*`
|
||||
- `freshness.latest_indexed_block.timestamp`
|
||||
- `sampling.stats_generated_at`
|
||||
- `sampling.rpc_probe_at`
|
||||
- `mode.kind`
|
||||
- `mode.updated_at`
|
||||
- `mode.reason`
|
||||
- `mode.scope`
|
||||
- `subsystems.*`
|
||||
|
||||
### Ship after backend wiring
|
||||
|
||||
These need real data acquisition or shared helpers.
|
||||
|
||||
- `freshness.chain_head.*`
|
||||
- `completeness.transactions_feed`
|
||||
- `completeness.blocks_feed`
|
||||
- `completeness.gas_metrics`
|
||||
- `completeness.utilization_metrics`
|
||||
|
||||
### Derived computations
|
||||
|
||||
These may be computed in backend code once the authoritative inputs exist.
|
||||
|
||||
- `freshness.latest_non_empty_block.distance_from_head`
|
||||
- subsystem `status`
|
||||
- completeness enums
|
||||
- optional `activity_interpretation`
|
||||
|
||||
### Frontend adoption order
|
||||
|
||||
1. Prefer new fields when present.
|
||||
2. Fall back to current inference when absent.
|
||||
3. Remove inference once fields are stable in production.
|
||||
|
||||
## Bucket 3: Freshness Semantics
|
||||
|
||||
Each field must answer a precise question.
|
||||
|
||||
### `freshness.chain_head`
|
||||
|
||||
- Meaning: latest chain head observed from the authoritative public RPC
|
||||
- Must not mean: latest indexed explorer block
|
||||
- If unknown: return `null` object members where needed plus completeness/confidence state
|
||||
|
||||
### `freshness.latest_indexed_block`
|
||||
|
||||
- Meaning: latest block successfully indexed into the explorer DB or visible explorer block source
|
||||
- Must not mean: latest RPC head
|
||||
|
||||
### `freshness.latest_indexed_transaction`
|
||||
|
||||
- Meaning: latest transaction currently visible in the public indexed transaction feed
|
||||
- Must not mean: latest mempool event or latest raw RPC tx if not visible in the explorer feed
|
||||
|
||||
### `freshness.latest_non_empty_block`
|
||||
|
||||
- Meaning: latest indexed block containing at least one visible indexed transaction
|
||||
- This is the critical disambiguator for quiet-chain vs stale-visibility interpretation
|
||||
|
||||
### `mode.kind`
|
||||
|
||||
- Meaning: the current homepage/mission-control delivery mode
|
||||
- Allowed values: `live`, `snapshot`, `mixed`, `unknown`
|
||||
|
||||
### `mode.scope`
|
||||
|
||||
- Meaning: which user-visible surface is affected by mode choice
|
||||
- Examples:
|
||||
- `relay_monitoring_homepage_card_only`
|
||||
- `homepage_summary_only`
|
||||
- `bridge_monitoring_and_homepage`
|
||||
|
||||
### `mode.reason`
|
||||
|
||||
- Meaning: why snapshot or mixed mode is active
|
||||
- Must be calm and operational, not blame-oriented
|
||||
- Examples:
|
||||
- `live_homepage_stream_not_attached`
|
||||
- `relay_snapshot_only_source`
|
||||
- `partial_observability_inputs`
|
||||
|
||||
### `subsystems.*`
|
||||
|
||||
- Meaning: freshness of each component, not overall product health
|
||||
- Recommended subsystem keys:
|
||||
- `rpc_head`
|
||||
- `tx_index`
|
||||
- `bridge_relay_monitoring`
|
||||
- `stats_summary`
|
||||
|
||||
## Bucket 4: Confidence And Completeness
|
||||
|
||||
Every nullable or derived field should have explicit semantics.
|
||||
|
||||
### Confidence
|
||||
|
||||
- `high`: authoritative source and recent sample
|
||||
- `medium`: authoritative source but partially stale, or a stable derived value from strong inputs
|
||||
- `low`: weakly derived or missing one of the underlying inputs
|
||||
- `unknown`: no basis to express confidence
|
||||
|
||||
### Completeness
|
||||
|
||||
- `complete`: field is current and supported by recent source data
|
||||
- `partial`: field exists but some required inputs are missing or weak
|
||||
- `stale`: field is known, but the latest available value is older than acceptable freshness
|
||||
- `unavailable`: no trustworthy value exists
|
||||
|
||||
### Null and zero handling
|
||||
|
||||
- Unknown must be `null`, not synthetic `0`
|
||||
- Zero may be returned only when zero is a real measured value
|
||||
- If a value is null, a sibling completeness/confidence field must explain why
|
||||
|
||||
## Acceptance Tests
|
||||
|
||||
These should be implemented in backend tests and used as rollout gates.
|
||||
|
||||
### 1. Current head, stale tx visibility
|
||||
|
||||
If chain head is current but tx visibility is stale:
|
||||
|
||||
- `freshness.chain_head` must be current
|
||||
- `freshness.latest_indexed_transaction` must be older
|
||||
- `freshness.latest_non_empty_block` must be exposed
|
||||
- completeness must not report all feeds as `complete`
|
||||
|
||||
### 2. Quiet chain, current visibility
|
||||
|
||||
If recent head blocks are genuinely empty:
|
||||
|
||||
- `freshness.chain_head` must still be current
|
||||
- `freshness.latest_non_empty_block` must be present
|
||||
- `freshness.latest_indexed_transaction` must be present
|
||||
- API must not force a stale diagnosis if visibility itself is current
|
||||
|
||||
### 3. Snapshot mode active
|
||||
|
||||
If snapshot mode is active:
|
||||
|
||||
- `mode.kind` must be `snapshot` or `mixed`
|
||||
- `mode.scope` must state what is affected
|
||||
- `mode.reason` must be present if known
|
||||
|
||||
### 4. Unknown fields
|
||||
|
||||
If a field is unknown:
|
||||
|
||||
- return `null`
|
||||
- expose confidence/completeness state
|
||||
- do not return fake zero values
|
||||
|
||||
## Backend Implementation Checklist
|
||||
|
||||
### `stats.go`
|
||||
|
||||
- [ ] Extend `explorerStats` with nullable freshness/completeness/sampling fields.
|
||||
- [ ] Add query/helper for latest indexed transaction.
|
||||
- [ ] Add query/helper for latest non-empty block.
|
||||
- [ ] Add query/helper for latest indexed block timestamp.
|
||||
- [ ] Add RPC helper for current chain head number and timestamp.
|
||||
- [ ] Compute `distance_from_head` when both chain head and latest non-empty block are present.
|
||||
- [ ] Compute completeness enums for blocks, transactions, gas metrics, and utilization.
|
||||
- [ ] Return `null` for unknowns rather than synthetic zero values.
|
||||
- [ ] Add internal tests covering:
|
||||
- healthy current state
|
||||
- quiet-chain state
|
||||
- stale tx visibility state
|
||||
- null/unknown field handling
|
||||
|
||||
### `mission_control.go`
|
||||
|
||||
- [ ] Extend bridge status response with `mode`.
|
||||
- [ ] Extend bridge status response with `subsystems`.
|
||||
- [ ] Reuse or call shared freshness helper for tx index freshness rather than duplicating logic.
|
||||
- [ ] Emit `mode.scope` and `mode.reason` only when backend can support them.
|
||||
- [ ] Use `unknown` or nullable values when reason/scope cannot be stated authoritatively.
|
||||
- [ ] Add tests covering:
|
||||
- live mode
|
||||
- snapshot mode
|
||||
- mixed mode
|
||||
- tx index stale while RPC head remains current
|
||||
|
||||
### Shared rollout
|
||||
|
||||
- [ ] Frontend reads new fields opportunistically.
|
||||
- [ ] Existing frontend inference remains as fallback until backend fields are stable.
|
||||
- [ ] Swagger/OpenAPI docs updated after implementation.
|
||||
- [ ] Public docs updated only after payload shape is live.
|
||||
|
||||
## Test Coverage Guidance
|
||||
|
||||
For every field, capture:
|
||||
|
||||
- who computes it
|
||||
- from what source
|
||||
- at what cadence
|
||||
- whether nullable or required
|
||||
- fallback behavior
|
||||
- confidence/completeness semantics
|
||||
- frontend dependency
|
||||
- backend test case name
|
||||
|
||||
That metadata is more important than perfect initial coverage breadth.
|
||||
|
||||
## Shortest Path To Value
|
||||
|
||||
If the team wants the fastest possible trust win, implement these first:
|
||||
|
||||
1. `freshness.chain_head`
|
||||
2. `freshness.latest_indexed_transaction`
|
||||
3. `freshness.latest_non_empty_block`
|
||||
4. `sampling.stats_generated_at`
|
||||
5. `mode.kind`
|
||||
6. `mode.scope`
|
||||
7. `mode.reason`
|
||||
|
||||
That is the minimum set that lets the frontend stop guessing about the most visible freshness ambiguity.
|
||||
@@ -1,5 +1,7 @@
|
||||
# Track API Contracts
|
||||
|
||||
See also: [EXPLORER_FRESHNESS_DIAGNOSTICS_CONTRACT.md](/home/intlc/projects/proxmox/explorer-monorepo/docs/api/EXPLORER_FRESHNESS_DIAGNOSTICS_CONTRACT.md) for the public freshness and observability fields required by the current SolaceScan frontend.
|
||||
|
||||
Complete API contract definitions for all 4 tracks of SolaceScan Explorer.
|
||||
|
||||
## Track 1: Public Meta Explorer (No Auth Required)
|
||||
|
||||
Reference in New Issue
Block a user