Files
git.stella-ops.org/docs/contracts/function-map-v1.md
2026-01-24 00:12:43 +02:00

238 lines
7.0 KiB
Markdown

# Function Map V1 Contract
> **Predicate Type:** `https://stella.ops/predicates/function-map/v1`
> **DSSE Payload Type:** `application/vnd.stellaops.function-map.v1+json`
> **Schema Version:** `1.0.0`
## Overview
A function map predicate declares the expected call paths for a service component, enabling verification of runtime behavior against static analysis. It follows the [in-toto attestation](https://github.com/in-toto/attestation) framework.
---
## Predicate Schema
```json
{
"type": "https://stella.ops/predicates/function-map/v1",
"subject": {
"purl": "pkg:oci/my-service@sha256:abc123...",
"digest": { "sha256": "abc123..." }
},
"predicate": {
"service": "my-backend",
"build_id": "build-456",
"expected_paths": [...],
"coverage": {
"min_observation_rate": 0.95,
"window_seconds": 1800,
"fail_on_unexpected": false
},
"generated_at": "2026-01-23T10:00:00Z",
"generated_from": {
"sbom_ref": "oci://registry/sbom@sha256:...",
"static_analysis_ref": "oci://registry/analysis@sha256:..."
},
"generator": {
"name": "stella-cli",
"version": "2.0.0",
"commit": "abc123"
}
}
}
```
---
## Subject
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `purl` | string | Yes | Package URL of the subject artifact |
| `digest` | object | Yes | Content digest (sha256, sha512, etc.) |
---
## Predicate Fields
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `service` | string | Yes | Service name for correlation |
| `build_id` | string | No | Build identifier for provenance correlation |
| `expected_paths` | array | Yes | List of expected call paths |
| `coverage` | object | Yes | Coverage thresholds for verification |
| `generated_at` | ISO 8601 | Yes | Generation timestamp |
| `generated_from` | object | No | Source references (SBOM, static analysis) |
| `generator` | object | No | Tool that generated the predicate |
---
## Expected Path
Each expected path represents a call chain starting from an entrypoint:
```json
{
"path_id": "path-001",
"entrypoint": {
"symbol": "handleRequest",
"node_hash": "sha256:..."
},
"expected_calls": [
{
"symbol": "crypto_sign",
"purl": "pkg:deb/libcrypto3@3.0.0",
"node_hash": "sha256:...",
"probe_types": ["uprobe"],
"optional": false,
"function_address": null,
"binary_path": "/usr/lib/libcrypto.so.3"
}
],
"path_hash": "sha256:...",
"optional": false,
"strict_ordering": false,
"tags": ["crypto"]
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `path_id` | string | Yes | Unique path identifier |
| `entrypoint` | object | Yes | Path entry point (symbol + node_hash) |
| `expected_calls` | array | Yes | List of expected function calls |
| `path_hash` | string | Yes | SHA-256(entrypoint \|\| sorted calls) |
| `optional` | boolean | No | Whether this path is optional (default false) |
| `strict_ordering` | boolean | No | Ordered sequence vs unordered set (default false) |
| `tags` | array | No | Categorization tags (crypto, auth, network, etc.) |
---
## Expected Call
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `symbol` | string | Yes | Function name (demangled) |
| `purl` | string | Yes | Package URL of the component containing this function |
| `node_hash` | string | Yes | SHA-256(PURL + normalized symbol) |
| `probe_types` | array | Yes | Acceptable probe types for observation |
| `optional` | boolean | No | Whether this call is optional (default false) |
| `function_address` | string | No | Address hint for probe attachment |
| `binary_path` | string | No | Binary path for uprobe attachment |
### Probe Types
| Type | Description |
|------|-------------|
| `kprobe` | Kernel function entry |
| `kretprobe` | Kernel function return |
| `uprobe` | User-space function entry |
| `uretprobe` | User-space function return |
| `tracepoint` | Kernel tracepoint |
| `usdt` | User-space statically defined tracing |
---
## Coverage Thresholds
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `min_observation_rate` | double | 0.95 | Minimum fraction of paths that must be observed |
| `window_seconds` | integer | 1800 | Observation window duration |
| `fail_on_unexpected` | boolean | false | Whether unexpected symbols cause verification failure |
---
## Node Hash Recipe
Node hashes provide content-addressable identifiers for function calls, matching the [Witness V1](witness-v1.md) convention:
```
node_hash = SHA-256(PURL + ":" + normalize(symbol))
```
Where `normalize(symbol)`:
1. Demangle C++/Rust symbols
2. Strip leading underscores (platform convention)
3. Lowercase the result
4. Remove whitespace
### Path Hash Recipe
```
path_hash = SHA-256(entrypoint.node_hash + ":" + sort(calls.map(c => c.node_hash)).join(":"))
```
The path hash is independent of call ordering (sorted) unless `strict_ordering` is true, in which case calls are not sorted before hashing.
---
## Coverage Calculation Algorithm
```
total_required = count(paths where optional == false)
observed_required = count(paths where optional == false AND has_matching_observation)
observation_rate = observed_required / total_required
= 0.0 if total_required == 0
verified = observation_rate >= coverage.min_observation_rate
```
For each path, an observation "matches" when:
- At least one observation has a `node_hash` matching any call in the path
- The observation falls within the time window
- The probe type is in the call's `probe_types` list
---
## Verification Algorithm
```
VERIFY(predicate, observations, options):
1. Filter observations to time window [now - window_seconds, now]
2. For each required expected_path:
a. For each expected_call in path:
- Find observations matching node_hash AND probe_type
- Mark call as "observed" if any match found
b. Mark path as "covered" if entrypoint OR any call observed
3. Compute observation_rate = covered_paths / required_paths
4. Collect unexpected = observations not matching any expected call
5. Collect missing = required calls with no matching observation
6. verified = observation_rate >= min_observation_rate
AND (NOT fail_on_unexpected OR unexpected.count == 0)
7. Return result with breakdown, unexpected, missing
```
---
## Media Types
| Usage | Media Type |
|-------|-----------|
| Function map predicate | `application/vnd.stella.function-map+json` |
| DSSE-signed predicate | `application/vnd.dsse+json` |
| Observations | `application/x-ndjson` |
| Verification report | `application/vnd.stella.verification-report+json` |
---
## Observation Record (NDJSON)
Each line in an observations file:
```json
{
"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_microseconds": 150
}
```