English · 한국어
pqcota-inventory — central inventory (stage 2)¶
Ingests, persists, and serves the observations Discovery produced. It accumulates repeated collections into an asset history and attaches machine metadata (endpoints, profiles) and app attribution, turning "what uses which cryptographic algorithm, and where" into a queryable inventory.
It is one of five repositories that make up pqcota: pqcota-common, pqcota-inventory, pqcota-discovery, pqcota-provisioning, and the integration repository pqcota (demo, examples, release bundles, contributing guide).
At a glance¶
flowchart LR
R["CollectionResult<br/>JSON files"] --> I["pqcota-ingest"] --> H["append-only<br/>history"]
H --> V["pqcota-inventory<br/>query · history · diff"]
What it consists of¶
| Piece | What it is |
|---|---|
Ingest — pqcota-ingest |
normalizes retrieved results and appends them to the history (pqcota-cbom-ingest appends CBOMs the same way; only pqcota-prune removes) |
| History | a snapshot at every point of change. Append-only, so earlier observations are never overwritten |
| Machine metadata | endpoints and profiles (environment, role, owner). Access secrets are not persisted |
Query — pqcota-inventory |
latest state, history, and diffs between snapshots. Read-only |
Assets have three layers: machine → app → process. An app is uniquely identified by (node_id, app_key); a process is volatile, so it is not stored and is resolved on demand instead.
Try it quickly¶
# ① ingest — read a directory of retrieved results
export PQCOTA_DSN='postgres://user:pw@host:5432/pqcota'
pqcota-ingest ./results
# ② query — latest state across all nodes
pqcota-inventory
# ③ history and change
pqcota-inventory -history node-01
pqcota-inventory -diff <older-id>,<newer-id>
A CBOM produced by an external tool, or a CMDB declaration, lands in the same history:
pqcota-cbom-ingest cbom.json cmdb://payment-gw # a CBOM scanned by an external tool
pqcota-declare cmdb.csv --out ./declared && pqcota-ingest ./declared # a CMDB declaration
To just collate files without a datastore, use pqcota-discover-view ./results. Per-command arguments → cmd/README.
If several organizations share one datastore — every command takes the organization from
PQCOTA_ORG. Without it, the store binds to default; with PQCOTA_REQUIRE_ORG=1 it will not open
at all without one. Each organization keeps its own node_id space, so two web-01s do not overwrite
each other.
export PQCOTA_ORG=acme PQCOTA_REQUIRE_ORG=1 PQCOTA_REQUIRE_SIGNATURE=1
Together these three close two paths that would otherwise pass quietly: opening without an organization,
and accepting results whose signature cannot be checked. PQCOTA_AUTO_DDL=0 separately stops the history
schema from being created where it was not there (the metadata and provisioning-record tables are still
created). Details in cmd/README.
What comes in¶
The command differs by origin, and so does the detection method recorded alongside it.
| Where it came from | Command | How it was seen → evidence strength |
|---|---|---|
| Observed directly by a collector | pqcota-ingest |
a running process was observed directly (runtime_introspection) → confirmedif JVM attach was blocked and only files were read, artifact → inferred_high |
| A CBOM scanned by an external tool (CBOMkit and friends) | pqcota-cbom-ingest |
a build artifact was read (artifact) → inferred_high |
| A record nobody scanned (CMDB, an existing inventory) | pqcota-declare → pqcota-ingest |
never seen — empty (unspecified) → no strength |
The first two are actually observed, whoever collected them — the strength just differs; seeing a running process beats reading a build artifact. The third has no observation at all and belongs to a different lane entirely: if written-down assumptions mix with observed facts, you can no longer tell apart "the CMDB says so but it was never observed." That distinction is the baseline for reconciliation.
A result carries only how it was seen (detection_method). Strength (evidence_strength) is derived from that by one rule (normalize.EvidenceStrength) and stored with the snapshot's rule version, so a value can be recomputed from the original result if you kept it. The full value list is in the contract.
What is here¶
| Path | What |
|---|---|
pkg/inventory/ |
the library: history (append-only store, in-memory and Postgres), normalize (raw capture to derived findings), ingest, resultio, declaration, and the views |
cmd/ |
the commands: pqcota-ingest, pqcota-inventory, pqcota-discover-view, pqcota-cbom-ingest, pqcota-declare, pqcota-declare-attribution, pqcota-profile, pqcota-prune |
examples/ |
runnable examples (inventory/: views, declared attribution, CBOM intake) and the shared sample results in examples/data/ that the discovery examples read too |
Depends on¶
pqcota-common only. Discovery and provisioning both depend on this module, so it imports neither.
Build and test¶
make # every check of this repository
go test ./... # unit tests only
go.mod reads the sibling repositories from ../ through replace directives (../pqcota-common and so on), so clone the repositories side by side. The replace lines stay: they are the local link between the repositories, while the require lines point at the release tag (currently v0.10.6), which is what a consumer outside this workspace receives. See the build guide.
See also¶
view, store, and declaration-import libraries pkg/inventory/ · runnable examples examples/inventory/
Contributing · security · license¶
Contributing and security reporting are described in the pqcota repository.
Copyright 2026 Great Honor randyinthedev@gmail.com. Licensed under the Apache License 2.0.