httk.core.project.sealing

Write, read, sign, and verify project seal documents.

A seal is a signed statement of what a subject contained at one moment. A project seal records the project’s loose files and, for every registered member, the digest of that member’s own seal, so a project seal transitively pins whole member subtrees without re-hashing them: a change to any covered byte becomes a discrepancy the moment the seal is verified.

The signature is over a domain-separated digest of the document body, exactly as a signed project manifest is signed, so a digest signed as a seal can never be replayed as anything else. Verification answers the two independent questions a signature always raises separately — does the seal still describe this subject, and was it made by a key this project trusts — and reports both.

Attributes

Exceptions

SealError

A seal cannot be written or verified.

SealedError

An action was refused because the subject, or its enclosure, is sealed.

Classes

SealKeys

The signing keys resolved for one seal, and the roles that were missing.

Discrepancy

One way a sealed subject no longer matches its seal.

Seal

One parsed seal document.

SealVerification

What verifying one seal established.

SealReport

The verdicts of verifying a seal and, when deep, every seal below it.

Functions

project_seal_path(project_root)

Return where one project's seal lives.

is_project_sealed(project_root)

Return whether one project carries a seal.

resolve_seal_keys(refs, *, project_root)

Resolve seal-key refs to the signing keys that are actually available.

default_project_keys(root[, refs])

Resolve a project's signing keys from its seal_keys member or refs.

build_seal_body(kind, subject, records)

Assemble the signed body of one seal document.

sign_seal_body(body, keys)

Digest a seal body and produce one detached signature per key.

write_seal(path, body, keys)

Sign a seal body and write the seal document atomically.

read_seal(path)

Read and structurally validate one seal document.

normalize_trusted_keys(trusted_keys)

Return trust anchors as canonical keys and fingerprints, skipping junk.

verify_seal(path, *[, trusted_keys, expected_roles])

Verify one seal's body digest and signatures, and classify the signers.

diff_records(recorded, actual)

Diff two path-keyed record lists into discrepancies.

seal_project(project_root, *[, keys])

Seal a project's loose files and every member's seal digest.

unseal_project(project_root)

Remove a project's seal.

verify_project(project_root, *[, trusted_keys, deep])

Verify a project's seal and, when deep, every member seal it references.

Module Contents

httk.core.project.sealing.VALID_TRUSTED = 'valid_trusted'[source]
httk.core.project.sealing.VALID_UNKNOWN_KEY = 'valid_unknown_key'[source]
httk.core.project.sealing.INVALID = 'invalid'[source]
httk.core.project.sealing.SealKey[source]
exception httk.core.project.sealing.SealError[source]

Bases: RuntimeError

A seal cannot be written or verified.

This is the cannot proceed failure: no signing key is available, or a member a project seal must cover is itself unsealed or has no handler. It never means an action was refused because something was already sealed; that is SealedError.

exception httk.core.project.sealing.SealedError[source]

Bases: RuntimeError

An action was refused because the subject, or its enclosure, is sealed.

Changing a project’s members while the project is sealed is refused rather than silently allowed: a seal a project commits to must not change beneath it.

class httk.core.project.sealing.SealKeys[source]

The signing keys resolved for one seal, and the roles that were missing.

Parameters:
  • keys – The available signing keys, in the order their refs resolved.

  • missing_roles – The roles requested but which could not be resolved.

keys: tuple[SealKey, Ellipsis][source]
missing_roles: tuple[str, Ellipsis][source]
class httk.core.project.sealing.Discrepancy[source]

One way a sealed subject no longer matches its seal.

Parameters:
  • path – The record path or member relpath that disagrees.

  • kind – The disagreement: missing, extra, mismatch, unsealed (present but not recorded), or missing_job (recorded but absent).

path: str[source]
kind: str[source]
class httk.core.project.sealing.Seal[source]

One parsed seal document.

Parameters:
  • kind – The sealed level (project for a project seal).

  • subject – The identifiers of the sealed subject.

  • created_at – When the seal was written.

  • records – The recorded contents of the sealed subject.

  • body_sha256 – The recorded digest of the signed body.

  • signatures – The detached signatures over the body digest.

  • body_bytes – The canonical bytes the recorded digest is taken over.

  • path – Where the seal was read from.

kind: str[source]
subject: dict[str, object][source]
created_at: str[source]
records: tuple[dict[str, object], Ellipsis][source]
body_sha256: str[source]
signatures: tuple[dict[str, object], Ellipsis][source]
body_bytes: bytes[source]
path: pathlib.Path[source]
class httk.core.project.sealing.SealVerification[source]

What verifying one seal established.

Parameters:
  • valid – Whether the seal describes its subject and a signature verified.

  • verdict – One of VALID_TRUSTED, VALID_UNKNOWN_KEY, or INVALID.

  • reason – A human-readable explanation of the verdict.

  • signers – The fingerprints of the signatures that verified.

  • missing_signers – The expected roles that the seal did not carry.

  • discrepancies – How the subject diverges from the seal, if at all.

valid: bool[source]
verdict: str[source]
reason: str[source]
signers: tuple[str, Ellipsis][source]
missing_signers: tuple[str, Ellipsis][source]
discrepancies: tuple[Discrepancy, Ellipsis][source]
as_entry(level, subject)[source]

Return this verdict as one whole-tree report entry.

Parameters:
  • level (str) – The subject level this verdict is for.

  • subject (str) – The subject identifier this verdict is for.

Returns:

The JSON-compatible report entry.

Return type:

dict[str, object]

class httk.core.project.sealing.SealReport[source]

The verdicts of verifying a seal and, when deep, every seal below it.

Parameters:
  • entries – A flat sequence of report-entry mappings, parent before child, each in the shape SealVerification.as_entry() produces.

  • ok – Whether every entry is valid and at least one entry exists.

entries: tuple[dict[str, object], Ellipsis][source]
ok: bool[source]
httk.core.project.sealing.project_seal_path(project_root)[source]

Return where one project’s seal lives.

Parameters:

project_root (str | os.PathLike[str]) – The project root whose seal path to build.

Returns:

The project seal path.

Return type:

pathlib.Path

httk.core.project.sealing.is_project_sealed(project_root)[source]

Return whether one project carries a seal.

Parameters:

project_root (str | os.PathLike[str]) – The project root to check.

Returns:

Whether the project seal file exists.

Return type:

bool

httk.core.project.sealing.resolve_seal_keys(refs, *, project_root)[source]

Resolve seal-key refs to the signing keys that are actually available.

A ref is project (the project’s own signing seed, discovered from project_root), identity (the default operator identity), identity:<short> (a named identity), or the path of a base64 Ed25519 seed file. An unavailable ref is skipped and logged rather than fatal; only resolving no key at all is an error.

Parameters:
Returns:

The resolved keys and the roles that could not be resolved.

Raises:

SealError – If no key at all could be resolved.

Return type:

SealKeys

httk.core.project.sealing.default_project_keys(root, refs=None)[source]

Resolve a project’s signing keys from its seal_keys member or refs.

Parameters:
Returns:

The resolved signing keys and the roles that could not be resolved.

Raises:

SealError – If the member is malformed or no key resolves.

Return type:

SealKeys

httk.core.project.sealing.build_seal_body(kind, subject, records)[source]

Assemble the signed body of one seal document.

The returned mapping is the exact, canonical body a signature is taken over — its created_at is stamped now — so a caller signs and writes it with sign_seal_body() and write_seal().

Parameters:
Returns:

The unsigned seal body.

Return type:

dict[str, object]

httk.core.project.sealing.sign_seal_body(body, keys)[source]

Digest a seal body and produce one detached signature per key.

Parameters:
Returns:

The hex body digest and the detached signatures.

Return type:

tuple[str, list[dict[str, object]]]

httk.core.project.sealing.write_seal(path, body, keys)[source]

Sign a seal body and write the seal document atomically.

Parameters:
Returns:

The written seal path.

Raises:

SealError – If no signing key is available.

Return type:

pathlib.Path

httk.core.project.sealing.read_seal(path)[source]

Read and structurally validate one seal document.

Parameters:

path (str | os.PathLike[str]) – The seal file to read.

Returns:

The parsed seal.

Raises:
  • ValueError – If the file is not a valid seal document.

  • OSError – If the file cannot be read.

Return type:

Seal

httk.core.project.sealing.normalize_trusted_keys(trusted_keys)[source]

Return trust anchors as canonical keys and fingerprints, skipping junk.

Parameters:

trusted_keys (collections.abc.Iterable[str]) – Trust anchors as ed25519: keys or sha256: fingerprints.

Returns:

The canonical keys and fingerprints, with unparseable entries dropped.

Return type:

set[str]

httk.core.project.sealing.verify_seal(path, *, trusted_keys=(), expected_roles=())[source]

Verify one seal’s body digest and signatures, and classify the signers.

This checks the seal against itself, not against the subject: at least one signature must verify, any signature that does not makes the seal invalid, and the seal is trusted when a verifying signer is a pinned key or fingerprint. Subject-level checks are done by verify_project().

Parameters:
Returns:

The signature verdict.

Raises:
  • ValueError – If the file is not a valid seal document.

  • OSError – If the file cannot be read.

Return type:

SealVerification

httk.core.project.sealing.diff_records(recorded, actual)[source]

Diff two path-keyed record lists into discrepancies.

Parameters:
Returns:

The per-path discrepancies, sorted by path.

Return type:

list[Discrepancy]

httk.core.project.sealing.seal_project(project_root, *, keys=None)[source]

Seal a project’s loose files and every member’s seal digest.

Every registered member must already be sealed, and every member kind must have a registered handler; otherwise the project cannot be sealed.

Parameters:
  • project_root (str | os.PathLike[str]) – The project root to seal.

  • keys (SealKeys | None) – The signing keys, or None to use the project default.

Returns:

The project seal path.

Raises:

SealError – If a member is unsealed, a kind has no handler, or no key resolves.

Return type:

pathlib.Path

httk.core.project.sealing.unseal_project(project_root)[source]

Remove a project’s seal.

Parameters:

project_root (str | os.PathLike[str]) – The project root to unseal.

httk.core.project.sealing.verify_project(project_root, *, trusted_keys=(), deep=True)[source]

Verify a project’s seal and, when deep, every member seal it references.

Parameters:
  • project_root (str | os.PathLike[str]) – The project root to verify.

  • trusted_keys (collections.abc.Iterable[str]) – Trust anchors to classify the signers against.

  • deep (bool) – Whether to delegate to each member handler’s own verification.

Returns:

The flat report of every verdict, with an overall ok.

Return type:

SealReport