Function Map V1 Contract

Predicate Type (canonical): https://stella.ops/predicates/function-map/v1 Predicate Type (legacy alias): stella.ops/functionMap@v1 DSSE Payload Type: application/vnd.stellaops.function-map.v1+json JSON Schema URI: https://stellaops.org/schemas/function-map-v1.json Schema Version: 1.0.0

Field-naming note: The predicate (subject + predicate payload) serializes with camelCase JSON property names (buildId, expectedPaths, nodeHash, …). The runtime observation record and the verification result serialize with snake_case property names (node_hash, function_name, observation_rate, …). Both conventions are intentional and are preserved below.

Overview

This contract is for engineers generating, signing, or verifying function maps in the Stella Ops reachability pipeline, and for clients of the Platform function-map API and CLI. A function map predicate declares the expected call paths for a service component, enabling verification of runtime behavior (eBPF observations) against static analysis. It serves as the “contract” that runtime observations are verified against, and is typically generated from SBOM + static analysis, then signed for attestation. It follows the in-toto attestation framework.

Ground-truth implementation:


Predicate Schema

The top-level predicate type field is serialized as _type (not type).

{
  "_type": "https://stella.ops/predicates/function-map/v1",
  "subject": {
    "purl": "pkg:oci/my-service@sha256:abc123...",
    "digest": { "sha256": "abc123..." },
    "name": "my-service"
  },
  "predicate": {
    "schemaVersion": "1.0.0",
    "service": "my-backend",
    "buildId": "build-456",
    "generatedFrom": {
      "sbomRef": "sha256:...",
      "staticAnalysisRef": "sha256:...",
      "binaryAnalysisRef": "sha256:...",
      "hotFunctionPatterns": ["SSL_*", "EVP_*"]
    },
    "expectedPaths": [...],
    "coverage": {
      "minObservationRate": 0.95,
      "windowSeconds": 1800,
      "minObservationCount": null,
      "failOnUnexpected": false
    },
    "generatedAt": "2026-01-23T10:00:00Z",
    "generator": {
      "name": "StellaOps.Scanner.Reachability.FunctionMap",
      "version": "2.0.0",
      "commit": "abc123"
    },
    "metadata": {}
  }
}

Subject

Source: FunctionMapSubject in FunctionMapPredicate.cs.

FieldTypeRequiredDescription
purlstringYesPackage URL of the subject artifact
digestobjectYesMap of algorithm (sha256, sha512) to hex-encoded hash
namestringNoOptional artifact name

Predicate Fields

Source: FunctionMapPredicatePayload in FunctionMapPredicate.cs.

FieldTypeRequiredDescription
schemaVersionstringNoSchema version; defaults to 1.0.0
servicestringYesService name that this function map applies to
buildIdstringNoBuild ID/version, for correlating with a specific build
generatedFromobjectNoSource references (SBOM, static/binary analysis, hot-function patterns)
expectedPathsarrayYesList of expected call paths
coverageobjectYesCoverage thresholds for verification
generatedAtISO 8601YesGeneration timestamp
generatorobjectNoTool that generated the predicate (name, version, commit)
metadataobjectNoFree-form extension metadata

Generated From

Source: FunctionMapGeneratedFrom.

FieldTypeRequiredDescription
sbomRefstringNoSHA-256 digest (sha256:...) of the SBOM used
staticAnalysisRefstringNoSHA-256 digest of the static-analysis results used
binaryAnalysisRefstringNoSHA-256 digest of the binary-analysis results used
hotFunctionPatternsarrayNoGlob patterns used to filter hot functions

Expected Path

Source: ExpectedPath / PathEntrypoint in ExpectedPath.cs. Each expected path represents a call chain starting from an entrypoint:

{
  "pathId": "path-001",
  "description": "TLS handshake via OpenSSL",
  "entrypoint": {
    "symbol": "handleRequest",
    "nodeHash": "sha256:...",
    "purl": "pkg:oci/my-service@sha256:..."
  },
  "expectedCalls": [
    {
      "symbol": "crypto_sign",
      "purl": "pkg:deb/libcrypto3@3.0.0",
      "nodeHash": "sha256:...",
      "probeTypes": ["uprobe"],
      "optional": false,
      "description": "libsodium signing",
      "functionAddress": null,
      "binaryPath": "/usr/lib/libcrypto.so.3"
    }
  ],
  "pathHash": "sha256:...",
  "optional": false,
  "strictOrdering": false,
  "tags": ["crypto"]
}
FieldTypeRequiredDescription
pathIdstringYesUnique path identifier
descriptionstringNoHuman-readable description of the call path
entrypointobjectYesPath entry point (symbol, nodeHash, optional purl)
expectedCallsarrayYesList of expected function calls
pathHashstringYesSHA-256(entrypoint.nodeHash || sorted call node hashes) (sha256:...)
optionalbooleanNoWhether this path is optional (default false). Optional paths are skipped in the coverage calculation
strictOrderingbooleanNoOrdered sequence vs unordered set (default false). NOTE: declared in the schema but not yet enforced by ClaimVerifier (set semantics are always used)
tagsarrayNoCategorization tags (crypto, auth, network, etc.)

Path Entrypoint

FieldTypeRequiredDescription
symbolstringYesSymbol name of the entrypoint function
nodeHashstringYesNode hash for this entrypoint (sha256:...)
purlstringNoPURL of the component containing the entrypoint

Expected Call

Source: ExpectedCall in ExpectedCall.cs.

FieldTypeRequiredDescription
symbolstringYesFunction name (e.g. SSL_connect)
purlstringYesPackage URL of the component containing this function
nodeHashstringYesSHA-256(normalized PURL + ":" + normalized symbol) (sha256:...)
probeTypesarrayYesAcceptable probe types for observation
optionalbooleanNoWhether this call is optional (default false). Optional calls do not count toward coverage
descriptionstringNoHuman-readable description of the expected call
functionAddressuint64NoAddress hint for probe attachment (numeric, not string)
binaryPathstringNoBinary path for uprobe attachment in containerized environments

Probe Types

TypeDescription
kprobeKernel function entry
kretprobeKernel function return
uprobeUser-space function entry
uretprobeUser-space function return
tracepointKernel tracepoint
usdtUser-space statically defined tracing

Coverage Thresholds

Source: CoverageThresholds in FunctionMapPredicate.cs.

FieldTypeDefaultDescription
minObservationRatedouble0.95Minimum fraction of expected (non-optional) calls that must be observed; range 0.0–1.0
windowSecondsinteger1800Observation window duration in seconds
minObservationCountintegernullMinimum number of observations required before verification can succeed (optional; declared in the schema but not yet enforced by ClaimVerifier)
failOnUnexpectedbooleanfalseWhether unexpected symbols cause verification failure

Node Hash Recipe

Node hashes provide content-addressable identifiers for function calls. The recipe is implemented in FunctionMapGenerator.ComputeNodeHash and intentionally matches PathWitnessBuilder (see Witness V1):

nodeHash = "sha256:" + lowerhex(SHA-256(normalize_purl(PURL) + ":" + normalize_symbol(symbol)))

Where:

The symbol is not demangled, not lower-cased, and leading underscores are not stripped — only surrounding whitespace is trimmed. (An earlier revision of this doc described a demangle/lowercase/strip-underscores recipe that the code does not implement.)

The output digest is hex-encoded lower-case and prefixed with sha256:.

Path Hash Recipe

Source: FunctionMapGenerator.ComputePathHash.

pathHash = "sha256:" + lowerhex(SHA-256(
    join(":", [entrypoint_hex] + sort(call_node_hashes_hex))))

Note the actual algorithm strips the sha256: prefix from each component hash before joining, sorts the call hashes (ordinal) while keeping the entrypoint hash first, and joins with :. The entrypoint hash is always first and is not part of the sort. Sorting is applied regardless of strictOrdering (the generator does not currently vary hashing on strictOrdering).


Coverage Calculation Algorithm

Source: ClaimVerifier.ComputeCoverage. Coverage is computed over expected calls (node hashes), not over whole paths:

for each non-optional path:
  for each non-optional expected_call:
    total_expected_calls += 1
    if observation exists for call.nodeHash:
      observed_calls += 1
      mark path as observed

coverageRate = observed_calls / total_expected_calls
             = 0.0 if total_expected_calls == 0

ComputeCoverage also reports totalPaths, observedPaths (paths with at least one observed call), and unexpectedSymbolCount (distinct observation node hashes not in the expected set).

ComputeCoverage matches purely on nodeHash presence and does not filter by time window or probe type. The full VerifyAsync path (below) applies window and probe-type matching.


Verification Algorithm

Source: ClaimVerifier.VerifyAsync.

VERIFY(functionMap, observations, options):
  1. Resolve effective thresholds (options overrides win over coverage thresholds):
        minObservationRate, windowSeconds, failOnUnexpected
  2. windowEnd   = options.To ?? now
     windowStart = options.From ?? windowEnd - windowSeconds
  3. Filter observations to [windowStart, windowEnd] and, if set,
     to options.ContainerIdFilter / options.PodNameFilter
  4. For each non-optional expected_path:
       For each non-optional expected_call:
         - Look up observations by call.nodeHash
         - If an observation's probeType is in call.probeTypes -> matched (probeTypeMatched=true)
         - If observations exist but probe type differs -> still counted as matched,
           flagged as probe-type mismatch (probeTypeMatched=false)
         - Otherwise -> missing
       path.observed = (no missing calls)
  5. unexpected = observed function names whose nodeHash is in no expected path/call
  6. observationRate = totalMatchedNodeHashes / (totalMatched + totalMissing)
  7. verified = observationRate >= minObservationRate
                AND (NOT failOnUnexpected OR unexpected.count == 0)
  8. Build evidence (functionMapDigest, observationsDigest, observationCount,
     window, verifierVersion) and warnings; return ClaimVerificationResult

Key behaviors that differ from a naive reading:


Media Types

Source: FunctionMapSchema (DSSE payload type / schema URI), FunctionMapCommandGroup (CLI sign/attest path).

UsageMedia Type
DSSE payload type (canonical, FunctionMapSchema.DssePayloadType)application/vnd.stellaops.function-map.v1+json
DSSE payload type used by CLI --sign / --attest (FunctionMapCommandGroup)application/vnd.stellaops.function-map+json
JSON schema URI (FunctionMapSchema.JsonSchemaUri)https://stellaops.org/schemas/function-map-v1.json
Offline observations file (CLI --observations)NDJSON (one ClaimObservation JSON object per line)

Discrepancy flagged: the canonical DssePayloadType const (application/vnd.stellaops.function-map.v1+json) and the payload type hard-coded in the CLI sign/attest path (application/vnd.stellaops.function-map+json, no .v1) do not match. An earlier revision of this doc listed application/vnd.stella.function-map+json and application/vnd.stella.verification-report+json, neither of which appears in the code base; those have been removed.


Observation Record (NDJSON)

Source: ClaimObservation in Verification/IClaimVerifier.cs. Unlike the predicate, observation fields use snake_case JSON property names. Each line in an offline observations file (CLI --offline --observations) is one such object:

{
  "observation_id": "obs-123",
  "node_hash": "sha256:...",
  "function_name": "crypto_sign",
  "probe_type": "uprobe",
  "observed_at": "2026-01-23T10:05:00Z",
  "observation_count": 42,
  "container_id": "abc123",
  "pod_name": "my-service-pod-xyz",
  "namespace": "production",
  "duration_us": 150
}
FieldTypeRequiredDescription
observation_idstringYesUnique observation ID (used for dedup and deterministic hashing)
node_hashstringYesNode hash (sha256:...) computed from PURL + normalized symbol
function_namestringYesObserved function name
probe_typestringYesProbe type that generated the observation
observed_atISO 8601YesWhen the observation occurred
observation_countintegerNoAggregated count for batched observations (default 1)
container_idstringNoContainer ID where the observation occurred
pod_namestringNoKubernetes pod name
namespacestringNoKubernetes namespace
duration_usint64NoCall duration in microseconds (serialized as duration_us, not duration_microseconds)

The Platform API ObservationDto carries the same snake_case fields except it omits duration_us.


Probe Types

Source: FunctionMapSchema.ProbeTypes.

TypeDescription
kprobeKernel function entry
kretprobeKernel function return
uprobeUser-space function entry
uretprobeUser-space function return
tracepointKernel tracepoint
usdtUser-space statically defined tracing

FunctionMapSchema.ProbeTypes.IsValid validates probe types case-insensitively; the generator validation step rejects any probeTypes value outside this set.


Verification Result

Source: ClaimVerificationResult in Verification/IClaimVerifier.cs. Returned by the claim verifier and by the CLI --format json. Fields use snake_case.

FieldTypeDescription
verifiedbooleanWhether verification passed
observation_ratedoubleOverall call-based observation rate (0.0–1.0)
target_ratedoubleEffective minimum observation rate
pathsarrayPer-path results (path_id, observed, observation_rate, observation_count, matched_node_hashes, missing_node_hashes, optional call_details)
unexpected_symbolsarrayObserved function names not present in the function map
missing_expected_symbolsarrayExpected symbols with no matching observation
evidenceobjectfunction_map_digest, observations_digest, observation_count, window_start, window_end, verifier_version
verified_atISO 8601When verification was performed
warningsarrayOptional verification warnings (low coverage, probe-type mismatches, unexpected symbols)

Per-call detail (call_details[], present when IncludeBreakdown is true): symbol, node_hash, observed, observation_count, matched_probe_type, probe_type_matched.


HTTP API (Platform)

Source: src/Platform/StellaOps.Platform.WebService/Endpoints/FunctionMapEndpoints.cs. All routes are mounted under /api/v1/function-maps, require a resolved tenant (RequireTenant()), and are guarded by Platform authorization policies that map to the Platform scopes below.

MethodRoutePolicy / scopeDescription
POST/api/v1/function-mapsFunctionMapWrite (functionmap.write)Create a function map from an SBOM ref + hot-function patterns (CreateFunctionMapRequest)
GET/api/v1/function-maps?limit&offsetFunctionMapRead (functionmap.read)List function maps for the tenant (FunctionMapSummary)
GET/api/v1/function-maps/{id}FunctionMapRead (functionmap.read)Get a function map by ID (FunctionMapDetail)
DELETE/api/v1/function-maps/{id}FunctionMapWrite (functionmap.write)Delete a function map by ID
POST/api/v1/function-maps/{id}/verifyFunctionMapVerify (functionmap.verify)Verify observations against a map (VerifyFunctionMapRequestFunctionMapVerifyResponse)
GET/api/v1/function-maps/{id}/coverageFunctionMapRead (functionmap.read)Current coverage statistics (FunctionMapCoverageResponse)

Scope note: these are Platform-local scopes declared in PlatformScopes.cs (functionmap.read / .write / .verify) and their policies in PlatformPolicies.cs (platform.functionmap.*). They are not present in the canonical Authority scope catalog (StellaOpsScopes.cs); there is no function-map entry there.

Request/response DTOs are defined in src/Platform/StellaOps.Platform.WebService/Contracts/FunctionMapModels.cs and use camelCase property names, except the embedded ObservationDto (snake_case, as noted above). All responses are wrapped in the platform envelope (PlatformItemResponse<T> / PlatformListResponse<T>) carrying tenantId, actorId, dataAsOf, cached, and cacheTtlSeconds.


CLI

Source: src/Cli/StellaOps.Cli/Commands/FunctionMap/FunctionMapCommandGroup.cs. Command group: stella function-map (alias stella fmap).

stella function-map generate

Generates a function_map predicate from an SBOM.

OptionAliasDefaultDescription
--sbom-s(required)Path to SBOM file (CycloneDX/SPDX JSON)
--service(required)Service name for the function map
--subjectderived pkg:generic/{service}Subject artifact PURL
--static-analysisPath to static-analysis results
--hot-functions-H[]Glob pattern(s) for hot functions (repeatable)
--min-rate0.95Minimum observation rate threshold (0.0–1.0)
--window1800Observation window in seconds
--fail-on-unexpectedfalseFail verification if unexpected symbols are observed
--output-ostdoutOutput file path
--format-fjsonOutput format: json or yaml
--signfalseSign the predicate as a DSSE envelope (requires a registered ISigner)
--attestfalseCreate a DSSE envelope and submit to a Rekor transparency log
--build-idBuild ID to embed in the predicate

stella function-map verify

Verifies runtime observations against a function_map.

OptionAliasDefaultDescription
--function-map-m(required)Path or OCI reference to the function_map predicate
--container-callContainer ID to filter observations
--from30 min agoStart of observation window (ISO 8601)
--tonowEnd of observation window (ISO 8601)
--output-ostdoutOutput verification report path
--format-ftableOutput format: json, table, or md
--strictfalseFail on any unexpected symbols (failOnUnexpected override)
--signfalseSign the verification report (not yet implemented; emits a warning)
--offlinefalseOffline mode (read observations from a bundled file)
--observationsPath to an NDJSON observations file (required with --offline)

Not yet implemented: online observation query (without --offline) is a stub that emits a warning and verifies against an empty observation set; verify --sign is a stub.

Exit codes (FunctionMapExitCodes)

CodeMeaning
0Success
10File not found
20Validation failed
25Verification failed
30Signing failed
40Attestation failed
99System error

Persistence

Source: src/Scanner/__Libraries/StellaOps.Scanner.Storage/Postgres/Migrations/023_runtime_observations.sql. Runtime observations are stored in scanner.runtime_observations and queried via IRuntimeObservationStore (PostgresRuntimeObservationStore).

ColumnTypeNotes
idUUID PKgen_random_uuid()
observation_idTEXTNOT NULL UNIQUE (dedup)
node_hashTEXTNOT NULL; indexed
function_nameTEXTNOT NULL; indexed
container_idTEXTpartial index where not null
pod_nameTEXTpartial index (pod_name, namespace) where not null
namespaceTEXT
probe_typeTEXTNOT NULL
observation_countINTEGERdefault 1
duration_usBIGINTcall duration in microseconds
observed_atTIMESTAMPTZNOT NULL; BRIN index for time-range queries/pruning
created_atTIMESTAMPTZdefault now()

A composite (node_hash, observed_at DESC) index supports the common lookup pattern. IRuntimeObservationStore exposes store/batch-store, query-by node-hash/container/pod, generic QueryAsync(ObservationQuery), GetSummaryAsync, and PruneOlderThanAsync.