Skip to content

Commit d6abce0

Browse files
Merge pull request #41 from tekwani/release/v0.2.0
v0.2.0 sync
2 parents 72e88b6 + d7159fc commit d6abce0

523 files changed

Lines changed: 26822 additions & 3724 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
# Changelog
22

3-
See also, the curated [release notes](./docs/reference/release-notes/0.0.1-release.md).
3+
For an overview, see the [release notes](./docs/reference/release-notes)
44

5-
## Overview v0.2.0
5+
## v0.2.0
66

7-
MDK v0.2.0 is a major architectural release. The monorepo has been restructured into three fully federated domains (`backend/core`, `backend/workers`, `ui`), the worker layer has been promoted to a first-class package with a formal protocol contract, the UI state layer has been rewritten around Zustand and React 19, and a new agent-first CLI and MCP endpoint land as net-new additions.
7+
MDK v0.2.0 is a major architectural overhaul release. The monorepo has been restructured into three fully federated domains (`backend/core`, `backend/workers`, `ui`), the worker layer has been promoted to a first-class package with a formal protocol contract, the UI state layer has been rewritten around Zustand and React 19, and a new agent-first CLI and MCP endpoint land as net-new additions.
88

99
## Breaking changes
1010

11-
### Node.js minimum version bumped to `>=22`, >`>=24` recommended
11+
### Node.js minimum version bumped to `>=24`
1212

13-
All packages now require Node.js 22+. The previous minimum was Node.js 20.
13+
All packages now require Node.js 24+. The previous minimum was Node.js 20.
1414

1515
### Monorepo directory layout restructured
1616

17-
| 0.1.0 path | 0.2.0 path |
17+
| 0.0.1 path | 0.2.0 path |
1818
|---|---|
1919
| `core/` | `backend/core/` |
2020
| `ui-client/` | `ui/` |
@@ -33,7 +33,7 @@ with their own install and test lifecycle.
3333

3434
Worker type identifiers changed:
3535

36-
| 0.1.0 | 0.2.0 |
36+
| 0.0.1 | 0.2.0 |
3737
|---|---|
3838
| `'mdk/ork'` | `'core/ork'` |
3939
| `'mdk/app-node'` | `'core/app-node'` |
@@ -48,7 +48,7 @@ Worker-specific paths follow the new `'workers/<category>/<provider>'` pattern (
4848

4949
The `packages/core` and `packages/foundation` packages have been removed and replaced by four new packages:
5050

51-
| Removed (0.1.0) | Replacement (0.2.0) |
51+
| Removed (0.0.1) | Replacement (0.2.0) |
5252
|---|---|
5353
| `packages/core` (monolithic component lib) | `@tetherto/mdk-react-devkit` |
5454
| `packages/foundation` (domain components) | `@tetherto/mdk-react-devkit` (foundation/) |
@@ -69,7 +69,7 @@ All UI packages now target React 19.
6969
`core/lib/mdk.js` (exporting `initType`, `startApi`, `initialize`) is replaced by `backend/core/mdk/index.js` with an explicit async API:
7070

7171
```js
72-
// 0.1.0
72+
// 0.0.1
7373
const { initType, startApi } = require('@tetherto/mdk-core')
7474
await startApi(port)
7575
await initType(MyMinerClass, rack)

CODE_REVIEW_FINDINGS.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
# Code Review Findings — release/v0.2.0
2+
3+
> **Scope**: Full codebase audit (security + correctness + quality)
4+
> **Date**: 2026-06-16
5+
> **Branch**: `release/v0.2.0`
6+
7+
---
8+
9+
## Critical
10+
11+
### 1. authCheck → capCheck TypeError on unauthenticated requests
12+
13+
**File**: `workers/lib/server/routes/users.routes.js:42` · `workers/lib/server/lib/routeHelpers.js:27`
14+
15+
When `authCheck` fails (missing or invalid token), it calls `rep.status(401).send(...)` and **returns** — it does not throw. The `onRequest` hook then continues to call `capCheck`, which calls `ctx.authLib.tokenHasPerms(req._info.authToken, ...)`. Since auth failed, `req._info.authToken` is `undefined`, causing `undefined.substring(...)` to throw a `TypeError`. Fastify's error handler then tries to send a 400 after the 401 was already sent, producing a "Reply already sent" error.
16+
17+
**Affected routes**: all permission-gated routes (users CRUD, global-data write, features write, etc.)
18+
19+
**Fix**: In `createAuthOnRequest` and all inline `onRequest` hooks, check `rep.sent` after `authCheck` and return early before calling `capCheck`:
20+
21+
```js
22+
async (req, rep) => {
23+
await authCheck(ctx, req, rep)
24+
if (rep.sent) return // ← add this guard
25+
if (perms) {
26+
await capCheck(ctx, req, rep, perms)
27+
}
28+
}
29+
```
30+
31+
---
32+
33+
### 2. WebSocket handler runs after failed auth — unauthenticated clients added to wsClients
34+
35+
**File**: `workers/lib/server/routes/ws.routes.js:13`
36+
37+
The WebSocket route's `onRequest` calls `authCheck`, which on failure writes a 401 HTTP response and returns (no throw). `@fastify/websocket` v11 gates its upgrade handler on `request.raw[kWs]` (presence of the upgrade socket), **not** on `reply.sent`. The WebSocket handshake therefore completes, and the handler runs unconditionally — adding the unauthenticated socket to `ctx.wsClients`. That socket then receives all subsequent alert broadcasts.
38+
39+
**Fix**: Throw instead of return on auth failure inside `onRequest` for WebSocket routes, or check `rep.sent` in the handler:
40+
41+
```js
42+
onRequest: async (req, rep) => {
43+
await authCheck(ctx, req, rep, req.query.token)
44+
if (rep.sent) throw new Error('ERR_AUTH_FAIL') // ← abort upgrade
45+
},
46+
```
47+
48+
---
49+
50+
## High
51+
52+
### 3. Revoked tokens remain valid for up to 60 seconds via stale lru_1m cache
53+
54+
**File**: `workers/lib/server/lib/authCheck.js:27`
55+
56+
`authCheck` caches successful auth results in `ctx.lru_1m` under the key `${token}:${ips}`. When a token is revoked via `_deleteTokensOfUser`, the library invalidates its own internal LRU entry (`gotokens:<token>`) but has no knowledge of `authCheck`'s separate `lru_1m` cache. Revoked tokens continue to pass auth checks from cache for up to 60 seconds.
57+
58+
**Fix**: Expose a cache invalidation callback from `authCheck`, or call `ctx.lru_1m.delete(...)` for all matching keys during token revocation in `users.handlers.js`.
59+
60+
---
61+
62+
## Medium
63+
64+
### 4. Token format validation dead code — regex missing `+` quantifier
65+
66+
**File**: `node_modules/@tetherto/svc-facs-auth/index.js:287`
67+
68+
```js
69+
if (typeof token !== 'string' || /^[a-zA-Z0-9:\-]$/.test(token)) {
70+
```
71+
72+
The regex `/^[a-zA-Z0-9:\-]$/` with no quantifier matches strings of **exactly one character**. All real tokens (e.g. `pub:api:<uuid>-5`) are longer, so the regex never matches and the guard is inoperative for every real token. Any string — including malformed inputs — proceeds to the database query. No SQL injection risk (parameterized query), but the format validation is completely disabled.
73+
74+
**Fix**: Add `+` quantifier: `/^[a-zA-Z0-9:\-]+$/`
75+
76+
---
77+
78+
### 5. `_rpcMapAllPages` has no maximum page count guard
79+
80+
**File**: `workers/lib/data.proxy.js`
81+
82+
The `while (true)` pagination loop breaks only when `batch.length === 0` or `batch.length < pageLimit`. If a misbehaving worker consistently returns exactly `pageLimit` items (e.g. due to a bug on the ork side), the loop never terminates. Each iteration is bounded by the RPC timeout (~15 s), but `allItems` grows without bound until the process runs out of memory or the timeout chain exhausts the event loop.
83+
84+
**Fix**: Add a maximum page count guard:
85+
86+
```js
87+
const MAX_PAGES = 1000
88+
let page = 0
89+
while (page++ < MAX_PAGES) {
90+
// ...
91+
if (batch.length < pageLimit) break
92+
}
93+
```
94+
95+
---
96+
97+
### 6. User `id` field schema uses `type: 'number'` instead of `type: 'integer'`
98+
99+
**File**: `workers/lib/server/routes/users.routes.js:101`
100+
101+
Fastify's AJV coercion accepts `1.5` as a valid `number`. A fractional ID passed to `deleteUser` or `updateUser` is forwarded to SQLite as-is. SQLite's integer affinity may silently find no row, causing the operation to succeed with 0 rows affected and no error returned to the caller.
102+
103+
**Fix**: Change `type: 'number'` to `type: 'integer'` for all user ID fields.
104+
105+
---
106+
107+
## Low / Quality
108+
109+
### 7. `configs.handlers.js` re-implements `parseJsonQueryParam` inline
110+
111+
**File**: `workers/lib/server/handlers/configs.handlers.js:19`
112+
113+
A `try { JSON.parse(...) } catch` block duplicates the shared `parseJsonQueryParam` utility already exported from `workers/lib/utils.js`. Fixes or enhancements to the shared utility (e.g. rejecting non-object JSON) will not be inherited.
114+
115+
**Fix**: Replace the inline block with `parseJsonQueryParam(jsonString, 'ERR_INVALID_JSON')`.
116+
117+
---
118+
119+
### 8. `data.proxy.js` defines its own `getRpcTimeout`, duplicating `utils.getRpcTimeout`
120+
121+
**File**: `workers/lib/data.proxy.js`
122+
123+
The module defines a local `getRpcTimeout` function that is byte-for-byte identical to the one in `workers/lib/utils.js`. If the timeout resolution logic changes in one copy, the other silently diverges.
124+
125+
**Fix**: Remove the local definition and import from `utils.js`.
126+
127+
---
128+
129+
### 9. `alerts.handlers.js` duplicates alert extraction logic from `AlertsService`
130+
131+
**File**: `workers/lib/server/handlers/alerts.handlers.js`
132+
133+
`extractAlertsFromThings` re-implements inline the same alert-extraction logic encapsulated in `workers/lib/alerts.js` (`AlertsService`). Behaviour fixes or threshold changes in `AlertsService` will not apply to the handler path.
134+
135+
**Fix**: Call `ctx.alertsService.extractAlerts(...)` (or the equivalent service method) instead of duplicating the logic.
136+
137+
---
138+
139+
### 10. Deep pagination causes O(orks × offset) data transfer per request
140+
141+
**File**: `workers/lib/server/handlers/miners.handlers.js:134`
142+
143+
Each ork is queried with `limit = offset + limit, offset = 0`, fetching all items from the beginning up to the requested page boundary. Results are merged in-memory, sorted, and sliced. At `offset=500, limit=50` with N orks, N × 550 items are transferred over RPC and 98% are discarded. Transfer volume grows linearly with both offset and ork count.
144+
145+
**Fix**: Implement cursor-based pagination at the ork level, or accept the scalability ceiling and document the maximum supported page depth.

README.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# MDK
22

3-
[![Status](https://img.shields.io/badge/status-pre--alpha-lightgrey?style=flat-square)](#status)
3+
[![Release](https://img.shields.io/github/v/release/tetherto/mdk?display_name=tag&style=flat-square)](https://github.com/tetherto/mdk/releases/tag/v0.2.0)
44
[![MDK UI CI](https://img.shields.io/github/actions/workflow/status/tetherto/mdk/ui.yaml?branch=main&label=UI%20CI&style=flat-square&logo=github)](https://github.com/tetherto/mdk/actions/workflows/ui.yaml)
55
[![MDK Core CI](https://img.shields.io/github/actions/workflow/status/tetherto/mdk/core.yaml?branch=main&label=Core%20CI&style=flat-square&logo=github)](https://github.com/tetherto/mdk/actions/workflows/core.yaml)
66
[![CodeQL](https://github.com/tetherto/mdk/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/tetherto/mdk/actions/workflows/github-code-scanning/codeql)
@@ -10,8 +10,9 @@
1010

1111
⚠️ **Work in Progress**
1212

13-
MDK is currently under active development and is **not yet considered stable**. To test the SDK or explore the latest
14-
functionality, use the **[`main` branch](https://github.com/tetherto/mdk/tree/main)**.
13+
MDK is under active development and is **not yet considered stable**.
14+
15+
Current release: [v0.2.0](https://github.com/tetherto/mdk/releases/tag/v0.2.0).
1516

1617
## Table of Contents
1718

@@ -87,7 +88,7 @@ Each worker has:
8788
via [`startWorker()`](backend/core/mdk/index.js).
8889
- **A [`mdk-contract.json`](backend/workers/miners/antminer/mdk-contract.json)**, the engineering source of truth. Declares every telemetry field
8990
(name, unit, type) and every command (name, params).
90-
- **A [mock server](backend/core/examples/mdk-e2e/server.js)**, a local HTTP server with canned responses for hardware-free development.
91+
- **A [mock server](examples/backend/mdk-e2e/server.js)**, a local HTTP server with canned responses for hardware-free development.
9192

9293
## Layer 2: Orchestration Kernel
9394

@@ -129,11 +130,10 @@ leverage the packages provided for React (Vue, Svelte, and Web Components on the
129130

130131
## Releases
131132

132-
The latest development code is available on the `main` branch, with pre-release versions tagged as `*-beta` and `*-rc`. Stable releases will be tagged
133-
using semantic versioning without suffixes (e.g., `1.0.0`).
133+
The latest development code is available on the [`main`](https://github.com/tetherto/mdk/tree/main) branch. MDK follows [Semantic Versioning 2.0.0](https://semver.org/): `0.y.z` versions are initial development (public API not stable until `1.0.0`).
134134

135-
- `*-beta`, `*-rc` → Active development tags (latest features and changes)
136-
- `x.y.z` → Reserved for stable releases
135+
- [`/docs/reference/release-notes/`](./docs/reference/release-notes)
136+
- Full [`CHANGELOG.md`](./CHANGELOG.md)
137137

138138
## Get started
139139

@@ -166,9 +166,9 @@ Full snippet in [`ui/README.md`](ui/README.md#use-the-toolkit-in-your-app).
166166

167167
### Run the backend stack locally (no hardware)
168168

169-
The [end-to-end demo](backend/core/examples/README.md#end-to-end-mdk-e2e) starts a mock data source + worker + ORK in one process. Browse
170-
[`backend/core/examples/`](backend/core/examples/README.md) for the full catalogue, including the [full-site demo](examples/core/site/README.md),
171-
the [single-process site demo](examples/core/site-single-process/README.md), and the [site-monitor UI example](examples/e2e/README.md).
169+
The [end-to-end demo](examples/backend/README.md#end-to-end-mdk-e2e) starts a mock data source + worker + ORK in one process. Browse
170+
[`examples/backend/`](examples/backend/README.md) for the full catalogue, including the [full-site demo](examples/backend/site/README.md),
171+
the [single-process site demo](examples/backend/site-single-process/README.md), and the [site-monitor UI example](examples/e2e/README.md).
172172

173173
### Integrate a new device, pool, or data feed
174174

@@ -181,7 +181,7 @@ If you're an LLM being pointed at this repo, read these three first:
181181

182182
- [`ui/AGENTS.md`](ui/AGENTS.md) — contract overview and a quick recipe
183183
- [`ui/docs/AGENT_FIRST.md`](ui/docs/AGENT_FIRST.md) — manifests, blueprints, registry
184-
- [`backend/core/examples/README.md`](backend/core/examples/README.md) — runnable shapes of the backend
184+
- [`examples/backend/README.md`](examples/backend/README.md) — runnable shapes of the backend
185185

186186
## Build and develop
187187

SECURITY.md

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,9 @@
22

33
## Supported versions
44

5-
MDK is currently in active development and has not reached a stable (`x.y.z`) release yet.
5+
MDK is in the `0.y.z` initial-development range; the public API is not considered stable until `1.0.0`.
66

7-
Until stable releases are available, security support is provided for:
8-
9-
- The latest commit on `main`
10-
- The most recent pre-release tags (`*-beta`, `*-rc`)
11-
12-
Older pre-release versions may not receive security fixes.
7+
Security fixes are handled on a best-effort basis for the latest commit on `main`. Tagged releases and older commits may not receive security fixes.
138

149
## Reporting a vulnerability
1510

backend/core/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Core infrastructure packages for MDK. These packages form the coordination layer
1212
| [`client/`](./client/README.md) | `@tetherto/mdk-client` | IPC client — connects App Node to ORK over Unix socket |
1313
| [`lib-stats/`](./lib-stats/README.md) | _(internal)_ | Telemetry aggregation operations (count, sum, avg, groupBy, …) |
1414
| [`mock-control-service/`](./mock-control-service/README.md) | `@tetherto/mdk-mock-control-service` | Mock vendor APIs for development and testing |
15-
| [`examples/`](./examples/README.md) | _(runnable demos)_ | End-to-end examples — single worker, full site, DHT multi-process |
15+
| [`examples/`](../../examples/backend/README.md) | _(runnable demos)_ | End-to-end examples — single worker, full site, DHT multi-process |
1616

1717
## Dependency Graph
1818

backend/core/app-node/package-lock.json

Lines changed: 23 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/core/app-node/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@tetherto/mdk-app-node",
3-
"version": "0.0.1",
3+
"version": "0.2.0",
44
"description": "MDK App Node",
55
"keywords": [
66
"tether"
@@ -28,6 +28,7 @@
2828
"@fastify/websocket": "11.2.0",
2929
"@tetherto/mdk-client": "file:../client",
3030
"@tetherto/mdk": "file:../mdk",
31+
"@tetherto/mdk-plugins": "file:../plugins",
3132
"@bitfinex/bfx-svc-boot-js": "1.2.0",
3233
"@bitfinex/bfx-facs-db-sqlite": "git+https://github.com/bitfinexcom/bfx-facs-db-sqlite.git",
3334
"@bitfinex/bfx-facs-http": "git+https://github.com/bitfinexcom/bfx-facs-http.git",

backend/core/app-node/tests/integration/api.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ const { createWorker } = require('tether-svc-test-helper').worker
66
const { setTimeout: sleep } = require('timers/promises')
77
const HttpFacility = require('@bitfinex/bfx-facs-http')
88
const { ENDPOINTS } = require('../../workers/lib/constants')
9-
const path = require('path')
109
const { MOCK_MINERS: mockMiners } = require('./helpers/mock-data')
10+
const path = require('path')
1111

1212
test('Api', { timeout: 90000 }, async (main) => {
1313
const baseDir = 'tests/integration'

0 commit comments

Comments
 (0)