Global symbol source and catalog management

The Vulnerabilities hub owns symbol-source configuration and installation-wide pack availability. Console’s Symbol Sources and Symbol Marketplace pages use /api/vulnerabilities/v1/symbols/; a tenant selection does not partition this global catalog. Tenant-specific policy decisions remain outside the hub.

Access

Reads require symbols:read. Source changes, pack registration and availability changes require all three capabilities: symbols:read, symbols:write, and ops.admin. An ordinary tenant writer cannot change global source or pack state. A tenant header does not grant access or select a separate catalog.

What activation does

A catalog pack groups existing admitted hub manifests. Registration names their debug IDs and SHA-256 content identities; it does not upload a new package, download a remote archive, or create symbol results. Each member must already reference a hub blob-catalog entry.

Activation checks that the source is enabled and that every member’s object exists in the configured CAS with the expected SHA-256 digest and length. Hashing uses the Stella Ops cryptography library. A missing object, corrupt content, absent membership or disabled source prevents activation. The response reports the persisted availability state.

An inactive managed manifest does not resolve through the hub’s debug-ID endpoint. When a manifest belongs to several packs, any active pack from an enabled source makes it available. Deactivation removes that pack’s availability; it never deletes shared manifests or CAS bytes. Disabling a source also deactivates its packs. Reenabling the source does not reactivate them; each subsequent activation must check the content again. Existing manifests without catalog membership retain their direct lookup behavior.

Content integrity does not establish signature validity or publisher trust. The catalog labels supplied DSSE digests as unverified metadata. It does not offer a signed-only policy or report signature verification without an actual configured verifier. Cryptographic key custody and purpose lookup remain in the existing cryptography services; this API introduces no key format options, private keys, or signing fallback.

Sources and observation

Operators create sources with a stable source key, display name, type (vendor, distro, debuginfod, or mirror), optional HTTP(S) URL, priority, enabled state and freshness SLA. URL credentials are rejected. Source creation records no successful synchronization, signature result, or fabricated trust score. Without an observed successful sync, freshness is unavailable and age is absent. Freshness is calculated at read time from the observed success and SLA.

The registry does not itself fetch remote content. There is no sync-queue endpoint in this surface: the retiring host returned sync_queued without enqueueing a job. The retiring install/uninstall handlers also ignored tenant identity and only changed a global Boolean; they did not verify signatures or retrieve content. Those claims are not carried into the hub.

Routes

Paths below are relative to /api/vulnerabilities/v1/symbols.

Method and pathResult
GET /sources?includeDisabled=falseSource configuration and observed freshness; bounded to 1,000 sources
GET /sources/summaryEnabled-source freshness counts
GET /sources/{sourceKey}Source detail
POST /sourcesCreate configuration; duplicate keys return 409
PUT /sources/{sourceKey}Update configuration; the body key must match the path
DELETE /sources/{sourceKey}Disable the source without deleting its history
GET /marketplace?sourceKey=...&search=...&limit=50&offset=0Catalog search with total match count and stable pagination
GET /marketplace/installed?limit=50&offset=0Globally active catalog selections
GET /marketplace/{sourceKey}/{packId}Pack metadata and activation eligibility
POST /marketplaceRegister membership of existing admitted manifests
POST /marketplace/{sourceKey}/{packId}/activateVerify CAS integrity, then activate availability
POST /marketplace/{sourceKey}/{packId}/deactivateDeactivate availability while preserving shared content

Manifest upload, lookup and address resolution

The same owner exposes these routes under /api/vulnerabilities/v1/symbols:

Method and pathResult
POST /manifestsAdmit a global document and its symbol content to CAS and metadata storage
GET /manifests/{manifestId}Read one available manifest by its sha256: identity
GET /manifestsQuery available manifest summaries by debug ID, code ID, binary name, platform, format, time or DSSE metadata presence
POST /resolveResolve a bounded address batch from available manifests
GET /statusRead available-manifest and available-symbol counts from the owner database

Upload requires the same three global writer/admin capabilities as catalog registration. Other manifest operations require symbols:read. An upload body contains document plus optional base64 blob, mediaType and fileName. The document uses schemaVersion: "stellaops.symbols.manifest.v1", debugId, binaryName, lowercase format (elf, pe, macho, wasm), and a nonempty symbols collection. Optional fields are codeId, platform and sourceMappings. Each symbol names its address, size and mangled name; type, binding and source-location metadata are retained. The shared DTOs serialize unsigned 64-bit addresses, sizes and offsets as decimal strings, while accepting numeric input too. This preserves high virtual-address bits through JSON. Symbol names retain their exact Unicode text; the owner does not silently normalize them into another symbol name.

Without an explicit blob, the deterministic document bytes are themselves the stored symbol payload. With a blob, its actual bytes are stored instead. The CAS hash is always measured by the cryptography library. The manifest identity binds the document and its CAS metadata; replaying the same content preserves both the identity and original creation time. The response includes the actual blob hash, object key, media type and length. No payload enters PostgreSQL.

Ungrouped admitted manifests are immediately available. Registering one in a catalog pack opts it into pack availability control, so the pack must be activated before that manifest resolves again. All symbol reads share the symbols.available_manifest view. The closed HTTP client rechecks the owner; the retiring client’s disk cache cannot hide deactivation. Address matches use declared half-open extents, or the exact start address when size is unknown. The old 4 KiB extrapolation beyond a symbol’s extent is not reported as a match.

Legacy tenant-bearing documents are rejected rather than silently published globally. Prepare a versioned global document explicitly; old tenantId, caller-generated manifestId, hashAlgorithm and purported provenance fields are not upload inputs. Upload does not assert that a publisher signature was verified.

CLI migration

stella symbols upload --manifest <global-document.json> [--blob <symbol-content>] uses the CLI’s configured Vulnerabilities owner URL and normal API-token/Authority authentication. The old per-command --server and --tenant switches and unauthenticated fallback provider are removed. Configure StellaOps:VulnerabilitiesUrl / STELLAOPS_VULNERABILITIES_URL, or the existing shared backend URL, together with normal CLI credentials.

The producer-owned HTTP SDK is StellaOps.Vulnerabilities.Symbols.Client; its only project dependency is the closed hub Contracts SDK. It preserves upload, full manifest reads, debug-ID queries, address resolution and status calls with caller-provided authorization.

Catalog pages have a default limit of 50 and a maximum of 200. Search applies to the entire server catalog, not only the displayed page. Failed reads and actions remain visible in Console; they do not become an empty catalog or a successful action.

Storage and host wiring

The service uses its own Vulnerabilities PostgreSQL connection. Forward migration 009_symbol_management.sql adds observation fields to symbols.source and the symbols.catalog_manifest membership relation. Migration 010 adds the content lookup index and the shared availability view. They carry no retiring persistence code, demo rows, tenant columns, binary payloads, or golden-set tables.

The web host registers AddVulnerabilitiesSymbolManagementApi(), AddVulnerabilitiesSymbolBlobStorage(configuration), and MapVulnerabilitiesSymbolManagement() plus MapVulnerabilitiesSymbolManifests(). It eagerly resolves IContentAddressedBlobStore so missing storage configuration fails startup. Configure the existing neutral CAS provider under Vulnerabilities:SymbolBlobStorage; a local root can also be supplied through STELLAOPS_VULNERABILITIES_SYMBOL_BLOB_ROOT. This must be the durable hub CAS root containing the vuln/symbols/ objects. Self-hosted S3-compatible configuration uses the same neutral provider contract. Manifest upload requires write access to this symbol CAS; the separate retained-corpus publication mount remains read-only on the web host. No payload is stored in PostgreSQL.

Reverification

Source baseline: e7f96bc58f, with the BIN-8 candidate changes. This documents the candidate behavior; it is not a deployment receipt.