content-addressable
Data carries its own proof of integrity, intrinsically.
IPLD-native content addressing for Rust and Python. A content address is derived from the data itself, not assigned by an authority — give someone the bytes and the address, and they can recompute the address and know, with no trusted third party, that the bytes are exactly what the address names. The proof travels with the data.
This crate is deliberately small and honest: it is the instrument, not the sky.
It speaks the multiformats / IPLD stack, so its artifacts interoperate with the wider content-addressed world (IPFS, IPLD, libp2p). The crate mints exactly two profiles, and the Rust/Python type says which:
| Type | CID | Codec | Multihash | Digest | Names |
|---|---|---|---|---|---|
ContentId |
v1 | DAG-CBOR (0x71) |
BLAKE3 (0x1e) |
32 bytes | a canonical structured value (encoding: canonical DAG-CBOR — strict key order, definite lengths, tag-42 links) |
RawContentId |
v1 | raw (0x55) |
BLAKE3 (0x1e) |
32 bytes | an opaque byte string — a file, a chunk, a binary, a payload |
The profile is semantic, not cosmetic. Codec + multihash + digest jointly
constitute identity: RawContentId(x) and ContentId(x) are different
identities even when their 32 digest bytes are the same, they never compare
equal, and each type's parsers reject the other's CIDs. A third type,
ClassifiedCid { Content | Raw | Foreign(ForeignCid) }, can carry and compare
any well-formed CID (a sha2-256 REAPI digest, a CIDv0, …) without this crate
ever minting one — algorithm agility in the verifier, no algorithm ambiguity in the
minter. Decision record: docs/adr/0003.
ClassifiedCid is canonical: the three profiles are pairwise disjoint and
jointly total over well-formed CIDs, and ForeignCid's constructors reject a
recognized profile — so every CID has exactly one representation, and no
variant can hold a CID outside its own profile, including through
Deserialize (the variant is derived from the bytes, never trusted from the
wire). It is named Classified, not Verified, because it proves structural
validity and profile membership — not that content matches a digest.
Rust is the core implementation; the Python package is a PyO3 binding over that same Rust core, so an id computed in Python is byte-identical to the one Rust computes for the same canonical IPLD value.
Status & stability
The package is 0.1.0 — the first release that freezes the core contract.
The core byte/wire and API contracts are locked for the whole 0.1.x line
(changing any is a breaking release outside 0.1.x), while the optional
unstable-merkle / unstable-store features are explicitly still moving and are
named to say so.
| Surface | Default | Stability |
|---|---|---|
ContentId, canonical encoding, core errors, presentation, MSRV |
Yes | Frozen for 0.1.x — changing any is a breaking release outside 0.1.x |
RawContentId, ClassifiedCid, ForeignCid (added #84) |
Yes | Stable, additive — the raw profile's bytes are fixed by the CID spec (CIDv1 · raw · BLAKE3-256) and pinned cross-language by tests/raw_vectors.json; the ContentId freeze is untouched |
| Python core parity | Separate package | Same core byte profiles |
unstable-merkle feature |
No | Experimental — serialized node bytes NOT frozen |
unstable-store feature |
No | Experimental — trait/API surface NOT frozen (no new wire format of its own) |
unstable-legacy feature |
No | Experimental, shrinking — explicit edge parsers for legacy identifier dialects (kyln envelope-hex, nessie <algo>:<hex>, bare BLAKE3 hex); exist to end those dialects |
unstable-migration feature |
No | Experimental — IdentityMigration record (from → to, reason); field names NOT frozen |
Details and rationale: docs/STABILITY.md.
Installation
Rust:
[dependencies]
content-addressable = "0.1.0"
With the optional (default-off) features:
content-addressable = { version = "0.1.0", features = ["unstable-merkle"] }
content-addressable = { version = "0.1.0", features = ["unstable-store"] }
content-addressable = { version = "0.1.0", features = ["unstable-merkle", "unstable-store"] }
Python:
pip install content-addressable
The PyPI distribution is content-addressable (hyphen); the import name is
content_addressable (underscore):
import content_addressable
Rust quick start
Implement ContentAddressable by providing canonical_form; content_id,
verify, and ensure_content_id come for free:
use content_addressable::{canonical, ContentAddressable, ContentError};
use serde::Serialize;
#[derive(Serialize)]
struct Record {
name: String,
}
impl ContentAddressable for Record {
fn canonical_form(&self) -> Result<Vec<u8>, ContentError> {
canonical::to_canonical_dagcbor(self)
}
}
let record = Record { name: "alpha".into() };
let id = record.content_id()?; // a CIDv1 (DAG-CBOR + BLAKE3)
assert!(record.verify(&id)?); // self-certifying: re-derive and compare
println!("{id}"); // "bafyr4i…" — the canonical text form
# Ok::<(), ContentError>(())
verify returns Ok(false) on a mismatch; its strict sibling
ensure_content_id returns Err(ContentError::VerificationFailed) instead. The
secondary digest and binary presentation forms are in
Presentation forms.
Python quick start
The Python face exposes the same byte profile, but not the Rust
ContentAddressable trait or verify — you canonicalize a native Python value
and take its content_id directly. This block is mirrored by
tests/test_readme.py (every call identical), so CI's python job proves it
still works:
from content_addressable import (
ContentId, content_id,
to_canonical_dagcbor, from_canonical_dagcbor,
)
# A value's content id (CIDv1, DAG-CBOR + BLAKE3). Key order is irrelevant.
record = {"name": "alpha", "attrs": {}}
cid = content_id(record)
assert str(cid).startswith("b") # base32-lower multibase text
assert len(cid.digest_hex()) == 64 # 64-char bare-digest-hex
assert isinstance(to_canonical_dagcbor(record), bytes)
# Canonical bytes round-trip; equal values -> equal bytes -> equal ids.
raw = to_canonical_dagcbor(record)
assert content_id(record) == ContentId.from_canonical_bytes(raw)
assert from_canonical_dagcbor(raw) == record
assert content_id({"attrs": {}, "name": "alpha"}) == cid # order-independent
# Parse an id back from its text / binary forms.
assert ContentId.parse(str(cid)) == cid
assert ContentId.from_bytes(cid.to_bytes()) == cid
# Wrap an already-computed 32-byte BLAKE3 digest with NO re-hash.
assert ContentId.from_blake3_content_digest(cid.digest_bytes()) == cid
assert len(cid.digest_bytes()) == 32 # the raw BLAKE3 hash
ContentId implements __eq__ / __hash__, so an id is usable as a dict key
or set member.
Choosing a construction path
Prefer the safe path. from_canonical_bytes is fast but carries a real
precondition — it is not universally safe.
| Use case | API (Rust / Python) | Contract |
|---|---|---|
| Hash a normal value | value.content_id() / content_id(value) |
Preferred safe path |
| Encode a value to bytes | canonical::to_canonical_dagcbor(v) / to_canonical_dagcbor(v) |
Produces canonical DAG-CBOR |
| Accept foreign / untrusted bytes | Rust: ContentId::from_canonical_bytes_checked(b) · Python: no single checked constructor yet |
Validates DAG-CBOR canonicality; errors on non-canonical |
| Hash already-trusted canonical bytes | ContentId::from_canonical_bytes(b) |
Unchecked precondition: caller asserts b is canonical DAG-CBOR |
| Identify opaque bytes (a file, chunk, binary, payload) | RawContentId::from_content(b) / RawContentId.from_content(b) |
Hashes the bytes; nothing to get wrong — the bytes are the content |
| Wrap an existing BLAKE3 digest of opaque bytes | RawContentId::from_blake3_digest(d) / RawContentId.from_blake3_digest(d) |
No rehash; the honest home of the no-rehash bridge (byte-identical to kyln raw CIDs / bare blake3 digests) |
| Wrap an existing BLAKE3 digest known to be over canonical DAG-CBOR | ContentId::from_dag_cbor_digest(d) / ContentId.from_dag_cbor_digest(d) |
No rehash; the name asserts the precondition. from_blake3_content_digest is deprecated in its favor (#84): it stamped DAG-CBOR on a digest it could not know came from DAG-CBOR |
Hold a CID you did not mint (REAPI sha2-256, CIDv0, …) |
ClassifiedCid::from_str / ClassifiedCid::from_bytes / ClassifiedCid::from_cid |
Classifies as Content / Raw / Foreign; foreign ids are carried and compared, never minted |
Presentation forms
ContentId and RawContentId each name the same four presentation forms, with
the same accessor names meaning the same things, so callers can't confuse them;
each is frozen for ContentId (changing any is a breaking release outside
0.1.x) and fixed by the CID spec for RawContentId:
| Form | Rust | Python | What it is |
|---|---|---|---|
| Canonical text | Display / to_string() |
str(id) |
multibase base32-lower (b…) — the IPLD-canonical CID string |
| Binary envelope | to_bytes() / from_bytes() |
to_bytes() / from_bytes() |
the full CID binary form (version + codec + multihash + digest) |
| Bare digest | digest_bytes() -> [u8; 32] |
digest_bytes() -> bytes |
the raw 32-byte BLAKE3 hash, no envelope |
| Bare-digest-hex | digest_hex() -> String |
digest_hex() -> str |
lower-hex of the 32-byte digest (64 chars, no prefix) |
Display is the inverse of FromStr for base32-lower, and that round-trip is
frozen and tested. Full CID bytes can be hex-encoded by a caller directly
(hex::encode(id.to_bytes())) — the crate deliberately does not bless a second
"hex" method; see docs/STABILITY.md for why.
Two rules follow from "the profile is semantic": emit only the canonical text
form (base32-lower b…), and compare identities as typed CID bytes —
never as digest_hex() (identical across the two profiles for the same digest)
and never as text. Legacy dialects (kyln envelope-hex, nessie blake3:<hex> /
sha2-256:<hex>, bare BLAKE3 hex) are read only through the explicit
unstable-legacy adapters (legacy::kyln::parse, legacy::nessie::parse,
legacy::bare_blake3::parse) at a system's edge; from_str never learns them.
Experimental features
All unstable-* features are default-off and exercised in CI via
--all-features. Do not depend on the unstable-merkle node bytes yet.
unstable-legacy — edge adapters that end the old dialects
legacy::kyln::parse(envelope_hex) -> RawContentId (kyln's hand-rolled CIDv1 is
byte-identical to CIDv1 raw/BLAKE3), legacy::nessie::parse("<algo>:<hex>") -> ClassifiedCid (blake3 → Raw, sha2-256 → Foreign), and
legacy::bare_blake3::parse(hex) -> RawContentId. Parsers only — canonical
output stays base32-lower. Expected to shrink as consumers migrate.
unstable-migration — identity changes are stated, never implied
IdentityMigration { from: ClassifiedCid, to: ClassifiedCid, reason: MigrationKind }
is a content-addressed record that one identity superseded another
(Recanonicalized / Reprofiled / HashRotated). Re-canonicalizing a value or
re-profiling a digest is an identity migration, and the record — not an
equality — is what provenance follows. Who may assert one, and how it is signed,
is a consumer concern.
Its fields are private with one construction path, IdentityMigration::new,
which enforces that to is mintable and that from != to; Deserialize
routes through that same constructor and rejects unknown fields, so a decoded
record satisfies exactly the invariants a constructed one does.
merkle — content-addressed DAG nodes
MerkleNode<T> is a payload: T plus parents: BTreeSet<ContentId>; its id is
derived from both the payload and the parent links, so a root id plus the
node bytes determines the whole DAG. Because parents are a BTreeSet, they are
deduplicated and ordered by content-derived Ord — equal parent sets always
produce equal bytes regardless of insertion order, and each parent serializes as
a real DAG-CBOR tag-42 link.
use content_addressable::merkle::MerkleNode; // feature = "unstable-merkle"
let root = MerkleNode::genesis("hello");
let root_id = root.id()?;
let child = MerkleNode::new("world", [root_id]);
assert!(child.parents().contains(&root_id));
# Ok::<(), content_addressable::ContentError>(())
The serialized node layout is experimental and NOT frozen — pinning it
(Merkle conformance vectors) is post-0.1.0 work.
store — the CID-addressed node store seam
A narrow, backend-agnostic seam: get/put by ContentId, with a verified
read path the extension-trait implementation establishes. The pieces:
NodeStore— the raw backend seam (two dumb ops:get_unverified,insert). Backends implement only this.NodeStoreExt— blanket-implemented, sealed-by-coherence verified operations (get,get_node,put,put_checked, …). A backend cannot re-implement them.VerifiedStore<B>— the recommended capability-safe facade: it exposes only the verified operations (dispatched via UFCS), so a backend's own inherent method cannot intercept a call made through it. Use this unless you have a reason to drop to raw ops.MemoryStore— the grow-only in-memory reference backend.AddressedBytes— an unforgeable, address-consistent(id, bytes)pair; it is the only thinginsertaccepts, so a backend can't be handed a mismatched pair.
Typed writes/reads are the strict doors:
use content_addressable::store::{MemoryStore, VerifiedStore};
use content_addressable::{canonical, ContentAddressable, ContentError};
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Record {
name: String,
}
impl ContentAddressable for Record {
fn canonical_form(&self) -> Result<Vec<u8>, ContentError> {
canonical::to_canonical_dagcbor(self)
}
}
let mut store = VerifiedStore::new(MemoryStore::new());
let record = Record { name: "alpha".into() };
let id = store.put_node(&record)?; // strict: rejects non-canonical
let recovered: Record = store.get_node(&id)?; // identity-preserving typed read
assert_eq!(record, recovered);
# Ok::<(), content_addressable::store::StoreError>(())
put_nodestrictly validates canonical DAG-CBOR before insertion;putis unchecked with respect to canonicality (put_checked/put_nodeare the strict doors).get_nodeperforms an identity-preserving typed read (decode, re-encode, and require the value to be the one named by the id). Rawgetproves only that the returned bytes hash to the requested CID — not that they are canonical.
Trust boundary. The seam derives addresses on write and verifies them on read
(seam theorems). Successful persistence, durability, no-rebind, and grow-only
behavior are backend obligations (backend refinement laws), not seam
theorems — MemoryStore discharges its documented in-memory obligations. The
formal Lean/TLA+ artifacts are deferred proof targets (tracked in
#71); the store
module docs in src/store.rs carry the full proof-obligation
catalog. The store trait/API is experimental and NOT frozen.
Stability details
The frozen 0.1.x contracts — CID profile, presentation, serde representation,
error policy, verify/ensure_content_id, crate-root exports, MSRV/edition, the
no-rehash digest bridge, and the experimental-feature exclusions — are recorded
in docs/STABILITY.md, with issue provenance. Treat the
frozen surfaces as durable.
Development
just check # the local gate: fmt + clippy + test + docs + leaf-deps
A pre-push hook runs the same checks; the individual cargo fmt / cargo clippy
/ cargo test --all-features steps work directly too.
Releasing
Tag-driven; see RELEASING.md for the wheel matrix, PyPI Trusted
Publishing, and crates.io steps.
License
Apache-2.0.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file content_addressable-0.1.1.tar.gz.
File metadata
- Download URL: content_addressable-0.1.1.tar.gz
- Upload date:
- Size: 187.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
af5db9c96b8d5aec6abd3a06fe7977d6bedfcd38d7706709128f9992ba5dfb67
|
|
| MD5 |
2e47349a5e10934cbe20d188afe770cb
|
|
| BLAKE2b-256 |
a5432fb7937b2a183c64a65aeb976f6c08d7a0b388c361f7bcf9ca11372ad102
|
Provenance
The following attestation bundles were made for content_addressable-0.1.1.tar.gz:
Publisher:
release.yml on hartsock/content-addressable
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
content_addressable-0.1.1.tar.gz -
Subject digest:
af5db9c96b8d5aec6abd3a06fe7977d6bedfcd38d7706709128f9992ba5dfb67 - Sigstore transparency entry: 2499356435
- Sigstore integration time:
-
Permalink:
hartsock/content-addressable@ba2eb59359c95bc297fe11025f9e68659e2c874b -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/hartsock
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ba2eb59359c95bc297fe11025f9e68659e2c874b -
Trigger Event:
push
-
Statement type:
File details
Details for the file content_addressable-0.1.1-cp39-abi3-win_amd64.whl.
File metadata
- Download URL: content_addressable-0.1.1-cp39-abi3-win_amd64.whl
- Upload date:
- Size: 285.1 kB
- Tags: CPython 3.9+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7b772ab347efbc9b6211cb6fa988da289255a22e8f794e5af79ad01beb4e3059
|
|
| MD5 |
3808ac30dcad49461d942d0a498df868
|
|
| BLAKE2b-256 |
26ab9f53251eed09b265b252ab8741e120c47f7815cdb8846f0e5ec9250473a5
|
Provenance
The following attestation bundles were made for content_addressable-0.1.1-cp39-abi3-win_amd64.whl:
Publisher:
release.yml on hartsock/content-addressable
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
content_addressable-0.1.1-cp39-abi3-win_amd64.whl -
Subject digest:
7b772ab347efbc9b6211cb6fa988da289255a22e8f794e5af79ad01beb4e3059 - Sigstore transparency entry: 2499356546
- Sigstore integration time:
-
Permalink:
hartsock/content-addressable@ba2eb59359c95bc297fe11025f9e68659e2c874b -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/hartsock
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ba2eb59359c95bc297fe11025f9e68659e2c874b -
Trigger Event:
push
-
Statement type:
File details
Details for the file content_addressable-0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: content_addressable-0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 441.4 kB
- Tags: CPython 3.9+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cb2b07929f0568cf615066cdc707a5cf2f6465cacc6b20326b60f15ecb2964c4
|
|
| MD5 |
5a55c6646f4e01ca1963d9fc423851f2
|
|
| BLAKE2b-256 |
fc4e28f05fba55d23293086aaedcf50b8355925cfa6afa37a2ef5313dca17ba1
|
Provenance
The following attestation bundles were made for content_addressable-0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
release.yml on hartsock/content-addressable
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
content_addressable-0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
cb2b07929f0568cf615066cdc707a5cf2f6465cacc6b20326b60f15ecb2964c4 - Sigstore transparency entry: 2499356626
- Sigstore integration time:
-
Permalink:
hartsock/content-addressable@ba2eb59359c95bc297fe11025f9e68659e2c874b -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/hartsock
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ba2eb59359c95bc297fe11025f9e68659e2c874b -
Trigger Event:
push
-
Statement type:
File details
Details for the file content_addressable-0.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: content_addressable-0.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 427.1 kB
- Tags: CPython 3.9+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8faeb9ea129fad3e44e46fa9a610a9e27f3d44feb557985ff2fdcb5b84b9a163
|
|
| MD5 |
c9928ed2487d2ef25b57ae8293dcf2fe
|
|
| BLAKE2b-256 |
553de70acb66267a02d0589886e6e5365d77fd6afa027fdb778b52405e1bfb4b
|
Provenance
The following attestation bundles were made for content_addressable-0.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
release.yml on hartsock/content-addressable
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
content_addressable-0.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
8faeb9ea129fad3e44e46fa9a610a9e27f3d44feb557985ff2fdcb5b84b9a163 - Sigstore transparency entry: 2499356685
- Sigstore integration time:
-
Permalink:
hartsock/content-addressable@ba2eb59359c95bc297fe11025f9e68659e2c874b -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/hartsock
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ba2eb59359c95bc297fe11025f9e68659e2c874b -
Trigger Event:
push
-
Statement type:
File details
Details for the file content_addressable-0.1.1-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl.
File metadata
- Download URL: content_addressable-0.1.1-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
- Upload date:
- Size: 768.6 kB
- Tags: CPython 3.9+, macOS 10.12+ universal2 (ARM64, x86-64), macOS 10.12+ x86-64, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6c990657138f5e0e18a1f3d1285b40915fa7374d749a1c4e47e5386880e1d271
|
|
| MD5 |
58e8de8edd52019385d2d8088fbae109
|
|
| BLAKE2b-256 |
b3a9346aa01eb546edc58a39d68cb177f3af12ca4e4e1c63a65e1e0074bace0f
|
Provenance
The following attestation bundles were made for content_addressable-0.1.1-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl:
Publisher:
release.yml on hartsock/content-addressable
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
content_addressable-0.1.1-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl -
Subject digest:
6c990657138f5e0e18a1f3d1285b40915fa7374d749a1c4e47e5386880e1d271 - Sigstore transparency entry: 2499356491
- Sigstore integration time:
-
Permalink:
hartsock/content-addressable@ba2eb59359c95bc297fe11025f9e68659e2c874b -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/hartsock
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ba2eb59359c95bc297fe11025f9e68659e2c874b -
Trigger Event:
push
-
Statement type: