127 lines
5.9 KiB
Markdown
127 lines
5.9 KiB
Markdown
# 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
|
||
|
||
- `fields` must 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 `fields` is missing entirely, reject with `400`. 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
|
||
|
||
```json
|
||
{ "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_ids` must have at least 1 and at most 3 entries.
|
||
- `narrative` must be non-empty.
|
||
- `vs_snapshot` and `dsc_snapshot` must both be present. A Scenario submitted without a snapshot of the participant's current VS and DSC understanding is rejected with `400` — 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_hash` is 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
|
||
|
||
```json
|
||
{ "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.
|