5.9 KiB
Receiver Spec
Endpoint: POST /receive
Contract version: 1.0
Status: design complete, not yet implemented
This is the single endpoint every Hubzilla addon on this node POSTs to. There is no per-addon route. The addon field in the envelope body determines how the orchestrator validates and stores the request.
Authentication
Every request must include:
X-Node-Token: {shared secret from /etc/civic-orchestrator/env_file}
A request with a missing or incorrect token is rejected with 401 before the body is parsed. See README.md for the trust model this implies — the token authenticates the node, not the specific addon.
Request Envelope — Common Fields
Every envelope, regardless of addon, must include:
| Field | Type | Description |
|---|---|---|
addon |
string | One of vs01, dsc01, scn01. Identifies the record type and storage rule. |
contract_version |
string | Must be "1.0" for this spec version. |
association_slug |
string | URL slug of the association. Matches a key in that addon's config.json associations object. |
association_channel_id |
string | Hubzilla channel ID of the association, stored as string. |
participant_id |
string | The participant's Placekey (e.g. SASE1-22c-at-5sb-8hm-54v). This is the permanent, address-bound identifier — see README.md for why. |
submitted_at |
string | ISO 8601 timestamp of submission. |
standing |
string | One of public, participant, professional, operator. The access state at time of submission. |
Anything beyond these common fields is addon-specific and described below.
vs01 and dsc01 — Overwrite-by-Placekey
Additional field
| Field | Type | Description |
|---|---|---|
fields |
object | Keyed object of all field values for this submission. Keys match the field IDs defined in that addon's schema files. |
Validation rules
fieldsmust be present and non-empty.- The orchestrator does not validate individual field values against the VS or DSC schema files — that validation already happened on the Hubzilla side before the envelope was assembled. The orchestrator's job is to store what it receives, not to re-validate business rules it does not own a copy of.
- If
fieldsis missing entirely, reject with400. An empty or malformed envelope is a bug on the sending side and should fail loudly, not be silently dropped or partially stored.
Storage behavior
Write (overwrite, not append) to:
/srv/civic-orchestrator/data/{addon}/{association_slug}/{participant_id}.json
The entire file is replaced with the new envelope on every write. No merge, no partial update, no history retained by the orchestrator.
Response
{ "status": "ok", "addon": "vs01", "participant_id": "SASE1-22c-at-5sb-8hm-54v", "stored_at": "2026-06-15T10:00:00+00:00" }
scn01 — Append-Only
Additional fields
| Field | Type | Description |
|---|---|---|
pinned_scenario_ids |
array of strings | 1–3 scenario IDs from scenarios.json, deduplicated. |
narrative |
string | Free text. |
vs_snapshot |
object | The participant's full VS fields object at time of submission. Embedded, not referenced. |
dsc_snapshot |
object | The participant's full DSC fields object at time of submission. Embedded, not referenced. |
g1_tx_hash |
string or null | Optional. Transaction hash if a Ğ1 payment was made for this submission. May be null or absent. Absence does not invalidate the record — see README.md. |
Validation rules
pinned_scenario_idsmust have at least 1 and at most 3 entries.narrativemust be non-empty.vs_snapshotanddsc_snapshotmust both be present. A Scenario submitted without a snapshot of the participant's current VS and DSC understanding is rejected with400— this is the prerequisite enforced at the envelope level. (The Hubzilla addon enforces this before the form is even shown; the orchestrator enforces it again here as the backstop, per defense-in-depth, not as the primary gate.)g1_tx_hashis never required for acceptance.
Storage behavior
Append the complete envelope as a new entry to the array at:
/srv/civic-orchestrator/data/scn01/{association_slug}/records.json
If the file does not exist, create it with a new array containing this one entry. Never overwrite or truncate existing entries. A write that would replace rather than append is a bug and must fail loudly rather than silently destroy prior records.
Response
{ "status": "ok", "addon": "scn01", "participant_id": "SASE1-22c-at-5sb-8hm-54v", "record_index": 14, "g1_tx_hash": null, "stored_at": "2026-06-15T10:00:00+00:00" }
record_index is the zero-based position of this entry in the association's records array, returned so the Hubzilla addon can display a confirmation reference if needed.
Error Responses
| Code | Condition |
|---|---|
401 |
Missing or incorrect X-Node-Token. |
400 |
Missing required common field, missing addon-specific required field, or unrecognized addon value. |
500 |
Storage write failed (disk, permissions, JSON encode failure). The orchestrator must log the full envelope to a separate failure log before returning this — a failed write must never silently lose the submission. |
Every error response includes a plain-language message field. No stack traces, no raw exception text.
What This Spec Does Not Cover
Ğ1 transaction broadcast and verification (signing happens in g1wallet, broadcast relay happens in g1wallet's spool, the orchestrator only records the resulting hash if one is provided — it does not initiate or verify the transaction itself). That flow is a separate, not-yet-built piece of work and will get its own contract addendum when it is built.
Operator manage/review read endpoints (GET routes for the orchestrator's stored data) are also out of scope for this version. This spec covers writes only.