Diagnostic Bundle Contract — icom-lan-bundle-v1¶
Schema name: icom-lan-bundle-v1
Status: stable, public, open-source contract.
Last updated: 2026-05-03
Purpose¶
This contract documents the public, anonymous-tier shape of diagnostic-bundle
uploads from open-core icom-lan to a maintainer-operated triage service. It is
the spec that icom_lan.diagnostics.upload_bundle builds against and that any
third-party server that wants to receive icom-lan reports must implement.
Boundary¶
This document is open-source and authoritative for the anonymous tier of bundle submissions — open-core users with no licence context.
The icom-lan-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 ICOM_LAN_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: "icom-lan-bundle-v1":
{
"schema_version": "icom-lan-bundle-v1",
"submission_id": "uuid-v4-generated-by-client",
"app": {
"name": "icom-lan",
"version": "0.20.0",
"build_id": "2026-05-03.1"
},
"platform": {
"os": "darwin",
"arch": "arm64",
"python_version": "3.11.14"
},
"user_description": "optional free text",
"issue_ref": "https://github.com/morozsm/icom-lan/issues/1234",
"contact": {
"email": "ham@example.com",
"callsign": "DL9EAC"
}
}
app.name is "icom-lan" 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
icom-lan. 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"icom-lan-bundle-v1"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.icom-lan-bundle-v2) and the server must continue to accepticom-lan-bundle-v1for a documented deprecation window.
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 icom-lan 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.
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 - icom-lan upload client:
src/icom_lan/diagnostics/upload.py - Open-core policy:
docs/architecture/open-core-policy.md§2 carve-out