Event Log JSONL
pydepgate 0.6 adds scan lifecycle events. An event log is a JSONL sidecar that records what scan work was authorized, when the scanner started and finished, whether decode ran, and whether evidence was requested.
The event log is not the finding report. Use --format json or --format sarif for findings. Use --event-log when you need a durable machine-readable custody trail for the scan itself.
Enable event logging
pydepgate scan package.whl --event-log scan.events.jsonl
The same default can be supplied with an environment variable:
PYDEPGATE_EVENT_LOG=scan.events.jsonl pydepgate scan package.whl
The Python API can also write the same JSONL event stream:
import pydepgate.api as pydepgate
result = pydepgate.scan(
"package.whl",
mode="static",
deep=True,
event_log="scan.events.jsonl",
)
print([event.event_type for event in result.events])
File format
The file is newline-delimited JSON. Each line is one complete event envelope. Consumers should parse it as JSONL, not as one large JSON array.
A typical event line looks like this:
{
"schema_version": 1,
"event_id": "019ec3e6-de01-7d8c-bd36-f88f87a786bf",
"event_type": "internal.scanner.scan_completed",
"producer": "pydepgate.cli.scan",
"run_id": "019ec3e6-ddf8-70a1-abf7-6b377764d715",
"correlation_id": "019ec3e6-ddf8-70a1-abf7-6b377764d715",
"parent_event_id": "019ec3e6-de00-70d9-9885-d4fb72d8a287",
"ticket_id": "sgt_019ec3e6-ddfe-7eee-bb29-3322289b8a90",
"occurred_at": "2026-06-14T02:04:18.413284Z",
"emitted_at": "2026-06-14T02:04:18.413286Z",
"severity": "info",
"payload_schema": null,
"payload_digest": "7fc5f42bf42c7203e2f807d84a3433e8653b0b096b2a9bbde3cc6923bde6d7d0",
"payload": {
"artifact_identity": "package.whl",
"artifact_kind": "wheel",
"finding_count": 43,
"diagnostic_count": 1
}
}
Envelope fields
| Field | Type | Description |
|---|---|---|
schema_version | integer | Event envelope schema version. Current value: 1. |
event_id | string | Unique event identifier. |
event_type | string | Namespaced event type. Must start with internal. or external.. |
producer | string | Component that emitted the event, for example pydepgate.cli.scan or pydepgate.api. |
run_id | string | Run identifier shared by events from the same scan. |
correlation_id | string | Correlation identifier. Defaults to run_id. |
parent_event_id | string or null | Previous event in the scan lifecycle, when applicable. |
ticket_id | string or null | Scan Granting Ticket ID associated with the event. |
occurred_at | string | UTC timestamp for when the event occurred. |
emitted_at | string | UTC timestamp for when the envelope was emitted. |
severity | string | One of debug, info, warning, error, or critical. |
payload_schema | string or null | Optional payload schema label. Reserved for future versioned payload contracts. |
payload_digest | string | Stable SHA-256 digest of the JSON-safe payload. |
payload | object | Event-specific JSON-safe payload. |
Envelope guarantees
Event envelopes are built for local custody and downstream processing:
- Payloads are deep-frozen during envelope construction.
- Payloads must be JSON-safe.
- The payload digest is computed from stable JSON serialization.
- The same immutable envelope is delivered to every configured sink.
- Event logs do not include decoded payload bytes or full payload strings.
- Scan grant events remove the ticket nonce before writing to the event log.
The event log is safe operational telemetry, not a malware sample container. Decoded payload material belongs in the explicit full-mode payload archive, not in events.
Scan lifecycle events
A successful static scan normally emits these events in order:
| Event type | Emitted when | Payload highlights |
|---|---|---|
internal.scanner.scan_grant_issued | A local Scan Granting Ticket has been minted. | Ticket ID, run ID, target kind, scan mode, allowed actions, budget, ruleset fingerprint, ticket digest. The ticket nonce is omitted. |
internal.scanner.engine_created | The static engine has been constructed. | Analyzer names, enricher count, rule count, deep mode, worker settings. |
internal.scanner.scan_started | Scanner execution is about to begin. | Target kind, target identity, scan mode, allowed actions, target reference. |
internal.scanner.scan_completed | Static scanner execution completed. | Artifact identity, kind, hashes, finding count, skipped count, diagnostic count, statistics. |
internal.scanner.decode_started | Recursive decode pass is about to begin. | Decode depth and IOC mode. Only emitted when decode is enabled. |
internal.scanner.decode_completed | Recursive decode pass finished. | Tree availability, root node count, total node count, IOC node count, IOC mode. Only emitted when decode is enabled. |
internal.evidence.write_requested | Local evidence DB write is about to begin. | Artifact identity, kind, decoded-tree availability. Only emitted with --save-to-db. |
internal.evidence.write_completed | Local evidence DB write returned. | Artifact identity and decoded-tree availability. Only emitted with --save-to-db. |
internal.scanner.run_completed | The scan command or API call finished. | Exit code for CLI, artifact identity, artifact kind, finding count, diagnostic count. |
If scanner execution raises an exception, pydepgate emits:
| Event type | Emitted when | Payload highlights |
|---|---|---|
internal.scanner.scan_failed | Static scanner execution raised before completion. | Exception type and message. |
Event ordering
Consumers should prefer event types and IDs over file position, but the JSONL writer appends events in emission order. A complete successful decode-enabled scan should contain this chain:
internal.scanner.scan_grant_issued
internal.scanner.engine_created
internal.scanner.scan_started
internal.scanner.scan_completed
internal.scanner.decode_started
internal.scanner.decode_completed
internal.scanner.run_completed
Evidence events appear between decode_completed and run_completed when --save-to-db is set.
Decode events are absent when the decode pipeline is not enabled. Evidence events are absent when --save-to-db is not set.
Scan Granting Tickets
A Scan Granting Ticket is the local authorization record for scanner work. The first public use is local scan authorization and event correlation. It is not a remote authentication protocol.
Ticket payload fields include:
| Field | Description |
|---|---|
ticket_id | Stable ticket identifier for the scan. |
ticket_digest | Stable digest over the ticket fields. |
run_id | Run identifier shared by the event stream. |
correlation_id | Correlation ID used by the event stream. |
issuer | Component that minted the ticket. |
actor | Local actor label. |
target_kind | Requested target kind, such as wheel, sdist, installed_package, or loose_file. |
target_identity | Requested target identity. |
scan_mode | Requested scan mode, such as static.deep or static.single. |
allowed_actions | Actions authorized by the ticket, such as scan, decode, and evidence.write. |
issued_at / expires_at | Ticket validity window. |
ruleset_fingerprint | Digest of the effective rule set when available. |
budget | Scan budget and mode controls, including deep mode, workers, decode settings, and IOC mode. |
local_invocation | Local process and call-stack evidence. |
metadata | Additional local scanner metadata. |
The ticket nonce is intentionally removed from the event-log representation.
Sink behavior
The first event sinks are intentionally small:
| Sink | Use |
|---|---|
MemoryEventSink | Tests, API results, local inspection. Stores immutable event envelopes in memory. |
JsonlEventSink | Writes one event envelope per JSONL line. Used by --event-log and API event_log. |
TeeEventSink | Sends the same event to multiple child sinks. |
NullEventSink | Accepts events and discards them. Useful for tests or disabled paths. |
CLI scans treat event sink failures as non-fatal warnings so telemetry cannot hide scan results. The public API is stricter and treats sink failure as an API error by default.
Consumer guidance
Consumers should:
- Treat
schema_versionas the envelope contract. - Treat
event_typeas the event discriminator. - Use
run_idorcorrelation_idto group events. - Use
parent_event_idto reconstruct the lifecycle chain. - Use
payload_digestto detect payload mutation or deduplicate identical payloads. - Treat unknown payload keys as additive.
- Avoid assuming event files are truncated before each run. Use a fresh path when one-run-per-file is required.
Consumers should not:
- Treat event logs as finding reports.
- Expect payload bytes, decoded source, or full encoded strings in events.
- Treat the local Scan Granting Ticket as remote authentication.
- Parse human output when JSON, SARIF, and event JSONL are available.
Example queries
List event types:
jq -r '.event_type' scan.events.jsonl
Show scan counts:
jq 'select(.event_type == "internal.scanner.scan_completed") | .payload | {
artifact_kind,
finding_count,
diagnostic_count,
skipped_count,
statistics
}' scan.events.jsonl
Show decode summary:
jq 'select(.event_type == "internal.scanner.decode_completed") | .payload' \
scan.events.jsonl
Detect failed scans:
jq 'select(.event_type == "internal.scanner.scan_failed") | .payload' \
scan.events.jsonl