Skip to content

Check a file against a manifest

evaluate_file is the one call the offline half of the SDK is built around: give it a check manifest and a file, get back evidence about that file. It is the same operation the evaluate CLI subcommand runs, so a payload you print from Python and a payload the CLI writes to stdout are the same object.

from doublegate_sdk import evaluate_file

evaluation = evaluate_file('gate.json', 'runbook.md', artifact_type='memory')
if evaluation.flagged:
    for finding in evaluation.findings:
        print(finding.detail)

artifact_type is keyword-only and required. The SDK never infers it from a file extension, and the manifest decides which types it covers: asking for one it does not list is refused rather than silently accepted.

What a result is, and is not

A clean outcome means every literal string the manifest requires was present in the file. That is the whole claim. It is not admission, not publication authority and not a completed review — human_review comes back on the result exactly as the manifest declared it, and a clean check does not satisfy it. Nothing in this module opens a connection or contacts a gate.

manifest_digest identifies the validated manifest data that produced this evidence, so a stored payload says which gate version was applied. It does not cover the evaluated file, and it is not a signature or a provenance record.

Handling failures

Every failure below is a subclass of DoublegateError, so one except clause covers the whole offline workflow while the specific types remain available when you want to branch:

from doublegate_sdk.errors import DoublegateError

try:
    evaluation = evaluate_file(manifest, path, artifact_type='memory')
except DoublegateError as error:
    print(f'check did not run: {error}')
Raised by Typical str(error) Cause
ManifestError invalid_manifest at /name: invalid_format The manifest failed schema validation. Carries path and reason.
PackageError invalid_json, unsafe_path, unreadable_file, file_too_large Unsafe/unreadable files or an oversized manifest. Oversized content is mapped to EvaluationError('input_too_large').
EvaluationError unsupported_artifact_type, invalid_utf8, input_too_large The file was read but could not be evaluated under this manifest.

Diagnostics are fixed strings. They never contain the file's contents, and PackageError deliberately omits paths, so an evaluation failure is safe to log.

DoublegateError currently covers this offline workflow and the gate client's GateError. Submission and observability helpers raise their own exception types and are not yet under that base; catch those explicitly if you use them.

When to use the low-level functions instead

evaluate_file composes three public functions that remain available and unchanged. Use them directly when the composition does not fit:

  • load_package once, then evaluate many times, to apply one manifest to many bodies without re-reading and re-hashing it.
  • evaluate alone, for content that never came from a file.
  • read_bounded alone, for the bounded, symlink-refusing read.

doublegate_sdk.authoring

One offline call: apply a check manifest to a file and get evidence back.

This is the authoring half of the SDK, and it stays offline. Nothing here opens a connection, and no result produced here is an admission decision: a clean outcome means the manifest's literal checks matched, nothing more. Human review stays outstanding exactly as the manifest declares it.

evaluate_file composes the three low-level steps callers otherwise wire up by hand -- :func:doublegate_sdk.package.load_package, :func:doublegate_sdk.package.read_bounded and :func:doublegate_sdk.runtime.evaluate -- and keeps the manifest identity that each step separately knows about attached to the result. Those functions remain public and unchanged; use them directly when you need to evaluate many bodies against one already-loaded manifest, or content that never came from a file.

FileEvaluation dataclass

Evidence from one manifest applied to one file, with the gate identified.

Source code in src/doublegate_sdk/authoring.py
@dataclass(frozen=True, slots=True)
class FileEvaluation:
    """Evidence from one manifest applied to one file, with the gate identified."""

    outcome: str
    findings: tuple[Finding, ...]
    gate_name: str
    gate_version: str
    artifact_type: str
    human_review: str
    manifest_digest: str

    @property
    def flagged(self) -> bool:
        return self.outcome == 'flagged'

    def to_payload(self) -> dict[str, Any]:
        return {'name': self.gate_name, 'version': self.gate_version,
                'digest': self.manifest_digest, 'human_review': self.human_review,
                'artifact_type': self.artifact_type, 'outcome': self.outcome,
                'findings': [finding.to_dict() for finding in self.findings]}

outcome instance-attribute

outcome: str

findings instance-attribute

findings: tuple[Finding, ...]

gate_name instance-attribute

gate_name: str

gate_version instance-attribute

gate_version: str

artifact_type instance-attribute

artifact_type: str

human_review instance-attribute

human_review: str

manifest_digest instance-attribute

manifest_digest: str

flagged property

flagged: bool

to_payload

to_payload() -> dict[str, Any]
Source code in src/doublegate_sdk/authoring.py
def to_payload(self) -> dict[str, Any]:
    return {'name': self.gate_name, 'version': self.gate_version,
            'digest': self.manifest_digest, 'human_review': self.human_review,
            'artifact_type': self.artifact_type, 'outcome': self.outcome,
            'findings': [finding.to_dict() for finding in self.findings]}

evaluate_file

evaluate_file(manifest_path: str | Path, content_path: str | Path, *, artifact_type: str) -> FileEvaluation

Load a manifest, read a bounded file and return evidence about it.

Source code in src/doublegate_sdk/authoring.py
def evaluate_file(manifest_path: str | Path, content_path: str | Path, *,
                  artifact_type: str) -> FileEvaluation:
    """Load a manifest, read a bounded file and return evidence about it."""
    loaded = load_package(manifest_path)
    package = loaded.package
    try:
        content = read_bounded(content_path, package.max_input_bytes).decode('utf-8')
    except UnicodeDecodeError:
        raise EvaluationError('invalid_utf8') from None
    except PackageError as error:
        if error.kind == 'file_too_large':
            raise EvaluationError('input_too_large') from None
        raise
    result = evaluate(package, content, artifact_type)
    return FileEvaluation(result.outcome, result.findings, package.name, package.version,
                          artifact_type, package.human_review, loaded.digest)