Diagnostic Bundle Contract — rigplane-bundle-v2¶
Schema name: rigplane-bundle-v2
Status: stable, public, open-source contract.
Last updated: 2026-05-04
Purpose¶
This contract documents the public, anonymous-tier shape of diagnostic-bundle
uploads from open-core rigplane to a maintainer-operated triage service. It is
the spec that rigplane.diagnostics.upload_bundle builds against and that any
third-party server that wants to receive rigplane reports must implement.
Differences from icom-lan-bundle-v1¶
This is the v2 revision of the contract previously published as
icom-lan-bundle-v1. The wire shape is otherwise
identical; only the rebrand-affected strings change.
| Field | v1 value | v2 value |
|---|---|---|
metadata.schema_version |
icom-lan-bundle-v1 |
rigplane-bundle-v2 |
metadata.app.name |
icom-lan |
rigplane |
system/system.json version key |
icom_lan_version |
rigplane_version |
| Endpoint override env var | ICOM_LAN_REPORT_ENDPOINT |
RIGPLANE_REPORT_ENDPOINT |
A conforming v2 server must keep accepting v1 bundles for at least one deprecation window (12 months from v2 publication) so rigplane v1.x clients keep working unchanged. v2 clients must emit v2 strings end-to-end.
The default maintainer-operated endpoint URL is unchanged; both schemas POST to the same path.
Boundary¶
This document is open-source and authoritative for the anonymous tier of bundle submissions — open-core users with no licence context.
The rigplane-pro distribution adds an authenticated tier (signed uploads tied to a customer support id, longer retention, customer-tier triage) on top of the same endpoint. Those Pro-only extensions are governed by a private contract and are deliberately out of scope here. Pro builds may set additional headers; their behaviour is documented in a private contract.
Endpoint¶
The endpoint is overridable via the RIGPLANE_REPORT_ENDPOINT environment
variable. This is intended for two use-cases:
- Self-hosting — point clients at a third-party backend that implements this contract (see Self-hosting below).
- Testing — integration tests and local development against a stub server.
When the override is unset, the default maintainer-operated endpoint is used.
Request¶
Content-Type: multipart/form-data
| Field | Type | Required | Notes |
|---|---|---|---|
bundle |
file | yes | ZIP archive, maximum 25 MiB after compression. |
metadata |
string (JSON) | yes | Bundle metadata, schema below. |
The anonymous tier sends no authentication headers.
Metadata schema¶
metadata is a JSON string with schema_version: "rigplane-bundle-v2":
{
"schema_version": "rigplane-bundle-v2",
"submission_id": "uuid-v4-generated-by-client",
"app": {
"name": "rigplane",
"version": "2.0.0",
"build_id": "2026-05-04.1"
},
"platform": {
"os": "darwin",
"arch": "arm64",
"python_version": "3.11.14"
},
"user_description": "optional free text",
"issue_ref": "https://github.com/rigplane/rigplane-core/issues/1234",
"contact": {
"email": "ham@example.com",
"callsign": "DL9EAC"
}
}
app.name is "rigplane" for open-core uploads.
Required fields — server returns metadata_invalid (HTTP 400) if absent:
schema_versionsubmission_idgenerated_at_unix— bundle assembly time as Unix epoch seconds.app.nameapp.versionplatform.osplatform.arch
Optional fields — omitted (not null) when data is unavailable. The
server must accept absence without error:
app.build_id— git describe / build pipeline identifier; absent for pip installs without git context.platform.python_version— best-effort, derived fromsys.version_info.user_description,issue_ref— user-supplied at submission time.contact.email,contact.callsign— opt-in user-supplied (see Privacy invariants).- Any field added in future schema versions.
Unknown JSON fields must be ignored. Clients should never send null for an
unavailable field; they must omit the key entirely.
Request rules¶
- Idempotency —
submission_idprovides idempotency: if the samesubmission_idarrives within 24 hours, the server returns the existingreport_idwith HTTP 200. - Content scanning — the server runs a forbidden-pattern regex pass on
the uncompressed text content of the bundle. Detected patterns (see
Forbidden content patterns) cause the bundle
to be rejected with
forbidden_contentand not stored. - Bundle size cap —
bundlesize > 25 MiB →bundle_too_large. - Anonymous rate limit — 5 reports per source IP per hour, 10 per IP per
day. Exceeding either limit →
rate_limitedwithretry_after_seconds. - Retention — anonymous bundles are retained for 90 days, then storage objects are purged.
Success response¶
{
"report_id": "rpt_01JZ7F8A0M5W6X3DKQNHFVHRE0",
"received_at_unix": 1777670400,
"support_url": "https://reports.msmsoft.net/r/rpt_01JZ7F8A0M5W6X3DKQNHFVHRE0",
"auth_class": "anonymous"
}
| Field | Type | Notes |
|---|---|---|
report_id |
string | ULID. Customer-safe identifier returned to the client. |
received_at_unix |
integer | Server-side receive timestamp (UTC, Unix seconds). |
support_url |
string | Customer-safe link the user can paste into a GitHub issue. |
auth_class |
string | "anonymous" for open-core uploads. |
auth_class is "anonymous" for every bundle submitted by open-core
rigplane. The support_url is a customer-safe link and never exposes
internal identifiers, source IP, or contact fields.
Stable error responses¶
All non-2xx responses follow a single envelope:
{
"error": {
"code": "metadata_invalid",
"message": "field 'app.version' is required",
"field": "app.version",
"retry_after_seconds": null
}
}
The relevant error.code values for the diagnostic-bundle endpoint:
| HTTP | Code | Extra fields | Client action |
|---|---|---|---|
| 400 | metadata_invalid |
field |
Reject; client should regenerate the bundle. |
| 413 | bundle_too_large |
— | Reject; client must trim or split the bundle. |
| 422 | forbidden_content |
pattern |
Reject; bundle contained patterns barred by privacy rules. |
| 429 | rate_limited |
retry_after_seconds |
Show retry later; honour retry_after_seconds. |
| 5xx | service_unavailable |
— | Surface a retry path; do not retry aggressively. |
error.message is safe to display, but clients may choose local copy.
retry_after_seconds is null when not applicable.
Privacy invariants¶
These invariants are binding for the anonymous tier and form the basis of the maintainer-operated server's privacy posture.
contact.emailandcontact.callsignare opt-in user-provided fields. They are stored only when supplied by the user via an explicit "Send report" consent form on the client. They must never be auto-populated from system state, environment variables, or local config.- Source IP is collected only for rate-limit enforcement. It is hashed and
dropped (set to
NULLin storage) after 24 hours. - Anonymous bundles must not be cross-correlated with license records by IP, machine fingerprint, or any other field.
- Public GitHub issues opened by the AI triage agent must reference reports
by
report_id/support_urlonly — never by source IP, contact fields, or any other identifying material.
Forbidden content patterns¶
The server rejects bundles whose uncompressed text content contains any of:
AWS_SECRET_ACCESS_KEY,AWS_ACCESS_KEY_IDand similar cloud-credential variable names.Authorization: Bearerheaders in captured logs or HTTP transcripts.- Raw activation codes or licence-code-shaped strings.
- PEM-encoded private keys (
-----BEGIN ... PRIVATE KEY-----). password=,passwd=, and similar credential assignments in plain text.
Clients should perform the same redaction pass locally before submission so that legitimate bundles are not rejected; the server check is a defence in depth, not a primary filter.
Versioning¶
schema_versionis"rigplane-bundle-v2"for this contract revision.- Unknown JSON fields must be ignored by the server. Clients may begin emitting new optional fields without coordination.
- Non-breaking changes — adding a new optional metadata field, adding a new optional response field. These do not bump the schema version.
- Breaking changes — removing or renaming a required field, changing a
field's type or semantics, removing a stable error code. These mint a new
schema_version(e.g.rigplane-bundle-v3) and the server must continue to accept the previous schema for a documented deprecation window. - Backwards compatibility — a v2 server must keep accepting
icom-lan-bundle-v1bundles for at least one 12-month deprecation window from v2 publication, so rigplane v1.x clients (the legacyicom-lanbrand) continue to work unchanged.
Clients must include schema_version in every submission so the server can
route to the correct validator.
Self-hosting¶
The default endpoint is operated by the rigplane maintainer. To redirect uploads to a self-hosted backend that implements this contract, set:
A conforming self-hosted backend must:
- Accept
multipart/form-datawithbundleandmetadatafields. - Validate
metadataagainst the schema in this document and returnmetadata_invalid(HTTP 400) on missing required fields. - Enforce the 25 MiB bundle cap and return
bundle_too_large(HTTP 413). - Honour
submission_ididempotency for at least 24 hours. - Return the success-response envelope documented above with
auth_class: "anonymous". - Use the stable error envelope and codes in Stable error responses.
- Continue to accept
icom-lan-bundle-v1bundles per the deprecation window (see Versioning).
A self-hosted backend may apply its own rate-limit and retention policy, but SHOULD honour the anonymous-tier defaults to avoid surprising users.
See also¶
- Open-core spec:
docs/plans/2026-05-03-diagnostic-data-collection-design.md§5.3 - rigplane upload client:
src/rigplane/diagnostics/upload.py - Open-core policy:
docs/architecture/open-core-policy.md§2 carve-out - Predecessor contract:
diagnostic-bundle-v1.md