Reference Herald (T-115)
Single-operator HERALD service. Composes:
- Operator key (Ed25519, persisted under
data/operator.key.jsonat first run) - Charter (R-203 template, signed at startup, served at
/charter) - CT log (T-101 schema / T-102 implementation,
lib/herald-ct-log) lineage_endorsementissuance ([EXT §3.2])
Endpoints
| Method | Path | Purpose |
|---|---|---|
GET | /health | { ok, sigil, charter_signed_at, log_size } |
GET | /charter | Operator's signed Charter (markdown + Ed25519 signature, domain herald.charter.v1) |
GET | /sth | Latest Signed Tree Head from the CT log |
GET | /entries?from=&to= | Log entries (minimum-metadata profile) |
GET | /sip/:envelope_id | Inclusion-proof metadata; full audit-path TBD |
POST | /endorse | Issue a lineage_endorsement envelope, log it, return receipt |
GET | /endorsements/:subject | List this Herald's endorsements for a sigil |
Quick start
cd services/reference-herald
npm install
cp config/charter.example.md config/charter.md
# fill in charter.md (operator legal entity, jurisdiction, evidence policy, ...)
npm startThe service binds :8120 by default (override with PORT).
Issue a test endorsement
curl -X POST local-service/endorse \
-H 'content-type: application/json' \
-d '{
"subject": "sigil:abc...",
"parent": "sigil:base-model-fingerprint",
"evidenceClass": "model_hash_verified",
"notes": "Operator verified weights against published model-hash spec on 2026-05-05"
}'Returns:
{
"envelope": { "from": "sigil:...", "id": "...", "signatures": [...], ... },
"domain": "herald.lineage_endorsement.v1",
"receipt": { "envelope_id": "...", "log_index": 0, "tree_size": 1, ... }
}The Herald's signature on the envelope is computed under theherald.lineage_endorsement.v1 domain tag rather than the genericherald.envelope.v1. Verifiers consulting EXT §3.2 know to validate
origin signatures on lineage_endorsement envelopes against this tag.
Charter
The Charter at config/charter.md is the operator's transparency
document per [EXT §3.5]. The R-203 template attemplates/Herald-Charter.md is the canonical fillable starting point.
The Charter is signed once at service startup. Verifiers retrieving/charter get the markdown body, the operator sigil, the signature
under domain tag herald.charter.v1, and the signing timestamp.
Per [EXT §3.5], Charters MUST be re-signed annually. A production
deployment runs a small cron (re-sign-charter) that updates the
signature and bumps the version number.
Storage
data/operator.key.json— Ed25519 seed + sigil. Mode 0600. Treat as secret.data/log.jsonl— append-only CT log entries (lib/herald-ct-log format).data/endorsements.jsonl— local index(envelope_id, subject, ...)for the/endorsements/:subjectendpoint.
In production, replace data/ with proper persistent storage and
move the operator key behind an HSM (per the LOTS / Charter posture).
What this service is NOT
- Not multi-tenant. One operator, one key, one Charter.
- Not high-availability. Single instance, file-backed log; restart loses no data but a crash mid-write may corrupt the latest entry.
- Not gossiped. STH gossip ([EXT §2.5]) is not implemented; this Herald is a single-log endpoint. R-215 trace-genesis gossip is also not wired up.
- Not authenticated.
POST /endorseaccepts requests from anyone reaching the port. A production deployment fronts this with auth (operator-controlled). - Not rate-limited. No throttling. Production puts a reverse proxy in front.
These are deliberate omissions for a reference implementation. The point is to demonstrate the protocol shape; production deployments add the operational scaffolding.
Cross-references
- [EXT §3] Heralds (the protocol-layer rules this service implements)
- [EXT §3.2]
lineage_endorsementenvelope shape - [EXT §3.5] Herald Charter requirement
- [EXT §2] CT log specification
- [SPEC §3] envelope shape
lib/herald-core-ts/— envelope build/sign/verifylib/herald-ct-log/— CT log primitivestemplates/Herald-Charter.md— R-203 fillable templatetemplates/LOTS.md— T-106 fillable template (for the Herald's CT log operator role)
Tests
npm testThree smoke tests cover:
1. Charter is loaded, signed at startup, served at /charter with a valid Ed25519 signature.
2. /endorse issues a lineage_endorsement envelope, logs it to the CT log, returns a receipt with consistent envelope_id / tree_size.
3. /endorsements/:subject lists the Herald's previous endorsements for a sigil.