Surveyor's canonical output is JSON.
Markdown exists for human-readable sharing but it is derived from the same canonical model. If a fact matters, it should exist in the JSON schema first.
The same rule applies to the discovery slice described in docs/discovery.md. The same rule applies to the audit slice described in docs/audit.md.
Current top-level report shape:
{
"schema_version": "1.0",
"tool_version": "dev",
"report_kind": "tls_scan",
"scope_kind": "explicit",
"scope_description": "explicit TLS targets from config",
"generated_at": "2026-04-14T01:45:00Z",
"scope": {
"scope_kind": "explicit",
"input_kind": "config"
},
"results": [],
"summary": {}
}Fields:
- type: string
- meaning: current baseline-compatible schema version for report comparison
- type: string
- meaning: emitting Surveyor build version, currently
devfor ordinary builds and tests
- type: string
- meaning: semantic top-level report kind, currently
tls_scan,discovery,audit,difforprioritization
- type: string
- meaning: high-level scope the report covers, currently
explicit,localorremote
- type: string
- optional: yes
- meaning: human-readable summary of the declared scope or target set represented by the report
- type: RFC3339 UTC timestamp
- meaning: when the report object was assembled
- type: report-scope object
- optional: yes
- meaning: structured declared scope metadata for the report, currently present for explicit TLS, discovery, audit and prioritization reports
- type: array of target results
- meaning: one entry per scanned target
- type: summary object
- meaning: aggregate counts derived from
results
Current per-target result shape:
{
"name": "primary-site",
"host": "example.com",
"port": 443,
"address": "203.0.113.10:443",
"scanned_at": "2026-04-14T01:00:00Z",
"reachable": true,
"tls_version": "TLS 1.3",
"cipher_suite": "TLS_AES_128_GCM_SHA256",
"leaf_key_algorithm": "rsa",
"leaf_key_size": 2048,
"leaf_signature_algorithm": "sha256-rsa",
"certificate_chain": [],
"classification": "modern_tls_classical_identity",
"findings": [],
"warnings": [],
"errors": []
}Fields:
- type: string
- optional: yes
- meaning: human label from configuration
- type: string
- optional: no
- meaning: configured host or IP literal
- type: integer
- optional: no
- meaning: configured TCP port
- type: string
- optional: yes
- meaning: remote address observed by the TLS connection
- type: RFC3339 UTC timestamp
- optional: no
- meaning: when collection for this target ran
- type: boolean
- optional: no
- meaning: whether a TLS connection completed far enough to produce a connection state
- type: string
- optional: yes
- meaning: negotiated TLS version as reported by the Go TLS stack, for example
TLS 1.2orTLS 1.3
- type: string
- optional: yes
- meaning: negotiated cipher suite name as reported by the Go TLS stack
- type: string
- optional: yes
- meaning: lower-case public-key algorithm name from the leaf certificate, for example
rsaorecdsa
- type: integer
- optional: yes
- meaning: inferred leaf public-key size in bits where that is available
- type: string
- optional: yes
- meaning: normalised lower-case leaf certificate signature algorithm, for example
sha256-rsa
- type: array of certificate references
- optional: yes
- meaning: presented peer certificates in observed order
- type: string
- optional: no
- meaning: Surveyor's current conservative migration-posture bucket
- type: array of findings
- optional: yes
- meaning: structured explanations and recommendations derived from the current evidence
- type: array of strings
- optional: yes
- meaning: non-fatal collection concerns, for example incomplete certificate observation
- type: array of strings
- optional: yes
- meaning: collection failures or hard result-level errors
Current certificate reference shape:
{
"subject": "CN=example.com",
"issuer": "CN=Example CA",
"serial_number": "1",
"not_before": "2026-04-01T00:00:00Z",
"not_after": "2026-10-01T00:00:00Z",
"dns_names": [
"example.com",
"www.example.com"
],
"public_key_algorithm": "rsa",
"public_key_size": 2048,
"signature_algorithm": "sha256-rsa",
"is_ca": false
}Fields:
subject: string form of the certificate subjectissuer: string form of the certificate issuerserial_number: decimal serial stringnot_before: RFC3339 UTC timestampnot_after: RFC3339 UTC timestampdns_names: SAN DNS names as presentedpublic_key_algorithm: lower-case public-key algorithm namepublic_key_size: inferred key size in bits where availablesignature_algorithm: lower-case normalised signature algorithm nameis_ca: whether the certificate is marked as a CA
Current finding shape:
{
"code": "classical-certificate-identity",
"severity": "medium",
"summary": "The observed certificate identity remains classical.",
"evidence": [
"leaf_key_algorithm=rsa",
"leaf_signature_algorithm=sha256-rsa"
],
"recommendation": "Inventory certificate replacement and related PKI dependencies as part of migration planning."
}Fields:
code: stable machine-readable identifierseverity: one ofinfo,low,medium,high,criticalsummary: short human-readable statementevidence: supporting observed factsrecommendation: suggested next action
Current summary shape:
{
"total_targets": 1,
"reachable_targets": 1,
"unreachable_targets": 0,
"classification_breakdown": {
"modern_tls_classical_identity": 1
}
}Fields:
total_targets: total number of resultsreachable_targets: number of results withreachable=trueunreachable_targets: number of results withreachable=falseclassification_breakdown: counts keyed by classification label
The current schema is an MVP contract, not a promise of permanent immutability.
Still, changes to these field names or meanings should be treated as public contract changes. If the schema changes:
- update this document
- update the examples
- update tests
- explain the behavioural impact in the PR
Current discovery report shape:
{
"schema_version": "1.0",
"tool_version": "dev",
"report_kind": "discovery",
"scope_kind": "remote",
"scope_description": "remote discovery from targets file examples/approved-hosts.txt over ports 443,8443",
"generated_at": "2026-04-15T01:45:00Z",
"scope": {
"scope_kind": "remote",
"input_kind": "targets_file",
"targets_file": "examples/approved-hosts.txt",
"ports": [443, 8443]
},
"execution": {
"profile": "cautious",
"max_hosts": 256,
"max_concurrency": 8,
"timeout": "3s"
},
"results": [],
"summary": {}
}Fields:
- type: string
- meaning: current baseline-compatible schema version for report comparison
- type: string
- meaning: emitting Surveyor build version, currently
devfor ordinary builds and tests
- type: string
- meaning: semantic top-level report kind, here
discovery
- type: string
- meaning: high-level scope the report covers, here
localorremote
- type: string
- optional: yes
- meaning: human-readable summary of the discovery scope represented by the report
- type: RFC3339 UTC timestamp
- meaning: when the discovery report object was assembled
- type: report-scope object
- meaning: declared scope metadata for the discovery report
- type: report-execution object
- optional: yes
- meaning: execution settings that materially shaped the run, currently present for remote discovery
- type: array of discovered endpoints
- meaning: one entry per observed endpoint in the discovery report, whether observed locally or within declared remote scope
- type: discovery summary object
- meaning: aggregate counts derived from
results
Current report-scope shape:
{
"scope_kind": "explicit",
"input_kind": "config"
}Fields:
scope_kind: whether the report coversexplicit,localorremotescopeinput_kind: declared input kind when relevant- explicit TLS reports currently use
configortargets - remote discovery and audit currently use
cidr,targets_fileorinventory_file
- explicit TLS reports currently use
cidr: declared remote CIDR when the report covers remote CIDR scopetargets_file: declared remote targets-file path when the report covers file-backed remote scopeinventory_file: declared structured inventory-file path when the report covers inventory-backed remote scopeadapter: selected inventory adapter when the report covers adapter-backed remote inventory scopeports: declared remote port set when the report covers remote scope
Current report-execution shape:
{
"profile": "cautious",
"max_hosts": 256,
"max_concurrency": 8,
"timeout": "3s"
}Fields:
profile: effective remote pace profilemax_hosts: effective expanded-host hard capmax_concurrency: effective probe concurrency captimeout: effective per-attempt timeout, currently also used for remote TLS connection attempts during audit
Current discovered-endpoint shape:
{
"scope_kind": "remote",
"host": "10.0.0.10",
"port": 443,
"transport": "tcp",
"state": "responsive",
"hints": [],
"warnings": [],
"errors": []
}Fields:
- type: string
- optional: no
- meaning: whether the endpoint was observed in
localorremotescope
- type: string
- optional: no
- meaning: observed host or IP within the declared scope; for local discovery this is the local bound address, for remote discovery this is the attempted remote host or IP
- type: integer
- optional: no
- meaning: observed or attempted port within the declared scope
- type: string
- optional: no
- meaning: transport name, currently
tcporudp
- type: string
- optional: no
- meaning: observed endpoint state
- local discovery currently uses
listeningorbound - remote discovery currently uses
responsiveorcandidate
- local discovery currently uses
- type: integer
- optional: yes
- meaning: process identifier where available without requiring elevation, currently local-only
- type: string
- optional: yes
- meaning: best-effort process name where available, currently local-only
- type: string
- optional: yes
- meaning: best-effort executable path where available, currently local-only
- type: inventory annotation object
- optional: yes
- meaning: imported inventory metadata and provenance attached to an endpoint discovered from structured inventory scope
- type: array of discovery hints
- optional: yes
- meaning: conservative protocol hints derived from observed facts
- type: array of strings
- optional: yes
- meaning: non-fatal discovery concerns, for example unavailable process metadata
- type: array of strings
- optional: yes
- meaning: endpoint-level discovery failures, including failed remote probe attempts
Current inventory-annotation shape:
{
"ports": [443, 8443],
"name": "Payments API",
"owner": "payments",
"environment": "prod",
"tags": ["critical", "external"],
"notes": "Internet-facing service",
"provenance": [
{
"source_kind": "inventory_file",
"source_format": "csv",
"source_name": "examples/inventory.csv",
"source_record": "line 2"
}
],
"adapter_warnings": [
{
"code": "host-without-declared-tls",
"summary": "The Ingress rule host did not declare TLS and was mapped only to port 80.",
"evidence": [
"adapter=kubernetes-ingress-v1"
]
}
]
}Fields:
ports: imported port set declared for the inventory entryname: optional imported asset labelowner: optional imported owner or teamenvironment: optional imported environment labeltags: optional imported tagsnotes: optional imported free-form notesprovenance: array of inventory provenance records describing where the entry came fromadapter_warnings: non-fatal warnings preserved from adapter-backed imports
Current provenance notes:
source_formatcurrently includesyaml,json,csvandcaddyfileadapteris present when the entry came through a platform-specific adaptersource_objectis present when the adapter preserved stable product identity
Current discovery-hint shape:
{
"protocol": "tls",
"confidence": "low",
"evidence": [
"transport=tcp",
"port=443"
]
}Fields:
protocol: hinted protocol family, for exampletls,sshorrdpconfidence: explicit qualitative confidence labelevidence: observed facts supporting the hint
Hints are not verified scan results.
Current discovery summary shape:
{
"total_endpoints": 2,
"tcp_endpoints": 1,
"udp_endpoints": 1,
"hint_breakdown": {
"tls": 1
}
}Fields:
total_endpoints: total number of discovered endpointstcp_endpoints: number of discovered TCP endpointsudp_endpoints: number of discovered UDP endpointshint_breakdown: counts keyed by hinted protocol label
Current top-level audit report shape:
{
"schema_version": "1.0",
"tool_version": "dev",
"report_kind": "audit",
"scope_kind": "remote",
"scope_description": "remote audit from targets file examples/approved-hosts.txt over ports 443,8443",
"generated_at": "2026-04-16T02:00:00Z",
"scope": {
"scope_kind": "remote",
"input_kind": "targets_file",
"targets_file": "examples/approved-hosts.txt",
"ports": [443, 8443]
},
"execution": {
"profile": "cautious",
"max_hosts": 256,
"max_concurrency": 8,
"timeout": "3s"
},
"results": [],
"summary": {}
}Fields:
- type: string
- meaning: current baseline-compatible schema version for report comparison
- type: string
- meaning: emitting Surveyor build version, currently
devfor ordinary builds and tests
- type: string
- meaning: semantic top-level report kind, here
audit
- type: string
- meaning: high-level scope the report covers, here
localorremote
- type: string
- optional: yes
- meaning: human-readable summary of the audit scope represented by the report
- type: RFC3339 UTC timestamp
- meaning: when the audit report object was assembled
- type: report-scope object
- meaning: declared scope metadata for the audit report
- type: report-execution object
- optional: yes
- meaning: execution settings that materially shaped the run, currently present for remote audit
- type: array of audit results
- meaning: one entry per discovered endpoint considered by the local or remote audit flow
- type: audit summary object
- meaning: aggregate counts derived from
results
Current per-endpoint audit-result shape:
{
"discovered_endpoint": {},
"selection": {},
"tls_result": {}
}Fields:
- type: discovered endpoint object
- meaning: observed endpoint facts and hints from the discovery layer
- type: selection object
- meaning: scanner decision for the endpoint, including skipped outcomes
- type: target result object
- optional: yes
- meaning: verified TLS result when the endpoint is selected for the TLS scanner and the scan runs
Current selection shape:
{
"status": "selected",
"selected_scanner": "tls",
"reason": "tls hint on tcp/443"
}Fields:
status: selection outcome, initiallyselectedorskippedselected_scanner: scanner identifier when selected, initiallytlsreason: explicit explanation for the decision
Hints are not verified scans, and selection is not verification.
Current audit summary shape:
{
"total_endpoints": 3,
"tls_candidates": 1,
"scanned_endpoints": 1,
"skipped_endpoints": 2,
"selection_breakdown": {
"tls": 1
},
"verified_classification_breakdown": {
"modern_tls_classical_identity": 1
}
}Fields:
total_endpoints: total number of discovered endpoints considered by the audit flowtls_candidates: endpoints selected for the TLS scannerscanned_endpoints: endpoints for which a supported scanner ranskipped_endpoints: endpoints not scannedselection_breakdown: counts keyed by selected scannerverified_classification_breakdown: counts keyed by verified TLS classification where a TLS scan completed
Current top-level diff report shape:
{
"schema_version": "1.0",
"tool_version": "dev",
"report_kind": "diff",
"scope_kind": "remote",
"scope_description": "diff of audit reports",
"generated_at": "2026-04-22T02:00:00Z",
"baseline_report_kind": "audit",
"current_report_kind": "audit",
"baseline_generated_at": "2026-04-20T02:00:00Z",
"current_generated_at": "2026-04-21T02:00:00Z",
"baseline_scope_description": "remote audit within CIDR 10.0.0.0/30 over ports 443",
"current_scope_description": "remote audit within CIDR 10.0.1.0/30 over ports 443",
"baseline_scope": {},
"current_scope": {},
"workflow_view": {},
"summary": {},
"grouped_summaries": [],
"workflow_findings": [],
"changes": []
}Fields:
baseline_report_kind: semantic kind of the baseline input reportcurrent_report_kind: semantic kind of the current input reportbaseline_generated_at: baseline report generation timecurrent_generated_at: current report generation timebaseline_scope_description: baseline scope summary, when presentcurrent_scope_description: current scope summary, when presentbaseline_scope: structured baseline scope metadata, when presentcurrent_scope: structured current scope metadata, when presentworkflow_view: applied workflow grouping and filtering view, when requestedsummary: diff summary objectgrouped_summaries: grouped aggregate sections derived fromchanges, when emittedworkflow_findings: workflow-oriented findings derived from report context when emittedchanges: array of diff changes
Current emission notes:
workflow_viewis present when workflow controls were appliedgrouped_summariesare currently emitted for inventory-backed audit comparisons when usable metadata existsworkflow_findingsis part of the canonical diff report shape but is not currently emitted by the diff engine
Current diff summary shape:
{
"total_baseline_entities": 1,
"total_current_entities": 2,
"added_entities": 1,
"removed_entities": 0,
"changed_entities": 1,
"unchanged_entities": 0,
"scope_changed": true,
"change_breakdown": {
"new_endpoint": 1
},
"direction_breakdown": {
"informational": 1
}
}Fields:
total_baseline_entities: total comparable entities in the baseline inputtotal_current_entities: total comparable entities in the current inputadded_entities: entities present only in the current inputremoved_entities: entities present only in the baseline inputchanged_entities: entities with one or more recorded changesunchanged_entities: entities with no recorded changesscope_changed: whether declared scope metadata differs materiallychange_breakdown: counts keyed by change codedirection_breakdown: counts keyed by change direction
Current diff change shape:
{
"identity_key": "remote|example.com|443|tcp",
"code": "classification_changed",
"direction": "improved",
"severity": "low",
"summary": "The verified TLS classification changed.",
"baseline_value": "legacy_tls_exposure",
"current_value": "modern_tls_classical_identity",
"evidence": [],
"recommendation": ""
}Fields:
identity_key: stable comparison key for the changed entitycode: stable machine-readable change identifierdirection: one ofworsened,improved,changedorinformationalseverity: stable machine-readable severity for the change entrysummary: short human-readable description of the changebaseline_value: baseline-side value when relevantcurrent_value: current-side value when relevantevidence: supporting observed facts when relevantrecommendation: suggested follow-up action when useful
Current top-level prioritization report shape:
{
"schema_version": "1.0",
"tool_version": "dev",
"report_kind": "prioritization",
"scope_kind": "remote",
"scope_description": "remote audit from targets file examples/approved-hosts.txt over ports 443",
"generated_at": "2026-04-22T03:00:00Z",
"profile": "migration-readiness",
"source_report_kind": "audit",
"source_generated_at": "2026-04-20T01:30:00Z",
"scope": {},
"workflow_view": {},
"summary": {},
"grouped_summaries": [],
"workflow_findings": [],
"items": []
}Fields:
profile: prioritization profile applied to the input reportsource_report_kind: semantic kind of the current input reportsource_generated_at: generation time of the current input reportscope: copied structured scope metadata when the source report carried itworkflow_view: applied workflow grouping and filtering view, when requestedsummary: prioritization summary objectgrouped_summaries: grouped aggregate sections derived fromitems, when emittedworkflow_findings: workflow-oriented findings derived from report context when emitteditems: ranked prioritization items
Current emission notes:
workflow_viewis present when workflow controls were appliedgrouped_summariesare currently emitted when--group-byis requested on inventory-backed audit inputworkflow_findingsare currently emitted for inventory-backed audit input when imported metadata is weak or overridden
Current prioritization summary shape:
{
"total_items": 3,
"severity_breakdown": {
"medium": 2
},
"code_breakdown": {
"classical-certificate-identity": 1
}
}Fields:
total_items: total ranked items in the prioritization reportseverity_breakdown: counts keyed by item severitycode_breakdown: counts keyed by item code
Current prioritization item shape:
{
"rank": 1,
"severity": "medium",
"code": "classical-certificate-identity",
"summary": "The observed certificate identity remains classical.",
"target_identity": "remote|example.com|443|tcp",
"reason": "Classical certificate identity is a direct migration dependency.",
"evidence": [
"leaf_key_algorithm=rsa"
],
"recommendation": "Inventory certificate replacement and related PKI dependencies as part of migration planning."
}Fields:
rank: stable 1-based display order after deterministic rankingseverity: stable machine-readable severity for the itemcode: stable machine-readable prioritization codesummary: short human-readable statementtarget_identity: stable identity key for the affected entityreason: explicit explanation for why the item is rankedevidence: supporting observed facts when relevantrecommendation: suggested next action when useful
Current workflow-view shape:
{
"group_by": "owner",
"filters": [
{
"field": "environment",
"values": ["prod"]
}
]
}Fields:
group_by: requested grouping dimension, currentlyowner,environmentorsourcefilters: applied workflow filters, currently emitted from the CLI forowner,environmentandtag
Current workflow-filter shape:
{
"field": "tag",
"values": ["external", "critical"]
}Fields:
field: filtered metadata fieldvalues: one or more accepted values for that field
Current grouped-summary shape:
{
"group_by": "owner",
"groups": [
{
"key": "payments",
"total_items": 2,
"severity_breakdown": {
"high": 1,
"medium": 1
},
"code_breakdown": {
"legacy-tls-version": 1
}
}
]
}Fields:
group_by: grouping dimension used for the sectiongroups: grouped-summary-group entries for that dimension
Current grouped-summary-group shape:
{
"key": "payments",
"total_items": 2,
"severity_breakdown": {
"high": 1,
"medium": 1
},
"code_breakdown": {
"legacy-tls-version": 1
},
"direction_breakdown": {
"changed": 1
},
"change_breakdown": {
"selection_changed": 1
}
}Fields:
key: rendered group key, for example an owner, environment or source nametotal_items: total items or changes counted in the groupseverity_breakdown: counts keyed by severity when relevantcode_breakdown: counts keyed by prioritization code when relevantdirection_breakdown: counts keyed by diff change direction when relevantchange_breakdown: counts keyed by diff change code when relevant
Prioritization grouped summaries currently populate severity_breakdown and code_breakdown.
Diff grouped summaries currently populate severity_breakdown, direction_breakdown and change_breakdown.
Current workflow-finding shape:
{
"severity": "low",
"code": "weak-provenance",
"summary": "The imported endpoint has no recorded source provenance.",
"target_identity": "remote|prod.example.com|443|tcp",
"reason": "Without provenance, later review and source reconciliation become weaker.",
"evidence": [
"host=prod.example.com",
"port=443"
],
"recommendation": "Preserve source file and record metadata when importing inventory."
}Fields:
severity: workflow-finding severitycode: stable machine-readable workflow finding identifiersummary: short human-readable statementtarget_identity: affected entity identity when relevantreason: explicit explanation for the workflow concernevidence: supporting facts for the workflow concernrecommendation: suggested next action when useful