Skip to content

Swarm Phase 3b: src/services/wire-validator.ts — implement rejection rules 1-13 from SWARM_SPEC §5 #86

@Dewinator

Description

@Dewinator

Why

SWARM_SPEC §5 defines 15 rejection rules every node MUST agree on. They are the single chokepoint that decides whether a received wire record influences local state. Implementing them as one pure, well-tested function now means every later ingest path (lessons endpoint, hubs endpoint, peer relay) becomes a one-line validate(...) call.

Background — read this BEFORE starting

  • mcp__vector-memory__prime_context with task_description "implement wire-validator rejection rules for mycelium swarm"
  • Read docs/SWARM_SPEC.md §5 — the 15-rule list is normative
  • Read src/services/wire-types.ts (Phase 3a) — depends on its types and canonicalizeForSigning
  • Read src/services/signature.ts (PR feat(swarm): signature service — Ed25519 sign/verify over JCS (#77) #82) — depends on its verifySignature

What this issue delivers

A new file src/services/wire-validator.ts exporting:

type ValidationOk = { ok: true };
type ValidationErr = { ok: false; rule: number; reason: string };
type ValidationResult = ValidationOk | ValidationErr;

export function validateWireRecord(
  record: unknown,
  kind: "lesson" | "hub_anchor" | "node_advertisement",
  opts: { ourSpecMajor: number; now: Date; getPubkeyForNode: (nodeId: string) => Uint8Array | null }
): Promise<ValidationResult>;

Implement rules 1-13 from §5. Specifically:

rule check
1 spec_version major != opts.ourSpecMajor
2 required field missing/null
3 type mismatch on a typed field
4 embedding length != 768 OR contains non-finite
5 Ed25519 signature verification fails
6 for NodeAdvertisement: node_id != multihash(pubkey)
7 signed_at > now + 5 min
8 signed_at < now − 90 days (Lesson, HubAnchor only)
9 Lesson: signed_at < created_at
10 duplicate id+signed_at with different signature → caller's job; NOT in this function
11 Lesson: synthesized_from_cluster_size < 2
12 size limits (Lesson.content > 8 KiB, HubAnchor.topic_label > 256, NodeAdvertisement.display_name > 64)
13 NodeAdvertisement: endpoint_url not https

Skip 14 (local trust — caller-side) and 15 (body cap — transport-layer). Document this skip in a top-of-file comment.

For rule 6 use the multihash multiformats helper already in the repo (introduced for Phase 1b — init-node-identity.mjs); if it's not exported, export it from there in this PR.

For rule 5 use verifySignature from signature.ts and the getPubkeyForNode callback from opts.

Acceptance criteria

  • src/services/wire-validator.ts exists and exports validateWireRecord
  • tests/services/wire-validator.test.ts covers:
    • one valid example per kind → { ok: true }
    • at least one negative case for each of rules 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13 (12 negative tests minimum), each asserting { ok: false, rule: <n> }
  • Pure function — no DB, no HTTP, no file I/O
  • All side-channel data (pubkey lookup, current time) injected via opts

Hard constraints

  • DO NOT modify SWARM_SPEC.
  • DO NOT implement rules 14 or 15.
  • DO NOT cache or memoize — keep this as a pure function for testability.

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent-eligibleAutonomous agent loop is allowed to pick thisfoundationPhase-0/1 Fundament für ein größeres FeatureswarmSchwarm-Foundation: dezentrale P2P-Architektur

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions