HarperFast / HarperFast/harper
deploy_component: `replicated: true` that reaches zero peers is reported as success (`peer_results: []`)
- Dominant language
- JavaScript
- Stars
- 89
- Forks
- 10
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 200
Description
## Summary
`deploy_component` with `replicated: true` on a multi-node cluster completed with `status: "success"` while replicating to **zero** peers. The `replicate` phase ran `start`→`done` in the same millisecond, `peer_results` is `[]`, the CLI exited 0, and the component exists on one node only. Nothing told the operator.
harper#1333 made a peer that *fails* to replicate an overall deploy failure. The case where there are *no peers to send to* — `server.nodes` empty on the origin worker even though `hdb_nodes` lists peers — is not a failure under that rule and passes as success. On a cluster this is the worse outcome: the deploy looked healthy and the gap was found ~3 hours later by noticing storage usage differed between nodes.
Observed on harper-pro 5.2.3 (2-node Fabric cluster, freshly cloned). The root cause of the empty `server.nodes` is HarperFast/harper-pro#746 (workers on a fresh clone don't see their peers for minutes); this issue is about `deploy_component` letting it through silently, which would apply to any future cause of an empty/short peer list as well.
## Evidence
`hdb_deployment` row (fields trimmed):
```json
{
"project": "…",
"status": "success",
"phase": "success",
"peer_results": [],
"event_log": [
{"t": 1787261361677, "event": "phase", "data": {"phase": "replicate", "status": "start"}},
{"t": 1787261361677, "event": "phase", "data": {"phase": "replicate", "status": "done"}},
{"t": 1787261361677, "event": "phase", "data": {"phase": "restart", "status": "start"}}
]
}
```
Request was `replicated=true` (`harper deploy target=… restart=true replicated=true …`). `hdb_nodes` on the origin had 2 records at the time.
## What `deployComponent` does today (`components/operations.ts`)
```js
emit('phase', { phase: 'replicate', status: 'start' });
let response = await server.replication.replicateOperation(req, { onPeerResult });
emit('phase', { phase: 'replicate', status: 'done' });
if (recorder && response?.replicated) recorder.recordPeers(response.replicated);
…
// failedPeers check only looks at entries that exist in peer_results
```
`replicateOperation` returns `{ replicated: [] }` when `server.nodes` is empty and `{ }` (no `replicated` key) when `req.replicated === false`. The row stores `peer_results: []` in both cases, so an observer can't tell "I asked for a single-node deploy" from "the cluster deploy reached nobody".
## Proposed behaviour
1. When `req.replicated !== false` and `replicateOperation` returns zero peers, compare against the cluster's known peer count (`hdb_nodes` minus self). If peers exist but none were targeted, treat it like a replication failure: record a synthetic failed `peer_results` entry (or a `replicate: { expected: N, targeted: 0 }` marker), surface it in the SSE stream, and fail the deploy unless `ignore_replication_errors` is set — same contract as harper#1333.
2. When `req.replicated === false`, record that explicitly (e.g. `replicated: false` / `replicate: "skipped"` on the row) so `peer_results: []` is no longer overloaded.
3. Log at `warn` when a replicated operation finds `server.nodes` empty on a node whose `hdb_nodes` has peers — useful beyond deploys.
## Related
- harper#1333 (replication failures → overall failure) — this is the gap it left.
- harper#885 / harper#886 / harper#641 — deployment tracking work this would slot into.
- HarperFast/harper-pro#746 — the root cause of this instance (workers on a fresh clone don't see their peers for minutes).
Contributor guide
Research direction
Start in components/operations.ts at deployComponent, then trace replication.replicateOperation and the existing failedPeers handling from harper#1333. Compare the returned peer list with hdb_nodes minus the origin, preserve an explicit non-replicated state, and verify that zero targeted peers are surfaced and fail the deploy unless ignore_replication_errors is set.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, node.js
- Domain
- backend, distributed-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100