stack-ids 0.1.1

Shared identity, scope, and trace primitives for the local-first AI systems stack
Documentation

stack-ids

Shared identity, scope, trace, and digest primitives for the local-first AI systems stack.

stack-ids is the identity foundation used by the stack's memory, bridge, verification, runtime, kernel, governance, and evidence crates. It provides opaque ID newtypes, scope partition keys, W3C-compatible trace context helpers, canonical BLAKE3 content digests, and shared status/citation structs.

This crate intentionally owns type contracts only. It does not own the rows, files, envelopes, policies, schemas, or runtime behavior that those identifiers point to.

Contents

What it provides

  • Opaque string-backed ID newtypes for stack artifacts.
  • UUID v4 generation for every ID type.
  • Transparent serde serialization for wire and storage contracts.
  • JSON Schema derivation through schemars.
  • Scope and ScopeKey for namespace/domain/workspace/repository partitioning.
  • Legacy namespace conversion helpers for migration paths.
  • TraceCtx with W3C traceparent parsing/formatting.
  • Bounded trace baggage with explicit validation errors.
  • Deterministic conversion from legacy trace IDs to W3C trace IDs.
  • ContentDigest for canonical BLAKE3 digests over bytes, strings, and JSON.
  • DigestBuilder for incremental multi-field digest domains.
  • Shared surface-status and constitution-citation types.

Current package status

Property Value
Crate stack-ids
Current local version 0.1.1
Rust edition 2021
MSRV Rust 1.75
License Apache-2.0
Storage None
Network None
Primary dependency role Foundation crate

Install

[dependencies]
stack-ids = "0.1.1"

Quick start

use stack_ids::{ContentDigest, EntityId, Scope, ScopeKey, TraceCtx};

let entity_id = EntityId::generate();
assert!(!entity_id.is_empty());

let scope = Scope::new("prod")
    .with_domain("code")
    .with_workspace("workspace-1")
    .with_repo("repo-1");

let scope_key: ScopeKey = scope.key();
assert_eq!(scope_key.to_string(), "prod/code@workspace-1#repo-1");

let trace = TraceCtx::generate();
let child = trace.child("abcdef0123456789");
let traceparent = child.to_traceparent()?;
let parsed = TraceCtx::from_traceparent(&traceparent)?;
assert_eq!(parsed.trace_id, child.trace_id);

let digest = ContentDigest::compute_str("stable input");
assert_eq!(digest.hex().len(), 64);
# Ok::<(), Box<dyn std::error::Error>>(())

Identity newtypes

ID types are opaque wrappers around strings. They exist to prevent accidental cross-use of semantically different identifiers that happen to have the same runtime representation.

Each ID type supports:

  • new(value)
  • generate() using UUID v4
  • as_str()
  • is_empty()
  • Display
  • FromStr
  • From<String>
  • From<&str>
  • AsRef<str>
  • Serialize and Deserialize
  • JsonSchema
  • Debug, Clone, PartialEq, Eq, Hash, PartialOrd, and Ord

Representative ID families include:

Family Examples
Core truth EnvelopeId, ClaimId, ClaimVersionId, EntityId, EpisodeId
Projection ProjectionId, RelationId, RelationVersionId, ImportBatchId
Execution AttemptId, TrialId, KernelRunId, ExecutionPermitId
Kernel ConstraintId, HyperedgeId, ResidualId, SyndromeId, WitnessId, CertificateId
Verification VerificationCaseId, CheckPlanId, ControlReceiptId, PolicyDecisionId
Governance ApprovalGrantId, PromotionDecisionId, RollbackPlanId, CalibrationSnapshotId
Semantics SemanticsProfileId, ClaimStateId, SemanticDiffId, SupportSetId
Experiment InterventionId, ExperimentCaseId, CohortContractId, DecisionTraceId
Attestation AttestationEnvelopeId, TrustRootSetId, TransparencyReceiptId
Effect EffectIntentId, EffectWindowId, EffectCommitDecisionId, CompensationPlanId
Authority CapabilityClassId, AuthorityLeaseId, DelegationBundleId, BreakGlassGrantId
Release and safety DeploymentProfileId, AssuranceCaseId, HazardRegisterId, CertificationBundleId

Scope keys

Scope is the builder-style input shape. ScopeKey is the compact partition key used by storage, import, and query contracts.

use stack_ids::{Scope, ScopeKey};

let scope = Scope::new("engineering")
    .with_domain("memory")
    .with_workspace("local")
    .with_repo("semantic-memory");

let key = scope.key();
assert_eq!(key.namespace, "engineering");
assert_eq!(key.domain.as_deref(), Some("memory"));

Display format:

namespace[/domain][@workspace_id][#repo_id]

Examples:

  • prod
  • prod/code
  • prod/code@workspace-1
  • prod/code@workspace-1#repo-1

Legacy namespace helpers:

  • ScopeKey::namespace_only(ns)
  • ScopeKey::from_legacy_namespace(ns)
  • scope_key.to_legacy_namespace()
  • scope_key.is_namespace_only()

Trace context

TraceCtx is the stack's shared trace-correlation shape. It is compatible with W3C trace context, while still supporting deterministic migration from legacy trace IDs.

use stack_ids::TraceCtx;

let trace = TraceCtx::generate()
    .with_parent("0123456789abcdef")
    .add_baggage("tenant", "local")?;

let header = trace.to_traceparent()?;
let parsed = TraceCtx::from_traceparent(&header)?;
assert_eq!(parsed.trace_id, trace.trace_id);
# Ok::<(), Box<dyn std::error::Error>>(())

Baggage limits are enforced:

  • maximum 16 entries,
  • maximum 256 bytes per key,
  • maximum 256 bytes per value.

Legacy trace IDs can be converted without losing determinism:

let trace = TraceCtx::from_legacy_trace_id("old-run-id");
assert_eq!(trace.trace_id.len(), 32);

Content digests

ContentDigest is a canonical BLAKE3 hex digest wrapper.

use stack_ids::{ContentDigest, DigestBuilder};
use serde_json::json;

let bytes_digest = ContentDigest::compute(b"bytes");
let text_digest = ContentDigest::compute_str("text");
let json_digest = ContentDigest::compute_json(&json!({
    "b": 2,
    "a": 1
}))?;

let composed = DigestBuilder::new()
    .update_str("domain")
    .separator()
    .update_str("value")
    .finalize();

assert_eq!(bytes_digest.hex().len(), 64);
assert_eq!(text_digest.hex().len(), 64);
assert_eq!(json_digest.hex().len(), 64);
assert_eq!(composed.hex().len(), 64);
# Ok::<(), Box<dyn std::error::Error>>(())

JSON digesting normalizes object keys recursively so equivalent JSON objects hash consistently across map-order differences.

Status and citations

The crate also exports small shared structs/enums used across stack contracts:

  • PhaseStatus
  • SurfaceStatus
  • V25ConstitutionCitation

These are lightweight data contracts, not policy engines.

Authority boundary

stack-ids is authoritative for:

  • typed ID shapes,
  • scope shape and display format,
  • trace context shape,
  • digest wrapper semantics.

stack-ids is not authoritative for:

  • what an ID points to,
  • whether an artifact exists,
  • whether an artifact is valid,
  • how rows are stored,
  • how runtime policy is enforced,
  • how memory retrieval is ranked.

Those responsibilities belong to the consuming domain crates.

Serialization and schemas

All public ID wrappers are serde-transparent. This means a JSON field such as ClaimId serializes as a string, not an object:

{
  "claim_id": "6f00d9e5-1e1c-47aa-bf2c-a4fb5a3b7d57"
}

Types derive JsonSchema where appropriate so higher-level crates can generate schema bundles without redefining identity primitives.

Operational notes

  • The crate performs no I/O.
  • The crate has no network behavior.
  • Generated IDs are UUID v4 strings.
  • Empty strings are representable because some import and migration paths need to validate legacy data explicitly.
  • Prefer typed IDs at API boundaries instead of raw String.
  • Use ScopeKey instead of packing scope into ad hoc namespace strings for new integrations.

Minimum supported Rust version

stack-ids declares rust-version = "1.75.0" and uses Rust 2021.

License

Apache-2.0. See LICENSE.